Skip unchanged material uniforms and skin palettes - #54
Merged
treeform merged 1 commit intoOct 1, 2026
Merged
Conversation
applyMaterial re-sent ~110 uniforms on every material switch, most of them default texture transforms that already match what pbrShader holds. Parts of one rig also re-uploaded the same joint palette for every primitive. Material uniforms now go through a per-location shadow of the values pbrShader holds, and the joint palette uploads only when it changed. Both reset with the existing uniform cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
applyMaterialre-sends every material uniform on each material switch: about 110glUniform*calls, most of them the ~22 KHR texture transforms (texCoord, offset, scale, rotation), which are usually still the defaults. Models with a distinct material per primitive switch on every draw. Polyworld's CharGen heroes are an example: one tinted material per body part, 182 different material contents across six heroes.renderPbrPrimitivealso re-uploads the full joint palette for every primitive, even when consecutive primitives belong to the same rig.On native GL this is cheap. In the browser it isn't: Chrome validates and translates each WebGL call in its GPU process. Profiling the Archers Warriors Mages web build (Chrome 153, ANGLE Metal, M1 Max) showed ~10,900 WebGL calls per frame for ~90 draws, about 8,700 of them scalar uniforms from
applyMaterial. Decoding them cost the GPU process ~25 ms per frame.Change
ctx.setUniform1i/1f/2f/3f/4f, which keep a per-location shadow of the valuespbrShaderholds (ctx.uniformShadow). The realglUniform*is called only when the value changed. Values are compared as raw bits, so NaNs and-0.0behave correctly.renderPbrPrimitiveskipsjointMatriceswhen it equals the palette last uploaded (ctx.lastJointMatrices).invalidateUniformCache(also called on IBL variant switches) and both places that resetpassValues. This follows the existingsyncPassValuecontract: uniform values are per-program GL state and survive foreign program binds.MaxShadowedUniforms(4096) always upload, so the shadow never grows unbounded.Nothing else changes: the
lastMaterialearly return, texture binds and per-pass uniforms are untouched.Results
Same AWM web build and scene:
The machine was in Low Power Mode, where Chrome caps pages at 30 fps, so these frame times don't show the uncapped frame rate.
Testing
nim r tests/tests.nimpasses.tests/test_pbr_pass.nim, which checks images and that the uniform cache never serves stale values, was not run: the glTF-Sample-Assets checkout wasn't available. Please run it, or the reference-image suite, before merging.🤖 Generated with Claude Code