From 0d2baba9c8b9c66f72182c9345b43939085f9693 Mon Sep 17 00:00:00 2001 From: Colin Marc Date: Sun, 8 Dec 2024 17:55:13 +0100 Subject: [PATCH] feat: support wp_linux_drm_syncobj_timeline Also known as explicit sync. --- mm-server/src/compositor.rs | 8 + mm-server/src/compositor/buffers.rs | 27 ++- mm-server/src/compositor/dispatch.rs | 1 + .../dispatch/wp_linux_drm_syncobj.rs | 181 ++++++++++++++++++ mm-server/src/compositor/surface.rs | 47 ++++- mm-server/src/compositor/video.rs | 24 ++- mm-server/src/vulkan/timeline.rs | 21 +- 7 files changed, 299 insertions(+), 10 deletions(-) create mode 100644 mm-server/src/compositor/dispatch/wp_linux_drm_syncobj.rs diff --git a/mm-server/src/compositor.rs b/mm-server/src/compositor.rs index 50079a1..fbd70af 100644 --- a/mm-server/src/compositor.rs +++ b/mm-server/src/compositor.rs @@ -47,6 +47,7 @@ use wayland_protocols::{ wp::{ fractional_scale::v1::server::wp_fractional_scale_manager_v1, linux_dmabuf::zv1::server::zwp_linux_dmabuf_v1, + linux_drm_syncobj::v1::server::wp_linux_drm_syncobj_manager_v1, pointer_constraints::zv1::server::zwp_pointer_constraints_v1, presentation_time::server::{wp_presentation, wp_presentation_feedback}, relative_pointer::zv1::server::zwp_relative_pointer_manager_v1, @@ -111,6 +112,7 @@ pub struct State { buffers: SlotMap, shm_pools: SlotMap, cached_dmabuf_feedback: buffers::CachedDmabufFeedback, + imported_buffer_timelines: SlotMap, pending_presentation_feedback: Vec<( wp_presentation_feedback::WpPresentationFeedback, VkTimelinePoint, @@ -213,6 +215,7 @@ impl Compositor { create_global::(&dh, 1); create_global::(&dh, 5); create_global::(&dh, 1); + create_global::(&dh, 1); create_global::(&dh, 1); create_global::(&dh, 2); @@ -252,6 +255,7 @@ impl Compositor { buffers: SlotMap::default(), shm_pools: SlotMap::default(), cached_dmabuf_feedback, + imported_buffer_timelines: SlotMap::default(), pending_presentation_feedback: Vec::new(), surface_stack: Vec::new(), @@ -825,6 +829,10 @@ impl Compositor { let buffer = &mut self.state.buffers[content.buffer]; let sync = match &mut buffer.backing { + buffers::BufferBacking::Dmabuf { .. } if content.explicit_sync.is_some() => { + let (acquire, _) = content.explicit_sync.as_ref().unwrap(); + Some(video::TextureSync::TimelineAcquire(acquire.clone())) + } buffers::BufferBacking::Dmabuf { fd, interop_sema, diff --git a/mm-server/src/compositor/buffers.rs b/mm-server/src/compositor/buffers.rs index 1986a4b..f75ff0b 100644 --- a/mm-server/src/compositor/buffers.rs +++ b/mm-server/src/compositor/buffers.rs @@ -15,13 +15,14 @@ use drm_fourcc::DrmModifier; use hashbrown::HashSet; pub use modifiers::*; use tracing::trace; +use wayland_protocols::wp::linux_drm_syncobj::v1::server::wp_linux_drm_syncobj_timeline_v1; use wayland_server::{protocol::wl_buffer, Resource as _}; use crate::{ - compositor::shm::Pool, - compositor::State, + compositor::{shm::Pool, State}, vulkan::{ create_image_view, select_memory_type, VkContext, VkHostBuffer, VkImage, VkTimelinePoint, + VkTimelineSemaphore, }, }; @@ -38,6 +39,10 @@ pub struct Buffer { /// buffer. pub release_wait: Option, + /// If set, we should signal this timeline point when we're done with + /// the buffer (instead of using the normal wl_buffer.release signal). + pub release_signal: Option, + /// Next time we release this buffer, we should destroy it as well. pub needs_destruction: bool, } @@ -101,6 +106,13 @@ pub struct PlaneMetadata { pub offset: u32, } +slotmap::new_key_type! { pub struct BufferTimelineKey; } + +pub struct BufferTimeline { + pub _wp_syncobj_timeline: wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1, + pub sema: VkTimelineSemaphore, +} + impl State { pub fn release_buffers(&mut self) -> anyhow::Result<()> { let mut used_buffers = HashSet::new(); @@ -128,7 +140,14 @@ impl State { "releasing buffer" ); - buffer.wl_buffer.release(); + if let Some(tp) = &buffer.release_signal.take() { + unsafe { + tp.signal()?; + } + } else { + buffer.wl_buffer.release(); + } + buffer.needs_release = false; buffer.release_wait = None; if buffer.needs_destruction { @@ -194,6 +213,7 @@ pub fn import_shm_buffer( }, needs_release: false, release_wait: None, + release_signal: None, needs_destruction: false, }) } @@ -347,6 +367,7 @@ pub fn import_dmabuf_buffer( }, needs_release: false, release_wait: None, + release_signal: None, needs_destruction: false, }) } diff --git a/mm-server/src/compositor/dispatch.rs b/mm-server/src/compositor/dispatch.rs index fcbe442..8c31c5b 100644 --- a/mm-server/src/compositor/dispatch.rs +++ b/mm-server/src/compositor/dispatch.rs @@ -11,6 +11,7 @@ mod wl_seat; mod wl_shm; mod wp_fractional_scale; mod wp_linux_dmabuf; +mod wp_linux_drm_syncobj; mod wp_pointer_constraints; mod wp_presentation; mod wp_relative_pointer; diff --git a/mm-server/src/compositor/dispatch/wp_linux_drm_syncobj.rs b/mm-server/src/compositor/dispatch/wp_linux_drm_syncobj.rs new file mode 100644 index 0000000..d506762 --- /dev/null +++ b/mm-server/src/compositor/dispatch/wp_linux_drm_syncobj.rs @@ -0,0 +1,181 @@ +// Copyright 2024 Colin Marc +// +// SPDX-License-Identifier: BUSL-1.1 + +use tracing::error; +use wayland_protocols::wp::linux_drm_syncobj::v1::server::{ + wp_linux_drm_syncobj_manager_v1, wp_linux_drm_syncobj_surface_v1, + wp_linux_drm_syncobj_timeline_v1, +}; +use wayland_server::Resource as _; + +use crate::{ + compositor::{ + buffers::{BufferTimeline, BufferTimelineKey}, + surface::SurfaceKey, + State, + }, + vulkan::VkTimelineSemaphore, +}; + +impl wayland_server::GlobalDispatch + for State +{ + fn bind( + _state: &mut Self, + _handle: &wayland_server::DisplayHandle, + _client: &wayland_server::Client, + resource: wayland_server::New, + _global_data: &(), + data_init: &mut wayland_server::DataInit<'_, Self>, + ) { + data_init.init(resource, ()); + } +} + +impl wayland_server::Dispatch + for State +{ + fn request( + state: &mut Self, + _client: &wayland_server::Client, + resource: &wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1, + request: wp_linux_drm_syncobj_manager_v1::Request, + _data: &(), + _dhandle: &wayland_server::DisplayHandle, + data_init: &mut wayland_server::DataInit<'_, Self>, + ) { + match request { + wp_linux_drm_syncobj_manager_v1::Request::GetSurface { id, surface } => { + if let Some(surface_key) = surface.data::() { + let wp_syncobj_surface = data_init.init(id, *surface_key); + + let surface = state + .surfaces + .get_mut(*surface_key) + .expect("surface has no entry"); + + if surface.wp_syncobj_surface.is_some() { + resource.post_error( + wp_linux_drm_syncobj_manager_v1::Error::SurfaceExists, + "A syncobj surface already exists for that wl_surface.", + ); + return; + } + + surface.wp_syncobj_surface = Some(wp_syncobj_surface); + } + } + wp_linux_drm_syncobj_manager_v1::Request::ImportTimeline { id, fd } => { + let sema = match VkTimelineSemaphore::from_syncobj_fd(state.vk.clone(), fd) { + Ok(t) => t, + Err(e) => { + error!("failed to import syncobj timeline: {e:#}"); + resource.post_error( + wp_linux_drm_syncobj_manager_v1::Error::InvalidTimeline, + "Failed to import timeline.", + ); + return; + } + }; + + state + .imported_buffer_timelines + .insert_with_key(|k| BufferTimeline { + _wp_syncobj_timeline: data_init.init(id, k), + sema, + }); + } + wp_linux_drm_syncobj_manager_v1::Request::Destroy => (), + _ => unreachable!(), + } + } +} + +impl + wayland_server::Dispatch< + wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1, + SurfaceKey, + > for State +{ + fn request( + state: &mut Self, + _client: &wayland_server::Client, + _resource: &wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1, + request: wp_linux_drm_syncobj_surface_v1::Request, + surface_key: &SurfaceKey, + _dhandle: &wayland_server::DisplayHandle, + _data_init: &mut wayland_server::DataInit<'_, Self>, + ) { + match request { + wp_linux_drm_syncobj_surface_v1::Request::SetAcquirePoint { + timeline, + point_hi, + point_lo, + } => { + let timeline = timeline + .data::() + .and_then(|key| state.imported_buffer_timelines.get(*key)) + .expect("timeline has no entry"); + + let surface = state + .surfaces + .get_mut(*surface_key) + .expect("surface has no entry"); + + surface.pending_acquire_point = + Some(timeline.sema.new_point(super::make_u64(point_hi, point_lo))) + } + wp_linux_drm_syncobj_surface_v1::Request::SetReleasePoint { + timeline, + point_hi, + point_lo, + } => { + let timeline = timeline + .data::() + .and_then(|key| state.imported_buffer_timelines.get(*key)) + .expect("timeline has no entry"); + + let surface = state + .surfaces + .get_mut(*surface_key) + .expect("surface has no entry"); + + surface.pending_release_point = + Some(timeline.sema.new_point(super::make_u64(point_hi, point_lo))) + } + wp_linux_drm_syncobj_surface_v1::Request::Destroy => (), + _ => unreachable!(), + } + } + + fn destroyed( + state: &mut Self, + _client: wayland_server::backend::ClientId, + _resource: &wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1, + surface_key: &SurfaceKey, + ) { + if let Some(surface) = state.surfaces.get_mut(*surface_key) { + surface.pending_acquire_point = None; + surface.pending_release_point = None; + } + } +} + +impl + wayland_server::Dispatch< + wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1, + BufferTimelineKey, + > for State +{ + fn request( + _state: &mut Self, + _client: &wayland_server::Client, + _resource: &wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1, + _request: wp_linux_drm_syncobj_timeline_v1::Request, + _data: &BufferTimelineKey, + _dhandle: &wayland_server::DisplayHandle, + _data_init: &mut wayland_server::DataInit<'_, Self>, + ) { + } +} diff --git a/mm-server/src/compositor/surface.rs b/mm-server/src/compositor/surface.rs index 8eed811..a25f953 100644 --- a/mm-server/src/compositor/surface.rs +++ b/mm-server/src/compositor/surface.rs @@ -8,6 +8,7 @@ use tracing::{debug, trace, warn}; use wayland_protocols::{ wp::{ fractional_scale::v1::server::wp_fractional_scale_v1, + linux_drm_syncobj::v1::server::wp_linux_drm_syncobj_surface_v1, presentation_time::server::wp_presentation_feedback, }, xdg::shell::server::{xdg_surface, xdg_toplevel}, @@ -23,11 +24,12 @@ use crate::{ xwayland, DisplayParams, State, }, pixel_scale::PixelScale, + vulkan::VkTimelinePoint, }; slotmap::new_key_type! { pub struct SurfaceKey; } -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub struct Surface { pub wl_surface: wl_surface::WlSurface, @@ -39,6 +41,10 @@ pub struct Surface { pub buffer_scale: DoubleBuffered, pub content: Option, + pub wp_syncobj_surface: Option, + pub pending_acquire_point: Option, + pub pending_release_point: Option, + pub role: DoubleBuffered, pub sent_configuration: Option, pub configuration: Option, @@ -60,6 +66,10 @@ impl Surface { buffer_scale: DoubleBuffered::default(), content: None, + wp_syncobj_surface: None, + pending_acquire_point: None, + pending_release_point: None, + role: DoubleBuffered::default(), sent_configuration: None, configuration: None, @@ -264,10 +274,13 @@ pub enum PendingBuffer { Detach, } -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub struct ContentUpdate { pub buffer: BufferKey, + /// Used for explicit sync. + pub explicit_sync: Option<(VkTimelinePoint, VkTimelinePoint)>, + /// The real dimensions of the buffer. This is how surface coordinates are /// determined in wayland. pub dimensions: glam::UVec2, @@ -355,8 +368,38 @@ impl State { buffer.wl_buffer.release(); } + // Check for explicit sync. + let explicit_sync = + surface + .wp_syncobj_surface + .as_ref() + .and_then(|wp_syncobj_surface| { + let Some(acquire_point) = surface.pending_acquire_point.take() else { + wp_syncobj_surface.post_error( + wp_linux_drm_syncobj_surface_v1::Error::NoAcquirePoint, + "No acquire point set.", + ); + return None; + }; + + let Some(release_point) = surface.pending_release_point.take() else { + wp_syncobj_surface.post_error( + wp_linux_drm_syncobj_surface_v1::Error::NoReleasePoint, + "No release point set.", + ); + return None; + }; + + Some((acquire_point, release_point)) + }); + + if let Some((_, release)) = explicit_sync.as_ref() { + buffer.release_signal = Some(release.clone()); + } + surface.content = Some(ContentUpdate { buffer: buffer_id, + explicit_sync, dimensions: buffer.dimensions(), wp_presentation_feedback: feedback, }); diff --git a/mm-server/src/compositor/video.rs b/mm-server/src/compositor/video.rs index 2acbec3..959e310 100644 --- a/mm-server/src/compositor/video.rs +++ b/mm-server/src/compositor/video.rs @@ -99,6 +99,7 @@ pub struct SwapFrame { convert_ds: vk::DescriptorSet, // Should be dropped first. draws: Vec<(vk::ImageView, glam::Vec2, glam::Vec2)>, texture_semas: Vec, + texture_acquire_points: Vec, /// An RGBA image to composite to. blend_image: VkImage, @@ -124,7 +125,7 @@ pub struct SwapFrame { pub enum TextureSync { BinaryAcquire(vk::Semaphore), - // Timeline + TimelineAcquire(VkTimelinePoint), } pub struct EncodePipeline { @@ -378,9 +379,13 @@ impl EncodePipeline { } }; - if let Some(TextureSync::BinaryAcquire(semaphore)) = sync { - frame.texture_semas.push(semaphore); - } + match sync { + Some(TextureSync::TimelineAcquire(acquire)) => { + frame.texture_acquire_points.push(acquire) + } + Some(TextureSync::BinaryAcquire(semaphore)) => frame.texture_semas.push(semaphore), + None => (), + }; // Convert the destination rect into clip coordinates. let display_size: glam::UVec2 = @@ -555,6 +560,16 @@ impl EncodePipeline { ); } + // Wait on explicit sync acquire points before sampling. + for acquire in frame.texture_acquire_points.drain(..) { + render_wait_infos.push( + vk::SemaphoreSubmitInfo::default() + .semaphore(acquire.timeline().as_semaphore()) + .value(acquire.value()) + .stage_mask(vk::PipelineStageFlags2::FRAGMENT_SHADER), + ) + } + let render_submit_info = vk::SubmitInfo2::default() .command_buffer_infos(&render_cb_infos) .wait_semaphore_infos(&render_wait_infos) @@ -696,6 +711,7 @@ fn new_swapframe( Ok(SwapFrame { convert_ds, texture_semas: Vec::new(), + texture_acquire_points: Vec::new(), draws: Vec::new(), blend_image, encode_image, diff --git a/mm-server/src/vulkan/timeline.rs b/mm-server/src/vulkan/timeline.rs index b9a0c4b..f3ede76 100644 --- a/mm-server/src/vulkan/timeline.rs +++ b/mm-server/src/vulkan/timeline.rs @@ -2,7 +2,10 @@ // // SPDX-License-Identifier: BUSL-1.1 -use std::sync::Arc; +use std::{ + os::fd::{IntoRawFd as _, OwnedFd}, + sync::Arc, +}; use ash::vk; use tracing::instrument; @@ -64,6 +67,22 @@ impl VkTimelineSemaphore { Ok(Self(Arc::new(Inner { vk, sema }))) } + pub fn from_syncobj_fd(vk: Arc, fd: OwnedFd) -> anyhow::Result { + let sema = Self::new(vk.clone(), 0)?; + + let import_info = vk::ImportSemaphoreFdInfoKHR::default() + .semaphore(sema.as_semaphore()) + .handle_type(vk::ExternalSemaphoreHandleTypeFlags::OPAQUE_FD) + .fd(fd.into_raw_fd()); // Vulkan owns the fd now. + + unsafe { + vk.external_semaphore_api + .import_semaphore_fd(&import_info)?; + } + + Ok(sema) + } + pub fn new_point(&self, value: u64) -> VkTimelinePoint { VkTimelinePoint(self.0.clone(), value) }