proof-engine 0.2.1

Real-time graphics from math: glyphs and particles moved by ODEs, strange attractors and force fields, drawn with HDR bloom on OpenGL.
Documentation

// ── Shader Program Representation ────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderProgram {
    pub id: u32,
    pub name: String,
    pub vertex_source: String,
    pub fragment_source: String,
    pub geometry_source: Option<String>,
    pub compute_source: Option<String>,
    pub defines: HashMap<String, String>,
    pub version: String,
    pub compiled: bool,
    pub compile_errors: Vec<String>,
    pub compile_warnings: Vec<String>,
}

impl ShaderProgram {
    pub fn new(id: u32, name: impl Into<String>) -> Self {
        Self { id, name: name.into(), vertex_source: String::new(), fragment_source: String::new(), geometry_source: None, compute_source: None, defines: HashMap::new(), version: "450".into(), compiled: false, compile_errors: Vec::new(), compile_warnings: Vec::new() }
    }
    pub fn with_vertex(mut self, src: impl Into<String>) -> Self { self.vertex_source = src.into(); self }
    pub fn with_fragment(mut self, src: impl Into<String>) -> Self { self.fragment_source = src.into(); self }
    pub fn add_define(&mut self, key: impl Into<String>, val: impl Into<String>) { self.defines.insert(key.into(), val.into()); }
    pub fn is_compute(&self) -> bool { self.compute_source.is_some() }
    pub fn has_geometry(&self) -> bool { self.geometry_source.is_some() }
    pub fn is_valid(&self) -> bool { self.compiled && self.compile_errors.is_empty() }
    pub fn error_count(&self) -> usize { self.compile_errors.len() }
    pub fn warning_count(&self) -> usize { self.compile_warnings.len() }
    pub fn add_error(&mut self, err: impl Into<String>) { self.compile_errors.push(err.into()); }
    pub fn add_warning(&mut self, warn: impl Into<String>) { self.compile_warnings.push(warn.into()); }
    pub fn clear_diagnostics(&mut self) { self.compile_errors.clear(); self.compile_warnings.clear(); }
    pub fn mark_compiled(&mut self) { self.compiled = true; }
}

#[derive(Clone, Debug)]
pub struct ShaderProgramRegistry {
    pub programs: HashMap<u32, ShaderProgram>,
    pub next_id: u32,
    pub active_program: Option<u32>,
}

impl ShaderProgramRegistry {
    pub fn new() -> Self { Self { programs: HashMap::new(), next_id: 1, active_program: None } }
    pub fn add(&mut self, prog: ShaderProgram) -> u32 {
        let id = self.next_id; self.next_id += 1;
        self.programs.insert(id, prog);
        id
    }
    pub fn get(&self, id: u32) -> Option<&ShaderProgram> { self.programs.get(&id) }
    pub fn get_mut(&mut self, id: u32) -> Option<&mut ShaderProgram> { self.programs.get_mut(&id) }
    pub fn find_by_name(&self, name: &str) -> Option<&ShaderProgram> { self.programs.values().find(|p| p.name == name) }
    pub fn count(&self) -> usize { self.programs.len() }
    pub fn valid_count(&self) -> usize { self.programs.values().filter(|p| p.is_valid()).count() }
    pub fn set_active(&mut self, id: u32) { self.active_program = Some(id); }
    pub fn remove(&mut self, id: u32) -> bool { self.programs.remove(&id).is_some() }
}

impl Default for ShaderProgramRegistry {
    fn default() -> Self { Self::new() }
}

// ── Shader Type System ────────────────────────────────────────────────────────

#[derive(Clone, Debug, PartialEq)]
pub enum ShaderDataType {
    Void,
    Bool, BVec2, BVec3, BVec4,
    Int, IVec2, IVec3, IVec4,
    UInt, UVec2, UVec3, UVec4,
    Float, Vec2, Vec3, Vec4,
    Mat2, Mat3, Mat4,
    Mat2x3, Mat2x4, Mat3x2, Mat3x4, Mat4x2, Mat4x3,
    Sampler2D, Sampler3D, SamplerCube, Sampler2DArray, SamplerShadow,
    Image2D, Image3D, ImageCube,
    Struct(String),
    Array(Box<ShaderDataType>, Option<u32>),
}

impl ShaderDataType {
    pub fn byte_size(&self) -> u32 {
        match self {
            ShaderDataType::Bool | ShaderDataType::Int | ShaderDataType::UInt | ShaderDataType::Float => 4,
            ShaderDataType::BVec2 | ShaderDataType::IVec2 | ShaderDataType::UVec2 | ShaderDataType::Vec2 => 8,
            ShaderDataType::BVec3 | ShaderDataType::IVec3 | ShaderDataType::UVec3 | ShaderDataType::Vec3 => 12,
            ShaderDataType::BVec4 | ShaderDataType::IVec4 | ShaderDataType::UVec4 | ShaderDataType::Vec4 => 16,
            ShaderDataType::Mat2 => 16, ShaderDataType::Mat3 => 36, ShaderDataType::Mat4 => 64,
            ShaderDataType::Array(inner, Some(n)) => inner.byte_size() * n,
            _ => 0,
        }
    }
    pub fn is_sampler(&self) -> bool { matches!(self, ShaderDataType::Sampler2D | ShaderDataType::Sampler3D | ShaderDataType::SamplerCube | ShaderDataType::Sampler2DArray | ShaderDataType::SamplerShadow) }
    pub fn is_matrix(&self) -> bool { matches!(self, ShaderDataType::Mat2 | ShaderDataType::Mat3 | ShaderDataType::Mat4 | ShaderDataType::Mat2x3 | ShaderDataType::Mat2x4 | ShaderDataType::Mat3x2 | ShaderDataType::Mat3x4 | ShaderDataType::Mat4x2 | ShaderDataType::Mat4x3) }
    pub fn is_vector(&self) -> bool { matches!(self, ShaderDataType::Vec2 | ShaderDataType::Vec3 | ShaderDataType::Vec4 | ShaderDataType::IVec2 | ShaderDataType::IVec3 | ShaderDataType::IVec4 | ShaderDataType::UVec2 | ShaderDataType::UVec3 | ShaderDataType::UVec4 | ShaderDataType::BVec2 | ShaderDataType::BVec3 | ShaderDataType::BVec4) }
    pub fn glsl_name(&self) -> &'static str {
        match self {
            ShaderDataType::Void => "void", ShaderDataType::Bool => "bool",
            ShaderDataType::Int => "int", ShaderDataType::UInt => "uint",
            ShaderDataType::Float => "float", ShaderDataType::Vec2 => "vec2",
            ShaderDataType::Vec3 => "vec3", ShaderDataType::Vec4 => "vec4",
            ShaderDataType::Mat4 => "mat4", ShaderDataType::Mat3 => "mat3",
            ShaderDataType::Mat2 => "mat2", ShaderDataType::Sampler2D => "sampler2D",
            ShaderDataType::SamplerCube => "samplerCube",
            _ => "unknown",
        }
    }
}

// ── Shader Variable ───────────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderVariable {
    pub name: String,
    pub data_type: ShaderDataType,
    pub qualifier: ShaderQualifier,
    pub location: Option<u32>,
    pub binding: Option<u32>,
    pub set: Option<u32>,
    pub is_builtin: bool,
    pub comment: String,
}

#[derive(Clone, Debug, PartialEq)]
pub enum ShaderQualifier { In, Out, InOut, Uniform, Const, Buffer, Shared }

impl ShaderVariable {
    pub fn uniform(name: impl Into<String>, data_type: ShaderDataType) -> Self {
        Self { name: name.into(), data_type, qualifier: ShaderQualifier::Uniform, location: None, binding: None, set: None, is_builtin: false, comment: String::new() }
    }
    pub fn input(name: impl Into<String>, data_type: ShaderDataType, location: u32) -> Self {
        Self { name: name.into(), data_type, qualifier: ShaderQualifier::In, location: Some(location), binding: None, set: None, is_builtin: false, comment: String::new() }
    }
    pub fn output(name: impl Into<String>, data_type: ShaderDataType, location: u32) -> Self {
        Self { name: name.into(), data_type, qualifier: ShaderQualifier::Out, location: Some(location), binding: None, set: None, is_builtin: false, comment: String::new() }
    }
    pub fn with_binding(mut self, binding: u32, set: u32) -> Self { self.binding = Some(binding); self.set = Some(set); self }
    pub fn is_uniform(&self) -> bool { self.qualifier == ShaderQualifier::Uniform }
    pub fn is_input(&self) -> bool { self.qualifier == ShaderQualifier::In }
    pub fn is_output(&self) -> bool { self.qualifier == ShaderQualifier::Out }
    pub fn size_bytes(&self) -> u32 { self.data_type.byte_size() }
}

// ── Shader Function ───────────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderFunction {
    pub name: String,
    pub return_type: ShaderDataType,
    pub parameters: Vec<ShaderVariable>,
    pub body: String,
    pub is_builtin: bool,
    pub line_start: u32,
    pub line_end: u32,
    pub comment: String,
}

impl ShaderFunction {
    pub fn new(name: impl Into<String>, return_type: ShaderDataType) -> Self {
        Self { name: name.into(), return_type, parameters: Vec::new(), body: String::new(), is_builtin: false, line_start: 0, line_end: 0, comment: String::new() }
    }
    pub fn add_param(&mut self, param: ShaderVariable) { self.parameters.push(param); }
    pub fn param_count(&self) -> usize { self.parameters.len() }
    pub fn is_main(&self) -> bool { self.name == "main" }
    pub fn signature(&self) -> String {
        let params: Vec<_> = self.parameters.iter().map(|p| format!("{} {}", p.data_type.glsl_name(), p.name)).collect();
        format!("{} {}({})", self.return_type.glsl_name(), self.name, params.join(", "))
    }
}

// ── Shader Struct Type ────────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderStruct {
    pub name: String,
    pub fields: Vec<ShaderVariable>,
    pub comment: String,
    pub std140_layout: bool,
}

impl ShaderStruct {
    pub fn new(name: impl Into<String>) -> Self { Self { name: name.into(), fields: Vec::new(), comment: String::new(), std140_layout: false } }
    pub fn add_field(&mut self, field: ShaderVariable) { self.fields.push(field); }
    pub fn size_bytes(&self) -> u32 { self.fields.iter().map(|f| f.size_bytes()).sum() }
    pub fn std140_size(&self) -> u32 {
        let mut size = 0u32;
        for f in &self.fields {
            let field_size = f.size_bytes();
            let align = field_size.max(4).next_power_of_two().min(16);
            size = (size + align - 1) & !(align - 1);
            size += field_size;
        }
        (size + 15) & !15
    }
    pub fn field_count(&self) -> usize { self.fields.len() }
    pub fn has_field(&self, name: &str) -> bool { self.fields.iter().any(|f| f.name == name) }
    pub fn generate_glsl(&self) -> String {
        let mut s = format!("struct {} {{\n", self.name);
        for f in &self.fields { s += &format!("    {} {};\n", f.data_type.glsl_name(), f.name); }
        s += "};\n";
        s
    }
}

// ── Shader Diagnostic ─────────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderDiagnostic {
    pub kind: DiagnosticKind,
    pub message: String,
    pub line: u32,
    pub column: u32,
    pub file: String,
    pub code: u32,
    pub suggestion: Option<String>,
}

#[derive(Clone, Debug, PartialEq)]
pub enum DiagnosticKind { Error, Warning, Info, Hint }

impl ShaderDiagnostic {
    pub fn error(msg: impl Into<String>, line: u32) -> Self {
        Self { kind: DiagnosticKind::Error, message: msg.into(), line, column: 0, file: String::new(), code: 0, suggestion: None }
    }
    pub fn warning(msg: impl Into<String>, line: u32) -> Self {
        Self { kind: DiagnosticKind::Warning, message: msg.into(), line, column: 0, file: String::new(), code: 0, suggestion: None }
    }
    pub fn with_suggestion(mut self, s: impl Into<String>) -> Self { self.suggestion = Some(s.into()); self }
    pub fn is_error(&self) -> bool { self.kind == DiagnosticKind::Error }
    pub fn is_warning(&self) -> bool { self.kind == DiagnosticKind::Warning }
    pub fn format(&self) -> String {
        let prefix = match self.kind { DiagnosticKind::Error => "error", DiagnosticKind::Warning => "warning", DiagnosticKind::Info => "info", DiagnosticKind::Hint => "hint" };
        format!("[{}] {}:{} — {}", prefix, self.file, self.line, self.message)
    }
}

#[derive(Clone, Debug, Default)]
pub struct DiagnosticList {
    pub items: Vec<ShaderDiagnostic>,
}

impl DiagnosticList {
    pub fn new() -> Self { Self::default() }
    pub fn push(&mut self, d: ShaderDiagnostic) { self.items.push(d); }
    pub fn errors(&self) -> Vec<&ShaderDiagnostic> { self.items.iter().filter(|d| d.is_error()).collect() }
    pub fn warnings(&self) -> Vec<&ShaderDiagnostic> { self.items.iter().filter(|d| d.is_warning()).collect() }
    pub fn has_errors(&self) -> bool { self.items.iter().any(|d| d.is_error()) }
    pub fn error_count(&self) -> usize { self.items.iter().filter(|d| d.is_error()).count() }
    pub fn warning_count(&self) -> usize { self.items.iter().filter(|d| d.is_warning()).count() }
    pub fn clear(&mut self) { self.items.clear(); }
    pub fn total(&self) -> usize { self.items.len() }
}

// ── Shader Preprocessor ───────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderPreprocessor {
    pub defines: HashMap<String, String>,
    pub include_paths: Vec<String>,
    pub max_include_depth: u32,
    pub strip_comments: bool,
    pub expand_macros: bool,
}

impl ShaderPreprocessor {
    pub fn new() -> Self {
        Self { defines: HashMap::new(), include_paths: Vec::new(), max_include_depth: 10, strip_comments: true, expand_macros: true }
    }
    pub fn define(&mut self, key: impl Into<String>, value: impl Into<String>) { self.defines.insert(key.into(), value.into()); }
    pub fn undefine(&mut self, key: &str) { self.defines.remove(key); }
    pub fn add_include_path(&mut self, path: impl Into<String>) { self.include_paths.push(path.into()); }
    pub fn preprocess(&self, source: &str) -> String {
        let mut output = String::with_capacity(source.len());
        for line in source.lines() {
            let trimmed = line.trim();
            if trimmed.starts_with("#define") {
                output.push_str(line);
            } else if trimmed.starts_with("//") && self.strip_comments {
                // skip
            } else {
                let mut processed = line.to_string();
                if self.expand_macros {
                    for (k, v) in &self.defines { processed = processed.replace(k.as_str(), v.as_str()); }
                }
                output.push_str(&processed);
            }
            output.push('\n');
        }
        output
    }
    pub fn is_defined(&self, key: &str) -> bool { self.defines.contains_key(key) }
    pub fn define_count(&self) -> usize { self.defines.len() }
}

impl Default for ShaderPreprocessor {
    fn default() -> Self { Self::new() }
}

// ── Shader Uniform Block ──────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct UniformBlock {
    pub name: String,
    pub binding: u32,
    pub set: u32,
    pub fields: Vec<ShaderVariable>,
    pub std140: bool,
    pub dynamic_offset: bool,
}

impl UniformBlock {
    pub fn new(name: impl Into<String>, binding: u32) -> Self {
        Self { name: name.into(), binding, set: 0, fields: Vec::new(), std140: true, dynamic_offset: false }
    }
    pub fn add_field(&mut self, f: ShaderVariable) { self.fields.push(f); }
    pub fn size_bytes(&self) -> u32 {
        let mut size = 0u32;
        for f in &self.fields {
            let fs = f.size_bytes();
            let align = fs.max(4).next_power_of_two().min(16);
            size = (size + align - 1) & !(align - 1);
            size += fs;
        }
        (size + 15) & !15
    }
    pub fn generate_glsl(&self) -> String {
        let mut s = format!("layout(std140, binding={}) uniform {} {{\n", self.binding, self.name);
        for f in &self.fields { s += &format!("    {} {};\n", f.data_type.glsl_name(), f.name); }
        s += "};\n";
        s
    }
    pub fn field_count(&self) -> usize { self.fields.len() }
}

// ── Shader Pipeline State ─────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct PipelineState {
    pub blend_enabled: bool,
    pub blend_src: BlendFactor,
    pub blend_dst: BlendFactor,
    pub blend_op: BlendOp,
    pub depth_test: bool,
    pub depth_write: bool,
    pub depth_func: CompareFunc,
    pub cull_mode: CullMode,
    pub front_face: FrontFace,
    pub scissor_test: bool,
    pub stencil_test: bool,
    pub polygon_mode: PolygonMode,
    pub line_width: f32,
    pub point_size: f32,
}

#[derive(Clone, Debug, PartialEq)]
pub enum BlendFactor { Zero, One, SrcAlpha, OneMinusSrcAlpha, DstAlpha, OneMinusDstAlpha, SrcColor, OneMinusSrcColor, ConstantAlpha }
#[derive(Clone, Debug, PartialEq)]
pub enum BlendOp { Add, Subtract, ReverseSubtract, Min, Max }
#[derive(Clone, Debug, PartialEq)]
pub enum CompareFunc { Never, Less, Equal, LessEqual, Greater, NotEqual, GreaterEqual, Always }
#[derive(Clone, Debug, PartialEq)]
pub enum CullMode { None, Front, Back, FrontAndBack }
#[derive(Clone, Debug, PartialEq)]
pub enum FrontFace { Clockwise, CounterClockwise }
#[derive(Clone, Debug, PartialEq)]
pub enum PolygonMode { Fill, Line, Point }

impl Default for PipelineState {
    fn default() -> Self {
        Self { blend_enabled: false, blend_src: BlendFactor::SrcAlpha, blend_dst: BlendFactor::OneMinusSrcAlpha, blend_op: BlendOp::Add, depth_test: true, depth_write: true, depth_func: CompareFunc::Less, cull_mode: CullMode::Back, front_face: FrontFace::CounterClockwise, scissor_test: false, stencil_test: false, polygon_mode: PolygonMode::Fill, line_width: 1.0, point_size: 1.0 }
    }
}

impl PipelineState {
    pub fn transparent() -> Self { Self { blend_enabled: true, depth_write: false, ..Default::default() } }
    pub fn additive() -> Self { Self { blend_enabled: true, blend_src: BlendFactor::One, blend_dst: BlendFactor::One, blend_op: BlendOp::Add, depth_write: false, ..Default::default() } }
    pub fn wireframe() -> Self { Self { polygon_mode: PolygonMode::Line, cull_mode: CullMode::None, ..Default::default() } }
    pub fn opaque() -> Self { Self::default() }
    pub fn is_transparent(&self) -> bool { self.blend_enabled }
    pub fn needs_sort(&self) -> bool { self.blend_enabled && !self.depth_write }
}

// ── Shader Compiler Constants ─────────────────────────────────────────────────

pub const SHADER_MAX_UNIFORMS: usize = 128;
pub const SHADER_MAX_ATTRIBUTES: usize = 32;
pub const SHADER_MAX_VARYINGS: usize = 32;
pub const SHADER_MAX_TEXTURE_UNITS: usize = 32;
pub const SHADER_MAX_UNIFORM_BLOCK_SIZE: u32 = 65536;
pub const SHADER_MAX_INCLUDE_DEPTH: u32 = 16;
pub const SHADER_VERSION_GLSL: &str = "450";
pub const SHADER_VERSION_WGSL: &str = "1.0";
pub const SHADER_MAX_SOURCE_LINES: usize = 10000;
pub const SHADER_MAX_FUNCTIONS: usize = 256;
pub const SHADER_MAX_STRUCTS: usize = 64;
pub const SHADER_MAX_PROGRAMS: usize = 1024;
pub const SHADER_CACHE_SIZE: usize = 256;
pub const SHADER_MAX_PUSH_CONSTANT_SIZE: u32 = 128;

pub fn shader_compiler_info() -> &'static str { "ShaderCompiler v1.0 — GLSL/WGSL, preprocessor, reflection, pipeline state" }