Skip to content

Commit f706374

Browse files
committed
Fixing of frame delay/wait routines
1 parent 14c45c4 commit f706374

File tree

6 files changed

+82
-45
lines changed

6 files changed

+82
-45
lines changed

src/ft2_events.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ void handleWaitVblQuirk(SDL_Event *event)
368368

369369
// reset vblank end time if we minimize window
370370
if (event->window.event == SDL_WINDOWEVENT_MINIMIZED || event->window.event == SDL_WINDOWEVENT_FOCUS_LOST)
371-
hpc_ResetEndTime(&video.vblankHpc);
371+
hpc_ResetCounters(&video.vblankHpc);
372372
}
373373
}
374374

src/ft2_hpc.c

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@
1313
#include <stdbool.h>
1414
#include "ft2_hpc.h"
1515

16-
#define FRAC_BITS 53
16+
#define FRAC_BITS 63
1717
#define FRAC_SCALE (1ULL << FRAC_BITS)
1818
#define FRAC_MASK (FRAC_SCALE-1)
1919

2020
hpcFreq_t hpcFreq;
2121

2222
#ifdef _WIN32 // Windows usleep() implementation
2323

24+
#define STATUS_SUCCESS 0
25+
26+
static bool canAdjustTimerResolution;
27+
2428
static NTSTATUS (__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval);
2529
static NTSTATUS (__stdcall *NtQueryTimerResolution)(PULONG MinimumResolution, PULONG MaximumResolution, PULONG ActualResolution);
2630
static NTSTATUS (__stdcall *NtSetTimerResolution)(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution);
@@ -31,26 +35,27 @@ static void usleepGood(int32_t usec)
3135
{
3236
LARGE_INTEGER delayInterval;
3337

34-
// NtDelayExecution() delays in 100ns-units, and negative value = delay from current time
38+
// NtDelayExecution() delays in 100ns-units, and a negative value means to delay from current time
3539
usec *= -10;
3640

3741
delayInterval.HighPart = 0xFFFFFFFF; // negative 64-bit value, we only set the lower dword
3842
delayInterval.LowPart = usec;
3943
NtDelayExecution(false, &delayInterval);
4044
}
4145

42-
static void usleepWeak(int32_t usec) // fallback if no NtDelayExecution()
46+
static void usleepPoor(int32_t usec) // fallback if no NtDelayExecution()
4347
{
4448
Sleep((usec + 500) / 1000);
4549
}
4650

4751
static void windowsSetupUsleep(void)
4852
{
4953
NtDelayExecution = (NTSTATUS (__stdcall *)(BOOL, PLARGE_INTEGER))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");
54+
usleep = (NtDelayExecution != NULL) ? usleepGood : usleepPoor;
55+
5056
NtQueryTimerResolution = (NTSTATUS (__stdcall *)(PULONG, PULONG, PULONG))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryTimerResolution");
5157
NtSetTimerResolution = (NTSTATUS (__stdcall *)(ULONG, BOOLEAN, PULONG))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtSetTimerResolution");
52-
53-
usleep = (NtDelayExecution != NULL) ? usleepGood : usleepWeak;
58+
canAdjustTimerResolution = (NtQueryTimerResolution != NULL && NtSetTimerResolution != NULL);
5459
}
5560
#endif
5661

@@ -60,49 +65,67 @@ void hpc_Init(void)
6065
windowsSetupUsleep();
6166
#endif
6267
hpcFreq.freq64 = SDL_GetPerformanceFrequency();
63-
hpcFreq.dFreq = (double)hpcFreq.freq64;
64-
hpcFreq.dFreqMulMs = 1000.0 / hpcFreq.dFreq;
65-
hpcFreq.dFreqMulMicro = (1000.0 * 1000.0) / hpcFreq.dFreq;
68+
69+
double dFreq = (double)hpcFreq.freq64;
70+
71+
hpcFreq.dFreqMulMs = 1000.0 / dFreq;
72+
hpcFreq.dFreqMulMicro = (1000.0 * 1000.0) / dFreq;
6673
}
6774

68-
void hpc_SetDurationInHz(hpc_t *hpc, const double dHz)
75+
// returns 64-bit fractional part of u64 divided by u32
76+
static uint64_t getFrac64FromU64DivU32(uint64_t dividend, uint32_t divisor)
6977
{
70-
const double dDuration = hpcFreq.dFreq / dHz;
78+
if (dividend == 0 || divisor == 0 || divisor >= dividend)
79+
return 0;
80+
81+
dividend %= divisor;
82+
83+
if (dividend == 0)
84+
return 0;
7185

72-
// break down duration into integer and frac parts
73-
double dDurationInt;
74-
double dDurationFrac = modf(dDuration, &dDurationInt);
86+
const uint32_t quotient = (uint32_t)((dividend << 32) / divisor);
87+
const uint32_t remainder = (uint32_t)((dividend << 32) % divisor);
7588

76-
// set 64:53fp values
77-
hpc->duration64Int = (uint64_t)dDurationInt;
78-
hpc->duration64Frac = (uint64_t)round(dDurationFrac * FRAC_SCALE);
89+
const uint32_t resultHi = quotient;
90+
const uint32_t resultLo = (uint32_t)(((uint64_t)remainder << 32) / divisor);
91+
92+
return ((uint64_t)resultHi << 32) | resultLo;
7993
}
8094

81-
void hpc_ResetEndTime(hpc_t *hpc)
95+
void hpc_SetDurationInHz(hpc_t *hpc, uint32_t hz)
8296
{
83-
hpc->endTime64Int = SDL_GetPerformanceCounter() + hpc->duration64Int;
84-
hpc->endTime64Frac = hpc->duration64Frac;
97+
// set 64:63fp value
98+
hpc->durationInt = hpcFreq.freq64 / hz;
99+
hpc->durationFrac = getFrac64FromU64DivU32(hpcFreq.freq64, hz) >> 1;
100+
101+
hpc->resetFrame = hz * 3600; // reset counters every hour
102+
85103
}
86104

87-
void hpc_Wait(hpc_t *hpc)
105+
void hpc_ResetCounters(hpc_t *hpc)
88106
{
89-
#ifdef _WIN32 // set resolution to 0.5ms (safest minium) - this is confirmed to improve NtDelayExecution() and Sleep()
90-
ULONG originalTimerResolution, minRes, maxRes, curRes;
107+
hpc->endTimeInt = SDL_GetPerformanceCounter() + hpc->durationInt;
108+
hpc->endTimeFrac = hpc->durationFrac;
109+
}
91110

92-
if (NtQueryTimerResolution != NULL && NtSetTimerResolution != NULL)
111+
void hpc_Wait(hpc_t *hpc)
112+
{
113+
#ifdef _WIN32
114+
/* Make sure resolution is set to 0.5ms (safest minimum) - this is confirmed to improve
115+
** NtDelayExecution() and Sleep(). This will only be changed when needed, not per frame.
116+
*/
117+
ULONG curRes, minRes, maxRes, junk;
118+
if (canAdjustTimerResolution && NtQueryTimerResolution(&minRes, &maxRes, &curRes) == STATUS_SUCCESS)
93119
{
94-
if (!NtQueryTimerResolution(&minRes, &maxRes, &originalTimerResolution))
95-
{
96-
if (originalTimerResolution != 5000 && maxRes <= 5000)
97-
NtSetTimerResolution(5000, TRUE, &curRes); // set to 0.5ms (safest minimum)
98-
}
120+
if (curRes != 5000 && maxRes <= 5000)
121+
NtSetTimerResolution(5000, TRUE, &junk); // 0.5ms
99122
}
100123
#endif
101124

102125
const uint64_t currTime64 = SDL_GetPerformanceCounter();
103-
if (currTime64 < hpc->endTime64Int)
126+
if (currTime64 < hpc->endTimeInt)
104127
{
105-
uint64_t timeLeft64 = hpc->endTime64Int - currTime64;
128+
uint64_t timeLeft64 = hpc->endTimeInt - currTime64;
106129

107130
// convert to int32_t for fast SSE2 SIMD usage lateron
108131
if (timeLeft64 > INT32_MAX)
@@ -117,12 +140,25 @@ void hpc_Wait(hpc_t *hpc)
117140

118141
// set next end time
119142

120-
hpc->endTime64Int += hpc->duration64Int;
143+
hpc->endTimeInt += hpc->durationInt;
144+
145+
// handle fractional part
146+
hpc->endTimeFrac += hpc->durationFrac;
147+
if (hpc->endTimeFrac >= FRAC_SCALE)
148+
{
149+
hpc->endTimeFrac &= FRAC_MASK;
150+
hpc->endTimeInt++;
151+
}
121152

122-
hpc->endTime64Frac += hpc->duration64Frac;
123-
if (hpc->endTime64Frac >= FRAC_SCALE)
153+
/* The counter ("endTimeInt") can accumulate major errors after a couple of hours,
154+
** since each frame is not happening at perfect intervals.
155+
** To fix this, reset the counter's int & frac once every hour. We should only get
156+
** up to one frame of stutter while they are resetting, then it's back to normal.
157+
*/
158+
hpc->frameCounter++;
159+
if (hpc->frameCounter >= hpc->resetFrame)
124160
{
125-
hpc->endTime64Frac &= FRAC_MASK;
126-
hpc->endTime64Int++;
161+
hpc->frameCounter = 0;
162+
hpc_ResetCounters(hpc);
127163
}
128164
}

src/ft2_hpc.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@
66
typedef struct
77
{
88
uint64_t freq64;
9-
double dFreq, dFreqMulMicro, dFreqMulMs;
9+
double dFreqMulMicro, dFreqMulMs;
1010
} hpcFreq_t;
1111

1212
typedef struct
1313
{
14-
uint64_t duration64Int, duration64Frac;
15-
uint64_t endTime64Int, endTime64Frac;
14+
uint64_t durationInt, durationFrac;
15+
uint64_t endTimeInt, endTimeFrac;
16+
uint64_t frameCounter, resetFrame;
1617
} hpc_t;
1718

1819
extern hpcFreq_t hpcFreq;
1920

2021
void hpc_Init(void);
21-
void hpc_SetDurationInHz(hpc_t *hpc, double dHz);
22-
void hpc_ResetEndTime(hpc_t *hpc);
22+
void hpc_SetDurationInHz(hpc_t *hpc, uint32_t dHz);
23+
void hpc_ResetCounters(hpc_t *hpc);
2324
void hpc_Wait(hpc_t *hpc);

src/ft2_main.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,11 @@ int main(int argc, char *argv[])
233233
SDL_DetachThread(initMidiThread); // don't wait for this thread, let it clean up when done
234234
#endif
235235

236-
hpc_ResetEndTime(&video.vblankHpc); // this is needed for potential okBox() calls in handleModuleLoadFromArg()
236+
hpc_ResetCounters(&video.vblankHpc); // quirk: this is needed for potential okBox() calls in handleModuleLoadFromArg()
237237
handleModuleLoadFromArg(argc, argv);
238238

239239
editor.mainLoopOngoing = true;
240-
hpc_ResetEndTime(&video.vblankHpc); // this must be the very last thing done before entering the main loop
240+
hpc_ResetCounters(&video.vblankHpc); // this must be the last thing we do before entering the main loop
241241

242242
while (editor.programRunning)
243243
{

src/ft2_video.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ void endFPSCounter(void)
187187
if (frameTimeDiff64 > INT32_MAX)
188188
frameTimeDiff64 = INT32_MAX;
189189

190-
dRunningFrameDuration += (int32_t)frameTimeDiff64 / (hpcFreq.dFreq / 1000.0);
190+
dRunningFrameDuration += (int32_t)frameTimeDiff64 * hpcFreq.dFreqMulMs;
191191
}
192192
}
193193

src/scopes/ft2_scopes.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ static int32_t SDLCALL scopeThreadFunc(void *ptr)
521521
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
522522

523523
hpc_SetDurationInHz(&scopeHpc, SCOPE_HZ);
524-
hpc_ResetEndTime(&scopeHpc);
524+
hpc_ResetCounters(&scopeHpc);
525525

526526
while (editor.programRunning)
527527
{

0 commit comments

Comments
 (0)