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
2020hpcFreq_t hpcFreq ;
2121
2222#ifdef _WIN32 // Windows usleep() implementation
2323
24+ #define STATUS_SUCCESS 0
25+
26+ static bool canAdjustTimerResolution ;
27+
2428static NTSTATUS (__stdcall * NtDelayExecution )(BOOL Alertable , PLARGE_INTEGER DelayInterval );
2529static NTSTATUS (__stdcall * NtQueryTimerResolution )(PULONG MinimumResolution , PULONG MaximumResolution , PULONG ActualResolution );
2630static 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
4751static 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}
0 commit comments