blob: 3b91bc4fa5b6bec77ddc7fd33b1fadc1fd3902d4 [file] [log] [blame]
// Copyright 2020 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.
//! gfxstream: Handles 3D virtio-gpu hypercalls using gfxstream.
//!
//! External code found at <https://android.googlesource.com/device/generic/vulkan-cereal/>.
#![cfg(feature = "gfxstream")]
use std::cell::RefCell;
use std::convert::TryInto;
use std::mem::{size_of, transmute};
use std::os::raw::{c_char, c_int, c_uint, c_void};
use std::ptr::{null, null_mut};
use std::rc::Rc;
use std::sync::Arc;
use base::{
ExternalMapping, ExternalMappingError, ExternalMappingResult, FromRawDescriptor,
IntoRawDescriptor, SafeDescriptor,
};
use crate::generated::virgl_renderer_bindings::{
iovec, virgl_box, virgl_renderer_resource_create_args,
};
use crate::renderer_utils::*;
use crate::rutabaga_core::{RutabagaComponent, RutabagaContext, RutabagaResource};
use crate::rutabaga_utils::*;
use data_model::VolatileSlice;
#[repr(C)]
pub struct VirglRendererGlCtxParam {
pub version: c_int,
pub shared: bool,
pub major_ver: c_int,
pub minor_ver: c_int,
}
// In gfxstream, only write_fence is used (for synchronization of commands delivered)
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VirglRendererCallbacks {
pub version: c_int,
pub write_fence: unsafe extern "C" fn(cookie: *mut c_void, fence: u32),
pub create_gl_context: Option<
unsafe extern "C" fn(
cookie: *mut c_void,
scanout_idx: c_int,
param: *mut VirglRendererGlCtxParam,
) -> *mut c_void,
>,
pub destroy_gl_context: Option<unsafe extern "C" fn(cookie: *mut c_void, ctx: *mut c_void)>,
pub make_current: Option<
unsafe extern "C" fn(cookie: *mut c_void, scanout_idx: c_int, ctx: *mut c_void) -> c_int,
>,
pub get_drm_fd: Option<unsafe extern "C" fn(cookie: *mut c_void) -> c_int>,
pub write_context_fence:
Option<unsafe extern "C" fn(cookie: *mut c_void, fence: u64, ctx_idx: u32, ring_idx: u8)>,
}
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub struct stream_renderer_handle {
pub os_handle: i64,
pub handle_type: u32,
}
#[allow(non_camel_case_types)]
type stream_renderer_create_blob = ResourceCreateBlob;
#[link(name = "gfxstream_backend")]
extern "C" {
// Function to globally init gfxstream backend's internal state, taking display/renderer
// parameters.
fn gfxstream_backend_init(
display_width: u32,
display_height: u32,
display_type: u32,
renderer_cookie: *mut c_void,
renderer_flags: i32,
renderer_callbacks: *mut VirglRendererCallbacks,
gfxstream_callbacks: *mut c_void,
);
// virtio-gpu-3d ioctl functions (begin)
// In gfxstream, the resource create/transfer ioctls correspond to creating buffers for API
// forwarding and the notification of new API calls forwarded by the guest, unless they
// correspond to minigbm resource targets (PIPE_TEXTURE_2D), in which case they create globally
// visible shared GL textures to support gralloc.
fn pipe_virgl_renderer_poll();
fn pipe_virgl_renderer_resource_create(
args: *mut virgl_renderer_resource_create_args,
iov: *mut iovec,
num_iovs: u32,
) -> c_int;
fn pipe_virgl_renderer_resource_unref(res_handle: u32);
fn pipe_virgl_renderer_context_create(handle: u32, nlen: u32, name: *const c_char) -> c_int;
fn pipe_virgl_renderer_context_destroy(handle: u32);
fn pipe_virgl_renderer_transfer_read_iov(
handle: u32,
ctx_id: u32,
level: u32,
stride: u32,
layer_stride: u32,
box_: *mut virgl_box,
offset: u64,
iov: *mut iovec,
iovec_cnt: c_int,
) -> c_int;
fn pipe_virgl_renderer_transfer_write_iov(
handle: u32,
ctx_id: u32,
level: c_int,
stride: u32,
layer_stride: u32,
box_: *mut virgl_box,
offset: u64,
iovec: *mut iovec,
iovec_cnt: c_uint,
) -> c_int;
fn pipe_virgl_renderer_submit_cmd(
commands: *mut c_void,
ctx_id: i32,
dword_count: i32,
) -> c_int;
fn pipe_virgl_renderer_resource_attach_iov(
res_handle: c_int,
iov: *mut iovec,
num_iovs: c_int,
) -> c_int;
fn pipe_virgl_renderer_resource_detach_iov(
res_handle: c_int,
iov: *mut *mut iovec,
num_iovs: *mut c_int,
);
fn pipe_virgl_renderer_create_fence(client_fence_id: c_int, ctx_id: u32) -> c_int;
fn pipe_virgl_renderer_ctx_attach_resource(ctx_id: c_int, res_handle: c_int);
fn pipe_virgl_renderer_ctx_detach_resource(ctx_id: c_int, res_handle: c_int);
fn stream_renderer_create_blob(
ctx_id: u32,
res_handle: u32,
create_blob: *const stream_renderer_create_blob,
iovecs: *const iovec,
num_iovs: u32,
handle: *const stream_renderer_handle,
) -> c_int;
fn stream_renderer_export_blob(res_handle: u32, handle: *mut stream_renderer_handle) -> c_int;
fn stream_renderer_resource_map(
res_handle: u32,
map: *mut *mut c_void,
out_size: *mut u64,
) -> c_int;
fn stream_renderer_resource_unmap(res_handle: u32) -> c_int;
fn stream_renderer_resource_map_info(res_handle: u32, map_info: *mut u32) -> c_int;
}
/// The virtio-gpu backend state tracker which supports accelerated rendering.
pub struct Gfxstream {
fence_state: Rc<RefCell<FenceState>>,
fence_handler: RutabagaFenceHandler,
}
struct GfxstreamContext {
ctx_id: u32,
}
impl RutabagaContext for GfxstreamContext {
fn submit_cmd(&mut self, commands: &mut [u8]) -> RutabagaResult<()> {
if commands.len() % size_of::<u32>() != 0 {
return Err(RutabagaError::InvalidCommandSize(commands.len()));
}
let dword_count = (commands.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 {
pipe_virgl_renderer_submit_cmd(
commands.as_mut_ptr() as *mut c_void,
self.ctx_id as i32,
dword_count,
)
};
ret_to_res(ret)
}
fn attach(&mut self, resource: &mut RutabagaResource) {
// The context id and resource id must be valid because the respective instances ensure
// their lifetime.
unsafe {
pipe_virgl_renderer_ctx_attach_resource(
self.ctx_id as i32,
resource.resource_id as i32,
);
}
}
fn detach(&mut self, resource: &RutabagaResource) {
// The context id and resource id must be valid because the respective instances ensure
// their lifetime.
unsafe {
pipe_virgl_renderer_ctx_detach_resource(
self.ctx_id as i32,
resource.resource_id as i32,
);
}
}
fn component_type(&self) -> RutabagaComponentType {
RutabagaComponentType::Gfxstream
}
}
impl Drop for GfxstreamContext {
fn drop(&mut self) {
// The context is safe to destroy because nothing else can be referencing it.
unsafe {
pipe_virgl_renderer_context_destroy(self.ctx_id);
}
}
}
const GFXSTREAM_RENDERER_CALLBACKS: &VirglRendererCallbacks = &VirglRendererCallbacks {
version: 3,
write_fence,
create_gl_context: None,
destroy_gl_context: None,
make_current: None,
get_drm_fd: None,
write_context_fence: None,
};
fn map_func(resource_id: u32) -> ExternalMappingResult<(u64, usize)> {
let mut map: *mut c_void = null_mut();
let map_ptr: *mut *mut c_void = &mut map;
let mut size: u64 = 0;
// Safe because the Stream renderer wraps and validates use of vkMapMemory.
let ret = unsafe { stream_renderer_resource_map(resource_id, map_ptr, &mut size) };
if ret != 0 {
return Err(ExternalMappingError::LibraryError(ret));
}
Ok((map as u64, size as usize))
}
fn unmap_func(resource_id: u32) {
unsafe { stream_renderer_resource_unmap(resource_id) };
}
impl Gfxstream {
pub fn init(
display_width: u32,
display_height: u32,
gfxstream_flags: GfxstreamFlags,
fence_handler: RutabagaFenceHandler,
) -> RutabagaResult<Box<dyn RutabagaComponent>> {
let fence_state = Rc::new(RefCell::new(FenceState {
latest_fence: 0,
handler: None,
}));
let cookie: *mut VirglCookie = Box::into_raw(Box::new(VirglCookie {
fence_state: Rc::clone(&fence_state),
render_server_fd: None,
}));
unsafe {
gfxstream_backend_init(
display_width,
display_height,
1, /* default to shmem display */
cookie as *mut c_void,
gfxstream_flags.into(),
transmute(GFXSTREAM_RENDERER_CALLBACKS),
null_mut(),
);
}
Ok(Box::new(Gfxstream {
fence_state,
fence_handler,
}))
}
fn map_info(&self, resource_id: u32) -> RutabagaResult<u32> {
let mut map_info = 0;
let ret = unsafe { stream_renderer_resource_map_info(resource_id, &mut map_info) };
ret_to_res(ret)?;
Ok(map_info)
}
fn export_blob(&self, resource_id: u32) -> RutabagaResult<Arc<RutabagaHandle>> {
let mut stream_handle: stream_renderer_handle = Default::default();
let ret = unsafe { stream_renderer_export_blob(resource_id as u32, &mut stream_handle) };
ret_to_res(ret)?;
// Safe because the handle was just returned by a successful gfxstream call so it must be
// valid and owned by us.
let raw_descriptor = stream_handle.os_handle.try_into()?;
let handle = unsafe { SafeDescriptor::from_raw_descriptor(raw_descriptor) };
Ok(Arc::new(RutabagaHandle {
os_handle: handle,
handle_type: stream_handle.handle_type,
}))
}
}
impl RutabagaComponent for Gfxstream {
fn get_capset_info(&self, _capset_id: u32) -> (u32, u32) {
(1, 0)
}
fn get_capset(&self, _capset_id: u32, _version: u32) -> Vec<u8> {
Vec::new()
}
fn create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
let ret = unsafe { pipe_virgl_renderer_create_fence(fence.fence_id as i32, fence.ctx_id) };
// This can be moved to the cookie once gfxstream directly calls the
// write_fence callback in pipe_virgl_renderer_create_fence
self.fence_handler.call(fence);
ret_to_res(ret)
}
fn poll(&self) -> u32 {
unsafe { pipe_virgl_renderer_poll() };
self.fence_state.borrow().latest_fence
}
fn create_3d(
&self,
resource_id: u32,
resource_create_3d: ResourceCreate3D,
) -> RutabagaResult<RutabagaResource> {
let mut args = virgl_renderer_resource_create_args {
handle: resource_id,
target: resource_create_3d.target,
format: resource_create_3d.format,
bind: resource_create_3d.bind,
width: resource_create_3d.width,
height: resource_create_3d.height,
depth: resource_create_3d.depth,
array_size: resource_create_3d.array_size,
last_level: resource_create_3d.last_level,
nr_samples: resource_create_3d.nr_samples,
flags: resource_create_3d.flags,
};
// 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 { pipe_virgl_renderer_resource_create(&mut args, null_mut(), 0) };
ret_to_res(ret)?;
Ok(RutabagaResource {
resource_id,
handle: None,
blob: false,
blob_mem: 0,
blob_flags: 0,
map_info: None,
info_2d: None,
info_3d: None,
vulkan_info: None,
backing_iovecs: None,
})
}
fn attach_backing(
&self,
resource_id: u32,
vecs: &mut Vec<RutabagaIovec>,
) -> RutabagaResult<()> {
let ret = unsafe {
pipe_virgl_renderer_resource_attach_iov(
resource_id as i32,
vecs.as_mut_ptr() as *mut iovec,
vecs.len() as i32,
)
};
ret_to_res(ret)
}
fn detach_backing(&self, resource_id: u32) {
unsafe {
pipe_virgl_renderer_resource_detach_iov(
resource_id as i32,
std::ptr::null_mut(),
std::ptr::null_mut(),
);
}
}
fn unref_resource(&self, resource_id: u32) {
// The resource is safe to unreference destroy because no user of these bindings can still
// be holding a reference.
unsafe {
pipe_virgl_renderer_resource_unref(resource_id);
}
}
fn transfer_write(
&self,
ctx_id: u32,
resource: &mut RutabagaResource,
transfer: Transfer3D,
) -> RutabagaResult<()> {
if transfer.is_empty() {
return Ok(());
}
let mut transfer_box = VirglBox {
x: transfer.x,
y: transfer.y,
z: transfer.z,
w: transfer.w,
h: transfer.h,
d: transfer.d,
};
// Safe because only stack variables of the appropriate type are used.
let ret = unsafe {
pipe_virgl_renderer_transfer_write_iov(
resource.resource_id,
ctx_id,
transfer.level as i32,
transfer.stride,
transfer.layer_stride,
&mut transfer_box as *mut VirglBox as *mut virgl_box,
transfer.offset,
null_mut(),
0,
)
};
ret_to_res(ret)
}
fn transfer_read(
&self,
ctx_id: u32,
resource: &mut RutabagaResource,
transfer: Transfer3D,
buf: Option<VolatileSlice>,
) -> RutabagaResult<()> {
if transfer.is_empty() {
return Ok(());
}
let mut transfer_box = VirglBox {
x: transfer.x,
y: transfer.y,
z: transfer.z,
w: transfer.w,
h: transfer.h,
d: transfer.d,
};
let mut iov = RutabagaIovec {
base: null_mut(),
len: 0,
};
let (iovecs, num_iovecs) = match buf {
Some(buf) => {
iov.base = buf.as_ptr() as *mut c_void;
iov.len = buf.size() as usize;
(&mut iov as *mut RutabagaIovec as *mut iovec, 1)
}
None => (null_mut(), 0),
};
// Safe because only stack variables of the appropriate type are used.
let ret = unsafe {
pipe_virgl_renderer_transfer_read_iov(
resource.resource_id,
ctx_id,
transfer.level,
transfer.stride,
transfer.layer_stride,
&mut transfer_box as *mut VirglBox as *mut virgl_box,
transfer.offset,
iovecs,
num_iovecs,
)
};
ret_to_res(ret)
}
fn create_blob(
&mut self,
ctx_id: u32,
resource_id: u32,
resource_create_blob: ResourceCreateBlob,
mut iovec_opt: Option<Vec<RutabagaIovec>>,
handle_opt: Option<RutabagaHandle>,
) -> RutabagaResult<RutabagaResource> {
let mut iovec_ptr = null_mut();
let mut num_iovecs = 0;
if let Some(ref mut iovecs) = iovec_opt {
iovec_ptr = iovecs.as_mut_ptr();
num_iovecs = iovecs.len() as u32;
}
let mut handle_ptr = null();
let mut stream_handle: stream_renderer_handle = Default::default();
if let Some(handle) = handle_opt {
stream_handle.handle_type = handle.handle_type;
stream_handle.os_handle = handle.os_handle.into_raw_descriptor() as i64;
handle_ptr = &stream_handle;
}
let ret = unsafe {
stream_renderer_create_blob(
ctx_id,
resource_id,
&resource_create_blob as *const stream_renderer_create_blob,
iovec_ptr as *const iovec,
num_iovecs,
handle_ptr as *const stream_renderer_handle,
)
};
ret_to_res(ret)?;
Ok(RutabagaResource {
resource_id,
handle: self.export_blob(resource_id).ok(),
blob: true,
blob_mem: resource_create_blob.blob_mem,
blob_flags: resource_create_blob.blob_flags,
map_info: self.map_info(resource_id).ok(),
info_2d: None,
info_3d: None,
vulkan_info: None,
backing_iovecs: iovec_opt,
})
}
fn map(&self, resource_id: u32) -> RutabagaResult<ExternalMapping> {
let map_result = unsafe { ExternalMapping::new(resource_id, map_func, unmap_func) };
match map_result {
Ok(mapping) => Ok(mapping),
Err(e) => Err(RutabagaError::MappingFailed(e)),
}
}
fn create_context(
&self,
ctx_id: u32,
_context_init: u32,
_fence_handler: RutabagaFenceHandler,
) -> RutabagaResult<Box<dyn RutabagaContext>> {
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 {
pipe_virgl_renderer_context_create(
ctx_id,
CONTEXT_NAME.len() as u32,
CONTEXT_NAME.as_ptr() as *const c_char,
)
};
ret_to_res(ret)?;
Ok(Box::new(GfxstreamContext { ctx_id }))
}
}