Summary
Move the per-light uniform data (uLightDir[], uLightColor[], uLightCount, uAmbient, and the 3D equivalents) out of individual uniform arrays and into a uniform buffer object, uploaded once per frame instead of set with N gl.uniform* calls. This requires porting the lit shaders to GLSL ES 3.00, and lets MAX_LIGHTS rise well above its current value of 8.
Motivation
MAX_LIGHTS = 8 (video/webgl/lighting/constants.ts) is not a design choice — it exists to keep the GLSL uniform arrays comfortably inside conservative per-shader uniform limits. Scenes that want more than 8 simultaneous lights silently drop the extras.
A UBO removes that pressure: light data becomes one buffer bound to a block, sized by the buffer rather than by the uniform-slot budget, and updated with a single bufferSubData instead of per-light uniform calls.
Prerequisite: GLSL ES 3.00 for the lit shaders
Uniform blocks do not exist in GLSL ES 1.00 — they were introduced in ES 3.00, and WebGL 2 only accepts them in #version 300 es shaders. The lit shaders are ES 1.00 today (varying, plain uniform vec3 uLightDir[__MAX_LIGHTS__]).
So this ticket is the first legitimate trigger of the rule set in #1509: individual shaders migrate to ES 3.00 when they need an ES 3.00 feature — as opposed to a wholesale dialect rewrite, which remains explicitly out of scope (the ShaderEffect wrapper and user-authored effect bodies stay ES 1.00; mixed dialects already coexist per-program, e.g. the ES 3.00 GPU tilemap shader).
Shaders in scope:
video/webgl/shaders/mesh-lit.frag / mesh-lit.vert
video/webgl/shaders/quad-multi-lit.vert
- the
video/webgl/shaders/multitexture-lit.js generator
JS side: video/webgl/lighting/pack.ts and pack3d.ts already pack light data into flat arrays, so they are the natural place to write std140-laid-out buffer contents instead.
Scope
Relationship to the WebGPU backend (#1184) — honest scoping
This is not strict WebGPU groundwork, and shouldn't be justified as such:
- The WebGPU backend rewrites these shaders in WGSL regardless, so the GLSL uniform-block work does not port.
- The GL UBO API (
bindBufferBase, uniformBlockBinding) has no WebGPU counterpart either.
- What does transfer is the JS-side model: pack a struct into a typed array and upload it once, rather than issuing N loose uniform calls. That is exactly the WebGPU shape (write a buffer, bind a bind group), so the packing code in
pack.ts / pack3d.ts gains lasting value.
Contrast with #1551, where the artifact being made neutral is a shared JS record both backends read directly — that one transfers whole. This one is mostly a WebGL 2 improvement in its own right, with a WebGPU-shaped side effect.
Scheduling
No ordering constraint — nothing in #1507, #1508, #1551 or #1184 depends on this landing first. Best picked up whenever lighting work comes up naturally. The headline is "more lights, modernized lit shaders"; WebGPU-shaped uniform packing is a bonus, not the reason.
Related: #1509 (established the "migrate a shader to ES 3.00 only when it needs a 3.00 feature" rule), #1184, #1536.
Summary
Move the per-light uniform data (
uLightDir[],uLightColor[],uLightCount,uAmbient, and the 3D equivalents) out of individual uniform arrays and into a uniform buffer object, uploaded once per frame instead of set with Ngl.uniform*calls. This requires porting the lit shaders to GLSL ES 3.00, and letsMAX_LIGHTSrise well above its current value of 8.Motivation
MAX_LIGHTS = 8(video/webgl/lighting/constants.ts) is not a design choice — it exists to keep the GLSL uniform arrays comfortably inside conservative per-shader uniform limits. Scenes that want more than 8 simultaneous lights silently drop the extras.A UBO removes that pressure: light data becomes one buffer bound to a block, sized by the buffer rather than by the uniform-slot budget, and updated with a single
bufferSubDatainstead of per-light uniform calls.Prerequisite: GLSL ES 3.00 for the lit shaders
Uniform blocks do not exist in GLSL ES 1.00 — they were introduced in ES 3.00, and WebGL 2 only accepts them in
#version 300 esshaders. The lit shaders are ES 1.00 today (varying, plainuniform vec3 uLightDir[__MAX_LIGHTS__]).So this ticket is the first legitimate trigger of the rule set in #1509: individual shaders migrate to ES 3.00 when they need an ES 3.00 feature — as opposed to a wholesale dialect rewrite, which remains explicitly out of scope (the
ShaderEffectwrapper and user-authored effect bodies stay ES 1.00; mixed dialects already coexist per-program, e.g. the ES 3.00 GPU tilemap shader).Shaders in scope:
video/webgl/shaders/mesh-lit.frag/mesh-lit.vertvideo/webgl/shaders/quad-multi-lit.vertvideo/webgl/shaders/multitexture-lit.jsgeneratorJS side:
video/webgl/lighting/pack.tsandpack3d.tsalready pack light data into flat arrays, so they are the natural place to write std140-laid-out buffer contents instead.Scope
#version 300 es(attribute→in,varying→in/out,texture2D→texture, explicitoutfragment color).utils/precision.jsis already#version-aware;utils/attributes.jsalready parses both dialects.std140uniform block for the light array; bind it withbindBufferBase/uniformBlockBinding.pack.ts/pack3d.tswrite std140-compatible layouts (mindvec3padding to 16 bytes — the classic std140 trap).MAX_LIGHTS, driven by the buffer size rather than uniform slots; pick a new cap and document it.spriteIlluminator,lights, the 3D lit mesh examples) — these are pixel-visible changes.Relationship to the WebGPU backend (#1184) — honest scoping
This is not strict WebGPU groundwork, and shouldn't be justified as such:
bindBufferBase,uniformBlockBinding) has no WebGPU counterpart either.pack.ts/pack3d.tsgains lasting value.Contrast with #1551, where the artifact being made neutral is a shared JS record both backends read directly — that one transfers whole. This one is mostly a WebGL 2 improvement in its own right, with a WebGPU-shaped side effect.
Scheduling
No ordering constraint — nothing in #1507, #1508, #1551 or #1184 depends on this landing first. Best picked up whenever lighting work comes up naturally. The headline is "more lights, modernized lit shaders"; WebGPU-shaped uniform packing is a bonus, not the reason.
Related: #1509 (established the "migrate a shader to ES 3.00 only when it needs a 3.00 feature" rule), #1184, #1536.