wayland: introduce explicit-synchronization helpers

This commit is contained in:
Victor Berger 2019-06-13 15:28:46 +02:00 committed by Victor Berger
parent 04dc563ea0
commit 1627d51cf6
3 changed files with 321 additions and 5 deletions

View File

@ -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"

View File

@ -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::<MyRoles, _, _>(
//! # &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<R: 'static>(surface: &WlSurface, compositor_token: CompositorToken<R>) {
//! 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<RawFd>,
/// 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<ExplicitBufferRelease>,
}
struct InternalState {
sync_state: ExplicitSyncState,
sync_resource: zwp_linux_surface_synchronization_v1::ZwpLinuxSurfaceSynchronizationV1,
}
struct ESUserData {
state: Option<InternalState>,
}
impl ESUserData {
fn take_state(&mut self) -> Option<ExplicitSyncState> {
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<ExplicitSyncState, ()> {
attrs
.user_data
.get_mut::<ESUserData>()
.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::<ESUserData>() {
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<R, L>(
display: &mut Display,
compositor: CompositorToken<R>,
logger: L,
) -> Global<zwp_linux_explicit_synchronization_v1::ZwpLinuxExplicitSynchronizationV1>
where
L: Into<Option<::slog::Logger>>,
R: 'static,
{
let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "wayland_explicit_synchronization"));
display.create_global::<zwp_linux_explicit_synchronization_v1::ZwpLinuxExplicitSynchronizationV1, _>(
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::<ESUserData>()
.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::<ESUserData>().unwrap();
data.state = Some(InternalState {
sync_state: ExplicitSyncState {
acquire: None,
release: None,
},
sync_resource: surface_sync,
});
});
}
_ => {}
},
None::<fn(_)>,
(),
);
},
)
}
fn implement_surface_sync<R>(
id: NewResource<zwp_linux_surface_synchronization_v1::ZwpLinuxSurfaceSynchronizationV1>,
surface: WlSurface,
compositor: CompositorToken<R>,
) -> 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::<ESUserData>().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::<ESUserData>().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::<ESUserData>() {
data.state = None;
}
});
}
_ => (),
},
None::<fn(_)>,
(),
)
}

View File

@ -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;