diff --git a/.cargo/config.toml b/.cargo/config.toml index 67d413617b..ab5eb3160a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,6 @@ [alias] compiletest = "run --release -p compiletests --" +difftest = "run --release -p difftests --" [target.x86_64-pc-windows-msvc] diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c667a85b24..0adc8aa451 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,6 +42,13 @@ jobs: - if: ${{ runner.os == 'Linux' }} name: Linux - Install native dependencies run: sudo apt install libwayland-cursor0 libxkbcommon-dev libwayland-dev + - if: ${{ runner.os == 'Linux' }} + name: Install xvfb, llvmpipe and lavapipe + run: | + sudo apt-get update -y -qq + sudo add-apt-repository ppa:kisak/turtle -y + sudo apt-get update + sudo apt install -y xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers # cargo version is a random command that forces the installation of rust-toolchain - name: install rust-toolchain run: cargo version @@ -64,6 +71,10 @@ jobs: if: ${{ matrix.target != 'aarch64-linux-android' }} run: cargo run -p compiletests --release --no-default-features --features "use-installed-tools" -- --target-env vulkan1.1,vulkan1.2,spv1.3 + - name: difftest + if: ${{ matrix.target != 'aarch64-linux-android' }} + run: cargo run -p difftests --release --no-default-features --features "use-installed-tools" + - name: workspace test if: ${{ matrix.target != 'aarch64-linux-android' }} run: cargo test --release @@ -147,6 +158,8 @@ jobs: run: cargo fmt --all -- --check - name: Rustfmt compiletests run: shopt -s globstar && rustfmt --check tests/compiletests/ui/**/*.rs + - name: Rustfmt difftests + run: cargo fmt --check --all --manifest-path tests/difftests/tests/Cargo.toml - name: Check docs are valid run: RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps - name: Check docs for `spirv-std` and `spirv-builder` on stable (for docs.rs) diff --git a/Cargo.lock b/Cargo.lock index 0038aed6fb..78e4a63e5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,6 +311,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "bytesize" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba" + [[package]] name = "calloop" version = "0.13.0" @@ -337,6 +343,38 @@ dependencies = [ "wayland-client 0.31.7", ] +[[package]] +name = "camino" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "cc" version = "1.2.6" @@ -665,6 +703,36 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "difftest" +version = "0.9.0" +dependencies = [ + "bytemuck", + "futures", + "serde", + "serde_json", + "spirv-builder", + "tempfile", + "thiserror 1.0.69", + "wgpu", +] + +[[package]] +name = "difftests" +version = "0.9.0" +dependencies = [ + "anyhow", + "bytesize", + "serde", + "serde_json", + "tempfile", + "tester", + "thiserror 1.0.69", + "toml", + "tracing", + "tracing-subscriber", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -2527,6 +2595,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2662,6 +2739,7 @@ dependencies = [ name = "spirv-builder" version = "0.9.0" dependencies = [ + "cargo_metadata", "clap", "memchr", "notify", @@ -2943,23 +3021,47 @@ dependencies = [ "strict-num", ] +[[package]] +name = "toml" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tracing" version = "0.1.41" @@ -3875,9 +3977,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.21" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f5bb5257f2407a5425c6e749bfd9692192a73e70a6060516ac04f889087d68" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 40481405c4..bdf011c205 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,8 @@ members = [ "tests/compiletests", "tests/compiletests/deps-helper", + "tests/difftests/bin", + "tests/difftests/lib", ] [workspace.package] diff --git a/crates/spirv-builder/Cargo.toml b/crates/spirv-builder/Cargo.toml index d8d2e68c95..f5c1bd2206 100644 --- a/crates/spirv-builder/Cargo.toml +++ b/crates/spirv-builder/Cargo.toml @@ -46,6 +46,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "2.0.12" semver = { version = "1.0.24", features = ["serde"] } +cargo_metadata = "0.19.2" notify = { version = "7.0", optional = true } # Pinning clap, as newer versions have raised min rustc version without being marked a breaking change diff --git a/crates/spirv-builder/src/lib.rs b/crates/spirv-builder/src/lib.rs index a0c47589ff..608cab3fd4 100644 --- a/crates/spirv-builder/src/lib.rs +++ b/crates/spirv-builder/src/lib.rs @@ -128,6 +128,8 @@ pub enum SpirvBuilderError { MetadataFileMissing(#[from] std::io::Error), #[error("unable to parse multi-module metadata file")] MetadataFileMalformed(#[from] serde_json::Error), + #[error("cargo metadata error")] + CargoMetadata(#[from] cargo_metadata::Error), } const SPIRV_TARGET_PREFIX: &str = "spirv-unknown-"; @@ -427,10 +429,11 @@ pub struct SpirvBuilder { /// [this RFC](https://rust-lang.github.io/rfcs/0131-target-specification.html). #[cfg_attr(feature = "clap", clap(skip))] pub path_to_target_spec: Option, - /// Set the target dir path within `./target` to use for building shaders. Defaults to `spirv-builder`, resulting - /// in the path `./target/spirv-builder`. + /// Set the target dir path to use for building shaders. Relative paths will be resolved + /// relative to the `target` dir of the shader crate, absolute paths are used as is. + /// Defaults to `spirv-builder`, resulting in the path `./target/spirv-builder`. #[cfg_attr(feature = "clap", clap(skip))] - pub target_dir_path: Option, + pub target_dir_path: Option, // `rustc_codegen_spirv::linker` codegen args /// Change the shader `panic!` handling strategy (see [`ShaderPanicStrategy`]). @@ -648,10 +651,11 @@ impl SpirvBuilder { self } - /// Set the target dir path within `./target` to use for building shaders. Defaults to `spirv-builder`, resulting - /// in the path `./target/spirv-builder`. + /// Set the target dir path to use for building shaders. Relative paths will be resolved + /// relative to the `target` dir of the shader crate, absolute paths are used as is. + /// Defaults to `spirv-builder`, resulting in the path `./target/spirv-builder`. #[must_use] - pub fn target_dir_path(mut self, name: impl Into) -> Self { + pub fn target_dir_path(mut self, name: impl Into) -> Self { self.target_dir_path = Some(name.into()); self } @@ -932,34 +936,21 @@ fn invoke_rustc(builder: &SpirvBuilder) -> Result { rustflags.extend(extra_rustflags.split_whitespace().map(|s| s.to_string())); } - // If we're nested in `cargo` invocation, use a different `--target-dir`, - // to avoid waiting on the same lock (which effectively dead-locks us). - let outer_target_dir = match (env::var("PROFILE"), env::var_os("OUT_DIR")) { - (Ok(outer_profile), Some(dir)) => { - // Strip `$outer_profile/build/*/out`. - [&outer_profile, "build", "*", "out"].iter().rev().try_fold( - PathBuf::from(dir), - |mut dir, &filter| { - if (filter == "*" || dir.ends_with(filter)) && dir.pop() { - Some(dir) - } else { - None - } - }, - ) - } - _ => None, + let target_dir_path = builder + .target_dir_path + .clone() + .unwrap_or_else(|| PathBuf::from("spirv-builder")); + let target_dir = if target_dir_path.is_absolute() { + target_dir_path + } else { + let metadata = cargo_metadata::MetadataCommand::new() + .current_dir(path_to_crate) + .exec()?; + metadata + .target_directory + .into_std_path_buf() + .join(target_dir_path) }; - // FIXME(eddyb) use `crate metadata` to always be able to get the "outer" - // (or "default") `--target-dir`, to append `/spirv-builder` to it. - let target_dir = outer_target_dir.map(|outer| { - outer.join( - builder - .target_dir_path - .as_deref() - .unwrap_or("spirv-builder"), - ) - }); let profile = if builder.release { "release" } else { "dev" }; @@ -1014,10 +1005,7 @@ fn invoke_rustc(builder: &SpirvBuilder) -> Result { .arg(builder.shader_crate_features.features.join(",")); } - // NOTE(eddyb) see above how this is computed and why it might be missing. - if let Some(target_dir) = target_dir { - cargo.arg("--target-dir").arg(target_dir); - } + cargo.arg("--target-dir").arg(target_dir); // Clear Cargo environment variables that we don't want to leak into the // inner invocation of Cargo (because e.g. build scripts might read them), diff --git a/deny.toml b/deny.toml index 66205b1108..4d99a3c874 100644 --- a/deny.toml +++ b/deny.toml @@ -60,6 +60,8 @@ skip-tree = [ { name = "num_cpus", version = "=1.16.0", depth = 2 }, # HACK(LegNeato) `tracing-tree` uses newer dependencies of `tracing`. { name = "tracing-tree", version = "=0.3.1" }, + # HACK(LegNeato) `thorin` has not yet released the version that bumps this. + { name = "gimli", version = "=0.30.0" }, ] diff --git a/docs/src/testing.md b/docs/src/testing.md index 40ad1378e2..b85e54e54b 100644 --- a/docs/src/testing.md +++ b/docs/src/testing.md @@ -1,14 +1,18 @@ # Testing Rust-GPU Rust-GPU has a couple of different kinds of tests, most can be ran through -`cargo test`, however Rust-GPU also has end-to-end tests for compiling Rust and -validating its SPIR-V output, which can ran by running `cargo compiletest`. +`cargo test`. Rust-GPU also has end-to-end tests for compiling Rust and +validating its SPIR-V output, which can ran by running `cargo compiletest`. Finally, +Rust-GPU has differential tests, which runs Rust and WGSL shaders and +makes sure they have the same output. These can be run with `cargo difftest`. ```bash -cargo test && cargo compiletest +cargo test && cargo compiletest && cargo difftest ``` -## Adding Tests +## Compile Tests + +### Adding Tests Rust-GPU's end-to-end test's use an external version of the [`compiletest`] tool as a testing framework. Be sure to check out the [repository][`compiletest`] and diff --git a/tests/difftests/README.md b/tests/difftests/README.md new file mode 100644 index 0000000000..d9d5a436dc --- /dev/null +++ b/tests/difftests/README.md @@ -0,0 +1,152 @@ +# Difftests + +Difftests verify correctness by running multiple implementations of the same logic and +comparing their outputs. Instead of relying on fixed reference outputs, they detect +discrepancies across implementations. + +## How It Works + +1. **Test Discovery** + + - The harness scans `tests/` for test cases. + - A test case is a directory containing at least two Rust binary packages / variants + to compare. + +2. **Configuration & Execution** + + - The harness creates a temporary output file for each variant binary. + - The harness runs each test variant binary with `cargo run --release`. + - A JSON config file with the output path and other settings is passed to the test + binary as `argv[1]`. + - The binary reads the config, runs its computation, and writes to the output file. + - Tests are run serially so they have full control of the GPU. + +3. **Output Comparison** + - The harness reads outputs as opaque bytes. + - If outputs differ, the test fails. + +Because the difftest harness merely runs Rust binaries in a directory, it supports +testing various setups. For example, you can: + +- Compare different CPU host code (`ash`, `wgpu`, etc.) with different GPU backends + (`rust-gpu`, `cuda`, `metal`, `wgsl`, etc.) against each other to make sure the output + is consistent. +- Verify that CPU and GPU implementations produce the same output. +- Ensure the same `rust-gpu` code gives identical results across different dispatch + methods. + +## Writing a Test + +Create a subdirectory under `tests/` with the test name. For example, `tests/foo/` for a +test named `foo`. In the test directory, create 2 or more Rust binary packages. Add the +packages to the top-level workspace `Cargo.toml` in the `tests/` directory. _Note that +this isn't the top-level workspace for the project._ The test binaries are in their own +workspace rather than the main workspace in order to not pollute our root workspace and +slow down cargo due to evaluating the potentially hundreds of cargo projects in +difftests. + +### Test Binary Example + +Each test binary must: + +1. Have a unique package name in `Cargo.toml`. +2. Read the config file path from `argv[1]`. +3. Load the config using `difftest::Config::from_path`. +4. Write its computed output to `output_path`. + +For example: + +```rust +use difftest::config::Config; +use std::{env, fs, io::Write}; + +fn main() { + let config_path = env::args().nth(1).expect("No config path provided"); + let config = Config::from_path(&config_path).expect("Invalid config"); + + // Real work would go here. + let output = compute_test_output(); + + let mut file = fs::File::create(&config.output_path) + .expect("Failed to create output file"); + file.write_all(&output).expect("Failed to write output"); +} +``` + +### Common test types + +Of course, many test will have common host and GPU needs. Rather than require every test +binary to reimplement functionality, we have created some common tests with reasonable +defaults in the `difftest` library. + +For example, this will handle compiling the current crate as a Rust compute shader, +running it via `wgpu`, and writing the output to the appropriate place: + +```rust +fn main() { + // Load the config from the harness. + let config = Config::from_path(std::env::args().nth(1).unwrap()).unwrap(); + + // Define test parameters, loading the rust shader from the current crate. + let test = WgpuComputeTest::new(RustComputeShader::default(), [1, 1, 1], 1024); + + // Run the test and write the output to a file. + test.run_test(&config).unwrap(); +} +``` + +and this will handle loading a shader named `shader.wgsl` or `compute.wgsl` in the root +of the current crate, running it via `wgpu`, and writing the output to the appropriate +place: + +```rust +fn main() { + // Load the config from the harness. + let config = Config::from_path(std::env::args().nth(1).unwrap()).unwrap(); + + // Define test parameters, loading the wgsl shader from the crate directory. + let test = WgpuComputeTest::new(WgslComputeShader::default(), [1, 1, 1], 1024); + + // Run the test and write the output to a file. + test.run_test(&config).unwrap(); +} +``` + +## Running Tests + +### Run all difftests: + +```sh +cargo difftest +``` + +Note that `cargo difftest` is an alias in `.cargo/config` for `cargo run --release -p +difftest --`. + +### Run specific tests by name: + +```sh +cargo difftest some_test_name +``` + +### Show stdout/stderr from tests: + +```sh +cargo difftest --nocapture +``` + +## Debugging Failing Tests + +If outputs differ, the error message lists: + +- Binary package names +- Their directories +- Output file paths + +Inspect the output files with your preferred tools to determine the differences. + +If you suspect a bug in the test harness, you can view detailed test harness logs: + +```sh +RUST_LOG=trace cargo difftest +``` diff --git a/tests/difftests/bin/Cargo.toml b/tests/difftests/bin/Cargo.toml new file mode 100644 index 0000000000..82b1fc2d93 --- /dev/null +++ b/tests/difftests/bin/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "difftests" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +# See rustc_codegen_spirv/Cargo.toml for details on these features +[features] +default = ["use-compiled-tools"] +use-installed-tools = [] +use-compiled-tools = [] + +[dependencies] +anyhow = "1.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } +tempfile = "3.5" +tester = "0.9.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0" +toml = { version = "0.8.20", default-features = false, features = ["parse"] } +bytesize = "2.0.1" + +[lints] +workspace = true diff --git a/tests/difftests/bin/src/main.rs b/tests/difftests/bin/src/main.rs new file mode 100644 index 0000000000..9c6b4236a0 --- /dev/null +++ b/tests/difftests/bin/src/main.rs @@ -0,0 +1,117 @@ +use anyhow::Result; +use std::{ + env, + process::{self, Command}, +}; +use tester::{ + ColorConfig, DynTestName, OutputFormat, RunIgnored, ShouldPanic, TestDesc, TestDescAndFn, + TestFn, TestType, run_tests_console, + test::{TestOpts, parse_opts}, +}; +use tracing_subscriber::FmtSubscriber; + +mod runner; +use runner::Runner; + +fn main() -> Result<()> { + let subscriber = FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .finish(); + tracing::subscriber::set_global_default(subscriber).expect("Failed to set global subscriber"); + + let args: Vec = env::args().collect(); + let opts: TestOpts = match parse_opts(&args) { + Some(Ok(o)) => TestOpts { + test_threads: Some(1), + ..o + }, + Some(Err(e)) => { + eprintln!("Error parsing test options: {}", e); + process::exit(1); + } + None => TestOpts { + list: false, + filters: vec![], + filter_exact: false, + force_run_in_process: false, + exclude_should_panic: false, + run_ignored: RunIgnored::No, + run_tests: true, + bench_benchmarks: false, + logfile: None, + nocapture: false, + color: ColorConfig::AutoColor, + format: OutputFormat::Pretty, + test_threads: Some(1), + skip: vec![], + time_options: None, + options: tester::Options { + display_output: true, + panic_abort: true, + }, + }, + }; + + // Find the manifest directory at compile time and locate tests in ../tests. + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let base = std::path::Path::new(manifest_dir) + .join("../tests") + .canonicalize() + .expect("Failed to canonicalize tests directory"); + tracing::debug!("Using tests directory: {}", base.display()); + + let runner = Runner::new(base.clone()); + + let test_cases = + Runner::collect_test_dirs(&base).expect("Failed to collect test case directories"); + if test_cases.is_empty() { + eprintln!("No valid tests found in {}", base.display()); + process::exit(1); + } + + // We build first to ensure that the tests are compiled before running them and to + // passthrough stdout and stderr from cargo to help debugging. + let mut cmd = Command::new("cargo"); + let cmd = cmd.arg("build").arg("--release"); + runner::forward_features(cmd); + cmd.current_dir(&base) + .stderr(process::Stdio::inherit()) + .stdout(process::Stdio::inherit()); + tracing::debug!("Running cargo command: {:?}", cmd); + + let output = cmd.output().expect("build output"); + let exit_code = output.status.code().unwrap_or(-1); + tracing::debug!("Cargo build exited with code {}", exit_code); + if !output.status.success() { + tracing::error!("Cargo build failed"); + process::exit(exit_code); + } + + let tests: Vec = test_cases + .into_iter() + .map(|case| { + let test_name = Runner::format_test_name(&case, &base); + TestDescAndFn { + desc: TestDesc { + name: DynTestName(test_name.clone()), + ignore: false, + should_panic: ShouldPanic::No, + allow_fail: false, + test_type: TestType::IntegrationTest, + }, + testfn: TestFn::DynTestFn(Box::new({ + let runner = runner.clone(); + move || { + runner + .run_test_case(&case) + .unwrap_or_else(|e| panic!("{}", e)); + } + })), + } + }) + .collect(); + + let passed = run_tests_console(&opts, tests).expect("Failed to run tests"); + + process::exit(if passed { 0 } else { 1 }); +} diff --git a/tests/difftests/bin/src/runner.rs b/tests/difftests/bin/src/runner.rs new file mode 100644 index 0000000000..221d830a7d --- /dev/null +++ b/tests/difftests/bin/src/runner.rs @@ -0,0 +1,465 @@ +use bytesize::ByteSize; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{HashMap, HashSet}, + fs, + io::Write, + path::{Component, Path, PathBuf}, + process::Command, +}; +use tempfile::NamedTempFile; +use thiserror::Error; +use tracing::{debug, error, info, trace}; + +#[derive(Debug, Error)] +pub enum RunnerError { + #[error("I/O error: {source}")] + Io { + #[from] + source: std::io::Error, + }, + #[error("TOML parse error in manifest at {path:?}: {source}")] + Toml { + path: PathBuf, + #[source] + source: toml::de::Error, + }, + #[error("Manifest error in manifest at {path:?}")] + Manifest { path: PathBuf }, + #[error("Insufficient binary packages: found {count}, but at least 2 are required.")] + InsufficientPackages { count: usize }, + #[error("All binaries produced empty output.")] + EmptyOutput, + #[error( + "Duplicate package name found: {pkg_name}. Packages must use unique names as we reuse the target directory to reduce compile times" + )] + DuplicatePackageName { pkg_name: String }, + #[error("Outputs differ:\n\n{0}")] + DifferingOutput(String), + #[error( + "Cargo execution failed in package '{pkg_name}' at '{package_path}' with exit code {exit_status}. Stderr:\n{stderr}" + )] + CargoExecutionFailed { + pkg_name: String, + package_path: PathBuf, + exit_status: i32, + stderr: String, + }, + #[error("Configuration error: {msg}")] + Config { msg: String }, +} + +pub type RunnerResult = std::result::Result; + +#[derive(Debug, Serialize)] +pub struct HarnessConfig { + pub output_path: PathBuf, +} + +#[derive(Deserialize)] +struct CargoPackage { + name: String, +} + +#[derive(Deserialize)] +struct CargoManifest { + package: CargoPackage, +} + +struct PackageOutput { + pkg_name: String, + package_path: PathBuf, + output: Vec, + temp_path: PathBuf, +} + +#[derive(Clone)] +pub struct Runner { + pub base_dir: PathBuf, +} + +impl Runner { + pub fn new(base_dir: PathBuf) -> Self { + Self { base_dir } + } + + pub fn run_test_case(&self, test_case: &Path) -> RunnerResult<()> { + trace!("Starting test case: {}", test_case.display()); + let packages = self.collect_packages(test_case)?; + debug!( + "Found {} package(s) in test case {}", + packages.len(), + test_case.display() + ); + if packages.len() < 2 { + error!("Insufficient packages in test case {}", test_case.display()); + return Err(RunnerError::InsufficientPackages { + count: packages.len(), + }); + } + + // Pre-check that package names are globally unique. + let mut names_seen = HashSet::new(); + for package in &packages { + let manifest_path = package.join("Cargo.toml"); + let pkg_name = self.get_package_name(&manifest_path)?; + if !names_seen.insert(pkg_name.clone()) { + return Err(RunnerError::DuplicatePackageName { pkg_name }); + } + } + + let mut temp_files: Vec = Vec::with_capacity(packages.len()); + let mut pkg_outputs: Vec = Vec::with_capacity(packages.len()); + + for package in packages { + trace!("Processing package at {}", package.display()); + let manifest_path = package.join("Cargo.toml"); + let pkg_name = self.get_package_name(&manifest_path)?; + debug!("Package '{}' detected", pkg_name); + + let output_file = NamedTempFile::new()?; + let temp_output_path = output_file.path().to_path_buf(); + temp_files.push(output_file); + trace!( + "Temporary output file created at {}", + temp_output_path.display() + ); + + let config = HarnessConfig { + output_path: temp_output_path.clone(), + }; + let config_json = serde_json::to_string(&config) + .map_err(|e| RunnerError::Config { msg: e.to_string() })?; + let mut config_file = NamedTempFile::new()?; + write!(config_file, "{}", config_json).map_err(|e| RunnerError::Io { source: e })?; + trace!("Config file created at {}", config_file.path().display()); + + let mut cmd = Command::new("cargo"); + cmd.arg("run").arg("--release").arg("--manifest-path").arg( + manifest_path + .to_str() + .ok_or_else(|| RunnerError::Manifest { + path: manifest_path.clone(), + })?, + ); + forward_features(&mut cmd); + cmd.arg("--").arg( + config_file + .path() + .to_str() + .ok_or_else(|| RunnerError::Config { + msg: "Invalid config file path".into(), + })?, + ); + debug!("Running cargo command: {:?}", cmd); + + let output = cmd + .current_dir(&package) + .output() + .map_err(|e| RunnerError::Io { source: e })?; + let exit_code = output.status.code().unwrap_or(-1); + debug!( + "Cargo run for package '{}' exited with code {}", + pkg_name, exit_code + ); + if !output.status.success() { + let stderr_str = String::from_utf8_lossy(&output.stderr).to_string(); + error!("Cargo execution failed for package '{}'", pkg_name); + return Err(RunnerError::CargoExecutionFailed { + pkg_name, + package_path: package, + exit_status: exit_code, + stderr: stderr_str, + }); + } + + let output_bytes = fs::read(temp_files.last().unwrap().path())?; + debug!( + "Read {} bytes of output for package '{}'", + output_bytes.len(), + pkg_name + ); + pkg_outputs.push(PackageOutput { + pkg_name, + package_path: package, + output: output_bytes, + temp_path: temp_output_path, + }); + } + + if pkg_outputs.iter().all(|po| po.output.is_empty()) { + error!("All packages produced empty output."); + return Err(RunnerError::EmptyOutput); + } + + let groups = self.group_outputs(&pkg_outputs); + if groups.len() > 1 { + let details = self.format_group_details(&pkg_outputs); + self.keep_temp_files(&mut temp_files); + return Err(RunnerError::DifferingOutput(details)); + } + info!( + "Test case '{}' passed.", + Runner::format_test_name(test_case, test_case.parent().unwrap_or(test_case)) + ); + Ok(()) + } + + #[allow(clippy::unused_self)] + fn collect_packages(&self, test_case: &Path) -> RunnerResult> { + let mut packages = Vec::new(); + for entry in fs::read_dir(test_case)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() && path.join("Cargo.toml").exists() { + debug!("Found package candidate: {}", path.display()); + packages.push(path); + } + } + Ok(packages) + } + + #[allow(clippy::unused_self)] + fn group_outputs<'a>( + &self, + pkg_outputs: &'a [PackageOutput], + ) -> HashMap, Vec<&'a PackageOutput>> { + let mut groups: HashMap, Vec<&'a PackageOutput>> = HashMap::new(); + for po in pkg_outputs { + groups.entry(po.output.clone()).or_default().push(po); + } + groups + } + + fn format_group_details(&self, pkg_outputs: &[PackageOutput]) -> String { + let groups = self.group_outputs(pkg_outputs); + const TOTAL_WIDTH: usize = 50; + let mut details = Vec::with_capacity(groups.len() * 4); + for (i, (output, group)) in groups.iter().enumerate() { + let group_index = i + 1; + let header = format!( + "╭─ Output {} ({}) ", + group_index, + ByteSize::b(output.len() as u64) + ); + let header = if header.len() < TOTAL_WIDTH { + format!("{}{}", header, "─".repeat(TOTAL_WIDTH - header.len())) + } else { + header + }; + details.push(header); + for po in group { + let p = po + .package_path + .strip_prefix(self.base_dir.parent().expect("base_dir is not root")) + .expect("base_path is not a prefix of package_path"); + details.push(format!("│ {} ({})", po.pkg_name, p.display())); + } + let footer = format!("╰──▶ {} \n", group[0].temp_path.display()); + details.push(footer); + } + details.join("\n") + } + + #[allow(clippy::unused_self)] + fn get_package_name(&self, manifest_path: &Path) -> RunnerResult { + trace!("Reading manifest from {}", manifest_path.display()); + let content = fs::read_to_string(manifest_path)?; + let manifest: CargoManifest = toml::from_str(&content).map_err(|e| RunnerError::Toml { + path: manifest_path.to_path_buf(), + source: e, + })?; + debug!("Package name '{}' found in manifest", manifest.package.name); + Ok(manifest.package.name) + } + + #[allow(clippy::unused_self)] + fn keep_temp_files(&self, temp_files: &mut Vec) { + for file in temp_files.drain(..) { + let _ = file.into_temp_path().keep(); + } + } + + pub fn format_test_name(test_case: &Path, base: &Path) -> String { + let name = test_case.strip_prefix(base).map_or_else( + |_| test_case.to_string_lossy().into_owned(), + |relative| { + relative + .components() + .filter_map(|comp| match comp { + Component::Normal(os_str) => Some(os_str.to_string_lossy()), + _ => None, + }) + .collect::>() + .join("::") + }, + ); + format!("difftests::{}", name) + } + + pub fn collect_test_dirs(root: &Path) -> RunnerResult> { + let mut test_cases = Vec::new(); + for entry in fs::read_dir(root)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + if Runner::collect_test_binaries(&path)?.len() >= 2 { + debug!("Test case found: {}", path.display()); + test_cases.push(path.clone()); + } + let mut subdirs = Runner::collect_test_dirs(&path)?; + test_cases.append(&mut subdirs); + } + } + Ok(test_cases) + } + + fn collect_test_binaries(test_case: &Path) -> RunnerResult> { + let mut packages = Vec::new(); + for entry in fs::read_dir(test_case)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() && path.join("Cargo.toml").exists() { + debug!("Found binary package candidate: {}", path.display()); + packages.push(path); + } + } + Ok(packages) + } +} + +pub fn forward_features(cmd: &mut Command) { + cmd.arg("--features"); + #[cfg(feature = "use-compiled-tools")] + { + cmd.arg("difftest/use-compiled-tools"); + } + #[cfg(feature = "use-installed-tools")] + { + cmd.arg("difftest/use-installed-tools"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{fs, io::Write, path::Path, path::PathBuf}; + use tempfile::{NamedTempFile, tempdir}; + + fn dummy_package_output(name: &str, path: &str, output: &[u8], temp: &str) -> PackageOutput { + PackageOutput { + pkg_name: name.to_string(), + package_path: PathBuf::from(path), + output: output.to_vec(), + temp_path: PathBuf::from(temp), + } + } + + #[test] + fn test_group_outputs_multiple_groups() { + let pkg1 = dummy_package_output("foo", "/path/to/foo", b"hello", "tmp1"); + let pkg2 = dummy_package_output("bar", "/path/to/bar", b"world", "tmp2"); + let pkg3 = dummy_package_output("baz", "/path/to/baz", b"hello", "tmp3"); + let outputs = vec![pkg1, pkg2, pkg3]; + let runner = Runner::new(PathBuf::from("dummy_base")); + let groups = runner.group_outputs(&outputs); + assert_eq!(groups.len(), 2); + } + + #[test] + fn test_format_test_name() { + let base = Path::new("/home/user/tests"); + let test_case = base.join("group1/testcase1"); + let formatted = Runner::format_test_name(&test_case, base); + assert_eq!(formatted, "difftests::group1::testcase1"); + } + + #[test] + fn test_get_package_name() { + let mut temp = NamedTempFile::new().expect("failed to create temp file"); + let cargo_toml = r#" + [package] + name = "dummy_package" + version = "0.1.0" + edition = "2021" + "#; + write!(temp, "{}", cargo_toml).expect("failed to write to temp file"); + let runner = Runner::new(PathBuf::from("dummy_base")); + let pkg_name = runner + .get_package_name(temp.path()) + .expect("failed to get package name"); + assert_eq!(pkg_name, "dummy_package"); + } + + #[test] + fn test_collect_packages() { + let temp_dir = tempdir().expect("failed to create temp dir"); + let dir_path = temp_dir.path(); + let pkg_dir = dir_path.join("pkg1"); + fs::create_dir(&pkg_dir).expect("failed to create pkg1 dir"); + fs::write(pkg_dir.join("Cargo.toml"), "[package]\nname = \"pkg1\"") + .expect("failed to write Cargo.toml"); + let runner = Runner::new(PathBuf::from("dummy_base")); + let packages = runner + .collect_packages(dir_path) + .expect("failed to collect packages"); + assert_eq!(packages.len(), 1); + assert_eq!(packages[0], pkg_dir); + } + + #[test] + fn test_collect_test_dirs() { + let temp_dir = tempdir().expect("failed to create temp dir"); + let base = temp_dir.path(); + let test_case_dir = base.join("test_case"); + fs::create_dir(&test_case_dir).expect("failed to create test_case dir"); + let pkg1_dir = test_case_dir.join("pkg1"); + fs::create_dir(&pkg1_dir).expect("failed to create pkg1"); + fs::write(pkg1_dir.join("Cargo.toml"), "[package]\nname = \"pkg1\"") + .expect("failed to write Cargo.toml for pkg1"); + let pkg2_dir = test_case_dir.join("pkg2"); + fs::create_dir(&pkg2_dir).expect("failed to create pkg2"); + fs::write(pkg2_dir.join("Cargo.toml"), "[package]\nname = \"pkg2\"") + .expect("failed to write Cargo.toml for pkg2"); + let test_dirs = Runner::collect_test_dirs(base).expect("failed to collect test dirs"); + assert!(test_dirs.contains(&test_case_dir)); + } + + #[test] + fn test_run_test_case_insufficient_packages() { + let temp_dir = tempdir().expect("failed to create temp dir"); + let test_case_dir = temp_dir.path().join("single_pkg"); + fs::create_dir(&test_case_dir).expect("failed to create test_case dir"); + let pkg_dir = test_case_dir.join("pkg1"); + fs::create_dir(&pkg_dir).expect("failed to create pkg1"); + fs::write(pkg_dir.join("Cargo.toml"), "[package]\nname = \"pkg1\"") + .expect("failed to write Cargo.toml for pkg1"); + let runner = Runner::new(PathBuf::from("dummy_base")); + let result = runner.run_test_case(&test_case_dir); + match result { + Err(RunnerError::InsufficientPackages { count }) => assert_eq!(count, 1), + _ => panic!("Expected InsufficientPackages error"), + } + } + + #[test] + fn test_duplicate_package_names() { + let temp_dir = tempdir().expect("failed to create temp dir"); + let test_case_dir = temp_dir.path().join("dup_pkg"); + fs::create_dir(&test_case_dir).expect("failed to create test_case dir"); + let pkg1_dir = test_case_dir.join("pkg1"); + fs::create_dir(&pkg1_dir).expect("failed to create pkg1"); + fs::write(pkg1_dir.join("Cargo.toml"), "[package]\nname = \"dup_pkg\"") + .expect("failed to write Cargo.toml for pkg1"); + let pkg2_dir = test_case_dir.join("pkg2"); + fs::create_dir(&pkg2_dir).expect("failed to create pkg2"); + fs::write(pkg2_dir.join("Cargo.toml"), "[package]\nname = \"dup_pkg\"") + .expect("failed to write Cargo.toml for pkg2"); + let runner = Runner::new(PathBuf::from("dummy_base")); + let result = runner.run_test_case(&test_case_dir); + match result { + Err(RunnerError::DuplicatePackageName { pkg_name }) => assert_eq!(pkg_name, "dup_pkg"), + _ => panic!("Expected DuplicatePackageName error"), + } + } +} diff --git a/tests/difftests/lib/Cargo.toml b/tests/difftests/lib/Cargo.toml new file mode 100644 index 0000000000..25bb14b3de --- /dev/null +++ b/tests/difftests/lib/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "difftest" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +# See rustc_codegen_spirv/Cargo.toml for details on these features +[features] +use-installed-tools = [ + "spirv-builder/use-installed-tools" +] +use-compiled-tools = [ + "spirv-builder/use-compiled-tools" +] + +[dependencies] +spirv-builder.workspace = true +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +wgpu = { version = "23", features = ["spirv", "vulkan-portability"] } +tempfile = "3.5" +futures = "0.3.31" +bytemuck = "1.21.0" +thiserror = "1.0" + +[lints] +workspace = true diff --git a/tests/difftests/lib/src/config.rs b/tests/difftests/lib/src/config.rs new file mode 100644 index 0000000000..28c6ebb6b3 --- /dev/null +++ b/tests/difftests/lib/src/config.rs @@ -0,0 +1,24 @@ +use serde::Deserialize; +use std::{fs, path::Path}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ConfigError { + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), +} + +#[derive(Debug, Deserialize)] +pub struct Config { + pub output_path: std::path::PathBuf, +} + +impl Config { + pub fn from_path>(path: P) -> Result { + let content = fs::read_to_string(path)?; + let config = serde_json::from_str(&content)?; + Ok(config) + } +} diff --git a/tests/difftests/lib/src/lib.rs b/tests/difftests/lib/src/lib.rs new file mode 100644 index 0000000000..fb32118b29 --- /dev/null +++ b/tests/difftests/lib/src/lib.rs @@ -0,0 +1,18 @@ +pub mod config; +pub mod scaffold; + +#[cfg(test)] +mod tests { + use super::config::Config; + use std::io::Write; + use tempfile::NamedTempFile; + + #[test] + fn test_config_from_path() { + let mut tmp = NamedTempFile::new().unwrap(); + let config_json = r#"{ "output_path": "/tmp/output.txt" }"#; + write!(tmp, "{}", config_json).unwrap(); + let config = Config::from_path(tmp.path()).unwrap(); + assert_eq!(config.output_path.to_str().unwrap(), "/tmp/output.txt"); + } +} diff --git a/tests/difftests/lib/src/scaffold/compute/mod.rs b/tests/difftests/lib/src/scaffold/compute/mod.rs new file mode 100644 index 0000000000..0478faecfd --- /dev/null +++ b/tests/difftests/lib/src/scaffold/compute/mod.rs @@ -0,0 +1,2 @@ +mod wgpu; +pub use wgpu::{RustComputeShader, WgpuComputeTest, WgslComputeShader}; diff --git a/tests/difftests/lib/src/scaffold/compute/wgpu.rs b/tests/difftests/lib/src/scaffold/compute/wgpu.rs new file mode 100644 index 0000000000..1125fd4f39 --- /dev/null +++ b/tests/difftests/lib/src/scaffold/compute/wgpu.rs @@ -0,0 +1,361 @@ +use crate::config::Config; +use bytemuck::Pod; +use futures::{channel::oneshot::Canceled, executor::block_on}; +use spirv_builder::{ModuleResult, SpirvBuilder}; +use std::{ + borrow::Cow, + env, + fs::{self, File}, + io::Write, + path::PathBuf, +}; +use thiserror::Error; +use wgpu::{BufferAsyncError, PipelineCompilationOptions, util::DeviceExt}; + +#[derive(Error, Debug)] +pub enum ComputeError { + #[error("Failed to find a suitable GPU adapter")] + AdapterNotFound, + #[error("Failed to create device: {0}")] + DeviceCreationFailed(String), + #[error("Failed to load shader: {0}")] + ShaderLoadFailed(String), + #[error("Mapping compute output future canceled: {0}")] + MappingCanceled(Canceled), + #[error("Mapping compute output failed: {0}")] + MappingFailed(BufferAsyncError), +} + +/// Trait that creates a shader module and provides its entry point. +pub trait ComputeShader { + fn create_module( + &self, + device: &wgpu::Device, + ) -> Result<(wgpu::ShaderModule, Option), ComputeError>; +} + +/// A compute shader written in Rust compiled with spirv-builder. +pub struct RustComputeShader { + pub path: PathBuf, +} + +impl RustComputeShader { + pub fn new>(path: P) -> Self { + Self { path: path.into() } + } +} + +impl ComputeShader for RustComputeShader { + fn create_module( + &self, + device: &wgpu::Device, + ) -> Result<(wgpu::ShaderModule, Option), ComputeError> { + let builder = SpirvBuilder::new(&self.path, "spirv-unknown-vulkan1.1") + .print_metadata(spirv_builder::MetadataPrintout::None) + .release(true) + .multimodule(false) + .shader_panic_strategy(spirv_builder::ShaderPanicStrategy::SilentExit) + .preserve_bindings(true); + let artifact = builder + .build() + .map_err(|e| ComputeError::ShaderLoadFailed(e.to_string()))?; + + if artifact.entry_points.len() != 1 { + return Err(ComputeError::ShaderLoadFailed(format!( + "Expected exactly one entry point, found {}", + artifact.entry_points.len() + ))); + } + let entry_point = artifact.entry_points.into_iter().next().unwrap(); + + let shader_bytes = match artifact.module { + ModuleResult::SingleModule(path) => { + fs::read(&path).map_err(|e| ComputeError::ShaderLoadFailed(e.to_string()))? + } + ModuleResult::MultiModule(_modules) => { + return Err(ComputeError::ShaderLoadFailed( + "Multiple modules produced".to_string(), + )); + } + }; + + if shader_bytes.len() % 4 != 0 { + return Err(ComputeError::ShaderLoadFailed( + "SPIR-V binary length is not a multiple of 4".to_string(), + )); + } + let shader_words: Vec = bytemuck::cast_slice(&shader_bytes).to_vec(); + let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Compute Shader"), + source: wgpu::ShaderSource::SpirV(Cow::Owned(shader_words)), + }); + Ok((module, Some(entry_point))) + } +} + +/// A WGSL compute shader. +pub struct WgslComputeShader { + pub path: PathBuf, + pub entry_point: Option, +} + +impl WgslComputeShader { + pub fn new>(path: P, entry_point: Option) -> Self { + Self { + path: path.into(), + entry_point, + } + } +} + +impl ComputeShader for WgslComputeShader { + fn create_module( + &self, + device: &wgpu::Device, + ) -> Result<(wgpu::ShaderModule, Option), ComputeError> { + let shader_source = fs::read_to_string(&self.path) + .map_err(|e| ComputeError::ShaderLoadFailed(e.to_string()))?; + let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Compute Shader"), + source: wgpu::ShaderSource::Wgsl(Cow::Owned(shader_source)), + }); + Ok((module, self.entry_point.clone())) + } +} + +/// Compute test that is generic over the shader type. +pub struct WgpuComputeTest { + shader: S, + dispatch: [u32; 3], + output_bytes: u64, +} + +impl WgpuComputeTest +where + S: ComputeShader, +{ + pub fn new(shader: S, dispatch: [u32; 3], output_bytes: u64) -> Self { + Self { + shader, + dispatch, + output_bytes, + } + } + + fn init() -> Result<(wgpu::Device, wgpu::Queue), ComputeError> { + block_on(async { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + #[cfg(target_os = "linux")] + backends: wgpu::Backends::VULKAN, + #[cfg(not(target_os = "linux"))] + backends: wgpu::Backends::PRIMARY, + dx12_shader_compiler: Default::default(), + flags: Default::default(), + gles_minor_version: Default::default(), + }); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: None, + force_fallback_adapter: false, + }) + .await + .ok_or(ComputeError::AdapterNotFound)?; + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: Some("wgpu Device"), + #[cfg(target_os = "linux")] + required_features: wgpu::Features::SPIRV_SHADER_PASSTHROUGH, + #[cfg(not(target_os = "linux"))] + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + memory_hints: Default::default(), + }, + None, + ) + .await + .map_err(|e| ComputeError::DeviceCreationFailed(e.to_string()))?; + Ok((device, queue)) + }) + } + + fn run_internal(self, input: Option) -> Result, ComputeError> + where + I: Sized + Pod, + { + let (device, queue) = Self::init()?; + let (module, entrypoint) = self.shader.create_module(&device)?; + let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("Compute Pipeline"), + layout: None, + module: &module, + entry_point: entrypoint.as_deref(), + compilation_options: PipelineCompilationOptions::default(), + cache: None, + }); + + // Create the output buffer. + let output_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Output Buffer"), + size: self.output_bytes, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: true, + }); + { + // Zero the buffer. + let initial_data = vec![0u8; self.output_bytes as usize]; + let mut mapping = output_buffer.slice(..).get_mapped_range_mut(); + mapping.copy_from_slice(&initial_data); + } + output_buffer.unmap(); + + // Build the bind group. + let bind_group = if let Some(input_val) = input { + let input_bytes = bytemuck::bytes_of(&input_val); + let input_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Input Buffer"), + contents: input_bytes, + usage: wgpu::BufferUsages::UNIFORM, + }); + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &pipeline.get_bind_group_layout(0), + entries: &[ + // Binding 0: uniform input. + wgpu::BindGroupEntry { + binding: 0, + resource: input_buffer.as_entire_binding(), + }, + // Binding 1: storage output. + wgpu::BindGroupEntry { + binding: 1, + resource: output_buffer.as_entire_binding(), + }, + ], + label: Some("Compute Bind Group (with input)"), + }) + } else { + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &pipeline.get_bind_group_layout(0), + entries: &[ + // Binding 0: storage output. + wgpu::BindGroupEntry { + binding: 0, + resource: output_buffer.as_entire_binding(), + }, + ], + label: Some("Compute Bind Group (no input)"), + }) + }; + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Compute Encoder"), + }); + { + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("Compute Pass"), + timestamp_writes: Default::default(), + }); + pass.set_pipeline(&pipeline); + pass.set_bind_group(0, &bind_group, &[]); + pass.dispatch_workgroups(self.dispatch[0], self.dispatch[1], self.dispatch[2]); + } + + // Create a staging buffer. + let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Staging Buffer"), + size: self.output_bytes, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + encoder.copy_buffer_to_buffer(&output_buffer, 0, &staging_buffer, 0, self.output_bytes); + queue.submit(Some(encoder.finish())); + + let buffer_slice = staging_buffer.slice(..); + let (sender, receiver) = futures::channel::oneshot::channel(); + buffer_slice.map_async(wgpu::MapMode::Read, move |res| { + let _ = sender.send(res); + }); + device.poll(wgpu::Maintain::Wait); + block_on(receiver) + .map_err(ComputeError::MappingCanceled)? + .map_err(ComputeError::MappingFailed)?; + let data = buffer_slice.get_mapped_range().to_vec(); + staging_buffer.unmap(); + Ok(data) + } + + /// Runs the compute shader with no input. + pub fn run(self) -> Result, ComputeError> { + self.run_internal::<()>(None) + } + + /// Runs the compute shader with provided input. + pub fn run_with_input(self, input: I) -> Result, ComputeError> + where + I: Sized + Pod, + { + self.run_internal(Some(input)) + } + + /// Runs the compute shader with no input and writes the output to a file. + pub fn run_test(self, config: &Config) -> Result<(), ComputeError> { + let output = self.run()?; + let mut f = File::create(&config.output_path).unwrap(); + f.write_all(&output).unwrap(); + Ok(()) + } + + /// Runs the compute shader with provided input and writes the output to a file. + pub fn run_test_with_input(self, config: &Config, input: I) -> Result<(), ComputeError> + where + I: Sized + Pod, + { + let output = self.run_with_input(input)?; + let mut f = File::create(&config.output_path).unwrap(); + f.write_all(&output).unwrap(); + Ok(()) + } +} + +/// For WGSL, the code checks for "shader.wgsl" then "compute.wgsl". +impl Default for WgslComputeShader { + fn default() -> Self { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + let manifest_path = PathBuf::from(manifest_dir); + let shader_path = manifest_path.join("shader.wgsl"); + let compute_path = manifest_path.join("compute.wgsl"); + + let (file, source) = if shader_path.exists() { + ( + shader_path.clone(), + fs::read_to_string(&shader_path).unwrap_or_default(), + ) + } else if compute_path.exists() { + ( + compute_path.clone(), + fs::read_to_string(&compute_path).unwrap_or_default(), + ) + } else { + panic!("No default WGSL shader found in manifest directory"); + }; + + let entry_point = if source.contains("fn main_cs(") { + Some("main_cs".to_string()) + } else if source.contains("fn main(") { + Some("main".to_string()) + } else { + None + }; + + Self::new(file, entry_point) + } +} + +/// For the SPIR-V shader, the manifest directory is used as the build path. +impl Default for RustComputeShader { + fn default() -> Self { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + Self::new(PathBuf::from(manifest_dir)) + } +} diff --git a/tests/difftests/lib/src/scaffold/mod.rs b/tests/difftests/lib/src/scaffold/mod.rs new file mode 100644 index 0000000000..6ad3d09910 --- /dev/null +++ b/tests/difftests/lib/src/scaffold/mod.rs @@ -0,0 +1 @@ +pub mod compute; diff --git a/tests/difftests/tests/Cargo.lock b/tests/difftests/tests/Cargo.lock new file mode 100644 index 0000000000..686e22f50d --- /dev/null +++ b/tests/difftests/tests/Cargo.lock @@ -0,0 +1,1407 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytemuck" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" + +[[package]] +name = "camino" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "difftest" +version = "0.9.0" +dependencies = [ + "bytemuck", + "futures", + "serde", + "serde_json", + "spirv-builder", + "tempfile", + "thiserror 1.0.69", + "wgpu", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi", + "windows-targets", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" +dependencies = [ + "libm", +] + +[[package]] +name = "glow" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.9.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +dependencies = [ + "bitflags 2.9.0", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.9.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "naga" +version = "23.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.9.0", + "cfg_aliases", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "petgraph", + "rustc-hash", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[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 = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "once_cell" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "raw-string" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0501e134c6905fee1f10fed25b0a7e1261bf676cffac9543a7d0730dec01af2" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rspirv" +version = "0.12.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cf3a93856b6e5946537278df0d3075596371b1950ccff012f02b0f7eafec8d" +dependencies = [ + "rustc-hash", + "spirv", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_codegen_spirv-types" +version = "0.9.0" +dependencies = [ + "rspirv", + "serde", + "serde_json", + "spirv", +] + +[[package]] +name = "rustix" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "simple-compute-rust" +version = "0.0.0" +dependencies = [ + "difftest", + "spirv-std", +] + +[[package]] +name = "simple-compute-wgsl" +version = "0.0.0" +dependencies = [ + "difftest", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.9.0", + "serde", +] + +[[package]] +name = "spirv-builder" +version = "0.9.0" +dependencies = [ + "cargo_metadata", + "memchr", + "raw-string", + "rustc_codegen_spirv-types", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "spirv-std" +version = "0.9.0" +dependencies = [ + "bitflags 1.3.2", + "glam", + "libm", + "num-traits", + "spirv-std-macros", + "spirv-std-types", +] + +[[package]] +name = "spirv-std-macros" +version = "0.9.0" +dependencies = [ + "proc-macro2", + "quote", + "spirv-std-types", + "syn", +] + +[[package]] +name = "spirv-std-types" +version = "0.9.0" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" +dependencies = [ + "arrayvec", + "cfg_aliases", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.9.0", + "bytemuck", + "cfg_aliases", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror 1.0.69", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.9.0", + "block", + "bytemuck", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows", + "windows-core", +] + +[[package]] +name = "wgpu-types" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +dependencies = [ + "bitflags 2.9.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "xml-rs" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" diff --git a/tests/difftests/tests/Cargo.toml b/tests/difftests/tests/Cargo.toml new file mode 100644 index 0000000000..34f08f9250 --- /dev/null +++ b/tests/difftests/tests/Cargo.toml @@ -0,0 +1,53 @@ +[workspace] +resolver = "2" +members = [ + "simple-compute/simple-compute-rust", + "simple-compute/simple-compute-wgsl", +] + +[workspace.package] +edition = "2021" +license = "MIT OR Apache-2.0" +publish = false + +[workspace.lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = [ + 'cfg(target_arch, values("spirv"))' +] } + +[workspace.dependencies] +spirv-builder = { path = "../../../crates/spirv-builder", version = "=0.9.0", default-features = false } +spirv-std = { path = "../../../crates/spirv-std", version = "=0.9.0" } +spirv-std-types = { path = "../../../crates/spirv-std/shared", version = "=0.9.0" } +spirv-std-macros = { path = "../../../crates/spirv-std/macros", version = "=0.9.0" } +difftest = { path = "../../../tests/difftests/lib" } +# External dependencies that need to be mentioned more than once. +num-traits = { version = "0.2.15", default-features = false } +glam = { version = ">=0.22, <=0.29", default-features = false } + +# Enable incremental by default in release mode. +[profile.release] +incremental = true +# HACK(eddyb) this is the default but without explicitly specifying it, Cargo +# will treat the identical settings in `[profile.release.build-override]` below +# as different sets of `rustc` flags and will not reuse artifacts between them. +codegen-units = 256 + +# Compile build-dependencies in release mode with the same settings +# as regular dependencies (including the incremental enabled above). +[profile.release.build-override] +opt-level = 3 +incremental = true +codegen-units = 256 + +# HACK(eddyb) reduce the number of linker exports and/or imports, by avoiding +# inter-CGU linkage, to stay under the 64Ki MSVC limit for `rustc_codegen_spirv` +# when building it in "debug mode" (only relevant to CI for now, realistically), +# i.e. working around this issue: https://github.com/rust-lang/rust/issues/53014. +[profile.dev] +# HACK(eddyb) fewer inter-crate exports/imports (not just inter-CGU), but sadly +# not configurable w/o breaking `Cargo.toml` parsing from non-nightly Cargo +# (moved to `.github/workflows/ci.yaml` as `RUSTFLAGS: -Zshare-generics=off`). +# +# rustflags = ["-Zshare-generics=off"] +codegen-units = 1 diff --git a/tests/difftests/tests/simple-compute/simple-compute-rust/Cargo.toml b/tests/difftests/tests/simple-compute/simple-compute-rust/Cargo.toml new file mode 100644 index 0000000000..b619027e6e --- /dev/null +++ b/tests/difftests/tests/simple-compute/simple-compute-rust/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "simple-compute-rust" +edition.workspace = true + +[lints] +workspace = true + +[lib] +crate-type = ["dylib"] + +# Common deps +[dependencies] + +# GPU deps +[target.'cfg(target_arch = "spirv")'.dependencies] +spirv-std.workspace = true + +# CPU deps +[target.'cfg(not(target_arch = "spirv"))'.dependencies] +difftest.workspace = true diff --git a/tests/difftests/tests/simple-compute/simple-compute-rust/src/lib.rs b/tests/difftests/tests/simple-compute/simple-compute-rust/src/lib.rs new file mode 100644 index 0000000000..a398192b92 --- /dev/null +++ b/tests/difftests/tests/simple-compute/simple-compute-rust/src/lib.rs @@ -0,0 +1,9 @@ +#![cfg(target_arch = "spirv")] +#![no_std] + +use spirv_std::spirv; + +#[spirv(compute(threads(1)))] +pub fn main_cs(#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] output: &mut [u32]) { + output[0] = 42; +} diff --git a/tests/difftests/tests/simple-compute/simple-compute-rust/src/main.rs b/tests/difftests/tests/simple-compute/simple-compute-rust/src/main.rs new file mode 100644 index 0000000000..b98488302a --- /dev/null +++ b/tests/difftests/tests/simple-compute/simple-compute-rust/src/main.rs @@ -0,0 +1,13 @@ +use difftest::config::Config; +use difftest::scaffold::compute::{RustComputeShader, WgpuComputeTest}; + +fn main() { + // Load the config from the harness. + let config = Config::from_path(std::env::args().nth(1).unwrap()).unwrap(); + + // Define test parameters, loading the rust shader from the current crate. + let test = WgpuComputeTest::new(RustComputeShader::default(), [1, 1, 1], 1024); + + // Run the test and write the output to a file. + test.run_test(&config).unwrap(); +} diff --git a/tests/difftests/tests/simple-compute/simple-compute-wgsl/Cargo.toml b/tests/difftests/tests/simple-compute/simple-compute-wgsl/Cargo.toml new file mode 100644 index 0000000000..ae5a79dda3 --- /dev/null +++ b/tests/difftests/tests/simple-compute/simple-compute-wgsl/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "simple-compute-wgsl" +edition.workspace = true + +[lints] +workspace = true + +[dependencies] +difftest.workspace = true diff --git a/tests/difftests/tests/simple-compute/simple-compute-wgsl/shader.wgsl b/tests/difftests/tests/simple-compute/simple-compute-wgsl/shader.wgsl new file mode 100644 index 0000000000..8088eddf31 --- /dev/null +++ b/tests/difftests/tests/simple-compute/simple-compute-wgsl/shader.wgsl @@ -0,0 +1,7 @@ +@group(0) @binding(0) +var output: array; + +@compute @workgroup_size(1) +fn main_cs() { + output[0] = 42u; +} diff --git a/tests/difftests/tests/simple-compute/simple-compute-wgsl/src/main.rs b/tests/difftests/tests/simple-compute/simple-compute-wgsl/src/main.rs new file mode 100644 index 0000000000..cdb733fd3b --- /dev/null +++ b/tests/difftests/tests/simple-compute/simple-compute-wgsl/src/main.rs @@ -0,0 +1,13 @@ +use difftest::config::Config; +use difftest::scaffold::compute::{WgpuComputeTest, WgslComputeShader}; + +fn main() { + // Load the config from the harness. + let config = Config::from_path(std::env::args().nth(1).unwrap()).unwrap(); + + // Define test parameters, loading the wgsl shader from the crate directory. + let test = WgpuComputeTest::new(WgslComputeShader::default(), [1, 1, 1], 1024); + + // Run the test and write the output to a file. + test.run_test(&config).unwrap(); +}