From 544f23ffb0622c21bc596146b5a2a55b6dd8921e Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 23 Mar 2024 08:58:35 +0100 Subject: [PATCH 01/15] xwayland: fix logging of stderr --- src/portal.rs | 31 +++++++++---------------------- src/utils.rs | 1 + src/utils/line_logger.rs | 36 ++++++++++++++++++++++++++++++++++++ src/xwayland.rs | 31 +++++++------------------------ 4 files changed, 53 insertions(+), 46 deletions(-) create mode 100644 src/utils/line_logger.rs diff --git a/src/portal.rs b/src/portal.rs index c66f6cfe..dc86725a 100644 --- a/src/portal.rs +++ b/src/portal.rs @@ -21,15 +21,14 @@ use { ptl_screencast::{add_screencast_dbus_members, ScreencastSession}, }, utils::{ - buf::Buf, clone3::{fork_with_pidfd, Forked}, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, + line_logger::log_lines, numcell::NumCell, oserror::OsError, process_name::set_process_name, run_toplevel::RunToplevel, - vecdeque_ext::VecDequeExt, xrd::xrd, }, video::dmabuf::DmaBufIds, @@ -38,7 +37,6 @@ use { }, log::Level, std::{ - collections::VecDeque, rc::{Rc, Weak}, sync::Arc, }, @@ -104,25 +102,14 @@ impl PortalStartup { let ring = ring.clone(); let logger = logger.clone(); async move { - let mut buf = VecDeque::::new(); - let mut buf2 = Buf::new(1024); - let mut done = false; - while !done { - match ring.read(&self.logs, buf2.clone()).await { - Ok(n) if n > 0 => buf.extend(&buf2[..n]), - Ok(_) => done = true, - Err(e) => { - log::error!("Could not read portal logs: {}", ErrorFmt(e)); - return; - } - }; - while let Some(pos) = buf.iter().position(|b| b == &b'\n') { - let (left, right) = buf.get_slices(..pos); - logger.write_raw(left); - logger.write_raw(right); - logger.write_raw(b" (portal)\n"); - buf.drain(..=pos); - } + let res = log_lines(&ring, &self.logs, |left, right| { + logger.write_raw(left); + logger.write_raw(right); + logger.write_raw(b" (portal)\n"); + }) + .await; + if let Err(e) = res { + log::error!("Could not read portal logs: {}", ErrorFmt(e)); } } }); diff --git a/src/utils.rs b/src/utils.rs index 5764751b..6dde581e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -16,6 +16,7 @@ pub mod errorfmt; pub mod fdcloser; pub mod gfx_api_ext; pub mod hex; +pub mod line_logger; pub mod linkedlist; pub mod log_on_drop; pub mod mmap; diff --git a/src/utils/line_logger.rs b/src/utils/line_logger.rs new file mode 100644 index 00000000..91c75d42 --- /dev/null +++ b/src/utils/line_logger.rs @@ -0,0 +1,36 @@ +use { + crate::{ + io_uring::{IoUring, IoUringError}, + utils::{buf::Buf, vecdeque_ext::VecDequeExt}, + }, + isnt::std_1::collections::IsntVecDequeExt, + std::{collections::VecDeque, rc::Rc}, + uapi::OwnedFd, +}; + +pub async fn log_lines( + ring: &IoUring, + fd: &Rc, + mut f: impl FnMut(&[u8], &[u8]), +) -> Result<(), IoUringError> { + let mut buf = VecDeque::::new(); + let mut buf2 = Buf::new(1024); + let mut done = false; + while !done { + let n = ring.read(fd, buf2.clone()).await?; + buf.extend(&buf2[..n]); + if n == 0 { + done = true; + } + while let Some(pos) = buf.iter().position(|b| b == &b'\n') { + let (left, right) = buf.get_slices(..pos); + f(left, right); + buf.drain(..=pos); + } + } + if buf.is_not_empty() { + let (left, right) = buf.as_slices(); + f(left, right); + } + Ok(()) +} diff --git a/src/xwayland.rs b/src/xwayland.rs index af43e885..e98199f8 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -18,7 +18,7 @@ use { io_uring::IoUringError, state::State, user_session::import_environment, - utils::{buf::Buf, errorfmt::ErrorFmt, oserror::OsError}, + utils::{errorfmt::ErrorFmt, line_logger::log_lines, oserror::OsError}, wire::WlSurfaceId, xcon::XconError, xwayland::{ @@ -217,29 +217,12 @@ pub fn build_args() -> (String, Vec) { async fn log_xwayland(state: Rc, stderr: OwnedFd) { let stderr = Rc::new(stderr); - let mut buf = vec![]; - let mut buf2 = Buf::new(128); - let mut done = false; - while !done { - loop { - match state.ring.read(&stderr, buf2.clone()).await { - Ok(n) if n > 0 => { - buf.extend_from_slice(&buf2[..n]); - } - Ok(_) => { - done = true; - break; - } - Err(e) => { - log::error!("Could not read from stderr fd: {}", ErrorFmt(e)); - return; - } - } - } - for line in buf.lines() { - log::info!("Xwayland: {}", line.as_bstr()); - } - buf.clear(); + let res = log_lines(&state.ring, &stderr, |left, right| { + log::info!("Xwayland: {}{}", left.as_bstr(), right.as_bstr()); + }) + .await; + if let Err(e) = res { + log::error!("Could not read from stderr fd: {}", ErrorFmt(e)); } } From 84e6b748936dda51bfaffa733251d995bb97434e Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 23 Mar 2024 00:39:15 +0100 Subject: [PATCH 02/15] all: address clippy lints --- build/wire_xcon.rs | 2 +- src/gfx_apis/vulkan/instance.rs | 3 +-- toml-config/src/toml/toml_lexer.rs | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/build/wire_xcon.rs b/build/wire_xcon.rs index 59fbac2b..f4a82486 100644 --- a/build/wire_xcon.rs +++ b/build/wire_xcon.rs @@ -813,7 +813,7 @@ struct Extension { #[derive(Debug)] enum NamedType { Struct(Rc), - Bitmask(Rc), + Bitmask(#[allow(dead_code)] Rc), Enum(Rc), } diff --git a/src/gfx_apis/vulkan/instance.rs b/src/gfx_apis/vulkan/instance.rs index e64a7453..56985968 100644 --- a/src/gfx_apis/vulkan/instance.rs +++ b/src/gfx_apis/vulkan/instance.rs @@ -146,8 +146,7 @@ impl Drop for VulkanInstance { const REQUIRED_INSTANCE_EXTENSIONS: &[&CStr] = &[ExtDebugUtilsFn::name()]; -const VALIDATION_LAYER: &CStr = - unsafe { CStr::from_bytes_with_nul_unchecked(b"VK_LAYER_KHRONOS_validation\0") }; +const VALIDATION_LAYER: &CStr = c"VK_LAYER_KHRONOS_validation"; pub type Extensions = AHashMap; diff --git a/toml-config/src/toml/toml_lexer.rs b/toml-config/src/toml/toml_lexer.rs index 9ff85dc2..a227fc12 100644 --- a/toml-config/src/toml/toml_lexer.rs +++ b/toml-config/src/toml/toml_lexer.rs @@ -96,9 +96,7 @@ impl<'a> Lexer<'a> { self.skip_ws(); - let Some(c) = get!(0) else { - return None; - }; + let c = get!(0)?; let pos = self.pos; macro_rules! span { From 86d37b5aa4a2bfad15ad14b06be211461e06a76f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 20 Mar 2024 18:45:18 +0100 Subject: [PATCH 03/15] surface: remove interior mutability from pending state --- src/ifs/wl_surface.rs | 90 +++++++++---------- src/ifs/wl_surface/wp_tearing_control_v1.rs | 4 +- src/ifs/wl_surface/wp_viewport.rs | 9 +- .../x_surface/xwayland_surface_v1.rs | 2 +- 4 files changed, 53 insertions(+), 52 deletions(-) diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 47d9a209..c34d51d2 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -133,7 +133,7 @@ pub struct WlSurface { pub client: Rc, visible: Cell, role: Cell, - pending: PendingState, + pending: RefCell, input_region: CloneCell>>, opaque_region: Cell>>, buffer_points: RefCell, @@ -258,20 +258,20 @@ impl SurfaceExt for NoneSurfaceExt { #[derive(Default)] struct PendingState { - buffer: Cell>>>, - offset: Cell<(i32, i32)>, - opaque_region: Cell>>>, - input_region: Cell>>>, - frame_request: RefCell>>, - damage: Cell, - presentation_feedback: RefCell>>, - src_rect: Cell>>, - dst_size: Cell>>, - scale: Cell>, - transform: Cell>, - xwayland_serial: Cell>, - tearing: Cell>, - content_type: Cell>>, + buffer: Option>>, + offset: (i32, i32), + opaque_region: Option>>, + input_region: Option>>, + frame_request: Vec>, + damage: bool, + presentation_feedback: Vec>, + src_rect: Option>, + dst_size: Option>, + scale: Option, + transform: Option, + xwayland_serial: Option, + tearing: Option, + content_type: Option>, } #[derive(Default)] @@ -400,8 +400,8 @@ impl WlSurface { pub fn add_presentation_feedback(&self, fb: &Rc) { self.pending - .presentation_feedback .borrow_mut() + .presentation_feedback .push(fb.clone()); } @@ -591,25 +591,26 @@ impl WlSurface { fn attach(self: &Rc, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { let req: Attach = self.parse(parser)?; + let pending = &mut *self.pending.borrow_mut(); if self.version >= OFFSET_SINCE { if req.x != 0 || req.y != 0 { return Err(WlSurfaceError::OffsetInAttach); } } else { - self.pending.offset.set((req.x, req.y)); + pending.offset = (req.x, req.y); } let buf = if req.buffer.is_some() { Some(self.client.lookup(req.buffer)?) } else { None }; - self.pending.buffer.set(Some(buf)); + pending.buffer = Some(buf); Ok(()) } fn damage(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { let _req: Damage = self.parse(parser)?; - self.pending.damage.set(true); + self.pending.borrow_mut().damage = true; Ok(()) } @@ -618,7 +619,7 @@ impl WlSurface { let cb = Rc::new(WlCallback::new(req.callback, &self.client)); track!(self.client, cb); self.client.add_client_obj(&cb)?; - self.pending.frame_request.borrow_mut().push(cb); + self.pending.borrow_mut().frame_request.push(cb); Ok(()) } @@ -629,7 +630,7 @@ impl WlSurface { } else { None }; - self.pending.opaque_region.set(Some(region)); + self.pending.borrow_mut().opaque_region = Some(region); Ok(()) } @@ -640,7 +641,7 @@ impl WlSurface { } else { None }; - self.pending.input_region.set(Some(region)); + self.pending.borrow_mut().input_region = Some(region); Ok(()) } @@ -657,22 +658,23 @@ impl WlSurface { } } } + let pending = &mut *self.pending.borrow_mut(); let mut scale_changed = false; - if let Some(scale) = self.pending.scale.take() { + if let Some(scale) = pending.scale.take() { scale_changed = true; self.buffer_scale.set(scale); } let mut buffer_transform_changed = false; - if let Some(transform) = self.pending.transform.take() { + if let Some(transform) = pending.transform.take() { buffer_transform_changed = true; self.buffer_transform.set(transform); } let mut viewport_changed = false; - if let Some(dst_size) = self.pending.dst_size.take() { + if let Some(dst_size) = pending.dst_size.take() { viewport_changed = true; self.dst_size.set(dst_size); } - if let Some(src_rect) = self.pending.src_rect.take() { + if let Some(src_rect) = pending.src_rect.take() { viewport_changed = true; self.src_rect.set(src_rect); } @@ -687,8 +689,8 @@ impl WlSurface { } let mut buffer_changed = false; let mut old_raw_size = None; - let (dx, dy) = self.pending.offset.take(); - if let Some(buffer_change) = self.pending.buffer.take() { + let (dx, dy) = mem::take(&mut pending.offset); + if let Some(buffer_change) = pending.buffer.take() { buffer_changed = true; if let Some(buffer) = self.buffer.take() { old_raw_size = Some(buffer.rect); @@ -806,34 +808,32 @@ impl WlSurface { self.buffer_abs_pos .set(self.buffer_abs_pos.get().with_size(width, height).unwrap()); } - { - let mut pfr = self.pending.frame_request.borrow_mut(); - self.frame_requests.borrow_mut().extend(pfr.drain(..)); - } + self.frame_requests + .borrow_mut() + .extend(pending.frame_request.drain(..)); { let mut fbs = self.presentation_feedback.borrow_mut(); for fb in fbs.drain(..) { fb.send_discarded(); let _ = self.client.remove_obj(&*fb); } - let mut pfbs = self.pending.presentation_feedback.borrow_mut(); - mem::swap(fbs.deref_mut(), pfbs.deref_mut()); + mem::swap(fbs.deref_mut(), &mut pending.presentation_feedback); } { - if let Some(region) = self.pending.input_region.take() { + if let Some(region) = pending.input_region.take() { self.input_region.set(region); } - if let Some(region) = self.pending.opaque_region.take() { + if let Some(region) = pending.opaque_region.take() { self.opaque_region.set(region); } } - if let Some(tearing) = self.pending.tearing.take() { + if let Some(tearing) = pending.tearing.take() { self.tearing.set(tearing); } - if let Some(content_type) = self.pending.content_type.take() { + if let Some(content_type) = pending.content_type.take() { self.content_type.set(content_type); } - if let Some(xwayland_serial) = self.pending.xwayland_serial.take() { + if let Some(xwayland_serial) = pending.xwayland_serial.take() { self.xwayland_serial.set(Some(xwayland_serial)); self.client .surfaces_by_xwayland_serial @@ -869,7 +869,7 @@ impl WlSurface { let Some(tf) = Transform::from_wl(req.transform) else { return Err(WlSurfaceError::UnknownBufferTransform(req.transform)); }; - self.pending.transform.set(Some(tf)); + self.pending.borrow_mut().transform = Some(tf); Ok(()) } @@ -878,19 +878,19 @@ impl WlSurface { if req.scale < 1 { return Err(WlSurfaceError::NonPositiveBufferScale); } - self.pending.scale.set(Some(req.scale)); + self.pending.borrow_mut().scale = Some(req.scale); Ok(()) } fn damage_buffer(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { let _req: DamageBuffer = self.parse(parser)?; - self.pending.damage.set(true); + self.pending.borrow_mut().damage = true; Ok(()) } fn offset(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { let req: Offset = self.parse(parser)?; - self.pending.offset.set((req.x, req.y)); + self.pending.borrow_mut().offset = (req.x, req.y); Ok(()) } @@ -1021,7 +1021,7 @@ impl WlSurface { } pub fn set_content_type(&self, content_type: Option) { - self.pending.content_type.set(Some(content_type)); + self.pending.borrow_mut().content_type = Some(content_type); } pub fn request_activation(&self) { @@ -1064,7 +1064,7 @@ impl Object for WlSurface { self.buffer.set(None); self.toplevel.set(None); self.idle_inhibitors.clear(); - self.pending.presentation_feedback.borrow_mut().clear(); + mem::take(self.pending.borrow_mut().deref_mut()); self.presentation_feedback.borrow_mut().clear(); self.viewporter.take(); self.fractional_scale.take(); diff --git a/src/ifs/wl_surface/wp_tearing_control_v1.rs b/src/ifs/wl_surface/wp_tearing_control_v1.rs index 0e7f2fa1..7e90f388 100644 --- a/src/ifs/wl_surface/wp_tearing_control_v1.rs +++ b/src/ifs/wl_surface/wp_tearing_control_v1.rs @@ -39,13 +39,13 @@ impl WpTearingControlV1 { ASYNC => true, _ => return Err(WpTearingControlV1Error::UnknownPresentationHint(req.hint)), }; - self.surface.pending.tearing.set(Some(tearing)); + self.surface.pending.borrow_mut().tearing = Some(tearing); Ok(()) } fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WpTearingControlV1Error> { let _req: Destroy = self.surface.client.parse(self, parser)?; - self.surface.pending.tearing.set(Some(false)); + self.surface.pending.borrow_mut().tearing = Some(false); self.surface.tearing_control.take(); self.surface.client.remove_obj(self)?; Ok(()) diff --git a/src/ifs/wl_surface/wp_viewport.rs b/src/ifs/wl_surface/wp_viewport.rs index 8ad67789..f7945fcf 100644 --- a/src/ifs/wl_surface/wp_viewport.rs +++ b/src/ifs/wl_surface/wp_viewport.rs @@ -38,8 +38,9 @@ impl WpViewport { fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), WpViewportError> { let _req: Destroy = self.client.parse(self, msg)?; - self.surface.pending.src_rect.set(Some(None)); - self.surface.pending.dst_size.set(Some(None)); + let pending = &mut *self.surface.pending.borrow_mut(); + pending.src_rect = Some(None); + pending.dst_size = Some(None); self.surface.viewporter.take(); self.client.remove_obj(self)?; Ok(()) @@ -56,7 +57,7 @@ impl WpViewport { } Some([req.x, req.y, req.width, req.height]) }; - self.surface.pending.src_rect.set(Some(rect)); + self.surface.pending.borrow_mut().src_rect = Some(rect); Ok(()) } @@ -69,7 +70,7 @@ impl WpViewport { } else { Some((req.width, req.height)) }; - self.surface.pending.dst_size.set(Some(size)); + self.surface.pending.borrow_mut().dst_size = Some(size); Ok(()) } } diff --git a/src/ifs/wl_surface/x_surface/xwayland_surface_v1.rs b/src/ifs/wl_surface/x_surface/xwayland_surface_v1.rs index 7c9958b7..70a391b3 100644 --- a/src/ifs/wl_surface/x_surface/xwayland_surface_v1.rs +++ b/src/ifs/wl_surface/x_surface/xwayland_surface_v1.rs @@ -32,7 +32,7 @@ impl XwaylandSurfaceV1 { return Err(XwaylandSurfaceV1Error::NonMonotonicSerial); } self.client.last_xwayland_serial.set(serial); - self.x.surface.pending.xwayland_serial.set(Some(serial)); + self.x.surface.pending.borrow_mut().xwayland_serial = Some(serial); Ok(()) } From 943626a7f774f9968cb5bf32bd9a04d0feb6b6f0 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 20 Mar 2024 19:21:40 +0100 Subject: [PATCH 04/15] surface: split ext::pre_commit into two parts --- src/ifs/wl_surface.rs | 24 +++++++++++++++------ src/ifs/wl_surface/wl_subsurface.rs | 12 +++++------ src/ifs/wl_surface/x_surface.rs | 4 ++-- src/ifs/wl_surface/xdg_surface.rs | 12 ++++++----- src/ifs/wl_surface/zwlr_layer_surface_v1.rs | 13 +++++------ 5 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index c34d51d2..48bd28d2 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -198,13 +198,22 @@ enum CommitAction { } trait SurfaceExt { - fn pre_commit(self: Rc, ctx: CommitContext) -> Result { + fn prepare_commit(&self, ctx: CommitContext, pending: &mut PendingState) -> CommitAction { let _ = ctx; - Ok(CommitAction::ContinueCommit) + let _ = pending; + CommitAction::ContinueCommit } - fn post_commit(self: Rc) { - // nothing + fn before_apply_commit( + self: Rc, + pending: &mut PendingState, + ) -> Result<(), WlSurfaceError> { + let _ = pending; + Ok(()) + } + + fn after_apply_commit(self: Rc, pending: &mut PendingState) { + let _ = pending; } fn is_some(&self) -> bool { @@ -647,9 +656,11 @@ impl WlSurface { fn do_commit(self: &Rc, ctx: CommitContext) -> Result<(), WlSurfaceError> { let ext = self.ext.get(); - if ext.clone().pre_commit(ctx)? == CommitAction::AbortCommit { + let pending = &mut *self.pending.borrow_mut(); + if ext.prepare_commit(ctx, pending) == CommitAction::AbortCommit { return Ok(()); } + ext.clone().before_apply_commit(pending)?; { let children = self.children.borrow(); if let Some(children) = children.deref() { @@ -658,7 +669,6 @@ impl WlSurface { } } } - let pending = &mut *self.pending.borrow_mut(); let mut scale_changed = false; if let Some(scale) = pending.scale.take() { scale_changed = true; @@ -853,7 +863,7 @@ impl WlSurface { cursor.update_hardware_cursor(); } } - ext.post_commit(); + ext.after_apply_commit(pending); self.client.state.damage(); Ok(()) } diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index f1fce993..c54a6a29 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -2,8 +2,8 @@ use { crate::{ client::ClientError, ifs::wl_surface::{ - CommitAction, CommitContext, StackElement, SurfaceExt, SurfaceRole, WlSurface, - WlSurfaceError, WlSurfaceId, + CommitAction, CommitContext, PendingState, StackElement, SurfaceExt, SurfaceRole, + WlSurface, WlSurfaceError, WlSurfaceId, }, leaks::Tracker, object::Object, @@ -264,15 +264,15 @@ impl Object for WlSubsurface { simple_add_obj!(WlSubsurface); impl SurfaceExt for WlSubsurface { - fn pre_commit(self: Rc, ctx: CommitContext) -> Result { + fn prepare_commit(&self, ctx: CommitContext, _pending: &mut PendingState) -> CommitAction { if ctx == CommitContext::RootCommit && self.sync() { log::debug!("Aborting commit due to sync"); - return Ok(CommitAction::AbortCommit); + return CommitAction::AbortCommit; } - Ok(CommitAction::ContinueCommit) + CommitAction::ContinueCommit } - fn post_commit(self: Rc) { + fn after_apply_commit(self: Rc, _pending: &mut PendingState) { if let Some(v) = self.pending.node.take() { v.pending.set(false); self.node.borrow_mut().replace(v); diff --git a/src/ifs/wl_surface/x_surface.rs b/src/ifs/wl_surface/x_surface.rs index 6fed1b2d..3da57314 100644 --- a/src/ifs/wl_surface/x_surface.rs +++ b/src/ifs/wl_surface/x_surface.rs @@ -2,7 +2,7 @@ use { crate::{ ifs::wl_surface::{ x_surface::{xwayland_surface_v1::XwaylandSurfaceV1, xwindow::Xwindow}, - SurfaceExt, WlSurface, WlSurfaceError, + PendingState, SurfaceExt, WlSurface, WlSurfaceError, }, leaks::Tracker, tree::ToplevelNode, @@ -23,7 +23,7 @@ pub struct XSurface { } impl SurfaceExt for XSurface { - fn post_commit(self: Rc) { + fn after_apply_commit(self: Rc, _pending: &mut PendingState) { if let Some(xwindow) = self.xwindow.get() { xwindow.map_status_changed(); } diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index 379d01dc..83fbb4e7 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -10,7 +10,7 @@ use { xdg_popup::{XdgPopup, XdgPopupError}, xdg_toplevel::{XdgToplevel, WM_CAPABILITIES_SINCE}, }, - CommitAction, CommitContext, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError, + PendingState, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError, }, xdg_wm_base::XdgWmBase, }, @@ -357,7 +357,10 @@ impl Object for XdgSurface { dedicated_add_obj!(XdgSurface, XdgSurfaceId, xdg_surfaces); impl SurfaceExt for XdgSurface { - fn pre_commit(self: Rc, _ctx: CommitContext) -> Result { + fn before_apply_commit( + self: Rc, + _pending: &mut PendingState, + ) -> Result<(), WlSurfaceError> { { let ase = self.acked_serial.get(); let rse = self.requested_serial.get(); @@ -368,17 +371,16 @@ impl SurfaceExt for XdgSurface { } self.send_configure(rse); } - // return CommitAction::AbortCommit; } } if let Some(geometry) = self.pending.geometry.take() { self.geometry.set(Some(geometry)); self.update_extents(); } - Ok(CommitAction::ContinueCommit) + Ok(()) } - fn post_commit(self: Rc) { + fn after_apply_commit(self: Rc, _pending: &mut PendingState) { if let Some(ext) = self.ext.get() { ext.post_commit(); } diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index 38f04daa..4eaf585d 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -3,9 +3,7 @@ use { client::{Client, ClientError}, ifs::{ wl_seat::NodeSeatState, - wl_surface::{ - CommitAction, CommitContext, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError, - }, + wl_surface::{PendingState, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError}, zwlr_layer_shell_v1::{ZwlrLayerShellV1, OVERLAY}, }, leaks::Tracker, @@ -313,12 +311,15 @@ impl ZwlrLayerSurfaceV1 { } impl SurfaceExt for ZwlrLayerSurfaceV1 { - fn pre_commit(self: Rc, _ctx: CommitContext) -> Result { + fn before_apply_commit( + self: Rc, + _pending: &mut PendingState, + ) -> Result<(), WlSurfaceError> { self.deref().pre_commit()?; - Ok(CommitAction::ContinueCommit) + Ok(()) } - fn post_commit(self: Rc) { + fn after_apply_commit(self: Rc, _pending: &mut PendingState) { let buffer = self.surface.buffer.get(); if self.mapped.get() { if buffer.is_none() { From 0c48247740a6c3fe4411eee991fd424326c66b5e Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 20 Mar 2024 19:26:29 +0100 Subject: [PATCH 05/15] surface: move pending state into the surface --- src/ifs/wl_surface.rs | 14 +++- src/ifs/wl_surface/wl_subsurface.rs | 48 ++++++----- src/ifs/wl_surface/xdg_surface.rs | 31 ++++--- src/ifs/wl_surface/zwlr_layer_surface_v1.rs | 89 ++++++++++++--------- 4 files changed, 108 insertions(+), 74 deletions(-) diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 48bd28d2..12c3e27a 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -25,11 +25,14 @@ use { NodeSeatState, SeatId, WlSeatGlobal, }, wl_surface::{ - cursor::CursorSurface, wl_subsurface::WlSubsurface, + cursor::CursorSurface, + wl_subsurface::{PendingSubsurfaceData, WlSubsurface}, wp_fractional_scale_v1::WpFractionalScaleV1, - wp_tearing_control_v1::WpTearingControlV1, wp_viewport::WpViewport, - x_surface::XSurface, xdg_surface::XdgSurfaceError, - zwlr_layer_surface_v1::ZwlrLayerSurfaceV1Error, + wp_tearing_control_v1::WpTearingControlV1, + wp_viewport::WpViewport, + x_surface::XSurface, + xdg_surface::{PendingXdgSurfaceData, XdgSurfaceError}, + zwlr_layer_surface_v1::{PendingLayerSurfaceData, ZwlrLayerSurfaceV1Error}, }, wp_content_type_v1::ContentType, wp_presentation_feedback::WpPresentationFeedback, @@ -281,6 +284,9 @@ struct PendingState { xwayland_serial: Option, tearing: Option, content_type: Option>, + subsurface: Option>, + xdg_surface: Option>, + layer_surface: Option>, } #[derive(Default)] diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index c54a6a29..039c3f23 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -12,11 +12,12 @@ use { buffd::{MsgParser, MsgParserError}, linkedlist::LinkedNode, numcell::NumCell, + option_ext::OptionExt, }, wire::{wl_subsurface::*, WlSubsurfaceId}, }, std::{ - cell::{Cell, RefCell}, + cell::{Cell, RefCell, RefMut}, ops::Deref, rc::Rc, }, @@ -37,14 +38,13 @@ pub struct WlSubsurface { sync_ancestor: Cell, node: RefCell>>, depth: NumCell, - pending: PendingSubsurfaceData, pub tracker: Tracker, } #[derive(Default)] -struct PendingSubsurfaceData { - node: RefCell>>, - position: Cell>, +pub struct PendingSubsurfaceData { + node: Option>, + position: Option<(i32, i32)>, } fn update_children_sync(surface: &WlSubsurface, sync: bool) { @@ -92,11 +92,16 @@ impl WlSubsurface { sync_ancestor: Cell::new(false), node: RefCell::new(None), depth: NumCell::new(0), - pending: Default::default(), tracker: Default::default(), } } + fn pending(&self) -> RefMut> { + RefMut::map(self.surface.pending.borrow_mut(), |m| { + m.subsurface.get_or_insert_default_ext() + }) + } + pub fn install(self: &Rc) -> Result<(), WlSubsurfaceError> { if self.surface.id == self.parent.id { return Err(WlSubsurfaceError::OwnParent(self.surface.id)); @@ -128,7 +133,7 @@ impl WlSubsurface { sub_surface: self.clone(), }) }; - *self.pending.node.borrow_mut() = Some(node); + self.pending().node = Some(node); self.surface.set_toplevel(self.parent.toplevel.get()); self.sync_ancestor.set(sync_ancestor); self.depth.set(depth); @@ -140,7 +145,7 @@ impl WlSubsurface { fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> { let _req: Destroy = self.surface.client.parse(self, parser)?; self.surface.unset_ext(); - *self.pending.node.borrow_mut() = None; + self.surface.pending.borrow_mut().subsurface.take(); *self.node.borrow_mut() = None; { let mut children = self.parent.children.borrow_mut(); @@ -164,7 +169,7 @@ impl WlSubsurface { fn set_position(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> { let req: SetPosition = self.surface.client.parse(self, parser)?; - self.pending.position.set(Some((req.x, req.y))); + self.pending().position = Some((req.x, req.y)); Ok(()) } @@ -188,7 +193,7 @@ impl WlSubsurface { Some(s) => s, _ => return Err(WlSubsurfaceError::NotASibling(sibling, self.surface.id)), }; - let node = match sibling.pending.node.borrow().deref() { + let node = match &sibling.pending().node { Some(n) => n.to_ref(), _ => match sibling.node.borrow().deref() { Some(n) => n.to_ref(), @@ -200,7 +205,7 @@ impl WlSubsurface { _ => node.prepend(element), } }; - self.pending.node.borrow_mut().replace(node); + self.pending().node.replace(node); } Ok(()) } @@ -256,7 +261,6 @@ object_base! { impl Object for WlSubsurface { fn break_loops(&self) { - *self.pending.node.borrow_mut() = None; *self.node.borrow_mut() = None; } } @@ -272,15 +276,17 @@ impl SurfaceExt for WlSubsurface { CommitAction::ContinueCommit } - fn after_apply_commit(self: Rc, _pending: &mut PendingState) { - if let Some(v) = self.pending.node.take() { - v.pending.set(false); - self.node.borrow_mut().replace(v); - } - if let Some((x, y)) = self.pending.position.take() { - self.position - .set(self.surface.buffer_abs_pos.get().at_point(x, y)); - self.parent.need_extents_update.set(true); + fn after_apply_commit(self: Rc, pending: &mut PendingState) { + if let Some(pending) = &mut pending.subsurface { + if let Some(v) = pending.node.take() { + v.pending.set(false); + self.node.borrow_mut().replace(v); + } + if let Some((x, y)) = pending.position.take() { + self.position + .set(self.surface.buffer_abs_pos.get().at_point(x, y)); + self.parent.need_extents_update.set(true); + } } } diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index 83fbb4e7..4563ab21 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -23,10 +23,15 @@ use { clonecell::CloneCell, copyhashmap::CopyHashMap, numcell::NumCell, + option_ext::OptionExt, }, wire::{xdg_surface::*, WlSurfaceId, XdgPopupId, XdgSurfaceId}, }, - std::{cell::Cell, fmt::Debug, rc::Rc}, + std::{ + cell::{Cell, RefMut}, + fmt::Debug, + rc::Rc, + }, thiserror::Error, }; @@ -65,14 +70,13 @@ pub struct XdgSurface { pub absolute_desired_extents: Cell, ext: CloneCell>>, popups: CopyHashMap>, - pending: PendingXdgSurfaceData, pub workspace: CloneCell>>, pub tracker: Tracker, } #[derive(Default, Debug)] -struct PendingXdgSurfaceData { - geometry: Cell>, +pub struct PendingXdgSurfaceData { + geometry: Option, } pub trait XdgSurfaceExt: Debug { @@ -103,7 +107,6 @@ impl XdgSurface { absolute_desired_extents: Cell::new(Default::default()), ext: Default::default(), popups: Default::default(), - pending: Default::default(), workspace: Default::default(), tracker: Default::default(), } @@ -270,6 +273,12 @@ impl XdgSurface { Ok(()) } + fn pending(&self) -> RefMut> { + RefMut::map(self.surface.pending.borrow_mut(), |p| { + p.xdg_surface.get_or_insert_default_ext() + }) + } + fn set_window_geometry(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgSurfaceError> { let req: SetWindowGeometry = self.surface.client.parse(self, parser)?; if req.height == 0 && req.width == 0 { @@ -280,7 +289,7 @@ impl XdgSurface { return Err(XdgSurfaceError::NonPositiveWidthHeight); } let extents = Rect::new_sized(req.x, req.y, req.width, req.height).unwrap(); - self.pending.geometry.set(Some(extents)); + self.pending().geometry = Some(extents); Ok(()) } @@ -359,7 +368,7 @@ dedicated_add_obj!(XdgSurface, XdgSurfaceId, xdg_surfaces); impl SurfaceExt for XdgSurface { fn before_apply_commit( self: Rc, - _pending: &mut PendingState, + pending: &mut PendingState, ) -> Result<(), WlSurfaceError> { { let ase = self.acked_serial.get(); @@ -373,9 +382,11 @@ impl SurfaceExt for XdgSurface { } } } - if let Some(geometry) = self.pending.geometry.take() { - self.geometry.set(Some(geometry)); - self.update_extents(); + if let Some(pending) = &mut pending.xdg_surface { + if let Some(geometry) = pending.geometry.take() { + self.geometry.set(Some(geometry)); + self.update_extents(); + } } Ok(()) } diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index 4eaf585d..09509342 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -17,10 +17,16 @@ use { cell_ext::CellExt, linkedlist::LinkedNode, numcell::NumCell, + option_ext::OptionExt, }, wire::{zwlr_layer_surface_v1::*, WlSurfaceId, ZwlrLayerSurfaceV1Id}, }, - std::{cell::Cell, ops::Deref, rc::Rc}, + std::{ + cell::{Cell, RefMut}, + mem, + ops::Deref, + rc::Rc, + }, thiserror::Error, }; @@ -48,7 +54,6 @@ pub struct ZwlrLayerSurfaceV1 { pos: Cell, mapped: Cell, layer: Cell, - pending: Pending, requested_serial: NumCell, acked_serial: Cell>, size: Cell<(i32, i32)>, @@ -61,14 +66,14 @@ pub struct ZwlrLayerSurfaceV1 { } #[derive(Default)] -struct Pending { - size: Cell>, - anchor: Cell>, - exclusive_zone: Cell>, - margin: Cell>, - keyboard_interactivity: Cell>, - layer: Cell>, - any: Cell, +pub struct PendingLayerSurfaceData { + size: Option<(i32, i32)>, + anchor: Option, + exclusive_zone: Option, + margin: Option<(i32, i32, i32, i32)>, + keyboard_interactivity: Option, + layer: Option, + any: bool, } impl ZwlrLayerSurfaceV1 { @@ -93,7 +98,6 @@ impl ZwlrLayerSurfaceV1 { pos: Default::default(), mapped: Cell::new(false), layer: Cell::new(layer), - pending: Default::default(), requested_serial: Default::default(), acked_serial: Cell::new(None), size: Cell::new((0, 0)), @@ -129,15 +133,20 @@ impl ZwlrLayerSurfaceV1 { self.client.event(Closed { self_id: self.id }); } + fn pending(&self) -> RefMut> { + RefMut::map(self.surface.pending.borrow_mut(), |m| { + m.layer_surface.get_or_insert_default_ext() + }) + } + fn set_size(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> { let req: SetSize = self.client.parse(self, parser)?; if req.width > u16::MAX as u32 || req.height > u16::MAX as u32 { return Err(ZwlrLayerSurfaceV1Error::ExcessiveSize); } - self.pending - .size - .set(Some((req.width as _, req.height as _))); - self.pending.any.set(true); + let mut pending = self.pending(); + pending.size = Some((req.width as _, req.height as _)); + pending.any = true; Ok(()) } @@ -146,24 +155,25 @@ impl ZwlrLayerSurfaceV1 { if req.anchor & !(LEFT | RIGHT | TOP | BOTTOM) != 0 { return Err(ZwlrLayerSurfaceV1Error::UnknownAnchor(req.anchor)); } - self.pending.anchor.set(Some(req.anchor)); - self.pending.any.set(true); + let mut pending = self.pending(); + pending.anchor = Some(req.anchor); + pending.any = true; Ok(()) } fn set_exclusive_zone(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> { let req: SetExclusiveZone = self.client.parse(self, parser)?; - self.pending.exclusive_zone.set(Some(req.zone)); - self.pending.any.set(true); + let mut pending = self.pending(); + pending.exclusive_zone = Some(req.zone); + pending.any = true; Ok(()) } fn set_margin(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> { let req: SetMargin = self.client.parse(self, parser)?; - self.pending - .margin - .set(Some((req.top, req.right, req.bottom, req.left))); - self.pending.any.set(true); + let mut pending = self.pending(); + pending.margin = Some((req.top, req.right, req.bottom, req.left)); + pending.any = true; Ok(()) } @@ -177,10 +187,9 @@ impl ZwlrLayerSurfaceV1 { req.keyboard_interactivity, )); } - self.pending - .keyboard_interactivity - .set(Some(req.keyboard_interactivity)); - self.pending.any.set(true); + let mut pending = self.pending(); + pending.keyboard_interactivity = Some(req.keyboard_interactivity); + pending.any = true; Ok(()) } @@ -208,29 +217,31 @@ impl ZwlrLayerSurfaceV1 { if req.layer > OVERLAY { return Err(ZwlrLayerSurfaceV1Error::UnknownLayer(req.layer)); } - self.pending.layer.set(Some(req.layer)); - self.pending.any.set(true); + let mut pending = self.pending(); + pending.layer = Some(req.layer); + pending.any = true; Ok(()) } - fn pre_commit(&self) -> Result<(), ZwlrLayerSurfaceV1Error> { - let mut send_configure = self.pending.any.replace(false); - if let Some(size) = self.pending.size.take() { + fn pre_commit(&self, pending: &mut PendingState) -> Result<(), ZwlrLayerSurfaceV1Error> { + let pending = pending.layer_surface.get_or_insert_default_ext(); + let mut send_configure = mem::replace(&mut pending.any, false); + if let Some(size) = pending.size.take() { self.size.set(size); } - if let Some(anchor) = self.pending.anchor.take() { + if let Some(anchor) = pending.anchor.take() { self.anchor.set(anchor); } - if let Some(ez) = self.pending.exclusive_zone.take() { + if let Some(ez) = pending.exclusive_zone.take() { self.exclusive_zone.set(ez); } - if let Some(margin) = self.pending.margin.take() { + if let Some(margin) = pending.margin.take() { self.margin.set(margin); } - if let Some(ki) = self.pending.keyboard_interactivity.take() { + if let Some(ki) = pending.keyboard_interactivity.take() { self.keyboard_interactivity.set(ki); } - if let Some(layer) = self.pending.layer.take() { + if let Some(layer) = pending.layer.take() { self.layer.set(layer); } { @@ -313,9 +324,9 @@ impl ZwlrLayerSurfaceV1 { impl SurfaceExt for ZwlrLayerSurfaceV1 { fn before_apply_commit( self: Rc, - _pending: &mut PendingState, + pending: &mut PendingState, ) -> Result<(), WlSurfaceError> { - self.deref().pre_commit()?; + self.deref().pre_commit(pending)?; Ok(()) } From 300deecc7d0f738ef5b33ef2741dfa21a54320c1 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 20 Mar 2024 19:41:43 +0100 Subject: [PATCH 06/15] surface: attach sync sub-surface commits to parent commits --- src/compositor.rs | 1 + src/ifs/wl_surface.rs | 156 ++++++++++++++++---- src/ifs/wl_surface/wl_subsurface.rs | 125 ++++++++++++---- src/ifs/wl_surface/xdg_surface.rs | 13 ++ src/ifs/wl_surface/zwlr_layer_surface_v1.rs | 19 +++ src/it/tests/t0007_subsurface.rs | 3 + src/it/tests/t0012_subsurface_focus.rs | 1 + src/state.rs | 2 + 8 files changed, 268 insertions(+), 52 deletions(-) diff --git a/src/compositor.rs b/src/compositor.rs index aeb2287f..c24161f1 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -221,6 +221,7 @@ fn start_compositor2( double_click_interval_usec: Cell::new(400 * 1000), double_click_distance: Cell::new(5), create_default_seat: Cell::new(true), + subsurface_ids: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 12c3e27a..c25b2870 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -26,7 +26,7 @@ use { }, wl_surface::{ cursor::CursorSurface, - wl_subsurface::{PendingSubsurfaceData, WlSubsurface}, + wl_subsurface::{PendingSubsurfaceData, SubsurfaceId, WlSubsurface}, wp_fractional_scale_v1::WpFractionalScaleV1, wp_tearing_control_v1::WpTearingControlV1, wp_viewport::WpViewport, @@ -67,6 +67,7 @@ use { jay_config::video::Transform, std::{ cell::{Cell, RefCell}, + collections::hash_map::{Entry, OccupiedEntry}, fmt::{Debug, Formatter}, mem, ops::{Deref, DerefMut}, @@ -188,12 +189,6 @@ struct BufferPoints { y2: f32, } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum CommitContext { - RootCommit, - ChildCommit, -} - #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum CommitAction { ContinueCommit, @@ -201,8 +196,7 @@ enum CommitAction { } trait SurfaceExt { - fn prepare_commit(&self, ctx: CommitContext, pending: &mut PendingState) -> CommitAction { - let _ = ctx; + fn commit_requested(self: Rc, pending: &mut PendingState) -> CommitAction { let _ = pending; CommitAction::ContinueCommit } @@ -258,6 +252,17 @@ trait SurfaceExt { fn into_xsurface(self: Rc) -> Option> { None } + + fn consume_pending_child( + &self, + surface: &WlSurface, + child: SubsurfaceId, + consume: &mut dyn FnMut( + OccupiedEntry, + ) -> Result<(), WlSurfaceError>, + ) -> Result<(), WlSurfaceError> { + surface.pending.borrow_mut().consume_child(child, consume) + } } pub struct NoneSurfaceExt; @@ -287,6 +292,98 @@ struct PendingState { subsurface: Option>, xdg_surface: Option>, layer_surface: Option>, + subsurfaces: AHashMap, +} + +struct CommittedSubsurface { + subsurface: Rc, + state: PendingState, +} + +impl PendingState { + fn merge(&mut self, next: &mut Self, client: &Rc) { + // discard state + + if next.buffer.is_some() { + if let Some(Some(prev)) = self.buffer.take() { + if !prev.destroyed() { + prev.send_release(); + } + } + } + for fb in self.presentation_feedback.drain(..) { + fb.send_discarded(); + let _ = client.remove_obj(&*fb); + } + + // overwrite state + + macro_rules! opt { + ($name:ident) => { + if let Some(n) = next.$name.take() { + self.$name = Some(n); + } + }; + } + opt!(buffer); + opt!(opaque_region); + opt!(input_region); + opt!(src_rect); + opt!(dst_size); + opt!(scale); + opt!(transform); + opt!(xwayland_serial); + opt!(tearing); + opt!(content_type); + { + let (dx1, dy1) = self.offset; + let (dx2, dy2) = mem::take(&mut next.offset); + self.offset = (dx1 + dx2, dy1 + dy2); + } + self.frame_request.append(&mut next.frame_request); + self.damage |= mem::take(&mut next.damage); + mem::swap( + &mut self.presentation_feedback, + &mut next.presentation_feedback, + ); + macro_rules! merge_ext { + ($name:ident) => { + if let Some(e) = &mut self.$name { + if let Some(n) = &mut next.$name { + e.merge(n); + } + } else { + self.$name = next.$name.take(); + } + }; + } + merge_ext!(subsurface); + merge_ext!(xdg_surface); + merge_ext!(layer_surface); + for (id, mut state) in next.subsurfaces.drain() { + match self.subsurfaces.entry(id) { + Entry::Occupied(mut o) => { + o.get_mut().state.merge(&mut state.state, client); + } + Entry::Vacant(v) => { + v.insert(state); + } + } + } + } + + fn consume_child( + &mut self, + child: SubsurfaceId, + consume: impl FnOnce( + OccupiedEntry, + ) -> Result<(), WlSurfaceError>, + ) -> Result<(), WlSurfaceError> { + match self.subsurfaces.entry(child) { + Entry::Occupied(oe) => consume(oe), + _ => Ok(()), + } + } } #[derive(Default)] @@ -660,21 +757,14 @@ impl WlSurface { Ok(()) } - fn do_commit(self: &Rc, ctx: CommitContext) -> Result<(), WlSurfaceError> { - let ext = self.ext.get(); - let pending = &mut *self.pending.borrow_mut(); - if ext.prepare_commit(ctx, pending) == CommitAction::AbortCommit { - return Ok(()); - } - ext.clone().before_apply_commit(pending)?; - { - let children = self.children.borrow(); - if let Some(children) = children.deref() { - for child in children.subsurfaces.values() { - child.surface.do_commit(CommitContext::ChildCommit)?; - } - } + fn apply_state(self: &Rc, pending: &mut PendingState) -> Result<(), WlSurfaceError> { + for (_, mut subsurface) in pending.subsurfaces.drain() { + subsurface + .subsurface + .surface + .apply_state(&mut subsurface.state)?; } + self.ext.get().before_apply_commit(pending)?; let mut scale_changed = false; if let Some(scale) = pending.scale.take() { scale_changed = true; @@ -869,14 +959,18 @@ impl WlSurface { cursor.update_hardware_cursor(); } } - ext.after_apply_commit(pending); + self.ext.get().after_apply_commit(pending); self.client.state.damage(); Ok(()) } fn commit(self: &Rc, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { let _req: Commit = self.parse(parser)?; - self.do_commit(CommitContext::RootCommit)?; + let ext = self.ext.get(); + let pending = &mut *self.pending.borrow_mut(); + if ext.commit_requested(pending) == CommitAction::ContinueCommit { + self.apply_state(pending)?; + } Ok(()) } @@ -1051,6 +1145,18 @@ impl WlSurface { consumer.send_feedback(fb); } } + + fn consume_pending_child( + &self, + child: SubsurfaceId, + mut consume: impl FnMut( + OccupiedEntry, + ) -> Result<(), WlSurfaceError>, + ) -> Result<(), WlSurfaceError> { + self.ext + .get() + .consume_pending_child(self, child, &mut consume) + } } object_base! { diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index 039c3f23..c40b41e3 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -2,7 +2,7 @@ use { crate::{ client::ClientError, ifs::wl_surface::{ - CommitAction, CommitContext, PendingState, StackElement, SurfaceExt, SurfaceRole, + CommitAction, CommittedSubsurface, PendingState, StackElement, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError, WlSurfaceId, }, leaks::Tracker, @@ -10,7 +10,8 @@ use { rect::Rect, utils::{ buffd::{MsgParser, MsgParserError}, - linkedlist::LinkedNode, + clonecell::CloneCell, + linkedlist::{LinkedNode, NodeRef}, numcell::NumCell, option_ext::OptionExt, }, @@ -18,6 +19,8 @@ use { }, std::{ cell::{Cell, RefCell, RefMut}, + collections::hash_map::{Entry, OccupiedEntry}, + mem, ops::Deref, rc::Rc, }, @@ -29,14 +32,18 @@ const BAD_SURFACE: u32 = 0; const MAX_SUBSURFACE_DEPTH: u32 = 100; +linear_ids!(SubsurfaceIds, SubsurfaceId, u64); + pub struct WlSubsurface { id: WlSubsurfaceId, + unique_id: SubsurfaceId, pub surface: Rc, pub(super) parent: Rc, pub position: Cell, sync_requested: Cell, sync_ancestor: Cell, node: RefCell>>, + latest_node: CloneCell>>, depth: NumCell, pub tracker: Tracker, } @@ -47,17 +54,17 @@ pub struct PendingSubsurfaceData { position: Option<(i32, i32)>, } -fn update_children_sync(surface: &WlSubsurface, sync: bool) { - let children = surface.surface.children.borrow(); - if let Some(children) = &*children { - for child in children.subsurfaces.values() { - let was_sync = child.sync(); - child.sync_ancestor.set(sync); - let is_sync = child.sync(); - if was_sync != is_sync { - update_children_sync(child, sync); - } +impl PendingSubsurfaceData { + pub fn merge(&mut self, next: &mut Self) { + macro_rules! opt { + ($name:ident) => { + if let Some(n) = next.$name.take() { + self.$name = Some(n); + } + }; } + opt!(node); + opt!(position); } } @@ -85,12 +92,14 @@ impl WlSubsurface { pub fn new(id: WlSubsurfaceId, surface: &Rc, parent: &Rc) -> Self { Self { id, + unique_id: surface.client.state.subsurface_ids.next(), surface: surface.clone(), parent: parent.clone(), position: Cell::new(Default::default()), sync_requested: Cell::new(false), sync_ancestor: Cell::new(false), node: RefCell::new(None), + latest_node: Default::default(), depth: NumCell::new(0), tracker: Default::default(), } @@ -133,6 +142,7 @@ impl WlSubsurface { sub_surface: self.clone(), }) }; + self.latest_node.set(Some(node.to_ref())); self.pending().node = Some(node); self.surface.set_toplevel(self.parent.toplevel.get()); self.sync_ancestor.set(sync_ancestor); @@ -145,8 +155,12 @@ impl WlSubsurface { fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> { let _req: Destroy = self.surface.client.parse(self, parser)?; self.surface.unset_ext(); + self.parent.consume_pending_child(self.unique_id, |oe| { + self.surface.apply_state(&mut oe.remove().state) + })?; self.surface.pending.borrow_mut().subsurface.take(); *self.node.borrow_mut() = None; + self.latest_node.take(); { let mut children = self.parent.children.borrow_mut(); if let Some(children) = &mut *children { @@ -193,18 +207,16 @@ impl WlSubsurface { Some(s) => s, _ => return Err(WlSubsurfaceError::NotASibling(sibling, self.surface.id)), }; - let node = match &sibling.pending().node { - Some(n) => n.to_ref(), - _ => match sibling.node.borrow().deref() { - Some(n) => n.to_ref(), - _ => return Ok(()), - }, + let sibling_node = match sibling.latest_node.get() { + Some(n) => n, + _ => return Ok(()), }; match above { - true => node.append(element), - _ => node.prepend(element), + true => sibling_node.append(element), + _ => sibling_node.prepend(element), } }; + self.latest_node.set(Some(node.to_ref())); self.pending().node.replace(node); } Ok(()) @@ -226,24 +238,56 @@ impl WlSubsurface { self.sync_requested.get() || self.sync_ancestor.get() } - fn update_sync(&self, sync: bool) { + fn update_sync(&self, sync: bool) -> Result<(), WlSurfaceError> { let was_sync = self.sync(); self.sync_requested.set(sync); let is_sync = self.sync(); if was_sync != is_sync { - update_children_sync(self, is_sync); + self.handle_sync_change(is_sync)?; } + Ok(()) + } + + fn handle_sync_change(&self, is_sync: bool) -> Result<(), WlSurfaceError> { + if !is_sync { + self.on_desync()?; + } + let children = self.surface.children.borrow(); + if let Some(children) = &*children { + for child in children.subsurfaces.values() { + let was_sync = child.sync(); + child.sync_ancestor.set(is_sync); + let is_sync = child.sync(); + if was_sync != is_sync { + child.handle_sync_change(is_sync)?; + } + } + } + Ok(()) + } + + fn on_desync(&self) -> Result<(), WlSurfaceError> { + let committed = self + .parent + .pending + .borrow_mut() + .subsurfaces + .remove(&self.unique_id); + if let Some(mut ps) = committed { + self.surface.apply_state(&mut ps.state)?; + } + Ok(()) } fn set_sync(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> { let _req: SetSync = self.surface.client.parse(self, parser)?; - self.update_sync(true); + self.update_sync(true)?; Ok(()) } fn set_desync(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> { let _req: SetDesync = self.surface.client.parse(self, parser)?; - self.update_sync(false); + self.update_sync(false)?; Ok(()) } } @@ -262,15 +306,27 @@ object_base! { impl Object for WlSubsurface { fn break_loops(&self) { *self.node.borrow_mut() = None; + self.latest_node.take(); } } simple_add_obj!(WlSubsurface); impl SurfaceExt for WlSubsurface { - fn prepare_commit(&self, ctx: CommitContext, _pending: &mut PendingState) -> CommitAction { - if ctx == CommitContext::RootCommit && self.sync() { - log::debug!("Aborting commit due to sync"); + fn commit_requested(self: Rc, pending: &mut PendingState) -> CommitAction { + if self.sync() { + let mut parent_pending = self.parent.pending.borrow_mut(); + match parent_pending.subsurfaces.entry(self.unique_id) { + Entry::Occupied(mut o) => { + o.get_mut().state.merge(pending, &self.surface.client); + } + Entry::Vacant(v) => { + v.insert(CommittedSubsurface { + subsurface: self.clone(), + state: mem::take(&mut *pending), + }); + } + } return CommitAction::AbortCommit; } CommitAction::ContinueCommit @@ -301,6 +357,21 @@ impl SurfaceExt for WlSubsurface { fn into_subsurface(self: Rc) -> Option> { Some(self) } + + fn consume_pending_child( + &self, + surface: &WlSurface, + child: SubsurfaceId, + consume: &mut dyn FnMut( + OccupiedEntry, + ) -> Result<(), WlSurfaceError>, + ) -> Result<(), WlSurfaceError> { + self.parent + .consume_pending_child(self.unique_id, |mut oe| { + oe.get_mut().state.consume_child(child, &mut *consume) + })?; + surface.pending.borrow_mut().consume_child(child, consume) + } } #[derive(Debug, Error)] diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index 4563ab21..8974381c 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -79,6 +79,19 @@ pub struct PendingXdgSurfaceData { geometry: Option, } +impl PendingXdgSurfaceData { + pub fn merge(&mut self, next: &mut Self) { + macro_rules! opt { + ($name:ident) => { + if let Some(n) = next.$name.take() { + self.$name = Some(n); + } + }; + } + opt!(geometry); + } +} + pub trait XdgSurfaceExt: Debug { fn initial_configure(self: Rc) -> Result<(), XdgSurfaceError> { Ok(()) diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index 09509342..edd0185b 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -76,6 +76,25 @@ pub struct PendingLayerSurfaceData { any: bool, } +impl PendingLayerSurfaceData { + pub fn merge(&mut self, next: &mut Self) { + macro_rules! opt { + ($name:ident) => { + if let Some(n) = next.$name.take() { + self.$name = Some(n); + } + }; + } + opt!(size); + opt!(anchor); + opt!(exclusive_zone); + opt!(margin); + opt!(keyboard_interactivity); + opt!(layer); + self.any |= mem::take(&mut next.any); + } +} + impl ZwlrLayerSurfaceV1 { pub fn new( id: ZwlrLayerSurfaceV1Id, diff --git a/src/it/tests/t0007_subsurface.rs b/src/it/tests/t0007_subsurface.rs index 0866140c..93b1a245 100644 --- a/src/it/tests/t0007_subsurface.rs +++ b/src/it/tests/t0007_subsurface.rs @@ -37,6 +37,7 @@ async fn test(run: Rc) -> Result<(), TestError> { buffer.fill(Color::from_rgba_straight(255, 255, 255, 255)); child.attach(buffer.id)?; + child.commit()?; parent.map().await?; @@ -45,10 +46,12 @@ async fn test(run: Rc) -> Result<(), TestError> { client.compare_screenshot("1").await?; sub.place_below(parent.surface.id)?; + child.commit()?; parent.map().await?; client.compare_screenshot("2").await?; sub.place_above(parent.surface.id)?; + child.commit()?; parent.map().await?; client.compare_screenshot("1").await?; diff --git a/src/it/tests/t0012_subsurface_focus.rs b/src/it/tests/t0012_subsurface_focus.rs index 2655c59a..4d978603 100644 --- a/src/it/tests/t0012_subsurface_focus.rs +++ b/src/it/tests/t0012_subsurface_focus.rs @@ -29,6 +29,7 @@ async fn test(run: Rc) -> TestResult { nss.set_position(100, 100)?; let buffer = client.shm.create_buffer(100, 100)?; ns.attach(buffer.id)?; + ns.commit()?; run.cfg.set_fullscreen(ds.seat.id(), true)?; client.sync().await; diff --git a/src/state.rs b/src/state.rs index 014387d4..a1d3ebc2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -30,6 +30,7 @@ use { wl_output::{OutputId, PersistentOutputState}, wl_seat::{SeatIds, WlSeatGlobal}, wl_surface::{ + wl_subsurface::SubsurfaceIds, zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1}, NoneSurfaceExt, WlSurface, }, @@ -157,6 +158,7 @@ pub struct State { pub double_click_interval_usec: Cell, pub double_click_distance: Cell, pub create_default_seat: Cell, + pub subsurface_ids: SubsurfaceIds, } // impl Drop for State { From ea4a1f027b950378569ad774279e6c2d4e3e4e2c Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 21 Mar 2024 14:48:24 +0100 Subject: [PATCH 07/15] render: remove TextureReservations --- src/backends/metal/video.rs | 20 +---- src/cursor.rs | 5 +- src/gfx_api.rs | 68 ++++++++-------- src/gfx_apis/gl/renderer/context.rs | 1 - src/gfx_apis/gl/renderer/image.rs | 1 - src/gfx_apis/gl/renderer/texture.rs | 7 +- src/gfx_apis/vulkan.rs | 2 - src/gfx_apis/vulkan/fence.rs | 9 ++- src/gfx_apis/vulkan/image.rs | 9 +-- src/gfx_apis/vulkan/renderer.rs | 78 ++++++++++--------- src/gfx_apis/vulkan/semaphore.rs | 6 +- src/ifs/wl_buffer.rs | 2 +- src/ifs/wl_surface.rs | 86 +++++++++++++-------- src/ifs/wl_surface/zwlr_layer_surface_v1.rs | 6 +- src/ifs/xdg_toplevel_drag_v1.rs | 2 +- src/portal/ptr_gui.rs | 2 + src/renderer.rs | 30 ++++--- src/renderer/renderer_base.rs | 26 ++++--- src/state.rs | 3 +- src/video/dmabuf.rs | 7 ++ 20 files changed, 198 insertions(+), 172 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index d04f696f..bd5decd9 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -9,7 +9,7 @@ use { drm_feedback::DrmFeedback, edid::Descriptor, format::{Format, ARGB8888, XRGB8888}, - gfx_api::{GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass, GfxTexture}, + gfx_api::{BufferResv, GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass, GfxTexture}, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, renderer::RenderResult, state::State, @@ -350,9 +350,9 @@ pub struct DirectScanoutCache { #[derive(Debug)] pub struct DirectScanoutData { tex: Rc, + _resv: Option>, fb: Rc, dma_buf_id: DmaBufId, - acquired: Cell, position: DirectScanoutPosition, } @@ -366,14 +366,6 @@ pub struct DirectScanoutPosition { pub crtc_height: i32, } -impl Drop for DirectScanoutData { - fn drop(&mut self) { - if self.acquired.replace(false) { - self.tex.reservations().release(); - } - } -} - #[derive(Debug)] pub struct PresentFb { fb: Rc, @@ -532,9 +524,9 @@ impl MetalConnector { if let Some(buffer) = cache.get(&dmabuf.id) { return buffer.fb.as_ref().map(|fb| DirectScanoutData { tex: buffer.tex.upgrade().unwrap(), + _resv: ct.buffer_resv.clone(), fb: fb.clone(), dma_buf_id: dmabuf.id, - acquired: Default::default(), position, }); } @@ -556,9 +548,9 @@ impl MetalConnector { let data = match self.dev.master.add_fb(dmabuf, Some(format.format)) { Ok(fb) => Some(DirectScanoutData { tex: ct.tex.clone(), + _resv: ct.buffer_resv.clone(), fb: Rc::new(fb), dma_buf_id: dmabuf.id, - acquired: Default::default(), position, }), Err(e) => { @@ -780,10 +772,6 @@ impl MetalConnector { Err(()) } else { if let Some(fb) = new_fb { - if let Some(dsd) = &fb.direct_scanout_data { - dsd.tex.reservations().acquire(); - dsd.acquired.set(true); - } self.next_framebuffer.set(Some(fb)); } self.can_present.set(false); diff --git a/src/cursor.rs b/src/cursor.rs index 126923cc..c644f28e 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -378,6 +378,7 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed None, scale, None, + None, ); } } @@ -391,7 +392,7 @@ impl Cursor for StaticCursor { if let Some(img) = self.image.scales.get(&renderer.scale()) { renderer .base - .render_texture(&img.tex, 0, 0, None, None, renderer.scale(), None); + .render_texture(&img.tex, 0, 0, None, None, renderer.scale(), None, None); } } @@ -421,7 +422,7 @@ impl Cursor for AnimatedCursor { if let Some(img) = img.scales.get(&renderer.scale()) { renderer .base - .render_texture(&img.tex, 0, 0, None, None, renderer.scale(), None); + .render_texture(&img.tex, 0, 0, None, None, renderer.scale(), None, None); } } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index a9f5d280..75fcf245 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -9,7 +9,7 @@ use { state::State, theme::Color, tree::{Node, OutputNode}, - utils::{numcell::NumCell, transform_ext::TransformExt}, + utils::{clonecell::UnsafeCellCloneSafe, transform_ext::TransformExt}, video::{dmabuf::DmaBuf, gbm::GbmDevice, Modifier}, }, ahash::AHashMap, @@ -21,9 +21,12 @@ use { error::Error, ffi::CString, fmt::{Debug, Formatter}, + ops::Deref, rc::Rc, + sync::atomic::{AtomicU64, Ordering::Relaxed}, }, thiserror::Error, + uapi::OwnedFd, }; pub enum GfxApiOpt { @@ -141,6 +144,34 @@ pub struct CopyTexture { pub tex: Rc, pub source: SampleRect, pub target: FramebufferRect, + pub buffer_resv: Option>, +} + +#[derive(Clone, Debug)] +pub struct SyncFile(pub Rc); + +impl Deref for SyncFile { + type Target = Rc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +unsafe impl UnsafeCellCloneSafe for SyncFile {} + +pub trait BufferResv: Debug { + fn set_sync_file(&self, user: BufferResvUser, sync_file: &SyncFile); +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct BufferResvUser(u64); + +impl Default for BufferResvUser { + fn default() -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(1); + Self(NEXT_ID.fetch_add(1, Relaxed)) + } } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -210,7 +241,7 @@ impl dyn GfxFramebuffer { let mut ops = self.take_render_ops(); let scale = Scale::from_int(1); let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); - renderer.render_texture(texture, x, y, None, None, scale, None); + renderer.render_texture(texture, x, y, None, None, scale, None, None); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); self.render(ops, clear); } @@ -376,38 +407,6 @@ pub trait GfxImage { fn height(&self) -> i32; } -#[derive(Default)] -pub struct TextureReservations { - reservations: NumCell, - on_release: Cell>>, -} - -impl TextureReservations { - pub fn has_reservation(&self) -> bool { - self.reservations.get() != 0 - } - - pub fn acquire(&self) { - self.reservations.fetch_add(1); - } - - pub fn release(&self) { - if self.reservations.fetch_sub(1) == 1 { - if let Some(cb) = self.on_release.take() { - cb(); - } - } - } - - pub fn on_released(&self, cb: C) { - if self.has_reservation() { - self.on_release.set(Some(Box::new(cb))); - } else { - cb(); - } - } -} - pub trait GfxTexture: Debug { fn size(&self) -> (i32, i32); fn as_any(&self) -> &dyn Any; @@ -423,7 +422,6 @@ pub trait GfxTexture: Debug { shm: &[Cell], ) -> Result<(), GfxError>; fn dmabuf(&self) -> Option<&DmaBuf>; - fn reservations(&self) -> &TextureReservations; fn format(&self) -> &'static Format; } diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index b019e555..bba4a6b4 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -186,7 +186,6 @@ impl GlRenderContext { Ok(Rc::new(Texture { ctx: self.clone(), gl, - resv: Default::default(), format, })) } diff --git a/src/gfx_apis/gl/renderer/image.rs b/src/gfx_apis/gl/renderer/image.rs index 4044206d..626d3431 100644 --- a/src/gfx_apis/gl/renderer/image.rs +++ b/src/gfx_apis/gl/renderer/image.rs @@ -27,7 +27,6 @@ impl Image { Ok(Rc::new(Texture { ctx: self.ctx.clone(), gl: GlTexture::import_img(&self.ctx.ctx, &self.gl)?, - resv: Default::default(), format: self.gl.dmabuf.format, })) } diff --git a/src/gfx_apis/gl/renderer/texture.rs b/src/gfx_apis/gl/renderer/texture.rs index efe2b029..7ef887e1 100644 --- a/src/gfx_apis/gl/renderer/texture.rs +++ b/src/gfx_apis/gl/renderer/texture.rs @@ -1,7 +1,7 @@ use { crate::{ format::Format, - gfx_api::{GfxError, GfxTexture, TextureReservations}, + gfx_api::{GfxError, GfxTexture}, gfx_apis::gl::{ gl::texture::GlTexture, renderer::{context::GlRenderContext, framebuffer::Framebuffer}, @@ -20,7 +20,6 @@ use { pub struct Texture { pub(in crate::gfx_apis::gl) ctx: Rc, pub(in crate::gfx_apis::gl) gl: GlTexture, - pub(in crate::gfx_apis::gl) resv: TextureReservations, pub(in crate::gfx_apis::gl) format: &'static Format, } @@ -79,10 +78,6 @@ impl GfxTexture for Texture { self.gl.img.as_ref().map(|i| &i.dmabuf) } - fn reservations(&self) -> &TextureReservations { - &self.resv - } - fn format(&self) -> &'static Format { self.format } diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 9cf7c7b2..83d3ecd2 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -153,8 +153,6 @@ pub enum VulkanError { IoctlExportSyncFile(#[source] OsError), #[error("Could not import a sync file into a semaphore")] ImportSyncFile(#[source] vk::Result), - #[error("Could not import a sync file into a dma-buf")] - IoctlImportSyncFile(#[source] OsError), #[error("Could not export a sync file from a semaphore")] ExportSyncFile(#[source] vk::Result), #[error("Could not fetch the render node of the device")] diff --git a/src/gfx_apis/vulkan/fence.rs b/src/gfx_apis/vulkan/fence.rs index defa0923..a971e173 100644 --- a/src/gfx_apis/vulkan/fence.rs +++ b/src/gfx_apis/vulkan/fence.rs @@ -1,5 +1,8 @@ use { - crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + crate::{ + gfx_api::SyncFile, + gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + }, ash::vk::{ ExportFenceCreateInfo, ExternalFenceHandleTypeFlags, Fence, FenceCreateInfo, FenceGetFdInfoKHR, @@ -38,12 +41,12 @@ impl VulkanDevice { } impl VulkanFence { - pub fn export_syncfile(&self) -> Result, VulkanError> { + pub fn export_sync_file(&self) -> Result { let info = FenceGetFdInfoKHR::builder() .fence(self.fence) .handle_type(ExternalFenceHandleTypeFlags::SYNC_FD); let res = unsafe { self.device.external_fence_fd.get_fence_fd(&info) }; res.map_err(VulkanError::ExportSyncFile) - .map(|fd| Rc::new(OwnedFd::new(fd))) + .map(|fd| SyncFile(Rc::new(OwnedFd::new(fd)))) } } diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 63f773a5..9385fb1c 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -1,7 +1,7 @@ use { crate::{ format::Format, - gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture, TextureReservations}, + gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture}, gfx_apis::vulkan::{ allocator::VulkanAllocation, device::VulkanDevice, format::VulkanMaxExtents, renderer::VulkanRenderer, util::OnDrop, VulkanError, @@ -53,7 +53,6 @@ pub struct VulkanImage { pub(super) is_undefined: Cell, pub(super) ty: VulkanImageMemory, pub(super) render_ops: CloneCell>, - pub(super) resv: TextureReservations, } pub enum VulkanImageMemory { @@ -212,7 +211,6 @@ impl VulkanRenderer { is_undefined: Cell::new(true), ty: VulkanImageMemory::Internal(shm), render_ops: Default::default(), - resv: Default::default(), })) } @@ -482,7 +480,6 @@ impl VulkanDmaBufImageTemplate { }), format: self.dmabuf.format, is_undefined: Cell::new(true), - resv: Default::default(), })) } } @@ -587,10 +584,6 @@ impl GfxTexture for VulkanImage { } } - fn reservations(&self) -> &TextureReservations { - &self.resv - } - fn format(&self) -> &'static Format { self.format } diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 9948dbf7..39727560 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -2,7 +2,9 @@ use { crate::{ async_engine::SpawnedFuture, format::Format, - gfx_api::{GfxApiOpt, GfxFormat, GfxFramebuffer, GfxTexture}, + gfx_api::{ + BufferResv, BufferResvUser, GfxApiOpt, GfxFormat, GfxFramebuffer, GfxTexture, SyncFile, + }, gfx_apis::vulkan::{ allocator::VulkanAllocator, command::{VulkanCommandBuffer, VulkanCommandPool}, @@ -21,10 +23,7 @@ use { io_uring::IoUring, theme::Color, utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack}, - video::dmabuf::{ - dma_buf_export_sync_file, dma_buf_import_sync_file, DMA_BUF_SYNC_READ, - DMA_BUF_SYNC_WRITE, - }, + video::dmabuf::{dma_buf_export_sync_file, DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE}, }, ahash::AHashMap, ash::{ @@ -49,7 +48,6 @@ use { rc::Rc, slice, }, - uapi::OwnedFd, }; pub struct VulkanRenderer { @@ -66,6 +64,12 @@ pub struct VulkanRenderer { pub(super) pending_frames: CopyHashMap>, pub(super) allocator: Rc, pub(super) last_point: NumCell, + pub(super) buffer_resv_user: BufferResvUser, +} + +pub(super) struct UsedTexture { + tex: Rc, + resv: Option>, } #[derive(Default)] @@ -73,20 +77,20 @@ pub(super) struct Memory { sample: Vec>, flush: Vec>, flush_staging: Vec<(Rc, VulkanStagingBuffer)>, - textures: Vec>, + textures: Vec, image_barriers: Vec, shm_barriers: Vec, wait_semaphores: Vec>, wait_semaphore_infos: Vec, release_fence: Option>, - release_syncfile: Option>, + release_sync_file: Option, } pub(super) struct PendingFrame { point: u64, renderer: Rc, cmd: Cell>>, - _textures: Vec>, + _textures: Vec, _staging: Vec<(Rc, VulkanStagingBuffer)>, wait_semaphores: Cell>>, waiter: Cell>>, @@ -157,6 +161,7 @@ impl VulkanDevice { pending_frames: Default::default(), allocator, last_point: Default::default(), + buffer_resv_user: Default::default(), })) } } @@ -177,7 +182,10 @@ impl VulkanRenderer { } } } - memory.textures.push(tex); + memory.textures.push(UsedTexture { + tex, + resv: c.buffer_resv.clone(), + }); } } } @@ -537,7 +545,7 @@ impl VulkanRenderer { let fd = dma_buf_export_sync_file(&plane.fd, flag) .map_err(VulkanError::IoctlExportSyncFile)?; let semaphore = self.allocate_semaphore()?; - semaphore.import_syncfile(fd)?; + semaphore.import_sync_file(fd)?; infos.push( SemaphoreSubmitInfo::builder() .semaphore(semaphore.semaphore) @@ -553,7 +561,7 @@ impl VulkanRenderer { import( &mut memory.wait_semaphore_infos, &mut memory.wait_semaphores, - texture, + &texture.tex, DMA_BUF_SYNC_READ, )?; } @@ -567,27 +575,25 @@ impl VulkanRenderer { } fn import_release_semaphore(&self, fb: &VulkanImage) { - let memory = self.memory.borrow(); - let syncfile = match memory.release_syncfile.as_ref() { - Some(syncfile) => syncfile, + let memory = &mut *self.memory.borrow_mut(); + let sync_file = match memory.release_sync_file.as_ref() { + Some(sync_file) => sync_file, _ => return, }; - let import = |img: &VulkanImage, flag: u32| { - if let VulkanImageMemory::DmaBuf(buf) = &img.ty { - for plane in &buf.template.dmabuf.planes { - let res = dma_buf_import_sync_file(&plane.fd, flag, &syncfile) - .map_err(VulkanError::IoctlImportSyncFile); - if let Err(e) = res { - log::error!("Could not import syncfile into dmabuf: {}", ErrorFmt(e)); - log::warn!("Relying on implicit sync"); - } + let import = |img: &VulkanImage, resv: Option>, flag: u32| { + if let Some(resv) = resv { + resv.set_sync_file(self.buffer_resv_user, sync_file); + } else if let VulkanImageMemory::DmaBuf(buf) = &img.ty { + if let Err(e) = buf.template.dmabuf.import_sync_file(flag, sync_file) { + log::error!("Could not import sync file into dmabuf: {}", ErrorFmt(e)); + log::warn!("Relying on implicit sync"); } } }; - for texture in &memory.textures { - import(texture, DMA_BUF_SYNC_WRITE); + for texture in &mut memory.textures { + import(&texture.tex, texture.resv.take(), DMA_BUF_SYNC_READ); } - import(fb, DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE); + import(fb, None, DMA_BUF_SYNC_WRITE); } fn submit(&self, buf: CommandBuffer) -> Result<(), VulkanError> { @@ -610,15 +616,15 @@ impl VulkanRenderer { ) .map_err(VulkanError::Submit)?; } - let release_syncfile = match release_fence.export_syncfile() { + let release_sync_file = match release_fence.export_sync_file() { Ok(s) => Some(s), Err(e) => { - log::error!("Could not export syncfile from fence: {}", ErrorFmt(e)); + log::error!("Could not export sync file from fence: {}", ErrorFmt(e)); None } }; memory.release_fence = Some(release_fence); - memory.release_syncfile = release_syncfile; + memory.release_sync_file = release_sync_file; Ok(()) } @@ -650,7 +656,7 @@ impl VulkanRenderer { }); self.pending_frames.set(frame.point, frame.clone()); let future = self.device.instance.eng.spawn(await_release( - memory.release_syncfile.take(), + memory.release_sync_file.take(), self.device.instance.ring.clone(), frame.clone(), self.clone(), @@ -769,7 +775,7 @@ impl VulkanRenderer { let fd = dma_buf_export_sync_file(&plane.fd, DMA_BUF_SYNC_READ) .map_err(VulkanError::IoctlExportSyncFile)?; let semaphore = self.allocate_semaphore()?; - semaphore.import_syncfile(fd)?; + semaphore.import_sync_file(fd)?; let semaphore_info = SemaphoreSubmitInfo::builder() .semaphore(semaphore.semaphore) .stage_mask(PipelineStageFlags2::TOP_OF_PIPE) @@ -841,7 +847,7 @@ impl VulkanRenderer { memory.sample.clear(); memory.wait_semaphores.clear(); memory.release_fence.take(); - memory.release_syncfile.take(); + memory.release_sync_file.take(); } res } @@ -964,14 +970,14 @@ fn image_barrier() -> ImageMemoryBarrier2Builder<'static> { } async fn await_release( - syncfile: Option>, + sync_file: Option, ring: Rc, frame: Rc, renderer: Rc, ) { let mut is_released = false; - if let Some(syncfile) = syncfile { - if let Err(e) = ring.readable(&syncfile).await { + if let Some(sync_file) = sync_file { + if let Err(e) = ring.readable(&sync_file).await { log::error!( "Could not wait for release semaphore to be signaled: {}", ErrorFmt(e) diff --git a/src/gfx_apis/vulkan/semaphore.rs b/src/gfx_apis/vulkan/semaphore.rs index 1d7b3f3c..e9b03681 100644 --- a/src/gfx_apis/vulkan/semaphore.rs +++ b/src/gfx_apis/vulkan/semaphore.rs @@ -36,9 +36,9 @@ impl VulkanDevice { } impl VulkanSemaphore { - pub fn import_syncfile(&self, syncfile: OwnedFd) -> Result<(), VulkanError> { + pub fn import_sync_file(&self, sync_file: OwnedFd) -> Result<(), VulkanError> { let fd_info = ImportSemaphoreFdInfoKHR::builder() - .fd(syncfile.raw()) + .fd(sync_file.raw()) .flags(SemaphoreImportFlags::TEMPORARY) .handle_type(ExternalSemaphoreHandleTypeFlags::SYNC_FD) .semaphore(self.semaphore); @@ -47,7 +47,7 @@ impl VulkanSemaphore { .external_semaphore_fd .import_semaphore_fd(&fd_info) }; - mem::forget(syncfile); + mem::forget(sync_file); res.map_err(VulkanError::ImportSyncFile)?; Ok(()) } diff --git a/src/ifs/wl_buffer.rs b/src/ifs/wl_buffer.rs index 60c347a2..1e5a742f 100644 --- a/src/ifs/wl_buffer.rs +++ b/src/ifs/wl_buffer.rs @@ -35,7 +35,7 @@ pub struct WlBuffer { pub client: Rc, pub rect: Rect, pub format: &'static Format, - dmabuf: Option, + pub dmabuf: Option, render_ctx_version: Cell, pub storage: RefCell>, pub color: Option, diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index c25b2870..e003ff2c 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -16,7 +16,7 @@ use { client::{Client, ClientError, RequestParser}, drm_feedback::DrmFeedback, fixed::Fixed, - gfx_api::SampleRect, + gfx_api::{BufferResv, BufferResvUser, SampleRect, SyncFile}, ifs::{ wl_buffer::WlBuffer, wl_callback::WlCallback, @@ -51,11 +51,13 @@ use { cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, linkedlist::LinkedList, numcell::NumCell, smallmap::SmallMap, transform_ext::TransformExt, }, + video::dmabuf::DMA_BUF_SYNC_READ, wire::{ wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id, ZwpLinuxDmabufFeedbackV1Id, @@ -131,6 +133,39 @@ impl NodeVisitorBase for SurfaceSendPreferredTransformVisitor { } } +pub struct SurfaceBuffer { + pub buffer: Rc, + sync_files: SmallMap, +} + +impl Drop for SurfaceBuffer { + fn drop(&mut self) { + let sync_files = self.sync_files.take(); + if let Some(dmabuf) = &self.buffer.dmabuf { + for (_, sync_file) in &sync_files { + if let Err(e) = dmabuf.import_sync_file(DMA_BUF_SYNC_READ, sync_file) { + log::error!("Could not import sync file: {}", ErrorFmt(e)); + } + } + } + if !self.buffer.destroyed() { + self.buffer.send_release(); + } + } +} + +impl Debug for SurfaceBuffer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SurfaceBuffer").finish_non_exhaustive() + } +} + +impl BufferResv for SurfaceBuffer { + fn set_sync_file(&self, user: BufferResvUser, sync_file: &SyncFile) { + self.sync_files.insert(user, sync_file.clone()); + } +} + pub struct WlSurface { pub id: WlSurfaceId, pub node_id: SurfaceNodeId, @@ -149,7 +184,7 @@ pub struct WlSurface { pub extents: Cell, pub buffer_abs_pos: Cell, pub need_extents_update: Cell, - pub buffer: CloneCell>>, + pub buffer: CloneCell>>, pub buf_x: NumCell, pub buf_y: NumCell, pub children: RefCell>>, @@ -683,11 +718,7 @@ impl WlSurface { } *children = None; } - if let Some(buffer) = self.buffer.set(None) { - if !buffer.destroyed() { - buffer.send_release(); - } - } + self.buffer.set(None); if let Some(xwayland_serial) = self.xwayland_serial.get() { self.client .surfaces_by_xwayland_serial @@ -799,30 +830,15 @@ impl WlSurface { if let Some(buffer_change) = pending.buffer.take() { buffer_changed = true; if let Some(buffer) = self.buffer.take() { - old_raw_size = Some(buffer.rect); - if !buffer.destroyed() { - 'handle_release: { - if let Some(tex) = buffer.texture.get() { - let resv = tex.reservations(); - if resv.has_reservation() { - let buffer = Rc::downgrade(&buffer); - resv.on_released(move || { - if let Some(buffer) = buffer.upgrade() { - if !buffer.destroyed() { - buffer.send_release(); - } - } - }); - break 'handle_release; - } - } - buffer.send_release(); - } - } + old_raw_size = Some(buffer.buffer.rect); } if let Some(buffer) = buffer_change { buffer.update_texture_or_log(); - self.buffer.set(Some(buffer)); + let surface_buffer = SurfaceBuffer { + buffer, + sync_files: Default::default(), + }; + self.buffer.set(Some(Rc::new(surface_buffer))); self.buf_x.fetch_add(dx); self.buf_y.fetch_add(dy); if (dx, dy) != (0, 0) { @@ -872,8 +888,10 @@ impl WlSurface { } if let Some(buffer) = self.buffer.get() { if new_size.is_none() { - let (mut width, mut height) = - self.buffer_transform.get().maybe_swap(buffer.rect.size()); + let (mut width, mut height) = self + .buffer_transform + .get() + .maybe_swap(buffer.buffer.rect.size()); let scale = self.buffer_scale.get(); if scale != 1 { width = (width + scale - 1) / scale; @@ -881,12 +899,14 @@ impl WlSurface { } new_size = Some((width, height)); } - if transform_changed || Some(buffer.rect) != old_raw_size { + if transform_changed || Some(buffer.buffer.rect) != old_raw_size { let (x1, y1, x2, y2) = if self.src_rect.is_none() { (0.0, 0.0, 1.0, 1.0) } else { - let (width, height) = - self.buffer_transform.get().maybe_swap(buffer.rect.size()); + let (width, height) = self + .buffer_transform + .get() + .maybe_swap(buffer.buffer.rect.size()); let width = width as f32; let height = height as f32; let x1 = buffer_points.x1 / width; diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index edd0185b..9e6c87e9 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -350,9 +350,9 @@ impl SurfaceExt for ZwlrLayerSurfaceV1 { } fn after_apply_commit(self: Rc, _pending: &mut PendingState) { - let buffer = self.surface.buffer.get(); + let buffer_is_some = self.surface.buffer.is_some(); if self.mapped.get() { - if buffer.is_none() { + if !buffer_is_some { self.destroy_node(); } else { let pos = self.pos.get(); @@ -361,7 +361,7 @@ impl SurfaceExt for ZwlrLayerSurfaceV1 { self.compute_position(); } } - } else if buffer.is_some() { + } else if buffer_is_some { let layer = &self.output.layers[self.layer.get() as usize]; self.link.set(Some(layer.add_last(self.clone()))); self.mapped.set(true); diff --git a/src/ifs/xdg_toplevel_drag_v1.rs b/src/ifs/xdg_toplevel_drag_v1.rs index 083c51fd..beadf987 100644 --- a/src/ifs/xdg_toplevel_drag_v1.rs +++ b/src/ifs/xdg_toplevel_drag_v1.rs @@ -71,7 +71,7 @@ impl XdgToplevelDragV1 { return Err(XdgToplevelDragV1Error::AlreadyDragged); } if let Some(prev) = self.toplevel.set(Some(toplevel)) { - if prev.xdg.surface.buffer.get().is_some() { + if prev.xdg.surface.buffer.is_some() { return Err(XdgToplevelDragV1Error::ToplevelAttached); } if prev.id != req.toplevel { diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 5fbb3793..28190beb 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -222,6 +222,7 @@ impl GuiElement for Button { None, r.scale(), None, + None, ); } } @@ -323,6 +324,7 @@ impl GuiElement for Label { None, r.scale(), None, + None, ); } } diff --git a/src/renderer.rs b/src/renderer.rs index e9999c21..8fe6eeee 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -2,10 +2,10 @@ use { crate::{ gfx_api::{GfxApiOpt, SampleRect}, ifs::{ - wl_buffer::WlBuffer, wl_callback::WlCallback, wl_surface::{ - xdg_surface::XdgSurface, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, WlSurface, + xdg_surface::XdgSurface, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, SurfaceBuffer, + WlSurface, }, wp_presentation_feedback::WpPresentationFeedback, }, @@ -152,12 +152,12 @@ impl Renderer<'_> { for title in &rd.titles { let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y); self.base - .render_texture(&title.tex, x, y, None, None, scale, None); + .render_texture(&title.tex, x, y, None, None, scale, None, None); } if let Some(status) = &rd.status { let (x, y) = self.base.scale_point(x + status.tex_x, y + status.tex_y); self.base - .render_texture(&status.tex.texture, x, y, None, None, scale, None); + .render_texture(&status.tex.texture, x, y, None, None, scale, None, None); } } if let Some(ws) = output.workspace.get() { @@ -194,7 +194,7 @@ impl Renderer<'_> { let x = x + (pos.width() - tex_width) / 2; let y = y + (pos.height() - tex_height) / 2; self.base - .render_texture(&tex.texture, x, y, None, None, self.base.scale, None); + .render_texture(&tex.texture, x, y, None, None, self.base.scale, None, None); } } @@ -231,6 +231,7 @@ impl Renderer<'_> { None, self.base.scale, None, + None, ); } } @@ -345,14 +346,14 @@ impl Renderer<'_> { pub fn render_buffer( &mut self, - buffer: &WlBuffer, + buffer: &Rc, x: i32, y: i32, tpoints: SampleRect, tsize: (i32, i32), bounds: Option<&Rect>, ) { - if let Some(tex) = buffer.texture.get() { + if let Some(tex) = buffer.buffer.texture.get() { self.base.render_texture( &tex, x, @@ -361,8 +362,9 @@ impl Renderer<'_> { Some(tsize), self.base.scale, bounds, + Some(buffer.clone()), ); - } else if let Some(color) = &buffer.color { + } else if let Some(color) = &buffer.buffer.color { if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) { let rect = match bounds { None => rect, @@ -409,8 +411,16 @@ impl Renderer<'_> { self.base.fill_boxes(&title_underline, &uc); if let Some(title) = floating.title_textures.get(&self.base.scale) { let (x, y) = self.base.scale_point(x + bw, y + bw); - self.base - .render_texture(&title.texture, x, y, None, None, self.base.scale, None); + self.base.render_texture( + &title.texture, + x, + y, + None, + None, + self.base.scale, + None, + None, + ); } let body = Rect::new_sized( x + bw, diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index 9d935d3b..ca9c622b 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -1,6 +1,8 @@ use { crate::{ - gfx_api::{CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, SampleRect}, + gfx_api::{ + BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, SampleRect, + }, rect::Rect, scale::Scale, theme::Color, @@ -130,6 +132,7 @@ impl RendererBase<'_> { tsize: Option<(i32, i32)>, tscale: Scale, bounds: Option<&Rect>, + buffer_resv: Option>, ) { let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity); @@ -154,18 +157,21 @@ impl RendererBase<'_> { } } + let target = FramebufferRect::new( + target_x[0] as f32, + target_y[0] as f32, + target_x[1] as f32, + target_y[1] as f32, + self.transform, + self.fb_width, + self.fb_height, + ); + self.ops.push(GfxApiOpt::CopyTexture(CopyTexture { tex: texture.clone(), source: texcoord, - target: FramebufferRect::new( - target_x[0] as f32, - target_y[0] as f32, - target_x[1] as f32, - target_y[1] as f32, - self.transform, - self.fb_width, - self.fb_height, - ), + target, + buffer_resv, })); } } diff --git a/src/state.rs b/src/state.rs index a1d3ebc2..78849dfe 100644 --- a/src/state.rs +++ b/src/state.rs @@ -408,7 +408,7 @@ impl State { } fn visit_surface(&mut self, node: &Rc) { if let Some(buffer) = node.buffer.get() { - buffer.handle_gfx_context_change(); + buffer.buffer.handle_gfx_context_change(); } node.node_visit_children(self); } @@ -814,6 +814,7 @@ impl State { size, Scale::from_int(1), None, + None, ); if render_hardware_cursors { for seat in self.globals.lock_seats().values() { diff --git a/src/video/dmabuf.rs b/src/video/dmabuf.rs index c217bfba..4ef66a96 100644 --- a/src/video/dmabuf.rs +++ b/src/video/dmabuf.rs @@ -48,6 +48,13 @@ impl DmaBuf { } false } + + pub fn import_sync_file(&self, flags: u32, sync_file: &OwnedFd) -> Result<(), OsError> { + for plane in &self.planes { + dma_buf_import_sync_file(&plane.fd, flags, sync_file)?; + } + Ok(()) + } } const DMA_BUF_BASE: u64 = b'b' as _; From d9fa3f67328eacc4dc01258610c84d156abd6f8e Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 21 Mar 2024 23:22:48 +0100 Subject: [PATCH 08/15] metal: preserve cursor state after present errors --- src/backends/metal/video.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index bd5decd9..df5a1bbb 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -709,16 +709,18 @@ impl MetalConnector { new_fb = Some(fb); } } + let mut cursor_swap_buffer = false; if self.cursor_changed.get() && cursor.is_some() { let plane = cursor.unwrap(); if self.cursor_enabled.get() { - let swap_buffer = self.cursor_swap_buffer.take(); - if swap_buffer { - self.cursor_front_buffer.fetch_add(1); + cursor_swap_buffer = self.cursor_swap_buffer.get(); + let mut front_buffer = self.cursor_front_buffer.get(); + if cursor_swap_buffer { + front_buffer = front_buffer.wrapping_add(1); } let buffers = self.cursor_buffers.get().unwrap(); - let buffer = &buffers[self.cursor_front_buffer.get() % buffers.len()]; - if swap_buffer { + let buffer = &buffers[front_buffer % buffers.len()]; + if cursor_swap_buffer { if let Some(tex) = &buffer.dev_tex { buffer.dev_fb.copy_texture(tex, 0, 0); } @@ -774,6 +776,10 @@ impl MetalConnector { if let Some(fb) = new_fb { self.next_framebuffer.set(Some(fb)); } + if cursor_swap_buffer { + self.cursor_swap_buffer.set(false); + self.cursor_front_buffer.fetch_add(1); + } self.can_present.set(false); self.has_damage.set(false); self.cursor_changed.set(false); From 1b4492c670b99e8121d32b15e41427380830260e Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 22 Mar 2024 13:26:56 +0100 Subject: [PATCH 09/15] render: propagate errors --- src/backends/metal.rs | 8 +++ src/backends/metal/video.rs | 79 +++++++++++++------------ src/backends/x.rs | 6 +- src/gfx_api.rs | 35 ++++++----- src/gfx_apis/gl/renderer/framebuffer.rs | 10 ++-- src/gfx_apis/vulkan.rs | 2 + src/gfx_apis/vulkan/image.rs | 6 +- src/gfx_apis/vulkan/renderer.rs | 4 +- src/ifs/jay_screencast.rs | 22 ++++--- src/ifs/wl_output.rs | 15 ++++- src/ifs/wl_seat.rs | 11 +++- src/portal/ptr_gui.rs | 20 ++++--- src/screenshoter.rs | 2 +- src/state.rs | 54 +++++++++-------- 14 files changed, 169 insertions(+), 105 deletions(-) diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 7130bf70..78a01341 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -114,6 +114,14 @@ pub enum MetalError { MissingDevModifier(&'static str), #[error("Device GFX API cannot read any buffers writable by the render GFX API (format {0})")] MissingRenderModifier(&'static str), + #[error("Could not render the frame")] + RenderFrame(#[source] GfxError), + #[error("Could not copy frame to output device")] + CopyToOutput(#[source] GfxError), + #[error("Could not perform atomic commit")] + Commit(#[source] DrmError), + #[error("Could not clear framebuffer")] + Clear(#[source] GfxError), } pub struct MetalBackend { diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index df5a1bbb..630a17b4 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -376,7 +376,9 @@ impl MetalConnector { async fn present_loop(self: Rc) { loop { self.present_trigger.triggered().await; - let _ = self.present(true); + if let Err(e) = self.present(true) { + log::error!("Could not present: {}", ErrorFmt(e)); + } } } @@ -585,7 +587,7 @@ impl MetalConnector { plane: &Rc, output: &OutputNode, try_direct_scanout: bool, - ) -> PresentFb { + ) -> Result { self.trim_scanout_cache(); let buffer_fb = buffer.render_fb(); let render_hw_cursor = !self.cursor_enabled.get(); @@ -630,23 +632,23 @@ impl MetalConnector { } let fb = match &direct_scanout_data { None => { + buffer_fb + .perform_render_pass(pass) + .map_err(MetalError::RenderFrame)?; + buffer.copy_to_dev()?; self.next_buffer.fetch_add(1); - buffer_fb.perform_render_pass(pass); - if let Some(tex) = &buffer.dev_tex { - buffer.dev_fb.copy_texture(tex, 0, 0); - } output.perform_screencopies(&buffer.render_tex, !render_hw_cursor, 0, 0, None); buffer.drm.clone() } Some(dsd) => dsd.fb.clone(), }; - PresentFb { + Ok(PresentFb { fb, direct_scanout_data, - } + }) } - pub fn present(&self, try_direct_scanout: bool) -> Result<(), ()> { + pub fn present(&self, try_direct_scanout: bool) -> Result<(), MetalError> { let crtc = match self.crtc.get() { Some(crtc) => crtc, _ => return Ok(()), @@ -676,7 +678,7 @@ impl MetalConnector { let buffer = &buffers[self.next_buffer.get() % buffers.len()]; let mut rr = self.render_result.borrow_mut(); let fb = - self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout); + self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout)?; rr.dispatch_frame_requests(); let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) = match &fb.direct_scanout_data { @@ -721,9 +723,7 @@ impl MetalConnector { let buffers = self.cursor_buffers.get().unwrap(); let buffer = &buffers[front_buffer % buffers.len()]; if cursor_swap_buffer { - if let Some(tex) = &buffer.dev_tex { - buffer.dev_fb.copy_texture(tex, 0, 0); - } + buffer.copy_to_dev()?; } let (width, height) = buffer.dev_fb.physical_size(); changes.change_object(plane.id, |c| { @@ -746,32 +746,28 @@ impl MetalConnector { } } if let Err(e) = changes.commit(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, 0) { - match e { - DrmError::Atomic(OsError(c::EACCES)) => { - log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master"); - } - _ => 'handle_failure: { - if let Some(fb) = &new_fb { - if let Some(dsd) = &fb.direct_scanout_data { - if self.present(false).is_ok() { - let mut cache = self.scanout_buffers.borrow_mut(); - if let Some(buffer) = cache.remove(&dsd.dma_buf_id) { - cache.insert( - dsd.dma_buf_id, - DirectScanoutCache { - tex: buffer.tex, - fb: None, - }, - ); - } - break 'handle_failure; - } + if let DrmError::Atomic(OsError(c::EACCES)) = e { + log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master"); + return Ok(()); + } + if let Some(fb) = &new_fb { + if let Some(dsd) = &fb.direct_scanout_data { + if self.present(false).is_ok() { + let mut cache = self.scanout_buffers.borrow_mut(); + if let Some(buffer) = cache.remove(&dsd.dma_buf_id) { + cache.insert( + dsd.dma_buf_id, + DirectScanoutCache { + tex: buffer.tex, + fb: None, + }, + ); } + return Ok(()); } - log::error!("Could not set plane framebuffer: {}", ErrorFmt(e)); } } - Err(()) + Err(MetalError::Commit(e)) } else { if let Some(fb) = new_fb { self.next_framebuffer.set(Some(fb)); @@ -2160,7 +2156,7 @@ impl MetalBackend { Ok(fb) => fb, Err(e) => return Err(MetalError::ImportFb(e)), }; - dev_fb.clear(); + dev_fb.clear().map_err(MetalError::Clear)?; let (dev_tex, render_tex, render_fb) = if dev.id == render_ctx.dev_id { let render_tex = match dev_img.to_texture() { Ok(fb) => fb, @@ -2209,7 +2205,7 @@ impl MetalBackend { Ok(fb) => fb, Err(e) => return Err(MetalError::ImportFb(e)), }; - render_fb.clear(); + render_fb.clear().map_err(MetalError::Clear)?; let render_tex = match render_img.to_texture() { Ok(fb) => fb, Err(e) => return Err(MetalError::ImportTexture(e)), @@ -2429,6 +2425,15 @@ impl RenderBuffer { .clone() .unwrap_or_else(|| self.dev_fb.clone()) } + + fn copy_to_dev(&self) -> Result<(), MetalError> { + let Some(tex) = &self.dev_tex else { + return Ok(()); + }; + self.dev_fb + .copy_texture(tex, 0, 0) + .map_err(MetalError::CopyToOutput) + } } fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool { diff --git a/src/backends/x.rs b/src/backends/x.rs index 77a35c81..1130e32c 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -738,13 +738,17 @@ impl XBackend { image.last_serial.set(serial); if let Some(node) = self.state.root.outputs.get(&output.id) { - self.state.present_output( + let res = self.state.present_output( &node, &image.fb.get(), &image.tex.get(), &mut self.render_result.borrow_mut(), true, ); + if let Err(e) = res { + log::error!("Could not render screen: {}", ErrorFmt(e)); + return; + } } let pp = PresentPixmap { diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 75fcf245..1d411222 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -189,7 +189,7 @@ pub trait GfxFramebuffer: Debug { fn physical_size(&self) -> (i32, i32); - fn render(&self, ops: Vec, clear: Option<&Color>); + fn render(&self, ops: Vec, clear: Option<&Color>) -> Result<(), GfxError>; fn copy_to_shm( self: Rc, @@ -206,13 +206,13 @@ pub trait GfxFramebuffer: Debug { } impl dyn GfxFramebuffer { - pub fn clear(&self) { - self.clear_with(0.0, 0.0, 0.0, 0.0); + pub fn clear(&self) -> Result<(), GfxError> { + self.clear_with(0.0, 0.0, 0.0, 0.0) } - pub fn clear_with(&self, r: f32, g: f32, b: f32, a: f32) { + pub fn clear_with(&self, r: f32, g: f32, b: f32, a: f32) -> Result<(), GfxError> { let ops = self.take_render_ops(); - self.render(ops, Some(&Color { r, g, b, a })); + self.render(ops, Some(&Color { r, g, b, a })) } pub fn logical_size(&self, transform: Transform) -> (i32, i32) { @@ -237,13 +237,18 @@ impl dyn GfxFramebuffer { } } - pub fn copy_texture(&self, texture: &Rc, x: i32, y: i32) { + pub fn copy_texture( + &self, + texture: &Rc, + x: i32, + y: i32, + ) -> Result<(), GfxError> { let mut ops = self.take_render_ops(); let scale = Scale::from_int(1); let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); renderer.render_texture(texture, x, y, None, None, scale, None, None); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); - self.render(ops, clear); + self.render(ops, clear) } pub fn render_custom( @@ -251,11 +256,11 @@ impl dyn GfxFramebuffer { scale: Scale, clear: Option<&Color>, f: &mut dyn FnMut(&mut RendererBase), - ) { + ) -> Result<(), GfxError> { let mut ops = self.take_render_ops(); let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); f(&mut renderer); - self.render(ops, clear); + self.render(ops, clear) } pub fn create_render_pass( @@ -326,7 +331,7 @@ impl dyn GfxFramebuffer { } } - pub fn perform_render_pass(&self, pass: GfxRenderPass) { + pub fn perform_render_pass(&self, pass: GfxRenderPass) -> Result<(), GfxError> { self.render(pass.ops, pass.clear.as_ref()) } @@ -338,7 +343,7 @@ impl dyn GfxFramebuffer { result: Option<&mut RenderResult>, scale: Scale, render_hardware_cursor: bool, - ) { + ) -> Result<(), GfxError> { self.render_node( node, state, @@ -361,7 +366,7 @@ impl dyn GfxFramebuffer { render_hardware_cursor: bool, black_background: bool, transform: Transform, - ) { + ) -> Result<(), GfxError> { let pass = self.create_render_pass( node, state, @@ -372,7 +377,7 @@ impl dyn GfxFramebuffer { black_background, transform, ); - self.perform_render_pass(pass); + self.perform_render_pass(pass) } pub fn render_hardware_cursor( @@ -381,7 +386,7 @@ impl dyn GfxFramebuffer { state: &State, scale: Scale, transform: Transform, - ) { + ) -> Result<(), GfxError> { let mut ops = self.take_render_ops(); let mut renderer = Renderer { base: self.renderer_base(&mut ops, scale, transform), @@ -394,7 +399,7 @@ impl dyn GfxFramebuffer { }, }; cursor.render_hardware_cursor(&mut renderer); - self.render(ops, Some(&Color::TRANSPARENT)); + self.render(ops, Some(&Color::TRANSPARENT)) } } diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 427f71f1..b409118c 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -10,6 +10,7 @@ use { renderer::context::GlRenderContext, run_ops, sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, + RenderError, }, theme::Color, }, @@ -64,9 +65,9 @@ impl Framebuffer { }); } - pub fn render(&self, ops: Vec, clear: Option<&Color>) { + pub fn render(&self, ops: Vec, clear: Option<&Color>) -> Result<(), RenderError> { let gles = self.ctx.ctx.dpy.gles; - let _ = self.ctx.ctx.with_current(|| { + let res = self.ctx.ctx.with_current(|| { unsafe { (gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo); (gles.glViewport)(0, 0, self.gl.width, self.gl.height); @@ -83,6 +84,7 @@ impl Framebuffer { Ok(()) }); *self.ctx.gfx_ops.borrow_mut() = ops; + res } } @@ -101,8 +103,8 @@ impl GfxFramebuffer for Framebuffer { (self.gl.width, self.gl.height) } - fn render(&self, ops: Vec, clear: Option<&Color>) { - self.render(ops, clear); + fn render(&self, ops: Vec, clear: Option<&Color>) -> Result<(), GfxError> { + self.render(ops, clear).map_err(|e| e.into()) } fn copy_to_shm( diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 83d3ecd2..3429d028 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -173,6 +173,8 @@ pub enum VulkanError { height: i32, stride: i32, }, + #[error(transparent)] + GfxError(GfxError), } impl From for GfxError { diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 9385fb1c..53cb0351 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -525,8 +525,10 @@ impl GfxFramebuffer for VulkanImage { (self.width as _, self.height as _) } - fn render(&self, ops: Vec, clear: Option<&Color>) { - self.renderer.execute(self, &ops, clear).unwrap(); + fn render(&self, ops: Vec, clear: Option<&Color>) -> Result<(), GfxError> { + self.renderer + .execute(self, &ops, clear) + .map_err(|e| e.into()) } fn copy_to_shm( diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 39727560..6b4cf5f4 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -698,7 +698,9 @@ impl VulkanRenderer { &[], true, )?; - (&*tmp_tex as &dyn GfxFramebuffer).copy_texture(&(tex.clone() as _), x, y); + (&*tmp_tex as &dyn GfxFramebuffer) + .copy_texture(&(tex.clone() as _), x, y) + .map_err(VulkanError::GfxError)?; self.read_all_pixels(&tmp_tex, stride, dst) } diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index f3a7a791..4e655d83 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -173,7 +173,7 @@ impl JayScreencast { let mut buffer = self.buffers.borrow_mut(); for (idx, buffer) in buffer.deref_mut().iter_mut().enumerate() { if buffer.free { - self.client.state.perform_screencopy( + let res = self.client.state.perform_screencopy( texture, &buffer.fb, on.global.pos.get(), @@ -183,12 +183,20 @@ impl JayScreencast { size, on.global.persistent.transform.get(), ); - self.client.event(Ready { - self_id: self.id, - idx: idx as _, - }); - buffer.free = false; - return; + match res { + Ok(_) => { + self.client.event(Ready { + self_id: self.id, + idx: idx as _, + }); + buffer.free = false; + return; + } + Err(e) => { + log::error!("Could not perform screencopy: {}", ErrorFmt(e)); + break; + } + } } } self.missed_frame.set(true); diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 8fb3f1cc..37d626ea 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -18,6 +18,7 @@ use { buffd::{MsgParser, MsgParserError}, clonecell::CloneCell, copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, linkedlist::LinkedList, transform_ext::TransformExt, }, @@ -243,7 +244,7 @@ impl WlOutputGlobal { if let Some(WlBufferStorage::Shm { mem, stride }) = wl_buffer.storage.borrow_mut().deref() { - self.state.perform_shm_screencopy( + let res = self.state.perform_shm_screencopy( tex, self.pos.get(), x_off, @@ -255,6 +256,11 @@ impl WlOutputGlobal { wl_buffer.format, Transform::None, ); + if let Err(e) = res { + log::warn!("Could not perform shm screencopy: {}", ErrorFmt(e)); + capture.send_failed(); + continue; + } } else { let fb = match wl_buffer.famebuffer.get() { Some(fb) => fb, @@ -264,7 +270,7 @@ impl WlOutputGlobal { continue; } }; - self.state.perform_screencopy( + let res = self.state.perform_screencopy( tex, &fb, self.pos.get(), @@ -274,6 +280,11 @@ impl WlOutputGlobal { size, Transform::None, ); + if let Err(e) = res { + log::warn!("Could not perform screencopy: {}", ErrorFmt(e)); + capture.send_failed(); + continue; + } } if capture.with_damage.get() { capture.send_damage(); diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 3edf241b..4e18cf6e 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -299,13 +299,20 @@ impl WlSeatGlobal { if extents.intersects(&Rect::new_sized(-x_rel, -y_rel, width, height).unwrap()) { if render { let buffer = hc.get_buffer(); - buffer.render_hardware_cursor( + let res = buffer.render_hardware_cursor( cursor.deref(), &self.state, scale, transform, ); - hc.swap_buffer(); + match res { + Ok(_) => { + hc.swap_buffer(); + } + Err(e) => { + log::error!("Could not render hardware cursor: {}", ErrorFmt(e)); + } + } } hc.set_enabled(true); let mode = output.global.mode.get(); diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 28190beb..570d671c 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -628,6 +628,19 @@ impl WindowData { } return; }; + + let res = buf + .fb + .render_custom(self.scale.get(), Some(&Color::from_gray(0)), &mut |r| { + if let Some(content) = self.content.get() { + content.render_at(r, 0.0, 0.0) + } + }); + if let Err(e) = res { + log::error!("Could not render frame: {}", ErrorFmt(e)); + return; + } + self.frame_missed.set(false); self.surface.frame({ @@ -643,13 +656,6 @@ impl WindowData { self.have_frame.set(false); buf.free.set(false); - buf.fb - .render_custom(self.scale.get(), Some(&Color::from_gray(0)), &mut |r| { - if let Some(content) = self.content.get() { - content.render_at(r, 0.0, 0.0) - } - }); - self.surface.attach(&buf.wl); self.surface.commit(); } diff --git a/src/screenshoter.rs b/src/screenshoter.rs index ec4b3f66..acfbaab4 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -78,7 +78,7 @@ pub fn take_screenshot(state: &State) -> Result true, false, Transform::None, - ); + )?; let drm = gbm.drm.dup_render()?.fd().clone(); Ok(Screenshot { drm, bo }) } diff --git a/src/state.rs b/src/state.rs index 78849dfe..2ac63154 100644 --- a/src/state.rs +++ b/src/state.rs @@ -81,6 +81,7 @@ use { sync::Arc, time::Duration, }, + thiserror::Error, }; pub struct State { @@ -769,7 +770,7 @@ impl State { tex: &Rc, rr: &mut RenderResult, render_hw_cursor: bool, - ) { + ) -> Result<(), GfxError> { fb.render_output( output, self, @@ -777,9 +778,10 @@ impl State { Some(rr), output.global.persistent.scale.get(), render_hw_cursor, - ); + )?; output.perform_screencopies(tex, !render_hw_cursor, 0, 0, None); rr.dispatch_frame_requests(); + Ok(()) } pub fn perform_screencopy( @@ -792,7 +794,7 @@ impl State { y_off: i32, size: Option<(i32, i32)>, transform: Transform, - ) { + ) -> Result<(), GfxError> { let mut ops = target.take_render_ops(); let mut renderer = Renderer { base: target.renderer_base(&mut ops, Scale::from_int(1), Transform::None), @@ -828,7 +830,7 @@ impl State { } } } - target.render(ops, Some(&Color::SOLID_BLACK)); + target.render(ops, Some(&Color::SOLID_BLACK)) } fn have_hardware_cursor(&self) -> bool { @@ -854,7 +856,7 @@ impl State { stride: i32, format: &'static Format, transform: Transform, - ) { + ) -> Result<(), ShmScreencopyError> { let (src_width, src_height) = src.size(); let mut needs_copy = capture.rect.x1() < x_off || capture.rect.x2() > x_off + src_width @@ -869,20 +871,11 @@ impl State { } let acc = if needs_copy { let Some(ctx) = self.render_ctx.get() else { - log::warn!("Cannot perform shm screencopy because there is no render context"); - return; + return Err(ShmScreencopyError::NoRenderContext); }; - let fb = - match ctx.create_fb(capture.rect.width(), capture.rect.height(), stride, format) { - Ok(f) => f, - Err(e) => { - log::warn!( - "Could not create temporary fb for screencopy: {}", - ErrorFmt(e) - ); - return; - } - }; + let fb = ctx + .create_fb(capture.rect.width(), capture.rect.height(), stride, format) + .map_err(ShmScreencopyError::CreateTemporaryFb)?; self.perform_screencopy( src, &fb, @@ -892,7 +885,8 @@ impl State { y_off - capture.rect.y1(), size, transform, - ); + ) + .map_err(ShmScreencopyError::CopyToTemporary)?; mem.access(|mem| { fb.copy_to_shm( 0, @@ -917,16 +911,12 @@ impl State { ) }) }; - let res = match acc { - Ok(res) => res, + match acc { + Ok(res) => res.map_err(ShmScreencopyError::ReadPixels), Err(e) => { capture.client.error(e); - return; + Ok(()) } - }; - if let Err(e) = res { - log::warn!("Could not read texture to memory: {}", ErrorFmt(e)); - capture.send_failed(); } } @@ -937,3 +927,15 @@ impl State { seat } } + +#[derive(Debug, Error)] +pub enum ShmScreencopyError { + #[error("There is no render context")] + NoRenderContext, + #[error("Could not create a bridge framebuffer")] + CreateTemporaryFb(#[source] GfxError), + #[error("Could not copy texture to bridge framebuffer")] + CopyToTemporary(#[source] GfxError), + #[error("Could not read pixels from texture")] + ReadPixels(#[source] GfxError), +} From 816315170f5c539029a68c7421826151a5152f53 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 21 Mar 2024 23:26:34 +0100 Subject: [PATCH 10/15] render: add support for explicit sync --- build/egl.rs | 28 +++++++ src/backend.rs | 3 +- src/backends/metal/video.rs | 59 ++++++++++--- src/cursor.rs | 34 ++++++-- src/gfx_api.rs | 73 +++++++++++++--- src/gfx_apis/gl.rs | 84 ++++++++++++++++--- src/gfx_apis/gl/egl/display.rs | 12 ++- src/gfx_apis/gl/egl/sys.rs | 3 + src/gfx_apis/gl/ext.rs | 6 ++ src/gfx_apis/gl/renderer.rs | 1 + src/gfx_apis/gl/renderer/context.rs | 8 +- src/gfx_apis/gl/renderer/framebuffer.rs | 24 ++++-- src/gfx_apis/gl/renderer/sync.rs | 107 ++++++++++++++++++++++++ src/gfx_apis/vulkan/image.rs | 8 +- src/gfx_apis/vulkan/renderer.rs | 84 ++++++++++++++----- src/gfx_apis/vulkan/semaphore.rs | 2 +- src/ifs/wl_seat.rs | 3 +- src/ifs/wl_surface.rs | 4 +- src/portal/ptr_gui.rs | 6 +- src/renderer.rs | 54 ++++++++++-- src/renderer/renderer_base.rs | 7 +- src/state.rs | 15 ++-- 22 files changed, 531 insertions(+), 94 deletions(-) create mode 100644 src/gfx_apis/gl/renderer/sync.rs diff --git a/build/egl.rs b/build/egl.rs index 148f421f..d82aa654 100644 --- a/build/egl.rs +++ b/build/egl.rs @@ -80,6 +80,34 @@ fn write_egl_procs(f: &mut W) -> anyhow::Result<()> { &[("target", "GLenum"), ("image", "GLeglImageOES")][..], ), ("glGetGraphicsResetStatusKHR", "GLenum", &[][..]), + ( + "eglCreateSyncKHR", + "EGLSyncKHR", + &[ + ("dpy", "EGLDisplay"), + ("ty", "EGLenum"), + ("attrib_list", "*const EGLint"), + ][..], + ), + ( + "eglDestroySyncKHR", + "EGLBoolean", + &[("dpy", "EGLDisplay"), ("sync", "EGLSyncKHR")][..], + ), + ( + "eglDupNativeFenceFDANDROID", + "EGLint", + &[("dpy", "EGLDisplay"), ("sync", "EGLSyncKHR")][..], + ), + ( + "eglWaitSyncKHR", + "EGLint", + &[ + ("dpy", "EGLDisplay"), + ("sync", "EGLSyncKHR"), + ("flags", "EGLint"), + ][..], + ), ]; writeln!(f, "use std::ptr;")?; diff --git a/src/backend.rs b/src/backend.rs index 82a2569e..da81118f 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -3,7 +3,7 @@ use { async_engine::SpawnedFuture, drm_feedback::DrmFeedback, fixed::Fixed, - gfx_api::GfxFramebuffer, + gfx_api::{GfxFramebuffer, SyncFile}, ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, libinput::consts::DeviceCapability, video::drm::{ConnectorType, DrmError, DrmVersion}, @@ -106,6 +106,7 @@ pub trait HardwareCursor: Debug { fn get_buffer(&self) -> Rc; fn set_position(&self, x: i32, y: i32); fn swap_buffer(&self); + fn set_sync_file(&self, sync_file: Option); fn commit(&self); fn size(&self) -> (i32, i32); } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 630a17b4..4f9c3328 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -9,7 +9,10 @@ use { drm_feedback::DrmFeedback, edid::Descriptor, format::{Format, ARGB8888, XRGB8888}, - gfx_api::{BufferResv, GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass, GfxTexture}, + gfx_api::{ + AcquireSync, BufferResv, GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass, + GfxTexture, ReleaseSync, SyncFile, + }, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, renderer::RenderResult, state::State, @@ -227,6 +230,7 @@ pub struct MetalConnector { pub cursor_buffers: CloneCell>>, pub cursor_front_buffer: NumCell, pub cursor_swap_buffer: Cell, + pub cursor_sync_file: CloneCell>, pub drm_feedback: CloneCell>>, pub scanout_buffers: RefCell>, @@ -244,6 +248,7 @@ pub struct MetalHardwareCursor { pub cursor_x_pending: Cell, pub cursor_y_pending: Cell, pub cursor_buffers: Rc<[RenderBuffer; 3]>, + pub sync_file: CloneCell>, pub have_changes: Cell, } @@ -270,6 +275,11 @@ impl HardwareCursor for MetalHardwareCursor { self.have_changes.set(true); } + fn set_sync_file(&self, sync_file: Option) { + self.sync_file.set(sync_file); + self.have_changes.set(true); + } + fn commit(&self) { if self.generation != self.connector.cursor_generation.get() { return; @@ -285,6 +295,7 @@ impl HardwareCursor for MetalHardwareCursor { if self.cursor_swap_buffer.take() { self.connector.cursor_swap_buffer.set(true); } + self.connector.cursor_sync_file.set(self.sync_file.take()); self.connector.cursor_changed.set(true); if self.connector.can_present.get() { self.connector.schedule_present(); @@ -350,6 +361,7 @@ pub struct DirectScanoutCache { #[derive(Debug)] pub struct DirectScanoutData { tex: Rc, + acquire_sync: AcquireSync, _resv: Option>, fb: Rc, dma_buf_id: DmaBufId, @@ -370,6 +382,7 @@ pub struct DirectScanoutPosition { pub struct PresentFb { fb: Rc, direct_scanout_data: Option, + sync_file: Option, } impl MetalConnector { @@ -396,6 +409,7 @@ impl MetalConnector { cursor_x_pending: Cell::new(self.cursor_x.get()), cursor_y_pending: Cell::new(self.cursor_y.get()), cursor_buffers: cp.clone(), + sync_file: Default::default(), have_changes: Cell::new(false), }) as _), _ => None, @@ -474,6 +488,10 @@ impl MetalConnector { } ct }; + if let AcquireSync::None = ct.acquire_sync { + // Cannot perform scanout without sync. + return None; + } if ct.source.buffer_transform != ct.target.output_transform { // Rotations and mirroring are not supported. return None; @@ -526,6 +544,7 @@ impl MetalConnector { if let Some(buffer) = cache.get(&dmabuf.id) { return buffer.fb.as_ref().map(|fb| DirectScanoutData { tex: buffer.tex.upgrade().unwrap(), + acquire_sync: ct.acquire_sync.clone(), _resv: ct.buffer_resv.clone(), fb: fb.clone(), dma_buf_id: dmabuf.id, @@ -550,6 +569,7 @@ impl MetalConnector { let data = match self.dev.master.add_fb(dmabuf, Some(format.format)) { Ok(fb) => Some(DirectScanoutData { tex: ct.tex.clone(), + acquire_sync: ct.acquire_sync.clone(), _resv: ct.buffer_resv.clone(), fb: Rc::new(fb), dma_buf_id: dmabuf.id, @@ -630,21 +650,31 @@ impl MetalConnector { }; log::debug!("{} direct scanout on {}", change, self.kernel_id()); } - let fb = match &direct_scanout_data { + let sync_file; + let fb; + match &direct_scanout_data { None => { - buffer_fb + let sf = buffer_fb .perform_render_pass(pass) .map_err(MetalError::RenderFrame)?; - buffer.copy_to_dev()?; + sync_file = buffer.copy_to_dev(sf)?; self.next_buffer.fetch_add(1); output.perform_screencopies(&buffer.render_tex, !render_hw_cursor, 0, 0, None); - buffer.drm.clone() + fb = buffer.drm.clone(); + } + Some(dsd) => { + sync_file = match &dsd.acquire_sync { + AcquireSync::None => None, + AcquireSync::Implicit => None, + AcquireSync::SyncFile { sync_file } => Some(sync_file.clone()), + }; + fb = dsd.fb.clone(); } - Some(dsd) => dsd.fb.clone(), }; Ok(PresentFb { fb, direct_scanout_data, + sync_file, }) } @@ -699,6 +729,7 @@ impl MetalConnector { ) } }; + let in_fence = fb.sync_file.as_ref().map(|s| s.raw()).unwrap_or(-1); changes.change_object(plane.id, |c| { c.change(plane.fb_id, fb.fb.id().0 as _); c.change(plane.src_w.id, (src_width as u64) << 16); @@ -707,11 +738,13 @@ impl MetalConnector { c.change(plane.crtc_y.id, crtc_y as u64); c.change(plane.crtc_w.id, crtc_w as u64); c.change(plane.crtc_h.id, crtc_h as u64); + c.change(plane.in_fence_fd, in_fence as u64); }); new_fb = Some(fb); } } let mut cursor_swap_buffer = false; + let mut cursor_sync_file = None; if self.cursor_changed.get() && cursor.is_some() { let plane = cursor.unwrap(); if self.cursor_enabled.get() { @@ -719,12 +752,14 @@ impl MetalConnector { let mut front_buffer = self.cursor_front_buffer.get(); if cursor_swap_buffer { front_buffer = front_buffer.wrapping_add(1); + cursor_sync_file = self.cursor_sync_file.get(); } let buffers = self.cursor_buffers.get().unwrap(); let buffer = &buffers[front_buffer % buffers.len()]; if cursor_swap_buffer { - buffer.copy_to_dev()?; + cursor_sync_file = buffer.copy_to_dev(cursor_sync_file)?; } + let in_fence = cursor_sync_file.as_ref().map(|s| s.raw()).unwrap_or(-1); let (width, height) = buffer.dev_fb.physical_size(); changes.change_object(plane.id, |c| { c.change(plane.fb_id, buffer.drm.id().0 as _); @@ -737,6 +772,7 @@ impl MetalConnector { c.change(plane.src_y.id, 0); c.change(plane.src_w.id, (width as u64) << 16); c.change(plane.src_h.id, (height as u64) << 16); + c.change(plane.in_fence_fd, in_fence as u64); }); } else { changes.change_object(plane.id, |c| { @@ -775,6 +811,7 @@ impl MetalConnector { if cursor_swap_buffer { self.cursor_swap_buffer.set(false); self.cursor_front_buffer.fetch_add(1); + self.cursor_sync_file.take(); } self.can_present.set(false); self.has_damage.set(false); @@ -1026,6 +1063,7 @@ fn create_connector( cursor_changed: Cell::new(false), cursor_front_buffer: Default::default(), cursor_swap_buffer: Cell::new(false), + cursor_sync_file: Default::default(), drm_feedback: Default::default(), scanout_buffers: Default::default(), active_framebuffer: Default::default(), @@ -2426,12 +2464,13 @@ impl RenderBuffer { .unwrap_or_else(|| self.dev_fb.clone()) } - fn copy_to_dev(&self) -> Result<(), MetalError> { + fn copy_to_dev(&self, sync_file: Option) -> Result, MetalError> { let Some(tex) = &self.dev_tex else { - return Ok(()); + return Ok(sync_file); }; + let acquire_point = AcquireSync::from_sync_file(sync_file); self.dev_fb - .copy_texture(tex, 0, 0) + .copy_texture(tex, acquire_point, ReleaseSync::Implicit, 0, 0) .map_err(MetalError::CopyToOutput) } } diff --git a/src/cursor.rs b/src/cursor.rs index c644f28e..a9759b47 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -2,7 +2,7 @@ use { crate::{ fixed::Fixed, format::ARGB8888, - gfx_api::{GfxContext, GfxError, GfxTexture}, + gfx_api::{AcquireSync, GfxContext, GfxError, GfxTexture, ReleaseSync}, rect::Rect, renderer::Renderer, scale::Scale, @@ -379,6 +379,8 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed scale, None, None, + AcquireSync::None, + ReleaseSync::None, ); } } @@ -390,9 +392,18 @@ impl Cursor for StaticCursor { fn render_hardware_cursor(&self, renderer: &mut Renderer) { if let Some(img) = self.image.scales.get(&renderer.scale()) { - renderer - .base - .render_texture(&img.tex, 0, 0, None, None, renderer.scale(), None, None); + renderer.base.render_texture( + &img.tex, + 0, + 0, + None, + None, + renderer.scale(), + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); } } @@ -420,9 +431,18 @@ impl Cursor for AnimatedCursor { fn render_hardware_cursor(&self, renderer: &mut Renderer) { let img = &self.images[self.idx.get()]; if let Some(img) = img.scales.get(&renderer.scale()) { - renderer - .base - .render_texture(&img.tex, 0, 0, None, None, renderer.scale(), None, None); + renderer.base.render_texture( + &img.tex, + 0, + 0, + None, + None, + renderer.scale(), + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); } } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 1d411222..f3187b5d 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -145,6 +145,8 @@ pub struct CopyTexture { pub source: SampleRect, pub target: FramebufferRect, pub buffer_resv: Option>, + pub acquire_sync: AcquireSync, + pub release_sync: ReleaseSync, } #[derive(Clone, Debug)] @@ -160,6 +162,40 @@ impl Deref for SyncFile { unsafe impl UnsafeCellCloneSafe for SyncFile {} +#[derive(Clone)] +pub enum AcquireSync { + None, + Implicit, + SyncFile { sync_file: SyncFile }, +} + +impl AcquireSync { + pub fn from_sync_file(sync_file: Option) -> Self { + match sync_file { + None => Self::Implicit, + Some(sync_file) => Self::SyncFile { sync_file }, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum ReleaseSync { + None, + Implicit, + Explicit, +} + +impl Debug for AcquireSync { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let name = match self { + AcquireSync::None => "None", + AcquireSync::Implicit => "Implicit", + AcquireSync::SyncFile { .. } => "SyncFile", + }; + f.debug_struct(name).finish_non_exhaustive() + } +} + pub trait BufferResv: Debug { fn set_sync_file(&self, user: BufferResvUser, sync_file: &SyncFile); } @@ -189,7 +225,11 @@ pub trait GfxFramebuffer: Debug { fn physical_size(&self) -> (i32, i32); - fn render(&self, ops: Vec, clear: Option<&Color>) -> Result<(), GfxError>; + fn render( + &self, + ops: Vec, + clear: Option<&Color>, + ) -> Result, GfxError>; fn copy_to_shm( self: Rc, @@ -206,11 +246,11 @@ pub trait GfxFramebuffer: Debug { } impl dyn GfxFramebuffer { - pub fn clear(&self) -> Result<(), GfxError> { + pub fn clear(&self) -> Result, GfxError> { self.clear_with(0.0, 0.0, 0.0, 0.0) } - pub fn clear_with(&self, r: f32, g: f32, b: f32, a: f32) -> Result<(), GfxError> { + pub fn clear_with(&self, r: f32, g: f32, b: f32, a: f32) -> Result, GfxError> { let ops = self.take_render_ops(); self.render(ops, Some(&Color { r, g, b, a })) } @@ -240,13 +280,26 @@ impl dyn GfxFramebuffer { pub fn copy_texture( &self, texture: &Rc, + acquire_sync: AcquireSync, + release_sync: ReleaseSync, x: i32, y: i32, - ) -> Result<(), GfxError> { + ) -> Result, GfxError> { let mut ops = self.take_render_ops(); let scale = Scale::from_int(1); let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); - renderer.render_texture(texture, x, y, None, None, scale, None, None); + renderer.render_texture( + texture, + x, + y, + None, + None, + scale, + None, + None, + acquire_sync, + release_sync, + ); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); self.render(ops, clear) } @@ -256,7 +309,7 @@ impl dyn GfxFramebuffer { scale: Scale, clear: Option<&Color>, f: &mut dyn FnMut(&mut RendererBase), - ) -> Result<(), GfxError> { + ) -> Result, GfxError> { let mut ops = self.take_render_ops(); let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); f(&mut renderer); @@ -331,7 +384,7 @@ impl dyn GfxFramebuffer { } } - pub fn perform_render_pass(&self, pass: GfxRenderPass) -> Result<(), GfxError> { + pub fn perform_render_pass(&self, pass: GfxRenderPass) -> Result, GfxError> { self.render(pass.ops, pass.clear.as_ref()) } @@ -343,7 +396,7 @@ impl dyn GfxFramebuffer { result: Option<&mut RenderResult>, scale: Scale, render_hardware_cursor: bool, - ) -> Result<(), GfxError> { + ) -> Result, GfxError> { self.render_node( node, state, @@ -366,7 +419,7 @@ impl dyn GfxFramebuffer { render_hardware_cursor: bool, black_background: bool, transform: Transform, - ) -> Result<(), GfxError> { + ) -> Result, GfxError> { let pass = self.create_render_pass( node, state, @@ -386,7 +439,7 @@ impl dyn GfxFramebuffer { state: &State, scale: Scale, transform: Transform, - ) -> Result<(), GfxError> { + ) -> Result, GfxError> { let mut ops = self.take_render_ops(); let mut renderer = Renderer { base: self.renderer_base(&mut ops, scale, transform), diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index ecfa58fc..db1dbfc4 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -68,8 +68,8 @@ macro_rules! dynload { use { crate::{ gfx_api::{ - CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, - SampleRect, + AcquireSync, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, + ReleaseSync, SyncFile, }, gfx_apis::gl::{ gl::texture::image_target, @@ -80,8 +80,9 @@ use { }, }, theme::Color, - utils::{rc_eq::rc_eq, vecstorage::VecStorage}, + utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage}, video::{ + dmabuf::DMA_BUF_SYNC_READ, drm::{Drm, DrmError}, gbm::GbmError, }, @@ -181,6 +182,14 @@ enum RenderError { NoSupportedFormats, #[error("Cannot convert a shm texture into a framebuffer")] ShmTextureToFb, + #[error("Could not create EGLSyncKHR")] + CreateEglSync, + #[error("Could not destroy EGLSyncKHR")] + DestroyEglSync, + #[error("Could not export sync file")] + ExportSyncFile, + #[error("Could not insert wait for EGLSyncKHR")] + WaitSync, } #[derive(Default)] @@ -190,7 +199,7 @@ struct GfxGlState { copy_tex: VecStorage<&'static CopyTexture>, } -fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) { +fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option { let mut state = fb.ctx.gl_state.borrow_mut(); let state = &mut *state; let mut fill_rect = state.fill_rect.take(); @@ -256,9 +265,30 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) { } } for tex in &*copy_tex { - render_texture(&fb.ctx, &tex.tex.as_gl(), &tex.target, &tex.source) + render_texture(&fb.ctx, tex); } } + if fb.ctx.ctx.dpy.explicit_sync { + let file = match fb.ctx.ctx.export_sync_file() { + Ok(f) => SyncFile(Rc::new(f)), + Err(e) => { + log::error!("Could not create sync file: {}", ErrorFmt(e)); + return None; + } + }; + let user = fb.ctx.buffer_resv_user; + for op in ops { + if let GfxApiOpt::CopyTexture(ct) = op { + if ct.release_sync == ReleaseSync::Explicit { + if let Some(resv) = &ct.buffer_resv { + resv.set_sync_file(user, &file); + } + } + } + } + return Some(file); + } + None } fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) { @@ -280,15 +310,13 @@ fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) { } } -fn render_texture( - ctx: &GlRenderContext, - texture: &Texture, - target_rect: &FramebufferRect, - src: &SampleRect, -) { +fn render_texture(ctx: &GlRenderContext, tex: &CopyTexture) { + let texture = tex.tex.as_gl(); assert!(rc_eq(&ctx.ctx, &texture.ctx.ctx)); let gles = ctx.ctx.dpy.gles; unsafe { + handle_explicit_sync(ctx, texture, &tex.acquire_sync); + (gles.glActiveTexture)(GL_TEXTURE0); let target = image_target(texture.gl.external_only); @@ -321,8 +349,8 @@ fn render_texture( (gles.glUniform1i)(prog.tex, 0); - let texcoord = src.to_points(); - let pos = target_rect.to_points(); + let texcoord = tex.source.to_points(); + let pos = tex.target.to_points(); (gles.glVertexAttribPointer)( prog.texcoord as _, @@ -346,6 +374,36 @@ fn render_texture( } } +fn handle_explicit_sync(ctx: &GlRenderContext, texture: &Texture, sync: &AcquireSync) { + let sync_file = match sync { + AcquireSync::None | AcquireSync::Implicit => return, + AcquireSync::SyncFile { sync_file } => sync_file, + }; + let sync_file = match uapi::fcntl_dupfd_cloexec(sync_file.raw(), 0) { + Ok(s) => s, + Err(e) => { + log::error!("Could not dup sync file: {}", ErrorFmt(e)); + return; + } + }; + if ctx.ctx.dpy.explicit_sync { + let sync = match ctx.ctx.create_sync(Some(sync_file)) { + Ok(s) => s, + Err(e) => { + log::error!("Could import sync file: {}", ErrorFmt(e)); + return; + } + }; + sync.wait(); + } else { + if let Some(img) = &texture.gl.img { + if let Err(e) = img.dmabuf.import_sync_file(DMA_BUF_SYNC_READ, &sync_file) { + log::error!("Could not import sync file into dmabuf: {}", ErrorFmt(e)); + } + } + } +} + impl dyn GfxTexture { fn as_gl(&self) -> &Texture { self.as_any() diff --git a/src/gfx_apis/gl/egl/display.rs b/src/gfx_apis/gl/egl/display.rs index 7237dc32..80e4a91c 100644 --- a/src/gfx_apis/gl/egl/display.rs +++ b/src/gfx_apis/gl/egl/display.rs @@ -24,9 +24,10 @@ use { PROCS, }, ext::{ - get_display_ext, get_gl_ext, DisplayExt, GlExt, EXT_CREATE_CONTEXT_ROBUSTNESS, - EXT_IMAGE_DMA_BUF_IMPORT_MODIFIERS, GL_OES_EGL_IMAGE, GL_OES_EGL_IMAGE_EXTERNAL, - KHR_IMAGE_BASE, KHR_NO_CONFIG_CONTEXT, KHR_SURFACELESS_CONTEXT, + get_display_ext, get_gl_ext, DisplayExt, GlExt, ANDROID_NATIVE_FENCE_SYNC, + EXT_CREATE_CONTEXT_ROBUSTNESS, EXT_IMAGE_DMA_BUF_IMPORT_MODIFIERS, + GL_OES_EGL_IMAGE, GL_OES_EGL_IMAGE_EXTERNAL, KHR_FENCE_SYNC, KHR_IMAGE_BASE, + KHR_NO_CONFIG_CONTEXT, KHR_SURFACELESS_CONTEXT, KHR_WAIT_SYNC, MESA_CONFIGLESS_CONTEXT, }, proc::ExtProc, @@ -65,6 +66,7 @@ pub struct EglDisplay { pub formats: AHashMap, pub gbm: Rc, pub dpy: EGLDisplay, + pub explicit_sync: bool, } impl EglDisplay { @@ -99,6 +101,7 @@ impl EglDisplay { formats: AHashMap::new(), gbm: Rc::new(gbm), dpy, + explicit_sync: false, }; let mut major = 0; let mut minor = 0; @@ -122,6 +125,9 @@ impl EglDisplay { return Err(RenderError::SurfacelessContext); } dpy.formats = query_formats(procs, dpy.dpy)?; + dpy.explicit_sync = dpy + .exts + .contains(KHR_FENCE_SYNC | KHR_WAIT_SYNC | ANDROID_NATIVE_FENCE_SYNC); Ok(Rc::new(dpy)) } diff --git a/src/gfx_apis/gl/egl/sys.rs b/src/gfx_apis/gl/egl/sys.rs index 044d4de5..0b4493e3 100644 --- a/src/gfx_apis/gl/egl/sys.rs +++ b/src/gfx_apis/gl/egl/sys.rs @@ -6,6 +6,7 @@ pub type EGLBoolean = c::c_uint; #[allow(dead_code)] pub type EGLuint64KHR = u64; pub type EGLAttrib = isize; +pub type EGLSyncKHR = *mut u8; egl_transparent!(EGLDisplay); egl_transparent!(EGLSurface); @@ -83,6 +84,8 @@ pub const EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT: EGLint = 0x3449; pub const EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT: EGLint = 0x344A; pub const EGL_IMAGE_PRESERVED_KHR: EGLint = 0x30D2; pub const EGL_LINUX_DMA_BUF_EXT: EGLint = 0x3270; +pub const EGL_SYNC_NATIVE_FENCE_ANDROID: EGLenum = 0x3144; +pub const EGL_SYNC_NATIVE_FENCE_FD_ANDROID: EGLint = 0x3145; dynload! { EGL: Egl from "libEGL.so" { diff --git a/src/gfx_apis/gl/ext.rs b/src/gfx_apis/gl/ext.rs index fdc23ea1..6967b4cd 100644 --- a/src/gfx_apis/gl/ext.rs +++ b/src/gfx_apis/gl/ext.rs @@ -78,6 +78,9 @@ bitflags! { KHR_SURFACELESS_CONTEXT = 1 << 5, IMG_CONTEXT_PRIORITY = 1 << 6, EXT_CREATE_CONTEXT_ROBUSTNESS = 1 << 7, + KHR_FENCE_SYNC = 1 << 8, + KHR_WAIT_SYNC = 1 << 9, + ANDROID_NATIVE_FENCE_SYNC = 1 << 10, } pub(crate) unsafe fn get_display_ext(dpy: EGLDisplay) -> DisplayExt { @@ -96,6 +99,9 @@ pub(crate) unsafe fn get_display_ext(dpy: EGLDisplay) -> DisplayExt { "EGL_EXT_create_context_robustness", EXT_CREATE_CONTEXT_ROBUSTNESS, ), + ("EGL_KHR_fence_sync", KHR_FENCE_SYNC), + ("EGL_KHR_wait_sync", KHR_WAIT_SYNC), + ("EGL_ANDROID_native_fence_sync", ANDROID_NATIVE_FENCE_SYNC), ]; match get_dpy_extensions(dpy) { Some(exts) => get_typed_ext(&exts, DisplayExt::none(), &map), diff --git a/src/gfx_apis/gl/renderer.rs b/src/gfx_apis/gl/renderer.rs index eaa9bc1d..9183d7b7 100644 --- a/src/gfx_apis/gl/renderer.rs +++ b/src/gfx_apis/gl/renderer.rs @@ -1,4 +1,5 @@ pub(super) mod context; pub(super) mod framebuffer; pub(super) mod image; +pub(super) mod sync; pub(super) mod texture; diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index bba4a6b4..8b931223 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -2,8 +2,8 @@ use { crate::{ format::{Format, XRGB8888}, gfx_api::{ - GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, GfxTexture, - ResetStatus, + BufferResvUser, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, + GfxTexture, ResetStatus, }, gfx_apis::gl::{ egl::{context::EglContext, display::EglDisplay, image::EglImage}, @@ -65,6 +65,8 @@ pub(in crate::gfx_apis::gl) struct GlRenderContext { pub(crate) gfx_ops: RefCell>, pub(in crate::gfx_apis::gl) gl_state: RefCell, + + pub(in crate::gfx_apis::gl) buffer_resv_user: BufferResvUser, } impl Debug for GlRenderContext { @@ -141,6 +143,8 @@ impl GlRenderContext { gfx_ops: Default::default(), gl_state: Default::default(), + + buffer_resv_user: Default::default(), }) } diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index b409118c..26e00faf 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -1,7 +1,7 @@ use { crate::{ format::Format, - gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer}, + gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, SyncFile}, gfx_apis::gl::{ gl::{ frame_buffer::GlFrameBuffer, @@ -65,7 +65,11 @@ impl Framebuffer { }); } - pub fn render(&self, ops: Vec, clear: Option<&Color>) -> Result<(), RenderError> { + pub fn render( + &self, + ops: Vec, + clear: Option<&Color>, + ) -> Result, RenderError> { let gles = self.ctx.ctx.dpy.gles; let res = self.ctx.ctx.with_current(|| { unsafe { @@ -77,11 +81,13 @@ impl Framebuffer { } (gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } - run_ops(self, &ops); - unsafe { - (gles.glFlush)(); + let fd = run_ops(self, &ops); + if fd.is_none() { + unsafe { + (gles.glFlush)(); + } } - Ok(()) + Ok(fd) }); *self.ctx.gfx_ops.borrow_mut() = ops; res @@ -103,7 +109,11 @@ impl GfxFramebuffer for Framebuffer { (self.gl.width, self.gl.height) } - fn render(&self, ops: Vec, clear: Option<&Color>) -> Result<(), GfxError> { + fn render( + &self, + ops: Vec, + clear: Option<&Color>, + ) -> Result, GfxError> { self.render(ops, clear).map_err(|e| e.into()) } diff --git a/src/gfx_apis/gl/renderer/sync.rs b/src/gfx_apis/gl/renderer/sync.rs new file mode 100644 index 00000000..588791bd --- /dev/null +++ b/src/gfx_apis/gl/renderer/sync.rs @@ -0,0 +1,107 @@ +use { + crate::{ + gfx_apis::gl::{ + egl::context::EglContext, + sys::{ + EGLBoolean, EGLSyncKHR, EGL_NONE, EGL_SYNC_NATIVE_FENCE_ANDROID, + EGL_SYNC_NATIVE_FENCE_FD_ANDROID, EGL_TRUE, + }, + RenderError, + }, + utils::errorfmt::ErrorFmt, + }, + std::rc::Rc, + uapi::OwnedFd, +}; + +pub struct EglSync { + ctx: Rc, + sync: EGLSyncKHR, +} + +impl EglContext { + pub fn export_sync_file(self: &Rc) -> Result { + self.create_sync(None)?.export_sync_file() + } + + pub fn create_sync(self: &Rc, file: Option) -> Result { + let mut attribs = [EGL_NONE; 3]; + if let Some(file) = &file { + attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID; + attribs[1] = file.raw(); + } + self.with_current(|| unsafe { + let sync = self.dpy.procs.eglCreateSyncKHR( + self.dpy.dpy, + EGL_SYNC_NATIVE_FENCE_ANDROID, + attribs.as_ptr(), + ); + if sync.is_null() { + Err(RenderError::CreateEglSync) + } else { + if let Some(file) = file { + file.unwrap(); + } + Ok(EglSync { + ctx: self.clone(), + sync, + }) + } + }) + } +} + +impl EglSync { + pub fn wait(&self) { + let res = self.ctx.with_current(|| unsafe { + let res = self + .ctx + .dpy + .procs + .eglWaitSyncKHR(self.ctx.dpy.dpy, self.sync, 0); + if res as EGLBoolean == EGL_TRUE { + Ok(()) + } else { + Err(RenderError::WaitSync) + } + }); + if let Err(e) = res { + log::warn!("Could not insert wait point: {}", ErrorFmt(e)); + } + } + + pub fn export_sync_file(&self) -> Result { + self.ctx.with_current(|| unsafe { + let fd = self + .ctx + .dpy + .procs + .eglDupNativeFenceFDANDROID(self.ctx.dpy.dpy, self.sync); + if fd == -1 { + Err(RenderError::ExportSyncFile) + } else { + Ok(OwnedFd::new(fd)) + } + }) + } +} + +impl Drop for EglSync { + fn drop(&mut self) { + let res = self.ctx.with_current(|| unsafe { + let res = self + .ctx + .dpy + .procs + .eglDestroySyncKHR(self.ctx.dpy.dpy, self.sync); + if res == EGL_TRUE { + Ok(()) + } else { + Err(RenderError::DestroyEglSync) + } + }); + if let Err(e) = res { + log::error!("{}", ErrorFmt(e)); + } + } +} diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 53cb0351..ce09554e 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -1,7 +1,7 @@ use { crate::{ format::Format, - gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture}, + gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture, SyncFile}, gfx_apis::vulkan::{ allocator::VulkanAllocation, device::VulkanDevice, format::VulkanMaxExtents, renderer::VulkanRenderer, util::OnDrop, VulkanError, @@ -525,7 +525,11 @@ impl GfxFramebuffer for VulkanImage { (self.width as _, self.height as _) } - fn render(&self, ops: Vec, clear: Option<&Color>) -> Result<(), GfxError> { + fn render( + &self, + ops: Vec, + clear: Option<&Color>, + ) -> Result, GfxError> { self.renderer .execute(self, &ops, clear) .map_err(|e| e.into()) diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 6b4cf5f4..e8529fdd 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -3,7 +3,8 @@ use { async_engine::SpawnedFuture, format::Format, gfx_api::{ - BufferResv, BufferResvUser, GfxApiOpt, GfxFormat, GfxFramebuffer, GfxTexture, SyncFile, + AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxFormat, GfxFramebuffer, + GfxTexture, ReleaseSync, SyncFile, }, gfx_apis::vulkan::{ allocator::VulkanAllocator, @@ -48,6 +49,7 @@ use { rc::Rc, slice, }, + uapi::OwnedFd, }; pub struct VulkanRenderer { @@ -70,6 +72,8 @@ pub struct VulkanRenderer { pub(super) struct UsedTexture { tex: Rc, resv: Option>, + acquire_sync: AcquireSync, + release_sync: ReleaseSync, } #[derive(Default)] @@ -185,6 +189,8 @@ impl VulkanRenderer { memory.textures.push(UsedTexture { tex, resv: c.buffer_resv.clone(), + acquire_sync: c.acquire_sync.clone(), + release_sync: c.release_sync, }); } } @@ -538,12 +544,11 @@ impl VulkanRenderer { let import = |infos: &mut Vec, semaphores: &mut Vec>, img: &VulkanImage, + sync: &AcquireSync, flag: u32| -> Result<(), VulkanError> { if let VulkanImageMemory::DmaBuf(buf) = &img.ty { - for plane in &buf.template.dmabuf.planes { - let fd = dma_buf_export_sync_file(&plane.fd, flag) - .map_err(VulkanError::IoctlExportSyncFile)?; + let mut import_sync_file = |fd: OwnedFd| -> Result<(), VulkanError> { let semaphore = self.allocate_semaphore()?; semaphore.import_sync_file(fd)?; infos.push( @@ -553,6 +558,22 @@ impl VulkanRenderer { .build(), ); semaphores.push(semaphore); + Ok(()) + }; + match sync { + AcquireSync::None => {} + AcquireSync::Implicit { .. } => { + for plane in &buf.template.dmabuf.planes { + let fd = dma_buf_export_sync_file(&plane.fd, flag) + .map_err(VulkanError::IoctlExportSyncFile)?; + import_sync_file(fd)?; + } + } + AcquireSync::SyncFile { sync_file } => { + let fd = uapi::fcntl_dupfd_cloexec(sync_file.raw(), 0) + .map_err(|e| VulkanError::Dupfd(e.into()))?; + import_sync_file(fd)?; + } } } Ok(()) @@ -562,6 +583,7 @@ impl VulkanRenderer { &mut memory.wait_semaphore_infos, &mut memory.wait_semaphores, &texture.tex, + &texture.acquire_sync, DMA_BUF_SYNC_READ, )?; } @@ -569,6 +591,7 @@ impl VulkanRenderer { &mut memory.wait_semaphore_infos, &mut memory.wait_semaphores, fb, + &AcquireSync::Implicit, DMA_BUF_SYNC_WRITE, )?; Ok(()) @@ -580,20 +603,31 @@ impl VulkanRenderer { Some(sync_file) => sync_file, _ => return, }; - let import = |img: &VulkanImage, resv: Option>, flag: u32| { - if let Some(resv) = resv { - resv.set_sync_file(self.buffer_resv_user, sync_file); - } else if let VulkanImageMemory::DmaBuf(buf) = &img.ty { - if let Err(e) = buf.template.dmabuf.import_sync_file(flag, sync_file) { - log::error!("Could not import sync file into dmabuf: {}", ErrorFmt(e)); - log::warn!("Relying on implicit sync"); + let import = + |img: &VulkanImage, sync: ReleaseSync, resv: Option>, flag: u32| { + if sync == ReleaseSync::None { + return; } - } - }; + if let Some(resv) = resv { + resv.set_sync_file(self.buffer_resv_user, sync_file); + } else if sync == ReleaseSync::Implicit { + if let VulkanImageMemory::DmaBuf(buf) = &img.ty { + if let Err(e) = buf.template.dmabuf.import_sync_file(flag, sync_file) { + log::error!("Could not import sync file into dmabuf: {}", ErrorFmt(e)); + log::warn!("Relying on implicit sync"); + } + } + } + }; for texture in &mut memory.textures { - import(&texture.tex, texture.resv.take(), DMA_BUF_SYNC_READ); + import( + &texture.tex, + texture.release_sync, + texture.resv.take(), + DMA_BUF_SYNC_READ, + ); } - import(fb, None, DMA_BUF_SYNC_WRITE); + import(fb, ReleaseSync::Implicit, None, DMA_BUF_SYNC_WRITE); } fn submit(&self, buf: CommandBuffer) -> Result<(), VulkanError> { @@ -656,7 +690,7 @@ impl VulkanRenderer { }); self.pending_frames.set(frame.point, frame.clone()); let future = self.device.instance.eng.spawn(await_release( - memory.release_sync_file.take(), + memory.release_sync_file.clone(), self.device.instance.ring.clone(), frame.clone(), self.clone(), @@ -699,7 +733,13 @@ impl VulkanRenderer { true, )?; (&*tmp_tex as &dyn GfxFramebuffer) - .copy_texture(&(tex.clone() as _), x, y) + .copy_texture( + &(tex.clone() as _), + AcquireSync::None, + ReleaseSync::None, + x, + y, + ) .map_err(VulkanError::GfxError)?; self.read_all_pixels(&tmp_tex, stride, dst) } @@ -839,9 +879,9 @@ impl VulkanRenderer { fb: &VulkanImage, opts: &[GfxApiOpt], clear: Option<&Color>, - ) -> Result<(), VulkanError> { + ) -> Result, VulkanError> { let res = self.try_execute(fb, opts, clear); - { + let sync_file = { let mut memory = self.memory.borrow_mut(); memory.flush.clear(); memory.textures.clear(); @@ -849,9 +889,9 @@ impl VulkanRenderer { memory.sample.clear(); memory.wait_semaphores.clear(); memory.release_fence.take(); - memory.release_sync_file.take(); - } - res + memory.release_sync_file.take() + }; + res.map(|_| sync_file) } fn allocate_command_buffer(&self) -> Result, VulkanError> { diff --git a/src/gfx_apis/vulkan/semaphore.rs b/src/gfx_apis/vulkan/semaphore.rs index e9b03681..0e25b801 100644 --- a/src/gfx_apis/vulkan/semaphore.rs +++ b/src/gfx_apis/vulkan/semaphore.rs @@ -47,8 +47,8 @@ impl VulkanSemaphore { .external_semaphore_fd .import_semaphore_fd(&fd_info) }; - mem::forget(sync_file); res.map_err(VulkanError::ImportSyncFile)?; + mem::forget(sync_file); Ok(()) } } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 4e18cf6e..3ed55acb 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -306,7 +306,8 @@ impl WlSeatGlobal { transform, ); match res { - Ok(_) => { + Ok(sync_file) => { + hc.set_sync_file(sync_file); hc.swap_buffer(); } Err(e) => { diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index e003ff2c..6acb2f11 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -16,7 +16,7 @@ use { client::{Client, ClientError, RequestParser}, drm_feedback::DrmFeedback, fixed::Fixed, - gfx_api::{BufferResv, BufferResvUser, SampleRect, SyncFile}, + gfx_api::{AcquireSync, BufferResv, BufferResvUser, SampleRect, SyncFile}, ifs::{ wl_buffer::WlBuffer, wl_callback::WlCallback, @@ -136,6 +136,7 @@ impl NodeVisitorBase for SurfaceSendPreferredTransformVisitor { pub struct SurfaceBuffer { pub buffer: Rc, sync_files: SmallMap, + pub sync: AcquireSync, } impl Drop for SurfaceBuffer { @@ -837,6 +838,7 @@ impl WlSurface { let surface_buffer = SurfaceBuffer { buffer, sync_files: Default::default(), + sync: AcquireSync::Implicit, }; self.buffer.set(Some(Rc::new(surface_buffer))); self.buf_x.fetch_add(dx); diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 570d671c..02a209cf 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -4,7 +4,7 @@ use { cursor::KnownCursor, fixed::Fixed, format::ARGB8888, - gfx_api::{GfxContext, GfxFramebuffer}, + gfx_api::{AcquireSync, GfxContext, GfxFramebuffer, ReleaseSync}, ifs::zwlr_layer_shell_v1::OVERLAY, portal::ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, renderer::renderer_base::RendererBase, @@ -223,6 +223,8 @@ impl GuiElement for Button { r.scale(), None, None, + AcquireSync::None, + ReleaseSync::None, ); } } @@ -325,6 +327,8 @@ impl GuiElement for Label { r.scale(), None, None, + AcquireSync::None, + ReleaseSync::None, ); } } diff --git a/src/renderer.rs b/src/renderer.rs index 8fe6eeee..95c14a6d 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,6 +1,6 @@ use { crate::{ - gfx_api::{GfxApiOpt, SampleRect}, + gfx_api::{AcquireSync, GfxApiOpt, ReleaseSync, SampleRect}, ifs::{ wl_callback::WlCallback, wl_surface::{ @@ -151,13 +151,33 @@ impl Renderer<'_> { let scale = output.global.persistent.scale.get(); for title in &rd.titles { let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y); - self.base - .render_texture(&title.tex, x, y, None, None, scale, None, None); + self.base.render_texture( + &title.tex, + x, + y, + None, + None, + scale, + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); } if let Some(status) = &rd.status { let (x, y) = self.base.scale_point(x + status.tex_x, y + status.tex_y); - self.base - .render_texture(&status.tex.texture, x, y, None, None, scale, None, None); + self.base.render_texture( + &status.tex.texture, + x, + y, + None, + None, + scale, + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); } } if let Some(ws) = output.workspace.get() { @@ -193,8 +213,18 @@ impl Renderer<'_> { let (tex_width, tex_height) = tex.texture.size(); let x = x + (pos.width() - tex_width) / 2; let y = y + (pos.height() - tex_height) / 2; - self.base - .render_texture(&tex.texture, x, y, None, None, self.base.scale, None, None); + self.base.render_texture( + &tex.texture, + x, + y, + None, + None, + self.base.scale, + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); } } @@ -232,6 +262,8 @@ impl Renderer<'_> { self.base.scale, None, None, + AcquireSync::None, + ReleaseSync::None, ); } } @@ -354,6 +386,10 @@ impl Renderer<'_> { bounds: Option<&Rect>, ) { if let Some(tex) = buffer.buffer.texture.get() { + let release_sync = match buffer.sync { + AcquireSync::Implicit => ReleaseSync::Implicit, + AcquireSync::None | AcquireSync::SyncFile { .. } => ReleaseSync::Explicit, + }; self.base.render_texture( &tex, x, @@ -363,6 +399,8 @@ impl Renderer<'_> { self.base.scale, bounds, Some(buffer.clone()), + buffer.sync.clone(), + release_sync, ); } else if let Some(color) = &buffer.buffer.color { if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) { @@ -420,6 +458,8 @@ impl Renderer<'_> { self.base.scale, None, None, + AcquireSync::None, + ReleaseSync::None, ); } let body = Rect::new_sized( diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index ca9c622b..759a8901 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -1,7 +1,8 @@ use { crate::{ gfx_api::{ - BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, SampleRect, + AcquireSync, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, + ReleaseSync, SampleRect, }, rect::Rect, scale::Scale, @@ -133,6 +134,8 @@ impl RendererBase<'_> { tscale: Scale, bounds: Option<&Rect>, buffer_resv: Option>, + acquire_sync: AcquireSync, + release_sync: ReleaseSync, ) { let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity); @@ -172,6 +175,8 @@ impl RendererBase<'_> { source: texcoord, target, buffer_resv, + acquire_sync, + release_sync, })); } } diff --git a/src/state.rs b/src/state.rs index 2ac63154..d365022e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -17,7 +17,10 @@ use { fixed::Fixed, forker::ForkerProxy, format::Format, - gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture, SampleRect}, + gfx_api::{ + AcquireSync, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync, SampleRect, + SyncFile, + }, gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, WaylandGlobal}, ifs::{ @@ -770,8 +773,8 @@ impl State { tex: &Rc, rr: &mut RenderResult, render_hw_cursor: bool, - ) -> Result<(), GfxError> { - fb.render_output( + ) -> Result, GfxError> { + let sync_file = fb.render_output( output, self, Some(output.global.pos.get()), @@ -781,7 +784,7 @@ impl State { )?; output.perform_screencopies(tex, !render_hw_cursor, 0, 0, None); rr.dispatch_frame_requests(); - Ok(()) + Ok(sync_file) } pub fn perform_screencopy( @@ -794,7 +797,7 @@ impl State { y_off: i32, size: Option<(i32, i32)>, transform: Transform, - ) -> Result<(), GfxError> { + ) -> Result, GfxError> { let mut ops = target.take_render_ops(); let mut renderer = Renderer { base: target.renderer_base(&mut ops, Scale::from_int(1), Transform::None), @@ -817,6 +820,8 @@ impl State { Scale::from_int(1), None, None, + AcquireSync::None, + ReleaseSync::Implicit, ); if render_hardware_cursors { for seat in self.globals.lock_seats().values() { From aaf73d6fdc2053e6a05aed50738fafdc66d225c4 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 21 Mar 2024 20:54:21 +0100 Subject: [PATCH 11/15] wayland: implement linux-drm-syncobj-v1 --- src/backends/metal/video.rs | 1 + src/client.rs | 9 +- src/client/objects.rs | 8 +- src/compositor.rs | 2 + src/gfx_api.rs | 6 +- src/gfx_apis/gl.rs | 2 +- src/gfx_apis/gl/renderer/context.rs | 12 +- src/gfx_apis/gl/renderer/framebuffer.rs | 7 +- src/gfx_apis/vulkan.rs | 8 +- src/gfx_apis/vulkan/device.rs | 7 +- src/gfx_apis/vulkan/renderer.rs | 1 + src/ifs.rs | 2 + src/ifs/wl_surface.rs | 113 ++++++- src/ifs/wl_surface/commit_timeline.rs | 295 ++++++++++++++++++ src/ifs/wl_surface/wl_subsurface.rs | 2 +- .../wp_linux_drm_syncobj_surface_v1.rs | 103 ++++++ src/ifs/wp_linux_drm_syncobj_manager_v1.rs | 130 ++++++++ src/ifs/wp_linux_drm_syncobj_timeline_v1.rs | 64 ++++ src/renderer.rs | 6 +- src/state.rs | 40 ++- src/utils/linkedlist.rs | 22 +- src/utils/smallmap.rs | 4 + src/video/drm.rs | 26 +- src/video/drm/sync_obj.rs | 238 ++++++++++++++ src/video/drm/sys.rs | 198 ++++++++++++ src/video/drm/wait_for_sync_obj.rs | 199 ++++++++++++ wire/wp_linux_drm_syncobj_manager_v1.txt | 15 + wire/wp_linux_drm_syncobj_surface_v1.txt | 17 + wire/wp_linux_drm_syncobj_timeline_v1.txt | 5 + 29 files changed, 1507 insertions(+), 35 deletions(-) create mode 100644 src/ifs/wl_surface/commit_timeline.rs create mode 100644 src/ifs/wl_surface/wp_linux_drm_syncobj_surface_v1.rs create mode 100644 src/ifs/wp_linux_drm_syncobj_manager_v1.rs create mode 100644 src/ifs/wp_linux_drm_syncobj_timeline_v1.rs create mode 100644 src/video/drm/sync_obj.rs create mode 100644 src/video/drm/wait_for_sync_obj.rs create mode 100644 wire/wp_linux_drm_syncobj_manager_v1.txt create mode 100644 wire/wp_linux_drm_syncobj_surface_v1.txt create mode 100644 wire/wp_linux_drm_syncobj_timeline_v1.txt diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 4f9c3328..072ea5cd 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -667,6 +667,7 @@ impl MetalConnector { AcquireSync::None => None, AcquireSync::Implicit => None, AcquireSync::SyncFile { sync_file } => Some(sync_file.clone()), + AcquireSync::Unnecessary => None, }; fb = dsd.fb.clone(); } diff --git a/src/client.rs b/src/client.rs index 392b41c1..1030fa69 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,7 +2,11 @@ use { crate::{ async_engine::SpawnedFuture, client::{error::LookupError, objects::Objects}, - ifs::{wl_display::WlDisplay, wl_registry::WlRegistry, wl_surface::WlSurface}, + ifs::{ + wl_display::WlDisplay, + wl_registry::WlRegistry, + wl_surface::{commit_timeline::CommitTimelines, WlSurface}, + }, leaks::Tracker, object::{Interface, Object, ObjectId, WL_DISPLAY_ID}, state::State, @@ -149,6 +153,7 @@ impl Clients { last_xwayland_serial: Cell::new(0), surfaces_by_xwayland_serial: Default::default(), activation_tokens: Default::default(), + commit_timelines: Rc::new(CommitTimelines::new(&global.wait_for_sync_obj)), }); track!(data, data); let display = Rc::new(WlDisplay::new(&data)); @@ -220,6 +225,7 @@ impl Drop for ClientHolder { self.data.shutdown.clear(); self.data.surfaces_by_xwayland_serial.clear(); self.data.remove_activation_tokens(); + self.data.commit_timelines.clear(); } } @@ -260,6 +266,7 @@ pub struct Client { pub last_xwayland_serial: Cell, pub surfaces_by_xwayland_serial: CopyHashMap>, pub activation_tokens: RefCell>, + pub commit_timelines: Rc, } pub const NUM_CACHED_SERIAL_RANGES: usize = 64; diff --git a/src/client/objects.rs b/src/client/objects.rs index 30375f2e..7e0df3ce 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -19,6 +19,7 @@ use { xdg_surface::{xdg_toplevel::XdgToplevel, XdgSurface}, WlSurface, }, + wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1, xdg_positioner::XdgPositioner, xdg_wm_base::XdgWmBase, }, @@ -29,8 +30,9 @@ use { }, wire::{ JayOutputId, JayScreencastId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, - WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, XdgPositionerId, - XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwpPrimarySelectionSourceV1Id, + WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, + WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId, XdgToplevelId, + XdgWmBaseId, ZwpPrimarySelectionSourceV1Id, }, }, std::{cell::RefCell, mem, rc::Rc}, @@ -56,6 +58,7 @@ pub struct Objects { pub xdg_wm_bases: CopyHashMap>, pub seats: CopyHashMap>, pub screencasts: CopyHashMap>, + pub timelines: CopyHashMap>, ids: RefCell>, } @@ -83,6 +86,7 @@ impl Objects { xdg_wm_bases: Default::default(), seats: Default::default(), screencasts: Default::default(), + timelines: Default::default(), ids: RefCell::new(vec![]), } } diff --git a/src/compositor.rs b/src/compositor.rs index c24161f1..be6ad83d 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -38,6 +38,7 @@ use { oserror::OsError, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, tri::Try, }, + video::drm::wait_for_sync_obj::WaitForSyncObj, wheel::{Wheel, WheelError}, xkbcommon::XkbContext, }, @@ -222,6 +223,7 @@ fn start_compositor2( double_click_distance: Cell::new(5), create_default_seat: Cell::new(true), subsurface_ids: Default::default(), + wait_for_sync_obj: Rc::new(WaitForSyncObj::new(&ring, &engine)), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/gfx_api.rs b/src/gfx_api.rs index f3187b5d..e8db5348 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -10,7 +10,7 @@ use { theme::Color, tree::{Node, OutputNode}, utils::{clonecell::UnsafeCellCloneSafe, transform_ext::TransformExt}, - video::{dmabuf::DmaBuf, gbm::GbmDevice, Modifier}, + video::{dmabuf::DmaBuf, drm::sync_obj::SyncObjCtx, gbm::GbmDevice, Modifier}, }, ahash::AHashMap, indexmap::IndexSet, @@ -167,6 +167,7 @@ pub enum AcquireSync { None, Implicit, SyncFile { sync_file: SyncFile }, + Unnecessary, } impl AcquireSync { @@ -191,6 +192,7 @@ impl Debug for AcquireSync { AcquireSync::None => "None", AcquireSync::Implicit => "Implicit", AcquireSync::SyncFile { .. } => "SyncFile", + AcquireSync::Unnecessary => "Unnecessary", }; f.debug_struct(name).finish_non_exhaustive() } @@ -517,6 +519,8 @@ pub trait GfxContext: Debug { stride: i32, format: &'static Format, ) -> Result, GfxError>; + + fn sync_obj_ctx(&self) -> &Rc; } #[derive(Debug)] diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index db1dbfc4..b2395573 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -376,7 +376,7 @@ fn render_texture(ctx: &GlRenderContext, tex: &CopyTexture) { fn handle_explicit_sync(ctx: &GlRenderContext, texture: &Texture, sync: &AcquireSync) { let sync_file = match sync { - AcquireSync::None | AcquireSync::Implicit => return, + AcquireSync::None | AcquireSync::Implicit | AcquireSync::Unnecessary => return, AcquireSync::SyncFile { sync_file } => sync_file, }; let sync_file = match uapi::fcntl_dupfd_cloexec(sync_file.raw(), 0) { diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index 8b931223..b740cb3b 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -14,7 +14,11 @@ use { renderer::{framebuffer::Framebuffer, image::Image}, GfxGlState, RenderError, Texture, }, - video::{dmabuf::DmaBuf, drm::Drm, gbm::GbmDevice}, + video::{ + dmabuf::DmaBuf, + drm::{sync_obj::SyncObjCtx, Drm}, + gbm::GbmDevice, + }, }, ahash::AHashMap, jay_config::video::GfxApi, @@ -53,6 +57,7 @@ pub(crate) struct TexProgs { pub(in crate::gfx_apis::gl) struct GlRenderContext { pub(crate) ctx: Rc, pub gbm: Rc, + pub sync_ctx: Rc, pub(crate) render_node: Rc, @@ -128,6 +133,7 @@ impl GlRenderContext { Ok(Self { ctx: ctx.clone(), gbm: ctx.dpy.gbm.clone(), + sync_ctx: Rc::new(SyncObjCtx::new(ctx.dpy.gbm.drm.fd())), render_node: node.clone(), @@ -271,4 +277,8 @@ impl GfxContext for GlRenderContext { })?; Ok(Rc::new(Framebuffer { ctx: self, gl: fb })) } + + fn sync_obj_ctx(&self) -> &Rc { + &self.sync_ctx + } } diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 26e00faf..8a2eea1e 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -67,7 +67,7 @@ impl Framebuffer { pub fn render( &self, - ops: Vec, + mut ops: Vec, clear: Option<&Color>, ) -> Result, RenderError> { let gles = self.ctx.ctx.dpy.gles; @@ -89,6 +89,7 @@ impl Framebuffer { } Ok(fd) }); + ops.clear(); *self.ctx.gfx_ops.borrow_mut() = ops; res } @@ -100,9 +101,7 @@ impl GfxFramebuffer for Framebuffer { } fn take_render_ops(&self) -> Vec { - let mut ops = mem::take(&mut *self.ctx.gfx_ops.borrow_mut()); - ops.clear(); - ops + mem::take(&mut *self.ctx.gfx_ops.borrow_mut()) } fn physical_size(&self) -> (i32, i32) { diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 3429d028..a23a232b 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -28,7 +28,7 @@ use { utils::oserror::OsError, video::{ dmabuf::DmaBuf, - drm::{Drm, DrmError}, + drm::{sync_obj::SyncObjCtx, Drm, DrmError}, gbm::{GbmDevice, GbmError}, }, }, @@ -93,7 +93,7 @@ pub enum VulkanError { LoadImageProperties(#[source] vk::Result), #[error("Device does not support rending and texturing from the XRGB8888 format")] XRGB8888, - #[error("Device does not support syncobj import")] + #[error("Device does not support sync obj import")] SyncobjImport, #[error("Could not start a command buffer")] BeginCommandBuffer(vk::Result), @@ -270,6 +270,10 @@ impl GfxContext for Context { .create_shm_texture(format, width, height, stride, &[], true)?; Ok(fb) } + + fn sync_obj_ctx(&self) -> &Rc { + &self.0.device.sync_ctx + } } impl Drop for Context { diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs index 0d0a6886..6649c713 100644 --- a/src/gfx_apis/vulkan/device.rs +++ b/src/gfx_apis/vulkan/device.rs @@ -10,7 +10,10 @@ use { util::OnDrop, VulkanError, }, - video::{drm::Drm, gbm::GbmDevice}, + video::{ + drm::{sync_obj::SyncObjCtx, Drm}, + gbm::GbmDevice, + }, }, ahash::AHashMap, arrayvec::ArrayVec, @@ -43,6 +46,7 @@ pub struct VulkanDevice { pub(super) physical_device: PhysicalDevice, pub(super) render_node: Rc, pub(super) gbm: GbmDevice, + pub(super) sync_ctx: Rc, pub(super) instance: Rc, pub(super) device: Device, pub(super) external_memory_fd: ExternalMemoryFd, @@ -279,6 +283,7 @@ impl VulkanInstance { Ok(Rc::new(VulkanDevice { physical_device: phy_dev, render_node, + sync_ctx: Rc::new(SyncObjCtx::new(gbm.drm.fd())), gbm, instance: self.clone(), device, diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index e8529fdd..d1dc47d6 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -574,6 +574,7 @@ impl VulkanRenderer { .map_err(|e| VulkanError::Dupfd(e.into()))?; import_sync_file(fd)?; } + AcquireSync::Unnecessary => {} } } Ok(()) diff --git a/src/ifs.rs b/src/ifs.rs index d1e0c6ea..8399ffc8 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -38,6 +38,8 @@ pub mod wp_content_type_v1; pub mod wp_cursor_shape_device_v1; pub mod wp_cursor_shape_manager_v1; pub mod wp_fractional_scale_manager_v1; +pub mod wp_linux_drm_syncobj_manager_v1; +pub mod wp_linux_drm_syncobj_timeline_v1; pub mod wp_presentation; pub mod wp_presentation_feedback; pub mod wp_single_pixel_buffer_manager_v1; diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 6acb2f11..df00cb99 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -1,7 +1,9 @@ +pub mod commit_timeline; pub mod cursor; pub mod ext_session_lock_surface_v1; pub mod wl_subsurface; pub mod wp_fractional_scale_v1; +pub mod wp_linux_drm_syncobj_surface_v1; pub mod wp_tearing_control_v1; pub mod wp_viewport; pub mod x_surface; @@ -16,7 +18,7 @@ use { client::{Client, ClientError, RequestParser}, drm_feedback::DrmFeedback, fixed::Fixed, - gfx_api::{AcquireSync, BufferResv, BufferResvUser, SampleRect, SyncFile}, + gfx_api::{AcquireSync, BufferResv, BufferResvUser, ReleaseSync, SampleRect, SyncFile}, ifs::{ wl_buffer::WlBuffer, wl_callback::WlCallback, @@ -25,9 +27,11 @@ use { NodeSeatState, SeatId, WlSeatGlobal, }, wl_surface::{ + commit_timeline::{ClearReason, CommitTimeline, CommitTimelineError}, cursor::CursorSurface, wl_subsurface::{PendingSubsurfaceData, SubsurfaceId, WlSubsurface}, wp_fractional_scale_v1::WpFractionalScaleV1, + wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1, wp_tearing_control_v1::WpTearingControlV1, wp_viewport::WpViewport, x_surface::XSurface, @@ -57,7 +61,10 @@ use { smallmap::SmallMap, transform_ext::TransformExt, }, - video::dmabuf::DMA_BUF_SYNC_READ, + video::{ + dmabuf::DMA_BUF_SYNC_READ, + drm::sync_obj::{SyncObj, SyncObjPoint}, + }, wire::{ wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id, ZwpLinuxDmabufFeedbackV1Id, @@ -66,6 +73,7 @@ use { xwayland::XWaylandEvent, }, ahash::AHashMap, + isnt::std_1::primitive::IsntSliceExt, jay_config::video::Transform, std::{ cell::{Cell, RefCell}, @@ -133,15 +141,46 @@ impl NodeVisitorBase for SurfaceSendPreferredTransformVisitor { } } +struct SurfaceBufferExplicitRelease { + sync_obj: Rc, + point: SyncObjPoint, +} + pub struct SurfaceBuffer { pub buffer: Rc, sync_files: SmallMap, pub sync: AcquireSync, + pub release_sync: ReleaseSync, + release: Option, } impl Drop for SurfaceBuffer { fn drop(&mut self) { let sync_files = self.sync_files.take(); + if let Some(release) = &self.release { + let Some(ctx) = self.buffer.client.state.render_ctx.get() else { + log::error!("Cannot signal release point because there is no render context"); + return; + }; + let ctx = ctx.sync_obj_ctx(); + if sync_files.is_not_empty() { + let res = ctx.import_sync_files( + &release.sync_obj, + release.point, + sync_files.iter().map(|f| &f.1), + ); + match res { + Ok(_) => return, + Err(e) => { + log::error!("Could not import sync files into sync obj: {}", ErrorFmt(e)); + } + } + } + if let Err(e) = ctx.signal(&release.sync_obj, release.point) { + log::error!("Could not signal release point: {}", ErrorFmt(e)); + } + return; + } if let Some(dmabuf) = &self.buffer.dmabuf { for (_, sync_file) in &sync_files { if let Err(e) = dmabuf.import_sync_file(DMA_BUF_SYNC_READ, sync_file) { @@ -173,7 +212,7 @@ pub struct WlSurface { pub client: Rc, visible: Cell, role: Cell, - pending: RefCell, + pending: RefCell>, input_region: CloneCell>>, opaque_region: Cell>>, buffer_points: RefCell, @@ -209,6 +248,9 @@ pub struct WlSurface { pub has_content_type_manager: Cell, content_type: Cell>, pub drm_feedback: CopyHashMap>, + sync_obj_surface: CloneCell>>, + destroyed: Cell, + commit_timeline: CommitTimeline, } impl Debug for WlSurface { @@ -232,7 +274,7 @@ enum CommitAction { } trait SurfaceExt { - fn commit_requested(self: Rc, pending: &mut PendingState) -> CommitAction { + fn commit_requested(self: Rc, pending: &mut Box) -> CommitAction { let _ = pending; CommitAction::ContinueCommit } @@ -329,11 +371,14 @@ struct PendingState { xdg_surface: Option>, layer_surface: Option>, subsurfaces: AHashMap, + acquire_point: Option<(Rc, SyncObjPoint)>, + release_point: Option<(Rc, SyncObjPoint)>, + explicit_sync: bool, } struct CommittedSubsurface { subsurface: Rc, - state: PendingState, + state: Box, } impl PendingState { @@ -341,7 +386,9 @@ impl PendingState { // discard state if next.buffer.is_some() { - if let Some(Some(prev)) = self.buffer.take() { + if let Some((sync_obj, point)) = self.release_point.take() { + client.state.signal_point(&sync_obj, point); + } else if let Some(Some(prev)) = self.buffer.take() { if !prev.destroyed() { prev.send_release(); } @@ -371,6 +418,8 @@ impl PendingState { opt!(xwayland_serial); opt!(tearing); opt!(content_type); + opt!(acquire_point); + opt!(release_point); { let (dx1, dy1) = self.offset; let (dx2, dy2) = mem::take(&mut next.offset); @@ -478,6 +527,9 @@ impl WlSurface { has_content_type_manager: Default::default(), content_type: Default::default(), drm_feedback: Default::default(), + sync_obj_surface: Default::default(), + destroyed: Cell::new(false), + commit_timeline: client.commit_timelines.create_timeline(), } } @@ -706,6 +758,7 @@ impl WlSurface { fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { let _req: Destroy = self.parse(parser)?; + self.commit_timeline.clear(ClearReason::Destroy); self.unset_dnd_icons(); self.unset_cursors(); self.ext.get().on_surface_destroy()?; @@ -730,6 +783,7 @@ impl WlSurface { self.client.remove_obj(self)?; self.idle_inhibitors.clear(); self.constraints.take(); + self.destroyed.set(true); Ok(()) } @@ -796,6 +850,9 @@ impl WlSurface { .surface .apply_state(&mut subsurface.state)?; } + if self.destroyed.get() { + return Ok(()); + } self.ext.get().before_apply_commit(pending)?; let mut scale_changed = false; if let Some(scale) = pending.scale.take() { @@ -835,10 +892,20 @@ impl WlSurface { } if let Some(buffer) = buffer_change { buffer.update_texture_or_log(); + let (sync, release_sync) = match pending.explicit_sync { + false => (AcquireSync::Implicit, ReleaseSync::Implicit), + true => (AcquireSync::Unnecessary, ReleaseSync::Explicit), + }; + let release = pending + .release_point + .take() + .map(|(sync_obj, point)| SurfaceBufferExplicitRelease { sync_obj, point }); let surface_buffer = SurfaceBuffer { buffer, sync_files: Default::default(), - sync: AcquireSync::Implicit, + sync, + release_sync, + release, }; self.buffer.set(Some(Rc::new(surface_buffer))); self.buf_x.fetch_add(dx); @@ -990,12 +1057,34 @@ impl WlSurface { let _req: Commit = self.parse(parser)?; let ext = self.ext.get(); let pending = &mut *self.pending.borrow_mut(); + self.verify_explicit_sync(pending)?; if ext.commit_requested(pending) == CommitAction::ContinueCommit { - self.apply_state(pending)?; + self.commit_timeline.commit(self, pending)?; } Ok(()) } + fn verify_explicit_sync(&self, pending: &mut PendingState) -> Result<(), WlSurfaceError> { + pending.explicit_sync = self.sync_obj_surface.is_some(); + if !pending.explicit_sync { + return Ok(()); + } + let have_new_buffer = match &pending.buffer { + None => false, + Some(b) => b.is_some(), + }; + match ( + pending.release_point.is_some(), + pending.acquire_point.is_some(), + have_new_buffer, + ) { + (true, true, true) => Ok(()), + (false, false, false) => Ok(()), + (_, _, true) => Err(WlSurfaceError::MissingSyncPoints), + (_, _, false) => Err(WlSurfaceError::UnexpectedSyncPoints), + } + } + fn set_buffer_transform(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { let req: SetBufferTransform = self.parse(parser)?; let Some(tf) = Transform::from_wl(req.transform) else { @@ -1215,6 +1304,7 @@ impl Object for WlSurface { self.tearing_control.take(); self.constraints.clear(); self.drm_feedback.clear(); + self.commit_timeline.clear(ClearReason::BreakLoops); } } @@ -1381,8 +1471,15 @@ pub enum WlSurfaceError { ViewportOutsideBuffer, #[error("attach request must not contain offset")] OffsetInAttach, + #[error(transparent)] + CommitTimelineError(Box), + #[error("Explicit sync buffer is attached but acquire or release points are not set")] + MissingSyncPoints, + #[error("No buffer is attached but acquire or release point is set")] + UnexpectedSyncPoints, } efrom!(WlSurfaceError, ClientError); efrom!(WlSurfaceError, XdgSurfaceError); efrom!(WlSurfaceError, ZwlrLayerSurfaceV1Error); efrom!(WlSurfaceError, MsgParserError); +efrom!(WlSurfaceError, CommitTimelineError); diff --git a/src/ifs/wl_surface/commit_timeline.rs b/src/ifs/wl_surface/commit_timeline.rs new file mode 100644 index 00000000..2ab8151e --- /dev/null +++ b/src/ifs/wl_surface/commit_timeline.rs @@ -0,0 +1,295 @@ +use { + crate::{ + ifs::wl_surface::{PendingState, WlSurface, WlSurfaceError}, + utils::{ + clonecell::CloneCell, + copyhashmap::CopyHashMap, + linkedlist::{LinkedList, LinkedNode, NodeRef}, + numcell::NumCell, + }, + video::drm::{ + sync_obj::{SyncObj, SyncObjPoint}, + wait_for_sync_obj::{SyncObjWaiter, WaitForSyncObj, WaitForSyncObjHandle}, + DrmError, + }, + }, + isnt::std_1::primitive::IsntSliceExt, + smallvec::SmallVec, + std::{ + cell::{Cell, RefCell}, + mem, + ops::{Deref, DerefMut}, + rc::Rc, + }, + thiserror::Error, +}; + +const MAX_TIMELINE_DEPTH: usize = 256; + +linear_ids!(CommitTimelineIds, CommitTimelineId, u64); + +pub struct CommitTimelines { + next_id: CommitTimelineIds, + wfs: Rc, + depth: NumCell, + gc: CopyHashMap>, +} + +pub struct CommitTimeline { + shared: Rc, + own_timeline: Rc, + effective_timeline: CloneCell>, + effective_timeline_id: Cell, +} + +struct Inner { + id: CommitTimelineId, + entries: LinkedList, +} + +fn add_entry( + list: &LinkedList, + shared: &Rc, + kind: EntryKind, +) -> NodeRef { + shared.depth.fetch_add(1); + let link = list.add_last(Entry { + link: Cell::new(None), + shared: shared.clone(), + kind, + }); + let noderef = link.to_ref(); + noderef.link.set(Some(link)); + noderef +} + +#[derive(Debug, Error)] +pub enum CommitTimelineError { + #[error(transparent)] + ImmediateCommit(WlSurfaceError), + #[error("Could not apply a delayed commit")] + DelayedCommit(#[source] WlSurfaceError), + #[error("Could not register a wait")] + RegisterWait(#[source] DrmError), + #[error("Syncobj wait failed")] + Wait(#[source] DrmError), + #[error("The client has too many pending commits")] + Depth, +} + +impl CommitTimelines { + pub fn new(wfs: &Rc) -> Self { + Self { + next_id: Default::default(), + depth: NumCell::new(0), + wfs: wfs.clone(), + gc: Default::default(), + } + } + + pub fn create_timeline(self: &Rc) -> CommitTimeline { + let id = self.next_id.next(); + let timeline = Rc::new(Inner { + id, + entries: Default::default(), + }); + CommitTimeline { + shared: self.clone(), + own_timeline: timeline.clone(), + effective_timeline: CloneCell::new(timeline), + effective_timeline_id: Cell::new(id), + } + } + + pub fn clear(&self) { + for (_, list) in self.gc.lock().drain() { + break_loops(&list); + } + } +} + +pub enum ClearReason { + BreakLoops, + Destroy, +} + +fn break_loops(list: &LinkedList) { + for entry in list.iter() { + entry.link.take(); + } +} + +impl CommitTimeline { + pub fn clear(&self, reason: ClearReason) { + match reason { + ClearReason::BreakLoops => break_loops(&self.own_timeline.entries), + ClearReason::Destroy => { + if self.own_timeline.entries.is_not_empty() { + let list = LinkedList::new(); + list.append_all(&self.own_timeline.entries); + add_entry(&list, &self.shared, EntryKind::Gc(self.own_timeline.id)); + self.shared.gc.set(self.own_timeline.id, list); + } + } + } + } + + pub(super) fn commit( + &self, + surface: &Rc, + pending: &mut Box, + ) -> Result<(), CommitTimelineError> { + let mut points = SmallVec::new(); + consume_acquire_points(pending, &mut points); + if points.is_empty() && self.own_timeline.entries.is_empty() { + return surface + .apply_state(pending) + .map_err(CommitTimelineError::ImmediateCommit); + } + if self.shared.depth.get() >= MAX_TIMELINE_DEPTH { + return Err(CommitTimelineError::Depth); + } + set_effective_timeline(self, pending, &self.own_timeline); + let noderef = add_entry( + &self.own_timeline.entries, + &self.shared, + EntryKind::Commit(Commit { + surface: surface.clone(), + pending: RefCell::new(mem::take(pending)), + sync_obj: NumCell::new(points.len()), + wait_handles: Cell::new(Default::default()), + }), + ); + if points.is_not_empty() { + let mut wait_handles = SmallVec::new(); + let noderef = Rc::new(noderef); + for (sync_obj, point) in points { + let handle = self + .shared + .wfs + .wait(&sync_obj, point, true, noderef.clone()) + .map_err(CommitTimelineError::RegisterWait)?; + wait_handles.push(handle); + } + let EntryKind::Commit(commit) = &noderef.kind else { + unreachable!(); + }; + commit.wait_handles.set(wait_handles); + } + Ok(()) + } +} + +impl SyncObjWaiter for NodeRef { + fn done(self: Rc, result: Result<(), DrmError>) { + let EntryKind::Commit(commit) = &self.kind else { + unreachable!(); + }; + if let Err(e) = result { + commit.surface.client.error(CommitTimelineError::Wait(e)); + return; + } + commit.sync_obj.fetch_sub(1); + if let Err(e) = flush_from(self.deref().clone()) { + commit + .surface + .client + .error(CommitTimelineError::DelayedCommit(e)); + } + } +} + +struct Entry { + link: Cell>>, + shared: Rc, + kind: EntryKind, +} + +enum EntryKind { + Commit(Commit), + Wait(Cell), + Signal(NodeRef), + Gc(CommitTimelineId), +} + +struct Commit { + surface: Rc, + pending: RefCell>, + sync_obj: NumCell, + wait_handles: Cell>, +} + +fn flush_from(mut point: NodeRef) -> Result<(), WlSurfaceError> { + let mut gc_list = None; + while point.maybe_apply(&mut gc_list)? { + point.shared.depth.fetch_sub(1); + let _link = point.link.take(); + match point.next() { + None => break, + Some(n) => point = n, + } + } + Ok(()) +} + +impl NodeRef { + fn maybe_apply(&self, gc_list: &mut Option>) -> Result { + if self.prev().is_some() { + return Ok(false); + } + match &self.kind { + EntryKind::Commit(c) => { + if c.sync_obj.get() > 0 { + return Ok(false); + } + c.surface.apply_state(c.pending.borrow_mut().deref_mut())?; + Ok(true) + } + EntryKind::Wait(signaled) => Ok(signaled.get()), + EntryKind::Signal(s) => match &s.kind { + EntryKind::Wait(signaled) => { + signaled.set(true); + flush_from(s.clone())?; + Ok(true) + } + _ => unreachable!(), + }, + EntryKind::Gc(id) => { + *gc_list = self.shared.gc.remove(id); + Ok(true) + } + } + } +} + +type Point = (Rc, SyncObjPoint); + +fn consume_acquire_points(pending: &mut PendingState, points: &mut SmallVec<[Point; 1]>) { + if let Some(point) = pending.acquire_point.take() { + points.push(point); + } + for ss in pending.subsurfaces.values_mut() { + consume_acquire_points(&mut ss.state, points); + } +} + +fn set_effective_timeline( + timeline: &CommitTimeline, + pending: &PendingState, + effective: &Rc, +) { + if timeline.effective_timeline_id.replace(effective.id) != effective.id { + let prev = timeline.effective_timeline.set(effective.clone()); + if prev.entries.is_not_empty() { + let noderef = add_entry( + &effective.entries, + &timeline.shared, + EntryKind::Wait(Cell::new(false)), + ); + add_entry(&prev.entries, &timeline.shared, EntryKind::Signal(noderef)); + } + } + for ss in pending.subsurfaces.values() { + set_effective_timeline(&ss.subsurface.surface.commit_timeline, &ss.state, effective); + } +} diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index c40b41e3..5af9ce06 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -313,7 +313,7 @@ impl Object for WlSubsurface { simple_add_obj!(WlSubsurface); impl SurfaceExt for WlSubsurface { - fn commit_requested(self: Rc, pending: &mut PendingState) -> CommitAction { + fn commit_requested(self: Rc, pending: &mut Box) -> CommitAction { if self.sync() { let mut parent_pending = self.parent.pending.borrow_mut(); match parent_pending.subsurfaces.entry(self.unique_id) { diff --git a/src/ifs/wl_surface/wp_linux_drm_syncobj_surface_v1.rs b/src/ifs/wl_surface/wp_linux_drm_syncobj_surface_v1.rs new file mode 100644 index 00000000..9df410ab --- /dev/null +++ b/src/ifs/wl_surface/wp_linux_drm_syncobj_surface_v1.rs @@ -0,0 +1,103 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_surface::WlSurface, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + video::drm::sync_obj::SyncObjPoint, + wire::{wp_linux_drm_syncobj_surface_v1::*, WpLinuxDrmSyncobjSurfaceV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpLinuxDrmSyncobjSurfaceV1 { + id: WpLinuxDrmSyncobjSurfaceV1Id, + client: Rc, + surface: Rc, + pub tracker: Tracker, +} + +impl WpLinuxDrmSyncobjSurfaceV1 { + pub fn new( + id: WpLinuxDrmSyncobjSurfaceV1Id, + client: &Rc, + surface: &Rc, + ) -> Self { + Self { + id, + client: client.clone(), + tracker: Default::default(), + surface: surface.clone(), + } + } + + pub fn install(self: &Rc) -> Result<(), WpLinuxDrmSyncobjSurfaceV1Error> { + if self.surface.sync_obj_surface.is_some() { + return Err(WpLinuxDrmSyncobjSurfaceV1Error::Exists); + } + self.surface.sync_obj_surface.set(Some(self.clone())); + Ok(()) + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WpLinuxDrmSyncobjSurfaceV1Error> { + let _req: Destroy = self.client.parse(self, parser)?; + self.surface.sync_obj_surface.take(); + let pending = &mut *self.surface.pending.borrow_mut(); + pending.release_point.take(); + pending.acquire_point.take(); + self.client.remove_obj(self)?; + Ok(()) + } + + fn set_acquire_point( + &self, + parser: MsgParser<'_, '_>, + ) -> Result<(), WpLinuxDrmSyncobjSurfaceV1Error> { + let req: SetAcquirePoint = self.client.parse(self, parser)?; + let point = point(req.point_hi, req.point_lo); + let timeline = self.client.lookup(req.timeline)?; + self.surface.pending.borrow_mut().acquire_point = Some((timeline.sync_obj.clone(), point)); + Ok(()) + } + + fn set_release_point( + &self, + parser: MsgParser<'_, '_>, + ) -> Result<(), WpLinuxDrmSyncobjSurfaceV1Error> { + let req: SetReleasePoint = self.client.parse(self, parser)?; + let point = point(req.point_hi, req.point_lo); + let timeline = self.client.lookup(req.timeline)?; + self.surface.pending.borrow_mut().release_point = Some((timeline.sync_obj.clone(), point)); + Ok(()) + } +} + +fn point(hi: u32, lo: u32) -> SyncObjPoint { + SyncObjPoint((hi as u64) << 32 | (lo as u64)) +} + +object_base! { + self = WpLinuxDrmSyncobjSurfaceV1; + + DESTROY => destroy, + SET_ACQUIRE_POINT => set_acquire_point, + SET_RELEASE_POINT => set_release_point, +} + +impl Object for WpLinuxDrmSyncobjSurfaceV1 {} + +simple_add_obj!(WpLinuxDrmSyncobjSurfaceV1); + +#[derive(Debug, Error)] +pub enum WpLinuxDrmSyncobjSurfaceV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error("The surface already has a syncobj extension attached")] + Exists, +} +efrom!(WpLinuxDrmSyncobjSurfaceV1Error, MsgParserError); +efrom!(WpLinuxDrmSyncobjSurfaceV1Error, ClientError); diff --git a/src/ifs/wp_linux_drm_syncobj_manager_v1.rs b/src/ifs/wp_linux_drm_syncobj_manager_v1.rs new file mode 100644 index 00000000..f31cdb7b --- /dev/null +++ b/src/ifs/wp_linux_drm_syncobj_manager_v1.rs @@ -0,0 +1,130 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::{ + wl_surface::wp_linux_drm_syncobj_surface_v1::{ + WpLinuxDrmSyncobjSurfaceV1, WpLinuxDrmSyncobjSurfaceV1Error, + }, + wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1, + }, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + video::drm::sync_obj::SyncObj, + wire::{wp_linux_drm_syncobj_manager_v1::*, WpLinuxDrmSyncobjManagerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpLinuxDrmSyncobjManagerV1Global { + pub name: GlobalName, +} + +pub struct WpLinuxDrmSyncobjManagerV1 { + pub id: WpLinuxDrmSyncobjManagerV1Id, + pub client: Rc, + pub tracker: Tracker, +} + +impl WpLinuxDrmSyncobjManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: WpLinuxDrmSyncobjManagerV1Id, + client: &Rc, + _version: u32, + ) -> Result<(), WpLinuxDrmSyncobjManagerV1Error> { + let obj = Rc::new(WpLinuxDrmSyncobjManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!( + WpLinuxDrmSyncobjManagerV1Global, + WpLinuxDrmSyncobjManagerV1, + WpLinuxDrmSyncobjManagerV1Error +); + +impl Global for WpLinuxDrmSyncobjManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(WpLinuxDrmSyncobjManagerV1Global); + +impl WpLinuxDrmSyncobjManagerV1 { + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), WpLinuxDrmSyncobjManagerV1Error> { + let _req: Destroy = self.client.parse(self, msg)?; + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_surface(&self, msg: MsgParser<'_, '_>) -> Result<(), WpLinuxDrmSyncobjManagerV1Error> { + let req: GetSurface = self.client.parse(self, msg)?; + let surface = self.client.lookup(req.surface)?; + let sync = Rc::new(WpLinuxDrmSyncobjSurfaceV1::new( + req.id, + &self.client, + &surface, + )); + track!(self.client, sync); + sync.install()?; + self.client.add_client_obj(&sync)?; + Ok(()) + } + + fn import_timeline( + &self, + msg: MsgParser<'_, '_>, + ) -> Result<(), WpLinuxDrmSyncobjManagerV1Error> { + let req: ImportTimeline = self.client.parse(self, msg)?; + let sync_obj = Rc::new(SyncObj::new(&req.fd)); + let sync = Rc::new(WpLinuxDrmSyncobjTimelineV1::new( + req.id, + &self.client, + &sync_obj, + )); + self.client.add_client_obj(&sync)?; + Ok(()) + } +} + +object_base! { + self = WpLinuxDrmSyncobjManagerV1; + + DESTROY => destroy, + GET_SURFACE => get_surface, + IMPORT_TIMELINE => import_timeline, +} + +impl Object for WpLinuxDrmSyncobjManagerV1 {} + +simple_add_obj!(WpLinuxDrmSyncobjManagerV1); + +#[derive(Debug, Error)] +pub enum WpLinuxDrmSyncobjManagerV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + WpLinuxDrmSyncobjSurfaceV1Error(#[from] WpLinuxDrmSyncobjSurfaceV1Error), +} +efrom!(WpLinuxDrmSyncobjManagerV1Error, MsgParserError); +efrom!(WpLinuxDrmSyncobjManagerV1Error, ClientError); diff --git a/src/ifs/wp_linux_drm_syncobj_timeline_v1.rs b/src/ifs/wp_linux_drm_syncobj_timeline_v1.rs new file mode 100644 index 00000000..e97e68e9 --- /dev/null +++ b/src/ifs/wp_linux_drm_syncobj_timeline_v1.rs @@ -0,0 +1,64 @@ +use { + crate::{ + client::{Client, ClientError}, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + video::drm::sync_obj::SyncObj, + wire::{wp_linux_drm_syncobj_timeline_v1::*, WpLinuxDrmSyncobjTimelineV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpLinuxDrmSyncobjTimelineV1 { + id: WpLinuxDrmSyncobjTimelineV1Id, + client: Rc, + pub sync_obj: Rc, + pub tracker: Tracker, +} + +impl WpLinuxDrmSyncobjTimelineV1 { + pub fn new( + id: WpLinuxDrmSyncobjTimelineV1Id, + client: &Rc, + sync_obj: &Rc, + ) -> Self { + Self { + id, + client: client.clone(), + tracker: Default::default(), + sync_obj: sync_obj.clone(), + } + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WpLinuxDrmSyncobjTimelineV1Error> { + let _destroy: Destroy = self.client.parse(self, parser)?; + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = WpLinuxDrmSyncobjTimelineV1; + + DESTROY => destroy, +} + +impl Object for WpLinuxDrmSyncobjTimelineV1 {} + +dedicated_add_obj!( + WpLinuxDrmSyncobjTimelineV1, + WpLinuxDrmSyncobjTimelineV1Id, + timelines +); + +#[derive(Debug, Error)] +pub enum WpLinuxDrmSyncobjTimelineV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(WpLinuxDrmSyncobjTimelineV1Error, MsgParserError); +efrom!(WpLinuxDrmSyncobjTimelineV1Error, ClientError); diff --git a/src/renderer.rs b/src/renderer.rs index 95c14a6d..0bb25b1f 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -386,10 +386,6 @@ impl Renderer<'_> { bounds: Option<&Rect>, ) { if let Some(tex) = buffer.buffer.texture.get() { - let release_sync = match buffer.sync { - AcquireSync::Implicit => ReleaseSync::Implicit, - AcquireSync::None | AcquireSync::SyncFile { .. } => ReleaseSync::Explicit, - }; self.base.render_texture( &tex, x, @@ -400,7 +396,7 @@ impl Renderer<'_> { bounds, Some(buffer.clone()), buffer.sync.clone(), - release_sync, + buffer.release_sync, ); } else if let Some(color) = &buffer.buffer.color { if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) { diff --git a/src/state.rs b/src/state.rs index d365022e..d7d6a87a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -37,6 +37,7 @@ use { zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1}, NoneSurfaceExt, WlSurface, }, + wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global, zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1Global, @@ -59,7 +60,14 @@ use { linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, }, - video::{dmabuf::DmaBufIds, drm::Drm}, + video::{ + dmabuf::DmaBufIds, + drm::{ + sync_obj::{SyncObj, SyncObjPoint}, + wait_for_sync_obj::WaitForSyncObj, + Drm, + }, + }, wheel::Wheel, wire::{ ExtForeignToplevelListV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, @@ -163,6 +171,7 @@ pub struct State { pub double_click_distance: Cell, pub create_default_seat: Cell, pub subsurface_ids: SubsurfaceIds, + pub wait_for_sync_obj: Rc, } // impl Drop for State { @@ -367,6 +376,8 @@ impl State { self.render_ctx_version.fetch_add(1); self.cursors.set(None); self.drm_feedback.set(None); + self.wait_for_sync_obj + .set_ctx(ctx.as_ref().map(|c| c.sync_obj_ctx().clone())); 'handle_new_feedback: { if let Some(ctx) = &ctx { @@ -435,11 +446,18 @@ impl State { seat.render_ctx_changed(); } - if ctx.is_some() && !self.render_ctx_ever_initialized.replace(true) { - self.add_global(&Rc::new(WlDrmGlobal::new(self.globals.name()))); - self.add_global(&Rc::new(ZwpLinuxDmabufV1Global::new(self.globals.name()))); - if let Some(config) = self.config.get() { - config.graphics_initialized(); + if let Some(ctx) = &ctx { + if !self.render_ctx_ever_initialized.replace(true) { + self.add_global(&Rc::new(WlDrmGlobal::new(self.globals.name()))); + self.add_global(&Rc::new(ZwpLinuxDmabufV1Global::new(self.globals.name()))); + if ctx.sync_obj_ctx().supports_async_wait() { + self.add_global(&Rc::new(WpLinuxDrmSyncobjManagerV1Global::new( + self.globals.name(), + ))); + } + if let Some(config) = self.config.get() { + config.graphics_initialized(); + } } } @@ -931,6 +949,16 @@ impl State { self.globals.add_global(self, &seat); seat } + + pub fn signal_point(&self, sync_obj: &SyncObj, point: SyncObjPoint) { + let Some(ctx) = self.render_ctx.get() else { + log::error!("Cannot signal sync obj point because there is no render context"); + return; + }; + if let Err(e) = ctx.sync_obj_ctx().signal(sync_obj, point) { + log::error!("Could not signal sync obj: {}", ErrorFmt(e)); + } + } } #[derive(Debug, Error)] diff --git a/src/utils/linkedlist.rs b/src/utils/linkedlist.rs index cf1b78e4..ab44af81 100644 --- a/src/utils/linkedlist.rs +++ b/src/utils/linkedlist.rs @@ -46,6 +46,23 @@ impl LinkedList { } } + pub fn append_all(&self, other: &LinkedList) { + if other.is_empty() { + return; + } + unsafe { + let o_root = other.root.data.as_ref(); + let o_first = o_root.next.get(); + let o_last = o_root.prev.get(); + let s_first = self.root.data; + let s_last = s_first.as_ref().prev.get(); + o_first.as_ref().prev.set(s_last); + s_last.as_ref().next.set(o_first); + o_last.as_ref().next.set(s_first); + s_first.as_ref().prev.set(o_last); + } + } + fn endpoint(&self, ep: NonNull>) -> Option> { unsafe { if ep != self.root.data { @@ -57,11 +74,14 @@ impl LinkedList { } } - #[allow(dead_code)] pub fn is_empty(&self) -> bool { self.last().is_none() } + pub fn is_not_empty(&self) -> bool { + !self.is_empty() + } + pub fn last(&self) -> Option> { unsafe { self.endpoint(self.root.data.as_ref().prev.get()) } } diff --git a/src/utils/smallmap.rs b/src/utils/smallmap.rs index e00ccb4e..1eb35979 100644 --- a/src/utils/smallmap.rs +++ b/src/utils/smallmap.rs @@ -58,6 +58,10 @@ impl SmallMap { unsafe { self.m.get().deref().is_empty() } } + pub fn is_not_empty(&self) -> bool { + !self.is_empty() + } + pub fn remove(&self, k: &K) -> Option { unsafe { self.m.get().deref_mut().remove(k) } } diff --git a/src/video/drm.rs b/src/video/drm.rs index 89fa48c6..78e57536 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -1,4 +1,6 @@ +pub mod sync_obj; mod sys; +pub mod wait_for_sync_obj; use { crate::{ @@ -52,7 +54,7 @@ pub use sys::{ #[derive(Debug, Error)] pub enum DrmError { #[error("Could not reopen a node")] - ReopenNode(#[source] crate::utils::oserror::OsError), + ReopenNode(#[source] OsError), #[error("Could not retrieve the render node name")] RenderNodeName(#[source] OsError), #[error("Could not retrieve the device node name")] @@ -113,6 +115,28 @@ pub enum DrmError { Version(#[source] OsError), #[error("Format of IN_FORMATS property is invalid")] InFormats, + #[error("Could not import a sync obj")] + ImportSyncObj(#[source] OsError), + #[error("Could not create a sync obj")] + CreateSyncObj(#[source] OsError), + #[error("Could not export a sync obj")] + ExportSyncObj(#[source] OsError), + #[error("Could not register an eventfd with a sync obj")] + RegisterEventfd(#[source] OsError), + #[error("Could not create an eventfd")] + EventFd(#[source] OsError), + #[error("Could not read from an eventfd")] + ReadEventFd(#[source] IoUringError), + #[error("No sync obj context available")] + NoSyncObjContextAvailable, + #[error("Could not signal the sync obj")] + SignalSyncObj(#[source] OsError), + #[error("Could not transfer a sync obj point")] + TransferPoint(#[source] OsError), + #[error("Could not merge two sync files")] + Merge(#[source] OsError), + #[error("Could not import a sync file into a sync obj")] + ImportSyncFile(#[source] OsError), } fn render_node_name(fd: c::c_int) -> Result { diff --git a/src/video/drm/sync_obj.rs b/src/video/drm/sync_obj.rs new file mode 100644 index 00000000..5f5a7d0d --- /dev/null +++ b/src/video/drm/sync_obj.rs @@ -0,0 +1,238 @@ +use { + crate::{ + gfx_api::SyncFile, + utils::{ + clonecell::{CloneCell, UnsafeCellCloneSafe}, + copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, + linkedlist::{LinkedList, LinkedNode}, + oserror::OsError, + }, + video::drm::{ + sys::{ + sync_ioc_merge, sync_obj_create, sync_obj_destroy, sync_obj_eventfd, + sync_obj_fd_to_handle, sync_obj_handle_to_fd, sync_obj_signal, sync_obj_transfer, + DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE, + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, + }, + DrmError, + }, + }, + std::{ + rc::Rc, + sync::atomic::{AtomicU64, Ordering::Relaxed}, + }, + uapi::{c, OwnedFd}, +}; + +static SYNCOBJ_ID: AtomicU64 = AtomicU64::new(0); + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct SyncObjId(u64); + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +struct SyncObjHandle(u32); + +unsafe impl UnsafeCellCloneSafe for SyncObjHandle {} + +pub struct SyncObj { + id: SyncObjId, + fd: Rc, + importers: LinkedList>, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct SyncObjPoint(pub u64); + +impl SyncObj { + pub fn new(fd: &Rc) -> Self { + Self { + id: SyncObjId(SYNCOBJ_ID.fetch_add(1, Relaxed)), + fd: fd.clone(), + importers: Default::default(), + } + } +} + +impl Drop for SyncObj { + fn drop(&mut self) { + let mut links = vec![]; + for importer in self.importers.iter() { + if let Some(handle) = importer.handles.remove(&self.id) { + destroy(&importer.drm, handle); + } + if let Some(link) = importer.links.remove(&self.id) { + links.push(link); + } + } + } +} + +struct Handles { + drm: Rc, + handles: CopyHashMap, + links: CopyHashMap>>, +} + +pub struct SyncObjCtx { + inner: Rc, + dummy: CloneCell>>, +} + +impl SyncObjCtx { + pub fn new(drm: &Rc) -> Self { + Self { + inner: Rc::new(Handles { + drm: drm.clone(), + handles: Default::default(), + links: Default::default(), + }), + dummy: Default::default(), + } + } + + fn get_handle(&self, sync_obj: &SyncObj) -> Result { + if let Some(handle) = self.inner.handles.get(&sync_obj.id) { + return Ok(handle); + } + let handle = sync_obj_fd_to_handle(self.inner.drm.raw(), sync_obj.fd.raw(), 0, 0) + .map_err(DrmError::ImportSyncObj)?; + let handle = SyncObjHandle(handle); + let link = sync_obj.importers.add_last(self.inner.clone()); + self.inner.handles.set(sync_obj.id, handle); + self.inner.links.set(sync_obj.id, link); + Ok(handle) + } + + pub fn create_sync_obj(&self) -> Result { + let handle = sync_obj_create(self.inner.drm.raw(), 0).map_err(DrmError::CreateSyncObj)?; + let handle = SyncObjHandle(handle); + let fd = sync_obj_handle_to_fd(self.inner.drm.raw(), handle.0, 0); + if fd.is_err() { + destroy(&self.inner.drm, handle); + } + let fd = fd.map_err(DrmError::ExportSyncObj).map(Rc::new)?; + let sync_obj = SyncObj::new(&fd); + let link = sync_obj.importers.add_last(self.inner.clone()); + self.inner.handles.set(sync_obj.id, handle); + self.inner.links.set(sync_obj.id, link); + Ok(sync_obj) + } + + pub fn wait_for_point( + &self, + eventfd: &OwnedFd, + sync_obj: &SyncObj, + point: SyncObjPoint, + signaled: bool, + ) -> Result<(), DrmError> { + let handle = self.get_handle(sync_obj)?; + let flags = match signaled { + true => 0, + false => DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, + }; + sync_obj_eventfd( + self.inner.drm.raw(), + eventfd.raw(), + handle.0, + point.0, + flags, + ) + .map_err(DrmError::RegisterEventfd) + } + + pub fn supports_async_wait(&self) -> bool { + self.supports_async_wait_().is_ok() + } + + fn supports_async_wait_(&self) -> Result<(), DrmError> { + let sync_obj = self.create_sync_obj()?; + let eventfd = uapi::eventfd(0, c::EFD_CLOEXEC) + .map_err(OsError::from) + .map_err(DrmError::EventFd)?; + self.wait_for_point(&eventfd, &sync_obj, SyncObjPoint(1), true)?; + Ok(()) + } + + pub fn signal(&self, sync_obj: &SyncObj, point: SyncObjPoint) -> Result<(), DrmError> { + let handle = self.get_handle(sync_obj)?; + sync_obj_signal(self.inner.drm.raw(), handle.0, point.0).map_err(DrmError::SignalSyncObj) + } + + pub fn import_sync_files<'a, I>( + &self, + sync_obj: &SyncObj, + point: SyncObjPoint, + sync_files: I, + ) -> Result<(), DrmError> + where + I: IntoIterator, + { + let mut sync_files = sync_files.into_iter(); + let Some(first) = sync_files.next() else { + return self.signal(sync_obj, point); + }; + let mut stash; + let mut fd = &*first.0; + for next in sync_files { + stash = sync_ioc_merge(fd.raw(), next.raw()).map_err(DrmError::Merge)?; + fd = &stash; + } + let dummy = self.get_dummy()?; + sync_obj_fd_to_handle( + self.inner.drm.raw(), + fd.raw(), + DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE, + self.get_handle(&dummy)?.0, + ) + .map_err(DrmError::ImportSyncFile)?; + self.transfer(&dummy, SyncObjPoint(0), sync_obj, point) + } + + fn transfer( + &self, + src_sync_obj: &SyncObj, + src_point: SyncObjPoint, + dst_sync_obj: &SyncObj, + dst_point: SyncObjPoint, + ) -> Result<(), DrmError> { + let src_handle = self.get_handle(src_sync_obj)?; + let dst_handle = self.get_handle(dst_sync_obj)?; + sync_obj_transfer( + self.inner.drm.raw(), + src_handle.0, + src_point.0, + dst_handle.0, + dst_point.0, + 0, + ) + .map_err(DrmError::TransferPoint) + } + + fn get_dummy(&self) -> Result, DrmError> { + match self.dummy.get() { + Some(d) => Ok(d), + None => { + let d = Rc::new(self.create_sync_obj()?); + self.dummy.set(Some(d.clone())); + Ok(d) + } + } + } +} + +impl Drop for SyncObjCtx { + fn drop(&mut self) { + self.inner.links.clear(); + let mut map = self.inner.handles.lock(); + for (_, handle) in map.drain() { + destroy(&self.inner.drm, handle); + } + } +} + +fn destroy(drm: &OwnedFd, handle: SyncObjHandle) { + if let Err(e) = sync_obj_destroy(drm.raw(), handle.0) { + log::error!("Could not destroy sync obj: {}", ErrorFmt(e)); + } +} diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index e33dd6c2..195c1029 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -1156,3 +1156,201 @@ pub struct drm_format_modifier { } unsafe impl Pod for drm_format_modifier {} + +// pub const DRM_SYNCOBJ_CREATE_SIGNALED: u32 = 1 << 0; + +#[repr(C)] +struct drm_syncobj_create { + handle: u32, + flags: u32, +} + +const DRM_IOCTL_SYNCOBJ_CREATE: u64 = drm_iowr::(0xBF); + +pub fn sync_obj_create(drm: c::c_int, flags: u32) -> Result { + let mut res = drm_syncobj_create { handle: 0, flags }; + unsafe { + ioctl(drm, DRM_IOCTL_SYNCOBJ_CREATE, &mut res)?; + } + Ok(res.handle) +} + +#[repr(C)] +struct drm_syncobj_destroy { + handle: u32, + pad: u32, +} + +const DRM_IOCTL_SYNCOBJ_DESTROY: u64 = drm_iowr::(0xC0); + +pub fn sync_obj_destroy(drm: c::c_int, handle: u32) -> Result<(), OsError> { + let mut res = drm_syncobj_destroy { handle, pad: 0 }; + unsafe { + ioctl(drm, DRM_IOCTL_SYNCOBJ_DESTROY, &mut res)?; + } + Ok(()) +} + +pub const DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE: u32 = 1 << 0; +// pub const DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE: u32 = 1 << 0; + +#[repr(C)] +struct drm_syncobj_handle { + handle: u32, + flags: u32, + fd: i32, + pad: u32, +} + +const DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD: u64 = drm_iowr::(0xC1); +const DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE: u64 = drm_iowr::(0xC2); + +pub fn sync_obj_handle_to_fd(drm: c::c_int, handle: u32, flags: u32) -> Result { + let mut res = drm_syncobj_handle { + handle, + flags, + fd: 0, + pad: 0, + }; + unsafe { + ioctl(drm, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, &mut res)?; + } + Ok(OwnedFd::new(res.fd)) +} + +pub fn sync_obj_fd_to_handle( + drm: c::c_int, + fd: c::c_int, + flags: u32, + handle: u32, +) -> Result { + let mut res = drm_syncobj_handle { + handle, + flags, + fd, + pad: 0, + }; + unsafe { + ioctl(drm, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, &mut res)?; + } + Ok(res.handle) +} + +// pub const DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL: u32 = 1 << 0; +// pub const DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT: u32 = 1 << 1; +pub const DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE: u32 = 1 << 2; + +#[repr(C)] +struct drm_syncobj_eventfd { + handle: u32, + flags: u32, + point: u64, + fd: i32, + pad: u32, +} + +const DRM_IOCTL_SYNCOBJ_EVENTFD: u64 = drm_iowr::(0xCF); + +pub fn sync_obj_eventfd( + drm: c::c_int, + eventfd: c::c_int, + handle: u32, + point: u64, + flags: u32, +) -> Result<(), OsError> { + let mut res = drm_syncobj_eventfd { + handle, + flags, + point, + fd: eventfd, + pad: 0, + }; + unsafe { + ioctl(drm, DRM_IOCTL_SYNCOBJ_EVENTFD, &mut res)?; + } + Ok(()) +} + +#[repr(C)] +struct drm_syncobj_timeline_array { + handles: u64, + points: u64, + count_handles: u32, + flags: u32, +} + +const DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL: u64 = drm_iowr::(0xCD); + +pub fn sync_obj_signal(drm: c::c_int, handle: u32, point: u64) -> Result<(), OsError> { + let mut res = drm_syncobj_timeline_array { + handles: &handle as *const u32 as u64, + points: &point as *const u64 as u64, + count_handles: 1, + flags: 0, + }; + unsafe { + ioctl(drm, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &mut res)?; + } + Ok(()) +} + +#[repr(C)] +struct drm_syncobj_transfer { + src_handle: u32, + dst_handle: u32, + src_point: u64, + dst_point: u64, + flags: u32, + pad: u32, +} + +const DRM_IOCTL_SYNCOBJ_TRANSFER: u64 = drm_iowr::(0xCC); + +pub fn sync_obj_transfer( + drm: c::c_int, + src_handle: u32, + src_point: u64, + dst_handle: u32, + dst_point: u64, + flags: u32, +) -> Result<(), OsError> { + let mut res = drm_syncobj_transfer { + src_handle, + dst_handle, + src_point, + dst_point, + flags, + pad: 0, + }; + unsafe { + ioctl(drm, DRM_IOCTL_SYNCOBJ_TRANSFER, &mut res)?; + } + Ok(()) +} + +#[repr(C)] +struct sync_merge_data { + name: [u8; 32], + fd2: i32, + fence: i32, + flags: u32, + pad: u32, +} + +const SYNC_IOC_MAGIC: u64 = b'>' as _; + +const SYNC_IOC_MERGE: u64 = uapi::_IOWR::(SYNC_IOC_MAGIC, 3); + +pub fn sync_ioc_merge(left: c::c_int, right: c::c_int) -> Result { + let mut res = sync_merge_data { + name: [0; 32], + fd2: right, + fence: 0, + flags: 0, + pad: 0, + }; + unsafe { + ioctl(left, SYNC_IOC_MERGE, &mut res)?; + } + Ok(OwnedFd::new(res.fence)) +} diff --git a/src/video/drm/wait_for_sync_obj.rs b/src/video/drm/wait_for_sync_obj.rs new file mode 100644 index 00000000..b4d10bed --- /dev/null +++ b/src/video/drm/wait_for_sync_obj.rs @@ -0,0 +1,199 @@ +use { + crate::{ + async_engine::{AsyncEngine, SpawnedFuture}, + io_uring::IoUring, + utils::{ + asyncevent::AsyncEvent, buf::Buf, clonecell::CloneCell, copyhashmap::CopyHashMap, + numcell::NumCell, oserror::OsError, stack::Stack, + }, + video::drm::{ + sync_obj::{SyncObj, SyncObjCtx, SyncObjPoint}, + DrmError, + }, + }, + std::{cell::Cell, rc::Rc}, + uapi::{c, OwnedFd}, +}; + +pub struct WaitForSyncObj { + inner: Rc, + eng: Rc, +} + +pub trait SyncObjWaiter { + fn done(self: Rc, result: Result<(), DrmError>); +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +struct JobId(u64); + +pub struct WaitForSyncObjHandle { + inner: Rc, + id: JobId, +} + +struct Inner { + ctx: CloneCell>>, + next_id: NumCell, + ring: Rc, + busy: CopyHashMap, + idle: Stack, +} + +struct BusyWaiter { + waiter: Waiter, + job: Job, + sow: Rc, +} + +struct Waiter { + _task: SpawnedFuture<()>, + inner: Rc, +} + +#[derive(Clone)] +struct Job { + id: JobId, + sync_obj: Rc, + point: SyncObjPoint, + signaled: bool, +} + +struct WaiterInner { + inner: Rc, + eventfd: Rc, + next: Cell>, + trigger: AsyncEvent, +} + +impl Drop for WaitForSyncObjHandle { + fn drop(&mut self) { + let _ = self.inner.busy.remove(&self.id); + } +} + +impl WaitForSyncObj { + pub fn new(ring: &Rc, eng: &Rc) -> Self { + Self { + inner: Rc::new(Inner { + ctx: Default::default(), + next_id: Default::default(), + ring: ring.clone(), + busy: Default::default(), + idle: Default::default(), + }), + eng: eng.clone(), + } + } + + pub fn set_ctx(&self, ctx: Option>) { + self.inner.ctx.set(ctx); + let busy_waiters: Vec<_> = self.inner.busy.lock().drain().map(|(_, w)| w).collect(); + for waiter in busy_waiters { + let res = self.submit_job( + waiter.job.id, + &waiter.job.sync_obj, + waiter.job.point, + waiter.job.signaled, + waiter.sow.clone(), + ); + if res.is_err() { + waiter.sow.done(res); + } + } + } + + pub fn wait( + &self, + sync_obj: &Rc, + point: SyncObjPoint, + signaled: bool, + sow: Rc, + ) -> Result { + let job_id = JobId(self.inner.next_id.fetch_add(1)); + self.submit_job(job_id, sync_obj, point, signaled, sow)?; + Ok(WaitForSyncObjHandle { + inner: self.inner.clone(), + id: job_id, + }) + } + + fn submit_job( + &self, + job_id: JobId, + sync_obj: &Rc, + point: SyncObjPoint, + signaled: bool, + sow: Rc, + ) -> Result<(), DrmError> { + let waiter = match self.inner.idle.pop() { + Some(w) => w, + None => { + let eventfd = uapi::eventfd(0, c::EFD_CLOEXEC) + .map_err(OsError::from) + .map_err(DrmError::EventFd)?; + let waiter = Rc::new(WaiterInner { + inner: self.inner.clone(), + eventfd: Rc::new(eventfd), + next: Cell::new(None), + trigger: Default::default(), + }); + Waiter { + _task: self.eng.spawn(waiter.clone().run()), + inner: waiter, + } + } + }; + let job = Job { + id: job_id, + sync_obj: sync_obj.clone(), + point, + signaled, + }; + let waiter = BusyWaiter { + waiter, + job: job.clone(), + sow: sow.clone(), + }; + waiter.waiter.inner.next.set(Some(job)); + waiter.waiter.inner.trigger.trigger(); + self.inner.busy.set(job_id, waiter); + Ok(()) + } +} + +impl Drop for WaitForSyncObj { + fn drop(&mut self) { + self.inner.busy.clear(); + self.inner.idle.take(); + } +} + +impl WaiterInner { + async fn run(self: Rc) { + let mut buf = Buf::new(8); + loop { + self.trigger.triggered().await; + let job = self.next.take().unwrap(); + let res = self.wait(&mut buf, &job).await; + if let Some(waiter) = self.inner.busy.remove(&job.id) { + waiter.sow.done(res); + self.inner.idle.push(waiter.waiter); + } + } + } + + async fn wait(&self, buf: &mut Buf, job: &Job) -> Result<(), DrmError> { + let ctx = match self.inner.ctx.get() { + None => return Err(DrmError::NoSyncObjContextAvailable), + Some(c) => c, + }; + ctx.wait_for_point(&self.eventfd, &job.sync_obj, job.point, job.signaled)?; + self.inner + .ring + .read(&self.eventfd, buf.clone()) + .await + .map(drop) + .map_err(DrmError::ReadEventFd) + } +} diff --git a/wire/wp_linux_drm_syncobj_manager_v1.txt b/wire/wp_linux_drm_syncobj_manager_v1.txt new file mode 100644 index 00000000..2efc8de3 --- /dev/null +++ b/wire/wp_linux_drm_syncobj_manager_v1.txt @@ -0,0 +1,15 @@ +# requests + +msg destroy = 0 { + +} + +msg get_surface = 1 { + id: id(wp_linux_drm_syncobj_surface_v1), + surface: id(wl_surface), +} + +msg import_timeline = 2 { + id: id(wp_linux_drm_syncobj_timeline_v1), + fd: fd, +} diff --git a/wire/wp_linux_drm_syncobj_surface_v1.txt b/wire/wp_linux_drm_syncobj_surface_v1.txt new file mode 100644 index 00000000..08cbcc23 --- /dev/null +++ b/wire/wp_linux_drm_syncobj_surface_v1.txt @@ -0,0 +1,17 @@ +# requests + +msg destroy = 0 { + +} + +msg set_acquire_point = 1 { + timeline: id(wp_linux_drm_syncobj_timeline_v1), + point_hi: u32, + point_lo: u32, +} + +msg set_release_point = 2 { + timeline: id(wp_linux_drm_syncobj_timeline_v1), + point_hi: u32, + point_lo: u32, +} diff --git a/wire/wp_linux_drm_syncobj_timeline_v1.txt b/wire/wp_linux_drm_syncobj_timeline_v1.txt new file mode 100644 index 00000000..35dc4c51 --- /dev/null +++ b/wire/wp_linux_drm_syncobj_timeline_v1.txt @@ -0,0 +1,5 @@ +# requests + +msg destroy = 0 { + +} From aa296a6aea643199377dc2828afb74eb56153914 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 26 Mar 2024 15:24:07 +0100 Subject: [PATCH 12/15] config: allow disabling explicit-sync --- jay-config/src/_private/client.rs | 4 ++++ jay-config/src/_private/ipc.rs | 3 +++ jay-config/src/lib.rs | 9 +++++++++ src/compositor.rs | 1 + src/config/handler.rs | 7 +++++++ src/state.rs | 3 ++- toml-config/src/config.rs | 1 + toml-config/src/config/parsers/config.rs | 3 +++ toml-config/src/lib.rs | 6 +++++- toml-spec/spec/spec.generated.json | 4 ++++ toml-spec/spec/spec.generated.md | 10 ++++++++++ toml-spec/spec/spec.yaml | 9 +++++++++ 12 files changed, 58 insertions(+), 2 deletions(-) diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 6915f056..6ad8d154 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -810,6 +810,10 @@ impl Client { self.send(&ClientMessage::SetIdle { timeout }) } + pub fn set_explicit_sync_enabled(&self, enabled: bool) { + self.send(&ClientMessage::SetExplicitSyncEnabled { enabled }) + } + pub fn set_seat(&self, device: InputDevice, seat: Seat) { self.send(&ClientMessage::SetSeat { device, seat }) } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 07c1f7ed..24854645 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -428,6 +428,9 @@ pub enum ClientMessage<'a> { workspace: WorkspaceSource, connector: Connector, }, + SetExplicitSyncEnabled { + enabled: bool, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index 83543401..4356d23f 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -222,3 +222,12 @@ pub fn workspaces() -> Vec { pub fn set_idle(timeout: Option) { get!().set_idle(timeout.unwrap_or_default()) } + +/// Enables or disables explicit sync. +/// +/// Calling this after the compositor has started has no effect. +/// +/// The default is `true`. +pub fn set_explicit_sync_enabled(enabled: bool) { + get!().set_explicit_sync_enabled(enabled); +} diff --git a/src/compositor.rs b/src/compositor.rs index be6ad83d..c435c5b8 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -224,6 +224,7 @@ fn start_compositor2( create_default_seat: Cell::new(true), subsurface_ids: Default::default(), wait_for_sync_obj: Rc::new(WaitForSyncObj::new(&ring, &engine)), + explicit_sync_enabled: Cell::new(true), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index f9bbb4f1..8940cfc5 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -800,6 +800,10 @@ impl ConfigProxyHandler { self.state.idle.set_timeout(timeout); } + fn handle_set_explicit_sync_enabled(&self, enabled: bool) { + self.state.explicit_sync_enabled.set(enabled); + } + fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_connector(connector)?; self.respond(Response::ConnectorConnected { @@ -1725,6 +1729,9 @@ impl ConfigProxyHandler { } => self .handle_move_to_output(workspace, connector) .wrn("move_to_output")?, + ClientMessage::SetExplicitSyncEnabled { enabled } => { + self.handle_set_explicit_sync_enabled(enabled) + } } Ok(()) } diff --git a/src/state.rs b/src/state.rs index d7d6a87a..ff1ad5c9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -172,6 +172,7 @@ pub struct State { pub create_default_seat: Cell, pub subsurface_ids: SubsurfaceIds, pub wait_for_sync_obj: Rc, + pub explicit_sync_enabled: Cell, } // impl Drop for State { @@ -450,7 +451,7 @@ impl State { if !self.render_ctx_ever_initialized.replace(true) { self.add_global(&Rc::new(WlDrmGlobal::new(self.globals.name()))); self.add_global(&Rc::new(ZwpLinuxDmabufV1Global::new(self.globals.name()))); - if ctx.sync_obj_ctx().supports_async_wait() { + if ctx.sync_obj_ctx().supports_async_wait() && self.explicit_sync_enabled.get() { self.add_global(&Rc::new(WpLinuxDrmSyncobjManagerV1Global::new( self.globals.name(), ))); diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index efdcceef..23e6153e 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -289,6 +289,7 @@ pub struct Config { pub render_device: Option, pub inputs: Vec, pub idle: Option, + pub explicit_sync_enabled: Option, } #[derive(Debug, Error)] diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs index 78da91df..ea70ab81 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/toml-config/src/config/parsers/config.rs @@ -95,6 +95,7 @@ impl Parser for ConfigParser<'_> { _, idle_val, ), + (explicit_sync,), ) = ext.extract(( ( opt(val("keymap")), @@ -120,6 +121,7 @@ impl Parser for ConfigParser<'_> { opt(val("$schema")), opt(val("idle")), ), + (recover(opt(bol("explicit-sync"))),), ))?; let mut keymap = None; if let Some(value) = keymap_val { @@ -271,6 +273,7 @@ impl Parser for ConfigParser<'_> { gfx_api, drm_devices, direct_scanout_enabled: direct_scanout.despan(), + explicit_sync_enabled: explicit_sync.despan(), render_device, inputs, idle, diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index edfd840d..6d107d03 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -19,7 +19,8 @@ use { is_reload, keyboard::{Keymap, ModifiedKeySym}, logging::set_log_level, - on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture, set_idle, + on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture, + set_explicit_sync_enabled, set_idle, status::{set_i3bar_separator, set_status, set_status_command, unset_status_command}, switch_to_vt, theme::{reset_colors, reset_font, reset_sizes, set_font}, @@ -789,6 +790,9 @@ fn load_config(initial_load: bool, persistent: &Rc) { if let Some(dse) = config.direct_scanout_enabled { set_direct_scanout_enabled(dse); } + if let Some(ese) = config.explicit_sync_enabled { + set_explicit_sync_enabled(ese); + } on_new_drm_device({ let state = state.clone(); move |d| { diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index cdf97b93..581b20a6 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -498,6 +498,10 @@ "type": "boolean", "description": "Configured whether the compositor attempts direct scanout.\n" }, + "explicit-sync": { + "type": "boolean", + "description": "Configures whether the compositor supports explicit sync.\n\nThis cannot be changed after the compositor has started.\n\nThe default is `true`.\n" + }, "render-device": { "description": "Selects the device to use for rendering in a system with multiple GPUs.\n\nThe first device that matches will be used.\n\n- Example:\n\n ```toml\n render-device.name = \"dedicated\"\n\n [[drm-devices]]\n name = \"dedicated\"\n match = { pci-vendor = 0x1002, pci-model = 0x73ff }\n ```\n", "$ref": "#/$defs/DrmDeviceMatch" diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 45c6201e..93646c40 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -887,6 +887,16 @@ The table has the following fields: The value of this field should be a boolean. +- `explicit-sync` (optional): + + Configures whether the compositor supports explicit sync. + + This cannot be changed after the compositor has started. + + The default is `true`. + + The value of this field should be a boolean. + - `render-device` (optional): Selects the device to use for rendering in a system with multiple GPUs. diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 458a05d6..2d4a7d43 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -1875,6 +1875,15 @@ Config: required: false description: | Configured whether the compositor attempts direct scanout. + explicit-sync: + kind: boolean + required: false + description: | + Configures whether the compositor supports explicit sync. + + This cannot be changed after the compositor has started. + + The default is `true`. render-device: ref: DrmDeviceMatch required: false From 112675a813b41c4b2a7cf52b2a2ae27afc504d66 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 27 Mar 2024 13:55:26 +0100 Subject: [PATCH 13/15] xdg_toplevel: send correct extents in initial configure event --- src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 4083bdda..271247fb 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -646,7 +646,8 @@ impl ToplevelNodeBase for XdgToplevel { impl XdgSurfaceExt for XdgToplevel { fn initial_configure(self: Rc) -> Result<(), XdgSurfaceError> { - self.send_configure(0, 0); + let rect = self.xdg.absolute_desired_extents.get(); + self.send_configure(rect.width(), rect.height()); Ok(()) } From 2e9beed77f2bc8f7b1a07bccaa34ac0442ae923a Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 27 Mar 2024 23:18:45 +0100 Subject: [PATCH 14/15] subsurface: change default mode to sync --- src/ifs/wl_surface/wl_subsurface.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index 5af9ce06..6c4146e1 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -96,7 +96,7 @@ impl WlSubsurface { surface: surface.clone(), parent: parent.clone(), position: Cell::new(Default::default()), - sync_requested: Cell::new(false), + sync_requested: Cell::new(true), sync_ancestor: Cell::new(false), node: RefCell::new(None), latest_node: Default::default(), From d057025d44f240bf1d772992e78e73d1e9125669 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 28 Mar 2024 17:51:03 +0100 Subject: [PATCH 15/15] docs: update the readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 204df31c..2d4a3dab 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ The following features have been implemented and should work: - Selecting the primary device in multi-GPU systems - An OpenGL backend - A Vulkan backend +- Explicit sync ### Missing Features