1 // Copyright 2021 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 //! gralloc: Cross-platform, Rust-based, Vulkan centric GPU allocation and 6 //! mapping. 7 8 use std::collections::BTreeMap as Map; 9 10 #[cfg(feature = "vulkano")] 11 use log::error; 12 13 use crate::rutabaga_gralloc::formats::*; 14 #[cfg(feature = "minigbm")] 15 use crate::rutabaga_gralloc::minigbm::MinigbmDevice; 16 use crate::rutabaga_gralloc::system_gralloc::SystemGralloc; 17 #[cfg(feature = "vulkano")] 18 use crate::rutabaga_gralloc::vulkano_gralloc::VulkanoGralloc; 19 use crate::rutabaga_os::round_up_to_page_size; 20 use crate::rutabaga_os::MappedRegion; 21 use crate::rutabaga_utils::*; 22 23 /* 24 * Rutabaga gralloc flags are copied from minigbm, but redundant legacy flags are left out. 25 * For example, USE_WRITE / USE_CURSOR_64X64 / USE_CURSOR don't add much value. 26 */ 27 const RUTABAGA_GRALLOC_USE_SCANOUT: u32 = 1 << 0; 28 const RUTABAGA_GRALLOC_USE_RENDERING: u32 = 1 << 2; 29 const RUTABAGA_GRALLOC_USE_LINEAR: u32 = 1 << 4; 30 const RUTABAGA_GRALLOC_USE_TEXTURING: u32 = 1 << 5; 31 const RUTABAGA_GRALLOC_USE_CAMERA_WRITE: u32 = 1 << 6; 32 const RUTABAGA_GRALLOC_USE_CAMERA_READ: u32 = 1 << 7; 33 #[allow(dead_code)] 34 const RUTABAGA_GRALLOC_USE_PROTECTED: u32 = 1 << 8; 35 36 /* SW_{WRITE,READ}_RARELY omitted since not even Android uses this much. */ 37 const RUTABAGA_GRALLOC_USE_SW_READ_OFTEN: u32 = 1 << 9; 38 const RUTABAGA_GRALLOC_USE_SW_WRITE_OFTEN: u32 = 1 << 11; 39 40 #[allow(dead_code)] 41 const RUTABAGA_GRALLOC_VIDEO_DECODER: u32 = 1 << 13; 42 #[allow(dead_code)] 43 const RUTABAGA_GRALLOC_VIDEO_ENCODER: u32 = 1 << 14; 44 45 /// Usage flags for constructing a buffer object. 46 #[derive(Copy, Clone, Eq, PartialEq, Default)] 47 pub struct RutabagaGrallocFlags(pub u32); 48 49 impl RutabagaGrallocFlags { 50 /// Returns empty set of flags. 51 #[inline(always)] empty() -> RutabagaGrallocFlags52 pub fn empty() -> RutabagaGrallocFlags { 53 RutabagaGrallocFlags(0) 54 } 55 56 /// Returns the given set of raw `RUTABAGA_GRALLOC` flags wrapped in a RutabagaGrallocFlags 57 /// struct. 58 #[inline(always)] new(raw: u32) -> RutabagaGrallocFlags59 pub fn new(raw: u32) -> RutabagaGrallocFlags { 60 RutabagaGrallocFlags(raw) 61 } 62 63 /// Sets the scanout flag's presence. 64 #[inline(always)] use_scanout(self, e: bool) -> RutabagaGrallocFlags65 pub fn use_scanout(self, e: bool) -> RutabagaGrallocFlags { 66 if e { 67 RutabagaGrallocFlags(self.0 | RUTABAGA_GRALLOC_USE_SCANOUT) 68 } else { 69 RutabagaGrallocFlags(self.0 & !RUTABAGA_GRALLOC_USE_SCANOUT) 70 } 71 } 72 73 /// Sets the rendering flag's presence. 74 #[inline(always)] use_rendering(self, e: bool) -> RutabagaGrallocFlags75 pub fn use_rendering(self, e: bool) -> RutabagaGrallocFlags { 76 if e { 77 RutabagaGrallocFlags(self.0 | RUTABAGA_GRALLOC_USE_RENDERING) 78 } else { 79 RutabagaGrallocFlags(self.0 & !RUTABAGA_GRALLOC_USE_RENDERING) 80 } 81 } 82 83 /// Sets the linear flag's presence. 84 #[inline(always)] use_linear(self, e: bool) -> RutabagaGrallocFlags85 pub fn use_linear(self, e: bool) -> RutabagaGrallocFlags { 86 if e { 87 RutabagaGrallocFlags(self.0 | RUTABAGA_GRALLOC_USE_LINEAR) 88 } else { 89 RutabagaGrallocFlags(self.0 & !RUTABAGA_GRALLOC_USE_LINEAR) 90 } 91 } 92 93 /// Sets the SW write flag's presence. 94 #[inline(always)] use_sw_write(self, e: bool) -> RutabagaGrallocFlags95 pub fn use_sw_write(self, e: bool) -> RutabagaGrallocFlags { 96 if e { 97 RutabagaGrallocFlags(self.0 | RUTABAGA_GRALLOC_USE_SW_WRITE_OFTEN) 98 } else { 99 RutabagaGrallocFlags(self.0 & !RUTABAGA_GRALLOC_USE_SW_WRITE_OFTEN) 100 } 101 } 102 103 /// Sets the SW read flag's presence. 104 #[inline(always)] use_sw_read(self, e: bool) -> RutabagaGrallocFlags105 pub fn use_sw_read(self, e: bool) -> RutabagaGrallocFlags { 106 if e { 107 RutabagaGrallocFlags(self.0 | RUTABAGA_GRALLOC_USE_SW_READ_OFTEN) 108 } else { 109 RutabagaGrallocFlags(self.0 & !RUTABAGA_GRALLOC_USE_SW_READ_OFTEN) 110 } 111 } 112 113 /// Returns true if the texturing flag is set. 114 #[inline(always)] uses_texturing(self) -> bool115 pub fn uses_texturing(self) -> bool { 116 self.0 & RUTABAGA_GRALLOC_USE_TEXTURING != 0 117 } 118 119 /// Returns true if the rendering flag is set. 120 #[inline(always)] uses_rendering(self) -> bool121 pub fn uses_rendering(self) -> bool { 122 self.0 & RUTABAGA_GRALLOC_USE_RENDERING != 0 123 } 124 125 /// Returns true if the memory will accessed by the CPU or an IP block that prefers host 126 /// visible allocations (i.e, camera). 127 #[inline(always)] host_visible(self) -> bool128 pub fn host_visible(self) -> bool { 129 self.0 & RUTABAGA_GRALLOC_USE_SW_READ_OFTEN != 0 130 || self.0 & RUTABAGA_GRALLOC_USE_SW_WRITE_OFTEN != 0 131 || self.0 & RUTABAGA_GRALLOC_USE_CAMERA_WRITE != 0 132 || self.0 & RUTABAGA_GRALLOC_USE_CAMERA_READ != 0 133 } 134 135 /// Returns true if the memory will read by the CPU or an IP block that prefers cached 136 /// allocations (i.e, camera). 137 #[inline(always)] host_cached(self) -> bool138 pub fn host_cached(self) -> bool { 139 self.0 & RUTABAGA_GRALLOC_USE_CAMERA_READ != 0 140 || self.0 & RUTABAGA_GRALLOC_USE_SW_READ_OFTEN != 0 141 } 142 } 143 144 /// Information required to allocate a swapchain image. 145 #[derive(Copy, Clone, Default)] 146 pub struct ImageAllocationInfo { 147 pub width: u32, 148 pub height: u32, 149 pub drm_format: DrmFormat, 150 pub flags: RutabagaGrallocFlags, 151 } 152 153 /// The memory requirements, compression and layout of a swapchain image. 154 #[derive(Copy, Clone, Default)] 155 pub struct ImageMemoryRequirements { 156 pub info: ImageAllocationInfo, 157 pub map_info: u32, 158 pub strides: [u32; 4], 159 pub offsets: [u32; 4], 160 pub modifier: u64, 161 pub size: u64, 162 pub vulkan_info: Option<VulkanInfo>, 163 } 164 165 /// Trait that needs to be implemented to service graphics memory requests. Two step allocation 166 /// process: 167 /// 168 /// (1) Get memory requirements for a given allocation request. 169 /// (2) Allocate using those requirements. 170 pub trait Gralloc: Send { 171 /// This function must return true if the implementation can: 172 /// 173 /// (1) allocate GPU memory and 174 /// (2) {export to}/{import from} into a OS-specific RutabagaHandle. supports_external_gpu_memory(&self) -> bool175 fn supports_external_gpu_memory(&self) -> bool; 176 177 /// This function must return true the implementation can {export to}/{import from} a Linux 178 /// dma-buf. This often used for sharing with the scanout engine or multimedia subsystems. supports_dmabuf(&self) -> bool179 fn supports_dmabuf(&self) -> bool; 180 181 /// Implementations must return the resource layout, compression, and caching properties of 182 /// an allocation request. get_image_memory_requirements( &mut self, info: ImageAllocationInfo, ) -> RutabagaResult<ImageMemoryRequirements>183 fn get_image_memory_requirements( 184 &mut self, 185 info: ImageAllocationInfo, 186 ) -> RutabagaResult<ImageMemoryRequirements>; 187 188 /// Implementations must allocate memory given the requirements and return a RutabagaHandle 189 /// upon success. allocate_memory(&mut self, reqs: ImageMemoryRequirements) -> RutabagaResult<RutabagaHandle>190 fn allocate_memory(&mut self, reqs: ImageMemoryRequirements) -> RutabagaResult<RutabagaHandle>; 191 192 /// Implementations must import the given `handle` and return a mapping, suitable for use with 193 /// KVM and other hypervisors. This is optional and only works with the Vulkano backend. import_and_map( &mut self, _handle: RutabagaHandle, _vulkan_info: VulkanInfo, _size: u64, ) -> RutabagaResult<Box<dyn MappedRegion>>194 fn import_and_map( 195 &mut self, 196 _handle: RutabagaHandle, 197 _vulkan_info: VulkanInfo, 198 _size: u64, 199 ) -> RutabagaResult<Box<dyn MappedRegion>> { 200 Err(RutabagaError::Unsupported) 201 } 202 } 203 204 /// Enumeration of possible allocation backends. 205 #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] 206 pub enum GrallocBackend { 207 #[allow(dead_code)] 208 Vulkano, 209 #[allow(dead_code)] 210 Minigbm, 211 System, 212 } 213 214 /// A container for a variety of allocation backends. 215 pub struct RutabagaGralloc { 216 grallocs: Map<GrallocBackend, Box<dyn Gralloc>>, 217 } 218 219 impl RutabagaGralloc { 220 /// Returns a new RutabagaGralloc instance upon success. All allocation backends that have 221 /// been built are initialized. The default system allocator is always initialized. new() -> RutabagaResult<RutabagaGralloc>222 pub fn new() -> RutabagaResult<RutabagaGralloc> { 223 let mut grallocs: Map<GrallocBackend, Box<dyn Gralloc>> = Default::default(); 224 225 let system = SystemGralloc::init()?; 226 grallocs.insert(GrallocBackend::System, system); 227 228 #[cfg(feature = "minigbm")] 229 { 230 // crosvm integration tests build with the "wl-dmabuf" feature, which translates in 231 // rutabaga to the "minigbm" feature. These tests run on hosts where a rendernode is 232 // not present, and minigbm can not be initialized. 233 // 234 // Thus, to keep kokoro happy, allow minigbm initialization to fail silently for now. 235 if let Ok(gbm_device) = MinigbmDevice::init() { 236 grallocs.insert(GrallocBackend::Minigbm, gbm_device); 237 } 238 } 239 240 #[cfg(feature = "vulkano")] 241 { 242 match VulkanoGralloc::init() { 243 Ok(vulkano) => { 244 grallocs.insert(GrallocBackend::Vulkano, vulkano); 245 } 246 Err(e) => { 247 error!("failed to init Vulkano gralloc: {:?}", e); 248 } 249 } 250 } 251 252 Ok(RutabagaGralloc { grallocs }) 253 } 254 255 /// Returns true if one of the allocation backends supports GPU external memory. supports_external_gpu_memory(&self) -> bool256 pub fn supports_external_gpu_memory(&self) -> bool { 257 for gralloc in self.grallocs.values() { 258 if gralloc.supports_external_gpu_memory() { 259 return true; 260 } 261 } 262 263 false 264 } 265 266 /// Returns true if one of the allocation backends supports dma_buf. supports_dmabuf(&self) -> bool267 pub fn supports_dmabuf(&self) -> bool { 268 for gralloc in self.grallocs.values() { 269 if gralloc.supports_dmabuf() { 270 return true; 271 } 272 } 273 274 false 275 } 276 277 /// Returns the best allocation backend to service a particular request. determine_optimal_backend(&self, _info: ImageAllocationInfo) -> GrallocBackend278 fn determine_optimal_backend(&self, _info: ImageAllocationInfo) -> GrallocBackend { 279 // This function could be more sophisticated and consider the allocation info. For example, 280 // nobody has ever tried Mali allocated memory + a mediatek/rockchip display and as such it 281 // probably doesn't work. In addition, YUV calculations in minigbm have yet to make it 282 // towards the Vulkan api. This function allows for a variety of quirks, but for now just 283 // choose the most shiny backend that the user has built. The rationale is "why would you 284 // build it if you don't want to use it". 285 #[allow(clippy::let_and_return)] 286 let mut _backend = GrallocBackend::System; 287 288 #[cfg(feature = "minigbm")] 289 { 290 // See note on "wl-dmabuf" and Kokoro in Gralloc::new(). 291 if self.grallocs.contains_key(&GrallocBackend::Minigbm) { 292 _backend = GrallocBackend::Minigbm; 293 } 294 } 295 296 #[cfg(feature = "vulkano")] 297 { 298 _backend = GrallocBackend::Vulkano; 299 } 300 301 _backend 302 } 303 304 /// Returns a image memory requirements for the given `info` upon success. get_image_memory_requirements( &mut self, info: ImageAllocationInfo, ) -> RutabagaResult<ImageMemoryRequirements>305 pub fn get_image_memory_requirements( 306 &mut self, 307 info: ImageAllocationInfo, 308 ) -> RutabagaResult<ImageMemoryRequirements> { 309 let backend = self.determine_optimal_backend(info); 310 311 let gralloc = self 312 .grallocs 313 .get_mut(&backend) 314 .ok_or(RutabagaError::InvalidGrallocBackend)?; 315 316 let mut reqs = gralloc.get_image_memory_requirements(info)?; 317 reqs.size = round_up_to_page_size(reqs.size)?; 318 Ok(reqs) 319 } 320 321 /// Allocates memory given the particular `reqs` upon success. allocate_memory( &mut self, reqs: ImageMemoryRequirements, ) -> RutabagaResult<RutabagaHandle>322 pub fn allocate_memory( 323 &mut self, 324 reqs: ImageMemoryRequirements, 325 ) -> RutabagaResult<RutabagaHandle> { 326 let backend = self.determine_optimal_backend(reqs.info); 327 328 let gralloc = self 329 .grallocs 330 .get_mut(&backend) 331 .ok_or(RutabagaError::InvalidGrallocBackend)?; 332 333 gralloc.allocate_memory(reqs) 334 } 335 336 /// Imports the `handle` using the given `vulkan_info`. Returns a mapping using Vulkano upon 337 /// success. Should not be used with minigbm or system gralloc backends. import_and_map( &mut self, handle: RutabagaHandle, vulkan_info: VulkanInfo, size: u64, ) -> RutabagaResult<Box<dyn MappedRegion>>338 pub fn import_and_map( 339 &mut self, 340 handle: RutabagaHandle, 341 vulkan_info: VulkanInfo, 342 size: u64, 343 ) -> RutabagaResult<Box<dyn MappedRegion>> { 344 let gralloc = self 345 .grallocs 346 .get_mut(&GrallocBackend::Vulkano) 347 .ok_or(RutabagaError::InvalidGrallocBackend)?; 348 349 gralloc.import_and_map(handle, vulkan_info, size) 350 } 351 } 352 353 #[cfg(test)] 354 mod tests { 355 use super::*; 356 357 #[test] 358 #[cfg_attr(target_os = "windows", ignore)] create_render_target()359 fn create_render_target() { 360 let gralloc_result = RutabagaGralloc::new(); 361 if gralloc_result.is_err() { 362 return; 363 } 364 365 let mut gralloc = gralloc_result.unwrap(); 366 367 let info = ImageAllocationInfo { 368 width: 512, 369 height: 1024, 370 drm_format: DrmFormat::new(b'X', b'R', b'2', b'4'), 371 flags: RutabagaGrallocFlags::empty().use_scanout(true), 372 }; 373 374 let reqs = gralloc.get_image_memory_requirements(info).unwrap(); 375 let min_reqs = canonical_image_requirements(info).unwrap(); 376 377 assert!(reqs.strides[0] >= min_reqs.strides[0]); 378 assert!(reqs.size >= min_reqs.size); 379 380 let _handle = gralloc.allocate_memory(reqs).unwrap(); 381 382 // Reallocate with same requirements 383 let _handle2 = gralloc.allocate_memory(reqs).unwrap(); 384 } 385 386 #[test] 387 #[cfg_attr(target_os = "windows", ignore)] create_video_buffer()388 fn create_video_buffer() { 389 let gralloc_result = RutabagaGralloc::new(); 390 if gralloc_result.is_err() { 391 return; 392 } 393 394 let mut gralloc = gralloc_result.unwrap(); 395 396 let info = ImageAllocationInfo { 397 width: 512, 398 height: 1024, 399 drm_format: DrmFormat::new(b'N', b'V', b'1', b'2'), 400 flags: RutabagaGrallocFlags::empty().use_linear(true), 401 }; 402 403 let reqs = gralloc.get_image_memory_requirements(info).unwrap(); 404 let min_reqs = canonical_image_requirements(info).unwrap(); 405 406 assert!(reqs.strides[0] >= min_reqs.strides[0]); 407 assert!(reqs.strides[1] >= min_reqs.strides[1]); 408 assert_eq!(reqs.strides[2], 0); 409 assert_eq!(reqs.strides[3], 0); 410 411 assert!(reqs.offsets[0] >= min_reqs.offsets[0]); 412 assert!(reqs.offsets[1] >= min_reqs.offsets[1]); 413 assert_eq!(reqs.offsets[2], 0); 414 assert_eq!(reqs.offsets[3], 0); 415 416 assert!(reqs.size >= min_reqs.size); 417 418 let _handle = gralloc.allocate_memory(reqs).unwrap(); 419 420 // Reallocate with same requirements 421 let _handle2 = gralloc.allocate_memory(reqs).unwrap(); 422 } 423 424 #[test] 425 #[cfg_attr(target_os = "windows", ignore)] export_and_map()426 fn export_and_map() { 427 let gralloc_result = RutabagaGralloc::new(); 428 if gralloc_result.is_err() { 429 return; 430 } 431 432 let mut gralloc = gralloc_result.unwrap(); 433 434 let info = ImageAllocationInfo { 435 width: 512, 436 height: 1024, 437 drm_format: DrmFormat::new(b'X', b'R', b'2', b'4'), 438 flags: RutabagaGrallocFlags::empty() 439 .use_linear(true) 440 .use_sw_write(true) 441 .use_sw_read(true), 442 }; 443 444 let mut reqs = gralloc.get_image_memory_requirements(info).unwrap(); 445 446 // Anything else can use the mmap(..) system call. 447 if reqs.vulkan_info.is_none() { 448 return; 449 } 450 451 let handle = gralloc.allocate_memory(reqs).unwrap(); 452 let vulkan_info = reqs.vulkan_info.take().unwrap(); 453 454 let mapping = gralloc 455 .import_and_map(handle, vulkan_info, reqs.size) 456 .unwrap(); 457 458 let addr = mapping.as_ptr(); 459 let size = mapping.size(); 460 461 assert_eq!(size as u64, reqs.size); 462 assert_ne!(addr as *const u8, std::ptr::null()); 463 } 464 } 465