Chapter 04
Control Flow & Functions
WGSL functions look compact, but the compiler is strict about return paths, loop structure, and where control flow can diverge across invocations. Treat each branch as something validation must reason about.
Functions are typed contracts
Parameters and return values are explicit. Helper functions cannot secretly access entry-point builtins; pass the values they need.
fn wave(i : u32, scale : f32) -> f32 {
return sin(f32(i) * scale);
}
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id : vec3u) {
data[id.x] = wave(id.x, 0.0625);
}
Branches can be data-dependent
Ordinary arithmetic can branch per invocation. Operations that need uniform control flow, such as some derivative and subgroup paths, require more care. Studio diagnostics are the fastest way to see which rule was hit.
fn classify(x : f32) -> f32 {
if (x < 0.0) {
return -1.0;
}
return 1.0;
}
Loops should expose their exit
Keep loop bounds easy to read when a pass touches buffers. A good loop says which index moves, which bound stops it, and which buffer location is affected.
fn sum4(base : u32) -> f32 {
var total = 0.0;
for (var k = 0u; k < 4u; k = k + 1u) {
total = total + data[base + k];
}
return total;
}