From cfbe8462ccd7d442f1dd7aa4807adf8b3e75e8e3 Mon Sep 17 00:00:00 2001 From: Markus Siglreithmaier Date: Tue, 2 Nov 2021 21:51:39 +0100 Subject: [PATCH] Windows: Increase wait timer resolution (#2007) Windows: Increase wait timer resolution for more accurate timing when using `WaitUntil`. --- CHANGELOG.md | 1 + Cargo.toml | 2 ++ src/platform_impl/windows/event_loop.rs | 36 +++++++++++++++++++++---- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5571d31b..59920a2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - On X11, if RANDR based scale factor is higher than 20 reset it to 1 - On Wayland, add an enabled-by-default feature called `wayland-dlopen` so users can opt out of using `dlopen` to load system libraries. - **Breaking:** On Android, bump `ndk` and `ndk-glue` to 0.4. +- On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`. # 0.25.0 (2021-05-15) diff --git a/Cargo.toml b/Cargo.toml index 557d3c39..310253f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,8 @@ features = [ "wingdi", "winnt", "winuser", + "mmsystem", + "timeapi" ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 8c9fc63b..0275f3fc 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -25,7 +25,7 @@ use winapi::{ windowsx, winerror, }, um::{ - libloaderapi, ole2, processthreadsapi, winbase, + libloaderapi, mmsystem, ole2, processthreadsapi, timeapi, winbase, winnt::{HANDLE, LONG, LPCSTR, SHORT}, winuser, }, @@ -324,6 +324,22 @@ fn get_wait_thread_id() -> DWORD { } } +lazy_static! { + static ref WAIT_PERIOD_MIN: Option = unsafe { + let mut caps = mmsystem::TIMECAPS { + wPeriodMin: 0, + wPeriodMax: 0, + }; + if timeapi::timeGetDevCaps(&mut caps, mem::size_of::() as _) + == mmsystem::TIMERR_NOERROR + { + Some(caps.wPeriodMin) + } else { + None + } + }; +} + fn wait_thread(parent_thread_id: DWORD, msg_window_id: HWND) { unsafe { let mut msg: winuser::MSG; @@ -366,16 +382,26 @@ fn wait_thread(parent_thread_id: DWORD, msg_window_id: HWND) { if let Some(wait_until) = wait_until_opt { let now = Instant::now(); if now < wait_until { - // MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract - // 1 millisecond from the requested time and spinlock for the remainder to - // compensate for that. + // Windows' scheduler has a default accuracy of several ms. This isn't good enough for + // `WaitUntil`, so we request the Windows scheduler to use a higher accuracy if possible. + // If we couldn't query the timer capabilities, then we use the default resolution. + if let Some(period) = *WAIT_PERIOD_MIN { + timeapi::timeBeginPeriod(period); + } + // `MsgWaitForMultipleObjects` is bound by the granularity of the scheduler period. + // Because of this, we try to reduce the requested time just enough to undershoot `wait_until` + // by the smallest amount possible, and then we busy loop for the remaining time inside the + // NewEvents message handler. let resume_reason = winuser::MsgWaitForMultipleObjectsEx( 0, ptr::null(), - dur2timeout(wait_until - now).saturating_sub(1), + dur2timeout(wait_until - now).saturating_sub(WAIT_PERIOD_MIN.unwrap_or(1)), winuser::QS_ALLEVENTS, winuser::MWMO_INPUTAVAILABLE, ); + if let Some(period) = *WAIT_PERIOD_MIN { + timeapi::timeEndPeriod(period); + } if resume_reason == winerror::WAIT_TIMEOUT { winuser::PostMessageW(msg_window_id, *PROCESS_NEW_EVENTS_MSG_ID, 0, 0); wait_until_opt = None;