From 1627d51cf6abf03f49f8c8a23b16ede86b304ad2 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Thu, 13 Jun 2019 15:28:46 +0200 Subject: [PATCH] wayland: introduce explicit-synchronization helpers --- Cargo.toml | 10 +- src/wayland/explicit_synchronization/mod.rs | 315 ++++++++++++++++++++ src/wayland/mod.rs | 1 + 3 files changed, 321 insertions(+), 5 deletions(-) create mode 100644 src/wayland/explicit_synchronization/mod.rs diff --git a/Cargo.toml b/Cargo.toml index deb2264..83e9848 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,9 @@ edition = "2018" members = [ "anvil" ] [dependencies] -wayland-server = { version = "0.23.2", optional = true } -wayland-commons = { version = "0.23.3", optional = true } -wayland-sys = { version = "0.23", optional = true } +wayland-server = { version = "0.23.4", optional = true } +wayland-commons = { version = "0.23.4", optional = true } +wayland-sys = { version = "0.23.4", optional = true } calloop = "0.4.2" bitflags = "1" nix = "0.13" @@ -22,7 +22,7 @@ tempfile = "3.0" slog = "2.1.1" slog-stdlog = "3.0.2" libloading = "0.5.0" -wayland-client = { version = "0.23", features = ["egl"], optional = true } +wayland-client = { version = "0.23.4", features = ["egl"], optional = true } winit = { version = "0.18.0", optional = true } drm = { version = "^0.3.4", optional = true } gbm = { version = "^0.5.0", optional = true, default-features = false, features = ["drm-support"] } @@ -31,7 +31,7 @@ input = { version = "0.4.1", optional = true } udev = { version = "0.2.0", optional = true } dbus = { version = "0.6.1", optional = true } systemd = { version = "0.4.0", optional = true } -wayland-protocols = { version = "0.23", features = ["unstable_protocols", "server"], optional = true } +wayland-protocols = { version = "0.23.4", features = ["unstable_protocols", "server"], optional = true } image = { version = "0.21.0", optional = true } error-chain = "0.12.0" lazy_static = "1.0.0" diff --git a/src/wayland/explicit_synchronization/mod.rs b/src/wayland/explicit_synchronization/mod.rs new file mode 100644 index 0000000..f1dea7d --- /dev/null +++ b/src/wayland/explicit_synchronization/mod.rs @@ -0,0 +1,315 @@ +//! Explicit buffer synchronization per wayland surface +//! +//! This interface allow clients to switch from a per-buffer signaling of buffer release (via the +//! `wl_buffer.release` event) to a per-surface signaling using `dma_fence`s. This is notably used for +//! efficient synchronization of OpenGL/Vulkan clients. +//! +//! At surface commit time, in addition to a buffer the client can have attached two more properties: +//! +//! - an acquire `dma_fence` file descriptor, that the compositor is required to wait on before it will +//! try to access the contents of the associated buffer +//! - an `ExplicitBufferRelease` object, that the compositor is expect to use to signal the client when it has +//! finished using the buffer for this surface (if the same buffer is attached to multiple surfaces, the +//! release only applies for the surface associated with this release object, not the whole buffer). +//! +//! The use of these `dma_fence`s in conjunction with the graphics stack allows for efficient synchronization +//! between the clients and the compositor. +//! +//! ## Usage +//! +//! First, you need to initialize the global: +//! +//! ``` +//! # extern crate wayland_server; +//! # #[macro_use] extern crate smithay; +//! # +//! # use smithay::wayland::compositor::roles::*; +//! # use smithay::wayland::compositor::CompositorToken; +//! use smithay::wayland::explicit_synchronization::*; +//! # define_roles!(MyRoles); +//! # +//! # fn main() { +//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap(); +//! # let mut display = wayland_server::Display::new(event_loop.handle()); +//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::( +//! # &mut display, +//! # |_, _, _| {}, +//! # None +//! # ); +//! init_explicit_synchronization_global( +//! &mut display, +//! compositor_token, +//! None /* You can insert a logger here */ +//! ); +//! # } +//! ``` +//! +//! Then when handling a surface commit, you can retrieve the synchronization information for the surface +//! data: +//! ```no_run +//! # extern crate wayland_server; +//! # #[macro_use] extern crate smithay; +//! # +//! # use wayland_server::protocol::wl_surface::WlSurface; +//! # use smithay::wayland::compositor::CompositorToken; +//! # use smithay::wayland::explicit_synchronization::*; +//! # +//! # fn dummy_function(surface: &WlSurface, compositor_token: CompositorToken) { +//! compositor_token.with_surface_data(&surface, |surface_attributes| { +//! // While you retrieve the surface data from the commit ... +//! // Check the explicit synchronization data: +//! match get_explicit_synchronization_state(surface_attributes) { +//! Ok(sync_state) => { +//! /* This surface is explicitly synchronized, you need to handle +//! the contents of sync_state +//! */ +//! }, +//! Err(()) => { +//! /* This surface is not explicitly synchronized, nothing more to do +//! */ +//! } +//! } +//! }); +//! # } +//! ``` + +use std::os::unix::io::RawFd; + +use wayland_protocols::unstable::linux_explicit_synchronization::v1::server::*; +use wayland_server::{protocol::wl_surface::WlSurface, Display, Global, NewResource}; + +use crate::wayland::compositor::{CompositorToken, SurfaceAttributes}; + +/// An object to signal end of use of a buffer +pub struct ExplicitBufferRelease { + release: zwp_linux_buffer_release_v1::ZwpLinuxBufferReleaseV1, +} + +impl ExplicitBufferRelease { + /// Immediately release the buffer + /// + /// The client can reuse it as soon as this event is sent. + pub fn immediate_release(self) { + self.release.immediate_release(); + } + + /// Send a release fence to the client + /// + /// The client will be allowed to reuse the buffer once you signal this `dma_fence`. + pub fn send_release_fence(self, fence: RawFd) { + self.release.fenced_release(fence); + } +} + +/// An explicit synchronization state +/// +/// The client is not required to fill both. `acquire` being `None` means that you don't need to wait +/// before acessing the buffer, `release` being `None` means that the client does not require additionnal +/// signaling that you are finished (you still need to send `wl_buffer.release`). +pub struct ExplicitSyncState { + /// An acquire `dma_fence` object, that you should wait on before accessing the contents of the + /// buffer associated with the surface. + pub acquire: Option, + /// A buffer release object, that you should use to signal the client when you are done using the + /// buffer associated with the surface. + pub release: Option, +} + +struct InternalState { + sync_state: ExplicitSyncState, + sync_resource: zwp_linux_surface_synchronization_v1::ZwpLinuxSurfaceSynchronizationV1, +} + +struct ESUserData { + state: Option, +} + +impl ESUserData { + fn take_state(&mut self) -> Option { + if let Some(ref mut state) = self.state { + Some(ExplicitSyncState { + acquire: state.sync_state.acquire.take(), + release: state.sync_state.release.take(), + }) + } else { + None + } + } +} + +/// Possible errors you can send to an ill-behaving clients +pub enum ExplicitSyncError { + /// An invalid file descriptor was sent by the client for an acquire fence + InvalidFence, + /// The client requested synchronization for a buffer type that does not support it + UnsupportedBuffer, + /// The client requested synchronization while not having attached any buffer + NoBuffer, +} + +/// Retrieve the explicit synchronization state commited by the client +/// +/// This state can contain an acquire fence and a release object, for synchronization (see module-level docs). +/// +/// This function will clear the pending state, preparing the surface for the next commit, as a result you +/// should always call it on surface commit to avoid getting out-of-sync with the client. +/// +/// This function returns an error if the client has not setup explicit synchronization for this surface. +pub fn get_explicit_synchronization_state(attrs: &mut SurfaceAttributes) -> Result { + attrs + .user_data + .get_mut::() + .and_then(|s| s.take_state()) + .ok_or(()) +} + +/// Send a synchronization error to a client +/// +/// See the enum definition for possible errors. These errors are protocol errors, meaning that +/// the client associated with this `SurfaceAttributes` will be killed as a result of calling this +/// function. +pub fn send_explicit_synchronization_error(attrs: &SurfaceAttributes, error: ExplicitSyncError) { + if let Some(ref data) = attrs.user_data.get::() { + if let Some(ref state) = data.state { + match error { + ExplicitSyncError::InvalidFence => state.sync_resource.as_ref().post_error( + zwp_linux_surface_synchronization_v1::Error::InvalidFence as u32, + "The fence specified by the client could not be imported.".into(), + ), + ExplicitSyncError::UnsupportedBuffer => state.sync_resource.as_ref().post_error( + zwp_linux_surface_synchronization_v1::Error::UnsupportedBuffer as u32, + "The buffer does not support explicit synchronization.".into(), + ), + ExplicitSyncError::NoBuffer => state.sync_resource.as_ref().post_error( + zwp_linux_surface_synchronization_v1::Error::NoBuffer as u32, + "No buffer was attached.".into(), + ), + } + } + } +} + +/// Initialize the explicit synchronization global +/// +/// See module-level documentation for its use. +pub fn init_explicit_synchronization_global( + display: &mut Display, + compositor: CompositorToken, + logger: L, +) -> Global +where + L: Into>, + R: 'static, +{ + let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "wayland_explicit_synchronization")); + + display.create_global::( + 2, + move |new_sync, version| { + new_sync.implement_closure( + move |req, explicit_sync| match req { + zwp_linux_explicit_synchronization_v1::Request::GetSynchronization { id, surface } => { + let exists = compositor.with_surface_data(&surface, |attrs| { + attrs.user_data.insert_if_missing(|| ESUserData { state: None }); + attrs + .user_data + .get::() + .map(|ud| ud.state.is_some()) + .unwrap() + }); + if exists { + explicit_sync.as_ref().post_error( + zwp_linux_explicit_synchronization_v1::Error::SynchronizationExists as u32, + "The surface already has a synchronization object associated.".into(), + ); + return; + } + let surface_sync = implement_surface_sync(id, surface.clone(), compositor); + compositor.with_surface_data(&surface, |attrs| { + let data = attrs.user_data.get_mut::().unwrap(); + data.state = Some(InternalState { + sync_state: ExplicitSyncState { + acquire: None, + release: None, + }, + sync_resource: surface_sync, + }); + }); + } + _ => {} + }, + None::, + (), + ); + }, + ) +} + +fn implement_surface_sync( + id: NewResource, + surface: WlSurface, + compositor: CompositorToken, +) -> zwp_linux_surface_synchronization_v1::ZwpLinuxSurfaceSynchronizationV1 +where + R: 'static, +{ + id.implement_closure( + move |req, surface_sync| match req { + zwp_linux_surface_synchronization_v1::Request::SetAcquireFence { fd } => { + if !surface.as_ref().is_alive() { + surface_sync.as_ref().post_error( + zwp_linux_surface_synchronization_v1::Error::NoSurface as u32, + "The associated wl_surface was destroyed.".into(), + ) + } + compositor.with_surface_data(&surface, |attrs| { + let data = attrs.user_data.get_mut::().unwrap(); + if let Some(ref mut state) = data.state { + if state.sync_state.acquire.is_some() { + surface_sync.as_ref().post_error( + zwp_linux_surface_synchronization_v1::Error::DuplicateFence as u32, + "Multiple fences added for a single surface commit.".into(), + ) + } else { + state.sync_state.acquire = Some(fd); + } + } + }); + } + zwp_linux_surface_synchronization_v1::Request::GetRelease { release } => { + if !surface.as_ref().is_alive() { + surface_sync.as_ref().post_error( + zwp_linux_surface_synchronization_v1::Error::NoSurface as u32, + "The associated wl_surface was destroyed.".into(), + ) + } + compositor.with_surface_data(&surface, |attrs| { + let data = attrs.user_data.get_mut::().unwrap(); + if let Some(ref mut state) = data.state { + if state.sync_state.acquire.is_some() { + surface_sync.as_ref().post_error( + zwp_linux_surface_synchronization_v1::Error::DuplicateRelease as u32, + "Multiple releases added for a single surface commit.".into(), + ) + } else { + state.sync_state.release = Some(ExplicitBufferRelease { + release: release.implement_dummy(), + }); + } + } + }); + } + zwp_linux_surface_synchronization_v1::Request::Destroy => { + // disable the ESUserData + compositor.with_surface_data(&surface, |attrs| { + if let Some(ref mut data) = attrs.user_data.get_mut::() { + data.state = None; + } + }); + } + _ => (), + }, + None::, + (), + ) +} diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 68da307..1a899b6 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -18,6 +18,7 @@ pub mod compositor; pub mod data_device; #[cfg(feature = "backend_drm")] pub mod dmabuf; +pub mod explicit_synchronization; pub mod output; pub mod seat; pub mod shell;