// SPDX-License-Identifier: AGPL-3.0-only //! Compiles the crate's GLSL shader sources to SPIR-V at build time. //! //! The resulting modules are written into `OUT_DIR` and embedded by `pipeline.rs` through `include_bytes!`, so no compiled artifact is committed to the repository and a source edit can never disagree with the binary shipped beside it. A compilation failure aborts the build, naming the offending shader and reproducing the compiler diagnostic verbatim. //! //! `#include` directives are deliberately not resolved. No shader uses one yet, and enabling them requires registering each included path for change tracking as well; a shared header added without that tracking would not retrigger compilation when edited, which is the exact staleness this script exists to prevent. use std::path::{Path, PathBuf}; /// The GLSL sources compiled into the crate, paired with the pipeline stage each one targets. const SHADERS: [(&str, shaderc::ShaderKind); 2] = [ ("cube.vert", shaderc::ShaderKind::Vertex), ("cube.frag", shaderc::ShaderKind::Fragment), ]; /// Compiles every entry of [`SHADERS`] into `OUT_DIR`. /// /// # Panics /// /// Panics if a Cargo-provided environment variable is absent, if the shader compiler or its options cannot be constructed, or if any individual shader fails to read, compile, or write. fn main() { let manifest_dir = required_var("CARGO_MANIFEST_DIR"); let out_dir = required_var("OUT_DIR"); // Shaders are shared repository assets rather than crate-local sources, so they resolve relative to the crate root instead of living under `src/`. let shader_dir = Path::new(&manifest_dir).join("../../assets/shaders"); let compiler = shaderc::Compiler::new() .unwrap_or_else(|error| panic!("failed to initialise the shader compiler: {error}")); let mut options = shaderc::CompileOptions::new() .unwrap_or_else(|error| panic!("failed to create the shader compiler options: {error}")); // The target environment must match the API the modules are consumed by; the renderer uses Vulkan 1.3 dynamic rendering. options.set_target_env( shaderc::TargetEnv::Vulkan, shaderc::EnvVersion::Vulkan1_3 as u32, ); options.set_optimization_level(shaderc::OptimizationLevel::Performance); for (name, kind) in SHADERS { compile_shader( &compiler, &options, &shader_dir, Path::new(&out_dir), name, kind, ); } } /// Compiles the shader `name` from `shader_dir` into `/.spv`. /// /// # Panics /// /// Panics if the source cannot be read, if the shader fails to compile, or if the resulting module cannot be written. fn compile_shader( compiler: &shaderc::Compiler, options: &shaderc::CompileOptions, shader_dir: &Path, out_dir: &Path, name: &str, kind: shaderc::ShaderKind, ) { let source_path = shader_dir.join(name); // Emitting any directive replaces Cargo's default of rerunning whenever the package changes, so every source consumed here must be registered explicitly or edits to it stop triggering a rebuild. println!("cargo::rerun-if-changed={}", source_path.display()); let source = std::fs::read_to_string(&source_path).unwrap_or_else(|error| { panic!( "failed to read the shader source {}: {error}", source_path.display() ) }); let artifact = compiler .compile_into_spirv(&source, kind, name, "main", Some(options)) .unwrap_or_else(|error| panic!("failed to compile the shader {name}:\n{error}")); let output_path: PathBuf = out_dir.join(format!("{name}.spv")); std::fs::write(&output_path, artifact.as_binary_u8()).unwrap_or_else(|error| { panic!( "failed to write the compiled shader {}: {error}", output_path.display() ) }); } /// Returns the value of a Cargo-provided environment variable. /// /// # Panics /// /// Panics if `name` is not set, which indicates the script was not invoked by Cargo. fn required_var(name: &str) -> String { std::env::var(name) .unwrap_or_else(|error| panic!("the environment variable {name} is not set: {error}")) }