@driftengine/core 4.10.0 → 4.11.1

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 (165) hide show
  1. package/CHANGELOG.json +107 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +6 -5
  4. package/dist/index.js +2 -2
  5. package/dist/render/ambientOcclusionPass.d.ts +3 -2
  6. package/dist/render/ambientOcclusionPass.js +4 -2
  7. package/dist/render/backend/api.d.ts +21 -1
  8. package/dist/render/backend/webgl2/renderer.d.ts +141 -3
  9. package/dist/render/backend/webgl2/renderer.js +454 -42
  10. package/dist/render/backend/webgpu/buffers.d.ts +5 -0
  11. package/dist/render/backend/webgpu/buffers.js +98 -48
  12. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  13. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  14. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  15. package/dist/render/backend/webgpu/clothOps.js +78 -0
  16. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  17. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  18. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  19. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  20. package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
  21. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  22. package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
  23. package/dist/render/backend/webgpu/flatPass.js +2 -1
  24. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  25. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  26. package/dist/render/backend/webgpu/latePass.js +1 -0
  27. package/dist/render/backend/webgpu/motionPass.js +15 -6
  28. package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
  29. package/dist/render/backend/webgpu/pipelineCache.js +39 -1
  30. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  31. package/dist/render/backend/webgpu/renderer.d.ts +100 -1
  32. package/dist/render/backend/webgpu/renderer.js +423 -95
  33. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  34. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  35. package/dist/render/backend/webgpu/staticBundles.js +2 -1
  36. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  37. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  38. package/dist/render/globalMediumPass.d.ts +2 -0
  39. package/dist/render/globalMediumPass.js +1 -0
  40. package/dist/render/indexWidth.d.ts +17 -0
  41. package/dist/render/indexWidth.js +23 -0
  42. package/dist/render/instances.d.ts +13 -1
  43. package/dist/render/instances.js +10 -0
  44. package/dist/render/lightMatrix.d.ts +11 -0
  45. package/dist/render/lightMatrix.js +14 -0
  46. package/dist/render/mesh.d.ts +9 -0
  47. package/dist/render/mesh.js +38 -14
  48. package/dist/render/occlusionFade.d.ts +21 -0
  49. package/dist/render/occlusionFade.js +27 -0
  50. package/dist/render/renderQuality.d.ts +30 -0
  51. package/dist/render/renderQuality.js +5 -0
  52. package/dist/render/rendererHarness.d.ts +7 -0
  53. package/dist/render/rendererHarness.js +12 -0
  54. package/dist/render/sceneTarget.d.ts +2 -0
  55. package/dist/render/sceneTarget.js +1 -1
  56. package/dist/render/shader.d.ts +15 -0
  57. package/dist/render/shader.js +41 -0
  58. package/dist/render/shaders/ambientOcclusion.js +12 -1
  59. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  60. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  61. package/dist/render/shaders/flat/glassTint.js +3 -0
  62. package/dist/render/shaders/flat/index.d.ts +15 -0
  63. package/dist/render/shaders/flat/index.js +24 -4
  64. package/dist/render/shaders/flat/layered.d.ts +27 -0
  65. package/dist/render/shaders/flat/layered.js +198 -0
  66. package/dist/render/shaders/flat/main.d.ts +1 -1
  67. package/dist/render/shaders/flat/main.js +50 -16
  68. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  69. package/dist/render/shaders/flat/mapReads.js +38 -0
  70. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  71. package/dist/render/shaders/flat/movingSun.js +46 -0
  72. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  73. package/dist/render/shaders/flat/normalMap.js +23 -3
  74. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  75. package/dist/render/shaders/flat/overlay.js +41 -19
  76. package/dist/render/shaders/flat/preamble.js +23 -5
  77. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  78. package/dist/render/shaders/flat/worldUv.js +113 -0
  79. package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
  80. package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
  81. package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
  82. package/dist/render/shaders/generated/flat.wgsl.js +791 -391
  83. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  84. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  85. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  86. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  87. package/dist/render/shaders/globalMedium.js +9 -1
  88. package/dist/render/shaders/lightVolume.js +9 -1
  89. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  90. package/dist/render/shaders/tangentFrame.js +16 -3
  91. package/dist/render/skinnedClothRun.d.ts +28 -0
  92. package/dist/render/skinnedClothRun.js +103 -35
  93. package/dist/render/surfaceLayers.d.ts +107 -0
  94. package/dist/render/surfaceLayers.js +82 -0
  95. package/dist/render/surfaceOverlay.d.ts +16 -3
  96. package/dist/render/surfaceOverlay.js +11 -6
  97. package/dist/render/surfaceProjection.d.ts +34 -0
  98. package/dist/render/surfaceProjection.js +30 -0
  99. package/dist/render/surfaceTexture.d.ts +39 -0
  100. package/dist/render/transmission.d.ts +21 -0
  101. package/dist/render/transmission.js +21 -0
  102. package/dist/render/vertexPacking.d.ts +48 -0
  103. package/dist/render/vertexPacking.js +68 -0
  104. package/dist/ui/splash.d.ts +42 -2
  105. package/dist/ui/splash.js +78 -13
  106. package/package.json +6 -6
  107. package/src/index.ts +6 -2
  108. package/src/render/ambientOcclusionPass.ts +4 -1
  109. package/src/render/backend/api.ts +31 -0
  110. package/src/render/backend/webgl2/renderer.ts +519 -56
  111. package/src/render/backend/webgpu/buffers.ts +153 -54
  112. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  113. package/src/render/backend/webgpu/clothOps.ts +86 -0
  114. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  115. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  116. package/src/render/backend/webgpu/drawCommand.ts +3 -0
  117. package/src/render/backend/webgpu/flatPass.ts +26 -28
  118. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  119. package/src/render/backend/webgpu/latePass.ts +1 -0
  120. package/src/render/backend/webgpu/motionPass.ts +18 -7
  121. package/src/render/backend/webgpu/pipelineCache.ts +46 -7
  122. package/src/render/backend/webgpu/renderer.ts +622 -164
  123. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  124. package/src/render/backend/webgpu/staticBundles.ts +2 -1
  125. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  126. package/src/render/globalMediumPass.ts +7 -0
  127. package/src/render/indexWidth.ts +27 -0
  128. package/src/render/instances.ts +18 -1
  129. package/src/render/lightMatrix.ts +15 -0
  130. package/src/render/mesh.ts +66 -14
  131. package/src/render/occlusionFade.ts +29 -0
  132. package/src/render/renderQuality.ts +38 -0
  133. package/src/render/rendererHarness.ts +15 -0
  134. package/src/render/sceneTarget.ts +3 -0
  135. package/src/render/shader.ts +59 -0
  136. package/src/render/shaders/ambientOcclusion.ts +12 -1
  137. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  138. package/src/render/shaders/flat/glassTint.ts +7 -1
  139. package/src/render/shaders/flat/index.ts +39 -4
  140. package/src/render/shaders/flat/layered.ts +198 -0
  141. package/src/render/shaders/flat/main.ts +50 -16
  142. package/src/render/shaders/flat/mapReads.ts +38 -0
  143. package/src/render/shaders/flat/movingSun.ts +46 -0
  144. package/src/render/shaders/flat/normalMap.ts +23 -3
  145. package/src/render/shaders/flat/overlay.ts +41 -19
  146. package/src/render/shaders/flat/preamble.ts +23 -5
  147. package/src/render/shaders/flat/worldUv.ts +113 -0
  148. package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
  149. package/src/render/shaders/generated/flat.wgsl.ts +791 -391
  150. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  151. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  152. package/src/render/shaders/globalMedium.ts +9 -1
  153. package/src/render/shaders/lightVolume.ts +9 -1
  154. package/src/render/shaders/tangentFrame.ts +16 -3
  155. package/src/render/skinnedClothRun.ts +138 -38
  156. package/src/render/surfaceLayers.ts +176 -0
  157. package/src/render/surfaceOverlay.ts +24 -7
  158. package/src/render/surfaceProjection.ts +57 -0
  159. package/src/render/surfaceTexture.ts +39 -0
  160. package/src/render/transmission.ts +27 -0
  161. package/src/render/vertexPacking.ts +109 -0
  162. package/src/ui/splash.ts +83 -14
  163. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  164. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  165. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -1,3 +1,5 @@
1
+ import { vertexPackingOf } from '../../vertexPacking.js';
2
+ import { markPacking, markPackingOfKey, packingKey } from './vertexFormats.js';
1
3
  import { mat4 } from 'gl-matrix';
2
4
  import { probeFaceRange } from '../../reflectionProbe.js';
3
5
  import { TEMPORAL_FLICKER, TEMPORAL_HISTORY_BLEND, TemporalHistory, flickerRule, jitterOffset, jitterProjection, } from '../../temporalAa.js';
@@ -60,6 +62,11 @@ import { gpuCompressedFormats } from './compressedUpload.js';
60
62
  import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTextures.js';
61
63
  import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel, skinProfileDistance, } from '../../surfaceModel.js';
62
64
  import { OVERLAY_FLOATS, overlayLays, packSurfaceOverlay, } from '../../surfaceOverlay.js';
65
+ import { PROJECTION_FLOATS, packSurfaceProjection } from '../../surfaceProjection.js';
66
+ import { resolveTransmission } from '../../transmission.js';
67
+ import { resolveOcclusionFade } from '../../occlusionFade.js';
68
+ import { orthographicDepthSpan } from '../../lightMatrix.js';
69
+ import { LAYER_FLOATS, layerMaskMap, layeredProjection, layersFitBeside, packSurfaceLayers, } from '../../surfaceLayers.js';
63
70
  import { frameReflectionsRefused, resolveFrameReflections, } from '../../frameReflections.js';
64
71
  import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.js';
65
72
  import { validateClothBinding } from '../../clothBindingData.js';
@@ -223,6 +230,11 @@ const MAX_OVERLAYS = 64;
223
230
  * and passes nobody has written yet; the cost is sixteen kilobytes.
224
231
  */
225
232
  const MAX_SKY_DRAWS = 64;
233
+ /** The draw a preparation asks pipelines for: every option at its default. See `prepareMesh`. */
234
+ const PREPARED_DRAW = {};
235
+ /** A preparation's morph states for a mesh with no targets, and for one with some. */
236
+ const NOT_MORPHED = [false];
237
+ const MORPHED_OR_NOT = [false, true];
226
238
  /**
227
239
  * Whether the environment probe's permutation fits this device's per-stage binding limits.
228
240
  *
@@ -857,7 +869,21 @@ export class WebGPURenderer {
857
869
  * flight at the time, so a consumer that builds more meshes later can wait again.
858
870
  */
859
871
  async ready() {
860
- await Promise.all(this.pipelineTargets.map((cache) => cache.ready()));
872
+ await Promise.all(this.pipelineCaches().map((cache) => cache.ready()));
873
+ }
874
+ /** Every distinct cache a lit draw reaches: the frame's, the canvas's, the late pass's, skin's. */
875
+ pipelineCaches() {
876
+ const caches = [...this.pipelineTargets];
877
+ for (const cache of [this.latePipelines, this.skinPipelines]) {
878
+ if (cache !== null && !caches.includes(cache))
879
+ caches.push(cache);
880
+ }
881
+ return caches;
882
+ }
883
+ /** Whether a lit draw whose pipeline is not built is left out while it compiles. */
884
+ skipCompiling(skips) {
885
+ for (const cache of this.pipelineCaches())
886
+ cache.skipsCompiling = skips;
861
887
  }
862
888
  presentedFrameCount = 0;
863
889
  /**
@@ -1627,6 +1653,8 @@ export class WebGPURenderer {
1627
1653
  temporalHistoryUsable = false;
1628
1654
  aoStaging = new ArrayBuffer(AO_FRAG_SIZE);
1629
1655
  aoFloats = new Float32Array(this.aoStaging);
1656
+ /** Where occlusion fades out with distance, `uFade`: none until a caller sets one. */
1657
+ aoFade = new Float32Array([-1, 0]);
1630
1658
  aoBindGroup = null;
1631
1659
  aoBlurLayout;
1632
1660
  /**
@@ -2177,6 +2205,7 @@ export class WebGPURenderer {
2177
2205
  }
2178
2206
  filmCommand.vertexCount = geometry.vertexBuffers.length;
2179
2207
  filmCommand.indexBuffer = geometry.indexBuffer;
2208
+ filmCommand.indexFormat = geometry.indexFormat;
2180
2209
  filmCommand.indexed = true;
2181
2210
  filmCommand.count = geometry.indexCount;
2182
2211
  }
@@ -2189,7 +2218,7 @@ export class WebGPURenderer {
2189
2218
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
2190
2219
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
2191
2220
  }
2192
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
2221
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
2193
2222
  pass.drawIndexed(geometry.indexCount);
2194
2223
  }
2195
2224
  }
@@ -2223,6 +2252,17 @@ export class WebGPURenderer {
2223
2252
  modelMap = null;
2224
2253
  /** The material's shading model, which picks the lit pipeline: null the standard. */
2225
2254
  materialModel = null;
2255
+ /** `uWorldUv`, the material's projection packed by `packSurfaceProjection`. */
2256
+ projection = new Float32Array(PROJECTION_FLOATS);
2257
+ /** `uLayers`, the material's layers packed by `packSurfaceLayers`. */
2258
+ layerFloats = new Float32Array(LAYER_FLOATS);
2259
+ /**
2260
+ * `uGlassTint` as the material leaves it, its light from behind and that light's colour, which a
2261
+ * pane overwrites for its own draw and `releaseSeeThrough` puts back. See `transmission.ts`.
2262
+ */
2263
+ transmission = new Float32Array([-1, -1, -1, 0]);
2264
+ warnedLayersModelMap = false;
2265
+ warnedLayersTriplanar = false;
2226
2266
  /** Its numbers, `uModelParams`, packed by `packModel`. */
2227
2267
  modelParams = new Float32Array(MODEL_PARAM_FLOATS);
2228
2268
  /**
@@ -2623,7 +2663,10 @@ export class WebGPURenderer {
2623
2663
  const normal = (material?.normal ?? null);
2624
2664
  const orm = (material?.orm ?? null);
2625
2665
  const emissiveMap = (material?.emissive ?? null);
2626
- const modelMap = (material?.modelMap ?? null);
2666
+ const ownModelMap = (material?.modelMap ?? null);
2667
+ /* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
2668
+ const layers = this.layersOf(material);
2669
+ const modelMap = ownModelMap ?? layerMaskMap(layers);
2627
2670
  this.materialModel = material?.model ?? null;
2628
2671
  const reflectivity = material?.reflectivity;
2629
2672
  const environmentGain = material?.environmentGain;
@@ -2634,7 +2677,7 @@ export class WebGPURenderer {
2634
2677
  this.writeEnvironmentDials();
2635
2678
  /* What the blur spreads this skin by: the last material to name a profile says. */
2636
2679
  const model = material?.model;
2637
- warnUnpagedLightmap(model, modelMap);
2680
+ warnUnpagedLightmap(model, ownModelMap);
2638
2681
  if (model?.kind === 'skin' && this.skinScatter !== null) {
2639
2682
  this.skinScatter.setProfile(model.profile, skinProfileDistance(model, 0), skinProfileDistance(model, 1), skinProfileDistance(model, 2));
2640
2683
  }
@@ -2682,10 +2725,26 @@ export class WebGPURenderer {
2682
2725
  f[emissive + 1] = asked?.[1] ?? 1;
2683
2726
  f[emissive + 2] = asked?.[2] ?? 1;
2684
2727
  /* The model's numbers and whether its map is bound. See `packModel`. */
2685
- packModel(material?.model ?? null, modelMap !== null, this.modelParams, material?.physicalSpecular === true);
2728
+ packModel(material?.model ?? null, ownModelMap !== null, this.modelParams, material?.physicalSpecular === true);
2686
2729
  /* The first physical highlight asked for is when the lit pipelines start carrying one. */
2687
2730
  if (material?.physicalSpecular === true)
2688
2731
  this.enableLit('PHYSICAL_SPECULAR');
2732
+ /* Maps placed by the world: the first material that asks is when lit pipelines carry it. */
2733
+ const projection = layeredProjection(material?.projection, layers);
2734
+ if (projection !== (material?.projection ?? null))
2735
+ this.warnLayersTriplanar();
2736
+ packSurfaceProjection(projection, this.projection);
2737
+ f.set(this.projection, this.materialField('uWorldUv'));
2738
+ /* How much light from behind it lets through, and its colour: transmission.ts. */
2739
+ resolveTransmission(material, this.transmission);
2740
+ f.set(this.transmission, this.materialField('uGlassTint'));
2741
+ if (this.projection[0] > 0)
2742
+ this.enableLit('WORLD_UVS');
2743
+ /* And its layers, the first time a material blends any: layered.ts. */
2744
+ packSurfaceLayers(layers, this.layerFloats);
2745
+ f.set(this.layerFloats, this.materialField('uLayers'));
2746
+ if (layers !== null)
2747
+ this.enableLit('LAYERED');
2689
2748
  f.set(this.modelParams, this.materialField('uModelParams'));
2690
2749
  });
2691
2750
  /*
@@ -2873,6 +2932,14 @@ export class WebGPURenderer {
2873
2932
  this.perFrameFloats[this.materialField('uWriteMode') + 1] = Math.min(1, Math.max(-1, amount));
2874
2933
  this.materials.dirty();
2875
2934
  }
2935
+ /** Whether the draws that follow spend their own opacity on that screen door. See `renderer.ts`. */
2936
+ setDitherOpacity(on) {
2937
+ if (this.surface.lost)
2938
+ return;
2939
+ /* The third lane of `uWriteMode`, beside the fade. */
2940
+ this.perFrameFloats[this.materialField('uWriteMode') + 2] = on ? 1 : 0;
2941
+ this.materials.dirty();
2942
+ }
2876
2943
  /**
2877
2944
  * How strong the microscopic relief on the surfaces drawn next is, and how coarse.
2878
2945
  *
@@ -4154,6 +4221,7 @@ export class WebGPURenderer {
4154
4221
  const sunShadow = env === undefined ? 0 : clamp(options.sunShadow ?? 0, 0, 1);
4155
4222
  ring.writeFloat(fragmentSlot, at('uSunShadow'), sunShadow);
4156
4223
  ring.writeFloats(fragmentSlot, at('uLightViewProj'), env === undefined ? NO_LIGHT_MATRIX : env.lightViewProj);
4224
+ ring.writeFloats(fragmentSlot, at('uMovingLightViewProj'), env === undefined ? NO_LIGHT_MATRIX : this.movingLightFor(env));
4157
4225
  ring.writeFloat(fragmentSlot, at('uShadowMapSize'), this.quality.directionalShadowMapSize);
4158
4226
  /* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
4159
4227
  ring.writeInt(fragmentSlot, at('uPeeledShadowEnabled'), this.peelFilled ? 1 : 0);
@@ -4182,6 +4250,7 @@ export class WebGPURenderer {
4182
4250
  }
4183
4251
  volumeCommand.vertexCount = mesh.vertexBuffers.length;
4184
4252
  volumeCommand.indexBuffer = mesh.indexBuffer;
4253
+ volumeCommand.indexFormat = mesh.indexFormat;
4185
4254
  volumeCommand.indexed = true;
4186
4255
  volumeCommand.count = mesh.indexCount;
4187
4256
  }
@@ -4191,7 +4260,7 @@ export class WebGPURenderer {
4191
4260
  for (let index = 0; index < mesh.vertexBuffers.length; index++) {
4192
4261
  pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
4193
4262
  }
4194
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
4263
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
4195
4264
  pass.drawIndexed(mesh.indexCount);
4196
4265
  }
4197
4266
  }
@@ -4907,6 +4976,22 @@ export class WebGPURenderer {
4907
4976
  * the texel-for-texel comparison that settled it.
4908
4977
  */
4909
4978
  correctedLightViewProj = new Float32Array(16);
4979
+ /**
4980
+ * What a receiver projects through to read the sun's static layers: the environment's
4981
+ * `lightViewProj`, taken at `bindMeshPass` as WebGL2 takes it there.
4982
+ *
4983
+ * **It was the last shadow pass's matrix**, which is the environment's wherever both layers are
4984
+ * drawn with one and the moving layer's wherever they are not: the static layer was then read
4985
+ * through the moving layer's tight square, and a distant pillar lost its shadow on WebGPU and kept
4986
+ * it on WebGL2, measured on demo/dev/sunLayers.html?moving=tight.
4987
+ */
4988
+ receiverLightViewProj = new Float32Array(16);
4989
+ /**
4990
+ * The matrix the sun's dynamic shadow pass was last drawn with, uncorrected as the lit stage
4991
+ * projects with, and whether one has been. See `movingLightFor`.
4992
+ */
4993
+ movingLightViewProj = new Float32Array(16);
4994
+ movingLightKnown = false;
4910
4995
  /**
4911
4996
  * The same matrix *uncorrected*, which is what the flat shader must project with.
4912
4997
  *
@@ -5003,6 +5088,53 @@ export class WebGPURenderer {
5003
5088
  this.glassSeen = true;
5004
5089
  this.enableLit('GLASS_SHADOWS');
5005
5090
  }
5091
+ /**
5092
+ * A material's layers where they can be drawn: none asked, or a model's map in the mask's place,
5093
+ * said once, leave one layer.
5094
+ */
5095
+ layersOf(material) {
5096
+ const layers = material?.layers ?? null;
5097
+ if (layers === null || layers.repeats.length === 0)
5098
+ return null;
5099
+ if (layersFitBeside(layers, material?.modelMap))
5100
+ return layers;
5101
+ if (!this.warnedLayersModelMap) {
5102
+ this.warnedLayersModelMap = true;
5103
+ console.warn("WebGPU: a material's layers are drawn as one: its mask is a map, read where a model's map " +
5104
+ "goes, and this material carries a `modelMap` too. A mask in the ORM array (`mask: 'orm'`) " +
5105
+ "or the vertex colour (`mask: 'vertex'`) leaves the slot free.");
5106
+ }
5107
+ return null;
5108
+ }
5109
+ /** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
5110
+ warnLayersTriplanar() {
5111
+ if (this.warnedLayersTriplanar)
5112
+ return;
5113
+ this.warnedLayersTriplanar = true;
5114
+ console.warn("WebGPU: a layered material's triplanar projection is laid on the horizontal plane: every " +
5115
+ "layer's maps on three planes would be three times the reads. See `SurfaceMaterial.layers`.");
5116
+ }
5117
+ /**
5118
+ * What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
5119
+ * where the lit pipelines carry one (`MOVING_SUN`); the environment's otherwise, which every caller
5120
+ * drawing both layers with one matrix hands it anyway.
5121
+ */
5122
+ movingLightFor(env) {
5123
+ return this.movingLightKnown && this.pipelines.litSwitches.MOVING_SUN
5124
+ ? this.movingLightViewProj
5125
+ : env.lightViewProj;
5126
+ }
5127
+ /** The moving layer's own matrix on, the first time a frame draws that layer with one. */
5128
+ enableMovingSun(env) {
5129
+ if (this.pipelines.litSwitches.MOVING_SUN || !this.movingLightKnown)
5130
+ return;
5131
+ for (let k = 0; k < 16; k++) {
5132
+ if (this.movingLightViewProj[k] !== env.lightViewProj[k]) {
5133
+ this.enableLit('MOVING_SUN');
5134
+ return;
5135
+ }
5136
+ }
5137
+ }
5006
5138
  /** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
5007
5139
  enableLit(feature) {
5008
5140
  if (this.pipelines.litSwitches[feature])
@@ -5056,7 +5188,7 @@ export class WebGPURenderer {
5056
5188
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
5057
5189
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
5058
5190
  }
5059
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
5191
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
5060
5192
  pass.drawIndexed(geometry.indexCount);
5061
5193
  },
5062
5194
  /**
@@ -5129,7 +5261,7 @@ export class WebGPURenderer {
5129
5261
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
5130
5262
  }
5131
5263
  pass.setVertexBuffer(index, gpuBatch.buffer);
5132
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
5264
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
5133
5265
  pass.drawIndexed(geometry.indexCount, count);
5134
5266
  },
5135
5267
  skinnedMesh: (mesh, model, palette, material, cloth) => {
@@ -5172,7 +5304,7 @@ export class WebGPURenderer {
5172
5304
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
5173
5305
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
5174
5306
  }
5175
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
5307
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
5176
5308
  pass.drawIndexed(geometry.indexCount);
5177
5309
  },
5178
5310
  /**
@@ -5543,6 +5675,8 @@ export class WebGPURenderer {
5543
5675
  this.overlayPipelines === this.pipelines
5544
5676
  ? [this.pipelines]
5545
5677
  : [this.pipelines, this.overlayPipelines];
5678
+ /* A lit draw first needed inside a frame waits for its compile or is left out while it runs. */
5679
+ this.skipCompiling(quality.pipelineCompile === 'skip');
5546
5680
  this.variant = flatVariant({
5547
5681
  directionalShadows: quality.directionalShadows,
5548
5682
  /* No `bakeReflectionProbe` here; see the field. */
@@ -6410,6 +6544,8 @@ export class WebGPURenderer {
6410
6544
  present['layers'] = key.includes(':layers');
6411
6545
  /* Nor is a dynamic mesh's split, which moves positions and normals into buffers of their own. */
6412
6546
  present['dynamic'] = key.includes(':dynamic');
6547
+ /* Nor how each attribute travels, which moves every field after a packed one. */
6548
+ markPackingOfKey(present, key);
6413
6549
  return present;
6414
6550
  }
6415
6551
  /**
@@ -6713,6 +6849,13 @@ export class WebGPURenderer {
6713
6849
  present['dynamic'] = true;
6714
6850
  key += ':dynamic';
6715
6851
  }
6852
+ /*
6853
+ * And how each attribute travels (`vertexPacking.ts`): a field read as sixteen bits rather than
6854
+ * thirty-two, or not interleaved at all, moves every field after it, so the key says so.
6855
+ */
6856
+ const packing = vertexPackingOf(data, dynamic);
6857
+ markPacking(present, packing);
6858
+ key += packingKey(packing);
6716
6859
  const fullKey = `${this.variant}|${key}`;
6717
6860
  /*
6718
6861
  * **For every target this mesh can land on, which is two when the overlay is its own.**
@@ -6920,6 +7063,8 @@ export class WebGPURenderer {
6920
7063
  bindMeshPass(camera, env) {
6921
7064
  if (this.surface.lost)
6922
7065
  return;
7066
+ /* The matrix every receiver projects through: the environment's, as WebGL2's. See the field. */
7067
+ this.receiverLightViewProj.set(env.lightViewProj);
6923
7068
  /* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
6924
7069
  if (!this.warnedAreaOverflow &&
6925
7070
  !this.quality.clusteredLights &&
@@ -7034,6 +7179,13 @@ export class WebGPURenderer {
7034
7179
  this.surfaceFog = FOG_RECEDE;
7035
7180
  /* Every material switch off — albedo, ORM map, emissive map, two-sided — as WebGL2 resets them. */
7036
7181
  i.fill(0, at('uMaterialFlags'), at('uMaterialFlags') + 4);
7182
+ /* And no projection: maps follow the mesh's coordinates until a material asks otherwise. */
7183
+ f.fill(0, at('uWorldUv'), at('uWorldUv') + 4);
7184
+ /* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
7185
+ resolveTransmission(null, this.transmission);
7186
+ f.set(this.transmission, at('uGlassTint'));
7187
+ /* Nor layers: one layer until a material asks for more. */
7188
+ f.fill(0, at('uLayers'), at('uLayers') + LAYER_FLOATS);
7037
7189
  f[at('uCutout')] = 0;
7038
7190
  f[at('uCutout') + 1] = 0;
7039
7191
  this.cutoutResolveStaged = 'hard';
@@ -7106,6 +7258,7 @@ export class WebGPURenderer {
7106
7258
  /* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
7107
7259
  f[at('uWriteMode')] = 0;
7108
7260
  f[at('uWriteMode') + 1] = 0;
7261
+ f[at('uWriteMode') + 2] = 0;
7109
7262
  /* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
7110
7263
  f[at('uEnvironmentDials') + 2] = 1;
7111
7264
  /* No overlay until a draw sets one. See `setSurfaceOverlay`. */
@@ -7386,6 +7539,11 @@ export class WebGPURenderer {
7386
7539
  f[at('uShadowStrength')] = env.shadowStrength;
7387
7540
  f[at('uShadowMapSize')] = this.quality.directionalShadowMapSize;
7388
7541
  f[at('uShadowDepthSpan')] = env.shadowDepthSpan;
7542
+ /* The moving layer's own matrix and span, where the lit stage reads one: `movingLightFor`. */
7543
+ this.enableMovingSun(env);
7544
+ const moving = this.movingLightFor(env);
7545
+ f.set(moving, at('uMovingLightViewProj'));
7546
+ f[at('uMovingShadowDepthSpan')] = orthographicDepthSpan(moving);
7389
7547
  f[at('uShadowMaxDistance')] = this.quality.directionalShadowMaxDistance;
7390
7548
  f[at('uShadowMaxSlope')] = this.quality.directionalShadowMaxSlope;
7391
7549
  /* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
@@ -7569,7 +7727,7 @@ export class WebGPURenderer {
7569
7727
  /** A view block: the camera, the light's matrix and the frame's wind, into `ring` at `slot`. */
7570
7728
  writeView(ring, slot, viewProj) {
7571
7729
  ring.writeFloats(slot, FLAT_VIEW_FIELDS.uViewProj.offset, viewProj);
7572
- ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.lightViewProj);
7730
+ ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.receiverLightViewProj);
7573
7731
  /* The scene's clock, which a bone animation's instances read their moment from. */
7574
7732
  ring.writeFloat(slot, FLAT_VIEW_FIELDS.uSceneTime.offset, this.animationTime);
7575
7733
  const d = this.frameWind;
@@ -7750,6 +7908,153 @@ export class WebGPURenderer {
7750
7908
  `TranslucentMeshOptions.tint` for why, and for the fade it was added for. */
7751
7909
  this.submitMesh(mesh, model, options.tint ?? null, Math.min(opacity, 1), true, options);
7752
7910
  }
7911
+ /**
7912
+ * Compile off the main thread every pipeline a draw of `mesh` in `material` takes. **The other
7913
+ * backend's `prepareMesh` carries the contract**; here a draw's key is `meshShape`'s, so a prepared
7914
+ * key is exactly the one the draw asks for, and the compiles are started as a skipping draw starts
7915
+ * them (`PipelineCache.lit`) whatever `pipelineCompile` says, into the frame's own caches.
7916
+ */
7917
+ prepareMesh(mesh, material, options = {}) {
7918
+ if (this.surface.lost)
7919
+ return;
7920
+ this.preparing(material, (into, blend) => {
7921
+ const skinned = mesh.isSkinned;
7922
+ const eight = skinned && mesh.isSkinnedEight;
7923
+ for (const morphed of mesh.morph === null ? NOT_MORPHED : MORPHED_OR_NOT) {
7924
+ this.meshShape(mesh, blend, PREPARED_DRAW, skinned, morphed, eight, false);
7925
+ this.meshPipeline(blend, false, skinned, morphed, eight, false, into);
7926
+ }
7927
+ }, options);
7928
+ }
7929
+ /** `prepareMesh` for an instanced batch. See the other backend's `prepareInstanced`. */
7930
+ prepareInstanced(batch, material, options = {}) {
7931
+ if (this.surface.lost)
7932
+ return;
7933
+ this.preparing(material, (into, blend) => {
7934
+ const coverage = !blend && this.cutoutResolveStaged === 'coverage';
7935
+ this.instancedPipeline(batch.mesh, batch.animation !== null, blend, false, PREPARED_DRAW, true, 0, coverage, into);
7936
+ }, options);
7937
+ }
7938
+ /**
7939
+ * `material` set, every cache a draw in it may build into passed to `start` with whether that draw
7940
+ * blends, compiles started rather than built (`PipelineCache.lit`), and then no material set and
7941
+ * each cache back to what `pipelineCompile` says.
7942
+ */
7943
+ preparing(material, start, options) {
7944
+ this.setMaterial(material);
7945
+ this.skipCompiling(true);
7946
+ try {
7947
+ start(this.pipelines, false);
7948
+ if (options.translucent === true) {
7949
+ start(this.pipelines, true);
7950
+ /* A blended draw in a reconstructing frame lands in the late pass, whose cache is its own. */
7951
+ if (this.latePipelines !== this.pipelines)
7952
+ start(this.latePipelines, true);
7953
+ }
7954
+ }
7955
+ finally {
7956
+ this.skipCompiling(this.quality.pipelineCompile === 'skip');
7957
+ this.setMaterial(null);
7958
+ }
7959
+ }
7960
+ /** What `meshShape` worked out for the mesh draw at hand. Reused, so a draw allocates no record. */
7961
+ drawShape = {
7962
+ base: '',
7963
+ key: '',
7964
+ depthWrite: true,
7965
+ layer: 0,
7966
+ adds: false,
7967
+ coverage: false,
7968
+ splits: false,
7969
+ modelKind: null,
7970
+ };
7971
+ /**
7972
+ * The key a mesh draw's pipeline is cached under, and the pipeline state that key encodes, under
7973
+ * the current material and these options, into `drawShape`. **The one statement of it**:
7974
+ * `submitMesh` draws by it and `prepareMesh` compiles by it, so the two cannot disagree about a key.
7975
+ */
7976
+ meshShape(mesh, blend, options, skinned, morphed, eight, clothed) {
7977
+ const modelKind = this.materialModel?.kind ?? null;
7978
+ const base = mesh.key ?? 'flat:s0:u0';
7979
+ const keyed = `${base}${morphed ? '|morph' : ''}${skinned ? (eight ? '|skin8' : '|skin') : ''}` +
7980
+ `${clothed ? '|cloth' : ''}`;
7981
+ /*
7982
+ * Depth writing and the overlay layer are pipeline state on this backend, so they belong in
7983
+ * the key: two draws of one mesh differing only in these would otherwise share a cached
7984
+ * pipeline and the second would silently take the first one's depth behaviour.
7985
+ *
7986
+ * **Both suffixes are empty for the defaults**, which is what keeps the keys `createMesh`
7987
+ * warms — `fullKey` and `${'`'}${'$'}{fullKey}|blend${'`'}` — the ones an ordinary draw still asks for. A
7988
+ * caller that opts in pays one compile on its first draw, the same bargain the overlay
7989
+ * pipelines above already make.
7990
+ */
7991
+ const depthWrite = options.depthWrite ?? true;
7992
+ const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
7993
+ /* Added rather than blended over: its own blend state, so its own pipeline. */
7994
+ const adds = blend && options.additive === true;
7995
+ const coverage = !blend && this.cutoutResolveStaged === 'coverage';
7996
+ /* Skin under the screen-space blur draws its two halves; see `keepSkinDiffuse`. */
7997
+ const splits = modelKind === 'skin' && !blend && this.skinSplits();
7998
+ const key = `${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
7999
+ `${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
8000
+ /* The two order-independent buffers are two formats and two blend states, so a draw into
8001
+ one must never share a cached pipeline with the same mesh drawn into the other. */
8002
+ `${this.oitMode === 'none' ? '' : `|oit${this.oitMode}`}` +
8003
+ /* Culling is pipeline state here: a two-sided surface culls nothing. */
8004
+ `${this.materialDoubleSided ? '|2s' : ''}` +
8005
+ /* And so is alpha-to-coverage, for a dithered cutout in a multisampled frame. */
8006
+ `${coverage ? '|a2c' : ''}` +
8007
+ /* And the material's shading model, which is the fragment stage's own switch. */
8008
+ `${modelKind === null ? '' : `|m:${modelKind}`}` +
8009
+ /* And which half of a skin it draws, under the screen-space blur. */
8010
+ `${splits ? '|ss' : ''}`;
8011
+ const shape = this.drawShape;
8012
+ shape.base = base;
8013
+ shape.key = key;
8014
+ shape.depthWrite = depthWrite;
8015
+ shape.layer = layer;
8016
+ shape.adds = adds;
8017
+ shape.coverage = coverage;
8018
+ shape.splits = splits;
8019
+ shape.modelKind = modelKind;
8020
+ return shape;
8021
+ }
8022
+ /**
8023
+ * The pipeline `drawShape` names: the one built, one built here, or none where draws skip a
8024
+ * compile and this one has not landed (`PipelineCache.lit`) — and none for a split skin until all
8025
+ * of its halves have.
8026
+ */
8027
+ meshPipeline(blend, lands, skinned, morphed, eight, clothed,
8028
+ /** Which cache to build into: the one the draw's target reads, unless a preparation says. */
8029
+ into = null) {
8030
+ const { base, key, depthWrite, layer, adds, coverage, splits, modelKind } = this.drawShape;
8031
+ /*
8032
+ * **Asynchronous by default, synchronous if it has to be, and never absent.**
8033
+ *
8034
+ * `createMesh` compiles off the main thread, so a draw can legitimately arrive before the
8035
+ * driver has finished — a consumer that awaits `ready()` never sees this, and one that
8036
+ * draws immediately gets the old behaviour rather than an exception. Correctness cannot be
8037
+ * allowed to depend on which of those happened, so the miss builds the pipeline here and
8038
+ * pays the compile it was trying to avoid. That is a slow frame; the alternative is a
8039
+ * thrown error in the middle of one.
8040
+ */
8041
+ const pipelines = into ?? (blend ? this.blendedPipelines(lands) : this.targetPipelines());
8042
+ let pipeline = pipelines.peek(key) ?? null;
8043
+ if (pipeline === null) {
8044
+ const present = this.meshPresent.get(base);
8045
+ if (present === undefined) {
8046
+ throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
8047
+ }
8048
+ pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, adds ? 'additive' : blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided, coverage, eight, clothed, modelKind, splits ? 'scene' : 'whole');
8049
+ }
8050
+ /* A split skin draws its three halves or none of them. */
8051
+ if (pipeline !== null &&
8052
+ splits &&
8053
+ !this.skinHalvesReady(key, base, skinned, morphed, layer, eight, clothed)) {
8054
+ pipeline = null;
8055
+ }
8056
+ return pipeline;
8057
+ }
7753
8058
  /**
7754
8059
  * @param blend Which of the two entry points above this came through, and **not** something to
7755
8060
  * infer from `opacity`.
@@ -7869,7 +8174,6 @@ export class WebGPURenderer {
7869
8174
  const modelKind = this.materialModel?.kind ?? null;
7870
8175
  if (modelKind === 'eye')
7871
8176
  this.writeEyeAxis(model, this.skinPaletteSlot >= 0 && mesh.isSkinned);
7872
- const base = mesh.key ?? 'flat:s0:u0';
7873
8177
  /*
7874
8178
  * A rigged mesh takes the skinned vertex variant, an unrigged one the plain variant, and the
7875
8179
  * key carries the flag so the two never share a cached pipeline. A rigged mesh whose caller
@@ -7884,38 +8188,6 @@ export class WebGPURenderer {
7884
8188
  /* A cloth set and a skinned draw: the pipeline built with CLOTH_BOUND on, warmed when the
7885
8189
  binding was made. See `createClothBinding`. */
7886
8190
  const clothed = skinned && this.cloth !== null;
7887
- const keyed = `${base}${morphed ? '|morph' : ''}${skinned ? (eight ? '|skin8' : '|skin') : ''}` +
7888
- `${clothed ? '|cloth' : ''}`;
7889
- /*
7890
- * Depth writing and the overlay layer are pipeline state on this backend, so they belong in
7891
- * the key: two draws of one mesh differing only in these would otherwise share a cached
7892
- * pipeline and the second would silently take the first one's depth behaviour.
7893
- *
7894
- * **Both suffixes are empty for the defaults**, which is what keeps the keys `createMesh`
7895
- * warms — `fullKey` and `${'`'}${'$'}{fullKey}|blend${'`'}` — the ones an ordinary draw still asks for. A
7896
- * caller that opts in pays one compile on its first draw, the same bargain the overlay
7897
- * pipelines above already make.
7898
- */
7899
- const depthWrite = options.depthWrite ?? true;
7900
- const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
7901
- /* Added rather than blended over: its own blend state, so its own pipeline. */
7902
- const adds = blend && options.additive === true;
7903
- const coverage = !blend && this.cutoutResolveStaged === 'coverage';
7904
- /* Skin under the screen-space blur draws its two halves; see `keepSkinDiffuse`. */
7905
- const splits = modelKind === 'skin' && !blend && this.skinSplits();
7906
- const key = `${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
7907
- `${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
7908
- /* The two order-independent buffers are two formats and two blend states, so a draw into
7909
- one must never share a cached pipeline with the same mesh drawn into the other. */
7910
- `${this.oitMode === 'none' ? '' : `|oit${this.oitMode}`}` +
7911
- /* Culling is pipeline state here: a two-sided surface culls nothing. */
7912
- `${this.materialDoubleSided ? '|2s' : ''}` +
7913
- /* And so is alpha-to-coverage, for a dithered cutout in a multisampled frame. */
7914
- `${coverage ? '|a2c' : ''}` +
7915
- /* And the material's shading model, which is the fragment stage's own switch. */
7916
- `${modelKind === null ? '' : `|m:${modelKind}`}` +
7917
- /* And which half of a skin it draws, under the screen-space blur. */
7918
- `${splits ? '|ss' : ''}`;
7919
8191
  /*
7920
8192
  * The morph uniforms, written only for a morphed draw and read from the *morphed* variant's
7921
8193
  * field table. `FLAT_VERT_FIELDS` is the plain variant's and does not name them at all — every
@@ -7928,25 +8200,9 @@ export class WebGPURenderer {
7928
8200
  this.perDraw.writeInt(slot, fields.uMorphTargetCount.offset, mesh.morph.targetCount);
7929
8201
  this.perDraw.writeInt(slot, fields.uMorphTextureWidth.offset, mesh.morph.width);
7930
8202
  }
7931
- /*
7932
- * **Asynchronous by default, synchronous if it has to be, and never absent.**
7933
- *
7934
- * `createMesh` compiles off the main thread, so a draw can legitimately arrive before the
7935
- * driver has finished — a consumer that awaits `ready()` never sees this, and one that
7936
- * draws immediately gets the old behaviour rather than an exception. Correctness cannot be
7937
- * allowed to depend on which of those happened, so the miss builds the pipeline here and
7938
- * pays the compile it was trying to avoid. That is a slow frame; the alternative is a
7939
- * thrown error in the middle of one.
7940
- */
7941
- const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
7942
- let pipeline = pipelines.peek(key);
7943
- if (pipeline === undefined) {
7944
- const present = this.meshPresent.get(base);
7945
- if (present === undefined) {
7946
- throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
7947
- }
7948
- pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, adds ? 'additive' : blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided, coverage, eight, clothed, modelKind, splits ? 'scene' : 'whole');
7949
- }
8203
+ /* Its pipeline, by the key a prepared draw compiled too: see `meshShape`. */
8204
+ const { base, key, layer, depthWrite, splits } = this.meshShape(mesh, blend, options, skinned, morphed, eight, clothed);
8205
+ let pipeline = this.meshPipeline(blend, lands, skinned, morphed, eight, clothed);
7950
8206
  /*
7951
8207
  * Guards the draw call only, not the restore below.
7952
8208
  *
@@ -7963,8 +8219,9 @@ export class WebGPURenderer {
7963
8219
  * close to the 256-slot budget before the gateway, the play controls, the waveform, the
7964
8220
  * label handles and the sparkles are even counted.
7965
8221
  */
7966
- const material = this.materialSlotForDraw();
7967
- if (material !== null) {
8222
+ /* A draw whose pipeline is still compiling is left out, its state restored all the same. */
8223
+ const material = pipeline === null ? null : this.materialSlotForDraw();
8224
+ if (pipeline !== null && material !== null) {
7968
8225
  /*
7969
8226
  * Record rather than issue. Every per-draw value is already in `slot` and `material` —
7970
8227
  * the uniform rings are the state arena this design would otherwise have had to build —
@@ -7986,6 +8243,7 @@ export class WebGPURenderer {
7986
8243
  }
7987
8244
  command.vertexCount = mesh.vertexBuffers.length;
7988
8245
  command.indexBuffer = mesh.indexBuffer;
8246
+ command.indexFormat = mesh.indexFormat;
7989
8247
  command.indexed = true;
7990
8248
  command.count = mesh.indexCount;
7991
8249
  }
@@ -8003,7 +8261,7 @@ export class WebGPURenderer {
8003
8261
  for (let index = 0; index < mesh.vertexBuffers.length; index++) {
8004
8262
  pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
8005
8263
  }
8006
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
8264
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
8007
8265
  pass.drawIndexed(mesh.indexCount);
8008
8266
  }
8009
8267
  if (splits) {
@@ -8250,6 +8508,8 @@ export class WebGPURenderer {
8250
8508
  const seeAt = this.materialField('uSeeThrough');
8251
8509
  this.perFrameFloats[seeAt] = 0;
8252
8510
  this.perFrameFloats[seeAt + 2] = 0;
8511
+ /* The material's light through, over the pane's tint: transmission.ts. */
8512
+ this.perFrameFloats.set(this.transmission, this.materialField('uGlassTint'));
8253
8513
  }
8254
8514
  /**
8255
8515
  * One draw, one material slot, one uniform slot, however many instances.
@@ -8263,6 +8523,41 @@ export class WebGPURenderer {
8263
8523
  * **What it gives up** against `drawMesh` is skinning and morphing, refused where the variant
8264
8524
  * is built, and a per-instance opacity: `opacity` here dims the whole batch.
8265
8525
  */
8526
+ /**
8527
+ * An instanced draw's pipeline under the current material and these options: the one built, one
8528
+ * built here, or none while it compiles (`PipelineCache.lit`), its key, base and model left in
8529
+ * `drawShape`. **The one statement of an instanced key**: `submitInstanced` draws by it and
8530
+ * `prepareInstanced` compiles by it.
8531
+ */
8532
+ instancedPipeline(mesh, animated, blend, lands, options, depthWrite, layer, coverage,
8533
+ /** Which cache to build into: the one the draw's target reads, unless a preparation says. */
8534
+ into = null) {
8535
+ /* A batch shades by its material's model too; an eye's axis is a placement's, not a batch's. */
8536
+ const modelKind = this.materialModel?.kind ?? null;
8537
+ const base = mesh.key ?? 'flat:s0:u0';
8538
+ /* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
8539
+ a third vertex buffer and a different vertex module, and sharing a cache entry with the
8540
+ plain one would hand a draw the wrong layout. */
8541
+ const adds = blend && options.additive === true;
8542
+ const key = `${base}|inst${!animated ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
8543
+ `${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
8544
+ `${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
8545
+ `${modelKind === null ? '' : `|m:${modelKind}`}`;
8546
+ const shape = this.drawShape;
8547
+ shape.base = base;
8548
+ shape.key = key;
8549
+ shape.modelKind = modelKind;
8550
+ const pipelines = into ?? (blend ? this.blendedPipelines(lands) : this.targetPipelines());
8551
+ let pipeline = pipelines.peek(key) ?? null;
8552
+ if (pipeline === null) {
8553
+ const present = this.meshPresent.get(base);
8554
+ if (present === undefined) {
8555
+ throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
8556
+ }
8557
+ pipeline = flatPipeline(pipelines, this.surface.device, !animated ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout, this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided, coverage, false, false, modelKind, 'whole', false, animated);
8558
+ }
8559
+ return pipeline;
8560
+ }
8266
8561
  /**
8267
8562
  * The next of a batch's cull slots this frame, with its cull queued against the current view; null
8268
8563
  * when the batch has drawn from `MAX_CULL_SLOTS` views already or the frame's job ring is full,
@@ -8366,28 +8661,12 @@ export class WebGPURenderer {
8366
8661
  const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog) || ditheredBlend;
8367
8662
  if (own)
8368
8663
  this.materials.dirty();
8369
- /* A batch shades by its material's model too; an eye's axis is a placement's, not a batch's. */
8370
- const modelKind = this.materialModel?.kind ?? null;
8371
- const base = mesh.key ?? 'flat:s0:u0';
8372
- /* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
8373
- a third vertex buffer and a different vertex module, and sharing a cache entry with the
8374
- plain one would hand a draw the wrong layout. */
8375
- const adds = blend && options.additive === true;
8376
- const key = `${base}|inst${animation === null ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
8377
- `${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
8378
- `${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
8379
- `${modelKind === null ? '' : `|m:${modelKind}`}`;
8380
- const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
8381
- let pipeline = pipelines.peek(key);
8382
- if (pipeline === undefined) {
8383
- const present = this.meshPresent.get(base);
8384
- if (present === undefined) {
8385
- throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
8386
- }
8387
- pipeline = flatPipeline(pipelines, this.surface.device, animation === null ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout, this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided, coverage, false, false, modelKind, 'whole', false, animation !== null);
8388
- }
8389
- const material = this.materialSlotForDraw();
8390
- if (material !== null) {
8664
+ /* Its pipeline, by the key a prepared batch compiled too: see `instancedPipeline`. */
8665
+ const pipeline = this.instancedPipeline(mesh, animation !== null, blend, lands, options, depthWrite, layer, coverage);
8666
+ const { base, key, modelKind } = this.drawShape;
8667
+ /* A batch whose pipeline is still compiling is left out, as a mesh is. */
8668
+ const material = pipeline === null ? null : this.materialSlotForDraw();
8669
+ if (pipeline !== null && material !== null) {
8391
8670
  const command = blend
8392
8671
  ? this.recordBlended(0, lands)
8393
8672
  : this.recordDraw(0, this.currentTarget());
@@ -8409,6 +8688,7 @@ export class WebGPURenderer {
8409
8688
  command.vertexBuffers[index] = instanceBuffer;
8410
8689
  command.vertexCount = mesh.vertexBuffers.length + 1;
8411
8690
  command.indexBuffer = mesh.indexBuffer;
8691
+ command.indexFormat = mesh.indexFormat;
8412
8692
  command.indexed = true;
8413
8693
  command.count = mesh.indexCount;
8414
8694
  command.instances = count;
@@ -8427,7 +8707,7 @@ export class WebGPURenderer {
8427
8707
  pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
8428
8708
  }
8429
8709
  pass.setVertexBuffer(index, instanceBuffer);
8430
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
8710
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
8431
8711
  if (cut === null)
8432
8712
  pass.drawIndexed(mesh.indexCount, count);
8433
8713
  else
@@ -8628,6 +8908,11 @@ export class WebGPURenderer {
8628
8908
  if (view === null)
8629
8909
  return false;
8630
8910
  this.lightViewProj.set(lightViewProj);
8911
+ /* The moving layer is read through the matrix it is drawn with: see `movingLightFor`. */
8912
+ if (dynamic) {
8913
+ this.movingLightViewProj.set(lightViewProj);
8914
+ this.movingLightKnown = true;
8915
+ }
8631
8916
  this.reopenViews();
8632
8917
  mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
8633
8918
  this.shadowLayerIsPeel = peel;
@@ -8912,11 +9197,14 @@ export class WebGPURenderer {
8912
9197
  this.lateViewBlock.slot = list.viewOffset(0);
8913
9198
  this.materials.dirty();
8914
9199
  this.staticRecording = recording;
9200
+ /* A recorded list keeps what it recorded, so every draw of it waits for its pipeline. */
9201
+ this.skipCompiling(false);
8915
9202
  try {
8916
9203
  this.sceneCasterMaterial = undefined;
8917
9204
  replayStaticDraws(list, this.sceneCasterSink);
8918
9205
  }
8919
9206
  finally {
9207
+ this.skipCompiling(this.quality.pipelineCompile === 'skip');
8920
9208
  this.staticRecording = null;
8921
9209
  this.perDraw = perDraw;
8922
9210
  this.passRing = passRing;
@@ -9186,7 +9474,7 @@ export class WebGPURenderer {
9186
9474
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
9187
9475
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
9188
9476
  }
9189
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
9477
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
9190
9478
  pass.drawIndexed(geometry.indexCount);
9191
9479
  },
9192
9480
  instanced: (batch, data, material, glass) => {
@@ -9220,7 +9508,7 @@ export class WebGPURenderer {
9220
9508
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
9221
9509
  }
9222
9510
  pass.setVertexBuffer(index, gpuBatch.buffer);
9223
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
9511
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
9224
9512
  pass.drawIndexed(geometry.indexCount, count);
9225
9513
  },
9226
9514
  skinnedMesh: (mesh, model, palette, _material, glass) => {
@@ -9248,7 +9536,7 @@ export class WebGPURenderer {
9248
9536
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
9249
9537
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
9250
9538
  }
9251
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
9539
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
9252
9540
  pass.drawIndexed(geometry.indexCount);
9253
9541
  },
9254
9542
  };
@@ -11377,6 +11665,7 @@ export class WebGPURenderer {
11377
11665
  /* Uncorrected, for the reason the shaft's own copy of this line carries: the shader does its
11378
11666
  own `* 0.5 + 0.5`, and correcting the matrix as well doubles it. */
11379
11667
  f.set(env?.lightViewProj ?? this.mediumIdentity, at('uLightViewProj'));
11668
+ f.set((env === null ? this.mediumIdentity : this.movingLightFor(env)), at('uMovingLightViewProj'));
11380
11669
  ints[at('uPeeledShadowEnabled')] = this.peelView === null || !this.peelFilled ? 0 : 1;
11381
11670
  device.queue.writeBuffer(this.mediumUniforms, 0, this.mediumStaging);
11382
11671
  const g = this.mediumUpsampleFloats;
@@ -11432,6 +11721,7 @@ export class WebGPURenderer {
11432
11721
  const f = this.aoFloats;
11433
11722
  const at = (name) => this.postField(AO_FRAG_FIELDS, name);
11434
11723
  f[at('uRadius')] = this.quality.ambientOcclusionRadius;
11724
+ f.set(this.aoFade, at('uFade'));
11435
11725
  f.set(this.aoProjScale, at('uProjScale'));
11436
11726
  f.set(this.aoInvProjection, at('uInvProjection'));
11437
11727
  device.queue.writeBuffer(this.aoUniforms, 0, this.aoStaging);
@@ -11778,6 +12068,9 @@ export class WebGPURenderer {
11778
12068
  const key = `${frameKey}|rs`;
11779
12069
  const pipeline = this.pipelines.peek(key) ??
11780
12070
  flatPipeline(this.pipelines, this.surface.device, animation === null ? this.flatLayoutFor(skinned, morphed) : this.animatedBindGroupLayout, this.variant, key, present, false, skinned, morphed, true, layer, instanceBuffer !== null, 'none', this.materialDoubleSided, false, eight, clothed, modelKind, 'whole', true, animation !== null);
12071
+ /* Still compiling where draws skip: the probe stands in for this surface until it lands. */
12072
+ if (pipeline === null)
12073
+ return;
11781
12074
  /* A static list's surface halves go into its own pool, beside its draws. */
11782
12075
  const command = this.staticRecording === null ? surface.take() : takeRecorded(this.staticRecording.surface);
11783
12076
  command.pipeline = pipeline;
@@ -11798,6 +12091,7 @@ export class WebGPURenderer {
11798
12091
  command.vertexBuffers[index++] = instanceBuffer;
11799
12092
  command.vertexCount = index;
11800
12093
  command.indexBuffer = mesh.indexBuffer;
12094
+ command.indexFormat = mesh.indexFormat;
11801
12095
  command.indexed = true;
11802
12096
  command.count = mesh.indexCount;
11803
12097
  command.instances = instances;
@@ -11809,6 +12103,30 @@ export class WebGPURenderer {
11809
12103
  * light alone and its colour alone into the blur's targets and test for the depth its frame half
11810
12104
  * wrote.
11811
12105
  */
12106
+ /**
12107
+ * Whether a split skin draw's two kept halves have pipelines, starting the compile of either that
12108
+ * has not: a skin drawn without them would lose its diffuse light for the frames they take, so a
12109
+ * split skin draws all three halves or none. See `PipelineCache.lit`.
12110
+ */
12111
+ skinHalvesReady(frameKey, base, skinned, morphed, layer, eight, clothed) {
12112
+ let ready = true;
12113
+ for (const half of SKIN_KEPT_HALVES) {
12114
+ const pipeline = this.skinHalfPipeline(half, frameKey, base, skinned, morphed, layer, eight, clothed);
12115
+ if (pipeline === null)
12116
+ ready = false;
12117
+ }
12118
+ return ready;
12119
+ }
12120
+ /** One kept half's pipeline, or none where it is still compiling: see `PipelineCache.lit`. */
12121
+ skinHalfPipeline(half, frameKey, base, skinned, morphed, layer, eight, clothed) {
12122
+ const pipelines = this.skinPipelines;
12123
+ const present = this.meshPresent.get(base);
12124
+ if (pipelines === null || present === undefined)
12125
+ return null;
12126
+ const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
12127
+ return (pipelines.peek(key) ??
12128
+ flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, false, skinned, morphed, true, layer, false, 'none', this.materialDoubleSided, false, eight, clothed, 'skin', half));
12129
+ }
11812
12130
  keepSkinDiffuse(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, slot, material) {
11813
12131
  /* A skin's halves are not bundled: a static list holding one replays draw by draw. */
11814
12132
  if (this.staticRecording !== null) {
@@ -11823,9 +12141,10 @@ export class WebGPURenderer {
11823
12141
  const bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
11824
12142
  /* The light alone, which the blur spreads, then the colour alone, which it is multiplied by. */
11825
12143
  for (const half of SKIN_KEPT_HALVES) {
11826
- const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
11827
- const pipeline = pipelines.peek(key) ??
11828
- flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, false, skinned, morphed, true, layer, false, 'none', this.materialDoubleSided, false, eight, clothed, 'skin', half);
12144
+ const pipeline = this.skinHalfPipeline(half, frameKey, base, skinned, morphed, layer, eight, clothed);
12145
+ /* Both landed before the draw was let through: see `skinHalvesReady`. */
12146
+ if (pipeline === null)
12147
+ return;
11829
12148
  const command = half === 'diffuse' ? skin.take() : skin.takeAlbedo();
11830
12149
  command.pipeline = pipeline;
11831
12150
  command.bindGroup = bindGroup;
@@ -11839,6 +12158,7 @@ export class WebGPURenderer {
11839
12158
  }
11840
12159
  command.vertexCount = mesh.vertexBuffers.length;
11841
12160
  command.indexBuffer = mesh.indexBuffer;
12161
+ command.indexFormat = mesh.indexFormat;
11842
12162
  command.indexed = true;
11843
12163
  command.count = mesh.indexCount;
11844
12164
  command.instances = 1;
@@ -12277,6 +12597,7 @@ export class WebGPURenderer {
12277
12597
  command.offsetCount = 0;
12278
12598
  command.vertexCount = 0;
12279
12599
  command.indexBuffer = null;
12600
+ command.indexFormat = 'uint32';
12280
12601
  command.indexed = false;
12281
12602
  command.instances = 1;
12282
12603
  command.indirect = null;
@@ -12344,7 +12665,7 @@ export class WebGPURenderer {
12344
12665
  pass.setVertexBuffer(i, buffer);
12345
12666
  }
12346
12667
  if (command.indexed && command.indexBuffer !== null) {
12347
- pass.setIndexBuffer(command.indexBuffer, 'uint32');
12668
+ pass.setIndexBuffer(command.indexBuffer, command.indexFormat);
12348
12669
  if (command.indirect !== null)
12349
12670
  pass.drawIndexedIndirect(command.indirect, 0);
12350
12671
  else
@@ -13992,6 +14313,13 @@ export class WebGPURenderer {
13992
14313
  this.exposurePass = new ExposurePass(this.surface.device, this.pipelines, this.postSampler);
13993
14314
  this.rebuildRushBindGroup();
13994
14315
  }
14316
+ /**
14317
+ * Where ambient occlusion fades out with distance. **The other backend's
14318
+ * `setAmbientOcclusionFade` carries the reasoning**; this is the same setter over the same state.
14319
+ */
14320
+ setAmbientOcclusionFade(distance, radius) {
14321
+ resolveOcclusionFade(distance, radius, this.aoFade);
14322
+ }
13995
14323
  /**
13996
14324
  * Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
13997
14325
  * to take. **The other backend's `setDepthOfField` carries the reasoning**, whole: the