perf(webgl): batch distinct shader nodes with equal uniform collections#120
Merged
Conversation
The render-op batch test rejected any two quads whose shader NODES were different instances (curShader !== shader), but apps create one shader node per card — so a rail of identical rounded cards never batched: one draw op per card, each paying useShader + bindRenderOp + scissor + drawElements dispatch on the driver CPU. Shader nodes with equal value keys already share their uniform collection by reference (the shader-value cache), and collections are immutable after fill — so reference equality implies value equality. Batch when the shader nodes resolve to the same program AND the same uniform collection. Program identity is required because the value key does not include the shader type, so an equal collection reached from a different program must not merge. WebGlShaderProgram.reuseRenderOp uses the same one-reference compare in place of the per-node for..in over resolvedProps (kept as fallback for distinct collections), and no longer invokes the node.time getter for programs without a time uniform. Measured on stress-tv, 200 rounded-rect cards (tier "rounded rect only"): 205 draws -> 6 draws for the identical scene. Different-props nodes (shader-rounded example) still split correctly. Co-Authored-By: Claude Fable 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.
What
The render-op batch test in
WebGlRenderer.reuseRenderOprejected any two quads whose shader nodes were different instances (curShader !== shader). Apps create one shader node per card (renderer.createShader('Rounded', {...})at construction), so a rail of visually identical rounded cards never batched — one render op per card, each payinguseShader+bindRenderOp+ scissor +drawElementsdispatch on the driver CPU. On TV SoCs (~0.5–2µs per GL call) that's a meaningful per-frame tax for the standard left-nav/rows layout.How
Shader nodes with equal value keys already share their uniform collection by reference (the shader-value cache in
WebGlShaderNode.update), and collections are immutable after fill — so reference equality implies value equality. The gate now batches distinct shader nodes when they resolve to:Program identity is required for correctness: the shader value key (
CoreShaderNode.createValueKey) does not include the shader type, so an equal collection reached from a different program (key collision across shader types) must never merge. A regression test covers this case.WebGlShaderProgram.reuseRenderOpgets the same one-reference compare in place of the per-nodefor..inoverresolvedProps(the prop walk is kept as a fallback for distinct collections), and no longer invokes thenode.timegetter for programs without a time uniform.Measured
?test=stress-tv&debug=true, 200 rounded-rect cards (tier "rounded rect only"), clean dev server per side:Reviewer notes
WebGlShaderProgram.reuseRenderOpafter the gate, so alpha-animated or differently-sized cards split correctly.shader-roundedexample (every node has different corner/border/shadow props) renders pixel-identical — borders, shadows, and per-corner radii intact. Thestress-tvinterleaved tier (cards + unique image textures) still splits on texture changes as before.Testing
WebGlRenderer.reuseRenderOp.test.ts(8 tests — shared-collection batching, cross-program rejection, clipping/default paths, program-level veto still consulted) andWebGlShaderProgram.reuseRenderOp.test.ts(7 tests — reference short-circuit, prop-compare fallback, alpha/dimension rejects,timegetter untouched,canBatchdelegation)pnpm buildclean; Prettier/ESLint clean (0 errors)🤖 Generated with Claude Code