From aff376e066359afbb78fca1c53355104281d9712 Mon Sep 17 00:00:00 2001 From: nanahi <130121847+na-na-hi@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:13:12 -0400 Subject: [PATCH] win32: increase hires timer resolution timeBeginPeriod() only allows setting minimum timer resolution to 1 ms. However, modern x86 platforms support a minimum timer resolution of 0.5 ms. Use NtSetTimerResolution() instead for the increased resolution, which can be set with MPV_HRT_RES. Additionally, change the units of mp_start_hires_timers(), mp_end_hires_timer(), MPV_HRT_RES, and MPV_HRT_MAX to nanoseconds, in accordance with other functions used in timer.h. --- meson.build | 1 + osdep/threads-win32.h | 2 +- osdep/timer-win32.c | 51 ++++++++++++++++++++++++++++--------------- osdep/timer.h | 6 ++--- test/meson.build | 1 + 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/meson.build b/meson.build index 6ccc8bbfb8..d31ec88eb0 100644 --- a/meson.build +++ b/meson.build @@ -497,6 +497,7 @@ if features['win32-desktop'] cc.find_library('dwmapi'), cc.find_library('gdi32'), cc.find_library('imm32'), + cc.find_library('ntdll'), cc.find_library('ole32'), cc.find_library('uuid'), cc.find_library('uxtheme'), diff --git a/osdep/threads-win32.h b/osdep/threads-win32.h index f1c654a5cb..ec90fe9387 100644 --- a/osdep/threads-win32.h +++ b/osdep/threads-win32.h @@ -115,7 +115,7 @@ static inline int mp_cond_timedwait(mp_cond *cond, mp_mutex *mutex, int64_t time timeout = MPCLAMP(timeout, 0, MP_TIME_MS_TO_NS(INFINITE)) / MP_TIME_MS_TO_NS(1); int ret = 0; - int hrt = mp_start_hires_timers(timeout); + int64_t hrt = mp_start_hires_timers(MP_TIME_MS_TO_NS(timeout)); BOOL bRet; if (mutex->use_cs) { diff --git a/osdep/timer-win32.c b/osdep/timer-win32.c index 7867b5a525..a2815cae0f 100644 --- a/osdep/timer-win32.c +++ b/osdep/timer-win32.c @@ -16,6 +16,8 @@ */ #include +#include +#include #include #include #include @@ -27,16 +29,23 @@ static LARGE_INTEGER perf_freq; -// ms values -static int hires_max = 50; -static int hires_res = 1; +static int64_t hires_max = MP_TIME_MS_TO_NS(50); +static int64_t hires_res = MP_TIME_MS_TO_NS(1); -int mp_start_hires_timers(int wait_ms) +// NtSetTimerResolution allows setting the timer resolution to less than 1 ms. +// Resolutions are specified in 100-ns units. +// If Set is TRUE, set the RequestedResolution. Otherwise, return to the previous resolution. +NTSTATUS NTAPI NtSetTimerResolution(ULONG RequestedResolution, BOOLEAN Set, PULONG ActualResolution); +// Acquire the valid timer resolution range. +NTSTATUS NTAPI NtQueryTimerResolution(PULONG MinimumResolution, PULONG MaximumResolution, PULONG ActualResolution); + +int64_t mp_start_hires_timers(int64_t wait_ns) { #if !HAVE_UWP - // policy: request hires_res ms resolution if wait < hires_max ms - if (wait_ms > 0 && wait_ms <= hires_max && - timeBeginPeriod(hires_res) == TIMERR_NOERROR) + ULONG actual_res = 0; + // policy: request hires_res resolution if wait < hires_max ns + if (wait_ns > 0 && wait_ns <= hires_max && + NtSetTimerResolution(hires_res / 100, TRUE, &actual_res) == STATUS_SUCCESS) { return hires_res; } @@ -44,11 +53,12 @@ int mp_start_hires_timers(int wait_ms) return 0; } -void mp_end_hires_timers(int res_ms) +void mp_end_hires_timers(int64_t res_ns) { #if !HAVE_UWP - if (res_ms > 0) - timeEndPeriod(res_ms); + ULONG actual_res = 0; + if (res_ns > 0) + NtSetTimerResolution(res_ns / 100, FALSE, &actual_res); #endif } @@ -57,7 +67,7 @@ void mp_sleep_ns(int64_t ns) if (ns < 0) return; - int hrt = mp_start_hires_timers(ns < 1e6 ? 1 : ns / 1e6); + int64_t hrt = mp_start_hires_timers(ns); #ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION #define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x2 @@ -107,21 +117,28 @@ void mp_raw_time_init(void) QueryPerformanceFrequency(&perf_freq); #if !HAVE_UWP + ULONG min_res, max_res, actual_res; + if (NtQueryTimerResolution(&min_res, &max_res, &actual_res) != STATUS_SUCCESS) { + min_res = 156250; + max_res = 10000; + } + // allow (undocumented) control of all the High Res Timers parameters, // for easier experimentation and diagnostic of bug reports. const char *v; + char *end; // 1..1000 ms max timetout for hires (used in "perwait" mode) if ((v = getenv("MPV_HRT_MAX"))) { - int hmax = atoi(v); - if (hmax >= 1 && hmax <= 1000) + int64_t hmax = strtoll(v, &end, 10); + if (*end == '\0' && hmax >= MP_TIME_MS_TO_NS(1) && hmax <= MP_TIME_MS_TO_NS(1000)) hires_max = hmax; } - // 1..15 ms hires resolution (not used in "never" mode) + // hires resolution clamped by the available resolution range (not used in "never" mode) if ((v = getenv("MPV_HRT_RES"))) { - int res = atoi(v); - if (res >= 1 && res <= 15) + int64_t res = strtoll(v, &end, 10); + if (*end == '\0' && res >= max_res * INT64_C(100) && res <= min_res * INT64_C(100)) hires_res = res; } @@ -134,8 +151,8 @@ void mp_raw_time_init(void) } else if (!strcmp(v, "never")) { hires_max = 0; } else { // "always" or unknown value + mp_start_hires_timers(hires_res); hires_max = 0; - timeBeginPeriod(hires_res); } #endif } diff --git a/osdep/timer.h b/osdep/timer.h index 3eab817626..d8ec185bfe 100644 --- a/osdep/timer.h +++ b/osdep/timer.h @@ -39,11 +39,11 @@ uint64_t mp_raw_time_ns(void); void mp_sleep_ns(int64_t ns); #ifdef _WIN32 -// returns: timer resolution in ms if needed and started successfully, else 0 -int mp_start_hires_timers(int wait_ms); +// returns: timer resolution in ns if needed and started successfully, else 0 +int64_t mp_start_hires_timers(int64_t wait_ns); // call unconditionally with the return value of mp_start_hires_timers -void mp_end_hires_timers(int resolution_ms); +void mp_end_hires_timers(int64_t resolution_ns); #endif /* _WIN32 */ // Converts time units to nanoseconds (int64_t) diff --git a/test/meson.build b/test/meson.build index b45f672c7a..7be1db4777 100644 --- a/test/meson.build +++ b/test/meson.build @@ -52,6 +52,7 @@ endif if features['win32-desktop'] test_utils_deps += cc.find_library('imm32') + test_utils_deps += cc.find_library('ntdll') test_utils_deps += cc.find_library('winmm') endif test_utils_objects = libmpv.extract_objects(test_utils_files)