use std::fmt::Write as _;
use std::fs;
use std::path::Path;
fn main() {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let shader_dir = Path::new(&manifest_dir).join("shaders");
let out_dir = std::env::var("OUT_DIR").unwrap();
println!("cargo:rerun-if-changed=shaders");
println!("cargo:rerun-if-changed=build.rs");
let mut entries: Vec<String> = Vec::new();
let mut files: Vec<_> = fs::read_dir(&shader_dir)
.unwrap_or_else(|e| panic!("rlx-vulkan: cannot read {}: {e}", shader_dir.display()))
.filter_map(Result::ok)
.map(|e| e.path())
.filter(|p| p.extension().map(|x| x == "comp").unwrap_or(false))
.collect();
files.sort();
for path in &files {
println!("cargo:rerun-if-changed={}", path.display());
let name = path.file_stem().unwrap().to_string_lossy().to_string();
let src = fs::read_to_string(path)
.unwrap_or_else(|e| panic!("rlx-vulkan: read {}: {e}", path.display()));
let words = compile_glsl_to_spirv(&name, &src);
let mut bytes = Vec::with_capacity(words.len() * 4);
for w in &words {
bytes.extend_from_slice(&w.to_le_bytes());
}
let spv_path = Path::new(&out_dir).join(format!("{name}.spv"));
fs::write(&spv_path, &bytes)
.unwrap_or_else(|e| panic!("rlx-vulkan: write {}: {e}", spv_path.display()));
entries.push(name);
}
let precompiled_dir = shader_dir.join("precompiled");
if precompiled_dir.is_dir() {
let mut spvs: Vec<_> = fs::read_dir(&precompiled_dir)
.unwrap_or_else(|e| {
panic!("rlx-vulkan: cannot read {}: {e}", precompiled_dir.display())
})
.filter_map(Result::ok)
.map(|e| e.path())
.filter(|p| p.extension().map(|x| x == "spv").unwrap_or(false))
.collect();
spvs.sort();
for path in &spvs {
println!("cargo:rerun-if-changed={}", path.display());
let name = path.file_stem().unwrap().to_string_lossy().to_string();
let bytes = fs::read(path)
.unwrap_or_else(|e| panic!("rlx-vulkan: read {}: {e}", path.display()));
let spv_path = Path::new(&out_dir).join(format!("{name}.spv"));
fs::write(&spv_path, &bytes)
.unwrap_or_else(|e| panic!("rlx-vulkan: write {}: {e}", spv_path.display()));
entries.push(name);
}
}
let mut out_src = String::new();
out_src.push_str("// @generated by build.rs — GLSL→SPIR-V compute kernels.\n");
out_src.push_str("/// (kernel name, SPIR-V byte blob) for every shader under `shaders/`.\n");
out_src.push_str("pub static SHADER_BLOBS: &[(&str, &[u8])] = &[\n");
for name in &entries {
writeln!(
out_src,
" ({name:?}, include_bytes!(concat!(env!(\"OUT_DIR\"), \"/{name}.spv\"))),"
)
.unwrap();
}
out_src.push_str("];\n");
let gen_path = Path::new(&out_dir).join("shaders_generated.rs");
fs::write(&gen_path, out_src).unwrap();
}
fn compile_glsl_to_spirv(name: &str, src: &str) -> Vec<u32> {
use naga::ShaderStage;
use naga::back::spv;
use naga::front::glsl::{Frontend, Options};
use naga::valid::{Capabilities, ValidationFlags, Validator};
let options = Options::from(ShaderStage::Compute);
let module = Frontend::default()
.parse(&options, src)
.unwrap_or_else(|e| panic!("rlx-vulkan: GLSL parse error in {name}.comp: {e:?}"));
let info = Validator::new(ValidationFlags::all(), Capabilities::all())
.validate(&module)
.unwrap_or_else(|e| panic!("rlx-vulkan: validation error in {name}.comp: {e:?}"));
let spv_opts = spv::Options::default();
let pipe_opts = spv::PipelineOptions {
shader_stage: ShaderStage::Compute,
entry_point: "main".to_string(),
};
spv::write_vec(&module, &info, &spv_opts, Some(&pipe_opts))
.unwrap_or_else(|e| panic!("rlx-vulkan: SPIR-V emit error in {name}.comp: {e:?}"))
}