// 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, } /// 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; /// Implementations must allocate memory given the requirements and return a RutabagaHandle /// upon success. fn allocate_memory(&mut self, reqs: ImageMemoryRequirements) -> RutabagaResult; /// 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> { 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>, } 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 { let mut grallocs: Map> = 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 { let backend = self.determine_optimal_backend(info); let gralloc = self .grallocs .get_mut(&backend) .ok_or(RutabagaError::Unsupported)?; 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 { let backend = self.determine_optimal_backend(reqs.info); let gralloc = self .grallocs .get_mut(&backend) .ok_or(RutabagaError::Unsupported)?; 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> { let gralloc = self .grallocs .get_mut(&GrallocBackend::Vulkano) .ok_or(RutabagaError::Unsupported)?; 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()); } }