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 { + +}