use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum GpuError {
#[error("GPU adapter not found")]
AdapterNotFound,
#[error("GPU device request failed")]
DeviceRequest(#[source] wgpu::RequestDeviceError),
#[error("surface configuration failed: {0}")]
SurfaceConfig(String),
#[error("surface texture acquisition timed out")]
SurfaceTimeout,
#[error("surface texture outdated — resize needed")]
SurfaceOutdated,
#[error("surface lost — reconfiguration required")]
SurfaceLost,
#[error("shader compilation failed: {0}")]
Shader(String),
#[error("pipeline creation failed: {0}")]
Pipeline(String),
#[error("texture error: {0}")]
Texture(String),
#[cfg(feature = "image")]
#[error("image decoding failed")]
ImageDecode(#[source] image::ImageError),
#[error("buffer error: {0}")]
Buffer(String),
#[error("GPU readback mapping failed")]
ReadbackMap(#[source] wgpu::BufferAsyncError),
#[error("GPU readback channel closed")]
ReadbackChannel,
#[error("dispatch workgroup count {actual} exceeds device limit {limit} on {axis} axis")]
WorkgroupLimitExceeded {
axis: &'static str,
actual: u32,
limit: u32,
},
#[error("texture dimension {actual} exceeds device limit {limit}")]
TextureDimensionExceeded { actual: u32, limit: u32 },
#[error(transparent)]
Other(#[from] Box<dyn std::error::Error + Send + Sync>),
}
impl GpuError {
#[must_use]
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::SurfaceTimeout | Self::SurfaceOutdated | Self::SurfaceConfig(_)
)
}
}
pub type Result<T> = std::result::Result<T, GpuError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_display() {
let err = GpuError::AdapterNotFound;
assert_eq!(err.to_string(), "GPU adapter not found");
}
#[test]
fn error_surface_variants() {
let timeout = GpuError::SurfaceTimeout;
assert!(timeout.to_string().contains("timed out"));
let outdated = GpuError::SurfaceOutdated;
assert!(outdated.to_string().contains("outdated"));
let lost = GpuError::SurfaceLost;
assert!(lost.to_string().contains("lost"));
}
#[test]
fn error_is_recoverable() {
assert!(GpuError::SurfaceTimeout.is_recoverable());
assert!(GpuError::SurfaceOutdated.is_recoverable());
assert!(GpuError::SurfaceConfig("test".into()).is_recoverable());
assert!(!GpuError::AdapterNotFound.is_recoverable());
assert!(!GpuError::SurfaceLost.is_recoverable());
assert!(!GpuError::ReadbackChannel.is_recoverable());
assert!(!GpuError::Shader("test".into()).is_recoverable());
}
#[test]
fn error_readback_variants() {
let channel = GpuError::ReadbackChannel;
assert!(channel.to_string().contains("channel"));
let map = GpuError::ReadbackMap(wgpu::BufferAsyncError);
assert!(map.to_string().contains("mapping"));
}
#[test]
fn error_workgroup_limit() {
let err = GpuError::WorkgroupLimitExceeded {
axis: "x",
actual: 100_000,
limit: 65535,
};
let s = err.to_string();
assert!(s.contains("100000"));
assert!(s.contains("65535"));
assert!(s.contains("x"));
}
#[test]
fn error_texture_dimension() {
let err = GpuError::TextureDimensionExceeded {
actual: 32768,
limit: 16384,
};
assert!(err.to_string().contains("32768"));
}
#[test]
fn error_other_variant() {
let inner: Box<dyn std::error::Error + Send + Sync> = "custom error".into();
let err = GpuError::Other(inner);
assert!(err.to_string().contains("custom error"));
}
#[test]
fn error_is_send_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<GpuError>();
assert_sync::<GpuError>();
}
#[test]
fn error_source_chain() {
use std::error::Error;
let map = GpuError::ReadbackMap(wgpu::BufferAsyncError);
assert!(map.source().is_some());
assert!(GpuError::AdapterNotFound.source().is_none());
assert!(GpuError::ReadbackChannel.source().is_none());
assert!(GpuError::SurfaceTimeout.source().is_none());
}
}