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