From 72ef07b36cc24c8b5516a1567d6166f9939a22ae Mon Sep 17 00:00:00 2001 From: J0R6IT0 Date: Sat, 25 Jul 2026 09:50:04 +0200 Subject: [PATCH] gpui_windows: Fix dropped frames under sustained input --- crates/gpui_windows/src/events.rs | 30 +++++++++++++++++++++++++++++ crates/gpui_windows/src/platform.rs | 10 ++++++++++ 2 files changed, 40 insertions(+) diff --git a/crates/gpui_windows/src/events.rs b/crates/gpui_windows/src/events.rs index 9f548722674715..84f4feac81c0c2 100644 --- a/crates/gpui_windows/src/events.rs +++ b/crates/gpui_windows/src/events.rs @@ -28,6 +28,7 @@ pub(crate) const WM_GPUI_FORCE_UPDATE_WINDOW: u32 = WM_USER + 5; pub(crate) const WM_GPUI_KEYBOARD_LAYOUT_CHANGED: u32 = WM_USER + 6; pub(crate) const WM_GPUI_GPU_DEVICE_LOST: u32 = WM_USER + 7; pub(crate) const WM_GPUI_KEYDOWN: u32 = WM_USER + 8; +pub(crate) const WM_GPUI_VSYNC_FRAME: u32 = WM_USER + 9; const SIZE_MOVE_LOOP_TIMER_ID: usize = 1; @@ -156,6 +157,7 @@ impl WindowsWindowInner { WM_SHOWWINDOW => self.handle_window_visibility_changed(handle, wparam), WM_GPUI_CURSOR_STYLE_CHANGED => self.handle_cursor_changed(lparam), WM_GPUI_FORCE_UPDATE_WINDOW => self.draw_window(handle, true), + WM_GPUI_VSYNC_FRAME => self.handle_vsync_frame(handle), WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam), DM_POINTERHITTEST => self.handle_dm_pointer_hit_test(wparam), WM_GETOBJECT => self.handle_wm_getobject(wparam, lparam), @@ -310,6 +312,34 @@ impl WindowsWindowInner { self.draw_window(handle, false) } + /// Deliver the frame the vsync thread invalidated. + /// + /// `WM_PAINT` is synthesized rather than queued. The thread only receives one + /// once its queue holds nothing else, so sustained input (a high-polling mouse, + /// a held key) starves it and frames stop reaching the display. A posted message + /// queues behind that input instead of losing to it, and `UpdateWindow` then + /// delivers the pending paint directly. + /// + /// Queued duplicates are dropped first, otherwise a window that can't keep up + /// with the refresh rate would accumulate them faster than it retires them, + /// since the vsync thread re-invalidates before the previous frame is done. + fn handle_vsync_frame(&self, handle: HWND) -> Option { + unsafe { + let mut msg = MSG::default(); + while PeekMessageW( + &mut msg, + Some(handle), + WM_GPUI_VSYNC_FRAME, + WM_GPUI_VSYNC_FRAME, + PM_REMOVE, + ) + .as_bool() + {} + UpdateWindow(handle).ok().log_err(); + } + Some(0) + } + fn handle_close_msg(&self) -> Option { let mut callback = self.state.callbacks.should_close.take()?; let should_close = callback(); diff --git a/crates/gpui_windows/src/platform.rs b/crates/gpui_windows/src/platform.rs index d3bb150407e665..65ee1d4fb11cba 100644 --- a/crates/gpui_windows/src/platform.rs +++ b/crates/gpui_windows/src/platform.rs @@ -347,6 +347,16 @@ impl WindowsPlatform { for hwnd in all_windows.read().iter() { unsafe { let _ = RedrawWindow(Some(hwnd.as_raw()), None, None, RDW_INVALIDATE); + // `WM_PAINT` is synthesized only once the target + // thread's queue is empty, so high-rate input + // starves it. Post a message too, which queues + // behind that input. See `handle_vsync_frame`. + let _ = PostMessageW( + Some(hwnd.as_raw()), + WM_GPUI_VSYNC_FRAME, + WPARAM(0), + LPARAM(0), + ); } } }