cgpu 0.1.0

A tunable GPU compute executor with automatic CPU fallback, byte-based batching, and inline shader generation.
Documentation
use std::error::Error;
use std::fmt::{self, Display, Formatter};

use crate::job::JobError;

#[derive(Debug)]
pub enum ExecutorInitError {
    NoGpuAvailable,
    #[cfg(feature = "gpu")]
    DeviceRequestFailed(wgpu::RequestDeviceError),
    #[cfg(not(feature = "gpu"))]
    DeviceRequestFailed(String),
    InvalidConfig(String),
}

impl Display for ExecutorInitError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::NoGpuAvailable => f.write_str("no GPU adapter is available"),
            Self::DeviceRequestFailed(error) => write!(f, "failed to request GPU device: {error}"),
            Self::InvalidConfig(message) => write!(f, "invalid executor config: {message}"),
        }
    }
}

impl Error for ExecutorInitError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            #[cfg(feature = "gpu")]
            Self::DeviceRequestFailed(error) => Some(error),
            _ => None,
        }
    }
}

#[derive(Debug)]
pub enum ExecutionError {
    JobEncoding(JobError),
    Gpu(GpuError),
    CpuFallback(JobError),
    AllBatchesFailed { attempted: usize, failed: usize },
}

impl Display for ExecutionError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::JobEncoding(error) => write!(f, "job encoding failed: {error}"),
            Self::Gpu(error) => write!(f, "GPU execution failed: {error}"),
            Self::CpuFallback(error) => write!(f, "CPU fallback failed: {error}"),
            Self::AllBatchesFailed { attempted, failed } => {
                write!(
                    f,
                    "all batches failed: attempted={attempted} failed={failed}"
                )
            }
        }
    }
}

impl Error for ExecutionError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::JobEncoding(error) | Self::CpuFallback(error) => Some(error),
            Self::Gpu(error) => Some(error),
            Self::AllBatchesFailed { .. } => None,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GpuFailure {
    BackendUnavailable,
    DeviceLost,
    OutOfMemory,
    ShaderCompilation,
    QueueSubmission,
    Readback,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GpuError {
    InvalidBudget(&'static str),
    ItemExceedsSafeBudget {
        item_index: usize,
        item_bytes: usize,
        safe_vram_bytes: usize,
    },
    InvalidBatchPlan(&'static str),
    GpuFailure(GpuFailure),
    GpuFailureDetail {
        failure: GpuFailure,
        message: String,
    },
    BackendUnavailable,
    DeviceLost,
    OutOfMemory {
        requested: usize,
        available: usize,
    },
    ShaderCompilation {
        message: String,
    },
    QueueSubmission(String),
    ReadbackTimeout {
        ms: u64,
    },
    BudgetExceeded {
        estimated: usize,
        budget: usize,
    },
}

impl Display for GpuError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidBudget(message) => write!(f, "invalid GPU memory budget: {message}"),
            Self::ItemExceedsSafeBudget {
                item_index,
                item_bytes,
                safe_vram_bytes,
            } => write!(
                f,
                "item {item_index} requires {item_bytes} bytes, above safe VRAM budget {safe_vram_bytes}"
            ),
            Self::InvalidBatchPlan(message) => write!(f, "invalid GPU batch plan: {message}"),
            Self::GpuFailure(failure) => write!(f, "GPU failure: {failure:?}"),
            Self::GpuFailureDetail { failure, message } => {
                write!(f, "GPU failure: {failure:?}: {message}")
            }
            Self::BackendUnavailable => f.write_str("GPU backend is unavailable"),
            Self::DeviceLost => f.write_str("GPU device was lost"),
            Self::OutOfMemory {
                requested,
                available,
            } => write!(
                f,
                "GPU out of memory: requested={requested} available={available}"
            ),
            Self::ShaderCompilation { message } => {
                write!(f, "shader compilation failed: {message}")
            }
            Self::QueueSubmission(message) => write!(f, "queue submission failed: {message}"),
            Self::ReadbackTimeout { ms } => write!(f, "GPU readback timed out after {ms} ms"),
            Self::BudgetExceeded { estimated, budget } => {
                write!(
                    f,
                    "GPU budget exceeded: estimated={estimated} budget={budget}"
                )
            }
        }
    }
}

impl Error for GpuError {}

impl From<GpuFailure> for GpuError {
    fn from(value: GpuFailure) -> Self {
        Self::GpuFailure(value)
    }
}