ref: f7063748617e4dc6742294e8a471383e7b2ba497
parent: 14c45c48c360db0aa89bda32c450a710bf94a79c
author: Olav Sørensen <olav.sorensen@live.no>
date: Tue Jan 24 17:46:00 EST 2023
Fixing of frame delay/wait routines
--- a/src/ft2_events.c
+++ b/src/ft2_events.c
@@ -368,7 +368,7 @@
// reset vblank end time if we minimize window
if (event->window.event == SDL_WINDOWEVENT_MINIMIZED || event->window.event == SDL_WINDOWEVENT_FOCUS_LOST)
- hpc_ResetEndTime(&video.vblankHpc);
+ hpc_ResetCounters(&video.vblankHpc);
}
}
--- a/src/ft2_hpc.c
+++ b/src/ft2_hpc.c
@@ -13,7 +13,7 @@
#include <stdbool.h>
#include "ft2_hpc.h"
-#define FRAC_BITS 53
+#define FRAC_BITS 63
#define FRAC_SCALE (1ULL << FRAC_BITS)
#define FRAC_MASK (FRAC_SCALE-1)
@@ -21,6 +21,10 @@
#ifdef _WIN32 // Windows usleep() implementation
+#define STATUS_SUCCESS 0
+
+static bool canAdjustTimerResolution;
+
static NTSTATUS (__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval);
static NTSTATUS (__stdcall *NtQueryTimerResolution)(PULONG MinimumResolution, PULONG MaximumResolution, PULONG ActualResolution);
static NTSTATUS (__stdcall *NtSetTimerResolution)(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution);
@@ -31,7 +35,7 @@
{
LARGE_INTEGER delayInterval;
- // NtDelayExecution() delays in 100ns-units, and negative value = delay from current time
+ // NtDelayExecution() delays in 100ns-units, and a negative value means to delay from current time
usec *= -10;
delayInterval.HighPart = 0xFFFFFFFF; // negative 64-bit value, we only set the lower dword
@@ -39,7 +43,7 @@
NtDelayExecution(false, &delayInterval);
}
-static void usleepWeak(int32_t usec) // fallback if no NtDelayExecution()
+static void usleepPoor(int32_t usec) // fallback if no NtDelayExecution()
{
Sleep((usec + 500) / 1000);
}
@@ -47,10 +51,11 @@
static void windowsSetupUsleep(void)
{
NtDelayExecution = (NTSTATUS (__stdcall *)(BOOL, PLARGE_INTEGER))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");
+ usleep = (NtDelayExecution != NULL) ? usleepGood : usleepPoor;
+
NtQueryTimerResolution = (NTSTATUS (__stdcall *)(PULONG, PULONG, PULONG))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryTimerResolution");
NtSetTimerResolution = (NTSTATUS (__stdcall *)(ULONG, BOOLEAN, PULONG))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtSetTimerResolution");
-
- usleep = (NtDelayExecution != NULL) ? usleepGood : usleepWeak;
+ canAdjustTimerResolution = (NtQueryTimerResolution != NULL && NtSetTimerResolution != NULL);
}
#endif
@@ -60,49 +65,67 @@
windowsSetupUsleep();
#endif
hpcFreq.freq64 = SDL_GetPerformanceFrequency();
- hpcFreq.dFreq = (double)hpcFreq.freq64;
- hpcFreq.dFreqMulMs = 1000.0 / hpcFreq.dFreq;
- hpcFreq.dFreqMulMicro = (1000.0 * 1000.0) / hpcFreq.dFreq;
+
+ double dFreq = (double)hpcFreq.freq64;
+
+ hpcFreq.dFreqMulMs = 1000.0 / dFreq;
+ hpcFreq.dFreqMulMicro = (1000.0 * 1000.0) / dFreq;
}
-void hpc_SetDurationInHz(hpc_t *hpc, const double dHz)
+// returns 64-bit fractional part of u64 divided by u32
+static uint64_t getFrac64FromU64DivU32(uint64_t dividend, uint32_t divisor)
{
- const double dDuration = hpcFreq.dFreq / dHz;
+ if (dividend == 0 || divisor == 0 || divisor >= dividend)
+ return 0;
- // break down duration into integer and frac parts
- double dDurationInt;
- double dDurationFrac = modf(dDuration, &dDurationInt);
+ dividend %= divisor;
- // set 64:53fp values
- hpc->duration64Int = (uint64_t)dDurationInt;
- hpc->duration64Frac = (uint64_t)round(dDurationFrac * FRAC_SCALE);
+ if (dividend == 0)
+ return 0;
+
+ const uint32_t quotient = (uint32_t)((dividend << 32) / divisor);
+ const uint32_t remainder = (uint32_t)((dividend << 32) % divisor);
+
+ const uint32_t resultHi = quotient;
+ const uint32_t resultLo = (uint32_t)(((uint64_t)remainder << 32) / divisor);
+
+ return ((uint64_t)resultHi << 32) | resultLo;
}
-void hpc_ResetEndTime(hpc_t *hpc)
+void hpc_SetDurationInHz(hpc_t *hpc, uint32_t hz)
{
- hpc->endTime64Int = SDL_GetPerformanceCounter() + hpc->duration64Int;
- hpc->endTime64Frac = hpc->duration64Frac;
+ // set 64:63fp value
+ hpc->durationInt = hpcFreq.freq64 / hz;
+ hpc->durationFrac = getFrac64FromU64DivU32(hpcFreq.freq64, hz) >> 1;
+
+ hpc->resetFrame = hz * 3600; // reset counters every hour
+
}
-void hpc_Wait(hpc_t *hpc)
+void hpc_ResetCounters(hpc_t *hpc)
{
-#ifdef _WIN32 // set resolution to 0.5ms (safest minium) - this is confirmed to improve NtDelayExecution() and Sleep()
- ULONG originalTimerResolution, minRes, maxRes, curRes;
+ hpc->endTimeInt = SDL_GetPerformanceCounter() + hpc->durationInt;
+ hpc->endTimeFrac = hpc->durationFrac;
+}
- if (NtQueryTimerResolution != NULL && NtSetTimerResolution != NULL)
+void hpc_Wait(hpc_t *hpc)
+{
+#ifdef _WIN32
+ /* Make sure resolution is set to 0.5ms (safest minimum) - this is confirmed to improve
+ ** NtDelayExecution() and Sleep(). This will only be changed when needed, not per frame.
+ */
+ ULONG curRes, minRes, maxRes, junk;
+ if (canAdjustTimerResolution && NtQueryTimerResolution(&minRes, &maxRes, &curRes) == STATUS_SUCCESS)
{
- if (!NtQueryTimerResolution(&minRes, &maxRes, &originalTimerResolution))
- {
- if (originalTimerResolution != 5000 && maxRes <= 5000)
- NtSetTimerResolution(5000, TRUE, &curRes); // set to 0.5ms (safest minimum)
- }
+ if (curRes != 5000 && maxRes <= 5000)
+ NtSetTimerResolution(5000, TRUE, &junk); // 0.5ms
}
#endif
const uint64_t currTime64 = SDL_GetPerformanceCounter();
- if (currTime64 < hpc->endTime64Int)
+ if (currTime64 < hpc->endTimeInt)
{
- uint64_t timeLeft64 = hpc->endTime64Int - currTime64;
+ uint64_t timeLeft64 = hpc->endTimeInt - currTime64;
// convert to int32_t for fast SSE2 SIMD usage lateron
if (timeLeft64 > INT32_MAX)
@@ -117,12 +140,25 @@
// set next end time
- hpc->endTime64Int += hpc->duration64Int;
+ hpc->endTimeInt += hpc->durationInt;
- hpc->endTime64Frac += hpc->duration64Frac;
- if (hpc->endTime64Frac >= FRAC_SCALE)
+ // handle fractional part
+ hpc->endTimeFrac += hpc->durationFrac;
+ if (hpc->endTimeFrac >= FRAC_SCALE)
{
- hpc->endTime64Frac &= FRAC_MASK;
- hpc->endTime64Int++;
+ hpc->endTimeFrac &= FRAC_MASK;
+ hpc->endTimeInt++;
+ }
+
+ /* The counter ("endTimeInt") can accumulate major errors after a couple of hours,
+ ** since each frame is not happening at perfect intervals.
+ ** To fix this, reset the counter's int & frac once every hour. We should only get
+ ** up to one frame of stutter while they are resetting, then it's back to normal.
+ */
+ hpc->frameCounter++;
+ if (hpc->frameCounter >= hpc->resetFrame)
+ {
+ hpc->frameCounter = 0;
+ hpc_ResetCounters(hpc);
}
}
--- a/src/ft2_hpc.h
+++ b/src/ft2_hpc.h
@@ -6,18 +6,19 @@
typedef struct
{
uint64_t freq64;
- double dFreq, dFreqMulMicro, dFreqMulMs;
+ double dFreqMulMicro, dFreqMulMs;
} hpcFreq_t;
typedef struct
{
- uint64_t duration64Int, duration64Frac;
- uint64_t endTime64Int, endTime64Frac;
+ uint64_t durationInt, durationFrac;
+ uint64_t endTimeInt, endTimeFrac;
+ uint64_t frameCounter, resetFrame;
} hpc_t;
extern hpcFreq_t hpcFreq;
void hpc_Init(void);
-void hpc_SetDurationInHz(hpc_t *hpc, double dHz);
-void hpc_ResetEndTime(hpc_t *hpc);
+void hpc_SetDurationInHz(hpc_t *hpc, uint32_t dHz);
+void hpc_ResetCounters(hpc_t *hpc);
void hpc_Wait(hpc_t *hpc);
--- a/src/ft2_main.c
+++ b/src/ft2_main.c
@@ -233,11 +233,11 @@
SDL_DetachThread(initMidiThread); // don't wait for this thread, let it clean up when done
#endif
- hpc_ResetEndTime(&video.vblankHpc); // this is needed for potential okBox() calls in handleModuleLoadFromArg()
+ hpc_ResetCounters(&video.vblankHpc); // quirk: this is needed for potential okBox() calls in handleModuleLoadFromArg()
handleModuleLoadFromArg(argc, argv);
editor.mainLoopOngoing = true;
- hpc_ResetEndTime(&video.vblankHpc); // this must be the very last thing done before entering the main loop
+ hpc_ResetCounters(&video.vblankHpc); // this must be the last thing we do before entering the main loop
while (editor.programRunning)
{
--- a/src/ft2_video.c
+++ b/src/ft2_video.c
@@ -187,7 +187,7 @@
if (frameTimeDiff64 > INT32_MAX)
frameTimeDiff64 = INT32_MAX;
- dRunningFrameDuration += (int32_t)frameTimeDiff64 / (hpcFreq.dFreq / 1000.0);
+ dRunningFrameDuration += (int32_t)frameTimeDiff64 * hpcFreq.dFreqMulMs;
}
}
--- a/src/scopes/ft2_scopes.c
+++ b/src/scopes/ft2_scopes.c
@@ -521,7 +521,7 @@
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
hpc_SetDurationInHz(&scopeHpc, SCOPE_HZ);
- hpc_ResetEndTime(&scopeHpc);
+ hpc_ResetCounters(&scopeHpc);
while (editor.programRunning)
{