Skip to main content

rutabaga_gfx/rutabaga_gralloc/
gralloc.rs

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