├── .cargo
└── config.toml
├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── .idea
├── .gitignore
├── modules.xml
├── vcs.xml
└── wg-math.iml
├── Cargo.toml
├── LICENSE-APACHE.txt
├── LICENSE-MIT.txt
├── README.md
├── assets
└── gguf
│ └── dummy.gguf
└── crates
├── wgcore-derive
├── Cargo.toml
└── src
│ └── lib.rs
├── wgcore
├── CHANGELOG.md
├── Cargo.toml
├── README.md
├── examples
│ ├── buffer_readback.rs
│ ├── compose.rs
│ ├── compose_dependency.wgsl
│ ├── compose_kernel.wgsl
│ ├── encase.rs
│ ├── encase.wgsl
│ ├── hot_reloading.rs
│ ├── hot_reloading.wgsl
│ ├── overwrite.rs
│ ├── overwritten_dependency.wgsl
│ ├── timestamp_queries.rs
│ └── timestamp_queries.wgsl
└── src
│ ├── composer.rs
│ ├── gpu.rs
│ ├── hot_reloading.rs
│ ├── kernel.rs
│ ├── lib.rs
│ ├── shader.rs
│ ├── shapes.rs
│ ├── tensor.rs
│ ├── timestamps.rs
│ └── utils.rs
├── wgebra
├── CHANGELOG.md
├── Cargo.toml
├── README.md
└── src
│ ├── geometry
│ ├── cholesky.rs
│ ├── cholesky.wgsl
│ ├── eig2.rs
│ ├── eig2.wgsl
│ ├── eig3.rs
│ ├── eig3.wgsl
│ ├── eig4.rs
│ ├── eig4.wgsl
│ ├── inv.rs
│ ├── inv.wgsl
│ ├── lu.rs
│ ├── lu.wgsl
│ ├── mod.rs
│ ├── qr2.rs
│ ├── qr2.wgsl
│ ├── qr3.rs
│ ├── qr3.wgsl
│ ├── qr4.rs
│ ├── qr4.wgsl
│ ├── quat.rs
│ ├── quat.wgsl
│ ├── rot2.rs
│ ├── rot2.wgsl
│ ├── sim2.rs
│ ├── sim2.wgsl
│ ├── sim3.rs
│ ├── sim3.wgsl
│ ├── svd2.rs
│ ├── svd2.wgsl
│ ├── svd3.rs
│ └── svd3.wgsl
│ ├── lib.rs
│ ├── linalg
│ ├── gemm.rs
│ ├── gemm.wgsl
│ ├── gemv.rs
│ ├── gemv.wgsl
│ ├── mod.rs
│ ├── op_assign.rs
│ ├── op_assign.wgsl
│ ├── reduce.rs
│ ├── reduce.wgsl
│ ├── shape.rs
│ └── shape.wgsl
│ └── utils
│ ├── min_max.rs
│ ├── min_max.wgsl
│ ├── mod.rs
│ ├── trig.rs
│ └── trig.wgsl
├── wgparry
├── CHANGELOG.md
├── README.md
├── crates
│ ├── wgparry2d
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
│ └── wgparry3d
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
└── src
│ ├── ball.rs
│ ├── ball.wgsl
│ ├── capsule.rs
│ ├── capsule.wgsl
│ ├── cone.rs
│ ├── cone.wgsl
│ ├── contact.rs
│ ├── contact.wgsl
│ ├── cuboid.rs
│ ├── cuboid.wgsl
│ ├── cylinder.rs
│ ├── cylinder.wgsl
│ ├── lib.rs
│ ├── projection.rs
│ ├── projection.wgsl
│ ├── ray.rs
│ ├── ray.wgsl
│ ├── segment.rs
│ ├── segment.wgsl
│ ├── shape.rs
│ ├── shape.wgsl
│ ├── shape_fake_cone.wgsl
│ ├── shape_fake_cylinder.wgsl
│ ├── triangle.rs
│ └── triangle.wgsl
└── wgrapier
├── CHANGELOG.md
├── README.md
├── crates
├── wgrapier2d
│ ├── Cargo.toml
│ ├── README.md
│ └── src
└── wgrapier3d
│ ├── Cargo.toml
│ ├── README.md
│ └── src
├── examples
├── gravity.rs
└── gravity.wgsl
└── src
├── dynamics
├── body.rs
├── body.wgsl
├── integrate.rs
├── integrate.wgsl
└── mod.rs
└── lib.rs
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [target.wasm32-unknown-unknown]
2 | runner = "wasm-server-runner"
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 | RUSTFLAGS: --deny warnings
12 | RUSTDOCFLAGS: --deny warnings
13 |
14 | jobs:
15 | # Run clippy lints.
16 | clippy:
17 | name: Clippy
18 | runs-on: ubuntu-latest
19 | timeout-minutes: 30
20 | steps:
21 | - name: Checkout repository
22 | uses: actions/checkout@v4
23 |
24 | - name: Install Rust toolchain
25 | uses: dtolnay/rust-toolchain@stable
26 | with:
27 | components: clippy
28 |
29 | - name: Install dependencies
30 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
31 |
32 | - name: Populate target directory from cache
33 | uses: Leafwing-Studios/cargo-cache@v2
34 | with:
35 | sweep-cache: true
36 |
37 | - name: Run clippy lints
38 | run: cargo clippy --locked --workspace --all-targets --all-features -- --deny warnings
39 |
40 | # Check formatting.
41 | format:
42 | name: Format
43 | runs-on: ubuntu-latest
44 | timeout-minutes: 30
45 | steps:
46 | - name: Checkout repository
47 | uses: actions/checkout@v4
48 |
49 | - name: Install Rust toolchain
50 | uses: dtolnay/rust-toolchain@stable
51 | with:
52 | components: rustfmt
53 |
54 | - name: Run cargo fmt
55 | run: cargo fmt --all -- --check
56 |
57 | # Check documentation.
58 | doc:
59 | name: Docs
60 | runs-on: ubuntu-latest
61 | timeout-minutes: 30
62 | steps:
63 | - name: Checkout repository
64 | uses: actions/checkout@v4
65 |
66 | - name: Install Rust toolchain
67 | uses: dtolnay/rust-toolchain@stable
68 |
69 | - name: Install dependencies
70 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
71 |
72 | - name: Populate target directory from cache
73 | uses: Leafwing-Studios/cargo-cache@v2
74 | with:
75 | sweep-cache: true
76 |
77 | - name: Check documentation
78 | run: cargo doc --locked --workspace --all-features --document-private-items --no-deps
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | debug/
4 | target/
5 |
6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
8 | Cargo.lock
9 |
10 | # These are backup files generated by rustfmt
11 | **/*.rs.bk
12 |
13 | # MSVC Windows builds of rustc generate these, which store debugging information
14 | *.pdb
15 |
16 | .lock
17 | dist
18 | assets
19 |
20 | *_bg.wasm
21 | website
22 |
23 | # JetBrain IDEs
24 | #
25 | #
26 | #
27 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
28 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
29 | # and can be added to the global gitignore or merged into this file. For a more nuclear
30 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
31 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
32 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
33 |
34 | # User-specific stuff
35 | .idea/**/workspace.xml
36 | .idea/**/tasks.xml
37 | .idea/**/usage.statistics.xml
38 | .idea/**/dictionaries
39 | .idea/**/shelf
40 |
41 | # AWS User-specific
42 | .idea/**/aws.xml
43 |
44 | # Generated files
45 | .idea/**/contentModel.xml
46 |
47 | # Sensitive or high-churn files
48 | .idea/**/dataSources/
49 | .idea/**/dataSources.ids
50 | .idea/**/dataSources.local.xml
51 | .idea/**/sqlDataSources.xml
52 | .idea/**/dynamic.xml
53 | .idea/**/uiDesigner.xml
54 | .idea/**/dbnavigator.xml
55 |
56 | # Gradle
57 | .idea/**/gradle.xml
58 | .idea/**/libraries
59 |
60 | # Gradle and Maven with auto-import
61 | # When using Gradle or Maven with auto-import, you should exclude module files,
62 | # since they will be recreated, and may cause churn. Uncomment if using
63 | # auto-import.
64 | # .idea/artifacts
65 | # .idea/compiler.xml
66 | # .idea/jarRepositories.xml
67 | # .idea/modules.xml
68 | # .idea/*.iml
69 | # .idea/modules
70 | # *.iml
71 | # *.ipr
72 |
73 | # CMake
74 | cmake-build-*/
75 |
76 | # Mongo Explorer plugin
77 | .idea/**/mongoSettings.xml
78 |
79 | # File-based project format
80 | *.iws
81 |
82 | # IntelliJ
83 | out/
84 |
85 | # mpeltonen/sbt-idea plugin
86 | .idea_modules/
87 |
88 | # JIRA plugin
89 | atlassian-ide-plugin.xml
90 |
91 | # Cursive Clojure plugin
92 | .idea/replstate.xml
93 |
94 | # SonarLint plugin
95 | .idea/sonarlint/
96 |
97 | # Crashlytics plugin (for Android Studio and IntelliJ)
98 | com_crashlytics_export_strings.xml
99 | crashlytics.properties
100 | crashlytics-build.properties
101 | fabric.properties
102 |
103 | # Editor-based Rest Client
104 | .idea/httpRequests
105 |
106 | # Android studio 3.1+ serialized cache file
107 | .idea/caches/build_file_checksums.ser
108 |
109 | .DS_store
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/wg-math.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "crates/wgcore", "crates/wgebra",
4 | "crates/wgparry/crates/wgparry2d", "crates/wgparry/crates/wgparry3d",
5 | "crates/wgrapier/crates/wgrapier2d", "crates/wgrapier/crates/wgrapier3d"
6 | ]
7 | resolver = "2"
8 |
9 | [workspace.dependencies]
10 | nalgebra = { version = "0.33.1", features = ["convert-bytemuck"] }
11 | parry2d = { version = "0.18", features = ["bytemuck", "encase"] }
12 | parry3d = { version = "0.18", features = ["bytemuck", "encase"] }
13 | wgpu = { version = "24", features = ["naga-ir"] }
14 | bytemuck = { version = "1", features = ["derive", "extern_crate_std"] }
15 | anyhow = "1"
16 | async-channel = "2"
17 | naga_oil = "0.17"
18 | thiserror = "1"
19 |
20 | encase = { version = "0.10.0", features = ["nalgebra"] }
21 |
22 | [workspace.lints]
23 | rust.unexpected_cfgs = { level = "warn", check-cfg = [
24 | 'cfg(feature, values("dim2", "dim3"))'
25 | ] }
26 |
27 | [profile.release]
28 | opt-level = 'z'
29 |
30 | [patch.crates-io]
31 | parry3d = { git = "https://github.com/dimforge/parry", branch = "encase" }
32 | parry2d = { git = "https://github.com/dimforge/parry", branch = "encase" }
33 | encase = { git = "https://github.com/sebcrozet/encase", branch = "nalgebra-points" }
34 |
--------------------------------------------------------------------------------
/LICENSE-MIT.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wgmath − GPU scientific computing on every platform
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | -----
13 |
14 | **wgmath** is a set of [Rust](https://www.rust-lang.org/) libraries exposing
15 | re-usable [WebGPU](https://www.w3.org/TR/WGSL/) shaders for scientific computing including:
16 |
17 | - The [**wgcore** crate](https://github.com/dimforge/wgmath/tree/main/crates/wgcore), a centerpiece of the **wgmath**
18 | ecosystem, exposes a set of proc-macros to facilitate sharing and composing shaders across Rust libraries.
19 | - Linear algebra with the [**wgebra** crate](https://github.com/dimforge/wgmath/tree/main/crates/wgebra).
20 | - AI (Large Language Models) with the [**wgml** crate](https://github.com/dimforge/wgml/tree/main).
21 | - Collision-detection with the
22 | [**wgparry2d** and **wgparry3d**](https://github.com/dimforge/wgmath/tree/main/crates/wgparry) crates (still very
23 | WIP).
24 | - Rigid-body physics with the
25 | [**wgrapier2d** and **wgrapier3d**](https://github.com/dimforge/wgmath/tree/main/crates/wgrapier3d) crates (still very
26 | WIP).
27 |
28 | By targeting WebGPU, these libraries run on most GPUs, including on mobile and on the web. It aims to promote open and
29 | cross-platform GPU computing for scientific applications, a field currently strongly dominated by proprietary
30 | solutions (like CUDA).
31 |
32 | ⚠️ All these libraries are still under heavy development and might be lacking some important features. Contributions
33 | are welcome!
34 |
35 | ----
36 |
37 | **See the readme of each individual crate (on the `crates` directory) for additional details.**
38 |
39 | ----
40 |
--------------------------------------------------------------------------------
/assets/gguf/dummy.gguf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimforge/wgmath/95538845080ef8680cf9906f1949623e30be3495/assets/gguf/dummy.gguf
--------------------------------------------------------------------------------
/crates/wgcore-derive/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "wgcore-derive"
3 | authors = ["Sébastien Crozet "]
4 | description = "Proc-macro for composable WGSL shaders."
5 | homepage = "https://wgmath.rs"
6 | repository = "https://github.com/dimforge/wgmath"
7 | version = "0.2.0"
8 | edition = "2021"
9 | license = "MIT OR Apache-2.0"
10 |
11 | [lib]
12 | name = "wgcore_derive"
13 | path = "src/lib.rs"
14 | proc-macro = true
15 |
16 | [dependencies]
17 | syn = "2.0.77"
18 | quote = "1.0.37"
19 | proc-macro2 = "1.0.86"
20 | darling = "0.20.10"
--------------------------------------------------------------------------------
/crates/wgcore/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Unreleased
2 |
3 | ### Added
4 |
5 | - Add `Shader::shader_module` to generate and return the shader’s `ShaderModule`.
6 |
7 | ### Changed
8 |
9 | - Rename `Shader::set_absolute_path` to `Shader::set_wgsl_path`.
10 | - Rename `Shader::absolute_path` to `Shader::wgsl_path`.
11 | - Workgroup memory automatic zeroing is now **disabled** by default due to its significant
12 | performance impact.
13 |
14 | ## v0.2.2
15 |
16 | ### Fixed
17 |
18 | - Fix crash in `HotReloadState` when targetting wasm.
19 |
20 | ## v0.2.1
21 |
22 | ### Fixed
23 |
24 | - Fix build when targeting wasm.
25 |
26 | ## v0.2.0
27 |
28 | ### Added
29 |
30 | - Add support for hot-reloading, see [#1](https://github.com/dimforge/wgmath/pull/1). This includes breaking changes to
31 | the `Shader` trait.
32 | - Add support for shader overwriting, see [#1](https://github.com/dimforge/wgmath/pull/1).
33 |
--------------------------------------------------------------------------------
/crates/wgcore/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "wgcore"
3 | authors = ["Sébastien Crozet "]
4 | description = "Utilities and abstractions for composable WGSL shaders."
5 | homepage = "https://wgmath.rs"
6 | repository = "https://github.com/dimforge/wgmath"
7 | readme = "README.md"
8 | version = "0.2.2"
9 | edition = "2021"
10 | license = "MIT OR Apache-2.0"
11 |
12 | [features]
13 | derive = ["wgcore-derive"]
14 |
15 | [dependencies]
16 | nalgebra = { workspace = true }
17 | wgpu = { workspace = true, features = ["wgsl"] }
18 | bytemuck = { workspace = true }
19 | anyhow = { workspace = true }
20 | async-channel = { workspace = true }
21 | naga_oil = { workspace = true }
22 | encase = { workspace = true }
23 |
24 | wgcore-derive = { version = "0.2", path = "../wgcore-derive", optional = true }
25 |
26 | dashmap = "5"
27 | notify = { version = "7" } # , optional = true }
28 |
29 | # For test_shader_compilation
30 | paste = "1"
31 |
32 | [dev-dependencies]
33 | nalgebra = { version = "0.33", features = ["rand"] }
34 | futures-test = "0.3"
35 | serial_test = "3"
36 | approx = "0.5"
37 | async-std = { version = "1", features = ["attributes"] }
38 |
--------------------------------------------------------------------------------
/crates/wgcore/README.md:
--------------------------------------------------------------------------------
1 | # wgcore − utilities and abstractions for composable WGSL shaders
2 |
3 | **wgcore** provides simple abstractions over shaders and gpu resources based on `wgpu`. It aims to:
4 |
5 | - Expose thin wrappers that are as unsurprising as possible. We do not rely on complex compiler
6 | magic like bitcode generation in frameworks like `cust` and `rust-gpu`.
7 | - Provide a proc-macro (through the `wgcore-derive` crate) to simplifies shader reuse across
8 | crates with very low boilerplate.
9 | - No ownership of the gpu device and queue. While `wgcore` does expose a utility struct
10 | [`gpu::GpuInstance`] to initialize the compute unit, it is completely optional. All the features
11 | of `wgcore` remain usable if the gpu device and queue are already own by, e.g., a game engine.
12 |
13 | ## Shader composition
14 |
15 | #### Basic usage
16 |
17 | Currently, **wgcore** relies on [naga-oil](https://github.com/bevyengine/naga_oil) for shader
18 | composition. Though we are keeping an eye on the ongoing [WESL](https://github.com/wgsl-tooling-wg)
19 | effort for an alternative to `naga-oil`.
20 |
21 | The main value added over `naga-oil` is the `wgcore::Shader` trait and proc-macro. This lets you
22 | declare composable shaders very concisely. For example, if the WGSL sources are at the path
23 | `./shader_sources.wgsl` relative to the `.rs` source file, all that’s needed for it to be composable
24 | is to `derive` she `Shader` trait:
25 |
26 | ```rust ignore
27 | #[derive(Shader)]
28 | #[shader(src = "shader_source.wgsl")]
29 | struct MyShader1;
30 | ```
31 |
32 | Then it becomes immediately importable (assuming the `.wgsl` source itself contains a
33 | `#define_import_path` statement) from another shader with the `shader(derive)` attribute:
34 |
35 | ```rust ignore
36 | #[derive(Shader)]
37 | #[shader(
38 | derive(MyShader1), // This shader depends on the `MyShader1` shader.
39 | src = "kernel.wgsl", // Shader source code, will be embedded in the exe with `include_str!`.
40 | )]
41 | struct MyShader2;
42 | ```
43 |
44 | Finally, if we want to use these shaders from another one which contains a kernel entry-point,
45 | it is possible to declare `ComputePipeline` fields on the struct deriving `Shader`:
46 |
47 | ```rust ignore
48 | #[derive(Shader)]
49 | #[shader(
50 | derive(MyShader1, MyShader2),
51 | src = "kernel.wgsl",
52 | )]
53 | struct MyKernel {
54 | // Note that the field name has to match the kernel entry-point’s name.
55 | main: ComputePipeline,
56 | }
57 | ```
58 |
59 | This will automatically generate the necessary boiler-place for creating the compute pipeline
60 | from a device: `MyKernel::from_device(device)`.
61 |
62 | #### Some customization
63 |
64 | The `Shader` proc-macro allows some customizations of the imported shaders:
65 |
66 | - `src_fn = "function_name"`: allows the input sources to be modified by an arbitrary string
67 | transformation function before being compiled as a naga module. This enables any custom
68 | preprocessor to run before naga-oil.
69 | - `shader_defs = "function_name"`: allows the declaration of shader definitions that can then be
70 | used in the shader in, e.g., `#ifdef MY_SHADER_DEF` statements (as well as `#if` statements and
71 | anything supported by the `naga-oil`’s shader definitions feature).
72 | - `composable = false`: specifies that the shader does not exports any reusable symbols to other
73 | shaders. in particular, this **must** be specified if the shader sources doesn’t contain any
74 | `#define_import_path` statement.
75 |
76 | ```rust ignore
77 | #[derive(Shader)]
78 | #[shader(
79 | derive(MyShader1, MyShader2),
80 | src = "kernel.wgsl",
81 | src_fn = "substitute_aliases",
82 | shader_defs = "dim_shader_defs"
83 | composable = false
84 | )]
85 | struct MyKernel {
86 | main: ComputePipeline,
87 | }
--------------------------------------------------------------------------------
/crates/wgcore/examples/buffer_readback.rs:
--------------------------------------------------------------------------------
1 | use nalgebra::DVector;
2 | use wgcore::gpu::GpuInstance;
3 | use wgcore::tensor::GpuVector;
4 | use wgpu::BufferUsages;
5 |
6 | #[async_std::main]
7 | async fn main() -> anyhow::Result<()> {
8 | // Initialize the gpu device and its queue.
9 | //
10 | // Note that `GpuInstance` is just a simple helper struct for initializing the gpu resources.
11 | // You are free to initialize them independently if more control is needed, or reuse the ones
12 | // that were already created/owned by e.g., a game engine.
13 | let gpu = GpuInstance::new().await?;
14 |
15 | // Create the buffers.
16 | const LEN: u32 = 10;
17 | let buffer_data = DVector::from_fn(LEN as usize, |i, _| i as u32);
18 | let buffer = GpuVector::init(
19 | gpu.device(),
20 | &buffer_data,
21 | BufferUsages::STORAGE | BufferUsages::COPY_SRC,
22 | );
23 | let staging = GpuVector::uninit(
24 | gpu.device(),
25 | LEN,
26 | BufferUsages::COPY_DST | BufferUsages::MAP_READ,
27 | );
28 |
29 | // Queue the operation.
30 | // Encode & submit the operation to the gpu.
31 | let mut encoder = gpu.device().create_command_encoder(&Default::default());
32 | // Copy the result to the staging buffer.
33 | staging.copy_from(&mut encoder, &buffer);
34 | gpu.queue().submit(Some(encoder.finish()));
35 |
36 | let read = DVector::from(staging.read(gpu.device()).await?);
37 | assert_eq!(buffer_data, read);
38 | println!("Buffer copy & read succeeded!");
39 | println!("Original: {:?}", buffer_data);
40 | println!("Readback: {:?}", read);
41 |
42 | Ok(())
43 | }
44 |
--------------------------------------------------------------------------------
/crates/wgcore/examples/compose.rs:
--------------------------------------------------------------------------------
1 | #[cfg(not(feature = "derive"))]
2 | std::compile_error!(
3 | r#"
4 | ###############################################################
5 | ## The `derive` feature must be enabled to run this example. ##
6 | ###############################################################
7 | "#
8 | );
9 |
10 | use nalgebra::DVector;
11 | use std::fmt::Debug;
12 | use wgcore::gpu::GpuInstance;
13 | use wgcore::kernel::{CommandEncoderExt, KernelDispatch};
14 | use wgcore::tensor::GpuVector;
15 | use wgcore::Shader;
16 | use wgpu::{BufferUsages, ComputePipeline};
17 |
18 | // Declare our shader module that contains our composable functions.
19 | // Note that we don’t build any compute pipeline from this wgsl file.
20 | #[derive(Shader)]
21 | #[shader(
22 | src = "compose_dependency.wgsl" // Shader source code, will be embedded in the exe with `include_str!`
23 | )]
24 | struct Composable;
25 |
26 | #[derive(Shader)]
27 | #[shader(
28 | derive(Composable), // This shader depends on the `Composable` shader.
29 | src = "compose_kernel.wgsl", // Shader source code, will be embedded in the exe with `include_str!`.
30 | composable = false // This shader doesn’t export any symbols reusable from other wgsl shaders.
31 | )]
32 | struct WgKernel {
33 | // This ComputePipeline field indicates that the Shader macro needs to generate the boilerplate
34 | // for loading the compute pipeline in `WgKernel::from_device`.
35 | main: ComputePipeline,
36 | }
37 |
38 | #[derive(Copy, Clone, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
39 | #[repr(C)]
40 | pub struct MyStruct {
41 | value: f32,
42 | }
43 |
44 | // Optional: makes the debug output more concise.
45 | impl Debug for MyStruct {
46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 | write!(f, "{}", self.value)
48 | }
49 | }
50 |
51 | #[async_std::main]
52 | async fn main() -> anyhow::Result<()> {
53 | // Initialize the gpu device and its queue.
54 | //
55 | // Note that `GpuInstance` is just a simple helper struct for initializing the gpu resources.
56 | // You are free to initialize them independently if more control is needed, or reuse the ones
57 | // that were already created/owned by e.g., a game engine.
58 | let gpu = GpuInstance::new().await?;
59 |
60 | // Load and compile our kernel. The `from_device` function was generated by the `Shader` derive.
61 | // Note that its dependency to `Composable` is automatically resolved by the `Shader` derive
62 | // too.
63 | let kernel = WgKernel::from_device(gpu.device())?;
64 | println!("######################################");
65 | println!("###### Composed shader sources: ######");
66 | println!("######################################");
67 | println!("{}", WgKernel::flat_wgsl()?);
68 |
69 | // Now, let’s actually run our kernel.
70 | let result = run_kernel(&gpu, &kernel).await;
71 | println!("Result: {:?}", result);
72 |
73 | Ok(())
74 | }
75 |
76 | async fn run_kernel(gpu: &GpuInstance, kernel: &WgKernel) -> Vec {
77 | // Create the buffers.
78 | const LEN: u32 = 10;
79 | let a_data = DVector::from_fn(LEN as usize, |i, _| MyStruct { value: i as f32 });
80 | let b_data = DVector::from_fn(LEN as usize, |i, _| MyStruct {
81 | value: i as f32 * 10.0,
82 | });
83 | let a_buf = GpuVector::init(
84 | gpu.device(),
85 | &a_data,
86 | BufferUsages::STORAGE | BufferUsages::COPY_SRC,
87 | );
88 | let b_buf = GpuVector::init(gpu.device(), &b_data, BufferUsages::STORAGE);
89 | let staging = GpuVector::uninit(
90 | gpu.device(),
91 | LEN,
92 | BufferUsages::COPY_DST | BufferUsages::MAP_READ,
93 | );
94 |
95 | // Encode & submit the operation to the gpu.
96 | let mut encoder = gpu.device().create_command_encoder(&Default::default());
97 | let mut pass = encoder.compute_pass("test", None);
98 | KernelDispatch::new(gpu.device(), &mut pass, &kernel.main)
99 | .bind0([a_buf.buffer(), b_buf.buffer()])
100 | .dispatch(LEN.div_ceil(64));
101 | drop(pass);
102 |
103 | // Copy the result to the staging buffer.
104 | staging.copy_from(&mut encoder, &a_buf);
105 | gpu.queue().submit(Some(encoder.finish()));
106 |
107 | // Read the result back from the gpu.
108 | staging
109 | .read(gpu.device())
110 | .await
111 | .expect("Failed to read result from the GPU.")
112 | }
113 |
--------------------------------------------------------------------------------
/crates/wgcore/examples/compose_dependency.wgsl:
--------------------------------------------------------------------------------
1 | #define_import_path composable::module
2 |
3 | struct MyStruct {
4 | value: f32,
5 | }
6 |
7 | fn shared_function(a: MyStruct, b: MyStruct) -> MyStruct {
8 | return MyStruct(a.value + b.value);
9 | }
--------------------------------------------------------------------------------
/crates/wgcore/examples/compose_kernel.wgsl:
--------------------------------------------------------------------------------
1 | #import composable::module as Dependency
2 |
3 | @group(0) @binding(0)
4 | var a: array;
5 | @group(0) @binding(1)
6 | var b: array;
7 |
8 | @compute @workgroup_size(64, 1, 1)
9 | fn main(@builtin(global_invocation_id) invocation_id: vec3) {
10 | let i = invocation_id.x;
11 | if i < arrayLength(&a) {
12 | a[i] = Dependency::shared_function(a[i], b[i]);
13 | }
14 | }
--------------------------------------------------------------------------------
/crates/wgcore/examples/encase.rs:
--------------------------------------------------------------------------------
1 | #[cfg(not(feature = "derive"))]
2 | std::compile_error!(
3 | r#"
4 | ###############################################################
5 | ## The `derive` feature must be enabled to run this example. ##
6 | ###############################################################
7 | "#
8 | );
9 |
10 | use nalgebra::Vector4;
11 | use wgcore::gpu::GpuInstance;
12 | use wgcore::kernel::{CommandEncoderExt, KernelDispatch};
13 | use wgcore::tensor::GpuVector;
14 | use wgcore::Shader;
15 | use wgpu::{BufferUsages, ComputePipeline};
16 |
17 | #[derive(Copy, Clone, PartialEq, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)]
18 | #[repr(C)]
19 | pub struct BytemuckStruct {
20 | value: f32,
21 | }
22 |
23 | #[derive(Copy, Clone, PartialEq, Debug, Default, encase::ShaderType)]
24 | #[repr(C)]
25 | pub struct EncaseStruct {
26 | value: f32,
27 | // This implies some internal padding, so we can’t rely on bytemuck.
28 | // Encase will handle that properly.
29 | value2: Vector4,
30 | }
31 |
32 | #[derive(Shader)]
33 | #[shader(src = "encase.wgsl", composable = false)]
34 | struct ShaderEncase {
35 | main: ComputePipeline,
36 | }
37 |
38 | #[async_std::main]
39 | async fn main() -> anyhow::Result<()> {
40 | // Initialize the gpu device and its queue.
41 | //
42 | // Note that `GpuInstance` is just a simple helper struct for initializing the gpu resources.
43 | // You are free to initialize them independently if more control is needed, or reuse the ones
44 | // that were already created/owned by e.g., a game engine.
45 | let gpu = GpuInstance::new().await?;
46 |
47 | // Load and compile our kernel. The `from_device` function was generated by the `Shader` derive.
48 | // Note that its dependency to `Composable` is automatically resolved by the `Shader` derive
49 | // too.
50 | let kernel = ShaderEncase::from_device(gpu.device())?;
51 |
52 | // Create the buffers.
53 | const LEN: u32 = 1000;
54 | let a_data = (0..LEN)
55 | .map(|x| EncaseStruct {
56 | value: x as f32,
57 | value2: Vector4::repeat(x as f32 * 10.0),
58 | })
59 | .collect::>();
60 | let b_data = (0..LEN)
61 | .map(|x| BytemuckStruct { value: x as f32 })
62 | .collect::>();
63 | // Call `encase` instead of `init` because `EncaseStruct` isn’t `Pod`.
64 | // The `encase` function has a bit of overhead so bytemuck should be preferred whenever possible.
65 | let a_buf = GpuVector::encase(gpu.device(), &a_data, BufferUsages::STORAGE);
66 | let b_buf = GpuVector::init(gpu.device(), &b_data, BufferUsages::STORAGE);
67 |
68 | // Encode & submit the operation to the gpu.
69 | let mut encoder = gpu.device().create_command_encoder(&Default::default());
70 | let mut pass = encoder.compute_pass("test", None);
71 | KernelDispatch::new(gpu.device(), &mut pass, &kernel.main)
72 | .bind0([a_buf.buffer(), b_buf.buffer()])
73 | .dispatch(LEN.div_ceil(64));
74 | drop(pass);
75 | gpu.queue().submit(Some(encoder.finish()));
76 |
77 | Ok(())
78 | }
79 |
--------------------------------------------------------------------------------
/crates/wgcore/examples/encase.wgsl:
--------------------------------------------------------------------------------
1 | @group(0) @binding(0)
2 | var a: array;
3 | @group(0) @binding(1)
4 | var b: array;
5 |
6 | struct BytemuckStruct {
7 | value: f32,
8 | }
9 |
10 | struct EncaseStruct {
11 | value: f32,
12 | value2: vec4
13 | }
14 |
15 | @compute @workgroup_size(64, 1, 1)
16 | fn main(@builtin(global_invocation_id) invocation_id: vec3) {
17 | let i = invocation_id.x;
18 | if i < arrayLength(&a) {
19 | a[i].value += b[i].value;
20 | a[i].value2 += vec4(b[i].value);
21 | }
22 | }
--------------------------------------------------------------------------------
/crates/wgcore/examples/hot_reloading.rs:
--------------------------------------------------------------------------------
1 | #[cfg(not(feature = "derive"))]
2 | std::compile_error!(
3 | r#"
4 | ###############################################################
5 | ## The `derive` feature must be enabled to run this example. ##
6 | ###############################################################
7 | "#
8 | );
9 |
10 | use wgcore::gpu::GpuInstance;
11 | use wgcore::hot_reloading::HotReloadState;
12 | use wgcore::kernel::{CommandEncoderExt, KernelDispatch};
13 | use wgcore::tensor::GpuScalar;
14 | use wgcore::Shader;
15 | use wgpu::{BufferUsages, ComputePipeline};
16 |
17 | #[derive(Shader)]
18 | #[shader(src = "hot_reloading.wgsl", composable = false)]
19 | struct ShaderHotReloading {
20 | main: ComputePipeline,
21 | }
22 |
23 | #[async_std::main]
24 | async fn main() -> anyhow::Result<()> {
25 | // Initialize the gpu device and its queue.
26 | //
27 | // Note that `GpuInstance` is just a simple helper struct for initializing the gpu resources.
28 | // You are free to initialize them independently if more control is needed, or reuse the ones
29 | // that were already created/owned by e.g., a game engine.
30 | let gpu = GpuInstance::new().await?;
31 |
32 | // Load and compile our kernel. The `from_device` function was generated by the `Shader` derive.
33 | // Note that its dependency to `Composable` is automatically resolved by the `Shader` derive
34 | // too.
35 | let mut kernel = ShaderHotReloading::from_device(gpu.device())?;
36 |
37 | // Create the buffers.
38 | let buffer = GpuScalar::init(
39 | gpu.device(),
40 | 0u32,
41 | BufferUsages::STORAGE | BufferUsages::COPY_SRC,
42 | );
43 | let staging = GpuScalar::init(
44 | gpu.device(),
45 | 0u32,
46 | BufferUsages::COPY_DST | BufferUsages::MAP_READ,
47 | );
48 |
49 | // Init hot-reloading.
50 | let mut hot_reload = HotReloadState::new()?;
51 | ShaderHotReloading::watch_sources(&mut hot_reload)?;
52 |
53 | // Queue the operation.
54 | println!("#############################");
55 | println!("Edit the file `hot_reloading.wgsl`.\nThe updated result will be printed below whenever a change is detected.");
56 | println!("#############################");
57 |
58 | for loop_id in 0.. {
59 | // Detect & apply changes.
60 | hot_reload.update_changes();
61 | match kernel.reload_if_changed(gpu.device(), &hot_reload) {
62 | Ok(changed) => {
63 | if changed || loop_id == 0 {
64 | // We detected a change (or this is the first loop).
65 | // Encode & submit the operation to the gpu.
66 | let mut encoder = gpu.device().create_command_encoder(&Default::default());
67 | // Run our kernel.
68 | let mut pass = encoder.compute_pass("test", None);
69 | KernelDispatch::new(gpu.device(), &mut pass, &kernel.main)
70 | .bind0([buffer.buffer()])
71 | .dispatch(1);
72 | drop(pass);
73 |
74 | // Copy the result to the staging buffer.
75 | staging.copy_from(&mut encoder, &buffer);
76 | gpu.queue().submit(Some(encoder.finish()));
77 |
78 | let result_read = staging.read(gpu.device()).await.unwrap();
79 | println!("Current result value: {}", result_read[0]);
80 | }
81 | }
82 | Err(e) => {
83 | // Hot-reloading failed, likely due to a syntax error in the shader.
84 | println!("Hot reloading error: {:?}", e);
85 | }
86 | }
87 | }
88 |
89 | Ok(())
90 | }
91 |
--------------------------------------------------------------------------------
/crates/wgcore/examples/hot_reloading.wgsl:
--------------------------------------------------------------------------------
1 | @group(0) @binding(0)
2 | var a: u32;
3 |
4 | @compute @workgroup_size(1, 1, 1)
5 | fn main(@builtin(global_invocation_id) invocation_id: vec3) {
6 | a = 1u; // Change this value and save the file while running the `hot_reloading` example.
7 | }
--------------------------------------------------------------------------------
/crates/wgcore/examples/overwrite.rs:
--------------------------------------------------------------------------------
1 | #[cfg(not(feature = "derive"))]
2 | std::compile_error!(
3 | r#"
4 | ###############################################################
5 | ## The `derive` feature must be enabled to run this example. ##
6 | ###############################################################
7 | "#
8 | );
9 |
10 | use nalgebra::DVector;
11 | use std::fmt::Debug;
12 | use wgcore::gpu::GpuInstance;
13 | use wgcore::kernel::{CommandEncoderExt, KernelDispatch};
14 | use wgcore::tensor::GpuVector;
15 | use wgcore::Shader;
16 | use wgpu::{BufferUsages, ComputePipeline};
17 |
18 | // Declare our shader module that contains our composable functions.
19 | // Note that we don’t build any compute pipeline from this wgsl file.
20 | #[derive(Shader)]
21 | #[shader(
22 | src = "compose_dependency.wgsl" // Shader source code, will be embedded in the exe with `include_str!`
23 | )]
24 | struct Composable;
25 |
26 | #[derive(Shader)]
27 | #[shader(
28 | derive(Composable), // This shader depends on the `Composable` shader.
29 | src = "compose_kernel.wgsl", // Shader source code, will be embedded in the exe with `include_str!`.
30 | composable = false // This shader doesn’t export any symbols reusable from other wgsl shaders.
31 | )]
32 | struct WgKernel {
33 | // This ComputePipeline field indicates that the Shader macro needs to generate the boilerplate
34 | // for loading the compute pipeline in `WgKernel::from_device`.
35 | main: ComputePipeline,
36 | }
37 |
38 | #[derive(Copy, Clone, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
39 | #[repr(C)]
40 | pub struct MyStruct {
41 | value: f32,
42 | }
43 |
44 | // Optional: makes the debug output more concise.
45 | impl Debug for MyStruct {
46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 | write!(f, "{}", self.value)
48 | }
49 | }
50 |
51 | #[async_std::main]
52 | async fn main() -> anyhow::Result<()> {
53 | // Initialize the gpu device and its queue.
54 | //
55 | // Note that `GpuInstance` is just a simple helper struct for initializing the gpu resources.
56 | // You are free to initialize them independently if more control is needed, or reuse the ones
57 | // that were already created/owned by e.g., a game engine.
58 | let gpu = GpuInstance::new().await?;
59 |
60 | // Load and compile our kernel. The `from_device` function was generated by the `Shader` derive.
61 | // Note that its dependency to `Composable` is automatically resolved by the `Shader` derive
62 | // too.
63 | let kernel_before_overwrite = WgKernel::from_device(gpu.device())?;
64 | // Run the original shader.
65 | let result_before_overwrite = run_kernel(&gpu, &kernel_before_overwrite).await;
66 |
67 | // Overwrite the sources of the dependency module.
68 | // Since we are running this with `cargo run --example`, the path is relative to the
69 | // `target/debug` folder.
70 | Composable::set_wgsl_path("../../crates/wgcore/examples/overwritten_dependency.wgsl");
71 | // Recompile our kernel.
72 | let kernel_after_overwrite = WgKernel::from_device(gpu.device())?;
73 | // Run the modified kernel.
74 | let result_after_overwrite = run_kernel(&gpu, &kernel_after_overwrite).await;
75 |
76 | println!("Result before overwrite: {:?}", result_before_overwrite);
77 | println!("Result after overwrite: {:?}", result_after_overwrite);
78 |
79 | Ok(())
80 | }
81 |
82 | async fn run_kernel(gpu: &GpuInstance, kernel: &WgKernel) -> Vec {
83 | // Create the buffers.
84 | const LEN: u32 = 10;
85 | let a_data = DVector::from_fn(LEN as usize, |i, _| MyStruct { value: i as f32 });
86 | let b_data = DVector::from_fn(LEN as usize, |i, _| MyStruct {
87 | value: i as f32 * 10.0,
88 | });
89 | let a_buf = GpuVector::init(
90 | gpu.device(),
91 | &a_data,
92 | BufferUsages::STORAGE | BufferUsages::COPY_SRC,
93 | );
94 | let b_buf = GpuVector::init(gpu.device(), &b_data, BufferUsages::STORAGE);
95 | let staging = GpuVector::uninit(
96 | gpu.device(),
97 | LEN,
98 | BufferUsages::COPY_DST | BufferUsages::MAP_READ,
99 | );
100 |
101 | // Encode & submit the operation to the gpu.
102 | let mut encoder = gpu.device().create_command_encoder(&Default::default());
103 | let mut pass = encoder.compute_pass("test", None);
104 | KernelDispatch::new(gpu.device(), &mut pass, &kernel.main)
105 | .bind0([a_buf.buffer(), b_buf.buffer()])
106 | .dispatch(LEN.div_ceil(64));
107 | drop(pass);
108 | // Copy the result to the staging buffer.
109 | staging.copy_from(&mut encoder, &a_buf);
110 | gpu.queue().submit(Some(encoder.finish()));
111 |
112 | // Read the result back from the gpu.
113 | staging
114 | .read(gpu.device())
115 | .await
116 | .expect("Failed to read result from the GPU.")
117 | }
118 |
--------------------------------------------------------------------------------
/crates/wgcore/examples/overwritten_dependency.wgsl:
--------------------------------------------------------------------------------
1 | #define_import_path composable::module
2 |
3 | struct MyStruct {
4 | value: f32,
5 | }
6 |
7 | fn shared_function(a: MyStruct, b: MyStruct) -> MyStruct {
8 | // Same as compose_dependency.wgsl but with a subtraction instead of an addition.
9 | return MyStruct(a.value - b.value);
10 | }
--------------------------------------------------------------------------------
/crates/wgcore/examples/timestamp_queries.rs:
--------------------------------------------------------------------------------
1 | #[cfg(not(feature = "derive"))]
2 | std::compile_error!(
3 | r#"
4 | ###############################################################
5 | ## The `derive` feature must be enabled to run this example. ##
6 | ###############################################################
7 | "#
8 | );
9 |
10 | use wgcore::gpu::GpuInstance;
11 | use wgcore::hot_reloading::HotReloadState;
12 | use wgcore::kernel::{CommandEncoderExt, KernelDispatch};
13 | use wgcore::tensor::GpuVector;
14 | use wgcore::timestamps::GpuTimestamps;
15 | use wgcore::Shader;
16 | use wgpu::{BufferUsages, ComputePipeline};
17 |
18 | #[derive(Shader)]
19 | #[shader(src = "timestamp_queries.wgsl", composable = false)]
20 | struct ShaderTimestampQueries {
21 | main: ComputePipeline,
22 | }
23 |
24 | #[async_std::main]
25 | async fn main() -> anyhow::Result<()> {
26 | // Initialize the gpu device and its queue.
27 | //
28 | // Note that `GpuInstance` is just a simple helper struct for initializing the gpu resources.
29 | // You are free to initialize them independently if more control is needed, or reuse the ones
30 | // that were already created/owned by e.g., a game engine.
31 | let gpu = GpuInstance::new().await?;
32 |
33 | // Load and compile our kernel. The `from_device` function was generated by the `Shader` derive.
34 | // Note that its dependency to `Composable` is automatically resolved by the `Shader` derive
35 | // too.
36 | let mut kernel = ShaderTimestampQueries::from_device(gpu.device())?;
37 |
38 | // Create the buffers.
39 | const LEN: u32 = 2_000_000;
40 | let buffer = GpuVector::init(
41 | gpu.device(),
42 | vec![0u32; LEN as usize],
43 | BufferUsages::STORAGE | BufferUsages::COPY_SRC,
44 | );
45 |
46 | // Init hot-reloading.
47 | // We are setting up hot-reloading so that we can change somme elements in the shader
48 | // (like the iteration count) and see how that affects performances live.
49 | let mut hot_reload = HotReloadState::new()?;
50 | ShaderTimestampQueries::watch_sources(&mut hot_reload)?;
51 |
52 | // Init timestamp queries.
53 | // To measure the time of one kernel, we need two timestamps (one for when it starts and one for
54 | // when it stopped).
55 | let mut timestamps = GpuTimestamps::new(gpu.device(), 2);
56 |
57 | // Queue the operation.
58 | println!("#############################");
59 | println!("Edit the file `timestamp_queries.wgsl` (for example by multiplying or dividing NUM_ITERS by 10).\nThe updated runtime will be printed below whenever a change is detected.");
60 | println!("#############################");
61 |
62 | for _loop_id in 0.. {
63 | // Detect & apply changes.
64 | hot_reload.update_changes();
65 | match kernel.reload_if_changed(gpu.device(), &hot_reload) {
66 | Ok(changed) => {
67 | if changed {
68 | // Clear the timestamps to reuse in the next loop.
69 | timestamps.clear();
70 | // We detected a change (or this is the first loop).
71 |
72 | // Encode & submit the operation to the gpu.
73 | let mut encoder = gpu.device().create_command_encoder(&Default::default());
74 | // Declare a compute pass with timestamps enabled.
75 | let mut pass =
76 | encoder.compute_pass("timestamp_queries_test", Some(&mut timestamps));
77 | // Dispatch our kernel.
78 | KernelDispatch::new(gpu.device(), &mut pass, &kernel.main)
79 | .bind0([buffer.buffer()])
80 | .dispatch(LEN.div_ceil(64));
81 | drop(pass);
82 | // Resolve the timestamp queries.
83 | timestamps.resolve(&mut encoder);
84 | gpu.queue().submit(Some(encoder.finish()));
85 |
86 | // Read and print the kernel’s runtime.
87 | let timestamps_read = timestamps.wait_for_results_ms(gpu.device(), gpu.queue());
88 | println!(
89 | "Current run time: {}ms",
90 | timestamps_read[1] - timestamps_read[0]
91 | );
92 | }
93 | }
94 | Err(e) => {
95 | // Hot-reloading failed, likely due to a syntax error in the shader.
96 | println!("Hot reloading error: {:?}", e);
97 | }
98 | }
99 | }
100 |
101 | Ok(())
102 | }
103 |
--------------------------------------------------------------------------------
/crates/wgcore/examples/timestamp_queries.wgsl:
--------------------------------------------------------------------------------
1 | @group(0) @binding(0)
2 | var a: array;
3 |
4 | @compute @workgroup_size(64, 1, 1)
5 | fn main(@builtin(global_invocation_id) invocation_id: vec3) {
6 | let i = invocation_id.x;
7 | if i < arrayLength(&a) {
8 | const NUM_ITERS: u32 = 10000u;
9 | for (var k = 0u; k < NUM_ITERS; k++) {
10 | a[i] = collatz_iterations(a[i] * 7919);
11 | }
12 | }
13 | }
14 |
15 | // This is taken from the wgpu "hello_compute" example:
16 | // https://github.com/gfx-rs/wgpu/blob/6f5014f0a3441bcbc3eb4223aee454b95904b087/examples/src/hello_compute/shader.wgsl
17 | // (Apache 2 / MIT license)
18 | //
19 | // The Collatz Conjecture states that for any integer n:
20 | // If n is even, n = n/2
21 | // If n is odd, n = 3n+1
22 | // And repeat this process for each new n, you will always eventually reach 1.
23 | // Though the conjecture has not been proven, no counterexample has ever been found.
24 | // This function returns how many times this recurrence needs to be applied to reach 1.
25 | fn collatz_iterations(n_base: u32) -> u32{
26 | var n: u32 = n_base;
27 | var i: u32 = 0u;
28 | loop {
29 | if (n <= 1u) {
30 | break;
31 | }
32 | if (n % 2u == 0u) {
33 | n = n / 2u;
34 | }
35 | else {
36 | // Overflow? (i.e. 3*n + 1 > 0xffffffffu?)
37 | if (n >= 1431655765u) { // 0x55555555u
38 | return 4294967295u; // 0xffffffffu
39 | }
40 |
41 | n = 3u * n + 1u;
42 | }
43 | i = i + 1u;
44 | }
45 | return i;
46 | }
--------------------------------------------------------------------------------
/crates/wgcore/src/composer.rs:
--------------------------------------------------------------------------------
1 | //! Extensions over naga-oil’s Composer.
2 |
3 | use naga_oil::compose::preprocess::Preprocessor;
4 | use naga_oil::compose::{
5 | ComposableModuleDefinition, ComposableModuleDescriptor, Composer, ComposerError, ErrSource,
6 | };
7 |
8 | /// An extension trait for the naga-oil `Composer` to work around some of its limitations.
9 | pub trait ComposerExt {
10 | /// Adds a composable module to `self` only if it hasn’t been added yet.
11 | ///
12 | /// Currently, `naga-oil` behaves strangely (some symbols stop resolving) if the same module is
13 | /// added twice. This function checks if the module has already been added. If it was already
14 | /// added, then `self` is left unchanged and `Ok(None)` is returned.
15 | fn add_composable_module_once(
16 | &mut self,
17 | desc: ComposableModuleDescriptor<'_>,
18 | ) -> Result