From a1e5f6bb7cc68f55fe3a0405a83bcdd63f2d8f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Thu, 16 May 2024 01:45:17 +0800 Subject: [PATCH] windows: Use `DwmFlush()` to trigger vsync event (#11731) 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 --- 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(-) diff --git a/Cargo.toml b/Cargo.toml index 314d92fc12..b4745c2ffb 100644 --- a/Cargo.toml +++ b/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", diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs index d456a4f4c3..e07800f5f2 100644 --- a/crates/gpui/src/platform/windows/events.rs +++ b/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) -> Option { - 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, @@ -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, -) -> Option { +fn handle_syskeyup_msg(wparam: WPARAM, state_ptr: Rc) -> Option { // 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, @@ -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, -) -> Option { +fn handle_keyup_msg(wparam: WPARAM, state_ptr: Rc) -> Option { 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, @@ -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 { } } -/// 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); diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index e441e68618..2430e94283 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/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) { 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 { 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 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 u32> { - static FN: OnceLock 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 { let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? }; let handle = unsafe {