| // 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 generated; |
| mod pipe_format_fourcc; |
| mod command_buffer; |
| |
| 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_void, c_int, c_uint, c_char}; |
| use std::os::unix::io::FromRawFd; |
| 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}; |
| |
| use generated::virglrenderer::*; |
| pub use generated::virglrenderer::{virgl_renderer_resource_create_args, |
| virgl_renderer_resource_info}; |
| use generated::epoxy_egl::{EGL_CONTEXT_CLIENT_VERSION, EGL_SURFACE_TYPE, EGL_OPENGL_ES_API, |
| EGL_NONE, EGL_GL_TEXTURE_2D_KHR, EGLDEBUGPROCKHR, EGLAttrib, |
| EGLuint64KHR, EGLNativeDisplayType, EGLConfig, EGLContext, EGLDisplay, |
| EGLSurface, EGLClientBuffer, EGLBoolean, EGLint, EGLenum, EGLImageKHR}; |
| use generated::p_defines::{PIPE_TEXTURE_1D, PIPE_TEXTURE_2D, PIPE_BIND_SAMPLER_VIEW}; |
| use generated::p_format::PIPE_FORMAT_B8G8R8X8_UNORM; |
| pub use pipe_format_fourcc::pipe_format_fourcc as format_fourcc; |
| pub use command_buffer::CommandBufferBuilder; |
| |
| /// 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<T> = result::Result<T, Error>; |
| |
| 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 VirglCookie { |
| display: EGLDisplay, |
| egl_config: EGLConfig, |
| egl_funcs: EGLFunctions, |
| } |
| |
| 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: None, |
| create_gl_context: Some(create_gl_context), |
| destroy_gl_context: Some(destroy_gl_context), |
| make_current: Some(make_current), |
| }; |
| |
| 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<EGLFunctionsInner>); |
| |
| impl EGLFunctions { |
| fn new() -> Result<EGLFunctions> { |
| use generated::epoxy_egl::{epoxy_eglBindAPI, epoxy_eglChooseConfig, |
| epoxy_eglCreateContext, epoxy_eglCreateImageKHR, |
| epoxy_eglDebugMessageControlKHR, epoxy_eglDestroyContext, |
| epoxy_eglDestroyImageKHR, epoxy_eglExportDRMImageMESA, |
| epoxy_eglExportDMABUFImageQueryMESA, |
| 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, |
| } |
| |
| 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<Renderer> { |
| // 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 cookie: *mut VirglCookie = Box::into_raw(Box::new(VirglCookie { |
| display, |
| egl_config, |
| egl_funcs: egl_funcs.clone(), |
| })); |
| |
| // 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, |
| }) |
| } |
| |
| /// 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<u8> { |
| 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<Context> { |
| 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<Resource> { |
| // 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, |
| }) |
| } |
| |
| /// Helper that creates a simple 1 dimensional resource with basic metadata. |
| pub fn create_tex_1d(&self, id: u32, width: u32) -> Result<Resource> { |
| 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<Resource> { |
| 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, |
| }) |
| } |
| } |
| |
| /// 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<T: AsMut<[u8]>>(&mut self, mut buf: T) -> Result<()> { |
| let buf = buf.as_mut(); |
| if buf.len() % size_of::<u32>() != 0 { |
| return Err(Error::InvalidCommandSize(buf.len())); |
| } |
| let dword_count = (buf.len() / size_of::<u32>()) 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 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<VirglVec>, |
| backing_mem: Option<GuestMemory>, |
| 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<ResourceInfo> { |
| // 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<ExportedResource> { |
| 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: 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); |
| } |
| } |