@driftengine/core 4.9.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 (231) hide show
  1. package/CHANGELOG.json +133 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +7 -4
  4. package/dist/index.js +5 -4
  5. package/dist/render/ambientHarmonics.d.ts +1 -1
  6. package/dist/render/ambientHarmonics.js +1 -1
  7. package/dist/render/areaLights.d.ts +4 -4
  8. package/dist/render/areaLights.js +1 -1
  9. package/dist/render/backend/api.d.ts +10 -3
  10. package/dist/render/backend/select.js +11 -5
  11. package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
  12. package/dist/render/backend/webgl2/boneAnimations.js +80 -0
  13. package/dist/render/backend/webgl2/instanced.d.ts +5 -1
  14. package/dist/render/backend/webgl2/instanced.js +13 -1
  15. package/dist/render/backend/webgl2/renderer.d.ts +189 -4
  16. package/dist/render/backend/webgl2/renderer.js +614 -66
  17. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
  18. package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
  19. package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
  20. package/dist/render/backend/webgpu/boneAnimations.js +76 -0
  21. package/dist/render/backend/webgpu/buffers.d.ts +24 -4
  22. package/dist/render/backend/webgpu/buffers.js +116 -59
  23. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  24. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  25. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  26. package/dist/render/backend/webgpu/clothOps.js +78 -0
  27. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  28. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  29. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  30. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  31. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  32. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  33. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  34. package/dist/render/backend/webgpu/depthPass.js +49 -14
  35. package/dist/render/backend/webgpu/drawCommand.d.ts +20 -4
  36. package/dist/render/backend/webgpu/drawCommand.js +11 -3
  37. package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
  38. package/dist/render/backend/webgpu/flatPass.js +137 -20
  39. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  40. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  41. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  42. package/dist/render/backend/webgpu/instanced.js +24 -1
  43. package/dist/render/backend/webgpu/latePass.js +1 -0
  44. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  45. package/dist/render/backend/webgpu/materialStore.js +265 -0
  46. package/dist/render/backend/webgpu/motionPass.js +15 -6
  47. package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
  48. package/dist/render/backend/webgpu/pipelineCache.js +46 -1
  49. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  50. package/dist/render/backend/webgpu/renderer.d.ts +200 -35
  51. package/dist/render/backend/webgpu/renderer.js +993 -168
  52. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  53. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  54. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  55. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  56. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  57. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  58. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  59. package/dist/render/backend/webgpu/staticBundles.js +242 -0
  60. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  61. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  62. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  63. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  64. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  65. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  66. package/dist/render/bloomChain.d.ts +3 -3
  67. package/dist/render/boneAnimation.d.ts +92 -0
  68. package/dist/render/boneAnimation.js +133 -0
  69. package/dist/render/compressedSource.d.ts +33 -13
  70. package/dist/render/compressedSource.js +70 -25
  71. package/dist/render/glCompressed.js +83 -0
  72. package/dist/render/globalMediumPass.d.ts +2 -0
  73. package/dist/render/globalMediumPass.js +1 -0
  74. package/dist/render/indexWidth.d.ts +17 -0
  75. package/dist/render/indexWidth.js +23 -0
  76. package/dist/render/instances.d.ts +26 -0
  77. package/dist/render/lightBudget.d.ts +10 -0
  78. package/dist/render/lightBudget.js +10 -0
  79. package/dist/render/lightMatrix.d.ts +11 -0
  80. package/dist/render/lightMatrix.js +14 -0
  81. package/dist/render/lightmap.d.ts +1 -1
  82. package/dist/render/mesh.d.ts +14 -0
  83. package/dist/render/mesh.js +44 -14
  84. package/dist/render/pointLightSelection.d.ts +4 -4
  85. package/dist/render/renderQuality.d.ts +30 -0
  86. package/dist/render/renderQuality.js +5 -0
  87. package/dist/render/rendererHarness.d.ts +7 -0
  88. package/dist/render/rendererHarness.js +12 -0
  89. package/dist/render/screenSpaceReflection.d.ts +2 -1
  90. package/dist/render/shader.d.ts +15 -0
  91. package/dist/render/shader.js +41 -0
  92. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  93. package/dist/render/shaders/boneAnimation.js +63 -0
  94. package/dist/render/shaders/depth.d.ts +7 -0
  95. package/dist/render/shaders/depth.js +35 -10
  96. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  97. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  98. package/dist/render/shaders/flat/glassTint.js +3 -0
  99. package/dist/render/shaders/flat/index.d.ts +21 -0
  100. package/dist/render/shaders/flat/index.js +42 -7
  101. package/dist/render/shaders/flat/layered.d.ts +19 -0
  102. package/dist/render/shaders/flat/layered.js +115 -0
  103. package/dist/render/shaders/flat/main.d.ts +1 -1
  104. package/dist/render/shaders/flat/main.js +58 -21
  105. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  106. package/dist/render/shaders/flat/mapReads.js +38 -0
  107. package/dist/render/shaders/flat/models.js +1 -1
  108. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  109. package/dist/render/shaders/flat/movingSun.js +46 -0
  110. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  111. package/dist/render/shaders/flat/normalMap.js +14 -3
  112. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  113. package/dist/render/shaders/flat/overlay.js +42 -20
  114. package/dist/render/shaders/flat/pointShadow.js +2 -2
  115. package/dist/render/shaders/flat/preamble.js +59 -27
  116. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  117. package/dist/render/shaders/flat/worldUv.js +113 -0
  118. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  119. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  120. package/dist/render/shaders/generated/flat.wgsl.d.ts +3616 -3042
  121. package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
  122. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  123. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  124. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  125. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  126. package/dist/render/shaders/globalMedium.js +9 -1
  127. package/dist/render/shaders/lightVolume.js +9 -1
  128. package/dist/render/shaders/outputTransform.d.ts +7 -0
  129. package/dist/render/shaders/outputTransform.js +28 -14
  130. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  131. package/dist/render/shaders/tangentFrame.js +16 -3
  132. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  133. package/dist/render/shaders/vertexChannel.js +26 -0
  134. package/dist/render/skinnedClothRun.d.ts +28 -0
  135. package/dist/render/skinnedClothRun.js +103 -35
  136. package/dist/render/staticDraws.d.ts +47 -0
  137. package/dist/render/staticDraws.js +43 -0
  138. package/dist/render/surfaceLayers.d.ts +39 -0
  139. package/dist/render/surfaceLayers.js +26 -0
  140. package/dist/render/surfaceOverlay.d.ts +16 -3
  141. package/dist/render/surfaceOverlay.js +11 -6
  142. package/dist/render/surfaceProjection.d.ts +34 -0
  143. package/dist/render/surfaceProjection.js +30 -0
  144. package/dist/render/surfaceTexture.d.ts +68 -7
  145. package/dist/render/surfaceTexture.js +56 -17
  146. package/dist/render/vertexPacking.d.ts +48 -0
  147. package/dist/render/vertexPacking.js +68 -0
  148. package/package.json +6 -6
  149. package/src/index.ts +12 -4
  150. package/src/render/ambientHarmonics.ts +1 -1
  151. package/src/render/areaLights.ts +4 -4
  152. package/src/render/backend/api.ts +13 -3
  153. package/src/render/backend/select.ts +10 -5
  154. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  155. package/src/render/backend/webgl2/instanced.ts +20 -1
  156. package/src/render/backend/webgl2/renderer.ts +723 -79
  157. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  158. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  159. package/src/render/backend/webgpu/buffers.ts +195 -70
  160. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  161. package/src/render/backend/webgpu/clothOps.ts +86 -0
  162. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  163. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  164. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  165. package/src/render/backend/webgpu/depthPass.ts +51 -10
  166. package/src/render/backend/webgpu/drawCommand.ts +25 -4
  167. package/src/render/backend/webgpu/flatPass.ts +212 -41
  168. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  169. package/src/render/backend/webgpu/instanced.ts +32 -1
  170. package/src/render/backend/webgpu/latePass.ts +1 -0
  171. package/src/render/backend/webgpu/materialStore.ts +276 -0
  172. package/src/render/backend/webgpu/motionPass.ts +18 -7
  173. package/src/render/backend/webgpu/pipelineCache.ts +53 -7
  174. package/src/render/backend/webgpu/renderer.ts +1201 -202
  175. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  176. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  177. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  178. package/src/render/backend/webgpu/staticBundles.ts +364 -0
  179. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  180. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  181. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  182. package/src/render/bloomChain.ts +3 -3
  183. package/src/render/boneAnimation.ts +223 -0
  184. package/src/render/compressedSource.ts +121 -29
  185. package/src/render/glCompressed.ts +83 -0
  186. package/src/render/globalMediumPass.ts +7 -0
  187. package/src/render/indexWidth.ts +27 -0
  188. package/src/render/instances.ts +27 -0
  189. package/src/render/lightBudget.ts +11 -0
  190. package/src/render/lightMatrix.ts +15 -0
  191. package/src/render/lightmap.ts +1 -1
  192. package/src/render/mesh.ts +72 -14
  193. package/src/render/pointLightSelection.ts +4 -4
  194. package/src/render/renderQuality.ts +38 -0
  195. package/src/render/rendererHarness.ts +15 -0
  196. package/src/render/screenSpaceReflection.ts +2 -1
  197. package/src/render/shader.ts +59 -0
  198. package/src/render/shaders/boneAnimation.ts +63 -0
  199. package/src/render/shaders/depth.ts +45 -10
  200. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  201. package/src/render/shaders/flat/glassTint.ts +7 -1
  202. package/src/render/shaders/flat/index.ts +63 -7
  203. package/src/render/shaders/flat/layered.ts +115 -0
  204. package/src/render/shaders/flat/main.ts +58 -21
  205. package/src/render/shaders/flat/mapReads.ts +38 -0
  206. package/src/render/shaders/flat/models.ts +1 -1
  207. package/src/render/shaders/flat/movingSun.ts +46 -0
  208. package/src/render/shaders/flat/normalMap.ts +14 -3
  209. package/src/render/shaders/flat/overlay.ts +42 -20
  210. package/src/render/shaders/flat/pointShadow.ts +2 -2
  211. package/src/render/shaders/flat/preamble.ts +59 -27
  212. package/src/render/shaders/flat/worldUv.ts +113 -0
  213. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  214. package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
  215. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  216. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  217. package/src/render/shaders/globalMedium.ts +9 -1
  218. package/src/render/shaders/lightVolume.ts +9 -1
  219. package/src/render/shaders/outputTransform.ts +30 -14
  220. package/src/render/shaders/tangentFrame.ts +16 -3
  221. package/src/render/shaders/vertexChannel.ts +27 -0
  222. package/src/render/skinnedClothRun.ts +138 -38
  223. package/src/render/staticDraws.ts +95 -0
  224. package/src/render/surfaceLayers.ts +57 -0
  225. package/src/render/surfaceOverlay.ts +24 -7
  226. package/src/render/surfaceProjection.ts +57 -0
  227. package/src/render/surfaceTexture.ts +124 -27
  228. package/src/render/vertexPacking.ts +109 -0
  229. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  230. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  231. 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';
@@ -77,7 +80,7 @@ import { MAX_AREA_LIGHTS } from '../../areaLights.js';
77
80
  import { areaShadowLayerCount, castingRange, createResolvedAreaShadows, firstAreaShadowLayer, AreaShadowSet, } from '../../areaShadowSet.js';
78
81
  import { equirectToCubeFaces } from '../../equirectToCube.js';
79
82
  import { SKY_FRAG, skyVertFor } from '../../shaders/sky.js';
80
- import { DEPTH_CUTOUT_FRAG, DEPTH_CUTOUT_VERT, DEPTH_FRAG, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_INSTANCED_VERT, DEPTH_SKINNED_VERT, DEPTH_VERT, } from '../../shaders/depth.js';
83
+ import { DEPTH_CUTOUT_FRAG, DEPTH_CUTOUT_VERT, DEPTH_FRAG, DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT, DEPTH_INSTANCED_ANIMATED_VERT, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_INSTANCED_VERT, DEPTH_SKINNED_VERT, DEPTH_VERT, } from '../../shaders/depth.js';
81
84
  import { OCTAHEDRAL_EDGE, PointShadowArray, strideLayers } from '../../pointShadowArray.js';
82
85
  import { SunShadowArray } from '../../shadowMap.js';
83
86
  import { POINT_SHADOW_SAMPLER, PointShadowSystem } from '../../pointShadowSystem.js';
@@ -85,6 +88,7 @@ import { castsDepth, glassOf } from '../../shadowCasters.js';
85
88
  import { GlassCasterList } from '../../glassCasters.js';
86
89
  import { SunGlassTint } from '../../sunGlassTint.js';
87
90
  import { GlassTintPrograms } from './glassTintPrograms.js';
91
+ import { captureStaticDraws, replayStaticDraws } from '../../staticDraws.js';
88
92
  import { FACE_COUNT, PointShadowMap } from '../../pointShadowMap.js';
89
93
  import { createResolvedPointShadows, DEFAULT_SOURCE_RADIUS } from '../../pointShadowImage.js';
90
94
  import { createBakeScratch, runPointShadowBakes, selectCastingLights, } from '../../pointShadowBudget.js';
@@ -117,6 +121,9 @@ import { DEFAULT_FILMIC_CURVE, FILMIC_CLAMPED, FILMIC_CONSTANTS, FILMIC_WITHOUT_
117
121
  import { deviceSnappedCellSize } from '../../textLayout.js';
118
122
  import { WindStreakRenderer } from '../../windStreakRenderer.js';
119
123
  import { InstancedBatch } from './instanced.js';
124
+ import { GlBoneAnimation } from './boneAnimations.js';
125
+ import { packBoneAnimation } from '../../boneAnimation.js';
126
+ import { BONE_PLACES_TEXTURE_UNIT, BONE_TURNS_TEXTURE_UNIT, INSTANCE_CLOCKS_TEXTURE_UNIT, } from '../../lightBudget.js';
120
127
  import { cutoutOf } from '../../cutoutCaster.js';
121
128
  import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.js';
122
129
  import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.js';
@@ -287,6 +294,18 @@ const NO_CLIP_PLANE = new Float32Array(4);
287
294
  const IDENTITY_CLIP = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
288
295
  /** Standing still, for a caller that asked for dust and not for wind. */
289
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'];
290
309
  export class WebGL2Renderer {
291
310
  /**
292
311
  * Private on purpose. The GL context never leaves this directory, so a
@@ -564,6 +583,23 @@ export class WebGL2Renderer {
564
583
  * compiled the first time a draw asks for one. See `modelProgram`.
565
584
  */
566
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;
567
603
  /** The last palette chosen, which an eye's axis is turned by. See `setSkinPalette`. */
568
604
  paletteData = null;
569
605
  emptyTexture2D;
@@ -626,7 +662,7 @@ export class WebGL2Renderer {
626
662
  oitReplaying = false;
627
663
  /** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
628
664
  surfaceScene = new Float32Array(4);
629
- /** 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. */
630
666
  oitWeight = 0;
631
667
  /**
632
668
  * How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
@@ -634,6 +670,7 @@ export class WebGL2Renderer {
634
670
  */
635
671
  surfaceFog = FOG_RECEDE;
636
672
  ditherFade = 0;
673
+ ditherOpacity = 0;
637
674
  /** Said once, not per frame, where the context cannot give the float target the sum needs. */
638
675
  oitRefused = false;
639
676
  /** Said once for the whole renderer. See `refuseOitMultisampled`. */
@@ -802,6 +839,11 @@ export class WebGL2Renderer {
802
839
  depthCutoutUniforms;
803
840
  depthInstancedCutoutProgram;
804
841
  depthInstancedCutoutUniforms;
842
+ /** A crowd's casters: the instanced depth programs that play a bone animation. */
843
+ depthAnimatedProgram;
844
+ depthAnimatedUniforms;
845
+ depthAnimatedCutoutProgram;
846
+ depthAnimatedCutoutUniforms;
805
847
  scatterDepthProgram;
806
848
  scatterDepthUniforms;
807
849
  /** The sun's static, moving and peeled maps, as layers of one array. See `shadowMap.ts`. */
@@ -896,6 +938,28 @@ export class WebGL2Renderer {
896
938
  overlayTexture = null;
897
939
  /** Whether this device's lit stage has room for the overlay's vectors: asked once. */
898
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;
899
963
  warnedOverlayRoom = false;
900
964
  warnedOverlayBlocks = false;
901
965
  /** Whether a probe is being baked, so the passes that would recurse into one can tell. */
@@ -1205,14 +1269,21 @@ export class WebGL2Renderer {
1205
1269
  const count = Math.min(data.count, gpuBatch.capacity);
1206
1270
  if (count === 0)
1207
1271
  return;
1272
+ const clip = gpuBatch.animation;
1273
+ if (clip !== null && clip.disposed)
1274
+ return;
1208
1275
  this.shadowDrawBudget.ask();
1209
1276
  const twoSided = material?.doubleSided === true;
1210
1277
  if (twoSided)
1211
1278
  gl.disable(gl.CULL_FACE);
1212
1279
  const cutout = cutoutOf(material);
1213
1280
  if (cutout !== null) {
1214
- gl.useProgram(this.depthInstancedCutoutProgram);
1215
- this.bindCutoutDepth(this.depthInstancedCutoutUniforms, cutout);
1281
+ /* A crowd casts where its clip has moved it, through the animated variant. */
1282
+ const uniforms = clip === null ? this.depthInstancedCutoutUniforms : this.depthAnimatedCutoutUniforms;
1283
+ gl.useProgram(clip === null ? this.depthInstancedCutoutProgram : this.depthAnimatedCutoutProgram);
1284
+ this.bindCutoutDepth(uniforms, cutout);
1285
+ if (clip !== null)
1286
+ this.bindAnimation(uniforms, gpuBatch);
1216
1287
  gpuBatch.draw(gl, count);
1217
1288
  if (twoSided && this.depthPassCullsFaces)
1218
1289
  gl.enable(gl.CULL_FACE);
@@ -1220,12 +1291,14 @@ export class WebGL2Renderer {
1220
1291
  this.restoreSurfaceTextureUnit();
1221
1292
  return;
1222
1293
  }
1223
- const u = this.depthInstancedUniforms;
1224
- gl.useProgram(this.depthInstancedProgram);
1294
+ const u = clip === null ? this.depthInstancedUniforms : this.depthAnimatedUniforms;
1295
+ gl.useProgram(clip === null ? this.depthInstancedProgram : this.depthAnimatedProgram);
1225
1296
  gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
1226
1297
  gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
1227
1298
  if (this.activeDepthPeel)
1228
1299
  gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
1300
+ if (clip !== null)
1301
+ this.bindAnimation(u, gpuBatch);
1229
1302
  gpuBatch.draw(gl, count);
1230
1303
  if (twoSided && this.depthPassCullsFaces)
1231
1304
  gl.enable(gl.CULL_FACE);
@@ -2091,6 +2164,14 @@ export class WebGL2Renderer {
2091
2164
  * Measured on an RX 9070 XT through ANGLE/Vulkan: offered. `scripts/depth-survey.mjs` is what
2092
2165
  * asks, and it also reports what WebGPU can do.
2093
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;
2094
2175
  this.clipControl = gl.getExtension('EXT_clip_control');
2095
2176
  if (REVERSED_DEPTH && this.clipControl !== null) {
2096
2177
  this.clipControl.clipControlEXT(this.clipControl.LOWER_LEFT_EXT, this.clipControl.ZERO_TO_ONE_EXT);
@@ -2230,10 +2311,15 @@ export class WebGL2Renderer {
2230
2311
  this.depthCutoutUniforms = uniformLocations(gl, this.depthCutoutProgram, 'depth-cutout');
2231
2312
  this.depthInstancedCutoutProgram = compileProgram(gl, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_CUTOUT_FRAG, 'depth-instanced-cutout');
2232
2313
  this.depthInstancedCutoutUniforms = uniformLocations(gl, this.depthInstancedCutoutProgram, 'depth-instanced-cutout');
2314
+ this.depthAnimatedProgram = compileProgram(gl, DEPTH_INSTANCED_ANIMATED_VERT, DEPTH_FRAG, 'depth-instanced-animated');
2315
+ this.depthAnimatedUniforms = uniformLocations(gl, this.depthAnimatedProgram, 'depth-instanced-animated');
2316
+ this.depthAnimatedCutoutProgram = compileProgram(gl, DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT, DEPTH_CUTOUT_FRAG, 'depth-instanced-animated-cutout');
2317
+ this.depthAnimatedCutoutUniforms = uniformLocations(gl, this.depthAnimatedCutoutProgram, 'depth-instanced-animated-cutout');
2233
2318
  /* The cutout map is always read from the surface unit, which the draw borrows and gives back. */
2234
2319
  for (const [program, uniforms] of [
2235
2320
  [this.depthCutoutProgram, this.depthCutoutUniforms],
2236
2321
  [this.depthInstancedCutoutProgram, this.depthInstancedCutoutUniforms],
2322
+ [this.depthAnimatedCutoutProgram, this.depthAnimatedCutoutUniforms],
2237
2323
  ]) {
2238
2324
  gl.useProgram(program);
2239
2325
  gl.uniform1i(uniforms['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
@@ -2330,7 +2416,33 @@ export class WebGL2Renderer {
2330
2416
  * this before its first frame gets the right behaviour on both without asking which backend
2331
2417
  * it has, which is the contract `RendererApi` exists to keep.
2332
2418
  */
2333
- 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
+ }
2334
2446
  dispose(options = {}) {
2335
2447
  if (this.disposed)
2336
2448
  return;
@@ -2401,6 +2513,8 @@ export class WebGL2Renderer {
2401
2513
  this.lightVolumeProgram,
2402
2514
  this.depthCutoutProgram,
2403
2515
  this.depthInstancedCutoutProgram,
2516
+ this.depthAnimatedProgram,
2517
+ this.depthAnimatedCutoutProgram,
2404
2518
  ]) {
2405
2519
  gl.deleteProgram(program);
2406
2520
  }
@@ -2718,7 +2832,7 @@ export class WebGL2Renderer {
2718
2832
  updateSurfaceTexture(texture, source) {
2719
2833
  if (this.contextLost)
2720
2834
  return;
2721
- texture.update(this.gl, source);
2835
+ texture.update(this.gl, source, this.compressedFormats);
2722
2836
  /* New pixels are a new copy for the overlay's slot: see `SurfaceTexture.flatLayer`. */
2723
2837
  if (texture === this.overlayMaps)
2724
2838
  this.bindOverlayMaps(texture);
@@ -2783,7 +2897,30 @@ export class WebGL2Renderer {
2783
2897
  return held;
2784
2898
  const { gl } = this;
2785
2899
  const label = `flat.${variant}.${kind}.${half}`;
2786
- 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;
2787
2924
  const made = { program, uniforms: uniformLocations(gl, program, label) };
2788
2925
  this.modelFlat.set(key, made);
2789
2926
  this.flatTargets.push(made);
@@ -2929,6 +3066,13 @@ export class WebGL2Renderer {
2929
3066
  this.materialDoubleSided = material?.doubleSided === true;
2930
3067
  this.materialHasMaps = materialHasMaps(material);
2931
3068
  this.currentMaterial = material;
3069
+ const reflectivity = material?.reflectivity;
3070
+ const environmentGain = material?.environmentGain;
3071
+ this.materialReflectivity =
3072
+ reflectivity === undefined ? null : Math.min(1, Math.max(0, reflectivity));
3073
+ this.materialEnvironmentGain =
3074
+ environmentGain === undefined ? null : Math.max(0, environmentGain);
3075
+ this.writeEnvironmentDials();
2932
3076
  /* What the blur spreads this skin by: the last material to name a profile says. */
2933
3077
  const model = material?.model;
2934
3078
  warnUnpagedLightmap(model, material?.modelMap);
@@ -2966,7 +3110,8 @@ export class WebGL2Renderer {
2966
3110
  const v = this.cutoutVector;
2967
3111
  v[0] = material?.cutout ?? 0;
2968
3112
  v[1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveNow(material, translucent)];
2969
- v[2] = this.cutoutFrame;
3113
+ /* z unused: the dither's frame is the pass's, in `uHighlightMin.w`. See `uCutout`. */
3114
+ v[2] = 0;
2970
3115
  /* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
2971
3116
  v[3] = lightChannelsOf(material?.lightChannels);
2972
3117
  return v;
@@ -3052,10 +3197,17 @@ export class WebGL2Renderer {
3052
3197
  * and read neither — one decision for every program, as `setMaterial` says.
3053
3198
  */
3054
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;
3055
3203
  gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
3056
3204
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
3057
- if (modelMap !== null)
3058
- 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);
3059
3211
  gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
3060
3212
  packModel(material?.model ?? null, modelMap !== null, this.modelParams, material?.physicalSpecular === true);
3061
3213
  /* The first physical highlight asked for is when the lit programs start carrying one. */
@@ -3064,6 +3216,16 @@ export class WebGL2Renderer {
3064
3216
  gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
3065
3217
  /* The draw's overlay, which a program born mid-pass must wear too: see `setSurfaceOverlay`. */
3066
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);
3067
3229
  /* The albedo's effects table, or the stand-in the shader reads as none. Before the early
3068
3230
  return, so a material with no albedo still leaves a complete texture on the unit. */
3069
3231
  if (albedo === null) {
@@ -3157,8 +3319,22 @@ export class WebGL2Renderer {
3157
3319
  setSurfaceReflectivity(amount) {
3158
3320
  if (this.contextLost)
3159
3321
  return;
3322
+ this.passReflectivity = Math.min(1, Math.max(0, amount));
3323
+ this.writeEnvironmentDials();
3324
+ }
3325
+ /**
3326
+ * The reflectivity and environment gain the setters gave the pass, and the ones the material
3327
+ * set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
3328
+ * material's where it has one and the pass's otherwise.
3329
+ */
3330
+ passReflectivity = 0;
3331
+ passEnvironmentGain = 1;
3332
+ materialReflectivity = null;
3333
+ materialEnvironmentGain = null;
3334
+ writeEnvironmentDials() {
3160
3335
  this.materials.dirty();
3161
- this.environmentDials[1] = Math.min(1, Math.max(0, amount));
3336
+ this.environmentDials[1] = this.materialReflectivity ?? this.passReflectivity;
3337
+ this.environmentDials[2] = this.materialEnvironmentGain ?? this.passEnvironmentGain;
3162
3338
  this.writeFlatVector('uEnvironmentDials', this.environmentDials);
3163
3339
  }
3164
3340
  /**
@@ -3214,14 +3390,98 @@ export class WebGL2Renderer {
3214
3390
  }
3215
3391
  /** What the lit shader at this renderer's budget leaves of the limit, with no overlay. */
3216
3392
  overlayRoomLeft() {
3217
- return (this.fragmentVectorLimit() - countUniformVectors(this.flatFragAt(this.lightBudget, false)));
3393
+ return (this.fragmentVectorLimit() -
3394
+ countUniformVectors(this.flatFragAt(this.lightBudget, { SURFACE_OVERLAY: false })));
3218
3395
  }
3219
3396
  /** Whether the lit shader with the overlay compiled in fits this GPU: counted once. */
3220
3397
  overlayFits() {
3221
- this.overlayRoom ??=
3222
- countUniformVectors(this.flatFragAt(this.lightBudget, true)) <= this.fragmentVectorLimit();
3398
+ this.overlayRoom ??= this.switchFits('SURFACE_OVERLAY');
3223
3399
  return this.overlayRoom;
3224
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
+ }
3225
3485
  /**
3226
3486
  * How bright the environment the surfaces drawn next reflect is. 1 is the default and identity.
3227
3487
  *
@@ -3237,9 +3497,8 @@ export class WebGL2Renderer {
3237
3497
  setEnvironmentGain(gain) {
3238
3498
  if (this.contextLost)
3239
3499
  return;
3240
- this.materials.dirty();
3241
- this.environmentDials[2] = Math.max(0, gain);
3242
- this.writeFlatVector('uEnvironmentDials', this.environmentDials);
3500
+ this.passEnvironmentGain = Math.max(0, gain);
3501
+ this.writeEnvironmentDials();
3243
3502
  }
3244
3503
  /**
3245
3504
  * Kept as a no-op that says so once, because it was a stand-in for a term that now exists.
@@ -3364,9 +3623,37 @@ export class WebGL2Renderer {
3364
3623
  }
3365
3624
  this.useFlatProgram();
3366
3625
  }
3367
- /** `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
+ */
3368
3655
  writeMode(u) {
3369
- this.gl.uniform2f(u['uWriteMode'] ?? null, this.oitWeight, this.ditherFade);
3656
+ this.gl.uniform3f(u['uWriteMode'] ?? null, this.oitWeight, this.ditherFade, this.ditherOpacity);
3370
3657
  }
3371
3658
  /**
3372
3659
  * How strong the microscopic relief on the surfaces drawn next is, and how coarse.
@@ -3508,8 +3795,95 @@ export class WebGL2Renderer {
3508
3795
  throw new Error('WebGL2: a skinned mesh cannot be instanced — an instanced draw binds the two attribute ' +
3509
3796
  'locations the joint indices and weights occupy, and this mesh supplies them.');
3510
3797
  }
3798
+ const animation = (options.animation ?? null);
3799
+ /* The bone a vertex follows is its second coordinates' u, which a mesh carries in its grain
3800
+ lane; a mesh with none has nothing to name a bone by. See `boneAnimation.ts`. */
3801
+ if (animation !== null && !mesh.hasGrain) {
3802
+ throw new Error('WebGL2: a bone animation reads each vertex’s bone from the mesh’s second coordinates ' +
3803
+ '(`MeshData.lightmapUvs`), and this mesh has none.');
3804
+ }
3511
3805
  this.ensureInstancedProgram();
3512
- return new InstancedBatch(this.gl, mesh, capacity, options.cull ?? false);
3806
+ if (animation !== null)
3807
+ this.ensureAnimatedProgram();
3808
+ return new InstancedBatch(this.gl, mesh, capacity, options.cull ?? false, animation);
3809
+ }
3810
+ /**
3811
+ * A bone animation an instanced batch can play (`InstancedOptions.animation`): per frame, a turn
3812
+ * and a place for each bone, which every vertex of the batch's mesh follows — the bone its second
3813
+ * coordinates' u names, times `boneScale` — at each instance's own moment of the clip. What a
3814
+ * crowd is made of: every instance moves at the frame's rate on its own clock, in the colour pass
3815
+ * and in every shadow, with nothing on the processor after this. See `boneAnimation.ts` for the
3816
+ * clip's layout and the arithmetic, which both backends run.
3817
+ *
3818
+ * What it gives up against skinning is the blend: a vertex follows one bone. Refused, with what
3819
+ * was wrong, for a clip whose numbers are not its size, or past 2,048 bones or frames.
3820
+ */
3821
+ createBoneAnimation(clip) {
3822
+ return new GlBoneAnimation(this.gl, packBoneAnimation(clip));
3823
+ }
3824
+ /** Release a clip. A batch still playing it draws nothing, and casts nothing, after. */
3825
+ disposeBoneAnimation(animation) {
3826
+ const clip = animation;
3827
+ if (this.contextLost || clip.disposed)
3828
+ return;
3829
+ clip.dispose(this.gl);
3830
+ }
3831
+ /**
3832
+ * The clock a bone animation plays by this frame, in seconds on the caller's clock: an instance's
3833
+ * moment is this times its rate plus its phase (`MeshInstances.clocks`). **Set it before the
3834
+ * shadows are drawn**, once a frame, as the wind is set: a shadow drawn under one clock and its
3835
+ * caster under another come apart at the frame's rate. 0 until set, which holds every crowd at
3836
+ * the moment its phases name.
3837
+ */
3838
+ setAnimationTime(seconds) {
3839
+ if (Number.isFinite(seconds))
3840
+ this.animationTime = seconds;
3841
+ }
3842
+ /** The clock `setAnimationTime` set. */
3843
+ animationTime = 0;
3844
+ /**
3845
+ * An animated batch's clip, clocks and numbers into whichever program is bound: the three vertex
3846
+ * textures on units of their own (`lightBudget.ts`), the clip's speed and bone scale, the clock.
3847
+ */
3848
+ bindAnimation(u, batch) {
3849
+ const clip = batch.animation;
3850
+ const clocks = batch.clocks;
3851
+ if (clip === null || clocks === null)
3852
+ return;
3853
+ const { gl } = this;
3854
+ gl.activeTexture(gl.TEXTURE0 + BONE_PLACES_TEXTURE_UNIT);
3855
+ gl.bindTexture(gl.TEXTURE_2D, clip.places);
3856
+ gl.activeTexture(gl.TEXTURE0 + BONE_TURNS_TEXTURE_UNIT);
3857
+ gl.bindTexture(gl.TEXTURE_2D, clip.turns);
3858
+ gl.activeTexture(gl.TEXTURE0 + INSTANCE_CLOCKS_TEXTURE_UNIT);
3859
+ gl.bindTexture(gl.TEXTURE_2D, clocks.texture);
3860
+ gl.uniform1i(u['uBonePlaces'] ?? null, BONE_PLACES_TEXTURE_UNIT);
3861
+ gl.uniform1i(u['uBoneTurns'] ?? null, BONE_TURNS_TEXTURE_UNIT);
3862
+ gl.uniform1i(u['uInstanceClocks'] ?? null, INSTANCE_CLOCKS_TEXTURE_UNIT);
3863
+ gl.uniform2f(u['uBoneClip'] ?? null, clip.framesPerSecond, clip.boneScale);
3864
+ gl.uniform1f(u['uSceneTime'] ?? null, this.animationTime);
3865
+ }
3866
+ /** The animated instanced program, compiled on first use. See `ensureInstancedProgram`. */
3867
+ flatAnimatedProgram = null;
3868
+ flatAnimatedUniforms = null;
3869
+ /** Compile the animated variant, once, and feed it this frame's pass state. */
3870
+ ensureAnimatedProgram() {
3871
+ if (this.contextLost || this.flatAnimatedProgram !== null)
3872
+ return;
3873
+ const { gl } = this;
3874
+ const program = compileProgram(gl, flatVert({ skinned: false, morphed: false, instanced: true, animated: true }), this.flatFragSource, 'flat.animated');
3875
+ const uniforms = uniformLocations(gl, program, 'flat.animated');
3876
+ this.flatAnimatedProgram = program;
3877
+ this.flatAnimatedUniforms = uniforms;
3878
+ this.flatTargets.push({ program, uniforms });
3879
+ const camera = this.lastPassCamera;
3880
+ const env = this.lastPassEnv;
3881
+ if (camera !== null && env !== null) {
3882
+ gl.useProgram(program);
3883
+ this.writeMeshPassState(uniforms, camera, env);
3884
+ this.writeMaterialState(uniforms, this.currentMaterial);
3885
+ this.useFlatProgram();
3886
+ }
3513
3887
  }
3514
3888
  /** Push placement and colour. Only the live prefix. */
3515
3889
  uploadInstanced(batch, data) {
@@ -3562,18 +3936,32 @@ export class WebGL2Renderer {
3562
3936
  const culling = !blend && batch.cull;
3563
3937
  if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion))
3564
3938
  return;
3939
+ /* A batch playing a bone animation takes the animated variant; a disposed clip draws nothing. */
3940
+ const clip = batch.animation;
3941
+ if (clip !== null && clip.disposed)
3942
+ return;
3565
3943
  this.drawBudget.ask();
3566
3944
  this.ensureInstancedProgram();
3945
+ if (clip !== null)
3946
+ this.ensureAnimatedProgram();
3567
3947
  /* A material with a model takes that model's instanced program; see `modelProgram`. */
3568
3948
  const modelKind = this.currentMaterial?.model?.kind ?? null;
3569
- const modelled = modelKind === null ? null : this.modelProgram('instanced', modelKind);
3570
- const program = modelled?.program ?? this.flatInstancedProgram;
3571
- const u = modelled?.uniforms ?? this.flatInstancedUniforms;
3949
+ const modelled = modelKind === null
3950
+ ? null
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;
3955
+ const program = modelled?.program ?? (clip === null ? this.flatInstancedProgram : this.flatAnimatedProgram);
3956
+ const u = modelled?.uniforms ??
3957
+ (clip === null ? this.flatInstancedUniforms : this.flatAnimatedUniforms);
3572
3958
  if (program === null || u === null)
3573
3959
  return;
3574
3960
  const { gl } = this;
3575
3961
  gl.useProgram(program);
3576
3962
  gl.uniform1i(u['uHasTangents'] ?? null, mesh.hasTangents ? 1 : 0);
3963
+ if (clip !== null)
3964
+ this.bindAnimation(u, batch);
3577
3965
  const lit = options.lit ?? true;
3578
3966
  /* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
3579
3967
  const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
@@ -3643,10 +4031,14 @@ export class WebGL2Renderer {
3643
4031
  const keeps = !blend && this.reflectionKeeps();
3644
4032
  if (keeps)
3645
4033
  this.reflectionTargets?.on();
3646
- if (culling && cullsInstances(count, mesh.indexCount))
4034
+ /* An animated batch reads its clocks by instance index, which the culled copy would reorder:
4035
+ it is culled whole and never instance by instance. */
4036
+ if (culling && clip === null && cullsInstances(count, mesh.indexCount)) {
3647
4037
  batch.drawCulled(gl, data, this.frustum);
3648
- else
4038
+ }
4039
+ else {
3649
4040
  batch.draw(gl, count);
4041
+ }
3650
4042
  if (keeps)
3651
4043
  this.reflectionTargets?.off();
3652
4044
  if (coverage)
@@ -3710,8 +4102,11 @@ export class WebGL2Renderer {
3710
4102
  const glass = this.glassScratch;
3711
4103
  /* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
3712
4104
  gl.uniform4f(u['uSeeThrough'] ?? null, Math.max(refraction, 0), options.thicknessM ?? 0, glass.transmission, glass.frost);
3713
- gl.uniform3fv(u['uRefractTint'] ?? null, options.refractTint ?? WHITE_TINT);
3714
- gl.uniform3fv(u['uGlassTint'] ?? null, glass.tint);
4105
+ /* w: this framebuffer's first row is the bottom, so up the screen is up the rows. */
4106
+ const tint = options.refractTint ?? WHITE_TINT;
4107
+ gl.uniform4f(u['uRefractTint'] ?? null, tint[0] ?? 1, tint[1] ?? 1, tint[2] ?? 1, 1);
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);
3715
4110
  return true;
3716
4111
  }
3717
4112
  /** Put back what `bindSeeThrough` bound: the binding outlives its frame (the 2026-08-27 rule). */
@@ -4599,6 +4994,11 @@ export class WebGL2Renderer {
4599
4994
  this.shadowPassActive = true;
4600
4995
  this.activeSunLayer = layer;
4601
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
+ }
4602
5002
  /* The static layer opens first, so a frame's peel counts from here. See `peelFilled`. */
4603
5003
  if (layer === 'static')
4604
5004
  this.peelFilled = false;
@@ -4753,6 +5153,69 @@ export class WebGL2Renderer {
4753
5153
  return;
4754
5154
  casters(this.casterSink);
4755
5155
  }
5156
+ /**
5157
+ * Draws that do not change between frames, enumerated once and kept: what a stage, a set or any
5158
+ * world that stands still draws, recorded so a frame replays it rather than issuing it.
5159
+ *
5160
+ * **The same enumeration `drawSceneCasters` takes**, called once, here: rigid meshes and instanced
5161
+ * batches, each with its material. A skinned mesh or a scatter batch is refused by name, since
5162
+ * both change every frame. What it captures is what the enumeration said at the time — the
5163
+ * matrices and the instance counts are copied, so a caller's scratch is free to move on — while
5164
+ * the meshes, batches, textures and materials stay the caller's: a list must be disposed, or made
5165
+ * again, before anything it holds is, and made again when a material it holds is changed.
5166
+ *
5167
+ * **What it is for is the frame's processor time.** WebGPU records the list into a render bundle
5168
+ * per view and replays it with one call, where a draw issued a frame at a time costs its encoding
5169
+ * every frame and again for every view that draws it. This backend has no bundles and replays the
5170
+ * entries through the scene-caster path every frame, at the cost a frame drawing them itself pays;
5171
+ * the picture is the same.
5172
+ *
5173
+ * What a list gives up against drawing each frame is culling: every entry is drawn in every view,
5174
+ * because a bundle cannot leave one out. A world split into lists by region culls by list.
5175
+ */
5176
+ createStaticDraws(casters) {
5177
+ return captureStaticDraws(casters);
5178
+ }
5179
+ /**
5180
+ * Draw a list into the open mesh pass, under the camera, the lights and the fog `bindMeshPass`
5181
+ * set — the frame's, a mirror's, a capture's. Opaque draws, through each entry's material.
5182
+ *
5183
+ * **Its draws are rigid and wear no overlay**, so a skin palette, morph weights, cloth or a surface
5184
+ * overlay set before it is put back to none, as `bindMeshPass` puts an overlay back; and no
5185
+ * material is left set after it, which is what `setMaterial(null)` leaves. The state a pass set —
5186
+ * the grain, the relief, the fog, the grade — is the list's to draw with, as it is any draw's, and
5187
+ * a list drawn under a different one is recorded again for it on WebGPU.
5188
+ */
5189
+ drawStaticDraws(draws) {
5190
+ if (this.contextLost)
5191
+ return;
5192
+ const list = draws;
5193
+ if (list.disposed) {
5194
+ this.warnStaticDisposed();
5195
+ return;
5196
+ }
5197
+ this.setSkinPalette(null);
5198
+ this.setMorphWeights(null);
5199
+ this.setCloth(null);
5200
+ this.setSurfaceOverlay(null);
5201
+ this.setMaterial(null);
5202
+ this.sceneCasterMaterial = undefined;
5203
+ replayStaticDraws(list, this.sceneCasterSink);
5204
+ this.setMaterial(null);
5205
+ this.sceneCasterMaterial = undefined;
5206
+ }
5207
+ /** Release a list. Its meshes, batches and materials are the caller's and are not released. */
5208
+ disposeStaticDraws(draws) {
5209
+ draws.disposed = true;
5210
+ }
5211
+ /** A disposed list drawn, said once: the frame loop does not throw. */
5212
+ warnStaticDisposed() {
5213
+ if (this.warnedStaticDisposed)
5214
+ return;
5215
+ this.warnedStaticDisposed = true;
5216
+ console.warn('drawStaticDraws: this list was disposed, and draws nothing.');
5217
+ }
5218
+ warnedStaticDisposed = false;
4756
5219
  /**
4757
5220
  * Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
4758
5221
  *
@@ -4934,7 +5397,9 @@ export class WebGL2Renderer {
4934
5397
  * throws a sentence with the device's numbers in it rather than a bare driver string.
4935
5398
  */
4936
5399
  /** The lit fragment source this renderer's profile compiles, at a light budget. */
4937
- 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 = {}) {
4938
5403
  return flatFrag({
4939
5404
  pointShadows: this.quality.pointShadows,
4940
5405
  directionalShadows: this.quality.directionalShadows,
@@ -4947,8 +5412,11 @@ export class WebGL2Renderer {
4947
5412
  surfaceEffects: this.litOn.SURFACE_EFFECTS,
4948
5413
  driftLight: this.litOn.DRIFT_LIGHT,
4949
5414
  physicalSpecular: this.litOn.PHYSICAL_SPECULAR,
4950
- surfaceOverlay: overlay,
5415
+ surfaceOverlay: asIf.SURFACE_OVERLAY ?? this.litOn.SURFACE_OVERLAY,
4951
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,
4952
5420
  maxLights: budget.maxLights,
4953
5421
  maxAreaLights: budget.maxAreaLights,
4954
5422
  });
@@ -4965,10 +5433,10 @@ export class WebGL2Renderer {
4965
5433
  const { gl } = this;
4966
5434
  Object.assign(this.litOn, this.litWanted);
4967
5435
  this.flatFragSource = this.flatFragAt(this.lightBudget);
4968
- const recompile = (old, skinned, morphed, instanced, label, eight = false) => {
5436
+ const recompile = (old, skinned, morphed, instanced, label, eight = false, animated = false) => {
4969
5437
  if (old === null)
4970
5438
  return null;
4971
- const vertex = flatVert({ skinned, morphed, instanced });
5439
+ const vertex = flatVert({ skinned, morphed, instanced, animated });
4972
5440
  const program = compileProgram(gl, eight ? skinEight(vertex) : vertex, this.flatFragSource, label);
4973
5441
  gl.deleteProgram(old);
4974
5442
  return { program, uniforms: uniformLocations(gl, program, label) };
@@ -4996,6 +5464,9 @@ export class WebGL2Renderer {
4996
5464
  const instanced = recompile(this.flatInstancedProgram, false, false, true, 'flat.instanced');
4997
5465
  this.flatInstancedProgram = instanced?.program ?? null;
4998
5466
  this.flatInstancedUniforms = instanced?.uniforms ?? null;
5467
+ const animated = recompile(this.flatAnimatedProgram, false, false, true, 'flat.animated', false, true);
5468
+ this.flatAnimatedProgram = animated?.program ?? null;
5469
+ this.flatAnimatedUniforms = animated?.uniforms ?? null;
4999
5470
  for (const [key, entry] of this.clothFlat) {
5000
5471
  const eight = key.startsWith('8');
5001
5472
  const withMorph = key.endsWith('m');
@@ -5005,6 +5476,10 @@ export class WebGL2Renderer {
5005
5476
  gl.deleteProgram(entry.program);
5006
5477
  this.clothFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
5007
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();
5008
5483
  for (const [key, entry] of this.modelFlat) {
5009
5484
  const [variant, kind, half] = key.split('|');
5010
5485
  const label = `flat.${variant}.${kind}.${half}`;
@@ -5013,7 +5488,7 @@ export class WebGL2Renderer {
5013
5488
  this.modelFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
5014
5489
  }
5015
5490
  this.flatTargets.length = 0;
5016
- for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced]) {
5491
+ for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced, animated]) {
5017
5492
  if (target !== null)
5018
5493
  this.flatTargets.push(target);
5019
5494
  }
@@ -6544,6 +7019,7 @@ export class WebGL2Renderer {
6544
7019
  ambient: env?.ambient ?? [0, 0, 0],
6545
7020
  sunShadow,
6546
7021
  lightViewProj: env?.lightViewProj ?? IDENTITY_MAT4,
7022
+ movingLightViewProj: env === null ? IDENTITY_MAT4 : this.movingLightFor(env),
6547
7023
  /* The placeholder rather than null where a map is merely absent, for the reason
6548
7024
  `emptyTexture.ts` gives. It is black, which as a depth is an occluder at the light,
6549
7025
  so it is safe only because `sunShadow` is 0 whenever these maps do not exist. */
@@ -6777,15 +7253,31 @@ export class WebGL2Renderer {
6777
7253
  this.currentSurfaceTexture = null;
6778
7254
  /* Nor an overlay: the atlas's unit is given the empty texture with the refraction copy's. */
6779
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);
6780
7262
  this.overlayMaps = null;
6781
7263
  this.overlayTexture = null;
6782
7264
  /* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
6783
7265
  this.ambientSH.fill(0);
6784
7266
  /* No reflection and an environment at its own brightness; the emission the world asks for. */
7267
+ this.passReflectivity = 0;
7268
+ this.passEnvironmentGain = 1;
7269
+ this.materialReflectivity = null;
7270
+ this.materialEnvironmentGain = null;
6785
7271
  this.environmentDials[1] = 0;
6786
7272
  this.environmentDials[2] = 1;
6787
7273
  this.emission[0] = env.emissiveGain;
6788
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;
6789
7281
  for (const target of this.flatTargets) {
6790
7282
  this.gl.useProgram(target.program);
6791
7283
  this.writeMeshPassState(target.uniforms, camera, env);
@@ -6908,11 +7400,14 @@ export class WebGL2Renderer {
6908
7400
  gl.uniform1i(u['uRefractScene'] ?? null, REFRACT_SCENE_TEXTURE_UNIT);
6909
7401
  /* The draws' overlay, none at a pass's start, and its atlas above: see `setSurfaceOverlay`. */
6910
7402
  gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
7403
+ gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
7404
+ gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
6911
7405
  /* Neither refracting nor glass, which is every draw that does not say otherwise. See
6912
7406
  `bindSeeThrough` for the four numbers in the one vector. */
6913
7407
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
6914
- gl.uniform3fv(u['uRefractTint'] ?? null, WHITE_TINT);
6915
- gl.uniform3fv(u['uGlassTint'] ?? null, WHITE_TINT);
7408
+ gl.uniform4f(u['uRefractTint'] ?? null, 1, 1, 1, 1);
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);
6916
7411
  gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
6917
7412
  gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
6918
7413
  gl.uniform3fv(u['uAmbient'] ?? null, env.ambient);
@@ -6979,10 +7474,9 @@ export class WebGL2Renderer {
6979
7474
  this.surfaceFog = FOG_RECEDE;
6980
7475
  /* Grain on by default, so every scene written before it was a choice looks unchanged. */
6981
7476
  gl.uniform1f(u['uGrain'] ?? null, 1);
6982
- /* No dither and no weighting: every draw whole, until a crossfade asks. See `setDitherFade`. */
6983
- this.oitWeight = 0;
6984
- this.ditherFade = 0;
6985
- 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);
6986
7480
  /* The draw's own ambient as it stands, which `bindMeshPass` has just cleared: a program built
6987
7481
  in the middle of a pass takes what the caller set. See `setAmbientSH`. */
6988
7482
  gl.uniform4fv(u['uAmbientSH[0]'] ?? null, this.ambientSH);
@@ -7021,11 +7515,18 @@ export class WebGL2Renderer {
7021
7515
  gl.uniform1f(u['uShadowStrength'] ?? null, this.sunShadows === null ? 0 : env.shadowStrength);
7022
7516
  gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
7023
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));
7024
7522
  gl.uniform1f(u['uShadowMaxDistance'] ?? null, this.quality.directionalShadowMaxDistance);
7025
7523
  gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
7026
7524
  gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
7027
7525
  gl.uniform1i(u['uShadowFilterTaps'] ?? null, this.quality.shadowFilterTaps);
7028
- gl.uniform3fv(u['uHighlightMin'] ?? null, env.highlightMin);
7526
+ /* The near corner, and in its w the frame the cutout dither moves with. See `uHighlightMin`.
7527
+ An environment written by hand may leave the box out, as the far corner says below. */
7528
+ const min = env.highlightMin;
7529
+ gl.uniform4f(u['uHighlightMin'] ?? null, min?.[0] ?? 0, min?.[1] ?? 0, min?.[2] ?? 0, this.cutoutFrame);
7029
7530
  /* The far corner, and the gain in its w. See `uHighlightMax`. */
7030
7531
  /* An environment written by hand may leave the box out; then there is none, as before. */
7031
7532
  const max = env.highlightMax ?? NO_HIGHLIGHT;
@@ -7328,6 +7829,9 @@ export class WebGL2Renderer {
7328
7829
  * light and its colour, each into the blur's own target.
7329
7830
  */
7330
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;
7331
7835
  /* An opaque lit draw into the frame writes its material's reflection too: see `reflectionKeeps`. */
7332
7836
  const keeps = this.reflectionKeeps();
7333
7837
  if (keeps)
@@ -7406,8 +7910,25 @@ export class WebGL2Renderer {
7406
7910
  this.useFlatProgram();
7407
7911
  }
7408
7912
  /** One draw of a mesh, as `drawMesh` describes, of the half of a skin `half` names. */
7409
- drawMeshHalf(mesh, model, depthLayer, tint, half) {
7410
- 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;
7411
7932
  /*
7412
7933
  * A rigged mesh takes the skinned program, an unrigged one the plain program.
7413
7934
  *
@@ -7435,9 +7956,8 @@ export class WebGL2Renderer {
7435
7956
  * when none were asked for — an unbound sampler would default to a shadow map's unit.
7436
7957
  */
7437
7958
  const cloth = wantSkin ? this.cloth : null;
7438
- const clothed = cloth === null
7439
- ? undefined
7440
- : 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);
7441
7961
  const bothUniforms = eight ? this.flatBoth8Uniforms : this.flatBothUniforms;
7442
7962
  const skinnedUniforms = eight ? this.flatSkinned8Uniforms : this.flatSkinnedUniforms;
7443
7963
  const both = wantSkin && wantMorph && bothUniforms !== null;
@@ -7450,24 +7970,27 @@ export class WebGL2Renderer {
7450
7970
  : morphed
7451
7971
  ? this.flatMorphedUniforms
7452
7972
  : null;
7453
- let u = clothed?.uniforms ?? variant ?? this.flatUniforms;
7454
- let program = clothed !== undefined
7455
- ? clothed.program
7456
- : skinned && morphed
7457
- ? eight
7458
- ? this.flatBoth8Program
7459
- : this.flatBothProgram
7460
- : 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
7461
7982
  ? eight
7462
- ? this.flatSkinned8Program
7463
- : this.flatSkinnedProgram
7464
- : morphed
7465
- ? this.flatMorphedProgram
7466
- : null;
7467
- /* A material with a model takes that model's program for the same vertex variant. */
7468
- const modelKind = this.currentMaterial?.model?.kind ?? null;
7469
- if (modelKind !== null) {
7470
- 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
7471
7994
  ? `cloth${eight ? 8 : 4}${mesh.morph !== null ? 'm' : '-'}`
7472
7995
  : skinned && morphed
7473
7996
  ? eight
@@ -7479,7 +8002,22 @@ export class WebGL2Renderer {
7479
8002
  : 'skinned'
7480
8003
  : morphed
7481
8004
  ? 'morphed'
7482
- : '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;
7483
8021
  program = modelled.program;
7484
8022
  u = modelled.uniforms;
7485
8023
  }
@@ -7658,6 +8196,9 @@ export class WebGL2Renderer {
7658
8196
  /* A material with a model takes that model's plain program; see `modelProgram`. */
7659
8197
  const modelKind = this.currentMaterial?.model?.kind ?? null;
7660
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;
7661
8202
  const u = modelled?.uniforms ?? this.flatUniforms;
7662
8203
  if (modelled === null)
7663
8204
  this.useFlatProgram();
@@ -8137,6 +8678,7 @@ export class WebGL2Renderer {
8137
8678
  /* Uncorrected, for the reason the lit program's copy of this line carries: the shaft's
8138
8679
  lookup does its own `* 0.5 + 0.5` and correcting the matrix as well doubles it. */
8139
8680
  gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, env.lightViewProj);
8681
+ gl.uniformMatrix4fv(u['uMovingLightViewProj'] ?? null, false, this.movingLightFor(env));
8140
8682
  gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
8141
8683
  gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
8142
8684
  gl.activeTexture(gl.TEXTURE0);
@@ -8350,6 +8892,12 @@ function litVertexOf(variant) {
8350
8892
  }
8351
8893
  const skinned = variant.startsWith('skinned') || variant.startsWith('both');
8352
8894
  const morphed = variant === 'morphed' || variant.startsWith('both');
8353
- const vertex = flatVert({ skinned, morphed, instanced: variant === 'instanced' });
8895
+ const animated = variant === 'animated';
8896
+ const vertex = flatVert({
8897
+ skinned,
8898
+ morphed,
8899
+ instanced: variant === 'instanced' || animated,
8900
+ animated,
8901
+ });
8354
8902
  return variant.endsWith('8') ? skinEight(vertex) : vertex;
8355
8903
  }