summaryrefslogtreecommitdiff
path: root/win32_timer.c
diff options
context:
space:
mode:
authorPeter Fors <peter.fors@mindkiller.com>2025-10-09 22:07:52 +0200
committerPeter Fors <peter.fors@mindkiller.com>2025-10-09 22:07:52 +0200
commit030724a9aea346e4a9843d5842fb28c6d6c4cf1a (patch)
treef06fb84aaef64b2f4e2d81b3d2d3eef71bad83ec /win32_timer.c
parent412b2ef851516c1de8ba5006ddd284192cbcaf9b (diff)
Rearrangement and refactoring and optimizations and more accuracy
Diffstat (limited to 'win32_timer.c')
-rw-r--r--win32_timer.c171
1 files changed, 171 insertions, 0 deletions
diff --git a/win32_timer.c b/win32_timer.c
new file mode 100644
index 0000000..c89000f
--- /dev/null
+++ b/win32_timer.c
@@ -0,0 +1,171 @@
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <mmsystem.h>
+#include <avrt.h>
+#include <winternl.h>
+
+#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);
+}