chore(renderer): compile shaders at build time
This commit is contained in:
parent
857aada981
commit
0fd4378c3c
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -33,8 +33,6 @@
|
|||
*.opus filter=lfs diff=lfs merge=lfs -text
|
||||
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||
*.aiff filter=lfs diff=lfs merge=lfs -text
|
||||
# Compiled shaders
|
||||
*.spv filter=lfs diff=lfs merge=lfs -text
|
||||
# Fonts
|
||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||
*.otf filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
|
|||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
|
@ -26,6 +26,9 @@ jobs:
|
|||
- name: Cache cargo artifacts
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install shader compiler
|
||||
run: sudo apt-get update && sudo apt-get install -y libshaderc-dev
|
||||
|
||||
- name: Check Rust Formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
|
|
|
|||
39
Cargo.lock
generated
39
Cargo.lock
generated
|
|
@ -1285,6 +1285,15 @@ dependencies = [
|
|||
"redox_syscall 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
|
|
@ -2153,6 +2162,7 @@ dependencies = [
|
|||
"glam 0.33.2",
|
||||
"gpu-allocator",
|
||||
"raw-window-handle",
|
||||
"shaderc",
|
||||
"shared",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
|
|
@ -2172,6 +2182,12 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.3"
|
||||
|
|
@ -2441,6 +2457,29 @@ dependencies = [
|
|||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shaderc"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ab2a6e36d1c1e2320c87e2b806a3e7b0dffaa67b82c14a39dad6cf7637208ae"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"shaderc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shaderc-sys"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdceb85b2c6d2c27b95ffe2d341063dfded0aca8046f7f60c544bbeaeaf8bcae"
|
||||
dependencies = [
|
||||
"cmake",
|
||||
"libc",
|
||||
"link-cplusplus",
|
||||
"pkg-config",
|
||||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f3f5c483dddf88cd90dbb62aa579c7f815b7ff304407d27c19a9a52cc450b4bb
|
||||
size 572
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:17461a207a6e1d6ba3e2b050aaa83ad0fb880a29e54e42295a1d43cbdfbdc115
|
||||
size 1888
|
||||
|
|
@ -8,6 +8,9 @@ version.workspace = true
|
|||
[lints]
|
||||
workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
shaderc = "0.10.1"
|
||||
|
||||
[dependencies]
|
||||
ash = "0.38.0"
|
||||
ash-window.workspace = true
|
||||
|
|
|
|||
100
crates/renderer/build.rs
Normal file
100
crates/renderer/build.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
// 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}"))
|
||||
}
|
||||
|
|
@ -166,14 +166,16 @@ pub fn create_graphics_pipeline(
|
|||
|
||||
/// Loads the vertex and fragment shader modules from embedded bytes.
|
||||
///
|
||||
/// The SPIR-V is produced from the GLSL sources by the crate's build script and embedded from `OUT_DIR`, so the modules always correspond to the shader sources present at compile time.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`RendererError::IoError`] if an embedded shader is not valid SPIR-V, or [`RendererError::VulkanError`] if module creation fails on the device.
|
||||
fn load_shader_modules(
|
||||
device: &Device,
|
||||
) -> Result<(vk::ShaderModule, vk::ShaderModule), RendererError> {
|
||||
let vert_bytes = include_bytes!("../../../assets/shaders/cube.vert.spv");
|
||||
let frag_bytes = include_bytes!("../../../assets/shaders/cube.frag.spv");
|
||||
let vert_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/cube.vert.spv"));
|
||||
let frag_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/cube.frag.spv"));
|
||||
|
||||
let vert_module = create_shader_module(device, vert_bytes)?;
|
||||
let frag_module = create_shader_module(device, frag_bytes)?;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
Implementation notes for the `renderer` crate and for code that imports geometry. The project-wide coordinate convention itself (+Y up, right-handed, 1 unit = 1 block) is stated in [`AGENTS.md`](../AGENTS.md#coordinate-system--units); this note collects the gotchas that arise because neighbouring systems use different conventions. These are not convention changes, only mismatches to handle in one agreed place.
|
||||
|
||||
## Shader compilation
|
||||
|
||||
GLSL sources under `assets/shaders/` are compiled to SPIR-V by the `renderer` crate's build script and embedded from `OUT_DIR`; no compiled module is committed. Building the crate therefore requires `libshaderc`, either as a distribution package (`libshaderc-dev` on Debian and Ubuntu, `shaderc` on Arch, the Vulkan SDK on Windows) or, failing that, a C++ toolchain with cmake and ninja so `shaderc-sys` can build the library from source.
|
||||
|
||||
A shader that fails to compile aborts the build, naming the source file and the offending line.
|
||||
|
||||
## Vulkan clip space
|
||||
|
||||
Vulkan clip space is **Y-down** by default, and its depth range is `[0, 1]` (not `[-1, 1]` as in OpenGL). The projection matrix must flip Y, or the viewport height is set negative, both are common idioms in `ash` examples. World and view space stay Y-up; only clip space differs.
|
||||
|
|
|
|||
Loading…
Reference in a new issue