proof-engine 0.2.3

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

// ── Shader Input/Output Binding Validation ────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderInterface {
    pub inputs: Vec<ShaderVariable>,
    pub outputs: Vec<ShaderVariable>,
    pub stage: ShaderStage,
}

impl ShaderInterface {
    pub fn new(stage: ShaderStage) -> Self { Self { inputs: Vec::new(), outputs: Vec::new(), stage } }
    pub fn add_input(&mut self, var: ShaderVariable) { self.inputs.push(var); }
    pub fn add_output(&mut self, var: ShaderVariable) { self.outputs.push(var); }
    pub fn input_count(&self) -> usize { self.inputs.len() }
    pub fn output_count(&self) -> usize { self.outputs.len() }
    pub fn find_input(&self, name: &str) -> Option<&ShaderVariable> { self.inputs.iter().find(|v| v.name == name) }
    pub fn find_output(&self, name: &str) -> Option<&ShaderVariable> { self.outputs.iter().find(|v| v.name == name) }
    pub fn validate_compatible(&self, next: &ShaderInterface) -> Vec<String> {
        let mut errors = Vec::new();
        for out in &self.outputs {
            if !next.inputs.iter().any(|i| i.name == out.name && i.data_type == out.data_type) {
                errors.push(format!("Output '{}' not matched in next stage", out.name));
            }
        }
        errors
    }
    pub fn total_output_components(&self) -> u32 { self.outputs.iter().map(|o| o.data_type.byte_size() / 4).sum() }
}

// ── Shader Program Linker ─────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderLinker {
    pub programs: HashMap<u32, ShaderProgram>,
    pub layouts: HashMap<u32, PipelineLayout>,
    pub link_errors: Vec<String>,
}

impl ShaderLinker {
    pub fn new() -> Self { Self { programs: HashMap::new(), layouts: HashMap::new(), link_errors: Vec::new() } }
    pub fn add_program(&mut self, prog: ShaderProgram) { self.programs.insert(prog.id, prog); }
    pub fn link(&mut self, prog_id: u32) -> bool {
        self.link_errors.clear();
        if !self.programs.contains_key(&prog_id) { self.link_errors.push("Program not found".into()); return false; }
        true
    }
    pub fn has_errors(&self) -> bool { !self.link_errors.is_empty() }
    pub fn error_count(&self) -> usize { self.link_errors.len() }
    pub fn clear_errors(&mut self) { self.link_errors.clear(); }
}

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

// ── Shader Export ─────────────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderExportOptions {
    pub target: CodeGenTarget,
    pub minify: bool,
    pub include_reflection: bool,
    pub include_source: bool,
    pub embed_spirv: bool,
    pub output_format: ShaderExportFormat,
    pub output_path: String,
}

#[derive(Clone, Debug, PartialEq)]
pub enum ShaderExportFormat { Glsl, SpirV, Wgsl, Json, Binary }

impl ShaderExportOptions {
    pub fn new(target: CodeGenTarget) -> Self {
        Self { target, minify: false, include_reflection: true, include_source: true, embed_spirv: false, output_format: ShaderExportFormat::Glsl, output_path: "./shaders".into() }
    }
    pub fn release(target: CodeGenTarget) -> Self {
        Self { minify: true, include_source: false, ..Self::new(target) }
    }
    pub fn spirv(target: CodeGenTarget) -> Self {
        Self { embed_spirv: true, output_format: ShaderExportFormat::SpirV, ..Self::new(target) }
    }
}

#[derive(Clone, Debug)]
pub struct ShaderExportResult {
    pub success: bool,
    pub programs_exported: u32,
    pub files_written: Vec<String>,
    pub errors: Vec<String>,
    pub warnings: Vec<String>,
    pub total_bytes: u64,
    pub duration_ms: f32,
}

impl ShaderExportResult {
    pub fn ok(count: u32, files: Vec<String>, bytes: u64) -> Self {
        Self { success: true, programs_exported: count, files_written: files, errors: Vec::new(), warnings: Vec::new(), total_bytes: bytes, duration_ms: 0.0 }
    }
    pub fn fail(msg: impl Into<String>) -> Self {
        Self { success: false, programs_exported: 0, files_written: Vec::new(), errors: vec![msg.into()], warnings: Vec::new(), total_bytes: 0, duration_ms: 0.0 }
    }
}

// ── Shader Linting ────────────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct LintRule {
    pub id: u32,
    pub name: String,
    pub description: String,
    pub severity: DiagnosticKind,
    pub enabled: bool,
    pub pattern: String,
}

impl LintRule {
    pub fn new(id: u32, name: impl Into<String>, severity: DiagnosticKind) -> Self {
        Self { id, name: name.into(), description: String::new(), severity, enabled: true, pattern: String::new() }
    }
    pub fn error(id: u32, name: impl Into<String>) -> Self { Self::new(id, name, DiagnosticKind::Error) }
    pub fn warn(id: u32, name: impl Into<String>) -> Self { Self::new(id, name, DiagnosticKind::Warning) }
    pub fn with_desc(mut self, desc: impl Into<String>) -> Self { self.description = desc.into(); self }
}

#[derive(Clone, Debug)]
pub struct ShaderLinter {
    pub rules: Vec<LintRule>,
    pub enabled: bool,
}

impl ShaderLinter {
    pub fn new() -> Self {
        let mut rules = Vec::new();
        rules.push(LintRule::warn(1, "unused_variable").with_desc("Variable declared but never used"));
        rules.push(LintRule::warn(2, "precision_missing").with_desc("Missing precision qualifier"));
        rules.push(LintRule::error(3, "undefined_variable").with_desc("Variable used before declaration"));
        rules.push(LintRule::warn(4, "divide_by_zero").with_desc("Potential division by constant zero"));
        rules.push(LintRule::warn(5, "comparison_float").with_desc("Float equality comparison may be imprecise"));
        rules.push(LintRule::warn(6, "redundant_cast").with_desc("Unnecessary type cast"));
        rules.push(LintRule::error(7, "wrong_argument_type").with_desc("Function argument type mismatch"));
        rules.push(LintRule::warn(8, "shadowed_variable").with_desc("Variable shadows outer scope declaration"));
        Self { rules, enabled: true }
    }
    pub fn lint(&self, _source: &str) -> DiagnosticList { DiagnosticList::new() }
    pub fn enabled_rules(&self) -> Vec<&LintRule> { self.rules.iter().filter(|r| r.enabled).collect() }
    pub fn disable_rule(&mut self, id: u32) { if let Some(r) = self.rules.iter_mut().find(|r| r.id == id) { r.enabled = false; } }
    pub fn enable_rule(&mut self, id: u32) { if let Some(r) = self.rules.iter_mut().find(|r| r.id == id) { r.enabled = true; } }
    pub fn rule_count(&self) -> usize { self.rules.len() }
}

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

// ── Shader Formatting ─────────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderFormatterConfig {
    pub indent_size: usize,
    pub use_tabs: bool,
    pub max_line_length: usize,
    pub brace_style: BraceStyle,
    pub space_around_operators: bool,
    pub space_after_comma: bool,
    pub trailing_newline: bool,
}

#[derive(Clone, Debug, PartialEq)]
pub enum BraceStyle { Allman, KnR, Stroustrup }

impl ShaderFormatterConfig {
    pub fn default_style() -> Self {
        Self { indent_size: 4, use_tabs: false, max_line_length: 120, brace_style: BraceStyle::KnR, space_around_operators: true, space_after_comma: true, trailing_newline: true }
    }
    pub fn compact() -> Self {
        Self { indent_size: 2, ..Self::default_style() }
    }
}

impl Default for ShaderFormatterConfig {
    fn default() -> Self { Self::default_style() }
}

#[derive(Clone, Debug)]
pub struct ShaderFormatter {
    pub config: ShaderFormatterConfig,
}

impl ShaderFormatter {
    pub fn new() -> Self { Self { config: ShaderFormatterConfig::default() } }
    pub fn format(&self, source: &str) -> String {
        let mut output = String::with_capacity(source.len());
        let mut indent = 0usize;
        for line in source.lines() {
            let trimmed = line.trim();
            if trimmed.starts_with('}') && indent > 0 { indent -= 1; }
            let indent_str = if self.config.use_tabs { "\t".repeat(indent) } else { " ".repeat(indent * self.config.indent_size) };
            output.push_str(&indent_str);
            output.push_str(trimmed);
            output.push('\n');
            if trimmed.ends_with('{') { indent += 1; }
        }
        if self.config.trailing_newline && !output.ends_with('\n') { output.push('\n'); }
        output
    }
    pub fn count_lines(source: &str) -> usize { source.lines().count() }
    pub fn count_characters(source: &str) -> usize { source.chars().count() }
}

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

// ── Final shader constants ────────────────────────────────────────────────────

pub const SHADER_LINTER_RULE_COUNT_DEFAULT: usize = 8;
pub const SHADER_FORMATTER_MAX_LINE_LENGTH: usize = 120;
pub const SHADER_EXPORT_FORMAT_COUNT: usize = 5;
pub const SHADER_LINKER_MAX_PROGRAMS: usize = 1024;
pub const SHADER_INTERFACE_MAX_VARYINGS: usize = 32;
pub const SHADER_PERMUTATION_KEY_MAX_FEATURES: usize = 16;

pub fn shader_compiler_module_list() -> &'static [&'static str] {
    &[
        "tokenizer", "preprocessor", "parser", "ast", "codegen",
        "optimizer", "cache", "templates", "variants", "permutations",
        "include_resolver", "reflection", "diagnostics", "pipeline_state",
        "materials", "textures", "buffers", "render_passes", "post_process",
        "node_graph", "hot_reload", "lighting", "library", "debugger",
        "profiler", "editor_state", "autocomplete", "linker", "export",
        "linter", "formatter", "binding_layout",
    ]
}

pub fn shader_module_count() -> usize { shader_compiler_module_list().len() }

pub fn full_shader_system_description() -> String {
    format!("ShaderCompiler: {} modules — full pipeline from GLSL source to compiled GPU program", shader_module_count())
}