Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hello, Triangle

This chapter walks through the canonical hello_triangle example end to end. The module is ordinary Rust that the #[wgsl] macro transpiles to WGSL, and it is also a valid Rust module you can compile and test.

The source

#![allow(unused)]
fn main() {
#[wgsl]
pub mod hello_triangle {
    use wgsl_rs::std::*;

    uniform!(group(0), binding(0), FRAME: u32);

    #[vertex]
    pub fn vtx_main(#[builtin(vertex_index)] vertex_index: u32) -> Vec4f {
        const POS: [Vec2f; 3] = [vec2f(0.0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5)];
        let position = POS[vertex_index as usize];
        vec4f(position.x, position.y, 0.0, 1.0)
    }

    #[fragment]
    pub fn frag_main() -> Vec4f {
        vec4f(1.0, sin(f32(get!(FRAME)) / 128.0), 0.0, 1.0)
    }
}
}

#[wgsl] pub mod hello_triangle { ... }

The #[wgsl] attribute marks a module for transpilation. The macro consumes the module body, builds an owned IR, and emits a WGSL_SOURCE static containing the generated shader text. The module remains valid Rust: the functions are callable from the CPU side, and the types resolve against wgsl_rs::std.

use wgsl_rs::std::*

The glob import is required. It brings the WGSL type aliases (Vec2f, Vec3f, Vec4f, ...), constructor functions (vec2f, vec3f, vec4f, ...), and the built-in WGSL functions (sin, cos, dot, ...) into scope so the Rust body type-checks and maps one-to-one onto WGSL declarations.

uniform!(...)

#![allow(unused)]
fn main() {
uniform!(group(0), binding(0), FRAME: u32);
}

The uniform! macro declares a uniform binding that is visible in both worlds. It expands to a WGSL var<uniform> declaration in the generated source and to a Rust handle that the runtime can bind and read. Here FRAME is a u32 at group 0, binding 0.

Entry points: #[vertex] and #[fragment]

Functions annotated with #[vertex] and #[fragment] become WGSL entry points tagged with @vertex and @fragment respectively. Other functions in the module without these annotations transpile to plain WGSL functions.

#[builtin(vertex_index)]

#![allow(unused)]
fn main() {
pub fn vtx_main(#[builtin(vertex_index)] vertex_index: u32) -> Vec4f
}

Argument annotations carry WGSL I/O attributes through to the generated signature. #[builtin(vertex_index)] becomes @builtin(vertex_index) in the WGSL output. The same mechanism supports @location(n), @interpolate(...), and other I/O attributes via the corresponding #[...] annotations.

Vector types and constructors

Vec4f and Vec2f are type aliases for vec4<f32> and vec2<f32> exposed by wgsl_rs::std. The lowercase vec2f / vec4f functions are the matching constructors. They mirror WGSL exactly, so Rust expressions like vec4f(1.0, 0.0, 0.0, 1.0) transpile directly to vec4<f32>(1.0, 0.0, 0.0, 1.0).

get!(FRAME)

#![allow(unused)]
fn main() {
sin(f32(get!(FRAME)) / 128.0)
}

get!(...) is the runtime accessor for a declared uniform. On the Rust side it reads the bound value; in the generated WGSL it expands to the bare uniform reference FRAME. This lets the same expression serve both CPU evaluation (e.g. in dispatch-runtime tests) and the shader.

Generated WGSL

@group(0) @binding(0) var<uniform> FRAME: u32;

@vertex
fn vtx_main(@builtin(vertex_index) vertex_index: u32) -> vec4<f32> {
    const POS: array<vec2<f32>, 3> = array<vec2<f32>, 3>(vec2<f32>(0.0, 0.5), vec2<f32>(-0.5, -0.5), vec2<f32>(0.5, -0.5));
    var position: vec2<f32> = POS[vertex_index];
    return vec4<f32>(position.x(), position.y(), 0.0, 1.0);
}

@fragment
fn frag_main() -> vec4<f32> {
    return vec4<f32>(1.0, sin(f32(FRAME) / 128.0), 0.0, 1.0);
}

Note how each Rust construct maps onto WGSL: const to const, let to var, array literals to explicit array<T, N>(...) constructors, and get!(FRAME) to the bare FRAME reference.

Validation

#[wgsl] auto-generates a hidden test:

#![allow(unused)]
fn main() {
#[test]
fn __validate_wgsl() { /* ... */ }
}

For non-template modules this test feeds WGSL_SOURCE through naga and fails on any validation error. Run it with:

cargo test hello_triangle

A passing test means the transpiled WGSL is well-formed according to naga.