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
This commit is contained in:
张小白 2024-05-16 01:45:17 +08:00 committed by GitHub
parent 4ae3396253
commit a1e5f6bb7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 16 additions and 107 deletions

View File

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

View File

@ -72,11 +72,11 @@ pub(crate) fn handle_msg(
WM_XBUTTONUP => handle_xbutton_msg(wparam, lparam, handle_mouse_up_msg, state_ptr), 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_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr),
WM_MOUSEHWHEEL => handle_mouse_horizontal_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_SYSKEYDOWN => handle_syskeydown_msg(wparam, lparam, state_ptr),
WM_SYSKEYUP => handle_syskeyup_msg(handle, wparam, state_ptr), WM_SYSKEYUP => handle_syskeyup_msg(wparam, state_ptr),
WM_KEYDOWN => handle_keydown_msg(handle, wparam, lparam, state_ptr), WM_KEYDOWN => handle_keydown_msg(wparam, lparam, state_ptr),
WM_KEYUP => handle_keyup_msg(handle, wparam, state_ptr), WM_KEYUP => handle_keyup_msg(wparam, state_ptr),
WM_CHAR => handle_char_msg(handle, wparam, lparam, state_ptr), WM_CHAR => handle_char_msg(wparam, lparam, state_ptr),
WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr), WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr), WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
WM_SETCURSOR => handle_set_cursor(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> { 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(); let mut lock = state_ptr.state.borrow_mut();
if let Some(mut request_frame) = lock.callbacks.request_frame.take() { if let Some(mut request_frame) = lock.callbacks.request_frame.take() {
drop(lock); drop(lock);
request_frame(); request_frame();
state_ptr.state.borrow_mut().callbacks.request_frame = Some(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) Some(0)
} }
@ -261,7 +259,6 @@ fn handle_mouse_move_msg(
} }
fn handle_syskeydown_msg( fn handle_syskeydown_msg(
handle: HWND,
wparam: WPARAM, wparam: WPARAM,
lparam: LPARAM, lparam: LPARAM,
state_ptr: Rc<WindowsWindowStatePtr>, state_ptr: Rc<WindowsWindowStatePtr>,
@ -281,7 +278,6 @@ fn handle_syskeydown_msg(
is_held: lparam.0 & (0x1 << 30) > 0, is_held: lparam.0 & (0x1 << 30) > 0,
}; };
let result = if func(PlatformInput::KeyDown(event)).default_prevented { let result = if func(PlatformInput::KeyDown(event)).default_prevented {
invalidate_client_area(handle);
Some(0) Some(0)
} else { } else {
None None
@ -291,11 +287,7 @@ fn handle_syskeydown_msg(
result result
} }
fn handle_syskeyup_msg( fn handle_syskeyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
handle: HWND,
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}` // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
// shortcuts. // shortcuts.
let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else { let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
@ -308,7 +300,6 @@ fn handle_syskeyup_msg(
drop(lock); drop(lock);
let event = KeyUpEvent { keystroke }; let event = KeyUpEvent { keystroke };
let result = if func(PlatformInput::KeyUp(event)).default_prevented { let result = if func(PlatformInput::KeyUp(event)).default_prevented {
invalidate_client_area(handle);
Some(0) Some(0)
} else { } else {
Some(1) Some(1)
@ -319,7 +310,6 @@ fn handle_syskeyup_msg(
} }
fn handle_keydown_msg( fn handle_keydown_msg(
handle: HWND,
wparam: WPARAM, wparam: WPARAM,
lparam: LPARAM, lparam: LPARAM,
state_ptr: Rc<WindowsWindowStatePtr>, state_ptr: Rc<WindowsWindowStatePtr>,
@ -337,7 +327,6 @@ fn handle_keydown_msg(
is_held: lparam.0 & (0x1 << 30) > 0, is_held: lparam.0 & (0x1 << 30) > 0,
}; };
let result = if func(PlatformInput::KeyDown(event)).default_prevented { let result = if func(PlatformInput::KeyDown(event)).default_prevented {
invalidate_client_area(handle);
Some(0) Some(0)
} else { } else {
Some(1) Some(1)
@ -347,11 +336,7 @@ fn handle_keydown_msg(
result result
} }
fn handle_keyup_msg( fn handle_keyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
handle: HWND,
wparam: WPARAM,
state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> {
let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else { let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else {
return Some(1); return Some(1);
}; };
@ -362,7 +347,6 @@ fn handle_keyup_msg(
drop(lock); drop(lock);
let event = KeyUpEvent { keystroke }; let event = KeyUpEvent { keystroke };
let result = if func(PlatformInput::KeyUp(event)).default_prevented { let result = if func(PlatformInput::KeyUp(event)).default_prevented {
invalidate_client_area(handle);
Some(0) Some(0)
} else { } else {
Some(1) Some(1)
@ -373,7 +357,6 @@ fn handle_keyup_msg(
} }
fn handle_char_msg( fn handle_char_msg(
handle: HWND,
wparam: WPARAM, wparam: WPARAM,
lparam: LPARAM, lparam: LPARAM,
state_ptr: Rc<WindowsWindowStatePtr>, state_ptr: Rc<WindowsWindowStatePtr>,
@ -396,7 +379,6 @@ fn handle_char_msg(
let mut lock = state_ptr.state.borrow_mut(); let mut lock = state_ptr.state.borrow_mut();
lock.callbacks.input = Some(func); lock.callbacks.input = Some(func);
if dispatch_event_result.default_prevented || !dispatch_event_result.propagate { if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
invalidate_client_area(handle);
return Some(0); return Some(0);
} }
let Some(ime_char) = ime_key else { let Some(ime_char) = ime_key else {
@ -407,7 +389,6 @@ fn handle_char_msg(
}; };
drop(lock); drop(lock);
input_handler.replace_text_in_range(None, &ime_char); input_handler.replace_text_in_range(None, &ime_char);
invalidate_client_area(handle);
state_ptr.state.borrow_mut().input_handler = Some(input_handler); state_ptr.state.borrow_mut().input_handler = Some(input_handler);
Some(0) Some(0)
@ -648,7 +629,6 @@ fn handle_ime_composition(
drop(lock); drop(lock);
input_handler.replace_text_in_range(None, &comp_result); input_handler.replace_text_in_range(None, &comp_result);
state_ptr.state.borrow_mut().input_handler = Some(input_handler); state_ptr.state.borrow_mut().input_handler = Some(input_handler);
invalidate_client_area(handle);
return Some(0); return Some(0);
} }
// currently, we don't care other stuff // 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") .context("unable to set window position after dpi has changed")
.log_err(); .log_err();
} }
invalidate_client_area(handle);
Some(0) 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)> { fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> {
unsafe { unsafe {
let ctx = ImmGetContext(handle); let ctx = ImmGetContext(handle);

View File

@ -4,11 +4,10 @@
use std::{ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
ffi::{c_void, OsString}, ffi::{c_void, OsString},
mem::transmute,
os::windows::ffi::{OsStrExt, OsStringExt}, os::windows::ffi::{OsStrExt, OsStringExt},
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, rc::Rc,
sync::{Arc, OnceLock}, sync::Arc,
}; };
use ::util::ResultExt; use ::util::ResultExt;
@ -26,7 +25,6 @@ use windows::{
Win32::{ Win32::{
Foundation::*, Foundation::*,
Graphics::Gdi::*, Graphics::Gdi::*,
Media::*,
Security::Credentials::*, Security::Credentials::*,
Storage::FileSystem::*, Storage::FileSystem::*,
System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*}, 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()>) { fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
on_finish_launching(); on_finish_launching();
let vsync_event = create_event().unwrap(); let vsync_event = create_event().unwrap();
let timer_stop_event = create_event().unwrap(); begin_vsync(vsync_event.to_raw());
let raw_timer_stop_event = timer_stop_event.to_raw();
begin_vsync_timer(vsync_event.to_raw(), timer_stop_event);
'a: loop { 'a: loop {
let wait_result = unsafe { let wait_result = unsafe {
MsgWaitForMultipleObjects( 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 { if let Some(ref mut callback) = self.state.borrow_mut().callbacks.quit {
callback(); callback();
@ -761,74 +756,15 @@ unsafe fn show_savefile_dialog(directory: PathBuf) -> Result<IFileSaveDialog> {
Ok(dialog) Ok(dialog)
} }
fn begin_vsync_timer(vsync_event: HANDLE, timer_stop_event: OwnedHandle) { fn begin_vsync(vsync_evnet: HANDLE) {
let vsync_fn = select_vsync_fn(); std::thread::spawn(move || unsafe {
std::thread::spawn(move || loop { loop {
if vsync_fn(timer_stop_event.to_raw()) { windows::Win32::Graphics::Dwm::DwmFlush().log_err();
if unsafe { SetEvent(vsync_event) }.log_err().is_none() { SetEvent(vsync_evnet).log_err();
break;
}
} }
}); });
} }
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> { fn load_icon() -> Result<HICON> {
let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? }; let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? };
let handle = unsafe { let handle = unsafe {