diff --git a/Cargo.toml b/Cargo.toml index d84e39dcb..545e3362c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ default = ["calloop", "xkbcommon"] pkg-config = "0.3" [dev-dependencies] +drm-fourcc = "2.2.0" image = "0.23" env_logger = "0.9" wgpu = "0.13.1" diff --git a/examples/dmabuf_formats.rs b/examples/dmabuf_formats.rs new file mode 100644 index 000000000..94b580165 --- /dev/null +++ b/examples/dmabuf_formats.rs @@ -0,0 +1,125 @@ +use drm_fourcc::{DrmFourcc, DrmModifier}; +use smithay_client_toolkit::{ + dmabuf::{DmabufFeedback, DmabufFormat, DmabufHandler, DmabufState}, + registry::{ProvidesRegistryState, RegistryState}, + registry_handlers, +}; +use wayland_client::{globals::registry_queue_init, protocol::wl_buffer, Connection, QueueHandle}; +use wayland_protocols::wp::linux_dmabuf::zv1::client::{ + zwp_linux_buffer_params_v1, zwp_linux_dmabuf_feedback_v1, +}; + +struct AppData { + registry_state: RegistryState, + dmabuf_state: DmabufState, + feedback: Option, +} + +impl DmabufHandler for AppData { + fn dmabuf_state(&mut self) -> &mut DmabufState { + &mut self.dmabuf_state + } + + fn dmabuf_feedback( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, + feedback: DmabufFeedback, + ) { + self.feedback = Some(feedback); + } + + fn created( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, + _buffer: wl_buffer::WlBuffer, + ) { + } + + fn failed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, + ) { + } +} + +impl ProvidesRegistryState for AppData { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + registry_handlers![,]; +} + +fn main() { + env_logger::init(); + + let conn = Connection::connect_to_env().unwrap(); + + let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); + let qh = event_queue.handle(); + + let mut app_data = AppData { + registry_state: RegistryState::new(&globals), + dmabuf_state: DmabufState::new(&globals, &qh), + feedback: None, + }; + + match app_data.dmabuf_state.version() { + None => println!("`zwp_linux_dmabuf_v1` version `>3` not supported by compositor."), + Some(0..=2) => unreachable!(), + Some(3) => { + println!("Version `3` of `zwp_linux_dmabuf_v1` supported. Showing modifiers.\n"); + + // Roundtrip after binding global to receive modifier events. + event_queue.roundtrip(&mut app_data).unwrap(); + + for entry in app_data.dmabuf_state.modifiers() { + print_format(entry); + } + + return; + } + Some(4..) => { + println!("Version `4` of `zwp_linux_dmabuf_v1` supported. Showing default dmabuf feedback.\n"); + + app_data.dmabuf_state.get_default_feedback(&qh).unwrap(); + + let feedback = loop { + event_queue.blocking_dispatch(&mut app_data).unwrap(); + if let Some(feedback) = app_data.feedback.as_ref() { + break feedback; + } + }; + + println!("Main device: {}", feedback.main_device()); + println!("Tranches:"); + let format_table = feedback.format_table(); + for tranche in feedback.tranches() { + println!(" Device: {}", tranche.device); + println!(" Flags: {:?}", tranche.flags); + println!(" Formats"); + for idx in &tranche.formats { + print!(" "); + print_format(&format_table[*idx as usize]); + } + } + } + } +} + +fn print_format(format: &DmabufFormat) { + print!("Format: "); + match DrmFourcc::try_from(format.format) { + Ok(format) => print!("{:?}", format), + Err(err) => print!("{:?}", err), + } + println!(", Modifier: {:?}", DrmModifier::from(format.modifier)); +} + +smithay_client_toolkit::delegate_dmabuf!(AppData); +smithay_client_toolkit::delegate_registry!(AppData); diff --git a/src/dmabuf.rs b/src/dmabuf.rs new file mode 100644 index 000000000..639d4e017 --- /dev/null +++ b/src/dmabuf.rs @@ -0,0 +1,413 @@ +use crate::{error::GlobalError, globals::GlobalData, registry::GlobalProxy}; +use memmap2::Mmap; +use std::{ + fmt, mem, + os::unix::io::{IntoRawFd, OwnedFd}, + slice, + sync::Mutex, +}; +use wayland_client::{ + globals::GlobalList, + protocol::{wl_buffer, wl_surface}, + Connection, Dispatch, Proxy, QueueHandle, WEnum, +}; +use wayland_protocols::wp::linux_dmabuf::zv1::client::{ + zwp_linux_buffer_params_v1, + zwp_linux_dmabuf_feedback_v1::{self, TrancheFlags}, + zwp_linux_dmabuf_v1, +}; + +// Workaround until `libc` updates to FreeBSD 12 ABI +#[cfg(target_os = "freebsd")] +type dev_t = u64; +#[cfg(not(target_os = "freebsd"))] +use nix::libc::dev_t; + +#[derive(Debug)] +pub struct DmabufFeedbackTranche { + /// `dev_t` value for preferred target device. May be scan-out or + /// renderer device. + pub device: dev_t, + /// Flags for tranche + pub flags: WEnum, + /// Indices of formats in the format table + pub formats: Vec, +} + +impl Default for DmabufFeedbackTranche { + fn default() -> DmabufFeedbackTranche { + DmabufFeedbackTranche { + device: 0, + flags: WEnum::Value(TrancheFlags::empty()), + formats: Vec::new(), + } + } +} + +// Must have correct representation to be able to mmap format table +#[repr(C)] +pub struct DmabufFormat { + /// Fourcc format + pub format: u32, + _padding: u32, + /// Modifier, or `DRM_FORMAT_MOD_INVALID` for implict modifier + pub modifier: u64, +} + +impl fmt::Debug for DmabufFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmabufFormat") + .field("format", &self.format) + .field("modifier", &self.modifier) + .finish() + } +} + +#[derive(Default)] +pub struct DmabufFeedback { + format_table: Option<(Mmap, usize)>, + main_device: dev_t, + tranches: Vec, +} + +impl fmt::Debug for DmabufFeedback { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmabufFeedback") + .field("format_table", &self.format_table()) + .field("main_device", &self.main_device) + .field("tranches", &self.tranches) + .finish() + } +} + +impl DmabufFeedback { + /// Format/modifier pairs + pub fn format_table(&self) -> &[DmabufFormat] { + self.format_table.as_ref().map_or(&[], |(mmap, len)| unsafe { + slice::from_raw_parts(mmap.as_ptr() as *const DmabufFormat, *len) + }) + } + + /// `dev_t` value for main device. Buffers must be importable from main device. + pub fn main_device(&self) -> dev_t { + self.main_device + } + + /// Tranches in descending order of preference + pub fn tranches(&self) -> &[DmabufFeedbackTranche] { + &self.tranches + } +} + +#[derive(Debug, Default)] +pub struct DmabufFeedbackData { + pending: Mutex, + pending_tranche: Mutex, +} + +#[derive(Debug)] +pub struct DmaBufferData; + +#[derive(Debug)] +pub struct DmabufState { + zwp_linux_dmabuf: GlobalProxy, + modifiers: Vec, +} + +impl DmabufState { + pub fn new(globals: &GlobalList, qh: &QueueHandle) -> Self + where + D: Dispatch + 'static, + { + // Mesa (at least the latest version) also requires version 3 or 4 + let zwp_linux_dmabuf = GlobalProxy::from(globals.bind(qh, 3..=4, GlobalData)); + Self { zwp_linux_dmabuf, modifiers: Vec::new() } + } + + /// Only populated in version `<4` + pub fn modifiers(&self) -> &[DmabufFormat] { + &self.modifiers + } + + /// Supported protocol version, if any + pub fn version(&self) -> Option { + Some(self.zwp_linux_dmabuf.get().ok()?.version()) + } + + /// Create a params object for constructing a buffer + pub fn create_params(&self, qh: &QueueHandle) -> Result + where + D: Dispatch + 'static, + { + let zwp_linux_dmabuf = self.zwp_linux_dmabuf.get()?; + let params = zwp_linux_dmabuf.create_params(qh, GlobalData); + Ok(DmabufParams { params }) + } + + /// Get default dmabuf feedback. Requires version `4`. + pub fn get_default_feedback( + &self, + qh: &QueueHandle, + ) -> Result + where + D: Dispatch + + 'static, + { + let zwp_linux_dmabuf = self.zwp_linux_dmabuf.with_min_version(4)?; + Ok(zwp_linux_dmabuf.get_default_feedback(qh, DmabufFeedbackData::default())) + } + + /// Get default dmabuf feedback for given surface. Requires version `4`. + pub fn get_surface_feedback( + &self, + surface: &wl_surface::WlSurface, + qh: &QueueHandle, + ) -> Result + where + D: Dispatch + + 'static, + { + let zwp_linux_dmabuf = self.zwp_linux_dmabuf.with_min_version(4)?; + Ok(zwp_linux_dmabuf.get_surface_feedback(surface, qh, DmabufFeedbackData::default())) + } +} + +pub trait DmabufHandler: Sized { + fn dmabuf_state(&mut self) -> &mut DmabufState; + + /// Server has sent dmabuf feedback information. This may be received multiple + /// times by a `ZwpLinuxDmabufFeedbackV1` object. + fn dmabuf_feedback( + &mut self, + conn: &Connection, + qh: &QueueHandle, + proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, + feedback: DmabufFeedback, + ); + + /// `wl_buffer` associated with `params` has been created successfully. + fn created( + &mut self, + conn: &Connection, + qh: &QueueHandle, + params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, + buffer: wl_buffer::WlBuffer, + ); + + /// Failed to create `wl_buffer` for `params`. + fn failed( + &mut self, + conn: &Connection, + qh: &QueueHandle, + params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, + ); +} + +#[derive(Debug)] +pub struct DmabufParams { + params: zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, +} + +impl DmabufParams { + /// Add a plane + /// + /// In version `4`, it is a protocol error if `format`/`modifier` pair wasn't + /// advertised as supported. + pub fn add(&self, fd: OwnedFd, plane_idx: u32, offset: u32, stride: u32, modifier: u64) { + let modifier_hi = (modifier >> 32) as u32; + let modifier_lo = (modifier & 0xffffffff) as u32; + self.params.add(fd.into_raw_fd(), plane_idx, offset, stride, modifier_hi, modifier_lo); + } + + /// Create buffer. + /// + /// [`DmabufHandler::created`] or [`DmabufHandler::failed`] will be invoked when the + /// operation succeeds or fails. + pub fn create( + self, + width: i32, + height: i32, + format: u32, + flags: zwp_linux_buffer_params_v1::Flags, + ) -> zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1 { + self.params.create(width, height, format, flags); + self.params + } + + /// Create buffer immediately. + /// + /// On failure buffer is invalid, and server may raise protocol error or + /// send [`DmabufHandler::failed`]. + pub fn create_immed( + self, + width: i32, + height: i32, + format: u32, + flags: zwp_linux_buffer_params_v1::Flags, + qh: &QueueHandle, + ) -> (wl_buffer::WlBuffer, zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1) + where + D: Dispatch + 'static, + { + let buffer = self.params.create_immed(width, height, format, flags, qh, DmaBufferData); + (buffer, self.params) + } +} + +impl Dispatch for DmabufState +where + D: Dispatch + DmabufHandler, +{ + fn event( + state: &mut D, + proxy: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, + event: zwp_linux_dmabuf_v1::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + match event { + zwp_linux_dmabuf_v1::Event::Format { format: _ } => { + // Formats are duplicated by modifier events since version 3. + // Ignore this event, like Mesa does. + } + zwp_linux_dmabuf_v1::Event::Modifier { format, modifier_hi, modifier_lo } => { + if proxy.version() < 4 { + let modifier = (u64::from(modifier_hi) << 32) | u64::from(modifier_lo); + state.dmabuf_state().modifiers.push(DmabufFormat { + format, + _padding: 0, + modifier, + }); + } + } + _ => unreachable!(), + } + } +} + +impl Dispatch + for DmabufState +where + D: Dispatch + + DmabufHandler, +{ + fn event( + state: &mut D, + proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, + event: zwp_linux_dmabuf_feedback_v1::Event, + data: &DmabufFeedbackData, + conn: &Connection, + qh: &QueueHandle, + ) { + match event { + zwp_linux_dmabuf_feedback_v1::Event::Done => { + let feedback = mem::take(&mut *data.pending.lock().unwrap()); + state.dmabuf_feedback(conn, qh, proxy, feedback); + } + zwp_linux_dmabuf_feedback_v1::Event::FormatTable { fd, size } => { + let size = size as usize; + let mmap = unsafe { Mmap::map(&fd).expect("Failed to map format table") }; + assert!(mmap.len() >= size); + let entry_size = mem::size_of::(); + assert!((size % entry_size) == 0); + let len = size / entry_size; + data.pending.lock().unwrap().format_table = Some((mmap, len)); + } + zwp_linux_dmabuf_feedback_v1::Event::MainDevice { device } => { + let device = dev_t::from_ne_bytes(device.try_into().unwrap()); + data.pending.lock().unwrap().main_device = device; + } + zwp_linux_dmabuf_feedback_v1::Event::TrancheDone => { + let tranche = mem::take(&mut *data.pending_tranche.lock().unwrap()); + data.pending.lock().unwrap().tranches.push(tranche); + } + zwp_linux_dmabuf_feedback_v1::Event::TrancheTargetDevice { device } => { + let device = dev_t::from_ne_bytes(device.try_into().unwrap()); + data.pending_tranche.lock().unwrap().device = device; + } + zwp_linux_dmabuf_feedback_v1::Event::TrancheFormats { indices } => { + assert!((indices.len() % 2) == 0); + let indices = + indices.chunks(2).map(|i| u16::from_ne_bytes([i[0], i[1]])).collect::>(); + data.pending_tranche.lock().unwrap().formats = indices; + } + zwp_linux_dmabuf_feedback_v1::Event::TrancheFlags { flags } => { + data.pending_tranche.lock().unwrap().flags = flags; + } + _ => unreachable!(), + } + } +} + +impl Dispatch for DmabufState +where + D: Dispatch + + Dispatch + + DmabufHandler + + 'static, +{ + fn event( + state: &mut D, + proxy: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, + event: zwp_linux_buffer_params_v1::Event, + _: &GlobalData, + conn: &Connection, + qh: &QueueHandle, + ) { + match event { + zwp_linux_buffer_params_v1::Event::Created { buffer } => { + state.created(conn, qh, proxy, buffer); + } + zwp_linux_buffer_params_v1::Event::Failed => { + state.failed(conn, qh, proxy); + } + _ => unreachable!(), + } + } + + wayland_client::event_created_child!(D, zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, [ + zwp_linux_buffer_params_v1::EVT_CREATED_OPCODE => (wl_buffer::WlBuffer, DmaBufferData) + ]); +} + +impl Dispatch for DmabufState +where + D: Dispatch, +{ + fn event( + _state: &mut D, + _proxy: &wl_buffer::WlBuffer, + _event: wl_buffer::Event, + _: &DmaBufferData, + _: &Connection, + _: &QueueHandle, + ) { + } +} + +#[macro_export] +macro_rules! delegate_dmabuf { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1: $crate::globals::GlobalData + ] => $crate::dmabuf::DmabufState + ); + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1: $crate::globals::GlobalData + ] => $crate::dmabuf::DmabufState + ); + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1: $crate::dmabuf::DmabufFeedbackData + ] => $crate::dmabuf::DmabufState + ); + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::client::protocol::wl_buffer::WlBuffer: $crate::dmabuf::DmaBufferData + ] => $crate::dmabuf::DmabufState + ); + }; +} diff --git a/src/lib.rs b/src/lib.rs index 1dfedc4d6..e88cd7738 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ pub mod reexports { } pub mod compositor; +pub mod dmabuf; pub mod error; #[cfg(feature = "calloop")] pub mod event_loop;