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;
12
13use crate::rutabaga_gralloc::formats::*;
14#[cfg(feature = "minigbm")]
15use crate::rutabaga_gralloc::minigbm::MinigbmDevice;
16use crate::rutabaga_gralloc::system_gralloc::SystemGralloc;
17#[cfg(feature = "vulkano")]
18use crate::rutabaga_gralloc::vulkano_gralloc::VulkanoGralloc;
19use crate::rutabaga_os::round_up_to_page_size;
20use crate::rutabaga_os::MappedRegion;
21use 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 */
27const RUTABAGA_GRALLOC_USE_SCANOUT: u32 = 1 << 0;
28const RUTABAGA_GRALLOC_USE_RENDERING: u32 = 1 << 2;
29const RUTABAGA_GRALLOC_USE_LINEAR: u32 = 1 << 4;
30const RUTABAGA_GRALLOC_USE_TEXTURING: u32 = 1 << 5;
31const RUTABAGA_GRALLOC_USE_CAMERA_WRITE: u32 = 1 << 6;
32const RUTABAGA_GRALLOC_USE_CAMERA_READ: u32 = 1 << 7;
33#[allow(dead_code)]
34const RUTABAGA_GRALLOC_USE_PROTECTED: u32 = 1 << 8;
35
36/* SW_{WRITE,READ}_RARELY omitted since not even Android uses this much. */
37const RUTABAGA_GRALLOC_USE_SW_READ_OFTEN: u32 = 1 << 9;
38const RUTABAGA_GRALLOC_USE_SW_WRITE_OFTEN: u32 = 1 << 11;
39
40#[allow(dead_code)]
41const RUTABAGA_GRALLOC_VIDEO_DECODER: u32 = 1 << 13;
42#[allow(dead_code)]
43const RUTABAGA_GRALLOC_VIDEO_ENCODER: u32 = 1 << 14;
44
45/// Usage flags for constructing a buffer object.
46#[derive(Copy, Clone, Eq, PartialEq, Default)]
47pub struct RutabagaGrallocFlags(pub u32);
48
49impl RutabagaGrallocFlags {
50    /// Returns empty set of flags.
51    #[inline(always)]
52    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)]
59    pub fn new(raw: u32) -> RutabagaGrallocFlags {
60        RutabagaGrallocFlags(raw)
61    }
62
63    /// Sets the scanout flag's presence.
64    #[inline(always)]
65    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)]
75    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)]
85    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)]
95    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)]
105    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)]
115    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)]
121    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)]
128    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)]
138    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)]
146pub 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)]
155pub 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.
170pub 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.
175    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.
179    fn supports_dmabuf(&self) -> bool;
180
181    /// Implementations must return the resource layout, compression, and caching properties of
182    /// an allocation request.
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.
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.
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)]
206pub 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.
215pub struct RutabagaGralloc {
216    grallocs: Map<GrallocBackend, Box<dyn Gralloc>>,
217}
218
219impl 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.
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.
256    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.
267    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.
278    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.
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.
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.
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)]
354mod tests {
355    use super::*;
356
357    #[test]
358    #[cfg_attr(target_os = "windows", ignore)]
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)]
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)]
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}