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}