Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

650 lines
24 KiB

// 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();
}*/