From d893509ea53cca81a019dd8f40e85eb33e459610 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Tue, 3 Jun 2025 10:57:06 +0200 Subject: [PATCH 1/6] report path of shader crate when it can't be found --- crates/cargo-gpu/src/spirv_source.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/cargo-gpu/src/spirv_source.rs b/crates/cargo-gpu/src/spirv_source.rs index 28e93bd..6284814 100644 --- a/crates/cargo-gpu/src/spirv_source.rs +++ b/crates/cargo-gpu/src/spirv_source.rs @@ -88,8 +88,12 @@ impl SpirvSource { Self::CratesIO(Version::parse(rust_gpu_version)?) } } else { - Self::get_rust_gpu_deps_from_shader(shader_crate_path) - .context("get_rust_gpu_deps_from_shader")? + Self::get_rust_gpu_deps_from_shader(shader_crate_path).with_context(|| { + format!( + "get spirv-std dependency from shader crate '{}'", + shader_crate_path.display() + ) + })? }; Ok(source) } From c60cc1238926d147a7534ddc39f57023690e0feb Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Tue, 3 Jun 2025 11:23:20 +0200 Subject: [PATCH 2/6] fix git parsing with no `?` in url --- Cargo.lock | 79 ++++++++++++++++++++++++++++ crates/cargo-gpu/Cargo.toml | 1 + crates/cargo-gpu/src/spirv_source.rs | 51 +++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fbc2f16..0adde73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,6 +134,7 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", + "derive_builder", "semver", "serde", "serde_json", @@ -217,6 +218,72 @@ dependencies = [ "winapi", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + [[package]] name = "directories" version = "5.0.1" @@ -300,6 +367,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -350,6 +423,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" version = "2.9.0" diff --git a/crates/cargo-gpu/Cargo.toml b/crates/cargo-gpu/Cargo.toml index 9152876..48f075e 100644 --- a/crates/cargo-gpu/Cargo.toml +++ b/crates/cargo-gpu/Cargo.toml @@ -27,6 +27,7 @@ semver.workspace = true [dev-dependencies] test-log.workspace = true +cargo_metadata = { workspace = true, features = ["builder"] } [lints] workspace = true diff --git a/crates/cargo-gpu/src/spirv_source.rs b/crates/cargo-gpu/src/spirv_source.rs index 6284814..e21a075 100644 --- a/crates/cargo-gpu/src/spirv_source.rs +++ b/crates/cargo-gpu/src/spirv_source.rs @@ -148,8 +148,8 @@ impl SpirvSource { let parse_git = || { let link = &source.repr.get(4..)?; let sharp_index = link.find('#')?; - let question_mark_index = link.find('?')?; - let url = link.get(..question_mark_index)?.to_owned(); + let url_end = link.find('?').unwrap_or(sharp_index); + let url = link.get(..url_end)?.to_owned(); let rev = link.get(sharp_index + 1..)?.to_owned(); Some(Self::Git { url, rev }) }; @@ -248,6 +248,7 @@ pub fn get_channel_from_rustc_codegen_spirv_build_script( #[cfg(test)] mod test { use super::*; + use cargo_metadata::{PackageBuilder, PackageId, Source}; #[test_log::test] fn parsing_spirv_std_dep_for_shader_template() { @@ -281,4 +282,50 @@ mod test { .unwrap(); assert_eq!("https___github_com_Rust-GPU_rust-gpu+86fc4803", &name); } + + #[test_log::test] + fn parse_git_with_rev() { + let source = parse_git( + "git+https://github.com/Rust-GPU/rust-gpu?rev=86fc48032c4cd4afb74f1d81ae859711d20386a1#86fc4803", + ); + assert_eq!( + source, + SpirvSource::Git { + url: "https://github.com/Rust-GPU/rust-gpu".to_owned(), + rev: "86fc4803".to_owned(), + } + ) + } + + #[test_log::test] + fn parse_git_no_question_mark() { + // taken directly from Graphite + let source = parse_git( + "git+https://github.com/Rust-GPU/rust-gpu.git#6e2c84d4fe64e32df4c060c5a7f3e35a32e45421", + ); + assert_eq!( + source, + SpirvSource::Git { + url: "https://github.com/Rust-GPU/rust-gpu.git".to_owned(), + rev: "6e2c84d4fe64e32df4c060c5a7f3e35a32e45421".to_owned(), + } + ) + } + + fn parse_git(source: &str) -> SpirvSource { + let package = PackageBuilder::new( + "spirv-std", + Version::new(0, 9, 0), + PackageId { + repr: "".to_owned(), + }, + "", + ) + .source(Some(Source { + repr: source.to_owned(), + })) + .build() + .unwrap(); + SpirvSource::parse_spirv_std_source_and_version(&package).unwrap() + } } From 490421608f6158006265f6fb155a4659f71123d6 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Tue, 3 Jun 2025 11:24:38 +0200 Subject: [PATCH 3/6] integrated naga transpiling to wgsl --- Cargo.lock | 136 ++++++++++++++++++++++++++++++++ Cargo.toml | 1 + crates/cargo-gpu/Cargo.toml | 5 ++ crates/cargo-gpu/src/lib.rs | 4 + crates/cargo-gpu/src/linkage.rs | 7 +- crates/cargo-gpu/src/naga.rs | 98 +++++++++++++++++++++++ 6 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 crates/cargo-gpu/src/naga.rs diff --git a/Cargo.lock b/Cargo.lock index 0adde73..aaa2e8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstream" version = "0.6.18" @@ -67,12 +73,33 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -108,6 +135,7 @@ dependencies = [ "directories", "env_logger 0.10.2", "log", + "naga", "relative-path", "rustc_codegen_spirv-target-specs", "semver", @@ -147,6 +175,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clap" version = "4.5.37" @@ -187,6 +221,17 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -218,6 +263,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + [[package]] name = "darling" version = "0.20.11" @@ -367,12 +418,24 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -399,11 +462,27 @@ dependencies = [ "wasi", ] +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", +] + [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -523,6 +602,12 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.3" @@ -583,6 +668,29 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "naga" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.9.0", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown", + "indexmap", + "log", + "num-traits", + "once_cell", + "petgraph", + "rustc-hash", + "spirv", + "thiserror 2.0.12", +] + [[package]] name = "notify" version = "7.0.0" @@ -621,6 +729,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -662,6 +780,18 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "petgraph" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06" +dependencies = [ + "fixedbitset", + "hashbrown", + "indexmap", + "serde", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1199,6 +1329,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 78a6b52..df21e06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ tempdir = "0.3.7" test-log = "0.2.16" cargo_metadata = "0.19.2" semver = "1.0.26" +naga = "25.0.1" # This crate MUST NEVER be upgraded, we need this particular "first" version to support old rust-gpu builds legacy_target_specs = { package = "rustc_codegen_spirv-target-specs", version = "0.9.0", features = ["include_str"] } diff --git a/crates/cargo-gpu/Cargo.toml b/crates/cargo-gpu/Cargo.toml index 48f075e..c2504e2 100644 --- a/crates/cargo-gpu/Cargo.toml +++ b/crates/cargo-gpu/Cargo.toml @@ -10,6 +10,10 @@ build = "build.rs" default-run = "cargo-gpu" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +# Enable naga support to convert spirv to wgsl +naga = ["dep:naga"] + [dependencies] cargo_metadata.workspace = true anyhow.workspace = true @@ -24,6 +28,7 @@ serde.workspace = true serde_json.workspace = true crossterm.workspace = true semver.workspace = true +naga = { workspace = true, optional = true, features = ["spv-in", "wgsl-out"] } [dev-dependencies] test-log.workspace = true diff --git a/crates/cargo-gpu/src/lib.rs b/crates/cargo-gpu/src/lib.rs index b3adabf..4ef669e 100644 --- a/crates/cargo-gpu/src/lib.rs +++ b/crates/cargo-gpu/src/lib.rs @@ -65,11 +65,15 @@ mod legacy_target_specs; mod linkage; mod lockfile; mod metadata; +#[cfg(feature = "naga")] +mod naga; mod show; mod spirv_source; mod test; pub use install::*; +#[cfg(feature = "naga")] +pub use naga::*; pub use spirv_builder; /// Central function to write to the user. diff --git a/crates/cargo-gpu/src/linkage.rs b/crates/cargo-gpu/src/linkage.rs index 346bdaf..9a2eeb8 100644 --- a/crates/cargo-gpu/src/linkage.rs +++ b/crates/cargo-gpu/src/linkage.rs @@ -23,8 +23,13 @@ impl Linkage { .map(|comp| comp.as_os_str().to_string_lossy()) .collect::>() .join("/"), - wgsl_entry_point: entry_point.as_ref().replace("::", ""), + wgsl_entry_point: spv_entry_point_to_wgsl(entry_point.as_ref()), entry_point: entry_point.as_ref().to_owned(), } } } + +/// Convert a spirv entry point to a valid wgsl entry point +pub fn spv_entry_point_to_wgsl(entry_point: &str) -> String { + entry_point.replace("::", "") +} diff --git a/crates/cargo-gpu/src/naga.rs b/crates/cargo-gpu/src/naga.rs new file mode 100644 index 0000000..c8729a8 --- /dev/null +++ b/crates/cargo-gpu/src/naga.rs @@ -0,0 +1,98 @@ +//! naga transpiling to wgsl support, hidden behind feature `naga` + +use crate::linkage::spv_entry_point_to_wgsl; +use anyhow::Context as _; +use naga::error::ShaderError; +pub use naga::valid::Capabilities; +use spirv_builder::{CompileResult, ModuleResult}; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +/// convert a single spv file to a wgsl file using naga +fn spv_to_wgsl(spv_src: &Path, wgsl_dst: &Path, capabilities: Capabilities) -> anyhow::Result<()> { + let inner = || -> anyhow::Result<()> { + let spv_bytes = std::fs::read(spv_src).context("could not read spv file")?; + let opts = naga::front::spv::Options::default(); + let module = naga::front::spv::parse_u8_slice(&spv_bytes, &opts) + .map_err(|err| ShaderError { + source: String::new(), + label: None, + inner: Box::new(err), + }) + .context("naga could not parse spv")?; + let mut validator = + naga::valid::Validator::new(naga::valid::ValidationFlags::default(), capabilities); + let info = validator + .validate(&module) + .map_err(|err| ShaderError { + source: String::new(), + label: None, + inner: Box::new(err), + }) + .context("validation of naga module failed")?; + let wgsl = + naga::back::wgsl::write_string(&module, &info, naga::back::wgsl::WriterFlags::empty()) + .context("naga conversion to wgsl failed")?; + std::fs::write(wgsl_dst, wgsl).context("failed to write wgsl file")?; + Ok(()) + }; + inner().with_context(|| { + format!( + "converting spv '{}' to wgsl '{}' failed ", + spv_src.display(), + wgsl_dst.display() + ) + }) +} + +/// convert spv file path to a valid unique wgsl file path +fn wgsl_file_name(path: &Path) -> PathBuf { + path.with_extension("wgsl") +} + +/// Extension trait for naga transpiling +pub trait CompileResultNagaExt { + /// Transpile the spirv binaries to wgsl source code using [`naga`], typically for webgpu compatibility. + /// + /// Converts this [`CompileResult`] of spirv binaries and entry points to a [`CompileResult`] pointing to wgsl source code files and their associated wgsl entry + /// points. + /// + /// # Errors + /// [`naga`] transpile may error in various ways + fn transpile_to_wgsl(&self, capabilities: Capabilities) -> anyhow::Result; +} + +impl CompileResultNagaExt for CompileResult { + #[inline] + fn transpile_to_wgsl(&self, capabilities: Capabilities) -> anyhow::Result { + Ok(match &self.module { + ModuleResult::SingleModule(spv) => { + let wgsl = wgsl_file_name(spv); + spv_to_wgsl(spv, &wgsl, capabilities)?; + let entry_points = self + .entry_points + .iter() + .map(|entry| spv_entry_point_to_wgsl(entry)) + .collect(); + Self { + entry_points, + module: ModuleResult::SingleModule(wgsl), + } + } + ModuleResult::MultiModule(map) => { + let new_map: BTreeMap = map + .iter() + .map(|(entry_point, spv)| { + let wgsl = wgsl_file_name(spv); + spv_to_wgsl(spv, &wgsl, capabilities)?; + Ok((spv_entry_point_to_wgsl(entry_point), wgsl)) + }) + .collect::>()?; + Self { + entry_points: new_map.keys().cloned().collect(), + module: ModuleResult::MultiModule(new_map), + } + } + }) + } +} From fbd6f45a6c35d579827c2fdcccf9ff55a4582978 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Fri, 30 May 2025 17:42:17 +0200 Subject: [PATCH 4/6] allow one naga module to transpile into multiple outputs --- Cargo.lock | 4 +- Cargo.toml | 2 +- crates/cargo-gpu/Cargo.toml | 6 +- crates/cargo-gpu/src/naga.rs | 128 +++++++++++++++++++---------------- 4 files changed, 78 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aaa2e8b..d7ccf8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -963,7 +963,7 @@ checksum = "6c89eaf493b3dfc730cda42a77014aad65e03213992c7afe0dff60a9f7d3dd94" [[package]] name = "rustc_codegen_spirv-types" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu?rev=86fc48032c4cd4afb74f1d81ae859711d20386a1#86fc48032c4cd4afb74f1d81ae859711d20386a1" +source = "git+https://github.com/Rust-GPU/rust-gpu?rev=8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919#8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919" dependencies = [ "rspirv", "serde", @@ -1113,7 +1113,7 @@ dependencies = [ [[package]] name = "spirv-builder" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu?rev=86fc48032c4cd4afb74f1d81ae859711d20386a1#86fc48032c4cd4afb74f1d81ae859711d20386a1" +source = "git+https://github.com/Rust-GPU/rust-gpu?rev=8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919#8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919" dependencies = [ "clap", "memchr", diff --git a/Cargo.toml b/Cargo.toml index df21e06..db26592 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ exclude = [ resolver = "2" [workspace.dependencies] -spirv-builder = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "86fc48032c4cd4afb74f1d81ae859711d20386a1", default-features = false } +spirv-builder = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919", default-features = false } anyhow = "1.0.94" clap = { version = "4.5.37", features = ["derive"] } crossterm = "0.28.1" diff --git a/crates/cargo-gpu/Cargo.toml b/crates/cargo-gpu/Cargo.toml index c2504e2..1e267de 100644 --- a/crates/cargo-gpu/Cargo.toml +++ b/crates/cargo-gpu/Cargo.toml @@ -11,8 +11,10 @@ default-run = "cargo-gpu" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -# Enable naga support to convert spirv to wgsl +# Enable naga transpiling naga = ["dep:naga"] +# Enable naga transpiling to wgsl +wgsl-out = ["naga", "naga/wgsl-out"] [dependencies] cargo_metadata.workspace = true @@ -28,7 +30,7 @@ serde.workspace = true serde_json.workspace = true crossterm.workspace = true semver.workspace = true -naga = { workspace = true, optional = true, features = ["spv-in", "wgsl-out"] } +naga = { workspace = true, optional = true, features = ["spv-in"] } [dev-dependencies] test-log.workspace = true diff --git a/crates/cargo-gpu/src/naga.rs b/crates/cargo-gpu/src/naga.rs index c8729a8..af9dc5e 100644 --- a/crates/cargo-gpu/src/naga.rs +++ b/crates/cargo-gpu/src/naga.rs @@ -1,16 +1,31 @@ //! naga transpiling to wgsl support, hidden behind feature `naga` -use crate::linkage::spv_entry_point_to_wgsl; use anyhow::Context as _; use naga::error::ShaderError; pub use naga::valid::Capabilities; -use spirv_builder::{CompileResult, ModuleResult}; -use std::collections::BTreeMap; +use naga::valid::ModuleInfo; +use naga::Module; +use spirv_builder::{CompileResult, GenericCompileResult}; use std::path::{Path, PathBuf}; +/// Naga [`Module`] with [`ModuleInfo`] +#[derive(Clone, Debug)] +#[expect( + clippy::exhaustive_structs, + reason = "never adding private members to this struct" +)] +pub struct NagaModule { + /// path to the original spv + pub spv_path: PathBuf, + /// naga shader [`Module`] + pub module: Module, + /// naga [`ModuleInfo`] from validation + pub info: ModuleInfo, +} + /// convert a single spv file to a wgsl file using naga -fn spv_to_wgsl(spv_src: &Path, wgsl_dst: &Path, capabilities: Capabilities) -> anyhow::Result<()> { - let inner = || -> anyhow::Result<()> { +fn parse_spv(spv_src: &Path, capabilities: Capabilities) -> anyhow::Result { + let inner = || -> anyhow::Result<_> { let spv_bytes = std::fs::read(spv_src).context("could not read spv file")?; let opts = naga::front::spv::Options::default(); let module = naga::front::spv::parse_u8_slice(&spv_bytes, &opts) @@ -30,69 +45,68 @@ fn spv_to_wgsl(spv_src: &Path, wgsl_dst: &Path, capabilities: Capabilities) -> a inner: Box::new(err), }) .context("validation of naga module failed")?; - let wgsl = - naga::back::wgsl::write_string(&module, &info, naga::back::wgsl::WriterFlags::empty()) - .context("naga conversion to wgsl failed")?; - std::fs::write(wgsl_dst, wgsl).context("failed to write wgsl file")?; - Ok(()) + Ok(NagaModule { + module, + info, + spv_path: PathBuf::from(spv_src), + }) }; - inner().with_context(|| { - format!( - "converting spv '{}' to wgsl '{}' failed ", - spv_src.display(), - wgsl_dst.display() - ) - }) -} - -/// convert spv file path to a valid unique wgsl file path -fn wgsl_file_name(path: &Path) -> PathBuf { - path.with_extension("wgsl") + inner().with_context(|| format!("parsing spv '{}' failed", spv_src.display())) } /// Extension trait for naga transpiling pub trait CompileResultNagaExt { - /// Transpile the spirv binaries to wgsl source code using [`naga`], typically for webgpu compatibility. - /// - /// Converts this [`CompileResult`] of spirv binaries and entry points to a [`CompileResult`] pointing to wgsl source code files and their associated wgsl entry - /// points. + /// Transpile the spirv binaries to some other format using [`naga`]. /// /// # Errors /// [`naga`] transpile may error in various ways - fn transpile_to_wgsl(&self, capabilities: Capabilities) -> anyhow::Result; + fn naga_transpile(&self, capabilities: Capabilities) -> anyhow::Result; } impl CompileResultNagaExt for CompileResult { #[inline] - fn transpile_to_wgsl(&self, capabilities: Capabilities) -> anyhow::Result { - Ok(match &self.module { - ModuleResult::SingleModule(spv) => { - let wgsl = wgsl_file_name(spv); - spv_to_wgsl(spv, &wgsl, capabilities)?; - let entry_points = self - .entry_points - .iter() - .map(|entry| spv_entry_point_to_wgsl(entry)) - .collect(); - Self { - entry_points, - module: ModuleResult::SingleModule(wgsl), - } - } - ModuleResult::MultiModule(map) => { - let new_map: BTreeMap = map - .iter() - .map(|(entry_point, spv)| { - let wgsl = wgsl_file_name(spv); - spv_to_wgsl(spv, &wgsl, capabilities)?; - Ok((spv_entry_point_to_wgsl(entry_point), wgsl)) - }) - .collect::>()?; - Self { - entry_points: new_map.keys().cloned().collect(), - module: ModuleResult::MultiModule(new_map), - } - } - }) + fn naga_transpile(&self, capabilities: Capabilities) -> anyhow::Result { + Ok(NagaTranspile(self.try_map( + |entry| Ok(entry.clone()), + |spv| parse_spv(spv, capabilities), + )?)) + } +} + +/// Main struct for naga transpilation +#[expect( + clippy::exhaustive_structs, + reason = "never adding private members to this struct" +)] +pub struct NagaTranspile(pub GenericCompileResult); + +impl NagaTranspile { + /// Transpile to wgsl source code, typically for webgpu compatibility. + /// + /// Returns a [`CompileResult`] of wgsl source code files and their associated wgsl entry points. + /// + /// # Errors + /// converting naga module to wgsl may fail + #[inline] + #[cfg(feature = "wgsl-out")] + pub fn to_wgsl( + &self, + writer_flags: naga::back::wgsl::WriterFlags, + ) -> anyhow::Result { + self.0.try_map( + |entry| Ok(crate::linkage::spv_entry_point_to_wgsl(entry)), + |module| { + let inner = || -> anyhow::Result<_> { + let wgsl_dst = module.spv_path.with_extension("wgsl"); + let wgsl = + naga::back::wgsl::write_string(&module.module, &module.info, writer_flags) + .context("naga conversion to wgsl failed")?; + std::fs::write(&wgsl_dst, wgsl).context("failed to write wgsl file")?; + Ok(wgsl_dst) + }; + inner() + .with_context(|| format!("transpiling to wgsl '{}'", module.spv_path.display())) + }, + ) } } From 70705eeef7397c432ebb8a112eccc30c350f07e4 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Wed, 28 May 2025 15:58:03 +0200 Subject: [PATCH 5/6] make wgsl-out its own mod, reexport naga --- crates/cargo-gpu/src/naga.rs | 69 +++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/crates/cargo-gpu/src/naga.rs b/crates/cargo-gpu/src/naga.rs index af9dc5e..2ac8d00 100644 --- a/crates/cargo-gpu/src/naga.rs +++ b/crates/cargo-gpu/src/naga.rs @@ -2,12 +2,14 @@ use anyhow::Context as _; use naga::error::ShaderError; -pub use naga::valid::Capabilities; +use naga::valid::Capabilities; use naga::valid::ModuleInfo; use naga::Module; use spirv_builder::{CompileResult, GenericCompileResult}; use std::path::{Path, PathBuf}; +pub use naga; + /// Naga [`Module`] with [`ModuleInfo`] #[derive(Clone, Debug)] #[expect( @@ -80,33 +82,42 @@ impl CompileResultNagaExt for CompileResult { )] pub struct NagaTranspile(pub GenericCompileResult); -impl NagaTranspile { - /// Transpile to wgsl source code, typically for webgpu compatibility. - /// - /// Returns a [`CompileResult`] of wgsl source code files and their associated wgsl entry points. - /// - /// # Errors - /// converting naga module to wgsl may fail - #[inline] - #[cfg(feature = "wgsl-out")] - pub fn to_wgsl( - &self, - writer_flags: naga::back::wgsl::WriterFlags, - ) -> anyhow::Result { - self.0.try_map( - |entry| Ok(crate::linkage::spv_entry_point_to_wgsl(entry)), - |module| { - let inner = || -> anyhow::Result<_> { - let wgsl_dst = module.spv_path.with_extension("wgsl"); - let wgsl = - naga::back::wgsl::write_string(&module.module, &module.info, writer_flags) - .context("naga conversion to wgsl failed")?; - std::fs::write(&wgsl_dst, wgsl).context("failed to write wgsl file")?; - Ok(wgsl_dst) - }; - inner() - .with_context(|| format!("transpiling to wgsl '{}'", module.spv_path.display())) - }, - ) +/// feature gate `wgsl-out` +#[cfg(feature = "wgsl-out")] +mod wgsl_out { + use crate::NagaTranspile; + use anyhow::Context as _; + use naga::back::wgsl::WriterFlags; + use spirv_builder::CompileResult; + + impl NagaTranspile { + /// Transpile to wgsl source code, typically for webgpu compatibility. + /// + /// Returns a [`CompileResult`] of wgsl source code files and their associated wgsl entry points. + /// + /// # Errors + /// converting naga module to wgsl may fail + #[inline] + pub fn to_wgsl(&self, writer_flags: WriterFlags) -> anyhow::Result { + self.0.try_map( + |entry| Ok(crate::linkage::spv_entry_point_to_wgsl(entry)), + |module| { + let inner = || -> anyhow::Result<_> { + let wgsl_dst = module.spv_path.with_extension("wgsl"); + let wgsl = naga::back::wgsl::write_string( + &module.module, + &module.info, + writer_flags, + ) + .context("naga conversion to wgsl failed")?; + std::fs::write(&wgsl_dst, wgsl).context("failed to write wgsl file")?; + Ok(wgsl_dst) + }; + inner().with_context(|| { + format!("transpiling to wgsl '{}'", module.spv_path.display()) + }) + }, + ) + } } } From 7f5358bf3bac7363c8017409942464365ca61fd8 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Wed, 28 May 2025 16:00:59 +0200 Subject: [PATCH 6/6] rename `mod naga` to `naga_transpile` to prevent name collision with naga reexport --- crates/cargo-gpu/src/lib.rs | 4 ++-- crates/cargo-gpu/src/{naga.rs => naga_transpile.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename crates/cargo-gpu/src/{naga.rs => naga_transpile.rs} (100%) diff --git a/crates/cargo-gpu/src/lib.rs b/crates/cargo-gpu/src/lib.rs index 4ef669e..c5293b2 100644 --- a/crates/cargo-gpu/src/lib.rs +++ b/crates/cargo-gpu/src/lib.rs @@ -66,14 +66,14 @@ mod linkage; mod lockfile; mod metadata; #[cfg(feature = "naga")] -mod naga; +mod naga_transpile; mod show; mod spirv_source; mod test; pub use install::*; #[cfg(feature = "naga")] -pub use naga::*; +pub use naga_transpile::*; pub use spirv_builder; /// Central function to write to the user. diff --git a/crates/cargo-gpu/src/naga.rs b/crates/cargo-gpu/src/naga_transpile.rs similarity index 100% rename from crates/cargo-gpu/src/naga.rs rename to crates/cargo-gpu/src/naga_transpile.rs