On Wayland, use frame callbacks to throttle RedrawRequested

Throttle RedrawRequested events by the frame callbacks, so the users
could render at the display refresh rate.
This commit is contained in:
Kirill Chibisov 2023-06-22 09:12:47 +04:00
parent 38f28d5836
commit 7a58fe58ce
6 changed files with 151 additions and 85 deletions

View file

@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased # 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. - 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. - On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`. - **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.

View file

@ -32,6 +32,7 @@ pub use proxy::EventLoopProxy;
use sink::EventSink; use sink::EventSink;
use super::state::{WindowCompositorUpdate, WinitState}; use super::state::{WindowCompositorUpdate, WinitState};
use super::window::state::FrameCallbackState;
use super::{DeviceId, WindowId}; use super::{DeviceId, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>; type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
@ -494,23 +495,30 @@ impl<T: 'static> EventLoop<T> {
mem::drop(state.windows.get_mut().remove(&window_id)); mem::drop(state.windows.get_mut().remove(&window_id));
false false
} else { } else {
let mut redraw_requested = window_requests let mut window = state
.get(&window_id)
.unwrap()
.take_redraw_requested();
// Redraw the frames while at it.
redraw_requested |= state
.windows .windows
.get_mut() .get_mut()
.get_mut(&window_id) .get_mut(&window_id)
.unwrap() .unwrap()
.lock() .lock()
.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() .unwrap()
.refresh_frame(); .take_redraw_requested();
// Redraw the frame while at it.
redraw_requested |= window.refresh_frame();
redraw_requested redraw_requested
} }
}
}); });
if request_redraw { if request_redraw {

View file

@ -321,7 +321,15 @@ impl CompositorHandler for WinitState {
self.scale_factor_changed(surface, scale_factor as f64, true) self.scale_factor_changed(surface, scale_factor as f64, true)
} }
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: u32) {} fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, 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 { impl ProvidesRegistryState for WinitState {

View file

@ -38,7 +38,7 @@ use super::state::WinitState;
use super::types::xdg_activation::XdgActivationTokenData; use super::types::xdg_activation::XdgActivationTokenData;
use super::{EventLoopWindowTarget, WindowId}; use super::{EventLoopWindowTarget, WindowId};
mod state; pub(crate) mod state;
pub use state::WindowState; pub use state::WindowState;
@ -295,7 +295,7 @@ impl Window {
#[inline] #[inline]
pub fn pre_present_notify(&self) { pub fn pre_present_notify(&self) {
// TODO self.window_state.lock().unwrap().request_frame_callback();
} }
#[inline] #[inline]

View file

@ -125,6 +125,9 @@ pub struct WindowState {
/// sends `None` for the new size in the configure. /// sends `None` for the new size in the configure.
stateless_size: LogicalSize<u32>, stateless_size: LogicalSize<u32>,
/// The state of the frame callback.
frame_callback_state: FrameCallbackState,
viewport: Option<WpViewport>, viewport: Option<WpViewport>,
fractional_scale: Option<WpFractionalScaleV1>, fractional_scale: Option<WpFractionalScaleV1>,
@ -134,26 +137,62 @@ pub struct WindowState {
has_pending_move: Option<u32>, has_pending_move: Option<u32>,
} }
/// 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,
}
}
}
impl WindowState { impl WindowState {
/// Create new window state.
pub fn new(
connection: Connection,
queue_handle: &QueueHandle<WinitState>,
winit_state: &WinitState,
size: LogicalSize<u32>,
window: Window,
theme: Option<Theme>,
) -> 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,
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),
}
}
/// Apply closure on the given pointer. /// Apply closure on the given pointer.
fn apply_on_poiner<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>( fn apply_on_poiner<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
&self, &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( pub fn configure(
&mut self, &mut self,
configure: WindowConfigure, configure: WindowConfigure,
@ -391,60 +457,6 @@ impl WindowState {
} }
} }
/// Create new window state.
pub fn new(
connection: Connection,
queue_handle: &QueueHandle<WinitState>,
winit_state: &WinitState,
size: LogicalSize<u32>,
window: Window,
theme: Option<Theme>,
) -> 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. /// Get the outer size of the window.
#[inline] #[inline]
pub fn outer_size(&self) -> LogicalSize<u32> { pub fn outer_size(&self) -> LogicalSize<u32> {
@ -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<ResizeDirection> for ResizeEdge { impl From<ResizeDirection> for ResizeEdge {
fn from(value: ResizeDirection) -> Self { fn from(value: ResizeDirection) -> Self {
match value { match value {

View file

@ -524,11 +524,12 @@ impl Window {
self.window.scale_factor() self.window.scale_factor()
} }
/// Requests a future [`Event::RedrawRequested`] event to be emitted in a way that is /// Queues a [`Event::RedrawRequested`] event to be emitted that aligns with the windowing
/// synchronized and / or throttled by the windowing system. /// system drawing loop.
/// ///
/// This is the **strongly encouraged** method of redrawing windows, as it can integrate with /// 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. /// 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 /// with respect to other events, since the requirements can vary significantly between
/// windowing systems. /// 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 /// ## Platform-specific
/// ///
/// - **Windows** This API uses `RedrawWindow` to request a `WM_PAINT` message and `RedrawRequested` /// - **Windows** This API uses `RedrawWindow` to request a `WM_PAINT` message and `RedrawRequested`
/// is emitted in sync with any `WM_PAINT` messages /// is emitted in sync with any `WM_PAINT` messages
/// - **iOS:** Can only be called on the main thread. /// - **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 /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested
#[inline] #[inline]