Concept · interactive
Compile graph — live.
Type xgis on the left, the compiler runs in the browser, every stage on the right updates as you type — tokens, AST, IR Scene, optimised IR, paint-routing decisions, ComputePlanEntry list, and the actual WGSL the runtime would hand to WebGPU.
match() on a string property — lowers to a compute kernel
1 Tokens Lexer · — tokens
2 AST Parser · top-level statements: —
3 IR Scene lower() · pre-optimisation · blueprint-style
raw IR JSON
4 Optimised IR optimize() · merge-layers + fold-stops + fold-case + dead-layer-elim
raw optimised IR JSON
5 Paint routing paint-routing · per axis classification
5b Palette atlas collectPalette · — constants · — gradients
6 Compute plan planComputeKernels · — entries, — unique kernels
7 Render-node WGSL shader-gen → shader-dsl IR · — variants
Each render node's fill / stroke paint is a typed
shader-dsl IR node (NodeLike<vec4<f32>>),
not a hand-built string. The WGSL below is that node lowered through
@xgis/shader-dsl's emitExpr
— the same DSL + emitter the runtime's geometry shaders (polygon, line, point,
raster, text) are authored in. Constants fold to literals; data-driven paint
becomes an unpack4x8unorm(compute_out[feat_id])
read wired to Stage 8's kernel.
7b Full assembled shader runtime geometry composer · @xgis/shader-dsl emitModule
The COMPLETE WGSL the runtime hands to WebGPU — the geometry shader (vertex +
fragment + projection / log-depth / back-face cull) the runtime authors in
@xgis/shader-dsl, per geometry kind.
Polygon splices the first render node's compiled
fill / stroke paint (it is the one composer that takes a
ShaderVariant); line / point / raster /
text show their base template — their paint is applied through their own path.
Stages 7 + 8 above are the fragments that get spliced in here.
8 Emitted compute kernels compute-gen · kernel-by-kernel · data-driven only
9 Live GPU timing measure runs in your browser via timestamp-query
Each compute kernel emitted above is dispatched onto your local
WebGPU device with synthetic feature data, the compute pass is
wrapped in a timestamp-query
pair, and the median / p95 / mean ± std over a few hundred
dispatches are reported. Use it to compare
strategies — change the source between a small match()
(if-chain) and a large match() (LUT) and watch the median shift,
or bump the synthetic feature count to see how the kernel scales.
Output buffers are read-and-discarded; nothing renders.
Routes — quick reference
Folded to a WGSL literal at compile time. Zero per-frame cost.
Pre-baked gradient atlas. One textureSampleLevel per fragment.
Per-feature compute kernel. Fragment shader reads from compute_out[feat_id].
CPU resolves every frame, writes a uniform. Spec-complete but never the fast path.
Kernel lowering — strategies
Inside a ComputeFeature kernel, compute-gen
picks the WGSL shape based on arm count. The threshold is
MATCH_LUT_THRESHOLD = 16
in compiler/src/codegen/compute-gen.ts.
Sequential if (id == K0) / else if branches. Cheap for small arm sets — the WGSL compiler reduces them well + branch prediction matches per fragment.
Const array<vec4<f32>, N> baked into the shader module. Single LUT[id] indexed read regardless of arm count. Demotiles' 428-country palette is the canonical fit.
Problem on this page?