#define WIN32_LEAN_AND_MEAN #include #include #include #include #define SPIN_THRESHOLD_NS 1000000 // NOTE(peter): 2000µs spin threshold for Windows struct timer_handle { uint64_t interval_ns; uint64_t qpc_frequency; uint64_t next_deadline; uint32_t running; HANDLE event; HANDLE timer_thread; HANDLE mmcss_handle; #ifdef TIMER_DEBUG uint64_t last_wait_start_ns; uint32_t overshoot_log[1000000]; uint32_t overshoot_index; #endif }; typedef LONG NTSTATUS; typedef NTSTATUS (__stdcall *NtDelayExecution_t)(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval); static NtDelayExecution_t pNtDelayExecution; #define NS_TO_100NS_UNITS 100 static inline uint64_t qpc_now_ns(uint64_t freq) { LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); uint64_t q = (uint64_t)qpc.QuadPart; uint64_t s = q / freq; uint64_t r = q % freq; return s * 1000000000ULL + (r * 1000000000ULL + (freq >> 1)) / freq; } static void try_set_nt_timer_resolution(void) { typedef LONG NTSTATUS; typedef NTSTATUS (__stdcall *NtSetTimerResolution_t)(ULONG, BOOLEAN, ULONG *); NtSetTimerResolution_t NtSetTimerResolution = (NtSetTimerResolution_t)(uintptr_t)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtSetTimerResolution"); if(NtSetTimerResolution) { ULONG requested = 5000; /* 0.5ms = 5000 * 100ns */ ULONG actual = 0; NtSetTimerResolution(requested, TRUE, &actual); } } static void set_realtime_priority(HANDLE *mmcss_handle) { DWORD task_index = 0; *mmcss_handle = AvSetMmThreadCharacteristicsW(L"Pro Audio", &task_index); if(!*mmcss_handle) { SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); } } static void timer_sleep(uint64_t ns_to_delay) { LONGLONG delay_100ns = -(LONGLONG)(ns_to_delay / NS_TO_100NS_UNITS); if(delay_100ns < 0) { LARGE_INTEGER delay; delay.QuadPart = delay_100ns; pNtDelayExecution(FALSE, &delay); } } static DWORD WINAPI timer_thread_func(LPVOID arg) { struct timer_handle *t = (struct timer_handle *)arg; SetThreadAffinityMask(GetCurrentThread(), 1); set_realtime_priority(&t->mmcss_handle); while(t->running) { uint64_t now = qpc_now_ns(t->qpc_frequency); if(now < t->next_deadline) { uint64_t diff = t->next_deadline - now; if(diff > SPIN_THRESHOLD_NS) { timer_sleep(diff - SPIN_THRESHOLD_NS); } while(qpc_now_ns(t->qpc_frequency) < t->next_deadline) { _mm_pause(); } } now = qpc_now_ns(t->qpc_frequency); SetEvent(t->event); #ifdef TIMER_DEBUG if(t->last_wait_start_ns > 0) { uint64_t overshoot_ns = (now > t->next_deadline) ? (now - t->next_deadline) : 0; t->overshoot_log[t->overshoot_index % 1000000] = (uint32_t)overshoot_ns; t->overshoot_index++; } t->last_wait_start_ns = now; #endif t->next_deadline += t->interval_ns; } return 0; } static void timer_init(void) { if (pNtDelayExecution == NULL) { pNtDelayExecution = (NtDelayExecution_t)(uintptr_t)GetProcAddress( GetModuleHandleW(L"ntdll.dll"), "NtDelayExecution" ); } timeBeginPeriod(1); try_set_nt_timer_resolution(); } static void timer_shutdown(void) { timeEndPeriod(1); } static struct timer_handle *timer_new(uint64_t interval_ns) { struct timer_handle *t = calloc(1, sizeof(struct timer_handle)); LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); t->qpc_frequency = freq.QuadPart; t->interval_ns = interval_ns; t->next_deadline = qpc_now_ns(t->qpc_frequency) + interval_ns; t->running = 1; t->mmcss_handle = 0; #ifdef TIMER_DEBUG t->last_wait_start_ns = 0; t->overshoot_index = 0; #endif t->event = CreateEvent(0, FALSE, FALSE, 0); t->timer_thread = CreateThread(0, 0, timer_thread_func, t, 0, 0); return t; } static uint32_t timer_wait(struct timer_handle *t) { WaitForSingleObject(t->event, INFINITE); return 1; } static void timer_destroy(struct timer_handle *t) { t->running = 0; WaitForSingleObject(t->timer_thread, INFINITE); CloseHandle(t->timer_thread); CloseHandle(t->event); if(t->mmcss_handle) { AvRevertMmThreadCharacteristics(t->mmcss_handle); } #ifdef TIMER_DEBUG uint32_t threshold = 10000; // 10µs uint32_t overshoot_count = 0; for(uint32_t i = 0; i < t->overshoot_index && i < 1000000; i++) { if(t->overshoot_log[i] >= threshold) { DEBUG_PRINT("Frame %u: overshoot %u ns\n", i, t->overshoot_log[i]); overshoot_count++; } } DEBUG_PRINT("Total frames: %u, Overshoots >= %u ns: %u\n", t->overshoot_index, threshold, overshoot_count); #endif free(t); }