// Copyright 2018 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. //! A crate for using hardware acceleration to render virtio-gpu's virgl command streams. extern crate data_model; extern crate libc; extern crate sys_util; mod command_buffer; mod generated; mod pipe_format_fourcc; use std::cell::RefCell; use std::ffi::CStr; use std::fmt; use std::fs::File; use std::marker::PhantomData; use std::mem::{size_of, transmute, uninitialized}; use std::ops::Deref; use std::os::raw::{c_char, c_int, c_uint, c_void}; use std::os::unix::io::{FromRawFd, RawFd}; use std::ptr::{null, null_mut}; use std::rc::Rc; use std::result; use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT}; use data_model::{VolatileMemory, VolatileSlice}; use sys_util::{GuestAddress, GuestMemory}; pub use command_buffer::CommandBufferBuilder; use generated::epoxy_egl::{ EGLAttrib, EGLBoolean, EGLClientBuffer, EGLConfig, EGLContext, EGLDisplay, EGLImageKHR, EGLNativeDisplayType, EGLSurface, EGLenum, EGLint, EGLuint64KHR, EGLDEBUGPROCKHR, EGL_CONTEXT_CLIENT_VERSION, EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_GL_TEXTURE_2D_KHR, EGL_HEIGHT, EGL_LINUX_DMA_BUF_EXT, EGL_LINUX_DRM_FOURCC_EXT, EGL_NONE, EGL_OPENGL_ES_API, EGL_SURFACE_TYPE, EGL_WIDTH, }; use generated::p_defines::{PIPE_BIND_SAMPLER_VIEW, PIPE_TEXTURE_1D, PIPE_TEXTURE_2D}; use generated::p_format::PIPE_FORMAT_B8G8R8X8_UNORM; use generated::virglrenderer::*; pub use generated::virglrenderer::{ virgl_renderer_resource_create_args, virgl_renderer_resource_info, }; pub use pipe_format_fourcc::pipe_format_fourcc as format_fourcc; /// Arguments used in `Renderer::create_resource`.. pub type ResourceCreateArgs = virgl_renderer_resource_create_args; /// Information returned from `Resource::get_info`. pub type ResourceInfo = virgl_renderer_resource_info; /// An error generated while using this crate. #[derive(Debug)] pub enum Error { /// Inidcates `Renderer` was already initialized, and only one renderer per process is allowed. AlreadyInitialized, /// Indicates libeopoxy was unable to load the EGL function with the given name. MissingEGLFunction(&'static str), /// A call to eglGetDisplay indicated failure. EGLGetDisplay, /// A call to eglInitialize indicated failure. EGLInitialize, /// A call to eglChooseConfig indicated failure. EGLChooseConfig, /// A call to eglBindAPI indicated failure. EGLBindAPI, /// A call to eglCreateContext indicated failure. EGLCreateContext, /// A call to eglMakeCurrent indicated failure. EGLMakeCurrent, /// An internal virglrenderer error was returned. Virglrenderer(i32), /// An EGLIMageKHR could not be created, indicating a EGL driver error. CreateImage, /// The EGL driver failed to export an EGLImageKHR as a dmabuf. ExportedResourceDmabuf, /// The indicated region of guest memory is invalid. InvalidIovec, /// A command size was submitted that was invalid. InvalidCommandSize(usize), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Error::*; match *self { AlreadyInitialized => write!(f, "global gpu renderer was already initailized"), MissingEGLFunction(name) => write!(f, "egl function `{}` was missing", name), EGLGetDisplay => write!(f, "call to eglGetDisplay failed"), EGLInitialize => write!(f, "call to eglInitialize failed"), EGLChooseConfig => write!(f, "call to eglChooseConfig failed"), EGLBindAPI => write!(f, "call to eglBindAPI failed"), EGLCreateContext => write!(f, "call to eglCreateContext failed"), EGLMakeCurrent => write!(f, "call to eglMakeCurrent failed"), Virglrenderer(ret) => write!(f, "virglrenderer failed with error {}", ret), CreateImage => write!(f, "failed to create EGLImage"), ExportedResourceDmabuf => write!(f, "failed to export dmabuf from EGLImageKHR"), InvalidIovec => write!(f, "an iovec is outside of guest memory's range"), InvalidCommandSize(s) => write!(f, "command buffer submitted with invalid size: {}", s), } } } /// The result of an operation in this crate. pub type Result = result::Result; fn ret_to_res(ret: i32) -> Result<()> { match ret { 0 => Ok(()), _ => Err(Error::Virglrenderer(ret)), } } #[derive(Debug)] #[repr(C)] struct VirglVec { base: *mut c_void, len: usize, } /// An axis aligned box in 3 dimensional space. #[derive(Debug)] #[repr(C)] pub struct Box3 { pub x: u32, pub y: u32, pub z: u32, pub w: u32, pub h: u32, pub d: u32, } impl Box3 { /// Constructs a 2 dimensional XY box in 3 dimensional space with unit depth and zero /// displacement on the Z axis. pub fn new_2d(x: u32, w: u32, y: u32, h: u32) -> Box3 { Box3 { x, y, z: 0, w, h, d: 1, } } } struct FenceState { latest_fence: u32, } impl FenceState { pub fn write(&mut self, latest_fence: u32) { if latest_fence > self.latest_fence { self.latest_fence = latest_fence; } } } struct VirglCookie { display: EGLDisplay, egl_config: EGLConfig, egl_funcs: EGLFunctions, fence_state: Rc>, } extern "C" fn write_fence(cookie: *mut c_void, fence: u32) { assert!(!cookie.is_null()); let cookie = unsafe { &*(cookie as *mut VirglCookie) }; // Track the most recent fence. let mut fence_state = cookie.fence_state.borrow_mut(); fence_state.write(fence); } unsafe extern "C" fn create_gl_context( cookie: *mut c_void, scanout_idx: c_int, param: *mut virgl_renderer_gl_ctx_param, ) -> virgl_renderer_gl_context { let _ = scanout_idx; let cookie = &*(cookie as *mut VirglCookie); let shared = if (*param).shared { (cookie.egl_funcs.GetCurrentContext)() } else { null_mut() }; let context_attribs = [EGL_CONTEXT_CLIENT_VERSION as i32, 3, EGL_NONE as i32]; (cookie.egl_funcs.CreateContext)( cookie.display, cookie.egl_config, shared, context_attribs.as_ptr(), ) } unsafe extern "C" fn make_current( cookie: *mut c_void, scanout_idx: c_int, ctx: virgl_renderer_gl_context, ) -> c_int { let _ = scanout_idx; let cookie = &*(cookie as *mut VirglCookie); (cookie.egl_funcs.MakeCurrent)(cookie.display, null_mut(), null_mut(), ctx) as c_int } unsafe extern "C" fn destroy_gl_context(cookie: *mut c_void, ctx: virgl_renderer_gl_context) { let cookie = &*(cookie as *mut VirglCookie); (cookie.egl_funcs.DestroyContext)(cookie.display, ctx); } const VIRGL_RENDERER_CALLBACKS: &virgl_renderer_callbacks = &virgl_renderer_callbacks { version: 1, write_fence: Some(write_fence), create_gl_context: Some(create_gl_context), destroy_gl_context: Some(destroy_gl_context), make_current: Some(make_current), get_drm_fd: None, }; unsafe extern "C" fn error_callback( error: c_uint, command: *const c_char, _: c_int, _: *mut c_void, _: *mut c_void, message: *const c_char, ) { eprint!("EGL ERROR {}: {:?}", error, CStr::from_ptr(command)); if !message.is_null() { eprint!(": {:?}", CStr::from_ptr(message)); } eprintln!(); } #[allow(non_snake_case)] struct EGLFunctionsInner { BindAPI: unsafe extern "C" fn(api: EGLenum) -> EGLBoolean, ChooseConfig: unsafe extern "C" fn( dpy: EGLDisplay, attrib_list: *const EGLint, configs: *mut EGLConfig, config_size: EGLint, num_config: *mut EGLint, ) -> EGLBoolean, CreateContext: unsafe extern "C" fn( dpy: EGLDisplay, config: EGLConfig, share_context: EGLContext, attrib_list: *const EGLint, ) -> EGLContext, CreateImageKHR: unsafe extern "C" fn( dpy: EGLDisplay, ctx: EGLContext, target: EGLenum, buffer: EGLClientBuffer, attrib_list: *const EGLint, ) -> EGLImageKHR, DebugMessageControlKHR: unsafe extern "C" fn(callback: EGLDEBUGPROCKHR, attrib_list: *const EGLAttrib) -> EGLint, DestroyContext: unsafe extern "C" fn(dpy: EGLDisplay, ctx: EGLContext) -> EGLBoolean, DestroyImageKHR: unsafe extern "C" fn(dpy: EGLDisplay, image: EGLImageKHR) -> EGLBoolean, ExportDRMImageMESA: unsafe extern "C" fn( dpy: EGLDisplay, image: EGLImageKHR, fds: *mut ::std::os::raw::c_int, strides: *mut EGLint, offsets: *mut EGLint, ) -> EGLBoolean, ExportDMABUFImageQueryMESA: unsafe extern "C" fn( dpy: EGLDisplay, image: EGLImageKHR, fourcc: *mut ::std::os::raw::c_int, num_planes: *mut ::std::os::raw::c_int, modifiers: *mut EGLuint64KHR, ) -> EGLBoolean, GetCurrentContext: unsafe extern "C" fn() -> EGLContext, GetCurrentDisplay: unsafe extern "C" fn() -> EGLDisplay, GetDisplay: unsafe extern "C" fn(display_id: EGLNativeDisplayType) -> EGLDisplay, Initialize: unsafe extern "C" fn(dpy: EGLDisplay, major: *mut EGLint, minor: *mut EGLint) -> EGLBoolean, MakeCurrent: unsafe extern "C" fn( dpy: EGLDisplay, draw: EGLSurface, read: EGLSurface, ctx: EGLContext, ) -> EGLBoolean, no_sync_send: PhantomData<*mut ()>, } #[derive(Clone)] struct EGLFunctions(Rc); impl EGLFunctions { fn new() -> Result { use generated::epoxy_egl::{ epoxy_eglBindAPI, epoxy_eglChooseConfig, epoxy_eglCreateContext, epoxy_eglCreateImageKHR, epoxy_eglDebugMessageControlKHR, epoxy_eglDestroyContext, epoxy_eglDestroyImageKHR, epoxy_eglExportDMABUFImageQueryMESA, epoxy_eglExportDRMImageMESA, epoxy_eglGetCurrentContext, epoxy_eglGetCurrentDisplay, epoxy_eglGetDisplay, epoxy_eglInitialize, epoxy_eglMakeCurrent, }; // This is unsafe because it is reading mutable static variables exported by epoxy. These // variables are initialized during the binary's init and never modified again, so it should // be safe to read them now. unsafe { Ok(EGLFunctions(Rc::new(EGLFunctionsInner { BindAPI: epoxy_eglBindAPI.ok_or(Error::MissingEGLFunction("eglBindAPI"))?, ChooseConfig: epoxy_eglChooseConfig .ok_or(Error::MissingEGLFunction("eglChooseConfig"))?, CreateContext: epoxy_eglCreateContext .ok_or(Error::MissingEGLFunction("eglCreateContext"))?, CreateImageKHR: epoxy_eglCreateImageKHR .ok_or(Error::MissingEGLFunction("eglCreateImageKHR"))?, DebugMessageControlKHR: epoxy_eglDebugMessageControlKHR .ok_or(Error::MissingEGLFunction("eglDebugMessageControlKHR"))?, DestroyContext: epoxy_eglDestroyContext .ok_or(Error::MissingEGLFunction("eglDestroyContext"))?, DestroyImageKHR: epoxy_eglDestroyImageKHR .ok_or(Error::MissingEGLFunction("eglDestroyImageKHR"))?, ExportDRMImageMESA: epoxy_eglExportDRMImageMESA .ok_or(Error::MissingEGLFunction("eglExportDRMImageMESA"))?, ExportDMABUFImageQueryMESA: epoxy_eglExportDMABUFImageQueryMESA .ok_or(Error::MissingEGLFunction("eglExportDMABUFImageQueryMESA"))?, GetCurrentContext: epoxy_eglGetCurrentContext .ok_or(Error::MissingEGLFunction("eglGetCurrentContext"))?, GetCurrentDisplay: epoxy_eglGetCurrentDisplay .ok_or(Error::MissingEGLFunction("eglGetCurrentDisplay"))?, GetDisplay: epoxy_eglGetDisplay .ok_or(Error::MissingEGLFunction("eglGetDisplay"))?, Initialize: epoxy_eglInitialize .ok_or(Error::MissingEGLFunction("eglInitialize"))?, MakeCurrent: epoxy_eglMakeCurrent .ok_or(Error::MissingEGLFunction("eglMakeCurrent"))?, no_sync_send: PhantomData, }))) } } } impl Deref for EGLFunctions { type Target = EGLFunctionsInner; fn deref(&self) -> &EGLFunctionsInner { self.0.deref() } } /// The global renderer handle used to query capability sets, and create resources and contexts. pub struct Renderer { no_sync_send: PhantomData<*mut ()>, egl_funcs: EGLFunctions, display: EGLDisplay, fence_state: Rc>, } impl Renderer { /// Initializes the renderer and returns a handle to it. /// /// This may only be called once per process. Calls after the first will return an error. pub fn init() -> Result { // virglrenderer is a global state backed library that uses thread bound OpenGL contexts. // Initialize it only once and use the non-send/non-sync Renderer struct to keep things tied // to whichever thread called this function first. static INIT_ONCE: AtomicBool = ATOMIC_BOOL_INIT; if INIT_ONCE.compare_and_swap(false, true, Ordering::Acquire) { return Err(Error::AlreadyInitialized); } let egl_funcs = EGLFunctions::new()?; // Safe because only valid callbacks are given and only one thread can execute this // function. unsafe { (egl_funcs.DebugMessageControlKHR)(Some(error_callback), null()); } // Trivially safe. let display = unsafe { (egl_funcs.GetDisplay)(null_mut()) }; if display.is_null() { return Err(Error::EGLGetDisplay); } // Safe because only a valid display is given. let ret = unsafe { (egl_funcs.Initialize)(display, null_mut(), null_mut()) }; if ret == 0 { return Err(Error::EGLInitialize); } let config_attribs = [EGL_SURFACE_TYPE as i32, -1, EGL_NONE as i32]; // Safe because these uninitialized variables get initialized by the ChooseConfig function. let mut egl_config: *mut c_void = unsafe { uninitialized() }; let mut num_configs = unsafe { uninitialized() }; // Safe because only a valid, initialized display is used, along with validly sized // pointers to stack variables. let ret = unsafe { (egl_funcs.ChooseConfig)( display, config_attribs.as_ptr(), &mut egl_config, 1, &mut num_configs, /* unused but can't be null */ ) }; if ret == 0 { return Err(Error::EGLChooseConfig); } // Cookie is intentionally never freed because virglrenderer never gets uninitialized. // Otherwise, Resource and Context would become invalid because their lifetime is not tied // to the Renderer instance. Doing so greatly simplifies the ownership for users of this // library. let fence_state = Rc::new(RefCell::new(FenceState { latest_fence: 0 })); let cookie: *mut VirglCookie = Box::into_raw(Box::new(VirglCookie { display, egl_config, egl_funcs: egl_funcs.clone(), fence_state: Rc::clone(&fence_state), })); // Safe because EGL was properly initialized before here.. let ret = unsafe { (egl_funcs.BindAPI)(EGL_OPENGL_ES_API) }; if ret == 0 { return Err(Error::EGLBindAPI); } let context_attribs = [EGL_CONTEXT_CLIENT_VERSION as i32, 3, EGL_NONE as i32]; // Safe because a valid display, config, and config_attribs pointer are given. let ctx = unsafe { (egl_funcs.CreateContext)(display, egl_config, null_mut(), context_attribs.as_ptr()) }; if ctx.is_null() { return Err(Error::EGLCreateContext); } // Safe because a valid display and context is used, and the two null surfaces are not // used. let ret = unsafe { (egl_funcs.MakeCurrent)(display, null_mut(), null_mut(), ctx) }; if ret == 0 { return Err(Error::EGLMakeCurrent); } // Safe because a valid cookie and set of callbacks is used and the result is checked for // error. let ret = unsafe { virgl_renderer_init( cookie as *mut c_void, 0, transmute(VIRGL_RENDERER_CALLBACKS), ) }; ret_to_res(ret)?; Ok(Renderer { no_sync_send: PhantomData, egl_funcs, display, fence_state, }) } /// Gets the version and size for the given capability set ID. pub fn get_cap_set_info(&self, id: u32) -> (u32, u32) { let mut version = 0; let mut size = 0; // Safe because virglrenderer is initialized by now and properly size stack variables are // used for the pointers. unsafe { virgl_renderer_get_cap_set(id, &mut version, &mut size); } (version, size) } /// Gets the capability set for the given ID and version. pub fn get_cap_set(&self, id: u32, version: u32) -> Vec { let (_, max_size) = self.get_cap_set_info(id); let mut buf = vec![0u8; max_size as usize]; // Safe because virglrenderer is initialized by now and the given buffer is sized properly // for the given cap id/version. unsafe { virgl_renderer_fill_caps(id, version, buf.as_mut_ptr() as *mut c_void); } buf } /// Creates a rendering context with the given id. pub fn create_context(&self, id: u32) -> Result { const CONTEXT_NAME: &[u8] = b"gpu_renderer"; // Safe because virglrenderer is initialized by now and the context name is statically // allocated. The return value is checked before returning a new context. let ret = unsafe { virgl_renderer_context_create( id, CONTEXT_NAME.len() as u32, CONTEXT_NAME.as_ptr() as *const c_char, ) }; ret_to_res(ret)?; Ok(Context { id, no_sync_send: PhantomData, }) } /// Creates a resource with the given arguments. pub fn create_resource( &self, mut args: virgl_renderer_resource_create_args, ) -> Result { // Safe because virglrenderer is initialized by now, and the return value is checked before // returning a new resource. The backing buffers are not supplied with this call. let ret = unsafe { virgl_renderer_resource_create(&mut args, null_mut(), 0) }; ret_to_res(ret)?; Ok(Resource { id: args.handle, backing_iovecs: Vec::new(), backing_mem: None, egl_funcs: self.egl_funcs.clone(), no_sync_send: PhantomData, }) } /// Imports a resource from an EGLImage. pub fn import_resource( &self, mut args: virgl_renderer_resource_create_args, image: &Image, ) -> Result { let ret = unsafe { virgl_renderer_resource_import_eglimage(&mut args, image.image) }; ret_to_res(ret)?; Ok(Resource { id: args.handle, backing_iovecs: Vec::new(), backing_mem: None, egl_funcs: self.egl_funcs.clone(), no_sync_send: PhantomData, }) } /// Helper that creates a simple 1 dimensional resource with basic metadata. pub fn create_tex_1d(&self, id: u32, width: u32) -> Result { self.create_resource(virgl_renderer_resource_create_args { handle: id, target: PIPE_TEXTURE_1D, format: PIPE_FORMAT_B8G8R8X8_UNORM, width, height: 1, depth: 1, array_size: 1, last_level: 0, nr_samples: 0, bind: PIPE_BIND_SAMPLER_VIEW, flags: 0, }) } /// Helper that creates a simple 2 dimensional resource with basic metadata. pub fn create_tex_2d(&self, id: u32, width: u32, height: u32) -> Result { self.create_resource(virgl_renderer_resource_create_args { handle: id, target: PIPE_TEXTURE_2D, format: PIPE_FORMAT_B8G8R8X8_UNORM, width, height, depth: 1, array_size: 1, last_level: 0, nr_samples: 0, bind: PIPE_BIND_SAMPLER_VIEW, flags: 0, }) } /// Creates an EGLImage from a DMA buffer. pub fn image_from_dmabuf( &self, fourcc: u32, width: u32, height: u32, fd: RawFd, offset: u32, stride: u32, ) -> Result { let mut attrs = [ EGL_WIDTH as EGLint, width as EGLint, EGL_HEIGHT as EGLint, height as EGLint, EGL_LINUX_DRM_FOURCC_EXT as EGLint, fourcc as EGLint, EGL_DMA_BUF_PLANE0_FD_EXT as EGLint, fd as EGLint, EGL_DMA_BUF_PLANE0_OFFSET_EXT as EGLint, offset as EGLint, EGL_DMA_BUF_PLANE0_PITCH_EXT as EGLint, stride as EGLint, EGL_NONE as EGLint, ]; let image = unsafe { (self.egl_funcs.CreateImageKHR)( self.display, 0 as EGLContext, EGL_LINUX_DMA_BUF_EXT, null_mut() as EGLClientBuffer, attrs.as_mut_ptr(), ) }; if image.is_null() { return Err(Error::CreateImage); } Ok(Image { egl_funcs: self.egl_funcs.clone(), egl_dpy: self.display, image, }) } pub fn poll(&self) -> u32 { unsafe { virgl_renderer_poll() }; self.fence_state.borrow().latest_fence } pub fn create_fence(&mut self, fence_id: u32, ctx_id: u32) -> Result<()> { let ret = unsafe { virgl_renderer_create_fence(fence_id as i32, ctx_id) }; ret_to_res(ret) } pub fn force_ctx_0(&self) { unsafe { virgl_renderer_force_ctx_0() }; } } /// A context in which resources can be attached/detached and commands can be submitted. pub struct Context { id: u32, no_sync_send: PhantomData<*mut ()>, } impl Context { /// Gets the ID assigned to this context when it was created. pub fn id(&self) -> u32 { self.id } /// Submits a command stream to this rendering context. pub fn submit>(&mut self, mut buf: T) -> Result<()> { let buf = buf.as_mut(); if buf.len() % size_of::() != 0 { return Err(Error::InvalidCommandSize(buf.len())); } let dword_count = (buf.len() / size_of::()) as i32; // Safe because the context and buffer are valid and virglrenderer will have been // initialized if there are Context instances. let ret = unsafe { virgl_renderer_submit_cmd(buf.as_mut_ptr() as *mut c_void, self.id as i32, dword_count) }; ret_to_res(ret) } /// Attaches the given resource to this rendering context. pub fn attach(&mut self, res: &Resource) { // The context id and resource id must be valid because the respective instances ensure // their lifetime. unsafe { virgl_renderer_ctx_attach_resource(self.id as i32, res.id() as i32); } } /// Detaches a previously attached resource from this rendering context. pub fn detach(&mut self, res: &Resource) { // The context id and resource id must be valid because the respective instances ensure // their lifetime. unsafe { virgl_renderer_ctx_detach_resource(self.id as i32, res.id() as i32); } } } impl Drop for Context { fn drop(&mut self) { // The context is safe to destroy because nothing else can be referencing it. unsafe { virgl_renderer_context_destroy(self.id); } } } /// A wrapper of an EGLImage to manage its destruction. pub struct Image { egl_funcs: EGLFunctions, egl_dpy: EGLDisplay, image: EGLImageKHR, } impl Drop for Image { fn drop(&mut self) { unsafe { (self.egl_funcs.DestroyImageKHR)(self.egl_dpy, self.image); } } } /// A DMABUF file descriptor handle and metadata returned from `Resource::export`. #[derive(Debug)] pub struct ExportedResource { /// The file descriptor that represents the DMABUF kernel object. pub dmabuf: File, /// The width in pixels of the exported resource. pub width: u32, /// The height in pixels of the exported resource. pub height: u32, /// The fourcc identifier for the format of the resource. pub fourcc: u32, /// Extra modifiers for the format. pub modifiers: u64, /// The number of bytes between successive rows in the exported resource. pub stride: u32, /// The number of bytes from the start of the exported resource to the first pixel. pub offset: u32, } /// A resource handle used by the renderer. pub struct Resource { id: u32, backing_iovecs: Vec, backing_mem: Option, egl_funcs: EGLFunctions, no_sync_send: PhantomData<*mut ()>, } impl Resource { /// Gets the ID assigned to this resource when it was created. pub fn id(&self) -> u32 { self.id } /// Retrieves metadata about this resource. pub fn get_info(&self) -> Result { // Safe because the resource info is filled in by the virgl call and only returned if it was // successful. let mut res_info = unsafe { uninitialized() }; let ret = unsafe { virgl_renderer_resource_get_info(self.id as i32, &mut res_info) }; ret_to_res(ret)?; Ok(res_info) } /// Performs an export of this resource so that it may be imported by other processes. pub fn export(&self) -> Result { let res_info = self.get_info()?; let mut fourcc = 0; let mut modifiers = 0; let mut fd = -1; let mut stride = 0; let mut offset = 0; // Always safe on the same thread with an already initialized virglrenderer. unsafe { virgl_renderer_force_ctx_0(); } // These are trivially safe and always return successfully because we bind the context in // the previous line. let egl_dpy: EGLDisplay = unsafe { (self.egl_funcs.GetCurrentDisplay)() }; let egl_ctx: EGLContext = unsafe { (self.egl_funcs.GetCurrentContext)() }; // Safe because a valid display, context, and texture ID are given. The attribute list is // not needed. The result is checked to ensure the returned image is valid. let image = unsafe { (self.egl_funcs.CreateImageKHR)( egl_dpy, egl_ctx, EGL_GL_TEXTURE_2D_KHR, res_info.tex_id as EGLClientBuffer, null(), ) }; if image.is_null() { return Err(Error::CreateImage); } // Safe because the display and image are valid and each function call is checked for // success. The returned image parameters are stored in stack variables of the correct type. let export_success = unsafe { (self.egl_funcs.ExportDMABUFImageQueryMESA)( egl_dpy, image, &mut fourcc, null_mut(), &mut modifiers, ) != 0 && (self.egl_funcs.ExportDRMImageMESA)( egl_dpy, image, &mut fd, &mut stride, &mut offset, ) != 0 }; // Safe because we checked that the image was valid and nobody else owns it. The image does // not need to be around for the dmabuf to be valid. unsafe { (self.egl_funcs.DestroyImageKHR)(egl_dpy, image); } if !export_success || fd < 0 { return Err(Error::ExportedResourceDmabuf); } // Safe because the FD was just returned by a successful EGL call so it must be valid and // owned by us. let dmabuf = unsafe { File::from_raw_fd(fd) }; Ok(ExportedResource { dmabuf, width: res_info.width, height: res_info.height, fourcc: fourcc as u32, modifiers, stride: stride as u32, offset: offset as u32, }) } /// Attaches a scatter-gather mapping of guest memory to this resource which used for transfers. pub fn attach_backing( &mut self, iovecs: &[(GuestAddress, usize)], mem: &GuestMemory, ) -> Result<()> { if iovecs .iter() .any(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).is_err()) { return Err(Error::InvalidIovec); } self.detach_backing(); self.backing_mem = Some(mem.clone()); for &(addr, len) in iovecs.iter() { // Unwrap will not panic because we already checked the slices. let slice = mem.get_slice(addr.offset(), len as u64).unwrap(); self.backing_iovecs.push(VirglVec { base: slice.as_ptr() as *mut c_void, len, }); } // Safe because the backing is into guest memory that we store a reference count for. let ret = unsafe { virgl_renderer_resource_attach_iov( self.id as i32, self.backing_iovecs.as_mut_ptr() as *mut iovec, self.backing_iovecs.len() as i32, ) }; let res = ret_to_res(ret); if res.is_err() { // Not strictly necessary, but it's good to clear out our collection of pointers to // memory we don't own or need. self.backing_iovecs.clear(); self.backing_mem = None; } res } /// Detaches previously attached scatter-gather memory from this resource. pub fn detach_backing(&mut self) { // Safe as we don't need the old backing iovecs returned and the reference to the guest // memory can be dropped as it will no longer be needed for this resource. unsafe { virgl_renderer_resource_detach_iov(self.id as i32, null_mut(), null_mut()); } self.backing_iovecs.clear(); self.backing_mem = None; } /// Performs a transfer to the given resource from its backing in guest memory. pub fn transfer_write( &self, ctx: Option<&Context>, level: u32, stride: u32, layer_stride: u32, mut transfer_box: Box3, offset: u64, ) -> Result<()> { // Safe because only stack variables of the appropriate type are used. let ret = unsafe { virgl_renderer_transfer_write_iov( self.id, ctx.map(Context::id).unwrap_or(0), level as i32, stride, layer_stride, &mut transfer_box as *mut Box3 as *mut virgl_box, offset, null_mut(), 0, ) }; ret_to_res(ret) } /// Performs a transfer from the given resource to its backing in guest memory. pub fn transfer_read( &self, ctx: Option<&Context>, level: u32, stride: u32, layer_stride: u32, mut transfer_box: Box3, offset: u64, ) -> Result<()> { // Safe because only stack variables of the appropriate type are used. let ret = unsafe { virgl_renderer_transfer_read_iov( self.id, ctx.map(Context::id).unwrap_or(0), level, stride, layer_stride, &mut transfer_box as *mut Box3 as *mut virgl_box, offset, null_mut(), 0, ) }; ret_to_res(ret) } /// Performs a transfer from the given resource to the provided `buf` pub fn transfer_read_buf( &self, ctx: Option<&Context>, level: u32, stride: u32, layer_stride: u32, mut transfer_box: Box3, offset: u64, buf: &mut [u8], ) -> Result<()> { let mut iov = VirglVec { base: buf.as_mut_ptr() as *mut c_void, len: buf.len(), }; // Safe because only stack variables of the appropriate type are used, along with a properly // sized buffer. let ret = unsafe { virgl_renderer_transfer_read_iov( self.id, ctx.map(Context::id).unwrap_or(0), level, stride, layer_stride, &mut transfer_box as *mut Box3 as *mut virgl_box, offset, &mut iov as *mut VirglVec as *mut iovec, 1, ) }; ret_to_res(ret) } /// Reads from this resource to a volatile slice of memory. pub fn read_to_volatile( &self, ctx: Option<&Context>, level: u32, stride: u32, layer_stride: u32, mut transfer_box: Box3, offset: u64, buf: VolatileSlice, ) -> Result<()> { let mut iov = VirglVec { base: buf.as_ptr() as *mut c_void, len: buf.size() as usize, }; // Safe because only stack variables of the appropriate type are used, along with a properly // sized buffer. let ret = unsafe { virgl_renderer_transfer_read_iov( self.id, ctx.map(Context::id).unwrap_or(0), level, stride, layer_stride, &mut transfer_box as *mut Box3 as *mut virgl_box, offset, &mut iov as *mut VirglVec as *mut iovec, 1, ) }; ret_to_res(ret) } } impl Drop for Resource { fn drop(&mut self) { // The resource is safe to unreference destroy because no user of these bindings can still // be holding a reference. unsafe { virgl_renderer_resource_unref(self.id); } } } #[cfg(test)] mod tests { use super::*; use generated::p_defines::PIPE_CLEAR_COLOR0; #[test] #[ignore] // Make sure a simple buffer clear works by using a command stream. fn simple_clear() { let render = Renderer::init().expect("failed to initialize virglrenderer"); let mut ctx = render.create_context(1).expect("failed to create context"); // Create a 50x50 texture with id=2. let resource = render .create_tex_2d(2, 50, 50) .expect("failed to create texture"); ctx.attach(&resource); // Create a command buffer that uses the resource as a render target and clears it. const CLEAR_COLOR: [f32; 4] = [0.5, 0.4, 0.3, 0.2]; let mut cbuf = CommandBufferBuilder::new(); cbuf.e_create_surface(1, &resource, PIPE_FORMAT_B8G8R8X8_UNORM, 0, 0, 0); cbuf.e_set_fb_state(&[1], None); cbuf.e_clear(PIPE_CLEAR_COLOR0, CLEAR_COLOR, 0.0, 0); ctx.submit(&mut cbuf) .expect("failed to submit command buffer to context"); // Read the result of the rendering into a buffer. let mut pix_buf = [0; 50 * 50 * 4]; resource .transfer_read_buf( Some(&ctx), 0, 50, 0, Box3::new_2d(0, 5, 0, 1), 0, &mut pix_buf[..], ) .expect("failed to read back resource data"); // Check that the pixels are the color we cleared to. The red and blue channels are switched // because the surface was created with the BGR format, but the colors are RGB order in the // command stream. assert_eq!(pix_buf[0], (256.0 * CLEAR_COLOR[2]) as u8); assert_eq!(pix_buf[1], (256.0 * CLEAR_COLOR[1]) as u8); assert_eq!(pix_buf[2], (256.0 * CLEAR_COLOR[0]) as u8); assert_eq!(pix_buf[3], (256.0 * CLEAR_COLOR[3]) as u8); } }