Skip to main content

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);