From 25068a76793db114eb25da75e0e4b0f22871dc22 Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 27 Apr 2025 01:28:21 -0400 Subject: [PATCH] feat(build): compile SPIR-V with rust native code --- Cargo.lock | 24 +++++++++++ Cargo.toml | 6 +++ build.rs | 120 +++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 127 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb809d9..77318c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,6 +384,12 @@ dependencies = [ "piper", ] +[[package]] +name = "build-print" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a2128d00b7061b82b72844a351e80acd29e05afc60e9261e2ac90dca9ecc2ac" + [[package]] name = "built" version = "0.7.7" @@ -1359,6 +1365,7 @@ dependencies = [ "num-traits", "once_cell", "petgraph", + "pp-rs", "rustc-hash", "spirv", "strum", @@ -1944,6 +1951,15 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +[[package]] +name = "pp-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee" +dependencies = [ + "unicode-xid", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2775,6 +2791,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "v_frame" version = "0.3.8" @@ -3686,9 +3708,11 @@ name = "zenyx" version = "0.1.0" dependencies = [ "allocator-api2", + "build-print", "bytemuck", "cgmath", "image", + "naga", "smol", "terminator", "thiserror 2.0.12", diff --git a/Cargo.toml b/Cargo.toml index 732a166..9cb8778 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,3 +58,9 @@ allocator-api2 = "0.2.21" [target.aarch64-linux-android.dependencies] winit = { version = "0.30.9", features = ["android-native-activity"] } + +[build-dependencies] +build-print = "0.1.1" +bytemuck = "1.22.0" +naga = { version = "25.0.1", features = ["glsl-in", "spv-out"] } +thiserror = "2.0.12" diff --git a/build.rs b/build.rs index 5235544..c7aec40 100644 --- a/build.rs +++ b/build.rs @@ -1,26 +1,100 @@ -use std::{env, process::Command}; +use build_print::{info, warn}; +use naga::{ + ShaderStage, + back::spv::{self, WriterFlags}, + front::glsl::{self, Options as GlslOptions, ParseErrors}, + valid::{ValidationError, ValidationFlags, Validator}, +}; +use std::{ + env, fs, + path::{Path, PathBuf}, +}; +use thiserror::Error; -fn main() { - println!("cargo::rerun-if-changed=shaders"); - let outdir = env::var("OUT_DIR").unwrap(); - let vert = Command::new("glslc") - .args(["shaders/shader.vert", "-o", &format!("{outdir}/vert.spv")]) - .output() - .expect("Failed to execute 'glslc'"); - let frag = Command::new("glslc") - .args(["shaders/shader.frag", "-o", &format!("{outdir}/frag.spv")]) - .output() - .expect("Failed to execute 'glslc'"); - if !vert.status.success() { - panic!( - "Failed to compile vertex shader: {}", - String::from_utf8(vert.stderr).unwrap() - ) - } - if !frag.status.success() { - panic!( - "Failed to compile fragment shader: {}", - String::from_utf8(frag.stderr).unwrap() - ) +#[derive(Debug, Error)] +pub enum BuildError { + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + #[error("environment variable error: {0}")] + EnvVar(#[from] env::VarError), + #[error("unsupported shader extension: {0}")] + UnsupportedExt(String), + #[error("GLSL parse errors in `{0}`:\n{1}")] + ParseErrors(String, ParseErrors), + #[error("validation errors in `{0}`: {1}")] + ValidateErrors(String, ValidationError), + #[error("SPIR-V write error for `{0}`: {1}")] + Spv(String, spv::Error), +} + +impl From<(String, ParseErrors)> for BuildError { + fn from((s, e): (String, ParseErrors)) -> Self { + BuildError::ParseErrors(s, e) } } + +impl From<(String, ValidationError)> for BuildError { + fn from((s, e): (String, ValidationError)) -> Self { + BuildError::ValidateErrors(s, e) + } +} + +impl From<(String, spv::Error)> for BuildError { + fn from((s, e): (String, spv::Error)) -> Self { + BuildError::Spv(s, e) + } +} + +fn compile_shader(path: &Path, out_dir: &Path) -> Result<(), BuildError> { + let ext = path + .extension() + .and_then(|e| e.to_str()) + .map(str::to_string) + .ok_or_else(|| BuildError::UnsupportedExt(path.display().to_string()))?; + let stage = match ext.as_str() { + "vert" => ShaderStage::Vertex, + "frag" => ShaderStage::Fragment, + "comp" => ShaderStage::Compute, + _ => return Err(BuildError::UnsupportedExt(ext)), + }; + let src = fs::read_to_string(path)?; + + let module = glsl::Frontend::default() + .parse(&GlslOptions::from(stage), &src) + .map_err(|e| (ext.clone(), e))?; + let info = Validator::new(ValidationFlags::all(), Default::default()) + .validate(&module) + .map_err(|e| (ext.clone(), e.into_inner()))?; + let mut writer = spv::Writer::new(&spv::Options { + flags: WriterFlags::empty(), + ..Default::default() + }) + .map_err(|e| (ext.clone(), e))?; + let mut spirv = Vec::new(); + writer + .write(&module, &info, None, &None, &mut spirv) + .map_err(|e| (ext.clone(), e))?; + let out_path = out_dir.join(format!("{}.spv", ext)); + fs::write(&out_path, bytemuck::cast_slice(&spirv))?; + info!("Compiled {} → {}", path.display(), out_path.display()); + Ok(()) +} + +fn main() -> Result<(), BuildError> { + println!("cargo:rerun-if-changed=shaders"); + let out_dir = PathBuf::from(env::var("OUT_DIR")?); + for entry in fs::read_dir("shaders")? { + let path = entry?.path(); + if !path.is_file() { + continue; + } + if let Err(e) = compile_shader(&path, &out_dir) { + if matches!(e, BuildError::UnsupportedExt(_)) { + warn!("{}", e); + continue; + } + return Err(e); + } + } + Ok(()) +}