Skip to main content

oxigdal_gpu/
error.rs

1//! GPU error types for OxiGDAL.
2//!
3//! This module provides comprehensive error handling for GPU operations,
4//! including device initialization, buffer management, shader compilation,
5//! and compute execution errors.
6
7use thiserror::Error;
8
9/// Result type for GPU operations.
10pub type GpuResult<T> = Result<T, GpuError>;
11
12/// Errors that can occur during GPU operations.
13#[derive(Debug, Error)]
14pub enum GpuError {
15    /// No suitable GPU adapter found.
16    #[error("No suitable GPU adapter found. Backends tried: {backends}")]
17    NoAdapter { backends: String },
18
19    /// Failed to request GPU device.
20    #[error("Failed to request GPU device: {reason}")]
21    DeviceRequest { reason: String },
22
23    /// GPU device lost or disconnected.
24    #[error("GPU device lost: {reason}")]
25    DeviceLost { reason: String },
26
27    /// Out of GPU memory.
28    #[error("Out of GPU memory: requested {requested} bytes, available {available} bytes")]
29    OutOfMemory { requested: u64, available: u64 },
30
31    /// Invalid buffer size or alignment.
32    #[error("Invalid buffer: {reason}")]
33    InvalidBuffer { reason: String },
34
35    /// Shader compilation error.
36    #[error("Shader compilation failed: {message}")]
37    ShaderCompilation { message: String },
38
39    /// Shader validation error.
40    #[error("Shader validation failed: {message}")]
41    ShaderValidation { message: String },
42
43    /// Compute pipeline creation error.
44    #[error("Failed to create compute pipeline: {reason}")]
45    PipelineCreation { reason: String },
46
47    /// Bind group creation error.
48    #[error("Failed to create bind group: {reason}")]
49    BindGroupCreation { reason: String },
50
51    /// Buffer mapping error.
52    #[error("Failed to map buffer: {reason}")]
53    BufferMapping { reason: String },
54
55    /// Compute execution timeout.
56    #[error("Compute execution timeout after {seconds} seconds")]
57    ExecutionTimeout { seconds: u64 },
58
59    /// Compute execution error.
60    #[error("Compute execution failed: {reason}")]
61    ExecutionFailed { reason: String },
62
63    /// Invalid workgroup size.
64    #[error("Invalid workgroup size: {actual}, max allowed: {max}")]
65    InvalidWorkgroupSize { actual: u32, max: u32 },
66
67    /// Incompatible data types.
68    #[error("Incompatible data types: expected {expected}, got {actual}")]
69    IncompatibleTypes { expected: String, actual: String },
70
71    /// Invalid kernel parameters.
72    #[error("Invalid kernel parameters: {reason}")]
73    InvalidKernelParams { reason: String },
74
75    /// Raster dimension mismatch.
76    #[error(
77        "Raster dimension mismatch: expected {expected_width}x{expected_height}, \
78         got {actual_width}x{actual_height}"
79    )]
80    DimensionMismatch {
81        expected_width: u32,
82        expected_height: u32,
83        actual_width: u32,
84        actual_height: u32,
85    },
86
87    /// Unsupported operation on current GPU.
88    #[error("Unsupported operation on current GPU: {operation}")]
89    UnsupportedOperation { operation: String },
90
91    /// Backend not available.
92    #[error("Backend {backend} not available on this platform")]
93    BackendNotAvailable { backend: String },
94
95    /// Core library error.
96    #[error("Core library error: {0}")]
97    Core(#[from] oxigdal_core::error::OxiGdalError),
98
99    /// IO error during GPU operations.
100    #[error("IO error: {0}")]
101    Io(#[from] std::io::Error),
102
103    /// Async task join error.
104    #[error("Async task failed: {0}")]
105    TaskJoin(String),
106
107    /// Internal error (should not happen).
108    #[error("Internal GPU error: {0}")]
109    Internal(String),
110
111    /// Unsupported texture format for storage-texture operations.
112    #[error("Unsupported storage texture format: {0}")]
113    UnsupportedFormat(String),
114}
115
116impl GpuError {
117    /// Create a new adapter not found error.
118    pub fn no_adapter(backends: impl Into<String>) -> Self {
119        Self::NoAdapter {
120            backends: backends.into(),
121        }
122    }
123
124    /// Create a new device request error.
125    pub fn device_request(reason: impl Into<String>) -> Self {
126        Self::DeviceRequest {
127            reason: reason.into(),
128        }
129    }
130
131    /// Create a new device lost error.
132    pub fn device_lost(reason: impl Into<String>) -> Self {
133        Self::DeviceLost {
134            reason: reason.into(),
135        }
136    }
137
138    /// Create a new out of memory error.
139    pub fn out_of_memory(requested: u64, available: u64) -> Self {
140        Self::OutOfMemory {
141            requested,
142            available,
143        }
144    }
145
146    /// Create a new invalid buffer error.
147    pub fn invalid_buffer(reason: impl Into<String>) -> Self {
148        Self::InvalidBuffer {
149            reason: reason.into(),
150        }
151    }
152
153    /// Create a new shader compilation error.
154    pub fn shader_compilation(message: impl Into<String>) -> Self {
155        Self::ShaderCompilation {
156            message: message.into(),
157        }
158    }
159
160    /// Create a new shader validation error.
161    pub fn shader_validation(message: impl Into<String>) -> Self {
162        Self::ShaderValidation {
163            message: message.into(),
164        }
165    }
166
167    /// Create a new pipeline creation error.
168    pub fn pipeline_creation(reason: impl Into<String>) -> Self {
169        Self::PipelineCreation {
170            reason: reason.into(),
171        }
172    }
173
174    /// Create a new bind group creation error.
175    pub fn bind_group_creation(reason: impl Into<String>) -> Self {
176        Self::BindGroupCreation {
177            reason: reason.into(),
178        }
179    }
180
181    /// Create a new buffer mapping error.
182    pub fn buffer_mapping(reason: impl Into<String>) -> Self {
183        Self::BufferMapping {
184            reason: reason.into(),
185        }
186    }
187
188    /// Create a new execution timeout error.
189    pub fn execution_timeout(seconds: u64) -> Self {
190        Self::ExecutionTimeout { seconds }
191    }
192
193    /// Create a new execution failed error.
194    pub fn execution_failed(reason: impl Into<String>) -> Self {
195        Self::ExecutionFailed {
196            reason: reason.into(),
197        }
198    }
199
200    /// Create a new invalid workgroup size error.
201    pub fn invalid_workgroup_size(actual: u32, max: u32) -> Self {
202        Self::InvalidWorkgroupSize { actual, max }
203    }
204
205    /// Create a new incompatible types error.
206    pub fn incompatible_types(expected: impl Into<String>, actual: impl Into<String>) -> Self {
207        Self::IncompatibleTypes {
208            expected: expected.into(),
209            actual: actual.into(),
210        }
211    }
212
213    /// Create a new invalid kernel parameters error.
214    pub fn invalid_kernel_params(reason: impl Into<String>) -> Self {
215        Self::InvalidKernelParams {
216            reason: reason.into(),
217        }
218    }
219
220    /// Create a new dimension mismatch error.
221    pub fn dimension_mismatch(
222        expected_width: u32,
223        expected_height: u32,
224        actual_width: u32,
225        actual_height: u32,
226    ) -> Self {
227        Self::DimensionMismatch {
228            expected_width,
229            expected_height,
230            actual_width,
231            actual_height,
232        }
233    }
234
235    /// Create a new unsupported operation error.
236    pub fn unsupported_operation(operation: impl Into<String>) -> Self {
237        Self::UnsupportedOperation {
238            operation: operation.into(),
239        }
240    }
241
242    /// Create a new backend not available error.
243    pub fn backend_not_available(backend: impl Into<String>) -> Self {
244        Self::BackendNotAvailable {
245            backend: backend.into(),
246        }
247    }
248
249    /// Create a new internal error.
250    pub fn internal(message: impl Into<String>) -> Self {
251        Self::Internal(message.into())
252    }
253
254    /// Check if this error is recoverable.
255    pub fn is_recoverable(&self) -> bool {
256        matches!(
257            self,
258            Self::ExecutionTimeout { .. }
259                | Self::BufferMapping { .. }
260                | Self::InvalidKernelParams { .. }
261        )
262    }
263
264    /// Check if this error suggests falling back to CPU.
265    pub fn should_fallback_to_cpu(&self) -> bool {
266        matches!(
267            self,
268            Self::NoAdapter { .. }
269                | Self::DeviceLost { .. }
270                | Self::OutOfMemory { .. }
271                | Self::UnsupportedOperation { .. }
272                | Self::BackendNotAvailable { .. }
273        )
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn test_error_creation() {
283        let err = GpuError::no_adapter("Vulkan, Metal, DX12");
284        assert!(matches!(err, GpuError::NoAdapter { .. }));
285        assert!(err.should_fallback_to_cpu());
286
287        let err = GpuError::out_of_memory(1_000_000_000, 500_000_000);
288        assert!(matches!(err, GpuError::OutOfMemory { .. }));
289        assert!(err.should_fallback_to_cpu());
290    }
291
292    #[test]
293    fn test_recoverable_errors() {
294        let err = GpuError::execution_timeout(30);
295        assert!(err.is_recoverable());
296
297        let err = GpuError::device_lost("GPU reset");
298        assert!(!err.is_recoverable());
299    }
300
301    #[test]
302    fn test_error_messages() {
303        let err = GpuError::dimension_mismatch(1024, 768, 512, 512);
304        let msg = err.to_string();
305        assert!(msg.contains("1024"));
306        assert!(msg.contains("768"));
307        assert!(msg.contains("512"));
308    }
309}