From c2b42e2babf389ef86719d845e37f3915e2c009e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=B1=B1=E9=A2=A8=E9=9C=B2?= Date: Sat, 16 Mar 2024 09:17:26 +0900 Subject: [PATCH] Windows: direct load DCompositionWaitForCompositorClock and fallback (#9351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …timer Fix: #9166 Release Notes: - N/A --- Cargo.toml | 18 +-- crates/gpui/src/platform/windows/display.rs | 23 ++- crates/gpui/src/platform/windows/platform.rs | 149 +++++++++++++++---- 3 files changed, 146 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2d9d075ba1..64b276c27f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -342,30 +342,22 @@ features = [ "implement", "Wdk_System_SystemServices", "Win32_Globalization", - "Win32_Graphics_DirectComposition", - "Win32_Graphics_Gdi", - "Win32_UI_Controls", "Win32_Graphics_DirectWrite", - "Win32_UI_WindowsAndMessaging", - "Win32_UI_Input_KeyboardAndMouse", - "Win32_UI_Shell", - "Win32_System_Com", - "Win32_UI_HiDpi", - "Win32_UI_Controls", - "Win32_System_SystemInformation", - "Win32_System_SystemServices", - "Win32_System_Time", + "Win32_Graphics_Gdi", + "Win32_Media", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Com_StructuredStorage", "Win32_System_DataExchange", + "Win32_System_LibraryLoader", "Win32_System_Ole", "Win32_System_SystemInformation", "Win32_System_SystemServices", - "Win32_System_Time", "Win32_System_Threading", + "Win32_System_Time", "Win32_UI_Controls", + "Win32_UI_HiDpi", "Win32_UI_Input_Ime", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", diff --git a/crates/gpui/src/platform/windows/display.rs b/crates/gpui/src/platform/windows/display.rs index 6eff3c7870..5f766339be 100644 --- a/crates/gpui/src/platform/windows/display.rs +++ b/crates/gpui/src/platform/windows/display.rs @@ -2,7 +2,10 @@ use itertools::Itertools; use smallvec::SmallVec; use std::rc::Rc; use uuid::Uuid; -use windows::Win32::{Foundation::*, Graphics::Gdi::*}; +use windows::{ + core::*, + Win32::{Foundation::*, Graphics::Gdi::*}, +}; use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size}; @@ -97,6 +100,24 @@ impl WindowsDisplay { }) .collect() } + + pub(crate) fn frequency(&self) -> Option { + available_monitors() + .get(self.display_id.0 as usize) + .and_then(|hmonitor| get_monitor_info(*hmonitor).ok()) + .and_then(|info| { + let mut devmode = DEVMODEW::default(); + unsafe { + EnumDisplaySettingsW( + PCWSTR(info.szDevice.as_ptr()), + ENUM_CURRENT_SETTINGS, + &mut devmode, + ) + } + .as_bool() + .then(|| devmode.dmDisplayFrequency) + }) + } } impl PlatformDisplay for WindowsDisplay { diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index b60d6ca368..968537e1fd 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -4,10 +4,11 @@ use std::{ cell::{Cell, RefCell}, ffi::{c_uint, c_void, OsString}, + mem::transmute, os::windows::ffi::{OsStrExt, OsStringExt}, path::{Path, PathBuf}, rc::Rc, - sync::Arc, + sync::{Arc, OnceLock}, time::Duration, }; @@ -25,8 +26,9 @@ use windows::{ Wdk::System::SystemServices::*, Win32::{ Foundation::*, - Graphics::{DirectComposition::*, Gdi::*}, - System::{Com::*, Ole::*, Threading::*, Time::*}, + Graphics::Gdi::*, + Media::*, + System::{Com::*, LibraryLoader::*, Ole::*, Threading::*, Time::*}, UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*}, }, }; @@ -55,7 +57,7 @@ pub(crate) struct WindowsPlatformInner { text_system: Arc, callbacks: Mutex, pub raw_window_handles: RwLock>, - pub(crate) event: HANDLE, + pub(crate) dispatch_event: HANDLE, pub(crate) settings: RefCell, } @@ -74,7 +76,7 @@ impl WindowsPlatformInner { impl Drop for WindowsPlatformInner { fn drop(&mut self) { - unsafe { CloseHandle(self.event) }.ok(); + unsafe { CloseHandle(self.dispatch_event) }.ok(); } } @@ -148,8 +150,8 @@ impl WindowsPlatform { OleInitialize(None).expect("unable to initialize Windows OLE"); } let (main_sender, main_receiver) = flume::unbounded::(); - let event = unsafe { CreateEventW(None, false, false, None) }.unwrap(); - let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, event)); + let dispatch_event = unsafe { CreateEventW(None, false, false, None) }.unwrap(); + let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, dispatch_event)); let background_executor = BackgroundExecutor::new(dispatcher.clone()); let foreground_executor = ForegroundExecutor::new(dispatcher); let text_system = Arc::new(WindowsTextSystem::new()); @@ -163,7 +165,7 @@ impl WindowsPlatform { text_system, callbacks, raw_window_handles, - event, + dispatch_event, settings, }); Self { inner } @@ -204,36 +206,54 @@ impl Platform for WindowsPlatform { fn run(&self, on_finish_launching: Box) { on_finish_launching(); - let dispatch_event = self.inner.event; - + let dispatch_event = self.inner.dispatch_event; + let vsync_event = unsafe { CreateEventW(None, false, false, None) }.unwrap(); + let timer_stop_event = unsafe { CreateEventW(None, false, false, None) }.unwrap(); + begin_vsync_timer(vsync_event, timer_stop_event); 'a: loop { - let mut msg = MSG::default(); - // will be 0 if woken up by self.inner.event or 1 if the compositor clock ticked - // SEE: https://learn.microsoft.com/en-us/windows/win32/directcomp/compositor-clock/compositor-clock - let wait_result = - unsafe { DCompositionWaitForCompositorClock(Some(&[dispatch_event]), INFINITE) }; + let wait_result = unsafe { + MsgWaitForMultipleObjects( + Some(&[vsync_event, dispatch_event]), + false, + INFINITE, + QS_ALLINPUT, + ) + }; - // compositor clock ticked so we should draw a frame - if wait_result == 1 { - self.redraw_all(); - unsafe { + match wait_result { + // compositor clock ticked so we should draw a frame + WAIT_EVENT(0) => { + self.redraw_all(); + } + // foreground tasks are dispatched + WAIT_EVENT(1) => { + self.run_foreground_tasks(); + } + // Windows thread messages are posted + WAIT_EVENT(2) => { let mut msg = MSG::default(); - - while PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE).as_bool() { - if msg.message == WM_QUIT { - break 'a; + unsafe { + while PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE).as_bool() { + if msg.message == WM_QUIT { + break 'a; + } + if msg.message == WM_SETTINGCHANGE { + self.inner.settings.borrow_mut().update_all(); + continue; + } + TranslateMessage(&msg); + DispatchMessageW(&msg); } - if msg.message == WM_SETTINGCHANGE { - self.inner.settings.borrow_mut().update_all(); - continue; - } - TranslateMessage(&msg); - DispatchMessageW(&msg); } } + _ => { + log::error!("Something went wrong while waiting {:?}", wait_result); + break; + } } - self.run_foreground_tasks(); } + end_vsync_timer(timer_stop_event); + unsafe { CloseHandle(dispatch_event) }.log_err(); let mut callbacks = self.inner.callbacks.lock(); if let Some(callback) = callbacks.quit.as_mut() { @@ -656,3 +676,72 @@ unsafe fn show_savefile_dialog(directory: PathBuf) -> Result { Ok(dialog) } + +fn begin_vsync_timer(vsync_event: HANDLE, timer_stop_event: HANDLE) { + let vsync_fn = select_vsync_fn(); + std::thread::spawn(move || { + while vsync_fn(timer_stop_event) { + if unsafe { SetEvent(vsync_event) }.log_err().is_none() { + break; + } + } + unsafe { CloseHandle(timer_stop_event) }.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 + } +}