Windows: direct load DCompositionWaitForCompositorClock and fallback (#9351)

…timer

Fix: #9166

Release Notes:

- N/A
This commit is contained in:
白山風露 2024-03-16 09:17:26 +09:00 committed by GitHub
parent ca6e588085
commit c2b42e2bab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 146 additions and 44 deletions

View File

@ -342,30 +342,22 @@ features = [
"implement", "implement",
"Wdk_System_SystemServices", "Wdk_System_SystemServices",
"Win32_Globalization", "Win32_Globalization",
"Win32_Graphics_DirectComposition",
"Win32_Graphics_Gdi",
"Win32_UI_Controls",
"Win32_Graphics_DirectWrite", "Win32_Graphics_DirectWrite",
"Win32_UI_WindowsAndMessaging", "Win32_Graphics_Gdi",
"Win32_UI_Input_KeyboardAndMouse", "Win32_Media",
"Win32_UI_Shell",
"Win32_System_Com",
"Win32_UI_HiDpi",
"Win32_UI_Controls",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Time",
"Win32_Security", "Win32_Security",
"Win32_Storage_FileSystem", "Win32_Storage_FileSystem",
"Win32_System_Com", "Win32_System_Com",
"Win32_System_Com_StructuredStorage", "Win32_System_Com_StructuredStorage",
"Win32_System_DataExchange", "Win32_System_DataExchange",
"Win32_System_LibraryLoader",
"Win32_System_Ole", "Win32_System_Ole",
"Win32_System_SystemInformation", "Win32_System_SystemInformation",
"Win32_System_SystemServices", "Win32_System_SystemServices",
"Win32_System_Time",
"Win32_System_Threading", "Win32_System_Threading",
"Win32_System_Time",
"Win32_UI_Controls", "Win32_UI_Controls",
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime", "Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell", "Win32_UI_Shell",

View File

@ -2,7 +2,10 @@ use itertools::Itertools;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::rc::Rc; use std::rc::Rc;
use uuid::Uuid; use uuid::Uuid;
use windows::Win32::{Foundation::*, Graphics::Gdi::*}; use windows::{
core::*,
Win32::{Foundation::*, Graphics::Gdi::*},
};
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size}; use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size};
@ -97,6 +100,24 @@ impl WindowsDisplay {
}) })
.collect() .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 { impl PlatformDisplay for WindowsDisplay {

View File

@ -4,10 +4,11 @@
use std::{ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
ffi::{c_uint, c_void, OsString}, ffi::{c_uint, 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, sync::{Arc, OnceLock},
time::Duration, time::Duration,
}; };
@ -25,8 +26,9 @@ use windows::{
Wdk::System::SystemServices::*, Wdk::System::SystemServices::*,
Win32::{ Win32::{
Foundation::*, Foundation::*,
Graphics::{DirectComposition::*, Gdi::*}, Graphics::Gdi::*,
System::{Com::*, Ole::*, Threading::*, Time::*}, Media::*,
System::{Com::*, LibraryLoader::*, Ole::*, Threading::*, Time::*},
UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*}, UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
}, },
}; };
@ -55,7 +57,7 @@ pub(crate) struct WindowsPlatformInner {
text_system: Arc<WindowsTextSystem>, text_system: Arc<WindowsTextSystem>,
callbacks: Mutex<Callbacks>, callbacks: Mutex<Callbacks>,
pub raw_window_handles: RwLock<SmallVec<[HWND; 4]>>, pub raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
pub(crate) event: HANDLE, pub(crate) dispatch_event: HANDLE,
pub(crate) settings: RefCell<WindowsPlatformSystemSettings>, pub(crate) settings: RefCell<WindowsPlatformSystemSettings>,
} }
@ -74,7 +76,7 @@ impl WindowsPlatformInner {
impl Drop for WindowsPlatformInner { impl Drop for WindowsPlatformInner {
fn drop(&mut self) { 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"); OleInitialize(None).expect("unable to initialize Windows OLE");
} }
let (main_sender, main_receiver) = flume::unbounded::<Runnable>(); let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
let event = unsafe { CreateEventW(None, false, false, None) }.unwrap(); let dispatch_event = unsafe { CreateEventW(None, false, false, None) }.unwrap();
let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, event)); let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, dispatch_event));
let background_executor = BackgroundExecutor::new(dispatcher.clone()); let background_executor = BackgroundExecutor::new(dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(dispatcher); let foreground_executor = ForegroundExecutor::new(dispatcher);
let text_system = Arc::new(WindowsTextSystem::new()); let text_system = Arc::new(WindowsTextSystem::new());
@ -163,7 +165,7 @@ impl WindowsPlatform {
text_system, text_system,
callbacks, callbacks,
raw_window_handles, raw_window_handles,
event, dispatch_event,
settings, settings,
}); });
Self { inner } Self { inner }
@ -204,36 +206,54 @@ 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 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 { 'a: loop {
let mut msg = MSG::default(); let wait_result = unsafe {
// will be 0 if woken up by self.inner.event or 1 if the compositor clock ticked MsgWaitForMultipleObjects(
// SEE: https://learn.microsoft.com/en-us/windows/win32/directcomp/compositor-clock/compositor-clock Some(&[vsync_event, dispatch_event]),
let wait_result = false,
unsafe { DCompositionWaitForCompositorClock(Some(&[dispatch_event]), INFINITE) }; INFINITE,
QS_ALLINPUT,
)
};
// compositor clock ticked so we should draw a frame match wait_result {
if wait_result == 1 { // compositor clock ticked so we should draw a frame
self.redraw_all(); WAIT_EVENT(0) => {
unsafe { 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(); let mut msg = MSG::default();
unsafe {
while PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE).as_bool() { while PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE).as_bool() {
if msg.message == WM_QUIT { if msg.message == WM_QUIT {
break 'a; 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(); let mut callbacks = self.inner.callbacks.lock();
if let Some(callback) = callbacks.quit.as_mut() { if let Some(callback) = callbacks.quit.as_mut() {
@ -656,3 +676,72 @@ unsafe fn show_savefile_dialog(directory: PathBuf) -> Result<IFileSaveDialog> {
Ok(dialog) 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
}
}