Skip to content

Commit

Permalink
wayland: add fifo protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
cmeissl committed Nov 14, 2024
1 parent 7fa6628 commit 459facb
Show file tree
Hide file tree
Showing 2 changed files with 368 additions and 0 deletions.
367 changes: 367 additions & 0 deletions src/wayland/fifo/mod.rs
Original file line number Diff line number Diff line change
@@ -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<D>(display: &DisplayHandle) -> Self
where
D: GlobalDispatch<WpFifoManagerV1, bool>,
D: Dispatch<WpFifoManagerV1, bool>,
D: 'static,
{
Self::new_internal::<D>(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<D>(display: &DisplayHandle) -> Self
where
D: GlobalDispatch<WpFifoManagerV1, bool>,
D: Dispatch<WpFifoManagerV1, bool>,
D: 'static,
{
Self::new_internal::<D>(display, false)
}

fn new_internal<D>(display: &DisplayHandle, is_managed: bool) -> Self
where
D: GlobalDispatch<WpFifoManagerV1, bool>,
D: Dispatch<WpFifoManagerV1, bool>,
D: 'static,
{
let global = display.create_global::<D, WpFifoManagerV1, _>(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<D> GlobalDispatch<WpFifoManagerV1, bool, D> for FifoManagerState
where
D: GlobalDispatch<WpFifoManagerV1, bool>,
D: Dispatch<WpFifoManagerV1, bool>,
D: 'static,
{
fn bind(
_state: &mut D,
_dh: &DisplayHandle,
_client: &wayland_server::Client,
resource: New<WpFifoManagerV1>,
global_data: &bool,
data_init: &mut DataInit<'_, D>,
) {
data_init.init(resource, *global_data);
}
}

impl<D> Dispatch<WpFifoManagerV1, bool, D> for FifoManagerState
where
D: Dispatch<WpFifoManagerV1, bool>,
D: Dispatch<WpFifoV1, Weak<WlSurface>>,
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::<RefCell<FifoMarker>>();
(
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::<D, _>(&surface, |_, _, surface| {
let fifo_barrier = with_states(surface, |states| {
let fifo_state = *states.cached_state.get::<FifoCachedState>().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::<FifoBarrierCachedState>()
.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::<FifoBarrierCachedState>()
.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<WpFifoV1>);

impl<D> Dispatch<WpFifoV1, Weak<WlSurface>, D> for FifoManagerState
where
D: Dispatch<WpFifoV1, Weak<WlSurface>>,
D: 'static,
{
fn request(
_state: &mut D,
_client: &wayland_server::Client,
resource: &WpFifoV1,
request: wp_fifo_v1::Request,
data: &Weak<WlSurface>,
_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::<FifoCachedState>().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::<FifoCachedState>()
.pending()
.wait_barrier = true;
});
}
wp_fifo_v1::Request::Destroy => {
if let Ok(surface) = data.upgrade() {
with_states(&surface, |states| {
states
.data_map
.get::<RefCell<FifoMarker>>()
.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<AtomicBool>);

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<FifoBarrier>,
}

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);
};
}
1 change: 1 addition & 0 deletions src/wayland/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 459facb

Please sign in to comment.