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:
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
Unblocked by #1509. Related: #1552, #1184, #1551.
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:vec3padding to 16 bytes)bindBufferBase/uniformBlockBindingplumbingWhichever lands first should establish that machinery for the other. This one has the wider blast radius (all shaders, including the
ShaderEffectwrapper 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
FrameGlobalsuniform block (projection matrix, time, global tint; leave room for viewport size / dpi)#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)setProjectionuploadShaderEffectwrapper 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 firstUnblocked by #1509. Related: #1552, #1184, #1551.