#[derive(Debug, thiserror::Error)]
pub enum VirglError {
#[error("initialization failed")]
InitializationFailed,
#[error("resource creation failed")]
ResourceCreationError,
#[error("context creation failed")]
ContextCreationError,
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("transfer failed")]
TransferError,
#[error("resource info query failed")]
ResourceInfoError,
#[error("mapping failed")]
MappingFailed,
#[error("unsupported")]
Unsupported,
#[error("invalid command size: {0}")]
InvalidCommandSize(usize),
#[error("failed to create fence")]
FenceError,
}
pub mod flags;
pub use flags::VirglRendererFlags;
use libc::dup;
use log::{log, Level};
use std::ffi::CStr;
use std::io::{IoSlice, IoSliceMut};
use std::mem::size_of;
use std::mem::ManuallyDrop;
use std::os::fd::{IntoRawFd, RawFd};
use std::os::raw::{c_char, c_int, c_void};
use std::os::unix::io::{FromRawFd, OwnedFd};
use std::panic::catch_unwind;
use std::process::abort;
use std::ptr::null_mut;
use std::sync::{Arc, Once};
use virglrenderer_sys as ffi;
pub const VIRGL_HANDLE_TYPE_MEM_OPAQUE_FD: u32 = 0x0001;
pub const VIRGL_HANDLE_TYPE_MEM_DMABUF: u32 = 0x0002;
pub const VIRGL_HANDLE_TYPE_MEM_SHM: u32 = 0x0004;
pub use ffi::VIRGL_RENDERER_MAP_CACHE_MASK as VIRGL_MAP_CACHE_MASK;
pub trait FenceHandler {
fn call(&self, fence_id: u64, ctx_id: u32, ring_idx: u8);
}
pub struct VirglHandle {
pub os_handle: OwnedFd,
pub handle_type: u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VirglBox {
pub x: u32,
pub y: u32,
pub z: u32,
pub w: u32,
pub h: u32,
pub d: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Iovec {
pub base: *mut c_void,
pub len: usize,
}
unsafe impl Send for Iovec {}
#[repr(C)]
#[derive(Clone)]
pub struct Resource3DInfo {
pub width: u32,
pub height: u32,
pub drm_fourcc: u32,
pub strides: [u32; 4],
pub offsets: [u32; 4],
pub modifier: u64,
pub guest_cpu_mappable: bool,
}
pub struct ResourceCreate3D {
pub target: u32,
pub format: u32,
pub bind: u32,
pub width: u32,
pub height: u32,
pub depth: u32,
pub array_size: u32,
pub last_level: u32,
pub nr_samples: u32,
pub flags: u32,
}
pub struct ResourceCreateBlob {
pub blob_mem: u32,
pub blob_flags: u32,
pub blob_id: u64,
pub size: u64,
}
#[repr(C)]
#[derive(Clone)]
pub struct VirglResource {
pub resource_id: u32,
pub width: u32,
pub height: u32,
pub handle: Option<Arc<VirglHandle>>,
pub info_3d: Option<Resource3DInfo>,
pub map_info: Option<u32>,
}
pub struct VirglRenderer {
_private: (),
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct Transfer3D {
pub x: u32,
pub y: u32,
pub z: u32,
pub w: u32,
pub h: u32,
pub d: u32,
pub level: u32,
pub stride: u32,
pub layer_stride: u32,
pub offset: u64,
}
impl Transfer3D {
pub fn new_2d(x: u32, y: u32, w: u32, h: u32, offset: u64) -> Transfer3D {
Transfer3D {
x,
y,
z: 0,
w,
h,
d: 1,
level: 0,
stride: 0,
layer_stride: 0,
offset,
}
}
pub fn is_empty(&self) -> bool {
self.w == 0 || self.h == 0 || self.d == 0
}
}
struct VirglCookie {
render_server_fd: Option<OwnedFd>,
drm_fd: Option<OwnedFd>,
fence_handler: Option<Box<dyn FenceHandler + Send + Sync>>,
}
static INIT_ONCE: Once = Once::new();
static mut VIRGL_COOKIE: ManuallyDrop<VirglCookie> = ManuallyDrop::new(VirglCookie {
render_server_fd: None,
drm_fd: None,
fence_handler: None,
});
extern "C" fn log_callback(
log_level: ffi::virgl_log_level_flags,
message: *const ::std::os::raw::c_char,
_user_data: *mut ::std::os::raw::c_void,
) {
let level = match log_level {
ffi::virgl_log_level_flags_VIRGL_LOG_LEVEL_DEBUG => Level::Debug,
ffi::virgl_log_level_flags_VIRGL_LOG_LEVEL_INFO => Level::Info,
ffi::virgl_log_level_flags_VIRGL_LOG_LEVEL_WARNING => Level::Warn,
ffi::virgl_log_level_flags_VIRGL_LOG_LEVEL_ERROR => Level::Error,
ffi::virgl_log_level_flags_VIRGL_LOG_LEVEL_SILENT => Level::Error, _ => Level::Trace,
};
let message_str = unsafe { CStr::from_ptr(message) };
log!(level, "{}", message_str.to_string_lossy());
}
extern "C" fn write_context_fence(cookie: *mut c_void, ctx_id: u32, ring_idx: u32, fence_id: u64) {
catch_unwind(|| {
assert!(!cookie.is_null());
let cookie = unsafe { &*(cookie as *const VirglCookie) };
if let Some(handler) = &cookie.fence_handler {
handler.call(fence_id, ctx_id, ring_idx as u8);
}
})
.unwrap_or_else(|_| abort())
}
unsafe extern "C" fn write_fence(cookie: *mut c_void, fence: u32) {
catch_unwind(|| {
assert!(!cookie.is_null());
let cookie = &*(cookie as *const VirglCookie);
if let Some(handler) = &cookie.fence_handler {
handler.call(fence as u64, 0, 0);
}
})
.unwrap_or_else(|_| abort())
}
unsafe extern "C" fn get_server_fd(cookie: *mut c_void, version: u32) -> c_int {
catch_unwind(|| {
assert!(!cookie.is_null());
let cookie = &mut *(cookie as *mut VirglCookie);
if version != 0 {
return -1;
}
cookie
.render_server_fd
.take()
.map(|o| o.into_raw_fd())
.unwrap_or(-1)
})
.unwrap_or_else(|_| abort())
}
unsafe extern "C" fn get_drm_fd(cookie: *mut c_void) -> c_int {
catch_unwind(|| {
assert!(!cookie.is_null());
let cookie = &*(cookie as *const VirglCookie);
cookie
.drm_fd
.as_ref()
.and_then(|fd| fd.try_clone().ok())
.map(|o| o.into_raw_fd())
.unwrap_or(-1)
})
.unwrap_or_else(|_| abort())
}
const VIRGL_RENDERER_CALLBACKS: &ffi::virgl_renderer_callbacks = &ffi::virgl_renderer_callbacks {
version: 3,
write_fence: Some(write_fence),
create_gl_context: None,
destroy_gl_context: None,
make_current: None,
get_drm_fd: Some(get_drm_fd),
write_context_fence: Some(write_context_fence),
get_server_fd: Some(get_server_fd),
get_egl_display: None,
};
impl VirglRenderer {
pub fn init(
flags: VirglRendererFlags,
fence_handler: Box<dyn FenceHandler + Send + Sync>,
render_server_fd: Option<OwnedFd>,
drm_fd: Option<OwnedFd>,
) -> Result<Self, VirglError> {
let has_gl_backend = (flags.bits() & flags::VIRGL_RENDERER_USE_EGL) != 0
|| (flags.bits() & flags::VIRGL_RENDERER_USE_GLX) != 0;
if !has_gl_backend {
return Err(VirglError::InitializationFailed);
}
let mut result = Err(VirglError::InitializationFailed);
INIT_ONCE.call_once(|| {
unsafe {
VIRGL_COOKIE = ManuallyDrop::new(VirglCookie {
render_server_fd,
drm_fd,
fence_handler: Some(fence_handler),
});
ffi::virgl_set_log_callback(Some(log_callback), null_mut(), None);
let ret = ffi::virgl_renderer_init(
&mut *VIRGL_COOKIE as *mut _ as *mut c_void,
flags.into(),
VIRGL_RENDERER_CALLBACKS as *const _ as *mut _,
);
if ret == 0 {
result = Ok(VirglRenderer { _private: () });
}
}
});
result
}
pub fn unref_resource(&self, resource_id: u32) {
unsafe {
ffi::virgl_renderer_resource_unref(resource_id)
};
}
pub fn create_3d(
&self,
resource_id: u32,
resource_create_3d: ResourceCreate3D,
) -> Result<VirglResource, VirglError> {
let mut args = ffi::virgl_renderer_resource_create_args {
handle: resource_id,
target: resource_create_3d.target,
format: resource_create_3d.format,
bind: resource_create_3d.bind,
width: resource_create_3d.width,
height: resource_create_3d.height,
depth: resource_create_3d.depth,
array_size: resource_create_3d.array_size,
last_level: resource_create_3d.last_level,
nr_samples: resource_create_3d.nr_samples,
flags: resource_create_3d.flags,
};
let ret = unsafe {
ffi::virgl_renderer_resource_create(&mut args, null_mut(), 0)
};
if ret != 0 {
return Err(VirglError::ResourceCreationError);
}
let mut resource_handle = self.export_blob(resource_id).ok();
let mut resource_info_3d = self.query(resource_id).ok();
if resource_handle.is_none() && resource_info_3d.is_none() {
if let Ok(info_ext) = self.get_resource_info_ext(resource_id) {
if let Ok(fd) = self.get_fd_for_texture(info_ext.base.tex_id) {
let fourcc: u32 = info_ext.base.drm_fourcc as u32;
let owned_fd = unsafe { OwnedFd::from_raw_fd(fd) };
resource_handle = Some(Arc::new(VirglHandle {
os_handle: owned_fd,
handle_type: VIRGL_HANDLE_TYPE_MEM_DMABUF,
}));
resource_info_3d = Some(Resource3DInfo {
width: info_ext.base.width,
height: info_ext.base.height,
drm_fourcc: fourcc,
strides: [info_ext.base.stride, 0, 0, 0],
offsets: [0, 0, 0, 0],
modifier: info_ext.modifiers,
guest_cpu_mappable: false,
});
}
}
}
Ok(VirglResource {
resource_id,
width: resource_create_3d.width,
height: resource_create_3d.height,
handle: resource_handle,
info_3d: resource_info_3d,
map_info: None,
})
}
pub fn create_blob(
&self,
ctx_id: u32,
width: u32,
height: u32,
resource_id: u32,
resource_create_blob: ResourceCreateBlob,
iovecs: Option<&[Iovec]>,
) -> Result<VirglResource, VirglError> {
let (iovec_ptr, num_iovecs) = match iovecs {
Some(iovs) => (iovs.as_ptr() as *const ffi::iovec, iovs.len()),
None => (null_mut() as *const ffi::iovec, 0),
};
let resource_create_args = ffi::virgl_renderer_resource_create_blob_args {
res_handle: resource_id,
ctx_id,
blob_mem: resource_create_blob.blob_mem,
blob_flags: resource_create_blob.blob_flags,
blob_id: resource_create_blob.blob_id,
size: resource_create_blob.size,
iovecs: iovec_ptr,
num_iovs: num_iovecs as u32,
};
let ret = unsafe {
ffi::virgl_renderer_resource_create_blob(&resource_create_args)
};
if ret != 0 {
return Err(VirglError::ResourceCreationError);
}
Ok(VirglResource {
resource_id,
width,
height,
handle: self.export_blob(resource_id).ok(),
info_3d: self.query(resource_id).ok(),
map_info: self.map_info(resource_id).ok(),
})
}
pub fn attach_backing(
&self,
resource_id: u32,
iovecs: &mut Vec<Iovec>,
) -> Result<(), VirglError> {
let ret = unsafe {
ffi::virgl_renderer_resource_attach_iov(
resource_id as i32,
iovecs.as_mut_ptr() as *mut ffi::iovec,
iovecs.len() as i32,
)
};
if ret == 0 {
Ok(())
} else {
Err(VirglError::IoError(std::io::Error::last_os_error()))
}
}
pub fn detach_backing(&self, resource_id: u32) {
unsafe {
ffi::virgl_renderer_resource_detach_iov(resource_id as i32, null_mut(), null_mut());
}
}
pub fn event_poll(&self) {
unsafe {
ffi::virgl_renderer_poll()
};
}
pub fn poll_descriptor(&self) -> Option<OwnedFd> {
let fd = unsafe {
ffi::virgl_renderer_get_poll_fd()
};
if fd >= 0 {
let dup_fd = unsafe {
dup(fd)
};
if dup_fd >= 0 {
return Some(unsafe { OwnedFd::from_raw_fd(dup_fd) });
}
}
None
}
pub fn get_capset_info(&self, capset_id: u32) -> (u32, u32) {
let mut version = 0;
let mut size = 0;
unsafe {
ffi::virgl_renderer_get_cap_set(capset_id, &mut version, &mut size);
}
(version, size)
}
pub fn get_capset(&self, capset_id: u32, version: u32) -> Vec<u8> {
let (_, max_size) = self.get_capset_info(capset_id);
let mut buf = vec![0u8; max_size as usize];
unsafe {
ffi::virgl_renderer_fill_caps(capset_id, version, buf.as_mut_ptr() as *mut c_void);
}
buf
}
pub fn force_ctx_0(&self) {
unsafe {
ffi::virgl_renderer_force_ctx_0();
}
}
pub fn create_fence(&self, fence_id: u32, ctx_id: u32) -> Result<(), VirglError> {
let ret = unsafe {
ffi::virgl_renderer_create_fence(fence_id as i32, ctx_id)
};
if ret != 0 {
Err(VirglError::FenceError)
} else {
Ok(())
}
}
pub fn export_fence(&self, fence_id: u64) -> Result<RawFd, VirglError> {
#[cfg(feature = "virgl_renderer_unstable")]
{
let mut fd: i32 = 0;
let ret = unsafe {
ffi::virgl_renderer_export_fence(fence_id, &mut fd)
};
if ret != 0 {
Err(VirglError::FenceError)
} else {
Ok(fd)
}
}
#[cfg(not(feature = "virgl_renderer_unstable"))]
Err(VirglError::Unsupported)
}
pub fn map(&self, resource_id: u32) -> Result<(*mut c_void, u64), VirglError> {
let mut map: *mut c_void = null_mut();
let mut size: u64 = 0;
unsafe {
let ret = ffi::virgl_renderer_resource_map(resource_id, &mut map, &mut size);
if ret != 0 {
return Err(VirglError::MappingFailed);
}
}
if map.is_null() {
return Err(VirglError::MappingFailed);
}
Ok((map, size))
}
pub fn unmap(&self, resource_id: u32) -> Result<(), VirglError> {
unsafe {
let ret = ffi::virgl_renderer_resource_unmap(resource_id);
if ret != 0 {
return Err(VirglError::MappingFailed);
}
}
Ok(())
}
pub fn transfer_read(
&self,
resource_id: u32,
ctx_id: u32,
transfer: Transfer3D,
buf: Option<IoSliceMut>,
) -> Result<(), VirglError> {
if transfer.is_empty() {
return Ok(());
}
let mut transfer_box = VirglBox {
x: transfer.x,
y: transfer.y,
z: transfer.z,
w: transfer.w,
h: transfer.h,
d: transfer.d,
};
let mut iov = Iovec {
base: null_mut(),
len: 0,
};
let (iovecs, num_iovecs) = match buf {
Some(mut buf) => {
iov.base = buf.as_mut_ptr() as *mut c_void;
iov.len = buf.len();
(&mut iov as *mut Iovec as *mut ffi::iovec, 1)
}
None => (null_mut(), 0),
};
let ret = unsafe {
ffi::virgl_renderer_transfer_read_iov(
resource_id,
ctx_id,
transfer.level,
transfer.stride,
transfer.layer_stride,
&mut transfer_box as *mut VirglBox as *mut ffi::virgl_box,
transfer.offset,
iovecs,
num_iovecs,
)
};
if ret == 0 {
Ok(())
} else {
Err(VirglError::TransferError)
}
}
pub fn transfer_write(
&self,
resource_id: u32,
ctx_id: u32,
transfer: Transfer3D,
buf: Option<&IoSlice>,
) -> Result<(), VirglError> {
if transfer.is_empty() {
return Ok(());
}
if buf.is_some() {
return Err(VirglError::Unsupported);
}
let mut transfer_box = VirglBox {
x: transfer.x,
y: transfer.y,
z: transfer.z,
w: transfer.w,
h: transfer.h,
d: transfer.d,
};
let ret = unsafe {
ffi::virgl_renderer_transfer_write_iov(
resource_id,
ctx_id,
transfer.level as i32,
transfer.stride,
transfer.layer_stride,
&mut transfer_box as *mut VirglBox as *mut ffi::virgl_box,
transfer.offset,
null_mut(),
0,
)
};
if ret == 0 {
Ok(())
} else {
Err(VirglError::TransferError)
}
}
fn export_blob(&self, resource_id: u32) -> Result<Arc<VirglHandle>, VirglError> {
let mut fd_type = 0;
let mut fd = 0;
let ret = unsafe {
ffi::virgl_renderer_resource_export_blob(resource_id, &mut fd_type, &mut fd)
};
if ret != 0 || fd < 0 {
return Err(VirglError::IoError(std::io::Error::last_os_error()));
}
let handle_type = match fd_type {
ffi::VIRGL_RENDERER_BLOB_FD_TYPE_DMABUF => VIRGL_HANDLE_TYPE_MEM_DMABUF,
ffi::VIRGL_RENDERER_BLOB_FD_TYPE_SHM => VIRGL_HANDLE_TYPE_MEM_SHM,
ffi::VIRGL_RENDERER_BLOB_FD_TYPE_OPAQUE => VIRGL_HANDLE_TYPE_MEM_OPAQUE_FD,
_ => return Err(VirglError::Unsupported),
};
let owned_fd = unsafe { OwnedFd::from_raw_fd(fd) };
Ok(Arc::new(VirglHandle {
os_handle: owned_fd,
handle_type,
}))
}
fn query(&self, resource_id: u32) -> Result<Resource3DInfo, VirglError> {
let mut query: ffi::virgl_renderer_export_query = unsafe {
std::mem::zeroed()
};
query.hdr.stype =
ffi::virgl_renderer_structure_type_v0_VIRGL_RENDERER_STRUCTURE_TYPE_EXPORT_QUERY;
query.hdr.stype_version = 0;
query.hdr.size = size_of::<ffi::virgl_renderer_export_query>() as u32;
query.in_resource_id = resource_id;
query.in_export_fds = 0;
let ret = unsafe {
ffi::virgl_renderer_execute(&mut query as *mut _ as *mut c_void, query.hdr.size)
};
if ret != 0 {
return Err(VirglError::ResourceInfoError);
}
if query.out_num_fds == 0 {
return Err(VirglError::Unsupported);
}
Ok(Resource3DInfo {
width: 0, height: 0, drm_fourcc: query.out_fourcc,
strides: query.out_strides,
offsets: query.out_offsets,
modifier: query.out_modifier,
guest_cpu_mappable: false,
})
}
fn get_resource_info_ext(
&self,
resource_id: u32,
) -> Result<ffi::virgl_renderer_resource_info_ext, VirglError> {
let mut info_ext = unsafe {
std::mem::zeroed()
};
let ret = unsafe {
ffi::virgl_renderer_resource_get_info_ext(resource_id as i32, &mut info_ext)
};
if ret != 0 {
return Err(VirglError::ResourceInfoError);
}
Ok(info_ext)
}
fn get_fd_for_texture(&self, tex_id: u32) -> Result<i32, VirglError> {
let mut fd = -1;
let ret = unsafe {
ffi::virgl_renderer_get_fd_for_texture(tex_id, &mut fd)
};
if ret != 0 || fd < 0 {
return Err(VirglError::IoError(std::io::Error::last_os_error()));
}
Ok(fd)
}
fn map_info(&self, resource_id: u32) -> Result<u32, VirglError> {
let mut map_info: u32 = 0;
let ret = unsafe {
ffi::virgl_renderer_resource_get_map_info(resource_id, &mut map_info)
};
if ret != 0 {
return Err(VirglError::MappingFailed);
}
Ok(map_info)
}
pub fn create_context(
&self,
ctx_id: u32,
context_init: u32,
context_name: Option<&str>,
) -> Result<(), VirglError> {
let mut name: &str = "gpu_renderer";
if let Some(name_string) = context_name.filter(|s| !s.is_empty()) {
name = name_string;
}
let ret = unsafe {
match context_init {
0 => ffi::virgl_renderer_context_create(
ctx_id,
name.len() as u32,
name.as_ptr() as *const c_char,
),
_ => ffi::virgl_renderer_context_create_with_flags(
ctx_id,
context_init,
name.len() as u32,
name.as_ptr() as *const c_char,
),
}
};
if ret == 0 {
Ok(())
} else {
Err(VirglError::ContextCreationError)
}
}
pub fn destroy_context(&self, ctx_id: u32) {
unsafe {
ffi::virgl_renderer_context_destroy(ctx_id);
};
}
pub fn ctx_attach_resource(&self, ctx_id: u32, resource_id: u32) {
unsafe {
ffi::virgl_renderer_ctx_attach_resource(ctx_id as i32, resource_id as i32)
};
}
pub fn ctx_detach_resource(&self, ctx_id: u32, resource_id: u32) {
unsafe {
ffi::virgl_renderer_ctx_detach_resource(ctx_id as i32, resource_id as i32)
};
}
pub fn submit_cmd(
&self,
ctx_id: u32,
commands: &mut [u8],
fence_ids: &[u64],
) -> Result<(), VirglError> {
if !commands.len().is_multiple_of(size_of::<u32>()) {
return Err(VirglError::InvalidCommandSize(commands.len()));
}
let dword_count = (commands.len() / size_of::<u32>()) as i32;
#[cfg(not(feature = "virgl_renderer_unstable"))]
let ret = unsafe {
ffi::virgl_renderer_submit_cmd(
commands.as_mut_ptr() as *mut c_void,
ctx_id as i32,
dword_count,
)
};
#[cfg(feature = "virgl_renderer_unstable")]
let ret = unsafe {
ffi::virgl_renderer_submit_cmd2(
commands.as_mut_ptr() as *mut c_void,
ctx_id as i32,
dword_count,
fence_ids.as_ptr() as *mut u64,
fence_ids.len() as u32,
)
};
if ret == 0 {
Ok(())
} else {
Err(VirglError::IoError(std::io::Error::last_os_error()))
}
}
}
impl Drop for VirglRenderer {
fn drop(&mut self) {
unsafe {
ffi::virgl_renderer_cleanup(null_mut())
};
}
}