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
112impl GpuError {
113    /// Create a new adapter not found error.
114    pub fn no_adapter(backends: impl Into<String>) -> Self {
115        Self::NoAdapter {
116            backends: backends.into(),
117        }
118    }
119
120    /// Create a new device request error.
121    pub fn device_request(reason: impl Into<String>) -> Self {
122        Self::DeviceRequest {
123            reason: reason.into(),
124        }
125    }
126
127    /// Create a new device lost error.
128    pub fn device_lost(reason: impl Into<String>) -> Self {
129        Self::DeviceLost {
130            reason: reason.into(),
131        }
132    }
133
134    /// Create a new out of memory error.
135    pub fn out_of_memory(requested: u64, available: u64) -> Self {
136        Self::OutOfMemory {
137            requested,
138            available,
139        }
140    }
141
142    /// Create a new invalid buffer error.
143    pub fn invalid_buffer(reason: impl Into<String>) -> Self {
144        Self::InvalidBuffer {
145            reason: reason.into(),
146        }
147    }
148
149    /// Create a new shader compilation error.
150    pub fn shader_compilation(message: impl Into<String>) -> Self {
151        Self::ShaderCompilation {
152            message: message.into(),
153        }
154    }
155
156    /// Create a new shader validation error.
157    pub fn shader_validation(message: impl Into<String>) -> Self {
158        Self::ShaderValidation {
159            message: message.into(),
160        }
161    }
162
163    /// Create a new pipeline creation error.
164    pub fn pipeline_creation(reason: impl Into<String>) -> Self {
165        Self::PipelineCreation {
166            reason: reason.into(),
167        }
168    }
169
170    /// Create a new bind group creation error.
171    pub fn bind_group_creation(reason: impl Into<String>) -> Self {
172        Self::BindGroupCreation {
173            reason: reason.into(),
174        }
175    }
176
177    /// Create a new buffer mapping error.
178    pub fn buffer_mapping(reason: impl Into<String>) -> Self {
179        Self::BufferMapping {
180            reason: reason.into(),
181        }
182    }
183
184    /// Create a new execution timeout error.
185    pub fn execution_timeout(seconds: u64) -> Self {
186        Self::ExecutionTimeout { seconds }
187    }
188
189    /// Create a new execution failed error.
190    pub fn execution_failed(reason: impl Into<String>) -> Self {
191        Self::ExecutionFailed {
192            reason: reason.into(),
193        }
194    }
195
196    /// Create a new invalid workgroup size error.
197    pub fn invalid_workgroup_size(actual: u32, max: u32) -> Self {
198        Self::InvalidWorkgroupSize { actual, max }
199    }
200
201    /// Create a new incompatible types error.
202    pub fn incompatible_types(expected: impl Into<String>, actual: impl Into<String>) -> Self {
203        Self::IncompatibleTypes {
204            expected: expected.into(),
205            actual: actual.into(),
206        }
207    }
208
209    /// Create a new invalid kernel parameters error.
210    pub fn invalid_kernel_params(reason: impl Into<String>) -> Self {
211        Self::InvalidKernelParams {
212            reason: reason.into(),
213        }
214    }
215
216    /// Create a new dimension mismatch error.
217    pub fn dimension_mismatch(
218        expected_width: u32,
219        expected_height: u32,
220        actual_width: u32,
221        actual_height: u32,
222    ) -> Self {
223        Self::DimensionMismatch {
224            expected_width,
225            expected_height,
226            actual_width,
227            actual_height,
228        }
229    }
230
231    /// Create a new unsupported operation error.
232    pub fn unsupported_operation(operation: impl Into<String>) -> Self {
233        Self::UnsupportedOperation {
234            operation: operation.into(),
235        }
236    }
237
238    /// Create a new backend not available error.
239    pub fn backend_not_available(backend: impl Into<String>) -> Self {
240        Self::BackendNotAvailable {
241            backend: backend.into(),
242        }
243    }
244
245    /// Create a new internal error.
246    pub fn internal(message: impl Into<String>) -> Self {
247        Self::Internal(message.into())
248    }
249
250    /// Check if this error is recoverable.
251    pub fn is_recoverable(&self) -> bool {
252        matches!(
253            self,
254            Self::ExecutionTimeout { .. }
255                | Self::BufferMapping { .. }
256                | Self::InvalidKernelParams { .. }
257        )
258    }
259
260    /// Check if this error suggests falling back to CPU.
261    pub fn should_fallback_to_cpu(&self) -> bool {
262        matches!(
263            self,
264            Self::NoAdapter { .. }
265                | Self::DeviceLost { .. }
266                | Self::OutOfMemory { .. }
267                | Self::UnsupportedOperation { .. }
268                | Self::BackendNotAvailable { .. }
269        )
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn test_error_creation() {
279        let err = GpuError::no_adapter("Vulkan, Metal, DX12");
280        assert!(matches!(err, GpuError::NoAdapter { .. }));
281        assert!(err.should_fallback_to_cpu());
282
283        let err = GpuError::out_of_memory(1_000_000_000, 500_000_000);
284        assert!(matches!(err, GpuError::OutOfMemory { .. }));
285        assert!(err.should_fallback_to_cpu());
286    }
287
288    #[test]
289    fn test_recoverable_errors() {
290        let err = GpuError::execution_timeout(30);
291        assert!(err.is_recoverable());
292
293        let err = GpuError::device_lost("GPU reset");
294        assert!(!err.is_recoverable());
295    }
296
297    #[test]
298    fn test_error_messages() {
299        let err = GpuError::dimension_mismatch(1024, 768, 512, 512);
300        let msg = err.to_string();
301        assert!(msg.contains("1024"));
302        assert!(msg.contains("768"));
303        assert!(msg.contains("512"));
304    }
305}