blob: 81d3b8a20209310d593bb364ea89675cc2e6f477 [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::mem::{size_of, transmute};
use std::os::raw::{c_char, c_int, c_uint, c_void};
use std::ptr::null_mut;
use std::rc::Rc;
use base::{ExternalMapping, ExternalMappingError, ExternalMappingResult};
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;
// In gfxstream, only write_fence is used (for synchronization of commands delivered)
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct GfxstreamRendererCallbacks {
pub version: c_int,
pub write_fence: unsafe extern "C" fn(cookie: *mut c_void, fence: u32),
}
#[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 GfxstreamRendererCallbacks,
);
// 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_resource_create_v2(res_handle: u32, hostmemId: u64);
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;
}
/// The virtio-gpu backend state tracker which supports accelerated rendering.
pub struct Gfxstream {
fence_state: Rc<RefCell<FenceState>>,
}
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,
);
}
}
}
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: &GfxstreamRendererCallbacks = &GfxstreamRendererCallbacks {
version: 1,
write_fence,
};
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,
) -> RutabagaResult<Box<dyn RutabagaComponent>> {
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),
}));
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),
);
}
Ok(Box::new(Gfxstream { fence_state }))
}
#[allow(clippy::unnecessary_wraps)]
fn map_info(&self, _resource_id: u32) -> RutabagaResult<u32> {
Ok(RUTABAGA_MAP_CACHE_WC)
}
}
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_data: RutabagaFenceData) -> RutabagaResult<()> {
let ret = unsafe {
pipe_virgl_renderer_create_fence(fence_data.fence_id as i32, fence_data.ctx_id)
};
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,
_iovec_opt: Option<Vec<RutabagaIovec>>,
) -> RutabagaResult<RutabagaResource> {
unsafe {
stream_renderer_resource_create_v2(resource_id, resource_create_blob.blob_id);
}
Ok(RutabagaResource {
resource_id,
handle: None,
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: None,
})
}
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,
) -> 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 }))
}
}