@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
@@ -90,7 +90,8 @@ import { clamp } from '../../../math/scalar.ts';
90
90
  import { attachContextLoss } from '../../contextLoss.ts';
91
91
  import { isWeakGpuFamily, webgl2RendererName } from '../../gpuCapability.ts';
92
92
  import { GpuTimer } from '../../gpuTimer.ts';
93
- import { compileProgram, uniformLocations } from '../../shader.ts';
93
+ import { compileProgram, finishProgram, startProgram, uniformLocations } from '../../shader.ts';
94
+ import type { StartedProgram } from '../../shader.ts';
94
95
  import {
95
96
  countUniformVectors,
96
97
  FULL_LIGHT_BUDGET,
@@ -104,6 +105,11 @@ import {
104
105
  packSurfaceOverlay,
105
106
  type SurfaceOverlay,
106
107
  } from '../../surfaceOverlay.ts';
108
+ import { PROJECTION_FLOATS, packSurfaceProjection, projects } from '../../surfaceProjection.ts';
109
+ import { orthographicDepthSpan } from '../../lightMatrix.ts';
110
+ import { LAYER_FLOATS, packSurfaceLayers } from '../../surfaceLayers.ts';
111
+ import type { SurfaceLayers } from '../../surfaceLayers.ts';
112
+ import type { SurfaceProjection } from '../../surfaceProjection.ts';
107
113
  import { FILM_FRAG, FILM_VERT } from '../../shaders/film.ts';
108
114
  import { SceneTarget } from '../../sceneTarget.ts';
109
115
  import type { ColourGradeLut } from '../../colourGrade.ts';
@@ -1049,6 +1055,37 @@ const IDENTITY_CLIP = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0
1049
1055
  /** Standing still, for a caller that asked for dust and not for wind. */
1050
1056
  const NO_DRIFT: Vec3 = [0, 0, 0];
1051
1057
 
1058
+ /** Whether two matrices hold the same sixteen numbers. */
1059
+ function sameMatrix(a: ArrayLike<number>, b: ArrayLike<number>): boolean {
1060
+ for (let k = 0; k < 16; k++) if (a[k] !== b[k]) return false;
1061
+ return true;
1062
+ }
1063
+
1064
+ /** How often `ready` asks the driver whether a program is done, and how many times before waiting. */
1065
+ const READY_POLL_MS = 4;
1066
+ const READY_MAX_POLLS = 500;
1067
+
1068
+ /** The halves a skin draws under the screen-space blur, each a program of its own. */
1069
+ const SPLIT_SKIN_HALVES = ['scene', 'diffuse', 'albedo'] as const;
1070
+
1071
+ /** A lit program with its uniform table, as every flat program is kept. */
1072
+ interface FlatProgram {
1073
+ program: WebGLProgram;
1074
+ uniforms: Record<string, WebGLUniformLocation>;
1075
+ }
1076
+
1077
+ /** The vertex variant a draw takes, and its program where it is not the plain one. */
1078
+ interface DrawVariant {
1079
+ /** What a model's program is keyed by: `plain`, `skinned8`, `cloth4m` and the rest. */
1080
+ name: string;
1081
+ skinned: boolean;
1082
+ morphed: boolean;
1083
+ cloth: { binding: GlClothBinding; particles: GlClothParticles } | null;
1084
+ clothed: FlatProgram | undefined;
1085
+ program: WebGLProgram | null;
1086
+ uniforms: Record<string, WebGLUniformLocation>;
1087
+ }
1088
+
1052
1089
  export class WebGL2Renderer implements RendererApi {
1053
1090
  /**
1054
1091
  * Private on purpose. The GL context never leaves this directory, so a
@@ -1347,6 +1384,23 @@ export class WebGL2Renderer implements RendererApi {
1347
1384
  string,
1348
1385
  { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> }
1349
1386
  >();
1387
+ /**
1388
+ * Model programs still compiling, where draws skip a compile (`pipelineCompile: 'skip'`) and the
1389
+ * context offers `KHR_parallel_shader_compile`; `parallelCompile` is its completion query then,
1390
+ * and null where draws wait for a program here instead. See `modelProgram`.
1391
+ */
1392
+ private readonly modelPending = new Map<string, StartedProgram>();
1393
+ /** What `drawVariantOf` answers, rewritten each draw rather than made. */
1394
+ private readonly drawVariant: DrawVariant = {
1395
+ name: 'plain',
1396
+ skinned: false,
1397
+ morphed: false,
1398
+ cloth: null,
1399
+ clothed: undefined,
1400
+ program: null,
1401
+ uniforms: {},
1402
+ };
1403
+ private readonly parallelCompile: { readonly COMPLETION_STATUS_KHR: number } | null;
1350
1404
  /** The last palette chosen, which an eye's axis is turned by. See `setSkinPalette`. */
1351
1405
  private paletteData: Float32Array | null = null;
1352
1406
 
@@ -1410,7 +1464,7 @@ export class WebGL2Renderer implements RendererApi {
1410
1464
  private oitReplaying = false;
1411
1465
  /** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
1412
1466
  private readonly surfaceScene = new Float32Array(4);
1413
- /** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
1467
+ /** The three lanes of `uWriteMode`, kept here because a `vec3` is written whole. */
1414
1468
  private oitWeight = 0;
1415
1469
  /**
1416
1470
  * How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
@@ -1418,6 +1472,7 @@ export class WebGL2Renderer implements RendererApi {
1418
1472
  */
1419
1473
  private surfaceFog = FOG_RECEDE;
1420
1474
  private ditherFade = 0;
1475
+ private ditherOpacity = 0;
1421
1476
  /** Said once, not per frame, where the context cannot give the float target the sum needs. */
1422
1477
  private oitRefused = false;
1423
1478
  /** Said once for the whole renderer. See `refuseOitMultisampled`. */
@@ -1697,6 +1752,28 @@ export class WebGL2Renderer implements RendererApi {
1697
1752
  private overlayTexture: WebGLTexture | null = null;
1698
1753
  /** Whether this device's lit stage has room for the overlay's vectors: asked once. */
1699
1754
  private overlayRoom: boolean | null = null;
1755
+ /**
1756
+ * The matrix the sun's dynamic shadow pass was last drawn with, uncorrected, and whether one has
1757
+ * been: what the moving layer is read through. See `movingLightFor`.
1758
+ */
1759
+ private readonly movingLightViewProj = new Float32Array(16);
1760
+ private movingLightKnown = false;
1761
+ /** Whether the lit stage has room for the moving layer's matrix, and whether its refusal was said. */
1762
+ private movingRoom: boolean | null = null;
1763
+ private warnedMovingRoom = false;
1764
+ /** The same answer for a material's projection, and whether its refusal was said. */
1765
+ private projectionRoom: boolean | null = null;
1766
+ private warnedProjectionRoom = false;
1767
+ /** `uWorldUv`, the current material's projection packed. See `surfaceProjection.ts`. */
1768
+ private readonly projection = new Float32Array(PROJECTION_FLOATS);
1769
+ /** The current material's `diffuseTransmission`, `uGlassTint.w`, 0 to 1. */
1770
+ private diffuseTransmission = 0;
1771
+ /** `uLayers`, the current material's layers packed. See `surfaceLayers.ts`. */
1772
+ private readonly layerFloats = new Float32Array(LAYER_FLOATS);
1773
+ /** Whether the lit stage has room for layers' two vectors, and which refusals were said. */
1774
+ private layersRoom: boolean | null = null;
1775
+ private warnedLayersRoom = false;
1776
+ private warnedLayersModelMap = false;
1700
1777
  private warnedOverlayRoom = false;
1701
1778
  private warnedOverlayBlocks = false;
1702
1779
  /** Whether a probe is being baked, so the passes that would recurse into one can tell. */
@@ -3024,6 +3101,16 @@ export class WebGL2Renderer implements RendererApi {
3024
3101
  * Measured on an RX 9070 XT through ANGLE/Vulkan: offered. `scripts/depth-survey.mjs` is what
3025
3102
  * asks, and it also reports what WebGPU can do.
3026
3103
  */
3104
+ /*
3105
+ * Where draws skip a compile, the query that says a program is done without waiting for it;
3106
+ * where the context has none, a program is waited for as it always was. See `modelProgram`.
3107
+ */
3108
+ this.parallelCompile =
3109
+ this.quality.pipelineCompile === 'skip'
3110
+ ? (gl.getExtension('KHR_parallel_shader_compile') as {
3111
+ readonly COMPLETION_STATUS_KHR: number;
3112
+ } | null)
3113
+ : null;
3027
3114
  this.clipControl = gl.getExtension('EXT_clip_control') as ClipControlExtension | null;
3028
3115
  if (REVERSED_DEPTH && this.clipControl !== null) {
3029
3116
  this.clipControl.clipControlEXT(
@@ -3361,7 +3448,36 @@ export class WebGL2Renderer implements RendererApi {
3361
3448
  * this before its first frame gets the right behaviour on both without asking which backend
3362
3449
  * it has, which is the contract `RendererApi` exists to keep.
3363
3450
  */
3364
- async ready(): Promise<void> {}
3451
+ /**
3452
+ * Resolve once every program a draw has started compiling has finished, adopting each: what a
3453
+ * loading screen awaits after drawing what its scene will use, where draws skip a compile. See
3454
+ * `RenderQuality.pipelineCompile`; nothing is started anywhere else here, so otherwise it is now.
3455
+ *
3456
+ * **It waits without holding the thread**, as the other backend's does: it asks the driver
3457
+ * whether each is done, which does not wait, and yields between asks. A driver that never says
3458
+ * done is asked the way that waits after `READY_MAX_POLLS`, so this cannot hang a loading screen.
3459
+ */
3460
+ async ready(): Promise<void> {
3461
+ for (let polls = 0; this.modelPending.size > 0 && !this.contextLost; polls++) {
3462
+ for (const [key, started] of this.modelPending) {
3463
+ const done =
3464
+ polls >= READY_MAX_POLLS ||
3465
+ this.parallelCompile === null ||
3466
+ this.gl.getProgramParameter(
3467
+ started.program,
3468
+ this.parallelCompile.COMPLETION_STATUS_KHR,
3469
+ ) === true;
3470
+ if (!done) continue;
3471
+ this.modelPending.delete(key);
3472
+ this.adoptModel(key, finishProgram(this.gl, started), started.label);
3473
+ }
3474
+ this.useFlatProgram();
3475
+ if (this.modelPending.size > 0) {
3476
+ // platform: browser default — a yield between two asks of the driver, in a browser module
3477
+ await new Promise<void>((resolve) => setTimeout(resolve, READY_POLL_MS));
3478
+ }
3479
+ }
3480
+ }
3365
3481
 
3366
3482
  dispose(options: { releaseContext?: boolean } = {}): void {
3367
3483
  if (this.disposed) return;
@@ -3845,18 +3961,43 @@ export class WebGL2Renderer implements RendererApi {
3845
3961
  kind: SurfaceModelKind,
3846
3962
  /** Which half of a skin it draws under the screen-space blur. See `SkinHalf`. */
3847
3963
  half: SkinHalf = 'whole',
3848
- ): { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> } {
3964
+ ): { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> } | null {
3849
3965
  const key = `${variant}|${kind}|${half}`;
3850
3966
  const held = this.modelFlat.get(key);
3851
3967
  if (held !== undefined) return held;
3852
3968
  const { gl } = this;
3853
3969
  const label = `flat.${variant}.${kind}.${half}`;
3854
- const program = compileProgram(
3855
- gl,
3856
- modelVertexOf(variant, kind),
3857
- skinHalfBound(modelBound(this.flatFragSource, kind), half),
3858
- label,
3859
- );
3970
+ const vertex = modelVertexOf(variant, kind);
3971
+ const fragment = skinHalfBound(modelBound(this.flatFragSource, kind), half);
3972
+ /*
3973
+ * **Where draws skip a compile, none until the driver says asking will not wait**: the program
3974
+ * is started at the first draw that wants it and that draw, and every one before it is ready,
3975
+ * is left out. See `RenderQuality.pipelineCompile`.
3976
+ */
3977
+ if (this.parallelCompile !== null) {
3978
+ const started = this.modelPending.get(key);
3979
+ if (started === undefined) {
3980
+ this.modelPending.set(key, startProgram(gl, vertex, fragment, label));
3981
+ return null;
3982
+ }
3983
+ if (
3984
+ gl.getProgramParameter(started.program, this.parallelCompile.COMPLETION_STATUS_KHR) !== true
3985
+ ) {
3986
+ return null;
3987
+ }
3988
+ this.modelPending.delete(key);
3989
+ return this.adoptModel(key, finishProgram(gl, started), label);
3990
+ }
3991
+ return this.adoptModel(key, compileProgram(gl, vertex, fragment, label), label);
3992
+ }
3993
+
3994
+ /** A model program compiled: kept, written to by every material setter, and fed the pass. */
3995
+ private adoptModel(
3996
+ key: string,
3997
+ program: WebGLProgram,
3998
+ label: string,
3999
+ ): { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> } {
4000
+ const { gl } = this;
3860
4001
  const made = { program, uniforms: uniformLocations(gl, program, label) };
3861
4002
  this.modelFlat.set(key, made);
3862
4003
  this.flatTargets.push(made);
@@ -4182,9 +4323,15 @@ export class WebGL2Renderer implements RendererApi {
4182
4323
  * and read neither — one decision for every program, as `setMaterial` says.
4183
4324
  */
4184
4325
  const modelMap = material?.modelMap ?? null;
4326
+ /* A layered material's mask goes where a model's map would: see `SurfaceMaterial.layers`. */
4327
+ const layers = this.layersOf(material);
4328
+ const boundMap = modelMap ?? layers?.mask ?? null;
4185
4329
  gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
4186
4330
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
4187
- if (modelMap !== null) modelMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
4331
+ if (boundMap !== null) boundMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
4332
+ packSurfaceLayers(layers, this.layerFloats);
4333
+ if (layers !== null) this.askLit('LAYERED');
4334
+ gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
4188
4335
  gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
4189
4336
  packModel(
4190
4337
  material?.model ?? null,
@@ -4197,6 +4344,15 @@ export class WebGL2Renderer implements RendererApi {
4197
4344
  gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
4198
4345
  /* The draw's overlay, which a program born mid-pass must wear too: see `setSurfaceOverlay`. */
4199
4346
  gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
4347
+ gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
4348
+ /* How much light from behind the surface lets through, in the glass tint's spare lane. */
4349
+ this.diffuseTransmission = Math.min(Math.max(material?.diffuseTransmission ?? 0, 0), 1) || 0;
4350
+ gl.uniform4f(u['uGlassTint'] ?? null, 1, 1, 1, this.diffuseTransmission);
4351
+ /* Maps placed by the world, where the material asks and the lit stage has room: worldUv.ts. */
4352
+ const projection = material?.projection ?? null;
4353
+ packSurfaceProjection(this.projectionFits(projection) ? projection : null, this.projection);
4354
+ if ((this.projection[0] as number) > 0) this.askLit('WORLD_UVS');
4355
+ gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
4200
4356
 
4201
4357
  /* The albedo's effects table, or the stand-in the shader reads as none. Before the early
4202
4358
  return, so a material with no albedo still leaves a complete texture on the unit. */
@@ -4371,17 +4527,110 @@ export class WebGL2Renderer implements RendererApi {
4371
4527
  /** What the lit shader at this renderer's budget leaves of the limit, with no overlay. */
4372
4528
  private overlayRoomLeft(): number {
4373
4529
  return (
4374
- this.fragmentVectorLimit() - countUniformVectors(this.flatFragAt(this.lightBudget, false))
4530
+ this.fragmentVectorLimit() -
4531
+ countUniformVectors(this.flatFragAt(this.lightBudget, { SURFACE_OVERLAY: false }))
4375
4532
  );
4376
4533
  }
4377
4534
 
4378
4535
  /** Whether the lit shader with the overlay compiled in fits this GPU: counted once. */
4379
4536
  private overlayFits(): boolean {
4380
- this.overlayRoom ??=
4381
- countUniformVectors(this.flatFragAt(this.lightBudget, true)) <= this.fragmentVectorLimit();
4537
+ this.overlayRoom ??= this.switchFits('SURFACE_OVERLAY');
4382
4538
  return this.overlayRoom;
4383
4539
  }
4384
4540
 
4541
+ /**
4542
+ * Whether a material's projection can be drawn: none asked, or the lit stage has the room for its
4543
+ * one vector. **Refused, said once, where it has not**, as the overlay is, and the material's
4544
+ * maps are then laid by the mesh's coordinates, as they were before it existed.
4545
+ */
4546
+ private projectionFits(projection: SurfaceProjection | null): boolean {
4547
+ if (!projects(projection)) return true;
4548
+ this.projectionRoom ??= this.switchFits('WORLD_UVS');
4549
+ if (!this.projectionRoom && !this.warnedProjectionRoom) {
4550
+ this.warnedProjectionRoom = true;
4551
+ console.warn(
4552
+ "Renderer: a material's projection is refused on this device. It takes one fragment " +
4553
+ 'uniform vector and the lit shader already uses all ' +
4554
+ `${this.fragmentVectorLimit()} this GPU offers; its maps follow the mesh's coordinates. ` +
4555
+ 'A lower `maxLights` makes the room, or WebGPU has it.',
4556
+ );
4557
+ }
4558
+ return this.projectionRoom;
4559
+ }
4560
+
4561
+ /**
4562
+ * What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
4563
+ * where the lit stage carries one (`MOVING_SUN`); the environment's otherwise, which every caller
4564
+ * drawing both layers with one matrix hands it anyway.
4565
+ */
4566
+ private movingLightFor(env: Environment): ReadonlyMat4 {
4567
+ return this.movingLightKnown && this.litOn.MOVING_SUN
4568
+ ? this.movingLightViewProj
4569
+ : env.lightViewProj;
4570
+ }
4571
+
4572
+ /**
4573
+ * Turn the moving layer's own matrix on the first time a frame draws that layer with one the
4574
+ * environment's does not share. **Refused, said once, where the lit stage has no room for its five
4575
+ * vectors**: the layer is then read through the environment's matrix, so its shadows land where
4576
+ * that matrix puts them, and a caller there draws both layers with one.
4577
+ */
4578
+ private askMovingSun(env: Environment): void {
4579
+ if (this.litWanted.MOVING_SUN || !this.movingLightKnown) return;
4580
+ if (sameMatrix(this.movingLightViewProj, env.lightViewProj)) return;
4581
+ this.movingRoom ??= this.switchFits('MOVING_SUN');
4582
+ if (this.movingRoom) {
4583
+ this.askLit('MOVING_SUN');
4584
+ return;
4585
+ }
4586
+ if (this.warnedMovingRoom) return;
4587
+ this.warnedMovingRoom = true;
4588
+ console.warn(
4589
+ "Renderer: the sun's moving layer cannot be read through a matrix of its own on this " +
4590
+ 'device; it takes five fragment uniform vectors the lit shader does not have. It is read ' +
4591
+ "through the environment's `lightViewProj`, so draw the dynamic shadow pass with that " +
4592
+ 'matrix here, or lower `maxLights` to make the room. WebGPU has it.',
4593
+ );
4594
+ }
4595
+
4596
+ /**
4597
+ * A material's layers where they can be drawn: none asked, a model's map in the mask's place, or
4598
+ * no room for their two vectors each leave one layer, the last two said once each.
4599
+ */
4600
+ private layersOf(material: SurfaceMaterial | null): SurfaceLayers<SurfaceTexture> | null {
4601
+ const layers = material?.layers ?? null;
4602
+ if (layers === null || layers.repeats.length === 0) return null;
4603
+ if ((material?.modelMap ?? null) !== null) {
4604
+ if (!this.warnedLayersModelMap) {
4605
+ this.warnedLayersModelMap = true;
4606
+ console.warn(
4607
+ "Renderer: a material's layers are drawn as one: its mask is read where a model's map " +
4608
+ 'goes, and this material carries a `modelMap` too.',
4609
+ );
4610
+ }
4611
+ return null;
4612
+ }
4613
+ this.layersRoom ??= this.switchFits('LAYERED');
4614
+ if (this.layersRoom) return layers as SurfaceLayers<SurfaceTexture>;
4615
+ if (!this.warnedLayersRoom) {
4616
+ this.warnedLayersRoom = true;
4617
+ console.warn(
4618
+ "Renderer: a material's layers are refused on this device. They take two fragment uniform " +
4619
+ 'vectors the lit shader does not have; the material is drawn as its base layer. A lower ' +
4620
+ '`maxLights` makes the room, or WebGPU has it.',
4621
+ );
4622
+ }
4623
+ return null;
4624
+ }
4625
+
4626
+ /** Whether the lit shader with `feature` compiled in fits this GPU's fragment uniform vectors. */
4627
+ private switchFits(feature: LitSwitch): boolean {
4628
+ const asIf: Partial<Record<LitSwitch, boolean>> = { [feature]: true };
4629
+ return (
4630
+ countUniformVectors(this.flatFragAt(this.lightBudget, asIf)) <= this.fragmentVectorLimit()
4631
+ );
4632
+ }
4633
+
4385
4634
  /**
4386
4635
  * How bright the environment the surfaces drawn next reflect is. 1 is the default and identity.
4387
4636
  *
@@ -4525,9 +4774,37 @@ export class WebGL2Renderer implements RendererApi {
4525
4774
  this.useFlatProgram();
4526
4775
  }
4527
4776
 
4528
- /** `uWriteMode`: the order-independent weight and the dither, which share one uniform row. */
4777
+ /**
4778
+ * Whether the draws that follow spend their own opacity on a screen door rather than on blending:
4779
+ * an instance's `MeshInstances.alphas`, or a vertex's channel alpha, keeps that share of the
4780
+ * pixels `setDitherFade` would and draws the rest not at all. So an opaque or cut-out batch fades
4781
+ * one instance at a time, with no sorting and no blend, and an instance at 1 is drawn whole.
4782
+ *
4783
+ * Off is what `bindMeshPass` restores. It shares the fade's pattern, so a crossfade at `t` and
4784
+ * `-t` of an instance at opacity `a` covers that instance's share once, without holes. Material
4785
+ * state like `setDitherFade`; instanced draws read it, and shadow casters do not.
4786
+ *
4787
+ * What it gives up: a fade reads as grain while it lasts, which a temporal resolve smooths and a
4788
+ * plain frame does not; a blended draw that asks spends its opacity here and blends none of it;
4789
+ * and a lightmapped batch carries regions where the opacities would be, so it has none to fade.
4790
+ */
4791
+ setDitherOpacity(on: boolean): void {
4792
+ if (this.contextLost) return;
4793
+ this.materials.dirty();
4794
+ this.ditherOpacity = on ? 1 : 0;
4795
+ for (const target of this.flatTargets) {
4796
+ this.gl.useProgram(target.program);
4797
+ this.writeMode(target.uniforms);
4798
+ }
4799
+ this.useFlatProgram();
4800
+ }
4801
+
4802
+ /**
4803
+ * `uWriteMode`: the order-independent weight, the fade and whether the opacity is dithered, which
4804
+ * share one uniform row.
4805
+ */
4529
4806
  private writeMode(u: Record<string, WebGLUniformLocation>): void {
4530
- this.gl.uniform2f(u['uWriteMode'] ?? null, this.oitWeight, this.ditherFade);
4807
+ this.gl.uniform3f(u['uWriteMode'] ?? null, this.oitWeight, this.ditherFade, this.ditherOpacity);
4531
4808
  }
4532
4809
 
4533
4810
  /**
@@ -4852,6 +5129,8 @@ export class WebGL2Renderer implements RendererApi {
4852
5129
  modelKind === null
4853
5130
  ? null
4854
5131
  : this.modelProgram(clip === null ? 'instanced' : 'animated', modelKind);
5132
+ /* Its model's program still compiling, where draws skip a compile: left out until it lands. */
5133
+ if (modelKind !== null && modelled === null) return;
4855
5134
  const program =
4856
5135
  modelled?.program ?? (clip === null ? this.flatInstancedProgram : this.flatAnimatedProgram);
4857
5136
  const u =
@@ -4996,7 +5275,14 @@ export class WebGL2Renderer implements RendererApi {
4996
5275
  /* w: this framebuffer's first row is the bottom, so up the screen is up the rows. */
4997
5276
  const tint = options.refractTint ?? WHITE_TINT;
4998
5277
  gl.uniform4f(u['uRefractTint'] ?? null, tint[0] ?? 1, tint[1] ?? 1, tint[2] ?? 1, 1);
4999
- gl.uniform3fv(u['uGlassTint'] ?? null, glass.tint);
5278
+ /* The pane's colour; w is the material's light through from behind, which glass never reads. */
5279
+ gl.uniform4f(
5280
+ u['uGlassTint'] ?? null,
5281
+ glass.tint[0] ?? 1,
5282
+ glass.tint[1] ?? 1,
5283
+ glass.tint[2] ?? 1,
5284
+ this.diffuseTransmission,
5285
+ );
5000
5286
  return true;
5001
5287
  }
5002
5288
 
@@ -6144,6 +6430,11 @@ export class WebGL2Renderer implements RendererApi {
6144
6430
  this.shadowPassActive = true;
6145
6431
  this.activeSunLayer = layer;
6146
6432
  this.glassCasters.clear();
6433
+ /* The moving layer is read through the matrix it is drawn with: see `movingLightFor`. */
6434
+ if (layer === 'dynamic') {
6435
+ this.movingLightViewProj.set(lightViewProj as Float32Array);
6436
+ this.movingLightKnown = true;
6437
+ }
6147
6438
  /* The static layer opens first, so a frame's peel counts from here. See `peelFilled`. */
6148
6439
  if (layer === 'static') this.peelFilled = false;
6149
6440
  /*
@@ -6560,7 +6851,11 @@ export class WebGL2Renderer implements RendererApi {
6560
6851
  * throws a sentence with the device's numbers in it rather than a bare driver string.
6561
6852
  */
6562
6853
  /** The lit fragment source this renderer's profile compiles, at a light budget. */
6563
- private flatFragAt(budget: LightBudget, overlay = this.litOn.SURFACE_OVERLAY): string {
6854
+ private flatFragAt(
6855
+ budget: LightBudget,
6856
+ /* A switch counted as it would be, for asking whether it fits: see `switchFits`. */
6857
+ asIf: Partial<Record<LitSwitch, boolean>> = {},
6858
+ ): string {
6564
6859
  return flatFrag({
6565
6860
  pointShadows: this.quality.pointShadows,
6566
6861
  directionalShadows: this.quality.directionalShadows,
@@ -6573,8 +6868,11 @@ export class WebGL2Renderer implements RendererApi {
6573
6868
  surfaceEffects: this.litOn.SURFACE_EFFECTS,
6574
6869
  driftLight: this.litOn.DRIFT_LIGHT,
6575
6870
  physicalSpecular: this.litOn.PHYSICAL_SPECULAR,
6576
- surfaceOverlay: overlay,
6871
+ surfaceOverlay: asIf.SURFACE_OVERLAY ?? this.litOn.SURFACE_OVERLAY,
6577
6872
  reflectionMaps: this.litOn.REFLECTION_MAPS,
6873
+ worldUvs: asIf.WORLD_UVS ?? this.litOn.WORLD_UVS,
6874
+ movingSun: asIf.MOVING_SUN ?? this.litOn.MOVING_SUN,
6875
+ layered: asIf.LAYERED ?? this.litOn.LAYERED,
6578
6876
  maxLights: budget.maxLights,
6579
6877
  maxAreaLights: budget.maxAreaLights,
6580
6878
  });
@@ -6662,6 +6960,9 @@ export class WebGL2Renderer implements RendererApi {
6662
6960
  this.clothFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
6663
6961
  }
6664
6962
 
6963
+ /* Started against the source before this switch: begun again at the next draw that asks. */
6964
+ for (const started of this.modelPending.values()) gl.deleteProgram(started.program);
6965
+ this.modelPending.clear();
6665
6966
  for (const [key, entry] of this.modelFlat) {
6666
6967
  const [variant, kind, half] = key.split('|') as [string, SurfaceModelKind, SkinHalf];
6667
6968
  const label = `flat.${variant}.${kind}.${half}`;
@@ -8450,6 +8751,7 @@ export class WebGL2Renderer implements RendererApi {
8450
8751
  ambient: env?.ambient ?? [0, 0, 0],
8451
8752
  sunShadow,
8452
8753
  lightViewProj: env?.lightViewProj ?? IDENTITY_MAT4,
8754
+ movingLightViewProj: env === null ? IDENTITY_MAT4 : this.movingLightFor(env),
8453
8755
  /* The placeholder rather than null where a map is merely absent, for the reason
8454
8756
  `emptyTexture.ts` gives. It is black, which as a depth is an occluder at the light,
8455
8757
  so it is safe only because `sunShadow` is 0 whenever these maps do not exist. */
@@ -8705,6 +9007,12 @@ export class WebGL2Renderer implements RendererApi {
8705
9007
  this.currentSurfaceTexture = null;
8706
9008
  /* Nor an overlay: the atlas's unit is given the empty texture with the refraction copy's. */
8707
9009
  this.overlayFloats.fill(0);
9010
+ /* Nor a projection: maps follow the mesh's coordinates until a material asks otherwise. */
9011
+ this.projection.fill(0);
9012
+ /* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
9013
+ this.diffuseTransmission = 0;
9014
+ /* Nor layers: one layer until a material asks for more. */
9015
+ this.layerFloats.fill(0);
8708
9016
  this.overlayMaps = null;
8709
9017
  this.overlayTexture = null;
8710
9018
  /* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
@@ -8718,6 +9026,12 @@ export class WebGL2Renderer implements RendererApi {
8718
9026
  this.environmentDials[2] = 1;
8719
9027
  this.emission[0] = env.emissiveGain;
8720
9028
  this.emission[1] = env.nightFactor;
9029
+ /* The moving layer's own matrix, the first time a frame draws that layer with one. */
9030
+ this.askMovingSun(env);
9031
+ /* No dither and no weighting: every draw whole, until a crossfade or an opacity asks. */
9032
+ this.oitWeight = 0;
9033
+ this.ditherFade = 0;
9034
+ this.ditherOpacity = 0;
8721
9035
  for (const target of this.flatTargets) {
8722
9036
  this.gl.useProgram(target.program);
8723
9037
  this.writeMeshPassState(target.uniforms, camera, env);
@@ -8871,11 +9185,14 @@ export class WebGL2Renderer implements RendererApi {
8871
9185
  gl.uniform1i(u['uRefractScene'] ?? null, REFRACT_SCENE_TEXTURE_UNIT);
8872
9186
  /* The draws' overlay, none at a pass's start, and its atlas above: see `setSurfaceOverlay`. */
8873
9187
  gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
9188
+ gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
9189
+ gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
8874
9190
  /* Neither refracting nor glass, which is every draw that does not say otherwise. See
8875
9191
  `bindSeeThrough` for the four numbers in the one vector. */
8876
9192
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
8877
9193
  gl.uniform4f(u['uRefractTint'] ?? null, 1, 1, 1, 1);
8878
- gl.uniform3fv(u['uGlassTint'] ?? null, WHITE_TINT);
9194
+ /* White, and no light through from behind: no material at a pass's start. */
9195
+ gl.uniform4f(u['uGlassTint'] ?? null, 1, 1, 1, this.diffuseTransmission);
8879
9196
  gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
8880
9197
  gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
8881
9198
  gl.uniform3fv(u['uAmbient'] ?? null, env.ambient);
@@ -8942,10 +9259,9 @@ export class WebGL2Renderer implements RendererApi {
8942
9259
  this.surfaceFog = FOG_RECEDE;
8943
9260
  /* Grain on by default, so every scene written before it was a choice looks unchanged. */
8944
9261
  gl.uniform1f(u['uGrain'] ?? null, 1);
8945
- /* No dither and no weighting: every draw whole, until a crossfade asks. See `setDitherFade`. */
8946
- this.oitWeight = 0;
8947
- this.ditherFade = 0;
8948
- gl.uniform2f(u['uWriteMode'] ?? null, 0, 0);
9262
+ /* The weighting and the dither as they stand, which `bindMeshPass` has just cleared: a program
9263
+ built in the middle of a pass takes what the caller set. See `setDitherFade`. */
9264
+ this.writeMode(u);
8949
9265
  /* The draw's own ambient as it stands, which `bindMeshPass` has just cleared: a program built
8950
9266
  in the middle of a pass takes what the caller set. See `setAmbientSH`. */
8951
9267
  gl.uniform4fv(u['uAmbientSH[0]'] ?? null, this.ambientSH);
@@ -8993,6 +9309,10 @@ export class WebGL2Renderer implements RendererApi {
8993
9309
  gl.uniform1f(u['uShadowStrength'] ?? null, this.sunShadows === null ? 0 : env.shadowStrength);
8994
9310
  gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
8995
9311
  gl.uniform1f(u['uShadowDepthSpan'] ?? null, env.shadowDepthSpan);
9312
+ /* The moving layer's own matrix and span, where the program reads one: see `movingLightFor`. */
9313
+ const moving = this.movingLightFor(env);
9314
+ gl.uniformMatrix4fv(u['uMovingLightViewProj'] ?? null, false, moving);
9315
+ gl.uniform1f(u['uMovingShadowDepthSpan'] ?? null, orthographicDepthSpan(moving));
8996
9316
  gl.uniform1f(u['uShadowMaxDistance'] ?? null, this.quality.directionalShadowMaxDistance);
8997
9317
  gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
8998
9318
  gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
@@ -9330,6 +9650,8 @@ export class WebGL2Renderer implements RendererApi {
9330
9650
  * light and its colour, each into the blur's own target.
9331
9651
  */
9332
9652
  const splits = this.skinSplits();
9653
+ /* A split skin draws its three halves or none, where a half may still be compiling. */
9654
+ if (splits && this.parallelCompile !== null && !this.skinHalvesReady(mesh)) return;
9333
9655
  /* An opaque lit draw into the frame writes its material's reflection too: see `reflectionKeeps`. */
9334
9656
  const keeps = this.reflectionKeeps();
9335
9657
  if (keeps) this.reflectionTargets?.on();
@@ -9410,14 +9732,25 @@ export class WebGL2Renderer implements RendererApi {
9410
9732
  }
9411
9733
 
9412
9734
  /** One draw of a mesh, as `drawMesh` describes, of the half of a skin `half` names. */
9413
- private drawMeshHalf(
9414
- mesh: Mesh,
9415
- model: ReadonlyMat4,
9416
- depthLayer: number,
9417
- tint: Vec3 | null,
9418
- half: SkinHalf,
9419
- ): void {
9420
- const { gl } = this;
9735
+ /**
9736
+ * Whether every half a split skin draws has its program, starting any that has not: a skin drawn
9737
+ * without its kept halves would lose its diffuse light for the frames they take to compile.
9738
+ */
9739
+ private skinHalvesReady(mesh: Mesh): boolean {
9740
+ const variant = this.drawVariantOf(mesh).name;
9741
+ let ready = true;
9742
+ for (const half of SPLIT_SKIN_HALVES) {
9743
+ if (this.modelProgram(variant, 'skin', half) === null) ready = false;
9744
+ }
9745
+ return ready;
9746
+ }
9747
+
9748
+ /**
9749
+ * The vertex variant a draw of `mesh` takes now, and its program and uniforms where it is not the
9750
+ * plain one: what a model's program is keyed by, and what `drawMeshHalf` draws with.
9751
+ */
9752
+ private drawVariantOf(mesh: Mesh): DrawVariant {
9753
+ const out = this.drawVariant;
9421
9754
  /*
9422
9755
  * A rigged mesh takes the skinned program, an unrigged one the plain program.
9423
9756
  *
@@ -9445,10 +9778,8 @@ export class WebGL2Renderer implements RendererApi {
9445
9778
  * when none were asked for — an unbound sampler would default to a shadow map's unit.
9446
9779
  */
9447
9780
  const cloth = wantSkin ? this.cloth : null;
9448
- const clothed =
9449
- cloth === null
9450
- ? undefined
9451
- : this.clothFlat.get(`${eight ? 8 : 4}|${mesh.morph !== null ? 'm' : '-'}`);
9781
+ const clothKey = `${eight ? 8 : 4}|${mesh.morph !== null ? 'm' : '-'}`;
9782
+ const clothed = cloth === null ? undefined : this.clothFlat.get(clothKey);
9452
9783
  const bothUniforms = eight ? this.flatBoth8Uniforms : this.flatBothUniforms;
9453
9784
  const skinnedUniforms = eight ? this.flatSkinned8Uniforms : this.flatSkinnedUniforms;
9454
9785
  const both = wantSkin && wantMorph && bothUniforms !== null;
@@ -9462,8 +9793,12 @@ export class WebGL2Renderer implements RendererApi {
9462
9793
  : morphed
9463
9794
  ? this.flatMorphedUniforms
9464
9795
  : null;
9465
- let u = clothed?.uniforms ?? variant ?? this.flatUniforms;
9466
- let program =
9796
+ out.skinned = skinned;
9797
+ out.morphed = morphed;
9798
+ out.cloth = cloth;
9799
+ out.clothed = clothed;
9800
+ out.uniforms = clothed?.uniforms ?? variant ?? this.flatUniforms;
9801
+ out.program =
9467
9802
  clothed !== undefined
9468
9803
  ? clothed.program
9469
9804
  : skinned && morphed
@@ -9477,26 +9812,41 @@ export class WebGL2Renderer implements RendererApi {
9477
9812
  : morphed
9478
9813
  ? this.flatMorphedProgram
9479
9814
  : null;
9815
+ out.name =
9816
+ clothed !== undefined
9817
+ ? `cloth${eight ? 8 : 4}${mesh.morph !== null ? 'm' : '-'}`
9818
+ : skinned && morphed
9819
+ ? eight
9820
+ ? 'both8'
9821
+ : 'both'
9822
+ : skinned
9823
+ ? eight
9824
+ ? 'skinned8'
9825
+ : 'skinned'
9826
+ : morphed
9827
+ ? 'morphed'
9828
+ : 'plain';
9829
+ return out;
9830
+ }
9831
+
9832
+ private drawMeshHalf(
9833
+ mesh: Mesh,
9834
+ model: ReadonlyMat4,
9835
+ depthLayer: number,
9836
+ tint: Vec3 | null,
9837
+ half: SkinHalf,
9838
+ ): void {
9839
+ const { gl } = this;
9840
+ const drawn = this.drawVariantOf(mesh);
9841
+ const { skinned, morphed, cloth, clothed } = drawn;
9842
+ let u = drawn.uniforms;
9843
+ let program = drawn.program;
9480
9844
  /* A material with a model takes that model's program for the same vertex variant. */
9481
9845
  const modelKind = this.currentMaterial?.model?.kind ?? null;
9482
9846
  if (modelKind !== null) {
9483
- const modelled = this.modelProgram(
9484
- clothed !== undefined
9485
- ? `cloth${eight ? 8 : 4}${mesh.morph !== null ? 'm' : '-'}`
9486
- : skinned && morphed
9487
- ? eight
9488
- ? 'both8'
9489
- : 'both'
9490
- : skinned
9491
- ? eight
9492
- ? 'skinned8'
9493
- : 'skinned'
9494
- : morphed
9495
- ? 'morphed'
9496
- : 'plain',
9497
- modelKind,
9498
- half,
9499
- );
9847
+ const modelled = this.modelProgram(drawn.name, modelKind, half);
9848
+ /* Still compiling, where draws skip a compile: left out until it lands. */
9849
+ if (modelled === null) return;
9500
9850
  program = modelled.program;
9501
9851
  u = modelled.uniforms;
9502
9852
  }
@@ -9664,6 +10014,8 @@ export class WebGL2Renderer implements RendererApi {
9664
10014
  /* A material with a model takes that model's plain program; see `modelProgram`. */
9665
10015
  const modelKind = this.currentMaterial?.model?.kind ?? null;
9666
10016
  const modelled = modelKind === null ? null : this.modelProgram('plain', modelKind);
10017
+ /* Still compiling, where draws skip a compile: left out until it lands. */
10018
+ if (modelKind !== null && modelled === null) return;
9667
10019
  const u = modelled?.uniforms ?? this.flatUniforms;
9668
10020
  if (modelled === null) this.useFlatProgram();
9669
10021
  else gl.useProgram(modelled.program);
@@ -10195,6 +10547,7 @@ export class WebGL2Renderer implements RendererApi {
10195
10547
  /* Uncorrected, for the reason the lit program's copy of this line carries: the shaft's
10196
10548
  lookup does its own `* 0.5 + 0.5` and correcting the matrix as well doubles it. */
10197
10549
  gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, env.lightViewProj);
10550
+ gl.uniformMatrix4fv(u['uMovingLightViewProj'] ?? null, false, this.movingLightFor(env));
10198
10551
  gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
10199
10552
  gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
10200
10553