1use thiserror::Error;
8
9pub type GpuResult<T> = Result<T, GpuError>;
11
12#[derive(Debug, Error)]
14pub enum GpuError {
15 #[error("No suitable GPU adapter found. Backends tried: {backends}")]
17 NoAdapter { backends: String },
18
19 #[error("Failed to request GPU device: {reason}")]
21 DeviceRequest { reason: String },
22
23 #[error("GPU device lost: {reason}")]
25 DeviceLost { reason: String },
26
27 #[error("Out of GPU memory: requested {requested} bytes, available {available} bytes")]
29 OutOfMemory { requested: u64, available: u64 },
30
31 #[error("Invalid buffer: {reason}")]
33 InvalidBuffer { reason: String },
34
35 #[error("Shader compilation failed: {message}")]
37 ShaderCompilation { message: String },
38
39 #[error("Shader validation failed: {message}")]
41 ShaderValidation { message: String },
42
43 #[error("Failed to create compute pipeline: {reason}")]
45 PipelineCreation { reason: String },
46
47 #[error("Failed to create bind group: {reason}")]
49 BindGroupCreation { reason: String },
50
51 #[error("Failed to map buffer: {reason}")]
53 BufferMapping { reason: String },
54
55 #[error("Compute execution timeout after {seconds} seconds")]
57 ExecutionTimeout { seconds: u64 },
58
59 #[error("Compute execution failed: {reason}")]
61 ExecutionFailed { reason: String },
62
63 #[error("Invalid workgroup size: {actual}, max allowed: {max}")]
65 InvalidWorkgroupSize { actual: u32, max: u32 },
66
67 #[error("Incompatible data types: expected {expected}, got {actual}")]
69 IncompatibleTypes { expected: String, actual: String },
70
71 #[error("Invalid kernel parameters: {reason}")]
73 InvalidKernelParams { reason: String },
74
75 #[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 #[error("Unsupported operation on current GPU: {operation}")]
89 UnsupportedOperation { operation: String },
90
91 #[error("Backend {backend} not available on this platform")]
93 BackendNotAvailable { backend: String },
94
95 #[error("Core library error: {0}")]
97 Core(#[from] oxigdal_core::error::OxiGdalError),
98
99 #[error("IO error: {0}")]
101 Io(#[from] std::io::Error),
102
103 #[error("Async task failed: {0}")]
105 TaskJoin(String),
106
107 #[error("Internal GPU error: {0}")]
109 Internal(String),
110
111 #[error("Unsupported storage texture format: {0}")]
113 UnsupportedFormat(String),
114}
115
116impl GpuError {
117 pub fn no_adapter(backends: impl Into<String>) -> Self {
119 Self::NoAdapter {
120 backends: backends.into(),
121 }
122 }
123
124 pub fn device_request(reason: impl Into<String>) -> Self {
126 Self::DeviceRequest {
127 reason: reason.into(),
128 }
129 }
130
131 pub fn device_lost(reason: impl Into<String>) -> Self {
133 Self::DeviceLost {
134 reason: reason.into(),
135 }
136 }
137
138 pub fn out_of_memory(requested: u64, available: u64) -> Self {
140 Self::OutOfMemory {
141 requested,
142 available,
143 }
144 }
145
146 pub fn invalid_buffer(reason: impl Into<String>) -> Self {
148 Self::InvalidBuffer {
149 reason: reason.into(),
150 }
151 }
152
153 pub fn shader_compilation(message: impl Into<String>) -> Self {
155 Self::ShaderCompilation {
156 message: message.into(),
157 }
158 }
159
160 pub fn shader_validation(message: impl Into<String>) -> Self {
162 Self::ShaderValidation {
163 message: message.into(),
164 }
165 }
166
167 pub fn pipeline_creation(reason: impl Into<String>) -> Self {
169 Self::PipelineCreation {
170 reason: reason.into(),
171 }
172 }
173
174 pub fn bind_group_creation(reason: impl Into<String>) -> Self {
176 Self::BindGroupCreation {
177 reason: reason.into(),
178 }
179 }
180
181 pub fn buffer_mapping(reason: impl Into<String>) -> Self {
183 Self::BufferMapping {
184 reason: reason.into(),
185 }
186 }
187
188 pub fn execution_timeout(seconds: u64) -> Self {
190 Self::ExecutionTimeout { seconds }
191 }
192
193 pub fn execution_failed(reason: impl Into<String>) -> Self {
195 Self::ExecutionFailed {
196 reason: reason.into(),
197 }
198 }
199
200 pub fn invalid_workgroup_size(actual: u32, max: u32) -> Self {
202 Self::InvalidWorkgroupSize { actual, max }
203 }
204
205 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 pub fn invalid_kernel_params(reason: impl Into<String>) -> Self {
215 Self::InvalidKernelParams {
216 reason: reason.into(),
217 }
218 }
219
220 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 pub fn unsupported_operation(operation: impl Into<String>) -> Self {
237 Self::UnsupportedOperation {
238 operation: operation.into(),
239 }
240 }
241
242 pub fn backend_not_available(backend: impl Into<String>) -> Self {
244 Self::BackendNotAvailable {
245 backend: backend.into(),
246 }
247 }
248
249 pub fn internal(message: impl Into<String>) -> Self {
251 Self::Internal(message.into())
252 }
253
254 pub fn is_recoverable(&self) -> bool {
256 matches!(
257 self,
258 Self::ExecutionTimeout { .. }
259 | Self::BufferMapping { .. }
260 | Self::InvalidKernelParams { .. }
261 )
262 }
263
264 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}