From 1a3170fd680f62631231df738f0084a9d4d946df Mon Sep 17 00:00:00 2001 From: harry Date: Sun, 21 May 2023 16:44:20 -0400 Subject: [PATCH] Changed Qt GUI frame throttling to use new time stamp record for computing timing. This fixes an issue where frame timing starts to lose precision when application time gets to float large values by performing calcultions using integer math --- src/drivers/Qt/sdl-throttle.cpp | 74 +++++++------ src/utils/timeStamp.cpp | 13 ++- src/utils/timeStamp.h | 183 ++++++++++++++++++++++++++++---- 3 files changed, 212 insertions(+), 58 deletions(-) diff --git a/src/drivers/Qt/sdl-throttle.cpp b/src/drivers/Qt/sdl-throttle.cpp index 5603eb6c..800ad121 100644 --- a/src/drivers/Qt/sdl-throttle.cpp +++ b/src/drivers/Qt/sdl-throttle.cpp @@ -37,7 +37,8 @@ static const double Fastest = 32; // 32x speed (around 1920 fps on NTSC) static const double Normal = 1.0; // 1x speed (around 60 fps on NTSC) static uint32 frameLateCounter = 0; -static double Lasttime=0, Nexttime=0, Latetime=0; +static FCEU::timeStampRecord Lasttime, Nexttime, Latetime; +static FCEU::timeStampRecord DesiredFrameTime, HalfFrameTime, QuarterFrameTime, DoubleFrameTime; static double desired_frametime = (1.0 / 60.099823); static double desired_frameRate = (60.099823); static double baseframeRate = (60.099823); @@ -119,9 +120,9 @@ static void setTimer( double hz ) //printf("Timer Set: %li ns\n", ispec.it_value.tv_nsec ); - Lasttime = getHighPrecTimeStamp(); - Nexttime = Lasttime + desired_frametime; - Latetime = Nexttime + (desired_frametime*0.50); + Lasttime.readNew(); + Nexttime = Lasttime + DesiredFrameTime; + Latetime = Nexttime + HalfFrameTime; } #endif @@ -270,10 +271,17 @@ RefreshThrottleFPS(void) if ( T < 0 ) T = 1; + DesiredFrameTime.fromSeconds( desired_frametime ); + HalfFrameTime = DesiredFrameTime / 2; + QuarterFrameTime = DesiredFrameTime / 4; + DoubleFrameTime = DesiredFrameTime * 2; + + //printf("FrameTime: %f %f %f %f \n", DesiredFrameTime.toSeconds(), + // HalfFrameTime.toSeconds(), QuarterFrameTime.toSeconds(), DoubleFrameTime.toSeconds() ); //printf("FrameTime: %llu %llu %f %lf \n", fps, fps >> 24, hz, desired_frametime ); - Lasttime=0; - Nexttime=0; + Lasttime.zero(); + Nexttime.zero(); InFrame=0; #ifdef __linux__ @@ -297,18 +305,17 @@ double getFrameRateAdjustmentRatio(void) return frmRateAdjRatio; } -int highPrecSleep( double timeSeconds ) +static int highPrecSleep( FCEU::timeStampRecord &ts ) { int ret = 0; #if defined(__linux__) || defined(__APPLE__) || defined(__unix__) struct timespec req, rem; - req.tv_sec = (long)timeSeconds; - req.tv_nsec = (long)((timeSeconds - (double)req.tv_sec) * 1e9); + req = ts.toTimeSpec(); ret = nanosleep( &req, &rem ); #else - SDL_Delay( (long)(timeSeconds * 1e3) ); + SDL_Delay( ts.toMilliSeconds() ); #endif return ret; } @@ -323,39 +330,37 @@ SpeedThrottle(void) { return 0; /* Done waiting */ } - double time_left; - double cur_time, idleStart; - double frame_time = desired_frametime; - double halfFrame = 0.500 * frame_time; - double quarterFrame = 0.250 * frame_time; + FCEU::timeStampRecord cur_time, idleStart, time_left; - idleStart = cur_time = getHighPrecTimeStamp(); + cur_time.readNew(); + idleStart = cur_time; - if (Lasttime < 1.0) + if (Lasttime.isZero()) { + //printf("Lasttime Reset\n"); Lasttime = cur_time; - Latetime = Lasttime + 2.0*frame_time; + Latetime = Lasttime + DoubleFrameTime; } if (!InFrame) { InFrame = 1; - Nexttime = Lasttime + frame_time; - Latetime = Nexttime + halfFrame; + Nexttime = Lasttime + DesiredFrameTime; + Latetime = Nexttime + HalfFrameTime; } if (cur_time >= Nexttime) { - time_left = 0; + time_left.zero(); } else { time_left = Nexttime - cur_time; } - if (time_left > 50) + if (time_left.toMilliSeconds() > 50) { - time_left = 50; + time_left.fromMilliSeconds(50); /* In order to keep input responsive, don't wait too long at once */ /* 50 ms wait gives us a 20 Hz responsetime which is nice. */ } @@ -381,7 +386,7 @@ SpeedThrottle(void) } } } - else if ( time_left > 0 ) + else if ( !time_left.isZero() ) { highPrecSleep( time_left ); } @@ -394,7 +399,7 @@ SpeedThrottle(void) } } #else - if ( time_left > 0 ) + if ( !time_left.isZero() ) { highPrecSleep( time_left ); } @@ -408,14 +413,15 @@ SpeedThrottle(void) } #endif - cur_time = getHighPrecTimeStamp(); + cur_time.readNew(); - if ( cur_time >= (Nexttime - quarterFrame) ) + if ( cur_time >= (Nexttime - QuarterFrameTime) ) { if ( keepFrameTimeStats ) { + FCEU::timeStampRecord diffTime = (cur_time - Lasttime); - frameDeltaCur = (cur_time - Lasttime); + frameDeltaCur = diffTime.toSeconds(); if ( frameDeltaCur < frameDeltaMin ) { @@ -426,7 +432,9 @@ SpeedThrottle(void) frameDeltaMax = frameDeltaCur; } - frameIdleCur = (cur_time - idleStart); + diffTime = (cur_time - idleStart); + + frameIdleCur = diffTime.toSeconds(); if ( frameIdleCur < frameIdleMin ) { @@ -440,14 +448,14 @@ SpeedThrottle(void) //printf("Frame Sleep Time: %f Target Error: %f us\n", time_left * 1e6, (cur_time - Nexttime) * 1e6 ); } Lasttime = Nexttime; - Nexttime = Lasttime + frame_time; - Latetime = Nexttime + halfFrame; + Nexttime = Lasttime + DesiredFrameTime; + Latetime = Nexttime + HalfFrameTime; if ( cur_time >= Nexttime ) { Lasttime = cur_time; - Nexttime = Lasttime + frame_time; - Latetime = Nexttime + halfFrame; + Nexttime = Lasttime + DesiredFrameTime; + Latetime = Nexttime + HalfFrameTime; } return 0; /* Done waiting */ } diff --git a/src/utils/timeStamp.cpp b/src/utils/timeStamp.cpp index e8eff27b..b746fc5e 100644 --- a/src/utils/timeStamp.cpp +++ b/src/utils/timeStamp.cpp @@ -29,7 +29,7 @@ static uint64_t rdtsc() namespace FCEU { -uint64_t timeStampRecord::tscFreq = 0; +uint64_t timeStampRecord::_tscFreq = 0; #if defined(WIN32) uint64_t timeStampRecord::qpcFreq = 0; #endif @@ -63,16 +63,15 @@ static timeStampModule module; bool timeStampModuleInitialized(void) { - bool initialized = false; #if defined(WIN32) - initialized = timeStampRecord::qpcFreq != 0; + bool initialized = timeStampRecord::qpcFreq != 0; #else - initialized = true; + bool initialized = true; #endif return initialized; } -void timeStampModuleCalibrate(int numSamples) +void timeStampRecord::tscCalibrate(int numSamples) { timeStampRecord t1, t2, td; uint64_t td_sum = 0; @@ -102,10 +101,10 @@ void timeStampModuleCalibrate(int numSamples) td_avg = static_cast(td_sum); - timeStampRecord::tscFreq = static_cast( td_avg / td.toSeconds() ); + timeStampRecord::_tscFreq = static_cast( td_avg / td.toSeconds() ); printf("%i Calibration: %f sec TSC:%llu TSC Freq: %f MHz\n", i, td.toSeconds(), - static_cast(td.tsc), static_cast(timeStampRecord::tscFreq) * 1.0e-6 ); + static_cast(td.tsc), static_cast(timeStampRecord::_tscFreq) * 1.0e-6 ); } } diff --git a/src/utils/timeStamp.h b/src/utils/timeStamp.h index 497c1ab5..0ac67c42 100644 --- a/src/utils/timeStamp.h +++ b/src/utils/timeStamp.h @@ -9,11 +9,10 @@ namespace FCEU { - struct timeStampRecord + class timeStampRecord { + public: #if defined(__linux__) || defined(__APPLE__) || defined(__unix__) - struct timespec ts; - uint64_t tsc; timeStampRecord(void) { @@ -37,7 +36,7 @@ namespace FCEU if (ts.tv_nsec >= 1000000000) { ts.tv_nsec -= 1000000000; - ts.tv_sec++; + ts.tv_sec++; } tsc += op.tsc; return *this; @@ -53,7 +52,7 @@ namespace FCEU if (res.ts.tv_nsec >= 1000000000) { res.ts.tv_nsec -= 1000000000; - res.ts.tv_sec++; + res.ts.tv_sec++; } res.tsc = tsc + op.tsc; return res; @@ -76,30 +75,84 @@ namespace FCEU return res; } + timeStampRecord operator * (const unsigned int multiplier) + { + timeStampRecord res; + + res.ts.tv_sec = ts.tv_sec * multiplier; + res.ts.tv_nsec = ts.tv_nsec * multiplier; + + if (res.ts.tv_nsec >= 1000000000) + { + res.ts.tv_nsec -= 1000000000; + res.ts.tv_sec++; + } + res.tsc = tsc * multiplier; + + return res; + } + + timeStampRecord operator / (const unsigned int divisor) + { + timeStampRecord res; + + res.ts.tv_sec = ts.tv_sec / divisor; + res.ts.tv_nsec = ts.tv_nsec / divisor; + res.tsc = tsc / divisor; + + return res; + } + bool operator > (const timeStampRecord& op) { - bool res = false; + bool res; if (ts.tv_sec == op.ts.tv_sec) { res = (ts.tv_nsec > op.ts.tv_nsec); } - else if (ts.tv_sec > op.ts.tv_sec) + else { - res = true; + res = (ts.tv_sec > op.ts.tv_sec); + } + return res; + } + bool operator >= (const timeStampRecord& op) + { + bool res; + if (ts.tv_sec == op.ts.tv_sec) + { + res = (ts.tv_nsec >= op.ts.tv_nsec); + } + else + { + res = (ts.tv_sec >= op.ts.tv_sec); } return res; } bool operator < (const timeStampRecord& op) { - bool res = false; + bool res; if (ts.tv_sec == op.ts.tv_sec) { res = (ts.tv_nsec < op.ts.tv_nsec); } - else if (ts.tv_sec < op.ts.tv_sec) + else { - res = true; + res = (ts.tv_sec < op.ts.tv_sec); + } + return res; + } + bool operator <= (const timeStampRecord& op) + { + bool res; + if (ts.tv_sec == op.ts.tv_sec) + { + res = (ts.tv_nsec <= op.ts.tv_nsec); + } + else + { + res = (ts.tv_sec <= op.ts.tv_sec); } return res; } @@ -111,6 +164,11 @@ namespace FCEU tsc = 0; } + bool isZero(void) + { + return (ts.tv_sec == 0) && (ts.tv_nsec == 0); + } + void fromSeconds(unsigned int sec) { ts.tv_sec = sec; @@ -118,12 +176,33 @@ namespace FCEU tsc = 0; } + void fromSeconds(double sec) + { + double ns; + ts.tv_sec = static_cast(sec); + ns = (sec - static_cast(ts.tv_sec)) * 1.0e9; + ts.tv_nsec = static_cast(ns); + tsc = 0; + } + double toSeconds(void) { double sec = static_cast(ts.tv_sec) + ( static_cast(ts.tv_nsec) * 1.0e-9 ); return sec; } + void fromMilliSeconds(uint64_t ms) + { + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms * 1000000) - (ts.tv_sec * 1000000000); + } + + uint64_t toMilliSeconds(void) + { + uint64_t ms = (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000 ); + return ms; + } + uint64_t toCounts(void) { return (ts.tv_sec * 1000000000) + ts.tv_nsec; @@ -133,9 +212,12 @@ namespace FCEU { return 1000000000; } + + struct timespec toTimeSpec(void) + { + return ts; + } #else // WIN32 - uint64_t ts; - uint64_t tsc; timeStampRecord(void) { @@ -176,15 +258,43 @@ namespace FCEU return res; } + timeStampRecord operator * (const unsigned int multiplier) + { + timeStampRecord res; + + res.ts = ts * multiplier; + res.tsc = tsc * multiplier; + + return res; + } + + timeStampRecord operator / (const unsigned int divisor) + { + timeStampRecord res; + + res.ts = ts / divisor; + res.tsc = tsc / divisor; + + return res; + } + bool operator > (const timeStampRecord& op) { return ts > op.ts; } + bool operator >= (const timeStampRecord& op) + { + return ts >= op.ts; + } bool operator < (const timeStampRecord& op) { return ts < op.ts; } + bool operator <= (const timeStampRecord& op) + { + return ts <= op.ts; + } void zero(void) { @@ -192,18 +302,41 @@ namespace FCEU tsc = 0; } + bool isZero(void) + { + return (ts == 0); + } + + void fromSeconds(unsigned int sec) { ts = sec * qpcFreq; tsc = 0; } + void fromSeconds(double sec) + { + ts = static_cast(sec * static_cast(qpcFreq)); + tsc = 0; + } + double toSeconds(void) { double sec = static_cast(ts) / static_cast(qpcFreq); return sec; } + void fromMilliSeconds(uint64_t ms) + { + ts = (ms * qpcFreq) / 1000; + } + + uint64_t toMilliSeconds(void) + { + uint64_t ms = (ts * 1000) / qpcFreq; + return ms; + } + uint64_t toCounts(void) { return ts; @@ -213,19 +346,33 @@ namespace FCEU { return qpcFreq; } - static uint64_t qpcFreq; #endif - static uint64_t tscFreq; + uint64_t getTSC(void){ return tsc; }; + + static uint64_t tscFreq(void) + { + return _tscFreq; + } static bool tscValid(void){ return tscFreq != 0; }; + // Call this function to calibrate the estimated TSC frequency + static void tscCalibrate(int numSamples = 0); + void readNew(void); + + private: +#if defined(__linux__) || defined(__APPLE__) || defined(__unix__) + struct timespec ts; +#else // Win32 + uint64_t ts; + static uint64_t qpcFreq; +#endif + uint64_t tsc; + static uint64_t _tscFreq; }; bool timeStampModuleInitialized(void); - // Call this function to calibrate the estimated TSC frequency - void timeStampModuleCalibrate(int numSamples = 1); - } // namespace FCEU