gpu: add Wayland cross-domain support via unified rutabaga backend
Replace the separate virglrenderer and gfxstream backends with a single rutabaga-based backend that supports cross-domain, virgl, and venus capsets simultaneously. This enables guest Wayland applications to composite on the host Wayland compositor via the virtio-gpu cross-domain protocol. Key changes: - New backend/rutabaga.rs implementing the Renderer trait via rutabaga_gfx - New --gpu-mode rutabaga (replaces virglrenderer/gfxstream modes) - New --capset cross-domain and --wayland-socket CLI args - Ungated rutabaga type conversions and capset constants - Implement get_shmem_config() for blob resource mapping (8 GiB region) - Old virgl.rs and gfxstream.rs kept as dead code for upstream merge compatibility Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1c021aaea6
commit
1460f1be1b
8 changed files with 1047 additions and 81 deletions
|
|
@ -14,7 +14,7 @@ edition = "2021"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["backend-virgl", "backend-gfxstream"]
|
default = []
|
||||||
xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"]
|
xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"]
|
||||||
backend-gfxstream = ["rutabaga_gfx/gfxstream"]
|
backend-gfxstream = ["rutabaga_gfx/gfxstream"]
|
||||||
backend-virgl = ["dep:virglrenderer"]
|
backend-virgl = ["dep:virglrenderer"]
|
||||||
|
|
@ -26,7 +26,7 @@ libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
[target.'cfg(not(target_env = "musl"))'.dependencies]
|
[target.'cfg(not(target_env = "musl"))'.dependencies]
|
||||||
rutabaga_gfx = "0.1.75"
|
rutabaga_gfx = { version = "0.1.75", features = ["virgl_renderer"] }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
virglrenderer = { git = "https://gitlab.freedesktop.org/mtjhrc/virglrenderer-rs.git", branch = "map_info", optional = true }
|
virglrenderer = { git = "https://gitlab.freedesktop.org/mtjhrc/virglrenderer-rs.git", branch = "map_info", optional = true }
|
||||||
vhost = { git = "https://git.dsg.is/dsg/vhost.git", features = ["vhost-user-backend"] }
|
vhost = { git = "https://git.dsg.is/dsg/vhost.git", features = ["vhost-user-backend"] }
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||||
|
|
||||||
#[cfg(any(feature = "backend-virgl", feature = "backend-gfxstream"))]
|
|
||||||
mod common;
|
mod common;
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
pub mod gfxstream;
|
pub mod gfxstream;
|
||||||
pub mod null;
|
pub mod null;
|
||||||
|
pub mod rutabaga;
|
||||||
#[cfg(feature = "backend-virgl")]
|
#[cfg(feature = "backend-virgl")]
|
||||||
pub mod virgl;
|
pub mod virgl;
|
||||||
|
|
|
||||||
845
vhost-device-gpu/src/backend/rutabaga.rs
Normal file
845
vhost-device-gpu/src/backend/rutabaga.rs
Normal file
|
|
@ -0,0 +1,845 @@
|
||||||
|
// Unified rutabaga backend device
|
||||||
|
// Copyright 2019 The ChromiumOS Authors
|
||||||
|
// Copyright 2026 Red Hat Inc
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
collections::BTreeMap,
|
||||||
|
io::IoSliceMut,
|
||||||
|
os::{
|
||||||
|
fd::{AsFd, FromRawFd},
|
||||||
|
raw::c_void,
|
||||||
|
},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::{debug, error, warn};
|
||||||
|
use rutabaga_gfx::{
|
||||||
|
ResourceCreate3D, Rutabaga, RutabagaBuilder, RutabagaFence, RutabagaFenceHandler,
|
||||||
|
RutabagaHandle, RutabagaIntoRawDescriptor, RutabagaIovec, RutabagaPath, Transfer3D,
|
||||||
|
RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD, RUTABAGA_MAP_ACCESS_MASK, RUTABAGA_MAP_ACCESS_READ,
|
||||||
|
RUTABAGA_MAP_ACCESS_RW, RUTABAGA_MAP_ACCESS_WRITE, RUTABAGA_MAP_CACHE_MASK,
|
||||||
|
RUTABAGA_PATH_TYPE_WAYLAND,
|
||||||
|
};
|
||||||
|
use vhost::vhost_user::{
|
||||||
|
gpu_message::{
|
||||||
|
VhostUserGpuCursorPos, VhostUserGpuEdidRequest, VhostUserGpuScanout, VhostUserGpuUpdate,
|
||||||
|
},
|
||||||
|
message::VhostUserMMapFlags,
|
||||||
|
Backend, GpuBackend,
|
||||||
|
};
|
||||||
|
use vhost_user_backend::{VringRwLock, VringT};
|
||||||
|
use virtio_bindings::virtio_gpu::VIRTIO_GPU_BLOB_MEM_HOST3D;
|
||||||
|
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryMmap, VolatileSlice};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
backend::{
|
||||||
|
common,
|
||||||
|
common::{
|
||||||
|
common_map_blob, common_set_scanout_disable, common_unmap_blob, AssociatedScanouts,
|
||||||
|
CursorConfig, VirtioGpuScanout,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
device::Error,
|
||||||
|
gpu_types::{FenceState, ResourceCreate3d, ResourceCreateBlob, Transfer3DDesc, VirtioGpuRing},
|
||||||
|
protocol::{
|
||||||
|
virtio_gpu_rect, GpuResponse,
|
||||||
|
GpuResponse::{
|
||||||
|
ErrInvalidParameter, ErrInvalidResourceId, ErrUnspec, OkCapset, OkCapsetInfo,
|
||||||
|
OkMapInfo, OkNoData, OkResourcePlaneInfo,
|
||||||
|
},
|
||||||
|
GpuResponsePlaneInfo, VirtioGpuResult, VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE,
|
||||||
|
VIRTIO_GPU_FLAG_INFO_RING_IDX, VIRTIO_GPU_MAX_SCANOUTS,
|
||||||
|
},
|
||||||
|
renderer::Renderer,
|
||||||
|
GpuConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Number of bytes per pixel for reading 2D resources (assuming RGBA8 format)
|
||||||
|
const READ_RESOURCE_BYTES_PER_PIXEL: u32 = 4;
|
||||||
|
|
||||||
|
// A local resource struct for the Rutabaga backend
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct RutabagaBackendResource {
|
||||||
|
pub id: u32,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
scanouts: common::AssociatedScanouts,
|
||||||
|
pub info_3d: Option<rutabaga_gfx::Resource3DInfo>,
|
||||||
|
pub handle: Option<Arc<RutabagaHandle>>,
|
||||||
|
pub blob_size: u64,
|
||||||
|
pub blob_shmem_offset: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RutabagaBackendResource {
|
||||||
|
fn calculate_size(&self) -> Result<usize, &str> {
|
||||||
|
let width = self.width as usize;
|
||||||
|
let height = self.height as usize;
|
||||||
|
let size = width
|
||||||
|
.checked_mul(height)
|
||||||
|
.ok_or("Multiplication of width and height overflowed")?
|
||||||
|
.checked_mul(READ_RESOURCE_BYTES_PER_PIXEL as usize)
|
||||||
|
.ok_or("Multiplication of result and bytes_per_pixel overflowed")?;
|
||||||
|
|
||||||
|
Ok(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RutabagaBackendResource {
|
||||||
|
/// Creates a new `RutabagaBackendResource` with 2D/3D metadata
|
||||||
|
pub fn new(resource_id: u32, width: u32, height: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
id: resource_id,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
scanouts: AssociatedScanouts::default(),
|
||||||
|
info_3d: None,
|
||||||
|
handle: None,
|
||||||
|
blob_size: 0,
|
||||||
|
blob_shmem_offset: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread-local storage for the Rutabaga instance.
|
||||||
|
// This allows each worker thread to have its own, non-shared instance.
|
||||||
|
thread_local! {
|
||||||
|
static TLS_RUTABAGA: RefCell<Option<Rutabaga>> = const { RefCell::new(None) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RutabagaAdapter {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
backend: Backend,
|
||||||
|
gpu_backend: Option<GpuBackend>,
|
||||||
|
resources: BTreeMap<u32, RutabagaBackendResource>,
|
||||||
|
fence_state: Arc<Mutex<FenceState>>,
|
||||||
|
scanouts: [Option<VirtioGpuScanout>; VIRTIO_GPU_MAX_SCANOUTS as usize],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RutabagaAdapter {
|
||||||
|
pub fn new(
|
||||||
|
queue_ctl: &VringRwLock,
|
||||||
|
backend: Backend,
|
||||||
|
gpu_config: &GpuConfig,
|
||||||
|
gpu_backend: Option<GpuBackend>,
|
||||||
|
) -> Self {
|
||||||
|
debug!("RutabagaAdapter::new() starting");
|
||||||
|
let fence_state = Arc::new(Mutex::new(FenceState::default()));
|
||||||
|
let fence = Self::create_fence_handler(queue_ctl.clone(), fence_state.clone());
|
||||||
|
|
||||||
|
// Lazily initialize Rutabaga for the thread
|
||||||
|
debug!("Initializing Rutabaga TLS");
|
||||||
|
TLS_RUTABAGA.with(|slot| {
|
||||||
|
if slot.borrow().is_none() {
|
||||||
|
debug!("Building Rutabaga (capsets: {}, wayland_socket: {:?})",
|
||||||
|
gpu_config.capsets(), gpu_config.wayland_socket());
|
||||||
|
let builder = Self::configure_rutabaga_builder(gpu_config, fence);
|
||||||
|
debug!("RutabagaBuilder configured, calling build()...");
|
||||||
|
let rb = match builder.build() {
|
||||||
|
Ok(rb) => {
|
||||||
|
debug!("Rutabaga built successfully");
|
||||||
|
rb
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to build Rutabaga: {e:?}");
|
||||||
|
panic!("Failed to build Rutabaga: {e:?}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*slot.borrow_mut() = Some(rb);
|
||||||
|
debug!("Rutabaga stored in TLS");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
debug!("RutabagaAdapter::new() complete");
|
||||||
|
Self {
|
||||||
|
backend,
|
||||||
|
gpu_backend,
|
||||||
|
fence_state,
|
||||||
|
resources: BTreeMap::new(),
|
||||||
|
scanouts: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_fence_handler(
|
||||||
|
queue_ctl: VringRwLock,
|
||||||
|
fence_state: Arc<Mutex<FenceState>>,
|
||||||
|
) -> RutabagaFenceHandler {
|
||||||
|
RutabagaFenceHandler::new(move |completed_fence: RutabagaFence| {
|
||||||
|
debug!(
|
||||||
|
"XXX - fence called: id={}, ring_idx={}",
|
||||||
|
completed_fence.fence_id, completed_fence.ring_idx
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut fence_state = fence_state.lock().unwrap();
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
let ring = match completed_fence.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX {
|
||||||
|
0 => VirtioGpuRing::Global,
|
||||||
|
_ => VirtioGpuRing::ContextSpecific {
|
||||||
|
ctx_id: completed_fence.ctx_id,
|
||||||
|
ring_idx: completed_fence.ring_idx,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
while i < fence_state.descs.len() {
|
||||||
|
debug!("XXX - fence_id: {}", fence_state.descs[i].fence_id);
|
||||||
|
if fence_state.descs[i].ring == ring
|
||||||
|
&& fence_state.descs[i].fence_id <= completed_fence.fence_id
|
||||||
|
{
|
||||||
|
let completed_desc = fence_state.descs.remove(i);
|
||||||
|
debug!(
|
||||||
|
"XXX - found fence: desc_index={}",
|
||||||
|
completed_desc.desc_index
|
||||||
|
);
|
||||||
|
|
||||||
|
queue_ctl
|
||||||
|
.add_used(completed_desc.desc_index, completed_desc.len)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
queue_ctl
|
||||||
|
.signal_used_queue()
|
||||||
|
.map_err(Error::NotificationFailed)
|
||||||
|
.unwrap();
|
||||||
|
debug!("Notification sent");
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the last completed fence for this context
|
||||||
|
fence_state
|
||||||
|
.completed_fences
|
||||||
|
.insert(ring, completed_fence.fence_id);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_rutabaga_builder(
|
||||||
|
gpu_config: &GpuConfig,
|
||||||
|
fence: RutabagaFenceHandler,
|
||||||
|
) -> RutabagaBuilder {
|
||||||
|
let mut builder = RutabagaBuilder::new(gpu_config.capsets().bits(), fence)
|
||||||
|
.set_use_egl(gpu_config.flags().use_egl)
|
||||||
|
.set_use_gles(gpu_config.flags().use_gles)
|
||||||
|
.set_use_surfaceless(gpu_config.flags().use_surfaceless)
|
||||||
|
.set_use_external_blob(true);
|
||||||
|
|
||||||
|
if let Some(wayland_socket) = gpu_config.wayland_socket() {
|
||||||
|
let paths = vec![RutabagaPath {
|
||||||
|
path: wayland_socket.to_path_buf(),
|
||||||
|
path_type: RUTABAGA_PATH_TYPE_WAYLAND,
|
||||||
|
}];
|
||||||
|
builder = builder.set_rutabaga_paths(Some(paths));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sglist_to_rutabaga_iovecs(
|
||||||
|
vecs: &[(GuestAddress, usize)],
|
||||||
|
mem: &GuestMemoryMmap,
|
||||||
|
) -> std::result::Result<Vec<RutabagaIovec>, ()> {
|
||||||
|
if vecs
|
||||||
|
.iter()
|
||||||
|
.any(|&(addr, len)| mem.get_slice(addr, len).is_err())
|
||||||
|
{
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rutabaga_iovecs: Vec<RutabagaIovec> = Vec::new();
|
||||||
|
for &(addr, len) in vecs {
|
||||||
|
let slice = mem.get_slice(addr, len).unwrap();
|
||||||
|
let iov = RutabagaIovec {
|
||||||
|
base: slice.ptr_guard_mut().as_ptr().cast::<c_void>(),
|
||||||
|
len,
|
||||||
|
};
|
||||||
|
rutabaga_iovecs.push(iov);
|
||||||
|
}
|
||||||
|
Ok(rutabaga_iovecs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_rutabaga<T, F: FnOnce(&mut Rutabaga) -> T>(f: F) -> T {
|
||||||
|
TLS_RUTABAGA.with(|slot| {
|
||||||
|
let mut opt = slot.borrow_mut();
|
||||||
|
let rb = opt.as_mut().expect("Rutabaga not initialized");
|
||||||
|
f(rb)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_2d_resource(
|
||||||
|
resource: &RutabagaBackendResource,
|
||||||
|
output: &mut [u8],
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let minimal_buffer_size = resource.calculate_size()?;
|
||||||
|
assert!(output.len() >= minimal_buffer_size);
|
||||||
|
|
||||||
|
let transfer = Transfer3D {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0,
|
||||||
|
w: resource.width,
|
||||||
|
h: resource.height,
|
||||||
|
d: 1,
|
||||||
|
level: 0,
|
||||||
|
stride: resource.width * READ_RESOURCE_BYTES_PER_PIXEL,
|
||||||
|
layer_stride: 0,
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
Self::with_rutabaga(|rutabaga| {
|
||||||
|
rutabaga.transfer_read(0, resource.id, transfer, Some(IoSliceMut::new(output)))
|
||||||
|
})
|
||||||
|
.map_err(|e| format!("{e}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn result_from_query(resource_id: u32) -> GpuResponse {
|
||||||
|
let Ok(query) = Self::with_rutabaga(|rutabaga| rutabaga.resource3d_info(resource_id))
|
||||||
|
else {
|
||||||
|
return OkNoData;
|
||||||
|
};
|
||||||
|
let mut plane_info = Vec::with_capacity(4);
|
||||||
|
for plane_index in 0..4 {
|
||||||
|
plane_info.push(GpuResponsePlaneInfo {
|
||||||
|
stride: query.strides[plane_index],
|
||||||
|
offset: query.offsets[plane_index],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let format_modifier = query.modifier;
|
||||||
|
OkResourcePlaneInfo {
|
||||||
|
format_modifier,
|
||||||
|
plane_info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderer for RutabagaAdapter {
|
||||||
|
fn resource_create_3d(&mut self, resource_id: u32, args: ResourceCreate3d) -> VirtioGpuResult {
|
||||||
|
let rutabaga_args: ResourceCreate3D = args.into();
|
||||||
|
Self::with_rutabaga(|rutabaga| rutabaga.resource_create_3d(resource_id, rutabaga_args))?;
|
||||||
|
|
||||||
|
let resource = RutabagaBackendResource {
|
||||||
|
id: resource_id,
|
||||||
|
width: rutabaga_args.width,
|
||||||
|
height: rutabaga_args.height,
|
||||||
|
scanouts: AssociatedScanouts::default(),
|
||||||
|
info_3d: None,
|
||||||
|
handle: None,
|
||||||
|
blob_size: 0,
|
||||||
|
blob_shmem_offset: None,
|
||||||
|
};
|
||||||
|
debug_assert!(
|
||||||
|
!self.resources.contains_key(&resource_id),
|
||||||
|
"Resource ID {resource_id} already exists in the resources map."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rely on rutabaga to check for duplicate resource ids.
|
||||||
|
self.resources.insert(resource_id, resource);
|
||||||
|
Ok(Self::result_from_query(resource_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unref_resource(&mut self, resource_id: u32) -> VirtioGpuResult {
|
||||||
|
let resource = self.resources.remove(&resource_id);
|
||||||
|
match resource {
|
||||||
|
None => return Err(ErrInvalidResourceId),
|
||||||
|
// The spec doesn't say anything about this situation and this doesn't actually seem
|
||||||
|
// to happen in practise but let's be careful and refuse to disable the resource.
|
||||||
|
// This keeps the internal state of the gpu device and the fronted consistent.
|
||||||
|
Some(resource) if resource.scanouts.has_any_enabled() => {
|
||||||
|
warn!(
|
||||||
|
"The driver requested unref_resource, but resource {resource_id} has \
|
||||||
|
associated scanouts, refusing to delete the resource."
|
||||||
|
);
|
||||||
|
return Err(ErrUnspec);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
Self::with_rutabaga(|rutabaga| rutabaga.unref_resource(resource_id))?;
|
||||||
|
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_write(
|
||||||
|
&mut self,
|
||||||
|
ctx_id: u32,
|
||||||
|
resource_id: u32,
|
||||||
|
transfer: Transfer3DDesc,
|
||||||
|
) -> VirtioGpuResult {
|
||||||
|
Self::with_rutabaga(|rutabaga| {
|
||||||
|
rutabaga.transfer_write(ctx_id, resource_id, transfer.into(), None)
|
||||||
|
})?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_write_2d(
|
||||||
|
&mut self,
|
||||||
|
ctx_id: u32,
|
||||||
|
resource_id: u32,
|
||||||
|
transfer: Transfer3DDesc,
|
||||||
|
) -> VirtioGpuResult {
|
||||||
|
Self::with_rutabaga(|rutabaga| {
|
||||||
|
rutabaga.transfer_write(ctx_id, resource_id, transfer.into(), None)
|
||||||
|
})?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_read(
|
||||||
|
&mut self,
|
||||||
|
ctx_id: u32,
|
||||||
|
resource_id: u32,
|
||||||
|
transfer: Transfer3DDesc,
|
||||||
|
buf: Option<VolatileSlice>,
|
||||||
|
) -> VirtioGpuResult {
|
||||||
|
let buf = buf.map(|vs| {
|
||||||
|
IoSliceMut::new(
|
||||||
|
// SAFETY: trivially safe
|
||||||
|
unsafe { std::slice::from_raw_parts_mut(vs.ptr_guard_mut().as_ptr(), vs.len()) },
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
Self::with_rutabaga(|rutabaga| {
|
||||||
|
rutabaga.transfer_read(ctx_id, resource_id, transfer.into(), buf)
|
||||||
|
})?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attach_backing(
|
||||||
|
&mut self,
|
||||||
|
resource_id: u32,
|
||||||
|
mem: &GuestMemoryMmap,
|
||||||
|
vecs: Vec<(GuestAddress, usize)>,
|
||||||
|
) -> VirtioGpuResult {
|
||||||
|
let rutabaga_iovecs =
|
||||||
|
Self::sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|()| GpuResponse::ErrUnspec)?;
|
||||||
|
Self::with_rutabaga(|rutabaga| rutabaga.attach_backing(resource_id, rutabaga_iovecs))?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detach_backing(&mut self, resource_id: u32) -> VirtioGpuResult {
|
||||||
|
Self::with_rutabaga(|rutabaga| rutabaga.detach_backing(resource_id))?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_cursor(
|
||||||
|
&mut self,
|
||||||
|
resource_id: u32,
|
||||||
|
cursor_pos: VhostUserGpuCursorPos,
|
||||||
|
hot_x: u32,
|
||||||
|
hot_y: u32,
|
||||||
|
) -> VirtioGpuResult {
|
||||||
|
if self.gpu_backend.is_none() {
|
||||||
|
return Ok(OkNoData);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = CursorConfig {
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cursor_resource = self
|
||||||
|
.resources
|
||||||
|
.get(&resource_id)
|
||||||
|
.ok_or(ErrInvalidResourceId)?;
|
||||||
|
|
||||||
|
if cursor_resource.width != config.width || cursor_resource.height != config.height {
|
||||||
|
error!("Cursor resource has invalid dimensions");
|
||||||
|
return Err(ErrInvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = common::common_read_cursor_resource(self, resource_id, config)?;
|
||||||
|
|
||||||
|
common::common_update_cursor(
|
||||||
|
// The existence of gpu_backend has been already checked above.
|
||||||
|
self.gpu_backend.as_ref().unwrap(),
|
||||||
|
cursor_pos,
|
||||||
|
hot_x,
|
||||||
|
hot_y,
|
||||||
|
&data,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_cursor(&mut self, resource_id: u32, cursor: VhostUserGpuCursorPos) -> VirtioGpuResult {
|
||||||
|
self.gpu_backend
|
||||||
|
.as_ref()
|
||||||
|
.map_or(Ok(OkNoData), |gpu_backend| {
|
||||||
|
common::common_move_cursor(gpu_backend, resource_id, cursor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource_assign_uuid(&self, _resource_id: u32) -> VirtioGpuResult {
|
||||||
|
error!("Not implemented: resource_assign_uuid");
|
||||||
|
Err(ErrUnspec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_capset_info(&self, index: u32) -> VirtioGpuResult {
|
||||||
|
debug!("get_capset_info index={index}");
|
||||||
|
let (capset_id, version, size) =
|
||||||
|
Self::with_rutabaga(|rutabaga| rutabaga.get_capset_info(index))?;
|
||||||
|
Ok(OkCapsetInfo {
|
||||||
|
capset_id,
|
||||||
|
version,
|
||||||
|
size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_capset(&self, capset_id: u32, version: u32) -> VirtioGpuResult {
|
||||||
|
let capset = Self::with_rutabaga(|rutabaga| rutabaga.get_capset(capset_id, version))?;
|
||||||
|
Ok(OkCapset(capset))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_context(
|
||||||
|
&mut self,
|
||||||
|
ctx_id: u32,
|
||||||
|
context_init: u32,
|
||||||
|
context_name: Option<&str>,
|
||||||
|
) -> VirtioGpuResult {
|
||||||
|
Self::with_rutabaga(|rutabaga| {
|
||||||
|
rutabaga.create_context(ctx_id, context_init, context_name)
|
||||||
|
})?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy_context(&mut self, ctx_id: u32) -> VirtioGpuResult {
|
||||||
|
Self::with_rutabaga(|rutabaga| rutabaga.destroy_context(ctx_id))?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn context_attach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult {
|
||||||
|
Self::with_rutabaga(|rutabaga| rutabaga.context_attach_resource(ctx_id, resource_id))?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn context_detach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult {
|
||||||
|
Self::with_rutabaga(|rutabaga| rutabaga.context_detach_resource(ctx_id, resource_id))?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_command(
|
||||||
|
&mut self,
|
||||||
|
ctx_id: u32,
|
||||||
|
commands: &mut [u8],
|
||||||
|
fence_ids: &[u64],
|
||||||
|
) -> VirtioGpuResult {
|
||||||
|
Self::with_rutabaga(|rutabaga| rutabaga.submit_command(ctx_id, commands, fence_ids))?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_fence(&mut self, rutabaga_fence: RutabagaFence) -> VirtioGpuResult {
|
||||||
|
Self::with_rutabaga(|rutabaga| rutabaga.create_fence(rutabaga_fence))?;
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_fence(
|
||||||
|
&mut self,
|
||||||
|
ring: VirtioGpuRing,
|
||||||
|
fence_id: u64,
|
||||||
|
desc_index: u16,
|
||||||
|
len: u32,
|
||||||
|
) -> bool {
|
||||||
|
common::common_process_fence(&self.fence_state, ring, fence_id, desc_index, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_event_poll_fd(&self) -> Option<EventFd> {
|
||||||
|
let result = Self::with_rutabaga(|rutabaga| {
|
||||||
|
rutabaga.poll_descriptor().map(|fd| {
|
||||||
|
// SAFETY: Safe, the fd should be valid, because Rutabaga guarantees it.
|
||||||
|
// into_raw_descriptor() returns a RawFd and makes sure SafeDescriptor::drop
|
||||||
|
// doesn't run.
|
||||||
|
unsafe { EventFd::from_raw_fd(fd.into_raw_descriptor()) }
|
||||||
|
})
|
||||||
|
});
|
||||||
|
debug!("get_event_poll_fd: {}", if result.is_some() { "Some(fd)" } else { "None" });
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event_poll(&self) {
|
||||||
|
Self::with_rutabaga(|rutabaga| {
|
||||||
|
rutabaga.event_poll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn force_ctx_0(&self) {
|
||||||
|
Self::with_rutabaga(|rutabaga| {
|
||||||
|
rutabaga.force_ctx_0();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_info(&self) -> VirtioGpuResult {
|
||||||
|
self.gpu_backend
|
||||||
|
.as_ref()
|
||||||
|
.map_or(Ok(GpuResponse::OkDisplayInfo(Vec::new())), |gpu_backend| {
|
||||||
|
common::common_display_info(gpu_backend)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_edid(&self, edid_req: VhostUserGpuEdidRequest) -> VirtioGpuResult {
|
||||||
|
self.gpu_backend.as_ref().map_or(
|
||||||
|
Ok(GpuResponse::OkEdid {
|
||||||
|
blob: Box::new([0u8; 0]),
|
||||||
|
}),
|
||||||
|
|gpu_backend| common::common_get_edid(gpu_backend, edid_req),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_scanout(
|
||||||
|
&mut self,
|
||||||
|
scanout_id: u32,
|
||||||
|
resource_id: u32,
|
||||||
|
rect: virtio_gpu_rect,
|
||||||
|
) -> VirtioGpuResult {
|
||||||
|
let Some(gpu_backend) = self.gpu_backend.as_ref() else {
|
||||||
|
return Ok(OkNoData);
|
||||||
|
};
|
||||||
|
|
||||||
|
let scanout_idx = scanout_id as usize;
|
||||||
|
if resource_id == 0 {
|
||||||
|
common_set_scanout_disable(&mut self.scanouts, scanout_idx);
|
||||||
|
|
||||||
|
gpu_backend
|
||||||
|
.set_scanout(&VhostUserGpuScanout {
|
||||||
|
scanout_id,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to disable scanout: {e:?}");
|
||||||
|
ErrUnspec
|
||||||
|
})?;
|
||||||
|
|
||||||
|
return Ok(OkNoData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there was a different resource previously associated with this scanout,
|
||||||
|
// disable the scanout on that old resource
|
||||||
|
if let Some(old_scanout) = &self.scanouts[scanout_idx] {
|
||||||
|
let old_resource_id = old_scanout.resource_id;
|
||||||
|
if old_resource_id != resource_id {
|
||||||
|
if let Some(old_resource) = self.resources.get_mut(&old_resource_id) {
|
||||||
|
old_resource.scanouts.disable(scanout_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let resource = self
|
||||||
|
.resources
|
||||||
|
.get_mut(&resource_id)
|
||||||
|
.ok_or(ErrInvalidResourceId)?;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Enabling legacy scanout scanout_id={scanout_id}, resource_id={resource_id}: {rect:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
gpu_backend
|
||||||
|
.set_scanout(&VhostUserGpuScanout {
|
||||||
|
scanout_id,
|
||||||
|
width: rect.width.into(),
|
||||||
|
height: rect.height.into(),
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to legacy set_scanout: {e:?}");
|
||||||
|
ErrUnspec
|
||||||
|
})?;
|
||||||
|
|
||||||
|
resource.scanouts.enable(scanout_id);
|
||||||
|
self.scanouts[scanout_idx] = Some(VirtioGpuScanout { resource_id });
|
||||||
|
|
||||||
|
// Send initial framebuffer update to QEMU
|
||||||
|
// This ensures the display is properly initialized
|
||||||
|
let resource_size = resource.calculate_size().map_err(|e| {
|
||||||
|
error!("Invalid resource size for scanout: {e:?}");
|
||||||
|
ErrUnspec
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut data = vec![0; resource_size];
|
||||||
|
|
||||||
|
if let Err(e) = Self::read_2d_resource(resource, &mut data) {
|
||||||
|
error!("Failed to read resource {resource_id} for initial scanout {scanout_id}: {e}");
|
||||||
|
} else {
|
||||||
|
// Send the initial framebuffer data to QEMU
|
||||||
|
gpu_backend
|
||||||
|
.update_scanout(
|
||||||
|
&VhostUserGpuUpdate {
|
||||||
|
scanout_id,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: resource.width,
|
||||||
|
height: resource.height,
|
||||||
|
},
|
||||||
|
&data,
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to send initial framebuffer update: {e:?}");
|
||||||
|
ErrUnspec
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_resource(&mut self, resource_id: u32, _rect: virtio_gpu_rect) -> VirtioGpuResult {
|
||||||
|
let Some(gpu_backend) = self.gpu_backend.as_ref() else {
|
||||||
|
return Ok(OkNoData);
|
||||||
|
};
|
||||||
|
|
||||||
|
if resource_id == 0 {
|
||||||
|
return Ok(OkNoData);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resource = self
|
||||||
|
.resources
|
||||||
|
.get(&resource_id)
|
||||||
|
.ok_or(ErrInvalidResourceId)?
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
for scanout_id in resource.scanouts.iter_enabled() {
|
||||||
|
let resource_size = resource.calculate_size().map_err(|e| {
|
||||||
|
error!("Invalid resource size for flushing: {e:?}");
|
||||||
|
ErrUnspec
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut data = vec![0; resource_size];
|
||||||
|
|
||||||
|
if let Err(e) = Self::read_2d_resource(&resource, &mut data) {
|
||||||
|
error!("Failed to read resource {resource_id} for scanout {scanout_id}: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpu_backend
|
||||||
|
.update_scanout(
|
||||||
|
&VhostUserGpuUpdate {
|
||||||
|
scanout_id,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: resource.width,
|
||||||
|
height: resource.height,
|
||||||
|
},
|
||||||
|
&data,
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to update_scanout: {e:?}");
|
||||||
|
ErrUnspec
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource_create_blob(
|
||||||
|
&mut self,
|
||||||
|
ctx_id: u32,
|
||||||
|
resource_create_blob: ResourceCreateBlob,
|
||||||
|
vecs: Vec<(vm_memory::GuestAddress, usize)>,
|
||||||
|
mem: &vm_memory::GuestMemoryMmap,
|
||||||
|
) -> VirtioGpuResult {
|
||||||
|
let mut rutabaga_iovecs = None;
|
||||||
|
|
||||||
|
if resource_create_blob.blob_flags & VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE != 0 {
|
||||||
|
panic!("GUEST_HANDLE unimplemented");
|
||||||
|
} else if resource_create_blob.blob_mem != VIRTIO_GPU_BLOB_MEM_HOST3D {
|
||||||
|
rutabaga_iovecs =
|
||||||
|
Some(Self::sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::with_rutabaga(|rutabaga| {
|
||||||
|
rutabaga.resource_create_blob(
|
||||||
|
ctx_id,
|
||||||
|
resource_create_blob.resource_id,
|
||||||
|
resource_create_blob.into(),
|
||||||
|
rutabaga_iovecs,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let resource = RutabagaBackendResource {
|
||||||
|
id: resource_create_blob.resource_id,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
scanouts: AssociatedScanouts::default(),
|
||||||
|
info_3d: None,
|
||||||
|
handle: None,
|
||||||
|
blob_size: resource_create_blob.size,
|
||||||
|
blob_shmem_offset: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
!self
|
||||||
|
.resources
|
||||||
|
.contains_key(&resource_create_blob.resource_id),
|
||||||
|
"Resource ID {} already exists in the resources map.",
|
||||||
|
resource_create_blob.resource_id
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rely on rutabaga to check for duplicate resource ids.
|
||||||
|
self.resources
|
||||||
|
.insert(resource_create_blob.resource_id, resource);
|
||||||
|
Ok(Self::result_from_query(resource_create_blob.resource_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource_map_blob(&mut self, resource_id: u32, offset: u64) -> VirtioGpuResult {
|
||||||
|
let resource = self
|
||||||
|
.resources
|
||||||
|
.get_mut(&resource_id)
|
||||||
|
.ok_or(ErrInvalidResourceId)?;
|
||||||
|
|
||||||
|
let map_info = Self::with_rutabaga(|rutabaga| rutabaga.map_info(resource_id))
|
||||||
|
.map_err(|_| ErrUnspec)?;
|
||||||
|
|
||||||
|
let export = Self::with_rutabaga(|rutabaga| rutabaga.export_blob(resource_id))
|
||||||
|
.map_err(|_| ErrUnspec)?;
|
||||||
|
|
||||||
|
// Check handle type - we don't support OPAQUE_FD mapping
|
||||||
|
if export.handle_type == RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD {
|
||||||
|
return Err(ErrUnspec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map_info access flags to VhostUserMMapFlags
|
||||||
|
let flags = match map_info & RUTABAGA_MAP_ACCESS_MASK {
|
||||||
|
RUTABAGA_MAP_ACCESS_READ => VhostUserMMapFlags::default(),
|
||||||
|
RUTABAGA_MAP_ACCESS_WRITE => VhostUserMMapFlags::WRITABLE,
|
||||||
|
RUTABAGA_MAP_ACCESS_RW => VhostUserMMapFlags::WRITABLE,
|
||||||
|
_ => {
|
||||||
|
error!("Invalid access mask for blob resource, map_info: {map_info}");
|
||||||
|
return Err(ErrUnspec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
common_map_blob(
|
||||||
|
&self.backend,
|
||||||
|
flags,
|
||||||
|
&export.os_handle.as_fd(),
|
||||||
|
resource.blob_size,
|
||||||
|
offset,
|
||||||
|
resource_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
resource.blob_shmem_offset = Some(offset);
|
||||||
|
|
||||||
|
// Return cache flags only (access flags not part of virtio-gpu spec)
|
||||||
|
Ok(OkMapInfo {
|
||||||
|
map_info: map_info & RUTABAGA_MAP_CACHE_MASK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource_unmap_blob(&mut self, resource_id: u32) -> VirtioGpuResult {
|
||||||
|
let resource = self
|
||||||
|
.resources
|
||||||
|
.get_mut(&resource_id)
|
||||||
|
.ok_or(ErrInvalidResourceId)?;
|
||||||
|
|
||||||
|
let Some(offset) = resource.blob_shmem_offset else {
|
||||||
|
log::warn!(
|
||||||
|
"Guest tried to unmap blob resource with resource_id={resource_id}, but it is not \
|
||||||
|
mapped!"
|
||||||
|
);
|
||||||
|
return Err(ErrInvalidParameter);
|
||||||
|
};
|
||||||
|
|
||||||
|
common_unmap_blob(&self.backend, resource.blob_size, offset)?;
|
||||||
|
|
||||||
|
resource.blob_shmem_offset = None;
|
||||||
|
|
||||||
|
Ok(OkNoData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -183,7 +183,6 @@ impl VirglRendererAdapter {
|
||||||
.use_render_server(venus_enabled)
|
.use_render_server(venus_enabled)
|
||||||
.use_egl(config.flags().use_egl)
|
.use_egl(config.flags().use_egl)
|
||||||
.use_gles(config.flags().use_gles)
|
.use_gles(config.flags().use_gles)
|
||||||
.use_glx(config.flags().use_glx)
|
|
||||||
.use_surfaceless(config.flags().use_surfaceless)
|
.use_surfaceless(config.flags().use_surfaceless)
|
||||||
.use_external_blob(true)
|
.use_external_blob(true)
|
||||||
.use_async_fence_cb(true)
|
.use_async_fence_cb(true)
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ use rutabaga_gfx::RutabagaFence;
|
||||||
use thiserror::Error as ThisError;
|
use thiserror::Error as ThisError;
|
||||||
use vhost::vhost_user::{
|
use vhost::vhost_user::{
|
||||||
gpu_message::{VhostUserGpuCursorPos, VhostUserGpuEdidRequest},
|
gpu_message::{VhostUserGpuCursorPos, VhostUserGpuEdidRequest},
|
||||||
message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures},
|
message::{VhostUserProtocolFeatures, VhostUserShMemConfig, VhostUserVirtioFeatures},
|
||||||
Backend, GpuBackend,
|
Backend, GpuBackend,
|
||||||
};
|
};
|
||||||
use vhost_user_backend::{VhostUserBackend, VringEpollHandler, VringRwLock, VringT};
|
use vhost_user_backend::{VhostUserBackend, VringEpollHandler, VringRwLock, VringT};
|
||||||
|
|
@ -76,6 +76,7 @@ use vmm_sys_util::{
|
||||||
|
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
use crate::backend::gfxstream::GfxstreamAdapter;
|
use crate::backend::gfxstream::GfxstreamAdapter;
|
||||||
|
use crate::backend::rutabaga::RutabagaAdapter;
|
||||||
#[cfg(feature = "backend-virgl")]
|
#[cfg(feature = "backend-virgl")]
|
||||||
use crate::backend::virgl::VirglRendererAdapter;
|
use crate::backend::virgl::VirglRendererAdapter;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -663,6 +664,22 @@ impl VhostUserGpuBackendInner {
|
||||||
vrings
|
vrings
|
||||||
),
|
),
|
||||||
|
|
||||||
|
GpuMode::Rutabaga => handle_adapter!(
|
||||||
|
RutabagaAdapter,
|
||||||
|
TLS_RUTABAGA_ADAPTER,
|
||||||
|
|control_vring, backend, gpu_backend| -> io::Result<RutabagaAdapter> {
|
||||||
|
Ok(RutabagaAdapter::new(
|
||||||
|
control_vring,
|
||||||
|
backend,
|
||||||
|
&self.gpu_config,
|
||||||
|
gpu_backend,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
self,
|
||||||
|
device_event,
|
||||||
|
vrings
|
||||||
|
),
|
||||||
|
|
||||||
GpuMode::Null => handle_adapter!(
|
GpuMode::Null => handle_adapter!(
|
||||||
NullAdapter,
|
NullAdapter,
|
||||||
TLS_NULL,
|
TLS_NULL,
|
||||||
|
|
@ -738,6 +755,17 @@ impl VhostUserBackend for VhostUserGpuBackend {
|
||||||
| VhostUserProtocolFeatures::SHMEM
|
| VhostUserProtocolFeatures::SHMEM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_shmem_config(&self) -> IoResult<VhostUserShMemConfig> {
|
||||||
|
// Shared memory region for mapping blob resources (dmabufs) into guest
|
||||||
|
// address space. ID 1 per virtio-gpu spec, size matches crosvm default.
|
||||||
|
const VIRTIO_GPU_SHM_ID_HOST_VISIBLE: u8 = 1;
|
||||||
|
const SHMEM_SIZE: u64 = 1 << 33; // 8 GiB
|
||||||
|
Ok(VhostUserShMemConfig::new(&[(
|
||||||
|
VIRTIO_GPU_SHM_ID_HOST_VISIBLE,
|
||||||
|
SHMEM_SIZE,
|
||||||
|
)]))
|
||||||
|
}
|
||||||
|
|
||||||
fn set_event_idx(&self, enabled: bool) {
|
fn set_event_idx(&self, enabled: bool) {
|
||||||
self.inner.lock().unwrap().event_idx_enabled = enabled;
|
self.inner.lock().unwrap().event_idx_enabled = enabled;
|
||||||
debug!("Event idx set to: {enabled}");
|
debug!("Event idx set to: {enabled}");
|
||||||
|
|
@ -939,8 +967,7 @@ mod tests {
|
||||||
|
|
||||||
fn init() -> (Arc<VhostUserGpuBackend>, GuestMemoryAtomic<GuestMemoryMmap>) {
|
fn init() -> (Arc<VhostUserGpuBackend>, GuestMemoryAtomic<GuestMemoryMmap>) {
|
||||||
let config = GpuConfigBuilder::default()
|
let config = GpuConfigBuilder::default()
|
||||||
.set_gpu_mode(GpuMode::VirglRenderer)
|
.set_gpu_mode(GpuMode::Null)
|
||||||
.set_capset(GpuCapset::VIRGL | GpuCapset::VIRGL2)
|
|
||||||
.set_flags(GpuFlags::default())
|
.set_flags(GpuFlags::default())
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1455,7 +1482,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_verify_backend() {
|
fn test_verify_backend() {
|
||||||
let gpu_config = GpuConfigBuilder::default()
|
let gpu_config = GpuConfigBuilder::default()
|
||||||
.set_gpu_mode(GpuMode::VirglRenderer)
|
.set_gpu_mode(GpuMode::Null)
|
||||||
.set_flags(GpuFlags::default())
|
.set_flags(GpuFlags::default())
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1522,6 +1549,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "backend-virgl")]
|
||||||
mod test_image {
|
mod test_image {
|
||||||
use super::*;
|
use super::*;
|
||||||
const GREEN_PIXEL: u32 = 0x00FF_00FF;
|
const GREEN_PIXEL: u32 = 0x00FF_00FF;
|
||||||
|
|
@ -1542,6 +1570,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "backend-virgl")]
|
||||||
fn split_into_mem_entries(
|
fn split_into_mem_entries(
|
||||||
addr: GuestAddress,
|
addr: GuestAddress,
|
||||||
len: u32,
|
len: u32,
|
||||||
|
|
@ -1572,6 +1601,7 @@ mod tests {
|
||||||
entries
|
entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "backend-virgl")]
|
||||||
fn new_hdr(type_: u32) -> virtio_gpu_ctrl_hdr {
|
fn new_hdr(type_: u32) -> virtio_gpu_ctrl_hdr {
|
||||||
virtio_gpu_ctrl_hdr {
|
virtio_gpu_ctrl_hdr {
|
||||||
type_: type_.into(),
|
type_: type_.into(),
|
||||||
|
|
@ -1579,6 +1609,24 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "backend-virgl")]
|
||||||
|
fn init_virgl() -> (Arc<VhostUserGpuBackend>, GuestMemoryAtomic<GuestMemoryMmap>) {
|
||||||
|
let config = GpuConfigBuilder::default()
|
||||||
|
.set_gpu_mode(GpuMode::VirglRenderer)
|
||||||
|
.set_capset(GpuCapset::VIRGL | GpuCapset::VIRGL2)
|
||||||
|
.set_flags(GpuFlags::default())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let backend = VhostUserGpuBackend::new(config).unwrap();
|
||||||
|
let mem = GuestMemoryAtomic::new(
|
||||||
|
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
backend.update_memory(mem.clone()).unwrap();
|
||||||
|
(backend, mem)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "backend-virgl")]
|
||||||
rusty_fork_test! {
|
rusty_fork_test! {
|
||||||
/// This test uses multiple gpu commands, it crates a resource, writes a test image into it and
|
/// This test uses multiple gpu commands, it crates a resource, writes a test image into it and
|
||||||
/// then present the display output.
|
/// then present the display output.
|
||||||
|
|
@ -1604,7 +1652,7 @@ mod tests {
|
||||||
fd_drm_fourcc: 875_708_993, // This is a placeholder; actual value depends on the backend.
|
fd_drm_fourcc: 875_708_993, // This is a placeholder; actual value depends on the backend.
|
||||||
};
|
};
|
||||||
|
|
||||||
let (backend, mem) = init();
|
let (backend, mem) = init_virgl();
|
||||||
let (mut gpu_frontend, gpu_backend) = gpu_backend_pair();
|
let (mut gpu_frontend, gpu_backend) = gpu_backend_pair();
|
||||||
gpu_frontend
|
gpu_frontend
|
||||||
.set_read_timeout(Some(Duration::from_secs(10)))
|
.set_read_timeout(Some(Duration::from_secs(10)))
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
/// Generates an implementation of `From<Transfer3DDesc>` for any compatible
|
/// Generates an implementation of `From<Transfer3DDesc>` for any compatible
|
||||||
/// target struct.
|
/// target struct.
|
||||||
#[cfg(any(feature = "backend-virgl", feature = "backend-gfxstream"))]
|
|
||||||
macro_rules! impl_transfer3d_from_desc {
|
macro_rules! impl_transfer3d_from_desc {
|
||||||
($target:path) => {
|
($target:path) => {
|
||||||
impl From<Transfer3DDesc> for $target {
|
impl From<Transfer3DDesc> for $target {
|
||||||
|
|
@ -26,7 +25,6 @@ macro_rules! impl_transfer3d_from_desc {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "backend-virgl", feature = "backend-gfxstream"))]
|
|
||||||
macro_rules! impl_from_resource_create3d {
|
macro_rules! impl_from_resource_create3d {
|
||||||
($target:ty) => {
|
($target:ty) => {
|
||||||
impl From<ResourceCreate3d> for $target {
|
impl From<ResourceCreate3d> for $target {
|
||||||
|
|
@ -48,7 +46,6 @@ macro_rules! impl_from_resource_create3d {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "backend-virgl", feature = "backend-gfxstream"))]
|
|
||||||
macro_rules! impl_from_resource_create_blob {
|
macro_rules! impl_from_resource_create_blob {
|
||||||
($target:ty) => {
|
($target:ty) => {
|
||||||
impl From<ResourceCreateBlob> for $target {
|
impl From<ResourceCreateBlob> for $target {
|
||||||
|
|
@ -66,7 +63,6 @@ macro_rules! impl_from_resource_create_blob {
|
||||||
|
|
||||||
use std::{collections::BTreeMap, os::raw::c_void};
|
use std::{collections::BTreeMap, os::raw::c_void};
|
||||||
|
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
|
||||||
use rutabaga_gfx::Transfer3D;
|
use rutabaga_gfx::Transfer3D;
|
||||||
#[cfg(feature = "backend-virgl")]
|
#[cfg(feature = "backend-virgl")]
|
||||||
use virglrenderer::Transfer3D as VirglTransfer3D;
|
use virglrenderer::Transfer3D as VirglTransfer3D;
|
||||||
|
|
@ -105,9 +101,8 @@ impl Transfer3DDesc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Invoke the macro for both targets
|
// Invoke the macro for all targets
|
||||||
// rutabaga_gfx::Transfer3D
|
// rutabaga_gfx::Transfer3D
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
|
||||||
impl_transfer3d_from_desc!(Transfer3D);
|
impl_transfer3d_from_desc!(Transfer3D);
|
||||||
// virglrenderer::Transfer3D
|
// virglrenderer::Transfer3D
|
||||||
#[cfg(feature = "backend-virgl")]
|
#[cfg(feature = "backend-virgl")]
|
||||||
|
|
@ -157,13 +152,11 @@ pub struct ResourceCreate3d {
|
||||||
pub flags: u32,
|
pub flags: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invoke the macro for both targets
|
// Invoke the macro for all targets
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
|
||||||
impl_from_resource_create3d!(rutabaga_gfx::ResourceCreate3D);
|
impl_from_resource_create3d!(rutabaga_gfx::ResourceCreate3D);
|
||||||
#[cfg(feature = "backend-virgl")]
|
#[cfg(feature = "backend-virgl")]
|
||||||
impl_from_resource_create3d!(virglrenderer::ResourceCreate3D);
|
impl_from_resource_create3d!(virglrenderer::ResourceCreate3D);
|
||||||
|
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
|
||||||
impl_from_resource_create_blob!(rutabaga_gfx::ResourceCreateBlob);
|
impl_from_resource_create_blob!(rutabaga_gfx::ResourceCreateBlob);
|
||||||
#[cfg(feature = "backend-virgl")]
|
#[cfg(feature = "backend-virgl")]
|
||||||
impl_from_resource_create_blob!(virglrenderer::ResourceCreateBlob);
|
impl_from_resource_create_blob!(virglrenderer::ResourceCreateBlob);
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,7 @@ pub mod renderer;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod testutils;
|
pub(crate) mod testutils;
|
||||||
|
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
use std::os::fd::OwnedFd;
|
use std::os::fd::OwnedFd;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Display, Formatter},
|
fmt::{Display, Formatter},
|
||||||
|
|
@ -31,22 +29,26 @@ use std::{
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use rutabaga_gfx::{
|
||||||
|
RUTABAGA_CAPSET_CROSS_DOMAIN, RUTABAGA_CAPSET_VENUS, RUTABAGA_CAPSET_VIRGL,
|
||||||
|
RUTABAGA_CAPSET_VIRGL2,
|
||||||
|
};
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
use rutabaga_gfx::{RUTABAGA_CAPSET_GFXSTREAM_GLES, RUTABAGA_CAPSET_GFXSTREAM_VULKAN};
|
use rutabaga_gfx::{RUTABAGA_CAPSET_GFXSTREAM_GLES, RUTABAGA_CAPSET_GFXSTREAM_VULKAN};
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
use rutabaga_gfx::{RUTABAGA_CAPSET_VENUS, RUTABAGA_CAPSET_VIRGL, RUTABAGA_CAPSET_VIRGL2};
|
|
||||||
use thiserror::Error as ThisError;
|
use thiserror::Error as ThisError;
|
||||||
use vhost_user_backend::VhostUserDaemon;
|
use vhost_user_backend::VhostUserDaemon;
|
||||||
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
|
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
|
||||||
|
|
||||||
use crate::device::VhostUserGpuBackend;
|
use crate::device::VhostUserGpuBackend;
|
||||||
|
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
pub const DEFAULT_VIRGLRENDER_CAPSET_MASK: GpuCapset = GpuCapset::ALL_VIRGLRENDERER_CAPSETS;
|
pub const DEFAULT_VIRGLRENDER_CAPSET_MASK: GpuCapset = GpuCapset::ALL_VIRGLRENDERER_CAPSETS;
|
||||||
|
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
pub const DEFAULT_GFXSTREAM_CAPSET_MASK: GpuCapset = GpuCapset::ALL_GFXSTREAM_CAPSETS;
|
pub const DEFAULT_GFXSTREAM_CAPSET_MASK: GpuCapset = GpuCapset::ALL_GFXSTREAM_CAPSETS;
|
||||||
|
|
||||||
|
pub const DEFAULT_RUTABAGA_CAPSET_MASK: GpuCapset =
|
||||||
|
GpuCapset::VIRGL2.union(GpuCapset::CROSS_DOMAIN);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
|
||||||
pub enum GpuMode {
|
pub enum GpuMode {
|
||||||
#[value(name = "virglrenderer", alias("virgl-renderer"))]
|
#[value(name = "virglrenderer", alias("virgl-renderer"))]
|
||||||
|
|
@ -54,6 +56,8 @@ pub enum GpuMode {
|
||||||
VirglRenderer,
|
VirglRenderer,
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
Gfxstream,
|
Gfxstream,
|
||||||
|
#[value(name = "rutabaga")]
|
||||||
|
Rutabaga,
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,6 +68,7 @@ impl Display for GpuMode {
|
||||||
Self::VirglRenderer => write!(f, "virglrenderer"),
|
Self::VirglRenderer => write!(f, "virglrenderer"),
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
Self::Gfxstream => write!(f, "gfxstream"),
|
Self::Gfxstream => write!(f, "gfxstream"),
|
||||||
|
Self::Rutabaga => write!(f, "rutabaga"),
|
||||||
Self::Null => write!(f, "null"),
|
Self::Null => write!(f, "null"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,13 +78,10 @@ bitflags! {
|
||||||
/// A bitmask for representing supported gpu capability sets.
|
/// A bitmask for representing supported gpu capability sets.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct GpuCapset: u64 {
|
pub struct GpuCapset: u64 {
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
const VIRGL = 1 << RUTABAGA_CAPSET_VIRGL as u64;
|
const VIRGL = 1 << RUTABAGA_CAPSET_VIRGL as u64;
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
const VIRGL2 = 1 << RUTABAGA_CAPSET_VIRGL2 as u64;
|
const VIRGL2 = 1 << RUTABAGA_CAPSET_VIRGL2 as u64;
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
const VENUS = 1 << RUTABAGA_CAPSET_VENUS as u64;
|
const VENUS = 1 << RUTABAGA_CAPSET_VENUS as u64;
|
||||||
#[cfg(feature = "backend-virgl")]
|
const CROSS_DOMAIN = 1 << RUTABAGA_CAPSET_CROSS_DOMAIN as u64;
|
||||||
const ALL_VIRGLRENDERER_CAPSETS = Self::VIRGL.bits() | Self::VIRGL2.bits() | Self::VENUS.bits();
|
const ALL_VIRGLRENDERER_CAPSETS = Self::VIRGL.bits() | Self::VIRGL2.bits() | Self::VENUS.bits();
|
||||||
|
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
|
|
@ -106,12 +108,10 @@ impl Display for GpuCapset {
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
match capset {
|
match capset {
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
Self::VIRGL => write!(f, "virgl")?,
|
Self::VIRGL => write!(f, "virgl")?,
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
Self::VIRGL2 => write!(f, "virgl2")?,
|
Self::VIRGL2 => write!(f, "virgl2")?,
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
Self::VENUS => write!(f, "venus")?,
|
Self::VENUS => write!(f, "venus")?,
|
||||||
|
Self::CROSS_DOMAIN => write!(f, "cross-domain")?,
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
Self::GFXSTREAM_VULKAN => write!(f, "gfxstream-vulkan")?,
|
Self::GFXSTREAM_VULKAN => write!(f, "gfxstream-vulkan")?,
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
|
|
@ -137,8 +137,8 @@ pub struct GpuConfig {
|
||||||
gpu_mode: GpuMode,
|
gpu_mode: GpuMode,
|
||||||
capset: GpuCapset,
|
capset: GpuCapset,
|
||||||
flags: GpuFlags,
|
flags: GpuFlags,
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
render_server_fd: Option<OwnedFd>,
|
render_server_fd: Option<OwnedFd>,
|
||||||
|
wayland_socket: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
@ -146,8 +146,8 @@ pub struct GpuConfigBuilder {
|
||||||
gpu_mode: Option<GpuMode>,
|
gpu_mode: Option<GpuMode>,
|
||||||
capset: Option<GpuCapset>,
|
capset: Option<GpuCapset>,
|
||||||
flags: Option<GpuFlags>,
|
flags: Option<GpuFlags>,
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
gpu_device: Option<PathBuf>,
|
gpu_device: Option<PathBuf>,
|
||||||
|
wayland_socket: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpuConfigBuilder {
|
impl GpuConfigBuilder {
|
||||||
|
|
@ -166,18 +166,32 @@ impl GpuConfigBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
pub fn set_gpu_device(mut self, gpu_device: PathBuf) -> Self {
|
pub fn set_gpu_device(mut self, gpu_device: PathBuf) -> Self {
|
||||||
self.gpu_device = Some(gpu_device);
|
self.gpu_device = Some(gpu_device);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_wayland_socket(mut self, path: PathBuf) -> Self {
|
||||||
|
self.wayland_socket = Some(path);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gpu_mode_supports_gpu_device(gpu_mode: GpuMode) -> bool {
|
||||||
|
match gpu_mode {
|
||||||
|
#[cfg(feature = "backend-virgl")]
|
||||||
|
GpuMode::VirglRenderer => true,
|
||||||
|
GpuMode::Rutabaga => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_capset(gpu_mode: GpuMode, capset: GpuCapset) -> Result<(), GpuConfigError> {
|
fn validate_capset(gpu_mode: GpuMode, capset: GpuCapset) -> Result<(), GpuConfigError> {
|
||||||
let supported_capset_mask = match gpu_mode {
|
let supported_capset_mask = match gpu_mode {
|
||||||
#[cfg(feature = "backend-virgl")]
|
#[cfg(feature = "backend-virgl")]
|
||||||
GpuMode::VirglRenderer => GpuCapset::ALL_VIRGLRENDERER_CAPSETS,
|
GpuMode::VirglRenderer => GpuCapset::ALL_VIRGLRENDERER_CAPSETS,
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
GpuMode::Gfxstream => GpuCapset::ALL_GFXSTREAM_CAPSETS,
|
GpuMode::Gfxstream => GpuCapset::ALL_GFXSTREAM_CAPSETS,
|
||||||
|
GpuMode::Rutabaga => GpuCapset::ALL_VIRGLRENDERER_CAPSETS | GpuCapset::CROSS_DOMAIN,
|
||||||
GpuMode::Null => GpuCapset::empty(),
|
GpuMode::Null => GpuCapset::empty(),
|
||||||
};
|
};
|
||||||
for capset in capset.iter() {
|
for capset in capset.iter() {
|
||||||
|
|
@ -197,6 +211,7 @@ impl GpuConfigBuilder {
|
||||||
GpuMode::VirglRenderer => DEFAULT_VIRGLRENDER_CAPSET_MASK,
|
GpuMode::VirglRenderer => DEFAULT_VIRGLRENDER_CAPSET_MASK,
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
GpuMode::Gfxstream => DEFAULT_GFXSTREAM_CAPSET_MASK,
|
GpuMode::Gfxstream => DEFAULT_GFXSTREAM_CAPSET_MASK,
|
||||||
|
GpuMode::Rutabaga => DEFAULT_RUTABAGA_CAPSET_MASK,
|
||||||
GpuMode::Null => GpuCapset::empty(),
|
GpuMode::Null => GpuCapset::empty(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -207,12 +222,23 @@ impl GpuConfigBuilder {
|
||||||
return Err(GpuConfigError::GlesRequiredByGfxstream);
|
return Err(GpuConfigError::GlesRequiredByGfxstream);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "backend-virgl")]
|
if self.gpu_device.is_some() && !Self::gpu_mode_supports_gpu_device(gpu_mode) {
|
||||||
if self.gpu_device.is_some() && !matches!(gpu_mode, GpuMode::VirglRenderer) {
|
|
||||||
return Err(GpuConfigError::GpuDeviceNotSupportedByMode);
|
return Err(GpuConfigError::GpuDeviceNotSupportedByMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "backend-virgl")]
|
// Cross-domain / wayland socket validation
|
||||||
|
if capset.contains(GpuCapset::CROSS_DOMAIN) && self.wayland_socket.is_none() {
|
||||||
|
return Err(GpuConfigError::WaylandSocketRequired);
|
||||||
|
}
|
||||||
|
if !capset.contains(GpuCapset::CROSS_DOMAIN) && self.wayland_socket.is_some() {
|
||||||
|
return Err(GpuConfigError::WaylandSocketWithoutCrossDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIRGL without VIRGL2 validation
|
||||||
|
if capset.contains(GpuCapset::VIRGL) && !capset.contains(GpuCapset::VIRGL2) {
|
||||||
|
return Err(GpuConfigError::VirglWithoutVirgl2);
|
||||||
|
}
|
||||||
|
|
||||||
let render_server_fd = if let Some(gpu_device) = self.gpu_device {
|
let render_server_fd = if let Some(gpu_device) = self.gpu_device {
|
||||||
let fd = File::open(&gpu_device)
|
let fd = File::open(&gpu_device)
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
|
|
@ -226,8 +252,8 @@ impl GpuConfigBuilder {
|
||||||
gpu_mode,
|
gpu_mode,
|
||||||
capset,
|
capset,
|
||||||
flags,
|
flags,
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
render_server_fd,
|
render_server_fd,
|
||||||
|
wayland_socket: self.wayland_socket,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -235,7 +261,6 @@ impl GpuConfigBuilder {
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct GpuFlags {
|
pub struct GpuFlags {
|
||||||
pub use_egl: bool,
|
pub use_egl: bool,
|
||||||
pub use_glx: bool,
|
|
||||||
pub use_gles: bool,
|
pub use_gles: bool,
|
||||||
pub use_surfaceless: bool,
|
pub use_surfaceless: bool,
|
||||||
pub headless: bool,
|
pub headless: bool,
|
||||||
|
|
@ -246,7 +271,6 @@ impl GpuFlags {
|
||||||
pub const fn new_default() -> Self {
|
pub const fn new_default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
use_egl: true,
|
use_egl: true,
|
||||||
use_glx: false,
|
|
||||||
use_gles: true,
|
use_gles: true,
|
||||||
use_surfaceless: true,
|
use_surfaceless: true,
|
||||||
headless: false,
|
headless: false,
|
||||||
|
|
@ -272,6 +296,12 @@ pub enum GpuConfigError {
|
||||||
GpuDeviceNotSupportedByMode,
|
GpuDeviceNotSupportedByMode,
|
||||||
#[error("Failed to open GPU device '{0}': {1}")]
|
#[error("Failed to open GPU device '{0}': {1}")]
|
||||||
InvalidGpuDevice(PathBuf, std::io::Error),
|
InvalidGpuDevice(PathBuf, std::io::Error),
|
||||||
|
#[error("--wayland-socket is required when cross-domain capset is enabled")]
|
||||||
|
WaylandSocketRequired,
|
||||||
|
#[error("--wayland-socket should only be specified with cross-domain capset")]
|
||||||
|
WaylandSocketWithoutCrossDomain,
|
||||||
|
#[error("virgl capset specified without virgl2 — virglrenderer will not initialize. Add virgl2 to the capset list")]
|
||||||
|
VirglWithoutVirgl2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpuConfig {
|
impl GpuConfig {
|
||||||
|
|
@ -287,10 +317,13 @@ impl GpuConfig {
|
||||||
&self.flags
|
&self.flags
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
pub fn render_server_fd(&self) -> Option<&OwnedFd> {
|
pub fn render_server_fd(&self) -> Option<&OwnedFd> {
|
||||||
self.render_server_fd.as_ref()
|
self.render_server_fd.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn wayland_socket(&self) -> Option<&Path> {
|
||||||
|
self.wayland_socket.as_deref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, ThisError)]
|
#[derive(Debug, ThisError)]
|
||||||
|
|
@ -322,16 +355,13 @@ pub fn start_backend(socket_path: &Path, config: GpuConfig) -> Result<(), StartE
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
fn assert_gpu_device_fails_for_mode(mode: GpuMode) {
|
fn assert_gpu_device_fails_for_mode(mode: GpuMode) {
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
let result = GpuConfigBuilder::default()
|
let result = GpuConfigBuilder::default()
|
||||||
.set_gpu_mode(mode)
|
.set_gpu_mode(mode)
|
||||||
.set_gpu_device(PathBuf::from("/dev/dri/renderD128"))
|
.set_gpu_device(PathBuf::from("/dev/dri/renderD128"))
|
||||||
|
|
@ -425,8 +455,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "backend-virgl")]
|
fn test_gpu_device_only_with_supported_modes() {
|
||||||
fn test_gpu_device_only_with_virglrenderer() {
|
|
||||||
assert_gpu_device_fails_for_mode(GpuMode::Null);
|
assert_gpu_device_fails_for_mode(GpuMode::Null);
|
||||||
|
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
|
|
@ -435,14 +464,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_num_capsets() {
|
fn test_default_num_capsets() {
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
assert_eq!(DEFAULT_VIRGLRENDER_CAPSET_MASK.num_capsets(), 3);
|
assert_eq!(DEFAULT_VIRGLRENDER_CAPSET_MASK.num_capsets(), 3);
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
assert_eq!(DEFAULT_GFXSTREAM_CAPSET_MASK.num_capsets(), 2);
|
assert_eq!(DEFAULT_GFXSTREAM_CAPSET_MASK.num_capsets(), 2);
|
||||||
|
assert_eq!(DEFAULT_RUTABAGA_CAPSET_MASK.num_capsets(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
fn test_capset_display_multiple() {
|
fn test_capset_display_multiple() {
|
||||||
let capset = GpuCapset::VIRGL | GpuCapset::VIRGL2;
|
let capset = GpuCapset::VIRGL | GpuCapset::VIRGL2;
|
||||||
let output = capset.to_string();
|
let output = capset.to_string();
|
||||||
|
|
@ -475,4 +503,76 @@ mod tests {
|
||||||
StartError::ServeFailed(_)
|
StartError::ServeFailed(_)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gpu_config_rutabaga_default_capset() {
|
||||||
|
let config = GpuConfigBuilder::default()
|
||||||
|
.set_gpu_mode(GpuMode::Rutabaga)
|
||||||
|
.set_flags(GpuFlags::default())
|
||||||
|
.set_wayland_socket(PathBuf::from("/tmp/wayland-0"))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(config.gpu_mode(), GpuMode::Rutabaga);
|
||||||
|
assert!(config.capsets().contains(GpuCapset::VIRGL2));
|
||||||
|
assert!(config.capsets().contains(GpuCapset::CROSS_DOMAIN));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gpu_config_cross_domain_requires_wayland_socket() {
|
||||||
|
let result = GpuConfigBuilder::default()
|
||||||
|
.set_gpu_mode(GpuMode::Rutabaga)
|
||||||
|
.set_capset(GpuCapset::CROSS_DOMAIN)
|
||||||
|
.set_flags(GpuFlags::default())
|
||||||
|
.build();
|
||||||
|
assert_matches!(result, Err(GpuConfigError::WaylandSocketRequired));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gpu_config_wayland_socket_without_cross_domain() {
|
||||||
|
let result = GpuConfigBuilder::default()
|
||||||
|
.set_gpu_mode(GpuMode::Rutabaga)
|
||||||
|
.set_capset(GpuCapset::VIRGL2)
|
||||||
|
.set_flags(GpuFlags::default())
|
||||||
|
.set_wayland_socket(PathBuf::from("/tmp/wayland-0"))
|
||||||
|
.build();
|
||||||
|
assert_matches!(result, Err(GpuConfigError::WaylandSocketWithoutCrossDomain));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gpu_config_virgl_without_virgl2() {
|
||||||
|
let result = GpuConfigBuilder::default()
|
||||||
|
.set_gpu_mode(GpuMode::Rutabaga)
|
||||||
|
.set_capset(GpuCapset::VIRGL | GpuCapset::CROSS_DOMAIN)
|
||||||
|
.set_flags(GpuFlags::default())
|
||||||
|
.set_wayland_socket(PathBuf::from("/tmp/wayland-0"))
|
||||||
|
.build();
|
||||||
|
assert_matches!(result, Err(GpuConfigError::VirglWithoutVirgl2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gpu_config_cross_domain_alone() {
|
||||||
|
let config = GpuConfigBuilder::default()
|
||||||
|
.set_gpu_mode(GpuMode::Rutabaga)
|
||||||
|
.set_capset(GpuCapset::CROSS_DOMAIN)
|
||||||
|
.set_flags(GpuFlags::default())
|
||||||
|
.set_wayland_socket(PathBuf::from("/tmp/wayland-0"))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(config.capsets(), GpuCapset::CROSS_DOMAIN);
|
||||||
|
assert_eq!(config.wayland_socket(), Some(Path::new("/tmp/wayland-0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gpu_config_cross_domain_plus_virgl() {
|
||||||
|
let config = GpuConfigBuilder::default()
|
||||||
|
.set_gpu_mode(GpuMode::Rutabaga)
|
||||||
|
.set_capset(GpuCapset::VIRGL | GpuCapset::VIRGL2 | GpuCapset::CROSS_DOMAIN)
|
||||||
|
.set_flags(GpuFlags::default())
|
||||||
|
.set_wayland_socket(PathBuf::from("/tmp/wayland-0"))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
assert!(config.capsets().contains(GpuCapset::VIRGL));
|
||||||
|
assert!(config.capsets().contains(GpuCapset::VIRGL2));
|
||||||
|
assert!(config.capsets().contains(GpuCapset::CROSS_DOMAIN));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,19 @@ use vhost_device_gpu::{
|
||||||
|
|
||||||
#[derive(ValueEnum, Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(ValueEnum, Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
#[repr(u64)]
|
#[repr(u64)]
|
||||||
// __Null is a placeholder to prevent a zero-variant enum when building with
|
|
||||||
// --no-default-features, not an implementation of the non-exhaustive pattern
|
|
||||||
#[allow(clippy::manual_non_exhaustive)]
|
|
||||||
pub enum CapsetName {
|
pub enum CapsetName {
|
||||||
/// [virglrenderer] OpenGL implementation, superseded by Virgl2
|
/// [virglrenderer] OpenGL implementation, superseded by Virgl2
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
Virgl = GpuCapset::VIRGL.bits(),
|
Virgl = GpuCapset::VIRGL.bits(),
|
||||||
|
|
||||||
/// [virglrenderer] OpenGL implementation
|
/// [virglrenderer] OpenGL implementation
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
Virgl2 = GpuCapset::VIRGL2.bits(),
|
Virgl2 = GpuCapset::VIRGL2.bits(),
|
||||||
|
|
||||||
/// [virglrenderer] Venus (Vulkan) implementation
|
/// [virglrenderer] Venus (Vulkan) implementation
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
Venus = GpuCapset::VENUS.bits(),
|
Venus = GpuCapset::VENUS.bits(),
|
||||||
|
|
||||||
|
/// Cross-domain support for Wayland forwarding
|
||||||
|
CrossDomain = GpuCapset::CROSS_DOMAIN.bits(),
|
||||||
|
|
||||||
/// [gfxstream] Vulkan implementation (partial support only){n}
|
/// [gfxstream] Vulkan implementation (partial support only){n}
|
||||||
/// NOTE: Can only be used for 2D display output for now, there is no
|
/// NOTE: Can only be used for 2D display output for now, there is no
|
||||||
/// hardware acceleration yet
|
/// hardware acceleration yet
|
||||||
|
|
@ -41,20 +38,10 @@ pub enum CapsetName {
|
||||||
/// hardware acceleration yet
|
/// hardware acceleration yet
|
||||||
#[cfg(feature = "backend-gfxstream")]
|
#[cfg(feature = "backend-gfxstream")]
|
||||||
GfxstreamGles = GpuCapset::GFXSTREAM_GLES.bits(),
|
GfxstreamGles = GpuCapset::GFXSTREAM_GLES.bits(),
|
||||||
|
|
||||||
/// Placeholder variant to prevent zero-variant enum when no backend
|
|
||||||
/// features are enabled. The null backend doesn't use capsets, so this
|
|
||||||
/// maps to GpuCapset::empty().
|
|
||||||
#[doc(hidden)]
|
|
||||||
__Null = 0,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CapsetName> for GpuCapset {
|
impl From<CapsetName> for GpuCapset {
|
||||||
fn from(capset_name: CapsetName) -> GpuCapset {
|
fn from(capset_name: CapsetName) -> GpuCapset {
|
||||||
if matches!(capset_name, CapsetName::__Null) {
|
|
||||||
return GpuCapset::empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
GpuCapset::from_bits(capset_name as u64)
|
GpuCapset::from_bits(capset_name as u64)
|
||||||
.expect("Internal error: CapsetName enum is incorrectly defined")
|
.expect("Internal error: CapsetName enum is incorrectly defined")
|
||||||
}
|
}
|
||||||
|
|
@ -82,6 +69,10 @@ pub struct GpuArgs {
|
||||||
#[clap(short, long, value_delimiter = ',')]
|
#[clap(short, long, value_delimiter = ',')]
|
||||||
pub capset: Option<Vec<CapsetName>>,
|
pub capset: Option<Vec<CapsetName>>,
|
||||||
|
|
||||||
|
/// Wayland socket path for cross-domain (e.g., /run/user/1000/wayland-0)
|
||||||
|
#[clap(long, value_name = "PATH")]
|
||||||
|
pub wayland_socket: Option<PathBuf>,
|
||||||
|
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub flags: GpuFlagsArgs,
|
pub flags: GpuFlagsArgs,
|
||||||
}
|
}
|
||||||
|
|
@ -97,14 +88,6 @@ pub struct GpuFlagsArgs {
|
||||||
)]
|
)]
|
||||||
pub use_egl: bool,
|
pub use_egl: bool,
|
||||||
|
|
||||||
/// Enable backend to use GLX
|
|
||||||
#[clap(
|
|
||||||
long,
|
|
||||||
action = ArgAction::Set,
|
|
||||||
default_value_t = GpuFlags::new_default().use_glx
|
|
||||||
)]
|
|
||||||
pub use_glx: bool,
|
|
||||||
|
|
||||||
/// Enable backend to use GLES
|
/// Enable backend to use GLES
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
|
|
@ -127,7 +110,6 @@ pub struct GpuFlagsArgs {
|
||||||
|
|
||||||
/// GPU device path (e.g., /dev/dri/renderD128)
|
/// GPU device path (e.g., /dev/dri/renderD128)
|
||||||
#[clap(long, value_name = "PATH")]
|
#[clap(long, value_name = "PATH")]
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
pub gpu_device: Option<PathBuf>,
|
pub gpu_device: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,7 +117,6 @@ impl From<GpuFlagsArgs> for GpuFlags {
|
||||||
fn from(args: GpuFlagsArgs) -> Self {
|
fn from(args: GpuFlagsArgs) -> Self {
|
||||||
GpuFlags {
|
GpuFlags {
|
||||||
use_egl: args.use_egl,
|
use_egl: args.use_egl,
|
||||||
use_glx: args.use_glx,
|
|
||||||
use_gles: args.use_gles,
|
use_gles: args.use_gles,
|
||||||
use_surfaceless: args.use_surfaceless,
|
use_surfaceless: args.use_surfaceless,
|
||||||
headless: args.headless,
|
headless: args.headless,
|
||||||
|
|
@ -144,7 +125,6 @@ impl From<GpuFlagsArgs> for GpuFlags {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config_from_args(args: GpuArgs) -> Result<(PathBuf, GpuConfig), GpuConfigError> {
|
pub fn config_from_args(args: GpuArgs) -> Result<(PathBuf, GpuConfig), GpuConfigError> {
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
let gpu_device = args.flags.gpu_device.clone();
|
let gpu_device = args.flags.gpu_device.clone();
|
||||||
|
|
||||||
let flags = GpuFlags::from(args.flags);
|
let flags = GpuFlags::from(args.flags);
|
||||||
|
|
@ -158,11 +138,14 @@ pub fn config_from_args(args: GpuArgs) -> Result<(PathBuf, GpuConfig), GpuConfig
|
||||||
builder = builder.set_capset(capset);
|
builder = builder.set_capset(capset);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
if let Some(gpu_device) = gpu_device {
|
if let Some(gpu_device) = gpu_device {
|
||||||
builder = builder.set_gpu_device(gpu_device);
|
builder = builder.set_gpu_device(gpu_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(wayland_socket) = args.wayland_socket {
|
||||||
|
builder = builder.set_wayland_socket(wayland_socket);
|
||||||
|
}
|
||||||
|
|
||||||
let config = builder.build()?;
|
let config = builder.build()?;
|
||||||
Ok((args.socket_path, config))
|
Ok((args.socket_path, config))
|
||||||
}
|
}
|
||||||
|
|
@ -222,19 +205,19 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "backend-virgl")]
|
||||||
fn test_config_from_args() {
|
fn test_config_from_args() {
|
||||||
let expected_path = Path::new("/some/test/path");
|
let expected_path = Path::new("/some/test/path");
|
||||||
let args = GpuArgs {
|
let args = GpuArgs {
|
||||||
socket_path: expected_path.into(),
|
socket_path: expected_path.into(),
|
||||||
gpu_mode: GpuMode::VirglRenderer,
|
gpu_mode: GpuMode::VirglRenderer,
|
||||||
capset: Some(vec![CapsetName::Virgl, CapsetName::Virgl2]),
|
capset: Some(vec![CapsetName::Virgl, CapsetName::Virgl2]),
|
||||||
|
wayland_socket: None,
|
||||||
flags: GpuFlagsArgs {
|
flags: GpuFlagsArgs {
|
||||||
use_egl: false,
|
use_egl: false,
|
||||||
use_glx: true,
|
|
||||||
use_gles: false,
|
use_gles: false,
|
||||||
use_surfaceless: false,
|
use_surfaceless: false,
|
||||||
headless: false,
|
headless: false,
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
gpu_device: None,
|
gpu_device: None,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -246,7 +229,6 @@ mod tests {
|
||||||
*config.flags(),
|
*config.flags(),
|
||||||
GpuFlags {
|
GpuFlags {
|
||||||
use_egl: false,
|
use_egl: false,
|
||||||
use_glx: true,
|
|
||||||
use_gles: false,
|
use_gles: false,
|
||||||
use_surfaceless: false,
|
use_surfaceless: false,
|
||||||
headless: false,
|
headless: false,
|
||||||
|
|
@ -256,16 +238,15 @@ mod tests {
|
||||||
assert_eq!(config.capsets(), GpuCapset::VIRGL | GpuCapset::VIRGL2);
|
assert_eq!(config.capsets(), GpuCapset::VIRGL | GpuCapset::VIRGL2);
|
||||||
|
|
||||||
// Test with invalid GPU device
|
// Test with invalid GPU device
|
||||||
#[cfg(feature = "backend-virgl")]
|
|
||||||
{
|
{
|
||||||
let invalid_gpu_device = Path::new("/nonexistent/gpu/device");
|
let invalid_gpu_device = Path::new("/nonexistent/gpu/device");
|
||||||
let invalid_args = GpuArgs {
|
let invalid_args = GpuArgs {
|
||||||
socket_path: expected_path.into(),
|
socket_path: expected_path.into(),
|
||||||
gpu_mode: GpuMode::VirglRenderer,
|
gpu_mode: GpuMode::VirglRenderer,
|
||||||
capset: Some(vec![CapsetName::Virgl]),
|
capset: Some(vec![CapsetName::Virgl]),
|
||||||
|
wayland_socket: None,
|
||||||
flags: GpuFlagsArgs {
|
flags: GpuFlagsArgs {
|
||||||
use_egl: true,
|
use_egl: true,
|
||||||
use_glx: false,
|
|
||||||
use_gles: true,
|
use_gles: true,
|
||||||
use_surfaceless: true,
|
use_surfaceless: true,
|
||||||
headless: false,
|
headless: false,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue