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