blob: be8c17a57ea4d67e331fcd7c8a6b7bd2011ee1e5 [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.
mod command_buffer;
mod generated;
use std::cell::RefCell;
use std::fmt::{self, Display};
use std::fs::File;
use std::marker::PhantomData;
use std::mem::{size_of, transmute};
use std::os::raw::{c_char, c_void};
use std::os::unix::io::FromRawFd;
use std::ptr::null_mut;
use std::rc::Rc;
use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use libc::close;
use data_model::{VolatileMemory, VolatileSlice};
use sys_util::{GuestAddress, GuestMemory};
use crate::generated::p_defines::{
PIPE_BIND_RENDER_TARGET, PIPE_BIND_SAMPLER_VIEW, PIPE_TEXTURE_1D, PIPE_TEXTURE_2D,
};
use crate::generated::p_format::PIPE_FORMAT_B8G8R8X8_UNORM;
use crate::generated::virglrenderer::*;
pub use crate::command_buffer::CommandBufferBuilder;
/// Arguments used in `Renderer::create_resource`..
pub type ResourceCreateArgs = virgl_renderer_resource_create_args;
/// Some of the information returned from `Resource::export_query`.
pub type Query = virgl_renderer_export_query;
/// 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,
/// An internal virglrenderer error was returned.
Virglrenderer(i32),
/// 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),
/// The command is unsupported.
Unsupported,
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
AlreadyInitialized => write!(f, "global gpu renderer was already initailized"),
Virglrenderer(ret) => write!(f, "virglrenderer failed with error {}", ret),
ExportedResourceDmabuf => write!(f, "failed to export dmabuf"),
InvalidIovec => write!(f, "an iovec is outside of guest memory's range"),
InvalidCommandSize(s) => write!(f, "command buffer submitted with invalid size: {}", s),
Unsupported => write!(f, "gpu renderer function unsupported"),
}
}
}
/// 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, y: u32, w: u32, h: u32) -> Box3 {
Box3 {
x,
y,
z: 0,
w,
h,
d: 1,
}
}
/// Returns true if this box represents a volume of zero.
pub fn is_empty(&self) -> bool {
self.w == 0 || self.h == 0 || self.d == 0
}
}
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 {
fence_state: Rc<RefCell<FenceState>>,
}
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);
}
const VIRGL_RENDERER_CALLBACKS: &virgl_renderer_callbacks = &virgl_renderer_callbacks {
version: 1,
write_fence: Some(write_fence),
create_gl_context: None,
destroy_gl_context: None,
make_current: None,
get_drm_fd: None,
};
#[derive(Copy, Clone)]
pub struct RendererFlags(u32);
impl Default for RendererFlags {
fn default() -> RendererFlags {
RendererFlags::new()
.use_egl(true)
.use_surfaceless(true)
.use_gles(true)
}
}
impl RendererFlags {
pub fn new() -> RendererFlags {
RendererFlags(0)
}
fn set_flag(self, bitmask: u32, set: bool) -> RendererFlags {
if set {
RendererFlags(self.0 | bitmask)
} else {
RendererFlags(self.0 & (!bitmask))
}
}
pub fn uses_egl(self) -> bool {
(self.0 & VIRGL_RENDERER_USE_EGL) != 0
}
pub fn use_egl(self, v: bool) -> RendererFlags {
self.set_flag(VIRGL_RENDERER_USE_EGL, v)
}
pub fn uses_glx(self) -> bool {
(self.0 & VIRGL_RENDERER_USE_GLX) != 0
}
pub fn use_glx(self, v: bool) -> RendererFlags {
self.set_flag(VIRGL_RENDERER_USE_GLX, v)
}
pub fn uses_surfaceless(self) -> bool {
(self.0 & VIRGL_RENDERER_USE_SURFACELESS) != 0
}
pub fn use_surfaceless(self, v: bool) -> RendererFlags {
self.set_flag(VIRGL_RENDERER_USE_SURFACELESS, v)
}
pub fn uses_gles(self) -> bool {
(self.0 & VIRGL_RENDERER_USE_GLES) != 0
}
pub fn use_gles(self, v: bool) -> RendererFlags {
self.set_flag(VIRGL_RENDERER_USE_GLES, v)
}
}
impl From<RendererFlags> for i32 {
fn from(flags: RendererFlags) -> i32 {
flags.0 as i32
}
}
/// The global renderer handle used to query capability sets, and create resources and contexts.
pub struct Renderer {
no_sync_send: PhantomData<*mut ()>,
fence_state: Rc<RefCell<FenceState>>,
}
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(flags: RendererFlags) -> 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 = AtomicBool::new(false);
if INIT_ONCE.compare_and_swap(false, true, Ordering::Acquire) {
return Err(Error::AlreadyInitialized);
}
// 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 {
fence_state: Rc::clone(&fence_state),
}));
// 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,
flags.into(),
transmute(VIRGL_RENDERER_CALLBACKS),
)
};
ret_to_res(ret)?;
Ok(Renderer {
no_sync_send: PhantomData,
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<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,
no_sync_send: PhantomData,
})
}
/// Helper that creates a simple 2 dimensional resource with basic metadata and usable for
/// display.
pub fn create_resource_2d(
&self,
id: u32,
width: u32,
height: u32,
format: u32,
) -> Result<Resource> {
self.create_resource(virgl_renderer_resource_create_args {
handle: id,
target: PIPE_TEXTURE_2D,
format,
width,
height,
depth: 1,
array_size: 1,
last_level: 0,
nr_samples: 0,
bind: PIPE_BIND_RENDER_TARGET,
flags: 0,
})
}
/// 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 and usable as a
/// texture.
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,
})
}
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() };
}
#[allow(unused_variables)]
pub fn allocation_metadata(&self, request: &[u8], response: &mut Vec<u8>) -> Result<()> {
#[cfg(feature = "virtio-gpu-next")]
{
let ret = unsafe {
virgl_renderer_allocation_metadata(
request.as_ptr() as *const c_void,
response.as_mut_ptr() as *mut c_void,
request.len() as u32,
response.len() as u32,
)
};
ret_to_res(ret)
}
#[cfg(not(feature = "virtio-gpu-next"))]
Err(Error::Unsupported)
}
#[allow(unused_variables)]
pub fn resource_create_v2(
&self,
resource_id: u32,
guest_memory_type: u32,
guest_caching_type: u32,
size: u64,
mem: &GuestMemory,
iovecs: &[(GuestAddress, usize)],
args: &[u8],
) -> Result<Resource> {
#[cfg(feature = "virtio-gpu-next")]
{
if iovecs
.iter()
.any(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).is_err())
{
return Err(Error::InvalidIovec);
}
let mut vecs = Vec::new();
for &(addr, len) in iovecs {
// Unwrap will not panic because we already checked the slices.
let slice = mem.get_slice(addr.offset(), len as u64).unwrap();
vecs.push(VirglVec {
base: slice.as_ptr() as *mut c_void,
len,
});
}
let ret = unsafe {
virgl_renderer_resource_create_v2(
resource_id,
guest_memory_type,
guest_caching_type,
size,
vecs.as_ptr() as *const iovec,
vecs.len() as u32,
args.as_ptr() as *const c_void,
args.len() as u32,
)
};
ret_to_res(ret)?;
Ok(Resource {
id: resource_id,
backing_iovecs: vecs,
backing_mem: None,
no_sync_send: PhantomData,
})
}
#[cfg(not(feature = "virtio-gpu-next"))]
Err(Error::Unsupported)
}
}
/// 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 resource handle used by the renderer.
pub struct Resource {
id: u32,
backing_iovecs: Vec<VirglVec>,
backing_mem: Option<GuestMemory>,
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 suitable for export about this resource. If "export_fd" is true,
/// performs an export of this resource so that it may be imported by other processes.
fn export_query(&self, export_fd: bool) -> Result<Query> {
let mut query: Query = Default::default();
query.hdr.stype = VIRGL_RENDERER_STRUCTURE_TYPE_EXPORT_QUERY;
query.hdr.stype_version = 0;
query.hdr.size = size_of::<Query>() as u32;
query.in_resource_id = self.id;
query.in_export_fds = if export_fd { 1 } else { 0 };
// Safe because the image parameters are stack variables of the correct type.
let ret =
unsafe { virgl_renderer_execute(&mut query as *mut _ as *mut c_void, query.hdr.size) };
ret_to_res(ret)?;
Ok(query)
}
/// Returns resource metadata.
pub fn query(&self) -> Result<Query> {
self.export_query(false)
}
/// Returns resource metadata and exports the associated dma-buf.
pub fn export(&self) -> Result<(Query, File)> {
let query = self.export_query(true)?;
if query.out_num_fds != 1 || query.out_fds[0] < 0 {
for fd in &query.out_fds {
if *fd >= 0 {
// Safe because the FD was just returned by a successful virglrenderer
// call so it must be valid and owned by us.
unsafe { close(*fd) };
}
}
return Err(Error::ExportedResourceDmabuf);
}
// Safe because the FD was just returned by a successful virglrenderer call so it must
// be valid and owned by us.
let dmabuf = unsafe { File::from_raw_fd(query.out_fds[0]) };
Ok((query, dmabuf))
}
/// 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 {
// 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<()> {
if transfer_box.is_empty() {
return Ok(());
}
// 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<()> {
if transfer_box.is_empty() {
return Ok(());
}
// 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<()> {
if transfer_box.is_empty() {
return Ok(());
}
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<()> {
if transfer_box.is_empty() {
return Ok(());
}
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 crate::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(RendererFlags::default()).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, 0, 5, 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);
}
}