Skip to main content

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}