Skip to content

Commit aadb24e

Browse files
committed
Fixing of frame delay/wait routines
1 parent 5f89c01 commit aadb24e

File tree

8 files changed

+91
-52
lines changed

8 files changed

+91
-52
lines changed

src/pt2_askbox.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ uint32_t askBox(uint32_t dialogType, const char *statusText)
321321

322322
// reset vblank end time if we minimize window
323323
if (event.window.event == SDL_WINDOWEVENT_MINIMIZED || event.window.event == SDL_WINDOWEVENT_FOCUS_LOST)
324-
hpc_ResetEndTime(&video.vblankHpc);
324+
hpc_ResetCounters(&video.vblankHpc);
325325
}
326326
else if (event.type == SDL_KEYDOWN)
327327
{

src/pt2_audio.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ static void generateTickLengthTable(bool vblankTimingFlag)
432432

433433
// BPM -> Hz -> tick length for performance counter (syncing visuals to audio)
434434
double dTimeInt;
435-
double dTimeFrac = modf(hpcFreq.dFreq / dHz, &dTimeInt);
435+
double dTimeFrac = modf((double)hpcFreq.freq64 / dHz, &dTimeInt);
436436
const int32_t timeInt = (int32_t)dTimeInt;
437437

438438
dTimeFrac = floor((dTimeFrac * (UINT32_MAX+1.0)) + 0.5); // fractional part (scaled to 0..2^32-1)

src/pt2_hpc.c

Lines changed: 76 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
// Hardware Performance Counter delay routines, by 8bitbubsy
1+
/*
2+
** Hardware Performance Counter delay routines
3+
*/
24

35
#ifdef _WIN32
46
#define WIN32_MEAN_AND_LEAN
@@ -11,15 +13,18 @@
1113
#include <stdbool.h>
1214
#include "pt2_hpc.h"
1315

14-
// more bits than this makes little sense (double -> uint64_t precision)
15-
#define FRAC_BITS 53
16+
#define FRAC_BITS 63
1617
#define FRAC_SCALE (1ULL << FRAC_BITS)
1718
#define FRAC_MASK (FRAC_SCALE-1)
1819

1920
hpcFreq_t hpcFreq;
2021

2122
#ifdef _WIN32 // Windows usleep() implementation
2223

24+
#define STATUS_SUCCESS 0
25+
26+
static bool canAdjustTimerResolution;
27+
2328
static NTSTATUS (__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval);
2429
static NTSTATUS (__stdcall *NtQueryTimerResolution)(PULONG MinimumResolution, PULONG MaximumResolution, PULONG ActualResolution);
2530
static NTSTATUS (__stdcall *NtSetTimerResolution)(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution);
@@ -30,26 +35,27 @@ static void usleepGood(int32_t usec)
3035
{
3136
LARGE_INTEGER delayInterval;
3237

33-
// 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
3439
usec *= -10;
3540

36-
delayInterval.HighPart = 0xFFFFFFFF;
41+
delayInterval.HighPart = 0xFFFFFFFF; // negative 64-bit value, we only set the lower dword
3742
delayInterval.LowPart = usec;
3843
NtDelayExecution(false, &delayInterval);
3944
}
4045

41-
static void usleepWeak(int32_t usec) // fallback if no NtDelayExecution()
46+
static void usleepPoor(int32_t usec) // fallback if no NtDelayExecution()
4247
{
4348
Sleep((usec + 500) / 1000);
4449
}
4550

4651
static void windowsSetupUsleep(void)
4752
{
4853
NtDelayExecution = (NTSTATUS (__stdcall *)(BOOL, PLARGE_INTEGER))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");
54+
usleep = (NtDelayExecution != NULL) ? usleepGood : usleepPoor;
55+
4956
NtQueryTimerResolution = (NTSTATUS (__stdcall *)(PULONG, PULONG, PULONG))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryTimerResolution");
5057
NtSetTimerResolution = (NTSTATUS (__stdcall *)(ULONG, BOOLEAN, PULONG))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtSetTimerResolution");
51-
52-
usleep = (NtDelayExecution != NULL) ? usleepGood : usleepWeak;
58+
canAdjustTimerResolution = (NtQueryTimerResolution != NULL && NtSetTimerResolution != NULL);
5359
}
5460
#endif
5561

@@ -59,50 +65,69 @@ void hpc_Init(void)
5965
windowsSetupUsleep();
6066
#endif
6167
hpcFreq.freq64 = SDL_GetPerformanceFrequency();
62-
hpcFreq.dFreq = (double)hpcFreq.freq64;
63-
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;
6473
}
6574

66-
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)
6777
{
68-
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;
6985

70-
// break down duration into integer and frac parts
71-
double dDurationInt;
72-
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);
7388

74-
// set 64:53fp values
75-
hpc->duration64Int = (uint64_t)dDurationInt;
76-
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;
7793
}
7894

79-
void hpc_ResetEndTime(hpc_t *hpc)
95+
void hpc_SetDurationInHz(hpc_t *hpc, uint32_t hz)
8096
{
81-
hpc->endTime64Int = SDL_GetPerformanceCounter() + hpc->duration64Int;
82-
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+
83103
}
84104

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

90-
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)
91119
{
92-
if (!NtQueryTimerResolution(&minRes, &maxRes, &originalTimerResolution))
93-
{
94-
if (originalTimerResolution != 5000 && maxRes <= 5000)
95-
NtSetTimerResolution(5000, TRUE, &curRes); // set to 0.5ms (safest minimum)
96-
}
120+
if (curRes != 5000 && maxRes <= 5000)
121+
NtSetTimerResolution(5000, TRUE, &junk); // 0.5ms
97122
}
98123
#endif
99124

100125
const uint64_t currTime64 = SDL_GetPerformanceCounter();
101-
if (currTime64 < hpc->endTime64Int)
126+
if (currTime64 < hpc->endTimeInt)
102127
{
103-
uint64_t timeLeft64 = hpc->endTime64Int - currTime64;
128+
uint64_t timeLeft64 = hpc->endTimeInt - currTime64;
104129

105-
// limit (and cast to) int32_t for fast SSE2 SIMD usage
130+
// convert to int32_t for fast SSE2 SIMD usage lateron
106131
if (timeLeft64 > INT32_MAX)
107132
timeLeft64 = INT32_MAX;
108133

@@ -115,12 +140,25 @@ void hpc_Wait(hpc_t *hpc)
115140

116141
// set next end time
117142

118-
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+
}
119152

120-
hpc->endTime64Frac += hpc->duration64Frac;
121-
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)
122160
{
123-
hpc->endTime64Frac &= FRAC_MASK;
124-
hpc->endTime64Int++;
161+
hpc->frameCounter = 0;
162+
hpc_ResetCounters(hpc);
125163
}
126164
}

src/pt2_hpc.h

Lines changed: 8 additions & 7 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;
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

19+
extern hpcFreq_t hpcFreq;
20+
1821
void hpc_Init(void);
19-
void hpc_SetDurationInHz(hpc_t *hpc, double dHz);
20-
void hpc_ResetEndTime(hpc_t *hpc);
22+
void hpc_SetDurationInHz(hpc_t *hpc, uint32_t dHz);
23+
void hpc_ResetCounters(hpc_t *hpc);
2124
void hpc_Wait(hpc_t *hpc);
22-
23-
extern hpcFreq_t hpcFreq;

src/pt2_main.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ int main(int argc, char *argv[])
336336
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
337337

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

341341
// XXX: if you change anything in the main loop, make sure it goes in the askBox()(pt2_askbox.c) loop too, if needed
342342
while (editor.programRunning)
@@ -377,7 +377,7 @@ static void handleInput(void)
377377

378378
// reset vblank end time if we minimize window
379379
if (event.window.event == SDL_WINDOWEVENT_MINIMIZED || event.window.event == SDL_WINDOWEVENT_FOCUS_LOST)
380-
hpc_ResetEndTime(&video.vblankHpc);
380+
hpc_ResetCounters(&video.vblankHpc);
381381
}
382382

383383
#ifdef _WIN32

src/pt2_sampling.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ void renderSamplingBox(void)
475475
{
476476
changeStatusText("PLEASE WAIT ...");
477477
flipFrame();
478-
hpc_ResetEndTime(&video.vblankHpc);
478+
hpc_ResetCounters(&video.vblankHpc);
479479

480480
editor.sampleZero = false;
481481
editor.blockMarkFlag = false;

src/pt2_scopes.c

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

288288
hpc_SetDurationInHz(&scopeHpc, SCOPE_HZ);
289-
hpc_ResetEndTime(&scopeHpc);
289+
hpc_ResetCounters(&scopeHpc);
290290

291291
while (editor.programRunning)
292292
{

src/pt2_visuals_sync.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ void calcAudioLatencyVars(int32_t audioBufferSize, int32_t audioFreq)
146146

147147
const double dAudioLatencySecs = audioBufferSize / (double)audioFreq;
148148

149-
dFrac = modf(dAudioLatencySecs * hpcFreq.dFreq, &dInt);
149+
dFrac = modf(dAudioLatencySecs * (double)hpcFreq.freq64, &dInt);
150150

151151
// integer part
152152
audLatencyPerfValInt = (uint32_t)dInt;

0 commit comments

Comments
 (0)