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 frame throttling source #2535

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ And please only add new entries to the top of this list, right below the `# Unre
- **Breaking:** Removed `WindowBuilderExtWindows::with_theme` and `WindowBuilderExtWayland::with_wayland_csd_theme` in favour of `WindowBuilder::with_theme`.
- **Breaking:** Removed `WindowExtWindows::theme` in favour of `Window::theme`.
- Enabled `doc_auto_cfg` when generating docs on docs.rs for feature labels.
- **Breaking:** Added `WindowEvent::CanRedrawFrame` event as a frame throttling hint event to help driving rendering loop.
- Added `Window::request_frame_throttling_hint` which can be used to request frame throtting hints from the system compositor, currently implemented on X11.

# 0.27.5

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], opt
sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.5.1", default_features = false, optional = true }
mio = { version = "0.8", features = ["os-ext"], optional = true }
x11-dl = { version = "2.18.5", optional = true }
x11-dl = { version = "2.20.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
libc = "0.2.64"

Expand Down
14 changes: 14 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,18 @@ pub enum WindowEvent<'a> {
/// Platform-specific behavior:
/// - **iOS / Android / Web / Wayland / Windows:** Unsupported.
Occluded(bool),

/// A redraw throttling hint which is sent by the system compositor when the redraw operation
/// should be performed. This event is useful when you want contiguous non-blocking drawing
/// at the refresh rate of the monitor without using `Vsync` extension and relying on a timer
/// based rendering.
///
/// The only way to obtain it is by using [`Window::request_frame_throttling_hint`].
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Wayland / Windows:** Unsupported.
CanRedrawFrame,
}

impl Clone for WindowEvent<'static> {
Expand Down Expand Up @@ -623,6 +635,7 @@ impl Clone for WindowEvent<'static> {
unreachable!("Static event can't be about scale factor changing")
}
Occluded(occluded) => Occluded(*occluded),
CanRedrawFrame => CanRedrawFrame,
};
}
}
Expand Down Expand Up @@ -727,6 +740,7 @@ impl<'a> WindowEvent<'a> {
ThemeChanged(theme) => Some(ThemeChanged(theme)),
ScaleFactorChanged { .. } => None,
Occluded(occluded) => Some(Occluded(occluded)),
CanRedrawFrame => Some(CanRedrawFrame),
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,11 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.request_redraw())
}

#[inline]
pub fn request_frame_throttling_hint(&self) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.request_frame_throttling_hint())
}

#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
match self {
Expand Down
5 changes: 5 additions & 0 deletions src/platform_impl/linux/wayland/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ impl Window {
self.send_request(WindowRequest::Redraw);
}

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

#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
self.size
Expand Down
29 changes: 28 additions & 1 deletion src/platform_impl/linux/x11/event_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) ime_receiver: ImeReceiver,
pub(super) ime_event_receiver: ImeEventReceiver,
pub(super) randr_event_offset: c_int,
pub(super) xpresent_event_offset: Option<c_int>,
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
pub(super) xi2ext: XExtension,
pub(super) target: Rc<RootELW<T>>,
Expand Down Expand Up @@ -653,7 +654,31 @@ impl<T: 'static> EventProcessor<T> {
} else {
return;
};

let xev = &guard.cookie;

// Handle xpresent.
if self.xpresent_event_offset == Some(xev.extension)
&& xev.evtype == ffi::PresentCompleteNotify
{
let xev: &ffi::XPresentCompleteNotifyEvent =
unsafe { &*(xev.data as *const _) };

let window_id = mkwid(xev.window);
if wt.windows.borrow().contains_key(&window_id.0) {
// Remove the event input.
let _ = wt
.xconn
.xpresent_free_input(xev.window, xev.eid as ffi::XID);

callback(Event::WindowEvent {
window_id,
event: crate::event::WindowEvent::CanRedrawFrame,
});
}
}

// Handle input.
if self.xi2ext.opcode != xev.extension {
return;
}
Expand Down Expand Up @@ -1194,7 +1219,9 @@ impl<T: 'static> EventProcessor<T> {
}
}

_ => {}
_ => {
println!("Here")
}
}
}
_ => {
Expand Down
2 changes: 1 addition & 1 deletion src/platform_impl/linux/x11/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use x11_dl::xmd::CARD32;
pub use x11_dl::{
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
xrandr::*, xrender::*,
xpresent::*, xrandr::*, xrender::*,
};

// Isn't defined by x11_dl
Expand Down
3 changes: 3 additions & 0 deletions src/platform_impl/linux/x11/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ impl<T: 'static> EventLoop<T> {
.select_xrandr_input(root)
.expect("Failed to query XRandR extension");

let xpresent_event_offset = xconn.xperest_event_offset();

let xi2ext = unsafe {
let mut ext = XExtension::default();

Expand Down Expand Up @@ -266,6 +268,7 @@ impl<T: 'static> EventLoop<T> {
dnd,
devices: Default::default(),
randr_event_offset,
xpresent_event_offset,
ime_receiver,
ime_event_receiver,
xi2ext,
Expand Down
1 change: 1 addition & 0 deletions src/platform_impl/linux/x11/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod input;
pub mod keys;
mod memory;
pub mod modifiers;
mod present;
mod randr;
mod window_property;
mod wm;
Expand Down
78 changes: 78 additions & 0 deletions src/platform_impl/linux/x11/util/present.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use std::os::raw::{c_int, c_uint};

use crate::error::{ExternalError, NotSupportedError};
use crate::platform_impl::OsError;

use super::ffi::{PresentCompleteNotifyMask, True, Window, XID};
use super::XConnection;

impl XConnection {
pub fn xpresent_select_input(&self, window: Window) -> Result<(), ExternalError> {
let xpresent = match self.xpresent.as_ref() {
Some(xpresent) => xpresent,
None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
};

let mask = PresentCompleteNotifyMask as c_uint;
unsafe {
let _ = (xpresent.XPresentSelectInput)(self.display, window, mask);
}

if let Err(err) = self.check_errors() {
Err(ExternalError::Os(os_error!(OsError::XError(err))))
} else {
Ok(())
}
}

pub fn xpresent_free_input(
&self,
window: Window,
event_id: XID,
) -> Result<(), NotSupportedError> {
let xpresent = match self.xpresent.as_ref() {
Some(xpresent) => xpresent,
None => return Err(NotSupportedError::new()),
};

unsafe {
(xpresent.XPresentFreeInput)(self.display, window, event_id);
}

// Drain errors.
let _ = self.check_errors();

Ok(())
}

pub fn xperest_event_offset(&self) -> Option<c_int> {
let xpresent = self.xpresent.as_ref()?;
unsafe {
let mut major = 0;
let mut minor = 0;
if (xpresent.XPresentQueryVersion)(self.display, &mut major, &mut minor) != True as i32
{
return None;
}
}

unsafe {
let mut event_offset = 0;
let mut error_offest = 0;
let mut dummy = 0;

// FIXME(kchibisov) the number of arguments is wrong, should fix upstream here.
if (xpresent.XPresentQueryExtension)(
self.display,
&mut event_offset,
&mut error_offest,
&mut dummy,
) != True as i32
{
None
} else {
Some(event_offset)
}
}
}
}
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 @@ -1532,6 +1532,11 @@ impl UnownedWindow {
self.redraw_sender.waker.wake().unwrap();
}

#[inline]
pub fn request_frame_throttling_hint(&self) -> Result<(), ExternalError> {
self.xconn.xpresent_select_input(self.xwindow)
}

#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = XlibWindowHandle::empty();
Expand Down
9 changes: 9 additions & 0 deletions src/platform_impl/linux/x11/xdisplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub struct XConnection {
pub xrandr: ffi::Xrandr_2_2_0,
/// Exposes XRandR functions from version = 1.5
pub xrandr_1_5: Option<ffi::Xrandr>,
/// Exposes XPresent.
pub xpresent: Option<ffi::Xpresent>,
pub xcursor: ffi::Xcursor,
pub xinput2: ffi::XInput2,
pub xlib_xcb: ffi::Xlib_xcb,
Expand All @@ -34,6 +36,12 @@ impl XConnection {
let xcursor = ffi::Xcursor::open()?;
let xrandr = ffi::Xrandr_2_2_0::open()?;
let xrandr_1_5 = ffi::Xrandr::open().ok();

let xpresent = ffi::Xpresent::open().ok();
if xpresent.is_none() {
log::warn!("lixXpresent is missing; requesting frame hints is not supported.");
}

let xinput2 = ffi::XInput2::open()?;
let xlib_xcb = ffi::Xlib_xcb::open()?;
let xrender = ffi::Xrender::open()?;
Expand Down Expand Up @@ -61,6 +69,7 @@ impl XConnection {
xinput2,
xlib_xcb,
xrender,
xpresent,
display,
x11_fd: fd,
latest_error: Mutex::new(None),
Expand Down
44 changes: 44 additions & 0 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,50 @@ impl Window {
pub fn request_redraw(&self) {
self.window.request_redraw()
}

/// Requests [`WindowEvent::CanRedrawFrame`] from the system compositor.
///
/// This method is **strongly recommended** to perform non-blocking,
/// contiguous drawing syncing to the monitor refresh rate. Usually system
/// compositor sends this event right after the vblank in responce to the
/// previously presented window via e.g `eglSwapBuffers`, meaning that
/// chances to miss vblank is very low if you drive your render loop with
/// this event.
///
/// **Note:** The major caveat with this method is that you **must draw**
/// right after requesting the hint, otherwise the event has chance of not
/// being delivered at all or being sent in not desired time.
///
/// An example of how the drawing loop could be approached.
///
/// ```no_run
/// // On the first iteration the `got_frame_hint` is `true`. And set back
/// // to `true` when getting `CanRedrawFrame` event.
/// if got_frame_hint {
/// window.request_frame_throttling_hint();
/// got_frame_hint = false;
///
/// // Present window on-screen with EGL.
/// egl_swap_buffers(&window);
/// }
/// ```
///
/// The `EGL` here is used as an example, any system Api that is presenting
/// to the screen should work, unless you're doing single buffer rendering
/// meaning that you should somehow tell the system that you actually drawn
/// into the screen.
///
/// # Platform-specific:
///
/// **X11:** - libXpresent is required.
/// ** Wayland / macOS / Windows / Web:** Not implemented.
/// ** iOS / Android:** Always returs [`NotSupportedError`].
///
/// [`WindowEvent::CanRedrawFrame`]: crate::event::WindowEvent::CanRedrawFrame
#[inline]
pub fn request_frame_throttling_hint(&self) -> Result<(), ExternalError> {
self.window.request_frame_throttling_hint()
}
}

/// Position and size functions.
Expand Down