Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FrameThrottled event and a way to request it #2883

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

- **Breaking:** Add `WindowEvent::FrameThrottled` event and `Window::request_frame_throttling_hint` to request it.
- 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.
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
Expand Down
10 changes: 10 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,14 @@ pub enum WindowEvent<'a> {
/// hovered.
HoveredFileCancelled,

/// An event from windowing system when it is a good time to start drawing a new frame.
///
/// This is useful for throttling redrawing operation and driving animations. The event
/// is delivered in reaction to [`Window::request_frame_throttling_hint`].
///
/// For platform specifics see [`Window::request_frame_throttling_hint`] request.
FrameThrottled,

/// The window gained or lost focus.
///
/// The parameter is true if the window has gained focus, and false if it has lost focus.
Expand Down Expand Up @@ -553,6 +561,7 @@ impl Clone for WindowEvent<'static> {
Moved(pos) => Moved(*pos),
CloseRequested => CloseRequested,
Destroyed => Destroyed,
FrameThrottled => FrameThrottled,
DroppedFile(file) => DroppedFile(file.clone()),
HoveredFile(file) => HoveredFile(file.clone()),
HoveredFileCancelled => HoveredFileCancelled,
Expand Down Expand Up @@ -656,6 +665,7 @@ impl<'a> WindowEvent<'a> {
Moved(position) => Some(Moved(position)),
CloseRequested => Some(CloseRequested),
Destroyed => Some(Destroyed),
FrameThrottled => Some(FrameThrottled),
DroppedFile(file) => Some(DroppedFile(file)),
HoveredFile(file) => Some(HoveredFile(file)),
HoveredFileCancelled => Some(HoveredFileCancelled),
Expand Down
4 changes: 4 additions & 0 deletions src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,10 @@ impl Window {
self.redraw_requester.request_redraw()
}

pub fn request_frame_throttling_hint(&self) -> Result<(), error::NotSupportedError> {
Err(error::NotSupportedError::new())
}

pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
Err(error::NotSupportedError::new())
}
Expand Down
4 changes: 4 additions & 0 deletions src/platform_impl/ios/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ impl Inner {
}
}

pub fn request_frame_throttling_hint(&self) -> Result<(), NotSupportedError> {
Err(NotSupportedError::new())
}

pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
unsafe {
let safe_area = self.safe_area_screen_space();
Expand Down
11 changes: 5 additions & 6 deletions src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,12 +541,11 @@ impl Window {
}
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.request_user_attention(request_type),
#[cfg(wayland_platform)]
Window::Wayland(ref w) => w.request_user_attention(request_type),
}
x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type))
}

pub fn request_frame_throttling_hint(&self) -> Result<(), NotSupportedError> {
x11_or_wayland!(match self; Window(w) => w.request_frame_throttling_hint())
}

#[inline]
Expand Down
7 changes: 6 additions & 1 deletion src/platform_impl/linux/wayland/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use sctk::shm::{Shm, ShmHandler};
use sctk::subcompositor::SubcompositorState;

use crate::dpi::LogicalSize;
use crate::event::WindowEvent;

use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
Expand Down Expand Up @@ -321,7 +322,11 @@ 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);
self.events_sink
.push_window_event(WindowEvent::FrameThrottled, window_id);
}
}

impl ProvidesRegistryState for WinitState {
Expand Down
6 changes: 6 additions & 0 deletions src/platform_impl/linux/wayland/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,12 @@ impl Window {
xdg_activation_token.commit();
}

pub fn request_frame_throttling_hint(&self) -> Result<(), NotSupportedError> {
let surface = self.window.wl_surface();
surface.frame(&self.queue_handle, surface.clone());
Ok(())
}

#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
self.window_state.lock().unwrap().set_cursor_grab(mode)
Expand Down
5 changes: 5 additions & 0 deletions src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,11 @@ impl UnownedWindow {
.expect("Failed to set urgency hint");
}

#[inline]
pub fn request_frame_throttling_hint(&self) -> Result<(), NotSupportedError> {
Err(NotSupportedError::new())
}

#[inline]
pub fn id(&self) -> WindowId {
WindowId(self.xwindow as _)
Expand Down
4 changes: 4 additions & 0 deletions src/platform_impl/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,10 @@ impl WinitWindow {
AppState::queue_redraw(RootWindowId(self.id()));
}

pub fn request_frame_throttling_hint(&self) -> Result<(), NotSupportedError> {
Err(NotSupportedError::new())
}

pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
let frame_rect = self.frame();
let position = LogicalPosition::new(
Expand Down
5 changes: 5 additions & 0 deletions src/platform_impl/orbital/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ impl Window {
}
}

#[inline]
pub fn request_frame_throttling_hint(&self) -> Result<(), error::NotSupportedError> {
Err(error::NotSupportedError::new())
}

#[inline]
pub fn reset_dead_keys(&self) {
// TODO?
Expand Down
8 changes: 8 additions & 0 deletions src/platform_impl/web/event_loop/window_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,14 @@ impl<T> EventLoopWindowTarget<T> {
}
},
);

let runner = self.runner.clone();
canvas.on_frame_throttle(move || {
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::FrameThrottled,
})
})
}

pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
Expand Down
17 changes: 15 additions & 2 deletions src/platform_impl/web/web_sys/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::super::WindowId;
use super::event_handle::EventListenerHandle;
use super::media_query_handle::MediaQueryListHandle;
use super::pointer::PointerHandler;
use super::{event, ButtonsState, ResizeScaleHandle};
use super::{event, ButtonsState, FrameThrottlingHandler, ResizeScaleHandle};
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::OsError as RootOE;
use crate::event::{Force, MouseButton, MouseScrollDelta};
Expand Down Expand Up @@ -37,6 +37,7 @@ pub struct Canvas {
on_dark_mode: Option<MediaQueryListHandle>,
pointer_handler: PointerHandler,
on_resize_scale: Option<ResizeScaleHandle>,
frame_throttling_handler: FrameThrottlingHandler,
}

pub struct Common {
Expand Down Expand Up @@ -87,7 +88,7 @@ impl Canvas {

Ok(Canvas {
common: Common {
window,
window: window.clone(),
raw: canvas,
old_size: Rc::default(),
current_size: Rc::default(),
Expand All @@ -105,6 +106,7 @@ impl Canvas {
on_dark_mode: None,
pointer_handler: PointerHandler::new(),
on_resize_scale: None,
frame_throttling_handler: FrameThrottlingHandler::new(window),
})
}

Expand Down Expand Up @@ -365,6 +367,13 @@ impl Canvas {
));
}

pub(crate) fn on_frame_throttle<F>(&mut self, f: F)
where
F: 'static + FnMut(),
{
self.frame_throttling_handler.on_frame_throttle(f)
}

pub fn request_fullscreen(&self) {
self.common.request_fullscreen()
}
Expand All @@ -373,6 +382,10 @@ impl Canvas {
self.common.is_fullscreen()
}

pub fn request_frame_throttling_hint(&self) {
self.frame_throttling_handler.request();
}

pub(crate) fn handle_scale_change<T: 'static>(
&self,
runner: &super::super::event_loop::runner::Shared<T>,
Expand Down
62 changes: 62 additions & 0 deletions src/platform_impl/web/web_sys/frame_throttling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use std::cell::Cell;
use std::rc::Rc;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;

pub struct FrameThrottlingHandler {
window: web_sys::Window,
closure: Closure<dyn FnMut()>,
handle: Rc<Cell<Option<i32>>>,
}

impl FrameThrottlingHandler {
pub fn new(window: web_sys::Window) -> Self {
let handle = Rc::new(Cell::new(None));
let closure = Closure::new({
let handle = handle.clone();
move || handle.set(None)
});

Self {
window,
closure,
handle,
}
}

pub fn on_frame_throttle<F>(&mut self, mut f: F)
where
F: 'static + FnMut(),
{
let handle = self.handle.clone();
self.closure = Closure::new(move || {
handle.set(None);
f();
})
}

pub fn request(&self) {
if let Some(handle) = self.handle.take() {
self.window
.cancel_animation_frame(handle)
.expect("Failed to cancel animation frame");
}

let handle = self
.window
.request_animation_frame(self.closure.as_ref().unchecked_ref())
.expect("Failed to request animation frame");

self.handle.set(Some(handle));
}
}

impl Drop for FrameThrottlingHandler {
fn drop(&mut self) {
if let Some(handle) = self.handle.take() {
self.window
.cancel_animation_frame(handle)
.expect("Failed to cancel animation frame");
}
}
}
2 changes: 2 additions & 0 deletions src/platform_impl/web/web_sys/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod canvas;
pub mod event;
mod event_handle;
mod frame_throttling;
mod media_query_handle;
mod pointer;
mod resize_scaling;
Expand All @@ -9,6 +10,7 @@ mod timeout;
pub use self::canvas::Canvas;
pub use self::event::ButtonsState;
pub use self::event_handle::EventListenerHandle;
pub use self::frame_throttling::FrameThrottlingHandler;
pub use self::resize_scaling::ResizeScaleHandle;
pub use self::timeout::{IdleCallback, Timeout};

Expand Down
8 changes: 8 additions & 0 deletions src/platform_impl/web/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,14 @@ impl Window {
// Currently an intentional no-op
}

#[inline]
pub fn request_frame_throttling_hint(&self) -> Result<(), NotSupportedError> {
self.inner.dispatch(move |inner| {
inner.canvas.borrow().request_frame_throttling_hint();
});
Ok(())
}

#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle)
Expand Down
5 changes: 5 additions & 0 deletions src/platform_impl/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ impl Window {
}
}

#[inline]
pub fn request_frame_throttling_hint(&self) -> Result<(), NotSupportedError> {
Err(NotSupportedError::new())
}

#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
util::WindowArea::Outer.get_rect(self.hwnd())
Expand Down
20 changes: 20 additions & 0 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,26 @@ impl Window {
self.window.request_user_attention(request_type)
}

/// Request a frame throttling hint from the windowing system.
///
/// The error is returned when frame couldn't be requested and the user should use their
/// fallback renderer scheduling logic, like using timer based on monitor refresh rate or
/// graphics API extensions (VSYNC).
///
/// For more see [`FrameThrottled`] event.
///
/// ## Platform-specific
///
/// - **Wayland:** Uses frame callbacks. The user must perform drawing operation resulting in
/// `wl_surface.commit`(eglSwapBuffers, etc) after issueing a request.
kchibisov marked this conversation as resolved.
Show resolved Hide resolved
/// - ** macOS / Windows / iOS / Android / X11 / Orbital:** Not supported.
///
/// [`FrameThrottled`]: crate::event::WindowEvent::FrameThrottled.
#[inline]
pub fn request_frame_throttling_hint(&self) -> Result<(), NotSupportedError> {
self.window.request_frame_throttling_hint()
}

/// Sets the current window theme. Use `None` to fallback to system default.
///
/// ## Platform-specific
Expand Down