diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b745bc148..ec02192afc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 40f56ffa55..309982a93a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/event.rs b/src/event.rs index da237672c7..e3fe473382 100644 --- a/src/event.rs +++ b/src/event.rs @@ -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> { @@ -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, }; } } @@ -727,6 +740,7 @@ impl<'a> WindowEvent<'a> { ThemeChanged(theme) => Some(ThemeChanged(theme)), ScaleFactorChanged { .. } => None, Occluded(occluded) => Some(Occluded(occluded)), + CanRedrawFrame => Some(CanRedrawFrame), } } } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 4e5f7610cf..563d786b56 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -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 { match self { diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 18b84afa2a..96b6395785 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -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 { self.size diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index a2a3c15f00..b9d6e572a9 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -28,6 +28,7 @@ pub(super) struct EventProcessor { pub(super) ime_receiver: ImeReceiver, pub(super) ime_event_receiver: ImeEventReceiver, pub(super) randr_event_offset: c_int, + pub(super) xpresent_event_offset: Option, pub(super) devices: RefCell>, pub(super) xi2ext: XExtension, pub(super) target: Rc>, @@ -653,7 +654,31 @@ impl EventProcessor { } 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; } @@ -1194,7 +1219,9 @@ impl EventProcessor { } } - _ => {} + _ => { + println!("Here") + } } } _ => { diff --git a/src/platform_impl/linux/x11/ffi.rs b/src/platform_impl/linux/x11/ffi.rs index 8027e56978..19453a9780 100644 --- a/src/platform_impl/linux/x11/ffi.rs +++ b/src/platform_impl/linux/x11/ffi.rs @@ -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 diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index c34eb2d460..a85eae270c 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -188,6 +188,8 @@ impl EventLoop { .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(); @@ -266,6 +268,7 @@ impl EventLoop { dnd, devices: Default::default(), randr_event_offset, + xpresent_event_offset, ime_receiver, ime_event_receiver, xi2ext, diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index df3d89138a..93847f144f 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -12,6 +12,7 @@ mod input; pub mod keys; mod memory; pub mod modifiers; +mod present; mod randr; mod window_property; mod wm; diff --git a/src/platform_impl/linux/x11/util/present.rs b/src/platform_impl/linux/x11/util/present.rs new file mode 100644 index 0000000000..184dee9222 --- /dev/null +++ b/src/platform_impl/linux/x11/util/present.rs @@ -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 { + 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) + } + } + } +} diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 2c600a81be..facf34a45c 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -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(); diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index 506e9b8249..7bdc42ac6f 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -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, + /// Exposes XPresent. + pub xpresent: Option, pub xcursor: ffi::Xcursor, pub xinput2: ffi::XInput2, pub xlib_xcb: ffi::Xlib_xcb, @@ -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()?; @@ -61,6 +69,7 @@ impl XConnection { xinput2, xlib_xcb, xrender, + xpresent, display, x11_fd: fd, latest_error: Mutex::new(None), diff --git a/src/window.rs b/src/window.rs index 65e652d8b3..b4387cbf36 100644 --- a/src/window.rs +++ b/src/window.rs @@ -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.