vhost-device-gpu: Add null backend and test

Add a no-op null backend that allows vhost-device-gpu
to compile with --no-default-features and update the
README, enabling testing of all feature combinations.
The null backend is automatically selected when
no-default features are enabled.

Signed-off-by: Dorinda Bassey <dbassey@redhat.com>
This commit is contained in:
Dorinda Bassey 2025-11-12 13:38:35 +01:00 committed by Stefano Garzarella
parent fc904aca8f
commit 736c5d109d
7 changed files with 532 additions and 12 deletions

View File

@ -18,8 +18,8 @@ A virtio-gpu device using the vhost-user protocol.
-g, --gpu-mode <GPU_MODE>
The mode specifies which backend implementation to use
[possible values: virglrenderer, gfxstream]
[possible values: virglrenderer, gfxstream, null]
-c, --capset <CAPSET>
Comma separated list of enabled capsets
@ -63,8 +63,9 @@ A virtio-gpu device using the vhost-user protocol.
Print version
```
_NOTE_: Option `-g, --gpu-mode` can only accept the `gfxstream` value if the
crate has been built with the `backend-gfxstream` feature, which is the default.
_NOTE_: Option `-g, --gpu-mode` can only accept the `virglrenderer` or `gfxstream`
values if the crate has been built with the `backend-virgl` or `backend-gfxstream`
features respectively (both are enabled by default). The `null` mode is always available.
## Limitations
@ -87,7 +88,7 @@ Because blob resources are not yet supported, some capsets are limited:
- gfxstream-vulkan and gfxstream-gles support are exposed, but can practically only be used for display output, there is no hardware acceleration yet.
## Features
This crate supports two GPU backends: gfxstream (default) and virglrenderer.
This crate supports three GPU backends: virglrenderer, gfxstream (both enabled by default), and null.
The **virglrenderer** backend uses the [virglrenderer-rs](https://crates.io/crates/virglrenderer-rs)
crate, which provides Rust bindings to the native virglrenderer library. It translates
@ -98,24 +99,36 @@ The **gfxstream** backend leverages the [rutabaga_gfx](https://crates.io/crates/
crate. With gfxstream rendering mode, GLES and Vulkan calls are forwarded to the host
with minimal modification.
The **null** backend is a no-op implementation that accepts all GPU commands but performs
no actual rendering. This backend is primarily intended for testing and CI purposes.
Install the development packages for your distro, then build with:
```session
$ cargo build
```
gfxstream support is compiled by default, it can be disabled by not building with the `backend-gfxstream` feature flag, for example:
Both virglrenderer and gfxstream support are compiled by default. The null backend is
always available. To build with specific backends:
```session
# Build with only virglrenderer (and null)
$ cargo build --no-default-features --features backend-virgl
# Build with only gfxstream (and null)
$ cargo build --no-default-features --features backend-gfxstream
# Build with null backend only (for testing)
$ cargo build --no-default-features
```
## Examples
First start the daemon on the host machine using either of the 2 gpu modes:
First start the daemon on the host machine using one of the available gpu modes:
1) `virglrenderer` (if the crate has been compiled with the feature `backend-virgl`)
2) `gfxstream` (if the crate has been compiled with the feature `backend-gfxstream`)
3) `null` (always available, for testing)
```shell
host# vhost-device-gpu --socket-path /tmp/gpu.socket --gpu-mode virglrenderer

View File

@ -2,8 +2,10 @@
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
#[cfg(any(feature = "backend-virgl", feature = "backend-gfxstream"))]
mod common;
#[cfg(feature = "backend-gfxstream")]
pub mod gfxstream;
pub mod null;
#[cfg(feature = "backend-virgl")]
pub mod virgl;

View File

@ -0,0 +1,463 @@
// Null backend
// Copyright 2025 Red Hat Inc
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use log::trace;
use rutabaga_gfx::RutabagaFence;
use vhost::vhost_user::{
gpu_message::{VhostUserGpuCursorPos, VhostUserGpuEdidRequest},
GpuBackend,
};
use vm_memory::{GuestAddress, GuestMemoryMmap, VolatileSlice};
use vmm_sys_util::eventfd::EventFd;
use crate::{
gpu_types::{ResourceCreate3d, Transfer3DDesc, VirtioGpuRing},
protocol::{virtio_gpu_rect, GpuResponse, VirtioGpuResult},
renderer::Renderer,
GpuConfig,
};
pub struct NullAdapter {
_gpu_backend: GpuBackend,
}
impl NullAdapter {
pub fn new(
_queue_ctl: &vhost_user_backend::VringRwLock,
_config: &GpuConfig,
gpu_backend: GpuBackend,
) -> Self {
trace!("NullAdapter created");
Self {
_gpu_backend: gpu_backend,
}
}
}
impl Renderer for NullAdapter {
fn resource_create_3d(
&mut self,
_resource_id: u32,
_args: ResourceCreate3d,
) -> VirtioGpuResult {
trace!("NullAdapter::resource_create_3d - no-op");
Ok(GpuResponse::OkNoData)
}
fn unref_resource(&mut self, _resource_id: u32) -> VirtioGpuResult {
trace!("NullAdapter::unref_resource - no-op");
Ok(GpuResponse::OkNoData)
}
fn transfer_write(
&mut self,
_ctx_id: u32,
_resource_id: u32,
_transfer: Transfer3DDesc,
) -> VirtioGpuResult {
trace!("NullAdapter::transfer_write - no-op");
Ok(GpuResponse::OkNoData)
}
fn transfer_write_2d(
&mut self,
_ctx_id: u32,
_resource_id: u32,
_transfer: Transfer3DDesc,
) -> VirtioGpuResult {
trace!("NullAdapter::transfer_write_2d - no-op");
Ok(GpuResponse::OkNoData)
}
fn transfer_read(
&mut self,
_ctx_id: u32,
_resource_id: u32,
_transfer: Transfer3DDesc,
_buf: Option<VolatileSlice>,
) -> VirtioGpuResult {
trace!("NullAdapter::transfer_read - no-op");
Ok(GpuResponse::OkNoData)
}
fn attach_backing(
&mut self,
_resource_id: u32,
_mem: &GuestMemoryMmap,
_vecs: Vec<(GuestAddress, usize)>,
) -> VirtioGpuResult {
trace!("NullAdapter::attach_backing - no-op");
Ok(GpuResponse::OkNoData)
}
fn detach_backing(&mut self, _resource_id: u32) -> VirtioGpuResult {
trace!("NullAdapter::detach_backing - no-op");
Ok(GpuResponse::OkNoData)
}
fn update_cursor(
&mut self,
_resource_id: u32,
_cursor_pos: VhostUserGpuCursorPos,
_hot_x: u32,
_hot_y: u32,
) -> VirtioGpuResult {
trace!("NullAdapter::update_cursor - no-op");
Ok(GpuResponse::OkNoData)
}
fn move_cursor(
&mut self,
_resource_id: u32,
_cursor: VhostUserGpuCursorPos,
) -> VirtioGpuResult {
trace!("NullAdapter::move_cursor - no-op");
Ok(GpuResponse::OkNoData)
}
fn resource_assign_uuid(&self, _resource_id: u32) -> VirtioGpuResult {
trace!("NullAdapter::resource_assign_uuid - no-op");
Ok(GpuResponse::OkNoData)
}
fn get_capset_info(&self, _capset_index: u32) -> VirtioGpuResult {
trace!("NullAdapter::get_capset_info - no capsets");
Ok(GpuResponse::OkNoData)
}
fn get_capset(&self, _capset_id: u32, _capset_version: u32) -> VirtioGpuResult {
trace!("NullAdapter::get_capset - no capsets");
Ok(GpuResponse::OkNoData)
}
fn create_context(
&mut self,
_ctx_id: u32,
_context_init: u32,
_context_name: Option<&str>,
) -> VirtioGpuResult {
trace!("NullAdapter::create_context - no-op");
Ok(GpuResponse::OkNoData)
}
fn destroy_context(&mut self, _ctx_id: u32) -> VirtioGpuResult {
trace!("NullAdapter::destroy_context - no-op");
Ok(GpuResponse::OkNoData)
}
fn context_attach_resource(&mut self, _ctx_id: u32, _resource_id: u32) -> VirtioGpuResult {
trace!("NullAdapter::context_attach_resource - no-op");
Ok(GpuResponse::OkNoData)
}
fn context_detach_resource(&mut self, _ctx_id: u32, _resource_id: u32) -> VirtioGpuResult {
trace!("NullAdapter::context_detach_resource - no-op");
Ok(GpuResponse::OkNoData)
}
fn submit_command(
&mut self,
_ctx_id: u32,
_commands: &mut [u8],
_fence_ids: &[u64],
) -> VirtioGpuResult {
trace!("NullAdapter::submit_command - no-op");
Ok(GpuResponse::OkNoData)
}
fn create_fence(&mut self, _rutabaga_fence: RutabagaFence) -> VirtioGpuResult {
trace!("NullAdapter::create_fence - no-op");
Ok(GpuResponse::OkNoData)
}
fn process_fence(
&mut self,
_ring: VirtioGpuRing,
_fence_id: u64,
_desc_index: u16,
_len: u32,
) -> bool {
trace!("NullAdapter::process_fence - no-op");
true
}
fn get_event_poll_fd(&self) -> Option<EventFd> {
trace!("NullAdapter::get_event_poll_fd - no-op");
None
}
fn event_poll(&self) {
trace!("NullAdapter::event_poll - no-op");
}
fn force_ctx_0(&self) {
trace!("NullAdapter::force_ctx_0 - no-op");
}
fn display_info(&self) -> VirtioGpuResult {
trace!("NullAdapter::display_info - no-op");
Ok(GpuResponse::OkNoData)
}
fn get_edid(&self, _edid_req: VhostUserGpuEdidRequest) -> VirtioGpuResult {
trace!("NullAdapter::get_edid - no-op");
Ok(GpuResponse::OkNoData)
}
fn set_scanout(
&mut self,
_scanout_id: u32,
_resource_id: u32,
_rect: virtio_gpu_rect,
) -> VirtioGpuResult {
trace!("NullAdapter::set_scanout - no-op");
Ok(GpuResponse::OkNoData)
}
fn flush_resource(&mut self, _resource_id: u32, _rect: virtio_gpu_rect) -> VirtioGpuResult {
trace!("NullAdapter::flush_resource - no-op");
Ok(GpuResponse::OkNoData)
}
fn resource_create_blob(
&mut self,
_ctx_id: u32,
_resource_id: u32,
_blob_id: u64,
_size: u64,
_blob_mem: u32,
_blob_flags: u32,
) -> VirtioGpuResult {
trace!("NullAdapter::resource_create_blob - no-op");
Ok(GpuResponse::OkNoData)
}
fn resource_map_blob(&mut self, _resource_id: u32, _offset: u64) -> VirtioGpuResult {
trace!("NullAdapter::resource_map_blob - no-op");
Ok(GpuResponse::OkNoData)
}
fn resource_unmap_blob(&mut self, _resource_id: u32) -> VirtioGpuResult {
trace!("NullAdapter::resource_unmap_blob - no-op");
Ok(GpuResponse::OkNoData)
}
}
#[cfg(test)]
mod tests {
use std::os::unix::net::UnixStream;
use vhost_user_backend::{VringRwLock, VringT};
use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap};
use super::*;
use crate::{GpuFlags, GpuMode};
fn create_null_adapter() -> NullAdapter {
let (_, backend) = UnixStream::pair().unwrap();
let gpu_backend = GpuBackend::from_stream(backend);
let mem = GuestMemoryAtomic::new(
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(),
);
let vring = VringRwLock::new(mem, 0x100).unwrap();
let config = GpuConfig::new(GpuMode::Null, None, GpuFlags::default()).unwrap();
NullAdapter::new(&vring, &config, gpu_backend)
}
#[test]
fn test_null_adapter_creation() {
// Verify that NullAdapter can be successfully created
let _adapter = create_null_adapter();
}
#[test]
fn test_null_adapter_resource_operations() {
let mut adapter = create_null_adapter();
// Verify resource creation returns success without doing anything
let resource_create = ResourceCreate3d {
target: 2,
format: 1,
bind: 1,
width: 640,
height: 480,
depth: 1,
array_size: 1,
last_level: 0,
nr_samples: 0,
flags: 0,
};
let result = adapter.resource_create_3d(1, resource_create);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify unreferencing a resource succeeds
let result = adapter.unref_resource(1);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify attaching and detaching backing memory succeeds
let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap();
let result = adapter.attach_backing(1, &mem, vec![]);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
let result = adapter.detach_backing(1);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
}
#[test]
fn test_null_adapter_transfer_operations() {
let mut adapter = create_null_adapter();
let transfer = Transfer3DDesc::new_2d(0, 0, 640, 480, 0);
// Verify 3D transfer write succeeds
let result = adapter.transfer_write(0, 1, transfer);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify 2D transfer write succeeds
let result = adapter.transfer_write_2d(0, 1, transfer);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify transfer read succeeds
let result = adapter.transfer_read(0, 1, transfer, None);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
}
#[test]
fn test_null_adapter_context_operations() {
let mut adapter = create_null_adapter();
// Verify context creation succeeds
let result = adapter.create_context(1, 0, Some("test"));
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify attaching a resource to a context succeeds
let result = adapter.context_attach_resource(1, 1);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify detaching a resource from a context succeeds
let result = adapter.context_detach_resource(1, 1);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify context destruction succeeds
let result = adapter.destroy_context(1);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
}
#[test]
fn test_null_adapter_display_operations() {
let mut adapter = create_null_adapter();
// Verify getting display info succeeds
let result = adapter.display_info();
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify getting EDID info succeeds
let result = adapter.get_edid(VhostUserGpuEdidRequest { scanout_id: 0 });
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify setting scanout succeeds
let result = adapter.set_scanout(0, 1, virtio_gpu_rect::default());
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify flushing a resource succeeds
let result = adapter.flush_resource(1, virtio_gpu_rect::default());
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
}
#[test]
fn test_null_adapter_cursor_operations() {
let mut adapter = create_null_adapter();
let cursor_pos = VhostUserGpuCursorPos {
scanout_id: 0,
x: 0,
y: 0,
};
// Verify updating cursor succeeds
let result = adapter.update_cursor(1, cursor_pos, 0, 0);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify moving cursor succeeds
let result = adapter.move_cursor(1, cursor_pos);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
}
#[test]
fn test_null_adapter_capset_operations() {
let adapter = create_null_adapter();
// Verify getting capset info returns success (null backend has no capsets)
let result = adapter.get_capset_info(0);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify getting capset returns success (null backend has no capsets)
let result = adapter.get_capset(0, 0);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
}
#[test]
fn test_null_adapter_command_operations() {
let mut adapter = create_null_adapter();
let mut commands = vec![0u8; 64];
// Verify submitting commands succeeds
let result = adapter.submit_command(1, &mut commands, &[]);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
}
#[test]
fn test_null_adapter_fence_operations() {
let mut adapter = create_null_adapter();
let fence = RutabagaFence {
flags: 0,
fence_id: 1,
ctx_id: 0,
ring_idx: 0,
};
// Verify creating a fence succeeds
let result = adapter.create_fence(fence);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify processing fence returns true (fence is immediately ready in null
// backend)
let ready = adapter.process_fence(VirtioGpuRing::Global, 1, 0, 0);
assert!(ready);
}
#[test]
fn test_null_adapter_blob_operations() {
let mut adapter = create_null_adapter();
// Verify blob resource creation succeeds
let result = adapter.resource_create_blob(0, 1, 1, 4096, 0, 0);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify mapping blob resource succeeds
let result = adapter.resource_map_blob(1, 0);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify unmapping blob resource succeeds
let result = adapter.resource_unmap_blob(1);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
}
#[test]
fn test_null_adapter_misc_operations() {
let adapter = create_null_adapter();
// Verify assigning UUID to resource succeeds
let result = adapter.resource_assign_uuid(1);
assert!(matches!(result, Ok(GpuResponse::OkNoData)));
// Verify no event poll fd is provided (null backend has no events)
let event_fd = adapter.get_event_poll_fd();
assert!(event_fd.is_none());
// Verify event polling and force_ctx_0 don't panic (they're no-ops)
adapter.event_poll();
adapter.force_ctx_0();
}
}

View File

@ -77,6 +77,7 @@ use crate::backend::gfxstream::GfxstreamAdapter;
#[cfg(feature = "backend-virgl")]
use crate::backend::virgl::VirglRendererAdapter;
use crate::{
backend::null::NullAdapter,
gpu_types::{ResourceCreate3d, Transfer3DDesc, VirtioGpuRing},
protocol::{
virtio_gpu_ctrl_hdr, virtio_gpu_ctx_create, virtio_gpu_get_edid,
@ -630,6 +631,17 @@ impl VhostUserGpuBackendInner {
device_event,
vrings
),
GpuMode::Null => handle_adapter!(
NullAdapter,
TLS_NULL,
|control_vring, gpu_backend| {
NullAdapter::new(control_vring, &self.gpu_config, gpu_backend)
},
self,
device_event,
vrings
),
}
}

View File

@ -4,6 +4,7 @@
/// Generates an implementation of `From<Transfer3DDesc>` for any compatible
/// target struct.
#[cfg(any(feature = "backend-virgl", feature = "backend-gfxstream"))]
macro_rules! impl_transfer3d_from_desc {
($target:path) => {
impl From<Transfer3DDesc> for $target {
@ -25,6 +26,7 @@ macro_rules! impl_transfer3d_from_desc {
};
}
#[cfg(any(feature = "backend-virgl", feature = "backend-gfxstream"))]
macro_rules! impl_from_resource_create3d {
($target:ty) => {
impl From<ResourceCreate3d> for $target {
@ -48,7 +50,9 @@ macro_rules! impl_from_resource_create3d {
use std::{collections::BTreeMap, os::raw::c_void};
#[cfg(feature = "backend-gfxstream")]
use rutabaga_gfx::Transfer3D;
#[cfg(feature = "backend-virgl")]
use virglrenderer::Transfer3D as VirglTransfer3D;
use crate::protocol::virtio_gpu_rect;
@ -87,8 +91,10 @@ impl Transfer3DDesc {
}
// Invoke the macro for both targets
// rutabaga_gfx::Transfer3D
#[cfg(feature = "backend-gfxstream")]
impl_transfer3d_from_desc!(Transfer3D);
// virglrenderer::Transfer3D
#[cfg(feature = "backend-virgl")]
impl_transfer3d_from_desc!(VirglTransfer3D);
// These are neutral types that can be used by all backends
@ -136,7 +142,9 @@ pub struct ResourceCreate3d {
}
// Invoke the macro for both targets
#[cfg(feature = "backend-gfxstream")]
impl_from_resource_create3d!(rutabaga_gfx::ResourceCreate3D);
#[cfg(feature = "backend-virgl")]
impl_from_resource_create3d!(virglrenderer::ResourceCreate3D);
#[derive(Debug, Clone, Copy)]

View File

@ -44,6 +44,7 @@ pub enum GpuMode {
VirglRenderer,
#[cfg(feature = "backend-gfxstream")]
Gfxstream,
Null,
}
impl Display for GpuMode {
@ -53,6 +54,7 @@ impl Display for GpuMode {
Self::VirglRenderer => write!(f, "virglrenderer"),
#[cfg(feature = "backend-gfxstream")]
Self::Gfxstream => write!(f, "gfxstream"),
Self::Null => write!(f, "null"),
}
}
}
@ -79,7 +81,12 @@ bitflags! {
impl Display for GpuCapset {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.is_empty() {
return write!(f, "none");
}
let mut first = true;
#[allow(unused_assignments)]
for capset in self.iter() {
if !first {
write!(f, ", ")?;
@ -88,15 +95,15 @@ impl Display for GpuCapset {
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-gfxstream")]
Self::GFXSTREAM_VULKAN => write!(f, "gfxstream-vulkan"),
Self::GFXSTREAM_VULKAN => write!(f, "gfxstream-vulkan")?,
#[cfg(feature = "backend-gfxstream")]
Self::GFXSTREAM_GLES => write!(f, "gfxstream-gles"),
Self::GFXSTREAM_GLES => write!(f, "gfxstream-gles")?,
_ => panic!("Unknown capset {:#x}", self.bits()),
}?;
}
}
Ok(())
@ -165,6 +172,7 @@ impl GpuConfig {
GpuMode::VirglRenderer => Self::DEFAULT_VIRGLRENDER_CAPSET_MASK,
#[cfg(feature = "backend-gfxstream")]
GpuMode::Gfxstream => Self::DEFAULT_GFXSTREAM_CAPSET_MASK,
GpuMode::Null => GpuCapset::empty(),
}
}
@ -174,6 +182,7 @@ impl GpuConfig {
GpuMode::VirglRenderer => GpuCapset::ALL_VIRGLRENDERER_CAPSETS,
#[cfg(feature = "backend-gfxstream")]
GpuMode::Gfxstream => GpuCapset::ALL_GFXSTREAM_CAPSETS,
GpuMode::Null => GpuCapset::empty(),
};
for capset in capset.iter() {
if !supported_capset_mask.contains(capset) {

View File

@ -12,6 +12,9 @@ use vhost_device_gpu::{start_backend, GpuCapset, GpuConfig, GpuConfigError, GpuF
#[derive(ValueEnum, Debug, Copy, Clone, Eq, PartialEq)]
#[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 {
/// [virglrenderer] OpenGL implementation, superseded by Virgl2
#[cfg(feature = "backend-virgl")]
@ -32,10 +35,20 @@ pub enum CapsetName {
/// hardware acceleration yet
#[cfg(feature = "backend-gfxstream")]
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 {
fn from(capset_name: CapsetName) -> GpuCapset {
if matches!(capset_name, CapsetName::__Null) {
return GpuCapset::empty();
}
GpuCapset::from_bits(capset_name as u64)
.expect("Internal error: CapsetName enum is incorrectly defined")
}