Skip to content

Commit

Permalink
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 Jun 23, 2023
1 parent 95ff88a commit 1e669fa
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 98 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.
- 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.
Expand Down
1 change: 0 additions & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 19 additions & 26 deletions src/platform_impl/linux/wayland/event_loop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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>, WinitState>;
Expand Down Expand Up @@ -331,22 +331,8 @@ impl<T: 'static> EventLoop<T> {
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(
Expand Down Expand Up @@ -412,22 +398,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_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 @@ -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;

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

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

#[inline]
Expand Down
178 changes: 111 additions & 67 deletions src/platform_impl/linux/wayland/window/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,30 +122,67 @@ 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>,
}

/// 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,
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<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
&self,
Expand All @@ -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,
Expand All @@ -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) => {
Expand Down Expand Up @@ -366,58 +431,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,
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<u32> {
Expand Down Expand Up @@ -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<ResizeDirection> for ResizeEdge {
fn from(value: ResizeDirection) -> Self {
match value {
Expand Down
8 changes: 7 additions & 1 deletion src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,18 +528,24 @@ 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:
/// * While processing `MainEventsCleared`.
/// * 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
Expand Down

0 comments on commit 1e669fa

Please sign in to comment.