One TSL node graph per material CONFIGURATION, shared by every material
of that configuration.
three's WebGPU renderer caches a node build (NodeManager.getForRender →
NodeBuilder.build, 11–28 ms for a gsplat graph) under a key that hashes
every node of the material's graph by its id. A wrapper that builds its
own graph over its own uniform() / texture() leaves therefore never
shares a build, so N materials of one configuration cost N builds — 92 of
them in a 6 s playback pass of a 50-timepoint partition (#2956's gating
activates each timepoint's parts on first visit). WebGL caches programs by
source and never paid this.
Here the graph is built ONCE per configuration key, over FORWARDING
leaves: each forwarding leaf is a fresh uniform() / texture() of the
same type whose updateBefore (object granularity — run for every draw,
before the draw's bindings upload) copies the value of the same-named leaf
of the material being drawn. The wrapper keeps its own leaves as plain
value holders, so its uniforms proxies, clone and setter paths are
unchanged; the cached NodeBuilderState (whose bindings point at the
shared forwarding leaves) is then valid for every material of the key,
and each draw still uploads its own material's values into its own
per-object binding clone. This is the pattern three's built-in node
materials use for map / color (materialReference), done generically
over a whole leaf set so it also covers .load() texture fetches, which a
ReferenceNode cannot express.
The graph slots shared are vertexNode, colorNode and depthNode (the
last written only by the pick factories).
Contract for a caller:
Everything the factory reads at graph-construction OR build time that
selects code must be in key (flags, the baked element-texture width,
…). Leaf presence and each leaf's type (texture type/format) are added
automatically.
The factory must read leaf VALUES only through the graph (never bake a
.value into code) except through key.
Material state the factory tail writes (blending, toneMapped) is
per-material and must be re-applied by the caller — the factory runs
against a scratch material, once per key.
Two texture leaves of one material must never hold the same texture.
three merges texture nodes of one texture into one binding at build
time, and a build three repeats for another render context sees the
values the forwarding leaves last held: the two leaves would then share
one binding for every material of the key. No wrapper does this today
(each has at most an element/base-colour texture and a colormap).
Kept out of tsl-helpers.ts (it needs three/webgpu; see
live-texture-tsl.ts for the import-cone reason).
One TSL node graph per material CONFIGURATION, shared by every material of that configuration.
three's WebGPU renderer caches a node build (
NodeManager.getForRender→NodeBuilder.build, 11–28 ms for a gsplat graph) under a key that hashes every node of the material's graph by itsid. A wrapper that builds its own graph over its ownuniform()/texture()leaves therefore never shares a build, so N materials of one configuration cost N builds — 92 of them in a 6 s playback pass of a 50-timepoint partition (#2956's gating activates each timepoint's parts on first visit). WebGL caches programs by source and never paid this.Here the graph is built ONCE per configuration key, over FORWARDING leaves: each forwarding leaf is a fresh
uniform()/texture()of the same type whoseupdateBefore(object granularity — run for every draw, before the draw's bindings upload) copies the value of the same-named leaf of the material being drawn. The wrapper keeps its own leaves as plain value holders, so itsuniformsproxies, clone and setter paths are unchanged; the cachedNodeBuilderState(whose bindings point at the shared forwarding leaves) is then valid for every material of the key, and each draw still uploads its own material's values into its own per-object binding clone. This is the pattern three's built-in node materials use formap/color(materialReference), done generically over a whole leaf set so it also covers.load()texture fetches, which aReferenceNodecannot express.The graph slots shared are
vertexNode,colorNodeanddepthNode(the last written only by the pick factories).Contract for a caller:
key(flags, the baked element-texture width, …). Leaf presence and each leaf's type (texture type/format) are added automatically..valueinto code) except throughkey.toneMapped) is per-material and must be re-applied by the caller — the factory runs against a scratch material, once per key.Kept out of
tsl-helpers.ts(it needsthree/webgpu; seelive-texture-tsl.tsfor the import-cone reason).