Repository navigation
Shared frame-globals uniform buffer (projection / tint / time across all shaders) #1555
Description
Summary
Put the per-frame values that every shader needs — projection matrix, global tint, elapsed time — into a single uniform buffer bound once per frame, instead of re-uploading them per program.
Motivation
The projection matrix is pushed into each program individually today: Batcher.setProjection() is called on every batcher switch (webgl_renderer.js, setBatcher), and post-effect / blit paths save, override and restore it around their passes. Every shader that wants time or a global tint takes the same per-program uniform treatment.
With a uniform block these become frame state: written once, visible to every program that declares the block, with no per-switch upload.
Relationship to #1552
Different scope, shared machinery. #1552 moves light data into a UBO to lift MAX_LIGHTS; this one moves frame globals used by every shader. They share:
- the GLSL ES 3.00 prerequisite (uniform blocks do not exist in ES 1.00 — see Lighting: uniform buffer objects for light data (raise MAX_LIGHTS, modernize the lit shaders to GLSL ES 3.00) #1552 for the shader-migration rule established in WebGL2-only: drop WebGL1 path, adopt VAOs (per-flush attribute setup → bindVertexArray) #1509)
- the std140 packing helpers (mind
vec3padding to 16 bytes) - the
bindBufferBase/uniformBlockBindingplumbing
Whichever lands first should establish that machinery for the other. This one has the wider blast radius (all shaders, including the ShaderEffect wrapper and user-authored effect bodies), so it likely wants to follow #1552 rather than lead it.
Why this is the most WebGPU-shaped of the set
Frame globals in a uniform buffer bound once per frame is exactly the WebGPU design — conventionally bind group 0, set once per render pass, with per-material and per-object data in higher groups. Unlike #1552 (whose GLSL is rewritten in WGSL at port time anyway), the structure here survives the port: "pack frame globals once, bind once" is the same shape in both backends, and it removes the per-program projection upload that has no WebGPU equivalent.
Part of the #1184 (WebGPU renderer) groundwork.
Scope sketch
- Define a
FrameGlobalsuniform block (projection matrix, time, global tint; leave room for viewport size / dpi) - Migrate the shaders that need it to
#version 300 es(per the WebGL2-only: drop WebGL1 path, adopt VAOs (per-flush attribute setup → bindVertexArray) #1509 rule: migrate a shader when it needs a 3.00 feature) - Write it once per frame; drop the per-batcher-switch
setProjectionupload - Decide the story for the
ShaderEffectwrapper and user effect bodies, which stay ES 1.00 — probably keep the existinguProjectionMatrix/uTimeuniforms working for them, so this is opt-in for engine shaders first - Keep the FBO / blit projection override working (it currently swaps the matrix around a pass)
Activity
WebGPU 2D pipeline: sprites, text, primitives, blend modes, clipping …
obiot commentedon Aug 23, 2026
Half of this shipped in 20.0.0, and the other half already has its own ticket, so closing rather than leaving an umbrella open indefinitely.
Done (WebGPU): frame globals live in a bind-group-0 uniform buffer, consumed by every pipeline, bound once per frame instead of re-uploaded per program. #1552 did the analogous thing for light data on both backends, moving it out of GLSL uniform arrays into a
std140block.Remaining (WebGL): tracked in #1568, which was split out of this ticket specifically to record the design constraint found while implementing the WebGPU side, so it would not be rediscovered later. The short version: user shaders are contractually GLSL ES 1.00, and uniform blocks do not exist there, so the WebGL half cannot simply mirror the WebGPU shape without either breaking that contract or splitting engine-owned shaders from user-facing ones. #1568 is filed for the record and not scheduled.
Anyone arriving here for the WebGL side should read #1568, which has the constraint written down.