blob: d950c4f5b375803ceef712d253f6c847062b720b [file] [log] [blame]
// Copyright 2021 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.
//! gralloc: Cross-platform, Rust-based, Vulkan centric GPU allocation and
//! mapping.
use std::collections::BTreeMap as Map;
use base::{round_up_to_page_size, MappedRegion};
use crate::rutabaga_gralloc::formats::*;
use crate::rutabaga_gralloc::system_gralloc::SystemGralloc;
use crate::rutabaga_utils::*;
#[cfg(feature = "minigbm")]
use crate::rutabaga_gralloc::minigbm::MinigbmDevice;
#[cfg(feature = "vulkano")]
use crate::rutabaga_gralloc::vulkano_gralloc::VulkanoGralloc;
/*
* Rutabaga gralloc flags are copied from minigbm, but redundant legacy flags are left out.
* For example, USE_WRITE / USE_CURSOR_64X64 / USE_CURSOR don't add much value.
*/
const RUTABAGA_GRALLOC_USE_SCANOUT: u32 = 1 << 0;
const RUTABAGA_GRALLOC_USE_RENDERING: u32 = 1 << 2;
const RUTABAGA_GRALLOC_USE_LINEAR: u32 = 1 << 4;
const RUTABAGA_GRALLOC_USE_TEXTURING: u32 = 1 << 5;
const RUTABAGA_GRALLOC_USE_CAMERA_WRITE: u32 = 1 << 6;
const RUTABAGA_GRALLOC_USE_CAMERA_READ: u32 = 1 << 7;
#[allow(dead_code)]
const RUTABAGA_GRALLOC_USE_PROTECTED: u32 = 1 << 8;
/* SW_{WRITE,READ}_RARELY omitted since not even Android uses this much. */
const RUTABAGA_GRALLOC_USE_SW_READ_OFTEN: u32 = 1 << 9;
const RUTABAGA_GRALLOC_USE_SW_WRITE_OFTEN: u32 = 1 << 11;
#[allow(dead_code)]
const RUTABAGA_GRALLOC_VIDEO_DECODER: u32 = 1 << 13;
#[allow(dead_code)]
const RUTABAGA_GRALLOC_VIDEO_ENCODER: u32 = 1 << 14;
/// Usage flags for constructing a buffer object.
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub struct RutabagaGrallocFlags(pub u32);
impl RutabagaGrallocFlags {
/// Returns empty set of flags.
#[inline(always)]
pub fn empty() -> RutabagaGrallocFlags {
RutabagaGrallocFlags(0)
}
/// Returns the given set of raw `RUTABAGA_GRALLOC` flags wrapped in a RutabagaGrallocFlags
/// struct.
#[inline(always)]
pub fn new(raw: u32) -> RutabagaGrallocFlags {
RutabagaGrallocFlags(raw)
}
/// Sets the scanout flag's presence.
#[inline(always)]
pub fn use_scanout(self, e: bool) -> RutabagaGrallocFlags {
if e {
RutabagaGrallocFlags(self.0 | RUTABAGA_GRALLOC_USE_SCANOUT)
} else {
RutabagaGrallocFlags(self.0 & !RUTABAGA_GRALLOC_USE_SCANOUT)
}
}
/// Sets the rendering flag's presence.
#[inline(always)]
pub fn use_rendering(self, e: bool) -> RutabagaGrallocFlags {
if e {
RutabagaGrallocFlags(self.0 | RUTABAGA_GRALLOC_USE_RENDERING)
} else {
RutabagaGrallocFlags(self.0 & !RUTABAGA_GRALLOC_USE_RENDERING)
}
}
/// Sets the linear flag's presence.
#[inline(always)]
pub fn use_linear(self, e: bool) -> RutabagaGrallocFlags {
if e {
RutabagaGrallocFlags(self.0 | RUTABAGA_GRALLOC_USE_LINEAR)
} else {
RutabagaGrallocFlags(self.0 & !RUTABAGA_GRALLOC_USE_LINEAR)
}
}
/// Sets the SW write flag's presence.
#[inline(always)]
pub fn use_sw_write(self, e: bool) -> RutabagaGrallocFlags {
if e {
RutabagaGrallocFlags(self.0 | RUTABAGA_GRALLOC_USE_SW_WRITE_OFTEN)
} else {
RutabagaGrallocFlags(self.0 & !RUTABAGA_GRALLOC_USE_SW_WRITE_OFTEN)
}
}
/// Sets the SW read flag's presence.
#[inline(always)]
pub fn use_sw_read(self, e: bool) -> RutabagaGrallocFlags {
if e {
RutabagaGrallocFlags(self.0 | RUTABAGA_GRALLOC_USE_SW_READ_OFTEN)
} else {
RutabagaGrallocFlags(self.0 & !RUTABAGA_GRALLOC_USE_SW_READ_OFTEN)
}
}
/// Returns true if the texturing flag is set.
#[inline(always)]
pub fn uses_texturing(self) -> bool {
self.0 & RUTABAGA_GRALLOC_USE_TEXTURING != 0
}
/// Returns true if the rendering flag is set.
#[inline(always)]
pub fn uses_rendering(self) -> bool {
self.0 & RUTABAGA_GRALLOC_USE_RENDERING != 0
}
/// Returns true if the memory will accessed by the CPU or an IP block that prefers host
/// visible allocations (i.e, camera).
#[inline(always)]
pub fn host_visible(self) -> bool {
self.0 & RUTABAGA_GRALLOC_USE_SW_READ_OFTEN != 0
|| self.0 & RUTABAGA_GRALLOC_USE_SW_WRITE_OFTEN != 0
|| self.0 & RUTABAGA_GRALLOC_USE_CAMERA_WRITE != 0
|| self.0 & RUTABAGA_GRALLOC_USE_CAMERA_READ != 0
}
/// Returns true if the memory will read by the CPU or an IP block that prefers cached
/// allocations (i.e, camera).
#[inline(always)]
pub fn host_cached(self) -> bool {
self.0 & RUTABAGA_GRALLOC_USE_CAMERA_READ != 0
|| self.0 & RUTABAGA_GRALLOC_USE_SW_READ_OFTEN != 0
}
}
/// Information required to allocate a swapchain image.
#[derive(Copy, Clone, Default)]
pub struct ImageAllocationInfo {
pub width: u32,
pub height: u32,
pub drm_format: DrmFormat,
pub flags: RutabagaGrallocFlags,
}
/// The memory requirements, compression and layout of a swapchain image.
#[derive(Copy, Clone, Default)]
pub struct ImageMemoryRequirements {
pub info: ImageAllocationInfo,
pub map_info: u32,
pub strides: [u32; 4],
pub offsets: [u32; 4],
pub modifier: u64,
pub size: u64,
pub vulkan_info: Option<VulkanInfo>,
}
/// Trait that needs to be implemented to service graphics memory requests. Two step allocation
/// process:
///
/// (1) Get memory requirements for a given allocation request.
/// (2) Allocate using those requirements.
pub trait Gralloc {
/// This function must return true if the implementation can:
///
/// (1) allocate GPU memory and
/// (2) {export to}/{import from} into a OS-specific RutabagaHandle.
fn supports_external_gpu_memory(&self) -> bool;
/// This function must return true the implementation can {export to}/{import from} a Linux
/// dma-buf. This often used for sharing with the scanout engine or multimedia subsystems.
fn supports_dmabuf(&self) -> bool;
/// Implementations must return the resource layout, compression, and caching properties of
/// an allocation request.
fn get_image_memory_requirements(
&mut self,
info: ImageAllocationInfo,
) -> RutabagaResult<ImageMemoryRequirements>;
/// Implementations must allocate memory given the requirements and return a RutabagaHandle
/// upon success.
fn allocate_memory(&mut self, reqs: ImageMemoryRequirements) -> RutabagaResult<RutabagaHandle>;
/// Implementations must import the given `handle` and return a mapping, suitable for use with
/// KVM and other hypervisors. This is optional and only works with the Vulkano backend.
fn import_and_map(
&mut self,
_handle: RutabagaHandle,
_vulkan_info: VulkanInfo,
_size: u64,
) -> RutabagaResult<Box<dyn MappedRegion>> {
Err(RutabagaError::Unsupported)
}
}
/// Enumeration of possible allocation backends.
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub enum GrallocBackend {
#[allow(dead_code)]
Vulkano,
#[allow(dead_code)]
Minigbm,
System,
}
/// A container for a variety of allocation backends.
pub struct RutabagaGralloc {
grallocs: Map<GrallocBackend, Box<dyn Gralloc>>,
}
impl RutabagaGralloc {
/// Returns a new RutabagaGralloc instance upon success. All allocation backends that have
/// been built are initialized. The default system allocator is always initialized.
pub fn new() -> RutabagaResult<RutabagaGralloc> {
let mut grallocs: Map<GrallocBackend, Box<dyn Gralloc>> = Default::default();
let system = SystemGralloc::init()?;
grallocs.insert(GrallocBackend::System, system);
#[cfg(feature = "minigbm")]
{
// crosvm integration tests build with the "wl-dmabuf" feature, which translates in
// rutabaga to the "minigbm" feature. These tests run on hosts where a rendernode is
// not present, and minigbm can not be initialized.
//
// Thus, to keep kokoro happy, allow minigbm initialization to fail silently for now.
if let Ok(gbm_device) = MinigbmDevice::init() {
grallocs.insert(GrallocBackend::Minigbm, gbm_device);
}
}
#[cfg(feature = "vulkano")]
{
let vulkano = VulkanoGralloc::init()?;
grallocs.insert(GrallocBackend::Vulkano, vulkano);
}
Ok(RutabagaGralloc { grallocs })
}
/// Returns true if one of the allocation backends supports GPU external memory.
pub fn supports_external_gpu_memory(&self) -> bool {
for gralloc in self.grallocs.values() {
if gralloc.supports_external_gpu_memory() {
return true;
}
}
false
}
/// Returns true if one of the allocation backends supports dma_buf.
pub fn supports_dmabuf(&self) -> bool {
for gralloc in self.grallocs.values() {
if gralloc.supports_dmabuf() {
return true;
}
}
false
}
/// Returns the best allocation backend to service a particular request.
fn determine_optimal_backend(&self, _info: ImageAllocationInfo) -> GrallocBackend {
// This function could be more sophisticated and consider the allocation info. For example,
// nobody has ever tried Mali allocated memory + a mediatek/rockchip display and as such it
// probably doesn't work. In addition, YUV calculations in minigbm have yet to make it
// towards the Vulkan api. This function allows for a variety of quirks, but for now just
// choose the most shiny backend that the user has built. The rationale is "why would you
// build it if you don't want to use it".
let mut _backend = GrallocBackend::System;
#[cfg(feature = "minigbm")]
{
// See note on "wl-dmabuf" and Kokoro in Gralloc::new().
if self.grallocs.contains_key(&GrallocBackend::Minigbm) {
_backend = GrallocBackend::Minigbm;
}
}
#[cfg(feature = "vulkano")]
{
_backend = GrallocBackend::Vulkano;
}
_backend
}
/// Returns a image memory requirements for the given `info` upon success.
pub fn get_image_memory_requirements(
&mut self,
info: ImageAllocationInfo,
) -> RutabagaResult<ImageMemoryRequirements> {
let backend = self.determine_optimal_backend(info);
let gralloc = self
.grallocs
.get_mut(&backend)
.ok_or(RutabagaError::InvalidGrallocBackend)?;
let mut reqs = gralloc.get_image_memory_requirements(info)?;
reqs.size = round_up_to_page_size(reqs.size as usize) as u64;
Ok(reqs)
}
/// Allocates memory given the particular `reqs` upon success.
pub fn allocate_memory(
&mut self,
reqs: ImageMemoryRequirements,
) -> RutabagaResult<RutabagaHandle> {
let backend = self.determine_optimal_backend(reqs.info);
let gralloc = self
.grallocs
.get_mut(&backend)
.ok_or(RutabagaError::InvalidGrallocBackend)?;
gralloc.allocate_memory(reqs)
}
/// Imports the `handle` using the given `vulkan_info`. Returns a mapping using Vulkano upon
/// success. Should not be used with minigbm or system gralloc backends.
pub fn import_and_map(
&mut self,
handle: RutabagaHandle,
vulkan_info: VulkanInfo,
size: u64,
) -> RutabagaResult<Box<dyn MappedRegion>> {
let gralloc = self
.grallocs
.get_mut(&GrallocBackend::Vulkano)
.ok_or(RutabagaError::InvalidGrallocBackend)?;
gralloc.import_and_map(handle, vulkan_info, size)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_render_target() {
let gralloc_result = RutabagaGralloc::new();
if gralloc_result.is_err() {
return;
}
let mut gralloc = gralloc_result.unwrap();
let info = ImageAllocationInfo {
width: 512,
height: 1024,
drm_format: DrmFormat::new(b'X', b'R', b'2', b'4'),
flags: RutabagaGrallocFlags::empty().use_scanout(true),
};
let reqs = gralloc.get_image_memory_requirements(info).unwrap();
let min_reqs = canonical_image_requirements(info).unwrap();
assert_eq!(reqs.strides[0] >= min_reqs.strides[0], true);
assert_eq!(reqs.size >= min_reqs.size, true);
let _handle = gralloc.allocate_memory(reqs).unwrap();
// Reallocate with same requirements
let _handle2 = gralloc.allocate_memory(reqs).unwrap();
}
#[test]
fn create_video_buffer() {
let gralloc_result = RutabagaGralloc::new();
if gralloc_result.is_err() {
return;
}
let mut gralloc = gralloc_result.unwrap();
let info = ImageAllocationInfo {
width: 512,
height: 1024,
drm_format: DrmFormat::new(b'N', b'V', b'1', b'2'),
flags: RutabagaGrallocFlags::empty().use_linear(true),
};
let reqs = gralloc.get_image_memory_requirements(info).unwrap();
let min_reqs = canonical_image_requirements(info).unwrap();
assert_eq!(reqs.strides[0] >= min_reqs.strides[0], true);
assert_eq!(reqs.strides[1] >= min_reqs.strides[1], true);
assert_eq!(reqs.strides[2], 0);
assert_eq!(reqs.strides[3], 0);
assert_eq!(reqs.offsets[0] >= min_reqs.offsets[0], true);
assert_eq!(reqs.offsets[1] >= min_reqs.offsets[1], true);
assert_eq!(reqs.offsets[2], 0);
assert_eq!(reqs.offsets[3], 0);
assert_eq!(reqs.size >= min_reqs.size, true);
let _handle = gralloc.allocate_memory(reqs).unwrap();
// Reallocate with same requirements
let _handle2 = gralloc.allocate_memory(reqs).unwrap();
}
#[test]
fn export_and_map() {
let gralloc_result = RutabagaGralloc::new();
if gralloc_result.is_err() {
return;
}
let mut gralloc = gralloc_result.unwrap();
let info = ImageAllocationInfo {
width: 512,
height: 1024,
drm_format: DrmFormat::new(b'X', b'R', b'2', b'4'),
flags: RutabagaGrallocFlags::empty()
.use_linear(true)
.use_sw_write(true)
.use_sw_read(true),
};
let mut reqs = gralloc.get_image_memory_requirements(info).unwrap();
// Anything else can use the mmap(..) system call.
if reqs.vulkan_info.is_none() {
return;
}
let handle = gralloc.allocate_memory(reqs).unwrap();
let vulkan_info = reqs.vulkan_info.take().unwrap();
let mapping = gralloc
.import_and_map(handle, vulkan_info, reqs.size)
.unwrap();
let addr = mapping.as_ptr();
let size = mapping.size();
assert_eq!(size as u64, reqs.size);
assert_ne!(addr as *const u8, std::ptr::null());
}
}