diff --git a/src/wayland/dmabuf/mod.rs b/src/wayland/dmabuf/mod.rs new file mode 100644 index 0000000..c43d430 --- /dev/null +++ b/src/wayland/dmabuf/mod.rs @@ -0,0 +1,377 @@ +use std::{cell::RefCell, os::unix::io::RawFd, rc::Rc}; + +pub use wayland_protocols::unstable::linux_dmabuf::v1::server::zwp_linux_buffer_params_v1::Flags; +use wayland_protocols::unstable::linux_dmabuf::v1::server::{ + zwp_linux_buffer_params_v1::{ + Error as ParamError, RequestHandler as ParamRequestHandler, ZwpLinuxBufferParamsV1 as BufferParams, + }, + zwp_linux_dmabuf_v1, +}; +use wayland_server::{protocol::wl_buffer, Display, Global, NewResource}; + +/// Representation of a Dmabuf format, as advertized to the client +pub struct Format { + /// The format identifier + pub format: u32, + /// High part of the supported modifiers + pub modifier_hi: u32, + /// Low part of the supported modifiers + pub modifier_lo: u32, + /// Number of planes used by this format + pub plane_count: u32, +} + +/// A plane send by the client +pub struct Plane { + /// The file descriptor + pub fd: RawFd, + /// The plane index + pub plane_idx: u32, + /// Offset from the start of the Fd + pub offset: u32, + /// Stride for this plane + pub stride: u32, + /// High part of the modifiers for this plane + pub modifier_hi: u32, + /// Low part of the modifiers for this plane + pub modifier_lo: u32, +} + +/// The complete information provided by the client to create a dmabuf buffer +pub struct BufferInfo { + /// The submitted planes + pub planes: Vec, + /// The width of this buffer + pub width: i32, + /// The height of this buffer + pub height: i32, + /// The format in use + pub format: u32, + /// The flags applied to it + /// + /// This is a bitflag, to be compared with the `Flags` enum reexported by this module. + pub flags: u32, +} + +/// Handler trait for dmabuf validation +/// +/// You need to provide an implementation of this trait that will validate the parameters provided by the +/// client and import it as a dmabuf. +pub trait DmabufHandler { + /// The data of a successfully imported dmabuf. + /// + /// This will be stored as the `user_data` of the `WlBuffer` associated with this dmabuf. + type BufferData: 'static; + /// Validate a dmabuf + /// + /// From the information provided by the client, you need to validate and/or import the buffer. + /// + /// You can then store any information your compositor will need to handle it later, when the client has + /// submitted the buffer by returning `Ok(BufferData)` where `BufferData` is the associated type of this, + /// trait, a type of your choosing. + /// + /// If the buffer could not be imported, whatever the reason, return `Err(())`. + fn validate_dmabuf(&mut self, info: BufferInfo) -> Result; + /// Create a buffer from validated buffer data. + /// + /// This method is pre-implemented for you by storing the provided `BufferData` as the `user_data` of the + /// provided `WlBuffer`. By default it assumes that your `BufferData` is not threadsafe. + /// + /// You can override it if you need your `BufferData` to be threadsafe, or which to register a destructor + /// for the `WlBuffer` for example. + fn create_buffer( + &mut self, + data: Self::BufferData, + buffer: NewResource, + ) -> wl_buffer::WlBuffer { + buffer.implement_closure(|_, _| {}, None::, data) + } +} + +/// Initialize a dmabuf global. +/// +/// You need to provide a vector of the supported formats, as well as an implementation fo the `DmabufHandler` +/// trait, which will receive the buffer creation requests from the clients. +pub fn init_dmabuf_global( + display: &mut Display, + formats: Vec, + handler: H, + logger: L, +) -> Global +where + L: Into>, + H: DmabufHandler + 'static, +{ + let log = crate::slog_or_stdlog(logger); + + let max_planes = formats.iter().map(|f| f.plane_count).max().unwrap_or(0); + let formats = Rc::new(formats); + let handler = Rc::new(RefCell::new(handler)); + + display.create_global(3, move |new_dmabuf, version| { + let dma_formats = formats.clone(); + let dma_handler = handler.clone(); + let dmabuf: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 = new_dmabuf.implement_closure( + move |req, _| match req { + zwp_linux_dmabuf_v1::Request::CreateParams { params_id } => { + params_id.implement( + ParamsHandler { + pending_planes: Vec::new(), + max_planes, + used: false, + formats: dma_formats.clone(), + handler: dma_handler.clone(), + }, + None::, + (), + ); + } + _ => (), + }, + None::, + (), + ); + + // send the supported formats + for f in &*formats { + dmabuf.format(f.format); + if version >= 3 { + dmabuf.modifier(f.format, f.modifier_hi, f.modifier_lo); + } + } + }) +} + +struct ParamsHandler { + pending_planes: Vec, + max_planes: u32, + used: bool, + formats: Rc>, + handler: Rc>, +} + +impl ParamRequestHandler for ParamsHandler { + fn add( + &mut self, + params: BufferParams, + fd: RawFd, + plane_idx: u32, + offset: u32, + stride: u32, + modifier_hi: u32, + modifier_lo: u32, + ) { + // protocol checks: + // Cannot reuse a params: + if self.used { + params.as_ref().post_error( + ParamError::AlreadyUsed as u32, + "This buffer_params has already been used to create a buffer.".into(), + ); + return; + } + // plane_idx is not too large + if plane_idx >= self.max_planes { + // plane_idx starts at 0 + params.as_ref().post_error( + ParamError::PlaneIdx as u32, + format!("Plane index {} is out of bounds.", plane_idx), + ); + return; + } + // plane_idx has already been set + if self.pending_planes.iter().any(|d| d.plane_idx == plane_idx) { + params.as_ref().post_error( + ParamError::PlaneSet as u32, + format!("Plane index {} is already set.", plane_idx), + ); + return; + } + // all checks passed, store the plane + self.pending_planes.push(Plane { + fd, + plane_idx, + offset, + stride, + modifier_hi, + modifier_lo, + }); + } + + fn create(&mut self, params: BufferParams, width: i32, height: i32, format: u32, flags: u32) { + // Cannot reuse a params: + if self.used { + params.as_ref().post_error( + ParamError::AlreadyUsed as u32, + "This buffer_params has already been used to create a buffer.".into(), + ); + return; + } + self.used = true; + if !buffer_basic_checks( + &self.formats, + &self.pending_planes, + ¶ms, + format, + width, + height, + ) { + return; + } + let info = BufferInfo { + planes: ::std::mem::replace(&mut self.pending_planes, Vec::new()), + width, + height, + format, + flags, + }; + let mut handler = self.handler.borrow_mut(); + if let Ok(data) = handler.validate_dmabuf(info) { + if let Some(buffer) = params + .as_ref() + .client() + .and_then(|c| c.create_resource::(1)) + { + let buffer = handler.create_buffer(data, buffer); + params.created(&buffer); + } + } else { + params.failed(); + } + } + + fn create_immed( + &mut self, + params: BufferParams, + buffer_id: NewResource, + width: i32, + height: i32, + format: u32, + flags: u32, + ) { + // Cannot reuse a params: + if self.used { + params.as_ref().post_error( + ParamError::AlreadyUsed as u32, + "This buffer_params has already been used to create a buffer.".into(), + ); + return; + } + self.used = true; + if !buffer_basic_checks( + &self.formats, + &self.pending_planes, + ¶ms, + format, + width, + height, + ) { + return; + } + let info = BufferInfo { + planes: ::std::mem::replace(&mut self.pending_planes, Vec::new()), + width, + height, + format, + flags, + }; + let mut handler = self.handler.borrow_mut(); + if let Ok(data) = handler.validate_dmabuf(info) { + handler.create_buffer(data, buffer_id); + } else { + params.as_ref().post_error( + ParamError::InvalidWlBuffer as u32, + "create_immed resulted in an invalid buffer.".into(), + ); + } + } +} + +fn buffer_basic_checks( + formats: &[Format], + pending_planes: &[Plane], + params: &BufferParams, + format: u32, + width: i32, + height: i32, +) -> bool { + // protocol_checks: + // This must be a known format + let format = match formats.iter().find(|f| f.format == format) { + Some(f) => f, + None => { + params.as_ref().post_error( + ParamError::InvalidFormat as u32, + format!("Format {:x} is not supported.", format), + ); + return false;; + } + }; + // The number of planes set must match what the format expects + let max_plane_set = pending_planes.iter().map(|d| d.plane_idx + 1).max().unwrap_or(0); + if max_plane_set != format.plane_count || pending_planes.len() < format.plane_count as usize { + params.as_ref().post_error( + ParamError::Incomplete as u32, + format!( + "Format {:x} requires {} planes but got {}.", + format.format, format.plane_count, max_plane_set + ), + ); + return false; + } + // Width and height must be positivie + if width < 1 || height < 1 { + params.as_ref().post_error( + ParamError::InvalidDimensions as u32, + format!("Dimensions ({},{}) are not valid.", width, height), + ); + return false; + } + // check the size of each plane buffer + for plane in pending_planes { + // check size for overflow + let end = match plane + .stride + .checked_mul(height as u32) + .and_then(|o| o.checked_add(plane.offset)) + { + None => { + params.as_ref().post_error( + ParamError::OutOfBounds as u32, + format!("Size overflow for plane {}.", plane.plane_idx), + ); + return false; + } + Some(e) => e, + }; + if let Ok(size) = ::nix::unistd::lseek(plane.fd, 0, ::nix::unistd::Whence::SeekEnd) { + if plane.offset as i64 > size { + params.as_ref().post_error( + ParamError::OutOfBounds as u32, + format!("Invalid offset {} for plane {}.", plane.offset, plane.plane_idx), + ); + return false; + } + if (plane.offset + plane.stride) as i64 > size { + params.as_ref().post_error( + ParamError::OutOfBounds as u32, + format!("Invalid stride {} for plane {}.", plane.stride, plane.plane_idx), + ); + return false; + } + // Planes > 0 can be subsampled, in which case 'size' will be smaller + // than expected. + if plane.plane_idx == 0 && end as i64 > size { + params.as_ref().post_error( + ParamError::OutOfBounds as u32, + format!( + "Invalid stride ({}) or height ({}) for plane {}.", + plane.stride, height, plane.plane_idx + ), + ); + return false; + } + } + } + return true; +} diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 8ad4276..794d472 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -16,6 +16,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; pub mod compositor; pub mod data_device; +pub mod dmabuf; pub mod output; pub mod seat; pub mod shell;