alpha2_render/error.rs
1use std::fmt;
2
3/// Render input or output failure.
4#[derive(Copy, Clone, Debug, Eq, PartialEq)]
5#[non_exhaustive]
6pub enum RenderError {
7 /// Render targets must have non-zero dimensions.
8 ZeroDimension,
9 /// Render dimensions exceed the configured or physical device limit.
10 DimensionTooLarge,
11 /// Colors must be finite, inside the unit interval, and premultiplied:
12 /// no color component above alpha.
13 InvalidColor,
14 /// A layer transform is non-finite, has zero scale, or invalid opacity.
15 InvalidVisual,
16 /// An effect parameter is non-finite or outside its supported range.
17 InvalidEffectParameter,
18 /// No effect with the requested identity is registered.
19 UnknownEffect,
20 /// Supplied parameter names or types do not match the effect schema.
21 InvalidEffectParams,
22 /// An uploaded effect resource has an invalid identity, shape, or payload.
23 InvalidEffectResource,
24 /// An effect invocation omitted a required registered resource.
25 MissingEffectResource,
26 /// A resource identity resolves to a different representation than required.
27 EffectResourceKindMismatch,
28 /// A transition invocation omitted its second texture.
29 MissingEffectInput,
30 /// A frame shows a caption, text generator or layout its host did not
31 /// prepare at the frame's size.
32 MissingText,
33 /// RGBA input byte length does not match the declared image size.
34 InvalidImageData,
35 /// A rendered texture could not be read back to host memory, and where.
36 TextureReadback(ReadbackFailure),
37 /// An internally constructed render graph was structurally invalid.
38 InvalidGraph,
39 /// A native texture could not synchronize with the render submission,
40 /// and what the platform's status for that said about the device, when
41 /// it said anything.
42 ExternalTextureSync(Option<alpha2_media::GpuFault>),
43 /// Caller-supplied NV12 planes are not an R8 luma and an RG8 chroma
44 /// render attachment at the output size and half of it.
45 InvalidPlaneTarget,
46 /// GPU work did not complete within the renderer's wait timeout.
47 GpuTimeout,
48 /// The device completed a submission without running it: a readback came
49 /// back without the bytes its submission copied. The renderer refuses
50 /// work from then on; only a new process gets a device that runs it.
51 GpuWorkDropped,
52 /// The GPU rejected this frame's work, for this kind of error: an
53 /// allocation it could not make, or invalid use (often of what failed to
54 /// allocate). Nothing in the submission ran; the device is fine, and the
55 /// renderer kept nothing of the frame.
56 Gpu {
57 /// The class of error the GPU reported.
58 kind: alpha2_gpu::GpuErrorKind,
59 },
60 /// The device is lost: it can no longer make the resources a frame
61 /// needs. The renderer refuses work from then on.
62 GpuDeviceLost,
63 /// An HDR (PQ or HLG) source met [`crate::ColorPolicy::RejectHdr`]: the
64 /// project's color pipeline refuses it rather than show it.
65 UnsupportedHdr,
66 /// A source belongs to the other renderer backend: a CPU source given to
67 /// the GPU renderer, or a GPU texture given to the CPU renderer.
68 InvalidSource,
69}
70
71impl fmt::Display for RenderError {
72 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73 match self {
74 Self::ZeroDimension => formatter.write_str("render dimensions must be non-zero"),
75 Self::DimensionTooLarge => {
76 formatter.write_str("render dimensions exceed the supported texture budget")
77 }
78 Self::InvalidColor => {
79 formatter.write_str(
80 "color components must be finite values from zero to one, premultiplied: none above alpha",
81 )
82 }
83 Self::InvalidVisual => formatter.write_str("layer presentation values are invalid"),
84 Self::InvalidEffectParameter => formatter.write_str("effect parameter is invalid"),
85 Self::UnknownEffect => formatter.write_str("effect is not registered"),
86 Self::InvalidEffectParams => {
87 formatter.write_str("effect parameters do not match schema")
88 }
89 Self::InvalidEffectResource => formatter.write_str("effect resource is invalid"),
90 Self::MissingEffectResource => {
91 formatter.write_str("effect resource is missing or not registered")
92 }
93 Self::MissingText => {
94 formatter.write_str("text was not prepared for this frame and size")
95 }
96 Self::EffectResourceKindMismatch => {
97 formatter.write_str("effect resource kind does not match schema")
98 }
99 Self::MissingEffectInput => formatter.write_str("effect input texture is missing"),
100 Self::InvalidImageData => {
101 formatter.write_str("RGBA byte length does not match the image size")
102 }
103 Self::TextureReadback(failure) => {
104 write!(formatter, "GPU texture readback failed: {failure}")
105 }
106 Self::InvalidGraph => formatter.write_str("render graph is structurally invalid"),
107 Self::ExternalTextureSync(None) => {
108 formatter.write_str("external texture synchronization failed")
109 }
110 Self::ExternalTextureSync(Some(fault)) => write!(
111 formatter,
112 "external texture synchronization failed ({})",
113 fault.as_str()
114 ),
115 Self::InvalidPlaneTarget => formatter.write_str(
116 "NV12 plane targets must be an R8 luma and an RG8 chroma render attachment at the output size and half of it",
117 ),
118 Self::GpuTimeout => formatter.write_str(GPU_TIMEOUT_MESSAGE),
119 Self::GpuWorkDropped => formatter.write_str(GPU_WORK_DROPPED_MESSAGE),
120 Self::Gpu { kind } => write!(
121 formatter,
122 "the GPU rejected this frame's work with a {} error",
123 kind.as_str()
124 ),
125 Self::GpuDeviceLost => formatter.write_str(GPU_DEVICE_LOST_MESSAGE),
126 Self::UnsupportedHdr => {
127 formatter.write_str("the color policy refuses HDR (PQ or HLG) sources")
128 }
129 Self::InvalidSource => {
130 formatter.write_str("the source belongs to another renderer backend")
131 }
132 }
133 }
134}
135
136impl std::error::Error for RenderError {}
137
138/// Where a texture readback failed.
139///
140/// A failed buffer mapping carries no cause in `wgpu` 30
141/// (`BufferAsyncError` is a unit), so [`Self::MapFailed`] says only that the
142/// mapping was refused. Whether the device was lost or out of memory at the
143/// time is for the device's diagnostics (`alpha2_gpu::GpuDiagnosticLog`) to
144/// show; none of these is itself evidence of memory exhaustion.
145#[derive(Copy, Clone, Debug, Eq, PartialEq)]
146#[non_exhaustive]
147pub enum ReadbackFailure {
148 /// The rendered texture's format or size is not one the readback packs.
149 UnsupportedTarget,
150 /// A row, plane, or buffer size overflowed or could not be represented.
151 Layout,
152 /// Mapping was requested twice for one readback.
153 DuplicateMapRequest,
154 /// The GPU refused to map the staging buffer.
155 MapFailed,
156 /// The mapping callback was dropped without reporting.
157 CallbackDropped,
158 /// The staging buffer was read before its mapping succeeded.
159 NotMapped,
160 /// A caller-supplied plane destination is too small for the frame.
161 DestinationTooSmall,
162 /// The device was polled for a submission it did not recognise.
163 SubmissionMismatch,
164}
165
166impl ReadbackFailure {
167 /// Stable lowercase name for logs and protocols.
168 #[must_use]
169 pub const fn as_str(self) -> &'static str {
170 match self {
171 Self::UnsupportedTarget => "unsupported-target",
172 Self::Layout => "layout",
173 Self::DuplicateMapRequest => "duplicate-map-request",
174 Self::MapFailed => "map-failed",
175 Self::CallbackDropped => "callback-dropped",
176 Self::NotMapped => "not-mapped",
177 Self::DestinationTooSmall => "destination-too-small",
178 Self::SubmissionMismatch => "submission-mismatch",
179 }
180 }
181}
182
183impl fmt::Display for ReadbackFailure {
184 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
185 formatter.write_str(self.as_str())
186 }
187}
188
189/// The message every GPU wait timeout carries, so a host that sees only error
190/// text can still classify it: one sentence for the renderer, the native
191/// interop helpers, and encoder inputs alike.
192pub use alpha2_media::GPU_TIMEOUT_MESSAGE;
193
194/// The message of [`RenderError::GpuWorkDropped`]. It names a lost device,
195/// which is what the host classifies it as.
196pub const GPU_WORK_DROPPED_MESSAGE: &str = "GPU device lost: it completed submitted work without \
197 running it (a readback lacked its canary), so this process can no longer render";
198
199/// The message of [`RenderError::GpuDeviceLost`].
200pub const GPU_DEVICE_LOST_MESSAGE: &str = "GPU device lost: it can no longer make the resources \
201 a frame needs, so this process can no longer render";
202
203/// How long a native caller waits for GPU work before giving up, unless the
204/// renderer is told otherwise.
205pub const DEFAULT_GPU_WAIT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
206
207#[cfg(not(target_arch = "wasm32"))]
208#[allow(clippy::needless_pass_by_value)] // Shaped for `map_err`.
209pub(crate) fn poll_error(error: wgpu::PollError) -> RenderError {
210 match error {
211 wgpu::PollError::Timeout => RenderError::GpuTimeout,
212 wgpu::PollError::WrongSubmissionIndex(..) => {
213 RenderError::TextureReadback(ReadbackFailure::SubmissionMismatch)
214 }
215 }
216}