@driftengine/core 4.10.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.json +71 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +4 -3
  4. package/dist/index.js +1 -1
  5. package/dist/render/backend/webgl2/renderer.d.ts +99 -2
  6. package/dist/render/backend/webgl2/renderer.js +361 -42
  7. package/dist/render/backend/webgpu/buffers.d.ts +5 -0
  8. package/dist/render/backend/webgpu/buffers.js +98 -48
  9. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  10. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  11. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  12. package/dist/render/backend/webgpu/clothOps.js +78 -0
  13. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  14. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  15. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  16. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  17. package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
  18. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  19. package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
  20. package/dist/render/backend/webgpu/flatPass.js +2 -1
  21. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  22. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  23. package/dist/render/backend/webgpu/latePass.js +1 -0
  24. package/dist/render/backend/webgpu/motionPass.js +15 -6
  25. package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
  26. package/dist/render/backend/webgpu/pipelineCache.js +39 -1
  27. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  28. package/dist/render/backend/webgpu/renderer.d.ts +48 -0
  29. package/dist/render/backend/webgpu/renderer.js +207 -27
  30. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  31. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  32. package/dist/render/backend/webgpu/staticBundles.js +2 -1
  33. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  34. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  35. package/dist/render/globalMediumPass.d.ts +2 -0
  36. package/dist/render/globalMediumPass.js +1 -0
  37. package/dist/render/indexWidth.d.ts +17 -0
  38. package/dist/render/indexWidth.js +23 -0
  39. package/dist/render/lightMatrix.d.ts +11 -0
  40. package/dist/render/lightMatrix.js +14 -0
  41. package/dist/render/mesh.d.ts +9 -0
  42. package/dist/render/mesh.js +38 -14
  43. package/dist/render/renderQuality.d.ts +30 -0
  44. package/dist/render/renderQuality.js +5 -0
  45. package/dist/render/rendererHarness.d.ts +7 -0
  46. package/dist/render/rendererHarness.js +12 -0
  47. package/dist/render/shader.d.ts +15 -0
  48. package/dist/render/shader.js +41 -0
  49. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  50. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  51. package/dist/render/shaders/flat/glassTint.js +3 -0
  52. package/dist/render/shaders/flat/index.d.ts +15 -0
  53. package/dist/render/shaders/flat/index.js +15 -2
  54. package/dist/render/shaders/flat/layered.d.ts +19 -0
  55. package/dist/render/shaders/flat/layered.js +115 -0
  56. package/dist/render/shaders/flat/main.d.ts +1 -1
  57. package/dist/render/shaders/flat/main.js +43 -16
  58. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  59. package/dist/render/shaders/flat/mapReads.js +38 -0
  60. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  61. package/dist/render/shaders/flat/movingSun.js +46 -0
  62. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  63. package/dist/render/shaders/flat/normalMap.js +14 -3
  64. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  65. package/dist/render/shaders/flat/overlay.js +41 -19
  66. package/dist/render/shaders/flat/preamble.js +23 -5
  67. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  68. package/dist/render/shaders/flat/worldUv.js +113 -0
  69. package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
  70. package/dist/render/shaders/generated/flat.wgsl.js +791 -391
  71. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  72. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  73. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  74. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  75. package/dist/render/shaders/globalMedium.js +9 -1
  76. package/dist/render/shaders/lightVolume.js +9 -1
  77. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  78. package/dist/render/shaders/tangentFrame.js +16 -3
  79. package/dist/render/skinnedClothRun.d.ts +28 -0
  80. package/dist/render/skinnedClothRun.js +103 -35
  81. package/dist/render/surfaceLayers.d.ts +39 -0
  82. package/dist/render/surfaceLayers.js +26 -0
  83. package/dist/render/surfaceOverlay.d.ts +16 -3
  84. package/dist/render/surfaceOverlay.js +11 -6
  85. package/dist/render/surfaceProjection.d.ts +34 -0
  86. package/dist/render/surfaceProjection.js +30 -0
  87. package/dist/render/surfaceTexture.d.ts +30 -0
  88. package/dist/render/vertexPacking.d.ts +48 -0
  89. package/dist/render/vertexPacking.js +68 -0
  90. package/package.json +6 -6
  91. package/src/index.ts +4 -2
  92. package/src/render/backend/webgl2/renderer.ts +408 -55
  93. package/src/render/backend/webgpu/buffers.ts +153 -54
  94. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  95. package/src/render/backend/webgpu/clothOps.ts +86 -0
  96. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  97. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  98. package/src/render/backend/webgpu/drawCommand.ts +3 -0
  99. package/src/render/backend/webgpu/flatPass.ts +26 -28
  100. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  101. package/src/render/backend/webgpu/latePass.ts +1 -0
  102. package/src/render/backend/webgpu/motionPass.ts +18 -7
  103. package/src/render/backend/webgpu/pipelineCache.ts +46 -7
  104. package/src/render/backend/webgpu/renderer.ts +274 -48
  105. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  106. package/src/render/backend/webgpu/staticBundles.ts +2 -1
  107. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  108. package/src/render/globalMediumPass.ts +7 -0
  109. package/src/render/indexWidth.ts +27 -0
  110. package/src/render/lightMatrix.ts +15 -0
  111. package/src/render/mesh.ts +66 -14
  112. package/src/render/renderQuality.ts +38 -0
  113. package/src/render/rendererHarness.ts +15 -0
  114. package/src/render/shader.ts +59 -0
  115. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  116. package/src/render/shaders/flat/glassTint.ts +7 -1
  117. package/src/render/shaders/flat/index.ts +30 -2
  118. package/src/render/shaders/flat/layered.ts +115 -0
  119. package/src/render/shaders/flat/main.ts +43 -16
  120. package/src/render/shaders/flat/mapReads.ts +38 -0
  121. package/src/render/shaders/flat/movingSun.ts +46 -0
  122. package/src/render/shaders/flat/normalMap.ts +14 -3
  123. package/src/render/shaders/flat/overlay.ts +41 -19
  124. package/src/render/shaders/flat/preamble.ts +23 -5
  125. package/src/render/shaders/flat/worldUv.ts +113 -0
  126. package/src/render/shaders/generated/flat.wgsl.ts +791 -391
  127. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  128. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  129. package/src/render/shaders/globalMedium.ts +9 -1
  130. package/src/render/shaders/lightVolume.ts +9 -1
  131. package/src/render/shaders/tangentFrame.ts +16 -3
  132. package/src/render/skinnedClothRun.ts +138 -38
  133. package/src/render/surfaceLayers.ts +57 -0
  134. package/src/render/surfaceOverlay.ts +24 -7
  135. package/src/render/surfaceProjection.ts +57 -0
  136. package/src/render/surfaceTexture.ts +30 -0
  137. package/src/render/vertexPacking.ts +109 -0
  138. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  139. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  140. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -33,9 +33,12 @@ import { clamp } from '../../../math/scalar.js';
33
33
  import { attachContextLoss } from '../../contextLoss.js';
34
34
  import { isWeakGpuFamily, webgl2RendererName } from '../../gpuCapability.js';
35
35
  import { GpuTimer } from '../../gpuTimer.js';
36
- import { compileProgram, uniformLocations } from '../../shader.js';
36
+ import { compileProgram, finishProgram, startProgram, uniformLocations } from '../../shader.js';
37
37
  import { countUniformVectors, FULL_LIGHT_BUDGET, nextLightBudget, planLightBudget, } from '../../uniformVectorBudget.js';
38
38
  import { OVERLAY_FLOATS, overlayLays, packSurfaceOverlay, } from '../../surfaceOverlay.js';
39
+ import { PROJECTION_FLOATS, packSurfaceProjection, projects } from '../../surfaceProjection.js';
40
+ import { orthographicDepthSpan } from '../../lightMatrix.js';
41
+ import { LAYER_FLOATS, packSurfaceLayers } from '../../surfaceLayers.js';
39
42
  import { FILM_FRAG, FILM_VERT } from '../../shaders/film.js';
40
43
  import { SceneTarget } from '../../sceneTarget.js';
41
44
  import { FILM_LOOK_WITHOUT_COMPOSITE } from '../../shaders/filmLook.js';
@@ -291,6 +294,18 @@ const NO_CLIP_PLANE = new Float32Array(4);
291
294
  const IDENTITY_CLIP = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
292
295
  /** Standing still, for a caller that asked for dust and not for wind. */
293
296
  const NO_DRIFT = [0, 0, 0];
297
+ /** Whether two matrices hold the same sixteen numbers. */
298
+ function sameMatrix(a, b) {
299
+ for (let k = 0; k < 16; k++)
300
+ if (a[k] !== b[k])
301
+ return false;
302
+ return true;
303
+ }
304
+ /** How often `ready` asks the driver whether a program is done, and how many times before waiting. */
305
+ const READY_POLL_MS = 4;
306
+ const READY_MAX_POLLS = 500;
307
+ /** The halves a skin draws under the screen-space blur, each a program of its own. */
308
+ const SPLIT_SKIN_HALVES = ['scene', 'diffuse', 'albedo'];
294
309
  export class WebGL2Renderer {
295
310
  /**
296
311
  * Private on purpose. The GL context never leaves this directory, so a
@@ -568,6 +583,23 @@ export class WebGL2Renderer {
568
583
  * compiled the first time a draw asks for one. See `modelProgram`.
569
584
  */
570
585
  modelFlat = new Map();
586
+ /**
587
+ * Model programs still compiling, where draws skip a compile (`pipelineCompile: 'skip'`) and the
588
+ * context offers `KHR_parallel_shader_compile`; `parallelCompile` is its completion query then,
589
+ * and null where draws wait for a program here instead. See `modelProgram`.
590
+ */
591
+ modelPending = new Map();
592
+ /** What `drawVariantOf` answers, rewritten each draw rather than made. */
593
+ drawVariant = {
594
+ name: 'plain',
595
+ skinned: false,
596
+ morphed: false,
597
+ cloth: null,
598
+ clothed: undefined,
599
+ program: null,
600
+ uniforms: {},
601
+ };
602
+ parallelCompile;
571
603
  /** The last palette chosen, which an eye's axis is turned by. See `setSkinPalette`. */
572
604
  paletteData = null;
573
605
  emptyTexture2D;
@@ -630,7 +662,7 @@ export class WebGL2Renderer {
630
662
  oitReplaying = false;
631
663
  /** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
632
664
  surfaceScene = new Float32Array(4);
633
- /** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
665
+ /** The three lanes of `uWriteMode`, kept here because a `vec3` is written whole. */
634
666
  oitWeight = 0;
635
667
  /**
636
668
  * How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
@@ -638,6 +670,7 @@ export class WebGL2Renderer {
638
670
  */
639
671
  surfaceFog = FOG_RECEDE;
640
672
  ditherFade = 0;
673
+ ditherOpacity = 0;
641
674
  /** Said once, not per frame, where the context cannot give the float target the sum needs. */
642
675
  oitRefused = false;
643
676
  /** Said once for the whole renderer. See `refuseOitMultisampled`. */
@@ -905,6 +938,28 @@ export class WebGL2Renderer {
905
938
  overlayTexture = null;
906
939
  /** Whether this device's lit stage has room for the overlay's vectors: asked once. */
907
940
  overlayRoom = null;
941
+ /**
942
+ * The matrix the sun's dynamic shadow pass was last drawn with, uncorrected, and whether one has
943
+ * been: what the moving layer is read through. See `movingLightFor`.
944
+ */
945
+ movingLightViewProj = new Float32Array(16);
946
+ movingLightKnown = false;
947
+ /** Whether the lit stage has room for the moving layer's matrix, and whether its refusal was said. */
948
+ movingRoom = null;
949
+ warnedMovingRoom = false;
950
+ /** The same answer for a material's projection, and whether its refusal was said. */
951
+ projectionRoom = null;
952
+ warnedProjectionRoom = false;
953
+ /** `uWorldUv`, the current material's projection packed. See `surfaceProjection.ts`. */
954
+ projection = new Float32Array(PROJECTION_FLOATS);
955
+ /** The current material's `diffuseTransmission`, `uGlassTint.w`, 0 to 1. */
956
+ diffuseTransmission = 0;
957
+ /** `uLayers`, the current material's layers packed. See `surfaceLayers.ts`. */
958
+ layerFloats = new Float32Array(LAYER_FLOATS);
959
+ /** Whether the lit stage has room for layers' two vectors, and which refusals were said. */
960
+ layersRoom = null;
961
+ warnedLayersRoom = false;
962
+ warnedLayersModelMap = false;
908
963
  warnedOverlayRoom = false;
909
964
  warnedOverlayBlocks = false;
910
965
  /** Whether a probe is being baked, so the passes that would recurse into one can tell. */
@@ -2109,6 +2164,14 @@ export class WebGL2Renderer {
2109
2164
  * Measured on an RX 9070 XT through ANGLE/Vulkan: offered. `scripts/depth-survey.mjs` is what
2110
2165
  * asks, and it also reports what WebGPU can do.
2111
2166
  */
2167
+ /*
2168
+ * Where draws skip a compile, the query that says a program is done without waiting for it;
2169
+ * where the context has none, a program is waited for as it always was. See `modelProgram`.
2170
+ */
2171
+ this.parallelCompile =
2172
+ this.quality.pipelineCompile === 'skip'
2173
+ ? gl.getExtension('KHR_parallel_shader_compile')
2174
+ : null;
2112
2175
  this.clipControl = gl.getExtension('EXT_clip_control');
2113
2176
  if (REVERSED_DEPTH && this.clipControl !== null) {
2114
2177
  this.clipControl.clipControlEXT(this.clipControl.LOWER_LEFT_EXT, this.clipControl.ZERO_TO_ONE_EXT);
@@ -2353,7 +2416,33 @@ export class WebGL2Renderer {
2353
2416
  * this before its first frame gets the right behaviour on both without asking which backend
2354
2417
  * it has, which is the contract `RendererApi` exists to keep.
2355
2418
  */
2356
- async ready() { }
2419
+ /**
2420
+ * Resolve once every program a draw has started compiling has finished, adopting each: what a
2421
+ * loading screen awaits after drawing what its scene will use, where draws skip a compile. See
2422
+ * `RenderQuality.pipelineCompile`; nothing is started anywhere else here, so otherwise it is now.
2423
+ *
2424
+ * **It waits without holding the thread**, as the other backend's does: it asks the driver
2425
+ * whether each is done, which does not wait, and yields between asks. A driver that never says
2426
+ * done is asked the way that waits after `READY_MAX_POLLS`, so this cannot hang a loading screen.
2427
+ */
2428
+ async ready() {
2429
+ for (let polls = 0; this.modelPending.size > 0 && !this.contextLost; polls++) {
2430
+ for (const [key, started] of this.modelPending) {
2431
+ const done = polls >= READY_MAX_POLLS ||
2432
+ this.parallelCompile === null ||
2433
+ this.gl.getProgramParameter(started.program, this.parallelCompile.COMPLETION_STATUS_KHR) === true;
2434
+ if (!done)
2435
+ continue;
2436
+ this.modelPending.delete(key);
2437
+ this.adoptModel(key, finishProgram(this.gl, started), started.label);
2438
+ }
2439
+ this.useFlatProgram();
2440
+ if (this.modelPending.size > 0) {
2441
+ // platform: browser default — a yield between two asks of the driver, in a browser module
2442
+ await new Promise((resolve) => setTimeout(resolve, READY_POLL_MS));
2443
+ }
2444
+ }
2445
+ }
2357
2446
  dispose(options = {}) {
2358
2447
  if (this.disposed)
2359
2448
  return;
@@ -2808,7 +2897,30 @@ export class WebGL2Renderer {
2808
2897
  return held;
2809
2898
  const { gl } = this;
2810
2899
  const label = `flat.${variant}.${kind}.${half}`;
2811
- const program = compileProgram(gl, modelVertexOf(variant, kind), skinHalfBound(modelBound(this.flatFragSource, kind), half), label);
2900
+ const vertex = modelVertexOf(variant, kind);
2901
+ const fragment = skinHalfBound(modelBound(this.flatFragSource, kind), half);
2902
+ /*
2903
+ * **Where draws skip a compile, none until the driver says asking will not wait**: the program
2904
+ * is started at the first draw that wants it and that draw, and every one before it is ready,
2905
+ * is left out. See `RenderQuality.pipelineCompile`.
2906
+ */
2907
+ if (this.parallelCompile !== null) {
2908
+ const started = this.modelPending.get(key);
2909
+ if (started === undefined) {
2910
+ this.modelPending.set(key, startProgram(gl, vertex, fragment, label));
2911
+ return null;
2912
+ }
2913
+ if (gl.getProgramParameter(started.program, this.parallelCompile.COMPLETION_STATUS_KHR) !== true) {
2914
+ return null;
2915
+ }
2916
+ this.modelPending.delete(key);
2917
+ return this.adoptModel(key, finishProgram(gl, started), label);
2918
+ }
2919
+ return this.adoptModel(key, compileProgram(gl, vertex, fragment, label), label);
2920
+ }
2921
+ /** A model program compiled: kept, written to by every material setter, and fed the pass. */
2922
+ adoptModel(key, program, label) {
2923
+ const { gl } = this;
2812
2924
  const made = { program, uniforms: uniformLocations(gl, program, label) };
2813
2925
  this.modelFlat.set(key, made);
2814
2926
  this.flatTargets.push(made);
@@ -3085,10 +3197,17 @@ export class WebGL2Renderer {
3085
3197
  * and read neither — one decision for every program, as `setMaterial` says.
3086
3198
  */
3087
3199
  const modelMap = material?.modelMap ?? null;
3200
+ /* A layered material's mask goes where a model's map would: see `SurfaceMaterial.layers`. */
3201
+ const layers = this.layersOf(material);
3202
+ const boundMap = modelMap ?? layers?.mask ?? null;
3088
3203
  gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
3089
3204
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
3090
- if (modelMap !== null)
3091
- modelMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
3205
+ if (boundMap !== null)
3206
+ boundMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
3207
+ packSurfaceLayers(layers, this.layerFloats);
3208
+ if (layers !== null)
3209
+ this.askLit('LAYERED');
3210
+ gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
3092
3211
  gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
3093
3212
  packModel(material?.model ?? null, modelMap !== null, this.modelParams, material?.physicalSpecular === true);
3094
3213
  /* The first physical highlight asked for is when the lit programs start carrying one. */
@@ -3097,6 +3216,16 @@ export class WebGL2Renderer {
3097
3216
  gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
3098
3217
  /* The draw's overlay, which a program born mid-pass must wear too: see `setSurfaceOverlay`. */
3099
3218
  gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
3219
+ gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
3220
+ /* How much light from behind the surface lets through, in the glass tint's spare lane. */
3221
+ this.diffuseTransmission = Math.min(Math.max(material?.diffuseTransmission ?? 0, 0), 1) || 0;
3222
+ gl.uniform4f(u['uGlassTint'] ?? null, 1, 1, 1, this.diffuseTransmission);
3223
+ /* Maps placed by the world, where the material asks and the lit stage has room: worldUv.ts. */
3224
+ const projection = material?.projection ?? null;
3225
+ packSurfaceProjection(this.projectionFits(projection) ? projection : null, this.projection);
3226
+ if (this.projection[0] > 0)
3227
+ this.askLit('WORLD_UVS');
3228
+ gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
3100
3229
  /* The albedo's effects table, or the stand-in the shader reads as none. Before the early
3101
3230
  return, so a material with no albedo still leaves a complete texture on the unit. */
3102
3231
  if (albedo === null) {
@@ -3261,14 +3390,98 @@ export class WebGL2Renderer {
3261
3390
  }
3262
3391
  /** What the lit shader at this renderer's budget leaves of the limit, with no overlay. */
3263
3392
  overlayRoomLeft() {
3264
- return (this.fragmentVectorLimit() - countUniformVectors(this.flatFragAt(this.lightBudget, false)));
3393
+ return (this.fragmentVectorLimit() -
3394
+ countUniformVectors(this.flatFragAt(this.lightBudget, { SURFACE_OVERLAY: false })));
3265
3395
  }
3266
3396
  /** Whether the lit shader with the overlay compiled in fits this GPU: counted once. */
3267
3397
  overlayFits() {
3268
- this.overlayRoom ??=
3269
- countUniformVectors(this.flatFragAt(this.lightBudget, true)) <= this.fragmentVectorLimit();
3398
+ this.overlayRoom ??= this.switchFits('SURFACE_OVERLAY');
3270
3399
  return this.overlayRoom;
3271
3400
  }
3401
+ /**
3402
+ * Whether a material's projection can be drawn: none asked, or the lit stage has the room for its
3403
+ * one vector. **Refused, said once, where it has not**, as the overlay is, and the material's
3404
+ * maps are then laid by the mesh's coordinates, as they were before it existed.
3405
+ */
3406
+ projectionFits(projection) {
3407
+ if (!projects(projection))
3408
+ return true;
3409
+ this.projectionRoom ??= this.switchFits('WORLD_UVS');
3410
+ if (!this.projectionRoom && !this.warnedProjectionRoom) {
3411
+ this.warnedProjectionRoom = true;
3412
+ console.warn("Renderer: a material's projection is refused on this device. It takes one fragment " +
3413
+ 'uniform vector and the lit shader already uses all ' +
3414
+ `${this.fragmentVectorLimit()} this GPU offers; its maps follow the mesh's coordinates. ` +
3415
+ 'A lower `maxLights` makes the room, or WebGPU has it.');
3416
+ }
3417
+ return this.projectionRoom;
3418
+ }
3419
+ /**
3420
+ * What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
3421
+ * where the lit stage carries one (`MOVING_SUN`); the environment's otherwise, which every caller
3422
+ * drawing both layers with one matrix hands it anyway.
3423
+ */
3424
+ movingLightFor(env) {
3425
+ return this.movingLightKnown && this.litOn.MOVING_SUN
3426
+ ? this.movingLightViewProj
3427
+ : env.lightViewProj;
3428
+ }
3429
+ /**
3430
+ * Turn the moving layer's own matrix on the first time a frame draws that layer with one the
3431
+ * environment's does not share. **Refused, said once, where the lit stage has no room for its five
3432
+ * vectors**: the layer is then read through the environment's matrix, so its shadows land where
3433
+ * that matrix puts them, and a caller there draws both layers with one.
3434
+ */
3435
+ askMovingSun(env) {
3436
+ if (this.litWanted.MOVING_SUN || !this.movingLightKnown)
3437
+ return;
3438
+ if (sameMatrix(this.movingLightViewProj, env.lightViewProj))
3439
+ return;
3440
+ this.movingRoom ??= this.switchFits('MOVING_SUN');
3441
+ if (this.movingRoom) {
3442
+ this.askLit('MOVING_SUN');
3443
+ return;
3444
+ }
3445
+ if (this.warnedMovingRoom)
3446
+ return;
3447
+ this.warnedMovingRoom = true;
3448
+ console.warn("Renderer: the sun's moving layer cannot be read through a matrix of its own on this " +
3449
+ 'device; it takes five fragment uniform vectors the lit shader does not have. It is read ' +
3450
+ "through the environment's `lightViewProj`, so draw the dynamic shadow pass with that " +
3451
+ 'matrix here, or lower `maxLights` to make the room. WebGPU has it.');
3452
+ }
3453
+ /**
3454
+ * A material's layers where they can be drawn: none asked, a model's map in the mask's place, or
3455
+ * no room for their two vectors each leave one layer, the last two said once each.
3456
+ */
3457
+ layersOf(material) {
3458
+ const layers = material?.layers ?? null;
3459
+ if (layers === null || layers.repeats.length === 0)
3460
+ return null;
3461
+ if ((material?.modelMap ?? null) !== null) {
3462
+ if (!this.warnedLayersModelMap) {
3463
+ this.warnedLayersModelMap = true;
3464
+ console.warn("Renderer: a material's layers are drawn as one: its mask is read where a model's map " +
3465
+ 'goes, and this material carries a `modelMap` too.');
3466
+ }
3467
+ return null;
3468
+ }
3469
+ this.layersRoom ??= this.switchFits('LAYERED');
3470
+ if (this.layersRoom)
3471
+ return layers;
3472
+ if (!this.warnedLayersRoom) {
3473
+ this.warnedLayersRoom = true;
3474
+ console.warn("Renderer: a material's layers are refused on this device. They take two fragment uniform " +
3475
+ 'vectors the lit shader does not have; the material is drawn as its base layer. A lower ' +
3476
+ '`maxLights` makes the room, or WebGPU has it.');
3477
+ }
3478
+ return null;
3479
+ }
3480
+ /** Whether the lit shader with `feature` compiled in fits this GPU's fragment uniform vectors. */
3481
+ switchFits(feature) {
3482
+ const asIf = { [feature]: true };
3483
+ return (countUniformVectors(this.flatFragAt(this.lightBudget, asIf)) <= this.fragmentVectorLimit());
3484
+ }
3272
3485
  /**
3273
3486
  * How bright the environment the surfaces drawn next reflect is. 1 is the default and identity.
3274
3487
  *
@@ -3410,9 +3623,37 @@ export class WebGL2Renderer {
3410
3623
  }
3411
3624
  this.useFlatProgram();
3412
3625
  }
3413
- /** `uWriteMode`: the order-independent weight and the dither, which share one uniform row. */
3626
+ /**
3627
+ * Whether the draws that follow spend their own opacity on a screen door rather than on blending:
3628
+ * an instance's `MeshInstances.alphas`, or a vertex's channel alpha, keeps that share of the
3629
+ * pixels `setDitherFade` would and draws the rest not at all. So an opaque or cut-out batch fades
3630
+ * one instance at a time, with no sorting and no blend, and an instance at 1 is drawn whole.
3631
+ *
3632
+ * Off is what `bindMeshPass` restores. It shares the fade's pattern, so a crossfade at `t` and
3633
+ * `-t` of an instance at opacity `a` covers that instance's share once, without holes. Material
3634
+ * state like `setDitherFade`; instanced draws read it, and shadow casters do not.
3635
+ *
3636
+ * What it gives up: a fade reads as grain while it lasts, which a temporal resolve smooths and a
3637
+ * plain frame does not; a blended draw that asks spends its opacity here and blends none of it;
3638
+ * and a lightmapped batch carries regions where the opacities would be, so it has none to fade.
3639
+ */
3640
+ setDitherOpacity(on) {
3641
+ if (this.contextLost)
3642
+ return;
3643
+ this.materials.dirty();
3644
+ this.ditherOpacity = on ? 1 : 0;
3645
+ for (const target of this.flatTargets) {
3646
+ this.gl.useProgram(target.program);
3647
+ this.writeMode(target.uniforms);
3648
+ }
3649
+ this.useFlatProgram();
3650
+ }
3651
+ /**
3652
+ * `uWriteMode`: the order-independent weight, the fade and whether the opacity is dithered, which
3653
+ * share one uniform row.
3654
+ */
3414
3655
  writeMode(u) {
3415
- this.gl.uniform2f(u['uWriteMode'] ?? null, this.oitWeight, this.ditherFade);
3656
+ this.gl.uniform3f(u['uWriteMode'] ?? null, this.oitWeight, this.ditherFade, this.ditherOpacity);
3416
3657
  }
3417
3658
  /**
3418
3659
  * How strong the microscopic relief on the surfaces drawn next is, and how coarse.
@@ -3708,6 +3949,9 @@ export class WebGL2Renderer {
3708
3949
  const modelled = modelKind === null
3709
3950
  ? null
3710
3951
  : this.modelProgram(clip === null ? 'instanced' : 'animated', modelKind);
3952
+ /* Its model's program still compiling, where draws skip a compile: left out until it lands. */
3953
+ if (modelKind !== null && modelled === null)
3954
+ return;
3711
3955
  const program = modelled?.program ?? (clip === null ? this.flatInstancedProgram : this.flatAnimatedProgram);
3712
3956
  const u = modelled?.uniforms ??
3713
3957
  (clip === null ? this.flatInstancedUniforms : this.flatAnimatedUniforms);
@@ -3861,7 +4105,8 @@ export class WebGL2Renderer {
3861
4105
  /* w: this framebuffer's first row is the bottom, so up the screen is up the rows. */
3862
4106
  const tint = options.refractTint ?? WHITE_TINT;
3863
4107
  gl.uniform4f(u['uRefractTint'] ?? null, tint[0] ?? 1, tint[1] ?? 1, tint[2] ?? 1, 1);
3864
- gl.uniform3fv(u['uGlassTint'] ?? null, glass.tint);
4108
+ /* The pane's colour; w is the material's light through from behind, which glass never reads. */
4109
+ gl.uniform4f(u['uGlassTint'] ?? null, glass.tint[0] ?? 1, glass.tint[1] ?? 1, glass.tint[2] ?? 1, this.diffuseTransmission);
3865
4110
  return true;
3866
4111
  }
3867
4112
  /** Put back what `bindSeeThrough` bound: the binding outlives its frame (the 2026-08-27 rule). */
@@ -4749,6 +4994,11 @@ export class WebGL2Renderer {
4749
4994
  this.shadowPassActive = true;
4750
4995
  this.activeSunLayer = layer;
4751
4996
  this.glassCasters.clear();
4997
+ /* The moving layer is read through the matrix it is drawn with: see `movingLightFor`. */
4998
+ if (layer === 'dynamic') {
4999
+ this.movingLightViewProj.set(lightViewProj);
5000
+ this.movingLightKnown = true;
5001
+ }
4752
5002
  /* The static layer opens first, so a frame's peel counts from here. See `peelFilled`. */
4753
5003
  if (layer === 'static')
4754
5004
  this.peelFilled = false;
@@ -5147,7 +5397,9 @@ export class WebGL2Renderer {
5147
5397
  * throws a sentence with the device's numbers in it rather than a bare driver string.
5148
5398
  */
5149
5399
  /** The lit fragment source this renderer's profile compiles, at a light budget. */
5150
- flatFragAt(budget, overlay = this.litOn.SURFACE_OVERLAY) {
5400
+ flatFragAt(budget,
5401
+ /* A switch counted as it would be, for asking whether it fits: see `switchFits`. */
5402
+ asIf = {}) {
5151
5403
  return flatFrag({
5152
5404
  pointShadows: this.quality.pointShadows,
5153
5405
  directionalShadows: this.quality.directionalShadows,
@@ -5160,8 +5412,11 @@ export class WebGL2Renderer {
5160
5412
  surfaceEffects: this.litOn.SURFACE_EFFECTS,
5161
5413
  driftLight: this.litOn.DRIFT_LIGHT,
5162
5414
  physicalSpecular: this.litOn.PHYSICAL_SPECULAR,
5163
- surfaceOverlay: overlay,
5415
+ surfaceOverlay: asIf.SURFACE_OVERLAY ?? this.litOn.SURFACE_OVERLAY,
5164
5416
  reflectionMaps: this.litOn.REFLECTION_MAPS,
5417
+ worldUvs: asIf.WORLD_UVS ?? this.litOn.WORLD_UVS,
5418
+ movingSun: asIf.MOVING_SUN ?? this.litOn.MOVING_SUN,
5419
+ layered: asIf.LAYERED ?? this.litOn.LAYERED,
5165
5420
  maxLights: budget.maxLights,
5166
5421
  maxAreaLights: budget.maxAreaLights,
5167
5422
  });
@@ -5221,6 +5476,10 @@ export class WebGL2Renderer {
5221
5476
  gl.deleteProgram(entry.program);
5222
5477
  this.clothFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
5223
5478
  }
5479
+ /* Started against the source before this switch: begun again at the next draw that asks. */
5480
+ for (const started of this.modelPending.values())
5481
+ gl.deleteProgram(started.program);
5482
+ this.modelPending.clear();
5224
5483
  for (const [key, entry] of this.modelFlat) {
5225
5484
  const [variant, kind, half] = key.split('|');
5226
5485
  const label = `flat.${variant}.${kind}.${half}`;
@@ -6760,6 +7019,7 @@ export class WebGL2Renderer {
6760
7019
  ambient: env?.ambient ?? [0, 0, 0],
6761
7020
  sunShadow,
6762
7021
  lightViewProj: env?.lightViewProj ?? IDENTITY_MAT4,
7022
+ movingLightViewProj: env === null ? IDENTITY_MAT4 : this.movingLightFor(env),
6763
7023
  /* The placeholder rather than null where a map is merely absent, for the reason
6764
7024
  `emptyTexture.ts` gives. It is black, which as a depth is an occluder at the light,
6765
7025
  so it is safe only because `sunShadow` is 0 whenever these maps do not exist. */
@@ -6993,6 +7253,12 @@ export class WebGL2Renderer {
6993
7253
  this.currentSurfaceTexture = null;
6994
7254
  /* Nor an overlay: the atlas's unit is given the empty texture with the refraction copy's. */
6995
7255
  this.overlayFloats.fill(0);
7256
+ /* Nor a projection: maps follow the mesh's coordinates until a material asks otherwise. */
7257
+ this.projection.fill(0);
7258
+ /* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
7259
+ this.diffuseTransmission = 0;
7260
+ /* Nor layers: one layer until a material asks for more. */
7261
+ this.layerFloats.fill(0);
6996
7262
  this.overlayMaps = null;
6997
7263
  this.overlayTexture = null;
6998
7264
  /* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
@@ -7006,6 +7272,12 @@ export class WebGL2Renderer {
7006
7272
  this.environmentDials[2] = 1;
7007
7273
  this.emission[0] = env.emissiveGain;
7008
7274
  this.emission[1] = env.nightFactor;
7275
+ /* The moving layer's own matrix, the first time a frame draws that layer with one. */
7276
+ this.askMovingSun(env);
7277
+ /* No dither and no weighting: every draw whole, until a crossfade or an opacity asks. */
7278
+ this.oitWeight = 0;
7279
+ this.ditherFade = 0;
7280
+ this.ditherOpacity = 0;
7009
7281
  for (const target of this.flatTargets) {
7010
7282
  this.gl.useProgram(target.program);
7011
7283
  this.writeMeshPassState(target.uniforms, camera, env);
@@ -7128,11 +7400,14 @@ export class WebGL2Renderer {
7128
7400
  gl.uniform1i(u['uRefractScene'] ?? null, REFRACT_SCENE_TEXTURE_UNIT);
7129
7401
  /* The draws' overlay, none at a pass's start, and its atlas above: see `setSurfaceOverlay`. */
7130
7402
  gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
7403
+ gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
7404
+ gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
7131
7405
  /* Neither refracting nor glass, which is every draw that does not say otherwise. See
7132
7406
  `bindSeeThrough` for the four numbers in the one vector. */
7133
7407
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
7134
7408
  gl.uniform4f(u['uRefractTint'] ?? null, 1, 1, 1, 1);
7135
- gl.uniform3fv(u['uGlassTint'] ?? null, WHITE_TINT);
7409
+ /* White, and no light through from behind: no material at a pass's start. */
7410
+ gl.uniform4f(u['uGlassTint'] ?? null, 1, 1, 1, this.diffuseTransmission);
7136
7411
  gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
7137
7412
  gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
7138
7413
  gl.uniform3fv(u['uAmbient'] ?? null, env.ambient);
@@ -7199,10 +7474,9 @@ export class WebGL2Renderer {
7199
7474
  this.surfaceFog = FOG_RECEDE;
7200
7475
  /* Grain on by default, so every scene written before it was a choice looks unchanged. */
7201
7476
  gl.uniform1f(u['uGrain'] ?? null, 1);
7202
- /* No dither and no weighting: every draw whole, until a crossfade asks. See `setDitherFade`. */
7203
- this.oitWeight = 0;
7204
- this.ditherFade = 0;
7205
- gl.uniform2f(u['uWriteMode'] ?? null, 0, 0);
7477
+ /* The weighting and the dither as they stand, which `bindMeshPass` has just cleared: a program
7478
+ built in the middle of a pass takes what the caller set. See `setDitherFade`. */
7479
+ this.writeMode(u);
7206
7480
  /* The draw's own ambient as it stands, which `bindMeshPass` has just cleared: a program built
7207
7481
  in the middle of a pass takes what the caller set. See `setAmbientSH`. */
7208
7482
  gl.uniform4fv(u['uAmbientSH[0]'] ?? null, this.ambientSH);
@@ -7241,6 +7515,10 @@ export class WebGL2Renderer {
7241
7515
  gl.uniform1f(u['uShadowStrength'] ?? null, this.sunShadows === null ? 0 : env.shadowStrength);
7242
7516
  gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
7243
7517
  gl.uniform1f(u['uShadowDepthSpan'] ?? null, env.shadowDepthSpan);
7518
+ /* The moving layer's own matrix and span, where the program reads one: see `movingLightFor`. */
7519
+ const moving = this.movingLightFor(env);
7520
+ gl.uniformMatrix4fv(u['uMovingLightViewProj'] ?? null, false, moving);
7521
+ gl.uniform1f(u['uMovingShadowDepthSpan'] ?? null, orthographicDepthSpan(moving));
7244
7522
  gl.uniform1f(u['uShadowMaxDistance'] ?? null, this.quality.directionalShadowMaxDistance);
7245
7523
  gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
7246
7524
  gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
@@ -7551,6 +7829,9 @@ export class WebGL2Renderer {
7551
7829
  * light and its colour, each into the blur's own target.
7552
7830
  */
7553
7831
  const splits = this.skinSplits();
7832
+ /* A split skin draws its three halves or none, where a half may still be compiling. */
7833
+ if (splits && this.parallelCompile !== null && !this.skinHalvesReady(mesh))
7834
+ return;
7554
7835
  /* An opaque lit draw into the frame writes its material's reflection too: see `reflectionKeeps`. */
7555
7836
  const keeps = this.reflectionKeeps();
7556
7837
  if (keeps)
@@ -7629,8 +7910,25 @@ export class WebGL2Renderer {
7629
7910
  this.useFlatProgram();
7630
7911
  }
7631
7912
  /** One draw of a mesh, as `drawMesh` describes, of the half of a skin `half` names. */
7632
- drawMeshHalf(mesh, model, depthLayer, tint, half) {
7633
- const { gl } = this;
7913
+ /**
7914
+ * Whether every half a split skin draws has its program, starting any that has not: a skin drawn
7915
+ * without its kept halves would lose its diffuse light for the frames they take to compile.
7916
+ */
7917
+ skinHalvesReady(mesh) {
7918
+ const variant = this.drawVariantOf(mesh).name;
7919
+ let ready = true;
7920
+ for (const half of SPLIT_SKIN_HALVES) {
7921
+ if (this.modelProgram(variant, 'skin', half) === null)
7922
+ ready = false;
7923
+ }
7924
+ return ready;
7925
+ }
7926
+ /**
7927
+ * The vertex variant a draw of `mesh` takes now, and its program and uniforms where it is not the
7928
+ * plain one: what a model's program is keyed by, and what `drawMeshHalf` draws with.
7929
+ */
7930
+ drawVariantOf(mesh) {
7931
+ const out = this.drawVariant;
7634
7932
  /*
7635
7933
  * A rigged mesh takes the skinned program, an unrigged one the plain program.
7636
7934
  *
@@ -7658,9 +7956,8 @@ export class WebGL2Renderer {
7658
7956
  * when none were asked for — an unbound sampler would default to a shadow map's unit.
7659
7957
  */
7660
7958
  const cloth = wantSkin ? this.cloth : null;
7661
- const clothed = cloth === null
7662
- ? undefined
7663
- : this.clothFlat.get(`${eight ? 8 : 4}|${mesh.morph !== null ? 'm' : '-'}`);
7959
+ const clothKey = `${eight ? 8 : 4}|${mesh.morph !== null ? 'm' : '-'}`;
7960
+ const clothed = cloth === null ? undefined : this.clothFlat.get(clothKey);
7664
7961
  const bothUniforms = eight ? this.flatBoth8Uniforms : this.flatBothUniforms;
7665
7962
  const skinnedUniforms = eight ? this.flatSkinned8Uniforms : this.flatSkinnedUniforms;
7666
7963
  const both = wantSkin && wantMorph && bothUniforms !== null;
@@ -7673,24 +7970,27 @@ export class WebGL2Renderer {
7673
7970
  : morphed
7674
7971
  ? this.flatMorphedUniforms
7675
7972
  : null;
7676
- let u = clothed?.uniforms ?? variant ?? this.flatUniforms;
7677
- let program = clothed !== undefined
7678
- ? clothed.program
7679
- : skinned && morphed
7680
- ? eight
7681
- ? this.flatBoth8Program
7682
- : this.flatBothProgram
7683
- : skinned
7973
+ out.skinned = skinned;
7974
+ out.morphed = morphed;
7975
+ out.cloth = cloth;
7976
+ out.clothed = clothed;
7977
+ out.uniforms = clothed?.uniforms ?? variant ?? this.flatUniforms;
7978
+ out.program =
7979
+ clothed !== undefined
7980
+ ? clothed.program
7981
+ : skinned && morphed
7684
7982
  ? eight
7685
- ? this.flatSkinned8Program
7686
- : this.flatSkinnedProgram
7687
- : morphed
7688
- ? this.flatMorphedProgram
7689
- : null;
7690
- /* A material with a model takes that model's program for the same vertex variant. */
7691
- const modelKind = this.currentMaterial?.model?.kind ?? null;
7692
- if (modelKind !== null) {
7693
- const modelled = this.modelProgram(clothed !== undefined
7983
+ ? this.flatBoth8Program
7984
+ : this.flatBothProgram
7985
+ : skinned
7986
+ ? eight
7987
+ ? this.flatSkinned8Program
7988
+ : this.flatSkinnedProgram
7989
+ : morphed
7990
+ ? this.flatMorphedProgram
7991
+ : null;
7992
+ out.name =
7993
+ clothed !== undefined
7694
7994
  ? `cloth${eight ? 8 : 4}${mesh.morph !== null ? 'm' : '-'}`
7695
7995
  : skinned && morphed
7696
7996
  ? eight
@@ -7702,7 +8002,22 @@ export class WebGL2Renderer {
7702
8002
  : 'skinned'
7703
8003
  : morphed
7704
8004
  ? 'morphed'
7705
- : 'plain', modelKind, half);
8005
+ : 'plain';
8006
+ return out;
8007
+ }
8008
+ drawMeshHalf(mesh, model, depthLayer, tint, half) {
8009
+ const { gl } = this;
8010
+ const drawn = this.drawVariantOf(mesh);
8011
+ const { skinned, morphed, cloth, clothed } = drawn;
8012
+ let u = drawn.uniforms;
8013
+ let program = drawn.program;
8014
+ /* A material with a model takes that model's program for the same vertex variant. */
8015
+ const modelKind = this.currentMaterial?.model?.kind ?? null;
8016
+ if (modelKind !== null) {
8017
+ const modelled = this.modelProgram(drawn.name, modelKind, half);
8018
+ /* Still compiling, where draws skip a compile: left out until it lands. */
8019
+ if (modelled === null)
8020
+ return;
7706
8021
  program = modelled.program;
7707
8022
  u = modelled.uniforms;
7708
8023
  }
@@ -7881,6 +8196,9 @@ export class WebGL2Renderer {
7881
8196
  /* A material with a model takes that model's plain program; see `modelProgram`. */
7882
8197
  const modelKind = this.currentMaterial?.model?.kind ?? null;
7883
8198
  const modelled = modelKind === null ? null : this.modelProgram('plain', modelKind);
8199
+ /* Still compiling, where draws skip a compile: left out until it lands. */
8200
+ if (modelKind !== null && modelled === null)
8201
+ return;
7884
8202
  const u = modelled?.uniforms ?? this.flatUniforms;
7885
8203
  if (modelled === null)
7886
8204
  this.useFlatProgram();
@@ -8360,6 +8678,7 @@ export class WebGL2Renderer {
8360
8678
  /* Uncorrected, for the reason the lit program's copy of this line carries: the shaft's
8361
8679
  lookup does its own `* 0.5 + 0.5` and correcting the matrix as well doubles it. */
8362
8680
  gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, env.lightViewProj);
8681
+ gl.uniformMatrix4fv(u['uMovingLightViewProj'] ?? null, false, this.movingLightFor(env));
8363
8682
  gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
8364
8683
  gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
8365
8684
  gl.activeTexture(gl.TEXTURE0);
@@ -75,8 +75,13 @@ export interface GpuMesh {
75
75
  /** The second four influences' offsets, or -1 for a mesh with four. */
76
76
  readonly joints2: number;
77
77
  readonly weights2: number;
78
+ /** How the weights are read: floats, or sixteen-bit fixed point (`vertexPacking.ts`). */
79
+ readonly weightsFormat: GPUVertexFormat;
80
+ readonly weights2Format: GPUVertexFormat;
78
81
  } | null;
79
82
  readonly indexBuffer: GPUBuffer;
83
+ /** Sixteen or thirty-two bits an index: every pass that binds `indexBuffer` says which. */
84
+ readonly indexFormat: GPUIndexFormat;
80
85
  /**
81
86
  * How many indices the mesh draws. **Zero is a mesh with nothing to draw, and no pass draws it**:
82
87
  * a level can hold none of some kind of geometry, and a draw of zero indices does nothing but