Skip to main content

emblema_hal/
resource.rs

1//! Resource descriptions: what the renderer asks a backend to allocate.
2
3use crate::format::{Extent2D, Fourcc, Modifier, PixelFormat};
4
5/// How a texture will be used, which backends need up front to choose a
6/// layout.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub struct TextureUsage {
9    /// Bound as a shader input.
10    pub sampled: bool,
11    /// Used as a color attachment.
12    pub render_target: bool,
13    /// Source or destination of a copy.
14    pub transfer: bool,
15    /// Will be exported for scanout.
16    ///
17    /// Backends that can allocate with explicit modifiers use this to pick a
18    /// layout the display controller accepts, rather than one that is optimal
19    /// for rendering alone.
20    pub scanout: bool,
21}
22
23impl TextureUsage {
24    /// Read by a shader and written from the processor, never rendered into.
25    pub const fn sampled() -> Self {
26        Self {
27            sampled: true,
28            render_target: false,
29            transfer: true,
30            scanout: false,
31        }
32    }
33
34    /// A plain offscreen render target: the golden and conformance suites run
35    /// entirely on these.
36    pub const fn offscreen() -> Self {
37        Self {
38            sampled: true,
39            render_target: true,
40            transfer: true,
41            scanout: false,
42        }
43    }
44}
45
46/// One plane of a dma-buf image.
47#[cfg(unix)]
48#[derive(Debug)]
49pub struct DmaBufPlane {
50    /// The dma-buf file descriptor. Ownership transfers to the importer.
51    pub fd: std::os::fd::OwnedFd,
52    pub offset: u32,
53    pub stride: u32,
54}
55
56/// An image that already exists, to be imported rather than allocated.
57///
58/// This is the first of the two requirements that exist in the HAL from day
59/// one for the sake of the DRM path. The renderer must be able to draw into
60/// images it did not allocate — buffers GBM made, or buffers another device
61/// allocated and shared — because on a split render/display SoC the display
62/// controller and the GPU are different devices with different ideas about
63/// memory layout.
64#[cfg(unix)]
65#[derive(Debug)]
66pub struct ExternalImageDesc {
67    /// Planes making up the image. Most formats are single-plane.
68    pub planes: Vec<DmaBufPlane>,
69    /// The DRM format code the buffer was allocated with.
70    pub fourcc: Fourcc,
71    /// The layout the buffer was allocated with.
72    ///
73    /// [`Modifier::INVALID`] is accepted only where no negotiation took place;
74    /// a buffer that came out of format negotiation always carries the
75    /// explicit modifier that was agreed on.
76    pub modifier: Modifier,
77}
78
79/// What to allocate, or what to import.
80#[derive(Debug)]
81pub struct TextureDescriptor {
82    pub extent: Extent2D,
83    pub format: PixelFormat,
84    pub usage: TextureUsage,
85    /// Sample count for MSAA targets. 1 means single-sampled.
86    pub sample_count: u32,
87    /// How many mip levels this texture holds. 1 is the image alone.
88    ///
89    /// A chain is not made for every texture, because it costs a third again
90    /// in memory and a pass of downsampling on upload, and most textures here
91    /// are drawn at or above their own size where it would never be read. A
92    /// caller who will minify states it, and [`Self::mipmapped`] works out how
93    /// many levels that takes.
94    ///
95    /// Levels past the first are filled by the backend when the texture is
96    /// written, not by the caller: there is no way to hand them in, because a
97    /// chain a caller built by some other rule would sample differently on the
98    /// two backends and this renderer's whole test model is that they agree.
99    pub mip_levels: u32,
100    /// When present, import this existing image instead of allocating.
101    #[cfg(unix)]
102    pub external: Option<ExternalImageDesc>,
103}
104
105/// How many mip levels an image of this size has, counting the image itself.
106///
107/// Halving the larger axis until it reaches one texel, which is what both
108/// backends mean by a complete chain: a level is not required to be square, and
109/// an axis that reaches one stays there while the other keeps halving.
110pub fn mip_levels_for(extent: Extent2D) -> u32 {
111    let longest = extent.width.max(extent.height).max(1);
112    // `ilog2` of a power of two is the exponent, and of anything else is the
113    // exponent below it -- which is the count of halvings that still leave more
114    // than one texel, so adding the level for the image itself is the whole
115    // chain either way.
116    longest.ilog2() + 1
117}
118
119impl TextureDescriptor {
120    /// An offscreen render target, the workhorse of the test suites.
121    pub fn offscreen(extent: Extent2D, format: PixelFormat) -> Self {
122        Self {
123            extent,
124            format,
125            usage: TextureUsage::offscreen(),
126            sample_count: 1,
127            mip_levels: 1,
128            #[cfg(unix)]
129            external: None,
130        }
131    }
132
133    /// A texture only ever read by a shader.
134    ///
135    /// A baked gradient ramp and an uploaded image are both this: written once
136    /// from the processor, sampled many times, never drawn into. Saying so
137    /// costs a backend nothing and saves it a color attachment -- which is not
138    /// merely tidiness on GLES, where a format can be filterable as a texture
139    /// and not renderable as an attachment. Asking for a target a caller does
140    /// not need is how a texture that would have worked fails to be created.
141    pub fn sampled(extent: Extent2D, format: PixelFormat) -> Self {
142        Self {
143            extent,
144            format,
145            usage: TextureUsage::sampled(),
146            sample_count: 1,
147            mip_levels: 1,
148            #[cfg(unix)]
149            external: None,
150        }
151    }
152
153    /// The same, with a full mip chain.
154    ///
155    /// Every level down to a single texel, which is what a caller minifying by
156    /// an unknown amount needs and is only a third again in memory however far
157    /// it goes -- each level is a quarter of the one above, and a quarter
158    /// summed forever is a third.
159    pub fn mipmapped(extent: Extent2D, format: PixelFormat) -> Self {
160        Self {
161            mip_levels: mip_levels_for(extent),
162            ..Self::offscreen(extent, format)
163        }
164    }
165
166    /// Whether this texture holds more than the image itself.
167    pub fn is_mipmapped(&self) -> bool {
168        self.mip_levels > 1
169    }
170
171    /// Whether this describes an import rather than an allocation.
172    pub fn is_external(&self) -> bool {
173        #[cfg(unix)]
174        {
175            self.external.is_some()
176        }
177        #[cfg(not(unix))]
178        {
179            false
180        }
181    }
182}
183
184/// How a buffer will be used.
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
186pub struct BufferUsage {
187    pub vertex: bool,
188    pub index: bool,
189    pub uniform: bool,
190    pub transfer: bool,
191}
192
193#[derive(Debug, Clone)]
194pub struct BufferDescriptor {
195    pub size: u64,
196    pub usage: BufferUsage,
197    /// Whether the host needs to write to this buffer directly, as per-frame
198    /// ring allocations do.
199    pub host_visible: bool,
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn offscreen_targets_are_not_external() {
208        let desc = TextureDescriptor::offscreen(Extent2D::new(1920, 1080), PixelFormat::Rgba8Unorm);
209        assert!(!desc.is_external());
210        assert_eq!(desc.sample_count, 1);
211        assert!(desc.usage.render_target);
212        // Offscreen targets are read back and sampled, so both must be set for
213        // the golden suites to work.
214        assert!(desc.usage.transfer);
215        assert!(desc.usage.sampled);
216        assert!(!desc.usage.scanout);
217    }
218
219    #[test]
220    fn scanout_usage_is_distinct_from_render_target_usage() {
221        // A backend allocating for scanout must pick a layout the display
222        // controller accepts, which is not necessarily the one that is fastest
223        // to render into, so the two flags cannot be conflated.
224        let usage = TextureUsage {
225            scanout: true,
226            ..TextureUsage::offscreen()
227        };
228        assert!(usage.scanout);
229        assert!(usage.render_target);
230        assert!(!TextureUsage::offscreen().scanout);
231    }
232}