// ── 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" ]
}