diff --git a/docs/features.md b/docs/features.md index 44028051..fe7416e6 100644 --- a/docs/features.md +++ b/docs/features.md @@ -156,6 +156,7 @@ Jay supports the following wayland protocols: | wl_shm | 2 | | | wl_subcompositor | 1 | | | wp_alpha_modifier_v1 | 1 | | +| wp_commit_timing_manager_v1 | 1 | | | wp_content_type_manager_v1 | 1 | | | wp_cursor_shape_manager_v1 | 1 | | | wp_drm_lease_device_v1 | 1 | | diff --git a/release-notes.md b/release-notes.md index f7d955e3..3040c00e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,7 @@ - Implement screencast session restoration. - Fix screen sharing in zoom. - Implement wp-fifo-v1. +- Implement wp-commit-timing-v1. # 1.6.0 (2024-09-25) diff --git a/src/backend.rs b/src/backend.rs index a7f66df8..3438eb1a 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -65,6 +65,15 @@ pub struct Mode { pub refresh_rate_millihz: u32, } +impl Mode { + pub fn refresh_nsec(&self) -> u64 { + match self.refresh_rate_millihz { + 0 => u64::MAX, + n => 1_000_000_000_000 / (n as u64), + } + } +} + #[derive(Clone, Debug)] pub struct MonitorInfo { pub modes: Vec, diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index e9289088..f99ac26f 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -101,12 +101,15 @@ impl MetalConnector { if !self.can_present.get() { continue; } + let Some(node) = self.state.root.outputs.get(&self.connector_id) else { + continue; + }; let mut expected_sequence = self.sequence.get() + 1; let mut start = Time::now_unchecked(); let use_frame_scheduling = !self.try_async_flip(); if use_frame_scheduling { let next_present = self - .next_flip_nsec + .next_vblank_nsec .get() .saturating_sub(self.pre_commit_margin.get()) .saturating_sub(self.post_commit_margin.get()); @@ -118,7 +121,15 @@ impl MetalConnector { } } frame!(frame_name); - if let Err(e) = self.present_once().await { + { + let now = start.nsec(); + let flip = match self.try_async_flip() { + true => now, + false => self.next_vblank_nsec.get(), + }; + node.before_latch(flip).await; + } + if let Err(e) = self.present_once(&node).await { log::error!("Could not present: {}", ErrorFmt(e)); continue; } @@ -138,7 +149,7 @@ impl MetalConnector { } } - async fn present_once(&self) -> Result<(), MetalError> { + async fn present_once(&self, node: &Rc) -> Result<(), MetalError> { let version = self.version.get(); if !self.can_present.get() { return Ok(()); @@ -146,9 +157,6 @@ impl MetalConnector { if !self.backend.check_render_context(&self.dev) { return Ok(()); } - let Some(node) = self.state.root.outputs.get(&self.connector_id) else { - return Ok(()); - }; let crtc = match self.crtc.get() { Some(crtc) => crtc, _ => return Ok(()), diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index eb17e8ea..6443aa19 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -445,7 +445,7 @@ pub struct MetalConnector { pub has_damage: NumCell, pub cursor_changed: Cell, pub cursor_damage: Cell, - pub next_flip_nsec: Cell, + pub next_vblank_nsec: Cell, pub display: RefCell, @@ -1085,7 +1085,7 @@ fn create_connector( active_framebuffer: Default::default(), next_framebuffer: Default::default(), direct_scanout_active: Cell::new(false), - next_flip_nsec: Cell::new(0), + next_vblank_nsec: Cell::new(0), tearing_requested: Cell::new(false), try_switch_format: Cell::new(false), version: Default::default(), @@ -1944,7 +1944,7 @@ impl MetalBackend { self.state.vblank(connector.connector_id); let dd = connector.display.borrow(); connector - .next_flip_nsec + .next_vblank_nsec .set(time_ns as u64 + dd.refresh as u64); } @@ -1993,7 +1993,9 @@ impl MetalBackend { { connector.schedule_present(); } - connector.next_flip_nsec.set(time_ns + dd.refresh as u64); + if connector.presentation_is_sync.get() { + connector.next_vblank_nsec.set(time_ns + dd.refresh as u64); + } { let mut flags = KIND_HW_COMPLETION; if connector.presentation_is_sync.get() { diff --git a/src/backends/x.rs b/src/backends/x.rs index c5fc89ef..0a230284 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -13,6 +13,7 @@ use { gfx_api::{AcquireSync, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync}, ifs::wl_output::OutputId, state::State, + time::Time, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, queue::AsyncQueue, syncqueue::SyncQueue, @@ -745,6 +746,8 @@ impl XBackend { image.last_serial.set(serial); if let Some(node) = self.state.root.outputs.get(&output.id) { + let now = Time::now_unchecked().nsec(); + node.before_latch(now).await; let res = self.state.present_output( &node, &image.fb.get(), diff --git a/src/compositor.rs b/src/compositor.rs index 2cc10eda..61236341 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -548,7 +548,7 @@ fn create_dummy_output(state: &Rc) { &backend::Mode { width: 0, height: 0, - refresh_rate_millihz: 0, + refresh_rate_millihz: 40_000, }, 0, 0, @@ -582,8 +582,10 @@ fn create_dummy_output(state: &Rc) { vblank_event: Default::default(), latch_event: Default::default(), presentation_event: Default::default(), + render_margin_ns: Default::default(), flip_margin_ns: Default::default(), ext_copy_sessions: Default::default(), + before_latch_event: Default::default(), }); let dummy_workspace = Rc::new(WorkspaceNode { id: state.node_ids.next(), diff --git a/src/globals.rs b/src/globals.rs index 132a8924..fdb6d302 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -37,6 +37,7 @@ use { wl_subcompositor::WlSubcompositorGlobal, wl_surface::xwayland_shell_v1::XwaylandShellV1Global, wp_alpha_modifier_v1::WpAlphaModifierV1Global, + wp_commit_timing_manager_v1::WpCommitTimingManagerV1Global, wp_content_type_manager_v1::WpContentTypeManagerV1Global, wp_cursor_shape_manager_v1::WpCursorShapeManagerV1Global, wp_fifo_manager_v1::WpFifoManagerV1Global, @@ -205,6 +206,7 @@ impl Globals { add_singleton!(ExtForeignToplevelImageCaptureSourceManagerV1Global); add_singleton!(ExtImageCopyCaptureManagerV1Global); add_singleton!(WpFifoManagerV1Global); + add_singleton!(WpCommitTimingManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index 96c6b411..904078ae 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -45,6 +45,7 @@ pub mod wl_shm_pool; pub mod wl_subcompositor; pub mod wl_surface; pub mod wp_alpha_modifier_v1; +pub mod wp_commit_timing_manager_v1; pub mod wp_content_type_manager_v1; pub mod wp_content_type_v1; pub mod wp_cursor_shape_device_v1; diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 486d5178..7472df98 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -60,6 +60,7 @@ pub struct WlOutputGlobal { pub pos: Cell, pub output_id: Rc, pub mode: Cell, + pub refresh_nsec: Cell, pub modes: Vec, pub formats: CloneCell>>, pub format: Cell<&'static Format>, @@ -157,6 +158,7 @@ impl WlOutputGlobal { pos: Cell::new(Rect::new_sized(x, y, width, height).unwrap()), output_id: output_id.clone(), mode: Cell::new(*mode), + refresh_nsec: Cell::new(mode.refresh_nsec()), modes, formats: CloneCell::new(Rc::new(vec![])), format: Cell::new(XRGB8888), diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index dc960018..8472729f 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -4,6 +4,7 @@ pub mod dnd_icon; pub mod ext_session_lock_surface_v1; pub mod wl_subsurface; pub mod wp_alpha_modifier_surface_v1; +pub mod wp_commit_timer_v1; pub mod wp_fifo_v1; pub mod wp_fractional_scale_v1; pub mod wp_linux_drm_syncobj_surface_v1; @@ -47,6 +48,7 @@ use { dnd_icon::DndIcon, wl_subsurface::{PendingSubsurfaceData, SubsurfaceId, WlSubsurface}, wp_alpha_modifier_surface_v1::WpAlphaModifierSurfaceV1, + wp_commit_timer_v1::WpCommitTimerV1, wp_fifo_v1::WpFifoV1, wp_fractional_scale_v1::WpFractionalScaleV1, wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1, @@ -60,14 +62,15 @@ use { wp_presentation_feedback::WpPresentationFeedback, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, }, + io_uring::IoUringError, leaks::Tracker, object::{Object, Version}, rect::{DamageQueue, Rect, Region}, renderer::Renderer, tree::{ - ContainerNode, FindTreeResult, FoundNode, LatchListener, Node, NodeId, NodeVisitor, - NodeVisitorBase, OutputNode, PlaceholderNode, PresentationListener, ToplevelNode, - VblankListener, + BeforeLatchListener, BeforeLatchResult, ContainerNode, FindTreeResult, FoundNode, + LatchListener, Node, NodeId, NodeVisitor, NodeVisitorBase, OutputNode, PlaceholderNode, + PresentationListener, ToplevelNode, VblankListener, }, utils::{ cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, @@ -321,6 +324,8 @@ pub struct WlSurface { latched_commit_version: Cell, fifo: CloneCell>>, clear_fifo_on_vblank: Cell, + commit_timer: CloneCell>>, + before_latch_listener: EventListener, } impl Debug for WlSurface { @@ -444,6 +449,7 @@ struct PendingState { explicit_sync: bool, fifo_barrier_set: bool, fifo_barrier_wait: bool, + commit_time: Option, } struct AttachedSubsurfaceState { @@ -494,6 +500,7 @@ impl PendingState { opt!(tearing); opt!(content_type); opt!(alpha_multiplier); + opt!(commit_time); { let (dx1, dy1) = self.offset; let (dx2, dy2) = mem::take(&mut next.offset); @@ -648,6 +655,8 @@ impl WlSurface { latched_commit_version: Default::default(), fifo: Default::default(), clear_fifo_on_vblank: Default::default(), + commit_timer: Default::default(), + before_latch_listener: EventListener::new(slf.clone()), } } @@ -1657,6 +1666,7 @@ impl Object for WlSurface { self.alpha_modifier.take(); self.text_input_connections.clear(); self.fifo.take(); + self.commit_timer.take(); } } @@ -2001,6 +2011,8 @@ pub enum WlSurfaceError { CreateAsyncShmTexture(#[source] GfxError), #[error("Could not prepare upload to a shm texture")] PrepareAsyncUpload(#[source] GfxError), + #[error("Could not register a commit timeout")] + RegisterCommitTimeout(#[source] IoUringError), } efrom!(WlSurfaceError, ClientError); efrom!(WlSurfaceError, XdgSurfaceError); @@ -2134,6 +2146,12 @@ impl VblankListener for WlSurface { } } +impl BeforeLatchListener for WlSurface { + fn before_latch(self: Rc, present: u64) -> BeforeLatchResult { + self.commit_timeline.before_latch(&self, present) + } +} + impl LatchListener for WlSurface { fn after_latch(self: Rc, _on: &OutputNode, tearing: bool) { if self.visible.get() { diff --git a/src/ifs/wl_surface/commit_timeline.rs b/src/ifs/wl_surface/commit_timeline.rs index d26247eb..d41d11a3 100644 --- a/src/ifs/wl_surface/commit_timeline.rs +++ b/src/ifs/wl_surface/commit_timeline.rs @@ -7,7 +7,10 @@ use { wl_buffer::WlBufferStorage, wl_surface::{PendingState, WlSurface, WlSurfaceError}, }, - io_uring::{IoUring, IoUringError, PendingPoll, PollCallback}, + io_uring::{ + IoUring, IoUringError, PendingPoll, PendingTimeout, PollCallback, TimeoutCallback, + }, + tree::BeforeLatchResult, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, @@ -28,7 +31,7 @@ use { std::{ cell::{Cell, RefCell}, mem, - ops::DerefMut, + ops::{Deref, DerefMut}, rc::{Rc, Weak}, slice, }, @@ -50,6 +53,11 @@ pub struct CommitTimelines { _flush_requests_future: SpawnedFuture<()>, } +struct CommitTimeWaiter { + node: NodeRef, + present: u64, +} + pub struct CommitTimeline { shared: Rc, own_timeline: Rc, @@ -57,6 +65,7 @@ pub struct CommitTimeline { effective_timeline_id: Cell, fifo_barrier_set: Cell, fifo_waiter: Cell>>, + commit_time_waiter: RefCell>, } struct Inner { @@ -98,6 +107,8 @@ pub enum CommitTimelineError { RegisterImplicitPoll(#[source] IoUringError), #[error("Could not wait for a dmabuf to become idle")] PollDmabuf(#[source] OsError), + #[error("Could not wait for the commit timeout")] + CommitTimeout(#[source] OsError), } impl CommitTimelines { @@ -136,6 +147,7 @@ impl CommitTimelines { effective_timeline_id: Cell::new(id), fifo_barrier_set: Cell::new(false), fifo_waiter: Default::default(), + commit_time_waiter: Default::default(), } } @@ -168,6 +180,7 @@ impl CommitTimeline { match reason { ClearReason::BreakLoops => { self.fifo_waiter.take(); + self.commit_time_waiter.take(); break_loops(&self.own_timeline.entries) } ClearReason::Destroy => { @@ -187,17 +200,22 @@ impl CommitTimeline { surface: &Rc, pending: &mut Box, ) -> Result<(), CommitTimelineError> { - let mut points = SmallVec::new(); - let mut pending_uploads = 0; - let mut implicit_dmabufs = SmallVec::new(); - collect_commit_data( - pending, - &mut points, - &mut pending_uploads, - &mut implicit_dmabufs, - ); - let has_dependencies = - points.is_not_empty() || pending_uploads > 0 || implicit_dmabufs.is_not_empty(); + let mut collector = CommitDataCollector { + acquire_points: Default::default(), + shm_uploads: 0, + implicit_dmabufs: Default::default(), + commit_time: Default::default(), + }; + collector.collect(pending); + let points = collector.acquire_points; + let pending_uploads = collector.shm_uploads; + let implicit_dmabufs = collector.implicit_dmabufs; + let commit_time = collector.commit_time; + let has_commit_time = commit_time > 0; + let has_dependencies = points.is_not_empty() + || pending_uploads > 0 + || implicit_dmabufs.is_not_empty() + || has_commit_time; let must_be_queued = has_dependencies || self.own_timeline.entries.is_not_empty() || (pending.fifo_barrier_wait && self.fifo_barrier_set.get()); @@ -227,6 +245,7 @@ impl CommitTimeline { num_pending_polls: NumCell::new(implicit_dmabufs.len()), pending_polls: Cell::new(Default::default()), fifo_state: Cell::new(commit_fifo_state), + commit_times: RefCell::new(CommitTimesState::Ready), }), ); let mut needs_flush = commit_fifo_state == CommitFifoState::Queued; @@ -263,6 +282,13 @@ impl CommitTimeline { } commit.pending_polls.set(pending_polls); } + if has_commit_time { + *commit.commit_times.borrow_mut() = CommitTimesState::Queued { + rc: noderef.clone(), + time: commit_time, + }; + needs_flush = true; + } } if needs_flush && noderef.prev().is_none() { flush_from(noderef.clone()).map_err(CommitTimelineError::DelayedCommit)?; @@ -284,6 +310,30 @@ impl CommitTimeline { pub fn has_fifo_barrier(&self) -> bool { self.fifo_barrier_set.get() } + + pub fn before_latch(&self, surface: &WlSurface, present: u64) -> BeforeLatchResult { + let waiter = &mut *self.commit_time_waiter.borrow_mut(); + if let Some(w) = waiter { + if w.present <= present { + let EntryKind::Commit(c) = &w.node.kind else { + unreachable!(); + }; + *c.commit_times.borrow_mut() = CommitTimesState::Ready; + self.shared + .flush_requests + .flush_waiters + .push(w.node.clone()); + *waiter = None; + surface.before_latch_listener.detach(); + BeforeLatchResult::Yield + } else { + BeforeLatchResult::None + } + } else { + surface.before_latch_listener.detach(); + BeforeLatchResult::None + } + } } impl SyncObjWaiter for NodeRef { @@ -343,6 +393,25 @@ impl PollCallback for NodeRef { } } +impl TimeoutCallback for NodeRef { + fn completed(self: Rc, res: Result<(), OsError>, _data: u64) { + let EntryKind::Commit(commit) = &self.kind else { + unreachable!(); + }; + commit.surface.commit_timeline.commit_time_waiter.take(); + commit.surface.before_latch_listener.detach(); + if let Err(e) = res { + commit + .surface + .client + .error(CommitTimelineError::CommitTimeout(e)); + return; + } + *commit.commit_times.borrow_mut() = CommitTimesState::Ready; + flush_commit(&self, commit); + } +} + struct Entry { link: Cell>>, shared: Rc, @@ -362,6 +431,12 @@ enum ShmUploadState { Scheduled(#[expect(dead_code)] SmallVec<[PendingShmTransfer; 1]>), } +enum CommitTimesState { + Ready, + Queued { rc: Rc>, time: u64 }, + Registered { _pending: PendingTimeout }, +} + struct Commit { surface: Rc, pending: RefCell>, @@ -372,6 +447,7 @@ struct Commit { num_pending_polls: NumCell, pending_polls: Cell>, fifo_state: Cell, + commit_times: RefCell, } fn flush_from(mut point: NodeRef) -> Result<(), WlSurfaceError> { @@ -421,6 +497,19 @@ impl NodeRef { CommitFifoState::Mailbox => {} } } + let commit_times = &mut *c.commit_times.borrow_mut(); + match commit_times { + CommitTimesState::Ready => {} + CommitTimesState::Queued { rc, time } => { + *commit_times = register_commit_time(tl, rc, c, *time)?; + if let CommitTimesState::Registered { .. } = commit_times { + has_unmet_dependencies = true; + } + } + CommitTimesState::Registered { .. } => { + has_unmet_dependencies = true; + } + } if has_unmet_dependencies { return Ok(false); } @@ -455,6 +544,36 @@ fn check_shm_uploads(c: &Commit) -> Result<(), WlSurfaceError> { Ok(()) } +fn register_commit_time( + tl: &CommitTimeline, + rc: &Rc>, + c: &Commit, + time: u64, +) -> Result { + let output = c.surface.output.get(); + let render_margin = output.render_margin_ns.get(); + let flip_margin = output.flip_margin_ns.get().unwrap_or_default(); + let refresh = output.global.refresh_nsec.get(); + let present_margin = render_margin.saturating_add(flip_margin).min(refresh); + let timeout = time.saturating_sub(present_margin); + if timeout <= c.surface.client.state.now_nsec() { + return Ok(CommitTimesState::Ready); + } + let pending = tl + .shared + .ring + .timeout_external(timeout, rc.clone(), 0) + .map_err(WlSurfaceError::RegisterCommitTimeout)?; + *tl.commit_time_waiter.borrow_mut() = Some(CommitTimeWaiter { + node: rc.deref().clone(), + present: time, + }); + c.surface + .before_latch_listener + .attach(&output.before_latch_event); + Ok(CommitTimesState::Registered { _pending: pending }) +} + fn schedule_async_uploads( node_ref: &Rc>, surface: &WlSurface, @@ -550,30 +669,37 @@ fn schedule_async_upload( type Point = (Rc, SyncObjPoint); -fn collect_commit_data( - pending: &mut PendingState, - acquire_points: &mut SmallVec<[Point; 1]>, - shm_uploads: &mut usize, - implicit_dmabufs: &mut SmallVec<[Rc; 1]>, -) { - if let Some(Some(buffer)) = &pending.buffer { - if buffer.is_shm() { - *shm_uploads += 1; - } - if !pending.explicit_sync { - if let Some(dmabuf) = &buffer.dmabuf { - for plane in &dmabuf.planes { - implicit_dmabufs.push(plane.fd.clone()); +struct CommitDataCollector { + acquire_points: SmallVec<[Point; 1]>, + shm_uploads: usize, + implicit_dmabufs: SmallVec<[Rc; 1]>, + commit_time: u64, +} + +impl CommitDataCollector { + fn collect(&mut self, pending: &mut PendingState) { + if let Some(Some(buffer)) = &pending.buffer { + if buffer.is_shm() { + self.shm_uploads += 1; + } + if !pending.explicit_sync { + if let Some(dmabuf) = &buffer.dmabuf { + for plane in &dmabuf.planes { + self.implicit_dmabufs.push(plane.fd.clone()); + } } } } - } - if let Some(point) = pending.acquire_point.take() { - acquire_points.push(point); - } - for ss in pending.subsurfaces.values_mut() { - if let Some(state) = &mut ss.pending.state { - collect_commit_data(state, acquire_points, shm_uploads, implicit_dmabufs); + if let Some(point) = pending.acquire_point.take() { + self.acquire_points.push(point); + } + if let Some(commit_time) = pending.commit_time.take() { + self.commit_time = self.commit_time.max(commit_time); + } + for ss in pending.subsurfaces.values_mut() { + if let Some(state) = &mut ss.pending.state { + self.collect(state); + } } } } diff --git a/src/ifs/wl_surface/wp_commit_timer_v1.rs b/src/ifs/wl_surface/wp_commit_timer_v1.rs new file mode 100644 index 00000000..f255b738 --- /dev/null +++ b/src/ifs/wl_surface/wp_commit_timer_v1.rs @@ -0,0 +1,94 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_surface::WlSurface, + leaks::Tracker, + object::{Object, Version}, + wire::{ + wp_commit_timer_v1::{Destroy, SetTimestamp, WpCommitTimerV1RequestHandler}, + WpCommitTimerV1Id, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpCommitTimerV1 { + pub id: WpCommitTimerV1Id, + pub client: Rc, + pub surface: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl WpCommitTimerV1 { + pub fn new(id: WpCommitTimerV1Id, version: Version, surface: &Rc) -> Self { + Self { + id, + client: surface.client.clone(), + surface: surface.clone(), + tracker: Default::default(), + version, + } + } + + pub fn install(self: &Rc) -> Result<(), WpCommitTimerV1Error> { + if self.surface.commit_timer.is_some() { + return Err(WpCommitTimerV1Error::Exists); + } + self.surface.commit_timer.set(Some(self.clone())); + Ok(()) + } +} + +impl WpCommitTimerV1RequestHandler for WpCommitTimerV1 { + type Error = WpCommitTimerV1Error; + + fn set_timestamp(&self, req: SetTimestamp, _slf: &Rc) -> Result<(), Self::Error> { + if req.tv_nsec >= 1_000_000_000 { + return Err(WpCommitTimerV1Error::InvalidNsec); + } + let nsec = (((req.tv_sec_hi as u64) << 32) | (req.tv_sec_lo as u64)) + .checked_mul(1_000_000_000) + .and_then(|n| n.checked_add(req.tv_nsec as u64)); + let Some(nsec) = nsec else { + return Err(WpCommitTimerV1Error::Overflow); + }; + let pending = &mut *self.surface.pending.borrow_mut(); + if pending.commit_time.is_some() { + return Err(WpCommitTimerV1Error::TimestampExists); + } + pending.commit_time = Some(nsec); + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.surface.commit_timer.take(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = WpCommitTimerV1; + version = self.version; +} + +impl Object for WpCommitTimerV1 {} + +simple_add_obj!(WpCommitTimerV1); + +#[derive(Debug, Error)] +pub enum WpCommitTimerV1Error { + #[error(transparent)] + ClientError(Box), + #[error("The surface already has a commit timer extension attached")] + Exists, + #[error("The tv_nsec is larger than 999_999_999")] + InvalidNsec, + #[error("The timestamp overflowed")] + Overflow, + #[error("The commit already has a timestamp")] + TimestampExists, +} +efrom!(WpCommitTimerV1Error, ClientError); diff --git a/src/ifs/wp_commit_timing_manager_v1.rs b/src/ifs/wp_commit_timing_manager_v1.rs new file mode 100644 index 00000000..a59107ce --- /dev/null +++ b/src/ifs/wp_commit_timing_manager_v1.rs @@ -0,0 +1,105 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::wl_surface::wp_commit_timer_v1::{WpCommitTimerV1, WpCommitTimerV1Error}, + leaks::Tracker, + object::{Object, Version}, + wire::{ + wp_commit_timing_manager_v1::{ + Destroy, GetTimer, WpCommitTimingManagerV1RequestHandler, + }, + WpCommitTimingManagerV1Id, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpCommitTimingManagerV1Global { + pub name: GlobalName, +} + +pub struct WpCommitTimingManagerV1 { + pub id: WpCommitTimingManagerV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl WpCommitTimingManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: WpCommitTimingManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), WpCommitTimingManagerV1Error> { + let obj = Rc::new(WpCommitTimingManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!( + WpCommitTimingManagerV1Global, + WpCommitTimingManagerV1, + WpCommitTimingManagerV1Error +); + +impl Global for WpCommitTimingManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(WpCommitTimingManagerV1Global); + +impl WpCommitTimingManagerV1RequestHandler for WpCommitTimingManagerV1 { + type Error = WpCommitTimingManagerV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_timer(&self, req: GetTimer, _slf: &Rc) -> Result<(), Self::Error> { + let surface = self.client.lookup(req.surface)?; + let obj = Rc::new(WpCommitTimerV1::new(req.id, self.version, &surface)); + track!(self.client, obj); + obj.install()?; + self.client.add_client_obj(&obj)?; + Ok(()) + } +} + +object_base! { + self = WpCommitTimingManagerV1; + version = self.version; +} + +impl Object for WpCommitTimingManagerV1 {} + +simple_add_obj!(WpCommitTimingManagerV1); + +#[derive(Debug, Error)] +pub enum WpCommitTimingManagerV1Error { + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + WpCommitTimerV1Error(#[from] WpCommitTimerV1Error), +} +efrom!(WpCommitTimingManagerV1Error, ClientError); diff --git a/src/io_uring.rs b/src/io_uring.rs index 4f9f9267..28978ed0 100644 --- a/src/io_uring.rs +++ b/src/io_uring.rs @@ -1,5 +1,6 @@ pub use ops::{ poll_external::{PendingPoll, PollCallback}, + timeout_external::{PendingTimeout, TimeoutCallback}, TaskResultExt, }; use { @@ -10,7 +11,8 @@ use { accept::AcceptTask, async_cancel::AsyncCancelTask, connect::ConnectTask, poll::PollTask, poll_external::PollExternalTask, read_write::ReadWriteTask, read_write_no_cancel::ReadWriteNoCancelTask, recvmsg::RecvmsgTask, - sendmsg::SendmsgTask, timeout::TimeoutTask, timeout_link::TimeoutLinkTask, + sendmsg::SendmsgTask, timeout::TimeoutTask, timeout_external::TimeoutExternalTask, + timeout_link::TimeoutLinkTask, }, pending_result::PendingResults, sys::{ @@ -216,6 +218,7 @@ impl IoUring { cached_sendmsg: Default::default(), cached_recvmsg: Default::default(), cached_timeouts: Default::default(), + cached_timeouts_external: Default::default(), cached_timeout_links: Default::default(), cached_cmsg_bufs: Default::default(), cached_connects: Default::default(), @@ -278,6 +281,7 @@ struct IoUringData { cached_sendmsg: Stack>, cached_recvmsg: Stack>, cached_timeouts: Stack>, + cached_timeouts_external: Stack>, cached_timeout_links: Stack>, cached_cmsg_bufs: Stack, cached_connects: Stack>, diff --git a/src/io_uring/ops.rs b/src/io_uring/ops.rs index e5f4bf0a..e6abdbee 100644 --- a/src/io_uring/ops.rs +++ b/src/io_uring/ops.rs @@ -10,6 +10,7 @@ pub mod read_write_no_cancel; pub mod recvmsg; pub mod sendmsg; pub mod timeout; +pub mod timeout_external; pub mod timeout_link; pub type TaskResult = Result, IoUringError>; diff --git a/src/io_uring/ops/timeout_external.rs b/src/io_uring/ops/timeout_external.rs new file mode 100644 index 00000000..cb7269ac --- /dev/null +++ b/src/io_uring/ops/timeout_external.rs @@ -0,0 +1,97 @@ +use { + crate::{ + io_uring::{ + ops::timeout::timespec64, + sys::{io_uring_sqe, IORING_OP_TIMEOUT, IORING_TIMEOUT_ABS}, + IoUring, IoUringData, IoUringError, IoUringTaskId, Task, + }, + utils::oserror::OsError, + }, + std::{cell::Cell, rc::Rc}, + uapi::c, +}; + +pub trait TimeoutCallback { + fn completed(self: Rc, res: Result<(), OsError>, data: u64); +} + +pub struct PendingTimeout { + data: Rc, + shared: Rc, + id: IoUringTaskId, +} + +impl Drop for PendingTimeout { + fn drop(&mut self) { + if self.shared.id.get() != self.id { + return; + } + self.shared.callback.take(); + self.data.cancel_task(self.id); + } +} + +#[derive(Default)] +struct TimeoutExternalTaskShared { + id: Cell, + callback: Cell>>, +} + +#[derive(Default)] +pub struct TimeoutExternalTask { + timespec: timespec64, + shared: Rc, + data: u64, +} + +impl IoUring { + pub fn timeout_external( + &self, + timeout_nsec: u64, + callback: Rc, + data: u64, + ) -> Result { + self.ring.check_destroyed()?; + let mut pw = self.ring.cached_timeouts_external.pop().unwrap_or_default(); + pw.shared.id.set(self.ring.id_raw()); + pw.shared.callback.set(Some(callback)); + pw.timespec = timespec64 { + tv_sec: (timeout_nsec / 1_000_000_000) as _, + tv_nsec: (timeout_nsec % 1_000_000_000) as _, + }; + pw.data = data; + let pending = PendingTimeout { + data: self.ring.clone(), + shared: pw.shared.clone(), + id: pw.shared.id.get(), + }; + self.ring.schedule(pw); + Ok(pending) + } +} + +unsafe impl Task for TimeoutExternalTask { + fn id(&self) -> IoUringTaskId { + self.shared.id.get() + } + + fn complete(self: Box, ring: &IoUringData, res: i32) { + if let Some(pr) = self.shared.callback.take() { + let res = if res == -c::ETIME { + Ok(()) + } else { + map_err!(res).map(drop) + }; + pr.completed(res, self.data); + } + ring.cached_timeouts_external.push(self); + } + + fn encode(&self, sqe: &mut io_uring_sqe) { + sqe.opcode = IORING_OP_TIMEOUT; + sqe.u2.addr = &self.timespec as *const _ as _; + sqe.len = 1; + sqe.u3.timeout_flags = IORING_TIMEOUT_ABS; + sqe.u1.off = 0; + } +} diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index c8e63c71..bcec654d 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -184,8 +184,10 @@ impl ConnectorHandler { latch_event: Default::default(), vblank_event: Default::default(), presentation_event: Default::default(), + render_margin_ns: Default::default(), flip_margin_ns: Default::default(), ext_copy_sessions: Default::default(), + before_latch_event: Default::default(), }); on.update_visible(); on.update_rects(); diff --git a/src/tree/output.rs b/src/tree/output.rs index d3db918d..b853a6b7 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -53,7 +53,7 @@ use { std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, - ops::Deref, + ops::{BitOrAssign, Deref}, rc::Rc, }, }; @@ -89,9 +89,29 @@ pub struct OutputNode { pub latch_event: EventSource, pub vblank_event: EventSource, pub presentation_event: EventSource, + pub render_margin_ns: Cell, pub flip_margin_ns: Cell>, pub ext_copy_sessions: CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc>, + pub before_latch_event: EventSource, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum BeforeLatchResult { + None, + Yield, +} + +impl BitOrAssign for BeforeLatchResult { + fn bitor_assign(&mut self, rhs: Self) { + if rhs == BeforeLatchResult::Yield { + *self = rhs; + } + } +} + +pub trait BeforeLatchListener { + fn before_latch(self: Rc, present: u64) -> BeforeLatchResult; } pub trait LatchListener { @@ -135,6 +155,16 @@ pub async fn output_render_data(state: Rc) { } impl OutputNode { + pub async fn before_latch(&self, present: u64) { + let mut res = BeforeLatchResult::None; + for listener in self.before_latch_event.iter() { + res |= listener.before_latch(present); + } + if res == BeforeLatchResult::Yield { + self.state.eng.yield_now().await; + } + } + pub fn latched(&self, tearing: bool) { self.schedule.latched(); for listener in self.latch_event.iter() { @@ -703,6 +733,7 @@ impl OutputNode { } let (old_width, old_height) = self.global.pixel_size(); self.global.mode.set(mode); + self.global.refresh_nsec.set(mode.refresh_nsec()); self.global.persistent.transform.set(transform); let (new_width, new_height) = self.global.pixel_size(); self.change_extents_(&self.calculate_extents()); diff --git a/wire/wp_commit_timer_v1.txt b/wire/wp_commit_timer_v1.txt new file mode 100644 index 00000000..3a98bee6 --- /dev/null +++ b/wire/wp_commit_timer_v1.txt @@ -0,0 +1,9 @@ +request set_timestamp { + tv_sec_hi: u32, + tv_sec_lo: u32, + tv_nsec: u32, +} + +request destroy { + +} diff --git a/wire/wp_commit_timing_manager_v1.txt b/wire/wp_commit_timing_manager_v1.txt new file mode 100644 index 00000000..c20860b8 --- /dev/null +++ b/wire/wp_commit_timing_manager_v1.txt @@ -0,0 +1,8 @@ +request destroy { + +} + +request get_timer { + id: id(wp_commit_timer_v1), + surface: id(wl_surface), +}