use std::fs::{read_to_string, write}; use std::path::{Path, PathBuf}; use anyhow::*; use glob::glob; struct ShaderData { src: String, src_path: PathBuf, spv_path: PathBuf, kind: shaderc::ShaderKind, } impl ShaderData { pub fn load(src_path: PathBuf, out_path: &Path) -> Result { let extension = src_path .extension() .context("File has no extension")? .to_str() .context("Extension cannot be converted to &str")?; let kind = match extension { "vert" => shaderc::ShaderKind::Vertex, "frag" => shaderc::ShaderKind::Fragment, "comp" => shaderc::ShaderKind::Compute, _ => bail!("Unsupported shader: {}", src_path.display()), }; let src = read_to_string(src_path.clone())?; let spv_path = out_path .join(src_path.file_name().unwrap()) .with_extension(format!("{}.spv", extension)); Ok(Self { src, src_path, spv_path, kind, }) } } fn main() -> Result<()> { let out_dir: PathBuf = std::env::var_os("OUT_DIR").unwrap().into(); // Collect all shaders recursively within /src/ let mut shader_paths = [ glob("./src/**/*.vert")?, glob("./src/**/*.frag")?, glob("./src/**/*.comp")?, ]; // This could be parallelized let shaders = shader_paths .iter_mut() .flatten() .map(|glob_result| ShaderData::load(glob_result?, out_dir.as_path())) .collect::>>() .into_iter() .collect::>>()?; let mut compiler = shaderc::Compiler::new().context("Unable to create shader compiler")?; // This can't be parallelized. The [shaderc::Compiler] is not // thread safe. Also, it creates a lot of resources. You could // spawn multiple processes to handle this, but it would probably // be better just to only compile shaders that have been changed // recently. for shader in shaders { // This tells cargo to rerun this script if something in /src/ changes. println!( "cargo:rerun-if-changed={}", shader.src_path.as_os_str().to_str().unwrap() ); let compiled = compiler.compile_into_spirv( &shader.src, shader.kind, &shader.src_path.to_str().unwrap(), "main", None, )?; write(shader.spv_path, compiled.as_binary_u8())?; } Ok(()) }