diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index c5300ded..72f8c354 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -711,6 +711,7 @@ impl MetalConnector { if let Some(node) = self.state.root.outputs.get(&self.connector_id) { let buffer = &buffers[self.next_buffer.get() % buffers.len()]; let mut rr = self.render_result.borrow_mut(); + rr.output_id = node.id; let fb = self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout)?; rr.dispatch_frame_requests(); diff --git a/src/client.rs b/src/client.rs index 1030fa69..4dc7b51d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -14,6 +14,7 @@ use { activation_token::ActivationToken, asyncevent::AsyncEvent, buffd::{MsgFormatter, MsgParser, MsgParserError, OutBufferSwapchain}, + clonecell::CloneCell, copyhashmap::{CopyHashMap, Locked}, errorfmt::ErrorFmt, numcell::NumCell, @@ -153,13 +154,15 @@ 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)), + commit_timelines: Default::default(), }); track!(data, data); let display = Rc::new(WlDisplay::new(&data)); track!(data, display); data.objects.display.set(Some(display.clone())); data.objects.add_client_object(display).expect(""); + let commit_timelines = Rc::new(CommitTimelines::new(&data)); + data.commit_timelines.set(Some(commit_timelines)); let client = ClientHolder { _handler: global.eng.spawn(tasks::client(data.clone())), data: data.clone(), @@ -225,7 +228,9 @@ 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(); + if let Some(ct) = self.data.commit_timelines.take() { + ct.clear(); + } } } @@ -266,7 +271,7 @@ pub struct Client { pub last_xwayland_serial: Cell, pub surfaces_by_xwayland_serial: CopyHashMap>, pub activation_tokens: RefCell>, - pub commit_timelines: Rc, + pub commit_timelines: CloneCell>>, } pub const NUM_CACHED_SERIAL_RANGES: usize = 64; diff --git a/src/globals.rs b/src/globals.rs index dcc01c2f..643a7e91 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -24,6 +24,7 @@ use { wl_surface::xwayland_shell_v1::XwaylandShellV1Global, wp_content_type_manager_v1::WpContentTypeManagerV1Global, wp_cursor_shape_manager_v1::WpCursorShapeManagerV1Global, + wp_fifo_manager_v1::WpFifoManagerV1Global, wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1Global, wp_presentation::WpPresentationGlobal, wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1Global, @@ -171,6 +172,7 @@ impl Globals { add_singleton!(ZwpIdleInhibitManagerV1Global); add_singleton!(ExtIdleNotifierV1Global); add_singleton!(XdgToplevelDragManagerV1Global); + add_singleton!(WpFifoManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index 8399ffc8..3e5b7a88 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -37,6 +37,7 @@ pub mod wp_content_type_manager_v1; pub mod wp_content_type_v1; pub mod wp_cursor_shape_device_v1; pub mod wp_cursor_shape_manager_v1; +pub mod wp_fifo_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; diff --git a/src/ifs/wl_compositor.rs b/src/ifs/wl_compositor.rs index 97086f5a..eaef0267 100644 --- a/src/ifs/wl_compositor.rs +++ b/src/ifs/wl_compositor.rs @@ -49,8 +49,16 @@ impl WlCompositorGlobal { impl WlCompositor { fn create_surface(&self, parser: MsgParser<'_, '_>) -> Result<(), WlCompositorError> { + let Some(commit_timelines) = self.client.commit_timelines.get() else { + return Err(WlCompositorError::CommitTimelines); + }; let surface: CreateSurface = self.client.parse(self, parser)?; - let surface = Rc::new(WlSurface::new(surface.id, &self.client, self.version)); + let surface = Rc::new(WlSurface::new( + surface.id, + &self.client, + self.version, + &commit_timelines, + )); track!(self.client, surface); self.client.add_client_obj(&surface)?; if self.client.is_xwayland { @@ -103,6 +111,8 @@ pub enum WlCompositorError { ClientError(Box), #[error("Parsing failed")] MsgParserError(#[source] Box), + #[error("Commit timelines are not available")] + CommitTimelines, } efrom!(WlCompositorError, ClientError); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 884afcf9..a66f8102 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -2,6 +2,7 @@ pub mod commit_timeline; pub mod cursor; pub mod ext_session_lock_surface_v1; pub mod wl_subsurface; +pub mod wp_fifo_v1; pub mod wp_fractional_scale_v1; pub mod wp_linux_drm_syncobj_surface_v1; pub mod wp_tearing_control_v1; @@ -27,9 +28,12 @@ use { NodeSeatState, SeatId, WlSeatGlobal, }, wl_surface::{ - commit_timeline::{ClearReason, CommitTimeline, CommitTimelineError}, + commit_timeline::{ + ClearReason, CommitTimeline, CommitTimelineError, CommitTimelines, + }, cursor::CursorSurface, wl_subsurface::{PendingSubsurfaceData, SubsurfaceId, WlSubsurface}, + wp_fifo_v1::WpFifoV1, wp_fractional_scale_v1::WpFractionalScaleV1, wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1, wp_tearing_control_v1::WpTearingControlV1, @@ -48,7 +52,7 @@ use { renderer::Renderer, tree::{ FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, NodeVisitorBase, OutputNode, - ToplevelNode, + OutputNodeId, ToplevelNode, }, utils::{ buffd::{MsgParser, MsgParserError}, @@ -251,6 +255,7 @@ pub struct WlSurface { sync_obj_surface: CloneCell>>, destroyed: Cell, commit_timeline: CommitTimeline, + fifo: CloneCell>>, } impl Debug for WlSurface { @@ -374,6 +379,7 @@ struct PendingState { acquire_point: Option<(Rc, SyncObjPoint)>, release_point: Option<(Rc, SyncObjPoint)>, explicit_sync: bool, + fifo: bool, } struct CommittedSubsurface { @@ -484,7 +490,12 @@ pub struct StackElement { } impl WlSurface { - pub fn new(id: WlSurfaceId, client: &Rc, version: u32) -> Self { + pub fn new( + id: WlSurfaceId, + client: &Rc, + version: u32, + commit_timelines: &Rc, + ) -> Self { Self { id, node_id: client.state.node_ids.next(), @@ -529,7 +540,8 @@ impl WlSurface { drm_feedback: Default::default(), sync_obj_surface: Default::default(), destroyed: Cell::new(false), - commit_timeline: client.commit_timelines.create_timeline(), + commit_timeline: commit_timelines.create_timeline(), + fifo: Default::default(), } } @@ -852,6 +864,7 @@ impl WlSurface { if self.destroyed.get() { return Ok(()); } + pending.fifo = false; self.ext.get().before_apply_commit(pending)?; let mut scale_changed = false; if let Some(scale) = pending.scale.take() { @@ -915,6 +928,9 @@ impl WlSurface { cursor.dec_hotspot(dx, dy); } } + if self.visible.get() { + self.commit_timeline.committed(); + } } else { self.buf_x.set(0); self.buf_y.set(0); @@ -1202,10 +1218,17 @@ impl WlSurface { } if !visible { self.send_seat_release_events(); + self.commit_timeline.presented(); } self.seat_state.set_visible(self, visible); } + pub fn presented(&self, on: OutputNodeId) { + if on == self.output.get().id { + self.commit_timeline.presented(); + } + } + pub fn detach_node(&self, set_invisible: bool) { for (_, constraint) in &self.constraints { constraint.deactivate(); @@ -1238,6 +1261,7 @@ impl WlSurface { } if set_invisible { self.visible.set(false); + self.commit_timeline.presented(); } } diff --git a/src/ifs/wl_surface/commit_timeline.rs b/src/ifs/wl_surface/commit_timeline.rs index 2ab8151e..92a5eca7 100644 --- a/src/ifs/wl_surface/commit_timeline.rs +++ b/src/ifs/wl_surface/commit_timeline.rs @@ -1,11 +1,14 @@ use { crate::{ + async_engine::SpawnedFuture, + client::Client, ifs::wl_surface::{PendingState, WlSurface, WlSurfaceError}, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::{LinkedList, LinkedNode, NodeRef}, numcell::NumCell, + queue::AsyncQueue, }, video::drm::{ sync_obj::{SyncObj, SyncObjPoint}, @@ -33,6 +36,8 @@ pub struct CommitTimelines { wfs: Rc, depth: NumCell, gc: CopyHashMap>, + flips: Rc, + _future: SpawnedFuture<()>, } pub struct CommitTimeline { @@ -40,6 +45,8 @@ pub struct CommitTimeline { own_timeline: Rc, effective_timeline: CloneCell>, effective_timeline_id: Cell, + fifo_state: Cell, + fifo_waiter: Cell>>, } struct Inner { @@ -78,11 +85,18 @@ pub enum CommitTimelineError { } impl CommitTimelines { - pub fn new(wfs: &Rc) -> Self { + pub fn new(client: &Rc) -> Self { + let state = &client.state; + let flips = Rc::new(Flips::default()); + let future = state + .eng + .spawn(process_flips(client.clone(), flips.clone())); Self { + flips, + _future: future, next_id: Default::default(), depth: NumCell::new(0), - wfs: wfs.clone(), + wfs: state.wait_for_sync_obj.clone(), gc: Default::default(), } } @@ -98,10 +112,13 @@ impl CommitTimelines { own_timeline: timeline.clone(), effective_timeline: CloneCell::new(timeline), effective_timeline_id: Cell::new(id), + fifo_state: Cell::new(SurfaceFifoState::Presented), + fifo_waiter: Default::default(), } } pub fn clear(&self) { + self.flips.flip_waiters.clear(); for (_, list) in self.gc.lock().drain() { break_loops(&list); } @@ -121,6 +138,7 @@ fn break_loops(list: &LinkedList) { impl CommitTimeline { pub fn clear(&self, reason: ClearReason) { + self.fifo_waiter.take(); match reason { ClearReason::BreakLoops => break_loops(&self.own_timeline.entries), ClearReason::Destroy => { @@ -141,7 +159,10 @@ impl CommitTimeline { ) -> Result<(), CommitTimelineError> { let mut points = SmallVec::new(); consume_acquire_points(pending, &mut points); - if points.is_empty() && self.own_timeline.entries.is_empty() { + let must_be_queued = points.is_not_empty() + || self.own_timeline.entries.is_not_empty() + || (pending.fifo && self.fifo_state.get() == SurfaceFifoState::Committed); + if !must_be_queued { return surface .apply_state(pending) .map_err(CommitTimelineError::ImmediateCommit); @@ -150,6 +171,10 @@ impl CommitTimeline { return Err(CommitTimelineError::Depth); } set_effective_timeline(self, pending, &self.own_timeline); + let commit_fifo_state = match pending.fifo { + true => CommitFifoState::Queued, + false => CommitFifoState::Mailbox, + }; let noderef = add_entry( &self.own_timeline.entries, &self.shared, @@ -158,11 +183,12 @@ impl CommitTimeline { pending: RefCell::new(mem::take(pending)), sync_obj: NumCell::new(points.len()), wait_handles: Cell::new(Default::default()), + fifo_state: Cell::new(commit_fifo_state), }), ); if points.is_not_empty() { let mut wait_handles = SmallVec::new(); - let noderef = Rc::new(noderef); + let noderef = Rc::new(noderef.clone()); for (sync_obj, point) in points { let handle = self .shared @@ -176,8 +202,22 @@ impl CommitTimeline { }; commit.wait_handles.set(wait_handles); } + if commit_fifo_state == CommitFifoState::Queued { + flush_from(noderef).map_err(CommitTimelineError::ImmediateCommit)?; + } Ok(()) } + + pub fn committed(&self) { + self.fifo_state.set(SurfaceFifoState::Committed); + } + + pub fn presented(&self) { + self.fifo_state.set(SurfaceFifoState::Presented); + if let Some(waiter) = self.fifo_waiter.take() { + self.shared.flips.flip_waiters.push(waiter); + } + } } impl SyncObjWaiter for NodeRef { @@ -217,6 +257,7 @@ struct Commit { pending: RefCell>, sync_obj: NumCell, wait_handles: Cell>, + fifo_state: Cell, } fn flush_from(mut point: NodeRef) -> Result<(), WlSurfaceError> { @@ -242,6 +283,18 @@ impl NodeRef { if c.sync_obj.get() > 0 { return Ok(false); } + let tl = &c.surface.commit_timeline; + if tl.fifo_state.get() == SurfaceFifoState::Committed { + match c.fifo_state.get() { + CommitFifoState::Queued => { + tl.fifo_waiter.set(Some(self.clone())); + c.fifo_state.set(CommitFifoState::Registered); + return Ok(false); + } + CommitFifoState::Registered => return Ok(false), + CommitFifoState::Mailbox => {} + } + } c.surface.apply_state(c.pending.borrow_mut().deref_mut())?; Ok(true) } @@ -293,3 +346,33 @@ fn set_effective_timeline( set_effective_timeline(&ss.subsurface.surface.commit_timeline, &ss.state, effective); } } + +#[derive(Default)] +struct Flips { + flip_waiters: AsyncQueue>, +} + +async fn process_flips(client: Rc, flips: Rc) { + loop { + flips.flip_waiters.non_empty().await; + while let Some(entry) = flips.flip_waiters.try_pop() { + if let Err(e) = flush_from(entry) { + client.error(e); + return; + } + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum SurfaceFifoState { + Committed, + Presented, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum CommitFifoState { + Queued, + Registered, + Mailbox, +} diff --git a/src/ifs/wl_surface/wp_fifo_v1.rs b/src/ifs/wl_surface/wp_fifo_v1.rs new file mode 100644 index 00000000..d7f84bf4 --- /dev/null +++ b/src/ifs/wl_surface/wp_fifo_v1.rs @@ -0,0 +1,74 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_surface::WlSurface, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{wp_fifo_v1::*, WpFifoV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpFifoV1 { + pub id: WpFifoV1Id, + pub client: Rc, + pub surface: Rc, + pub tracker: Tracker, +} + +impl WpFifoV1 { + pub fn new(id: WpFifoV1Id, surface: &Rc) -> Self { + Self { + id, + client: surface.client.clone(), + surface: surface.clone(), + tracker: Default::default(), + } + } + + pub fn install(self: &Rc) -> Result<(), WpFifoV1Error> { + if self.surface.fifo.is_some() { + return Err(WpFifoV1Error::Exists); + } + self.surface.fifo.set(Some(self.clone())); + Ok(()) + } + + fn fifo(&self, msg: MsgParser<'_, '_>) -> Result<(), WpFifoV1Error> { + let _req: Fifo = self.client.parse(self, msg)?; + self.surface.pending.borrow_mut().fifo = true; + Ok(()) + } + + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), WpFifoV1Error> { + let _req: Destroy = self.client.parse(self, msg)?; + self.surface.fifo.take(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = WpFifoV1; + + FIFO => fifo, + DESTROY => destroy, +} + +impl Object for WpFifoV1 {} + +simple_add_obj!(WpFifoV1); + +#[derive(Debug, Error)] +pub enum WpFifoV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error("The surface already has a fifo extension attached")] + Exists, +} +efrom!(WpFifoV1Error, MsgParserError); +efrom!(WpFifoV1Error, ClientError); diff --git a/src/ifs/wp_fifo_manager_v1.rs b/src/ifs/wp_fifo_manager_v1.rs new file mode 100644 index 00000000..693227cc --- /dev/null +++ b/src/ifs/wp_fifo_manager_v1.rs @@ -0,0 +1,100 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::wl_surface::wp_fifo_v1::{WpFifoV1, WpFifoV1Error}, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{wp_fifo_manager_v1::*, WpFifoManagerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpFifoManagerV1Global { + pub name: GlobalName, +} + +pub struct WpFifoManagerV1 { + pub id: WpFifoManagerV1Id, + pub client: Rc, + pub tracker: Tracker, +} + +impl WpFifoManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: WpFifoManagerV1Id, + client: &Rc, + _version: u32, + ) -> Result<(), WpFifoManagerV1Error> { + let obj = Rc::new(WpFifoManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!(WpFifoManagerV1Global, WpFifoManagerV1, WpFifoManagerV1Error); + +impl Global for WpFifoManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(WpFifoManagerV1Global); + +impl WpFifoManagerV1 { + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), WpFifoManagerV1Error> { + let _req: Destroy = self.client.parse(self, msg)?; + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_fifo(&self, msg: MsgParser<'_, '_>) -> Result<(), WpFifoManagerV1Error> { + let req: GetFifo = self.client.parse(self, msg)?; + let surface = self.client.lookup(req.surface)?; + let fs = Rc::new(WpFifoV1::new(req.id, &surface)); + track!(self.client, fs); + fs.install()?; + self.client.add_client_obj(&fs)?; + Ok(()) + } +} + +object_base! { + self = WpFifoManagerV1; + + DESTROY => destroy, + GET_FIFO => get_fifo, +} + +impl Object for WpFifoManagerV1 {} + +simple_add_obj!(WpFifoManagerV1); + +#[derive(Debug, Error)] +pub enum WpFifoManagerV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + WpFifoV1Error(#[from] WpFifoV1Error), +} +efrom!(WpFifoManagerV1Error, MsgParserError); +efrom!(WpFifoManagerV1Error, ClientError); diff --git a/src/macros.rs b/src/macros.rs index f570d831..62320424 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -305,6 +305,11 @@ macro_rules! tree_id { pub fn raw(&self) -> u32 { self.0 } + + #[allow(dead_code)] + pub fn none() -> Self { + Self(0) + } } impl std::fmt::Display for $id { diff --git a/src/renderer.rs b/src/renderer.rs index 93922a9d..c2243e20 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -15,8 +15,8 @@ use { state::State, theme::Color, tree::{ - ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelNodeBase, - WorkspaceNode, + ContainerNode, DisplayNode, FloatNode, OutputNode, OutputNodeId, PlaceholderNode, + ToplevelNodeBase, WorkspaceNode, }, }, std::{ @@ -29,10 +29,20 @@ use { pub mod renderer_base; -#[derive(Default)] pub struct RenderResult { pub frame_requests: Vec>, pub presentation_feedbacks: Vec>, + pub output_id: OutputNodeId, +} + +impl Default for RenderResult { + fn default() -> Self { + Self { + frame_requests: Default::default(), + presentation_feedbacks: Default::default(), + output_id: OutputNodeId::none(), + } + } } impl RenderResult { @@ -373,6 +383,7 @@ impl Renderer<'_> { let mut fbs = surface.presentation_feedback.borrow_mut(); result.presentation_feedbacks.extend(fbs.drain(..)); } + surface.presented(result.output_id); } } diff --git a/src/state.rs b/src/state.rs index a6047fe4..df49f383 100644 --- a/src/state.rs +++ b/src/state.rs @@ -794,6 +794,7 @@ impl State { rr: &mut RenderResult, render_hw_cursor: bool, ) -> Result, GfxError> { + rr.output_id = output.id; let sync_file = fb.render_output( output, self, diff --git a/wire/wp_fifo_manager_v1.txt b/wire/wp_fifo_manager_v1.txt new file mode 100644 index 00000000..c21b9317 --- /dev/null +++ b/wire/wp_fifo_manager_v1.txt @@ -0,0 +1,10 @@ +# requests + +msg destroy = 0 { + +} + +msg get_fifo = 1 { + id: id(wp_fifo_v1), + surface: id(wl_surface), +} diff --git a/wire/wp_fifo_v1.txt b/wire/wp_fifo_v1.txt new file mode 100644 index 00000000..2512a375 --- /dev/null +++ b/wire/wp_fifo_v1.txt @@ -0,0 +1,9 @@ +# requests + +msg fifo = 0 { + +} + +msg destroy = 1 { + +}