impeller_hal/lib.rs
1//! Rendering HAL trait: the backend-agnostic contract every rendering backend
2//! implements.
3//!
4//! The HAL answers how draw commands become pixels in a GPU image.
5//! Presentation — how a finished image reaches a display, and how the frame
6//! loop is paced — is a separate, orthogonal axis and lives in
7//! `impeller-present`.
8//!
9//! # Shape of the trait
10//!
11//! Deliberately Vulkan-leaning. Recording commands and replaying them as GL
12//! calls works; extracting Vulkan-grade explicitness from an immediate-mode
13//! abstraction does not. GLES pays a small CPU cost for command recording, and
14//! that is the accepted trade. The trait is never reduced to a lowest common
15//! denominator to accommodate a weaker backend: backends implement, emulate,
16//! or capability-gate.
17//!
18//! # Two requirements present from day one
19//!
20//! Both exist for the sake of the DRM presentation path, and both are here
21//! before any DRM code, because retrofitting either is a breaking redesign:
22//!
23//! 1. **External render targets** — [`ExternalImageDesc`] lets the renderer
24//! draw into images it did not allocate, carrying dma-buf fd, DRM fourcc,
25//! and format modifier.
26//! 2. **Exportable sync** — [`HalFence::export_sync_file`] converts GPU
27//! completion into a `sync_file` fd that can ride an atomic commit as
28//! `IN_FENCE_FD`.
29//!
30//! # Branch on capabilities, never on backend identity
31//!
32//! [`Capabilities`] is the only thing layers above the HAL are allowed to
33//! condition on. A Mali GPU lacking fence export and a desktop Vulkan driver
34//! lacking `VK_KHR_external_fence_fd` are the same problem, and code asking
35//! "is this GLES?" instead of "can this export a fence?" gets both wrong.
36
37pub mod batch;
38pub mod blend;
39pub mod capabilities;
40pub mod error;
41pub mod format;
42pub mod material;
43pub mod resource;
44pub mod scissor;
45pub mod sync;
46
47pub use batch::{Batch, BatchDraw, ClipRole, ClipState, Vertex};
48pub use blend::{BlendFactor, BlendFactors, BlendMode};
49pub use capabilities::{Capabilities, DmaBufSupport, SampleCounts, SyncSupport};
50pub use error::{Error, Result};
51pub use format::{Extent2D, FormatModifierSet, Fourcc, Modifier, PixelFormat};
52/// A caller's fragment program, in the forms each backend can take one.
53///
54/// Both are given because a program is registered once and drawn on whichever
55/// backend the context happens to be, and neither payload can be derived from
56/// the other here: nothing in this workspace translates shaders at run time,
57/// by the decision recorded in the architecture document. A caller's build
58/// produces both, which is what the reference implementation's toolchain does
59/// and what this project's own build does.
60#[derive(Debug, Clone)]
61pub struct RuntimeProgram {
62 /// A SPIR-V fragment module, for Vulkan.
63 pub spirv: Vec<u32>,
64 /// A GLSL ES 300 fragment source, for GLES.
65 pub glsl_es: String,
66}
67
68pub use material::{
69 ColorFilter, ColorForm, Gamma, Material, MaterialVariant, Sampling, Stop, TileMode,
70 MATERIAL_FLOATS, MAX_EFFECT_TEXTURES, MAX_STOPS, MORPHOLOGY_TAPS, RUNTIME_FLOATS,
71};
72pub use resource::{
73 mip_levels_for, BufferDescriptor, BufferUsage, TextureDescriptor, TextureUsage,
74};
75pub use scissor::Scissor;
76pub use sync::{HalFence, FRAME_WAIT_TIMEOUT};
77
78#[cfg(unix)]
79pub use resource::{DmaBufPlane, ExternalImageDesc};
80
81/// A rendering backend, as a family of associated types.
82///
83/// Implementors are zero-sized markers; the types they name carry the work.
84pub trait Hal: 'static {
85 type Context: HalContext<Hal = Self>;
86 type Texture: HalTexture;
87 /// GPU completion, for callers that submit without waiting.
88 ///
89 /// Named here because the DRM presentation path consumes one: it needs
90 /// something to hand the kernel and something to decide when a frame slot
91 /// is free again.
92 type Fence: HalFence;
93
94 /// Backend name, for logs and report fingerprints.
95 const NAME: &'static str;
96}
97
98/// What a target must be able to tell the layers above it.
99pub trait HalTexture {
100 fn extent(&self) -> Extent2D;
101 fn format(&self) -> PixelFormat;
102}
103
104/// A device, and the resources created from it.
105///
106/// # Why a batch rather than a command buffer
107///
108/// An earlier shape of this trait had callers record incrementally — begin a
109/// pass, bind a pipeline, draw, finish — mirroring how Vulkan itself works.
110/// Building a real backend showed that to be the wrong seam. What a backend
111/// actually wants is the whole [`Batch`] at once, because the useful decisions
112/// are all global to it: which draws can share a pipeline binding, how to lay
113/// out one shared vertex buffer, what to upload in a single copy. Handing over
114/// a stream of calls forces each backend to reconstruct that shape, and a
115/// record-and-replay backend would have to buffer the stream anyway just to see
116/// what it was given.
117///
118/// This stays explicit in the sense that matters — nothing is discovered at
119/// draw time and the caller states its whole intent up front — while leaving
120/// each backend free to realize it natively.
121///
122/// # Threading
123///
124/// These take `&mut self`, so a context is not yet usable for resource
125/// creation from several threads at once. The intended design is thread-safe
126/// creation behind `&self`, which needs interior mutability around the
127/// allocator. That is deferred rather than decided against: nothing creates
128/// resources off the recording thread yet, and adding the synchronization
129/// before there is a caller to shape it around would be guesswork.
130pub trait HalContext {
131 type Hal: Hal;
132
133 /// What this device can do. The only thing callers branch on.
134 fn capabilities(&self) -> &Capabilities;
135
136 /// Allocate a texture, or import an external image when the descriptor
137 /// carries one.
138 fn create_texture(&mut self, desc: &TextureDescriptor) -> Result<<Self::Hal as Hal>::Texture>;
139
140 /// Release a texture and its memory.
141 fn destroy_texture(&mut self, texture: <Self::Hal as Hal>::Texture);
142
143 /// Register a caller's fragment program and return the name for it.
144 ///
145 /// Registering the same payload twice gives the same name back rather than
146 /// a second entry. That is not a convenience: a program leads to pipelines
147 /// keyed by it, so registering one repeatedly would multiply the pipeline
148 /// cache by the number of times a caller happened to ask -- and a caller
149 /// with no place to cache an index, which is every caller that renders a
150 /// list of scenes, would do exactly that.
151 fn register_program(&mut self, program: &RuntimeProgram) -> Result<u32>;
152
153 /// Draw a batch into a target.
154 ///
155 /// A descriptor that preserves rather than clears composes several batches
156 /// onto one target. A multisampled descriptor renders to a transient
157 /// multisample buffer and resolves into the target, so the target stays
158 /// single-sampled and readable either way.
159 fn submit_batch(
160 &mut self,
161 target: &mut <Self::Hal as Hal>::Texture,
162 batch: &Batch,
163 pass: PassDescriptor,
164 ) -> Result<()> {
165 self.submit_batch_textured(target, batch, pass, &[])
166 }
167
168 /// Draw a batch whose materials sample textures.
169 ///
170 /// `textures` is the table a [`Material::Image`] slot indexes. It is passed
171 /// alongside the batch rather than held inside it because a batch is a
172 /// description a recorder produces without touching the device, and a
173 /// backend texture handle is not something it can name.
174 ///
175 /// The target is borrowed mutably and the table immutably, so a batch
176 /// cannot sample the target it draws into. That restriction is real rather
177 /// than incidental — reading an attachment being written in the same pass
178 /// needs machinery this does not have — and having the borrow checker state
179 /// it is better than discovering it as a driver-dependent picture.
180 ///
181 /// A slot with no entry is an error rather than a fallback: a paint
182 /// silently drawn as something else is the failure nobody debugs from.
183 fn submit_batch_textured(
184 &mut self,
185 target: &mut <Self::Hal as Hal>::Texture,
186 batch: &Batch,
187 pass: PassDescriptor,
188 textures: &[&<Self::Hal as Hal>::Texture],
189 ) -> Result<()>;
190
191 /// Copy a target back to host memory, tightly packed.
192 ///
193 /// Part of the trait rather than a backend extra because the offscreen
194 /// target is a first-class citizen: the entire golden and conformance
195 /// apparatus is built on rendering to one and reading it back.
196 fn read_texture(&mut self, texture: &mut <Self::Hal as Hal>::Texture) -> Result<Vec<u8>>;
197
198 /// Fill a texture from host memory, tightly packed and top row first.
199 ///
200 /// The exact inverse of [`Self::read_texture`], stated in the same layout,
201 /// so a round trip through the pair is the identity on every backend. That
202 /// is what lets it be checked without a decoder: write known bytes, read
203 /// them back, compare.
204 ///
205 /// Color must be **premultiplied**, matching what a render target holds
206 /// and what a paint sampling this expects. A decoder usually produces
207 /// straight alpha, so converting is the caller's job. The distinction is
208 /// invisible for an opaque image, which is what makes it worth stating
209 /// here rather than leaving to be discovered.
210 ///
211 /// Decoding images is out of scope for this project; getting already
212 /// decoded pixels onto the device is not. Without this, an image shader
213 /// could sample nothing but what the renderer itself had drawn.
214 fn write_texture(
215 &mut self,
216 texture: &mut <Self::Hal as Hal>::Texture,
217 pixels: &[u8],
218 ) -> Result<()>;
219
220 /// Allocate a target that can be shared with a display controller.
221 ///
222 /// `modifiers` are the layouts the other side accepts, in preference order.
223 /// The default reports the capability as absent, which is the honest answer
224 /// for a backend that cannot do it: callers check
225 /// [`DmaBufSupport::can_allocate_scanout`] and take the GBM-allocated path
226 /// instead. This is capability gating rather than a stub — a backend that
227 /// answered every method this way would be useless, but one that answers
228 /// only the optional ones is correctly describing itself.
229 fn create_exportable_texture(
230 &mut self,
231 _extent: Extent2D,
232 _format: PixelFormat,
233 _modifiers: &[Modifier],
234 ) -> Result<<Self::Hal as Hal>::Texture> {
235 Err(Error::Unsupported("allocating exportable images"))
236 }
237
238 /// Export a texture as a dma-buf for scanout or cross-device sharing.
239 ///
240 /// Returns [`Error::Unsupported`] where [`DmaBufSupport::export`] is false;
241 /// the DRM path then allocates through GBM and imports instead.
242 #[cfg(unix)]
243 fn export_texture(
244 &mut self,
245 _texture: &<Self::Hal as Hal>::Texture,
246 ) -> Result<ExternalImageDesc> {
247 Err(Error::Unsupported("dma-buf export"))
248 }
249
250 /// Submit a batch without waiting, returning something that signals when
251 /// the GPU has finished.
252 ///
253 /// A frame loop needs this rather than the waiting form: the returned
254 /// fence is what gets handed to a display commit, and what decides when a
255 /// frame slot may be reused.
256 ///
257 /// Ordering between two of these is the caller's, and nothing here checks
258 /// it. Submitting twice into one target without waiting for the first is a
259 /// write-after-write hazard: both calls are well formed, both succeed, and
260 /// what lands is whichever the device finished last. A frame loop avoids it
261 /// by construction, since a ring hands out a different slot each frame and
262 /// will not reuse one until its fence has retired.
263 fn submit_batch_deferred(
264 &mut self,
265 target: &mut <Self::Hal as Hal>::Texture,
266 batch: &Batch,
267 pass: PassDescriptor,
268 ) -> Result<<Self::Hal as Hal>::Fence> {
269 self.submit_batch_deferred_textured(target, batch, pass, &[])
270 }
271
272 /// The same, for a batch whose materials sample textures.
273 ///
274 /// What a frame with layers needs. The pass that lands in the image being
275 /// presented is the one that composites the layers, so it samples the
276 /// targets they were rendered into — and a deferred submission that could
277 /// not sample anything meant such a frame could be rendered offscreen and
278 /// never displayed.
279 ///
280 /// The textures must outlive the submission, which the caller arranges:
281 /// they cannot travel with the fence, because a fence is handed to a
282 /// display commit and has to stay sendable while a texture tracks mutable
283 /// state of its own.
284 fn submit_batch_deferred_textured(
285 &mut self,
286 _target: &mut <Self::Hal as Hal>::Texture,
287 _batch: &Batch,
288 _pass: PassDescriptor,
289 _textures: &[&<Self::Hal as Hal>::Texture],
290 ) -> Result<<Self::Hal as Hal>::Fence> {
291 Err(Error::Unsupported("deferred submission"))
292 }
293
294 /// Release a deferred submission once its work has completed.
295 fn retire_fence(&mut self, _fence: <Self::Hal as Hal>::Fence) {}
296}
297
298/// How a pass is configured, beyond the draws themselves.
299///
300/// Grouped rather than passed as loose parameters so later pass-level state —
301/// stencil, depth, damage regions — extends this without changing every
302/// backend's signature.
303#[derive(Debug, Clone, Copy, PartialEq)]
304pub struct PassDescriptor {
305 /// Clear to this color first, or preserve the target's contents.
306 pub clear: Option<[f32; 4]>,
307 /// MSAA sample count. 1 disables multisampling.
308 ///
309 /// Rendering is multisampled and resolved into the target, so the target
310 /// itself stays single-sampled and directly readable.
311 pub samples: u32,
312 /// Where this pass's clip space lands in the target, if not the whole of it.
313 ///
314 /// `None` maps clip space onto the target exactly, which is what every
315 /// pass wants whose geometry was recorded against the target it renders
316 /// into. A pass whose geometry was recorded against a *larger* space sets
317 /// this to crop instead of scale — see [`PassViewport`].
318 pub viewport: Option<PassViewport>,
319}
320
321/// A clip space larger than the target it lands in, and where it lands.
322///
323/// A layer records its draws before anything is known about how much of the
324/// target they will cover. The geometry and every material go into clip space
325/// as they are recorded — `docs/architecture.md` states that a recording is
326/// tessellated geometry rather than a command list — so by the time the extent
327/// could be narrowed, narrowing it would mean rewriting all of that.
328///
329/// This narrows the *target* instead and leaves the recording alone. The
330/// viewport keeps the extent the geometry was recorded against, and `offset`
331/// slides it so that the wanted sub-rectangle lands on the target. Clip space
332/// maps onto the viewport, not onto the target, so the picture is cropped
333/// rather than scaled and every recorded coordinate still means what it meant.
334///
335/// `offset` is normally negative: a target holding the region starting at
336/// device (x, y) of the recorded space offsets by (-x, -y).
337#[derive(Debug, Clone, Copy, PartialEq)]
338pub struct PassViewport {
339 /// Where the recorded space's origin sits, in target pixels.
340 pub offset: [f32; 2],
341 /// The extent the geometry was recorded against.
342 pub extent: Extent2D,
343}
344
345impl Default for PassDescriptor {
346 fn default() -> Self {
347 Self {
348 clear: None,
349 samples: 1,
350 viewport: None,
351 }
352 }
353}
354
355impl PassDescriptor {
356 pub fn clear(color: [f32; 4]) -> Self {
357 Self {
358 clear: Some(color),
359 samples: 1,
360 viewport: None,
361 }
362 }
363
364 /// Land this pass's clip space on part of the target rather than all of it.
365 pub fn with_viewport(mut self, viewport: PassViewport) -> Self {
366 self.viewport = Some(viewport);
367 self
368 }
369
370 pub fn preserve() -> Self {
371 Self::default()
372 }
373
374 pub fn with_samples(mut self, samples: u32) -> Self {
375 self.samples = samples;
376 self
377 }
378
379 pub fn is_multisampled(&self) -> bool {
380 self.samples > 1
381 }
382}