Concepts
One typed IR, read by everything downstream — the type checker, both backends, reflection, and a CPU oracle — so they cannot disagree.
One typed IR
The type is the guardrail. Value-nodes carry their type key (vec3<f32>) in the TypeScript type, so a vec2 used where a vec3 belongs is a compile error before any shader compiler runs. That one IR is what the backends, reflection, and oracle below all read — they cannot disagree.
Two backends, one source
WGSL is canonical; GLSL is translated from it. WGSL emits byte-stable for WebGPU; GLSL ES 3.00 re-spells the same IR for WebGL2 — std140 blocks, in/out varyings, remapped intrinsics (atan2→atan) — none of it authored twice.
Reflection
Binding layout is derived, never emitted. reflect(module) reads the IR to recover bind groups, std140/std430 byte offsets, and entry signatures; the host packs its uniforms straight from those offsets.
CPU f64 oracle
The same module also runs on the CPU in f64. Diffing the GPU shader against that mirror on identical inputs catches sibling-path divergences (fill vs outline, CPU vs GPU projection) that hide sub-pixel until zoom amplifies them.
Optimize + lint
Emit is deterministic: optimized, then linted. Standard passes (CSE, DCE, LICM, const-fold) run before WGSL is written, so identical input yields byte-identical output; a linter blocks dead bindings and float-equality before anything ships.
Fail-closed capabilities
Unsupported features throw — they never mis-emit. GLSL ES 3.00 has no compute, SSBO, or MSAA-load, so a capability gate raises UnsupportedFeatureError up front rather than emitting invalid GLSL.
See it both ways
One gradient
module — the source once, then the two emissions it produces. The backends diverge
(vec4<f32> → vec4,
the std140 block, the synthesised main()); the source doesn’t.
gradient.ts — one source
// ═══ @xgis/shader-dsl example — fullscreen gradient pass ═══//// A self-contained render pass: an oversized fullscreen triangle (no vertex buffer)// feeds a fragment stage that mixes two colours by the screen-space UV, modulated by a// uniform. Shows the std140 uniform block + the `If/elif` control-flow combinator. The// /shader-dsl site page packs `top`/`bottom`/`mix_bias` into the UBO from reflect().
import { fn, module, vec2, vec4, f32, mix, f32T, u32T, vec2fT, vec4fT, If, ioStruct, builtin, location, uniformStruct,} from '../src/index.ts'import type { ShaderExample } from './_shared.ts'
// Uniform block — std140-laid-out by reflect(). `top`/`bottom` are the two gradient// endpoints; `mix_bias` shifts the blend up or down.const U = uniformStruct( 'Uniforms', { group: 0, binding: 0, as: 'u' }, { top: vec4fT, bottom: vec4fT, mix_bias: f32T, },)
// Vertex → fragment interface struct.const VsOut = ioStruct('VsOut', { pos: builtin('position', vec4fT), uv: location(0, vec2fT),})
// Oversized fullscreen triangle (3 verts, NDC −1..3) — covers the screen from a single// non-indexed draw with no vertex buffer.const vsFull = fn( 'vs_full', { idx: builtin('vertex_index', u32T) }, (p) => { const pos = vec2(-1, -1) If(p.idx.eq(1), () => { pos.assign(vec2(3, -1)) }).elif(p.idx.eq(2), () => { pos.assign(vec2(-1, 3)) }) return VsOut.construct({ pos: vec4(pos, 0, 1), uv: vec2(pos.x.add(1).mul(0.5), pos.y.add(1).mul(0.5)), }) }, { stage: 'vertex' },)
// Fragment — vertical gradient between the two uniform colours, biased. `vo` (not `in`,// a GLSL reserved word) is the fragment input.const fsGradient = fn( 'fs_gradient', { vo: VsOut }, (p) => { const t = p.vo.uv.y.add(U.field.mix_bias) const rgb = mix(U.field.bottom.rgb, U.field.top.rgb, t) return vec4(rgb, f32(1)) }, { stage: 'fragment', retAttr: '@location(0)' },)
const gradientModule = module({ structs: [U.struct, VsOut.decl], bindings: [U.binding], funcs: [vsFull, fsGradient],})
export const gradient: ShaderExample = { id: 'gradient', title: 'Gradient pass', blurb: 'A two-colour vertical gradient with a biasable blend — the minimal render pass. `top`/`bottom`/`mix_bias` are packed into one std140 uniform block recovered from reflect().', category: 'generic', file: 'gradient-pass.ts', module: gradientModule, renderable: true, controls: { top: { kind: 'const', value: [0.16, 0.5, 0.95, 1] }, // sky blue bottom: { kind: 'const', value: [0.02, 0.03, 0.09, 1] }, // near-black mix_bias: { kind: 'slider', label: 'Mix bias', min: -0.5, max: 0.5, step: 0.01, value: 0 }, },}WGSL — emitModule(m)
struct Uniforms { top: vec4<f32>, bottom: vec4<f32>, mix_bias: f32,}
struct VsOut { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32>,}
@group(0) @binding(0) var<uniform> u: Uniforms;
@vertexfn vs_full(@builtin(vertex_index) idx: u32) -> VsOut { var _av0: vec2<f32> = vec2<f32>(-1.0, -1.0); if ((idx == 1u)) { _av0 = vec2<f32>(3.0, -1.0); } else if ((idx == 2u)) { _av0 = vec2<f32>(-1.0, 3.0); } return VsOut(vec4<f32>(_av0, 0.0, 1.0), vec2<f32>(((_av0.x + 1.0) * 0.5), ((_av0.y + 1.0) * 0.5)));}
@fragmentfn fs_gradient(vo: VsOut) -> @location(0) vec4<f32> { return vec4<f32>(mix(u.bottom.rgb, u.top.rgb, (vo.uv.y + u.mix_bias)), 1.0);}GLSL ES 3.00 — emitGlslModule(m, …)
#version 300 esprecision highp float;precision highp int;
struct VsOut { vec4 pos; vec2 uv;};out vec2 uv;
VsOut vs_full_impl(uint idx) { vec2 _av0 = vec2(-1.0, -1.0); if ((idx == 1u)) { _av0 = vec2(3.0, -1.0); } else if ((idx == 2u)) { _av0 = vec2(-1.0, 3.0); } return VsOut(vec4(_av0, 0.0, 1.0), vec2(((_av0.x + 1.0) * 0.5), ((_av0.y + 1.0) * 0.5)));}
void main() { VsOut _out = vs_full_impl(uint(gl_VertexID)); gl_Position = _out.pos; uv = _out.uv;}#version 300 esprecision highp float;precision highp int;
struct VsOut { vec4 pos; vec2 uv;};layout(std140) uniform Uniforms { vec4 top; vec4 bottom; float mix_bias;} u;in vec2 uv;layout(location = 0) out vec4 _ret;
vec4 fs_gradient_impl(VsOut vo) { return vec4(mix(u.bottom.rgb, u.top.rgb, (vo.uv.y + u.mix_bias)), 1.0);}
void main() { VsOut vo; vo.pos = gl_FragCoord; vo.uv = uv; _ret = fs_gradient_impl(vo);}Each example shows these concepts end to end: the DSL source, the emitted WGSL and GLSL, and the reflection JSON — next to the frame they produce.