ash_renderer 0.4.0

Production-quality Vulkan renderer in Rust using ASH - ECS-free, pure rendering engine
Documentation
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

fn main() {
    println!("cargo:rerun-if-changed=shaders");

    let shader_dir = Path::new("shaders");
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());

    if shader_dir.exists() {
        let generated_shaders =
            compile_shaders(shader_dir, &out_dir).expect("Failed to compile shaders");

        // Generate a registry file that uses include_bytes! safely
        let mut registry = String::from("// Auto-generated shader registry\n\n");
        for (const_name, file_name) in generated_shaders {
            registry.push_str(&format!(
                "pub const {const_name}: &[u8] = include_bytes!(concat!(env!(\"OUT_DIR\"), \"/{file_name}\"));\n"
            ));
        }
        fs::write(out_dir.join("shaders_registry.rs"), registry).expect("Failed to write registry");
    }
}

fn compile_shaders(
    dir: &Path,
    out_dir: &Path,
) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> {
    let compiler = shaderc::Compiler::new().unwrap();
    let mut options = shaderc::CompileOptions::new().unwrap();
    options.set_optimization_level(shaderc::OptimizationLevel::Performance);

    let mut generated = Vec::new();

    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            generated.extend(compile_shaders(&path, out_dir)?);
            continue;
        }

        let extension = match path.extension().and_then(|s| s.to_str()) {
            Some(ext) => ext,
            None => continue,
        };

        let kind = match extension {
            "vert" => shaderc::ShaderKind::Vertex,
            "frag" => shaderc::ShaderKind::Fragment,
            "comp" => shaderc::ShaderKind::Compute,
            _ => continue,
        };

        // Skip if it's already an spv file
        if extension == "spv" {
            continue;
        }

        let src_content = fs::read_to_string(&path)?;
        let file_name = path.file_name().unwrap().to_str().unwrap();

        let binary_result =
            compiler.compile_into_spirv(&src_content, kind, file_name, "main", Some(&options));

        match binary_result {
            Ok(binary) => {
                let out_name = if file_name == "vert.vert" {
                    "vert.spv".to_string()
                } else if file_name == "frag.frag" {
                    "frag.spv".to_string()
                } else {
                    format!("{file_name}.spv")
                };

                let out_path = out_dir.join(&out_name);
                fs::write(&out_path, binary.as_binary_u8())?;

                let const_name = out_name.replace(".", "_").to_uppercase();
                generated.push((const_name, out_name));
            }
            Err(e) => {
                eprintln!("Failed to compile shader {}: {}", path.display(), e);
                return Err(Box::new(e));
            }
        }
    }
    Ok(generated)
}