Skip to main content

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}