@geastack/native-webgl-angle 0.1.12 → 0.1.14

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.
package/geatsc-plugin.mjs CHANGED
@@ -98,6 +98,13 @@ const hostFunctions = [
98
98
  ...nativeUploadFunctions,
99
99
  ...nativeReadbackFunctions,
100
100
  ['threeWebGLBlitFramebuffer','gea_three_webgl_blit_framebuffer','void','extern "C" void gea_three_webgl_blit_framebuffer(double,double,double,double,double,double,double,double,double,double);'],
101
+ ['threeWebGLReadPixelsPack','gea_three_webgl_read_pixels_pack','void','extern "C" void gea_three_webgl_read_pixels_pack(double,double,double,double,double,double,double);'],
102
+ ['threeWebGLFenceSync','gea_three_webgl_fence_sync','double','extern "C" double gea_three_webgl_fence_sync();'],
103
+ ['threeWebGLClientWaitSync','gea_three_webgl_client_wait_sync','double','extern "C" double gea_three_webgl_client_wait_sync(double,double,double);'],
104
+ ['threeWebGLDeleteSync','gea_three_webgl_delete_sync','void','extern "C" void gea_three_webgl_delete_sync(double);'],
105
+ ['threeWebGLTimerBegin','gea_three_webgl_timer_begin','double','extern "C" double gea_three_webgl_timer_begin();'],
106
+ ['threeWebGLTimerEnd','gea_three_webgl_timer_end','void','extern "C" void gea_three_webgl_timer_end();'],
107
+ ['threeWebGLTimerResult','gea_three_webgl_timer_result','double','extern "C" double gea_three_webgl_timer_result(double);'],
101
108
  ['threeWebGLRenderbufferStorageMultisample','gea_three_webgl_renderbuffer_storage_multisample','void','extern "C" void gea_three_webgl_renderbuffer_storage_multisample(double,double,double,double,double);'],
102
109
  [
103
110
  "threeNativeProfilePhase",
@@ -513,6 +520,18 @@ const hostFunctions = [
513
520
  "void",
514
521
  'extern "C" void gea_three_webgl_tex_storage_2d(double target, double levels, double internalFormat, double width, double height);',
515
522
  ],
523
+ [
524
+ "threeWebGLTexStorage3D",
525
+ "gea_three_webgl_tex_storage_3d",
526
+ "void",
527
+ 'extern "C" void gea_three_webgl_tex_storage_3d(double target, double levels, double internalFormat, double width, double height, double depth);',
528
+ ],
529
+ [
530
+ "threeWebGLFramebufferTextureLayer",
531
+ "gea_three_webgl_framebuffer_texture_layer",
532
+ "void",
533
+ 'extern "C" void gea_three_webgl_framebuffer_texture_layer(double target, double attachment, double texture, double level, double layer);',
534
+ ],
516
535
 
517
536
  [
518
537
  "threeWebGLCreateVertexArray",
@@ -2676,7 +2695,81 @@ class WebGLRenderer {
2676
2695
  }
2677
2696
  transformed = transformed.replaceAll(
2678
2697
  rendererUniformUploadSource,
2679
- "\t\t\t\tWebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures, materialProperties.clippingPlanes );",
2698
+ "\t\t\t\tWebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures, materialProperties.clippingPlanes, getUniformSlots( materialProperties ) );",
2699
+ );
2700
+ // Each upload looked every uniform's value cell up by name in the
2701
+ // material's uniforms dictionary. Resolve the cells once beside the
2702
+ // uniform list they belong to; both are rebuilt together, and a list
2703
+ // only ever changes by being recomputed.
2704
+ transformed = replaceOne(
2705
+ transformed,
2706
+ "\t\tfunction getUniformList( materialProperties ) {",
2707
+ `\t\t/** @param {import('./webgl/WebGLProperties.js').NativeWebGLPropertyBag} materialProperties */
2708
+ \t\tfunction getUniformSlots( materialProperties ) {
2709
+
2710
+ \t\t\tconst list = getUniformList( materialProperties );
2711
+
2712
+ \t\t\tif ( materialProperties.nativeUniformSlotsList !== list ) {
2713
+
2714
+ \t\t\t\tmaterialProperties.nativeUniformSlots = WebGLUniforms.slotsOf( list, materialProperties.uniforms );
2715
+ \t\t\t\tmaterialProperties.nativeUniformSlotsList = list;
2716
+
2717
+ \t\t\t}
2718
+
2719
+ \t\t\treturn materialProperties.nativeUniformSlots;
2720
+
2721
+ \t\t}
2722
+
2723
+ \t\tfunction getUniformList( materialProperties ) {`,
2724
+ fileName,
2725
+ "WebGLRenderer resolved uniform slots",
2726
+ );
2727
+ // envMapIntensity and dfgLUT were looked up by name on every draw, and the
2728
+ // ten light cells on every refresh; resolve them once per uniforms object.
2729
+ transformed = replaceOne(
2730
+ transformed,
2731
+ "import { WebGLProperties } from './webgl/WebGLProperties.js';",
2732
+ "import { WebGLProperties, NativeMaterialCells } from './webgl/WebGLProperties.js';",
2733
+ fileName,
2734
+ "WebGLRenderer material cells import",
2735
+ );
2736
+ transformed = replaceOne(
2737
+ transformed,
2738
+ "\t\t/** @param {import('./webgl/WebGLProperties.js').NativeWebGLPropertyBag} materialProperties */\n\t\tfunction getUniformSlots( materialProperties ) {",
2739
+ `\t\t/** @param {import('./webgl/WebGLProperties.js').NativeWebGLPropertyBag} materialProperties @param {import('./shaders/UniformsLib.js').NativeUniforms} uniforms @return {NativeMaterialCells} */
2740
+ \t\tfunction nativeCells( materialProperties, uniforms ) {
2741
+
2742
+ \t\t\tlet cells = materialProperties.nativeCells;
2743
+
2744
+ \t\t\tif ( cells === undefined || cells.uniforms !== uniforms ) {
2745
+
2746
+ \t\t\t\tcells = new NativeMaterialCells( uniforms );
2747
+ \t\t\t\tmaterialProperties.nativeCells = cells;
2748
+
2749
+ \t\t\t}
2750
+
2751
+ \t\t\treturn cells;
2752
+
2753
+ \t\t}
2754
+
2755
+ \t\t/** @param {import('./webgl/WebGLProperties.js').NativeWebGLPropertyBag} materialProperties */
2756
+ \t\tfunction getUniformSlots( materialProperties ) {`,
2757
+ fileName,
2758
+ "WebGLRenderer material cells helper",
2759
+ );
2760
+ transformed = replaceOne(
2761
+ transformed,
2762
+ "\t\t\t\tm_uniforms.envMapIntensity.value = scene.environmentIntensity;",
2763
+ "\t\t\t\tconst envMapIntensity = nativeCells( materialProperties, m_uniforms ).envMapIntensity;\n\t\t\t\tif ( envMapIntensity !== undefined ) envMapIntensity.value = scene.environmentIntensity;",
2764
+ fileName,
2765
+ "WebGLRenderer envMapIntensity cell",
2766
+ );
2767
+ transformed = replaceOne(
2768
+ transformed,
2769
+ "\t\t\tif ( m_uniforms.dfgLUT !== undefined ) {\n\n\t\t\t\tm_uniforms.dfgLUT.value = getDFGLUT();",
2770
+ "\t\t\tconst dfgLUT = nativeCells( materialProperties, m_uniforms ).dfgLUT;\n\n\t\t\tif ( dfgLUT !== undefined ) {\n\n\t\t\t\tdfgLUT.value = getDFGLUT();",
2771
+ fileName,
2772
+ "WebGLRenderer dfgLUT cell",
2680
2773
  );
2681
2774
  transformed = replaceOne(
2682
2775
  transformed,
@@ -2803,7 +2896,7 @@ class WebGLRenderer {
2803
2896
  );
2804
2897
  }
2805
2898
  for (const name of nativeUniformNamesEnabled
2806
- ? ["toneMappingExposure", "cameraPosition"]
2899
+ ? ["toneMappingExposure", "cameraPosition", "receiveShadow", "isOrthographic"]
2807
2900
  : []) {
2808
2901
  transformed = replaceOne(
2809
2902
  transformed,
@@ -2842,6 +2935,89 @@ class WebGLRenderer {
2842
2935
  `WebGLRenderer ${field} uniform`,
2843
2936
  );
2844
2937
  }
2938
+ if (nativeUniformNamesEnabled && nativeUniformBindingsEnabled) {
2939
+ // Every lit draw re-bound every shadow map: a unit allocation, a sampler
2940
+ // uniform check and a texture bind per map, all redundant while the same
2941
+ // program draws again with the same maps and nothing has rebound their
2942
+ // units since. The program remembers what it bound and the slot stamps
2943
+ // (WebGLState) of the units it used; when both still hold, the draw only
2944
+ // allocates the same units, so later textures keep their unit numbers.
2945
+ const shadowSetters =
2946
+ "\t\t\t\tif ( directionalShadowMaps.length > 0 ) {\n\n\t\t\t\t\tp_uniforms.set_directionalShadowMap( _gl, directionalShadowMaps, textures );\n\n\t\t\t\t}\n\n" +
2947
+ "\t\t\t\tif ( spotShadowMaps.length > 0 ) {\n\n\t\t\t\t\tp_uniforms.set_spotShadowMap( _gl, spotShadowMaps, textures );\n\n\t\t\t\t}\n\n" +
2948
+ "\t\t\t\tif ( pointShadowMaps.length > 0 ) {\n\n\t\t\t\t\tp_uniforms.set_pointShadowMap( _gl, pointShadowMaps, textures );\n\n\t\t\t\t}\n";
2949
+ transformed = replaceOne(
2950
+ transformed,
2951
+ shadowSetters,
2952
+ "\t\t\t\tif ( nativeShadowMapsBound( program, directionalShadowMaps, spotShadowMaps, pointShadowMaps ) ) {\n\n" +
2953
+ "\t\t\t\t\tfor ( let unit = 0; unit < program.nativeShadowUnits; unit ++ ) textures.allocateTextureUnit();\n\n" +
2954
+ "\t\t\t\t} else {\n\n" +
2955
+ shadowSetters.replaceAll("\n\t\t\t\t", "\n\t\t\t\t\t").replace(/^\t\t\t\t/, "\t\t\t\t\t") +
2956
+ "\n\t\t\t\t\tnativeShadowMapsRecord( program, p_uniforms, directionalShadowMaps, spotShadowMaps, pointShadowMaps );\n\n" +
2957
+ "\t\t\t\t}\n",
2958
+ fileName,
2959
+ "WebGLRenderer shadow-map binding fast path",
2960
+ );
2961
+ transformed = replaceOne(
2962
+ transformed,
2963
+ "\t\tfunction setProgram( camera, scene, geometry, material, object ) {",
2964
+ `\t\t/**
2965
+ \t\t * @param {import('./webgl/WebGLProgram.js').WebGLProgram} program
2966
+ \t\t * @param {Array<import('../textures/Texture.js').Texture|null>} directional
2967
+ \t\t * @param {Array<import('../textures/Texture.js').Texture|null>} spot
2968
+ \t\t * @param {Array<import('../textures/Texture.js').Texture|null>} point
2969
+ \t\t * @return {boolean}
2970
+ \t\t */
2971
+ \t\tfunction nativeShadowMapsBound( program, directional, spot, point ) {
2972
+
2973
+ \t\t\tconst counts = program.nativeShadowCounts;
2974
+ \t\t\tif ( counts[ 0 ] !== directional.length || counts[ 1 ] !== spot.length || counts[ 2 ] !== point.length ) return false;
2975
+ \t\t\tconst maps = program.nativeShadowMaps;
2976
+ \t\t\tlet k = 0;
2977
+ \t\t\tfor ( let i = 0; i < directional.length; i ++ ) if ( maps[ k ++ ] !== directional[ i ] ) return false;
2978
+ \t\t\tfor ( let i = 0; i < spot.length; i ++ ) if ( maps[ k ++ ] !== spot[ i ] ) return false;
2979
+ \t\t\tfor ( let i = 0; i < point.length; i ++ ) if ( maps[ k ++ ] !== point[ i ] ) return false;
2980
+ \t\t\tconst stamps = program.nativeShadowStamps;
2981
+ \t\t\tfor ( let unit = 0; unit < stamps.length; unit ++ ) if ( state.nativeSlotStamp( _gl.TEXTURE0 + unit ) !== stamps[ unit ] ) return false;
2982
+ \t\t\treturn true;
2983
+
2984
+ \t\t}
2985
+
2986
+ \t\t/**
2987
+ \t\t * @param {import('./webgl/WebGLProgram.js').WebGLProgram} program
2988
+ \t\t * @param {import('./webgl/WebGLUniforms.js').WebGLUniforms} p_uniforms
2989
+ \t\t * @param {Array<import('../textures/Texture.js').Texture|null>} directional
2990
+ \t\t * @param {Array<import('../textures/Texture.js').Texture|null>} spot
2991
+ \t\t * @param {Array<import('../textures/Texture.js').Texture|null>} point
2992
+ \t\t */
2993
+ \t\tfunction nativeShadowMapsRecord( program, p_uniforms, directional, spot, point ) {
2994
+
2995
+ \t\t\tprogram.nativeShadowCounts[ 0 ] = directional.length;
2996
+ \t\t\tprogram.nativeShadowCounts[ 1 ] = spot.length;
2997
+ \t\t\tprogram.nativeShadowCounts[ 2 ] = point.length;
2998
+ \t\t\t/** @type {Array<import('../textures/Texture.js').Texture|null>} */
2999
+ \t\t\tconst maps = [];
3000
+ \t\t\tfor ( let i = 0; i < directional.length; i ++ ) maps.push( directional[ i ] );
3001
+ \t\t\tfor ( let i = 0; i < spot.length; i ++ ) maps.push( spot[ i ] );
3002
+ \t\t\tfor ( let i = 0; i < point.length; i ++ ) maps.push( point[ i ] );
3003
+ \t\t\tprogram.nativeShadowMaps = maps;
3004
+ \t\t\t// A setter whose uniform the program lacks allocates nothing.
3005
+ \t\t\tconst units = ( p_uniforms.fixed_directionalShadowMap !== undefined ? directional.length : 0 ) +
3006
+ \t\t\t\t( p_uniforms.fixed_spotShadowMap !== undefined ? spot.length : 0 ) +
3007
+ \t\t\t\t( p_uniforms.fixed_pointShadowMap !== undefined ? point.length : 0 );
3008
+ \t\t\tprogram.nativeShadowUnits = units;
3009
+ \t\t\t/** @type {number[]} */
3010
+ \t\t\tconst stamps = [];
3011
+ \t\t\tfor ( let unit = 0; unit < units; unit ++ ) stamps.push( state.nativeSlotStamp( _gl.TEXTURE0 + unit ) );
3012
+ \t\t\tprogram.nativeShadowStamps = stamps;
3013
+
3014
+ \t\t}
3015
+
3016
+ \t\tfunction setProgram( camera, scene, geometry, material, object ) {`,
3017
+ fileName,
3018
+ "WebGLRenderer shadow-map binding fast path helpers",
3019
+ );
3020
+ }
2845
3021
  // Three tests a tag and then reads fields off the untyped object; a
2846
3022
  // class test narrows the object to the class that declares those fields,
2847
3023
  // so each read has a type and each uniform value arrives already typed.
@@ -3023,6 +3199,27 @@ class WebGLRenderer {
3023
3199
  fileName,
3024
3200
  "WebGLRenderer native framebuffer selection",
3025
3201
  );
3202
+ // The typed property bag keeps cube and mipmap framebuffers in their own arrays, so `__webglFramebuffer`
3203
+ // stays undefined for those targets. three asks exactly that to decide whether a target is set up yet, so
3204
+ // every setRenderTarget of a cube target ran setupRenderTarget again: six new framebuffers (and six face
3205
+ // allocations) per call, never deleted (probe faces and each sky-cube step at a lighting change).
3206
+ const setUpTarget = (name) =>
3207
+ `${name}.__webglFramebuffer === undefined && ${name}.__webglMipmapFramebuffers.length === 0 && ` +
3208
+ `${name}.__webglCubeFramebuffers.length === 0 && ${name}.__webglCubeMipmapFramebuffers.length === 0`;
3209
+ transformed = replaceOne(
3210
+ transformed,
3211
+ "\t\t\t\t} else if ( renderTargetProperties.__webglFramebuffer === undefined ) {\n\n\t\t\t\t\ttextures.setupRenderTarget( renderTarget );",
3212
+ `\t\t\t\t} else if ( ${setUpTarget("renderTargetProperties")} ) {\n\n\t\t\t\t\ttextures.setupRenderTarget( renderTarget );`,
3213
+ fileName,
3214
+ "WebGLRenderer render target set up once",
3215
+ );
3216
+ transformed = replaceOne(
3217
+ transformed,
3218
+ "\t\t\tif ( properties.get( target ).__webglFramebuffer === undefined ) {\n\n\t\t\t\ttextures.setupRenderTarget( target );",
3219
+ `\t\t\tconst targetProperties = properties.get( target );\n\n\t\t\tif ( ${setUpTarget("targetProperties")} ) {\n\n\t\t\t\ttextures.setupRenderTarget( target );`,
3220
+ fileName,
3221
+ "WebGLRenderer initRenderTarget set up once",
3222
+ );
3026
3223
  transformed = replaceOne(
3027
3224
  transformed,
3028
3225
  "\t\tthis.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) {",
@@ -4847,12 +5044,14 @@ function requireWebGLStateTextureImageSource( value ) {
4847
5044
 
4848
5045
  \t\t\t} else {
4849
5046
 
4850
- \t\t\t\tgl.texImage2D( ${sourceArguments(9, 8, true)} );
5047
+ \t\t\t\tgl.texImage2D( ${sourceArguments(9, 8, true).replace("arguments[ 5 ]", "0")} );
4851
5048
 
4852
5049
  \t\t\t}`,
4853
5050
  fileName,
4854
5051
  "WebGLState native TexImageSource image expansion",
4855
5052
  );
5053
+ // The pixel form above passes border 0 (WebGL requires it): forwarding `arguments[ 5 ]` types it from
5054
+ // every caller's sixth argument (border or image), a nested union the compiler's emitter recasts to undefined.
4856
5055
  // Preserve the actual argument count (and Function#length) while stating
4857
5056
  // the native upload protocol at the forwarding boundary. An arguments
4858
5057
  // object with no formal parameters otherwise erases every pixel source
@@ -5029,21 +5228,34 @@ function requireWebGLStateTextureImageSource( value ) {
5029
5228
  transformed = replaceOne(
5030
5229
  transformed,
5031
5230
  "\tlet currentBoundTextures = {};",
5032
- "\t/** @type {Record<number, { type: number|undefined, texture: import('@geastack/native-webgl-angle/nativeWebGL').NativeHandle|null|undefined }>} */\n\tlet currentBoundTextures = {};",
5231
+ // Indexed by slot - TEXTURE0 in a dense array: a texture bind on every
5232
+ // sampler of every draw read this table, and an object keyed by 33984+
5233
+ // is a hashed dictionary natively.
5234
+ "\t/** @typedef {{ type: number|undefined, texture: import('@geastack/native-webgl-angle/nativeWebGL').NativeHandle|null|undefined }} NativeBoundTexture */\n" +
5235
+ "\t/** @type {Array<NativeBoundTexture|undefined>} */\n\tlet currentBoundTextures = [];\n\n" +
5236
+ "\t/** @param {number} slot @return {NativeBoundTexture|undefined} */\n" +
5237
+ "\tfunction boundTextureAt( slot ) {\n\n\t\tconst index = slot - 0x84C0;\n\t\treturn index >= 0 && index < currentBoundTextures.length ? currentBoundTextures[ index ] : undefined;\n\n\t}\n\n" +
5238
+ "\t/** @param {number} slot @param {NativeBoundTexture} bound */\n" +
5239
+ "\tfunction setBoundTextureAt( slot, bound ) {\n\n\t\tconst index = slot - 0x84C0;\n\t\twhile ( currentBoundTextures.length <= index ) currentBoundTextures.push( undefined );\n\t\tcurrentBoundTextures[ index ] = bound;\n\n\t}\n\n" +
5240
+ "\t/** @type {number[]} */\n\tlet slotStamps = [];\n\tlet nextSlotStamp = 0;\n\n" +
5241
+ "\t/** @param {number} slot */\n" +
5242
+ "\tfunction stampSlot( slot ) {\n\n\t\tconst index = slot - 0x84C0;\n\t\twhile ( slotStamps.length <= index ) slotStamps.push( - 1 );\n\t\tslotStamps[ index ] = ++ nextSlotStamp;\n\n\t}\n\n" +
5243
+ "\t/** @param {number} slot @return {number} The slot's stamp, -1 if never bound since the last reset. */\n" +
5244
+ "\tfunction slotStamp( slot ) {\n\n\t\tconst index = slot - 0x84C0;\n\t\treturn index >= 0 && index < slotStamps.length ? slotStamps[ index ] : - 1;\n\n\t}",
5033
5245
  fileName,
5034
5246
  "WebGLState bound texture table",
5035
5247
  );
5036
5248
  transformed = replaceOne(
5037
5249
  transformed,
5038
5250
  "\tfunction unbindTexture() {\n\n\t\tconst boundTexture = currentBoundTextures[ currentTextureSlot ];",
5039
- "\tfunction unbindTexture() {\n\n\t\tif ( typeof currentTextureSlot !== 'number' ) return;\n\n\t\tconst textureSlot = currentTextureSlot;\n\t\tconst boundTexture = currentBoundTextures[ textureSlot ];",
5251
+ "\tfunction unbindTexture() {\n\n\t\tif ( typeof currentTextureSlot !== 'number' ) return;\n\n\t\tconst textureSlot = currentTextureSlot;\n\t\tconst boundTexture = boundTextureAt( textureSlot );",
5040
5252
  fileName,
5041
5253
  "WebGLState current texture slot narrowing",
5042
5254
  );
5043
5255
  transformed = replaceOne(
5044
5256
  transformed,
5045
5257
  "\t\tcurrentBoundTextures = {};",
5046
- "\t\tcurrentBoundTextures = /** @type {Record<number, { type: number|undefined, texture: import('@geastack/native-webgl-angle/nativeWebGL').NativeHandle|null|undefined }>} */ ( {} );",
5258
+ "\t\tcurrentBoundTextures = [];",
5047
5259
  fileName,
5048
5260
  "WebGLState bound texture reset",
5049
5261
  );
@@ -5113,7 +5325,7 @@ function requireWebGLStateTextureImageSource( value ) {
5113
5325
  transformed = replaceOne(
5114
5326
  transformed,
5115
5327
  "\t\tlet boundTexture = currentBoundTextures[ webglSlot ];",
5116
- "\t\tconst textureSlot = /** @type {number} */ ( webglSlot );\n\n\t\tlet boundTexture = currentBoundTextures[ textureSlot ];",
5328
+ "\t\tconst textureSlot = /** @type {number} */ ( webglSlot );\n\n\t\tlet boundTexture = boundTextureAt( textureSlot );",
5117
5329
  fileName,
5118
5330
  "WebGLState required texture slot",
5119
5331
  );
@@ -5133,7 +5345,7 @@ function requireWebGLStateTextureImageSource( value ) {
5133
5345
  .slice(bindTextureStart, bindTextureEnd)
5134
5346
  .replace(
5135
5347
  "currentBoundTextures[ webglSlot ] = boundTexture;",
5136
- "currentBoundTextures[ textureSlot ] = boundTexture;",
5348
+ "setBoundTextureAt( textureSlot, boundTexture );",
5137
5349
  )
5138
5350
  .replaceAll(
5139
5351
  "currentTextureSlot !== webglSlot",
@@ -5154,10 +5366,34 @@ function requireWebGLStateTextureImageSource( value ) {
5154
5366
  transformed = replaceOne(
5155
5367
  transformed,
5156
5368
  "\t\t\tgl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );",
5157
- "\t\t\tconst textureToBind = webglTexture || emptyTextures[ webglType ];\n\n\t\t\tif ( textureToBind === undefined ) throw new Error( 'THREE.WebGLState: Missing empty texture for binding target.' );\n\n\t\t\tgl.bindTexture( webglType, textureToBind );",
5369
+ "\t\t\tconst textureToBind = webglTexture || emptyTextures[ webglType ];\n\n\t\t\tif ( textureToBind === undefined ) throw new Error( 'THREE.WebGLState: Missing empty texture for binding target.' );\n\n\t\t\tgl.bindTexture( webglType, textureToBind );\n\t\t\tstampSlot( textureSlot );",
5158
5370
  fileName,
5159
5371
  "WebGLState bound texture fallback",
5160
5372
  );
5373
+ // Each texture slot carries a stamp that changes whenever the slot is
5374
+ // rebound, so a caller can tell its earlier binds are still in place
5375
+ // (WebGLRenderer's shadow-map fast path).
5376
+ transformed = replaceOne(
5377
+ transformed,
5378
+ "\t\t\tgl.bindTexture( boundTexture.type, null );",
5379
+ "\t\t\tgl.bindTexture( boundTexture.type, null );\n\t\t\tstampSlot( textureSlot );",
5380
+ fileName,
5381
+ "WebGLState unbind slot stamp",
5382
+ );
5383
+ transformed = replaceOne(
5384
+ transformed,
5385
+ "\t\tcurrentBoundTextures = [];",
5386
+ "\t\tcurrentBoundTextures = [];\n\t\tslotStamps = [];",
5387
+ fileName,
5388
+ "WebGLState slot stamp reset",
5389
+ );
5390
+ transformed = replaceOne(
5391
+ transformed,
5392
+ "\t\tbindTexture: bindTexture,\n",
5393
+ "\t\tbindTexture: bindTexture,\n\t\tnativeSlotStamp: slotStamp,\n",
5394
+ fileName,
5395
+ "WebGLState slot stamp export",
5396
+ );
5161
5397
  transformed = replaceOne(
5162
5398
  transformed,
5163
5399
  "\t\tcurrentDrawbuffers = new WeakMap();",
@@ -5749,8 +5985,331 @@ ${transformed}`;
5749
5985
  );
5750
5986
  transformed = transformed.slice(0, start) + body + transformed.slice(end);
5751
5987
  }
5988
+ // Every render pass updates every drawn geometry's attributes, and each
5989
+ // update hashed the attribute into `buffers`. The WeakMap stays the
5990
+ // authority; each data attribute also carries the entry its owning
5991
+ // WebGLAttributes stored, tagged with that instance's number, so the
5992
+ // per-draw read is a field load. Every write and delete of `buffers`
5993
+ // keeps the field in step.
5994
+ transformed = replaceOne(
5995
+ transformed,
5996
+ "function WebGLAttributes( gl ) {\n",
5997
+ "let nextNativeAttributesOwner = 1;\n\nfunction WebGLAttributes( gl ) {\n\n\tconst nativeOwner = nextNativeAttributesOwner ++;\n",
5998
+ fileName,
5999
+ "WebGLAttributes front cache owner",
6000
+ );
6001
+ transformed = replaceOne(
6002
+ transformed,
6003
+ "\t\treturn buffers.get( dataAttribute );",
6004
+ "\t\tif ( dataAttribute.__nativeBufferOwner === nativeOwner ) return dataAttribute.__nativeBuffer;\n\n\t\treturn buffers.get( dataAttribute );",
6005
+ fileName,
6006
+ "WebGLAttributes front cache get",
6007
+ );
6008
+ transformed = replaceOne(
6009
+ transformed,
6010
+ "\t\t\tbuffers.delete( dataAttribute );",
6011
+ "\t\t\tbuffers.delete( dataAttribute );\n\n\t\t\tif ( dataAttribute.__nativeBufferOwner === nativeOwner ) {\n\n\t\t\t\tdataAttribute.__nativeBufferOwner = 0;\n\t\t\t\tdataAttribute.__nativeBuffer = undefined;\n\n\t\t\t}",
6012
+ fileName,
6013
+ "WebGLAttributes front cache delete",
6014
+ );
6015
+ transformed = replaceOne(
6016
+ transformed,
6017
+ `\t\t\t\tbuffers.set( dataAttribute, {
6018
+ \t\t\t\t\tbuffer: dataAttribute.buffer,
6019
+ \t\t\t\t\ttype: dataAttribute.type,
6020
+ \t\t\t\t\tbytesPerElement: dataAttribute.elementSize,
6021
+ \t\t\t\t\tversion: dataAttribute.version
6022
+ \t\t\t\t} );`,
6023
+ `\t\t\t\t/** @type {NativeAttributeBufferCache} */
6024
+ \t\t\t\tconst entry = {
6025
+ \t\t\t\t\tbuffer: dataAttribute.buffer,
6026
+ \t\t\t\t\ttype: dataAttribute.type,
6027
+ \t\t\t\t\tbytesPerElement: dataAttribute.elementSize,
6028
+ \t\t\t\t\tversion: dataAttribute.version
6029
+ \t\t\t\t};
6030
+ \t\t\t\tbuffers.set( dataAttribute, entry );
6031
+ \t\t\t\tdataAttribute.__nativeBufferOwner = nativeOwner;
6032
+ \t\t\t\tdataAttribute.__nativeBuffer = entry;`,
6033
+ fileName,
6034
+ "WebGLAttributes front cache GL attribute write",
6035
+ );
6036
+ transformed = replaceOne(
6037
+ transformed,
6038
+ `\t\tconst data = buffers.get( dataAttribute );
6039
+
6040
+ \t\tif ( data === undefined ) {
6041
+
6042
+ \t\t\tbuffers.set( dataAttribute, createBuffer( dataAttribute, bufferType ) );`,
6043
+ `\t\tconst data = dataAttribute.__nativeBufferOwner === nativeOwner ? dataAttribute.__nativeBuffer : buffers.get( dataAttribute );
6044
+
6045
+ \t\tif ( data === undefined ) {
6046
+
6047
+ \t\t\tconst entry = createBuffer( dataAttribute, bufferType );
6048
+ \t\t\tbuffers.set( dataAttribute, entry );
6049
+ \t\t\tdataAttribute.__nativeBufferOwner = nativeOwner;
6050
+ \t\t\tdataAttribute.__nativeBuffer = entry;`,
6051
+ fileName,
6052
+ "WebGLAttributes front cache update",
6053
+ );
6054
+ }
6055
+ if (
6056
+ normalized.endsWith("/three/src/core/BufferAttribute.js") ||
6057
+ normalized.endsWith("/three/src/core/InterleavedBuffer.js") ||
6058
+ normalized.endsWith("/three/src/core/GLBufferAttribute.js")
6059
+ ) {
6060
+ // The native WebGLAttributes front cache (see WebGLAttributes above).
6061
+ transformed = replaceOne(
6062
+ transformed,
6063
+ "\t\tthis.version = 0;\n",
6064
+ `\t\tthis.version = 0;
6065
+
6066
+ \t\t/** @type {number} The WebGLAttributes instance whose entry \`__nativeBuffer\` holds; 0 when none. */
6067
+ \t\tthis.__nativeBufferOwner = 0;
6068
+ \t\t/** @type {{ buffer: import('@geastack/native-webgl-angle/nativeWebGL').NativeHandle, type: number, bytesPerElement: number, version: number, size?: number }|undefined} */
6069
+ \t\tthis.__nativeBuffer = undefined;
6070
+ `,
6071
+ fileName,
6072
+ "native attribute buffer front cache fields",
6073
+ );
6074
+ }
6075
+ if (
6076
+ normalized.endsWith("/three/src/materials/Material.js") ||
6077
+ normalized.endsWith("/three/src/textures/Texture.js") ||
6078
+ normalized.endsWith("/three/src/textures/Source.js")
6079
+ ) {
6080
+ // The native WebGLProperties front cache (see WebGLProperties).
6081
+ transformed = replaceOne(
6082
+ transformed,
6083
+ "\t\tthis.version = 0;\n",
6084
+ `\t\tthis.version = 0;
6085
+
6086
+ \t\t/** @type {number} The WebGLProperties instance whose bag \`__nativeProperties\` holds; 0 when none. */
6087
+ \t\tthis.__nativePropertiesOwner = 0;
6088
+ \t\t/** @type {import('../renderers/webgl/WebGLProperties.js').NativeWebGLPropertyBag|undefined} */
6089
+ \t\tthis.__nativeProperties = undefined;
6090
+ `,
6091
+ fileName,
6092
+ "native property bag front cache fields",
6093
+ );
6094
+ }
6095
+ if (normalized.endsWith("/three/src/core/Object3D.js") || normalized.endsWith("/three/src/core/BufferGeometry.js")) {
6096
+ // The native WebGLObjects front cache (see WebGLObjects).
6097
+ transformed = replaceOne(
6098
+ transformed,
6099
+ "\t\tthis.uuid = generateUUID();\n",
6100
+ `\t\tthis.uuid = generateUUID();
6101
+
6102
+ \t\t/** @type {number} The WebGLObjects instance whose update frame \`__nativeObjectsFrame\` holds; 0 when none. */
6103
+ \t\tthis.__nativeObjectsOwner = 0;
6104
+ \t\t/** @type {number} */
6105
+ \t\tthis.__nativeObjectsFrame = - 1;
6106
+ `,
6107
+ fileName,
6108
+ "native objects update front cache fields",
6109
+ );
6110
+ }
6111
+ if (normalized.endsWith("/three/src/core/BufferGeometry.js")) {
6112
+ // The attribute-set stamp WebGLBindingStates' needsUpdate fast path reads.
6113
+ transformed = replaceOne(
6114
+ transformed,
6115
+ "\t\t */\n\t\tthis.attributes = {};\n",
6116
+ "\t\t */\n\t\tthis.attributes = {};\n\n\t\t/** @type {number} Bumped whenever the attribute set changes. */\n\t\tthis.__nativeAttributeStamp = 0;\n",
6117
+ fileName,
6118
+ "native attribute-set stamp field",
6119
+ );
6120
+ transformed = replaceOne(
6121
+ transformed,
6122
+ "\t\tthis.index = null;\n\t\tthis.attributes = {};\n",
6123
+ "\t\tthis.index = null;\n\t\tthis.attributes = {};\n\t\tthis.__nativeAttributeStamp ++;\n",
6124
+ fileName,
6125
+ "native attribute-set stamp on copy",
6126
+ );
6127
+ transformed = replaceOne(
6128
+ transformed,
6129
+ "\t\tthis.attributes[ name ] = attribute;\n",
6130
+ "\t\tthis.attributes[ name ] = attribute;\n\t\tthis.__nativeAttributeStamp ++;\n",
6131
+ fileName,
6132
+ "native attribute-set stamp on set",
6133
+ );
6134
+ transformed = replaceOne(
6135
+ transformed,
6136
+ "\t\tdelete this.attributes[ name ];\n",
6137
+ "\t\tdelete this.attributes[ name ];\n\t\tthis.__nativeAttributeStamp ++;\n",
6138
+ fileName,
6139
+ "native attribute-set stamp on delete",
6140
+ );
6141
+ }
6142
+ // Light uniforms (the light, shadow, ambient and probe structures
6143
+ // markUniformsLightsNeedsUpdate flags) are written only by WebGLLights.setup
6144
+ // and setupView. three re-uploads them whenever the program changes, which
6145
+ // natively is a walk over every light struct member on nearly every draw.
6146
+ // Each setup/setupView takes a new stamp; a program records the stamp whose
6147
+ // values it last received and skips the upload while that stamp is current,
6148
+ // because its GL uniforms already hold exactly those values. Shadow-map
6149
+ // samplers are bound separately and still every draw.
6150
+ if (normalized.endsWith("/three/src/renderers/webgl/WebGLLights.js")) {
6151
+ transformed = replaceOne(
6152
+ transformed,
6153
+ "\tconst state = {\n\n\t\tversion: 0,\n",
6154
+ "\tconst state = {\n\n\t\tversion: 0,\n\n\t\tnativeStamp: 0,\n",
6155
+ fileName,
6156
+ "WebGLLights native upload stamp field",
6157
+ );
6158
+ transformed = replaceOne(
6159
+ transformed,
6160
+ "\tfunction setup( lights ) {\n",
6161
+ "\tfunction setup( lights ) {\n\n\t\tstate.nativeStamp = ++ nativeLightsStamp;\n",
6162
+ fileName,
6163
+ "WebGLLights.setup native upload stamp",
6164
+ );
6165
+ transformed = replaceOne(
6166
+ transformed,
6167
+ "\tfunction setupView( lights, camera ) {\n",
6168
+ "\tfunction setupView( lights, camera ) {\n\n\t\tstate.nativeStamp = ++ nativeLightsStamp;\n",
6169
+ fileName,
6170
+ "WebGLLights.setupView native upload stamp",
6171
+ );
6172
+ transformed = replaceOne(
6173
+ transformed,
6174
+ "function WebGLLights(",
6175
+ `let nativeLightsStamp = 0;
6176
+
6177
+ // Every render state (the main view, and each nested render such as a mirror)
6178
+ // owns a WebGLLights whose version counts its own hash changes, so a material
6179
+ // drawn in two states sees a "new" version on every switch and three rebuilds
6180
+ // its program parameters to find the same program. Equal hashes (light and
6181
+ // shadow counts) share one structure id, which lets the renderer tell a
6182
+ // re-wire from a real program change.
6183
+ /** @type {Map<string, number>} */
6184
+ const nativeLightStructures = new Map();
6185
+ /** @param {{ directionalLength: number, pointLength: number, spotLength: number, rectAreaLength: number, hemiLength: number, numDirectionalShadows: number, numPointShadows: number, numSpotShadows: number, numSpotMaps: number, numLightProbes: number }} hash @return {number} */
6186
+ function nativeLightStructure( hash ) {
6187
+
6188
+ \tconst key = [ hash.directionalLength, hash.pointLength, hash.spotLength, hash.rectAreaLength, hash.hemiLength,
6189
+ \t\thash.numDirectionalShadows, hash.numPointShadows, hash.numSpotShadows, hash.numSpotMaps, hash.numLightProbes ].join( ',' );
6190
+ \tlet structure = nativeLightStructures.get( key );
6191
+
6192
+ \tif ( structure === undefined ) {
6193
+
6194
+ \t\tstructure = nativeLightStructures.size + 1;
6195
+ \t\tnativeLightStructures.set( key, structure );
6196
+
6197
+ \t}
6198
+
6199
+ \treturn structure;
6200
+
6201
+ }
6202
+
6203
+ function WebGLLights(`,
6204
+ fileName,
6205
+ "WebGLLights native upload stamp counter",
6206
+ );
6207
+ transformed = replaceOne(
6208
+ transformed,
6209
+ "\t\tnativeStamp: 0,\n",
6210
+ "\t\tnativeStamp: 0,\n\n\t\tnativeStructure: 0,\n",
6211
+ fileName,
6212
+ "WebGLLights native structure field",
6213
+ );
6214
+ transformed = replaceOne(
6215
+ transformed,
6216
+ "\t\t\tstate.version = nextVersion ++;",
6217
+ "\t\t\tstate.version = nextVersion ++;\n\t\t\tstate.nativeStructure = nativeLightStructure( hash );",
6218
+ fileName,
6219
+ "WebGLLights native structure on hash change",
6220
+ );
6221
+ }
6222
+ if (normalized.endsWith("/three/src/renderers/webgl/WebGLProgram.js")) {
6223
+ transformed = replaceOne(
6224
+ transformed,
6225
+ "\tthis.cacheKey = cacheKey;\n",
6226
+ "\tthis.cacheKey = cacheKey;\n\t/** The WebGLLights stamp whose light uniforms this program holds; 0 when none. */\n\tthis.nativeLightsStamp = 0;\n" +
6227
+ "\t/** @type {Array<import('../../textures/Texture.js').Texture|null>} The shadow maps (directional, spot, point, in order) this program last bound, see WebGLRenderer's shadow-map fast path. */\n\tthis.nativeShadowMaps = [];\n" +
6228
+ "\t/** @type {number[]} Lengths of the three arrays in nativeShadowMaps. */\n\tthis.nativeShadowCounts = [ - 1, - 1, - 1 ];\n" +
6229
+ "\t/** @type {number[]} WebGLState slot stamps of the units those maps were bound to. */\n\tthis.nativeShadowStamps = [];\n" +
6230
+ "\t/** The texture units the shadow-map setters allocated. */\n\tthis.nativeShadowUnits = 0;\n",
6231
+ fileName,
6232
+ "WebGLProgram native light upload stamp",
6233
+ );
6234
+ }
6235
+ if (normalized.endsWith("/three/src/renderers/WebGLRenderer.js")) {
6236
+ transformed = replaceOne(
6237
+ transformed,
6238
+ "\t\t\t\t\tmarkUniformsLightsNeedsUpdate( m_uniforms, refreshLights );",
6239
+ `\t\t\t\t\tconst lightsStamp = lights.state.nativeStamp;
6240
+ \t\t\t\t\tconst uploadLights = refreshLights && program.nativeLightsStamp !== lightsStamp;
6241
+ \t\t\t\t\t// The ten light cells, resolved once per uniforms object (nativeCells).
6242
+ \t\t\t\t\tfor ( const cell of nativeCells( materialProperties, m_uniforms ).lights ) cell.needsUpdate = uploadLights;
6243
+ \t\t\t\t\tif ( uploadLights ) program.nativeLightsStamp = lightsStamp;`,
6244
+ fileName,
6245
+ "WebGLRenderer light uniform upload stamp",
6246
+ );
6247
+ const lightWiring = [
6248
+ ["ambientLightColor", "ambient"], ["lightProbe", "probe"],
6249
+ ["directionalLights", "directional"], ["directionalLightShadows", "directionalShadow"],
6250
+ ["spotLights", "spot"], ["spotLightShadows", "spotShadow"], ["rectAreaLights", "rectArea"],
6251
+ ["ltc_1", "rectAreaLTC1"], ["ltc_2", "rectAreaLTC2"],
6252
+ ["pointLights", "point"], ["pointLightShadows", "pointShadow"], ["hemisphereLights", "hemi"],
6253
+ ["directionalShadowMatrix", "directionalShadowMatrix"], ["spotLightMatrix", "spotLightMatrix"],
6254
+ ["spotLightMap", "spotLightMap"], ["pointShadowMatrix", "pointShadowMatrix"],
6255
+ ];
6256
+ const wiringText = lightWiring.map(([u, s]) => `\t\t\t\tuniforms.${u}.value = lights.state.${s};\n`).join("");
6257
+ // getProgram's wiring must still be exactly this list, or the re-wire below would miss a field.
6258
+ if (!transformed.includes(wiringText.replace("\t\t\t\tuniforms.directionalShadowMatrix", "\n\t\t\t\tuniforms.directionalShadowMatrix"))) {
6259
+ throw new Error(`Three WebGLRenderer light wiring at ${fileName} no longer matches the native re-wire list`);
6260
+ }
6261
+ transformed = replaceOne(
6262
+ transformed,
6263
+ "\t\t\tmaterialProperties.lightsStateVersion = lightsStateVersion;\n",
6264
+ "\t\t\tmaterialProperties.lightsStateVersion = lightsStateVersion;\n\t\t\tmaterialProperties.nativeLightsStructure = lights.state.nativeStructure;\n\t\t\tmaterialProperties.nativeShadowCount = shadowsArray.length;\n",
6265
+ fileName,
6266
+ "WebGLRenderer program light structure",
6267
+ );
6268
+ // A lights version from another render state with the same structure and
6269
+ // shadow count selects the same program: point the material's light
6270
+ // uniforms at this state's arrays (getProgram's wiring) instead of
6271
+ // rebuilding the program parameters. Every other program check still runs.
6272
+ transformed = replaceOne(
6273
+ transformed,
6274
+ "\t\t\tif ( material.version === materialProperties.__version ) {\n\n\t\t\t\tif ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) {",
6275
+ `\t\t\tif ( material.version === materialProperties.__version && materialProperties.needsLights &&
6276
+ \t\t\t\tmaterialProperties.lightsStateVersion !== lights.state.version &&
6277
+ \t\t\t\tmaterialProperties.nativeLightsStructure === lights.state.nativeStructure &&
6278
+ \t\t\t\tmaterialProperties.nativeShadowCount === currentRenderState.state.shadowsArray.length ) {
6279
+
6280
+ \t\t\t\tconst uniforms = materialProperties.uniforms;
6281
+ ${wiringText.replace(/\t\t\t\t/g, "\t\t\t\t")}
6282
+ \t\t\t\tmaterialProperties.lightsStateVersion = lights.state.version;
6283
+
6284
+ \t\t\t}
6285
+
6286
+ \t\t\tif ( material.version === materialProperties.__version ) {
6287
+
6288
+ \t\t\t\tif ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) {`,
6289
+ fileName,
6290
+ "WebGLRenderer light re-wire across render states",
6291
+ );
5752
6292
  }
5753
6293
  if (normalized.endsWith("/three/src/renderers/webgl/WebGLShadowMap.js")) {
6294
+ // An app that replaces `shadowMap.render` (the browser's cached sun shadow)
6295
+ // cannot do so natively: the field holds a `this`-bound function expression
6296
+ // no app function converts to. This hook is an ordinary function-typed
6297
+ // field instead; render() offers each call to it first, and a hook that
6298
+ // returns true has rendered the shadows itself.
6299
+ transformed = replaceOne(
6300
+ transformed,
6301
+ "\tconst scope = this;\n",
6302
+ "\tconst scope = this;\n\n\t/** @type {((lights: Array<import('../../lights/Light.js').Light>, scene: import('../../scenes/Scene.js').Scene, camera: import('../../cameras/Camera.js').Camera) => boolean)|null} */\n\tthis.nativeRenderHook = null;\n",
6303
+ fileName,
6304
+ "WebGLShadowMap native render hook field",
6305
+ );
6306
+ transformed = replaceOne(
6307
+ transformed,
6308
+ "\t\tif ( scope.enabled === false ) return;\n",
6309
+ "\t\tconst nativeRenderHook = scope.nativeRenderHook;\n\t\tif ( nativeRenderHook !== null && nativeRenderHook( lights, scene, camera ) ) return;\n\n\t\tif ( scope.enabled === false ) return;\n",
6310
+ fileName,
6311
+ "WebGLShadowMap native render hook call",
6312
+ );
5754
6313
  transformed = replaceOne(
5755
6314
  transformed,
5756
6315
  "\t\tuniforms: {\n\t\t\tshadow_pass:",
@@ -6157,13 +6716,9 @@ class WebGLBackgroundCubeMaterial extends ShaderMaterial {
6157
6716
  \tsetEnvironment( texture ) {
6158
6717
 
6159
6718
  \t\tthis.uniforms.envMap.value = texture;
6160
-
6161
- \t}
6162
-
6163
- \t/** @this {WebGLBackgroundCubeMaterial} */
6164
- \tget envMap() {
6165
-
6166
- \t\treturn uniformTexture( this.uniforms.envMap );
6719
+ \t\t// The renderer reads material.envMap as the base Material field (a subclass getter is not
6720
+ \t\t// dispatched through that read), so keep the field itself in step.
6721
+ \t\tthis.envMap = texture;
6167
6722
 
6168
6723
  \t}
6169
6724
 
@@ -6175,13 +6730,7 @@ class WebGLBackgroundPlaneMaterial extends ShaderMaterial {
6175
6730
  \tsetMap( texture ) {
6176
6731
 
6177
6732
  \t\tthis.uniforms.t2D.value = texture;
6178
-
6179
- \t}
6180
-
6181
- \t/** @this {WebGLBackgroundPlaneMaterial} */
6182
- \tget map() {
6183
-
6184
- \t\treturn uniformTexture( this.uniforms.t2D );
6733
+ \t\tthis.map = texture;
6185
6734
 
6186
6735
  \t}
6187
6736
 
@@ -6551,23 +7100,31 @@ class WebGLBackgroundPlaneMaterial extends ShaderMaterial {
6551
7100
  transformed = transformUniformsUtils(transformed, fileName);
6552
7101
  }
6553
7102
  if (normalized.endsWith("/three/src/utils.js")) {
6554
- const probeStart = "function probeAsync( gl, sync, interval ) {";
6555
- const probeEnd = "\n}\n\n/**\n * Converts a projection matrix";
6556
- const probeStartIndex = transformed.indexOf(probeStart);
6557
- const probeEndIndex = transformed.indexOf(probeEnd, probeStartIndex);
6558
- if (
6559
- probeStartIndex < 0 ||
6560
- probeEndIndex < 0 ||
6561
- transformed.indexOf(probeStart, probeStartIndex + 1) >= 0
6562
- ) {
7103
+ // probeAsync keeps three's fence polling (the host implements fenceSync/clientWaitSync, so the
7104
+ // async readback resolves when the GPU has finished the copy), once per frame: setTimeout's result
7105
+ // types as Node's Timeout, which has no native boundary.
7106
+ const probeRetries = transformed.split("setTimeout( probe, interval );").length - 1;
7107
+ if (probeRetries !== 2) {
6563
7108
  throw new Error(
6564
- `Three async WebGL probe source at ${fileName} no longer matches the native adaptation`,
7109
+ `Three async WebGL probe source at ${fileName} has ${probeRetries} probe timers; expected 2`,
6565
7110
  );
6566
7111
  }
7112
+ transformed = transformed.replaceAll("setTimeout( probe, interval );", "requestAnimationFrame( probe );");
7113
+ // Resolve with the sync status: a value-less resolve() of a Promise<void> lowers its optional
7114
+ // payload incorrectly, and the callers only await completion.
7115
+ const probeAt = transformed.indexOf("function probeAsync( gl, sync, interval ) {");
7116
+ const probeDocAt = transformed.lastIndexOf(" * @return {Promise<void>}", probeAt);
7117
+ const probeEndAt = transformed.indexOf("\n}\n", probeAt);
7118
+ const probeBody = transformed.slice(probeAt, probeEndAt);
7119
+ if (probeAt < 0 || probeDocAt < 0 || probeDocAt < transformed.lastIndexOf("/**", probeAt) || probeBody.split("resolve();").length !== 2) {
7120
+ throw new Error(`Three async WebGL probe source at ${fileName} no longer matches the native adaptation`);
7121
+ }
6567
7122
  transformed =
6568
- transformed.slice(0, probeStartIndex) +
6569
- "function probeAsync( gl, sync, interval ) {\n\n\treturn Promise.resolve( undefined );" +
6570
- transformed.slice(probeEndIndex);
7123
+ transformed.slice(0, probeDocAt) +
7124
+ " * @return {Promise<number>}" +
7125
+ transformed.slice(probeDocAt + " * @return {Promise<void>}".length, probeAt) +
7126
+ probeBody.replace("resolve();", "resolve( 1 );") +
7127
+ transformed.slice(probeEndAt);
6571
7128
  transformed = replaceOne(
6572
7129
  transformed,
6573
7130
  "\t\t\tparams[ 0 ] += ' ' + stackTrace.getLocation();",
@@ -6633,13 +7190,6 @@ class WebGLBackgroundPlaneMaterial extends ShaderMaterial {
6633
7190
  `Three utils source at ${fileName} has ${warnCalls} console.warn calls; expected 2`,
6634
7191
  );
6635
7192
  transformed = transformed.replaceAll("console.warn(", "console.error(");
6636
- transformed = replaceOne(
6637
- transformed,
6638
- "@param {number} interval - The polling interval in milliseconds.\n * @return {Promise<void>} A promise that resolves when the sync completes or rejects if it fails.",
6639
- "@param {number} interval - The polling interval in milliseconds.\n * @return {Promise<undefined>} A promise that resolves when the sync completes or rejects if it fails.",
6640
- fileName,
6641
- "probeAsync resolved payload",
6642
- );
6643
7193
  }
6644
7194
  if (
6645
7195
  normalized.endsWith("/three/src/renderers/webgl/WebGLClipping.js") &&
@@ -7319,6 +7869,76 @@ ${indent}}`,
7319
7869
  fileName,
7320
7870
  "WebGLBindingStates wireframe cache write",
7321
7871
  );
7872
+ // The nested cache is keyed by ids spelled as strings, so every draw formats
7873
+ // three numbers and hashes them through string dictionaries. A numeric front
7874
+ // cache answers repeat lookups: geometry id, then one exact integer packing
7875
+ // object id, program id and wireframe (program ids past 2^16 skip it). Any
7876
+ // release clears it before the nested states are deleted, so it never holds a
7877
+ // state the nested cache no longer has.
7878
+ transformed = replaceOne(
7879
+ transformed,
7880
+ "\tconst bindingStates = {};",
7881
+ "\tconst bindingStates = {};\n\t/** @type {Map<number, Map<number, NativeBindingState>>} */\n\tconst fastBindingStates = new Map();",
7882
+ fileName,
7883
+ "WebGLBindingStates numeric front cache",
7884
+ );
7885
+ transformed = replaceOne(
7886
+ transformed,
7887
+ "\t\tconst wireframe = ( material.wireframe === true );\n\n\t\tlet objectMap = bindingStates[ geometry.id ];",
7888
+ `\t\tconst wireframe = ( material.wireframe === true );
7889
+ \t\tconst fastKey = program.id < 65536 ? ( ( ( object.isInstancedMesh === true ) ? object.id : 0 ) * 65536 + program.id ) * 2 + ( wireframe ? 1 : 0 ) : - 1;
7890
+ \t\tlet fastGeometry = fastBindingStates.get( geometry.id );
7891
+
7892
+ \t\tif ( fastKey >= 0 && fastGeometry !== undefined ) {
7893
+
7894
+ \t\t\tconst cached = fastGeometry.get( fastKey );
7895
+
7896
+ \t\t\tif ( cached !== undefined ) return cached;
7897
+
7898
+ \t\t}
7899
+
7900
+ \t\tlet objectMap = bindingStates[ geometry.id ];`,
7901
+ fileName,
7902
+ "WebGLBindingStates numeric front cache read",
7903
+ );
7904
+ transformed = replaceOne(
7905
+ transformed,
7906
+ "\t\t\tstateMap[ wireframeKey ] = state;\n\n\t\t}\n\n\t\treturn state;",
7907
+ `\t\t\tstateMap[ wireframeKey ] = state;
7908
+
7909
+ \t\t}
7910
+
7911
+ \t\tif ( fastKey >= 0 ) {
7912
+
7913
+ \t\t\tif ( fastGeometry === undefined ) {
7914
+
7915
+ \t\t\t\tfastGeometry = new Map();
7916
+ \t\t\t\tfastBindingStates.set( geometry.id, fastGeometry );
7917
+
7918
+ \t\t\t}
7919
+
7920
+ \t\t\tfastGeometry.set( fastKey, state );
7921
+
7922
+ \t\t}
7923
+
7924
+ \t\treturn state;`,
7925
+ fileName,
7926
+ "WebGLBindingStates numeric front cache write",
7927
+ );
7928
+ for (const header of [
7929
+ "\tfunction dispose() {\n\n\t\treset();",
7930
+ "\tfunction releaseStatesOfGeometry( geometry ) {",
7931
+ "\tfunction releaseStatesOfProgram( program ) {",
7932
+ "\tfunction releaseStatesOfObject( object ) {",
7933
+ ]) {
7934
+ transformed = replaceOne(
7935
+ transformed,
7936
+ header,
7937
+ `${header}\n\n\t\tfastBindingStates.clear();`,
7938
+ fileName,
7939
+ "WebGLBindingStates numeric front cache release",
7940
+ );
7941
+ }
7322
7942
  transformed = replaceOne(
7323
7943
  transformed,
7324
7944
  "\tfunction releaseStatesOfGeometry( geometry ) {",
@@ -7413,9 +8033,48 @@ ${indent}}`,
7413
8033
  "geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;",
7414
8034
  "geometry._maxInstanceCount = requireVertexScalar( geometryAttribute.meshPerAttribute ) * requireVertexScalar( geometryAttribute.count );",
7415
8035
  );
8036
+ // needsUpdate walked the program's attributes by name on every draw: two
8037
+ // string-keyed lookups per attribute. A binding state is already specific
8038
+ // to its geometry and program, so the set it cached can only change when
8039
+ // the geometry's attribute set changes (setAttribute/deleteAttribute bump
8040
+ // __nativeAttributeStamp) or the object's instance attributes are replaced.
8041
+ // With both unchanged the walk's answer is the index comparison alone.
8042
+ transformed = replaceOne(
8043
+ transformed,
8044
+ "\t\t\tindex: /** @type {import('../../core/BufferAttribute.js').BufferAttribute|null} */ ( null )\n",
8045
+ "\t\t\tindex: /** @type {import('../../core/BufferAttribute.js').BufferAttribute|null} */ ( null ),\n" +
8046
+ "\t\t\tnativeAttributeStamp: - 1,\n" +
8047
+ "\t\t\t/** @type {import('../../core/InstancedBufferAttribute.js').InstancedBufferAttribute|null} */\n" +
8048
+ "\t\t\tnativeInstanceMatrix: null,\n" +
8049
+ "\t\t\t/** @type {import('../../core/InstancedBufferAttribute.js').InstancedBufferAttribute|null} */\n" +
8050
+ "\t\t\tnativeInstanceColor: null\n",
8051
+ fileName,
8052
+ "WebGLBindingStates attribute-set stamp fields",
8053
+ );
8054
+ transformed = replaceOne(
8055
+ transformed,
8056
+ "\tfunction needsUpdate( object, geometry, program, index ) {\n",
8057
+ "\tfunction needsUpdate( object, geometry, program, index ) {\n\n" +
8058
+ "\t\tconst instanced = object instanceof InstancedMesh ? object : null;\n" +
8059
+ "\t\tif ( currentState.nativeAttributeStamp === geometry.__nativeAttributeStamp &&\n" +
8060
+ "\t\t\tcurrentState.nativeInstanceMatrix === ( instanced !== null ? instanced.instanceMatrix : null ) &&\n" +
8061
+ "\t\t\tcurrentState.nativeInstanceColor === ( instanced !== null ? instanced.instanceColor : null ) ) return currentState.index !== index;\n",
8062
+ fileName,
8063
+ "WebGLBindingStates attribute-set fast path",
8064
+ );
8065
+ transformed = replaceOne(
8066
+ transformed,
8067
+ "\t\tcurrentState.attributes = cache;\n\t\tcurrentState.attributesNum = attributesNum;\n",
8068
+ "\t\tcurrentState.attributes = cache;\n\t\tcurrentState.attributesNum = attributesNum;\n" +
8069
+ "\t\tcurrentState.nativeAttributeStamp = geometry.__nativeAttributeStamp;\n" +
8070
+ "\t\tcurrentState.nativeInstanceMatrix = object instanceof InstancedMesh ? object.instanceMatrix : null;\n" +
8071
+ "\t\tcurrentState.nativeInstanceColor = object instanceof InstancedMesh ? object.instanceColor : null;\n",
8072
+ fileName,
8073
+ "WebGLBindingStates attribute-set stamp record",
8074
+ );
7416
8075
  }
7417
8076
  if (normalized.endsWith("/three/src/renderers/webgl/WebGLObjects.js")) {
7418
- transformed = `import { BufferGeometry } from '../../core/BufferGeometry.js';\n\n${transformed}`;
8077
+ transformed = `import { BufferGeometry } from '../../core/BufferGeometry.js';\nimport { Object3D } from '../../core/Object3D.js';\n\n${transformed}`;
7419
8078
  transformed = replaceOne(
7420
8079
  transformed,
7421
8080
  "\t\tconst instancedMesh = event.target;",
@@ -7460,13 +8119,52 @@ ${indent}}`,
7460
8119
  "updateMap.set( skeleton, frame )",
7461
8120
  "skeletonUpdateMap.set( skeleton, frame )",
7462
8121
  );
8122
+ // Every pass updates every drawn object, and each update read the geometry's
8123
+ // (and an instanced mesh's) frame out of a WeakMap. The maps stay the
8124
+ // authority; the geometry and object also carry the frame this instance
8125
+ // stored, tagged with its number, so the per-draw read is a field load.
8126
+ transformed = replaceOne(
8127
+ transformed,
8128
+ "function WebGLObjects( gl, geometries, attributes, bindingStates, info ) {\n",
8129
+ "let nextNativeObjectsOwner = 1;\n\nfunction WebGLObjects( gl, geometries, attributes, bindingStates, info ) {\n\n\tconst nativeOwner = nextNativeObjectsOwner ++;\n",
8130
+ fileName,
8131
+ "WebGLObjects front cache owner",
8132
+ );
7463
8133
  transformed = replaceOne(
7464
8134
  transformed,
7465
8135
  "\t\tif ( geometryUpdateMap.get( buffergeometry ) !== frame ) {",
7466
- "\t\tconst geometryFrame = geometryUpdateMap.get( buffergeometry );\n\n\t\tif ( geometryFrame !== frame ) {",
8136
+ "\t\tconst geometryFrame = buffergeometry.__nativeObjectsOwner === nativeOwner ? buffergeometry.__nativeObjectsFrame : geometryUpdateMap.get( buffergeometry );\n\n\t\tif ( geometryFrame !== frame ) {",
7467
8137
  fileName,
7468
8138
  "WebGLObjects stable geometry frame lookup",
7469
8139
  );
8140
+ transformed = replaceOne(
8141
+ transformed,
8142
+ "\t\t\tgeometryUpdateMap.set( buffergeometry, frame );",
8143
+ "\t\t\tgeometryUpdateMap.set( buffergeometry, frame );\n\t\t\tbuffergeometry.__nativeObjectsOwner = nativeOwner;\n\t\t\tbuffergeometry.__nativeObjectsFrame = frame;",
8144
+ fileName,
8145
+ "WebGLObjects geometry frame front cache",
8146
+ );
8147
+ transformed = replaceOne(
8148
+ transformed,
8149
+ "\t\t\tif ( instancedMeshUpdateMap.get( object ) !== frame ) {",
8150
+ "\t\t\tif ( ( object.__nativeObjectsOwner === nativeOwner ? object.__nativeObjectsFrame : instancedMeshUpdateMap.get( object ) ) !== frame ) {",
8151
+ fileName,
8152
+ "WebGLObjects instanced frame lookup",
8153
+ );
8154
+ transformed = replaceOne(
8155
+ transformed,
8156
+ "\t\t\t\tinstancedMeshUpdateMap.set( object, frame );",
8157
+ "\t\t\t\tinstancedMeshUpdateMap.set( object, frame );\n\t\t\t\tobject.__nativeObjectsOwner = nativeOwner;\n\t\t\t\tobject.__nativeObjectsFrame = frame;",
8158
+ fileName,
8159
+ "WebGLObjects instanced frame front cache",
8160
+ );
8161
+ transformed = replaceOne(
8162
+ transformed,
8163
+ "\t\tinstancedMeshUpdateMap.delete( instancedMesh );",
8164
+ "\t\tinstancedMeshUpdateMap.delete( instancedMesh );\n\n\t\tif ( instancedMesh instanceof Object3D && instancedMesh.__nativeObjectsOwner === nativeOwner ) instancedMesh.__nativeObjectsOwner = 0;",
8165
+ fileName,
8166
+ "WebGLObjects instanced front cache cleanup",
8167
+ );
7470
8168
  transformed = replaceOne(
7471
8169
  transformed,
7472
8170
  "\t\t\tgeometries.update( buffergeometry );",
@@ -7493,6 +8191,7 @@ ${indent}}`,
7493
8191
 
7494
8192
  \t\tbuffergeometry.removeEventListener( 'dispose', onBufferGeometryDispose );
7495
8193
  \t\tgeometryUpdateMap.delete( buffergeometry );
8194
+ \t\tif ( buffergeometry.__nativeObjectsOwner === nativeOwner ) buffergeometry.__nativeObjectsOwner = 0;
7496
8195
 
7497
8196
  \t}
7498
8197
 
@@ -7588,12 +8287,22 @@ class NativeWebGLPropertyBag {
7588
8287
  \t\tthis.needsLights = undefined;
7589
8288
  \t\t/** @type {number} */
7590
8289
  \t\tthis.lightsStateVersion = -1;
8290
+ \t\t/** @type {number} light structure (WebGLLights nativeStructure) the program was built for */
8291
+ \t\tthis.nativeLightsStructure = -1;
8292
+ \t\t/** @type {number} shadow caster count the program was built for */
8293
+ \t\tthis.nativeShadowCount = -1;
7591
8294
  \t\t/** @type {boolean|import('./WebGLRenderStates.js').NativeLightProbeGrid|null} */
7592
8295
  \t\tthis.lightProbeGrid = null;
7593
8296
  \t\t/** @type {import('../shaders/UniformsLib.js').NativeUniforms} */
7594
8297
  \t\tthis.uniforms = {};
7595
8298
  \t\t/** @type {Array<import('./WebGLUniforms.js').NativeUniform>|null} */
7596
8299
  \t\tthis.uniformsList = null;
8300
+ \t\t/** @type {Array<import('../shaders/UniformsLib.js').NativeUniformSlot|undefined>|undefined} The value cells of \`uniformsList\`, in order. */
8301
+ \t\tthis.nativeUniformSlots = undefined;
8302
+ \t\t/** @type {Array<import('./WebGLUniforms.js').NativeUniform>|null} The list \`nativeUniformSlots\` was resolved for. */
8303
+ \t\tthis.nativeUniformSlotsList = null;
8304
+ \t\t/** @type {NativeMaterialCells|undefined} */
8305
+ \t\tthis.nativeCells = undefined;
7597
8306
  \t\t/** @type {string} */
7598
8307
  \t\tthis.outputColorSpace = '';
7599
8308
  \t\t/** @type {boolean} */
@@ -7657,49 +8366,148 @@ class NativeWebGLPropertyBag {
7657
8366
 
7658
8367
  }
7659
8368
 
8369
+ /**
8370
+ * The value cells of one material uniforms object that WebGLRenderer touches on
8371
+ * every draw or every refresh, resolved once instead of by name each time.
8372
+ * Built for one uniforms object; a material given another gets a new one.
8373
+ */
8374
+ class NativeMaterialCells {
8375
+
8376
+ \t/** @param {import('../shaders/UniformsLib.js').NativeUniforms} uniforms */
8377
+ \tconstructor( uniforms ) {
8378
+
8379
+ \t\tthis.uniforms = uniforms;
8380
+ \t\t/** @type {import('../shaders/UniformsLib.js').NativeUniformSlot|undefined} */
8381
+ \t\tthis.envMapIntensity = uniforms.envMapIntensity;
8382
+ \t\t/** @type {import('../shaders/UniformsLib.js').NativeUniformSlot|undefined} */
8383
+ \t\tthis.dfgLUT = uniforms.dfgLUT;
8384
+ \t\t/** @type {Array<import('../shaders/UniformsLib.js').NativeUniformSlot>} The light cells markUniformsLightsNeedsUpdate flags. */
8385
+ \t\tthis.lights = [];
8386
+ \t\tfor ( const name of [ 'ambientLightColor', 'lightProbe', 'directionalLights', 'directionalLightShadows', 'pointLights', 'pointLightShadows', 'spotLights', 'spotLightShadows', 'rectAreaLights', 'hemisphereLights' ] ) {
8387
+
8388
+ \t\t\tconst cell = uniforms[ name ];
8389
+ \t\t\tif ( cell !== undefined ) this.lights.push( cell );
8390
+
8391
+ \t\t}
8392
+
8393
+ \t}
8394
+
8395
+ }
8396
+
8397
+ let nextNativePropertiesOwner = 1;
8398
+
7660
8399
  function WebGLProperties() {
7661
8400
 
7662
- \t/** @type {Map<string, NativeWebGLPropertyBag>} */
7663
- \tconst properties = new Map();
8401
+ \tlet nativeOwner = nextNativePropertiesOwner ++;
7664
8402
 
7665
- \t/** @param {WebGLPropertyOwner} object */
7666
- \tfunction propertyKey( object ) {
8403
+ \t// One map per owner kind, keyed by the owner's numeric id: a lookup per draw
8404
+ \t// hashes a number instead of building and hashing a 'kind:id' string. A render
8405
+ \t// target is keyed by its texture's id in its own map, as before.
8406
+ \t/** @type {Map<number, NativeWebGLPropertyBag>} */
8407
+ \tconst materials = new Map();
8408
+ \t/** @type {Map<number, NativeWebGLPropertyBag>} */
8409
+ \tconst sources = new Map();
8410
+ \t/** @type {Map<number, NativeWebGLPropertyBag>} */
8411
+ \tconst textures = new Map();
8412
+ \t/** @type {Map<number, NativeWebGLPropertyBag>} */
8413
+ \tconst targets = new Map();
8414
+
8415
+ \t/** @param {WebGLPropertyOwner} object @return {Map<number, NativeWebGLPropertyBag>} */
8416
+ \tfunction propertyMap( object ) {
8417
+
8418
+ \t\tif ( object instanceof Material ) return materials;
8419
+ \t\tif ( object instanceof Source ) return sources;
8420
+ \t\tif ( object instanceof Texture ) return textures;
8421
+ \t\treturn targets;
8422
+
8423
+ \t}
8424
+
8425
+ \t/** @param {WebGLPropertyOwner} object @return {number} */
8426
+ \tfunction propertyId( object ) {
8427
+
8428
+ \t\tif ( object instanceof Material ) return object.id;
8429
+ \t\tif ( object instanceof Source ) return object.id;
8430
+ \t\tif ( object instanceof Texture ) return object.id;
8431
+ \t\treturn object.texture.id;
8432
+
8433
+ \t}
8434
+
8435
+ \t/** @param {Map<number, NativeWebGLPropertyBag>} map @param {number} id @return {NativeWebGLPropertyBag} */
8436
+ \tfunction bagIn( map, id ) {
8437
+
8438
+ \t\tlet bag = map.get( id );
8439
+
8440
+ \t\tif ( bag === undefined ) {
8441
+
8442
+ \t\t\tbag = new NativeWebGLPropertyBag();
8443
+ \t\t\tmap.set( id, bag );
8444
+
8445
+ \t\t}
7667
8446
 
7668
- \t\tif ( object instanceof Material ) return 'material:' + object.id;
7669
- \t\tif ( object instanceof Source ) return 'source:' + object.id;
7670
- \t\tif ( object instanceof Texture ) return 'texture:' + object.id;
7671
- \t\treturn 'target:' + object.texture.id;
8447
+ \t\treturn bag;
7672
8448
 
7673
8449
  \t}
7674
8450
 
7675
8451
  \t/** @param {WebGLPropertyOwner} object */
7676
8452
  \tfunction has( object ) {
7677
8453
 
7678
- \t\treturn properties.has( propertyKey( object ) );
8454
+ \t\treturn propertyMap( object ).has( propertyId( object ) );
7679
8455
 
7680
8456
  \t}
7681
8457
 
7682
8458
  \t/** @param {WebGLPropertyOwner} object */
7683
8459
  \tfunction get( object ) {
7684
8460
 
7685
- \t\tconst key = propertyKey( object );
7686
- \t\tlet map = properties.get( key );
8461
+ \t\t// Materials, sources and textures are read on every draw (textures once per
8462
+ \t\t// bound sampler). Each also carries the bag this instance stored for it,
8463
+ \t\t// tagged with the instance's number, so the read is a field load; the maps
8464
+ \t\t// stay the authority and remove/dispose keep the fields in step.
8465
+ \t\tif ( object instanceof Material ) {
8466
+
8467
+ \t\t\tif ( object.__nativePropertiesOwner === nativeOwner && object.__nativeProperties !== undefined ) return object.__nativeProperties;
8468
+ \t\t\tconst bag = bagIn( materials, object.id );
8469
+ \t\t\tobject.__nativePropertiesOwner = nativeOwner;
8470
+ \t\t\tobject.__nativeProperties = bag;
8471
+ \t\t\treturn bag;
7687
8472
 
7688
- \t\tif ( map === undefined ) {
8473
+ \t\t}
8474
+ \t\tif ( object instanceof Source ) {
7689
8475
 
7690
- \t\t\tmap = new NativeWebGLPropertyBag();
7691
- \t\t\tproperties.set( key, map );
8476
+ \t\t\tif ( object.__nativePropertiesOwner === nativeOwner && object.__nativeProperties !== undefined ) return object.__nativeProperties;
8477
+ \t\t\tconst bag = bagIn( sources, object.id );
8478
+ \t\t\tobject.__nativePropertiesOwner = nativeOwner;
8479
+ \t\t\tobject.__nativeProperties = bag;
8480
+ \t\t\treturn bag;
7692
8481
 
7693
8482
  \t\t}
8483
+ \t\tif ( object instanceof Texture ) {
7694
8484
 
7695
- \t\treturn map;
8485
+ \t\t\tif ( object.__nativePropertiesOwner === nativeOwner && object.__nativeProperties !== undefined ) return object.__nativeProperties;
8486
+ \t\t\tconst bag = bagIn( textures, object.id );
8487
+ \t\t\tobject.__nativePropertiesOwner = nativeOwner;
8488
+ \t\t\tobject.__nativeProperties = bag;
8489
+ \t\t\treturn bag;
8490
+
8491
+ \t\t}
8492
+ \t\treturn bagIn( targets, object.texture.id );
7696
8493
 
7697
8494
  \t}
7698
8495
 
7699
8496
  \t/** @param {WebGLPropertyOwner} object */
7700
8497
  \tfunction remove( object ) {
7701
8498
 
7702
- \t\tproperties.delete( propertyKey( object ) );
8499
+ \t\tpropertyMap( object ).delete( propertyId( object ) );
8500
+
8501
+ \t\tif ( object instanceof Material || object instanceof Source || object instanceof Texture ) {
8502
+
8503
+ \t\t\tif ( object.__nativePropertiesOwner === nativeOwner ) {
8504
+
8505
+ \t\t\t\tobject.__nativePropertiesOwner = 0;
8506
+ \t\t\t\tobject.__nativeProperties = undefined;
8507
+
8508
+ \t\t\t}
8509
+
8510
+ \t\t}
7703
8511
 
7704
8512
  \t}
7705
8513
 
@@ -7716,7 +8524,12 @@ function WebGLProperties() {
7716
8524
 
7717
8525
  \tfunction dispose() {
7718
8526
 
7719
- \t\tproperties.clear();
8527
+ \t\tmaterials.clear();
8528
+ \t\tsources.clear();
8529
+ \t\ttextures.clear();
8530
+ \t\ttargets.clear();
8531
+ \t\t// Every field still tagged with the old number is now stale.
8532
+ \t\tnativeOwner = nextNativePropertiesOwner ++;
7720
8533
 
7721
8534
  \t}
7722
8535
 
@@ -7724,7 +8537,7 @@ function WebGLProperties() {
7724
8537
 
7725
8538
  }
7726
8539
 
7727
- export { WebGLProperties };
8540
+ export { WebGLProperties, NativeWebGLPropertyBag, NativeMaterialCells };
7728
8541
  `;
7729
8542
  }
7730
8543
  if (normalized.endsWith("/three/src/renderers/webgl/WebGLInfo.js")) {
@@ -8848,9 +9661,31 @@ function WebGLRenderState( extensions ) {`,
8848
9661
 
8849
9662
  \t\t\trenderTarget.depthTexture.image.width = renderTarget.width;
8850
9663
  \t\t\trenderTarget.depthTexture.image.height = renderTarget.height;`,
8851
- `\t\tconst textureProperties = properties.get( renderTarget.depthTexture );
9664
+ `\t\t// A render target disposed before it was set up again has no onRenderTargetDispose listener, so a
9665
+ \t\t// second dispose (RenderTarget.setSize) leaves its depth texture alone; and a material that samples
9666
+ \t\t// that depth texture meanwhile re-uploads it at its old image size (setSize does not resize
9667
+ \t\t// depthTexture.image). The size check below then only marks it needsUpdate, which never re-specifies
9668
+ \t\t// immutable texStorage2D storage: the target was set up with a depth attachment of the old size (a
9669
+ \t\t// multisample resolve past it removes the D3D12 device; WebGL reports FRAMEBUFFER_INCOMPLETE_DIMENSIONS).
9670
+ \t\t// Release that storage here so the upload below allocates it at the target's size.
9671
+ \t\tif ( ! isCube && properties.has( renderTarget.depthTexture ) ) {
9672
+
9673
+ \t\t\tconst allocated = properties.get( renderTarget.depthTexture );
9674
+ \t\t\tconst allocatedImage = requireTextureImageRecord( renderTarget.depthTexture.image );
9675
+
9676
+ \t\t\tif ( allocated.__webglTexture !== undefined && allocated.__webglTexture !== null &&
9677
+ \t\t\t\t( allocatedImage.width !== renderTarget.width || allocatedImage.height !== renderTarget.height ) ) {
9678
+
9679
+ \t\t\t\tdeallocateTexture( renderTarget.depthTexture );
9680
+
9681
+ \t\t\t}
9682
+
9683
+ \t\t}
9684
+
9685
+ \t\tconst textureProperties = properties.get( renderTarget.depthTexture );
8852
9686
  \t\ttextureProperties.__renderTarget = renderTarget;
8853
- \t\tconst depthTextureImage = requireTextureImageRecord( renderTarget.depthTexture.image );
9687
+ \t\t// A CubeDepthTexture's image is its six face records (one shared object); size it through face 0.
9688
+ \t\tconst depthTextureImage = isCube ? requireTextureImageRecords( renderTarget.depthTexture.image )[ 0 ] : requireTextureImageRecord( renderTarget.depthTexture.image );
8854
9689
 
8855
9690
  \t\t// upload an empty depth texture with framebuffer size
8856
9691
  \t\tif ( ! textureProperties.__webglTexture ||
@@ -8858,7 +9693,10 @@ function WebGLRenderState( extensions ) {`,
8858
9693
  \t\t\t\tdepthTextureImage.height !== renderTarget.height ) {
8859
9694
 
8860
9695
  \t\t\tdepthTextureImage.width = renderTarget.width;
8861
- \t\t\tdepthTextureImage.height = renderTarget.height;`,
9696
+ \t\t\tdepthTextureImage.height = renderTarget.height;
9697
+ \t\t\t// requireTextureImageRecord returns a copy, so a 2D depth texture's own image must be replaced
9698
+ \t\t\t// for the upload below to use the new size (upstream writes the image in place).
9699
+ \t\t\tif ( ! isCube ) renderTarget.depthTexture.image = { width: renderTarget.width, height: renderTarget.height, depth: depthTextureImage.depth !== undefined ? depthTextureImage.depth : 1 };`,
8862
9700
  fileName,
8863
9701
  "WebGLTextures depth image record narrowing",
8864
9702
  );
@@ -8939,7 +9777,9 @@ function WebGLRenderState( extensions ) {`,
8939
9777
  );
8940
9778
  transformed =
8941
9779
  transformed.slice(0, dimensionsStartIndex) +
8942
- "\tfunction getDimensions( image ) {\n\n\t\tconst imageRecord = requireTextureImageRecord( image );\n\t\t_imageDimensions.width = imageRecord.width;\n\t\t_imageDimensions.height = imageRecord.height;\n\n\t\treturn _imageDimensions;" +
9780
+ // The records reaching here already carry width and height; passing them through
9781
+ // requireTextureImageRecord recasts them into a nested union the emitter drops to undefined.
9782
+ "\tfunction getDimensions( image ) {\n\n\t\t_imageDimensions.width = image.width;\n\t\t_imageDimensions.height = image.height;\n\n\t\treturn _imageDimensions;" +
8943
9783
  transformed.slice(dimensionsEndIndex);
8944
9784
  transformed = replaceOne(
8945
9785
  transformed,
@@ -10216,6 +11056,11 @@ class Texture extends EventDispatcher {`,
10216
11056
  fileName,
10217
11057
  "native volume texture source image",
10218
11058
  );
11059
+ // Same JSDoc defect as DataTexture: a number documented as `boolean`. Without
11060
+ // the correction an array texture is not a Texture and cannot be a uniform.
11061
+ transformed = transformed
11062
+ .replace(dataTextureUnpackAlignmentSource, nativeDataTextureUnpackAlignmentSource)
11063
+ .replace(dataTextureOverlayUnpackAlignmentSource, nativeDataTextureOverlayUnpackAlignmentSource);
10219
11064
  }
10220
11065
  if (normalized.endsWith("/three/src/textures/ExternalTexture.js")) {
10221
11066
  transformed = replaceOne(