@threenative/core 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/capabilities.json +5292 -0
  4. package/dist/assets-kyoF7JlJ.d.ts +103 -0
  5. package/dist/audio-BFiGneTL.d.ts +156 -0
  6. package/dist/canvas-layer-BLVijiUJ.d.ts +62 -0
  7. package/dist/game-XGrTzapq.d.ts +1166 -0
  8. package/dist/gpu-readback-D2iRvoe9.d.ts +112 -0
  9. package/dist/hot.d.ts +20 -2
  10. package/dist/hot.js +14 -2
  11. package/dist/index.d.ts +2108 -55
  12. package/dist/index.js +15645 -2222
  13. package/dist/net.d.ts +65 -0
  14. package/dist/net.js +643 -0
  15. package/dist/playtest.d.ts +37 -4
  16. package/dist/playtest.js +246 -546
  17. package/dist/react.d.ts +177 -0
  18. package/dist/react.js +635 -0
  19. package/dist/renderer-C6hqZpoG.d.ts +770 -0
  20. package/dist/ui-layer.d.ts +306 -0
  21. package/dist/ui-layer.js +425 -0
  22. package/dist/world.d.ts +254 -0
  23. package/dist/world.js +2686 -0
  24. package/gpl/LICENSE.GPL +117 -0
  25. package/gpl/convert.py +192 -0
  26. package/gpl/recipes/_common.py +169 -0
  27. package/gpl/recipes/bake_ao.py +111 -0
  28. package/gpl/recipes/decimate.py +64 -0
  29. package/gpl/recipes/retarget.py +131 -0
  30. package/gpl/recipes/unwrap.py +71 -0
  31. package/mcp/assets.mjs +5 -0
  32. package/mcp/blender-server.mjs +632 -0
  33. package/mcp/blender.mjs +27 -0
  34. package/mcp/engine-server.mjs +501 -0
  35. package/mcp/engine.mjs +31 -0
  36. package/mcp/install.d.mts +37 -0
  37. package/mcp/install.mjs +145 -0
  38. package/mcp/launch.mjs +72 -0
  39. package/mcp/sculpt.mjs +5 -0
  40. package/mcp/servers.d.mts +34 -0
  41. package/mcp/servers.mjs +160 -0
  42. package/package.json +76 -6
  43. package/patches/three@0.185.1.patch +522 -0
  44. package/scripts/apply-three-patch.mjs +297 -0
  45. package/scripts/ensure-mcp.mjs +43 -0
  46. package/scripts/postinstall.mjs +6 -0
  47. package/dist/audio-CEAw0w5y.d.ts +0 -35
  48. package/dist/game-DRt1Qhq3.d.ts +0 -429
@@ -0,0 +1,522 @@
1
+ diff --git a/build/three.cjs b/build/three.cjs
2
+ index 8939a67dd76325ccd25ee8806fc4d22b1caea88a..b0f917241f1f2cd25baa21dec20edda32176ef6a 100644
3
+ --- a/build/three.cjs
4
+ +++ b/build/three.cjs
5
+ @@ -26116,6 +26116,8 @@ function copyArrayContents( src, target ) {
6
+ */
7
+ class BatchedMesh extends Mesh {
8
+
9
+ + static threeNativeBatchedVelocityPatch = true;
10
+ +
11
+ /**
12
+ * Constructs a new batched mesh.
13
+ *
14
+ diff --git a/build/three.core.js b/build/three.core.js
15
+ index 0bb9262cd029f411933d077fd44197a51ec5e8e9..868a634b4294be5f32664912a50813d55d6dd6e7 100644
16
+ --- a/build/three.core.js
17
+ +++ b/build/three.core.js
18
+ @@ -26136,6 +26136,8 @@ function copyArrayContents( src, target ) {
19
+ */
20
+ class BatchedMesh extends Mesh {
21
+
22
+ + static threeNativeBatchedVelocityPatch = true;
23
+ +
24
+ /**
25
+ * Constructs a new batched mesh.
26
+ *
27
+ diff --git a/build/three.webgpu.js b/build/three.webgpu.js
28
+ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..d05599255744ccfca19dbfd0433da612583ce6ea 100644
29
+ --- a/build/three.webgpu.js
30
+ +++ b/build/three.webgpu.js
31
+ @@ -18500,7 +18500,8 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => {
32
+
33
+ const previousInstanceData = _previousInstanceMatrices.get( object );
34
+
35
+ - previousInstanceData.previousInstanceMatrix.array.set( matrices.array );
36
+ + const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ];
37
+ + previousInstanceData.previousInstanceMatrix.array.set( scheduledPrevious ?? matrices.array );
38
+
39
+ } );
40
+
41
+ @@ -18599,6 +18600,7 @@ const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => {
42
+ const indirectId = getIndirectIndex( batchMesh._indirectTexture, int( batchingIdNode ) );
43
+
44
+ const matricesTexture = batchMesh._matricesTexture;
45
+ + const previousMatricesTexture = batchMesh._previousMatricesTexture ?? matricesTexture;
46
+
47
+ const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst();
48
+ const j = float( indirectId ).mul( 4 ).toInt().toConst();
49
+ @@ -18626,6 +18628,19 @@ const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => {
50
+
51
+ positionLocal.assign( batchingMatrix.mul( positionLocal ) );
52
+
53
+ + if ( builder.needsPreviousData() ) {
54
+ +
55
+ + const previousBatchingMatrix = mat4(
56
+ + textureLoad( previousMatricesTexture, ivec2( x, y ) ),
57
+ + textureLoad( previousMatricesTexture, ivec2( x.add( 1 ), y ) ),
58
+ + textureLoad( previousMatricesTexture, ivec2( x.add( 2 ), y ) ),
59
+ + textureLoad( previousMatricesTexture, ivec2( x.add( 3 ), y ) )
60
+ + );
61
+ +
62
+ + positionPrevious.assign( previousBatchingMatrix.mul( positionPrevious ).xyz );
63
+ +
64
+ + }
65
+ +
66
+ const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) );
67
+
68
+ const batchingNormal = bm.mul( transformedNormal ).xyz;
69
+ @@ -18777,7 +18792,8 @@ const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => {
70
+
71
+ if ( skeletonData !== undefined ) {
72
+
73
+ - skeletonData.previousBoneMatrices.set( skeleton.boneMatrices );
74
+ + const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousBoneMatrices' ) ];
75
+ + skeletonData.previousBoneMatrices.set( scheduledPrevious ?? skeleton.boneMatrices );
76
+
77
+ }
78
+
79
+ @@ -18844,7 +18860,8 @@ const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = null ],
80
+
81
+ if ( state !== undefined ) {
82
+
83
+ - state.previousBoneMatrices.set( skeleton.boneMatrices );
84
+ + const scheduledPrevious = skinnedMesh[ Symbol.for( 'threenative.velocity.previousBoneMatrices' ) ];
85
+ + state.previousBoneMatrices.set( scheduledPrevious ?? skeleton.boneMatrices );
86
+
87
+ }
88
+
89
+ @@ -29911,6 +29928,18 @@ class RenderObject {
90
+ */
91
+ this._sourceMaterial = renderer._currentSourceMaterial;
92
+
93
+ + /**
94
+ + * The version of `_sourceMaterial` this render object was last validated against.
95
+ + *
96
+ + * An override material is shared by every object in the pass and is used as a per-object
97
+ + * scratchpad, so its own `version` moves for reasons that have nothing to do with this
98
+ + * render object. The source material's version is the signal that does.
99
+ + *
100
+ + * @type {number}
101
+ + * @default 0
102
+ + */
103
+ + this.sourceVersion = ( this._sourceMaterial === null || this._sourceMaterial === undefined ) ? 0 : this._sourceMaterial.version;
104
+ +
105
+ /**
106
+ * An event listener which is defined by `RenderObjects`. It performs
107
+ * clean up tasks when `dispose()` on this render object.
108
+ @@ -30707,9 +30736,18 @@ class RenderObjects {
109
+
110
+ }
111
+
112
+ - if ( renderObject.version !== material.version || renderObject.needsUpdate ) {
113
+ + // When the renderer is drawing through an override material, that material is shared by
114
+ + // the whole pass and mutated per object, so its `version` alone answers the wrong
115
+ + // question. The source material this object actually carries answers the right one.
116
+ +
117
+ + const sourceMaterial = this.renderer._currentSourceMaterial;
118
+ + const sourceTracked = sourceMaterial !== null && sourceMaterial !== undefined;
119
+ + const sourceReplaced = sourceTracked && renderObject._sourceMaterial !== sourceMaterial;
120
+ + const sourceChanged = sourceTracked && ( sourceReplaced || renderObject.sourceVersion !== sourceMaterial.version );
121
+
122
+ - if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
123
+ + if ( renderObject.version !== material.version || sourceChanged || renderObject.needsUpdate ) {
124
+ +
125
+ + if ( sourceReplaced || renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
126
+
127
+ renderObject.dispose();
128
+
129
+ @@ -30719,6 +30757,8 @@ class RenderObjects {
130
+
131
+ renderObject.version = material.version;
132
+
133
+ + if ( sourceTracked ) renderObject.sourceVersion = sourceMaterial.version;
134
+ +
135
+ }
136
+
137
+ }
138
+ @@ -39725,7 +39765,8 @@ class VelocityNode extends TempNode {
139
+
140
+ const previousModelMatrix = getPreviousMatrix( object );
141
+
142
+ - this.previousModelWorldMatrix.value.copy( previousModelMatrix );
143
+ + const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousWorldMatrix' ) ];
144
+ + this.previousModelWorldMatrix.value.copy( scheduledPrevious ?? previousModelMatrix );
145
+
146
+ //
147
+
148
+ @@ -53841,7 +53882,7 @@ class NodeBuilder {
149
+
150
+ const mrt = this.renderer.getMRT();
151
+
152
+ - return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( this.object ).useVelocity === true;
153
+ + return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( this.object ).useVelocity === true || this.object.userData?.useVelocity === true;
154
+
155
+ }
156
+
157
+ @@ -62731,6 +62772,7 @@ class Renderer {
158
+ let materialDisplacementMap;
159
+ let materialDisplacementScale;
160
+ let materialDisplacementBias;
161
+ + let materialAlphaTest;
162
+
163
+ const previousSourceMaterial = this._currentSourceMaterial;
164
+
165
+ @@ -62756,6 +62798,7 @@ class Renderer {
166
+ materialDisplacementMap = overrideMaterial.displacementMap;
167
+ materialDisplacementScale = overrideMaterial.displacementScale;
168
+ materialDisplacementBias = overrideMaterial.displacementBias;
169
+ + materialAlphaTest = overrideMaterial.alphaTest;
170
+
171
+ if ( material.positionNode && material.positionNode.isNode ) {
172
+
173
+ @@ -62763,7 +62806,15 @@ class Renderer {
174
+
175
+ }
176
+
177
+ - overrideMaterial.alphaTest = material.alphaTest;
178
+ + // `Material.alphaTest`'s setter bumps `version` whenever alpha-test-ness flips, and this
179
+ + // override material is shared by every caster in the pass. A scene that mixes alpha-tested
180
+ + // casters with opaque ones therefore moves that shared version several times per frame,
181
+ + // and every OTHER render object drawn with it then fails the version gate in
182
+ + // `RenderObjects.get` and recomputes a full material cache key that always matches.
183
+ + // Writing the backing field leaves the `alphaTest` getter — and so the cache key — still
184
+ + // reporting this caster's value, while each render object tracks the staleness signal
185
+ + // that belongs to it: its own source material's version.
186
+ + overrideMaterial._alphaTest = material.alphaTest;
187
+ overrideMaterial.alphaMap = material.alphaMap;
188
+ overrideMaterial.displacementMap = material.displacementMap;
189
+ overrideMaterial.displacementScale = material.displacementScale;
190
+ @@ -62825,6 +62876,7 @@ class Renderer {
191
+ scene.overrideMaterial.displacementMap = materialDisplacementMap;
192
+ scene.overrideMaterial.displacementScale = materialDisplacementScale;
193
+ scene.overrideMaterial.displacementBias = materialDisplacementBias;
194
+ + scene.overrideMaterial._alphaTest = materialAlphaTest;
195
+
196
+ }
197
+
198
+ @@ -79586,7 +79638,8 @@ class WGSLNodeBuilder extends NodeBuilder {
199
+
200
+ uniformGPU = buffer;
201
+
202
+ - uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id;
203
+ + // The base builder supplies a unique name within this program. A process-wide
204
+ + // allocation id changes otherwise identical shader source for every buffer.
205
+
206
+ } else {
207
+
208
+ @@ -83814,6 +83867,14 @@ class WebGPUBackend extends Backend {
209
+ */
210
+ this.compatibilityMode = null;
211
+
212
+ + /**
213
+ + * A reference to the WebGPU instance that creates adapters.
214
+ + *
215
+ + * @type {?GPU}
216
+ + * @default null
217
+ + */
218
+ + this.gpu = null;
219
+ +
220
+ /**
221
+ * A reference to the device.
222
+ *
223
+ @@ -83926,7 +83987,9 @@ class WebGPUBackend extends Backend {
224
+ xrCompatible: renderer.xr.enabled
225
+ };
226
+
227
+ - const adapter = ( typeof navigator !== 'undefined' ) ? await navigator.gpu.requestAdapter( adapterOptions ) : null;
228
+ + this.gpu = ( typeof navigator !== 'undefined' ) ? navigator.gpu : null;
229
+ +
230
+ + const adapter = this.gpu !== null ? await this.gpu.requestAdapter( adapterOptions ) : null;
231
+
232
+ if ( adapter === null ) {
233
+
234
+ diff --git a/src/nodes/accessors/Batch.js b/src/nodes/accessors/Batch.js
235
+ index ab62e98dadab2539d7e1d663fd42267bbc8ebcaa..2cf4bb6039853d5c922e89041419250b243694ef 100644
236
+ --- a/src/nodes/accessors/Batch.js
237
+ +++ b/src/nodes/accessors/Batch.js
238
+ @@ -1,6 +1,6 @@
239
+
240
+ import { normalLocal } from './Normal.js';
241
+ -import { positionLocal } from './Position.js';
242
+ +import { positionLocal, positionPrevious } from './Position.js';
243
+ import { vec3, mat3, mat4, int, ivec2, float, Fn } from '../tsl/TSLBase.js';
244
+ import { textureLoad } from './TextureNode.js';
245
+ import { textureSize } from './TextureSizeNode.js';
246
+ @@ -64,6 +64,7 @@ export const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => {
247
+ const indirectId = getIndirectIndex( batchMesh._indirectTexture, int( batchingIdNode ) );
248
+
249
+ const matricesTexture = batchMesh._matricesTexture;
250
+ + const previousMatricesTexture = batchMesh._previousMatricesTexture ?? matricesTexture;
251
+
252
+ const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst();
253
+ const j = float( indirectId ).mul( 4 ).toInt().toConst();
254
+ @@ -90,6 +91,19 @@ export const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => {
255
+ const bm = mat3( batchingMatrix );
256
+
257
+ positionLocal.assign( batchingMatrix.mul( positionLocal ) );
258
+ + if ( builder.needsPreviousData() ) {
259
+ +
260
+ + const previousBatchingMatrix = mat4(
261
+ + textureLoad( previousMatricesTexture, ivec2( x, y ) ),
262
+ + textureLoad( previousMatricesTexture, ivec2( x.add( 1 ), y ) ),
263
+ + textureLoad( previousMatricesTexture, ivec2( x.add( 2 ), y ) ),
264
+ + textureLoad( previousMatricesTexture, ivec2( x.add( 3 ), y ) )
265
+ + );
266
+ +
267
+ + positionPrevious.assign( previousBatchingMatrix.mul( positionPrevious ).xyz );
268
+ +
269
+ + }
270
+ +
271
+
272
+ const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) );
273
+
274
+ @@ -104,5 +118,3 @@ export const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => {
275
+ }
276
+
277
+ }, 'void' );
278
+ -
279
+ -
280
+ diff --git a/src/nodes/accessors/Instance.js b/src/nodes/accessors/Instance.js
281
+ index b89834b9166c75fd946ade24db265bf8c367d244..c7b7bdd7a31d852a581d4bc75a1e1afc1d214e5e 100644
282
+ --- a/src/nodes/accessors/Instance.js
283
+ +++ b/src/nodes/accessors/Instance.js
284
+ @@ -220,7 +220,8 @@ export const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder
285
+
286
+ const previousInstanceData = _previousInstanceMatrices.get( object );
287
+
288
+ - previousInstanceData.previousInstanceMatrix.array.set( matrices.array );
289
+ + const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ];
290
+ + previousInstanceData.previousInstanceMatrix.array.set( scheduledPrevious ?? matrices.array );
291
+
292
+ } );
293
+
294
+ diff --git a/src/nodes/accessors/Skinning.js b/src/nodes/accessors/Skinning.js
295
+ index 2356aed8656dceb1ea74b73ea2e739371a960484..aaeea633a8ece291be5faf868e312867ddad01b6 100644
296
+ --- a/src/nodes/accessors/Skinning.js
297
+ +++ b/src/nodes/accessors/Skinning.js
298
+ @@ -149,7 +149,8 @@ export const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => {
299
+
300
+ if ( skeletonData !== undefined ) {
301
+
302
+ - skeletonData.previousBoneMatrices.set( skeleton.boneMatrices );
303
+ + const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousBoneMatrices' ) ];
304
+ + skeletonData.previousBoneMatrices.set( scheduledPrevious ?? skeleton.boneMatrices );
305
+
306
+ }
307
+
308
+ @@ -216,7 +217,8 @@ export const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = n
309
+
310
+ if ( state !== undefined ) {
311
+
312
+ - state.previousBoneMatrices.set( skeleton.boneMatrices );
313
+ + const scheduledPrevious = skinnedMesh[ Symbol.for( 'threenative.velocity.previousBoneMatrices' ) ];
314
+ + state.previousBoneMatrices.set( scheduledPrevious ?? skeleton.boneMatrices );
315
+
316
+ }
317
+
318
+ diff --git a/src/nodes/accessors/VelocityNode.js b/src/nodes/accessors/VelocityNode.js
319
+ index 373548b0b587e7da3ef0993bfe316af420e7f66c..c48002abbce20ee8b29908cf314d7b03afe0db03 100644
320
+ --- a/src/nodes/accessors/VelocityNode.js
321
+ +++ b/src/nodes/accessors/VelocityNode.js
322
+ @@ -106,7 +106,8 @@ class VelocityNode extends TempNode {
323
+
324
+ const previousModelMatrix = getPreviousMatrix( object );
325
+
326
+ - this.previousModelWorldMatrix.value.copy( previousModelMatrix );
327
+ + const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousWorldMatrix' ) ];
328
+ + this.previousModelWorldMatrix.value.copy( scheduledPrevious ?? previousModelMatrix );
329
+
330
+ //
331
+
332
+ diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js
333
+ index be00b81ef36e1af83b0066603e616f8f1f81a115..1990da8fe8f1f29a7b53030756dfd966d8de3cb6 100644
334
+ --- a/src/nodes/core/NodeBuilder.js
335
+ +++ b/src/nodes/core/NodeBuilder.js
336
+ @@ -3450,7 +3450,7 @@ class NodeBuilder {
337
+
338
+ const mrt = this.renderer.getMRT();
339
+
340
+ - return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( this.object ).useVelocity === true;
341
+ + return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( this.object ).useVelocity === true || this.object.userData?.useVelocity === true;
342
+
343
+ }
344
+
345
+ diff --git a/src/objects/BatchedMesh.js b/src/objects/BatchedMesh.js
346
+ index 2ef7cb257438826059eb2408b3756c4891f06a10..35cc0ffe83a69ea9fa5fca6dec2d820ca193f88d 100644
347
+ --- a/src/objects/BatchedMesh.js
348
+ +++ b/src/objects/BatchedMesh.js
349
+ @@ -181,6 +181,8 @@ function copyArrayContents( src, target ) {
350
+ */
351
+ class BatchedMesh extends Mesh {
352
+
353
+ + static threeNativeBatchedVelocityPatch = true;
354
+ +
355
+ /**
356
+ * Constructs a new batched mesh.
357
+ *
358
+ diff --git a/src/renderers/common/Bindings.js b/src/renderers/common/Bindings.js
359
+ index 5f3d213a016b37db3b2e5d022da3dd0234d86f6d..7a9a168f29773f37ef9a51ef9f07ade9f3ea95ef 100644
360
+ --- a/src/renderers/common/Bindings.js
361
+ +++ b/src/renderers/common/Bindings.js
362
+ @@ -206,10 +206,14 @@ class Bindings extends DataMap {
363
+
364
+ } else if ( binding.isSampledTexture ) {
365
+
366
+ + // Async node compilation can yield while a viewport node changes targets.
367
+ + // Refresh its reference before creating the first GPU binding, not after.
368
+ + binding.update();
369
+ this.textures.updateTexture( binding.texture );
370
+
371
+ } else if ( binding.isSampler ) {
372
+
373
+ + binding.update();
374
+ this.textures.updateSampler( binding );
375
+
376
+ } else if ( binding.isStorageBuffer ) {
377
+ diff --git a/src/renderers/common/RenderObject.js b/src/renderers/common/RenderObject.js
378
+ index c900fa4c9654f8cf3512e96de52a2c1c83e189a6..d0a87eabffc8f667ebe9b2f0eccda0ddf3309652 100644
379
+ --- a/src/renderers/common/RenderObject.js
380
+ +++ b/src/renderers/common/RenderObject.js
381
+ @@ -299,6 +299,18 @@ class RenderObject {
382
+ */
383
+ this._sourceMaterial = renderer._currentSourceMaterial;
384
+
385
+ + /**
386
+ + * The version of `_sourceMaterial` this render object was last validated against.
387
+ + *
388
+ + * An override material is shared by every object in the pass and is used as a per-object
389
+ + * scratchpad, so its own `version` moves for reasons that have nothing to do with this
390
+ + * render object. The source material's version is the signal that does.
391
+ + *
392
+ + * @type {number}
393
+ + * @default 0
394
+ + */
395
+ + this.sourceVersion = ( this._sourceMaterial === null || this._sourceMaterial === undefined ) ? 0 : this._sourceMaterial.version;
396
+ +
397
+ /**
398
+ * An event listener which is defined by `RenderObjects`. It performs
399
+ * clean up tasks when `dispose()` on this render object.
400
+ diff --git a/src/renderers/common/RenderObjects.js b/src/renderers/common/RenderObjects.js
401
+ index 676cc2bd4169427ed420f2467b1e6684bd2aeebc..0fa661db793548254a15ff43f39207abcc43ef49 100644
402
+ --- a/src/renderers/common/RenderObjects.js
403
+ +++ b/src/renderers/common/RenderObjects.js
404
+ @@ -124,9 +124,18 @@ class RenderObjects {
405
+
406
+ }
407
+
408
+ - if ( renderObject.version !== material.version || renderObject.needsUpdate ) {
409
+ + // When the renderer is drawing through an override material, that material is shared by
410
+ + // the whole pass and mutated per object, so its `version` alone answers the wrong
411
+ + // question. The source material this object actually carries answers the right one.
412
+
413
+ - if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
414
+ + const sourceMaterial = this.renderer._currentSourceMaterial;
415
+ + const sourceTracked = sourceMaterial !== null && sourceMaterial !== undefined;
416
+ + const sourceReplaced = sourceTracked && renderObject._sourceMaterial !== sourceMaterial;
417
+ + const sourceChanged = sourceTracked && ( sourceReplaced || renderObject.sourceVersion !== sourceMaterial.version );
418
+ +
419
+ + if ( renderObject.version !== material.version || sourceChanged || renderObject.needsUpdate ) {
420
+ +
421
+ + if ( sourceReplaced || renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
422
+
423
+ renderObject.dispose();
424
+
425
+ @@ -136,6 +145,8 @@ class RenderObjects {
426
+
427
+ renderObject.version = material.version;
428
+
429
+ + if ( sourceTracked ) renderObject.sourceVersion = sourceMaterial.version;
430
+ +
431
+ }
432
+
433
+ }
434
+ diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js
435
+ index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..e9d27b3ddad3b1f080a717d1f6683b60afd2b259 100644
436
+ --- a/src/renderers/common/Renderer.js
437
+ +++ b/src/renderers/common/Renderer.js
438
+ @@ -3550,6 +3550,7 @@ class Renderer {
439
+ let materialDisplacementMap;
440
+ let materialDisplacementScale;
441
+ let materialDisplacementBias;
442
+ + let materialAlphaTest;
443
+
444
+ const previousSourceMaterial = this._currentSourceMaterial;
445
+
446
+ @@ -3575,6 +3576,7 @@ class Renderer {
447
+ materialDisplacementMap = overrideMaterial.displacementMap;
448
+ materialDisplacementScale = overrideMaterial.displacementScale;
449
+ materialDisplacementBias = overrideMaterial.displacementBias;
450
+ + materialAlphaTest = overrideMaterial.alphaTest;
451
+
452
+ if ( material.positionNode && material.positionNode.isNode ) {
453
+
454
+ @@ -3582,7 +3584,15 @@ class Renderer {
455
+
456
+ }
457
+
458
+ - overrideMaterial.alphaTest = material.alphaTest;
459
+ + // `Material.alphaTest`'s setter bumps `version` whenever alpha-test-ness flips, and this
460
+ + // override material is shared by every caster in the pass. A scene that mixes alpha-tested
461
+ + // casters with opaque ones therefore moves that shared version several times per frame,
462
+ + // and every OTHER render object drawn with it then fails the version gate in
463
+ + // `RenderObjects.get` and recomputes a full material cache key that always matches.
464
+ + // Writing the backing field leaves the `alphaTest` getter — and so the cache key — still
465
+ + // reporting this caster's value, while each render object tracks the staleness signal
466
+ + // that belongs to it: its own source material's version.
467
+ + overrideMaterial._alphaTest = material.alphaTest;
468
+ overrideMaterial.alphaMap = material.alphaMap;
469
+ overrideMaterial.displacementMap = material.displacementMap;
470
+ overrideMaterial.displacementScale = material.displacementScale;
471
+ @@ -3644,6 +3654,7 @@ class Renderer {
472
+ scene.overrideMaterial.displacementMap = materialDisplacementMap;
473
+ scene.overrideMaterial.displacementScale = materialDisplacementScale;
474
+ scene.overrideMaterial.displacementBias = materialDisplacementBias;
475
+ + scene.overrideMaterial._alphaTest = materialAlphaTest;
476
+
477
+ }
478
+
479
+ diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js
480
+ index 06e203693ed9b168a2bddf773cd8ce8dc23196e4..37b5e0be09967e8acbd38e570b71eb201fc9bad7 100644
481
+ --- a/src/renderers/webgpu/WebGPUBackend.js
482
+ +++ b/src/renderers/webgpu/WebGPUBackend.js
483
+ @@ -102,6 +102,14 @@ class WebGPUBackend extends Backend {
484
+ */
485
+ this.compatibilityMode = null;
486
+
487
+ + /**
488
+ + * A reference to the WebGPU instance that creates adapters.
489
+ + *
490
+ + * @type {?GPU}
491
+ + * @default null
492
+ + */
493
+ + this.gpu = null;
494
+ +
495
+ /**
496
+ * A reference to the device.
497
+ *
498
+ @@ -214,7 +222,9 @@ class WebGPUBackend extends Backend {
499
+ xrCompatible: renderer.xr.enabled
500
+ };
501
+
502
+ - const adapter = ( typeof navigator !== 'undefined' ) ? await navigator.gpu.requestAdapter( adapterOptions ) : null;
503
+ + this.gpu = ( typeof navigator !== 'undefined' ) ? navigator.gpu : null;
504
+ +
505
+ + const adapter = this.gpu !== null ? await this.gpu.requestAdapter( adapterOptions ) : null;
506
+
507
+ if ( adapter === null ) {
508
+
509
+ diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
510
+ index be07536301cd47ef2544e4a54f6abe17407f52af..6144b98ddeb5d53c5cf280bb51401385a4427547 100644
511
+ --- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
512
+ +++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
513
+ @@ -1323,7 +1323,8 @@ class WGSLNodeBuilder extends NodeBuilder {
514
+
515
+ uniformGPU = buffer;
516
+
517
+ - uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id;
518
+ + // The base builder supplies a unique name within this program. A process-wide
519
+ + // allocation id changes otherwise identical shader source for every buffer.
520
+
521
+ } else {
522
+