PATTERN Cited by 1 source
Shader source translator pipeline¶
A shader source translator pipeline maintains shader source in one canonical language / dialect and emits equivalent shaders in every target language the runtime backends need. A combination of an in-house preprocessor (for language-level upgrades) plus an open-source translator (for IR-level conversion) is the typical shape.
When to reach for it¶
- You run on multiple graphics backends simultaneously (WebGL + WebGPU; or native Metal + Vulkan + D3D12; or combinations).
- Each backend requires a different shader language (GLSL, WGSL, MSL, HLSL, SPIR-V).
- Duplicating shaders by hand in every target language is not feasible — the maintenance cost multiplies by the number of backends.
The naïve approach (don't do this)¶
Maintain N copies of every shader, one per target language. Engineers must update each when the shader changes. Drift is guaranteed; correctness parity across backends is unverifiable.
The translator-pipeline shape¶
Canonical source language (e.g. WebGL-1 GLSL)
│
▼
Custom preprocessor
- Parse + normalize older-dialect features
- Resolve #include-style modularity
- Rewrite to a newer dialect
- Extract metadata (input types, data layouts)
│
├──► Emit newer GLSL ──► WebGL backend
│
▼
Open-source translator (e.g. naga)
- newer GLSL → IR
- IR → target language
│
├──► WGSL ──► WebGPU backend
├──► MSL ──► Metal backend
└──► SPIR-V ──► Vulkan backend
Key idea: don't try to write a translator from scratch. Use an existing open-source one at the IR level (naga is the canonical one in the WebGPU ecosystem); invest the in-house work in the preprocessor — handling your specific source-language quirks and emitting something the translator accepts.
Why a preprocessor is usually required¶
Translators like naga typically target a modern dialect of their source language. A mature codebase's shaders are often in an older dialect with language-level differences that the translator can't auto-handle. Figma's case: WebGL-1 GLSL (individual uniform declarations at top level) vs. naga's required newer GLSL (uniforms in explicit blocks, like uniform buffers).
The preprocessor bridges the language-level gap. The translator handles the target-language emission.
Additional wins¶
- Metadata extraction. The preprocessor already parses the shader source; it can emit structured metadata (input types, data layouts, binding slot assignments) the runtime needs to correctly bind resources. No need to duplicate this work in the runtime or parse the shader a second time.
- Code modularity.
#includesupport lets shader authors share common utilities across shaders — not natively supported by GLSL, added by the preprocessor. - Dialect upgrades decoupled from backend migrations. The preprocessor can rewrite older-dialect code to newer-dialect code once, keeping the source of truth stable as backends come and go.
Trade-offs¶
- Custom preprocessor is real work. Writing a GLSL parser is non-trivial.
- One more failure mode. Bugs in the preprocessor manifest as subtle shader-behavior differences across backends.
- Debugging shaders gets harder. The shader the GPU runs is not the shader the author wrote. Good line-number mapping from source → emitted is a requirement for usability.
Canonical instance¶
Figma's shader pipeline. Shaders written in WebGL-1 GLSL are fed to a custom shader processor that:
- Parses the WebGL-1 GLSL.
- Normalizes older-dialect constructs.
- Rewrites to a newer GLSL dialect.
- Extracts input types and data-layout metadata for the runtime.
- Handles
#include-style modularity. - Emits both newer GLSL (for the WebGL backend) and, via naga, WGSL (for the WebGPU backend).
One source of truth, zero duplication, no drift between backends.
(Source: sources/2026-04-21-figma-rendering-powered-by-webgpu)
Seen in¶
- sources/2026-04-21-figma-rendering-powered-by-webgpu — Figma's canonical instance, described in detail including the motivation (maintaining two parallel shader corpora is infeasible) and the pipeline shape (custom preprocessor + naga).