windows: Use `DwmFlush()` to trigger vsync event (#11731)

张小白 created

Currently , on Windows 10, we used a `Timer` to trigger the vsync event,
but the `Timer`'s time precision is only about 15ms, which means a
maximum of 60FPS. This PR introduces a new function to allow for higher
frame rates on Windows 10.

And after reading the codes, I found that zed triggers a draw after
handling mouse or keyboard events, so we don't need to call draw again
when we handle `WM_*` messages. Therefore, I removed the
`invalidate_client_area` function.

Release Notes:

- N/A

Change summary

Cargo.toml                                   |  2 
crates/gpui/src/platform/windows/events.rs   | 43 ++---------
crates/gpui/src/platform/windows/platform.rs | 78 +--------------------
3 files changed, 16 insertions(+), 107 deletions(-)

Detailed changes

Cargo.toml 🔗

@@ -402,11 +402,11 @@ features = [
     "Win32_Graphics_Direct2D",
     "Win32_Graphics_Direct2D_Common",
     "Win32_Graphics_DirectWrite",
+    "Win32_Graphics_Dwm",
     "Win32_Graphics_Dxgi_Common",
     "Win32_Graphics_Gdi",
     "Win32_Graphics_Imaging",
     "Win32_Graphics_Imaging_D2D",
-    "Win32_Media",
     "Win32_Security",
     "Win32_Security_Credentials",
     "Win32_Storage_FileSystem",

crates/gpui/src/platform/windows/events.rs 🔗

@@ -72,11 +72,11 @@ pub(crate) fn handle_msg(
         WM_XBUTTONUP => handle_xbutton_msg(wparam, lparam, handle_mouse_up_msg, state_ptr),
         WM_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr),
         WM_MOUSEHWHEEL => handle_mouse_horizontal_wheel_msg(handle, wparam, lparam, state_ptr),
-        WM_SYSKEYDOWN => handle_syskeydown_msg(handle, wparam, lparam, state_ptr),
-        WM_SYSKEYUP => handle_syskeyup_msg(handle, wparam, state_ptr),
-        WM_KEYDOWN => handle_keydown_msg(handle, wparam, lparam, state_ptr),
-        WM_KEYUP => handle_keyup_msg(handle, wparam, state_ptr),
-        WM_CHAR => handle_char_msg(handle, wparam, lparam, state_ptr),
+        WM_SYSKEYDOWN => handle_syskeydown_msg(wparam, lparam, state_ptr),
+        WM_SYSKEYUP => handle_syskeyup_msg(wparam, state_ptr),
+        WM_KEYDOWN => handle_keydown_msg(wparam, lparam, state_ptr),
+        WM_KEYUP => handle_keyup_msg(wparam, state_ptr),
+        WM_CHAR => handle_char_msg(wparam, lparam, state_ptr),
         WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
         WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
         WM_SETCURSOR => handle_set_cursor(lparam, state_ptr),
@@ -179,15 +179,13 @@ fn handle_timer_msg(
 }
 
 fn handle_paint_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
-    let mut paint_struct = PAINTSTRUCT::default();
-    let _hdc = unsafe { BeginPaint(handle, &mut paint_struct) };
     let mut lock = state_ptr.state.borrow_mut();
     if let Some(mut request_frame) = lock.callbacks.request_frame.take() {
         drop(lock);
         request_frame();
         state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame);
     }
-    unsafe { EndPaint(handle, &paint_struct).ok().log_err() };
+    unsafe { ValidateRect(handle, None).ok().log_err() };
     Some(0)
 }
 
@@ -261,7 +259,6 @@ fn handle_mouse_move_msg(
 }
 
 fn handle_syskeydown_msg(
-    handle: HWND,
     wparam: WPARAM,
     lparam: LPARAM,
     state_ptr: Rc<WindowsWindowStatePtr>,
@@ -281,7 +278,6 @@ fn handle_syskeydown_msg(
         is_held: lparam.0 & (0x1 << 30) > 0,
     };
     let result = if func(PlatformInput::KeyDown(event)).default_prevented {
-        invalidate_client_area(handle);
         Some(0)
     } else {
         None
@@ -291,11 +287,7 @@ fn handle_syskeydown_msg(
     result
 }
 
-fn handle_syskeyup_msg(
-    handle: HWND,
-    wparam: WPARAM,
-    state_ptr: Rc<WindowsWindowStatePtr>,
-) -> Option<isize> {
+fn handle_syskeyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
     // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
     // shortcuts.
     let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
@@ -308,7 +300,6 @@ fn handle_syskeyup_msg(
     drop(lock);
     let event = KeyUpEvent { keystroke };
     let result = if func(PlatformInput::KeyUp(event)).default_prevented {
-        invalidate_client_area(handle);
         Some(0)
     } else {
         Some(1)
@@ -319,7 +310,6 @@ fn handle_syskeyup_msg(
 }
 
 fn handle_keydown_msg(
-    handle: HWND,
     wparam: WPARAM,
     lparam: LPARAM,
     state_ptr: Rc<WindowsWindowStatePtr>,
@@ -337,7 +327,6 @@ fn handle_keydown_msg(
         is_held: lparam.0 & (0x1 << 30) > 0,
     };
     let result = if func(PlatformInput::KeyDown(event)).default_prevented {
-        invalidate_client_area(handle);
         Some(0)
     } else {
         Some(1)
@@ -347,11 +336,7 @@ fn handle_keydown_msg(
     result
 }
 
-fn handle_keyup_msg(
-    handle: HWND,
-    wparam: WPARAM,
-    state_ptr: Rc<WindowsWindowStatePtr>,
-) -> Option<isize> {
+fn handle_keyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
     let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else {
         return Some(1);
     };
@@ -362,7 +347,6 @@ fn handle_keyup_msg(
     drop(lock);
     let event = KeyUpEvent { keystroke };
     let result = if func(PlatformInput::KeyUp(event)).default_prevented {
-        invalidate_client_area(handle);
         Some(0)
     } else {
         Some(1)
@@ -373,7 +357,6 @@ fn handle_keyup_msg(
 }
 
 fn handle_char_msg(
-    handle: HWND,
     wparam: WPARAM,
     lparam: LPARAM,
     state_ptr: Rc<WindowsWindowStatePtr>,
@@ -396,7 +379,6 @@ fn handle_char_msg(
     let mut lock = state_ptr.state.borrow_mut();
     lock.callbacks.input = Some(func);
     if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
-        invalidate_client_area(handle);
         return Some(0);
     }
     let Some(ime_char) = ime_key else {
@@ -407,7 +389,6 @@ fn handle_char_msg(
     };
     drop(lock);
     input_handler.replace_text_in_range(None, &ime_char);
-    invalidate_client_area(handle);
     state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 
     Some(0)
@@ -648,7 +629,6 @@ fn handle_ime_composition(
         drop(lock);
         input_handler.replace_text_in_range(None, &comp_result);
         state_ptr.state.borrow_mut().input_handler = Some(input_handler);
-        invalidate_client_area(handle);
         return Some(0);
     }
     // currently, we don't care other stuff
@@ -771,7 +751,6 @@ fn handle_dpi_changed_msg(
         .context("unable to set window position after dpi has changed")
         .log_err();
     }
-    invalidate_client_area(handle);
 
     Some(0)
 }
@@ -1161,12 +1140,6 @@ fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
     }
 }
 
-/// mark window client rect to be re-drawn
-/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
-pub(crate) fn invalidate_client_area(handle: HWND) {
-    unsafe { InvalidateRect(handle, None, FALSE).ok().log_err() };
-}
-
 fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> {
     unsafe {
         let ctx = ImmGetContext(handle);

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -4,11 +4,10 @@
 use std::{
     cell::{Cell, RefCell},
     ffi::{c_void, OsString},
-    mem::transmute,
     os::windows::ffi::{OsStrExt, OsStringExt},
     path::{Path, PathBuf},
     rc::Rc,
-    sync::{Arc, OnceLock},
+    sync::Arc,
 };
 
 use ::util::ResultExt;
@@ -26,7 +25,6 @@ use windows::{
     Win32::{
         Foundation::*,
         Graphics::Gdi::*,
-        Media::*,
         Security::Credentials::*,
         Storage::FileSystem::*,
         System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*},
@@ -164,9 +162,7 @@ impl Platform for WindowsPlatform {
     fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
         on_finish_launching();
         let vsync_event = create_event().unwrap();
-        let timer_stop_event = create_event().unwrap();
-        let raw_timer_stop_event = timer_stop_event.to_raw();
-        begin_vsync_timer(vsync_event.to_raw(), timer_stop_event);
+        begin_vsync(vsync_event.to_raw());
         'a: loop {
             let wait_result = unsafe {
                 MsgWaitForMultipleObjects(
@@ -210,7 +206,6 @@ impl Platform for WindowsPlatform {
                 }
             }
         }
-        end_vsync_timer(raw_timer_stop_event);
 
         if let Some(ref mut callback) = self.state.borrow_mut().callbacks.quit {
             callback();
@@ -761,74 +756,15 @@ unsafe fn show_savefile_dialog(directory: PathBuf) -> Result<IFileSaveDialog> {
     Ok(dialog)
 }
 
-fn begin_vsync_timer(vsync_event: HANDLE, timer_stop_event: OwnedHandle) {
-    let vsync_fn = select_vsync_fn();
-    std::thread::spawn(move || loop {
-        if vsync_fn(timer_stop_event.to_raw()) {
-            if unsafe { SetEvent(vsync_event) }.log_err().is_none() {
-                break;
-            }
+fn begin_vsync(vsync_evnet: HANDLE) {
+    std::thread::spawn(move || unsafe {
+        loop {
+            windows::Win32::Graphics::Dwm::DwmFlush().log_err();
+            SetEvent(vsync_evnet).log_err();
         }
     });
 }
 
-fn end_vsync_timer(timer_stop_event: HANDLE) {
-    unsafe { SetEvent(timer_stop_event) }.log_err();
-}
-
-fn select_vsync_fn() -> Box<dyn Fn(HANDLE) -> bool + Send> {
-    if let Some(dcomp_fn) = load_dcomp_vsync_fn() {
-        log::info!("use DCompositionWaitForCompositorClock for vsync");
-        return Box::new(move |timer_stop_event| {
-            // will be 0 if woken up by timer_stop_event or 1 if the compositor clock ticked
-            // SEE: https://learn.microsoft.com/en-us/windows/win32/directcomp/compositor-clock/compositor-clock
-            (unsafe { dcomp_fn(1, &timer_stop_event, INFINITE) }) == 1
-        });
-    }
-    log::info!("use fallback vsync function");
-    Box::new(fallback_vsync_fn())
-}
-
-fn load_dcomp_vsync_fn() -> Option<unsafe extern "system" fn(u32, *const HANDLE, u32) -> u32> {
-    static FN: OnceLock<Option<unsafe extern "system" fn(u32, *const HANDLE, u32) -> u32>> =
-        OnceLock::new();
-    *FN.get_or_init(|| {
-        let hmodule = unsafe { LoadLibraryW(windows::core::w!("dcomp.dll")) }.ok()?;
-        let address = unsafe {
-            GetProcAddress(
-                hmodule,
-                windows::core::s!("DCompositionWaitForCompositorClock"),
-            )
-        }?;
-        Some(unsafe { transmute(address) })
-    })
-}
-
-fn fallback_vsync_fn() -> impl Fn(HANDLE) -> bool + Send {
-    let freq = WindowsDisplay::primary_monitor()
-        .and_then(|monitor| monitor.frequency())
-        .unwrap_or(60);
-    log::info!("primaly refresh rate is {freq}Hz");
-
-    let interval = (1000 / freq).max(1);
-    log::info!("expected interval is {interval}ms");
-
-    unsafe { timeBeginPeriod(1) };
-
-    struct TimePeriod;
-    impl Drop for TimePeriod {
-        fn drop(&mut self) {
-            unsafe { timeEndPeriod(1) };
-        }
-    }
-    let period = TimePeriod;
-
-    move |timer_stop_event| {
-        let _ = (&period,);
-        (unsafe { WaitForSingleObject(timer_stop_event, interval) }) == WAIT_TIMEOUT
-    }
-}
-
 fn load_icon() -> Result<HICON> {
     let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? };
     let handle = unsafe {