possibly-slightly-better-behaving autoframeskip logic. It's still not completely immune to FPS drops, however (because that seems to require either knowledge from the future, systematic over-estimation, or complete disregard for visual consistency, although it can surely be improved somehow).
This commit is contained in:
parent
a6eb5c11d9
commit
97dd427407
|
@ -145,6 +145,8 @@ void HK_StateSaveSlot(int num, bool justPressed)
|
||||||
savestate_slot(num); //Savestate
|
savestate_slot(num); //Savestate
|
||||||
|
|
||||||
LoadSaveStateInfo();
|
LoadSaveStateInfo();
|
||||||
|
|
||||||
|
AutoFrameSkip_IgnorePreviousDelay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,6 +166,8 @@ void HK_StateLoadSlot(int num, bool justPressed)
|
||||||
NDS_UnPause();
|
NDS_UnPause();
|
||||||
else
|
else
|
||||||
Display();
|
Display();
|
||||||
|
|
||||||
|
AutoFrameSkip_IgnorePreviousDelay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1805,6 +1805,7 @@ static BOOL LoadROM(const char * filename, const char * logicalName)
|
||||||
OpenRWRecentFile(0);
|
OpenRWRecentFile(0);
|
||||||
RamWatchHWnd = CreateDialog(hAppInst, MAKEINTRESOURCE(IDD_RAMWATCH), MainWindow->getHWnd(), (DLGPROC) RamWatchProc);
|
RamWatchHWnd = CreateDialog(hAppInst, MAKEINTRESOURCE(IDD_RAMWATCH), MainWindow->getHWnd(), (DLGPROC) RamWatchProc);
|
||||||
}
|
}
|
||||||
|
if (autoframeskipenab) AutoFrameSkip_IgnorePreviousDelay();
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
@ -3292,6 +3293,7 @@ int HandleKeyMessage(WPARAM wParam, LPARAM lParam, int modifiers)
|
||||||
void Unpause()
|
void Unpause()
|
||||||
{
|
{
|
||||||
lastPauseFromLostFocus = FALSE;
|
lastPauseFromLostFocus = FALSE;
|
||||||
|
if (emu_paused && autoframeskipenab) AutoFrameSkip_IgnorePreviousDelay();
|
||||||
if (!execute && !emu_paused) NDS_Pause(false), emu_paused=true;
|
if (!execute && !emu_paused) NDS_Pause(false), emu_paused=true;
|
||||||
if (emu_paused) NDS_UnPause();
|
if (emu_paused) NDS_UnPause();
|
||||||
emu_paused = 0;
|
emu_paused = 0;
|
||||||
|
@ -3308,6 +3310,7 @@ void Pause()
|
||||||
void TogglePause()
|
void TogglePause()
|
||||||
{
|
{
|
||||||
lastPauseFromLostFocus = FALSE;
|
lastPauseFromLostFocus = FALSE;
|
||||||
|
if (emu_paused && autoframeskipenab) AutoFrameSkip_IgnorePreviousDelay();
|
||||||
if (emu_paused) NDS_UnPause();
|
if (emu_paused) NDS_UnPause();
|
||||||
else NDS_Pause();
|
else NDS_Pause();
|
||||||
emu_paused ^= 1;
|
emu_paused ^= 1;
|
||||||
|
@ -3444,6 +3447,7 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM
|
||||||
{
|
{
|
||||||
case WM_EXITMENULOOP:
|
case WM_EXITMENULOOP:
|
||||||
SPU_Pause(0);
|
SPU_Pause(0);
|
||||||
|
if (autoframeskipenab) AutoFrameSkip_IgnorePreviousDelay();
|
||||||
break;
|
break;
|
||||||
case WM_ENTERMENULOOP: //Update menu items that needs to be updated dynamically
|
case WM_ENTERMENULOOP: //Update menu items that needs to be updated dynamically
|
||||||
{
|
{
|
||||||
|
@ -4746,6 +4750,7 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM
|
||||||
frameskiprate = LOWORD(wParam) - (IDC_FRAMESKIPAUTO1 - 1);
|
frameskiprate = LOWORD(wParam) - (IDC_FRAMESKIPAUTO1 - 1);
|
||||||
sprintf(text, "AUTO%d", frameskiprate);
|
sprintf(text, "AUTO%d", frameskiprate);
|
||||||
WritePrivateProfileString("Video", "FrameSkip", text, IniName);
|
WritePrivateProfileString("Video", "FrameSkip", text, IniName);
|
||||||
|
AutoFrameSkip_IgnorePreviousDelay();
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
case IDC_FRAMESKIP0:
|
case IDC_FRAMESKIP0:
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include "../types.h"
|
#include "../types.h"
|
||||||
#include "../debug.h"
|
#include "../debug.h"
|
||||||
#include "../console.h"
|
#include "../console.h"
|
||||||
#include <windows.h>
|
#include "throttle.h"
|
||||||
|
|
||||||
int FastForward=0;
|
int FastForward=0;
|
||||||
static u64 tmethod,tfreq,afsfreq;
|
static u64 tmethod,tfreq,afsfreq;
|
||||||
|
@ -70,17 +70,23 @@ void InitSpeedThrottle(void)
|
||||||
else
|
else
|
||||||
afsfreq=1000;
|
afsfreq=1000;
|
||||||
tfreq = afsfreq << 16;
|
tfreq = afsfreq << 16;
|
||||||
|
|
||||||
|
AutoFrameSkip_IgnorePreviousDelay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void AutoFrameSkip_BeforeThrottle();
|
||||||
|
|
||||||
|
static u64 ltime;
|
||||||
|
|
||||||
void SpeedThrottle()
|
void SpeedThrottle()
|
||||||
{
|
{
|
||||||
static u64 ttime, ltime;
|
AutoFrameSkip_BeforeThrottle();
|
||||||
|
|
||||||
waiter:
|
waiter:
|
||||||
if(FastForward)
|
if(FastForward)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ttime = GetCurTime();
|
u64 ttime = GetCurTime();
|
||||||
|
|
||||||
if((ttime - ltime) < (tfreq / desiredfps))
|
if((ttime - ltime) < (tfreq / desiredfps))
|
||||||
{
|
{
|
||||||
|
@ -106,70 +112,79 @@ waiter:
|
||||||
|
|
||||||
// auto frameskip
|
// auto frameskip
|
||||||
|
|
||||||
static u64 beginticks=0, endticks=0, diffticks=0;
|
static u64 beginticks=0, endticks=0, preThrottleEndticks=0;
|
||||||
static std::vector<float> diffs;
|
|
||||||
static const int SLIDING_WINDOW_SIZE = 32;
|
|
||||||
static float fSkipFrames = 0;
|
static float fSkipFrames = 0;
|
||||||
static float fSkipFramesError = 0;
|
static float fSkipFramesError = 0;
|
||||||
static int minSkip = 0, maxSkip = 9;
|
static int lastSkip = 0;
|
||||||
|
static float lastError = 0;
|
||||||
|
static float integral = 0;
|
||||||
|
|
||||||
static float maxDiff(const std::vector<float>& diffs)
|
void AutoFrameSkip_IgnorePreviousDelay()
|
||||||
{
|
{
|
||||||
float maximum = 0;
|
beginticks = GetCurTime();
|
||||||
for(int i = 0; i < diffs.size(); i++)
|
|
||||||
if(maximum < diffs[i])
|
// this seems to be a stable way of allowing the skip frames to
|
||||||
maximum = diffs[i];
|
// quickly adjust to a faster environment (e.g. after a loadstate)
|
||||||
return maximum;
|
// without causing oscillation or a sudden change in skip rate
|
||||||
|
fSkipFrames *= 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void addDiff(float diff, std::vector<float>& diffs)
|
static void AutoFrameSkip_BeforeThrottle()
|
||||||
{
|
{
|
||||||
// limit to about double the maximum, to reduce the impact of the occasional huge diffs
|
preThrottleEndticks = GetCurTime();
|
||||||
// (using the last or average entry is a bad idea because some games do their own frameskipping)
|
|
||||||
float maximum = maxDiff(diffs);
|
|
||||||
if(!diffs.empty() && diff > maximum * 2 + 0.0001f)
|
|
||||||
diff = maximum * 2 + 0.0001f;
|
|
||||||
|
|
||||||
diffs.push_back(diff);
|
|
||||||
|
|
||||||
if(diffs.size() > SLIDING_WINDOW_SIZE)
|
|
||||||
diffs.erase(diffs.begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
static float average(const std::vector<float>& diffs)
|
|
||||||
{
|
|
||||||
float avg = 0;
|
|
||||||
for(int i = 0; i < diffs.size(); i++)
|
|
||||||
avg += diffs[i];
|
|
||||||
if(diffs.size())
|
|
||||||
avg /= diffs.size();
|
|
||||||
return avg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoFrameSkip_NextFrame()
|
void AutoFrameSkip_NextFrame()
|
||||||
{
|
{
|
||||||
endticks = GetCurTime();
|
endticks = GetCurTime();
|
||||||
diffticks = endticks - beginticks;
|
|
||||||
|
|
||||||
|
// calculate time since last frame
|
||||||
|
u64 diffticks = endticks - beginticks;
|
||||||
float diff = (float)diffticks / afsfreq;
|
float diff = (float)diffticks / afsfreq;
|
||||||
addDiff(diff,diffs);
|
|
||||||
|
|
||||||
float avg = average(diffs);
|
// calculate time since last frame not including throttle sleep time
|
||||||
float overby = (avg - (desiredspf + (fSkipFrames + 2) * 0.000025f)) * 8;
|
if(!preThrottleEndticks) // if we didn't throttle, use the non-throttle time
|
||||||
// try to avoid taking too long to catch up to the game running fast again
|
preThrottleEndticks = endticks;
|
||||||
if(overby < 0 && fSkipFrames > 4)
|
u64 diffticksUnthrottled = preThrottleEndticks - beginticks;
|
||||||
overby = -fSkipFrames / 4;
|
float diffUnthrottled = (float)diffticksUnthrottled / afsfreq;
|
||||||
else if(avg < desiredspf)
|
|
||||||
overby *= 8;
|
|
||||||
|
|
||||||
fSkipFrames += overby;
|
|
||||||
if(fSkipFrames < minSkip-1)
|
|
||||||
fSkipFrames = minSkip-1;
|
|
||||||
if(fSkipFrames > maxSkip+1)
|
|
||||||
fSkipFrames = maxSkip+1;
|
|
||||||
|
|
||||||
//printf("avg = %g, overby = %g, skipframes = %g\n", avg, overby, fSkipFrames);
|
float error = diffUnthrottled - desiredspf;
|
||||||
|
|
||||||
|
|
||||||
|
// reset way-out-of-range values
|
||||||
|
if(diff > 1)
|
||||||
|
diff = 1;
|
||||||
|
if(error > 1 || error < -1)
|
||||||
|
error = 0;
|
||||||
|
if(diffUnthrottled > 1)
|
||||||
|
diffUnthrottled = desiredspf;
|
||||||
|
|
||||||
|
float derivative = (error - lastError) / diff;
|
||||||
|
lastError = error;
|
||||||
|
|
||||||
|
integral = integral + (error * diff);
|
||||||
|
integral *= 0.99f; // since our integral isn't reliable, reduce it to 0 over time.
|
||||||
|
|
||||||
|
// "PID controller" constants
|
||||||
|
// this stuff is probably being done all wrong, but these seem to work ok
|
||||||
|
static const float Kp = 40.0f;
|
||||||
|
static const float Ki = 0.55f;
|
||||||
|
static const float Kd = 0.04f;
|
||||||
|
|
||||||
|
float errorTerm = error * Kp;
|
||||||
|
float derivativeTerm = derivative * Kd;
|
||||||
|
float integralTerm = integral * Ki;
|
||||||
|
float adjustment = errorTerm + derivativeTerm + integralTerm;
|
||||||
|
|
||||||
|
// apply the output adjustment
|
||||||
|
fSkipFrames += adjustment;
|
||||||
|
|
||||||
|
// if we're running too slowly, prevent the throttle from kicking in
|
||||||
|
if(adjustment > 0 && fSkipFrames > 0)
|
||||||
|
ltime-=tfreq/desiredfps;
|
||||||
|
|
||||||
|
preThrottleEndticks = 0;
|
||||||
beginticks = GetCurTime();
|
beginticks = GetCurTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,25 +192,41 @@ int AutoFrameSkip_GetSkipAmount(int min, int max)
|
||||||
{
|
{
|
||||||
int rv = (int)fSkipFrames;
|
int rv = (int)fSkipFrames;
|
||||||
fSkipFramesError += fSkipFrames - rv;
|
fSkipFramesError += fSkipFrames - rv;
|
||||||
while(fSkipFramesError >= 1.0f)
|
|
||||||
|
// resolve accumulated fractional error
|
||||||
|
// where doing so doesn't push us out of range
|
||||||
|
while(fSkipFramesError >= 1.0f && rv <= lastSkip && rv < max)
|
||||||
{
|
{
|
||||||
fSkipFramesError -= 1.0f;
|
fSkipFramesError -= 1.0f;
|
||||||
rv++;
|
rv++;
|
||||||
}
|
}
|
||||||
while(fSkipFramesError <= -1.0f)
|
while(fSkipFramesError <= -1.0f && rv >= lastSkip && rv > min)
|
||||||
{
|
{
|
||||||
fSkipFramesError += 1.0f;
|
fSkipFramesError += 1.0f;
|
||||||
rv--;
|
rv--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// restrict skip amount to requested range
|
||||||
if(rv < min)
|
if(rv < min)
|
||||||
rv = min;
|
rv = min;
|
||||||
if(rv > max)
|
if(rv > max)
|
||||||
rv = max;
|
rv = max;
|
||||||
minSkip = min;
|
|
||||||
maxSkip = max;
|
|
||||||
|
|
||||||
//printf("SKIPPED: %d\n", rv);
|
// limit maximum error accumulation (it's mainly only for fractional components)
|
||||||
|
if(fSkipFramesError >= 4.0f)
|
||||||
|
fSkipFramesError = 4.0f;
|
||||||
|
if(fSkipFramesError <= -4.0f)
|
||||||
|
fSkipFramesError = -4.0f;
|
||||||
|
|
||||||
|
// limit ongoing skipframes to requested range + 1 on each side
|
||||||
|
if(fSkipFrames < min-1)
|
||||||
|
fSkipFrames = min-1;
|
||||||
|
if(fSkipFrames > max+1)
|
||||||
|
fSkipFrames = max+1;
|
||||||
|
|
||||||
|
// printf("%d", rv);
|
||||||
|
|
||||||
|
lastSkip = rv;
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ void InitSpeedThrottle();
|
||||||
void SpeedThrottle();
|
void SpeedThrottle();
|
||||||
|
|
||||||
void AutoFrameSkip_NextFrame();
|
void AutoFrameSkip_NextFrame();
|
||||||
|
void AutoFrameSkip_IgnorePreviousDelay();
|
||||||
int AutoFrameSkip_GetSkipAmount(int min=0, int max=9);
|
int AutoFrameSkip_GetSkipAmount(int min=0, int max=9);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue