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