Skip to main content

concinnity_core/render/
error.rs

1//! The typed error vocabulary of the `RenderBackend` boundary. Backends map
2//! their native failure codes (VkResult, HRESULT, MTLCommandBuffer status) into
3//! these classes at the detection sites; the frame loop dispatches recovery
4//! policy on the class, never on prose. `Other` carries legacy string errors so
5//! interior call sites can migrate incrementally.
6
7use alloc::string::String;
8use alloc::string::ToString;
9use thiserror::Error;
10
11/// Why the GPU device stopped servicing work, as reported by the backend API.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
13pub enum DeviceLostReason {
14    /// The physical device left (unplugged eGPU, driver upgrade teardown).
15    #[error("device removed")]
16    Removed,
17    /// The device reset underneath the app (TDR without a hang verdict).
18    #[error("device reset")]
19    Reset,
20    /// The OS killed the device after deciding our workload hung it.
21    #[error("device hung")]
22    Hung,
23    /// The presentation surface died; the device may be healthy, but the
24    /// backend cannot present without recreating the surface.
25    #[error("surface lost")]
26    SurfaceLost,
27    /// The backend reported loss without a usable reason code.
28    #[error("unknown")]
29    Unknown,
30}
31
32/// A failure crossing the `RenderBackend` boundary, classified for recovery.
33#[derive(Debug, Clone, PartialEq, Eq, Error)]
34pub enum RenderError {
35    /// The device is gone; no further GPU work can succeed on it. `detail` is
36    /// backend prose for the log (e.g. the `GetDeviceRemovedReason` message).
37    #[error("device lost ({reason}): {detail}")]
38    DeviceLost {
39        /// Why the device was lost.
40        reason: DeviceLostReason,
41        /// Backend prose for the log.
42        detail: String,
43    },
44    /// A GPU allocation failed for lack of device memory.
45    #[error("out of device memory: {0}")]
46    OutOfDeviceMemory(String),
47    /// The swapchain no longer matches the surface; the frame did not present.
48    /// Transient: the backend recreates the swapchain and the next frame
49    /// normally succeeds.
50    #[error("swapchain out of date")]
51    SwapchainOutOfDate,
52    /// A shader failed to compile or link into a pipeline.
53    #[error("shader compile: {0}")]
54    ShaderCompile(String),
55    /// An unclassified failure carrying the original message.
56    #[error("{0}")]
57    Other(String),
58}
59
60/// A backend call's result.
61pub type RenderResult<T> = Result<T, RenderError>;
62
63impl From<String> for RenderError {
64    fn from(message: String) -> Self {
65        RenderError::Other(message)
66    }
67}
68
69impl From<&str> for RenderError {
70    fn from(message: &str) -> Self {
71        RenderError::Other(message.to_string())
72    }
73}
74
75// Bridge for interior call sites still reporting `Result<_, String>`: a typed
76// error crossing one decays to its message, so a detection site can go typed
77// before every caller above it has migrated.
78impl From<RenderError> for String {
79    fn from(error: RenderError) -> Self {
80        error.to_string()
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn string_coerces_to_other() {
90        fn fails() -> RenderResult<()> {
91            Err::<(), String>("boom".to_string())?;
92            Ok(())
93        }
94        assert_eq!(fails(), Err(RenderError::Other("boom".to_string())));
95    }
96
97    #[test]
98    fn display_includes_reason_and_detail() {
99        let e = RenderError::DeviceLost {
100            reason: DeviceLostReason::Hung,
101            detail: "queue submit".to_string(),
102        };
103        assert_eq!(e.to_string(), "device lost (device hung): queue submit");
104    }
105}