PATTERN Cited by 1 source
Graphics API interface layer¶
A graphics API interface layer is an internal abstraction that sits between the application's rendering code and any specific graphics backend (WebGL, WebGPU, Metal, Vulkan, D3D12). The pattern: shape the interface around the stricter/newer backend, implement it for the older backend first, then add the newer backend as a peer without touching any call site.
When to reach for it¶
- You have a large rendering codebase written against one graphics API and want to migrate (all or partial) to another.
- You want to run both backends side-by-side in production during rollout, gated by device / session / feature flag.
- You want to support dynamic fallback mid-session if the preferred backend fails.
- You want a single C++/Rust rendering codebase that runs in both a browser (via WebAssembly + browser WebGPU) and as a native binary (via Dawn or a native graphics library).
Design recipe¶
- Audit the existing call shape. If the current interface
mirrors the old backend's semantics (e.g.
bindX()thendraw()— implicit global state), the pre-migration task is to reshape it. - Shape the interface around the newer backend's semantics.
Explicit state as arguments to
draw(), grouped uniform uploads, explicit pipeline state. This is the shape required by the newer backend; the older backend can handle it trivially (lazy rebinding underneath, per-uniform upload underneath). - Push batching/encoding decisions into the interface. If the newer backend has a substantially different cost model for some operation (e.g. uniform upload, see patterns/uniform-buffer-batching), expose an encode/submit or similar amortizing surface even on the older backend. This way application code doesn't need to change when the newer backend is enabled.
- Implement both backends behind the same interface. Lazy reconciliation for the legacy backend; direct passthrough for the new backend.
- Ship the refactor independent of the migration. The refactor usually fixes latent bugs in the legacy backend just by making implicit state explicit.
- Only then enable the new backend — feature-flagged, blocklisted by unsupported device classes, with dynamic fallback for mid-session failures.
Why shape around the newer backend¶
The opposite shape — an interface modeled on the legacy backend, with the new backend simulating the old semantics — forces expensive emulation on the new backend. The new backend's native cost model is different; any permissive pattern that was cheap on the old backend can be catastrophic on the new one.
Shaping around the newer backend pushes the application toward patterns that are efficient on both.
Trade-offs¶
- Upfront refactor cost — a mature codebase's interface may have accumulated years of quirks. Figma framed this as an explicit project phase before any WebGPU work.
- Loss of some legacy-backend-specific optimizations — the interface shape discourages patterns that were cheap on the legacy backend (e.g. lots of individual uniform updates). Most of these are usually acceptable to lose; measure first.
- Doesn't help if the two backends have incompatible programming models that can't cleanly be unified (e.g. immediate-mode vs. retained-mode in some use cases). For WebGL → WebGPU, they're compatible enough that one interface works.
Canonical instance¶
Figma's C++ renderer. Figma's pre-WebGPU interface mirrored
WebGL's bindX() + draw() shape. The WebGPU project's first
phase was a graphics-interface rewrite to explicit-state,
encode/submit-shaped calls. This phase:
- Fixed latent WebGL bugs caused by implicit state.
- Introduced an encode/submit API for uniform uploads, batching implemented on WebGPU, trivial on WebGL.
- Provided the substrate for both backends to live in one codebase and for mid-session fallback.
- Enabled the native-binary + Wasm binary to share the same C++ code targeting Dawn on both sides.
(Source: sources/2026-04-21-figma-rendering-powered-by-webgpu)
Seen in¶
- sources/2026-04-21-figma-rendering-powered-by-webgpu — Figma canvas renderer, WebGL → WebGPU. Canonical instance.