Skip to content

Commit 32ddba1

Browse files
committed
gpu: Add support for dmabuf scanout
Add support for a zero-copy dmabuf scanout of resources. If the dmabuf scanout is not supported we still fallback to the basic_framebuffer feature (CPU copy). Signed-off-by: Matej Hrica <[email protected]>
1 parent 4529e56 commit 32ddba1

File tree

1 file changed

+201
-33
lines changed

1 file changed

+201
-33
lines changed

src/devices/src/virtio/gpu/virtio_gpu.rs

Lines changed: 201 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,22 @@ use super::protocol::{
1515
#[cfg(target_os = "macos")]
1616
use crossbeam_channel::{unbounded, Sender};
1717
use krun_display::{
18-
DisplayBackend, DisplayBackendBasicFramebuffer, DisplayBackendInstance, Rect, ResourceFormat,
18+
DisplayBackend, DisplayBackendBasicFramebuffer, DisplayBackendInstance, DmabufExport, Rect,
19+
ResourceFormat,
1920
};
2021
use libc::c_void;
2122
#[cfg(target_os = "macos")]
2223
use rutabaga_gfx::RUTABAGA_MEM_HANDLE_TYPE_APPLE;
24+
#[cfg(target_os = "linux")]
25+
use rutabaga_gfx::RUTABAGA_MEM_HANDLE_TYPE_DMABUF;
2326
#[cfg(all(not(feature = "virgl_resource_map2"), target_os = "linux"))]
2427
use rutabaga_gfx::RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD;
2528
#[cfg(all(feature = "virgl_resource_map2", target_os = "linux"))]
2629
use rutabaga_gfx::RUTABAGA_MEM_HANDLE_TYPE_SHM;
2730
use rutabaga_gfx::{
2831
ResourceCreate3D, ResourceCreateBlob, Rutabaga, RutabagaBuilder, RutabagaChannel,
29-
RutabagaFence, RutabagaFenceHandler, RutabagaIovec, Transfer3D, RUTABAGA_CHANNEL_TYPE_WAYLAND,
30-
RUTABAGA_MAP_CACHE_MASK,
32+
RutabagaFence, RutabagaFenceHandler, RutabagaIntoRawDescriptor, RutabagaIovec, Transfer3D,
33+
RUTABAGA_CHANNEL_TYPE_WAYLAND, RUTABAGA_MAP_CACHE_MASK,
3134
};
3235
#[cfg(target_os = "linux")]
3336
use rutabaga_gfx::{
@@ -116,6 +119,9 @@ struct VirtioGpuResource {
116119
size: u64, // only for blob resources
117120
shmem_offset: Option<u64>,
118121
rutabaga_external_mapping: bool,
122+
#[cfg(target_os = "linux")]
123+
// The id under which this resource is imported to the display
124+
display_dmabuf_id: Option<u32>,
119125
}
120126

121127
impl VirtioGpuResource {
@@ -137,12 +143,15 @@ impl VirtioGpuResource {
137143
format,
138144
shmem_offset: None,
139145
rutabaga_external_mapping: false,
146+
#[cfg(target_os = "linux")]
147+
display_dmabuf_id: None,
140148
}
141149
}
142150
}
143151

144152
pub struct VirtioGpuScanout {
145153
resource_id: u32,
154+
uses_dmabuf: bool,
146155
}
147156

148157
pub struct VirtioGpu {
@@ -367,6 +376,14 @@ impl VirtioGpu {
367376
return Err(ErrUnspec);
368377
}
369378

379+
if self.display_backend.supports_dmabuf() {
380+
if let Some(display_dmabuf_id) = resource.display_dmabuf_id {
381+
if let Err(e) = self.display_backend.unref_dmabuf(display_dmabuf_id) {
382+
warn!("Failed to unref display DMABUF resource, resource_id: {resource_id}:, display_dmabuf_id: {display_dmabuf_id}, error: {e:?}");
383+
}
384+
}
385+
}
386+
370387
if resource.rutabaga_external_mapping {
371388
self.rutabaga.unmap(resource_id)?;
372389
}
@@ -382,26 +399,21 @@ impl VirtioGpu {
382399
width: u32,
383400
height: u32,
384401
) -> VirtioGpuResult {
385-
let scanout = self
386-
.scanouts
387-
.get_mut(scanout_id as usize)
388-
.ok_or(ErrInvalidScanoutId)?;
389-
390-
// If a resource is already associated with this scanout, make sure to disable
391-
// this scanout for that resource
392-
if let Some(resource_id) = scanout.as_ref().map(|scanout| scanout.resource_id) {
393-
let resource = self
394-
.resources
395-
.get_mut(&resource_id)
396-
.ok_or(ErrInvalidResourceId)?;
402+
if scanout_id as usize >= self.scanouts.len() {
403+
return Err(ErrInvalidScanoutId);
404+
}
397405

398-
resource.scanouts.disable(scanout_id);
406+
// If a resource is already associated with this scanout, disable it for that resource
407+
if let Some(old_scanout) = &self.scanouts[scanout_id as usize] {
408+
if let Some(resource) = self.resources.get_mut(&old_scanout.resource_id) {
409+
resource.scanouts.disable(scanout_id);
410+
}
399411
}
400412

401413
// Virtio spec: "The driver can use resource_id = 0 to disable a scanout."
402414
if resource_id == 0 {
403415
debug!("Disabling scanout {scanout_id:?}");
404-
*scanout = None;
416+
self.scanouts[scanout_id as usize] = None;
405417
self.display_backend.disable_scanout(scanout_id)?;
406418
return Ok(OkNoData);
407419
}
@@ -412,28 +424,163 @@ impl VirtioGpu {
412424
.get_mut(&resource_id)
413425
.ok_or(ErrInvalidResourceId)?;
414426
resource.scanouts.enable(scanout_id);
427+
let resource_format = resource.format;
428+
429+
let (display_width, display_height) = {
430+
let display_info = self
431+
.displays
432+
.get(scanout_id as usize)
433+
.ok_or(ErrInvalidScanoutId)?;
434+
(display_info.width, display_info.height)
435+
};
436+
437+
// Try dmabuf path first if supported
438+
#[cfg(target_os = "linux")]
439+
if self.display_backend.supports_dmabuf() {
440+
if let Ok(dmabuf_scanout) = self.try_configure_dmabuf_scanout(
441+
scanout_id,
442+
resource_id,
443+
display_width,
444+
display_height,
445+
width,
446+
height,
447+
) {
448+
self.scanouts[scanout_id as usize] = Some(dmabuf_scanout);
449+
return Ok(OkNoData);
450+
}
451+
}
415452

416-
let Some(format) = resource.format else {
417-
warn!("Cannot use resource {resource_id} with unknown format for scanout");
453+
let Some(format) = resource_format else {
454+
warn!("Cannot use resource {resource_id} with unknown format for basic framebuffer scanout");
418455
return Err(ErrUnspec);
419456
};
420457

421-
let display_info = self
422-
.displays
423-
.get(scanout_id as usize)
424-
.ok_or(ErrInvalidScanoutId)?;
458+
// Fallback to basic framebuffer
459+
let basic_scanout = self.configure_basic_framebuffer_scanout(
460+
scanout_id,
461+
resource_id,
462+
display_width,
463+
display_height,
464+
width,
465+
height,
466+
format,
467+
)?;
468+
self.scanouts[scanout_id as usize] = Some(basic_scanout);
469+
Ok(OkNoData)
470+
}
471+
472+
#[cfg(target_os = "linux")]
473+
fn try_configure_dmabuf_scanout(
474+
&mut self,
475+
scanout_id: u32,
476+
resource_id: u32,
477+
display_width: u32,
478+
display_height: u32,
479+
_width: u32,
480+
_height: u32,
481+
) -> std::result::Result<VirtioGpuScanout, ()> {
482+
// Check if this resource has already been exported to the display
483+
let resource = self.resources.get_mut(&resource_id).ok_or(())?;
484+
let dmabuf_id = match resource.display_dmabuf_id {
485+
Some(old_dmabuf_id) => {
486+
trace!("Resource {resource_id} already imported as dmabuf with ID {old_dmabuf_id}");
487+
old_dmabuf_id
488+
}
489+
None => {
490+
let export = self.rutabaga.export_blob(resource_id).map_err(|e| {
491+
debug!("Failed to export resource {resource_id} as dmabuf: {e}");
492+
})?;
493+
494+
// Verify that the exported handle is actually a dmabuf
495+
if export.handle_type != RUTABAGA_MEM_HANDLE_TYPE_DMABUF {
496+
debug!(
497+
"Scanout resource {resource_id} was exported with handle type 0x{:x}, not DMABUF (0x{:x})",
498+
export.handle_type, RUTABAGA_MEM_HANDLE_TYPE_DMABUF
499+
);
500+
return Err(());
501+
}
425502

503+
let info_3d = self.rutabaga.query(resource_id).map_err(|e| {
504+
debug!("Failed to query resource {resource_id} for dmabuf info: {e}");
505+
})?;
506+
507+
debug!("Resource {resource_id} dmabuf info: fourcc=0x{:08x}, modifier=0x{:016x}, strides={:?}, offsets={:?}",
508+
info_3d.drm_fourcc, info_3d.modifier, info_3d.strides, info_3d.offsets
509+
);
510+
511+
// Currently we only ever have 1 plane, but the display frontend API is generic and
512+
// supports multiple
513+
const NUM_PLANES: u32 = 1;
514+
let dmabuf_fd = export.os_handle.into_raw_descriptor();
515+
let dmabuf_fds = [dmabuf_fd, -1, -1, -1];
516+
517+
let dmabuf_export = DmabufExport {
518+
dmabuf_fds,
519+
n_planes: NUM_PLANES,
520+
width: info_3d.width,
521+
height: info_3d.height,
522+
fourcc: info_3d.drm_fourcc,
523+
strides: info_3d.strides,
524+
offsets: info_3d.offsets,
525+
modifier: info_3d.modifier,
526+
};
527+
528+
// Import the dmabuf into the display backend
529+
let dmabuf_id =
530+
self.display_backend
531+
.import_dmabuf(&dmabuf_export)
532+
.map_err(|e| {
533+
warn!("Failed to import dmabuf for resource {resource_id}: {e}");
534+
})?;
535+
536+
debug!("Imported resource {resource_id} as dmabuf with ID {dmabuf_id}");
537+
538+
// Store the dmabuf ID in the resource
539+
resource.display_dmabuf_id = Some(dmabuf_id);
540+
dmabuf_id
541+
}
542+
};
543+
544+
// Configure scanout to use the imported dmabuf (no src_rect for now, use entire dmabuf)
545+
self.display_backend
546+
.configure_scanout_dmabuf(scanout_id, display_width, display_height, dmabuf_id, None)
547+
.map_err(|e| {
548+
debug!("Failed to configure dmabuf scanout for resource {resource_id}: {e}");
549+
})?;
550+
551+
debug!(
552+
"Successfully configured scanout {scanout_id} with dmabuf for resource {resource_id}"
553+
);
554+
Ok(VirtioGpuScanout {
555+
resource_id,
556+
uses_dmabuf: true,
557+
})
558+
}
559+
560+
#[allow(clippy::too_many_arguments)]
561+
fn configure_basic_framebuffer_scanout(
562+
&mut self,
563+
scanout_id: u32,
564+
resource_id: u32,
565+
display_width: u32,
566+
display_height: u32,
567+
width: u32,
568+
height: u32,
569+
format: ResourceFormat,
570+
) -> std::result::Result<VirtioGpuScanout, GpuResponse> {
426571
self.display_backend.configure_scanout(
427572
scanout_id,
428-
display_info.width,
429-
display_info.height,
573+
display_width,
574+
display_height,
430575
width,
431576
height,
432577
format,
433578
)?;
434579

435-
*scanout = Some(VirtioGpuScanout { resource_id });
436-
Ok(OkNoData)
580+
Ok(VirtioGpuScanout {
581+
resource_id,
582+
uses_dmabuf: false,
583+
})
437584
}
438585

439586
fn read_2d_resource(
@@ -473,14 +620,35 @@ impl VirtioGpu {
473620
.get(&resource_id)
474621
.ok_or(ErrInvalidResourceId)?;
475622

623+
#[cfg(target_os = "linux")]
624+
unsafe {
625+
#[link(name = "GL")]
626+
extern "C" {
627+
fn glFlush();
628+
}
629+
630+
glFlush()
631+
};
632+
476633
for scanout_id in resource.scanouts.iter_enabled() {
477-
let (frame_id, buffer) = self.display_backend.alloc_frame(scanout_id)?;
478-
if let Err(e) = Self::read_2d_resource(&mut self.rutabaga, resource, buffer) {
479-
log::error!("Failed to read resource {resource_id} for scanout {scanout_id}: {e}");
480-
return Err(ErrUnspec);
634+
if self.scanouts[scanout_id as usize]
635+
.as_ref()
636+
.unwrap()
637+
.uses_dmabuf
638+
{
639+
self.display_backend
640+
.present_dmabuf(scanout_id, Some(&rect))?;
641+
} else {
642+
let (frame_id, buffer) = self.display_backend.alloc_frame(scanout_id)?;
643+
if let Err(e) = Self::read_2d_resource(&mut self.rutabaga, resource, buffer) {
644+
log::error!(
645+
"Failed to read resource {resource_id} for scanout {scanout_id}: {e}"
646+
);
647+
return Err(ErrUnspec);
648+
}
649+
self.display_backend
650+
.present_frame(scanout_id, frame_id, Some(&rect))?
481651
}
482-
self.display_backend
483-
.present_frame(scanout_id, frame_id, Some(&rect))?
484652
}
485653

486654
#[cfg(windows)]

0 commit comments

Comments
 (0)