X-GIS

Concept

Compile pipeline.

What happens between .xgis source and a rendered frame: how the language turns into WGSL the GPU can run.

Stages

.xgis source String Lexer Token[] Parser AST lower() IR (Scene) optimize() constant folding · expression classify emit() codegen() SceneCommands runtime instructions ShaderVariant[] WGSL programs → Runtime + GPU / Canvas 2D fallback ←

Lexer + Parser

The lexer in @xgis/compiler recognizes the usual tokens (identifiers, numbers, strings, punctuation) plus keywords reserved by the language: source, layer, background, preset, symbol, keyframes, fn, and the modifier prefix z<N>:.

The parser produces a typed AST (see compiler/src/parser/ast.ts): every top-level statement becomes one of SourceStatement, LayerStatement, BackgroundStatement, PresetStatement, etc.

AST → IR (Scene)

lower() walks the AST and produces the Scene IR — a flat list of RenderNode records, one per layer, with utility classes already resolved into structured fields (fill, stroke, opacity, size, etc.). Presets get expanded inline. Keyframes get attached to their referencing layers.

The IR is what the optimizer reads and what the codegen writes from. It deliberately knows nothing about WGSL or GPU buffers — that keeps the language semantics testable in pure TypeScript.

Optimizer: constant fold + classify

Every expression is classified into one of four buckets:

Expression Class Handling
360 / 12constantFolded to 30 at compile time
opacity-[interpolate(zoom, 8, 40, 16, 100)]zoom-dependentCPU-interpolated per frame
speed / 50 | clamp(4,24)per-feature GPUWGSL codegen, evaluated in vertex shader
threat_size(5)constant (user fn)User function inlined and folded

Classification decides which pipeline the layer needs and which uniforms get uploaded each frame.

Codegen: WGSL shader variants

Layers with no per-feature GPU expressions share a common tile_default shader pipeline. Layers that DO use per-feature expressions (e.g., fill match(.continent)) get a custom WGSL variant emitted at compile time — the match table becomes a chained if/elif in the fragment shader, with branch values constant-baked from the IR.

Variants are deduplicated by content hash so a 50-layer scene with identical styling produces one pipeline, not 50.

Runtime

@xgis/runtime consumes the emitted SceneCommands and ShaderVariants, owns the GPU device, manages tile catalogs (PMTiles MVT / GeoJSON), and dispatches per-frame draws. WebGPU is the primary backend; Canvas 2D engages as fallback when no adapter is available.

The runtime is the only component that touches the GPU. Compiler unit tests can fully exercise the language without a graphics context.

Was this page helpful?

Tell us what's missing