Counters: Tighten tolerances on framelimiter (#3785)

Smooths out frame pace and keeps average framerate closer to target.
This commit is contained in:
RedPanda4552 2020-10-06 09:14:07 -04:00 committed by GitHub
parent 11bb7bdff9
commit b62270ea2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 48 additions and 42 deletions

View File

@ -103,7 +103,7 @@ static __fi void _rcntSet( int cntidx )
if (c < nextCounter)
{
nextCounter = c;
cpuSetNextEvent( nextsCounter, nextCounter ); //Need to update on counter resets/target changes
cpuSetNextEvent( nextsCounter, nextCounter ); // Need to update on counter resets/target changes
}
// Ignore target diff if target is currently disabled.
@ -121,7 +121,7 @@ static __fi void _rcntSet( int cntidx )
if (c < nextCounter)
{
nextCounter = c;
cpuSetNextEvent( nextsCounter, nextCounter ); //Need to update on counter resets/target changes
cpuSetNextEvent(nextsCounter, nextCounter); // Need to update on counter resets/target changes
}
}
}
@ -139,6 +139,7 @@ static __fi void cpuRcntSet()
// sanity check!
if( nextCounter < 0 ) nextCounter = 0;
cpuSetNextEvent(nextsCounter, nextCounter); // Need to update on counter resets/target changes
}
void rcntInit()
@ -394,58 +395,65 @@ void frameLimitReset()
m_iStart = GetCPUTicks();
}
// Convenience function to update UI thread and set patches.
static __fi void frameLimitUpdateCore()
{
GetCoreThread().VsyncInThread();
Cpu->CheckExecutionState();
}
// Framelimiter - Measures the delta time between calls and stalls until a
// certain amount of time passes if such time hasn't passed yet.
// See the GS FrameSkip function for details on why this is here and not in the GS.
static __fi void frameLimit()
{
// 999 means the user would rather just have framelimiting turned off...
if( !EmuConfig.GS.FrameLimitEnable ) return;
u64 uExpectedEnd = m_iStart + m_iTicks;
u64 iEnd = GetCPUTicks();
s64 sDeltaTime = iEnd - uExpectedEnd;
// If the framerate drops too low, reset the expected value. This avoids
// excessive amounts of "fast forward" syndrome which would occur if we
// tried to catch up too much.
if( sDeltaTime > m_iTicks*8 )
// Framelimiter off in settings? Framelimiter go brrr.
if (!EmuConfig.GS.FrameLimitEnable)
{
m_iStart = iEnd - m_iTicks;
frameLimitUpdateCore();
return;
}
// use the expected frame completion time as our starting point.
// improves smoothness by making the framelimiter more adaptive to the
// imperfect TIMESLICE() wait, and allows it to speed up a wee bit after
// slow frames to "catch up."
u64 uExpectedEnd = m_iStart + m_iTicks; // Compute when we would expect this frame to end, assuming everything goes perfectly perfect.
u64 iEnd = GetCPUTicks(); // The current tick we actually stopped on.
s64 sDeltaTime = iEnd - uExpectedEnd; // The diff between when we stopped and when we expected to.
// If frame ran too long...
if (sDeltaTime >= m_iTicks)
{
// ... Fudge the next frame start over a bit. Prevents fast forward zoomies.
m_iStart += (sDeltaTime / m_iTicks) * m_iTicks;
frameLimitUpdateCore();
return;
}
// Conversion of delta from CPU ticks (microseconds) to milliseconds
s32 msec = (int) ((sDeltaTime * -1000) / (s64) GetTickFrequency());
// If any integer value of milliseconds exists, sleep it off.
// Prior comments suggested that 1-2 ms sleeps were inaccurate on some OSes;
// further testing suggests instead that this was utter bullshit.
if (msec > 1)
{
Threading::Sleep(msec - 1);
}
// Conversion to milliseconds loses some precision; after sleeping off whole milliseconds,
// spin the thread without sleeping until we finally reach our expected end time.
while (GetCPUTicks() < uExpectedEnd)
{
// SKREEEEEEEE
}
// Finally, set our next frame start to when this one ends
m_iStart = uExpectedEnd;
// Shortcut for cases where no waiting is needed (they're running slow already,
// so don't bog 'em down with extra math...)
if( sDeltaTime >= 0 ) return;
// If we're way ahead then we can afford to sleep the thread a bit.
// (note, on Windows sleep(1) thru sleep(2) tend to be the least accurate sleeps,
// and longer sleeps tend to be pretty reliable, so that's why the convoluted if/
// else below. The same generally isn't true for Linux, but no harm either way
// really.)
s32 msec = (int)((sDeltaTime*-1000) / (s64)GetTickFrequency());
if( msec > 4 ) Threading::Sleep( msec );
else if( msec > 2 ) Threading::Sleep( 1 );
// Sleep is not picture-perfect accurate, but it's actually not necessary to
// maintain a "perfect" lock to uExpectedEnd anyway. if we're a little ahead
// starting this frame, it'll just sleep longer the next to make up for it. :)
frameLimitUpdateCore();
}
static __fi void VSyncStart(u32 sCycle)
{
GetCoreThread().VsyncInThread();
Cpu->CheckExecutionState();
frameLimit(); // limit FPS
gsPostVsyncStart(); // MUST be after framelimit; doing so before causes funk with frame times!
if(EmuConfig.Trace.Enabled && EmuConfig.Trace.EE.m_EnableAll)
SysTrace.EE.Counters.Write( " ================ EE COUNTER VSYNC START (frame: %d) ================", g_FrameCount );
@ -460,7 +468,7 @@ static __fi void VSyncStart(u32 sCycle)
hwIntcIrq(INTC_VBLANK_S);
psxVBlankStart();
gsPostVsyncStart();
if (gates) rcntStartGate(true, sCycle); // Counters Start Gate code
// INTC - VB Blank Start Hack --
@ -514,8 +522,6 @@ static __fi void VSyncEnd(u32 sCycle)
if (!(g_FrameCount % 60))
sioNextFrame();
frameLimit(); // limit FPS
// This doesn't seem to be needed here. Games only seem to break with regard to the
// vsyncstart irq.
//cpuRegs.eCycle[30] = 2;