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
112impl GpuError {
113 pub fn no_adapter(backends: impl Into<String>) -> Self {
115 Self::NoAdapter {
116 backends: backends.into(),
117 }
118 }
119
120 pub fn device_request(reason: impl Into<String>) -> Self {
122 Self::DeviceRequest {
123 reason: reason.into(),
124 }
125 }
126
127 pub fn device_lost(reason: impl Into<String>) -> Self {
129 Self::DeviceLost {
130 reason: reason.into(),
131 }
132 }
133
134 pub fn out_of_memory(requested: u64, available: u64) -> Self {
136 Self::OutOfMemory {
137 requested,
138 available,
139 }
140 }
141
142 pub fn invalid_buffer(reason: impl Into<String>) -> Self {
144 Self::InvalidBuffer {
145 reason: reason.into(),
146 }
147 }
148
149 pub fn shader_compilation(message: impl Into<String>) -> Self {
151 Self::ShaderCompilation {
152 message: message.into(),
153 }
154 }
155
156 pub fn shader_validation(message: impl Into<String>) -> Self {
158 Self::ShaderValidation {
159 message: message.into(),
160 }
161 }
162
163 pub fn pipeline_creation(reason: impl Into<String>) -> Self {
165 Self::PipelineCreation {
166 reason: reason.into(),
167 }
168 }
169
170 pub fn bind_group_creation(reason: impl Into<String>) -> Self {
172 Self::BindGroupCreation {
173 reason: reason.into(),
174 }
175 }
176
177 pub fn buffer_mapping(reason: impl Into<String>) -> Self {
179 Self::BufferMapping {
180 reason: reason.into(),
181 }
182 }
183
184 pub fn execution_timeout(seconds: u64) -> Self {
186 Self::ExecutionTimeout { seconds }
187 }
188
189 pub fn execution_failed(reason: impl Into<String>) -> Self {
191 Self::ExecutionFailed {
192 reason: reason.into(),
193 }
194 }
195
196 pub fn invalid_workgroup_size(actual: u32, max: u32) -> Self {
198 Self::InvalidWorkgroupSize { actual, max }
199 }
200
201 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 pub fn invalid_kernel_params(reason: impl Into<String>) -> Self {
211 Self::InvalidKernelParams {
212 reason: reason.into(),
213 }
214 }
215
216 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 pub fn unsupported_operation(operation: impl Into<String>) -> Self {
233 Self::UnsupportedOperation {
234 operation: operation.into(),
235 }
236 }
237
238 pub fn backend_not_available(backend: impl Into<String>) -> Self {
240 Self::BackendNotAvailable {
241 backend: backend.into(),
242 }
243 }
244
245 pub fn internal(message: impl Into<String>) -> Self {
247 Self::Internal(message.into())
248 }
249
250 pub fn is_recoverable(&self) -> bool {
252 matches!(
253 self,
254 Self::ExecutionTimeout { .. }
255 | Self::BufferMapping { .. }
256 | Self::InvalidKernelParams { .. }
257 )
258 }
259
260 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}