From 459facbfbe3bba4aab4ab4047b5c5864503cd6cf Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Thu, 14 Nov 2024 18:04:59 +0100 Subject: [PATCH] wayland: add fifo protocol --- src/wayland/fifo/mod.rs | 367 ++++++++++++++++++++++++++++++++++++++++ src/wayland/mod.rs | 1 + 2 files changed, 368 insertions(+) create mode 100644 src/wayland/fifo/mod.rs diff --git a/src/wayland/fifo/mod.rs b/src/wayland/fifo/mod.rs new file mode 100644 index 000000000000..a583a53a102b --- /dev/null +++ b/src/wayland/fifo/mod.rs @@ -0,0 +1,367 @@ +//! TODO: Module docs +use std::{ + cell::RefCell, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +use wayland_backend::server::GlobalId; +use wayland_protocols::wp::fifo::v1::server::{ + wp_fifo_manager_v1::{self, WpFifoManagerV1}, + wp_fifo_v1::{self, WpFifoV1}, +}; +use wayland_server::{ + protocol::wl_surface::WlSurface, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, Weak, +}; + +use crate::wayland::compositor::{add_blocker, add_pre_commit_hook}; + +use super::compositor::{with_states, Blocker, BlockerState, Cacheable}; + +/// State for the [`WpFifoManagerV1`] global +#[derive(Debug)] +pub struct FifoManagerState { + global: GlobalId, + is_managed: bool, +} + +impl FifoManagerState { + /// Create a new [`WpFifoManagerV1`] global + // + /// The id provided by [`FifoManagerState::global`] may be used to + /// remove or disable this global in the future. + pub fn new(display: &DisplayHandle) -> Self + where + D: GlobalDispatch, + D: Dispatch, + D: 'static, + { + Self::new_internal::(display, true) + } + + /// Create a new unmanaged [`WpFifoManagerV1`] global + // + /// The id provided by [`FifoManagerState::global`] may be used to + /// remove or disable this global in the future. + pub fn unmanaged(display: &DisplayHandle) -> Self + where + D: GlobalDispatch, + D: Dispatch, + D: 'static, + { + Self::new_internal::(display, false) + } + + fn new_internal(display: &DisplayHandle, is_managed: bool) -> Self + where + D: GlobalDispatch, + D: Dispatch, + D: 'static, + { + let global = display.create_global::(1, is_managed); + + Self { global, is_managed } + } + + /// Returns the id of the [`WpFifoManagerV1`] global. + pub fn global(&self) -> GlobalId { + self.global.clone() + } + + /// Returns if this [`FifoManagerState`] operates in managed mode. + pub fn is_managed(&self) -> bool { + self.is_managed + } +} + +impl GlobalDispatch for FifoManagerState +where + D: GlobalDispatch, + D: Dispatch, + D: 'static, +{ + fn bind( + _state: &mut D, + _dh: &DisplayHandle, + _client: &wayland_server::Client, + resource: New, + global_data: &bool, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, *global_data); + } +} + +impl Dispatch for FifoManagerState +where + D: Dispatch, + D: Dispatch>, + D: 'static, +{ + fn request( + _state: &mut D, + _client: &wayland_server::Client, + _resource: &WpFifoManagerV1, + request: wp_fifo_manager_v1::Request, + data: &bool, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + let is_managed = *data; + + match request { + wp_fifo_manager_v1::Request::GetFifo { id, surface } => { + let (is_initial, has_active_fifo) = with_states(&surface, |states| { + let marker = states.data_map.get::>(); + ( + marker.is_none(), + marker.map(|m| m.borrow().0.is_some()).unwrap_or(false), + ) + }); + + // The protocol mandates that only a single fifo object is associated with a surface at all times + if has_active_fifo { + surface.post_error( + wp_fifo_manager_v1::Error::AlreadyExists, + "the surface has already a fifo object associated", + ); + return; + } + + // Make sure we do not install the hook more then once in case the surface is being reused + if is_managed && is_initial { + add_pre_commit_hook::(&surface, |_, _, surface| { + let fifo_barrier = with_states(surface, |states| { + let fifo_state = *states.cached_state.get::().pending(); + + // The pending state will contain any previously set barrier on this surface + // In case this commit updates the barrier with `set_barrier`, but also mandates to + // wait for a previously set barrier it is important to first retrieve the previously + // set barrier to not overwrite it with our own. + let fifo_barrier = fifo_state + .wait_barrier + .then(|| { + states + .cached_state + .get::() + .pending() + .barrier + .take() + }) + .flatten(); + + // If requested set the barrier for this commit. + // The barrier will be available for the next commit requesting to wait on it + // in the pending state. + // The barrier will also be either put in the current state in case this commit + // is not blocked or into a transaction otherwise eventually ending in the current + // state when it is unblocked. + if fifo_state.set_barrier { + states + .cached_state + .get::() + .pending() + .barrier = Some(FifoBarrier::new(false)); + } + + fifo_barrier + }); + + if let Some(barrier) = fifo_barrier { + // If multiple consecutive commits only call wait_barrier, but not set_barrier + // we might end up with the same barrier in multiple commits. It could happen + // that the barrier is already signaled in which case there is no need to + // further delay this commit + if !barrier.is_signaled() { + add_blocker(surface, barrier); + } + } + }); + } + + let fifo: WpFifoV1 = data_init.init(id, surface.downgrade()); + + with_states(&surface, |states| { + states + .data_map + .get_or_insert(|| RefCell::new(FifoMarker(None))) + .borrow_mut() + .0 = Some(fifo); + }); + } + wp_fifo_manager_v1::Request::Destroy => (), + _ => unreachable!(), + } + } +} + +// Internal marker to track if a fifo object is currently associated with a surface. +// Used to realize the `AlreadyExists` protocol check. +struct FifoMarker(Option); + +impl Dispatch, D> for FifoManagerState +where + D: Dispatch>, + D: 'static, +{ + fn request( + _state: &mut D, + _client: &wayland_server::Client, + resource: &WpFifoV1, + request: wp_fifo_v1::Request, + data: &Weak, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + wp_fifo_v1::Request::SetBarrier => { + let Ok(surface) = data.upgrade() else { + resource.post_error( + wp_fifo_v1::Error::SurfaceDestroyed as u32, + "the surface associated with this fifo object has been destroyed".to_string(), + ); + return; + }; + with_states(&surface, move |states| { + states.cached_state.get::().pending().set_barrier = true; + }); + } + wp_fifo_v1::Request::WaitBarrier => { + let Ok(surface) = data.upgrade() else { + resource.post_error( + wp_fifo_v1::Error::SurfaceDestroyed as u32, + "the surface associated with this fifo object has been destroyed".to_string(), + ); + return; + }; + with_states(&surface, move |states| { + states + .cached_state + .get::() + .pending() + .wait_barrier = true; + }); + } + wp_fifo_v1::Request::Destroy => { + if let Ok(surface) = data.upgrade() { + with_states(&surface, |states| { + states + .data_map + .get::>() + .unwrap() + .borrow_mut() + .0 = None; + }); + } + } + _ => unreachable!(), + } + } +} + +/// State for the [`WpFifoV1`] object +#[derive(Debug, Default, Copy, Clone)] +pub struct FifoCachedState { + /// The content update requested a barrier to be set + pub set_barrier: bool, + + /// The content update requested to wait on a previously + /// set barrier + pub wait_barrier: bool, +} + +impl Cacheable for FifoCachedState { + fn commit(&mut self, _dh: &DisplayHandle) -> Self { + std::mem::take(self) + } + + fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) { + *into = self; + } +} + +/// A simple barrier that can be queried and signaled +#[derive(Debug, Clone)] +pub struct FifoBarrier(Arc); + +impl PartialEq for FifoBarrier { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl FifoBarrier { + /// Initialize a new barrier with the provided state + pub fn new(signaled: bool) -> Self { + Self(Arc::new(AtomicBool::new(signaled))) + } + + /// Query the current state of the barrier + #[inline] + pub fn is_signaled(&self) -> bool { + self.0.load(Ordering::Relaxed) + } + + /// Signal the barrier + pub fn signal(&self) { + self.0.store(true, Ordering::Relaxed) + } +} + +impl Blocker for FifoBarrier { + fn state(&self) -> BlockerState { + if self.is_signaled() { + BlockerState::Released + } else { + BlockerState::Pending + } + } +} + +/// Fifo barrier per surface state +#[derive(Debug, Default)] +pub struct FifoBarrierCachedState { + /// The barrier set for the current content update + pub barrier: Option, +} + +impl Cacheable for FifoBarrierCachedState { + fn commit(&mut self, _dh: &DisplayHandle) -> Self { + Self { + barrier: self.barrier.clone(), + } + } + + fn merge_into(mut self, into: &mut Self, _dh: &DisplayHandle) { + let Some(barrier) = self.barrier.take() else { + return; + }; + + if into.barrier.as_ref() == Some(&barrier) || barrier.is_signaled() { + return; + } + + if let Some(barrier) = into.barrier.replace(barrier) { + barrier.signal(); + } + } +} + +/// Macro used to delegate [`WpFifoManagerV1`] events +#[macro_export] +macro_rules! delegate_fifo { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::wp::fifo::v1::server::wp_fifo_manager_v1::WpFifoManagerV1: bool + ] => $crate::wayland::fifo::FifoManagerState); + + $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::wp::fifo::v1::server::wp_fifo_manager_v1::WpFifoManagerV1: bool + ] => $crate::wayland::fifo::FifoManagerState); + $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::wp::fifo::v1::server::wp_fifo_v1::WpFifoV1: $crate::reexports::wayland_server::Weak<$crate::reexports::wayland_server::protocol::wl_surface::WlSurface> + ] => $crate::wayland::fifo::FifoManagerState); + }; +} diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 8eda887be15e..007d2644c616 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -55,6 +55,7 @@ pub mod dmabuf; pub mod drm_lease; #[cfg(feature = "backend_drm")] pub mod drm_syncobj; +pub mod fifo; pub mod foreign_toplevel_list; pub mod fractional_scale; pub mod idle_inhibit;