@geastack/native-webgl-angle 0.1.11 → 0.1.13

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
@@ -3,6 +3,7 @@ import {
3
3
  transformCanonicalInstanceHooks,
4
4
  } from "./geatsc-plugin-instancing.mjs";
5
5
  import fs from "node:fs";
6
+ import { nativeReadbackFunctions } from "./geatsc-plugin-readback.mjs";
6
7
  import { nativeUploadFunctions } from "./geatsc-plugin-uploads.mjs";
7
8
  import { fileURLToPath } from "node:url";
8
9
 
@@ -74,6 +75,8 @@ function nativeVoidMemberCallContract(
74
75
  }
75
76
 
76
77
  const hostFunctions = [
78
+ ['threeWebGLCopyTexImage2D','gea_three_webgl_copy_tex_image_2d','void','extern "C" void gea_three_webgl_copy_tex_image_2d(double,double,double,double,double,double,double,double);'],
79
+ ['threeWebGLCopyTexSubImage2D','gea_three_webgl_copy_tex_sub_image_2d','void','extern "C" void gea_three_webgl_copy_tex_sub_image_2d(double,double,double,double,double,double,double,double);'],
77
80
  [
78
81
  "threeWebGLVertexAttribDivisor",
79
82
  "gea_three_webgl_vertex_attrib_divisor",
@@ -93,6 +96,16 @@ const hostFunctions = [
93
96
  'extern "C" void gea_three_webgl_draw_arrays_instanced(double mode, double first, double count, double instanceCount);',
94
97
  ],
95
98
  ...nativeUploadFunctions,
99
+ ...nativeReadbackFunctions,
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);'],
108
+ ['threeWebGLRenderbufferStorageMultisample','gea_three_webgl_renderbuffer_storage_multisample','void','extern "C" void gea_three_webgl_renderbuffer_storage_multisample(double,double,double,double,double);'],
96
109
  [
97
110
  "threeNativeProfilePhase",
98
111
  "gea_three_native_profile_phase",
@@ -507,6 +520,18 @@ const hostFunctions = [
507
520
  "void",
508
521
  'extern "C" void gea_three_webgl_tex_storage_2d(double target, double levels, double internalFormat, double width, double height);',
509
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
+ ],
510
535
 
511
536
  [
512
537
  "threeWebGLCreateVertexArray",
@@ -2670,7 +2695,81 @@ class WebGLRenderer {
2670
2695
  }
2671
2696
  transformed = transformed.replaceAll(
2672
2697
  rendererUniformUploadSource,
2673
- "\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",
2674
2773
  );
2675
2774
  transformed = replaceOne(
2676
2775
  transformed,
@@ -2797,7 +2896,7 @@ class WebGLRenderer {
2797
2896
  );
2798
2897
  }
2799
2898
  for (const name of nativeUniformNamesEnabled
2800
- ? ["toneMappingExposure", "cameraPosition"]
2899
+ ? ["toneMappingExposure", "cameraPosition", "receiveShadow", "isOrthographic"]
2801
2900
  : []) {
2802
2901
  transformed = replaceOne(
2803
2902
  transformed,
@@ -2836,6 +2935,89 @@ class WebGLRenderer {
2836
2935
  `WebGLRenderer ${field} uniform`,
2837
2936
  );
2838
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
+ }
2839
3021
  // Three tests a tag and then reads fields off the untyped object; a
2840
3022
  // class test narrows the object to the class that declares those fields,
2841
3023
  // so each read has a type and each uniform value arrives already typed.
@@ -3017,6 +3199,27 @@ class WebGLRenderer {
3017
3199
  fileName,
3018
3200
  "WebGLRenderer native framebuffer selection",
3019
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
+ );
3020
3223
  transformed = replaceOne(
3021
3224
  transformed,
3022
3225
  "\t\tthis.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) {",
@@ -4841,12 +5044,14 @@ function requireWebGLStateTextureImageSource( value ) {
4841
5044
 
4842
5045
  \t\t\t} else {
4843
5046
 
4844
- \t\t\t\tgl.texImage2D( ${sourceArguments(9, 8, true)} );
5047
+ \t\t\t\tgl.texImage2D( ${sourceArguments(9, 8, true).replace("arguments[ 5 ]", "0")} );
4845
5048
 
4846
5049
  \t\t\t}`,
4847
5050
  fileName,
4848
5051
  "WebGLState native TexImageSource image expansion",
4849
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.
4850
5055
  // Preserve the actual argument count (and Function#length) while stating
4851
5056
  // the native upload protocol at the forwarding boundary. An arguments
4852
5057
  // object with no formal parameters otherwise erases every pixel source
@@ -5023,21 +5228,34 @@ function requireWebGLStateTextureImageSource( value ) {
5023
5228
  transformed = replaceOne(
5024
5229
  transformed,
5025
5230
  "\tlet currentBoundTextures = {};",
5026
- "\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}",
5027
5245
  fileName,
5028
5246
  "WebGLState bound texture table",
5029
5247
  );
5030
5248
  transformed = replaceOne(
5031
5249
  transformed,
5032
5250
  "\tfunction unbindTexture() {\n\n\t\tconst boundTexture = currentBoundTextures[ currentTextureSlot ];",
5033
- "\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 );",
5034
5252
  fileName,
5035
5253
  "WebGLState current texture slot narrowing",
5036
5254
  );
5037
5255
  transformed = replaceOne(
5038
5256
  transformed,
5039
5257
  "\t\tcurrentBoundTextures = {};",
5040
- "\t\tcurrentBoundTextures = /** @type {Record<number, { type: number|undefined, texture: import('@geastack/native-webgl-angle/nativeWebGL').NativeHandle|null|undefined }>} */ ( {} );",
5258
+ "\t\tcurrentBoundTextures = [];",
5041
5259
  fileName,
5042
5260
  "WebGLState bound texture reset",
5043
5261
  );
@@ -5107,7 +5325,7 @@ function requireWebGLStateTextureImageSource( value ) {
5107
5325
  transformed = replaceOne(
5108
5326
  transformed,
5109
5327
  "\t\tlet boundTexture = currentBoundTextures[ webglSlot ];",
5110
- "\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 );",
5111
5329
  fileName,
5112
5330
  "WebGLState required texture slot",
5113
5331
  );
@@ -5127,7 +5345,7 @@ function requireWebGLStateTextureImageSource( value ) {
5127
5345
  .slice(bindTextureStart, bindTextureEnd)
5128
5346
  .replace(
5129
5347
  "currentBoundTextures[ webglSlot ] = boundTexture;",
5130
- "currentBoundTextures[ textureSlot ] = boundTexture;",
5348
+ "setBoundTextureAt( textureSlot, boundTexture );",
5131
5349
  )
5132
5350
  .replaceAll(
5133
5351
  "currentTextureSlot !== webglSlot",
@@ -5148,10 +5366,34 @@ function requireWebGLStateTextureImageSource( value ) {
5148
5366
  transformed = replaceOne(
5149
5367
  transformed,
5150
5368
  "\t\t\tgl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );",
5151
- "\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 );",
5152
5370
  fileName,
5153
5371
  "WebGLState bound texture fallback",
5154
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
+ );
5155
5397
  transformed = replaceOne(
5156
5398
  transformed,
5157
5399
  "\t\tcurrentDrawbuffers = new WeakMap();",
@@ -5743,8 +5985,331 @@ ${transformed}`;
5743
5985
  );
5744
5986
  transformed = transformed.slice(0, start) + body + transformed.slice(end);
5745
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
+ );
5746
6292
  }
5747
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
+ );
5748
6313
  transformed = replaceOne(
5749
6314
  transformed,
5750
6315
  "\t\tuniforms: {\n\t\t\tshadow_pass:",
@@ -6151,13 +6716,9 @@ class WebGLBackgroundCubeMaterial extends ShaderMaterial {
6151
6716
  \tsetEnvironment( texture ) {
6152
6717
 
6153
6718
  \t\tthis.uniforms.envMap.value = texture;
6154
-
6155
- \t}
6156
-
6157
- \t/** @this {WebGLBackgroundCubeMaterial} */
6158
- \tget envMap() {
6159
-
6160
- \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;
6161
6722
 
6162
6723
  \t}
6163
6724
 
@@ -6169,13 +6730,7 @@ class WebGLBackgroundPlaneMaterial extends ShaderMaterial {
6169
6730
  \tsetMap( texture ) {
6170
6731
 
6171
6732
  \t\tthis.uniforms.t2D.value = texture;
6172
-
6173
- \t}
6174
-
6175
- \t/** @this {WebGLBackgroundPlaneMaterial} */
6176
- \tget map() {
6177
-
6178
- \t\treturn uniformTexture( this.uniforms.t2D );
6733
+ \t\tthis.map = texture;
6179
6734
 
6180
6735
  \t}
6181
6736
 
@@ -6545,23 +7100,31 @@ class WebGLBackgroundPlaneMaterial extends ShaderMaterial {
6545
7100
  transformed = transformUniformsUtils(transformed, fileName);
6546
7101
  }
6547
7102
  if (normalized.endsWith("/three/src/utils.js")) {
6548
- const probeStart = "function probeAsync( gl, sync, interval ) {";
6549
- const probeEnd = "\n}\n\n/**\n * Converts a projection matrix";
6550
- const probeStartIndex = transformed.indexOf(probeStart);
6551
- const probeEndIndex = transformed.indexOf(probeEnd, probeStartIndex);
6552
- if (
6553
- probeStartIndex < 0 ||
6554
- probeEndIndex < 0 ||
6555
- transformed.indexOf(probeStart, probeStartIndex + 1) >= 0
6556
- ) {
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) {
6557
7108
  throw new Error(
6558
- `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`,
6559
7110
  );
6560
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
+ }
6561
7122
  transformed =
6562
- transformed.slice(0, probeStartIndex) +
6563
- "function probeAsync( gl, sync, interval ) {\n\n\treturn Promise.resolve( undefined );" +
6564
- 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);
6565
7128
  transformed = replaceOne(
6566
7129
  transformed,
6567
7130
  "\t\t\tparams[ 0 ] += ' ' + stackTrace.getLocation();",
@@ -6627,13 +7190,6 @@ class WebGLBackgroundPlaneMaterial extends ShaderMaterial {
6627
7190
  `Three utils source at ${fileName} has ${warnCalls} console.warn calls; expected 2`,
6628
7191
  );
6629
7192
  transformed = transformed.replaceAll("console.warn(", "console.error(");
6630
- transformed = replaceOne(
6631
- transformed,
6632
- "@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.",
6633
- "@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.",
6634
- fileName,
6635
- "probeAsync resolved payload",
6636
- );
6637
7193
  }
6638
7194
  if (
6639
7195
  normalized.endsWith("/three/src/renderers/webgl/WebGLClipping.js") &&
@@ -7313,6 +7869,76 @@ ${indent}}`,
7313
7869
  fileName,
7314
7870
  "WebGLBindingStates wireframe cache write",
7315
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
+ }
7316
7942
  transformed = replaceOne(
7317
7943
  transformed,
7318
7944
  "\tfunction releaseStatesOfGeometry( geometry ) {",
@@ -7407,9 +8033,48 @@ ${indent}}`,
7407
8033
  "geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;",
7408
8034
  "geometry._maxInstanceCount = requireVertexScalar( geometryAttribute.meshPerAttribute ) * requireVertexScalar( geometryAttribute.count );",
7409
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
+ );
7410
8075
  }
7411
8076
  if (normalized.endsWith("/three/src/renderers/webgl/WebGLObjects.js")) {
7412
- 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}`;
7413
8078
  transformed = replaceOne(
7414
8079
  transformed,
7415
8080
  "\t\tconst instancedMesh = event.target;",
@@ -7454,13 +8119,52 @@ ${indent}}`,
7454
8119
  "updateMap.set( skeleton, frame )",
7455
8120
  "skeletonUpdateMap.set( skeleton, frame )",
7456
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
+ );
7457
8133
  transformed = replaceOne(
7458
8134
  transformed,
7459
8135
  "\t\tif ( geometryUpdateMap.get( buffergeometry ) !== frame ) {",
7460
- "\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 ) {",
7461
8137
  fileName,
7462
8138
  "WebGLObjects stable geometry frame lookup",
7463
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
+ );
7464
8168
  transformed = replaceOne(
7465
8169
  transformed,
7466
8170
  "\t\t\tgeometries.update( buffergeometry );",
@@ -7487,6 +8191,7 @@ ${indent}}`,
7487
8191
 
7488
8192
  \t\tbuffergeometry.removeEventListener( 'dispose', onBufferGeometryDispose );
7489
8193
  \t\tgeometryUpdateMap.delete( buffergeometry );
8194
+ \t\tif ( buffergeometry.__nativeObjectsOwner === nativeOwner ) buffergeometry.__nativeObjectsOwner = 0;
7490
8195
 
7491
8196
  \t}
7492
8197
 
@@ -7582,12 +8287,22 @@ class NativeWebGLPropertyBag {
7582
8287
  \t\tthis.needsLights = undefined;
7583
8288
  \t\t/** @type {number} */
7584
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;
7585
8294
  \t\t/** @type {boolean|import('./WebGLRenderStates.js').NativeLightProbeGrid|null} */
7586
8295
  \t\tthis.lightProbeGrid = null;
7587
8296
  \t\t/** @type {import('../shaders/UniformsLib.js').NativeUniforms} */
7588
8297
  \t\tthis.uniforms = {};
7589
8298
  \t\t/** @type {Array<import('./WebGLUniforms.js').NativeUniform>|null} */
7590
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;
7591
8306
  \t\t/** @type {string} */
7592
8307
  \t\tthis.outputColorSpace = '';
7593
8308
  \t\t/** @type {boolean} */
@@ -7651,49 +8366,148 @@ class NativeWebGLPropertyBag {
7651
8366
 
7652
8367
  }
7653
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
+
7654
8399
  function WebGLProperties() {
7655
8400
 
7656
- \t/** @type {Map<string, NativeWebGLPropertyBag>} */
7657
- \tconst properties = new Map();
8401
+ \tlet nativeOwner = nextNativePropertiesOwner ++;
7658
8402
 
7659
- \t/** @param {WebGLPropertyOwner} object */
7660
- \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}
7661
8446
 
7662
- \t\tif ( object instanceof Material ) return 'material:' + object.id;
7663
- \t\tif ( object instanceof Source ) return 'source:' + object.id;
7664
- \t\tif ( object instanceof Texture ) return 'texture:' + object.id;
7665
- \t\treturn 'target:' + object.texture.id;
8447
+ \t\treturn bag;
7666
8448
 
7667
8449
  \t}
7668
8450
 
7669
8451
  \t/** @param {WebGLPropertyOwner} object */
7670
8452
  \tfunction has( object ) {
7671
8453
 
7672
- \t\treturn properties.has( propertyKey( object ) );
8454
+ \t\treturn propertyMap( object ).has( propertyId( object ) );
7673
8455
 
7674
8456
  \t}
7675
8457
 
7676
8458
  \t/** @param {WebGLPropertyOwner} object */
7677
8459
  \tfunction get( object ) {
7678
8460
 
7679
- \t\tconst key = propertyKey( object );
7680
- \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;
7681
8472
 
7682
- \t\tif ( map === undefined ) {
8473
+ \t\t}
8474
+ \t\tif ( object instanceof Source ) {
7683
8475
 
7684
- \t\t\tmap = new NativeWebGLPropertyBag();
7685
- \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;
7686
8481
 
7687
8482
  \t\t}
8483
+ \t\tif ( object instanceof Texture ) {
7688
8484
 
7689
- \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 );
7690
8493
 
7691
8494
  \t}
7692
8495
 
7693
8496
  \t/** @param {WebGLPropertyOwner} object */
7694
8497
  \tfunction remove( object ) {
7695
8498
 
7696
- \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}
7697
8511
 
7698
8512
  \t}
7699
8513
 
@@ -7710,7 +8524,12 @@ function WebGLProperties() {
7710
8524
 
7711
8525
  \tfunction dispose() {
7712
8526
 
7713
- \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 ++;
7714
8533
 
7715
8534
  \t}
7716
8535
 
@@ -7718,7 +8537,7 @@ function WebGLProperties() {
7718
8537
 
7719
8538
  }
7720
8539
 
7721
- export { WebGLProperties };
8540
+ export { WebGLProperties, NativeWebGLPropertyBag, NativeMaterialCells };
7722
8541
  `;
7723
8542
  }
7724
8543
  if (normalized.endsWith("/three/src/renderers/webgl/WebGLInfo.js")) {
@@ -8842,9 +9661,31 @@ function WebGLRenderState( extensions ) {`,
8842
9661
 
8843
9662
  \t\t\trenderTarget.depthTexture.image.width = renderTarget.width;
8844
9663
  \t\t\trenderTarget.depthTexture.image.height = renderTarget.height;`,
8845
- `\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 );
8846
9686
  \t\ttextureProperties.__renderTarget = renderTarget;
8847
- \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 );
8848
9689
 
8849
9690
  \t\t// upload an empty depth texture with framebuffer size
8850
9691
  \t\tif ( ! textureProperties.__webglTexture ||
@@ -8852,7 +9693,10 @@ function WebGLRenderState( extensions ) {`,
8852
9693
  \t\t\t\tdepthTextureImage.height !== renderTarget.height ) {
8853
9694
 
8854
9695
  \t\t\tdepthTextureImage.width = renderTarget.width;
8855
- \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 };`,
8856
9700
  fileName,
8857
9701
  "WebGLTextures depth image record narrowing",
8858
9702
  );
@@ -8933,7 +9777,9 @@ function WebGLRenderState( extensions ) {`,
8933
9777
  );
8934
9778
  transformed =
8935
9779
  transformed.slice(0, dimensionsStartIndex) +
8936
- "\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;" +
8937
9783
  transformed.slice(dimensionsEndIndex);
8938
9784
  transformed = replaceOne(
8939
9785
  transformed,
@@ -10210,6 +11056,11 @@ class Texture extends EventDispatcher {`,
10210
11056
  fileName,
10211
11057
  "native volume texture source image",
10212
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);
10213
11064
  }
10214
11065
  if (normalized.endsWith("/three/src/textures/ExternalTexture.js")) {
10215
11066
  transformed = replaceOne(
@@ -10468,7 +11319,7 @@ export default {
10468
11319
  embeddedHostNoThrowFunctions,
10469
11320
  hostNativeArrayFunctions: hostFunctions
10470
11321
  .filter(([, , , declaration]) =>
10471
- declaration.includes("HostNumericArgument<"),
11322
+ declaration.includes("HostNumericArgument<") || declaration.includes("void* bytes"),
10472
11323
  )
10473
11324
  .map(([, cppName]) => cppName),
10474
11325
  hostArraySnapshotFunctions: hostFunctions