Skip to main content

frust_engine/
error.rs

1use thiserror::Error;
2
3/// Engine render errors — returned on frame paths, never panicked.
4///
5/// Mirrors the reference sparse-strips renderer's error cases plus engine-specific constraints.
6/// The engine always returns errors on invalid or oversized resources rather than
7/// panicking, per Frust's frame-path invariant (E17).
8#[derive(Debug, Clone, Error)]
9pub enum EngineError {
10    #[error("atlas allocation failed")]
11    AtlasError,
12
13    #[error("missing texture binding")]
14    MissingTextureBinding,
15
16    #[error("intermediate texture too large")]
17    IntermediateTextureTooLarge,
18
19    #[error("intermediate texture limit reached")]
20    IntermediateTextureLimitReached,
21
22    #[error("target exceeds u16 ceiling (65,535 pixels)")]
23    TargetTooLarge,
24
25    #[error("non-finite transform refused")]
26    InvalidTransform,
27
28    /// A command's own geometry — a rectangle's extents, a corner radius, a
29    /// path point, a stroke width, a dash length or phase — is non-finite.
30    ///
31    /// Separate from [`InvalidTransform`](Self::InvalidTransform) because the
32    /// two name different halves of a frame's input: a transform maps geometry
33    /// onto the device grid, while this is the geometry itself, and a caller
34    /// chasing a blank frame needs to know which of the two it recorded wrong.
35    /// Both are refused by the same up-front walk, before any lowering runs.
36    #[error("non-finite geometry refused")]
37    InvalidGeometry,
38
39    /// A frame's layer shape is outside what the engine's scheduler serves.
40    ///
41    /// `reason` names what was found — a layer graph needing a third live
42    /// intermediate page at one pass, a chain deeper than the two-page
43    /// ping-pong serves, a filter layer, a non-default blend mode — because the
44    /// caller's own log is where a frame that produced no pixels has to be
45    /// explainable from.
46    ///
47    /// Returned rather than panicked (E17), but it is not a route to a second
48    /// renderer: the engine tier carries none, and which renderer draws a
49    /// surface is settled when that surface is configured, not per frame. The
50    /// caller decides what becomes of the frame, and skipping it is the only
51    /// answer available — `frust-render`'s engine arm releases the acquired
52    /// texture unpresented, counts the refusal and logs `reason` rate-limited,
53    /// leaving whatever was presented last on the screen.
54    #[error("scheduler escalation: {reason}")]
55    SchedulerEscalation {
56        /// What the scheduler found that it does not serve.
57        reason: String,
58    },
59
60    /// A [`Command::DrawRetained`](frust_scene::Command::DrawRetained) named a
61    /// retained page the engine cannot composite it from: the key is not
62    /// resident (evicted, never recorded, or dropped with the store), or it is
63    /// resident at a placement the draw cannot be served from — another linear
64    /// transform or extent, or under a coverage-mask clip a composite cannot
65    /// honour (see [`crate::cache::retained`]).
66    ///
67    /// The frame is refused before anything is recorded, exactly like every
68    /// other variant here, and the retained store is cleared on the way out:
69    /// the resident-key ledger the widget tree painted against was wrong, and
70    /// the cheapest way back to an agreeing pair is an empty store whose empty
71    /// ledger makes the next frame paint every body again. Logged once per
72    /// refusal by the caller, like any refused frame.
73    #[error("retained page {key} is not resident at the placement a DrawRetained asked for")]
74    RetainedKeyMissing {
75        /// The raw key the `DrawRetained` carried.
76        key: u64,
77    },
78
79    /// A frame records more
80    /// [`Command::BackdropBlur`](frust_scene::Command::BackdropBlur)s than the
81    /// engine serves in one frame
82    /// ([`MAX_BACKDROPS_PER_FRAME`](crate::filters::MAX_BACKDROPS_PER_FRAME)).
83    ///
84    /// Each backdrop costs the frame a cut of its surface pass, a region copy
85    /// and a blur's whole pass sequence, so the count is bounded rather than
86    /// left to grow with a widget tree; a frame past the bound is refused up
87    /// front, before anything is recorded, exactly like a scheduler
88    /// escalation — never rendered with some of its backdrops missing.
89    #[error(
90        "{recorded} backdrop blurs in one frame, more than the {max} the engine serves per frame"
91    )]
92    TooManyBackdrops {
93        /// How many backdrop blurs the frame recorded.
94        recorded: usize,
95        /// The per-frame bound.
96        max: usize,
97    },
98
99    #[error("alpha capacity exhausted")]
100    AlphaCapacity,
101
102    #[error("encoded-paint capacity exhausted")]
103    PaintCapacity,
104}