smithay/backend/drm/
gbm.rs

1//! Utilities to attach [`framebuffer::Handle`]s to gbm backed buffers
2
3use std::os::unix::io::AsFd;
4
5use thiserror::Error;
6
7use drm::{
8    buffer::PlanarBuffer,
9    control::{framebuffer, Device, FbCmd2Flags},
10};
11use drm_fourcc::DrmModifier;
12use tracing::{trace, warn};
13#[cfg(feature = "wayland_frontend")]
14use wayland_server::protocol::wl_buffer::WlBuffer;
15
16#[cfg(feature = "wayland_frontend")]
17use crate::backend::allocator::Buffer;
18use crate::backend::{
19    allocator::{
20        dmabuf::Dmabuf,
21        format::{get_bpp, get_depth, get_opaque},
22        gbm::GbmBuffer,
23        Fourcc,
24    },
25    drm::DrmDeviceFd,
26};
27use crate::utils::DevPath;
28
29use super::{error::AccessError, warn_legacy_fb_export, Framebuffer};
30
31/// A GBM backed framebuffer
32#[derive(Debug)]
33pub struct GbmFramebuffer {
34    fb: framebuffer::Handle,
35    format: drm_fourcc::DrmFormat,
36    drm: DrmDeviceFd,
37}
38
39impl Drop for GbmFramebuffer {
40    #[inline]
41    fn drop(&mut self) {
42        trace!(fb = ?self.fb, "destroying framebuffer");
43        if let Err(err) = self.drm.destroy_framebuffer(self.fb) {
44            warn!(fb = ?self.fb, ?err, "failed to destroy framebuffer");
45        }
46    }
47}
48
49impl AsRef<framebuffer::Handle> for GbmFramebuffer {
50    #[inline]
51    fn as_ref(&self) -> &framebuffer::Handle {
52        &self.fb
53    }
54}
55
56impl Framebuffer for GbmFramebuffer {
57    #[inline]
58    fn format(&self) -> drm_fourcc::DrmFormat {
59        self.format
60    }
61}
62
63/// Attach a framebuffer for a [`WlBuffer`]
64///
65/// This tries to import the buffer to gbm and attach a [`framebuffer::Handle`] for
66/// the imported [`BufferObject`][gbm::BufferObject].
67///
68/// Returns `Ok(None)` for unknown buffer types and buffer types that do not
69/// support attaching a framebuffer (e.g. shm-buffers)
70#[cfg(feature = "wayland_frontend")]
71#[profiling::function]
72pub fn framebuffer_from_wayland_buffer<A: AsFd + 'static>(
73    drm: &DrmDeviceFd,
74    gbm: &gbm::Device<A>,
75    buffer: &WlBuffer,
76    use_opaque: bool,
77) -> Result<Option<GbmFramebuffer>, Error> {
78    if let Ok(dmabuf) = crate::wayland::dmabuf::get_dmabuf(buffer) {
79        // From weston:
80        /* We should not import to KMS a buffer that has been allocated using no
81         * modifiers. Usually drivers use linear layouts to allocate with no
82         * modifiers, but this is not a rule. The driver could use, for
83         * instance, a tiling layout under the hood - and both Weston and the
84         * KMS driver can't know. So giving the buffer to KMS is not safe, as
85         * not knowing its layout can result in garbage being displayed. In
86         * short, importing a buffer to KMS requires explicit modifiers. */
87        if dmabuf.format().modifier != DrmModifier::Invalid {
88            return Ok(Some(framebuffer_from_dmabuf(
89                drm, gbm, dmabuf, use_opaque, false,
90            )?));
91        }
92    }
93
94    #[cfg(all(feature = "backend_egl", feature = "use_system_lib"))]
95    if matches!(
96        crate::backend::renderer::buffer_type(buffer),
97        Some(crate::backend::renderer::BufferType::Egl)
98    ) {
99        let bo = gbm
100            .import_buffer_object_from_wayland::<()>(buffer, gbm::BufferObjectFlags::SCANOUT)
101            .map(|bo| GbmBuffer::from_bo(bo, true))
102            .map_err(Error::Import)?;
103        let (fb, format) = framebuffer_from_bo_internal(
104            drm,
105            BufferObjectInternal {
106                bo: &bo,
107                offsets: None,
108                pitches: None,
109            },
110            use_opaque,
111            true,
112        )
113        .map_err(Error::Drm)?;
114
115        return Ok(Some(GbmFramebuffer {
116            fb,
117            format,
118            drm: drm.clone(),
119        }));
120    }
121
122    Ok(None)
123}
124
125/// Possible errors for attaching a [`framebuffer::Handle`]
126#[derive(Error, Debug)]
127pub enum Error {
128    /// Importing the [`Dmabuf`] to gbm failed
129    #[error("failed to import the dmabuf to gbm")]
130    Import(std::io::Error),
131    /// Failed to add a framebuffer for the bo
132    #[error("failed to add a framebuffer for the bo")]
133    Drm(AccessError),
134}
135
136/// Attach a framebuffer for a [`Dmabuf`]
137///
138/// This tries to import the [`Dmabuf`] using gbm and attach
139/// a [`framebuffer::Handle`] for the imported [`BufferObject`][gbm::BufferObject]
140#[profiling::function]
141pub fn framebuffer_from_dmabuf<A: AsFd + 'static>(
142    drm: &DrmDeviceFd,
143    gbm: &gbm::Device<A>,
144    dmabuf: &Dmabuf,
145    use_opaque: bool,
146    allow_legacy: bool,
147) -> Result<GbmFramebuffer, Error> {
148    let bo: GbmBuffer = dmabuf
149        .import_to(gbm, gbm::BufferObjectFlags::SCANOUT)
150        .map_err(Error::Import)?;
151
152    // We override the offsets and pitches here cause the imported bo
153    // can return the wrong values. bo will only return the correct values
154    // for buffers we have allocated, but not for all client provided buffers.
155    let mut offsets: [u32; 4] = [0; 4];
156    let mut pitches: [u32; 4] = [0; 4];
157
158    for (index, offset) in dmabuf.offsets().enumerate() {
159        offsets[index] = offset;
160    }
161
162    for (index, stride) in dmabuf.strides().enumerate() {
163        pitches[index] = stride;
164    }
165
166    framebuffer_from_bo_internal(
167        drm,
168        BufferObjectInternal {
169            bo: &bo,
170            offsets: Some(offsets),
171            pitches: Some(pitches),
172        },
173        use_opaque,
174        allow_legacy,
175    )
176    .map_err(Error::Drm)
177    .map(|(fb, format)| GbmFramebuffer {
178        fb,
179        format,
180        drm: drm.clone(),
181    })
182}
183
184/// Attach a [`framebuffer::Handle`] to an [`BufferObject`][gbm::BufferObject]
185#[profiling::function]
186pub fn framebuffer_from_bo(
187    drm: &DrmDeviceFd,
188    bo: &GbmBuffer,
189    use_opaque: bool,
190) -> Result<GbmFramebuffer, AccessError> {
191    framebuffer_from_bo_internal(
192        drm,
193        BufferObjectInternal {
194            bo,
195            offsets: None,
196            pitches: None,
197        },
198        use_opaque,
199        true,
200    )
201    .map(|(fb, format)| GbmFramebuffer {
202        fb,
203        format,
204        drm: drm.clone(),
205    })
206}
207
208struct BufferObjectInternal<'a> {
209    bo: &'a GbmBuffer,
210    pitches: Option<[u32; 4]>,
211    offsets: Option<[u32; 4]>,
212}
213
214impl std::ops::Deref for BufferObjectInternal<'_> {
215    type Target = GbmBuffer;
216
217    #[inline]
218    fn deref(&self) -> &Self::Target {
219        self.bo
220    }
221}
222
223impl PlanarBuffer for BufferObjectInternal<'_> {
224    #[inline]
225    fn size(&self) -> (u32, u32) {
226        PlanarBuffer::size(self.bo)
227    }
228
229    #[inline]
230    fn format(&self) -> drm_fourcc::DrmFourcc {
231        PlanarBuffer::format(self.bo)
232    }
233
234    #[inline]
235    fn modifier(&self) -> Option<DrmModifier> {
236        PlanarBuffer::modifier(self.bo)
237    }
238
239    #[inline]
240    fn pitches(&self) -> [u32; 4] {
241        self.pitches.unwrap_or_else(|| PlanarBuffer::pitches(self.bo))
242    }
243
244    #[inline]
245    fn handles(&self) -> [Option<drm::buffer::Handle>; 4] {
246        PlanarBuffer::handles(self.bo)
247    }
248
249    #[inline]
250    fn offsets(&self) -> [u32; 4] {
251        self.offsets.unwrap_or_else(|| PlanarBuffer::offsets(self.bo))
252    }
253}
254
255struct OpaqueBufferWrapper<'a, B>(&'a B);
256impl<B> PlanarBuffer for OpaqueBufferWrapper<'_, B>
257where
258    B: PlanarBuffer,
259{
260    #[inline]
261    fn size(&self) -> (u32, u32) {
262        self.0.size()
263    }
264
265    #[inline]
266    fn format(&self) -> Fourcc {
267        let fmt = self.0.format();
268        get_opaque(fmt).unwrap_or(fmt)
269    }
270
271    #[inline]
272    fn modifier(&self) -> Option<DrmModifier> {
273        self.0.modifier()
274    }
275
276    #[inline]
277    fn pitches(&self) -> [u32; 4] {
278        self.0.pitches()
279    }
280
281    #[inline]
282    fn handles(&self) -> [Option<drm::buffer::Handle>; 4] {
283        self.0.handles()
284    }
285
286    #[inline]
287    fn offsets(&self) -> [u32; 4] {
288        self.0.offsets()
289    }
290}
291
292#[profiling::function]
293#[inline]
294fn framebuffer_from_bo_internal<D>(
295    drm: &D,
296    bo: BufferObjectInternal<'_>,
297    use_opaque: bool,
298    allow_legacy: bool,
299) -> Result<(framebuffer::Handle, drm_fourcc::DrmFormat), AccessError>
300where
301    D: drm::control::Device + DevPath,
302{
303    let modifier = bo.modifier();
304    let flags = if bo.modifier().is_some() {
305        FbCmd2Flags::MODIFIERS
306    } else {
307        FbCmd2Flags::empty()
308    };
309
310    let ret = if use_opaque {
311        let opaque_wrapper = OpaqueBufferWrapper(&bo);
312        drm.add_planar_framebuffer(&opaque_wrapper, flags).map(|fb| {
313            (
314                fb,
315                drm_fourcc::DrmFormat {
316                    code: opaque_wrapper.format(),
317                    modifier: modifier.unwrap_or(DrmModifier::Invalid),
318                },
319            )
320        })
321    } else {
322        drm.add_planar_framebuffer(&bo, flags).map(|fb| {
323            (
324                fb,
325                drm_fourcc::DrmFormat {
326                    code: bo.format(),
327                    modifier: modifier.unwrap_or(DrmModifier::Invalid),
328                },
329            )
330        })
331    };
332
333    let (fb, format) = match ret {
334        Ok(fb) => fb,
335        Err(source) => {
336            if !allow_legacy {
337                return Err(AccessError {
338                    errmsg: "Failed to add framebuffer",
339                    dev: drm.dev_path(),
340                    source,
341                });
342            }
343
344            // We only support this as a fallback of last resort like xf86-video-modesetting does.
345            warn_legacy_fb_export();
346
347            if bo.plane_count() > 1 {
348                return Err(AccessError {
349                    errmsg: "Failed to add framebuffer",
350                    dev: drm.dev_path(),
351                    source,
352                });
353            }
354
355            let fourcc = bo.format();
356            let (depth, bpp) = get_depth(fourcc)
357                .and_then(|d| get_bpp(fourcc).map(|b| (d, b)))
358                .ok_or_else(|| AccessError {
359                    errmsg: "Unknown format for legacy framebuffer",
360                    dev: drm.dev_path(),
361                    source,
362                })?;
363
364            let fb = drm
365                .add_framebuffer(&*bo, depth as u32, bpp as u32)
366                .map_err(|source| AccessError {
367                    errmsg: "Failed to add framebuffer",
368                    dev: drm.dev_path(),
369                    source,
370                })?;
371            (
372                fb,
373                drm_fourcc::DrmFormat {
374                    code: fourcc,
375                    modifier: drm_fourcc::DrmModifier::Invalid,
376                },
377            )
378        }
379    };
380    Ok((fb, format))
381}