cvkg-render-gpu 0.2.8

Cyber Viking Kvasir Graph (CVKG) - High-fidelity agentic UI framework
Documentation
//! Build script — generates material WGSL from material graph definitions.
//!
//! This runs at compile time and generates `materials_generated.wgsl` containing
//! the WGSL fragment functions for all built-in materials. The main shader
//! includes this file, replacing the old mode-based if/else dispatch.

use std::env;
use std::fs;
use std::path::Path;

fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("materials_generated.wgsl");

    // Generate WGSL for each built-in material
    let mut output = String::new();

    output.push_str("// ── Auto-generated material functions ──\n");
    output.push_str("// DO NOT EDIT — generated by build.rs from material graph definitions\n\n");

    // Material 0: Solid (passthrough)
    output.push_str("fn material_0_solid(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    return col;\n");
    output.push_str("}\n\n");

    // Material 1: Neon Line
    output.push_str("fn material_1_neon_line(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    return col * 1.5;\n");
    output.push_str("}\n\n");

    // Material 2: Textured
    output.push_str("fn material_2_textured(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let tex_color = textureSample(t_diffuse[in.tex_index], s_diffuse, in.uv);\n");
    output.push_str("    return col * tex_color;\n");
    output.push_str("}\n\n");

    // Material 3: Rounded Rectangle
    output.push_str("fn material_3_rounded_rect(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let half_size = in.size * 0.5;\n");
    output.push_str("    let d = sd_round_rect(in.logical - half_size, half_size - in.radius, in.radius);\n");
    output.push_str("    let aa = fwidth(d);\n");
    output.push_str("    return vec4<f32>(col.rgb, col.a * (1.0 - smoothstep(0.0, aa, d)));\n");
    output.push_str("}\n\n");

    // Material 4: Ellipse
    output.push_str("fn material_4_ellipse(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let half_size = in.size * 0.5;\n");
    output.push_str("    let safe_half = max(half_size, vec2<f32>(0.001));\n");
    output.push_str("    let d = length((in.logical - half_size) / safe_half) - 1.0;\n");
    output.push_str("    let aa = fwidth(d);\n");
    output.push_str("    return vec4<f32>(col.rgb, col.a * (1.0 - smoothstep(0.0, aa, d)));\n");
    output.push_str("}\n\n");

    // Material 6: Text (premultiplied alpha)
    output.push_str("fn material_6_text(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let tex_color = textureSample(t_diffuse[in.tex_index], s_diffuse, in.uv);\n");
    output.push_str("    return vec4<f32>(col.rgb, col.a * tex_color.a);\n");
    output.push_str("}\n\n");

    // Material 7: Glass
    output.push_str("fn material_7_glass(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let uv = clamp(in.uv, vec2<f32>(0.0), vec2<f32>(1.0));\n");
    output.push_str("    let local = in.logical / in.size;\n");
    output.push_str("    let centered = local - vec2<f32>(0.5, 0.5);\n");
    output.push_str("    let lens_dir = normalize(centered + vec2<f32>(1e-5, 1e-5));\n");
    output.push_str("    let lens_dist = length(centered);\n");
    output.push_str("    let fresnel = pow(lens_dist * 1.8, 2.5);\n");
    output.push_str("    let lens = lens_dir * lens_dist * 0.08;\n");
    output.push_str("    let blur_mip = theme.glass_blur_strength;\n");
    output.push_str("    let env_base = textureSampleLevel(t_env, s_env, uv, blur_mip).rgb;\n");
    output.push_str("    let brightness = dot(env_base, vec3<f32>(0.299, 0.587, 0.114));\n");
    output.push_str("    var distortion = lens * 1.2;\n");
    output.push_str("    distortion *= (1.0 + brightness * 0.7);\n");
    output.push_str("    distortion *= 2.0;\n");
    output.push_str("    let ab_offset = distortion * 0.04;\n");
    output.push_str("    let r_sample = textureSampleLevel(t_env, s_env, uv + distortion + ab_offset * 1.2, blur_mip).r;\n");
    output.push_str("    let g_sample = textureSampleLevel(t_env, s_env, uv + distortion, blur_mip).g;\n");
    output.push_str("    let b_sample = textureSampleLevel(t_env, s_env, uv + distortion - ab_offset * 1.2, blur_mip).b;\n");
    output.push_str("    let refracted = vec3<f32>(r_sample, g_sample, b_sample);\n");
    output.push_str("    let tint = vec3<f32>(0.85, 0.9, 1.0);\n");
    output.push_str("    var final_rgb = refracted * tint;\n");
    output.push_str("    final_rgb += (brightness * 0.2) * (0.9 + vnoise(uv * 20.0 + scene.time * 3.0) * 0.1);\n");
    output.push_str("    let half_size = in.size * 0.5;\n");
    output.push_str("    let p_sdf = in.logical - half_size;\n");
    output.push_str("    let q_sdf = abs(p_sdf) - (half_size - in.radius);\n");
    output.push_str("    let d_sdf = length(max(q_sdf, vec2(0.0))) + min(max(q_sdf.x, q_sdf.y), 0.0) - in.radius;\n");
    output.push_str("    let d_norm = clamp(-d_sdf / 20.0, 0.0, 1.0);\n");
    output.push_str("    let flicker = 0.9 + vnoise(uv * 20.0 + scene.time * 3.0) * 0.1;\n");
    output.push_str("    final_rgb += smoothstep(1.0, 0.96, d_norm) * 0.25 * flicker * vec3<f32>(0.7, 1.0, 1.3);\n");
    output.push_str("    final_rgb -= smoothstep(0.96, 0.88, d_norm) * 0.15;\n");
    output.push_str("    let light_dir_h = normalize(vec2<f32>(-0.4, -0.8));\n");
    output.push_str("    let l = dot(uv, light_dir_h);\n");
    output.push_str("    final_rgb += smoothstep(0.45, 0.55, l) * 0.12;\n");
    output.push_str("    return vec4<f32>(final_rgb, 0.02 + fresnel * 0.15) * (1.0 - smoothstep(-length(vec2(dpdx(in.logical.x), dpdy(in.logical.y))), length(vec2(dpdx(in.logical.x), dpdy(in.logical.y))), d_sdf));\n");
    output.push_str("}\n\n");

    // Material 8: Neon Glow
    output.push_str("fn material_8_neon_glow(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let center = in.size * 0.5;\n");
    output.push_str("    let dist = length(in.logical - center) / max(in.size.x, in.size.y);\n");
    output.push_str("    let glow = exp(-dist * 4.0) * 1.5;\n");
    output.push_str("    return vec4<f32>(col.rgb * glow, col.a);\n");
    output.push_str("}\n\n");

    // Material 9: Lightning
    output.push_str("fn material_9_lightning(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let d = length((in.uv - 0.5) * vec2<f32>(1.0, 4.0));\n");
    output.push_str("    return theme.primary_neon * neon_glow(d, 0.01, 0.2);\n");
    output.push_str("}\n\n");

    // Material 10: Rune Glow
    output.push_str("fn material_10_rune_glow(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let p = (in.uv - 0.5) * 2.0;\n");
    output.push_str("    let d = min(sd_segment(p, vec2(-0.5, -0.8), vec2(0.5, 0.8)), sd_segment(p, vec2(0.5, -0.8), vec2(-0.5, 0.8)));\n");
    output.push_str("    return theme.rune_glow * neon_glow(d, 0.02, 0.15) * theme.rune_opacity;\n");
    output.push_str("}\n\n");

    // Material 12: Heatmap
    output.push_str("fn material_12_heatmap(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let val = textureSample(t_diffuse[in.tex_index], s_diffuse, in.uv).r;\n");
    output.push_str("    return vec4<f32>(heatmap_palette(val), col.a);\n");
    output.push_str("}\n\n");

    // Material 13: 3D PBR
    output.push_str("fn material_13_pbr(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let metallic = in.slice.x;\n");
    output.push_str("    let roughness = in.slice.y;\n");
    output.push_str("    let opacity = in.slice.z;\n");
    output.push_str("    let n = normalize(in.normal);\n");
    output.push_str("    let light_dir = normalize(vec3<f32>(0.5, 0.8, 0.6));\n");
    output.push_str("    let light_color = vec3<f32>(1.0, 0.95, 0.9);\n");
    output.push_str("    let n_dot_l = max(dot(n, light_dir), 0.0);\n");
    output.push_str("    let diffuse = n_dot_l * light_color;\n");
    output.push_str("    let view_dir = vec3<f32>(0.0, 0.0, 1.0);\n");
    output.push_str("    let half_dir = normalize(light_dir + view_dir);\n");
    output.push_str("    let n_dot_h = max(dot(n, half_dir), 0.0);\n");
    output.push_str("    let shininess = mix(8.0, 256.0, 1.0 - roughness);\n");
    output.push_str("    let spec = pow(n_dot_h, shininess) * light_color;\n");
    output.push_str("    let f0 = mix(vec3<f32>(0.04), col.rgb, metallic);\n");
    output.push_str("    let fresnel = f0 + (vec3<f32>(1.0) - f0) * pow(1.0 - max(dot(n, -view_dir), 0.0), 5.0);\n");
    output.push_str("    let ambient = vec3<f32>(0.06, 0.07, 0.1);\n");
    output.push_str("    var lit_color = col.rgb * (ambient + diffuse);\n");
    output.push_str("    lit_color += spec * mix(vec3<f32>(1.0), col.rgb, metallic) * fresnel;\n");
    output.push_str("    let depth = in.clip_position.z;\n");
    output.push_str("    let fog_factor = clamp(1.0 - depth * 0.0005, 0.7, 1.0);\n");
    output.push_str("    lit_color *= fog_factor;\n");
    output.push_str("    return vec4<f32>(lit_color, col.a * opacity);\n");
    output.push_str("}\n\n");

    // Material 14: Raymarched Reflections
    output.push_str("fn material_14_raymarch(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let ro = vec3<f32>(in.uv.x - 0.5, in.uv.y - 0.5, -2.0);\n");
    output.push_str("    let rd = normalize(vec3<f32>(in.uv.x - 0.5, in.uv.y - 0.5, 1.0));\n");
    output.push_str("    let t = ray_march(ro, rd);\n");
    output.push_str("    if t > 0.0 {\n");
    output.push_str("        let p = ro + rd * t;\n");
    output.push_str("        let n = calc_normal(p);\n");
    output.push_str("        let light_dir = normalize(vec3<f32>(1.0, 1.0, -1.0));\n");
    output.push_str("        let diff = max(dot(n, light_dir), 0.2);\n");
    output.push_str("        let ref_rd = reflect(rd, n);\n");
    output.push_str("        let ref_t = ray_march(p + n * 0.01, ref_rd);\n");
    output.push_str("        var reflection_color = vec3<f32>(0.05, 0.05, 0.1);\n");
    output.push_str("        if ref_t > 0.0 { reflection_color = mix(theme.primary_neon.rgb, theme.shatter_neon.rgb, 0.5); }\n");
    output.push_str("        return vec4<f32>(mix(col.rgb * diff, reflection_color, 0.3), 1.0);\n");
    output.push_str("    } else { discard; }\n");
    output.push_str("}\n\n");

    // Material 15: Linear Gradient
    output.push_str("fn material_15_linear_grad(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let angle = in.uv.x + scene.time * 0.5;\n");
    output.push_str("    let t = dot(in.logical / in.size - 0.5, vec2(cos(angle), sin(angle))) + 0.5;\n");
    output.push_str("    let end_color = vec4<f32>(in.slice.rgb, col.a);\n");
    output.push_str("    return mix(col, end_color, clamp(t, 0.0, 1.0));\n");
    output.push_str("}\n\n");

    // Material 16: Radial Gradient
    output.push_str("fn material_16_radial_grad(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let dist = length(in.uv - 0.5) * 2.0;\n");
    output.push_str("    let t = clamp(dist, 0.0, 1.0);\n");
    output.push_str("    let end_color = vec4<f32>(in.slice.rgb, in.slice.a);\n");
    output.push_str("    return mix(col, end_color, t);\n");
    output.push_str("}\n\n");

    // Material 17: Stroke
    output.push_str("fn material_17_stroke(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let half_size = in.size * 0.5;\n");
    output.push_str("    let d = sd_round_rect(in.logical - half_size, half_size - in.radius, in.radius);\n");
    output.push_str("    let thickness = max(in.slice.x, 1.0);\n");
    output.push_str("    let fw = length(vec2(dpdx(in.logical.x), dpdy(in.logical.y)));\n");
    output.push_str("    return vec4<f32>(col.rgb, col.a * (1.0 - smoothstep(-fw, fw, abs(d + thickness * 0.5) - thickness * 0.5)));\n");
    output.push_str("}\n\n");

    // Material 18: Drop Shadow
    output.push_str("fn material_18_drop_shadow(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let margin = in.uv.x;\n");
    output.push_str("    let blur = max(in.uv.y, 1.0);\n");
    output.push_str("    let original_size = in.size - 2.0 * margin;\n");
    output.push_str("    let half_size = original_size * 0.5;\n");
    output.push_str("    let p = in.logical - margin - half_size;\n");
    output.push_str("    let d = sd_round_rect(p, half_size - in.radius, in.radius);\n");
    output.push_str("    return vec4<f32>(col.rgb, col.a * smoothstep(blur, 0.0, d));\n");
    output.push_str("}\n\n");

    // Material 19: Dashed Stroke
    output.push_str("fn material_19_dashed(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let half_size = in.size * 0.5;\n");
    output.push_str("    let d = sd_round_rect(in.logical - half_size, half_size - in.radius, in.radius);\n");
    output.push_str("    let thickness = max(in.slice.x, 1.0);\n");
    output.push_str("    let perimeter = (in.uv.x + in.uv.y) * max(in.size.x, in.size.y);\n");
    output.push_str("    var alpha = 1.0 - smoothstep(-length(vec2(dpdx(in.logical.x), dpdy(in.logical.y))), length(vec2(dpdx(in.logical.x), dpdy(in.logical.y))), abs(d + thickness * 0.5) - thickness * 0.5);\n");
    output.push_str("    if (perimeter + scene.time * 20.0) % (max(in.slice.y, 1.0) + max(in.slice.z, 1.0)) > max(in.slice.y, 1.0) { alpha = 0.0; }\n");
    output.push_str("    return vec4<f32>(col.rgb, col.a * alpha);\n");
    output.push_str("}\n\n");

    // Material 20: 9-Slice
    output.push_str("fn material_20_nine_slice(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let tex_color = textureSample(t_diffuse[in.tex_index], s_diffuse, in.uv);\n");
    output.push_str("    return col * tex_color;\n");
    output.push_str("}\n\n");

    // Material 21: Raymarched Cube
    output.push_str("fn material_21_raymarch_cube(in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    let uv = (in.uv - 0.5) * 2.0;\n");
    output.push_str("    let ro = vec3<f32>(0.0, 0.0, -2.5);\n");
    output.push_str("    let rd = normalize(vec3<f32>(uv.x, uv.y, 1.5));\n");
    output.push_str("    let m = rotX(in.slice.x) * rotY(in.slice.y) * rotZ(in.slice.z);\n");
    output.push_str("    var t = 0.0;\n");
    output.push_str("    var hit = false;\n");
    output.push_str("    var d = 0.0;\n");
    output.push_str("    for (var i = 0; i < 40; i++) {\n");
    output.push_str("        let p = m * (ro + rd * t);\n");
    output.push_str("        d = sd_box_3d(p, vec3(0.5, 0.5, 0.5));\n");
    output.push_str("        if d < 0.001 { hit = true; break; }\n");
    output.push_str("        t += d;\n");
    output.push_str("        if t > 5.0 { break; }\n");
    output.push_str("    }\n");
    output.push_str("    if hit {\n");
    output.push_str("        let p = m * (ro + rd * t);\n");
    output.push_str("        let eps = vec2(0.001, 0.0);\n");
    output.push_str("        let n = normalize(vec3(\n");
    output.push_str("            sd_box_3d(p + eps.xyy, vec3(0.5)) - sd_box_3d(p - eps.xyy, vec3(0.5)),\n");
    output.push_str("            sd_box_3d(p + eps.yxy, vec3(0.5)) - sd_box_3d(p - eps.yxy, vec3(0.5)),\n");
    output.push_str("            sd_box_3d(p + eps.yyx, vec3(0.5)) - sd_box_3d(p - eps.yyx, vec3(0.5))\n");
    output.push_str("        ));\n");
    output.push_str("        let light_dir = normalize(vec3(1.0, 1.0, -2.0));\n");
    output.push_str("        let diff = max(dot(n, light_dir), 0.1);\n");
    output.push_str("        let rim = pow(1.0 - max(dot(n, -rd), 0.0), 3.0) * 0.5;\n");
    output.push_str("        return vec4<f32>(col.rgb * diff + rim, col.a);\n");
    output.push_str("    } else {\n");
    output.push_str("        discard;\n");
    output.push_str("    }\n");
    output.push_str("}\n\n");

    // Fragment dispatch function
    output.push_str("/// Dispatch to the correct material function based on material_id.\n");
    output.push_str("/// This replaces the old mode-based if/else chain.\n");
    output.push_str("fn dispatch_material(material_id: u32, in: VertexOutput, col: vec4<f32>) -> vec4<f32> {\n");
    output.push_str("    switch material_id {\n");
    output.push_str("        case 0u: { return material_0_solid(in, col); }\n");
    output.push_str("        case 1u: { return material_1_neon_line(in, col); }\n");
    output.push_str("        case 2u: { return material_2_textured(in, col); }\n");
    output.push_str("        case 3u: { return material_3_rounded_rect(in, col); }\n");
    output.push_str("        case 4u: { return material_4_ellipse(in, col); }\n");
    output.push_str("        case 6u: { return material_6_text(in, col); }\n");
    output.push_str("        case 7u: { return material_7_glass(in, col); }\n");
    output.push_str("        case 8u: { return material_8_neon_glow(in, col); }\n");
    output.push_str("        case 9u: { return material_9_lightning(in, col); }\n");
    output.push_str("        case 10u: { return material_10_rune_glow(in, col); }\n");
    output.push_str("        case 12u: { return material_12_heatmap(in, col); }\n");
    output.push_str("        case 13u: { return material_13_pbr(in, col); }\n");
    output.push_str("        case 14u: { return material_14_raymarch(in, col); }\n");
    output.push_str("        case 15u: { return material_15_linear_grad(in, col); }\n");
    output.push_str("        case 16u: { return material_16_radial_grad(in, col); }\n");
    output.push_str("        case 17u: { return material_17_stroke(in, col); }\n");
    output.push_str("        case 18u: { return material_18_drop_shadow(in, col); }\n");
    output.push_str("        case 19u: { return material_19_dashed(in, col); }\n");
    output.push_str("        case 20u: { return material_20_nine_slice(in, col); }\n");
    output.push_str("        case 21u: { return material_21_raymarch_cube(in, col); }\n");
    output.push_str("        default: { return col; }\n");
    output.push_str("    }\n");
    output.push_str("}\n");

    fs::write(&dest_path, output).unwrap();
    println!("cargo:rerun-if-changed=build.rs");
}