From df603fff084faeedd718161ce6dde00b220f129f Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 30 May 2024 21:10:44 -0700 Subject: [PATCH] WIP linux-drm-syncobj-v1 --- anvil/src/shell/mod.rs | 26 ++ anvil/src/udev.rs | 26 +- src/backend/drm/compositor/mod.rs | 11 +- src/backend/drm/device/fd.rs | 4 +- src/backend/renderer/utils/wayland.rs | 48 +++- src/wayland/compositor/tree.rs | 24 ++ src/wayland/drm_syncobj/mod.rs | 340 ++++++++++++++++++++++++++ src/wayland/drm_syncobj/sync_point.rs | 198 +++++++++++++++ src/wayland/mod.rs | 2 + 9 files changed, 660 insertions(+), 19 deletions(-) create mode 100644 src/wayland/drm_syncobj/mod.rs create mode 100644 src/wayland/drm_syncobj/sync_point.rs diff --git a/anvil/src/shell/mod.rs b/anvil/src/shell/mod.rs index 072f3c699a33..3f60760dc434 100644 --- a/anvil/src/shell/mod.rs +++ b/anvil/src/shell/mod.rs @@ -3,6 +3,8 @@ use std::cell::RefCell; #[cfg(feature = "xwayland")] use smithay::xwayland::{X11Wm, XWaylandClientData}; +#[cfg(feature = "udev")] +use smithay::wayland::drm_syncobj::DrmSyncobjCachedState; use smithay::{ backend::renderer::utils::on_commit_buffer_handler, desktop::{ @@ -112,7 +114,16 @@ impl CompositorHandler for AnvilState { fn new_surface(&mut self, surface: &WlSurface) { add_pre_commit_hook::(surface, move |state, _dh, surface| { + #[cfg(feature = "udev")] + let mut acquire_point = None; let maybe_dmabuf = with_states(surface, |surface_data| { + #[cfg(feature = "udev")] + acquire_point.clone_from( + &surface_data + .cached_state + .pending::() + .acquire_point, + ); surface_data .cached_state .pending::() @@ -124,6 +135,21 @@ impl CompositorHandler for AnvilState { }) }); if let Some(dmabuf) = maybe_dmabuf { + #[cfg(feature = "udev")] + if let Some(acquire_point) = acquire_point { + if let Ok((blocker, source)) = acquire_point.generate_blocker() { + let client = surface.client().unwrap(); + let res = state.handle.insert_source(source, move |_, _, data| { + let dh = data.display_handle.clone(); + data.client_compositor_state(&client).blocker_cleared(data, &dh); + Ok(()) + }); + if res.is_ok() { + add_blocker(surface, blocker); + return; + } + } + } if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) { if let Some(client) = surface.client() { let res = state.handle.insert_source(source, move |_, _, data| { diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index d6499741cc02..df3f609f4c7b 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -84,6 +84,7 @@ use smithay::{ drm_lease::{ DrmLease, DrmLeaseBuilder, DrmLeaseHandler, DrmLeaseRequest, DrmLeaseState, LeaseRejected, }, + drm_syncobj::{DrmSyncobjHandler, DrmSyncobjState}, }, }; use smithay_drm_extras::{ @@ -123,9 +124,9 @@ pub struct UdevData { pub session: LibSeatSession, dh: DisplayHandle, dmabuf_state: Option<(DmabufState, DmabufGlobal)>, - primary_gpu: DrmNode, + pub primary_gpu: DrmNode, gpus: GpuManager>, - backends: HashMap, + pub backends: HashMap, pointer_images: Vec<(xcursor::parser::Image, MemoryRenderBuffer)>, pointer_element: PointerElement, #[cfg(feature = "debug")] @@ -537,6 +538,20 @@ impl DrmLeaseHandler for AnvilState { delegate_drm_lease!(AnvilState); +impl DrmSyncobjHandler for AnvilState { + fn import_device(&self) -> &smithay::backend::drm::DrmDeviceFd { + self.backend_data.backends[&self + .backend_data + .primary_gpu + .node_with_type(smithay::backend::drm::NodeType::Primary) + .unwrap() + .unwrap()] + .drm + .device_fd() + } +} +smithay::delegate_drm_syncobj!(AnvilState); + pub type RenderSurface = GbmBufferedSurface, Option>; pub type GbmDrmCompositor = DrmCompositor< @@ -727,13 +742,13 @@ impl Drop for SurfaceData { } } -struct BackendData { +pub struct BackendData { surfaces: HashMap, non_desktop_connectors: Vec<(connector::Handle, crtc::Handle)>, leasing_global: Option, active_leases: Vec, gbm: GbmDevice, - drm: DrmDevice, + pub drm: DrmDevice, drm_scanner: DrmScanner, render_node: DrmNode, registration_token: RegistrationToken, @@ -860,6 +875,9 @@ impl AnvilState { .add_node(render_node, gbm.clone()) .map_err(DeviceAddError::AddNode)?; + // TODO only expose if main device supports drm_syncobj_eventfd? + DrmSyncobjState::new::(&self.display_handle); + self.backend_data.backends.insert( node, BackendData { diff --git a/src/backend/drm/compositor/mod.rs b/src/backend/drm/compositor/mod.rs index af146cd3b582..04948adb81ed 100644 --- a/src/backend/drm/compositor/mod.rs +++ b/src/backend/drm/compositor/mod.rs @@ -203,6 +203,15 @@ enum ScanoutBuffer { Cursor(GbmBuffer), } +impl ScanoutBuffer { + fn acquire_point(&self) -> Option { + if let Self::Wayland(buffer) = self { + return buffer.acquire_point().cloned().map(SyncPoint::from); + } + None + } +} + impl ScanoutBuffer { #[inline] fn from_underlying_storage(storage: UnderlyingStorage<'_>) -> Option { @@ -3947,7 +3956,7 @@ where buffer: element_config.buffer.clone(), damage_clips, plane_claim, - sync: None, + sync: element_config.buffer.buffer.acquire_point().map(|p| (p, None)), }; let is_compatible = previous_state diff --git a/src/backend/drm/device/fd.rs b/src/backend/drm/device/fd.rs index f59c828d1bbe..e01548a1d9ee 100644 --- a/src/backend/drm/device/fd.rs +++ b/src/backend/drm/device/fd.rs @@ -7,7 +7,7 @@ use tracing::{error, info, warn}; use crate::utils::{DevPath, DeviceFd}; -#[derive(Debug)] +#[derive(Debug, PartialEq)] struct InternalDrmDeviceFd { fd: DeviceFd, privileged: bool, @@ -33,7 +33,7 @@ impl BasicDevice for InternalDrmDeviceFd {} impl ControlDevice for InternalDrmDeviceFd {} /// Ref-counted file descriptor of an open drm device -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct DrmDeviceFd(Arc); impl AsFd for DrmDeviceFd { diff --git a/src/backend/renderer/utils/wayland.rs b/src/backend/renderer/utils/wayland.rs index 112ed706e701..f6aa70f9e175 100644 --- a/src/backend/renderer/utils/wayland.rs +++ b/src/backend/renderer/utils/wayland.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "backend_drm")] +use crate::wayland::drm_syncobj::{DrmSyncPoint, DrmSyncobjCachedState}; use crate::{ backend::renderer::{buffer_dimensions, buffer_has_alpha, element::RenderElement, ImportAll, Renderer}, utils::{Buffer as BufferCoord, Coordinate, Logical, Physical, Point, Rectangle, Scale, Size, Transform}, @@ -50,12 +52,24 @@ pub struct RendererSurfaceState { } #[derive(Debug)] -struct InnerBuffer(WlBuffer); +struct InnerBuffer { + buffer: WlBuffer, + #[cfg(feature = "backend_drm")] + acquire_point: Option, + #[cfg(feature = "backend_drm")] + release_point: Option, +} impl Drop for InnerBuffer { #[inline] fn drop(&mut self) { - self.0.release(); + self.buffer.release(); + #[cfg(feature = "backend_drm")] + if let Some(release_point) = &self.release_point { + if let Err(err) = release_point.signal() { + tracing::error!("Failed to signal syncobj release point: {}", err); + } + } } } @@ -65,12 +79,11 @@ pub struct Buffer { inner: Arc, } -impl From for Buffer { - #[inline] - fn from(buffer: WlBuffer) -> Self { - Buffer { - inner: Arc::new(InnerBuffer(buffer)), - } +impl Buffer { + #[cfg(feature = "backend_drm")] + #[allow(dead_code)] + pub(crate) fn acquire_point(&self) -> Option<&DrmSyncPoint> { + self.inner.acquire_point.as_ref() } } @@ -79,27 +92,30 @@ impl std::ops::Deref for Buffer { #[inline] fn deref(&self) -> &Self::Target { - &self.inner.0 + &self.inner.buffer } } impl PartialEq for Buffer { #[inline] fn eq(&self, other: &WlBuffer) -> bool { - self.inner.0 == *other + self.inner.buffer == *other } } impl PartialEq for &Buffer { #[inline] fn eq(&self, other: &WlBuffer) -> bool { - self.inner.0 == *other + self.inner.buffer == *other } } impl RendererSurfaceState { #[profiling::function] pub(crate) fn update_buffer(&mut self, states: &SurfaceData) { + #[cfg(feature = "backend_drm")] + let mut syncobj_state = states.cached_state.current::(); + let mut attrs = states.cached_state.current::(); self.buffer_delta = attrs.buffer_delta.take(); @@ -122,7 +138,15 @@ impl RendererSurfaceState { self.buffer_transform = attrs.buffer_transform.into(); if !self.buffer.as_ref().map_or(false, |b| b == buffer) { - self.buffer = Some(Buffer::from(buffer)); + self.buffer = Some(Buffer { + inner: Arc::new(InnerBuffer { + buffer, + #[cfg(feature = "backend_drm")] + acquire_point: syncobj_state.acquire_point.take(), + #[cfg(feature = "backend_drm")] + release_point: syncobj_state.release_point.take(), + }), + }); } self.textures.clear(); diff --git a/src/wayland/compositor/tree.rs b/src/wayland/compositor/tree.rs index 43687fb5c9b0..6c9300921b1b 100644 --- a/src/wayland/compositor/tree.rs +++ b/src/wayland/compositor/tree.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "backend_drm")] +use crate::wayland::drm_syncobj::DrmSyncobjCachedState; use crate::{utils::Serial, wayland::compositor::SUBSURFACE_ROLE}; use super::{ @@ -156,6 +158,28 @@ impl PrivateSurfaceData { { buffer.release(); }; + #[cfg(feature = "backend_drm")] + if let Some(release_point) = &my_data + .public_data + .cached_state + .pending::() + .release_point + { + if let Err(err) = release_point.signal() { + tracing::error!("Failed to signal syncobj release point: {}", err); + } + } + #[cfg(feature = "backend_drm")] + if let Some(release_point) = &my_data + .public_data + .cached_state + .current::() + .release_point + { + if let Err(err) = release_point.signal() { + tracing::error!("Failed to signal syncobj release point: {}", err); + } + } let hooks = my_data.destruction_hooks.clone(); // don't hold the mutex while the hooks are invoked diff --git a/src/wayland/drm_syncobj/mod.rs b/src/wayland/drm_syncobj/mod.rs new file mode 100644 index 000000000000..4e268b316135 --- /dev/null +++ b/src/wayland/drm_syncobj/mod.rs @@ -0,0 +1,340 @@ +//! DRM syncobj protocol + +use std::{cell::RefCell, os::unix::io::AsFd}; +use wayland_protocols::wp::linux_drm_syncobj::v1::server::{ + wp_linux_drm_syncobj_manager_v1::{self, WpLinuxDrmSyncobjManagerV1}, + wp_linux_drm_syncobj_surface_v1::{self, WpLinuxDrmSyncobjSurfaceV1}, + wp_linux_drm_syncobj_timeline_v1::{self, WpLinuxDrmSyncobjTimelineV1}, +}; +use wayland_server::{ + protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, + Resource, Weak, +}; + +use super::{ + compositor::{self, with_states, BufferAssignment, Cacheable, SurfaceAttributes}, + dmabuf::get_dmabuf, +}; +use crate::backend::drm::DrmDeviceFd; + +mod sync_point; +pub use sync_point::{DrmSyncPoint, DrmTimeline}; + +/// Handler trait for DRM syncobj protocol. +pub trait DrmSyncobjHandler { + // TODO better way to deal with this? + /// DRM device for importing syncobj file descriptors + fn import_device(&self) -> &DrmDeviceFd; +} + +/// Data associated with a drm syncobj global +#[allow(missing_debug_implementations)] +pub struct DrmSyncobjGlobalData { + filter: Box Fn(&'c Client) -> bool + Send + Sync>, +} + +/// Pending DRM syncobj sync point state +#[derive(Debug, Default)] +pub struct DrmSyncobjCachedState { + /// Timeline point signaled when buffer is ready to read + pub acquire_point: Option, + /// Timeline point to be signaled when server is done with buffer + pub release_point: Option, +} + +impl Cacheable for DrmSyncobjCachedState { + fn commit(&mut self, _dh: &DisplayHandle) -> Self { + Self { + acquire_point: self.acquire_point.take(), + release_point: self.release_point.take(), + } + } + + fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) { + if self.acquire_point.is_some() && self.release_point.is_some() { + if let Some(release_point) = &into.release_point { + if let Err(err) = release_point.signal() { + tracing::error!("Failed to signal syncobj release point: {}", err); + } + } + into.acquire_point = self.acquire_point; + into.release_point = self.release_point; + } + } +} + +/// Delegate type for a `wp_linux_drm_syncobj_manager_v1` global +#[derive(Debug)] +pub struct DrmSyncobjState {} + +impl DrmSyncobjState { + /// Create a new `wp_linux_drm_syncobj_manager_v1` global + pub fn new(display: &DisplayHandle) -> Self + where + D: GlobalDispatch, + D: 'static, + { + Self::new_with_filter::(display, |_| true) + } + + /// Create a new `wp_linuxdrm_syncobj_manager_v1` global with a client filter + pub fn new_with_filter(display: &DisplayHandle, filter: F) -> Self + where + D: GlobalDispatch, + D: 'static, + F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static, + { + let _global = display.create_global::( + 1, + DrmSyncobjGlobalData { + filter: Box::new(filter), + }, + ); + + Self {} + } +} + +impl GlobalDispatch for DrmSyncobjState +where + D: Dispatch, +{ + fn bind( + _state: &mut D, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &DrmSyncobjGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init::<_, _>(resource, ()); + } + + fn can_view(client: Client, global_data: &DrmSyncobjGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +fn commit_hook(_data: &mut D, _dh: &DisplayHandle, surface: &WlSurface) { + compositor::with_states(surface, |states| { + let cached = &states.cached_state; + let pending = cached.pending::(); + let new_buffer = pending.buffer.as_ref().and_then(|buffer| match buffer { + BufferAssignment::NewBuffer(buffer) => Some(buffer), + _ => None, + }); + if let Some(data) = states + .data_map + .get::>>() + { + if let Some(syncobj_surface) = data.borrow().as_ref() { + let pending = cached.pending::(); + if pending.acquire_point.is_some() && new_buffer.is_none() { + syncobj_surface.post_error( + wp_linux_drm_syncobj_surface_v1::Error::NoBuffer as u32, + "acquire point without buffer".to_string(), + ); + } else if pending.acquire_point.is_some() && pending.release_point.is_none() { + syncobj_surface.post_error( + wp_linux_drm_syncobj_surface_v1::Error::NoReleasePoint as u32, + "acquire point without release point".to_string(), + ); + } else if pending.acquire_point.is_none() && pending.release_point.is_some() { + syncobj_surface.post_error( + wp_linux_drm_syncobj_surface_v1::Error::NoAcquirePoint as u32, + "release point without acquire point".to_string(), + ); + } else if let (Some(acquire), Some(release)) = + (pending.acquire_point.as_ref(), pending.release_point.as_ref()) + { + if acquire.timeline == release.timeline && acquire.point <= release.point { + syncobj_surface.post_error( + wp_linux_drm_syncobj_surface_v1::Error::ConflictingPoints as u32, + format!( + "release point '{}' is not greater than acquire point '{}'", + release.point, acquire.point + ), + ); + } + if let Some(buffer) = new_buffer { + if get_dmabuf(buffer).is_err() { + syncobj_surface.post_error( + wp_linux_drm_syncobj_surface_v1::Error::UnsupportedBuffer as u32, + "sync points with non-dmabuf buffer".to_string(), + ); + } + } + } + } + } + }); +} + +impl Dispatch for DrmSyncobjState +where + D: Dispatch, + D: Dispatch, + D: DrmSyncobjHandler, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &WpLinuxDrmSyncobjManagerV1, + request: wp_linux_drm_syncobj_manager_v1::Request, + _data: &(), + _dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + wp_linux_drm_syncobj_manager_v1::Request::GetSurface { id, surface } => { + let already_exists = with_states(&surface, |states| { + states + .data_map + .get::>>() + .map(|v| v.borrow().is_some()) + .unwrap_or(false) + }); + if already_exists { + resource.post_error( + wp_linux_drm_syncobj_manager_v1::Error::SurfaceExists as u32, + "the surface already has a syncobj_surface object associated".to_string(), + ); + return; + } + let syncobj_surface = data_init.init::<_, _>( + id, + DrmSyncobjSurfaceData { + surface: surface.downgrade(), + }, + ); + with_states(&surface, |states| { + states + .data_map + .insert_if_missing(|| RefCell::new(Some(syncobj_surface))) + }); + compositor::add_pre_commit_hook::(&surface, commit_hook); + } + wp_linux_drm_syncobj_manager_v1::Request::ImportTimeline { id, fd } => { + match DrmTimeline::new(state.import_device(), fd.as_fd()) { + Ok(timeline) => { + data_init.init::<_, _>(id, DrmSyncobjTimelineData { timeline }); + } + Err(err) => { + resource.post_error( + wp_linux_drm_syncobj_manager_v1::Error::InvalidTimeline as u32, + format!("failed to import syncobj timeline: {}", err), + ); + } + } + } + wp_linux_drm_syncobj_manager_v1::Request::Destroy => {} + _ => unreachable!(), + } + } +} + +/// Data attached to wp_linux_drm_syncobj_surface_v1 objects +#[derive(Debug)] +pub struct DrmSyncobjSurfaceData { + surface: Weak, +} + +impl Dispatch for DrmSyncobjState { + fn request( + _state: &mut D, + _client: &Client, + _resource: &WpLinuxDrmSyncobjSurfaceV1, + request: wp_linux_drm_syncobj_surface_v1::Request, + data: &DrmSyncobjSurfaceData, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + let Ok(surface) = data.surface.upgrade() else { + return; + }; + match request { + wp_linux_drm_syncobj_surface_v1::Request::Destroy => {} + wp_linux_drm_syncobj_surface_v1::Request::SetAcquirePoint { + timeline, + point_hi, + point_lo, + } => { + let sync_point = DrmSyncPoint { + timeline: timeline + .data::() + .unwrap() + .timeline + .clone(), + point: ((point_hi as u64) << 32) + (point_lo as u64), + }; + with_states(&surface, |states| { + let mut cached_state = states.cached_state.pending::(); + cached_state.acquire_point = Some(sync_point); + }); + } + wp_linux_drm_syncobj_surface_v1::Request::SetReleasePoint { + timeline, + point_hi, + point_lo, + } => { + let sync_point = DrmSyncPoint { + timeline: timeline + .data::() + .unwrap() + .timeline + .clone(), + point: ((point_hi as u64) << 32) + (point_lo as u64), + }; + with_states(&surface, |states| { + let mut cached_state = states.cached_state.pending::(); + cached_state.release_point = Some(sync_point); + }); + } + _ => unreachable!(), + } + } +} + +/// Data attached to wp_linux_drm_syncobj_timeline_v1 objects +#[derive(Debug)] +pub struct DrmSyncobjTimelineData { + timeline: DrmTimeline, +} + +impl Dispatch for DrmSyncobjState { + fn request( + _state: &mut D, + _client: &Client, + _resource: &WpLinuxDrmSyncobjTimelineV1, + request: wp_linux_drm_syncobj_timeline_v1::Request, + _data: &DrmSyncobjTimelineData, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + wp_linux_drm_syncobj_timeline_v1::Request::Destroy => {} + _ => unreachable!(), + } + } +} + +/// Macro to delegate implementation of the drm syncobj protocol to [`DrmSyncobjState`]. +/// +/// You must also implement [`DrmSyncobjHandler`] to use this. +#[macro_export] +macro_rules! delegate_drm_syncobj { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::wp::linux_drm_syncobj::v1::server::wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1: $crate::wayland::drm_syncobj::DrmSyncobjGlobalData + ] => $crate::wayland::drm_syncobj::DrmSyncobjState); + $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::wp::linux_drm_syncobj::v1::server::wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1: () + ] => $crate::wayland::drm_syncobj::DrmSyncobjState); + $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::wp::linux_drm_syncobj::v1::server::wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1: $crate::wayland::drm_syncobj::DrmSyncobjSurfaceData + ] => $crate::wayland::drm_syncobj::DrmSyncobjState); + $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::wp::linux_drm_syncobj::v1::server::wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1: $crate::wayland::drm_syncobj::DrmSyncobjTimelineData + ] => $crate::wayland::drm_syncobj::DrmSyncobjState); + } +} diff --git a/src/wayland/drm_syncobj/sync_point.rs b/src/wayland/drm_syncobj/sync_point.rs new file mode 100644 index 000000000000..bda009ceae7a --- /dev/null +++ b/src/wayland/drm_syncobj/sync_point.rs @@ -0,0 +1,198 @@ +use calloop::generic::Generic; +use calloop::{EventSource, Interest, Mode, Poll, PostAction, Readiness, Token, TokenFactory}; +use drm::control::Device; +use std::{ + io, + os::unix::io::{AsFd, BorrowedFd, OwnedFd}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +use crate::backend::drm::DrmDeviceFd; +use crate::backend::renderer::sync::{Fence, Interrupted}; +use crate::wayland::compositor::{Blocker, BlockerState}; + +#[derive(Debug, PartialEq)] +struct DrmTimelineInner { + device: DrmDeviceFd, + syncobj: drm::control::syncobj::Handle, +} + +impl Drop for DrmTimelineInner { + fn drop(&mut self) { + let _ = self.device.destroy_syncobj(self.syncobj); + } +} + +/// DRM timeline syncobj +#[derive(Clone, Debug, PartialEq)] +pub struct DrmTimeline(Arc); + +impl DrmTimeline { + /// Import DRM timeline from file descriptor + pub fn new(device: &DrmDeviceFd, fd: BorrowedFd<'_>) -> io::Result { + Ok(Self(Arc::new(DrmTimelineInner { + device: device.clone(), + syncobj: device.fd_to_syncobj(fd, false)?, + }))) + } + + /// Query the last signalled timeline point + pub fn query_signalled_point(&self) -> io::Result { + let mut points = [0]; + self.0 + .device + .syncobj_timeline_query(&[self.0.syncobj], &mut points, false)?; + Ok(points[0]) + } +} + +/// Point on a DRM timeline syncobj +#[derive(Clone, Debug, PartialEq)] +pub struct DrmSyncPoint { + pub(super) timeline: DrmTimeline, + pub(super) point: u64, +} + +impl DrmSyncPoint { + /// Create an eventfd that will be signaled by the syncpoint + pub fn eventfd(&self) -> io::Result { + let fd = rustix::event::eventfd( + 0, + rustix::event::EventfdFlags::CLOEXEC | rustix::event::EventfdFlags::NONBLOCK, + )?; + self.timeline + .0 + .device + .syncobj_eventfd(self.timeline.0.syncobj, self.point, fd.as_fd(), false)?; + Ok(fd) + } + + /// Signal the sync point. + pub fn signal(&self) -> io::Result<()> { + self.timeline + .0 + .device + .syncobj_timeline_signal(&[self.timeline.0.syncobj], &[self.point]) + } + + /// Wait for sync point. + pub fn wait(&self, timeout_nsec: i64) -> io::Result<()> { + self.timeline.0.device.syncobj_timeline_wait( + &[self.timeline.0.syncobj], + &[self.point], + timeout_nsec, + false, + false, + false, + )?; + Ok(()) + } + + /// Export DRM sync file for sync point. + pub fn export_sync_file(&self) -> io::Result { + let syncobj = self.timeline.0.device.create_syncobj(false)?; + // Wrap in `DrmTimelineInner` to destroy on drop + let syncobj = DrmTimelineInner { + device: self.timeline.0.device.clone(), + syncobj, + }; + syncobj + .device + .syncobj_timeline_transfer(self.timeline.0.syncobj, syncobj.syncobj, self.point, 0)?; + syncobj.device.syncobj_to_fd(syncobj.syncobj, true) + } + + /// Create an [`calloop::EventSource`] and [`crate::wayland::compositor::Blocker`] for this sync point. + pub fn generate_blocker(&self) -> io::Result<(DrmSyncPointBlocker, DrmSyncPointSource)> { + let fd = self.eventfd()?; + let signal = Arc::new(AtomicBool::new(false)); + let blocker = DrmSyncPointBlocker { + signal: signal.clone(), + }; + let source = DrmSyncPointSource { + source: Generic::new(fd, Interest::READ, Mode::Level), + signal, + }; + Ok((blocker, source)) + } +} + +impl Fence for DrmSyncPoint { + fn is_signaled(&self) -> bool { + self.timeline + .query_signalled_point() + .ok() + .map_or(false, |point| point >= self.point) + } + + fn wait(&self) -> Result<(), Interrupted> { + self.wait(i64::MAX).map_err(|_| Interrupted) + } + + fn is_exportable(&self) -> bool { + true + } + + fn export(&self) -> Option { + self.export_sync_file().ok() + } +} + +#[derive(Debug)] +pub struct DrmSyncPointSource { + source: Generic, + signal: Arc, +} + +impl EventSource for DrmSyncPointSource { + type Event = (); + type Metadata = (); + type Ret = Result<(), std::io::Error>; + type Error = io::Error; + + fn process_events( + &mut self, + readiness: Readiness, + token: Token, + mut callback: C, + ) -> Result + where + C: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, + { + self.signal.store(true, Ordering::SeqCst); + self.source + .process_events(readiness, token, |_, _| Ok(PostAction::Remove))?; + callback((), &mut ())?; + Ok(PostAction::Remove) + } + + fn register(&mut self, poll: &mut Poll, token_factory: &mut TokenFactory) -> calloop::Result<()> { + self.source.register(poll, token_factory) + } + + fn reregister(&mut self, poll: &mut Poll, token_factory: &mut TokenFactory) -> calloop::Result<()> { + self.source.reregister(poll, token_factory) + } + + fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> { + self.source.unregister(poll) + } +} + +#[derive(Debug)] +pub struct DrmSyncPointBlocker { + signal: Arc, +} + +impl Blocker for DrmSyncPointBlocker { + fn state(&self) -> BlockerState { + if self.signal.load(Ordering::SeqCst) { + BlockerState::Released + } else { + BlockerState::Pending + } + } +} diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 42c3b26dde8e..7463eefc7311 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -53,6 +53,8 @@ pub mod cursor_shape; pub mod dmabuf; #[cfg(feature = "backend_drm")] pub mod drm_lease; +#[cfg(feature = "backend_drm")] +pub mod drm_syncobj; pub mod fractional_scale; pub mod idle_inhibit; pub mod idle_notify;