ADR-010: A WebGPU renderer beside the WebGL one
- Status: Accepted (2026-10-18).
- Evidence:
packages/effect-three/src/webgpu(the TSL render kit),packages/effect-three/src/kernel/contactShadowPasses.ts,packages/effect-three-foldkit/src/dom/webgpu.tsandwebgpuPostprocessing.ts;e2e/staging.test.tsande2e/everything.test.ts(each case on WebGL, onWebGPURenderer’s WebGL 2 backend, and on real WebGPU through SwiftShader). - References: three.js 0.186.1
WebGPURenderer(three/webgpu),objects/SkyMesh.js,tsl/display/BloomNode.jsandFilmNode.js,RenderPipeline;PLAN.mditem 7.
Context
PLAN.md designed the renderer boundary for WebGPU: an async start, no GLSL in
descriptions, one three entry point per backend. The kernel still held GLSL (the sky,
the shader grid, contact shadows) until it moved into a WebGL render kit (ADR-003).
Decision
- A renderer brings a render kit.
RenderKit(kernel types) makes the shaded parts:webglKit(@opsydyn/effect-three/webgl, GLSL) andwebgpuKit(@opsydyn/effect-three/webgpu, TSL: three’sSkyMesh, the grid and contact shadow materials as node materials). ARendererFactorycarries its kit; the runtime hands it to the scene graphs it makes. - Contact shadows are shared passes with kit materials. The kernel runs the passes (camera, targets, blurs, plane); a kit supplies the depth-to-shadow material, the blur passes and whether its targets read flipped (WebGPU’s do, on either backend).
webgpuRendererFactory(Foldkit adapter) wraps three’sWebGPURenderer: WebGPU where available, otherwise its WebGL 2 backend (forceWebGLforces it), opaque by default asWebGLRendereris. It starts asynchronously:SceneRenderer.ready(), which the runtime awaits before the first frame (a failed start isRendererUnavailable). Post-processing is aRenderPipelineof TSL nodes (bloom, vignette, film grain written afterFilmNode), with the pipeline’s output transform for tone mapping and colour space.- XR goes through either renderer. Presenting uses a structural
PresentableRendererand a per-renderer loop setter, set before the session starts (WebGPU’s XR manager captures the renderer’s loop then). - Labels need WebGL (superseded, see below). troika’s labels are WebGL materials;
real WebGPU rejects their draw calls and blanks the frame. A kit said
drawsLabels; underwebgpuKitthe scene graph gave the label engine nothing to draw. - Since (2026-10-19): a kit names its kind of material.
RenderKit.shadingis'webgl'(webglKit) or'tsl'(webgpuKit, node materials), replacingdrawsLabels; under a'tsl'kit the scene graph asks the label engine’screateTsl, and draws no labels when the engine has none.troikaLabelsfrom@opsydyn/effect-three-troika/webgpuhas one (troika’s layout, its shader ported to TSL); see ADR-009.
Consequences
- The same scenes draw on both renderers, checked by pixel for contact shadows and the
grid on three backends. Some output differs by design:
WebGPURendererblends in linear colour before its output transform (lighter translucent lines, softer shadows), and its TSL bloom is gentler on HDR skies thanUnrealBloomPass. - WebXR with
WebGPURendereris wired but untested: IWER’s emulated layer has no framebuffer object, which three’s WebGL 2 fallback backend cannot render into. - The samples take
?renderer=webgpuin the page address.