@driftengine/core 4.10.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.json +71 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +4 -3
  4. package/dist/index.js +1 -1
  5. package/dist/render/backend/webgl2/renderer.d.ts +99 -2
  6. package/dist/render/backend/webgl2/renderer.js +361 -42
  7. package/dist/render/backend/webgpu/buffers.d.ts +5 -0
  8. package/dist/render/backend/webgpu/buffers.js +98 -48
  9. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  10. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  11. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  12. package/dist/render/backend/webgpu/clothOps.js +78 -0
  13. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  14. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  15. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  16. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  17. package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
  18. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  19. package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
  20. package/dist/render/backend/webgpu/flatPass.js +2 -1
  21. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  22. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  23. package/dist/render/backend/webgpu/latePass.js +1 -0
  24. package/dist/render/backend/webgpu/motionPass.js +15 -6
  25. package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
  26. package/dist/render/backend/webgpu/pipelineCache.js +39 -1
  27. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  28. package/dist/render/backend/webgpu/renderer.d.ts +48 -0
  29. package/dist/render/backend/webgpu/renderer.js +207 -27
  30. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  31. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  32. package/dist/render/backend/webgpu/staticBundles.js +2 -1
  33. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  34. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  35. package/dist/render/globalMediumPass.d.ts +2 -0
  36. package/dist/render/globalMediumPass.js +1 -0
  37. package/dist/render/indexWidth.d.ts +17 -0
  38. package/dist/render/indexWidth.js +23 -0
  39. package/dist/render/lightMatrix.d.ts +11 -0
  40. package/dist/render/lightMatrix.js +14 -0
  41. package/dist/render/mesh.d.ts +9 -0
  42. package/dist/render/mesh.js +38 -14
  43. package/dist/render/renderQuality.d.ts +30 -0
  44. package/dist/render/renderQuality.js +5 -0
  45. package/dist/render/rendererHarness.d.ts +7 -0
  46. package/dist/render/rendererHarness.js +12 -0
  47. package/dist/render/shader.d.ts +15 -0
  48. package/dist/render/shader.js +41 -0
  49. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  50. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  51. package/dist/render/shaders/flat/glassTint.js +3 -0
  52. package/dist/render/shaders/flat/index.d.ts +15 -0
  53. package/dist/render/shaders/flat/index.js +15 -2
  54. package/dist/render/shaders/flat/layered.d.ts +19 -0
  55. package/dist/render/shaders/flat/layered.js +115 -0
  56. package/dist/render/shaders/flat/main.d.ts +1 -1
  57. package/dist/render/shaders/flat/main.js +43 -16
  58. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  59. package/dist/render/shaders/flat/mapReads.js +38 -0
  60. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  61. package/dist/render/shaders/flat/movingSun.js +46 -0
  62. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  63. package/dist/render/shaders/flat/normalMap.js +14 -3
  64. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  65. package/dist/render/shaders/flat/overlay.js +41 -19
  66. package/dist/render/shaders/flat/preamble.js +23 -5
  67. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  68. package/dist/render/shaders/flat/worldUv.js +113 -0
  69. package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
  70. package/dist/render/shaders/generated/flat.wgsl.js +791 -391
  71. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  72. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  73. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  74. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  75. package/dist/render/shaders/globalMedium.js +9 -1
  76. package/dist/render/shaders/lightVolume.js +9 -1
  77. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  78. package/dist/render/shaders/tangentFrame.js +16 -3
  79. package/dist/render/skinnedClothRun.d.ts +28 -0
  80. package/dist/render/skinnedClothRun.js +103 -35
  81. package/dist/render/surfaceLayers.d.ts +39 -0
  82. package/dist/render/surfaceLayers.js +26 -0
  83. package/dist/render/surfaceOverlay.d.ts +16 -3
  84. package/dist/render/surfaceOverlay.js +11 -6
  85. package/dist/render/surfaceProjection.d.ts +34 -0
  86. package/dist/render/surfaceProjection.js +30 -0
  87. package/dist/render/surfaceTexture.d.ts +30 -0
  88. package/dist/render/vertexPacking.d.ts +48 -0
  89. package/dist/render/vertexPacking.js +68 -0
  90. package/package.json +6 -6
  91. package/src/index.ts +4 -2
  92. package/src/render/backend/webgl2/renderer.ts +408 -55
  93. package/src/render/backend/webgpu/buffers.ts +153 -54
  94. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  95. package/src/render/backend/webgpu/clothOps.ts +86 -0
  96. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  97. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  98. package/src/render/backend/webgpu/drawCommand.ts +3 -0
  99. package/src/render/backend/webgpu/flatPass.ts +26 -28
  100. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  101. package/src/render/backend/webgpu/latePass.ts +1 -0
  102. package/src/render/backend/webgpu/motionPass.ts +18 -7
  103. package/src/render/backend/webgpu/pipelineCache.ts +46 -7
  104. package/src/render/backend/webgpu/renderer.ts +274 -48
  105. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  106. package/src/render/backend/webgpu/staticBundles.ts +2 -1
  107. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  108. package/src/render/globalMediumPass.ts +7 -0
  109. package/src/render/indexWidth.ts +27 -0
  110. package/src/render/lightMatrix.ts +15 -0
  111. package/src/render/mesh.ts +66 -14
  112. package/src/render/renderQuality.ts +38 -0
  113. package/src/render/rendererHarness.ts +15 -0
  114. package/src/render/shader.ts +59 -0
  115. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  116. package/src/render/shaders/flat/glassTint.ts +7 -1
  117. package/src/render/shaders/flat/index.ts +30 -2
  118. package/src/render/shaders/flat/layered.ts +115 -0
  119. package/src/render/shaders/flat/main.ts +43 -16
  120. package/src/render/shaders/flat/mapReads.ts +38 -0
  121. package/src/render/shaders/flat/movingSun.ts +46 -0
  122. package/src/render/shaders/flat/normalMap.ts +14 -3
  123. package/src/render/shaders/flat/overlay.ts +41 -19
  124. package/src/render/shaders/flat/preamble.ts +23 -5
  125. package/src/render/shaders/flat/worldUv.ts +113 -0
  126. package/src/render/shaders/generated/flat.wgsl.ts +791 -391
  127. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  128. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  129. package/src/render/shaders/globalMedium.ts +9 -1
  130. package/src/render/shaders/lightVolume.ts +9 -1
  131. package/src/render/shaders/tangentFrame.ts +16 -3
  132. package/src/render/skinnedClothRun.ts +138 -38
  133. package/src/render/surfaceLayers.ts +57 -0
  134. package/src/render/surfaceOverlay.ts +24 -7
  135. package/src/render/surfaceProjection.ts +57 -0
  136. package/src/render/surfaceTexture.ts +30 -0
  137. package/src/render/vertexPacking.ts +109 -0
  138. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  139. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  140. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -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';
@@ -194,6 +196,10 @@ import {
194
196
  packSurfaceOverlay,
195
197
  type SurfaceOverlay,
196
198
  } from '../../surfaceOverlay.ts';
199
+ import { PROJECTION_FLOATS, packSurfaceProjection } from '../../surfaceProjection.ts';
200
+ import { orthographicDepthSpan } from '../../lightMatrix.ts';
201
+ import { LAYER_FLOATS, packSurfaceLayers } from '../../surfaceLayers.ts';
202
+ import type { SurfaceLayers } from '../../surfaceLayers.ts';
197
203
  import {
198
204
  frameReflectionsRefused,
199
205
  resolveFrameReflections,
@@ -1530,7 +1536,21 @@ export class WebGPURenderer implements RendererApi {
1530
1536
  * flight at the time, so a consumer that builds more meshes later can wait again.
1531
1537
  */
1532
1538
  async ready(): Promise<void> {
1533
- await Promise.all(this.pipelineTargets.map((cache) => cache.ready()));
1539
+ await Promise.all(this.pipelineCaches().map((cache) => cache.ready()));
1540
+ }
1541
+
1542
+ /** Every distinct cache a lit draw reaches: the frame's, the canvas's, the late pass's, skin's. */
1543
+ private pipelineCaches(): PipelineCache[] {
1544
+ const caches = [...this.pipelineTargets];
1545
+ for (const cache of [this.latePipelines, this.skinPipelines]) {
1546
+ if (cache !== null && !caches.includes(cache)) caches.push(cache);
1547
+ }
1548
+ return caches;
1549
+ }
1550
+
1551
+ /** Whether a lit draw whose pipeline is not built is left out while it compiles. */
1552
+ private skipCompiling(skips: boolean): void {
1553
+ for (const cache of this.pipelineCaches()) cache.skipsCompiling = skips;
1534
1554
  }
1535
1555
 
1536
1556
  private presentedFrameCount = 0;
@@ -2927,6 +2947,7 @@ export class WebGPURenderer implements RendererApi {
2927
2947
  }
2928
2948
  filmCommand.vertexCount = geometry.vertexBuffers.length;
2929
2949
  filmCommand.indexBuffer = geometry.indexBuffer;
2950
+ filmCommand.indexFormat = geometry.indexFormat;
2930
2951
  filmCommand.indexed = true;
2931
2952
  filmCommand.count = geometry.indexCount;
2932
2953
  } else {
@@ -2937,7 +2958,7 @@ export class WebGPURenderer implements RendererApi {
2937
2958
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
2938
2959
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
2939
2960
  }
2940
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
2961
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
2941
2962
  pass.drawIndexed(geometry.indexCount);
2942
2963
  }
2943
2964
  }
@@ -2974,6 +2995,11 @@ export class WebGPURenderer implements RendererApi {
2974
2995
  private modelMap: GpuSurfaceTexture | null = null;
2975
2996
  /** The material's shading model, which picks the lit pipeline: null the standard. */
2976
2997
  private materialModel: SurfaceModel | null = null;
2998
+ /** `uWorldUv`, the material's projection packed by `packSurfaceProjection`. */
2999
+ private readonly projection = new Float32Array(PROJECTION_FLOATS);
3000
+ /** `uLayers`, the material's layers packed by `packSurfaceLayers`. */
3001
+ private readonly layerFloats = new Float32Array(LAYER_FLOATS);
3002
+ private warnedLayersModelMap = false;
2977
3003
  /** Its numbers, `uModelParams`, packed by `packModel`. */
2978
3004
  private readonly modelParams = new Float32Array(MODEL_PARAM_FLOATS);
2979
3005
  /**
@@ -3448,7 +3474,10 @@ export class WebGPURenderer implements RendererApi {
3448
3474
  const normal = (material?.normal ?? null) as GpuSurfaceTexture | null;
3449
3475
  const orm = (material?.orm ?? null) as GpuSurfaceTexture | null;
3450
3476
  const emissiveMap = (material?.emissive ?? null) as GpuSurfaceTexture | null;
3451
- const modelMap = (material?.modelMap ?? null) as GpuSurfaceTexture | null;
3477
+ const ownModelMap = (material?.modelMap ?? null) as GpuSurfaceTexture | null;
3478
+ /* A layered material's mask goes where a model's map would: see `SurfaceMaterial.layers`. */
3479
+ const layers = this.layersOf(material);
3480
+ const modelMap = ownModelMap ?? ((layers?.mask ?? null) as GpuSurfaceTexture | null);
3452
3481
  this.materialModel = material?.model ?? null;
3453
3482
  const reflectivity = material?.reflectivity;
3454
3483
  const environmentGain = material?.environmentGain;
@@ -3459,7 +3488,7 @@ export class WebGPURenderer implements RendererApi {
3459
3488
  this.writeEnvironmentDials();
3460
3489
  /* What the blur spreads this skin by: the last material to name a profile says. */
3461
3490
  const model = material?.model;
3462
- warnUnpagedLightmap(model, modelMap);
3491
+ warnUnpagedLightmap(model, ownModelMap);
3463
3492
  if (model?.kind === 'skin' && this.skinScatter !== null) {
3464
3493
  this.skinScatter.setProfile(
3465
3494
  model.profile,
@@ -3518,12 +3547,23 @@ export class WebGPURenderer implements RendererApi {
3518
3547
  /* The model's numbers and whether its map is bound. See `packModel`. */
3519
3548
  packModel(
3520
3549
  material?.model ?? null,
3521
- modelMap !== null,
3550
+ ownModelMap !== null,
3522
3551
  this.modelParams,
3523
3552
  material?.physicalSpecular === true,
3524
3553
  );
3525
3554
  /* The first physical highlight asked for is when the lit pipelines start carrying one. */
3526
3555
  if (material?.physicalSpecular === true) this.enableLit('PHYSICAL_SPECULAR');
3556
+ /* Maps placed by the world: the first material that asks is when lit pipelines carry it. */
3557
+ packSurfaceProjection(material?.projection, this.projection);
3558
+ f.set(this.projection, this.materialField('uWorldUv'));
3559
+ /* How much light from behind it lets through, in the glass tint's spare lane. */
3560
+ f[this.materialField('uGlassTint') + 3] =
3561
+ Math.min(Math.max(material?.diffuseTransmission ?? 0, 0), 1) || 0;
3562
+ if ((this.projection[0] as number) > 0) this.enableLit('WORLD_UVS');
3563
+ /* And its layers, the first time a material blends any: layered.ts. */
3564
+ packSurfaceLayers(layers, this.layerFloats);
3565
+ f.set(this.layerFloats, this.materialField('uLayers'));
3566
+ if (layers !== null) this.enableLit('LAYERED');
3527
3567
  f.set(this.modelParams, this.materialField('uModelParams'));
3528
3568
  });
3529
3569
  /*
@@ -3731,6 +3771,14 @@ export class WebGPURenderer implements RendererApi {
3731
3771
  this.materials.dirty();
3732
3772
  }
3733
3773
 
3774
+ /** Whether the draws that follow spend their own opacity on that screen door. See `renderer.ts`. */
3775
+ setDitherOpacity(on: boolean): void {
3776
+ if (this.surface.lost) return;
3777
+ /* The third lane of `uWriteMode`, beside the fade. */
3778
+ this.perFrameFloats[this.materialField('uWriteMode') + 2] = on ? 1 : 0;
3779
+ this.materials.dirty();
3780
+ }
3781
+
3734
3782
  /**
3735
3783
  * How strong the microscopic relief on the surfaces drawn next is, and how coarse.
3736
3784
  *
@@ -5228,6 +5276,11 @@ export class WebGPURenderer implements RendererApi {
5228
5276
  at('uLightViewProj'),
5229
5277
  env === undefined ? NO_LIGHT_MATRIX : (env.lightViewProj as Float32Array),
5230
5278
  );
5279
+ ring.writeFloats(
5280
+ fragmentSlot,
5281
+ at('uMovingLightViewProj'),
5282
+ env === undefined ? NO_LIGHT_MATRIX : (this.movingLightFor(env) as Float32Array),
5283
+ );
5231
5284
  ring.writeFloat(fragmentSlot, at('uShadowMapSize'), this.quality.directionalShadowMapSize);
5232
5285
  /* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
5233
5286
  ring.writeInt(fragmentSlot, at('uPeeledShadowEnabled'), this.peelFilled ? 1 : 0);
@@ -5267,6 +5320,7 @@ export class WebGPURenderer implements RendererApi {
5267
5320
  }
5268
5321
  volumeCommand.vertexCount = mesh.vertexBuffers.length;
5269
5322
  volumeCommand.indexBuffer = mesh.indexBuffer;
5323
+ volumeCommand.indexFormat = mesh.indexFormat;
5270
5324
  volumeCommand.indexed = true;
5271
5325
  volumeCommand.count = mesh.indexCount;
5272
5326
  } else {
@@ -5275,7 +5329,7 @@ export class WebGPURenderer implements RendererApi {
5275
5329
  for (let index = 0; index < mesh.vertexBuffers.length; index++) {
5276
5330
  pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
5277
5331
  }
5278
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
5332
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
5279
5333
  pass.drawIndexed(mesh.indexCount);
5280
5334
  }
5281
5335
  }
@@ -6116,6 +6170,22 @@ export class WebGPURenderer implements RendererApi {
6116
6170
  * the texel-for-texel comparison that settled it.
6117
6171
  */
6118
6172
  private readonly correctedLightViewProj = new Float32Array(16);
6173
+ /**
6174
+ * What a receiver projects through to read the sun's static layers: the environment's
6175
+ * `lightViewProj`, taken at `bindMeshPass` as WebGL2 takes it there.
6176
+ *
6177
+ * **It was the last shadow pass's matrix**, which is the environment's wherever both layers are
6178
+ * drawn with one and the moving layer's wherever they are not: the static layer was then read
6179
+ * through the moving layer's tight square, and a distant pillar lost its shadow on WebGPU and kept
6180
+ * it on WebGL2, measured on demo/dev/sunLayers.html?moving=tight.
6181
+ */
6182
+ private readonly receiverLightViewProj = new Float32Array(16);
6183
+ /**
6184
+ * The matrix the sun's dynamic shadow pass was last drawn with, uncorrected as the lit stage
6185
+ * projects with, and whether one has been. See `movingLightFor`.
6186
+ */
6187
+ private readonly movingLightViewProj = new Float32Array(16);
6188
+ private movingLightKnown = false;
6119
6189
  /**
6120
6190
  * The same matrix *uncorrected*, which is what the flat shader must project with.
6121
6191
  *
@@ -6246,6 +6316,48 @@ export class WebGPURenderer implements RendererApi {
6246
6316
  this.enableLit('GLASS_SHADOWS');
6247
6317
  }
6248
6318
 
6319
+ /**
6320
+ * A material's layers where they can be drawn: none asked, or a model's map in the mask's place,
6321
+ * said once, leave one layer.
6322
+ */
6323
+ private layersOf(
6324
+ material: SurfaceMaterial<GpuSurfaceTexture> | null,
6325
+ ): SurfaceLayers<GpuSurfaceTexture> | null {
6326
+ const layers = material?.layers ?? null;
6327
+ if (layers === null || layers.repeats.length === 0) return null;
6328
+ if ((material?.modelMap ?? null) === null) return layers;
6329
+ if (!this.warnedLayersModelMap) {
6330
+ this.warnedLayersModelMap = true;
6331
+ console.warn(
6332
+ "WebGPU: a material's layers are drawn as one: its mask is read where a model's map goes, " +
6333
+ 'and this material carries a `modelMap` too.',
6334
+ );
6335
+ }
6336
+ return null;
6337
+ }
6338
+
6339
+ /**
6340
+ * What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
6341
+ * where the lit pipelines carry one (`MOVING_SUN`); the environment's otherwise, which every caller
6342
+ * drawing both layers with one matrix hands it anyway.
6343
+ */
6344
+ private movingLightFor(env: Environment): ReadonlyMat4 {
6345
+ return this.movingLightKnown && this.pipelines.litSwitches.MOVING_SUN
6346
+ ? this.movingLightViewProj
6347
+ : env.lightViewProj;
6348
+ }
6349
+
6350
+ /** The moving layer's own matrix on, the first time a frame draws that layer with one. */
6351
+ private enableMovingSun(env: Environment): void {
6352
+ if (this.pipelines.litSwitches.MOVING_SUN || !this.movingLightKnown) return;
6353
+ for (let k = 0; k < 16; k++) {
6354
+ if (this.movingLightViewProj[k] !== env.lightViewProj[k]) {
6355
+ this.enableLit('MOVING_SUN');
6356
+ return;
6357
+ }
6358
+ }
6359
+ }
6360
+
6249
6361
  /** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
6250
6362
  private enableLit(feature: LitSwitch): void {
6251
6363
  if (this.pipelines.litSwitches[feature]) return;
@@ -6311,7 +6423,7 @@ export class WebGPURenderer implements RendererApi {
6311
6423
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
6312
6424
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
6313
6425
  }
6314
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
6426
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
6315
6427
  pass.drawIndexed(geometry.indexCount);
6316
6428
  },
6317
6429
  /**
@@ -6401,7 +6513,7 @@ export class WebGPURenderer implements RendererApi {
6401
6513
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
6402
6514
  }
6403
6515
  pass.setVertexBuffer(index, gpuBatch.buffer);
6404
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
6516
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
6405
6517
  pass.drawIndexed(geometry.indexCount, count);
6406
6518
  },
6407
6519
  skinnedMesh: (mesh, model, palette, material, cloth) => {
@@ -6463,7 +6575,7 @@ export class WebGPURenderer implements RendererApi {
6463
6575
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
6464
6576
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
6465
6577
  }
6466
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
6578
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
6467
6579
  pass.drawIndexed(geometry.indexCount);
6468
6580
  },
6469
6581
  /**
@@ -6886,6 +6998,8 @@ export class WebGPURenderer implements RendererApi {
6886
6998
  this.overlayPipelines === this.pipelines
6887
6999
  ? [this.pipelines]
6888
7000
  : [this.pipelines, this.overlayPipelines];
7001
+ /* A lit draw first needed inside a frame waits for its compile or is left out while it runs. */
7002
+ this.skipCompiling(quality.pipelineCompile === 'skip');
6889
7003
  this.variant = flatVariant({
6890
7004
  directionalShadows: quality.directionalShadows,
6891
7005
  /* No `bakeReflectionProbe` here; see the field. */
@@ -8138,6 +8252,8 @@ export class WebGPURenderer implements RendererApi {
8138
8252
  present['layers'] = key.includes(':layers');
8139
8253
  /* Nor is a dynamic mesh's split, which moves positions and normals into buffers of their own. */
8140
8254
  present['dynamic'] = key.includes(':dynamic');
8255
+ /* Nor how each attribute travels, which moves every field after a packed one. */
8256
+ markPackingOfKey(present, key);
8141
8257
  return present;
8142
8258
  }
8143
8259
 
@@ -8447,6 +8563,13 @@ export class WebGPURenderer implements RendererApi {
8447
8563
  present['dynamic'] = true;
8448
8564
  key += ':dynamic';
8449
8565
  }
8566
+ /*
8567
+ * And how each attribute travels (`vertexPacking.ts`): a field read as sixteen bits rather than
8568
+ * thirty-two, or not interleaved at all, moves every field after it, so the key says so.
8569
+ */
8570
+ const packing = vertexPackingOf(data, dynamic);
8571
+ markPacking(present, packing);
8572
+ key += packingKey(packing);
8450
8573
  const fullKey = `${this.variant}|${key}`;
8451
8574
  /*
8452
8575
  * **For every target this mesh can land on, which is two when the overlay is its own.**
@@ -8691,6 +8814,8 @@ export class WebGPURenderer implements RendererApi {
8691
8814
  */
8692
8815
  bindMeshPass(camera: Camera, env: Environment): void {
8693
8816
  if (this.surface.lost) return;
8817
+ /* The matrix every receiver projects through: the environment's, as WebGL2's. See the field. */
8818
+ this.receiverLightViewProj.set(env.lightViewProj as Float32Array);
8694
8819
 
8695
8820
  /* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
8696
8821
  if (
@@ -8809,6 +8934,12 @@ export class WebGPURenderer implements RendererApi {
8809
8934
  this.surfaceFog = FOG_RECEDE;
8810
8935
  /* Every material switch off — albedo, ORM map, emissive map, two-sided — as WebGL2 resets them. */
8811
8936
  i.fill(0, at('uMaterialFlags'), at('uMaterialFlags') + 4);
8937
+ /* And no projection: maps follow the mesh's coordinates until a material asks otherwise. */
8938
+ f.fill(0, at('uWorldUv'), at('uWorldUv') + 4);
8939
+ /* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
8940
+ f[at('uGlassTint') + 3] = 0;
8941
+ /* Nor layers: one layer until a material asks for more. */
8942
+ f.fill(0, at('uLayers'), at('uLayers') + LAYER_FLOATS);
8812
8943
  f[at('uCutout')] = 0;
8813
8944
  f[at('uCutout') + 1] = 0;
8814
8945
  this.cutoutResolveStaged = 'hard';
@@ -8891,6 +9022,7 @@ export class WebGPURenderer implements RendererApi {
8891
9022
  /* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
8892
9023
  f[at('uWriteMode')] = 0;
8893
9024
  f[at('uWriteMode') + 1] = 0;
9025
+ f[at('uWriteMode') + 2] = 0;
8894
9026
  /* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
8895
9027
  f[at('uEnvironmentDials') + 2] = 1;
8896
9028
  /* No overlay until a draw sets one. See `setSurfaceOverlay`. */
@@ -9184,6 +9316,11 @@ export class WebGPURenderer implements RendererApi {
9184
9316
  f[at('uShadowStrength')] = env.shadowStrength;
9185
9317
  f[at('uShadowMapSize')] = this.quality.directionalShadowMapSize;
9186
9318
  f[at('uShadowDepthSpan')] = env.shadowDepthSpan;
9319
+ /* The moving layer's own matrix and span, where the lit stage reads one: `movingLightFor`. */
9320
+ this.enableMovingSun(env);
9321
+ const moving = this.movingLightFor(env);
9322
+ f.set(moving as Float32Array, at('uMovingLightViewProj'));
9323
+ f[at('uMovingShadowDepthSpan')] = orthographicDepthSpan(moving);
9187
9324
  f[at('uShadowMaxDistance')] = this.quality.directionalShadowMaxDistance;
9188
9325
  f[at('uShadowMaxSlope')] = this.quality.directionalShadowMaxSlope;
9189
9326
  /* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
@@ -9386,7 +9523,7 @@ export class WebGPURenderer implements RendererApi {
9386
9523
  /** A view block: the camera, the light's matrix and the frame's wind, into `ring` at `slot`. */
9387
9524
  private writeView(ring: UniformRing, slot: number, viewProj: Float32Array): void {
9388
9525
  ring.writeFloats(slot, FLAT_VIEW_FIELDS.uViewProj.offset, viewProj);
9389
- ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.lightViewProj);
9526
+ ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.receiverLightViewProj);
9390
9527
  /* The scene's clock, which a bone animation's instances read their moment from. */
9391
9528
  ring.writeFloat(slot, FLAT_VIEW_FIELDS.uSceneTime.offset, this.animationTime);
9392
9529
  const d = this.frameWind;
@@ -9793,8 +9930,8 @@ export class WebGPURenderer implements RendererApi {
9793
9930
  * thrown error in the middle of one.
9794
9931
  */
9795
9932
  const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
9796
- let pipeline = pipelines.peek(key);
9797
- if (pipeline === undefined) {
9933
+ let pipeline: GPURenderPipeline | null = pipelines.peek(key) ?? null;
9934
+ if (pipeline === null) {
9798
9935
  const present = this.meshPresent.get(base);
9799
9936
  if (present === undefined) {
9800
9937
  throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
@@ -9821,6 +9958,14 @@ export class WebGPURenderer implements RendererApi {
9821
9958
  splits ? 'scene' : 'whole',
9822
9959
  );
9823
9960
  }
9961
+ /* A split skin draws its three halves or none of them. */
9962
+ if (
9963
+ pipeline !== null &&
9964
+ splits &&
9965
+ !this.skinHalvesReady(key, base, skinned, morphed, layer, eight, clothed)
9966
+ ) {
9967
+ pipeline = null;
9968
+ }
9824
9969
 
9825
9970
  /*
9826
9971
  * Guards the draw call only, not the restore below.
@@ -9838,8 +9983,9 @@ export class WebGPURenderer implements RendererApi {
9838
9983
  * close to the 256-slot budget before the gateway, the play controls, the waveform, the
9839
9984
  * label handles and the sparkles are even counted.
9840
9985
  */
9841
- const material = this.materialSlotForDraw();
9842
- if (material !== null) {
9986
+ /* A draw whose pipeline is still compiling is left out, its state restored all the same. */
9987
+ const material = pipeline === null ? null : this.materialSlotForDraw();
9988
+ if (pipeline !== null && material !== null) {
9843
9989
  /*
9844
9990
  * Record rather than issue. Every per-draw value is already in `slot` and `material` —
9845
9991
  * the uniform rings are the state arena this design would otherwise have had to build —
@@ -9861,6 +10007,7 @@ export class WebGPURenderer implements RendererApi {
9861
10007
  }
9862
10008
  command.vertexCount = mesh.vertexBuffers.length;
9863
10009
  command.indexBuffer = mesh.indexBuffer;
10010
+ command.indexFormat = mesh.indexFormat;
9864
10011
  command.indexed = true;
9865
10012
  command.count = mesh.indexCount;
9866
10013
  } else {
@@ -9876,7 +10023,7 @@ export class WebGPURenderer implements RendererApi {
9876
10023
  for (let index = 0; index < mesh.vertexBuffers.length; index++) {
9877
10024
  pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
9878
10025
  }
9879
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
10026
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
9880
10027
  pass.drawIndexed(mesh.indexCount);
9881
10028
  }
9882
10029
  if (splits) {
@@ -10355,8 +10502,8 @@ export class WebGPURenderer implements RendererApi {
10355
10502
  `${modelKind === null ? '' : `|m:${modelKind}`}`;
10356
10503
 
10357
10504
  const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
10358
- let pipeline = pipelines.peek(key);
10359
- if (pipeline === undefined) {
10505
+ let pipeline: GPURenderPipeline | null = pipelines.peek(key) ?? null;
10506
+ if (pipeline === null) {
10360
10507
  const present = this.meshPresent.get(base);
10361
10508
  if (present === undefined) {
10362
10509
  throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
@@ -10386,8 +10533,9 @@ export class WebGPURenderer implements RendererApi {
10386
10533
  );
10387
10534
  }
10388
10535
 
10389
- const material = this.materialSlotForDraw();
10390
- if (material !== null) {
10536
+ /* A batch whose pipeline is still compiling is left out, as a mesh is. */
10537
+ const material = pipeline === null ? null : this.materialSlotForDraw();
10538
+ if (pipeline !== null && material !== null) {
10391
10539
  const command = blend
10392
10540
  ? this.recordBlended(0, lands)
10393
10541
  : this.recordDraw(0, this.currentTarget());
@@ -10409,6 +10557,7 @@ export class WebGPURenderer implements RendererApi {
10409
10557
  command.vertexBuffers[index] = instanceBuffer;
10410
10558
  command.vertexCount = mesh.vertexBuffers.length + 1;
10411
10559
  command.indexBuffer = mesh.indexBuffer;
10560
+ command.indexFormat = mesh.indexFormat;
10412
10561
  command.indexed = true;
10413
10562
  command.count = mesh.indexCount;
10414
10563
  command.instances = count;
@@ -10429,7 +10578,7 @@ export class WebGPURenderer implements RendererApi {
10429
10578
  pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
10430
10579
  }
10431
10580
  pass.setVertexBuffer(index, instanceBuffer);
10432
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
10581
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
10433
10582
  if (cut === null) pass.drawIndexed(mesh.indexCount, count);
10434
10583
  else pass.drawIndexedIndirect(cut.args, 0);
10435
10584
  }
@@ -10664,6 +10813,11 @@ export class WebGPURenderer implements RendererApi {
10664
10813
  if (view === null) return false;
10665
10814
 
10666
10815
  this.lightViewProj.set(lightViewProj as Float32Array);
10816
+ /* The moving layer is read through the matrix it is drawn with: see `movingLightFor`. */
10817
+ if (dynamic) {
10818
+ this.movingLightViewProj.set(lightViewProj as Float32Array);
10819
+ this.movingLightKnown = true;
10820
+ }
10667
10821
  this.reopenViews();
10668
10822
  mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
10669
10823
  this.shadowLayerIsPeel = peel;
@@ -10975,10 +11129,13 @@ export class WebGPURenderer implements RendererApi {
10975
11129
  this.lateViewBlock.slot = list.viewOffset(0);
10976
11130
  this.materials.dirty();
10977
11131
  this.staticRecording = recording;
11132
+ /* A recorded list keeps what it recorded, so every draw of it waits for its pipeline. */
11133
+ this.skipCompiling(false);
10978
11134
  try {
10979
11135
  this.sceneCasterMaterial = undefined;
10980
11136
  replayStaticDraws(list, this.sceneCasterSink);
10981
11137
  } finally {
11138
+ this.skipCompiling(this.quality.pipelineCompile === 'skip');
10982
11139
  this.staticRecording = null;
10983
11140
  this.perDraw = perDraw;
10984
11141
  this.passRing = passRing;
@@ -11285,7 +11442,7 @@ export class WebGPURenderer implements RendererApi {
11285
11442
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
11286
11443
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
11287
11444
  }
11288
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
11445
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
11289
11446
  pass.drawIndexed(geometry.indexCount);
11290
11447
  },
11291
11448
  instanced: (batch, data, material, glass) => {
@@ -11330,7 +11487,7 @@ export class WebGPURenderer implements RendererApi {
11330
11487
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
11331
11488
  }
11332
11489
  pass.setVertexBuffer(index, gpuBatch.buffer);
11333
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
11490
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
11334
11491
  pass.drawIndexed(geometry.indexCount, count);
11335
11492
  },
11336
11493
  skinnedMesh: (mesh, model, palette, _material, glass) => {
@@ -11366,7 +11523,7 @@ export class WebGPURenderer implements RendererApi {
11366
11523
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
11367
11524
  pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
11368
11525
  }
11369
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
11526
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
11370
11527
  pass.drawIndexed(geometry.indexCount);
11371
11528
  },
11372
11529
  };
@@ -13865,6 +14022,10 @@ export class WebGPURenderer implements RendererApi {
13865
14022
  /* Uncorrected, for the reason the shaft's own copy of this line carries: the shader does its
13866
14023
  own `* 0.5 + 0.5`, and correcting the matrix as well doubles it. */
13867
14024
  f.set(env?.lightViewProj ?? this.mediumIdentity, at('uLightViewProj'));
14025
+ f.set(
14026
+ (env === null ? this.mediumIdentity : this.movingLightFor(env)) as Float32Array,
14027
+ at('uMovingLightViewProj'),
14028
+ );
13868
14029
  ints[at('uPeeledShadowEnabled')] = this.peelView === null || !this.peelFilled ? 0 : 1;
13869
14030
  device.queue.writeBuffer(this.mediumUniforms, 0, this.mediumStaging);
13870
14031
 
@@ -14389,6 +14550,8 @@ export class WebGPURenderer implements RendererApi {
14389
14550
  true,
14390
14551
  animation !== null,
14391
14552
  );
14553
+ /* Still compiling where draws skip: the probe stands in for this surface until it lands. */
14554
+ if (pipeline === null) return;
14392
14555
  /* A static list's surface halves go into its own pool, beside its draws. */
14393
14556
  const command =
14394
14557
  this.staticRecording === null ? surface.take() : takeRecorded(this.staticRecording.surface);
@@ -14409,6 +14572,7 @@ export class WebGPURenderer implements RendererApi {
14409
14572
  if (instanceBuffer !== null) command.vertexBuffers[index++] = instanceBuffer;
14410
14573
  command.vertexCount = index;
14411
14574
  command.indexBuffer = mesh.indexBuffer;
14575
+ command.indexFormat = mesh.indexFormat;
14412
14576
  command.indexed = true;
14413
14577
  command.count = mesh.indexCount;
14414
14578
  command.instances = instances;
@@ -14421,6 +14585,78 @@ export class WebGPURenderer implements RendererApi {
14421
14585
  * light alone and its colour alone into the blur's targets and test for the depth its frame half
14422
14586
  * wrote.
14423
14587
  */
14588
+ /**
14589
+ * Whether a split skin draw's two kept halves have pipelines, starting the compile of either that
14590
+ * has not: a skin drawn without them would lose its diffuse light for the frames they take, so a
14591
+ * split skin draws all three halves or none. See `PipelineCache.lit`.
14592
+ */
14593
+ private skinHalvesReady(
14594
+ frameKey: string,
14595
+ base: string,
14596
+ skinned: boolean,
14597
+ morphed: boolean,
14598
+ layer: number,
14599
+ eight: boolean,
14600
+ clothed: boolean,
14601
+ ): boolean {
14602
+ let ready = true;
14603
+ for (const half of SKIN_KEPT_HALVES) {
14604
+ const pipeline = this.skinHalfPipeline(
14605
+ half,
14606
+ frameKey,
14607
+ base,
14608
+ skinned,
14609
+ morphed,
14610
+ layer,
14611
+ eight,
14612
+ clothed,
14613
+ );
14614
+ if (pipeline === null) ready = false;
14615
+ }
14616
+ return ready;
14617
+ }
14618
+
14619
+ /** One kept half's pipeline, or none where it is still compiling: see `PipelineCache.lit`. */
14620
+ private skinHalfPipeline(
14621
+ half: (typeof SKIN_KEPT_HALVES)[number],
14622
+ frameKey: string,
14623
+ base: string,
14624
+ skinned: boolean,
14625
+ morphed: boolean,
14626
+ layer: number,
14627
+ eight: boolean,
14628
+ clothed: boolean,
14629
+ ): GPURenderPipeline | null {
14630
+ const pipelines = this.skinPipelines;
14631
+ const present = this.meshPresent.get(base);
14632
+ if (pipelines === null || present === undefined) return null;
14633
+ const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
14634
+ return (
14635
+ pipelines.peek(key) ??
14636
+ flatPipeline(
14637
+ pipelines,
14638
+ this.surface.device,
14639
+ this.flatLayoutFor(skinned, morphed),
14640
+ this.variant,
14641
+ key,
14642
+ present,
14643
+ false,
14644
+ skinned,
14645
+ morphed,
14646
+ true,
14647
+ layer,
14648
+ false,
14649
+ 'none',
14650
+ this.materialDoubleSided,
14651
+ false,
14652
+ eight,
14653
+ clothed,
14654
+ 'skin',
14655
+ half,
14656
+ )
14657
+ );
14658
+ }
14659
+
14424
14660
  private keepSkinDiffuse(
14425
14661
  mesh: GpuMesh,
14426
14662
  frameKey: string,
@@ -14446,30 +14682,18 @@ export class WebGPURenderer implements RendererApi {
14446
14682
  const bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
14447
14683
  /* The light alone, which the blur spreads, then the colour alone, which it is multiplied by. */
14448
14684
  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
- );
14685
+ const pipeline = this.skinHalfPipeline(
14686
+ half,
14687
+ frameKey,
14688
+ base,
14689
+ skinned,
14690
+ morphed,
14691
+ layer,
14692
+ eight,
14693
+ clothed,
14694
+ );
14695
+ /* Both landed before the draw was let through: see `skinHalvesReady`. */
14696
+ if (pipeline === null) return;
14473
14697
  const command = half === 'diffuse' ? skin.take() : skin.takeAlbedo();
14474
14698
  command.pipeline = pipeline;
14475
14699
  command.bindGroup = bindGroup;
@@ -14483,6 +14707,7 @@ export class WebGPURenderer implements RendererApi {
14483
14707
  }
14484
14708
  command.vertexCount = mesh.vertexBuffers.length;
14485
14709
  command.indexBuffer = mesh.indexBuffer;
14710
+ command.indexFormat = mesh.indexFormat;
14486
14711
  command.indexed = true;
14487
14712
  command.count = mesh.indexCount;
14488
14713
  command.instances = 1;
@@ -14936,6 +15161,7 @@ export class WebGPURenderer implements RendererApi {
14936
15161
  command.offsetCount = 0;
14937
15162
  command.vertexCount = 0;
14938
15163
  command.indexBuffer = null;
15164
+ command.indexFormat = 'uint32';
14939
15165
  command.indexed = false;
14940
15166
  command.instances = 1;
14941
15167
  command.indirect = null;
@@ -14995,7 +15221,7 @@ export class WebGPURenderer implements RendererApi {
14995
15221
  if (buffer != null) pass.setVertexBuffer(i, buffer);
14996
15222
  }
14997
15223
  if (command.indexed && command.indexBuffer !== null) {
14998
- pass.setIndexBuffer(command.indexBuffer, 'uint32');
15224
+ pass.setIndexBuffer(command.indexBuffer, command.indexFormat);
14999
15225
  if (command.indirect !== null) pass.drawIndexedIndirect(command.indirect, 0);
15000
15226
  else pass.drawIndexed(command.count, command.instances);
15001
15227
  } else {