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}