1use std::fmt;
2
3#[derive(Copy, Clone, Debug, Eq, PartialEq)]
5#[non_exhaustive]
6pub enum RenderError {
7 ZeroDimension,
9 DimensionTooLarge,
11 InvalidColor,
13 InvalidVisual,
15 InvalidEffectParameter,
17 UnknownEffect,
19 InvalidEffectParams,
21 InvalidEffectResource,
23 MissingEffectResource,
25 EffectResourceKindMismatch,
27 MissingEffectInput,
29 MissingText,
31 InvalidImageData,
33 TextureReadback(ReadbackFailure),
35 InvalidGraph,
37 ExternalTextureSync(Option<alpha2_media::GpuFault>),
40 InvalidPlaneTarget,
42 GpuTimeout,
44 GpuWorkDropped,
47 Gpu {
50 kind: alpha2_gpu::GpuErrorKind,
52 },
53 GpuDeviceLost,
56 GpuDeviceHung {
59 frame: Option<u64>,
61 time_ticks: Option<i64>,
63 gpu_ms: u32,
65 watchdog_ms: u32,
67 },
68 UnsupportedHdr,
70 InvalidSource,
72}
73
74impl fmt::Display for RenderError {
75 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::ZeroDimension => formatter.write_str("render dimensions must be non-zero"),
78 Self::DimensionTooLarge => {
79 formatter.write_str("render dimensions exceed the supported texture budget")
80 }
81 Self::InvalidColor => {
82 formatter.write_str(
83 "color components must be finite values from zero to one, premultiplied: none above alpha",
84 )
85 }
86 Self::InvalidVisual => formatter.write_str("layer presentation values are invalid"),
87 Self::InvalidEffectParameter => formatter.write_str("effect parameter is invalid"),
88 Self::UnknownEffect => formatter.write_str("effect is not registered"),
89 Self::InvalidEffectParams => {
90 formatter.write_str("effect parameters do not match schema")
91 }
92 Self::InvalidEffectResource => formatter.write_str("effect resource is invalid"),
93 Self::MissingEffectResource => {
94 formatter.write_str("effect resource is missing or not registered")
95 }
96 Self::MissingText => {
97 formatter.write_str("text was not prepared for this frame and size")
98 }
99 Self::EffectResourceKindMismatch => {
100 formatter.write_str("effect resource kind does not match schema")
101 }
102 Self::MissingEffectInput => formatter.write_str("effect input texture is missing"),
103 Self::InvalidImageData => {
104 formatter.write_str("RGBA byte length does not match the image size")
105 }
106 Self::TextureReadback(failure) => {
107 write!(formatter, "GPU texture readback failed: {failure}")
108 }
109 Self::InvalidGraph => formatter.write_str("render graph is structurally invalid"),
110 Self::ExternalTextureSync(None) => {
111 formatter.write_str("external texture synchronization failed")
112 }
113 Self::ExternalTextureSync(Some(fault)) => write!(
114 formatter,
115 "external texture synchronization failed ({})",
116 fault.as_str()
117 ),
118 Self::InvalidPlaneTarget => formatter.write_str(
119 "NV12 plane targets must be an R8 luma and an RG8 chroma render attachment at the output size and half of it",
120 ),
121 Self::GpuTimeout => formatter.write_str(GPU_TIMEOUT_MESSAGE),
122 Self::GpuWorkDropped => formatter.write_str(GPU_WORK_DROPPED_MESSAGE),
123 Self::Gpu { kind } => write!(
124 formatter,
125 "the GPU rejected this frame's work with a {} error",
126 kind.as_str()
127 ),
128 Self::GpuDeviceLost => formatter.write_str(GPU_DEVICE_LOST_MESSAGE),
129 Self::GpuDeviceHung {
130 frame,
131 gpu_ms,
132 watchdog_ms,
133 ..
134 } => {
135 formatter.write_str("GPU device hung: ")?;
136 match frame {
137 Some(frame) => write!(formatter, "frame {frame}")?,
138 None => formatter.write_str("a submission")?,
139 }
140 write!(
141 formatter,
142 " ran {gpu_ms} ms in one submission against a {watchdog_ms} ms watchdog; this \
143 work stops the GPU, so it is not retried on the GPU"
144 )
145 }
146 Self::UnsupportedHdr => {
147 formatter.write_str("the color policy refuses HDR (PQ or HLG) sources")
148 }
149 Self::InvalidSource => {
150 formatter.write_str("the source belongs to another renderer backend")
151 }
152 }
153 }
154}
155
156impl std::error::Error for RenderError {}
157
158#[derive(Copy, Clone, Debug, Eq, PartialEq)]
164#[non_exhaustive]
165pub enum ReadbackFailure {
166 UnsupportedTarget,
168 Layout,
170 DuplicateMapRequest,
172 MapFailed,
174 CallbackDropped,
176 NotMapped,
178 DestinationTooSmall,
180 SubmissionMismatch,
182}
183
184impl ReadbackFailure {
185 #[must_use]
187 pub const fn as_str(self) -> &'static str {
188 match self {
189 Self::UnsupportedTarget => "unsupported-target",
190 Self::Layout => "layout",
191 Self::DuplicateMapRequest => "duplicate-map-request",
192 Self::MapFailed => "map-failed",
193 Self::CallbackDropped => "callback-dropped",
194 Self::NotMapped => "not-mapped",
195 Self::DestinationTooSmall => "destination-too-small",
196 Self::SubmissionMismatch => "submission-mismatch",
197 }
198 }
199}
200
201impl fmt::Display for ReadbackFailure {
202 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
203 formatter.write_str(self.as_str())
204 }
205}
206
207pub use alpha2_media::GPU_TIMEOUT_MESSAGE;
209
210pub const GPU_WORK_DROPPED_MESSAGE: &str = "GPU device lost: it completed submitted work without \
212 running it (a readback lacked its canary), so this process can no longer render";
213
214pub const GPU_DEVICE_LOST_MESSAGE: &str = "GPU device lost: it can no longer make the resources \
216 a frame needs, so this process can no longer render";
217
218pub const DEFAULT_GPU_WAIT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
220
221#[cfg(not(target_arch = "wasm32"))]
222#[allow(clippy::needless_pass_by_value)] pub(crate) fn poll_error(error: wgpu::PollError) -> RenderError {
224 match error {
225 wgpu::PollError::Timeout => RenderError::GpuTimeout,
226 wgpu::PollError::WrongSubmissionIndex(..) => {
227 RenderError::TextureReadback(ReadbackFailure::SubmissionMismatch)
228 }
229 }
230}