gpu_handle_types/error.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::borrow::Cow;
4
5use crate::{BackendKind, DeviceId};
6
7/// Domain error for every leaf operation in `gpu-handle-types`.
8///
9/// Downstream crates keep their own domain errors with `#[from]` into this
10/// one — this crate has zero backend-library knowledge (no ffmpeg, no wgpu,
11/// no ocl error `From` impls).
12#[derive(thiserror::Error, Debug)]
13#[non_exhaustive]
14pub enum Error {
15 #[error("io: {0}")]
16 Io(#[from] std::io::Error),
17
18 #[error("out of memory")]
19 OutOfMemory,
20
21 #[error("out of gpu memory: need {required_bytes}, have {available_bytes} ({backend:?})")]
22 OutOfGpuMemory { required_bytes: u64, available_bytes: u64, backend: BackendKind },
23
24 #[error("not supported: {0}")]
25 NotSupported(Cow<'static, str>),
26
27 #[error("invalid argument: {0}")]
28 InvalidArgument(Cow<'static, str>),
29
30 /// An operation was invoked while the object is in a state that
31 /// cannot service it — an encoder that was already finished, a
32 /// flush issued while a deferred-submit frame is still awaiting its
33 /// submit notification, etc. Distinct from [`Error::InvalidArgument`]
34 /// (the *argument* is fine; the *lifecycle position* is wrong) and
35 /// classified as [`ErrorKind::Configuration`] (the caller fixes the
36 /// call order and retries; the same call from the same state fails
37 /// identically). Carries a `&'static str` because every state-machine
38 /// transition message is a compile-time literal — no formatting.
39 #[error("invalid state: {0}")]
40 InvalidState(&'static str),
41
42 /// A deliberate, caller-initiated cancellation — an interrupt-token
43 /// cancel aborting a blocking read, a dropped wait future, an SDK job
44 /// abort. Not a failure: the operation stopped because it was asked
45 /// to. Classified as [`ErrorKind::Cancelled`] so upper layers can
46 /// void it (no retry, no error state, no error-level logging) instead
47 /// of surfacing it as a fault.
48 #[error("cancelled")]
49 Cancelled,
50
51 #[error("timeout")]
52 Timeout,
53
54 /// A non-blocking operation has no result available **yet** — the caller
55 /// should retry later, nothing has failed. This is the leaf form of the
56 /// "temporary unavailability" signal a poll/try surface needs: e.g. a
57 /// browser WebCodecs decoder's synchronous next-frame call returns it
58 /// when a frame is in flight but not yet delivered (an async poll
59 /// surface uses `Poll::Pending` instead and never produces this).
60 /// Distinct from [`Error::Timeout`] (a *wait* elapsed) and from
61 /// `Ok(None)`/EOF (the stream genuinely ended).
62 /// Classified [`ErrorKind::Recoverable`].
63 #[error("would block")]
64 WouldBlock,
65
66 /// The underlying GPU / accelerator device is in an unrecoverable
67 /// state (Vulkan `VK_ERROR_DEVICE_LOST`, DXGI `DXGI_ERROR_DEVICE_REMOVED`,
68 /// CUDA `CUDA_ERROR_LAUNCH_FAILED` after a TDR, OpenCL
69 /// `CL_INVALID_COMMAND_QUEUE` after a fault, …). Distinct from
70 /// [`Error::Timeout`] (which is recoverable — the wait simply hadn't
71 /// completed yet) and from [`Error::NotSupported`] (which means the
72 /// platform never supported the operation). Callers MUST tear down
73 /// any in-flight work bound to the device and rebuild — same-device
74 /// retries will fail identically.
75 #[error("device lost ({backend:?})")]
76 DeviceLost { backend: BackendKind },
77
78 #[error("unknown param: {backend:?}/{key}")]
79 UnknownParam { backend: BackendKind, key: String },
80
81 #[error("device mismatch: expected {expected:?}, got {actual:?}")]
82 DeviceMismatch { expected: DeviceId, actual: DeviceId },
83
84 #[error("no cpu shader provided")]
85 NoCpuShader,
86
87 #[error("explicit sync is invalid in auto mode")]
88 ExplicitSyncInAutoMode,
89
90 #[error("user kernel panic: {backtrace}")]
91 UserKernelPanic { backtrace: String },
92
93 /// Shader compilation failed. The string carries the backend-specific
94 /// diagnostic (naga / NVRTC / clBuildProgram output).
95 #[error("shader compile error: {0}")]
96 ShaderCompile(String),
97
98 /// Bound output / input format does not match what the pipeline /
99 /// shader requires.
100 #[error("format mismatch: {0}")]
101 FormatMismatch(String),
102
103 /// An operation was handed an `ExternalQueue` whose backend does not
104 /// match the backend of the processor receiving it.
105 #[error("queue backend mismatch: expected {expected:?}, got {actual:?}")]
106 QueueBackendMismatch { expected: BackendKind, actual: BackendKind },
107
108 /// Unrecoverable internal invariant violation that the caller cannot
109 /// undo by re-configuring — e.g. saturation of a finite counter the
110 /// crate cannot recycle. Distinct from [`Error::DeviceLost`] (which is
111 /// driver-level) and from [`Error::NotSupported`] (which can be fixed by
112 /// rebuilding with different features). Always classified as
113 /// [`ErrorKind::Fatal`].
114 #[error("fatal: {0}")]
115 Fatal(&'static str),
116
117 /// A **fatal backend error that carries its full diagnostic** — the SDK /
118 /// backend message (and its `source()` chain), preserved rather than
119 /// collapsed to a static category string. Downstream crates lower their
120 /// own fatal domain errors here (a decoder or SDK fault that is not a
121 /// device-loss) so the exact SDK message propagates all the way to the
122 /// caller / log instead of a generic "backend fatal error". Boxed to keep
123 /// the leaf enum
124 /// pointer-sized. Classified as [`ErrorKind::Fatal`] — same abort contract
125 /// as [`Error::Fatal`], but with the detail retained.
126 #[error("fatal backend: {0}")]
127 FatalBackend(Box<dyn std::error::Error + Send + Sync + 'static>),
128
129 /// Decoder / encoder / resampler / muxer backend error raised by a
130 /// downstream media crate. Boxed so the leaf-crate [`Error`] stays a small
131 /// pointer-sized enum, and so a backend can layer its own error enum
132 /// underneath without pulling SDK-specific `From` impls into the leaf
133 /// crate.
134 ///
135 /// Classified as [`ErrorKind::Recoverable`] by default. Backends
136 /// that have richer classification of their own should expose a
137 /// `kind()` accessor through their own type and consume the leaf
138 /// variant only at the outermost boundary.
139 #[error("backend: {0}")]
140 Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
141}
142
143/// Classification of an [`Error`]. Callers branch on `.kind()` for
144/// retry / abort / reconfigure decisions without pattern-matching every
145/// variant.
146#[derive(Debug, Copy, Clone, PartialEq, Eq)]
147pub enum ErrorKind {
148 /// A deliberate, caller-initiated cancellation (an interrupt-token
149 /// abort of a blocking read, a dropped wait future, an SDK job
150 /// abort). Not a failure: the operation stopped because it was asked
151 /// to. Callers void it — no retry, no error state, no error-level
152 /// logging; whatever superseded the cancelled work carries on.
153 Cancelled,
154 /// Safe to retry or ignore — typically a transient resource shortage.
155 Recoverable,
156 /// Caller supplied something the backend cannot do. Fix the config
157 /// and retry; same input will fail the same way.
158 Configuration,
159 /// Backend is in an unrecoverable state. Abort the pipeline.
160 Fatal,
161}
162
163impl Error {
164 /// Classify this error. See [`ErrorKind`].
165 pub fn kind(&self) -> ErrorKind {
166 match self {
167 Error::Cancelled => ErrorKind::Cancelled,
168
169 Error::Timeout
170 | Error::WouldBlock
171 | Error::OutOfMemory
172 | Error::OutOfGpuMemory { .. }
173 | Error::Backend(_) => ErrorKind::Recoverable,
174
175 Error::NotSupported(_)
176 | Error::InvalidArgument(_)
177 | Error::InvalidState(_)
178 | Error::UnknownParam { .. }
179 | Error::DeviceMismatch { .. }
180 | Error::NoCpuShader
181 | Error::ExplicitSyncInAutoMode
182 | Error::ShaderCompile(_)
183 | Error::FormatMismatch(_)
184 | Error::QueueBackendMismatch { .. } => ErrorKind::Configuration,
185
186 Error::Io(_)
187 | Error::UserKernelPanic { .. }
188 | Error::DeviceLost { .. }
189 | Error::Fatal(_)
190 | Error::FatalBackend(_) => ErrorKind::Fatal,
191 }
192 }
193}
194
195static_assertions::assert_impl_all!(Error: Send, Sync);