shdrlib 0.1.5

A three-tiered Vulkan shader compilation and rendering framework built in pure Rust
Documentation
//! Error types for the EX tier
//!
//! EX tier errors wrap CORE tier errors and add additional context specific to
//! manager operations. All errors include helpful context and can be chained
//! for traceback information.

use thiserror::Error;
use std::fmt;

/// Errors that can occur during RuntimeManager operations
#[derive(Error, Debug)]
pub enum RuntimeError {
    /// Instance creation failed
    #[error("Instance creation failed: {0}\n\nContext: This happens during Vulkan initialization.\nSuggestions:\n  • Check that Vulkan runtime is installed\n  • Update GPU drivers\n  • Enable/disable validation layers")]
    InstanceCreationFailed(#[from] crate::core::InstanceError),

    /// Device creation failed
    #[error("Device creation failed: {0}\n\nContext: Failed to create logical device from physical GPU.\nSuggestions:\n  • Check device extensions are supported\n  • Verify required features are available\n  • Try a different physical device")]
    DeviceCreationFailed(#[from] crate::core::DeviceError),

    /// No graphics queue family found on selected device
    #[error("No graphics queue family found on selected device\n\nContext: The selected GPU doesn't support graphics operations.\nSuggestions:\n  • Verify GPU supports Vulkan graphics\n  • Try a different physical device\n  • Check if device is compute-only")]
    NoGraphicsQueue,

    /// No suitable physical device found
    #[error("No suitable physical device found (total devices: {0})\n\nContext: Found {0} physical device(s) but none met requirements.\nSuggestions:\n  • Update GPU drivers\n  • Relax device selection criteria\n  • Check if integrated GPU is available\n  • Verify Vulkan 1.3 support")]
    NoSuitableDevice(usize),

    /// Fence operation failed
    #[error("Fence synchronization failed: {0}\n\nContext: Failed to synchronize with GPU using fence.\nSuggestions:\n  • Check for device loss\n  • Verify timeout is reasonable\n  • Enable validation layers for details")]
    FenceSyncFailed(#[from] crate::core::FenceError),

    /// Semaphore creation failed
    #[error("Semaphore creation failed: {0}\n\nContext: Failed to create synchronization primitive.\nSuggestions:\n  • Check for resource exhaustion\n  • Verify device is valid\n  • May indicate too many simultaneous operations")]
    SemaphoreCreationFailed(#[from] crate::core::SemaphoreError),

    /// Command pool creation failed
    #[error("Command pool creation failed: {0}\n\nContext: Failed to create command buffer pool.\nSuggestions:\n  • Verify queue family index is valid\n  • Check device memory availability\n  • Ensure device hasn't been lost")]
    CommandPoolCreationFailed(#[from] crate::core::CommandPoolError),

    /// Command buffer operation failed
    #[error("Command buffer operation failed: {0}\n\nContext: Error during command buffer recording or execution.\nSuggestions:\n  • Check validation layer output\n  • Verify all resources are valid\n  • Ensure proper synchronization\n  • Check command buffer state")]
    CommandBufferError(#[from] crate::core::CommandBufferError),

    /// Queue operation failed
    #[error("Queue operation failed: {0}\n\nContext: Failed to submit work to GPU or wait for completion.\nSuggestions:\n  • Check for device loss\n  • Verify semaphore/fence validity\n  • Enable validation layers\n  • Check for shader infinite loops")]
    QueueError(#[from] crate::core::QueueError),

    /// Frame presentation failed
    #[error("Frame presentation failed: {details}\n\nOperation: {operation}\nFrame: {frame_index}\n\nSuggestions:\n  • Check if window was resized\n  • Verify swapchain is valid\n  • Handle ERROR_OUT_OF_DATE_KHR by recreating swapchain")]
    FramePresentationFailed {
        operation: String,
        frame_index: usize,
        details: String,
    },

    /// Resource exhaustion
    #[error("Resource exhaustion: {resource_type}\n\nContext: Ran out of {resource_type}.\nCurrent usage: {current_count}\nLimit: {limit}\n\nSuggestions:\n  • Free unused resources\n  • Check for resource leaks\n  • Increase pool sizes\n  • Batch operations")]
    ResourceExhausted {
        resource_type: String,
        current_count: usize,
        limit: usize,
    },

    /// Other runtime errors (fallback for helper utilities)
    #[error("{0}")]
    Other(String),
}

/// Errors that can occur during ShaderManager operations
#[derive(Error, Debug)]
pub enum ShaderManagerError {
    /// Shader compilation failed
    #[error("Shader compilation failed: {0}\n\nContext: GLSL→SPIR-V compilation error.\nSuggestions:\n  • Check GLSL syntax\n  • Verify shader version (#version 450)\n  • Review error messages above for line numbers\n  • Use glslangValidator for offline validation")]
    ShaderCompilationFailed(#[from] crate::core::ShaderError),

    /// Pipeline creation failed (from CORE)
    #[error("Pipeline creation failed: {0}\n\nContext: Failed to create graphics or compute pipeline.\nSuggestions:\n  • Verify shader stages are compatible\n  • Check descriptor set layouts match shader\n  • Ensure render pass formats match\n  • Enable validation layers for detailed diagnostics")]
    PipelineCreationFailed(#[from] crate::core::PipelineError),

    /// Pipeline builder error (from EX)
    #[error("Pipeline builder error: {0}\n\nContext: Pipeline configuration is incomplete or invalid.\nSuggestions:\n  • Check that all required stages are set\n  • Verify color attachment formats\n  • Ensure descriptor layouts are valid")]
    PipelineBuilderError(#[from] PipelineError),

    /// Invalid shader ID provided
    #[error("Invalid shader ID: {0:?}\n\nContext: Attempted to use shader that doesn't exist.\nSuggestions:\n  • Verify shader was compiled successfully\n  • Check shader hasn't been deleted\n  • Use valid ShaderId from compile_shader()")]
    InvalidShaderId(ShaderId),

    /// Invalid pipeline ID provided
    #[error("Invalid pipeline ID: {0:?}\n\nContext: Attempted to use pipeline that doesn't exist.\nSuggestions:\n  • Verify pipeline was created successfully\n  • Check pipeline hasn't been deleted\n  • Use valid PipelineId from create_pipeline()")]
    InvalidPipelineId(PipelineId),

    /// Descriptor pool creation failed
    #[error("Descriptor pool creation failed: {0}\n\nContext: Failed to create descriptor set pool.\nSuggestions:\n  • Check pool sizes are sufficient\n  • Verify max sets count\n  • Ensure device memory is available\n  • Review descriptor requirements")]
    DescriptorPoolCreationFailed(#[from] crate::core::DescriptorPoolError),

    /// Descriptor operation failed
    #[error("Descriptor operation failed: {0}\n\nContext: Error during descriptor set allocation or update.\nSuggestions:\n  • Check pool isn't exhausted\n  • Verify descriptor types match layout\n  • Ensure buffers/images are valid\n  • Reset pool if fragmented")]
    DescriptorError(#[from] crate::core::DescriptorError),

    /// Shader reflection failed
    #[error("Shader reflection failed for '{shader_name}'\n\nContext: Could not extract descriptor bindings from SPIR-V.\nDetails: {details}\n\nSuggestions:\n  • Ensure shader uses standard descriptor bindings\n  • Verify SPIR-V is valid\n  • Check shader was compiled correctly")]
    ReflectionFailed {
        shader_name: String,
        details: String,
    },
}

/// Errors that can occur during pipeline building
#[derive(Error, Debug)]
pub enum PipelineError {
    /// No vertex shader provided (required for graphics pipelines)
    #[error("No vertex shader provided - vertex shader is required for graphics pipelines\n\nSuggestions:\n  • Call .vertex_shader() on the pipeline builder\n  • Check shader compilation succeeded\n  • Verify you're building a graphics pipeline")]
    NoVertexShader,

    /// No fragment shader provided (required for graphics pipelines)
    #[error("No fragment shader provided - fragment shader is required for graphics pipelines\n\nSuggestions:\n  • Call .fragment_shader() on the pipeline builder\n  • Fragment shaders handle pixel output\n  • Check shader compilation succeeded")]
    NoFragmentShader,

    /// No compute shader provided (required for compute pipelines)
    #[error("No compute shader provided - compute shader is required for compute pipelines\n\nSuggestions:\n  • Call .compute_shader() on the pipeline builder\n  • Verify shader stage is COMPUTE\n  • Check shader compilation succeeded")]
    NoComputeShader,

    /// Pipeline creation failed in CORE tier
    #[error("Pipeline creation failed: {0}")]
    CreationFailed(#[from] crate::core::PipelineError),

    /// Invalid descriptor set layout
    #[error("Invalid descriptor set layout configuration\n\nContext: Descriptor layout doesn't match shader requirements.\nSuggestions:\n  • Use shader reflection to auto-generate layouts\n  • Verify binding numbers match shader\n  • Check descriptor types are correct\n  • Ensure set numbers are sequential")]
    InvalidDescriptorLayout,

    /// No color attachment formats specified (required for graphics pipelines)
    #[error("No color attachment formats specified - at least one format is required\n\nSuggestions:\n  • Call .color_attachment_format() at least once\n  • Common format: vk::Format::B8G8R8A8_SRGB\n  • Format must match render target")]
    NoColorAttachmentFormats,

    /// Incompatible shader stages
    #[error("Incompatible shader stages: {details}\n\nContext: Shader outputs don't match next stage inputs.\nSuggestions:\n  • Check vertex shader outputs match fragment inputs\n  • Verify location decorations in GLSL\n  • Enable validation layers for detailed info")]
    IncompatibleShaderStages { details: String },
}

/// Newtype for type-safe shader IDs
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ShaderId(pub(crate) usize);

impl fmt::Display for ShaderId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Shader({})", self.0)
    }
}

/// Newtype for type-safe pipeline IDs
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PipelineId(pub(crate) usize);

impl fmt::Display for PipelineId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Pipeline({})", self.0)
    }
}