@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.ts';
2
+ import { markPacking, markPackingOfKey, packingKey } from './vertexFormats.ts';
1
3
  import { mat4 } from 'gl-matrix';
2
4
  import type { ProbeBakeOptions } from '../../reflectionProbe.ts';
3
5
  import { probeFaceRange } from '../../reflectionProbe.ts';
@@ -57,7 +59,7 @@ import type {
57
59
  SkyColors,
58
60
  TranslucentMeshOptions,
59
61
  } from '../webgl2/renderer.ts';
60
- import type { IncrementalMeshHandle, MeshOptions } from '../api.ts';
62
+ import type { IncrementalMeshHandle, MeshOptions, PrepareOptions } from '../api.ts';
61
63
  import { resolveAtmosphere, type ResolvedAtmosphere } from '../../atmosphere.ts';
62
64
  import { AMBIENT_SH_FLOATS, packAmbientSH } from '../../ambientHarmonics.ts';
63
65
  import {
@@ -194,6 +196,18 @@ import {
194
196
  packSurfaceOverlay,
195
197
  type SurfaceOverlay,
196
198
  } from '../../surfaceOverlay.ts';
199
+ import { PROJECTION_FLOATS, packSurfaceProjection } from '../../surfaceProjection.ts';
200
+ import { resolveTransmission } from '../../transmission.ts';
201
+ import { resolveOcclusionFade } from '../../occlusionFade.ts';
202
+ import { orthographicDepthSpan } from '../../lightMatrix.ts';
203
+ import {
204
+ LAYER_FLOATS,
205
+ layerMaskMap,
206
+ layeredProjection,
207
+ layersFitBeside,
208
+ packSurfaceLayers,
209
+ } from '../../surfaceLayers.ts';
210
+ import type { SurfaceLayers } from '../../surfaceLayers.ts';
197
211
  import {
198
212
  frameReflectionsRefused,
199
213
  resolveFrameReflections,
@@ -750,6 +764,12 @@ const MAX_OVERLAYS = 64;
750
764
  */
751
765
  const MAX_SKY_DRAWS = 64;
752
766
 
767
+ /** The draw a preparation asks pipelines for: every option at its default. See `prepareMesh`. */
768
+ const PREPARED_DRAW: TranslucentMeshOptions = {};
769
+ /** A preparation's morph states for a mesh with no targets, and for one with some. */
770
+ const NOT_MORPHED: readonly boolean[] = [false];
771
+ const MORPHED_OR_NOT: readonly boolean[] = [false, true];
772
+
753
773
  /**
754
774
  * Whether the environment probe's permutation fits this device's per-stage binding limits.
755
775
  *
@@ -1530,7 +1550,21 @@ export class WebGPURenderer implements RendererApi {
1530
1550
  * flight at the time, so a consumer that builds more meshes later can wait again.
1531
1551
  */
1532
1552
  async ready(): Promise<void> {
1533
- await Promise.all(this.pipelineTargets.map((cache) => cache.ready()));
1553
+ await Promise.all(this.pipelineCaches().map((cache) => cache.ready()));
1554
+ }
1555
+
1556
+ /** Every distinct cache a lit draw reaches: the frame's, the canvas's, the late pass's, skin's. */
1557
+ private pipelineCaches(): PipelineCache[] {
1558
+ const caches = [...this.pipelineTargets];
1559
+ for (const cache of [this.latePipelines, this.skinPipelines]) {
1560
+ if (cache !== null && !caches.includes(cache)) caches.push(cache);
1561
+ }
1562
+ return caches;
1563
+ }
1564
+
1565
+ /** Whether a lit draw whose pipeline is not built is left out while it compiles. */
1566
+ private skipCompiling(skips: boolean): void {
1567
+ for (const cache of this.pipelineCaches()) cache.skipsCompiling = skips;
1534
1568
  }
1535
1569
 
1536
1570
  private presentedFrameCount = 0;
@@ -2327,6 +2361,8 @@ export class WebGPURenderer implements RendererApi {
2327
2361
 
2328
2362
  private readonly aoStaging = new ArrayBuffer(AO_FRAG_SIZE);
2329
2363
  private readonly aoFloats = new Float32Array(this.aoStaging);
2364
+ /** Where occlusion fades out with distance, `uFade`: none until a caller sets one. */
2365
+ private readonly aoFade = new Float32Array([-1, 0]);
2330
2366
  private aoBindGroup: GPUBindGroup | null = null;
2331
2367
  private readonly aoBlurLayout: GPUBindGroupLayout;
2332
2368
  /**
@@ -2927,6 +2963,7 @@ export class WebGPURenderer implements RendererApi {
2927
2963
  }
2928
2964
  filmCommand.vertexCount = geometry.vertexBuffers.length;
2929
2965
  filmCommand.indexBuffer = geometry.indexBuffer;
2966
+ filmCommand.indexFormat = geometry.indexFormat;
2930
2967
  filmCommand.indexed = true;
2931
2968
  filmCommand.count = geometry.indexCount;
2932
2969
  } else {
@@ -2937,7 +2974,7 @@ export class WebGPURenderer implements RendererApi {
2937
2974
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
2938
2975
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
2939
2976
  }
2940
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
2977
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
2941
2978
  pass.drawIndexed(geometry.indexCount);
2942
2979
  }
2943
2980
  }
@@ -2974,6 +3011,17 @@ export class WebGPURenderer implements RendererApi {
2974
3011
  private modelMap: GpuSurfaceTexture | null = null;
2975
3012
  /** The material's shading model, which picks the lit pipeline: null the standard. */
2976
3013
  private materialModel: SurfaceModel | null = null;
3014
+ /** `uWorldUv`, the material's projection packed by `packSurfaceProjection`. */
3015
+ private readonly projection = new Float32Array(PROJECTION_FLOATS);
3016
+ /** `uLayers`, the material's layers packed by `packSurfaceLayers`. */
3017
+ private readonly layerFloats = new Float32Array(LAYER_FLOATS);
3018
+ /**
3019
+ * `uGlassTint` as the material leaves it, its light from behind and that light's colour, which a
3020
+ * pane overwrites for its own draw and `releaseSeeThrough` puts back. See `transmission.ts`.
3021
+ */
3022
+ private readonly transmission = new Float32Array([-1, -1, -1, 0]);
3023
+ private warnedLayersModelMap = false;
3024
+ private warnedLayersTriplanar = false;
2977
3025
  /** Its numbers, `uModelParams`, packed by `packModel`. */
2978
3026
  private readonly modelParams = new Float32Array(MODEL_PARAM_FLOATS);
2979
3027
  /**
@@ -3448,7 +3496,10 @@ export class WebGPURenderer implements RendererApi {
3448
3496
  const normal = (material?.normal ?? null) as GpuSurfaceTexture | null;
3449
3497
  const orm = (material?.orm ?? null) as GpuSurfaceTexture | null;
3450
3498
  const emissiveMap = (material?.emissive ?? null) as GpuSurfaceTexture | null;
3451
- const modelMap = (material?.modelMap ?? null) as GpuSurfaceTexture | null;
3499
+ const ownModelMap = (material?.modelMap ?? null) as GpuSurfaceTexture | null;
3500
+ /* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
3501
+ const layers = this.layersOf(material);
3502
+ const modelMap = ownModelMap ?? layerMaskMap(layers);
3452
3503
  this.materialModel = material?.model ?? null;
3453
3504
  const reflectivity = material?.reflectivity;
3454
3505
  const environmentGain = material?.environmentGain;
@@ -3459,7 +3510,7 @@ export class WebGPURenderer implements RendererApi {
3459
3510
  this.writeEnvironmentDials();
3460
3511
  /* What the blur spreads this skin by: the last material to name a profile says. */
3461
3512
  const model = material?.model;
3462
- warnUnpagedLightmap(model, modelMap);
3513
+ warnUnpagedLightmap(model, ownModelMap);
3463
3514
  if (model?.kind === 'skin' && this.skinScatter !== null) {
3464
3515
  this.skinScatter.setProfile(
3465
3516
  model.profile,
@@ -3518,12 +3569,25 @@ export class WebGPURenderer implements RendererApi {
3518
3569
  /* The model's numbers and whether its map is bound. See `packModel`. */
3519
3570
  packModel(
3520
3571
  material?.model ?? null,
3521
- modelMap !== null,
3572
+ ownModelMap !== null,
3522
3573
  this.modelParams,
3523
3574
  material?.physicalSpecular === true,
3524
3575
  );
3525
3576
  /* The first physical highlight asked for is when the lit pipelines start carrying one. */
3526
3577
  if (material?.physicalSpecular === true) this.enableLit('PHYSICAL_SPECULAR');
3578
+ /* Maps placed by the world: the first material that asks is when lit pipelines carry it. */
3579
+ const projection = layeredProjection(material?.projection, layers);
3580
+ if (projection !== (material?.projection ?? null)) this.warnLayersTriplanar();
3581
+ packSurfaceProjection(projection, this.projection);
3582
+ f.set(this.projection, this.materialField('uWorldUv'));
3583
+ /* How much light from behind it lets through, and its colour: transmission.ts. */
3584
+ resolveTransmission(material, this.transmission);
3585
+ f.set(this.transmission, this.materialField('uGlassTint'));
3586
+ if ((this.projection[0] as number) > 0) this.enableLit('WORLD_UVS');
3587
+ /* And its layers, the first time a material blends any: layered.ts. */
3588
+ packSurfaceLayers(layers, this.layerFloats);
3589
+ f.set(this.layerFloats, this.materialField('uLayers'));
3590
+ if (layers !== null) this.enableLit('LAYERED');
3527
3591
  f.set(this.modelParams, this.materialField('uModelParams'));
3528
3592
  });
3529
3593
  /*
@@ -3731,6 +3795,14 @@ export class WebGPURenderer implements RendererApi {
3731
3795
  this.materials.dirty();
3732
3796
  }
3733
3797
 
3798
+ /** Whether the draws that follow spend their own opacity on that screen door. See `renderer.ts`. */
3799
+ setDitherOpacity(on: boolean): void {
3800
+ if (this.surface.lost) return;
3801
+ /* The third lane of `uWriteMode`, beside the fade. */
3802
+ this.perFrameFloats[this.materialField('uWriteMode') + 2] = on ? 1 : 0;
3803
+ this.materials.dirty();
3804
+ }
3805
+
3734
3806
  /**
3735
3807
  * How strong the microscopic relief on the surfaces drawn next is, and how coarse.
3736
3808
  *
@@ -5228,6 +5300,11 @@ export class WebGPURenderer implements RendererApi {
5228
5300
  at('uLightViewProj'),
5229
5301
  env === undefined ? NO_LIGHT_MATRIX : (env.lightViewProj as Float32Array),
5230
5302
  );
5303
+ ring.writeFloats(
5304
+ fragmentSlot,
5305
+ at('uMovingLightViewProj'),
5306
+ env === undefined ? NO_LIGHT_MATRIX : (this.movingLightFor(env) as Float32Array),
5307
+ );
5231
5308
  ring.writeFloat(fragmentSlot, at('uShadowMapSize'), this.quality.directionalShadowMapSize);
5232
5309
  /* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
5233
5310
  ring.writeInt(fragmentSlot, at('uPeeledShadowEnabled'), this.peelFilled ? 1 : 0);
@@ -5267,6 +5344,7 @@ export class WebGPURenderer implements RendererApi {
5267
5344
  }
5268
5345
  volumeCommand.vertexCount = mesh.vertexBuffers.length;
5269
5346
  volumeCommand.indexBuffer = mesh.indexBuffer;
5347
+ volumeCommand.indexFormat = mesh.indexFormat;
5270
5348
  volumeCommand.indexed = true;
5271
5349
  volumeCommand.count = mesh.indexCount;
5272
5350
  } else {
@@ -5275,7 +5353,7 @@ export class WebGPURenderer implements RendererApi {
5275
5353
  for (let index = 0; index < mesh.vertexBuffers.length; index++) {
5276
5354
  pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
5277
5355
  }
5278
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
5356
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
5279
5357
  pass.drawIndexed(mesh.indexCount);
5280
5358
  }
5281
5359
  }
@@ -6116,6 +6194,22 @@ export class WebGPURenderer implements RendererApi {
6116
6194
  * the texel-for-texel comparison that settled it.
6117
6195
  */
6118
6196
  private readonly correctedLightViewProj = new Float32Array(16);
6197
+ /**
6198
+ * What a receiver projects through to read the sun's static layers: the environment's
6199
+ * `lightViewProj`, taken at `bindMeshPass` as WebGL2 takes it there.
6200
+ *
6201
+ * **It was the last shadow pass's matrix**, which is the environment's wherever both layers are
6202
+ * drawn with one and the moving layer's wherever they are not: the static layer was then read
6203
+ * through the moving layer's tight square, and a distant pillar lost its shadow on WebGPU and kept
6204
+ * it on WebGL2, measured on demo/dev/sunLayers.html?moving=tight.
6205
+ */
6206
+ private readonly receiverLightViewProj = new Float32Array(16);
6207
+ /**
6208
+ * The matrix the sun's dynamic shadow pass was last drawn with, uncorrected as the lit stage
6209
+ * projects with, and whether one has been. See `movingLightFor`.
6210
+ */
6211
+ private readonly movingLightViewProj = new Float32Array(16);
6212
+ private movingLightKnown = false;
6119
6213
  /**
6120
6214
  * The same matrix *uncorrected*, which is what the flat shader must project with.
6121
6215
  *
@@ -6246,6 +6340,59 @@ export class WebGPURenderer implements RendererApi {
6246
6340
  this.enableLit('GLASS_SHADOWS');
6247
6341
  }
6248
6342
 
6343
+ /**
6344
+ * A material's layers where they can be drawn: none asked, or a model's map in the mask's place,
6345
+ * said once, leave one layer.
6346
+ */
6347
+ private layersOf(
6348
+ material: SurfaceMaterial<GpuSurfaceTexture> | null,
6349
+ ): SurfaceLayers<GpuSurfaceTexture> | null {
6350
+ const layers = material?.layers ?? null;
6351
+ if (layers === null || layers.repeats.length === 0) return null;
6352
+ if (layersFitBeside(layers, material?.modelMap)) return layers;
6353
+ if (!this.warnedLayersModelMap) {
6354
+ this.warnedLayersModelMap = true;
6355
+ console.warn(
6356
+ "WebGPU: a material's layers are drawn as one: its mask is a map, read where a model's map " +
6357
+ "goes, and this material carries a `modelMap` too. A mask in the ORM array (`mask: 'orm'`) " +
6358
+ "or the vertex colour (`mask: 'vertex'`) leaves the slot free.",
6359
+ );
6360
+ }
6361
+ return null;
6362
+ }
6363
+
6364
+ /** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
6365
+ private warnLayersTriplanar(): void {
6366
+ if (this.warnedLayersTriplanar) return;
6367
+ this.warnedLayersTriplanar = true;
6368
+ console.warn(
6369
+ "WebGPU: a layered material's triplanar projection is laid on the horizontal plane: every " +
6370
+ "layer's maps on three planes would be three times the reads. See `SurfaceMaterial.layers`.",
6371
+ );
6372
+ }
6373
+
6374
+ /**
6375
+ * What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
6376
+ * where the lit pipelines carry one (`MOVING_SUN`); the environment's otherwise, which every caller
6377
+ * drawing both layers with one matrix hands it anyway.
6378
+ */
6379
+ private movingLightFor(env: Environment): ReadonlyMat4 {
6380
+ return this.movingLightKnown && this.pipelines.litSwitches.MOVING_SUN
6381
+ ? this.movingLightViewProj
6382
+ : env.lightViewProj;
6383
+ }
6384
+
6385
+ /** The moving layer's own matrix on, the first time a frame draws that layer with one. */
6386
+ private enableMovingSun(env: Environment): void {
6387
+ if (this.pipelines.litSwitches.MOVING_SUN || !this.movingLightKnown) return;
6388
+ for (let k = 0; k < 16; k++) {
6389
+ if (this.movingLightViewProj[k] !== env.lightViewProj[k]) {
6390
+ this.enableLit('MOVING_SUN');
6391
+ return;
6392
+ }
6393
+ }
6394
+ }
6395
+
6249
6396
  /** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
6250
6397
  private enableLit(feature: LitSwitch): void {
6251
6398
  if (this.pipelines.litSwitches[feature]) return;
@@ -6311,7 +6458,7 @@ export class WebGPURenderer implements RendererApi {
6311
6458
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
6312
6459
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
6313
6460
  }
6314
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
6461
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
6315
6462
  pass.drawIndexed(geometry.indexCount);
6316
6463
  },
6317
6464
  /**
@@ -6401,7 +6548,7 @@ export class WebGPURenderer implements RendererApi {
6401
6548
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
6402
6549
  }
6403
6550
  pass.setVertexBuffer(index, gpuBatch.buffer);
6404
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
6551
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
6405
6552
  pass.drawIndexed(geometry.indexCount, count);
6406
6553
  },
6407
6554
  skinnedMesh: (mesh, model, palette, material, cloth) => {
@@ -6463,7 +6610,7 @@ export class WebGPURenderer implements RendererApi {
6463
6610
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
6464
6611
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
6465
6612
  }
6466
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
6613
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
6467
6614
  pass.drawIndexed(geometry.indexCount);
6468
6615
  },
6469
6616
  /**
@@ -6886,6 +7033,8 @@ export class WebGPURenderer implements RendererApi {
6886
7033
  this.overlayPipelines === this.pipelines
6887
7034
  ? [this.pipelines]
6888
7035
  : [this.pipelines, this.overlayPipelines];
7036
+ /* A lit draw first needed inside a frame waits for its compile or is left out while it runs. */
7037
+ this.skipCompiling(quality.pipelineCompile === 'skip');
6889
7038
  this.variant = flatVariant({
6890
7039
  directionalShadows: quality.directionalShadows,
6891
7040
  /* No `bakeReflectionProbe` here; see the field. */
@@ -8138,6 +8287,8 @@ export class WebGPURenderer implements RendererApi {
8138
8287
  present['layers'] = key.includes(':layers');
8139
8288
  /* Nor is a dynamic mesh's split, which moves positions and normals into buffers of their own. */
8140
8289
  present['dynamic'] = key.includes(':dynamic');
8290
+ /* Nor how each attribute travels, which moves every field after a packed one. */
8291
+ markPackingOfKey(present, key);
8141
8292
  return present;
8142
8293
  }
8143
8294
 
@@ -8447,6 +8598,13 @@ export class WebGPURenderer implements RendererApi {
8447
8598
  present['dynamic'] = true;
8448
8599
  key += ':dynamic';
8449
8600
  }
8601
+ /*
8602
+ * And how each attribute travels (`vertexPacking.ts`): a field read as sixteen bits rather than
8603
+ * thirty-two, or not interleaved at all, moves every field after it, so the key says so.
8604
+ */
8605
+ const packing = vertexPackingOf(data, dynamic);
8606
+ markPacking(present, packing);
8607
+ key += packingKey(packing);
8450
8608
  const fullKey = `${this.variant}|${key}`;
8451
8609
  /*
8452
8610
  * **For every target this mesh can land on, which is two when the overlay is its own.**
@@ -8691,6 +8849,8 @@ export class WebGPURenderer implements RendererApi {
8691
8849
  */
8692
8850
  bindMeshPass(camera: Camera, env: Environment): void {
8693
8851
  if (this.surface.lost) return;
8852
+ /* The matrix every receiver projects through: the environment's, as WebGL2's. See the field. */
8853
+ this.receiverLightViewProj.set(env.lightViewProj as Float32Array);
8694
8854
 
8695
8855
  /* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
8696
8856
  if (
@@ -8809,6 +8969,13 @@ export class WebGPURenderer implements RendererApi {
8809
8969
  this.surfaceFog = FOG_RECEDE;
8810
8970
  /* Every material switch off — albedo, ORM map, emissive map, two-sided — as WebGL2 resets them. */
8811
8971
  i.fill(0, at('uMaterialFlags'), at('uMaterialFlags') + 4);
8972
+ /* And no projection: maps follow the mesh's coordinates until a material asks otherwise. */
8973
+ f.fill(0, at('uWorldUv'), at('uWorldUv') + 4);
8974
+ /* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
8975
+ resolveTransmission(null, this.transmission);
8976
+ f.set(this.transmission, at('uGlassTint'));
8977
+ /* Nor layers: one layer until a material asks for more. */
8978
+ f.fill(0, at('uLayers'), at('uLayers') + LAYER_FLOATS);
8812
8979
  f[at('uCutout')] = 0;
8813
8980
  f[at('uCutout') + 1] = 0;
8814
8981
  this.cutoutResolveStaged = 'hard';
@@ -8891,6 +9058,7 @@ export class WebGPURenderer implements RendererApi {
8891
9058
  /* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
8892
9059
  f[at('uWriteMode')] = 0;
8893
9060
  f[at('uWriteMode') + 1] = 0;
9061
+ f[at('uWriteMode') + 2] = 0;
8894
9062
  /* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
8895
9063
  f[at('uEnvironmentDials') + 2] = 1;
8896
9064
  /* No overlay until a draw sets one. See `setSurfaceOverlay`. */
@@ -9184,6 +9352,11 @@ export class WebGPURenderer implements RendererApi {
9184
9352
  f[at('uShadowStrength')] = env.shadowStrength;
9185
9353
  f[at('uShadowMapSize')] = this.quality.directionalShadowMapSize;
9186
9354
  f[at('uShadowDepthSpan')] = env.shadowDepthSpan;
9355
+ /* The moving layer's own matrix and span, where the lit stage reads one: `movingLightFor`. */
9356
+ this.enableMovingSun(env);
9357
+ const moving = this.movingLightFor(env);
9358
+ f.set(moving as Float32Array, at('uMovingLightViewProj'));
9359
+ f[at('uMovingShadowDepthSpan')] = orthographicDepthSpan(moving);
9187
9360
  f[at('uShadowMaxDistance')] = this.quality.directionalShadowMaxDistance;
9188
9361
  f[at('uShadowMaxSlope')] = this.quality.directionalShadowMaxSlope;
9189
9362
  /* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
@@ -9386,7 +9559,7 @@ export class WebGPURenderer implements RendererApi {
9386
9559
  /** A view block: the camera, the light's matrix and the frame's wind, into `ring` at `slot`. */
9387
9560
  private writeView(ring: UniformRing, slot: number, viewProj: Float32Array): void {
9388
9561
  ring.writeFloats(slot, FLAT_VIEW_FIELDS.uViewProj.offset, viewProj);
9389
- ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.lightViewProj);
9562
+ ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.receiverLightViewProj);
9390
9563
  /* The scene's clock, which a bone animation's instances read their moment from. */
9391
9564
  ring.writeFloat(slot, FLAT_VIEW_FIELDS.uSceneTime.offset, this.animationTime);
9392
9565
  const d = this.frameWind;
@@ -9595,6 +9768,225 @@ export class WebGPURenderer implements RendererApi {
9595
9768
  );
9596
9769
  }
9597
9770
 
9771
+ /**
9772
+ * Compile off the main thread every pipeline a draw of `mesh` in `material` takes. **The other
9773
+ * backend's `prepareMesh` carries the contract**; here a draw's key is `meshShape`'s, so a prepared
9774
+ * key is exactly the one the draw asks for, and the compiles are started as a skipping draw starts
9775
+ * them (`PipelineCache.lit`) whatever `pipelineCompile` says, into the frame's own caches.
9776
+ */
9777
+ prepareMesh(
9778
+ mesh: GpuMesh,
9779
+ material: SurfaceMaterial<GpuSurfaceTexture> | null,
9780
+ options: PrepareOptions = {},
9781
+ ): void {
9782
+ if (this.surface.lost) return;
9783
+ this.preparing(
9784
+ material,
9785
+ (into, blend) => {
9786
+ const skinned = mesh.isSkinned;
9787
+ const eight = skinned && mesh.isSkinnedEight;
9788
+ for (const morphed of mesh.morph === null ? NOT_MORPHED : MORPHED_OR_NOT) {
9789
+ this.meshShape(mesh, blend, PREPARED_DRAW, skinned, morphed, eight, false);
9790
+ this.meshPipeline(blend, false, skinned, morphed, eight, false, into);
9791
+ }
9792
+ },
9793
+ options,
9794
+ );
9795
+ }
9796
+
9797
+ /** `prepareMesh` for an instanced batch. See the other backend's `prepareInstanced`. */
9798
+ prepareInstanced(
9799
+ batch: GpuInstancedBatch,
9800
+ material: SurfaceMaterial<GpuSurfaceTexture> | null,
9801
+ options: PrepareOptions = {},
9802
+ ): void {
9803
+ if (this.surface.lost) return;
9804
+ this.preparing(
9805
+ material,
9806
+ (into, blend) => {
9807
+ const coverage = !blend && this.cutoutResolveStaged === 'coverage';
9808
+ this.instancedPipeline(
9809
+ batch.mesh,
9810
+ batch.animation !== null,
9811
+ blend,
9812
+ false,
9813
+ PREPARED_DRAW,
9814
+ true,
9815
+ 0,
9816
+ coverage,
9817
+ into,
9818
+ );
9819
+ },
9820
+ options,
9821
+ );
9822
+ }
9823
+
9824
+ /**
9825
+ * `material` set, every cache a draw in it may build into passed to `start` with whether that draw
9826
+ * blends, compiles started rather than built (`PipelineCache.lit`), and then no material set and
9827
+ * each cache back to what `pipelineCompile` says.
9828
+ */
9829
+ private preparing(
9830
+ material: SurfaceMaterial<GpuSurfaceTexture> | null,
9831
+ start: (into: PipelineCache, blend: boolean) => void,
9832
+ options: PrepareOptions,
9833
+ ): void {
9834
+ this.setMaterial(material);
9835
+ this.skipCompiling(true);
9836
+ try {
9837
+ start(this.pipelines, false);
9838
+ if (options.translucent === true) {
9839
+ start(this.pipelines, true);
9840
+ /* A blended draw in a reconstructing frame lands in the late pass, whose cache is its own. */
9841
+ if (this.latePipelines !== this.pipelines) start(this.latePipelines, true);
9842
+ }
9843
+ } finally {
9844
+ this.skipCompiling(this.quality.pipelineCompile === 'skip');
9845
+ this.setMaterial(null);
9846
+ }
9847
+ }
9848
+
9849
+ /** What `meshShape` worked out for the mesh draw at hand. Reused, so a draw allocates no record. */
9850
+ private readonly drawShape = {
9851
+ base: '',
9852
+ key: '',
9853
+ depthWrite: true,
9854
+ layer: 0,
9855
+ adds: false,
9856
+ coverage: false,
9857
+ splits: false,
9858
+ modelKind: null as SurfaceModelKind | null,
9859
+ };
9860
+
9861
+ /**
9862
+ * The key a mesh draw's pipeline is cached under, and the pipeline state that key encodes, under
9863
+ * the current material and these options, into `drawShape`. **The one statement of it**:
9864
+ * `submitMesh` draws by it and `prepareMesh` compiles by it, so the two cannot disagree about a key.
9865
+ */
9866
+ private meshShape(
9867
+ mesh: GpuMesh,
9868
+ blend: boolean,
9869
+ options: TranslucentMeshOptions,
9870
+ skinned: boolean,
9871
+ morphed: boolean,
9872
+ eight: boolean,
9873
+ clothed: boolean,
9874
+ ): WebGPURenderer['drawShape'] {
9875
+ const modelKind = this.materialModel?.kind ?? null;
9876
+ const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
9877
+ const keyed =
9878
+ `${base}${morphed ? '|morph' : ''}${skinned ? (eight ? '|skin8' : '|skin') : ''}` +
9879
+ `${clothed ? '|cloth' : ''}`;
9880
+ /*
9881
+ * Depth writing and the overlay layer are pipeline state on this backend, so they belong in
9882
+ * the key: two draws of one mesh differing only in these would otherwise share a cached
9883
+ * pipeline and the second would silently take the first one's depth behaviour.
9884
+ *
9885
+ * **Both suffixes are empty for the defaults**, which is what keeps the keys `createMesh`
9886
+ * warms — `fullKey` and `${'`'}${'$'}{fullKey}|blend${'`'}` — the ones an ordinary draw still asks for. A
9887
+ * caller that opts in pays one compile on its first draw, the same bargain the overlay
9888
+ * pipelines above already make.
9889
+ */
9890
+ const depthWrite = options.depthWrite ?? true;
9891
+ const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
9892
+ /* Added rather than blended over: its own blend state, so its own pipeline. */
9893
+ const adds = blend && options.additive === true;
9894
+ const coverage = !blend && this.cutoutResolveStaged === 'coverage';
9895
+ /* Skin under the screen-space blur draws its two halves; see `keepSkinDiffuse`. */
9896
+ const splits = modelKind === 'skin' && !blend && this.skinSplits();
9897
+ const key =
9898
+ `${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
9899
+ `${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
9900
+ /* The two order-independent buffers are two formats and two blend states, so a draw into
9901
+ one must never share a cached pipeline with the same mesh drawn into the other. */
9902
+ `${this.oitMode === 'none' ? '' : `|oit${this.oitMode}`}` +
9903
+ /* Culling is pipeline state here: a two-sided surface culls nothing. */
9904
+ `${this.materialDoubleSided ? '|2s' : ''}` +
9905
+ /* And so is alpha-to-coverage, for a dithered cutout in a multisampled frame. */
9906
+ `${coverage ? '|a2c' : ''}` +
9907
+ /* And the material's shading model, which is the fragment stage's own switch. */
9908
+ `${modelKind === null ? '' : `|m:${modelKind}`}` +
9909
+ /* And which half of a skin it draws, under the screen-space blur. */
9910
+ `${splits ? '|ss' : ''}`;
9911
+
9912
+ const shape = this.drawShape;
9913
+ shape.base = base;
9914
+ shape.key = key;
9915
+ shape.depthWrite = depthWrite;
9916
+ shape.layer = layer;
9917
+ shape.adds = adds;
9918
+ shape.coverage = coverage;
9919
+ shape.splits = splits;
9920
+ shape.modelKind = modelKind;
9921
+ return shape;
9922
+ }
9923
+
9924
+ /**
9925
+ * The pipeline `drawShape` names: the one built, one built here, or none where draws skip a
9926
+ * compile and this one has not landed (`PipelineCache.lit`) — and none for a split skin until all
9927
+ * of its halves have.
9928
+ */
9929
+ private meshPipeline(
9930
+ blend: boolean,
9931
+ lands: boolean,
9932
+ skinned: boolean,
9933
+ morphed: boolean,
9934
+ eight: boolean,
9935
+ clothed: boolean,
9936
+ /** Which cache to build into: the one the draw's target reads, unless a preparation says. */
9937
+ into: PipelineCache | null = null,
9938
+ ): GPURenderPipeline | null {
9939
+ const { base, key, depthWrite, layer, adds, coverage, splits, modelKind } = this.drawShape;
9940
+ /*
9941
+ * **Asynchronous by default, synchronous if it has to be, and never absent.**
9942
+ *
9943
+ * `createMesh` compiles off the main thread, so a draw can legitimately arrive before the
9944
+ * driver has finished — a consumer that awaits `ready()` never sees this, and one that
9945
+ * draws immediately gets the old behaviour rather than an exception. Correctness cannot be
9946
+ * allowed to depend on which of those happened, so the miss builds the pipeline here and
9947
+ * pays the compile it was trying to avoid. That is a slow frame; the alternative is a
9948
+ * thrown error in the middle of one.
9949
+ */
9950
+ const pipelines = into ?? (blend ? this.blendedPipelines(lands) : this.targetPipelines());
9951
+ let pipeline: GPURenderPipeline | null = pipelines.peek(key) ?? null;
9952
+ if (pipeline === null) {
9953
+ const present = this.meshPresent.get(base);
9954
+ if (present === undefined) {
9955
+ throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
9956
+ }
9957
+ pipeline = flatPipeline(
9958
+ pipelines,
9959
+ this.surface.device,
9960
+ this.flatLayoutFor(skinned, morphed),
9961
+ this.variant,
9962
+ key,
9963
+ present,
9964
+ adds ? 'additive' : blend,
9965
+ skinned,
9966
+ morphed,
9967
+ depthWrite,
9968
+ layer,
9969
+ false,
9970
+ this.oitMode,
9971
+ this.materialDoubleSided,
9972
+ coverage,
9973
+ eight,
9974
+ clothed,
9975
+ modelKind,
9976
+ splits ? 'scene' : 'whole',
9977
+ );
9978
+ }
9979
+ /* A split skin draws its three halves or none of them. */
9980
+ if (
9981
+ pipeline !== null &&
9982
+ splits &&
9983
+ !this.skinHalvesReady(key, base, skinned, morphed, layer, eight, clothed)
9984
+ ) {
9985
+ pipeline = null;
9986
+ }
9987
+ return pipeline;
9988
+ }
9989
+
9598
9990
  /**
9599
9991
  * @param blend Which of the two entry points above this came through, and **not** something to
9600
9992
  * infer from `opacity`.
@@ -9720,7 +10112,6 @@ export class WebGPURenderer implements RendererApi {
9720
10112
  /* An eye's axis is the draw's, written into the open material. See `eyeAxisInWorld`. */
9721
10113
  const modelKind = this.materialModel?.kind ?? null;
9722
10114
  if (modelKind === 'eye') this.writeEyeAxis(model, this.skinPaletteSlot >= 0 && mesh.isSkinned);
9723
- const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
9724
10115
  /*
9725
10116
  * A rigged mesh takes the skinned vertex variant, an unrigged one the plain variant, and the
9726
10117
  * key carries the flag so the two never share a cached pipeline. A rigged mesh whose caller
@@ -9735,41 +10126,6 @@ export class WebGPURenderer implements RendererApi {
9735
10126
  /* A cloth set and a skinned draw: the pipeline built with CLOTH_BOUND on, warmed when the
9736
10127
  binding was made. See `createClothBinding`. */
9737
10128
  const clothed = skinned && this.cloth !== null;
9738
- const keyed =
9739
- `${base}${morphed ? '|morph' : ''}${skinned ? (eight ? '|skin8' : '|skin') : ''}` +
9740
- `${clothed ? '|cloth' : ''}`;
9741
- /*
9742
- * Depth writing and the overlay layer are pipeline state on this backend, so they belong in
9743
- * the key: two draws of one mesh differing only in these would otherwise share a cached
9744
- * pipeline and the second would silently take the first one's depth behaviour.
9745
- *
9746
- * **Both suffixes are empty for the defaults**, which is what keeps the keys `createMesh`
9747
- * warms — `fullKey` and `${'`'}${'$'}{fullKey}|blend${'`'}` — the ones an ordinary draw still asks for. A
9748
- * caller that opts in pays one compile on its first draw, the same bargain the overlay
9749
- * pipelines above already make.
9750
- */
9751
- const depthWrite = options.depthWrite ?? true;
9752
- const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
9753
- /* Added rather than blended over: its own blend state, so its own pipeline. */
9754
- const adds = blend && options.additive === true;
9755
- const coverage = !blend && this.cutoutResolveStaged === 'coverage';
9756
- /* Skin under the screen-space blur draws its two halves; see `keepSkinDiffuse`. */
9757
- const splits = modelKind === 'skin' && !blend && this.skinSplits();
9758
- const key =
9759
- `${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
9760
- `${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
9761
- /* The two order-independent buffers are two formats and two blend states, so a draw into
9762
- one must never share a cached pipeline with the same mesh drawn into the other. */
9763
- `${this.oitMode === 'none' ? '' : `|oit${this.oitMode}`}` +
9764
- /* Culling is pipeline state here: a two-sided surface culls nothing. */
9765
- `${this.materialDoubleSided ? '|2s' : ''}` +
9766
- /* And so is alpha-to-coverage, for a dithered cutout in a multisampled frame. */
9767
- `${coverage ? '|a2c' : ''}` +
9768
- /* And the material's shading model, which is the fragment stage's own switch. */
9769
- `${modelKind === null ? '' : `|m:${modelKind}`}` +
9770
- /* And which half of a skin it draws, under the screen-space blur. */
9771
- `${splits ? '|ss' : ''}`;
9772
-
9773
10129
  /*
9774
10130
  * The morph uniforms, written only for a morphed draw and read from the *morphed* variant's
9775
10131
  * field table. `FLAT_VERT_FIELDS` is the plain variant's and does not name them at all — every
@@ -9782,45 +10138,17 @@ export class WebGPURenderer implements RendererApi {
9782
10138
  this.perDraw.writeInt(slot, fields.uMorphTargetCount.offset, mesh.morph.targetCount);
9783
10139
  this.perDraw.writeInt(slot, fields.uMorphTextureWidth.offset, mesh.morph.width);
9784
10140
  }
9785
- /*
9786
- * **Asynchronous by default, synchronous if it has to be, and never absent.**
9787
- *
9788
- * `createMesh` compiles off the main thread, so a draw can legitimately arrive before the
9789
- * driver has finished — a consumer that awaits `ready()` never sees this, and one that
9790
- * draws immediately gets the old behaviour rather than an exception. Correctness cannot be
9791
- * allowed to depend on which of those happened, so the miss builds the pipeline here and
9792
- * pays the compile it was trying to avoid. That is a slow frame; the alternative is a
9793
- * thrown error in the middle of one.
9794
- */
9795
- const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
9796
- let pipeline = pipelines.peek(key);
9797
- if (pipeline === undefined) {
9798
- const present = this.meshPresent.get(base);
9799
- if (present === undefined) {
9800
- throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
9801
- }
9802
- pipeline = flatPipeline(
9803
- pipelines,
9804
- this.surface.device,
9805
- this.flatLayoutFor(skinned, morphed),
9806
- this.variant,
9807
- key,
9808
- present,
9809
- adds ? 'additive' : blend,
9810
- skinned,
9811
- morphed,
9812
- depthWrite,
9813
- layer,
9814
- false,
9815
- this.oitMode,
9816
- this.materialDoubleSided,
9817
- coverage,
9818
- eight,
9819
- clothed,
9820
- modelKind,
9821
- splits ? 'scene' : 'whole',
9822
- );
9823
- }
10141
+ /* Its pipeline, by the key a prepared draw compiled too: see `meshShape`. */
10142
+ const { base, key, layer, depthWrite, splits } = this.meshShape(
10143
+ mesh,
10144
+ blend,
10145
+ options,
10146
+ skinned,
10147
+ morphed,
10148
+ eight,
10149
+ clothed,
10150
+ );
10151
+ let pipeline = this.meshPipeline(blend, lands, skinned, morphed, eight, clothed);
9824
10152
 
9825
10153
  /*
9826
10154
  * Guards the draw call only, not the restore below.
@@ -9838,8 +10166,9 @@ export class WebGPURenderer implements RendererApi {
9838
10166
  * close to the 256-slot budget before the gateway, the play controls, the waveform, the
9839
10167
  * label handles and the sparkles are even counted.
9840
10168
  */
9841
- const material = this.materialSlotForDraw();
9842
- if (material !== null) {
10169
+ /* A draw whose pipeline is still compiling is left out, its state restored all the same. */
10170
+ const material = pipeline === null ? null : this.materialSlotForDraw();
10171
+ if (pipeline !== null && material !== null) {
9843
10172
  /*
9844
10173
  * Record rather than issue. Every per-draw value is already in `slot` and `material` —
9845
10174
  * the uniform rings are the state arena this design would otherwise have had to build —
@@ -9861,6 +10190,7 @@ export class WebGPURenderer implements RendererApi {
9861
10190
  }
9862
10191
  command.vertexCount = mesh.vertexBuffers.length;
9863
10192
  command.indexBuffer = mesh.indexBuffer;
10193
+ command.indexFormat = mesh.indexFormat;
9864
10194
  command.indexed = true;
9865
10195
  command.count = mesh.indexCount;
9866
10196
  } else {
@@ -9876,7 +10206,7 @@ export class WebGPURenderer implements RendererApi {
9876
10206
  for (let index = 0; index < mesh.vertexBuffers.length; index++) {
9877
10207
  pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
9878
10208
  }
9879
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
10209
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
9880
10210
  pass.drawIndexed(mesh.indexCount);
9881
10211
  }
9882
10212
  if (splits) {
@@ -10209,6 +10539,8 @@ export class WebGPURenderer implements RendererApi {
10209
10539
  const seeAt = this.materialField('uSeeThrough');
10210
10540
  this.perFrameFloats[seeAt] = 0;
10211
10541
  this.perFrameFloats[seeAt + 2] = 0;
10542
+ /* The material's light through, over the pane's tint: transmission.ts. */
10543
+ this.perFrameFloats.set(this.transmission, this.materialField('uGlassTint'));
10212
10544
  }
10213
10545
 
10214
10546
  /**
@@ -10223,6 +10555,76 @@ export class WebGPURenderer implements RendererApi {
10223
10555
  * **What it gives up** against `drawMesh` is skinning and morphing, refused where the variant
10224
10556
  * is built, and a per-instance opacity: `opacity` here dims the whole batch.
10225
10557
  */
10558
+ /**
10559
+ * An instanced draw's pipeline under the current material and these options: the one built, one
10560
+ * built here, or none while it compiles (`PipelineCache.lit`), its key, base and model left in
10561
+ * `drawShape`. **The one statement of an instanced key**: `submitInstanced` draws by it and
10562
+ * `prepareInstanced` compiles by it.
10563
+ */
10564
+ private instancedPipeline(
10565
+ mesh: GpuMesh,
10566
+ animated: boolean,
10567
+ blend: boolean,
10568
+ lands: boolean,
10569
+ options: TranslucentMeshOptions,
10570
+ depthWrite: boolean,
10571
+ layer: number,
10572
+ coverage: boolean,
10573
+ /** Which cache to build into: the one the draw's target reads, unless a preparation says. */
10574
+ into: PipelineCache | null = null,
10575
+ ): GPURenderPipeline | null {
10576
+ /* A batch shades by its material's model too; an eye's axis is a placement's, not a batch's. */
10577
+ const modelKind = this.materialModel?.kind ?? null;
10578
+ const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
10579
+ /* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
10580
+ a third vertex buffer and a different vertex module, and sharing a cache entry with the
10581
+ plain one would hand a draw the wrong layout. */
10582
+ const adds = blend && options.additive === true;
10583
+ const key =
10584
+ `${base}|inst${!animated ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
10585
+ `${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
10586
+ `${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
10587
+ `${modelKind === null ? '' : `|m:${modelKind}`}`;
10588
+
10589
+ const shape = this.drawShape;
10590
+ shape.base = base;
10591
+ shape.key = key;
10592
+ shape.modelKind = modelKind;
10593
+ const pipelines = into ?? (blend ? this.blendedPipelines(lands) : this.targetPipelines());
10594
+ let pipeline: GPURenderPipeline | null = pipelines.peek(key) ?? null;
10595
+ if (pipeline === null) {
10596
+ const present = this.meshPresent.get(base);
10597
+ if (present === undefined) {
10598
+ throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
10599
+ }
10600
+ pipeline = flatPipeline(
10601
+ pipelines,
10602
+ this.surface.device,
10603
+ !animated ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout,
10604
+ this.variant,
10605
+ key,
10606
+ present,
10607
+ adds ? 'additive' : blend,
10608
+ false,
10609
+ false,
10610
+ depthWrite,
10611
+ layer,
10612
+ true,
10613
+ 'none',
10614
+ this.materialDoubleSided,
10615
+ coverage,
10616
+ false,
10617
+ false,
10618
+ modelKind,
10619
+ 'whole',
10620
+ false,
10621
+ animated,
10622
+ );
10623
+ }
10624
+
10625
+ return pipeline;
10626
+ }
10627
+
10226
10628
  /**
10227
10629
  * The next of a batch's cull slots this frame, with its cull queued against the current view; null
10228
10630
  * when the batch has drawn from `MAX_CULL_SLOTS` views already or the frame's job ring is full,
@@ -10341,53 +10743,22 @@ export class WebGPURenderer implements RendererApi {
10341
10743
  const own =
10342
10744
  ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog) || ditheredBlend;
10343
10745
  if (own) this.materials.dirty();
10344
- /* A batch shades by its material's model too; an eye's axis is a placement's, not a batch's. */
10345
- const modelKind = this.materialModel?.kind ?? null;
10346
- const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
10347
- /* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
10348
- a third vertex buffer and a different vertex module, and sharing a cache entry with the
10349
- plain one would hand a draw the wrong layout. */
10350
- const adds = blend && options.additive === true;
10351
- const key =
10352
- `${base}|inst${animation === null ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
10353
- `${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
10354
- `${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
10355
- `${modelKind === null ? '' : `|m:${modelKind}`}`;
10356
-
10357
- const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
10358
- let pipeline = pipelines.peek(key);
10359
- if (pipeline === undefined) {
10360
- const present = this.meshPresent.get(base);
10361
- if (present === undefined) {
10362
- throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
10363
- }
10364
- pipeline = flatPipeline(
10365
- pipelines,
10366
- this.surface.device,
10367
- animation === null ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout,
10368
- this.variant,
10369
- key,
10370
- present,
10371
- adds ? 'additive' : blend,
10372
- false,
10373
- false,
10374
- depthWrite,
10375
- layer,
10376
- true,
10377
- 'none',
10378
- this.materialDoubleSided,
10379
- coverage,
10380
- false,
10381
- false,
10382
- modelKind,
10383
- 'whole',
10384
- false,
10385
- animation !== null,
10386
- );
10387
- }
10746
+ /* Its pipeline, by the key a prepared batch compiled too: see `instancedPipeline`. */
10747
+ const pipeline = this.instancedPipeline(
10748
+ mesh,
10749
+ animation !== null,
10750
+ blend,
10751
+ lands,
10752
+ options,
10753
+ depthWrite,
10754
+ layer,
10755
+ coverage,
10756
+ );
10757
+ const { base, key, modelKind } = this.drawShape;
10388
10758
 
10389
- const material = this.materialSlotForDraw();
10390
- if (material !== null) {
10759
+ /* A batch whose pipeline is still compiling is left out, as a mesh is. */
10760
+ const material = pipeline === null ? null : this.materialSlotForDraw();
10761
+ if (pipeline !== null && material !== null) {
10391
10762
  const command = blend
10392
10763
  ? this.recordBlended(0, lands)
10393
10764
  : this.recordDraw(0, this.currentTarget());
@@ -10409,6 +10780,7 @@ export class WebGPURenderer implements RendererApi {
10409
10780
  command.vertexBuffers[index] = instanceBuffer;
10410
10781
  command.vertexCount = mesh.vertexBuffers.length + 1;
10411
10782
  command.indexBuffer = mesh.indexBuffer;
10783
+ command.indexFormat = mesh.indexFormat;
10412
10784
  command.indexed = true;
10413
10785
  command.count = mesh.indexCount;
10414
10786
  command.instances = count;
@@ -10429,7 +10801,7 @@ export class WebGPURenderer implements RendererApi {
10429
10801
  pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
10430
10802
  }
10431
10803
  pass.setVertexBuffer(index, instanceBuffer);
10432
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
10804
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
10433
10805
  if (cut === null) pass.drawIndexed(mesh.indexCount, count);
10434
10806
  else pass.drawIndexedIndirect(cut.args, 0);
10435
10807
  }
@@ -10664,6 +11036,11 @@ export class WebGPURenderer implements RendererApi {
10664
11036
  if (view === null) return false;
10665
11037
 
10666
11038
  this.lightViewProj.set(lightViewProj as Float32Array);
11039
+ /* The moving layer is read through the matrix it is drawn with: see `movingLightFor`. */
11040
+ if (dynamic) {
11041
+ this.movingLightViewProj.set(lightViewProj as Float32Array);
11042
+ this.movingLightKnown = true;
11043
+ }
10667
11044
  this.reopenViews();
10668
11045
  mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
10669
11046
  this.shadowLayerIsPeel = peel;
@@ -10975,10 +11352,13 @@ export class WebGPURenderer implements RendererApi {
10975
11352
  this.lateViewBlock.slot = list.viewOffset(0);
10976
11353
  this.materials.dirty();
10977
11354
  this.staticRecording = recording;
11355
+ /* A recorded list keeps what it recorded, so every draw of it waits for its pipeline. */
11356
+ this.skipCompiling(false);
10978
11357
  try {
10979
11358
  this.sceneCasterMaterial = undefined;
10980
11359
  replayStaticDraws(list, this.sceneCasterSink);
10981
11360
  } finally {
11361
+ this.skipCompiling(this.quality.pipelineCompile === 'skip');
10982
11362
  this.staticRecording = null;
10983
11363
  this.perDraw = perDraw;
10984
11364
  this.passRing = passRing;
@@ -11285,7 +11665,7 @@ export class WebGPURenderer implements RendererApi {
11285
11665
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
11286
11666
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
11287
11667
  }
11288
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
11668
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
11289
11669
  pass.drawIndexed(geometry.indexCount);
11290
11670
  },
11291
11671
  instanced: (batch, data, material, glass) => {
@@ -11330,7 +11710,7 @@ export class WebGPURenderer implements RendererApi {
11330
11710
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
11331
11711
  }
11332
11712
  pass.setVertexBuffer(index, gpuBatch.buffer);
11333
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
11713
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
11334
11714
  pass.drawIndexed(geometry.indexCount, count);
11335
11715
  },
11336
11716
  skinnedMesh: (mesh, model, palette, _material, glass) => {
@@ -11366,7 +11746,7 @@ export class WebGPURenderer implements RendererApi {
11366
11746
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
11367
11747
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
11368
11748
  }
11369
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
11749
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
11370
11750
  pass.drawIndexed(geometry.indexCount);
11371
11751
  },
11372
11752
  };
@@ -13865,6 +14245,10 @@ export class WebGPURenderer implements RendererApi {
13865
14245
  /* Uncorrected, for the reason the shaft's own copy of this line carries: the shader does its
13866
14246
  own `* 0.5 + 0.5`, and correcting the matrix as well doubles it. */
13867
14247
  f.set(env?.lightViewProj ?? this.mediumIdentity, at('uLightViewProj'));
14248
+ f.set(
14249
+ (env === null ? this.mediumIdentity : this.movingLightFor(env)) as Float32Array,
14250
+ at('uMovingLightViewProj'),
14251
+ );
13868
14252
  ints[at('uPeeledShadowEnabled')] = this.peelView === null || !this.peelFilled ? 0 : 1;
13869
14253
  device.queue.writeBuffer(this.mediumUniforms, 0, this.mediumStaging);
13870
14254
 
@@ -13945,6 +14329,7 @@ export class WebGPURenderer implements RendererApi {
13945
14329
  const f = this.aoFloats;
13946
14330
  const at = (name: string): number => this.postField(AO_FRAG_FIELDS, name);
13947
14331
  f[at('uRadius')] = this.quality.ambientOcclusionRadius;
14332
+ f.set(this.aoFade, at('uFade'));
13948
14333
  f.set(this.aoProjScale, at('uProjScale'));
13949
14334
  f.set(this.aoInvProjection, at('uInvProjection'));
13950
14335
  device.queue.writeBuffer(this.aoUniforms, 0, this.aoStaging);
@@ -14389,6 +14774,8 @@ export class WebGPURenderer implements RendererApi {
14389
14774
  true,
14390
14775
  animation !== null,
14391
14776
  );
14777
+ /* Still compiling where draws skip: the probe stands in for this surface until it lands. */
14778
+ if (pipeline === null) return;
14392
14779
  /* A static list's surface halves go into its own pool, beside its draws. */
14393
14780
  const command =
14394
14781
  this.staticRecording === null ? surface.take() : takeRecorded(this.staticRecording.surface);
@@ -14409,6 +14796,7 @@ export class WebGPURenderer implements RendererApi {
14409
14796
  if (instanceBuffer !== null) command.vertexBuffers[index++] = instanceBuffer;
14410
14797
  command.vertexCount = index;
14411
14798
  command.indexBuffer = mesh.indexBuffer;
14799
+ command.indexFormat = mesh.indexFormat;
14412
14800
  command.indexed = true;
14413
14801
  command.count = mesh.indexCount;
14414
14802
  command.instances = instances;
@@ -14421,6 +14809,78 @@ export class WebGPURenderer implements RendererApi {
14421
14809
  * light alone and its colour alone into the blur's targets and test for the depth its frame half
14422
14810
  * wrote.
14423
14811
  */
14812
+ /**
14813
+ * Whether a split skin draw's two kept halves have pipelines, starting the compile of either that
14814
+ * has not: a skin drawn without them would lose its diffuse light for the frames they take, so a
14815
+ * split skin draws all three halves or none. See `PipelineCache.lit`.
14816
+ */
14817
+ private skinHalvesReady(
14818
+ frameKey: string,
14819
+ base: string,
14820
+ skinned: boolean,
14821
+ morphed: boolean,
14822
+ layer: number,
14823
+ eight: boolean,
14824
+ clothed: boolean,
14825
+ ): boolean {
14826
+ let ready = true;
14827
+ for (const half of SKIN_KEPT_HALVES) {
14828
+ const pipeline = this.skinHalfPipeline(
14829
+ half,
14830
+ frameKey,
14831
+ base,
14832
+ skinned,
14833
+ morphed,
14834
+ layer,
14835
+ eight,
14836
+ clothed,
14837
+ );
14838
+ if (pipeline === null) ready = false;
14839
+ }
14840
+ return ready;
14841
+ }
14842
+
14843
+ /** One kept half's pipeline, or none where it is still compiling: see `PipelineCache.lit`. */
14844
+ private skinHalfPipeline(
14845
+ half: (typeof SKIN_KEPT_HALVES)[number],
14846
+ frameKey: string,
14847
+ base: string,
14848
+ skinned: boolean,
14849
+ morphed: boolean,
14850
+ layer: number,
14851
+ eight: boolean,
14852
+ clothed: boolean,
14853
+ ): GPURenderPipeline | null {
14854
+ const pipelines = this.skinPipelines;
14855
+ const present = this.meshPresent.get(base);
14856
+ if (pipelines === null || present === undefined) return null;
14857
+ const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
14858
+ return (
14859
+ pipelines.peek(key) ??
14860
+ flatPipeline(
14861
+ pipelines,
14862
+ this.surface.device,
14863
+ this.flatLayoutFor(skinned, morphed),
14864
+ this.variant,
14865
+ key,
14866
+ present,
14867
+ false,
14868
+ skinned,
14869
+ morphed,
14870
+ true,
14871
+ layer,
14872
+ false,
14873
+ 'none',
14874
+ this.materialDoubleSided,
14875
+ false,
14876
+ eight,
14877
+ clothed,
14878
+ 'skin',
14879
+ half,
14880
+ )
14881
+ );
14882
+ }
14883
+
14424
14884
  private keepSkinDiffuse(
14425
14885
  mesh: GpuMesh,
14426
14886
  frameKey: string,
@@ -14446,30 +14906,18 @@ export class WebGPURenderer implements RendererApi {
14446
14906
  const bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
14447
14907
  /* The light alone, which the blur spreads, then the colour alone, which it is multiplied by. */
14448
14908
  for (const half of SKIN_KEPT_HALVES) {
14449
- const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
14450
- const pipeline =
14451
- pipelines.peek(key) ??
14452
- flatPipeline(
14453
- pipelines,
14454
- this.surface.device,
14455
- this.flatLayoutFor(skinned, morphed),
14456
- this.variant,
14457
- key,
14458
- present,
14459
- false,
14460
- skinned,
14461
- morphed,
14462
- true,
14463
- layer,
14464
- false,
14465
- 'none',
14466
- this.materialDoubleSided,
14467
- false,
14468
- eight,
14469
- clothed,
14470
- 'skin',
14471
- half,
14472
- );
14909
+ const pipeline = this.skinHalfPipeline(
14910
+ half,
14911
+ frameKey,
14912
+ base,
14913
+ skinned,
14914
+ morphed,
14915
+ layer,
14916
+ eight,
14917
+ clothed,
14918
+ );
14919
+ /* Both landed before the draw was let through: see `skinHalvesReady`. */
14920
+ if (pipeline === null) return;
14473
14921
  const command = half === 'diffuse' ? skin.take() : skin.takeAlbedo();
14474
14922
  command.pipeline = pipeline;
14475
14923
  command.bindGroup = bindGroup;
@@ -14483,6 +14931,7 @@ export class WebGPURenderer implements RendererApi {
14483
14931
  }
14484
14932
  command.vertexCount = mesh.vertexBuffers.length;
14485
14933
  command.indexBuffer = mesh.indexBuffer;
14934
+ command.indexFormat = mesh.indexFormat;
14486
14935
  command.indexed = true;
14487
14936
  command.count = mesh.indexCount;
14488
14937
  command.instances = 1;
@@ -14936,6 +15385,7 @@ export class WebGPURenderer implements RendererApi {
14936
15385
  command.offsetCount = 0;
14937
15386
  command.vertexCount = 0;
14938
15387
  command.indexBuffer = null;
15388
+ command.indexFormat = 'uint32';
14939
15389
  command.indexed = false;
14940
15390
  command.instances = 1;
14941
15391
  command.indirect = null;
@@ -14995,7 +15445,7 @@ export class WebGPURenderer implements RendererApi {
14995
15445
  if (buffer != null) pass.setVertexBuffer(i, buffer);
14996
15446
  }
14997
15447
  if (command.indexed && command.indexBuffer !== null) {
14998
- pass.setIndexBuffer(command.indexBuffer, 'uint32');
15448
+ pass.setIndexBuffer(command.indexBuffer, command.indexFormat);
14999
15449
  if (command.indirect !== null) pass.drawIndexedIndirect(command.indirect, 0);
15000
15450
  else pass.drawIndexed(command.count, command.instances);
15001
15451
  } else {
@@ -16781,6 +17231,14 @@ export class WebGPURenderer implements RendererApi {
16781
17231
  this.rebuildRushBindGroup();
16782
17232
  }
16783
17233
 
17234
+ /**
17235
+ * Where ambient occlusion fades out with distance. **The other backend's
17236
+ * `setAmbientOcclusionFade` carries the reasoning**; this is the same setter over the same state.
17237
+ */
17238
+ setAmbientOcclusionFade(distance: number, radius: number): void {
17239
+ resolveOcclusionFade(distance, radius, this.aoFade);
17240
+ }
17241
+
16784
17242
  /**
16785
17243
  * Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
16786
17244
  * to take. **The other backend's `setDepthOfField` carries the reasoning**, whole: the