xref: /aosp_15_r20/external/crosvm/rutabaga_gfx/src/rutabaga_gralloc/gralloc.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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