Skip to content

Shared frame-globals uniform buffer (projection / tint / time across all shaders) #1555

Closed

Description

@obiot

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

  • Define a FrameGlobals uniform 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 setProjection upload
  • Decide the story for the ShaderEffect wrapper and user effect bodies, which stay ES 1.00 — probably keep the existing uProjectionMatrix / uTime uniforms 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)

Unblocked by #1509. Related: #1552, #1184, #1551.

Activity

  1. obiot commented on Aug 23, 2026

    @obiot
    MemberAuthor

    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 std140 block.

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions