diff --git a/CHANGELOG.md b/CHANGELOG.md index 7586ddaff34..bfcff212773 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 Android, changed default behavior of Android to ignore volume keys letting the operating system handle them. - On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually. diff --git a/src/event.rs b/src/event.rs index 4111e1f64ae..a58933a17e1 100644 --- a/src/event.rs +++ b/src/event.rs @@ -207,7 +207,6 @@ pub enum Event<'a, T: 'static> { /// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless /// something changes, like most non-game GUIs. /// - /// /// ## Platform-specific /// /// - **macOS / iOS:** Due to implementation difficulties, this will often, but not always, be diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 6ddb457115d..7d7503a8a90 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -7,7 +7,6 @@ use std::marker::PhantomData; use std::mem; use std::process; use std::rc::Rc; -use std::sync::atomic::Ordering; use std::time::{Duration, Instant}; use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle}; @@ -29,6 +28,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>; @@ -331,22 +331,8 @@ impl EventLoop { let physical_size = self.with_state(|state| { let windows = state.windows.get_mut(); let window = windows.get(&window_id).unwrap().lock().unwrap(); - - let scale_factor = window.scale_factor(); - let physical_size = logical_to_physical_rounded(size, scale_factor); - // TODO could probably bring back size reporting optimization. - - // Mark the window as needed a redraw. - state - .window_requests - .get_mut() - .get_mut(&window_id) - .unwrap() - .redraw_requested - .store(true, Ordering::Relaxed); - - physical_size + logical_to_physical_rounded(size, window.scale_factor()) }); sticky_exit_callback( @@ -412,22 +398,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(); - - redraw_requested + .unwrap(); + + 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_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 f9b7c4afca4..849a48f0c46 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 18b819a9053..5ba08ce7010 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -37,7 +37,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; @@ -276,7 +276,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 12d55c9646f..93ad736b1ab 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -122,30 +122,67 @@ 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, } -/// 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 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)); -impl GrabState { - fn new() -> Self { Self { - user_grab_mode: CursorGrabMode::None, - current_grab_mode: CursorGrabMode::None, + compositor, + connection, + theme, + csd_fails: false, + 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, + frame_callback_state: FrameCallbackState::None, + viewport, + window: ManuallyDrop::new(window), } } -} -impl WindowState { /// Apply closure on the given pointer. fn apply_on_poiner, &WinitPointerData)>( &self, @@ -160,6 +197,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, @@ -182,6 +246,7 @@ impl WindowState { frame.set_title(&self.title); // Ensure that the frame is not hidden. frame.set_hidden(false); + println!("dirty: {}", frame.is_dirty()); self.frame = Some(frame); } Err(err) => { @@ -366,58 +431,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, - 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), - } - } - /// Get the outer size of the window. #[inline] pub fn outer_size(&self) -> LogicalSize { @@ -842,6 +855,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 4ad04bcbc57..0f6b660335d 100644 --- a/src/window.rs +++ b/src/window.rs @@ -528,7 +528,8 @@ impl Window { /// events have been processed by the event 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. /// /// This function can cause `RedrawRequested` events to be emitted after [`Event::MainEventsCleared`] /// but before `Event::NewEvents` if called in the following circumstances: @@ -536,10 +537,15 @@ impl Window { /// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any /// directly subsequent `RedrawRequested` event. /// + /// However the event may not arrived on the next event loop, winit will try to align the + /// receiving the evente with the windowing system drawing loop. + /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. /// - **Android:** Subsequent calls after `MainEventsCleared` are not handled. + /// - **Wayland:** The events are aligned with the frame callbacks when [`Window::pre_present_notify`] + /// is used. /// /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested /// [`Event::MainEventsCleared`]: crate::event::Event::MainEventsCleared