Synvael/crates/renderer/build.rs

101 lines
4.1 KiB
Rust

// 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 `<out_dir>/<name>.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}"))
}