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

Vertex / Fragment / Compute

WGSL has three shader stages, each with its own entry-point attribute. wgsl-rs exposes them as Rust attributes that the macro translates to @vertex, @fragment, and @compute.

AttributeWGSLNotes
#[vertex]@vertexOne per pipeline's vertex stage.
#[fragment]@fragmentOne per pipeline's fragment stage.
#[compute]@computeRequires #[workgroup_size(...)].

A module may contain any combination of entry points. Functions without these attributes transpile to ordinary WGSL functions.

Vertex

A vertex entry point takes per-vertex/per-instance inputs (builtins and location-tagged values) and returns a position. Returning a bare Vec4f is automatically annotated with @builtin(position); see Default Annotations.

#![allow(unused)]
fn main() {
#[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)
}
}
@vertex
fn vtx_main(@builtin(vertex_index) vertex_index: u32) -> vec4<f32> {
  /* ... */
}

Fragment

A fragment entry point takes inter-stage inputs (locations and builtins such as @builtin(front_facing)) and returns a color. Returning a bare Vec4f is automatically annotated with @location(0).

#![allow(unused)]
fn main() {
#[fragment]
pub fn frag_main() -> Vec4f {
    vec4f(1.0, 0.0, 0.0, 1.0)
}
}
@fragment
fn frag_main() -> vec4<f32> {
  return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}

Compute

A compute entry point must declare a workgroup size. Use a single integer for a 1D dispatch or three integers for a 3D dispatch:

#[compute]
#[workgroup_size(64)]
pub fn main(#[builtin(global_invocation_id)] global_id: Vec3u) {
    let idx = global_id.x() as usize;
    /* ... */
}

#[compute]
#[workgroup_size(8, 8, 1)]
pub fn tiled(#[builtin(global_invocation_id)] global_id: Vec3u) {
    let x = global_id.x();
    let y = global_id.y();
    /* ... */
}
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { /* ... */ }

@compute @workgroup_size(8, 8, 1)
fn tiled(@builtin(global_invocation_id) global_id: vec3<u32>) { /* ... */ }

Compute entry points frequently take no return value (a () return type) and access storage or workgroup resources via the binding macros (see Binding Macros).

Inputs

Entry-point inputs are declared as ordinary function parameters. Each parameter may carry an I/O attribute:

  • #[builtin(NAME)] — a WGSL builtin value.
  • #[location(N)] — a per-vertex attribute (vertex stage) or inter-stage value (fragment stage).

For complex inter-stage IO, use a struct input/output; see Inter-stage IO.

All Three Stages Together

A single module may declare all three stages. The example module below shows vertex, fragment, and compute entry points coexisting (see Binding Macros for storage! and get_mut!):

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

    storage!(group(0), binding(0), read_write, COUNTER: u32);

    #[vertex]
    pub fn vs_main(#[builtin(vertex_index)] vi: u32) -> Vec4f {
        vec4f(0.0, 0.0, 0.0, 1.0)
    }

    #[fragment]
    pub fn fs_main() -> Vec4f {
        vec4f(1.0, 1.0, 1.0, 1.0)
    }

    #[compute]
    #[workgroup_size(64)]
    pub fn cs_main(#[builtin(global_invocation_id)] gid: Vec3u) {
        let i = gid.x() as usize;
        get_mut!(COUNTER);
    }
}
}