retroglyph_wgpu/error.rs
1//! [`SurfaceError`]: the wgpu backend's surface-lifecycle error type.
2
3use std::fmt;
4
5/// A failure creating or driving the wgpu device and surface.
6///
7/// The variants split along the one distinction the event loop acts on: whether retrying is worth
8/// anything. [`RecoverableError`](retroglyph_window::RecoverableError) reads that split, and
9/// `retroglyph_window::winit::run` uses it to decide between logging-and-continuing and exiting.
10#[derive(Debug)]
11#[non_exhaustive]
12pub enum SurfaceError {
13 /// No adapter, device, or surface could be created for this window.
14 ///
15 /// Treated as fatal: without a device there is nothing to draw with, and retrying the same
16 /// request against the same hardware will fail the same way. Usually means the requested
17 /// backends are all unavailable (no Vulkan/Metal/D3D12 driver), which
18 /// [`WGPU_BACKEND`](crate#environment-variables) can sometimes work around.
19 Init(String),
20 /// Acquiring the next frame from the swap chain failed for a reason reconfiguring can't fix.
21 ///
22 /// Treated as recoverable so the event loop's consecutive-failure heuristic can retry: a
23 /// timed-out or occluded frame usually resolves on its own, and the crate reconfigures the
24 /// surface itself for the outdated/lost cases before ever returning this.
25 Frame(String),
26 /// The device was lost (a GPU reset, a driver update, or an eviction) and every resource
27 /// created from it is now invalid.
28 ///
29 /// Treated as recoverable, because the recovery that helps is the one the event loop performs:
30 /// after enough consecutive present failures it calls
31 /// [`Presenter::init_surface`](retroglyph_window::Presenter::init_surface) again, which
32 /// rebuilds the adapter, device, surface, and every GPU resource from scratch. Reporting this
33 /// as unrecoverable would skip that path, leaving the loop logging the same failure every
34 /// frame with nothing rebuilding.
35 DeviceLost(String),
36}
37
38impl fmt::Display for SurfaceError {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Self::Init(msg) => write!(f, "wgpu surface init: {msg}"),
42 Self::Frame(msg) => write!(f, "wgpu frame acquire: {msg}"),
43 Self::DeviceLost(msg) => write!(f, "wgpu device lost: {msg}"),
44 }
45 }
46}
47
48impl std::error::Error for SurfaceError {}
49
50impl retroglyph_window::RecoverableError for SurfaceError {
51 fn is_recoverable(&self) -> bool {
52 // Only a failed init is unrecoverable: it is the very call a retry would make again, so
53 // there is nothing left to escalate to.
54 !matches!(self, Self::Init(_))
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::SurfaceError;
61 use retroglyph_window::RecoverableError as _;
62
63 #[test]
64 fn only_init_failures_are_unrecoverable() {
65 assert!(!SurfaceError::Init("no adapter".into()).is_recoverable());
66 assert!(SurfaceError::Frame("timeout".into()).is_recoverable());
67 // A lost device is recoverable *through `init_surface`*, which the event loop only reaches
68 // for a recoverable error; reporting `false` here would strand the loop instead.
69 assert!(SurfaceError::DeviceLost("reset".into()).is_recoverable());
70 }
71
72 #[test]
73 fn display_names_the_stage_and_keeps_the_message() {
74 assert_eq!(
75 SurfaceError::Init("no adapter".into()).to_string(),
76 "wgpu surface init: no adapter"
77 );
78 assert_eq!(
79 SurfaceError::Frame("timeout".into()).to_string(),
80 "wgpu frame acquire: timeout"
81 );
82 assert_eq!(
83 SurfaceError::DeviceLost("reset".into()).to_string(),
84 "wgpu device lost: reset"
85 );
86 }
87}