use thiserror::Error;
pub type GpuResult<T> = Result<T, GpuError>;
#[derive(Debug, Error)]
pub enum GpuError {
#[error("No suitable GPU adapter found. Backends tried: {backends}")]
NoAdapter { backends: String },
#[error("Failed to request GPU device: {reason}")]
DeviceRequest { reason: String },
#[error("GPU device lost: {reason}")]
DeviceLost { reason: String },
#[error("Out of GPU memory: requested {requested} bytes, available {available} bytes")]
OutOfMemory { requested: u64, available: u64 },
#[error("Invalid buffer: {reason}")]
InvalidBuffer { reason: String },
#[error("Shader compilation failed: {message}")]
ShaderCompilation { message: String },
#[error("Shader validation failed: {message}")]
ShaderValidation { message: String },
#[error("Failed to create compute pipeline: {reason}")]
PipelineCreation { reason: String },
#[error("Failed to create bind group: {reason}")]
BindGroupCreation { reason: String },
#[error("Failed to map buffer: {reason}")]
BufferMapping { reason: String },
#[error("Compute execution timeout after {seconds} seconds")]
ExecutionTimeout { seconds: u64 },
#[error("Compute execution failed: {reason}")]
ExecutionFailed { reason: String },
#[error("Invalid workgroup size: {actual}, max allowed: {max}")]
InvalidWorkgroupSize { actual: u32, max: u32 },
#[error("Incompatible data types: expected {expected}, got {actual}")]
IncompatibleTypes { expected: String, actual: String },
#[error("Invalid kernel parameters: {reason}")]
InvalidKernelParams { reason: String },
#[error(
"Raster dimension mismatch: expected {expected_width}x{expected_height}, \
got {actual_width}x{actual_height}"
)]
DimensionMismatch {
expected_width: u32,
expected_height: u32,
actual_width: u32,
actual_height: u32,
},
#[error("Unsupported operation on current GPU: {operation}")]
UnsupportedOperation { operation: String },
#[error("Backend {backend} not available on this platform")]
BackendNotAvailable { backend: String },
#[error("Core library error: {0}")]
Core(#[from] oxigdal_core::error::OxiGdalError),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Async task failed: {0}")]
TaskJoin(String),
#[error("Internal GPU error: {0}")]
Internal(String),
}
impl GpuError {
pub fn no_adapter(backends: impl Into<String>) -> Self {
Self::NoAdapter {
backends: backends.into(),
}
}
pub fn device_request(reason: impl Into<String>) -> Self {
Self::DeviceRequest {
reason: reason.into(),
}
}
pub fn device_lost(reason: impl Into<String>) -> Self {
Self::DeviceLost {
reason: reason.into(),
}
}
pub fn out_of_memory(requested: u64, available: u64) -> Self {
Self::OutOfMemory {
requested,
available,
}
}
pub fn invalid_buffer(reason: impl Into<String>) -> Self {
Self::InvalidBuffer {
reason: reason.into(),
}
}
pub fn shader_compilation(message: impl Into<String>) -> Self {
Self::ShaderCompilation {
message: message.into(),
}
}
pub fn shader_validation(message: impl Into<String>) -> Self {
Self::ShaderValidation {
message: message.into(),
}
}
pub fn pipeline_creation(reason: impl Into<String>) -> Self {
Self::PipelineCreation {
reason: reason.into(),
}
}
pub fn bind_group_creation(reason: impl Into<String>) -> Self {
Self::BindGroupCreation {
reason: reason.into(),
}
}
pub fn buffer_mapping(reason: impl Into<String>) -> Self {
Self::BufferMapping {
reason: reason.into(),
}
}
pub fn execution_timeout(seconds: u64) -> Self {
Self::ExecutionTimeout { seconds }
}
pub fn execution_failed(reason: impl Into<String>) -> Self {
Self::ExecutionFailed {
reason: reason.into(),
}
}
pub fn invalid_workgroup_size(actual: u32, max: u32) -> Self {
Self::InvalidWorkgroupSize { actual, max }
}
pub fn incompatible_types(expected: impl Into<String>, actual: impl Into<String>) -> Self {
Self::IncompatibleTypes {
expected: expected.into(),
actual: actual.into(),
}
}
pub fn invalid_kernel_params(reason: impl Into<String>) -> Self {
Self::InvalidKernelParams {
reason: reason.into(),
}
}
pub fn dimension_mismatch(
expected_width: u32,
expected_height: u32,
actual_width: u32,
actual_height: u32,
) -> Self {
Self::DimensionMismatch {
expected_width,
expected_height,
actual_width,
actual_height,
}
}
pub fn unsupported_operation(operation: impl Into<String>) -> Self {
Self::UnsupportedOperation {
operation: operation.into(),
}
}
pub fn backend_not_available(backend: impl Into<String>) -> Self {
Self::BackendNotAvailable {
backend: backend.into(),
}
}
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal(message.into())
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::ExecutionTimeout { .. }
| Self::BufferMapping { .. }
| Self::InvalidKernelParams { .. }
)
}
pub fn should_fallback_to_cpu(&self) -> bool {
matches!(
self,
Self::NoAdapter { .. }
| Self::DeviceLost { .. }
| Self::OutOfMemory { .. }
| Self::UnsupportedOperation { .. }
| Self::BackendNotAvailable { .. }
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = GpuError::no_adapter("Vulkan, Metal, DX12");
assert!(matches!(err, GpuError::NoAdapter { .. }));
assert!(err.should_fallback_to_cpu());
let err = GpuError::out_of_memory(1_000_000_000, 500_000_000);
assert!(matches!(err, GpuError::OutOfMemory { .. }));
assert!(err.should_fallback_to_cpu());
}
#[test]
fn test_recoverable_errors() {
let err = GpuError::execution_timeout(30);
assert!(err.is_recoverable());
let err = GpuError::device_lost("GPU reset");
assert!(!err.is_recoverable());
}
#[test]
fn test_error_messages() {
let err = GpuError::dimension_mismatch(1024, 768, 512, 512);
let msg = err.to_string();
assert!(msg.contains("1024"));
assert!(msg.contains("768"));
assert!(msg.contains("512"));
}
}