// ── 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())
}