Skip to main content

drmkit_gbm/
buffer.rs

1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4use std::os::fd::{FromRawFd as _, OwnedFd};
5use std::sync::Arc;
6
7use crate::device::{DeviceInner, SCANOUT_RENDERING};
8use crate::ffi::{self, BoPtr};
9use crate::surface::SurfaceInner;
10use crate::{GbmDevice, GbmError};
11
12/// How a CPU mapping will be used.
13///
14/// GBM needs this up front: a driver may have to move or detile a buffer to
15/// make it CPU-readable, and it can skip that when the caller only writes.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[allow(
18    clippy::exhaustive_enums,
19    reason = "a closed set: there is no further variant to add"
20)]
21pub enum MapAccess {
22    /// Read the existing pixels.
23    Read,
24    /// Overwrite them.
25    Write,
26    /// Both.
27    ReadWrite,
28}
29
30/// Who gives the buffer back, and how.
31enum Owner {
32    /// Allocated here: `gbm_bo_destroy`.
33    Allocated,
34    /// Locked from a surface: `gbm_surface_release_buffer`, which hands it
35    /// back to the swap chain rather than freeing it. Holding the surface is
36    /// what keeps it alive until then.
37    Surface(Arc<SurfaceInner>),
38}
39
40/// A GBM buffer: one this crate allocated, or one locked from a
41/// [`GbmSurface`](crate::GbmSurface).
42pub struct GbmBuffer {
43    ptr: BoPtr,
44    owner: Owner,
45    /// The device and the descriptor the buffer's GEM handle lives on. Freed
46    /// after the buffer, since fields drop after `Drop::drop` runs; see
47    /// [`GbmDevice`].
48    device: Arc<DeviceInner>,
49}
50
51// SAFETY: as `DeviceInner`'s: a `gbm_bo` is not tied to a thread, and the
52// pointer is given back exactly once, on drop.
53unsafe impl Send for GbmBuffer {}
54// SAFETY: as above; every `&self` method only reads the buffer.
55unsafe impl Sync for GbmBuffer {}
56
57impl Drop for GbmBuffer {
58    fn drop(&mut self) {
59        let gbm = self.device.gbm;
60        match &self.owner {
61            // SAFETY: allocated by `gbm_bo_create*` and not yet destroyed.
62            Owner::Allocated => unsafe { (gbm.bo_destroy)(self.ptr) },
63            // SAFETY: locked from this surface, which `owner` keeps alive, and
64            // not yet released.
65            Owner::Surface(surface) => unsafe {
66                (gbm.surface_release_buffer)(surface.ptr, self.ptr);
67            },
68        }
69    }
70}
71
72impl std::fmt::Debug for GbmBuffer {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        f.debug_struct("GbmBuffer")
75            .field("width", &self.width())
76            .field("height", &self.height())
77            .field("modifier", &format_args!("{:#x}", self.modifier()))
78            .finish_non_exhaustive()
79    }
80}
81
82impl GbmBuffer {
83    /// Take ownership of a buffer locked from `surface`.
84    pub(crate) fn locked(ptr: BoPtr, surface: Arc<SurfaceInner>) -> Self {
85        Self {
86            ptr,
87            device: Arc::clone(&surface.device),
88            owner: Owner::Surface(surface),
89        }
90    }
91
92    /// Allocate a scanout-capable buffer.
93    ///
94    /// The driver chooses the layout and reports it through
95    /// [`modifier`](Self::modifier). Asking for scanout up front matters: a
96    /// buffer allocated only for rendering may land in a layout the display
97    /// engine cannot read, and the failure then surfaces at commit time as a
98    /// rejected framebuffer rather than here.
99    ///
100    /// # Errors
101    ///
102    /// [`GbmError::Allocation`] if the driver refuses the format or size.
103    pub fn create(
104        device: &GbmDevice,
105        width: u32,
106        height: u32,
107        fourcc: u32,
108    ) -> Result<Self, GbmError> {
109        let inner = &device.inner;
110        // SAFETY: the device is live.
111        let ptr =
112            unsafe { (inner.gbm.bo_create)(inner.ptr, width, height, fourcc, SCANOUT_RENDERING) };
113        if ptr.is_null() {
114            return Err(GbmError::Allocation(ffi::last_error("gbm_bo_create")));
115        }
116        Ok(Self {
117            ptr,
118            owner: Owner::Allocated,
119            device: Arc::clone(inner),
120        })
121    }
122
123    /// Allocate a scanout-capable buffer, constrained to `modifiers`.
124    ///
125    /// The driver picks one of them and reports which through
126    /// [`modifier`](Self::modifier). Constraining matters when the display
127    /// engine and the GPU disagree about layouts: left to itself the driver
128    /// picks what renders fastest, which on a tiling GPU is a layout no plane
129    /// can scan out, and the failure then surfaces at commit time as a rejected
130    /// framebuffer rather than here.
131    ///
132    /// An empty list means "no constraint" and falls back to
133    /// [`create`](Self::create). So does a driver that refuses every modifier
134    /// offered, or a libgbm with no modifier entry point at all: the caller
135    /// gets a buffer whose `modifier` may be outside the list it asked for,
136    /// which is why the modifier is worth reading back rather than assumed.
137    /// Drivers substitute on their own too -- measured, the SA8155P's GBM
138    /// answers a tiled request with linear, and NXP's with `INVALID` -- so the
139    /// fallback is not the only way to get there.
140    ///
141    /// `gbm_bo_create_with_modifiers2` is used where the library has it, for
142    /// its usage flags, and `gbm_bo_create_with_modifiers` where it does not
143    /// -- the SA8155P's libgbm has only the latter (P-39).
144    ///
145    /// **The usage is not the same on the two.** v2 is asked for
146    /// `SCANOUT | RENDERING`. v1 takes no usage at all, and Mesa's v1 does
147    /// not mean "none": it calls v2 with `SCANOUT` alone, preserving the
148    /// requirement older callers relied on, so `RENDERING` is dropped. A
149    /// vendor libgbm's v1 means whatever it means. Usage moves the layouts a
150    /// driver honors -- v3d refuses every non-linear one once `SCANOUT` is in
151    /// it (P-13) -- so a set of honored modifiers measured through v1 answers
152    /// a different request from one measured through v2, and the per-board
153    /// floors are only comparable between boards on the same entry point.
154    /// The SA8155P's are v1; every Mesa board here is v2.
155    ///
156    /// # Errors
157    ///
158    /// [`GbmError::Allocation`] if the driver refuses the format or the size.
159    /// Refusing every modifier offered is not an error; see above.
160    pub fn create_with_modifiers(
161        device: &GbmDevice,
162        width: u32,
163        height: u32,
164        fourcc: u32,
165        modifiers: &[u64],
166    ) -> Result<Self, GbmError> {
167        if modifiers.is_empty() {
168            return Self::create(device, width, height, fourcc);
169        }
170        let inner = &device.inner;
171        let Ok(count) = u32::try_from(modifiers.len()) else {
172            return Self::create(device, width, height, fourcc);
173        };
174        let mut ptr = std::ptr::null_mut();
175        if let Some(create) = inner.gbm.bo_create_with_modifiers2 {
176            // SAFETY: the device is live; `modifiers` is `count` long and only
177            // read for the duration of the call.
178            ptr = unsafe {
179                create(
180                    inner.ptr,
181                    width,
182                    height,
183                    fourcc,
184                    modifiers.as_ptr(),
185                    count,
186                    SCANOUT_RENDERING,
187                )
188            };
189        } else if let Some(create) = inner.gbm.bo_create_with_modifiers {
190            // SAFETY: as above.
191            ptr = unsafe { create(inner.ptr, width, height, fourcc, modifiers.as_ptr(), count) };
192        }
193        if ptr.is_null() {
194            return Self::create(device, width, height, fourcc);
195        }
196        Ok(Self {
197            ptr,
198            owner: Owner::Allocated,
199            device: Arc::clone(inner),
200        })
201    }
202
203    /// Width in pixels.
204    #[must_use]
205    pub fn width(&self) -> u32 {
206        // SAFETY: the buffer is live.
207        unsafe { (self.device.gbm.bo_get_width)(self.ptr) }
208    }
209
210    /// Height in pixels.
211    #[must_use]
212    pub fn height(&self) -> u32 {
213        // SAFETY: the buffer is live.
214        unsafe { (self.device.gbm.bo_get_height)(self.ptr) }
215    }
216
217    /// Bytes per row.
218    #[must_use]
219    pub fn stride(&self) -> u32 {
220        // SAFETY: the buffer is live.
221        unsafe { (self.device.gbm.bo_get_stride)(self.ptr) }
222    }
223
224    /// The DRM `FourCC` the driver allocated.
225    #[must_use]
226    pub fn fourcc(&self) -> u32 {
227        // SAFETY: the buffer is live.
228        unsafe { (self.device.gbm.bo_get_format)(self.ptr) }
229    }
230
231    /// The layout modifier the driver chose.
232    ///
233    /// Not a formality. A tiled or compressed buffer scanned out as though it
234    /// were linear is not slightly wrong — it is unreadable — so this has to
235    /// travel with the buffer to whatever registers the framebuffer.
236    ///
237    /// `DRM_FORMAT_MOD_INVALID` from a libgbm older than Mesa 17.0, which
238    /// cannot say: the layout is the driver's implicit one, and a framebuffer
239    /// is registered without a modifier.
240    #[must_use]
241    pub fn modifier(&self) -> u64 {
242        const INVALID: u64 = (1 << 56) - 1;
243        self.device.gbm.bo_get_modifier.map_or(INVALID, |get| {
244            // SAFETY: the buffer is live.
245            unsafe { get(self.ptr) }
246        })
247    }
248
249    /// How many planes the layout uses.
250    ///
251    /// One from a libgbm older than Mesa 17.0, which cannot say and allocates
252    /// nothing else; `0` if libgbm reports an error.
253    #[must_use]
254    pub fn plane_count(&self) -> u32 {
255        self.device.gbm.bo_get_plane_count.map_or(1, |get| {
256            // SAFETY: the buffer is live.
257            let count = unsafe { get(self.ptr) };
258            u32::try_from(count).unwrap_or(0)
259        })
260    }
261
262    /// Plane 0's GEM handle on the device's descriptor, which is what a
263    /// framebuffer is registered over.
264    ///
265    /// Plane 0 only: `gbm_bo_get_handle` has no plane argument, and the
266    /// per-plane entry points are not bound. For a layout with more than one
267    /// [`plane_count`](Self::plane_count) this is part of the answer, not all
268    /// of it.
269    #[must_use]
270    pub fn handle(&self) -> u32 {
271        // SAFETY: the buffer is live.
272        let handle = unsafe { (self.device.gbm.bo_get_handle)(self.ptr) };
273        // SAFETY: every arm of the union is plain data, and the 32-bit one is
274        // the one a DRM GEM handle uses.
275        unsafe { handle.u32_ }
276    }
277
278    /// Export a dma-buf descriptor for the buffer.
279    ///
280    /// The caller owns it, and each call makes a new one. This is how a GBM
281    /// buffer reaches anything outside this process, or reaches KMS through
282    /// the external-source path.
283    ///
284    /// Plane 0's, as [`handle`](Self::handle) is: on a multi-plane layout the
285    /// other planes usually share the descriptor at their own offsets, but
286    /// this does not say where, so check [`plane_count`](Self::plane_count)
287    /// first.
288    ///
289    /// # Errors
290    ///
291    /// [`GbmError::Allocation`] if the driver cannot export it.
292    pub fn export(&self) -> Result<OwnedFd, GbmError> {
293        // SAFETY: the buffer is live.
294        let fd = unsafe { (self.device.gbm.bo_get_fd)(self.ptr) };
295        if fd < 0 {
296            return Err(GbmError::Allocation(ffi::last_error("gbm_bo_get_fd")));
297        }
298        // SAFETY: `gbm_bo_get_fd` returns a new descriptor the caller owns.
299        Ok(unsafe { OwnedFd::from_raw_fd(fd) })
300    }
301
302    /// The `struct gbm_bo *`, for an API that takes one: an EGL image import,
303    /// say.
304    ///
305    /// Borrowed: valid while this buffer is alive.
306    #[must_use]
307    pub fn as_raw(&self) -> *mut std::ffi::c_void {
308        self.ptr
309    }
310}