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 Uniform Tracking ───────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct UniformTracker {
    pub float_uniforms: HashMap<String, f32>,
    pub vec2_uniforms: HashMap<String, [f32; 2]>,
    pub vec3_uniforms: HashMap<String, [f32; 3]>,
    pub vec4_uniforms: HashMap<String, [f32; 4]>,
    pub int_uniforms: HashMap<String, i32>,
    pub bool_uniforms: HashMap<String, bool>,
    pub mat4_uniforms: HashMap<String, [[f32; 4]; 4]>,
    pub dirty_flags: HashSet<String>,
    pub upload_count: u64,
}

impl UniformTracker {
    pub fn new() -> Self { Self { float_uniforms: HashMap::new(), vec2_uniforms: HashMap::new(), vec3_uniforms: HashMap::new(), vec4_uniforms: HashMap::new(), int_uniforms: HashMap::new(), bool_uniforms: HashMap::new(), mat4_uniforms: HashMap::new(), dirty_flags: HashSet::new(), upload_count: 0 } }
    pub fn set_float(&mut self, name: impl Into<String>, v: f32) { let n = name.into(); if self.float_uniforms.get(&n) != Some(&v) { self.float_uniforms.insert(n.clone(), v); self.dirty_flags.insert(n); } }
    pub fn set_vec3(&mut self, name: impl Into<String>, v: [f32; 3]) { let n = name.into(); self.vec3_uniforms.insert(n.clone(), v); self.dirty_flags.insert(n); }
    pub fn set_vec4(&mut self, name: impl Into<String>, v: [f32; 4]) { let n = name.into(); self.vec4_uniforms.insert(n.clone(), v); self.dirty_flags.insert(n); }
    pub fn set_int(&mut self, name: impl Into<String>, v: i32) { let n = name.into(); self.int_uniforms.insert(n.clone(), v); self.dirty_flags.insert(n); }
    pub fn set_bool(&mut self, name: impl Into<String>, v: bool) { let n = name.into(); self.bool_uniforms.insert(n.clone(), v); self.dirty_flags.insert(n); }
    pub fn mark_uploaded(&mut self) { self.dirty_flags.clear(); self.upload_count += 1; }
    pub fn dirty_count(&self) -> usize { self.dirty_flags.len() }
    pub fn has_dirty(&self) -> bool { !self.dirty_flags.is_empty() }
    pub fn get_float(&self, name: &str) -> Option<f32> { self.float_uniforms.get(name).copied() }
    pub fn get_vec4(&self, name: &str) -> Option<[f32; 4]> { self.vec4_uniforms.get(name).copied() }
    pub fn total_uniform_count(&self) -> usize { self.float_uniforms.len() + self.vec2_uniforms.len() + self.vec3_uniforms.len() + self.vec4_uniforms.len() + self.int_uniforms.len() + self.bool_uniforms.len() + self.mat4_uniforms.len() }
}

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

// ── Shader Error Reporting ────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct ShaderErrorReport {
    pub program_id: u32,
    pub program_name: String,
    pub stage: String,
    pub errors: Vec<ShaderDiagnostic>,
    pub warnings: Vec<ShaderDiagnostic>,
    pub source_snippet: Vec<(u32, String)>,
    pub timestamp: u64,
    pub is_fatal: bool,
}

impl ShaderErrorReport {
    pub fn new(program_id: u32, name: impl Into<String>, stage: impl Into<String>) -> Self {
        Self { program_id, program_name: name.into(), stage: stage.into(), errors: Vec::new(), warnings: Vec::new(), source_snippet: Vec::new(), timestamp: 0, is_fatal: false }
    }
    pub fn add_error(&mut self, err: ShaderDiagnostic) { if err.is_error() { self.is_fatal = true; } self.errors.push(err); }
    pub fn add_warning(&mut self, w: ShaderDiagnostic) { self.warnings.push(w); }
    pub fn add_source_line(&mut self, line_no: u32, line: impl Into<String>) { self.source_snippet.push((line_no, line.into())); }
    pub fn has_errors(&self) -> bool { !self.errors.is_empty() }
    pub fn format_report(&self) -> String {
        let mut s = format!("[{}] {}:{}\n", if self.is_fatal { "FATAL" } else { "WARN" }, self.program_name, self.stage);
        for e in &self.errors { s += &format!("  {}\n", e.format()); }
        for w in &self.warnings { s += &format!("  {}\n", w.format()); }
        s
    }
}

// ── Built-in Shader Snippets ──────────────────────────────────────────────────

pub fn glsl_preamble(version: &str, is_es: bool) -> String {
    if is_es { format!("#version {} es\nprecision highp float;\nprecision highp int;\n", version) }
    else { format!("#version {}\n", version) }
}

pub fn glsl_common_utils() -> &'static str {
    r#"
const float PI = 3.14159265358979323846;
const float TWO_PI = 6.28318530717958647692;
const float HALF_PI = 1.57079632679489661923;
const float INV_PI = 0.31830988618379067154;
const float E = 2.71828182845904523536;
const float GOLDEN_RATIO = 1.61803398874989484820;
const float EPSILON = 1e-6;
const float INF = 1.0/0.0;

float saturate(float v) { return clamp(v, 0.0, 1.0); }
vec2 saturate(vec2 v) { return clamp(v, vec2(0.0), vec2(1.0)); }
vec3 saturate(vec3 v) { return clamp(v, vec3(0.0), vec3(1.0)); }
vec4 saturate(vec4 v) { return clamp(v, vec4(0.0), vec4(1.0)); }

float remap(float v, float fromMin, float fromMax, float toMin, float toMax) {
    return toMin + (v - fromMin) / (fromMax - fromMin) * (toMax - toMin);
}
float luminance(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); }
vec3 rgb_to_hsv(vec3 c) {
    vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
float rand(vec2 co) { return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); }
vec3 hash33(vec3 p) {
    p = fract(p * vec3(443.8975, 397.2973, 491.1871));
    p += dot(p.zxy, p.yxz + 19.19);
    return fract(vec3(p.x * p.y, p.z * p.x, p.y * p.z));
}
"#
}

pub fn glsl_pbr_brdf() -> &'static str {
    r#"
// PBR BRDF functions (GGX/Schlick/Smith)
vec3 F_Schlick(vec3 f0, float f90, float u) {
    return f0 + (f90 - f0) * pow(1.0 - u, 5.0);
}
float V_SmithGGXCorrelated(float NdotV, float NdotL, float roughness) {
    float a2 = roughness * roughness;
    float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - a2) + a2);
    float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - a2) + a2);
    return 0.5 / max(GGXV + GGXL, 1e-5);
}
float D_GGX(float NdotH, float roughness) {
    float a2 = roughness * roughness;
    float f = (NdotH * a2 - NdotH) * NdotH + 1.0;
    return a2 / (PI * f * f);
}
vec3 eval_pbr(vec3 N, vec3 V, vec3 L, vec3 albedo, float roughness, float metalness) {
    vec3 H = normalize(V + L);
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    float NdotH = max(dot(N, H), 0.0);
    float LdotH = max(dot(L, H), 0.0);
    vec3 f0 = mix(vec3(0.04), albedo, metalness);
    float D = D_GGX(NdotH, roughness);
    float V_vis = V_SmithGGXCorrelated(NdotV, NdotL, roughness);
    vec3 F = F_Schlick(f0, 1.0, LdotH);
    vec3 Fr = D * V_vis * F;
    vec3 Fd = (1.0 - F) * (1.0 - metalness) * albedo / PI;
    return (Fd + Fr) * NdotL;
}
"#
}

pub fn glsl_shadow_functions() -> &'static str {
    r#"
float shadow_hard(sampler2DShadow shadowMap, vec4 shadowCoord) {
    vec3 projCoords = shadowCoord.xyz / shadowCoord.w;
    return texture(shadowMap, projCoords);
}
float shadow_soft_pcf(sampler2DShadow shadowMap, vec4 shadowCoord, int samples) {
    vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
    vec3 projCoords = shadowCoord.xyz / shadowCoord.w;
    float shadow = 0.0;
    float total = 0.0;
    for(int x = -samples; x <= samples; x++) {
        for(int y = -samples; y <= samples; y++) {
            vec3 offset = vec3(vec2(x, y) * texelSize, 0.0);
            shadow += texture(shadowMap, projCoords + offset);
            total += 1.0;
        }
    }
    return shadow / total;
}
vec2 poisson_disk[16] = vec2[](
    vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725),
    vec2(-0.094184101, -0.92938870), vec2(0.34495938, 0.29387760),
    vec2(-0.91588581, 0.45771432), vec2(-0.81544232, -0.87912464),
    vec2(-0.38277543, 0.27676845), vec2(0.97484398, 0.75648379),
    vec2(0.44323325, -0.97511554), vec2(0.53742981, -0.47373420),
    vec2(-0.26496911, -0.41893023), vec2(0.79197514, 0.19090188),
    vec2(-0.24188840, 0.99706507), vec2(-0.81409955, 0.91437590),
    vec2(0.19984126, 0.78641367), vec2(0.14383161, -0.14100790)
);
float shadow_poisson(sampler2DShadow shadowMap, vec4 shadowCoord, float radius) {
    vec3 projCoords = shadowCoord.xyz / shadowCoord.w;
    float shadow = 0.0;
    for(int i = 0; i < 16; i++) {
        vec2 offset = poisson_disk[i] * radius;
        shadow += texture(shadowMap, projCoords + vec3(offset, 0.0));
    }
    return shadow / 16.0;
}
"#
}

pub fn glsl_atmosphere() -> &'static str {
    r#"
// Simple atmospheric scattering approximation
vec3 atmosphere(vec3 ray_dir, vec3 sun_dir, vec3 sun_color) {
    float sun_dot = max(dot(ray_dir, sun_dir), 0.0);
    vec3 sky_color = vec3(0.1, 0.3, 0.7);
    vec3 horizon_color = vec3(0.7, 0.5, 0.3);
    float horizon_blend = pow(1.0 - abs(ray_dir.y), 4.0);
    vec3 sky = mix(sky_color, horizon_color, horizon_blend);
    float sun_disc = smoothstep(0.998, 1.0, sun_dot);
    float halo = pow(sun_dot, 8.0) * 0.3;
    return sky + sun_color * (sun_disc + halo);
}
vec3 fog(vec3 color, float depth, vec3 fog_color, float fog_start, float fog_end) {
    float factor = clamp((depth - fog_start) / (fog_end - fog_start), 0.0, 1.0);
    return mix(color, fog_color, factor);
}
vec3 exponential_fog(vec3 color, float depth, vec3 fog_color, float density) {
    float factor = 1.0 - exp(-density * depth);
    return mix(color, fog_color, factor);
}
"#
}

// ── Shader Constant Buffer Layouts ────────────────────────────────────────────

pub fn per_frame_cbuffer_glsl() -> &'static str {
    r#"
layout(std140, binding = 0) uniform PerFrame {
    mat4 view;
    mat4 proj;
    mat4 view_proj;
    mat4 inv_view;
    mat4 inv_proj;
    vec4 camera_pos;
    vec4 camera_dir;
    vec4 viewport_size;
    float time;
    float delta_time;
    float near_plane;
    float far_plane;
    vec4 sun_direction;
    vec4 sun_color;
    vec4 ambient_color;
    vec2 jitter_offset;
    uint frame_index;
    float exposure;
} u_frame;
"#
}

pub fn per_object_cbuffer_glsl() -> &'static str {
    r#"
layout(std140, binding = 1) uniform PerObject {
    mat4 model;
    mat4 model_view;
    mat4 mvp;
    mat4 normal_matrix;
    vec4 object_color;
    vec4 object_id;
    float lod_bias;
    uint material_flags;
    float opacity;
    float pad;
} u_object;
"#
}

pub fn per_material_cbuffer_glsl() -> &'static str {
    r#"
layout(std140, binding = 2) uniform PerMaterial {
    vec4 albedo_color;
    vec4 emissive_color;
    float roughness;
    float metalness;
    float normal_scale;
    float occlusion_strength;
    float emissive_intensity;
    float opacity;
    float alpha_cutoff;
    uint flags;
    vec4 uv_transform;
} u_material;
"#
}

// ── Shader statistics & info ──────────────────────────────────────────────────

pub const SHADER_BUILTIN_SNIPPETS: usize = 11;
pub const SHADER_GLSL_BUILTIN_FUNCTIONS: usize = 28;
pub const SHADER_CBUFFER_BINDINGS: u32 = 3;
pub const SHADER_ATMOSPHERE_SAMPLES: u32 = 8;
pub const SHADER_SHADOW_PCF_SAMPLES: u32 = 9;
pub const SHADER_SHADOW_POISSON_SAMPLES: u32 = 16;
pub const GLSL_PRECISION_HIGHP: &str = "highp";
pub const GLSL_PRECISION_MEDIUMP: &str = "mediump";
pub const GLSL_PRECISION_LOWP: &str = "lowp";

pub fn all_glsl_builtin_types() -> &'static [&'static str] {
    &[ "void", "bool", "int", "uint", "float", "double",
       "bvec2", "bvec3", "bvec4", "ivec2", "ivec3", "ivec4",
       "uvec2", "uvec3", "uvec4", "vec2", "vec3", "vec4",
       "dvec2", "dvec3", "dvec4",
       "mat2", "mat3", "mat4", "mat2x3", "mat2x4", "mat3x2",
       "mat3x4", "mat4x2", "mat4x3",
       "sampler2D", "sampler3D", "samplerCube", "sampler2DArray",
       "sampler2DShadow", "samplerCubeShadow",
       "isampler2D", "usampler2D", "image2D", "imageCube" ]
}

pub fn all_glsl_keywords() -> &'static [&'static str] {
    &[ "if", "else", "for", "while", "do", "switch", "case", "default",
       "break", "continue", "return", "discard",
       "void", "struct", "precision", "highp", "mediump", "lowp",
       "in", "out", "inout", "uniform", "const", "layout",
       "attribute", "varying", "flat", "smooth", "centroid",
       "noperspective", "invariant", "coherent", "volatile",
       "restrict", "readonly", "writeonly" ]
}