1 // Copyright 2025 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::cell::RefCell;
6 use std::fs::File;
7 use std::iter::zip;
8 use std::os::fd::AsRawFd;
9 #[cfg(feature = "v4l2")]
10 use std::os::fd::BorrowedFd;
11 use std::os::fd::FromRawFd;
12 use std::path::Path;
13 use std::ptr;
14 #[cfg(feature = "vaapi")]
15 use std::rc::Rc;
16 use std::slice;
17 use std::sync::Arc;
18
19 use crate::utils::align_up;
20 use crate::utils::buffer_size_for_area;
21 use crate::video_frame::generic_dma_video_frame::GenericDmaVideoFrame;
22 use crate::video_frame::ReadMapping;
23 use crate::video_frame::VideoFrame;
24 use crate::video_frame::WriteMapping;
25 #[cfg(feature = "vaapi")]
26 use crate::DecodedFormat;
27 use crate::Fourcc;
28 use crate::FrameLayout;
29 use crate::PlaneLayout;
30 use crate::Resolution;
31
32 use drm_fourcc::DrmFourcc;
33 #[cfg(feature = "v4l2")]
34 use gbm_sys::gbm_import_fd_data;
35 #[cfg(feature = "vaapi")]
36 use gbm_sys::gbm_import_fd_modifier_data;
37 use gbm_sys::{
38 gbm_bo, gbm_bo_create, gbm_bo_destroy, gbm_bo_flags, gbm_bo_get_fd, gbm_bo_get_height,
39 gbm_bo_get_modifier, gbm_bo_get_offset, gbm_bo_get_stride_for_plane, gbm_bo_get_width,
40 gbm_bo_import, gbm_bo_map, gbm_bo_transfer_flags, gbm_bo_unmap, gbm_create_device, gbm_device,
41 gbm_device_destroy,
42 };
43 #[cfg(feature = "vaapi")]
44 use libva::{
45 Display, ExternalBufferDescriptor, MemoryType, Surface, UsageHint, VADRMPRIMESurfaceDescriptor,
46 VADRMPRIMESurfaceDescriptorLayer, VADRMPRIMESurfaceDescriptorObject,
47 };
48 use nix::libc;
49 #[cfg(feature = "v4l2")]
50 use v4l2r::bindings::v4l2_plane;
51 #[cfg(feature = "v4l2")]
52 use v4l2r::device::Device;
53 #[cfg(feature = "v4l2")]
54 use v4l2r::ioctl::V4l2Buffer;
55 #[cfg(feature = "v4l2")]
56 use v4l2r::memory::{DmaBufHandle, DmaBufSource, PlaneHandle};
57 #[cfg(feature = "v4l2")]
58 use v4l2r::Format;
59
60 // The gbm crate's wrapper for map() doesn't have the lifetime semantics that we want, so we need
61 // to implement our own.
62 // Unfortunately, because the GBM crate's BufferObject class does not expose the raw gbm_bo
63 // pointer, we are going to need to reimplement a lot of its functionality. This means a lot of
64 // "unsafe" code will be needed in this module. We guarantee the safety of this code by using
65 // Rust's native lifetime system to ensure that parent objects do not get deallocated before all of
66 // their children. For example, map objects maintain a reference to the underlying video frame,
67 // and video frames maintain a reference to the GBM device that allocated them.
68
map_bo( bo: *mut gbm_bo, is_writable: bool, ) -> Result<(*mut libc::c_void, *mut libc::c_void), String>69 fn map_bo(
70 bo: *mut gbm_bo,
71 is_writable: bool,
72 ) -> Result<(*mut libc::c_void, *mut libc::c_void), String> {
73 let permissions = if is_writable {
74 gbm_bo_transfer_flags::GBM_BO_TRANSFER_READ_WRITE
75 } else {
76 gbm_bo_transfer_flags::GBM_BO_TRANSFER_READ
77 };
78 let mut stride: u32 = 0;
79 let mut map_data: *mut libc::c_void = ptr::null_mut();
80 // SAFETY: This should be safe because we validate that bo is non-null on creation and we tie
81 // the lifetimes of the VideoFrames to the underlying GBM device.
82 unsafe {
83 let map_result = gbm_bo_map(
84 bo,
85 0,
86 0,
87 gbm_bo_get_width(bo),
88 gbm_bo_get_height(bo),
89 permissions,
90 &mut stride as *mut u32,
91 &mut map_data as *mut *mut libc::c_void,
92 );
93 if map_result.is_null() {
94 Err(format!(
95 "Failed to map GBM buffer object read {}!",
96 if is_writable { "write" } else { "only" }
97 ))
98 } else {
99 Ok((map_result, map_data))
100 }
101 }
102 }
103
104 #[cfg(feature = "v4l2")]
import_bo_from_dmabuf_fd( device: *mut gbm_device, dma: BorrowedFd<'_>, width: u32, height: u32, stride: u32, format: u32, usage: u32, ) -> *mut gbm_bo105 fn import_bo_from_dmabuf_fd(
106 device: *mut gbm_device,
107 dma: BorrowedFd<'_>,
108 width: u32,
109 height: u32,
110 stride: u32,
111 format: u32,
112 usage: u32,
113 ) -> *mut gbm_bo {
114 let mut import_data = gbm_import_fd_data {
115 fd: dma.as_raw_fd(),
116 width: width,
117 height: height,
118 stride: stride,
119 format: format,
120 };
121 // SAFETY: This assumes that gbm_device is valid and that the FD given points to a DRM Prime FD
122 // with the layout dictated by the other parameters to this function.
123 unsafe {
124 gbm_bo_import(
125 device,
126 gbm_sys::GBM_BO_IMPORT_FD,
127 &mut import_data as *mut gbm_import_fd_data as *mut libc::c_void,
128 usage,
129 )
130 }
131 }
132
133 pub struct GbmMapping<'a> {
134 map_datas: Vec<*mut libc::c_void>,
135 raw_mems: Vec<*mut libc::c_void>,
136 lens: Vec<usize>,
137 is_writable: bool,
138 frame: &'a GbmVideoFrame,
139 }
140
141 impl<'a> ReadMapping<'a> for GbmMapping<'a> {
get(&self) -> Vec<&[u8]>142 fn get(&self) -> Vec<&[u8]> {
143 // SAFETY: This should be safe because we validate that gbm_bo_map returns a non-null
144 // pointer before instantiating a GbmMapping object.
145 unsafe {
146 zip(self.raw_mems.iter(), self.lens.iter())
147 .map(|x| slice::from_raw_parts(*x.0 as *const u8, *x.1))
148 .collect()
149 }
150 }
151 }
152
153 impl<'a> WriteMapping<'a> for GbmMapping<'a> {
get(&self) -> Vec<RefCell<&'a mut [u8]>>154 fn get(&self) -> Vec<RefCell<&'a mut [u8]>> {
155 if !self.is_writable {
156 panic!("Attempted to get writable slice to read only mapping!");
157 }
158
159 // SAFETY: This should be safe because we validate that gbm_bo_map returns a non-null
160 // pointer before instantiating a GbmMapping object. The above check prevents us from
161 // attempting to get a writable slice to a read-only mapping.
162 unsafe {
163 zip(self.raw_mems.iter(), self.lens.iter())
164 .map(|x| RefCell::new(slice::from_raw_parts_mut(*x.0 as *mut u8, *x.1)))
165 .collect()
166 }
167 }
168 }
169
170 impl<'a> Drop for GbmMapping<'a> {
drop(&mut self)171 fn drop(&mut self) {
172 for plane_idx in 0..self.map_datas.len() {
173 let bo =
174 if self.frame.bo.len() == 1 { self.frame.bo[0] } else { self.frame.bo[plane_idx] };
175 let map_data = self.map_datas[plane_idx];
176 // SAFETY: This should be safe because we validate that gbm_bo_map returns a non-null
177 // pointer before instantiating a GbmMapping object.
178 unsafe { gbm_bo_unmap(bo, map_data) };
179 }
180 }
181 }
182
183 #[derive(Debug)]
184 pub struct GbmVideoFrame {
185 fourcc: Fourcc,
186 resolution: Resolution,
187 bo: Vec<*mut gbm_bo>,
188 // This reference is just to create a lifetime constraint. We don't want someone to close the
189 // device before free'ing up all allocated VideoFrames. We keep it optional just so that we can
190 // create default video frames for the purpose of signaling a drain event.
191 _device: Option<Arc<GbmDevice>>,
192 // This ties the lifetime of the DRM FDs to the lifetime of this object so that we can export
193 // to V4L2.
194 #[cfg(feature = "v4l2")]
195 export_handles: Vec<DmaBufHandle<File>>,
196 }
197
198 impl GbmVideoFrame {
get_plane_offset(&self) -> Vec<usize>199 fn get_plane_offset(&self) -> Vec<usize> {
200 let mut ret: Vec<usize> = vec![];
201 for plane_idx in 0..self.num_planes() {
202 // SAFETY: This assumes self.bo contains valid GBM buffer objects.
203 if self.bo.len() > 1 {
204 ret.push(unsafe { gbm_bo_get_offset(self.bo[plane_idx], 0) as usize });
205 } else {
206 ret.push(unsafe {
207 gbm_bo_get_offset(self.bo[0], plane_idx as libc::c_int) as usize
208 });
209 }
210 }
211 ret
212 }
213
214 #[allow(dead_code)]
get_modifier(&self) -> u64215 fn get_modifier(&self) -> u64 {
216 // SAFETY: This assumes self.bo[0] contains a valid GBM buffer object.
217 unsafe { gbm_bo_get_modifier(self.bo[0]) }
218 }
219
map_helper(&self, is_writable: bool) -> Result<GbmMapping, String>220 fn map_helper(&self, is_writable: bool) -> Result<GbmMapping, String> {
221 let offsets = self.get_plane_offset();
222 let mut ret = GbmMapping {
223 map_datas: vec![],
224 raw_mems: vec![],
225 lens: self.get_plane_size(),
226 is_writable: is_writable,
227 frame: self,
228 };
229 for plane_idx in 0..self.num_planes() {
230 let bo = if self.bo.len() == 1 { self.bo[0] } else { self.bo[plane_idx] };
231 let (raw_mem, map_data) = map_bo(bo, is_writable)?;
232 ret.map_datas.push(map_data);
233 // SAFETY: This assumes that GBM gave us a valid offset.
234 ret.raw_mems.push(unsafe { raw_mem.offset(offsets[plane_idx] as isize) });
235 }
236 Ok(ret)
237 }
238
to_generic_dma_video_frame(self) -> Result<GenericDmaVideoFrame<()>, String>239 pub fn to_generic_dma_video_frame(self) -> Result<GenericDmaVideoFrame<()>, String> {
240 let planes: Vec<PlaneLayout> = zip(self.get_plane_offset(), self.get_plane_pitch())
241 .enumerate()
242 .map(|x| PlaneLayout { buffer_index: x.0, offset: x.1 .0, stride: x.1 .1 })
243 .collect();
244 // SAFETY: gbm_bo_get_fd returns a new, owned FD every time it is called, so this should be
245 // safe as long as self.bo contains valid buffer objects.
246 let dma_handles: Vec<File> =
247 self.bo.iter().map(|bo| unsafe { File::from_raw_fd(gbm_bo_get_fd(*bo)) }).collect();
248 GenericDmaVideoFrame::new(
249 (),
250 dma_handles,
251 FrameLayout {
252 format: (self.fourcc, self.get_modifier()),
253 size: self.resolution(),
254 planes: planes,
255 },
256 )
257 }
258 }
259
260 #[cfg(feature = "vaapi")]
261 pub struct GbmExternalBufferDescriptor {
262 fourcc: Fourcc,
263 modifier: u64,
264 resolution: Resolution,
265 pitches: Vec<usize>,
266 offsets: Vec<usize>,
267 // We use a proper File object here to correctly manage the lifetimes of the exported FDs.
268 // Otherwise we risk exhausting the FD limit on the machine for certain test vectors.
269 export_file: File,
270 }
271
272 #[cfg(feature = "vaapi")]
273 impl ExternalBufferDescriptor for GbmExternalBufferDescriptor {
274 const MEMORY_TYPE: MemoryType = MemoryType::DrmPrime2;
275 type DescriptorAttribute = VADRMPRIMESurfaceDescriptor;
276
va_surface_attribute(&mut self) -> Self::DescriptorAttribute277 fn va_surface_attribute(&mut self) -> Self::DescriptorAttribute {
278 let objects = [
279 VADRMPRIMESurfaceDescriptorObject {
280 fd: self.export_file.as_raw_fd(),
281 size: self.export_file.metadata().unwrap().len() as u32,
282 drm_format_modifier: self.modifier,
283 },
284 Default::default(),
285 Default::default(),
286 Default::default(),
287 ];
288
289 let layers = [
290 VADRMPRIMESurfaceDescriptorLayer {
291 drm_format: u32::from(self.fourcc),
292 num_planes: self.pitches.len() as u32,
293 object_index: [0, 0, 0, 0],
294 offset: self
295 .offsets
296 .iter()
297 .map(|x| *x as u32)
298 .chain(std::iter::repeat(0))
299 .take(4)
300 .collect::<Vec<_>>()
301 .try_into()
302 .unwrap(),
303 pitch: self
304 .pitches
305 .iter()
306 .map(|x| *x as u32)
307 .chain(std::iter::repeat(0))
308 .take(4)
309 .collect::<Vec<_>>()
310 .try_into()
311 .unwrap(),
312 },
313 Default::default(),
314 Default::default(),
315 Default::default(),
316 ];
317 let resolution = self.resolution;
318 let ret = VADRMPRIMESurfaceDescriptor {
319 fourcc: u32::from(self.fourcc),
320 width: resolution.width,
321 height: resolution.height,
322 num_objects: 1,
323 objects,
324 num_layers: 1,
325 layers,
326 };
327 ret
328 }
329 }
330
331 impl VideoFrame for GbmVideoFrame {
332 #[cfg(feature = "v4l2")]
333 type NativeHandle = DmaBufHandle<File>;
334
335 #[cfg(feature = "vaapi")]
336 type MemDescriptor = GbmExternalBufferDescriptor;
337 #[cfg(feature = "vaapi")]
338 type NativeHandle = Surface<GbmExternalBufferDescriptor>;
339
fourcc(&self) -> Fourcc340 fn fourcc(&self) -> Fourcc {
341 self.fourcc.clone()
342 }
343
resolution(&self) -> Resolution344 fn resolution(&self) -> Resolution {
345 self.resolution.clone()
346 }
347
348 // GBM allocates all the planes in one buffer and doesn't expose a means to get the size of a
349 // single plane, so just stub this method.
get_plane_size(&self) -> Vec<usize>350 fn get_plane_size(&self) -> Vec<usize> {
351 let mut ret: Vec<usize> = vec![];
352 let vertical_subsampling = self.get_vertical_subsampling();
353 let plane_pitch = self.get_plane_pitch();
354 for plane_idx in 0..self.num_planes() {
355 ret.push(
356 plane_pitch[plane_idx]
357 * align_up(self.resolution().height as usize, vertical_subsampling[plane_idx])
358 / vertical_subsampling[plane_idx],
359 );
360 }
361 ret
362 }
363
get_plane_pitch(&self) -> Vec<usize>364 fn get_plane_pitch(&self) -> Vec<usize> {
365 let mut ret: Vec<usize> = vec![];
366 for plane_idx in 0..self.num_planes() {
367 // SAFETY: This assumes self.bo contains valid GBM buffer objects.
368 if self.bo.len() > 1 {
369 ret.push(unsafe { gbm_bo_get_stride_for_plane(self.bo[plane_idx], 0) as usize });
370 } else {
371 ret.push(unsafe {
372 gbm_bo_get_stride_for_plane(self.bo[0], plane_idx as libc::c_int) as usize
373 });
374 }
375 }
376 ret
377 }
378
map<'a>(&'a self) -> Result<Box<dyn ReadMapping<'a> + 'a>, String>379 fn map<'a>(&'a self) -> Result<Box<dyn ReadMapping<'a> + 'a>, String> {
380 Ok(Box::new(self.map_helper(false)?))
381 }
382
map_mut<'a>(&'a mut self) -> Result<Box<dyn WriteMapping<'a> + 'a>, String>383 fn map_mut<'a>(&'a mut self) -> Result<Box<dyn WriteMapping<'a> + 'a>, String> {
384 Ok(Box::new(self.map_helper(true)?))
385 }
386
387 #[cfg(feature = "v4l2")]
fill_v4l2_plane(&self, index: usize, plane: &mut v4l2_plane)388 fn fill_v4l2_plane(&self, index: usize, plane: &mut v4l2_plane) {
389 // TODO: Support |data_offset| here.
390 self.export_handles[index].fill_v4l2_plane(plane)
391 }
392
393 // No-op for GBM buffers since the backing FD already disambiguates them.
394 #[cfg(feature = "v4l2")]
process_dqbuf(&mut self, _device: Arc<Device>, _format: &Format, _buf: &V4l2Buffer)395 fn process_dqbuf(&mut self, _device: Arc<Device>, _format: &Format, _buf: &V4l2Buffer) {}
396
397 #[cfg(feature = "vaapi")]
to_native_handle(&self, display: &Rc<Display>) -> Result<Self::NativeHandle, String>398 fn to_native_handle(&self, display: &Rc<Display>) -> Result<Self::NativeHandle, String> {
399 if self.is_compressed() {
400 return Err("Compressed buffer export to VA-API is not currently supported".to_string());
401 }
402 if !self.is_contiguous() {
403 return Err(
404 "Exporting non-contiguous GBM buffers to VA-API is not currently supported"
405 .to_string(),
406 );
407 }
408
409 // TODO: Add more supported formats
410 let rt_format = match self.decoded_format().unwrap() {
411 DecodedFormat::I420 | DecodedFormat::NV12 => libva::VA_RT_FORMAT_YUV420,
412 _ => return Err("Format unsupported for VA-API export".to_string()),
413 };
414
415 let export_descriptor = vec![GbmExternalBufferDescriptor {
416 fourcc: self.fourcc.clone(),
417 modifier: self.get_modifier(),
418 resolution: self.resolution(),
419 pitches: self.get_plane_pitch(),
420 offsets: self.get_plane_offset(),
421 // SAFETY: gbm_bo_get_fd returns a new, owned FD every time it is called, so this should
422 // be safe as long as self.bo contains valid buffer objects.
423 export_file: unsafe { File::from_raw_fd(gbm_bo_get_fd(self.bo[0])) },
424 }];
425
426 let mut ret = display
427 .create_surfaces(
428 rt_format,
429 Some(u32::from(self.fourcc)),
430 self.resolution().width,
431 self.resolution().height,
432 Some(UsageHint::USAGE_HINT_DECODER),
433 export_descriptor,
434 )
435 .map_err(|_| "Error importing GbmVideoFrame to VA-API".to_string())?;
436
437 Ok(ret.pop().unwrap())
438 }
439 }
440
441 impl Drop for GbmVideoFrame {
drop(&mut self)442 fn drop(&mut self) {
443 for bo in self.bo.iter() {
444 // SAFETY: This should be safe because we would not instantiate a GbmVideoFrame if the
445 // buffer object creation failed.
446 unsafe { gbm_bo_destroy(*bo) };
447 }
448 }
449 }
450
451 // SAFETY: We will only access the raw BOs from the worker thread, and there are no other copies of
452 // these pointers.
453 unsafe impl Send for GbmVideoFrame {}
454 unsafe impl Sync for GbmVideoFrame {}
455
456 #[derive(Clone, Debug, PartialEq)]
457 pub enum GbmUsage {
458 Decode,
459 Encode,
460 }
461
462 #[derive(Debug)]
463 pub struct GbmDevice {
464 device: *mut gbm_device,
465 // Keeps device file descriptors valid as long as the GbmDevice is alive.
466 _device_file: std::fs::File,
467 }
468
469 impl GbmDevice {
open<P: AsRef<Path>>(path: P) -> Result<Arc<Self>, String>470 pub fn open<P: AsRef<Path>>(path: P) -> Result<Arc<Self>, String> {
471 let device_file = std::fs::OpenOptions::new()
472 .read(true)
473 .write(true)
474 .open(path)
475 .map_err(|_| "Error opening GBM device!".to_string())?;
476 // SAFETY: We keep the GBM device File inside GbmDevice specifically for the purpose of
477 // making sure this FD is valid for the lifetime of the GbmDevice.
478 let device = unsafe { gbm_create_device(device_file.as_raw_fd()) };
479 if device.is_null() {
480 Err("Could not create GBM device from file!".to_string())
481 } else {
482 Ok(Arc::new(Self { device: device, _device_file: device_file }))
483 }
484 }
485
new_frame( self: Arc<Self>, fourcc: Fourcc, visible_resolution: Resolution, coded_resolution: Resolution, usage: GbmUsage, ) -> Result<GbmVideoFrame, String>486 pub fn new_frame(
487 self: Arc<Self>,
488 fourcc: Fourcc,
489 visible_resolution: Resolution,
490 coded_resolution: Resolution,
491 usage: GbmUsage,
492 ) -> Result<GbmVideoFrame, String> {
493 let mut ret = GbmVideoFrame {
494 fourcc: fourcc,
495 resolution: visible_resolution,
496 bo: vec![],
497 _device: Some(Arc::clone(&self)),
498 #[cfg(feature = "v4l2")]
499 export_handles: vec![],
500 };
501
502 if ret.is_compressed() {
503 let buffer_size = buffer_size_for_area(coded_resolution.width, coded_resolution.height);
504 // The width and height values are ultimately arbitrary, but we should try to pick some
505 // that won't trigger the GEM driver to add padding, so we only allocate what we need.
506 // 32 pixels is a common enough alignment that this provides us with a decent guess.
507 let fake_width = align_up((buffer_size as f64).sqrt() as u32, 32);
508 let fake_height = align_up(buffer_size as u32, fake_width) / fake_width;
509 // SAFETY: This should be safe because we would not instantiate a GbmDevice unless the
510 // call to gbm_create_device was successful.
511 let bo = unsafe {
512 gbm_bo_create(
513 self.device,
514 fake_width,
515 fake_height,
516 DrmFourcc::R8 as u32,
517 gbm_bo_flags::GBM_BO_USE_LINEAR as u32,
518 )
519 };
520 if bo.is_null() {
521 return Err("Error allocating compressed buffer!".to_string());
522 }
523
524 ret.bo.push(bo);
525 } else if ret.is_contiguous() {
526 // These flags are not present in every system's GBM headers.
527 const GBM_BO_USE_HW_VIDEO_DECODER: u32 = 1 << 13;
528 const GBM_BO_USE_HW_VIDEO_ENCODER: u32 = 1 << 14;
529 // It's important that we use the correct use flag for platforms that support directly
530 // importing GBM allocated frame buffers to the video decoding hardware because the
531 // video decoding hardware sometimes makes assumptions about the modifier flags. If we
532 // try to force everything to be linear, we can end up getting a tiled frame when we
533 // try to map it.
534 // SAFETY: This should be safe because we would not instantiate a GbmDevice unless the
535 // call to gbm_create_device was successful.
536 let bo = unsafe {
537 gbm_bo_create(
538 self.device,
539 coded_resolution.width,
540 coded_resolution.height,
541 u32::from(fourcc),
542 if usage == GbmUsage::Decode {
543 GBM_BO_USE_HW_VIDEO_DECODER
544 } else {
545 GBM_BO_USE_HW_VIDEO_ENCODER
546 },
547 )
548 };
549 if bo.is_null() {
550 return Err(format!(
551 "Error allocating contiguous buffer! Fourcc: {} width: {} height: {}",
552 fourcc.to_string(),
553 coded_resolution.width,
554 coded_resolution.height
555 ));
556 }
557
558 ret.bo.push(bo);
559 } else {
560 // We hack multiplanar formats into GBM by making a bunch of separate BO's.
561 // The usage flag is ignored here because R8 are generally not supported
562 // decoder and encoder formats, so we just have to accept we're on our own for figuring
563 // out alignment, modifier, fourcc, etc.
564 let horizontal_subsampling = ret.get_horizontal_subsampling();
565 let vertical_subsampling = ret.get_vertical_subsampling();
566 let bytes_per_element = ret.get_bytes_per_element();
567 for plane_idx in 0..ret.num_planes() {
568 // SAFETY: This should be safe because we would not instantiate a GbmDevice unless
569 // the call to gbm_create_device was successful.
570 let bo = unsafe {
571 gbm_bo_create(
572 self.device,
573 (align_up(
574 coded_resolution.width as usize,
575 horizontal_subsampling[plane_idx],
576 ) / horizontal_subsampling[plane_idx]
577 * bytes_per_element[plane_idx]) as u32,
578 (align_up(
579 coded_resolution.height as usize,
580 vertical_subsampling[plane_idx],
581 ) / vertical_subsampling[plane_idx]) as u32,
582 DrmFourcc::R8 as u32,
583 gbm_bo_flags::GBM_BO_USE_LINEAR as u32,
584 )
585 };
586 if bo.is_null() {
587 return Err(format!(
588 "Error allocating plane {} for format {}",
589 plane_idx,
590 fourcc.to_string()
591 ));
592 }
593
594 ret.bo.push(bo);
595 }
596 }
597
598 #[cfg(feature = "v4l2")]
599 {
600 // SAFETY: gbm_bo_get_fd returns a new, owned FD every time it is called, so this
601 // should be safe as long as ret.bo contains valid buffer objects.
602 ret.export_handles = ret
603 .bo
604 .iter()
605 .map(|bo| unsafe {
606 DmaBufHandle::from(File::from_raw_fd(gbm_bo_get_fd(bo.clone())))
607 })
608 .collect::<_>();
609 }
610
611 Ok(ret)
612 }
613
614 #[cfg(feature = "v4l2")]
import_from_v4l2<S: DmaBufSource>( self: Arc<Self>, fourcc: Fourcc, resolution: Resolution, strides: Vec<usize>, native_handle: Vec<DmaBufHandle<S>>, ) -> Result<GbmVideoFrame, String>615 pub fn import_from_v4l2<S: DmaBufSource>(
616 self: Arc<Self>,
617 fourcc: Fourcc,
618 resolution: Resolution,
619 strides: Vec<usize>,
620 native_handle: Vec<DmaBufHandle<S>>,
621 ) -> Result<GbmVideoFrame, String> {
622 let mut ret = GbmVideoFrame {
623 fourcc: fourcc,
624 resolution: resolution,
625 bo: vec![],
626 export_handles: vec![],
627 _device: Some(Arc::clone(&self)),
628 };
629
630 if strides.is_empty() || native_handle.is_empty() {
631 return Err("Cannot import empty V4L2 handle!".to_string());
632 }
633 if !ret.is_contiguous() && strides.len() != ret.num_planes() {
634 return Err(format!("Invalid number of strides for format {}", fourcc.to_string()));
635 }
636 if !ret.is_contiguous() && native_handle.len() != ret.num_planes() {
637 return Err(format!("Invalid number of V4L2 planes for format {}", fourcc.to_string()));
638 }
639
640 // TODO: Pass in a parameter that determines this usage rather than just OR'ing everything
641 // together.
642 let usage = gbm_bo_flags::GBM_BO_USE_LINEAR as u32;
643 if ret.is_contiguous() {
644 let bo = import_bo_from_dmabuf_fd(
645 self.device,
646 native_handle[0].0.as_fd(),
647 ret.resolution.width,
648 ret.resolution.height,
649 strides[0] as u32,
650 u32::from(fourcc),
651 usage,
652 );
653 if bo.is_null() {
654 return Err("Error importing contiguous V4L2 buffer!".to_string());
655 }
656 ret.bo.push(bo);
657 } else {
658 let horizontal_subsampling = ret.get_horizontal_subsampling();
659 let vertical_subsampling = ret.get_vertical_subsampling();
660 let bytes_per_element = ret.get_bytes_per_element();
661 for plane_idx in 0..ret.num_planes() {
662 let bo = import_bo_from_dmabuf_fd(
663 self.device,
664 native_handle[plane_idx].0.as_fd(),
665 (align_up(resolution.width as usize, horizontal_subsampling[plane_idx])
666 / horizontal_subsampling[plane_idx]
667 * bytes_per_element[plane_idx]) as u32,
668 (align_up(resolution.height as usize, vertical_subsampling[plane_idx])
669 / vertical_subsampling[plane_idx]) as u32,
670 strides[plane_idx] as u32,
671 DrmFourcc::R8 as u32,
672 usage,
673 );
674 if bo.is_null() {
675 return Err(format!("Error importing plane {}", plane_idx));
676 }
677 ret.bo.push(bo);
678 }
679 }
680
681 // SAFETY: gbm_bo_get_fd returns a new, owned FD every time it is called, so this should be
682 // safe as long as self.bo contains valid buffer objects.
683 ret.export_handles = ret
684 .bo
685 .iter()
686 .map(|bo| unsafe { DmaBufHandle::from(File::from_raw_fd(gbm_bo_get_fd(bo.clone()))) })
687 .collect::<_>();
688
689 Ok(ret)
690 }
691
692 #[cfg(feature = "vaapi")]
import_from_vaapi( self: Arc<Self>, surface: &Surface<GbmExternalBufferDescriptor>, ) -> Result<GbmVideoFrame, String>693 pub fn import_from_vaapi(
694 self: Arc<Self>,
695 surface: &Surface<GbmExternalBufferDescriptor>,
696 ) -> Result<GbmVideoFrame, String> {
697 let descriptor = surface
698 .export_prime()
699 .map_err(|err| format!("Could not export VA-API surface! {err:?}"))?;
700
701 if descriptor.layers.len() != 1 {
702 return Err("Cannot import more than 1 layers as a single GBM buffer".to_string());
703 }
704
705 for idx in descriptor.layers[0].object_index {
706 if idx as usize >= descriptor.objects.len() {
707 return Err("Object index {idx} out of bounds".to_string());
708 }
709 }
710
711 let mut ret = GbmVideoFrame {
712 _device: Some(Arc::clone(&self)),
713 fourcc: Fourcc::from(descriptor.fourcc),
714 resolution: Resolution { width: descriptor.width, height: descriptor.height },
715 bo: vec![],
716 };
717
718 let buffers = [
719 descriptor.objects[descriptor.layers[0].object_index[0] as usize].fd.as_raw_fd()
720 as libc::c_int,
721 -1,
722 -1,
723 -1,
724 ];
725 let mut import_data = gbm_import_fd_modifier_data {
726 width: descriptor.width,
727 height: descriptor.height,
728 format: descriptor.fourcc,
729 num_fds: 1,
730 fds: buffers,
731 strides: [
732 descriptor.layers[0].pitch[0] as libc::c_int,
733 descriptor.layers[0].pitch[1] as libc::c_int,
734 descriptor.layers[0].pitch[2] as libc::c_int,
735 descriptor.layers[0].pitch[3] as libc::c_int,
736 ],
737 offsets: [
738 descriptor.layers[0].offset[0] as libc::c_int,
739 descriptor.layers[0].offset[1] as libc::c_int,
740 descriptor.layers[0].offset[2] as libc::c_int,
741 descriptor.layers[0].offset[3] as libc::c_int,
742 ],
743 modifier: descriptor.objects[0].drm_format_modifier,
744 };
745
746 // TODO: Plumb real usage flags in here.
747 let usage = gbm_bo_flags::GBM_BO_USE_SCANOUT as u32;
748
749 // The constant in gbm_sys is wrong for some reason
750 const GBM_BO_IMPORT_FD_MODIFIER: u32 = 0x5505;
751 // SAFETY: This should be safe as long as VA-API correctly exports the DRM Prime FD. The
752 // GBM device is guaranteed to be valid or we wouldn't have instantiated the GbmDevice in
753 // the first place.
754 let bo = unsafe {
755 gbm_bo_import(
756 self.device,
757 GBM_BO_IMPORT_FD_MODIFIER,
758 &mut import_data as *mut gbm_import_fd_modifier_data as *mut libc::c_void,
759 usage,
760 )
761 };
762
763 if bo.is_null() {
764 Err("Error importing VA-API surface!".to_string())
765 } else {
766 ret.bo.push(bo);
767 Ok(ret)
768 }
769 }
770 }
771
772 impl Drop for GbmDevice {
drop(&mut self)773 fn drop(&mut self) {
774 // SAFETY: device should be valid because we wouldn't instantiate a GbmDevice if
775 // gbm_create_device failed.
776 unsafe { gbm_device_destroy(self.device) }
777 }
778 }
779
780 // UNSAFE: We will only access the raw GBM device from the worker thread, and there are no other
781 // copies of the raw pointer available.
782 unsafe impl Send for GbmDevice {}
783 unsafe impl Sync for GbmDevice {}
784
785 // TODO: Add unit tests
786