Control flow
A loop has to be one the GPU can finish, and a choice between two values has to be one the target has an operator for.
-
ifAn
if, as written.TypeScript export function capped(x: f32): f32 {if (x > 1.) {return 1.}return x}Emitted WGSL fn capped(x: f32) -> f32 {if ((x > 1.0)) {return 1.0;}return x;} -
Counted
forA counted loop: an integer variable, a constant step, and an exit that compares it to a bound the body does not write. block-scope
TypeScript export function ramp(x: f32): f32 {let acc: f32 = 0.for (let i = 0; i < 4; i += 1) {acc = acc + x * f32(i)}return acc}Emitted WGSL fn ramp(x: f32) -> f32 {var acc: f32 = 0.0;for (var i: i32 = 0; (i < 4); i += 1) {acc = (acc + (x * f32(i)));}return acc;} -
forto a runtime boundThe start and the bound may be values the program learns at run time, and no trip count is too many.
TypeScript export function ramp(x: f32, n: i32): f32 {let acc: f32 = 0.for (let i = 0; i < n; i += 1) {acc = acc + x}return acc}Emitted WGSL fn ramp(x: f32, n: i32) -> f32 {var acc: f32 = 0.0;for (var i: i32 = 0; (i < n); i += 1) {acc = (acc + x);}return acc;} -
whileA loop with a counter the compiler adds. Nothing checks that the body moves toward the bound.
TypeScript export function halve(x: f32): f32 {let acc: f32 = xlet w: i32 = 0while (w < 4) {acc = acc * 0.5w = w + 1}return acc}Emitted WGSL fn halve(x: f32) -> f32 {var acc: f32 = x;var w: i32 = 0;for (var _w: i32 = 0; (w < 4); _w = (_w + 1)) {acc = (acc * 0.5);w = (w + 1);}return acc;} -
switchA
switch. Each case ends with abreak, which the lowering drops. A case whose end is reachable is refused (TS8017), because TypeScript would run on into the next case and WGSL runs only the one. bitfield-bandsTypeScript export function band(m: i32): f32 {let v: f32 = 0.switch (m) {case 0:v = 0.25breakdefault:v = 1.break}return v}Emitted WGSL fn band(m: i32) -> f32 {var v: f32 = 0.0;switch m {case 0: {v = 0.25;}default: {v = 1.0;}}return v;} -
Ternary on a scalar
A
selecton WGSL, and the ternary each GLSL ES 3.00 driver has.TypeScript export function nearer(a: f32, b: f32): f32 {return a < b ? a : b}Emitted WGSL fn nearer(a: f32, b: f32) -> f32 {return select(b, a, (a < b));} -
Ternary on a struct
Neither target has an operator for it, so the value is hoisted into a slot and an
if. pick-compositeTypeScript export function chosen(warm: Palette, cool: Palette, c: bool): vec3 {const shade: Palette = c ? warm : coolreturn shade.lo}Emitted WGSL fn chosen(warm: Palette, cool: Palette, c: bool) -> vec3<f32> {var _sel0: Palette;if (c) {_sel0 = warm;} else {_sel0 = cool;}let shade = _sel0;return shade.lo;} -
breakA
break, which is how a counted loop leaves early. julia-twinTypeScript export function escape(x: f32): f32 {let acc: f32 = xfor (let i = 0; i < 8; i += 1) {acc = acc * 2.if (acc > 1.) {break}}return acc}Emitted WGSL fn escape(x: f32) -> f32 {var acc: f32 = x;for (var i: i32 = 0; (i < 8); i += 1) {acc = (acc * 2.0);if ((acc > 1.0)) {break;}}return acc;} -
continueA
continue. In aswitchthat no loop encloses it is refused.TypeScript export function odds(x: f32): f32 {let acc: f32 = 0.for (let i = 0; i < 4; i += 1) {if (i === 2) {continue}acc = acc + x}return acc}Emitted WGSL fn odds(x: f32) -> f32 {var acc: f32 = 0.0;for (var i: i32 = 0; (i < 4); i += 1) {if ((i == 2)) {continue;}acc = (acc + x);}return acc;} -
discardA
discard, in a fragment entry or in a function one calls. cutoutTypeScript @fragmentexport function fs(@builtin("position") frag: vec4): vec4 {if (frag.x < 0.5) {discard}return vec4(1.)}Emitted WGSL @fragmentfn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {if ((frag.x < 0.5)) {discard;}return vec4<f32>(1.0, 1.0, 1.0, 1.0);}