From 42ffd4ee6931134cf83c090c9818e7fe0bc5a974 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 7 Mar 2024 18:49:46 -0800 Subject: [PATCH] WIP linux-drm-syncobj-v1 Store drm device in `DrmTimeline`; destroy timeline on drop WIP OUT_FENCE_FD; always signalled in fence DRM git rev Remove patch for drm remove OUT_FENCE_PTR remove comment release WIP fix destroy syncobj feature checks clone_from WIP --- Cargo.toml | 19 + anvil/src/shell/mod.rs | 22 ++ anvil/src/state.rs | 35 +- anvil/src/udev.rs | 8 +- src/backend/drm/device/fd.rs | 4 +- src/backend/renderer/utils/wayland.rs | 42 ++- src/wayland/compositor/tree.rs | 24 ++ src/wayland/drm_syncobj/mod.rs | 345 +++++++++++++++++++ src/wayland/drm_syncobj/sync_point.rs | 141 ++++++++ src/wayland/mod.rs | 2 + src/wayland/shell/xdg/handlers/positioner.rs | 2 +- 11 files changed, 621 insertions(+), 23 deletions(-) create mode 100644 src/wayland/drm_syncobj/mod.rs create mode 100644 src/wayland/drm_syncobj/sync_point.rs diff --git a/Cargo.toml b/Cargo.toml index 4f53f42f0c5f..c40499c8eb1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,25 @@ profiling = "1.0" smallvec = "1.11" pixman = { version = "0.1.0", features = ["drm-fourcc"], optional = true } +[patch.crates-io] +wayland-egl = { git = "https://github.com/smithay/wayland-rs" } +wayland-protocols = { git = "https://github.com/smithay/wayland-rs" } +wayland-protocols-wlr = { git = "https://github.com/smithay/wayland-rs" } +wayland-protocols-misc = { git = "https://github.com/smithay/wayland-rs" } +wayland-server = { git = "https://github.com/smithay/wayland-rs" } +wayland-client = { git = "https://github.com/smithay/wayland-rs" } +wayland-sys = { git = "https://github.com/smithay/wayland-rs" } +wayland-backend = { git = "https://github.com/smithay/wayland-rs" } +wayland-scanner = { git = "https://github.com/smithay/wayland-rs" } +# wayland-egl = { path = "../wayland-rs/wayland-egl/" } +# wayland-protocols = { path = "../wayland-rs/wayland-protocols/" } +# wayland-protocols-wlr = { path = "../wayland-rs/wayland-protocols-wlr/" } +# wayland-protocols-misc = { path = "../wayland-rs/wayland-protocols-misc/" } +# wayland-server = { path = "../wayland-rs/wayland-server/" } +# wayland-client = { path = "../wayland-rs/wayland-client/" } +# wayland-sys = { path = "../wayland-rs/wayland-sys/" } +# wayland-backend = { path = "../wayland-rs/wayland-backend/" } +# wayland-scanner = { path = "../wayland-rs/wayland-scanner/" } [dev-dependencies] clap = { version = "4", features = ["derive"] } diff --git a/anvil/src/shell/mod.rs b/anvil/src/shell/mod.rs index 072f3c699a33..86585c729245 100644 --- a/anvil/src/shell/mod.rs +++ b/anvil/src/shell/mod.rs @@ -26,6 +26,7 @@ use smithay::{ CompositorState, SurfaceAttributes, TraversalAction, }, dmabuf::get_dmabuf, + drm_syncobj::DrmSyncobjCachedState, shell::{ wlr_layer::{ Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData, WlrLayerShellHandler, @@ -112,7 +113,14 @@ impl CompositorHandler for AnvilState { fn new_surface(&mut self, surface: &WlSurface) { add_pre_commit_hook::(surface, move |state, _dh, surface| { + let mut acquire_point = None; let maybe_dmabuf = with_states(surface, |surface_data| { + acquire_point.clone_from( + &surface_data + .cached_state + .pending::() + .acquire_point, + ); surface_data .cached_state .pending::() @@ -124,6 +132,20 @@ impl CompositorHandler for AnvilState { }) }); if let Some(dmabuf) = maybe_dmabuf { + 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/state.rs b/anvil/src/state.rs index 444b9cdda5d1..0391386bd5be 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -1,4 +1,5 @@ use std::{ + any::Any, os::unix::io::OwnedFd, sync::{atomic::AtomicBool, Arc}, time::Duration, @@ -13,12 +14,13 @@ use smithay::{ default_primary_scanout_output_compare, utils::select_dmabuf_feedback, RenderElementStates, }, }, - delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale, - delegate_input_method_manager, delegate_keyboard_shortcuts_inhibit, delegate_layer_shell, - delegate_output, delegate_pointer_constraints, delegate_pointer_gestures, delegate_presentation, - delegate_primary_selection, delegate_relative_pointer, delegate_seat, delegate_security_context, - delegate_shm, delegate_tablet_manager, delegate_text_input_manager, delegate_viewporter, - delegate_virtual_keyboard_manager, delegate_xdg_activation, delegate_xdg_decoration, delegate_xdg_shell, + delegate_compositor, delegate_data_control, delegate_data_device, delegate_drm_syncobj, + delegate_fractional_scale, delegate_input_method_manager, delegate_keyboard_shortcuts_inhibit, + delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_pointer_gestures, + delegate_presentation, delegate_primary_selection, delegate_relative_pointer, delegate_seat, + delegate_security_context, delegate_shm, delegate_tablet_manager, delegate_text_input_manager, + delegate_viewporter, delegate_virtual_keyboard_manager, delegate_xdg_activation, delegate_xdg_decoration, + delegate_xdg_shell, desktop::{ space::SpaceElement, utils::{ @@ -48,6 +50,7 @@ use smithay::{ wayland::{ compositor::{get_parent, with_states, CompositorClientState, CompositorState}, dmabuf::DmabufFeedback, + drm_syncobj::{DrmSyncobjHandler, DrmSyncobjState}, fractional_scale::{with_fractional_scale, FractionalScaleHandler, FractionalScaleManagerState}, input_method::{InputMethodHandler, InputMethodManagerState, PopupSurface}, keyboard_shortcuts_inhibit::{ @@ -535,6 +538,22 @@ impl XdgForeignHandler for AnvilState { } smithay::delegate_xdg_foreign!(@ AnvilState); +impl DrmSyncobjHandler for AnvilState { + fn import_device(&self) -> &smithay::backend::drm::DrmDeviceFd { + let udev_data = &::downcast_ref::>(self) + .expect("syncobj protocol used on backend other than DRM") + .backend_data; + udev_data.backends[&udev_data + .primary_gpu + .node_with_type(smithay::backend::drm::NodeType::Primary) + .unwrap() + .unwrap()] + .drm + .device_fd() + } +} +delegate_drm_syncobj!(@ AnvilState); + impl AnvilState { pub fn init( display: Display>, @@ -613,6 +632,10 @@ impl AnvilState { .get_data::() .map_or(true, |client_state| client_state.security_context.is_none()) }); + if ::downcast_ref::>(&backend_data).is_some() { + // TODO only expose if main device supports drm_syncobj_eventfd? + DrmSyncobjState::new::(&dh); + } // init input let seat_name = backend_data.seat_name(); diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index d6499741cc02..9e11bcba80ee 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -123,9 +123,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")] @@ -727,13 +727,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, 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..28de7037c8f0 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,22 @@ pub struct RendererSurfaceState { } #[derive(Debug)] -struct InnerBuffer(WlBuffer); +struct InnerBuffer { + buffer: WlBuffer, + #[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,11 +77,14 @@ pub struct Buffer { inner: Arc, } -impl From for Buffer { - #[inline] - fn from(buffer: WlBuffer) -> Self { +impl Buffer { + fn new(buffer: WlBuffer, #[cfg(feature = "backend_drm")] release_point: Option) -> Self { Buffer { - inner: Arc::new(InnerBuffer(buffer)), + inner: Arc::new(InnerBuffer { + buffer, + #[cfg(feature = "backend_drm")] + release_point, + }), } } } @@ -79,27 +94,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.pending::(); + let mut attrs = states.cached_state.current::(); self.buffer_delta = attrs.buffer_delta.take(); @@ -122,7 +140,11 @@ 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::new( + buffer, + #[cfg(feature = "backend_drm")] + 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..e436c9d71be5 --- /dev/null +++ b/src/wayland/drm_syncobj/mod.rs @@ -0,0 +1,345 @@ +//! 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}; +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; + } else { + into.acquire_point = None; + into.release_point = None; + } + } +} + +/// 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 has_new_buffer = matches!( + cached.pending::().buffer, + Some(BufferAssignment::NewBuffer(_)) + ); + // TODO what if syncobj surface is destroyed? + 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() && !has_new_buffer { + 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 + ), + ); + } + } + // TODO unsupported buffer error + } + } + + /* + // XXX wrong place to release + let current = cached.pending::(); + if let Some(release_point) = ¤t.release_point { + if let Err(err) = release_point.signal() { + tracing::error!("Failed to signal syncobj release point: {}", err); + } + } + */ + }); +} + +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 => { + // TODO + } + 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..0689589dbcd9 --- /dev/null +++ b/src/wayland/drm_syncobj/sync_point.rs @@ -0,0 +1,141 @@ +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::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)?, + }))) + } +} + +/// Point on a DRM timeline syncobj +#[derive(Clone, Debug)] +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, + )?; + // TODO wait_avialable? + self.timeline + .0 + .device + .syncobj_eventfd(self.timeline.0.syncobj, self.point, fd.as_fd(), false)?; + Ok(fd) + } + + /// Signal the syncpoint + pub fn signal(&self) -> io::Result<()> { + self.timeline + .0 + .device + .syncobj_timeline_signal(&[self.timeline.0.syncobj], &[self.point]) + } + + /// 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)) + } +} + +#[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 6ea2a51ba08e..900b84297a2a 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -52,6 +52,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; diff --git a/src/wayland/shell/xdg/handlers/positioner.rs b/src/wayland/shell/xdg/handlers/positioner.rs index c054e377bf0c..4ceda29411a2 100644 --- a/src/wayland/shell/xdg/handlers/positioner.rs +++ b/src/wayland/shell/xdg/handlers/positioner.rs @@ -69,7 +69,7 @@ where constraint_adjustment, } => { let constraint_adjustment = - xdg_positioner::ConstraintAdjustment::from_bits_truncate(constraint_adjustment); + xdg_positioner::ConstraintAdjustment::from_bits_truncate(constraint_adjustment.into()); state.constraint_adjustment = constraint_adjustment; } xdg_positioner::Request::SetOffset { x, y } => {