smithay_client_toolkit/
dmabuf.rs

1use crate::{error::GlobalError, globals::GlobalData, registry::GlobalProxy};
2use memmap2::{Mmap, MmapOptions};
3use std::{fmt, mem, os::unix::io::BorrowedFd, slice, sync::Mutex};
4use wayland_client::{
5    globals::GlobalList,
6    protocol::{wl_buffer, wl_surface},
7    Connection, Dispatch, Proxy, QueueHandle, WEnum,
8};
9use wayland_protocols::wp::linux_dmabuf::zv1::client::{
10    zwp_linux_buffer_params_v1,
11    zwp_linux_dmabuf_feedback_v1::{self, TrancheFlags},
12    zwp_linux_dmabuf_v1,
13};
14
15// Workaround until `libc` updates to FreeBSD 12 ABI
16#[cfg(target_os = "freebsd")]
17type dev_t = u64;
18#[cfg(not(target_os = "freebsd"))]
19use libc::dev_t;
20
21/// A preference tranche of dmabuf formats
22#[derive(Debug)]
23pub struct DmabufFeedbackTranche {
24    /// `dev_t` value for preferred target device. May be scan-out or
25    /// renderer device.
26    pub device: dev_t,
27    /// Flags for tranche
28    pub flags: WEnum<TrancheFlags>,
29    /// Indices of formats in the format table
30    pub formats: Vec<u16>,
31}
32
33impl Default for DmabufFeedbackTranche {
34    fn default() -> DmabufFeedbackTranche {
35        DmabufFeedbackTranche {
36            device: 0,
37            flags: WEnum::Value(TrancheFlags::empty()),
38            formats: Vec::new(),
39        }
40    }
41}
42
43/// A single dmabuf format/modifier pair
44// Must have correct representation to be able to mmap format table
45#[repr(C)]
46pub struct DmabufFormat {
47    /// Fourcc format
48    pub format: u32,
49    _padding: u32,
50    /// Modifier, or `DRM_FORMAT_MOD_INVALID` for implict modifier
51    pub modifier: u64,
52}
53
54impl fmt::Debug for DmabufFormat {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        f.debug_struct("DmabufFormat")
57            .field("format", &self.format)
58            .field("modifier", &self.modifier)
59            .finish()
60    }
61}
62
63/// Description of supported and preferred dmabuf formats
64#[derive(Default)]
65pub struct DmabufFeedback {
66    format_table: Option<(Mmap, usize)>,
67    main_device: dev_t,
68    tranches: Vec<DmabufFeedbackTranche>,
69}
70
71impl fmt::Debug for DmabufFeedback {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        f.debug_struct("DmabufFeedback")
74            .field("format_table", &self.format_table())
75            .field("main_device", &self.main_device)
76            .field("tranches", &self.tranches)
77            .finish()
78    }
79}
80
81impl DmabufFeedback {
82    /// Format/modifier pairs
83    pub fn format_table(&self) -> &[DmabufFormat] {
84        self.format_table.as_ref().map_or(&[], |(mmap, len)| unsafe {
85            slice::from_raw_parts(mmap.as_ptr() as *const DmabufFormat, *len)
86        })
87    }
88
89    /// `dev_t` value for main device. Buffers must be importable from main device.
90    pub fn main_device(&self) -> dev_t {
91        self.main_device
92    }
93
94    /// Tranches in descending order of preference
95    pub fn tranches(&self) -> &[DmabufFeedbackTranche] {
96        &self.tranches
97    }
98}
99
100#[doc(hidden)]
101#[derive(Debug, Default)]
102pub struct DmabufFeedbackData {
103    pending: Mutex<DmabufFeedback>,
104    pending_tranche: Mutex<DmabufFeedbackTranche>,
105}
106
107#[doc(hidden)]
108#[derive(Debug)]
109pub struct DmaBufferData;
110
111/// A handler for [`zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1`]
112#[derive(Debug)]
113pub struct DmabufState {
114    zwp_linux_dmabuf: GlobalProxy<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>,
115    modifiers: Vec<DmabufFormat>,
116}
117
118impl DmabufState {
119    /// Bind `zwp_linux_dmabuf_v1` global version 3 or 4, if it exists.
120    ///
121    /// This does not fail if the global does not exist.
122    pub fn new<D>(globals: &GlobalList, qh: &QueueHandle<D>) -> Self
123    where
124        D: Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, GlobalData> + 'static,
125    {
126        // Mesa (at least the latest version) also requires version 3 or 4
127        let zwp_linux_dmabuf = GlobalProxy::from(globals.bind(qh, 3..=5, GlobalData));
128        Self { zwp_linux_dmabuf, modifiers: Vec::new() }
129    }
130
131    /// Only populated in version `<4`
132    ///
133    /// On version `4`, use [`DmabufState::get_surface_feedback`].
134    pub fn modifiers(&self) -> &[DmabufFormat] {
135        &self.modifiers
136    }
137
138    /// Supported protocol version, if any
139    pub fn version(&self) -> Option<u32> {
140        Some(self.zwp_linux_dmabuf.get().ok()?.version())
141    }
142
143    /// Create a params object for constructing a buffer
144    ///
145    /// Errors if `zwp_linux_dmabuf_v1` does not exist or has unsupported
146    /// version. An application can then fallback to using `shm` buffers.
147    pub fn create_params<D>(&self, qh: &QueueHandle<D>) -> Result<DmabufParams, GlobalError>
148    where
149        D: Dispatch<zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, GlobalData> + 'static,
150    {
151        let zwp_linux_dmabuf = self.zwp_linux_dmabuf.get()?;
152        let params = zwp_linux_dmabuf.create_params(qh, GlobalData);
153        Ok(DmabufParams { params })
154    }
155
156    /// Get default dmabuf feedback. Requires version `4`.
157    ///
158    /// On version `3`, use [`DmabufState::modifiers`].
159    pub fn get_default_feedback<D>(
160        &self,
161        qh: &QueueHandle<D>,
162    ) -> Result<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, GlobalError>
163    where
164        D: Dispatch<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, DmabufFeedbackData>
165            + 'static,
166    {
167        let zwp_linux_dmabuf = self.zwp_linux_dmabuf.with_min_version(4)?;
168        Ok(zwp_linux_dmabuf.get_default_feedback(qh, DmabufFeedbackData::default()))
169    }
170
171    /// Get default dmabuf feedback for given surface. Requires version `4`.
172    ///
173    /// On version `3`, use [`DmabufState::modifiers`].
174    pub fn get_surface_feedback<D>(
175        &self,
176        surface: &wl_surface::WlSurface,
177        qh: &QueueHandle<D>,
178    ) -> Result<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, GlobalError>
179    where
180        D: Dispatch<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, DmabufFeedbackData>
181            + 'static,
182    {
183        let zwp_linux_dmabuf = self.zwp_linux_dmabuf.with_min_version(4)?;
184        Ok(zwp_linux_dmabuf.get_surface_feedback(surface, qh, DmabufFeedbackData::default()))
185    }
186}
187
188pub trait DmabufHandler: Sized {
189    fn dmabuf_state(&mut self) -> &mut DmabufState;
190
191    /// Server has sent dmabuf feedback information. This may be received multiple
192    /// times by a `ZwpLinuxDmabufFeedbackV1` object.
193    fn dmabuf_feedback(
194        &mut self,
195        conn: &Connection,
196        qh: &QueueHandle<Self>,
197        proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
198        feedback: DmabufFeedback,
199    );
200
201    /// `wl_buffer` associated with `params` has been created successfully.
202    fn created(
203        &mut self,
204        conn: &Connection,
205        qh: &QueueHandle<Self>,
206        params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
207        buffer: wl_buffer::WlBuffer,
208    );
209
210    /// Failed to create `wl_buffer` for `params`.
211    fn failed(
212        &mut self,
213        conn: &Connection,
214        qh: &QueueHandle<Self>,
215        params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
216    );
217
218    /// Compositor has released a `wl_buffer` created through [`DmabufParams`].
219    fn released(&mut self, conn: &Connection, qh: &QueueHandle<Self>, buffer: &wl_buffer::WlBuffer);
220}
221
222/// Builder for a dmabuf backed buffer
223#[derive(Debug)]
224pub struct DmabufParams {
225    params: zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
226}
227
228impl DmabufParams {
229    /// Add a plane
230    ///
231    /// In version `4`, it is a protocol error if `format`/`modifier` pair wasn't
232    /// advertised as supported.
233    ///
234    /// `modifier` should be the same for all planes. It is a protocol error in version `5` if
235    /// they differ.
236    pub fn add(&self, fd: BorrowedFd<'_>, plane_idx: u32, offset: u32, stride: u32, modifier: u64) {
237        let modifier_hi = (modifier >> 32) as u32;
238        let modifier_lo = (modifier & 0xffffffff) as u32;
239        self.params.add(fd, plane_idx, offset, stride, modifier_hi, modifier_lo);
240    }
241
242    /// Create buffer.
243    ///
244    /// [`DmabufHandler::created`] or [`DmabufHandler::failed`] will be invoked when the
245    /// operation succeeds or fails.
246    pub fn create(
247        self,
248        width: i32,
249        height: i32,
250        format: u32,
251        flags: zwp_linux_buffer_params_v1::Flags,
252    ) -> zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1 {
253        self.params.create(width, height, format, flags);
254        self.params
255    }
256
257    /// Create buffer immediately.
258    ///
259    /// On failure buffer is invalid, and server may raise protocol error or
260    /// send [`DmabufHandler::failed`].
261    pub fn create_immed<D>(
262        self,
263        width: i32,
264        height: i32,
265        format: u32,
266        flags: zwp_linux_buffer_params_v1::Flags,
267        qh: &QueueHandle<D>,
268    ) -> (wl_buffer::WlBuffer, zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1)
269    where
270        D: Dispatch<wl_buffer::WlBuffer, DmaBufferData> + 'static,
271    {
272        let buffer = self.params.create_immed(width, height, format, flags, qh, DmaBufferData);
273        (buffer, self.params)
274    }
275}
276
277impl<D> Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, GlobalData, D> for DmabufState
278where
279    D: Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, GlobalData> + DmabufHandler,
280{
281    fn event(
282        state: &mut D,
283        proxy: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
284        event: zwp_linux_dmabuf_v1::Event,
285        _: &GlobalData,
286        _: &Connection,
287        _: &QueueHandle<D>,
288    ) {
289        match event {
290            zwp_linux_dmabuf_v1::Event::Format { format: _ } => {
291                // Formats are duplicated by modifier events since version 3.
292                // Ignore this event, like Mesa does.
293            }
294            zwp_linux_dmabuf_v1::Event::Modifier { format, modifier_hi, modifier_lo } => {
295                if proxy.version() < 4 {
296                    let modifier = (u64::from(modifier_hi) << 32) | u64::from(modifier_lo);
297                    state.dmabuf_state().modifiers.push(DmabufFormat {
298                        format,
299                        _padding: 0,
300                        modifier,
301                    });
302                }
303            }
304            _ => unreachable!(),
305        }
306    }
307}
308
309impl<D> Dispatch<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, DmabufFeedbackData, D>
310    for DmabufState
311where
312    D: Dispatch<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, DmabufFeedbackData>
313        + DmabufHandler,
314{
315    fn event(
316        state: &mut D,
317        proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
318        event: zwp_linux_dmabuf_feedback_v1::Event,
319        data: &DmabufFeedbackData,
320        conn: &Connection,
321        qh: &QueueHandle<D>,
322    ) {
323        match event {
324            zwp_linux_dmabuf_feedback_v1::Event::Done => {
325                let feedback = mem::take(&mut *data.pending.lock().unwrap());
326                state.dmabuf_feedback(conn, qh, proxy, feedback);
327            }
328            zwp_linux_dmabuf_feedback_v1::Event::FormatTable { fd, size } => {
329                let size = size as usize;
330                let mmap = unsafe {
331                    MmapOptions::new().map_copy_read_only(&fd).expect("Failed to map format table")
332                };
333                assert!(mmap.len() >= size);
334                let entry_size = mem::size_of::<DmabufFormat>();
335                assert!((size % entry_size) == 0);
336                let len = size / entry_size;
337                data.pending.lock().unwrap().format_table = Some((mmap, len));
338            }
339            zwp_linux_dmabuf_feedback_v1::Event::MainDevice { device } => {
340                let device = dev_t::from_ne_bytes(device.try_into().unwrap());
341                data.pending.lock().unwrap().main_device = device;
342            }
343            zwp_linux_dmabuf_feedback_v1::Event::TrancheDone => {
344                let tranche = mem::take(&mut *data.pending_tranche.lock().unwrap());
345                data.pending.lock().unwrap().tranches.push(tranche);
346            }
347            zwp_linux_dmabuf_feedback_v1::Event::TrancheTargetDevice { device } => {
348                let device = dev_t::from_ne_bytes(device.try_into().unwrap());
349                data.pending_tranche.lock().unwrap().device = device;
350            }
351            zwp_linux_dmabuf_feedback_v1::Event::TrancheFormats { indices } => {
352                assert!((indices.len() % 2) == 0);
353                let indices =
354                    indices.chunks(2).map(|i| u16::from_ne_bytes([i[0], i[1]])).collect::<Vec<_>>();
355                data.pending_tranche.lock().unwrap().formats = indices;
356            }
357            zwp_linux_dmabuf_feedback_v1::Event::TrancheFlags { flags } => {
358                data.pending_tranche.lock().unwrap().flags = flags;
359            }
360            _ => unreachable!(),
361        }
362    }
363}
364
365impl<D> Dispatch<zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, GlobalData, D> for DmabufState
366where
367    D: Dispatch<zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, GlobalData>
368        + Dispatch<wl_buffer::WlBuffer, DmaBufferData>
369        + DmabufHandler
370        + 'static,
371{
372    fn event(
373        state: &mut D,
374        proxy: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
375        event: zwp_linux_buffer_params_v1::Event,
376        _: &GlobalData,
377        conn: &Connection,
378        qh: &QueueHandle<D>,
379    ) {
380        match event {
381            zwp_linux_buffer_params_v1::Event::Created { buffer } => {
382                state.created(conn, qh, proxy, buffer);
383            }
384            zwp_linux_buffer_params_v1::Event::Failed => {
385                state.failed(conn, qh, proxy);
386            }
387            _ => unreachable!(),
388        }
389    }
390
391    wayland_client::event_created_child!(D, zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, [
392        zwp_linux_buffer_params_v1::EVT_CREATED_OPCODE => (wl_buffer::WlBuffer, DmaBufferData)
393    ]);
394}
395
396impl<D> Dispatch<wl_buffer::WlBuffer, DmaBufferData, D> for DmabufState
397where
398    D: Dispatch<wl_buffer::WlBuffer, DmaBufferData> + DmabufHandler,
399{
400    fn event(
401        state: &mut D,
402        proxy: &wl_buffer::WlBuffer,
403        event: wl_buffer::Event,
404        _: &DmaBufferData,
405        conn: &Connection,
406        qh: &QueueHandle<D>,
407    ) {
408        match event {
409            wl_buffer::Event::Release => state.released(conn, qh, proxy),
410            _ => unreachable!(),
411        }
412    }
413}
414
415#[macro_export]
416macro_rules! delegate_dmabuf {
417    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
418        $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
419            [
420                $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1: $crate::globals::GlobalData
421            ] => $crate::dmabuf::DmabufState
422        );
423        $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
424            [
425                $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1: $crate::globals::GlobalData
426            ] => $crate::dmabuf::DmabufState
427        );
428        $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
429            [
430                $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1: $crate::dmabuf::DmabufFeedbackData
431            ] => $crate::dmabuf::DmabufState
432        );
433        $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
434            [
435                $crate::reexports::client::protocol::wl_buffer::WlBuffer: $crate::dmabuf::DmaBufferData
436            ] => $crate::dmabuf::DmabufState
437        );
438    };
439}