101 lines
4.1 KiB
Rust
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}"))
|
|
}
|