The 16-Context Limit: How shaderjoy Multiplexes WebGL
By rendering to a single hidden framebuffer and transferring pixels to standard 2D canvases, Chenglou's zero-dependency playground bypasses a hard browser constraint.

Shaders art made with pure CSS, with an editable highlighted code area also made in pure CSS. Zero JavaScript!
- Browsers strictly limit active WebGL contexts to prevent GPU exhaustion, crippling multi-canvas layouts.
- shaderjoy circumvents this by rendering all shaders to a single hidden context and copying pixels to cheap 2D canvases.
- A dirty-flag scheduler replaces continuous animation loops, drastically reducing battery drain.
- The entire architecture relies on batched DOM reads instead of modern frameworks, proving the viability of zero-dependency design.
The Context Ceiling
Modern web development is obsessed with frameworks, but frameworks cannot save you from hard browser constraints. If you have ever tried to build a grid of 20 standard WebGL canvases, you have likely encountered the silent killer: WEBGL_lose_context. Browsers are actively hostile toward multiple WebGL contexts to prevent GPU memory exhaustion. They enforce a strict ceiling, typically capping at 8 to 16 active contexts.
When that limit is breached, older contexts are forcefully discarded, or the page simply crashes. For a shader playground designed to display dozens of live previews simultaneously, this constraint presents a fundamental architectural roadblock.
The Multiplexing Illusion
The most fascinating aspect of shaderjoy is not that it is a shader playground, but how it radically bypasses this limit. It implements a "multiplexing" architecture. Instead of creating a WebGL context for every preview pane, shaderjoy creates exactly one hidden WebGL context.
It renders a specific shader to a hidden framebuffer, extracts the raw pixel data using gl.readPixels, and then paints those pixels onto an abundant, cheap 2D canvas using ctx.putImageData. It is a masterclass in mechanical sympathy.
The Dirty-Flag Scheduler
Most WebGL applications run a continuous, battery-draining requestAnimationFrame loop. They demand maximum GPU utilization even when nothing on the screen is changing. shaderjoy treats rendering more like a text editor.
It utilizes a "dirty-flag" pattern. Renders are only scheduled when the user types code or interacts with the interface. This targeted execution saves significant laptop battery life, contrasting sharply with the continuous, wasteful loops of typical graphics applications.
The Anti-Framework Architecture
Zooming out to the codebase itself reveals zero NPM production dependencies. There is no Webpack, no React, and no virtual DOM. The entire application logic resides in a single index.ts file.
Instead of relying on a framework for layout, shaderjoy performs batched DOM reads to calculate layout grids mathematically. This manual orchestration avoids layout thrashing while simultaneously managing 16 CodeMirror instances. It is the "anti-framework" approach to high-performance browser UIs.
| Strategy | Context Count | GPU Usage | CPU Overhead | Scalability Limit |
|---|---|---|---|---|
| Standard WebGL Grid | 1 per frame | High | Low | Fails at ~16 contexts |
| Shadertoy (Classic) | 1 total | High | Low | 1 full-screen view |
| shaderjoy | 1 hidden | Low | High (readPixels) | Infinite 2D canvases |