blob: b6f1383a8b5c4cd73de5e331c1c865b789997b9c [file] [log] [blame]
// 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);
}
}