// Copyright © 2010, Valve Corporation, All rights reserved. ======== #include "tier0/platform.h" #include "tier0/dbg.h" #include "tier1/convar.h" #include "ps3/ps3gcmlabels.h" #include "ps3gcmstate.h" #include "spugcm.h" #include "rsxflip.h" CFlipHandler g_flipHandler; ConVar r_drop_user_commands( "r_drop_user_commands", "0" ); ConVar r_ps3_mlaa( "r_ps3_mlaa", "1" ); // ConVar r_ps3_vblank_miss_threshold( "r_ps3_vblank_miss_threshold", "0.08", FCVAR_DEVELOPMENTONLY, "How much % of vsync time is allowed after vblank for frames that missed vsync to tear and flip immediately" ); #if GCM_ALLOW_TIMESTAMPS int32 g_ps3_timestampBeginIdx = GCM_REPORT_TIMESTAMP_FRAME_FIRST; #endif #if 0 // defined(_DEBUG) char ALIGN16 g_flipLog[256][32] ALIGN16_POST; uint g_flipLogIdx = 0; #define FLIP_LOG(MSG,...) \ { \ uint nLogIdx = cellAtomicIncr32( &g_flipLogIdx ) & ( ARRAYSIZE( g_flipLog ) - 1 ); \ int nCount = V_snprintf( g_flipLog[nLogIdx], sizeof( g_flipLog[nLogIdx] ), MSG, ##__VA_ARGS__ ); \ int zeroSize = sizeof( g_flipLog[0] ) - 4 - nCount; \ V_memset( g_flipLog[nLogIdx] + nCount, 0, zeroSize ); \ *(uint32*)( g_flipLog[nLogIdx] + sizeof( g_flipLog[0] ) - 4 ) = __mftb(); \ } #define ENABLE_FLIP_LOG 1 #define FlipAssert( X ) do{if(!(X))DebuggerBreak();}while(false) uint g_flipUserCommands[1024][2]; #else #define FLIP_LOG(MSG,...) #define FlipAssert( X ) #define ENABLE_FLIP_LOG 0 #endif void CEdgePostWorkload::Kick( void * dst, uint nSetLabel ) { if( !m_isInitialized ) return; extern ConVar r_ps3_mlaa; FLIP_LOG("mlaa %d,mode=%Xh,label=%d", nSetLabel, g_flipHandler.m_nMlaaFlagsThisFrame, *m_mlaaContext.rsxLabelAddress ); edgePostMlaaWait( &m_mlaaContext ); FlipAssert( vec_all_eq( *g_spuGcm.m_pMlaaBufferCookie, g_vuSpuGcmCookie ) ); //FLIP_LOG("mlaa init %d", nSetLabel ); edgePostInitializeWorkload( &m_workload, m_stages, STAGE_COUNT ); bool isMlaaRelativeEdgeDetection = true; uint8 nMlaaThresholdBase (0x0a), // from Edge sample: these are pretty good threshold values, but you might find better ones... nMlaaThresholdFactor(0x59), nMlaaAbsoluteThreshold(0x20); uint nWidth = g_ps3gcmGlobalState.m_nRenderSize[0], nHeight = g_ps3gcmGlobalState.m_nRenderSize[1]; FlipAssert( nWidth <= 1280 && nWidth >= 640 && nHeight <= 720 && nHeight >= 480 ); //FLIP_LOG("mlaa prep %d", nSetLabel ); edgePostMlaaPrepareWithRelativeThreshold( &m_mlaaContext, g_spuGcm.m_pMlaaBuffer, IsResultInMainMemory()? g_spuGcm.m_pMlaaBufferOut : dst, nWidth, nHeight, g_ps3gcmGlobalState.m_nSurfaceRenderPitch, isMlaaRelativeEdgeDetection?nMlaaThresholdBase:nMlaaAbsoluteThreshold, isMlaaRelativeEdgeDetection?nMlaaThresholdFactor:0, g_flipHandler.m_nMlaaFlagsThisFrame, nSetLabel ); //FLIP_LOG("mlaa kick %d", nSetLabel ); edgePostMlaaKickTasks( &m_mlaaContext ); FLIP_LOG("mlaa kicked %d,label=%d", nSetLabel, *m_mlaaContext.rsxLabelAddress ); FlipAssert( vec_all_eq( *g_spuGcm.m_pMlaaBufferCookie, g_vuSpuGcmCookie ) ); } void RsxInterruptFifo::Init() { m_nGet = m_nPut = 0; } uint RsxInterruptFifo::Queue( uint8 nCause, uint8 nSurfaceFlipIdx ) { Event_t event; event.m_nCause = nCause; event.m_nSurfaceFlipIdx = nSurfaceFlipIdx; return Queue( event ); } uint RsxInterruptFifo::Queue( const Event_t &event ) { while( ( m_nPut - m_nGet ) >= MAX_EVENT_COUNT - 1 ) { sys_timer_usleep( 100 ); // this should NEVER happen } #if ENABLE_FLIP_LOG switch( event.m_nCause ) { case GCM_USERCMD_POSTPROCESS: FLIP_LOG( "queue:post %d", event.m_nSurfaceFlipIdx ); break; case GCM_USERCMD_FLIPREADY: FLIP_LOG( "queue:flip %d sys%d", event.m_nSurfaceFlipIdx, g_flipHandler.m_nSystemFlipId[ event.m_nSurfaceFlipIdx ] ); break; default: FLIP_LOG("Unknown event %d", event.m_nCause ); break; } #endif m_queue[ m_nPut & ( MAX_EVENT_COUNT - 1 ) ] = event; return ++m_nPut; // Should be atomic if there are multiple event producer threads } uint RsxInterruptFifo::GetPutMarker()const { return m_nPut; } int RsxInterruptFifo::HasEvents( uint nMarker ) { uint nGet = m_nGet; Assert( int( nMarker - nGet ) >= 0 ); return int( nMarker - nGet ); } RsxInterruptFifo::Event_t & RsxInterruptFifo::PeekEvent() { uint nGet = m_nGet; Assert( nGet != m_nPut ); return m_queue[ nGet & ( MAX_EVENT_COUNT - 1 ) ]; } const RsxInterruptFifo::Event_t RsxInterruptFifo::DequeueEvent( ) { Event_t event = PeekEvent(); m_nGet++; // should be atomic if there's more than one consumer return event; } void RsxInterruptFifo::QueueRsxInterrupt() { uint32 *pReplace = NULL; #if ENABLE_FLIP_LOG //FLIP_LOG( "q%X", m_nPut ); g_flipUserCommands[ m_nPut & ( ARRAYSIZE( g_flipUserCommands ) - 1 ) ][ 0 ] = m_nPut; pReplace = &g_flipUserCommands[ m_nPut & ( ARRAYSIZE( g_flipUserCommands ) - 1 ) ][ 1 ]; *pReplace = uint32( gCellGcmCurrentContext->current ); #endif /* if( IsCert() // don't deliberately drop anything in CERT || 0 == r_drop_user_commands.GetInt() // don't drop anything if drop==0 || ( ( rand() % 100 ) >= r_drop_user_commands.GetInt() ) // drop 1% means in 99% of cases we still want to SetUserCommand ) GCM_FUNC( cellGcmSetUserCommand, m_nPut ); GCM_FUNC( cellGcmSetWriteTextureLabel, GCM_LABEL_LAST_INTERRUPT_GET, m_nPut ); */ // directly putting it to SPUGCM queue instead of routing it through GCM_FUNC g_spuGcm.GetDrawQueue()->Push3( SPUDRAWQUEUE_QUEUE_RSX_INTERRUPT_METHOD | GCM_LABEL_LAST_INTERRUPT_GET, m_nPut, ( uintp )pReplace ); } void CFlipHandler::Init() { m_interruptFifo.Init(); /* V_memset( m_nDebugStates, 0, sizeof( m_nDebugStates ) ); m_nDebugStates[RENDERING_SURFACE] = -1; */ m_nFlipSurfaceIdx = 0; m_nFlipSurfaceCount = 0; m_nVblankCounter = 100; // how many vblanks since the last flip? m_bEdgePostResultAlreadyInLocalMemory = false; m_nMlaaFlagsThisFrame = 0; // disable MLAA before the first BeginScene() is called m_nMlaaFlagMaskNextFrame = ~0u; for( int i = 0; i < ARRAYSIZE( m_surfaceEdgePost ) ; ++i ) // initially, the post processing of surfaces is disabled m_surfaceEdgePost[i] = 0; // simulated initial state: we just flipped to surface 1, then 2, thus leaving surface 1 (then 0) available to render into // event[1] may not be set for MLAA mode because in order to start rendering into surface 0 (which we're rendering into), we "waited" for event 1 for ( int j = 2; j < ARRAYSIZE( m_evFlipReady ); ++ j ) m_evFlipReady[j].Set(); //m_nLastFlippedSurfaceIdx = CPs3gcmDisplay::SURFACE_COUNT - 1 ; m_pLastInterruptGet = cellGcmGetLabelAddress( GCM_LABEL_LAST_INTERRUPT_GET ); *m_pLastInterruptGet = 0; cellGcmSetVBlankHandler( INTERRUPT_VBlankHandler ); cellGcmSetUserHandler( INTERRUPT_UserHandler ); } void CFlipHandler::Shutdown() { cellGcmSetVBlankHandler( NULL ); cellGcmSetUserHandler( NULL ); } ////////////////////////////////////////////////////////////////////////// // 1. draw PS/3 system menus into the surface // 2. queue a reliable "flip ready" event for GCM interrupt thread to process and flip surface to this // void CFlipHandler::QmsPrepareFlipSubmit( GcmUserCommandEnum_t nEvent, uint surfaceFlipIdx ) { uint32 nSystemFlipId = GCM_FUNC_NOINLINE( cellGcmSetPrepareFlip, surfaceFlipIdx ); m_nSystemFlipId[surfaceFlipIdx] = nSystemFlipId; Assert( !m_evFlipReady[ surfaceFlipIdx ].Check() ); m_interruptFifo.Queue( nEvent, surfaceFlipIdx ); } ConVar r_ps3_mlaa_pulse( "r_ps3_mlaa_pulse", "0" ); enum EdgePostFlags_t { EDGE_POST_MLAA_FLAG_MASK = ( EDGE_POST_MLAA_MODE_ENABLED | EDGE_POST_MLAA_MODE_SHOW_EDGES | EDGE_POST_MLAA_MODE_SINGLE_SPU_TRANSPOSE | EDGE_POST_MLAA_MODE_TRANSPOSE_64 ) }; void CFlipHandler::BeginScene() { #if GCM_ALLOW_TIMESTAMPS if ( g_ps3_timestampBeginIdx >= 0 ) { GCM_FUNC( cellGcmSetTimeStamp, g_ps3_timestampBeginIdx ); g_ps3_timestampBeginIdx = -1; } #endif m_nMlaaFlagsThisFrame = r_ps3_mlaa.GetInt() & EDGE_POST_MLAA_FLAG_MASK; if( int nPulse = r_ps3_mlaa_pulse.GetInt() ) { if( 1 & ( g_spuGcm.m_nFrame / nPulse ) ) { m_nMlaaFlagsThisFrame = 0; // disable for 16 frames = 1/2 second } } m_nMlaaFlagsThisFrame &= m_nMlaaFlagMaskNextFrame; //m_nMlaaFlagMaskNextFrame = (uint)-1; } void CFlipHandler::TransferMlaaResultIfNecessary( uint nSurfacePrevFlipIdx ) { if( m_bEdgePostResultAlreadyInLocalMemory ) return; if( g_edgePostWorkload.ShouldUseLabelForSynchronization() ) { GCM_FUNC( cellGcmSetWaitLabel, GCM_LABEL_EDGEPOSTMLAA, nSurfacePrevFlipIdx ); } else { // wait for SPU to finish post-processing previous surface uint32 *pPrevJts = &g_spuGcm.m_pEdgePostRsxLock[ nSurfacePrevFlipIdx ]; if( *pPrevJts != CELL_GCM_RETURN() ) { GCM_FUNC( cellGcmSetCallCommand, uintp( pPrevJts ) + g_spuGcmShared.m_nIoOffsetDelta ); } } // // NOTE: we can start post-processing before SetPrepareFlip, it only makes sense since we don't always use interrupt to do so // if we ever do proper synchronization with SPU workload, we should kick Edge Post here, before SetPrepareFlip // if( g_edgePostWorkload.IsResultInMainMemory() ) { CPs3gcmLocalMemoryBlockSystemGlobal & prevSurfaceColor = g_ps3gcmGlobalState.m_display.surfaceColor[nSurfacePrevFlipIdx]; GCM_FUNC( cellGcmSetTransferImage, CELL_GCM_TRANSFER_MAIN_TO_LOCAL, prevSurfaceColor.Offset(), g_ps3gcmGlobalState.m_nSurfaceRenderPitch, 0, 0, uintp( g_spuGcm.m_pMlaaBufferOut ) + g_ps3gcmGlobalState.m_nIoOffsetDelta, g_ps3gcmGlobalState.m_nSurfaceRenderPitch, 0, 0, g_ps3gcmGlobalState.m_nRenderSize[0], g_ps3gcmGlobalState.m_nRenderSize[1], 4 ); } m_bEdgePostResultAlreadyInLocalMemory = true; } bool CFlipHandler::QmsAdviceBeforeDrawPrevFramebuffer() { uint nSurfacePrevFlipIdx = g_ps3gcmGlobalState.m_display.PrevSurfaceIndex( 1 ); uint8 prevPostProcessed = m_surfaceEdgePost[nSurfacePrevFlipIdx]; if( prevPostProcessed ) // did previous surface need post-processing? { // we'd actually be free to start MLAA here instead of in Flip, for the cost of one more RSX->PPU interrupt // but we don't do that because we only may do so when the LAST player draws, and we don't know if this post processing // that will now start is related to the LAST player // we don't need to do that until flip if we're using deferred queue // although if we're using deferred queue and we run out of space there, we stop using it, replay it and start defer-render into previous frame TransferMlaaResultIfNecessary( nSurfacePrevFlipIdx ); // do the post-processing on this frame, in the mean time render into previous frame return true; } return false; // there's no need to switch surfaces now } void CFlipHandler::Flip() { #if GCM_ALLOW_TIMESTAMPS OnFrameTimestampAvailableMST( 1.0f ); #endif extern ConVar mat_vsync; m_bVSync = mat_vsync.GetBool(); g_ps3gcmGlobalState.CmdBufferFlush( CPs3gcmGlobalState::kFlushForcefully ); g_spuGcm.GetDrawQueue()->Push1( SPUDRAWQUEUE_FRAMEEVENT_METHOD | SDQFE_END_FRAME ); uint surfaceFlipIdx = g_ps3gcmGlobalState.m_display.surfaceFlipIdx, nSurfaceNextFlipIdx = g_ps3gcmGlobalState.m_display.NextSurfaceIndex( 1 ), nSurfaceAfterNextFlipIdx = g_ps3gcmGlobalState.m_display.NextSurfaceIndex( 2 ), nSurfacePrevFlipIdx = g_ps3gcmGlobalState.m_display.PrevSurfaceIndex( 1 ); /* uint nScreenWidth = g_ps3gcmGlobalState.m_nRenderSize[0]; uint nScreenY = 40; g_ps3gcmGlobalState.DrawDebugStripe( nScreenWidth * surfaceFlipIdx / 3, nScreenY, 0, nScreenWidth / 3, 4 ); g_ps3gcmGlobalState.DrawDebugStripe( ( g_spuGcm.m_nFrame & 0xF ) * ( nScreenWidth / 16 ), 34, 0, ( nScreenWidth / 16 ) * ( 1 + m_nFlipSurfaceCount ), 1 ); */ // let interrupt know we're ready to post-process the new frame, and we wanna flip the previous frame //g_ps3gcmGlobalState.CmdBufferFinish(); uint32 * pThisJts = g_spuGcm.m_pEdgePostRsxLock + surfaceFlipIdx; // may be NULL + idx Assert( !g_spuGcm.m_pEdgePostRsxLock || *pThisJts == CELL_GCM_RETURN() ); uint8 prevPostProcessed = m_surfaceEdgePost[nSurfacePrevFlipIdx]; uint8 thisPostProcess = g_spuGcm.m_pMlaaBuffer ? ( uint8 ) ( m_nMlaaFlagsThisFrame & EDGE_POST_MLAA_FLAG_MASK ): 0 ; if( prevPostProcessed ) // did previous surface need post-processing? { TransferMlaaResultIfNecessary( nSurfacePrevFlipIdx ); //if( g_spuGcm.m_bUseDeferredDrawQueue ) { // now is the time to execute all the deferred commands, if there are any // NOTE: this will often do nothing , because current frame would've flushed previous frame deferred commands already // right before starting writing its own g_spuGcm.ExecuteDeferredDrawQueue( 1 ); } //g_ps3gcmGlobalState.DrawDebugStripe( nScreenWidth * surfaceFlipIdx / 3, 44, surfaceFlipIdx, nScreenWidth / 3, 2, -1 ); // prepare flip of previous frame - Edge Post processed buffer // the previous frame was post-processed; we'll prepare flip on it. QmsPrepareFlipSubmit( GCM_USERCMD_FLIPREADY, nSurfacePrevFlipIdx ); } else { // if previous frame wasn't post-processed, don't flip it because we don't want to flip the same framebuffer twice (although we probably could) // so we don't have anything to flip here, but have a frame to post-process g_spuGcm.ExecuteDeferredDrawQueue( 1 ); } m_surfaceEdgePost[surfaceFlipIdx] = thisPostProcess; // is post-process required for this surface ? if( thisPostProcess ) { if( !( m_nMlaaFlagsThisFrame & EDGE_POST_MLAA_MODE_ENABLED ) ) { m_bEdgePostResultAlreadyInLocalMemory = true; // don't attempt to transfer the results; we don't _really_ do edge post processing, so we consider the results are in memory already } else { // EDGE POST TODO: JTS - the previous EdgePost must release it. To avoid overwriting edge post buffer before it finished tranferring back to local memory // to release JTS from the future, we can use a separate ring buffer "JTS-RET" sequences and just call into it here. // or we can wait for a label and set it from SPU // as a simplification, we can just wait for edge post to finish synchronously on ppu // we can also use a mutex of sorts and insert JTS here only when edge post is not finished yet // we only can start transferring the image after the SPU is done streaming previous frame (if previous frame was post-processed) // so wait for SPU to release previous frame, if it was post-processed. // Also, if SPU didn't finish post-processing, then we need to synchronize (wait on RSX for SPU to be done) // but in many cases SPU will be done by now, so we don't need to spend 900+ns in RSX front-end on CALL+RET if( !g_edgePostWorkload.ShouldUseLabelForSynchronization() ) { *pThisJts = CELL_GCM_JUMP( uintp( pThisJts ) + g_spuGcmShared.m_nIoOffsetDelta ); // this will be JTS for SPU to overwrite when post-processing of this frame is done } CPs3gcmLocalMemoryBlockSystemGlobal & surfaceColor = g_ps3gcmGlobalState.m_display.surfaceColor[surfaceFlipIdx]; GCM_FUNC( cellGcmSetTransferImage, CELL_GCM_TRANSFER_LOCAL_TO_MAIN, uintp( g_spuGcm.m_pMlaaBuffer ) + g_ps3gcmGlobalState.m_nIoOffsetDelta, g_ps3gcmGlobalState.m_nSurfaceRenderPitch, 0, 0, surfaceColor.Offset(), g_ps3gcmGlobalState.m_nSurfaceRenderPitch, 0, 0, g_ps3gcmGlobalState.m_nRenderSize[0], g_ps3gcmGlobalState.m_nRenderSize[1], 4 ); // This frame was rendered and transferred to main memory; we'll let interrupt thread know it's ready for Edge Post processing m_interruptFifo.Queue( GCM_USERCMD_POSTPROCESS, surfaceFlipIdx ); m_bEdgePostResultAlreadyInLocalMemory = false; } } else { // we aren't post-processing this frame, so we need to just prepare flip and flip this framebuffer g_spuGcm.ExecuteDeferredDrawQueue( 0 ); QmsPrepareFlipSubmit( GCM_USERCMD_FLIPREADY, surfaceFlipIdx ); m_bEdgePostResultAlreadyInLocalMemory = true; // don't attempt to transfer the results; we don't do edge post - processing, so we consider the results are in memory already } g_spuGcm.FlipDeferredDrawQueue( ); if( thisPostProcess && !prevPostProcessed ) { // we absolutely MUST reset RSX state before the next frame. // QmsPrepareFlipSubmit() does that by definition, but if we don't call it in this Flip (i.e. when !prevPostProcessed && thisPostProcess) // we must FORCE RSX state reset g_spuGcm.GetDrawQueue()->Push1( SPUDRAWQUEUE_RESETRSXSTATE_METHOD ); } #if GCM_ALLOW_TIMESTAMPS { // The current frame has just finished, insert a timestamp instruction right before flip GCM_FUNC( cellGcmSetTimeStamp, surfaceFlipIdx * 2 + GCM_REPORT_TIMESTAMP_FRAME_FIRST + 1 ); g_ps3_timestampBeginIdx = nSurfaceNextFlipIdx * 2 + GCM_REPORT_TIMESTAMP_FRAME_FIRST; } #endif m_interruptFifo.QueueRsxInterrupt(); g_ps3gcmGlobalState.CmdBufferFlush( CPs3gcmGlobalState::kFlushEndFrame ); //g_ps3gcmGlobalState.CmdBufferFinish(); // // Make sure that the next framebuffer is free to render into. For that to be so, // the flip should happen from the next to the buffer after next. When that happens, // the TV shows the buffer after next, and the next buffer is not visible to the user, // so it's allowed to render into the next buffer. // FLIP_LOG( "ev Wait %d", nSurfaceAfterNextFlipIdx ); m_evFlipReady[ nSurfaceAfterNextFlipIdx ].Wait(); m_evFlipReady[ nSurfaceAfterNextFlipIdx ].Reset(); FLIP_LOG( "Draw %d, ev Reset %d", nSurfaceNextFlipIdx, nSurfaceAfterNextFlipIdx ); #if GCM_ALLOW_TIMESTAMPS { // Since the previous flip completely finished, we can grab its timestamps now uint32 uiLastFrameTimestampIdx = ( nSurfaceAfterNextFlipIdx ) * 2 + GCM_REPORT_TIMESTAMP_FRAME_FIRST; uint64 uiStartTimestamp = cellGcmGetTimeStamp( uiLastFrameTimestampIdx ); uint64 uiEndTimestamp = cellGcmGetTimeStamp( uiLastFrameTimestampIdx + 1 ); uint64 uiRsxTimeInNanoSeconds = uiEndTimestamp - uiStartTimestamp; OnFrameTimestampAvailableRsx( uiRsxTimeInNanoSeconds / 1000000.0f ); } #endif } bool IsRsxReadyForNoninteractiveRefresh( ) { uint nSurfaceAfterNextFlipIdx = g_ps3gcmGlobalState.m_display.NextSurfaceIndex( 2 ); return g_flipHandler.m_evFlipReady[ nSurfaceAfterNextFlipIdx ].Check(); // if we are 3 vblanks past last flip already, another refresh would be welcome ; if we have no surfaces to flip in this case, we are most likely ready to flip right away // another thing to check is the interrupt FIFO: if it's not idle, let's just postpone being ready // return g_flipHandler.m_nVblankCounter > 3 && g_flipHandler.m_nFlipSurfaceCount == 0 && g_flipHandler.m_interruptFifo.IsIdle(); } void CFlipHandler::TryFlipVblank() { // artificially simulate an interrupt for cause, because there's suspicion it was dropped // // only attempt to generate artificial interrupts if our ready flip queue is empty, otherwise there's no need // to tap the narrow 15.6Mb/s bus uint nMarker = *g_flipHandler.m_pLastInterruptGet; m_nVblankCounter ++; #if ENABLE_FLIP_LOG static int m_nLastFlipLogIdx = 0; if( m_nLastFlipLogIdx != g_flipLogIdx ) { V_snprintf( g_flipLog[m_nLastFlipLogIdx], sizeof( g_flipLog[m_nLastFlipLogIdx] ), "%X.Vblanks ..%d", nMarker, m_nVblankCounter ); } else { m_nLastFlipLogIdx = cellAtomicIncr32( &g_flipLogIdx ) & ( ARRAYSIZE( g_flipLog ) - 1 ); V_snprintf( g_flipLog[m_nLastFlipLogIdx], sizeof( g_flipLog[m_nLastFlipLogIdx] ), "%X.Vblank %d", nMarker, m_nVblankCounter ); } #endif TryPumpEvents( nMarker, 1 ); } bool CFlipHandler::TryFlipSurface( uint isVblank ) { if( !m_nFlipSurfaceCount ) { return false; } if( m_bVSync ) { if( m_nVblankCounter < m_nPresentFrequency ) { //FLIP_LOG( "no flip: %d vblanks", m_nVblankCounter, m_nPresentFrequency ); return false; } if( !isVblank ) { double flVSyncInterval = m_flVBlankTimestamp - m_flVBlankTimestamp0, flMissThreshold = r_ps3_vblank_miss_threshold.GetFloat() * flVSyncInterval; double flMiss = Plat_FloatTime() - m_flVBlankTimestamp; if ( flMiss > flMissThreshold ) { FLIP_LOG("no flip: %.2fms miss", flMiss * 1000 ); return false; // wait for another vsync, missed by too much } } } // flip the surface immediately uint nSystemFlipId = m_nSystemFlipId[ m_nFlipSurfaceIdx ]; cellGcmSetFlipImmediate( nSystemFlipId ); #ifdef GCM_ALLOW_TIMESTAMPS // Collect time since previous flip double flFlipImmediateTimestamp = Plat_FloatTime(); OnFrameTimestampAvailableFlip( ( flFlipImmediateTimestamp - m_flFlipImmediateTimestamp ) * 1000.0f ); m_flFlipImmediateTimestamp = flFlipImmediateTimestamp; #endif FLIP_LOG( isVblank ? "vFlip%u, ev Set %u" : "_Flip%u, ev Set %u", nSystemFlipId, m_nFlipSurfaceIdx ); // Release PPU QMS thread waiting for this flip m_evFlipReady[m_nFlipSurfaceIdx].Set(); m_nFlipSurfaceIdx = ( m_nFlipSurfaceIdx + 1 ) % CPs3gcmDisplay::SURFACE_COUNT; m_nFlipSurfaceCount--; m_nVblankCounter = 0; return true; } void CFlipHandler::TryPumpEvents( uint nMarker, uint isVblank ) { if ( m_mutexOfInterruptThread.TryLock() ) { PumpEventsUnsafe( nMarker ); TryFlipSurface( isVblank ); // this will often be duplicate call g_flipHandler.m_mutexOfInterruptThread.Unlock(); } } void CFlipHandler::PumpEventsUnsafe( uint nMarker ) { while( m_interruptFifo.HasEvents( nMarker ) ) { if( !OnRsxInterrupt( m_interruptFifo.DequeueEvent() ) ) break; } } bool RsxInterruptFifo::IsValidMarker( uint nMarker ) { return ( nMarker - m_nGet ) <= MAX_EVENT_COUNT; } bool CFlipHandler::OnRsxInterrupt( const RsxInterruptFifo::Event_t event ) { switch( event.m_nCause ) { case GCM_USERCMD_POSTPROCESS: { // start edge post processing phase here; we can't do the flip yet because we didn't post-process the buffer yet // Simulating MLAA job running and adding the cause to the end of the array some time in the nearest (4-5ms) future void * pColorSurface = g_ps3gcmGlobalState.m_display.surfaceColor[event.m_nSurfaceFlipIdx].DataInLocalMemory(); if( true ) { // g_spuGcm.SyncMlaa(); g_edgePostWorkload.Kick( pColorSurface, event.m_nSurfaceFlipIdx ); } else { FLIP_LOG( "mlaa sync %d", event.m_nSurfaceFlipIdx ); g_spuGcm.SyncMlaa( pColorSurface ); g_spuGcm.m_pEdgePostRsxLock[event.m_nSurfaceFlipIdx] = CELL_GCM_RETURN(); // this will be poked by the SPU job } } break; case GCM_USERCMD_FLIPREADY: FlipAssert( ( m_nFlipSurfaceIdx + m_nFlipSurfaceCount ) % CPs3gcmDisplay::SURFACE_COUNT == event.m_nSurfaceFlipIdx ); FLIP_LOG( "flip ready %d:sys%d", event.m_nSurfaceFlipIdx, m_nSystemFlipId[event.m_nSurfaceFlipIdx] ); m_nFlipSurfaceCount++; break; } return true; } void CFlipHandler::INTERRUPT_VBlankHandler( const uint32 head ) { double flVBlankTimestampSave = g_flipHandler.m_flVBlankTimestamp; g_flipHandler.m_flVBlankTimestamp = Plat_FloatTime(); g_flipHandler.m_flVBlankTimestamp0 = flVBlankTimestampSave; g_flipHandler.TryFlipVblank( ); } void CFlipHandler::INTERRUPT_UserHandler( const uint32 nMarker ) { if( g_flipHandler.m_interruptFifo.IsValidMarker( nMarker ) ) { //FLIP_LOG( "%X.UserInterrupt", nMarker ); g_flipHandler.TryPumpEvents( nMarker, 0 ); } else { // invalid marker: this marker has already happened; skip it //FLIP_LOG( "%X.ERROR.UserInterrupt", nMarker ); DebuggerBreak(); } } void Ps3gcmFlip_SetFlipPresentFrequency( int nNumVBlanks ) { if ( g_flipHandler.m_nPresentFrequency != nNumVBlanks ) { nNumVBlanks = MAX( 1, nNumVBlanks ); nNumVBlanks = MIN( 12, nNumVBlanks ); if ( g_flipHandler.m_nPresentFrequency != nNumVBlanks ) { g_flipHandler.m_nPresentFrequency = nNumVBlanks; } } } /* void CFlipHandler::OnState( int nState, int nValue ) { m_nDebugStates[nState] = nValue; if( m_nDebugStates[RENDERING_SURFACE] == m_nDebugStates[DISPLAYING_SURFACE] ) DebuggerBreak(); }*/