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
|
*.opus filter=lfs diff=lfs merge=lfs -text
|
||||||
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||||
*.aiff 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
|
# Fonts
|
||||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
*.otf 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
|
- name: Cache cargo artifacts
|
||||||
uses: Swatinem/rust-cache@v2
|
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
|
- name: Check Rust Formatting
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
|
|
|
||||||
39
Cargo.lock
generated
39
Cargo.lock
generated
|
|
@ -1285,6 +1285,15 @@ dependencies = [
|
||||||
"redox_syscall 0.9.0",
|
"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]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.15"
|
version = "0.4.15"
|
||||||
|
|
@ -2153,6 +2162,7 @@ dependencies = [
|
||||||
"glam 0.33.2",
|
"glam 0.33.2",
|
||||||
"gpu-allocator",
|
"gpu-allocator",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
|
"shaderc",
|
||||||
"shared",
|
"shared",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -2172,6 +2182,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
|
|
@ -2441,6 +2457,29 @@ dependencies = [
|
||||||
"tracing-subscriber",
|
"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]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
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]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
shaderc = "0.10.1"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ash = "0.38.0"
|
ash = "0.38.0"
|
||||||
ash-window.workspace = true
|
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.
|
/// 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
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns [`RendererError::IoError`] if an embedded shader is not valid SPIR-V, or [`RendererError::VulkanError`] if module creation fails on the device.
|
/// 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(
|
fn load_shader_modules(
|
||||||
device: &Device,
|
device: &Device,
|
||||||
) -> Result<(vk::ShaderModule, vk::ShaderModule), RendererError> {
|
) -> Result<(vk::ShaderModule, vk::ShaderModule), RendererError> {
|
||||||
let vert_bytes = include_bytes!("../../../assets/shaders/cube.vert.spv");
|
let vert_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/cube.vert.spv"));
|
||||||
let frag_bytes = include_bytes!("../../../assets/shaders/cube.frag.spv");
|
let frag_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/cube.frag.spv"));
|
||||||
|
|
||||||
let vert_module = create_shader_module(device, vert_bytes)?;
|
let vert_module = create_shader_module(device, vert_bytes)?;
|
||||||
let frag_module = create_shader_module(device, frag_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.
|
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
|
||||||
|
|
||||||
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.
|
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