With wgsl-rs you write a subset of Rust code and it automatically generates WGSL shaders and wgpu runtime linkage.
Rust code written this way is fully operational (it can be run on the CPU) while the transpiled WGSL is isomorphic and
should generate the same results on the GPU.
In short, with wgsl-rs, you can unit test and run your code on the CPU in Rust, and use the generated WGSL on the GPU,
while sharing the same type definitions between the two.
Procedural macros are provided by the wgsl-rs-macros crate.
This project is funded through NGI Zero Commons, a fund established by NLnet with financial support from the European Commission's Next Generation Internet program. Learn more at the 2025 NLnet project page.
This work will always be free and open source. If you use it (outright or for inspiration), please consider donating.
There is a project plan for getting to beta here
Yes! See the example, which transpiles the shader from Tour of WGSL.
wgsl-rs validates your WGSL at compile-time using naga.
Validation errors are mapped back to Rust source spans, so they show up in your IDE via rust-analyzer.
| Module Type | Validation |
|---|---|
| Standalone (no imports) | Compile-time via naga |
With imports from other #[wgsl] modules |
Test-time via auto-generated #[test] function |
#[wgsl(skip_validation)] |
No validation |
Why test-time for modules with imports?
Modules that import from other #[wgsl] modules cannot be validated at compile-time because
the imported symbols aren't available during macro expansion. Instead, wgsl-rs generates a
#[test] fn __validate_wgsl() that validates the concatenated WGSL source at test-time.
// Standalone module - validated at compile-time
#[wgsl]
pub mod constants {
pub const PI: f32 = 3.14159;
}
// Module with imports - validated at test-time
#[wgsl]
pub mod shader {
use super::constants::*;
pub fn circle_area(r: f32) -> f32 {
PI * r * r
}
}
// Skip all validation
#[wgsl(skip_validation)]
pub mod experimental {
// ...
}You can also validate modules at runtime using Module::validate():
use wgsl_rs::wgsl;
#[wgsl]
pub mod my_shader {
// ...
}
fn main() {
// Validate manually (requires "validation" feature)
my_shader::WGSL_MODULE.validate().expect("WGSL validation failed");
}To disable validation entirely, disable the validation feature:
[dependencies]
wgsl-rs = { version = "...", default-features = false }Maybe — it depends on your needs.
- Lower barrier to entry: No custom Rust compiler backend required.
- Works with stable Rust: No need for nightly or custom toolchains.
- Editor support: The
#[wgsl]macro makes supported syntax explicit, so your editor (via rust-analyzer) can help you write valid code. - Immediate WGSL output: Use, inspect, and debug the generated WGSL anywhere WGSL is supported, including browsers and non-Rust projects.
- Human readable WGSL output: The WGSL that
wgsl-rsproduces is very close in structure to the Rust code you write, including binding names and types. - Easy interop: Generated WGSL can be used in any WebGPU environment.
- WGSL only: Only works on platforms that support WGSL.
- Limited to WebGPU features: No support for features not present in WGSL (e.g., bindless resources).
- Subset of Rust: Only a strict subset of Rust is supported.
- No traits
- No borrowing
- Very restricted module support
Note: wgsl-rs and Rust-GPU are not mutually exclusive! You can start with wgsl-rs and switch to Rust-GPU when you need more advanced features. I'm working on making them co-habitable.
The project is split into a few parts:
wgsl-rs-macrosProvides thewgslprocedural macro for writing WGSL modules in Rust. Handles parsing and code generation for the supported Rust subset.wgsl-rsProvides theModuletype,wgsl::std, and exports thewgslmacro.
There's also a devlog that explains some of the decisions and tradeoffs made during the making of this library.
Contributions, feedback, and questions are welcome!
