Skip to content

Commit

Permalink
On Wayland, use frame callbacks to throttle RedrawRequested
Browse files Browse the repository at this point in the history
Throttle RedrawRequested events by the frame callbacks, so the users
could render at the display refresh rate.
  • Loading branch information
kchibisov committed Jul 28, 2023
1 parent 16f9fb9 commit 687c1e2
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 87 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
- On Wayland, make double clicking and moving the CSD frame more reliable.
Expand Down
1 change: 0 additions & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,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
Expand Down
30 changes: 19 additions & 11 deletions src/platform_impl/linux/wayland/event_loop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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>, WinitState>;
Expand Down Expand Up @@ -496,22 +497,29 @@ impl<T: 'static> EventLoop<T> {
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 the frame while at it.
redraw_requested |= window.refresh_frame();

redraw_requested
}
}
});

Expand Down
10 changes: 9 additions & 1 deletion src/platform_impl/linux/wayland/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,15 @@ impl CompositorHandler for WinitState {
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 {
Expand Down
4 changes: 2 additions & 2 deletions src/platform_impl/linux/wayland/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -295,7 +295,7 @@ impl Window {

#[inline]
pub fn pre_present_notify(&self) {
// TODO
self.window_state.lock().unwrap().request_frame_callback();
}

#[inline]
Expand Down
181 changes: 112 additions & 69 deletions src/platform_impl/linux/wayland/window/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ pub struct WindowState {
/// sends `None` for the new size in the configure.
stateless_size: LogicalSize<u32>,

/// The state of the frame callback.
frame_callback_state: FrameCallbackState,

viewport: Option<WpViewport>,
fractional_scale: Option<WpFractionalScaleV1>,

Expand All @@ -134,26 +137,62 @@ pub struct WindowState {
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 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));

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<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
&self,
Expand All @@ -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,
Expand Down Expand Up @@ -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.
#[inline]
pub fn outer_size(&self) -> LogicalSize<u32> {
Expand Down Expand Up @@ -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 {
fn from(value: ResizeDirection) -> Self {
match value {
Expand Down
12 changes: 9 additions & 3 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,23 +524,29 @@ 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.
///
/// There are no strong guarantees about when exactly a `RedrawRequest` event will be emitted
/// 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]
Expand Down

0 comments on commit 687c1e2

Please sign in to comment.