Rust-to-WGSL transpiler using proc macros.
Workspace with wgsl-rs, wgsl-rs-ir, wgsl-rs-macros, example, and xtask crates.
The proc-macro converts a parsed Rust module to an owned IR (defined in
wgsl-rs-ir), then emits a WGSL_MODULE static carrying a constructor
that builds that IR at runtime. The runtime crate (wgsl-rs) renders the
IR to WGSL on demand and supports IR-level type substitution for generic
shader instantiation.
Historical data, insights, affirmations, etc.
A key insight is that wgsl-rs maintains two parallel representations:
- Rust World: The code must compile as valid Rust (design decision #1 in DEVLOG.md) that runs on the CPU.
- WGSL World: The proc-macro transpiles to WGSL that runs on the GPU.
These are fundamentally different execution contexts with different memory models, and yet running a wgsl-rs
program should produce roughly the same results in both "worlds".
Program setup (or preamble, if you will) and the runtime behavior is expected to be different for each world, but the results should match, within reason.
cargo build # Build all crates
cargo test # Run all tests
cargo test -p wgsl-rs-macros # Test specific crate (wgsl-rs-macros in this case)
cargo test -- test_name # Run a single test by name
cargo fmt && cargo clippy # Format and lint (note: use `cargo +nightly fmt` — rustfmt.toml uses unstable features)
cargo run -p roundtrip-tests # Run the round-trip tests to ensure the "two worlds" agree
cargo run -p example # Run the example, showing help text about subcommands
cargo run -p example -- show # Show the names of available example modules
cargo run -p example -- source {name} # Validate and print a single example module's WGSL source
cargo expand -p example -- examples::{name} # Expand the example which uses the `wgsl` macro, showing the generated WGSL_MODULE
cargo clippy --all-features # Show all clippy lintsAlways remember to run cargo +nightly fmt after making changes (the rustfmt.toml uses unstable features that require the nightly toolchain; stable cargo fmt silently skips them and may leave formatting diffs).
This repo contains a cargo xtask that provides agents with some
shorthand commands for common development tasks.
cargo xtask wgsl-spec provides access to the WGSL specification without
overwhelming an agent's context window.
cargo xtask wgsl-spec toc # List WGSL spec table of contents
cargo xtask wgsl-spec section <anchor> # Fetch a spec section with subsections
cargo xtask wgsl-spec section --shallow <anchor> # Fetch section without subsections
cargo xtask wgsl-spec section <anchor> <sub> # Fetch a specific subsectioncargo xtask ci provides access to continuous integration actions.
Most importantly this includes a subcommand to run after making changes, before committing or pushing a PR:
cargo xtask ci --help # Show CI subcommands
cargo xtask ci pr-check # Run all checks before pushing a PR- Imports: Standard lib and external crates first, then
use crate::for internal modules - Errors: Use
snafuwith span info for IDE integration (SomethingWrongSnafu { span, note }.fail()?) - Naming: PascalCase types, snake_case functions/modules, SCREAMING_SNAKE_CASE constants
- Patterns:
TryFromfor AST conversions, traits per WGSL builtin, macros for repetitive impls, and useunreachable!when encountering an invariant that is impossible. Similarly use.expect("{reason}")where appropriate. - Spans: Preserve
proc_macro2::Spanon all parsed types for error mapping back to Rust source - Comments: Prefer to split distinct concerns into separate modules with module-level
documentation rather than reaching for
// ===== Section =====headers in a single file. That said,// =====headers are acceptable inside long files (e.g.monomorphize.rs,parse.rs) where splitting into sub-modules would itself be a larger refactor than the organizational benefit warrants. Please document all functions. If parameters are self-explanatory, don't bother writing parameter docs.
All code contributions generated by AI, or in collaboration with AI must be disclosed.
When collaborating with generative AI, please author commits with the format:
{human-author} with {llm-name} {llm-version} <{human-email}>
This can be accomplished easily with two steps:
- A normal commit via
git commit ... - Amend the author with
git commit --amend --author "{human-author} with {llm-name} {llm-version} <{human-email}>"
For more information, see NLnet's AI Disclosure Policy.
The DEVLOG is a set of long-lived development notes.
Most importantly, it contains design decisions made during development.
These decisions answer why wgsl-rs is the way it is.
Each design decision should have an entry with the heading "### YYYY-MM-DD: {description}", followed by an explanation of the problem, the decision and justification, etc. Multiple decisions made on the same day should be separate entries — the date is metadata; what matters is that each entry captures one distinct decision.