diff --git a/CHANGELOG.md b/CHANGELOG.md index f8543dc8..27aa1010 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor. - Add `Window::pre_present_notify` to notify winit before presenting to the windowing system. - On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name. - **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`. diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index f33cde0f..0527d6e5 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -32,6 +32,7 @@ pub use proxy::EventLoopProxy; use sink::EventSink; use super::state::{WindowCompositorUpdate, WinitState}; +use super::window::state::FrameCallbackState; use super::{DeviceId, WindowId}; type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>; @@ -494,22 +495,29 @@ impl EventLoop { mem::drop(state.windows.get_mut().remove(&window_id)); false } else { - let mut redraw_requested = window_requests - .get(&window_id) - .unwrap() - .take_redraw_requested(); - - // Redraw the frames while at it. - redraw_requested |= state + let mut window = state .windows .get_mut() .get_mut(&window_id) .unwrap() .lock() - .unwrap() - .refresh_frame(); + .unwrap(); - redraw_requested + if window.frame_callback_state() == FrameCallbackState::Requested { + false + } else { + // Reset the frame callbacks state. + window.frame_callback_reset(); + let mut redraw_requested = window_requests + .get(&window_id) + .unwrap() + .take_redraw_requested(); + + // Redraw the frame while at it. + redraw_requested |= window.refresh_frame(); + + redraw_requested + } } }); diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index f9b7c4af..849a48f0 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -321,7 +321,15 @@ impl CompositorHandler for WinitState { self.scale_factor_changed(surface, scale_factor as f64, true) } - fn frame(&mut self, _: &Connection, _: &QueueHandle, _: &WlSurface, _: u32) {} + fn frame(&mut self, _: &Connection, _: &QueueHandle, surface: &WlSurface, _: u32) { + let window_id = super::make_wid(surface); + let window = match self.windows.get_mut().get(&window_id) { + Some(window) => window, + None => return, + }; + + window.lock().unwrap().frame_callback_received(); + } } impl ProvidesRegistryState for WinitState { diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 88b0893c..26bb8ba6 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -38,7 +38,7 @@ use super::state::WinitState; use super::types::xdg_activation::XdgActivationTokenData; use super::{EventLoopWindowTarget, WindowId}; -mod state; +pub(crate) mod state; pub use state::WindowState; @@ -295,7 +295,7 @@ impl Window { #[inline] pub fn pre_present_notify(&self) { - // TODO + self.window_state.lock().unwrap().request_frame_callback(); } #[inline] diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index 479413e6..e0c49202 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -125,6 +125,9 @@ pub struct WindowState { /// sends `None` for the new size in the configure. stateless_size: LogicalSize, + /// The state of the frame callback. + frame_callback_state: FrameCallbackState, + viewport: Option, fractional_scale: Option, @@ -134,26 +137,62 @@ pub struct WindowState { has_pending_move: Option, } -/// The state of the cursor grabs. -#[derive(Clone, Copy)] -struct GrabState { - /// The grab mode requested by the user. - user_grab_mode: CursorGrabMode, +impl WindowState { + /// Create new window state. + pub fn new( + connection: Connection, + queue_handle: &QueueHandle, + winit_state: &WinitState, + size: LogicalSize, + window: Window, + theme: Option, + ) -> Self { + let compositor = winit_state.compositor_state.clone(); + let pointer_constraints = winit_state.pointer_constraints.clone(); + let viewport = winit_state + .viewporter_state + .as_ref() + .map(|state| state.get_viewport(window.wl_surface(), queue_handle)); + let fractional_scale = winit_state + .fractional_scaling_manager + .as_ref() + .map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle)); - /// The current grab mode. - current_grab_mode: CursorGrabMode, -} - -impl GrabState { - fn new() -> Self { Self { - user_grab_mode: CursorGrabMode::None, - current_grab_mode: CursorGrabMode::None, + compositor, + connection, + csd_fails: false, + cursor_grab_mode: GrabState::new(), + cursor_icon: CursorIcon::Default, + cursor_visible: true, + decorate: true, + fractional_scale, + frame: None, + frame_callback_state: FrameCallbackState::None, + has_focus: false, + has_pending_move: None, + ime_allowed: false, + ime_purpose: ImePurpose::Normal, + last_configure: None, + max_inner_size: None, + min_inner_size: MIN_WINDOW_SIZE, + pointer_constraints, + pointers: Default::default(), + queue_handle: queue_handle.clone(), + resizable: true, + scale_factor: 1., + shm: winit_state.shm.wl_shm().clone(), + size, + stateless_size: size, + text_inputs: Vec::new(), + theme, + title: String::default(), + transparent: false, + viewport, + window: ManuallyDrop::new(window), } } -} -impl WindowState { /// Apply closure on the given pointer. fn apply_on_poiner, &WinitPointerData)>( &self, @@ -168,6 +207,33 @@ impl WindowState { }) } + /// Get the current state of the frame callback. + pub fn frame_callback_state(&self) -> FrameCallbackState { + self.frame_callback_state + } + + /// The frame callback was received, but not yet sent to the user. + pub fn frame_callback_received(&mut self) { + self.frame_callback_state = FrameCallbackState::Received; + } + + /// Reset the frame callbacks state. + pub fn frame_callback_reset(&mut self) { + self.frame_callback_state = FrameCallbackState::None; + } + + /// Request a frame callback if we don't have one for this window in flight. + pub fn request_frame_callback(&mut self) { + let surface = self.window.wl_surface(); + match self.frame_callback_state { + FrameCallbackState::None | FrameCallbackState::Received => { + self.frame_callback_state = FrameCallbackState::Requested; + surface.frame(&self.queue_handle, surface.clone()); + } + FrameCallbackState::Requested => (), + } + } + pub fn configure( &mut self, configure: WindowConfigure, @@ -391,60 +457,6 @@ impl WindowState { } } - /// Create new window state. - pub fn new( - connection: Connection, - queue_handle: &QueueHandle, - winit_state: &WinitState, - size: LogicalSize, - window: Window, - theme: Option, - ) -> Self { - let compositor = winit_state.compositor_state.clone(); - let pointer_constraints = winit_state.pointer_constraints.clone(); - let viewport = winit_state - .viewporter_state - .as_ref() - .map(|state| state.get_viewport(window.wl_surface(), queue_handle)); - let fractional_scale = winit_state - .fractional_scaling_manager - .as_ref() - .map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle)); - - Self { - compositor, - connection, - theme, - csd_fails: false, - decorate: true, - cursor_grab_mode: GrabState::new(), - cursor_icon: CursorIcon::Default, - cursor_visible: true, - fractional_scale, - frame: None, - has_focus: false, - ime_allowed: false, - ime_purpose: ImePurpose::Normal, - last_configure: None, - max_inner_size: None, - min_inner_size: MIN_WINDOW_SIZE, - pointer_constraints, - pointers: Default::default(), - queue_handle: queue_handle.clone(), - scale_factor: 1., - shm: winit_state.shm.wl_shm().clone(), - size, - stateless_size: size, - text_inputs: Vec::new(), - title: String::default(), - transparent: false, - resizable: true, - viewport, - window: ManuallyDrop::new(window), - has_pending_move: None, - } - } - /// Get the outer size of the window. #[inline] pub fn outer_size(&self) -> LogicalSize { @@ -892,6 +904,37 @@ impl Drop for WindowState { } } +/// The state of the cursor grabs. +#[derive(Clone, Copy)] +struct GrabState { + /// The grab mode requested by the user. + user_grab_mode: CursorGrabMode, + + /// The current grab mode. + current_grab_mode: CursorGrabMode, +} + +impl GrabState { + fn new() -> Self { + Self { + user_grab_mode: CursorGrabMode::None, + current_grab_mode: CursorGrabMode::None, + } + } +} + +/// The state of the frame callback. +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +pub enum FrameCallbackState { + /// No frame callback was requsted. + #[default] + None, + /// The frame callback was requested, but not yet arrived, the redraw events are throttled. + Requested, + /// The callback was marked as done, and user could receive redraw requested + Received, +} + impl From for ResizeEdge { fn from(value: ResizeDirection) -> Self { match value { diff --git a/src/window.rs b/src/window.rs index dc64ee9f..b8a1f5cc 100644 --- a/src/window.rs +++ b/src/window.rs @@ -524,11 +524,12 @@ impl Window { self.window.scale_factor() } - /// Requests a future [`Event::RedrawRequested`] event to be emitted in a way that is - /// synchronized and / or throttled by the windowing system. + /// Queues a [`Event::RedrawRequested`] event to be emitted that aligns with the windowing + /// system drawing loop. /// /// This is the **strongly encouraged** method of redrawing windows, as it can integrate with - /// OS-requested redraws (e.g. when a window gets resized). + /// OS-requested redraws (e.g. when a window gets resized). To improve the event delivery + /// consider using [`Window::pre_present_notify`] as described in docs. /// /// Applications should always aim to redraw whenever they receive a `RedrawRequested` event. /// @@ -536,11 +537,16 @@ impl Window { /// with respect to other events, since the requirements can vary significantly between /// windowing systems. /// + /// However as the event aligns with the windowing system drawing loop, it may not arrive in + /// same or even next event loop iteration. + /// /// ## Platform-specific /// /// - **Windows** This API uses `RedrawWindow` to request a `WM_PAINT` message and `RedrawRequested` /// is emitted in sync with any `WM_PAINT` messages /// - **iOS:** Can only be called on the main thread. + /// - **Wayland:** The events are aligned with the frame callbacks when [`Window::pre_present_notify`] + /// is used. /// /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested #[inline]