Windows: direct load DCompositionWaitForCompositorClock and fallback (#9351)

白山風露 created

…timer

Fix: #9166

Release Notes:

- N/A

Change summary

Cargo.toml                                   |  18 -
crates/gpui/src/platform/windows/display.rs  |  23 +++
crates/gpui/src/platform/windows/platform.rs | 151 +++++++++++++++++----
3 files changed, 147 insertions(+), 45 deletions(-)

Detailed changes

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",

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<u32> {
+        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 {

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<WindowsTextSystem>,
     callbacks: Mutex<Callbacks>,
     pub raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
-    pub(crate) event: HANDLE,
+    pub(crate) dispatch_event: HANDLE,
     pub(crate) settings: RefCell<WindowsPlatformSystemSettings>,
 }
 
@@ -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::<Runnable>();
-        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<dyn 'static + FnOnce()>) {
         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) };
-
-            // compositor clock ticked so we should draw a frame
-            if wait_result == 1 {
-                self.redraw_all();
-                unsafe {
+            let wait_result = unsafe {
+                MsgWaitForMultipleObjects(
+                    Some(&[vsync_event, dispatch_event]),
+                    false,
+                    INFINITE,
+                    QS_ALLINPUT,
+                )
+            };
+
+            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;
-                        }
-                        if msg.message == WM_SETTINGCHANGE {
-                            self.inner.settings.borrow_mut().update_all();
-                            continue;
+                    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);
                         }
-                        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<IFileSaveDialog> {
 
     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<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
+    }
+}