@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
@@ -133,6 +133,10 @@ export declare class WebGPURenderer implements RendererApi {
133
133
  * flight at the time, so a consumer that builds more meshes later can wait again.
134
134
  */
135
135
  ready(): Promise<void>;
136
+ /** Every distinct cache a lit draw reaches: the frame's, the canvas's, the late pass's, skin's. */
137
+ private pipelineCaches;
138
+ /** Whether a lit draw whose pipeline is not built is left out while it compiles. */
139
+ private skipCompiling;
136
140
  private presentedFrameCount;
137
141
  /**
138
142
  * The joint palettes this frame has set, one slot each. The WebGL2 field of this name is a
@@ -1179,6 +1183,11 @@ export declare class WebGPURenderer implements RendererApi {
1179
1183
  private modelMap;
1180
1184
  /** The material's shading model, which picks the lit pipeline: null the standard. */
1181
1185
  private materialModel;
1186
+ /** `uWorldUv`, the material's projection packed by `packSurfaceProjection`. */
1187
+ private readonly projection;
1188
+ /** `uLayers`, the material's layers packed by `packSurfaceLayers`. */
1189
+ private readonly layerFloats;
1190
+ private warnedLayersModelMap;
1182
1191
  /** Its numbers, `uModelParams`, packed by `packModel`. */
1183
1192
  private readonly modelParams;
1184
1193
  /**
@@ -1398,6 +1407,8 @@ export declare class WebGPURenderer implements RendererApi {
1398
1407
  * this one is called per crossfading region per frame and a closure per call is an allocation.
1399
1408
  */
1400
1409
  setDitherFade(amount: number): void;
1410
+ /** Whether the draws that follow spend their own opacity on that screen door. See `renderer.ts`. */
1411
+ setDitherOpacity(on: boolean): void;
1401
1412
  /**
1402
1413
  * How strong the microscopic relief on the surfaces drawn next is, and how coarse.
1403
1414
  *
@@ -2013,6 +2024,22 @@ export declare class WebGPURenderer implements RendererApi {
2013
2024
  * the texel-for-texel comparison that settled it.
2014
2025
  */
2015
2026
  private readonly correctedLightViewProj;
2027
+ /**
2028
+ * What a receiver projects through to read the sun's static layers: the environment's
2029
+ * `lightViewProj`, taken at `bindMeshPass` as WebGL2 takes it there.
2030
+ *
2031
+ * **It was the last shadow pass's matrix**, which is the environment's wherever both layers are
2032
+ * drawn with one and the moving layer's wherever they are not: the static layer was then read
2033
+ * through the moving layer's tight square, and a distant pillar lost its shadow on WebGPU and kept
2034
+ * it on WebGL2, measured on demo/dev/sunLayers.html?moving=tight.
2035
+ */
2036
+ private readonly receiverLightViewProj;
2037
+ /**
2038
+ * The matrix the sun's dynamic shadow pass was last drawn with, uncorrected as the lit stage
2039
+ * projects with, and whether one has been. See `movingLightFor`.
2040
+ */
2041
+ private readonly movingLightViewProj;
2042
+ private movingLightKnown;
2016
2043
  /**
2017
2044
  * The same matrix *uncorrected*, which is what the flat shader must project with.
2018
2045
  *
@@ -2061,6 +2088,19 @@ export declare class WebGPURenderer implements RendererApi {
2061
2088
  * the lit pass on every surface whether they find anything or not.
2062
2089
  */
2063
2090
  private noteGlassCaster;
2091
+ /**
2092
+ * A material's layers where they can be drawn: none asked, or a model's map in the mask's place,
2093
+ * said once, leave one layer.
2094
+ */
2095
+ private layersOf;
2096
+ /**
2097
+ * What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
2098
+ * where the lit pipelines carry one (`MOVING_SUN`); the environment's otherwise, which every caller
2099
+ * drawing both layers with one matrix hands it anyway.
2100
+ */
2101
+ private movingLightFor;
2102
+ /** The moving layer's own matrix on, the first time a frame draws that layer with one. */
2103
+ private enableMovingSun;
2064
2104
  /** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
2065
2105
  private enableLit;
2066
2106
  /** One resolution every glass caster is read through, so recording one allocates nothing. */
@@ -3202,6 +3242,14 @@ export declare class WebGPURenderer implements RendererApi {
3202
3242
  * light alone and its colour alone into the blur's targets and test for the depth its frame half
3203
3243
  * wrote.
3204
3244
  */
3245
+ /**
3246
+ * Whether a split skin draw's two kept halves have pipelines, starting the compile of either that
3247
+ * has not: a skin drawn without them would lose its diffuse light for the frames they take, so a
3248
+ * split skin draws all three halves or none. See `PipelineCache.lit`.
3249
+ */
3250
+ private skinHalvesReady;
3251
+ /** One kept half's pipeline, or none where it is still compiling: see `PipelineCache.lit`. */
3252
+ private skinHalfPipeline;
3205
3253
  private keepSkinDiffuse;
3206
3254
  /**
3207
3255
  * Spread the frame's skin and add it back: once, at the first blended draw or before the frame's
@@ -1,3 +1,5 @@
1
+ import { vertexPackingOf } from '../../vertexPacking.js';
2
+ import { markPacking, markPackingOfKey, packingKey } from './vertexFormats.js';
1
3
  import { mat4 } from 'gl-matrix';
2
4
  import { probeFaceRange } from '../../reflectionProbe.js';
3
5
  import { TEMPORAL_FLICKER, TEMPORAL_HISTORY_BLEND, TemporalHistory, flickerRule, jitterOffset, jitterProjection, } from '../../temporalAa.js';
@@ -60,6 +62,9 @@ import { gpuCompressedFormats } from './compressedUpload.js';
60
62
  import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTextures.js';
61
63
  import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel, skinProfileDistance, } from '../../surfaceModel.js';
62
64
  import { OVERLAY_FLOATS, overlayLays, packSurfaceOverlay, } from '../../surfaceOverlay.js';
65
+ import { PROJECTION_FLOATS, packSurfaceProjection } from '../../surfaceProjection.js';
66
+ import { orthographicDepthSpan } from '../../lightMatrix.js';
67
+ import { LAYER_FLOATS, packSurfaceLayers } from '../../surfaceLayers.js';
63
68
  import { frameReflectionsRefused, resolveFrameReflections, } from '../../frameReflections.js';
64
69
  import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.js';
65
70
  import { validateClothBinding } from '../../clothBindingData.js';
@@ -857,7 +862,21 @@ export class WebGPURenderer {
857
862
  * flight at the time, so a consumer that builds more meshes later can wait again.
858
863
  */
859
864
  async ready() {
860
- await Promise.all(this.pipelineTargets.map((cache) => cache.ready()));
865
+ await Promise.all(this.pipelineCaches().map((cache) => cache.ready()));
866
+ }
867
+ /** Every distinct cache a lit draw reaches: the frame's, the canvas's, the late pass's, skin's. */
868
+ pipelineCaches() {
869
+ const caches = [...this.pipelineTargets];
870
+ for (const cache of [this.latePipelines, this.skinPipelines]) {
871
+ if (cache !== null && !caches.includes(cache))
872
+ caches.push(cache);
873
+ }
874
+ return caches;
875
+ }
876
+ /** Whether a lit draw whose pipeline is not built is left out while it compiles. */
877
+ skipCompiling(skips) {
878
+ for (const cache of this.pipelineCaches())
879
+ cache.skipsCompiling = skips;
861
880
  }
862
881
  presentedFrameCount = 0;
863
882
  /**
@@ -2177,6 +2196,7 @@ export class WebGPURenderer {
2177
2196
  }
2178
2197
  filmCommand.vertexCount = geometry.vertexBuffers.length;
2179
2198
  filmCommand.indexBuffer = geometry.indexBuffer;
2199
+ filmCommand.indexFormat = geometry.indexFormat;
2180
2200
  filmCommand.indexed = true;
2181
2201
  filmCommand.count = geometry.indexCount;
2182
2202
  }
@@ -2189,7 +2209,7 @@ export class WebGPURenderer {
2189
2209
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
2190
2210
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
2191
2211
  }
2192
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
2212
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
2193
2213
  pass.drawIndexed(geometry.indexCount);
2194
2214
  }
2195
2215
  }
@@ -2223,6 +2243,11 @@ export class WebGPURenderer {
2223
2243
  modelMap = null;
2224
2244
  /** The material's shading model, which picks the lit pipeline: null the standard. */
2225
2245
  materialModel = null;
2246
+ /** `uWorldUv`, the material's projection packed by `packSurfaceProjection`. */
2247
+ projection = new Float32Array(PROJECTION_FLOATS);
2248
+ /** `uLayers`, the material's layers packed by `packSurfaceLayers`. */
2249
+ layerFloats = new Float32Array(LAYER_FLOATS);
2250
+ warnedLayersModelMap = false;
2226
2251
  /** Its numbers, `uModelParams`, packed by `packModel`. */
2227
2252
  modelParams = new Float32Array(MODEL_PARAM_FLOATS);
2228
2253
  /**
@@ -2623,7 +2648,10 @@ export class WebGPURenderer {
2623
2648
  const normal = (material?.normal ?? null);
2624
2649
  const orm = (material?.orm ?? null);
2625
2650
  const emissiveMap = (material?.emissive ?? null);
2626
- const modelMap = (material?.modelMap ?? null);
2651
+ const ownModelMap = (material?.modelMap ?? null);
2652
+ /* A layered material's mask goes where a model's map would: see `SurfaceMaterial.layers`. */
2653
+ const layers = this.layersOf(material);
2654
+ const modelMap = ownModelMap ?? (layers?.mask ?? null);
2627
2655
  this.materialModel = material?.model ?? null;
2628
2656
  const reflectivity = material?.reflectivity;
2629
2657
  const environmentGain = material?.environmentGain;
@@ -2634,7 +2662,7 @@ export class WebGPURenderer {
2634
2662
  this.writeEnvironmentDials();
2635
2663
  /* What the blur spreads this skin by: the last material to name a profile says. */
2636
2664
  const model = material?.model;
2637
- warnUnpagedLightmap(model, modelMap);
2665
+ warnUnpagedLightmap(model, ownModelMap);
2638
2666
  if (model?.kind === 'skin' && this.skinScatter !== null) {
2639
2667
  this.skinScatter.setProfile(model.profile, skinProfileDistance(model, 0), skinProfileDistance(model, 1), skinProfileDistance(model, 2));
2640
2668
  }
@@ -2682,10 +2710,23 @@ export class WebGPURenderer {
2682
2710
  f[emissive + 1] = asked?.[1] ?? 1;
2683
2711
  f[emissive + 2] = asked?.[2] ?? 1;
2684
2712
  /* The model's numbers and whether its map is bound. See `packModel`. */
2685
- packModel(material?.model ?? null, modelMap !== null, this.modelParams, material?.physicalSpecular === true);
2713
+ packModel(material?.model ?? null, ownModelMap !== null, this.modelParams, material?.physicalSpecular === true);
2686
2714
  /* The first physical highlight asked for is when the lit pipelines start carrying one. */
2687
2715
  if (material?.physicalSpecular === true)
2688
2716
  this.enableLit('PHYSICAL_SPECULAR');
2717
+ /* Maps placed by the world: the first material that asks is when lit pipelines carry it. */
2718
+ packSurfaceProjection(material?.projection, this.projection);
2719
+ f.set(this.projection, this.materialField('uWorldUv'));
2720
+ /* How much light from behind it lets through, in the glass tint's spare lane. */
2721
+ f[this.materialField('uGlassTint') + 3] =
2722
+ Math.min(Math.max(material?.diffuseTransmission ?? 0, 0), 1) || 0;
2723
+ if (this.projection[0] > 0)
2724
+ this.enableLit('WORLD_UVS');
2725
+ /* And its layers, the first time a material blends any: layered.ts. */
2726
+ packSurfaceLayers(layers, this.layerFloats);
2727
+ f.set(this.layerFloats, this.materialField('uLayers'));
2728
+ if (layers !== null)
2729
+ this.enableLit('LAYERED');
2689
2730
  f.set(this.modelParams, this.materialField('uModelParams'));
2690
2731
  });
2691
2732
  /*
@@ -2873,6 +2914,14 @@ export class WebGPURenderer {
2873
2914
  this.perFrameFloats[this.materialField('uWriteMode') + 1] = Math.min(1, Math.max(-1, amount));
2874
2915
  this.materials.dirty();
2875
2916
  }
2917
+ /** Whether the draws that follow spend their own opacity on that screen door. See `renderer.ts`. */
2918
+ setDitherOpacity(on) {
2919
+ if (this.surface.lost)
2920
+ return;
2921
+ /* The third lane of `uWriteMode`, beside the fade. */
2922
+ this.perFrameFloats[this.materialField('uWriteMode') + 2] = on ? 1 : 0;
2923
+ this.materials.dirty();
2924
+ }
2876
2925
  /**
2877
2926
  * How strong the microscopic relief on the surfaces drawn next is, and how coarse.
2878
2927
  *
@@ -4154,6 +4203,7 @@ export class WebGPURenderer {
4154
4203
  const sunShadow = env === undefined ? 0 : clamp(options.sunShadow ?? 0, 0, 1);
4155
4204
  ring.writeFloat(fragmentSlot, at('uSunShadow'), sunShadow);
4156
4205
  ring.writeFloats(fragmentSlot, at('uLightViewProj'), env === undefined ? NO_LIGHT_MATRIX : env.lightViewProj);
4206
+ ring.writeFloats(fragmentSlot, at('uMovingLightViewProj'), env === undefined ? NO_LIGHT_MATRIX : this.movingLightFor(env));
4157
4207
  ring.writeFloat(fragmentSlot, at('uShadowMapSize'), this.quality.directionalShadowMapSize);
4158
4208
  /* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
4159
4209
  ring.writeInt(fragmentSlot, at('uPeeledShadowEnabled'), this.peelFilled ? 1 : 0);
@@ -4182,6 +4232,7 @@ export class WebGPURenderer {
4182
4232
  }
4183
4233
  volumeCommand.vertexCount = mesh.vertexBuffers.length;
4184
4234
  volumeCommand.indexBuffer = mesh.indexBuffer;
4235
+ volumeCommand.indexFormat = mesh.indexFormat;
4185
4236
  volumeCommand.indexed = true;
4186
4237
  volumeCommand.count = mesh.indexCount;
4187
4238
  }
@@ -4191,7 +4242,7 @@ export class WebGPURenderer {
4191
4242
  for (let index = 0; index < mesh.vertexBuffers.length; index++) {
4192
4243
  pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
4193
4244
  }
4194
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
4245
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
4195
4246
  pass.drawIndexed(mesh.indexCount);
4196
4247
  }
4197
4248
  }
@@ -4907,6 +4958,22 @@ export class WebGPURenderer {
4907
4958
  * the texel-for-texel comparison that settled it.
4908
4959
  */
4909
4960
  correctedLightViewProj = new Float32Array(16);
4961
+ /**
4962
+ * What a receiver projects through to read the sun's static layers: the environment's
4963
+ * `lightViewProj`, taken at `bindMeshPass` as WebGL2 takes it there.
4964
+ *
4965
+ * **It was the last shadow pass's matrix**, which is the environment's wherever both layers are
4966
+ * drawn with one and the moving layer's wherever they are not: the static layer was then read
4967
+ * through the moving layer's tight square, and a distant pillar lost its shadow on WebGPU and kept
4968
+ * it on WebGL2, measured on demo/dev/sunLayers.html?moving=tight.
4969
+ */
4970
+ receiverLightViewProj = new Float32Array(16);
4971
+ /**
4972
+ * The matrix the sun's dynamic shadow pass was last drawn with, uncorrected as the lit stage
4973
+ * projects with, and whether one has been. See `movingLightFor`.
4974
+ */
4975
+ movingLightViewProj = new Float32Array(16);
4976
+ movingLightKnown = false;
4910
4977
  /**
4911
4978
  * The same matrix *uncorrected*, which is what the flat shader must project with.
4912
4979
  *
@@ -5003,6 +5070,44 @@ export class WebGPURenderer {
5003
5070
  this.glassSeen = true;
5004
5071
  this.enableLit('GLASS_SHADOWS');
5005
5072
  }
5073
+ /**
5074
+ * A material's layers where they can be drawn: none asked, or a model's map in the mask's place,
5075
+ * said once, leave one layer.
5076
+ */
5077
+ layersOf(material) {
5078
+ const layers = material?.layers ?? null;
5079
+ if (layers === null || layers.repeats.length === 0)
5080
+ return null;
5081
+ if ((material?.modelMap ?? null) === null)
5082
+ return layers;
5083
+ if (!this.warnedLayersModelMap) {
5084
+ this.warnedLayersModelMap = true;
5085
+ console.warn("WebGPU: a material's layers are drawn as one: its mask is read where a model's map goes, " +
5086
+ 'and this material carries a `modelMap` too.');
5087
+ }
5088
+ return null;
5089
+ }
5090
+ /**
5091
+ * What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
5092
+ * where the lit pipelines carry one (`MOVING_SUN`); the environment's otherwise, which every caller
5093
+ * drawing both layers with one matrix hands it anyway.
5094
+ */
5095
+ movingLightFor(env) {
5096
+ return this.movingLightKnown && this.pipelines.litSwitches.MOVING_SUN
5097
+ ? this.movingLightViewProj
5098
+ : env.lightViewProj;
5099
+ }
5100
+ /** The moving layer's own matrix on, the first time a frame draws that layer with one. */
5101
+ enableMovingSun(env) {
5102
+ if (this.pipelines.litSwitches.MOVING_SUN || !this.movingLightKnown)
5103
+ return;
5104
+ for (let k = 0; k < 16; k++) {
5105
+ if (this.movingLightViewProj[k] !== env.lightViewProj[k]) {
5106
+ this.enableLit('MOVING_SUN');
5107
+ return;
5108
+ }
5109
+ }
5110
+ }
5006
5111
  /** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
5007
5112
  enableLit(feature) {
5008
5113
  if (this.pipelines.litSwitches[feature])
@@ -5056,7 +5161,7 @@ export class WebGPURenderer {
5056
5161
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
5057
5162
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
5058
5163
  }
5059
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
5164
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
5060
5165
  pass.drawIndexed(geometry.indexCount);
5061
5166
  },
5062
5167
  /**
@@ -5129,7 +5234,7 @@ export class WebGPURenderer {
5129
5234
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
5130
5235
  }
5131
5236
  pass.setVertexBuffer(index, gpuBatch.buffer);
5132
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
5237
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
5133
5238
  pass.drawIndexed(geometry.indexCount, count);
5134
5239
  },
5135
5240
  skinnedMesh: (mesh, model, palette, material, cloth) => {
@@ -5172,7 +5277,7 @@ export class WebGPURenderer {
5172
5277
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
5173
5278
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
5174
5279
  }
5175
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
5280
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
5176
5281
  pass.drawIndexed(geometry.indexCount);
5177
5282
  },
5178
5283
  /**
@@ -5543,6 +5648,8 @@ export class WebGPURenderer {
5543
5648
  this.overlayPipelines === this.pipelines
5544
5649
  ? [this.pipelines]
5545
5650
  : [this.pipelines, this.overlayPipelines];
5651
+ /* A lit draw first needed inside a frame waits for its compile or is left out while it runs. */
5652
+ this.skipCompiling(quality.pipelineCompile === 'skip');
5546
5653
  this.variant = flatVariant({
5547
5654
  directionalShadows: quality.directionalShadows,
5548
5655
  /* No `bakeReflectionProbe` here; see the field. */
@@ -6410,6 +6517,8 @@ export class WebGPURenderer {
6410
6517
  present['layers'] = key.includes(':layers');
6411
6518
  /* Nor is a dynamic mesh's split, which moves positions and normals into buffers of their own. */
6412
6519
  present['dynamic'] = key.includes(':dynamic');
6520
+ /* Nor how each attribute travels, which moves every field after a packed one. */
6521
+ markPackingOfKey(present, key);
6413
6522
  return present;
6414
6523
  }
6415
6524
  /**
@@ -6713,6 +6822,13 @@ export class WebGPURenderer {
6713
6822
  present['dynamic'] = true;
6714
6823
  key += ':dynamic';
6715
6824
  }
6825
+ /*
6826
+ * And how each attribute travels (`vertexPacking.ts`): a field read as sixteen bits rather than
6827
+ * thirty-two, or not interleaved at all, moves every field after it, so the key says so.
6828
+ */
6829
+ const packing = vertexPackingOf(data, dynamic);
6830
+ markPacking(present, packing);
6831
+ key += packingKey(packing);
6716
6832
  const fullKey = `${this.variant}|${key}`;
6717
6833
  /*
6718
6834
  * **For every target this mesh can land on, which is two when the overlay is its own.**
@@ -6920,6 +7036,8 @@ export class WebGPURenderer {
6920
7036
  bindMeshPass(camera, env) {
6921
7037
  if (this.surface.lost)
6922
7038
  return;
7039
+ /* The matrix every receiver projects through: the environment's, as WebGL2's. See the field. */
7040
+ this.receiverLightViewProj.set(env.lightViewProj);
6923
7041
  /* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
6924
7042
  if (!this.warnedAreaOverflow &&
6925
7043
  !this.quality.clusteredLights &&
@@ -7034,6 +7152,12 @@ export class WebGPURenderer {
7034
7152
  this.surfaceFog = FOG_RECEDE;
7035
7153
  /* Every material switch off — albedo, ORM map, emissive map, two-sided — as WebGL2 resets them. */
7036
7154
  i.fill(0, at('uMaterialFlags'), at('uMaterialFlags') + 4);
7155
+ /* And no projection: maps follow the mesh's coordinates until a material asks otherwise. */
7156
+ f.fill(0, at('uWorldUv'), at('uWorldUv') + 4);
7157
+ /* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
7158
+ f[at('uGlassTint') + 3] = 0;
7159
+ /* Nor layers: one layer until a material asks for more. */
7160
+ f.fill(0, at('uLayers'), at('uLayers') + LAYER_FLOATS);
7037
7161
  f[at('uCutout')] = 0;
7038
7162
  f[at('uCutout') + 1] = 0;
7039
7163
  this.cutoutResolveStaged = 'hard';
@@ -7106,6 +7230,7 @@ export class WebGPURenderer {
7106
7230
  /* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
7107
7231
  f[at('uWriteMode')] = 0;
7108
7232
  f[at('uWriteMode') + 1] = 0;
7233
+ f[at('uWriteMode') + 2] = 0;
7109
7234
  /* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
7110
7235
  f[at('uEnvironmentDials') + 2] = 1;
7111
7236
  /* No overlay until a draw sets one. See `setSurfaceOverlay`. */
@@ -7386,6 +7511,11 @@ export class WebGPURenderer {
7386
7511
  f[at('uShadowStrength')] = env.shadowStrength;
7387
7512
  f[at('uShadowMapSize')] = this.quality.directionalShadowMapSize;
7388
7513
  f[at('uShadowDepthSpan')] = env.shadowDepthSpan;
7514
+ /* The moving layer's own matrix and span, where the lit stage reads one: `movingLightFor`. */
7515
+ this.enableMovingSun(env);
7516
+ const moving = this.movingLightFor(env);
7517
+ f.set(moving, at('uMovingLightViewProj'));
7518
+ f[at('uMovingShadowDepthSpan')] = orthographicDepthSpan(moving);
7389
7519
  f[at('uShadowMaxDistance')] = this.quality.directionalShadowMaxDistance;
7390
7520
  f[at('uShadowMaxSlope')] = this.quality.directionalShadowMaxSlope;
7391
7521
  /* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
@@ -7569,7 +7699,7 @@ export class WebGPURenderer {
7569
7699
  /** A view block: the camera, the light's matrix and the frame's wind, into `ring` at `slot`. */
7570
7700
  writeView(ring, slot, viewProj) {
7571
7701
  ring.writeFloats(slot, FLAT_VIEW_FIELDS.uViewProj.offset, viewProj);
7572
- ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.lightViewProj);
7702
+ ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.receiverLightViewProj);
7573
7703
  /* The scene's clock, which a bone animation's instances read their moment from. */
7574
7704
  ring.writeFloat(slot, FLAT_VIEW_FIELDS.uSceneTime.offset, this.animationTime);
7575
7705
  const d = this.frameWind;
@@ -7939,14 +8069,20 @@ export class WebGPURenderer {
7939
8069
  * thrown error in the middle of one.
7940
8070
  */
7941
8071
  const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
7942
- let pipeline = pipelines.peek(key);
7943
- if (pipeline === undefined) {
8072
+ let pipeline = pipelines.peek(key) ?? null;
8073
+ if (pipeline === null) {
7944
8074
  const present = this.meshPresent.get(base);
7945
8075
  if (present === undefined) {
7946
8076
  throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
7947
8077
  }
7948
8078
  pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, adds ? 'additive' : blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided, coverage, eight, clothed, modelKind, splits ? 'scene' : 'whole');
7949
8079
  }
8080
+ /* A split skin draws its three halves or none of them. */
8081
+ if (pipeline !== null &&
8082
+ splits &&
8083
+ !this.skinHalvesReady(key, base, skinned, morphed, layer, eight, clothed)) {
8084
+ pipeline = null;
8085
+ }
7950
8086
  /*
7951
8087
  * Guards the draw call only, not the restore below.
7952
8088
  *
@@ -7963,8 +8099,9 @@ export class WebGPURenderer {
7963
8099
  * close to the 256-slot budget before the gateway, the play controls, the waveform, the
7964
8100
  * label handles and the sparkles are even counted.
7965
8101
  */
7966
- const material = this.materialSlotForDraw();
7967
- if (material !== null) {
8102
+ /* A draw whose pipeline is still compiling is left out, its state restored all the same. */
8103
+ const material = pipeline === null ? null : this.materialSlotForDraw();
8104
+ if (pipeline !== null && material !== null) {
7968
8105
  /*
7969
8106
  * Record rather than issue. Every per-draw value is already in `slot` and `material` —
7970
8107
  * the uniform rings are the state arena this design would otherwise have had to build —
@@ -7986,6 +8123,7 @@ export class WebGPURenderer {
7986
8123
  }
7987
8124
  command.vertexCount = mesh.vertexBuffers.length;
7988
8125
  command.indexBuffer = mesh.indexBuffer;
8126
+ command.indexFormat = mesh.indexFormat;
7989
8127
  command.indexed = true;
7990
8128
  command.count = mesh.indexCount;
7991
8129
  }
@@ -8003,7 +8141,7 @@ export class WebGPURenderer {
8003
8141
  for (let index = 0; index < mesh.vertexBuffers.length; index++) {
8004
8142
  pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
8005
8143
  }
8006
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
8144
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
8007
8145
  pass.drawIndexed(mesh.indexCount);
8008
8146
  }
8009
8147
  if (splits) {
@@ -8378,16 +8516,17 @@ export class WebGPURenderer {
8378
8516
  `${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
8379
8517
  `${modelKind === null ? '' : `|m:${modelKind}`}`;
8380
8518
  const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
8381
- let pipeline = pipelines.peek(key);
8382
- if (pipeline === undefined) {
8519
+ let pipeline = pipelines.peek(key) ?? null;
8520
+ if (pipeline === null) {
8383
8521
  const present = this.meshPresent.get(base);
8384
8522
  if (present === undefined) {
8385
8523
  throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
8386
8524
  }
8387
8525
  pipeline = flatPipeline(pipelines, this.surface.device, animation === null ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout, this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided, coverage, false, false, modelKind, 'whole', false, animation !== null);
8388
8526
  }
8389
- const material = this.materialSlotForDraw();
8390
- if (material !== null) {
8527
+ /* A batch whose pipeline is still compiling is left out, as a mesh is. */
8528
+ const material = pipeline === null ? null : this.materialSlotForDraw();
8529
+ if (pipeline !== null && material !== null) {
8391
8530
  const command = blend
8392
8531
  ? this.recordBlended(0, lands)
8393
8532
  : this.recordDraw(0, this.currentTarget());
@@ -8409,6 +8548,7 @@ export class WebGPURenderer {
8409
8548
  command.vertexBuffers[index] = instanceBuffer;
8410
8549
  command.vertexCount = mesh.vertexBuffers.length + 1;
8411
8550
  command.indexBuffer = mesh.indexBuffer;
8551
+ command.indexFormat = mesh.indexFormat;
8412
8552
  command.indexed = true;
8413
8553
  command.count = mesh.indexCount;
8414
8554
  command.instances = count;
@@ -8427,7 +8567,7 @@ export class WebGPURenderer {
8427
8567
  pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
8428
8568
  }
8429
8569
  pass.setVertexBuffer(index, instanceBuffer);
8430
- pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
8570
+ pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
8431
8571
  if (cut === null)
8432
8572
  pass.drawIndexed(mesh.indexCount, count);
8433
8573
  else
@@ -8628,6 +8768,11 @@ export class WebGPURenderer {
8628
8768
  if (view === null)
8629
8769
  return false;
8630
8770
  this.lightViewProj.set(lightViewProj);
8771
+ /* The moving layer is read through the matrix it is drawn with: see `movingLightFor`. */
8772
+ if (dynamic) {
8773
+ this.movingLightViewProj.set(lightViewProj);
8774
+ this.movingLightKnown = true;
8775
+ }
8631
8776
  this.reopenViews();
8632
8777
  mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
8633
8778
  this.shadowLayerIsPeel = peel;
@@ -8912,11 +9057,14 @@ export class WebGPURenderer {
8912
9057
  this.lateViewBlock.slot = list.viewOffset(0);
8913
9058
  this.materials.dirty();
8914
9059
  this.staticRecording = recording;
9060
+ /* A recorded list keeps what it recorded, so every draw of it waits for its pipeline. */
9061
+ this.skipCompiling(false);
8915
9062
  try {
8916
9063
  this.sceneCasterMaterial = undefined;
8917
9064
  replayStaticDraws(list, this.sceneCasterSink);
8918
9065
  }
8919
9066
  finally {
9067
+ this.skipCompiling(this.quality.pipelineCompile === 'skip');
8920
9068
  this.staticRecording = null;
8921
9069
  this.perDraw = perDraw;
8922
9070
  this.passRing = passRing;
@@ -9186,7 +9334,7 @@ export class WebGPURenderer {
9186
9334
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
9187
9335
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
9188
9336
  }
9189
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
9337
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
9190
9338
  pass.drawIndexed(geometry.indexCount);
9191
9339
  },
9192
9340
  instanced: (batch, data, material, glass) => {
@@ -9220,7 +9368,7 @@ export class WebGPURenderer {
9220
9368
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
9221
9369
  }
9222
9370
  pass.setVertexBuffer(index, gpuBatch.buffer);
9223
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
9371
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
9224
9372
  pass.drawIndexed(geometry.indexCount, count);
9225
9373
  },
9226
9374
  skinnedMesh: (mesh, model, palette, _material, glass) => {
@@ -9248,7 +9396,7 @@ export class WebGPURenderer {
9248
9396
  for (let index = 0; index < geometry.vertexBuffers.length; index++) {
9249
9397
  pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
9250
9398
  }
9251
- pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
9399
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
9252
9400
  pass.drawIndexed(geometry.indexCount);
9253
9401
  },
9254
9402
  };
@@ -11377,6 +11525,7 @@ export class WebGPURenderer {
11377
11525
  /* Uncorrected, for the reason the shaft's own copy of this line carries: the shader does its
11378
11526
  own `* 0.5 + 0.5`, and correcting the matrix as well doubles it. */
11379
11527
  f.set(env?.lightViewProj ?? this.mediumIdentity, at('uLightViewProj'));
11528
+ f.set((env === null ? this.mediumIdentity : this.movingLightFor(env)), at('uMovingLightViewProj'));
11380
11529
  ints[at('uPeeledShadowEnabled')] = this.peelView === null || !this.peelFilled ? 0 : 1;
11381
11530
  device.queue.writeBuffer(this.mediumUniforms, 0, this.mediumStaging);
11382
11531
  const g = this.mediumUpsampleFloats;
@@ -11778,6 +11927,9 @@ export class WebGPURenderer {
11778
11927
  const key = `${frameKey}|rs`;
11779
11928
  const pipeline = this.pipelines.peek(key) ??
11780
11929
  flatPipeline(this.pipelines, this.surface.device, animation === null ? this.flatLayoutFor(skinned, morphed) : this.animatedBindGroupLayout, this.variant, key, present, false, skinned, morphed, true, layer, instanceBuffer !== null, 'none', this.materialDoubleSided, false, eight, clothed, modelKind, 'whole', true, animation !== null);
11930
+ /* Still compiling where draws skip: the probe stands in for this surface until it lands. */
11931
+ if (pipeline === null)
11932
+ return;
11781
11933
  /* A static list's surface halves go into its own pool, beside its draws. */
11782
11934
  const command = this.staticRecording === null ? surface.take() : takeRecorded(this.staticRecording.surface);
11783
11935
  command.pipeline = pipeline;
@@ -11798,6 +11950,7 @@ export class WebGPURenderer {
11798
11950
  command.vertexBuffers[index++] = instanceBuffer;
11799
11951
  command.vertexCount = index;
11800
11952
  command.indexBuffer = mesh.indexBuffer;
11953
+ command.indexFormat = mesh.indexFormat;
11801
11954
  command.indexed = true;
11802
11955
  command.count = mesh.indexCount;
11803
11956
  command.instances = instances;
@@ -11809,6 +11962,30 @@ export class WebGPURenderer {
11809
11962
  * light alone and its colour alone into the blur's targets and test for the depth its frame half
11810
11963
  * wrote.
11811
11964
  */
11965
+ /**
11966
+ * Whether a split skin draw's two kept halves have pipelines, starting the compile of either that
11967
+ * has not: a skin drawn without them would lose its diffuse light for the frames they take, so a
11968
+ * split skin draws all three halves or none. See `PipelineCache.lit`.
11969
+ */
11970
+ skinHalvesReady(frameKey, base, skinned, morphed, layer, eight, clothed) {
11971
+ let ready = true;
11972
+ for (const half of SKIN_KEPT_HALVES) {
11973
+ const pipeline = this.skinHalfPipeline(half, frameKey, base, skinned, morphed, layer, eight, clothed);
11974
+ if (pipeline === null)
11975
+ ready = false;
11976
+ }
11977
+ return ready;
11978
+ }
11979
+ /** One kept half's pipeline, or none where it is still compiling: see `PipelineCache.lit`. */
11980
+ skinHalfPipeline(half, frameKey, base, skinned, morphed, layer, eight, clothed) {
11981
+ const pipelines = this.skinPipelines;
11982
+ const present = this.meshPresent.get(base);
11983
+ if (pipelines === null || present === undefined)
11984
+ return null;
11985
+ const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
11986
+ return (pipelines.peek(key) ??
11987
+ flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, false, skinned, morphed, true, layer, false, 'none', this.materialDoubleSided, false, eight, clothed, 'skin', half));
11988
+ }
11812
11989
  keepSkinDiffuse(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, slot, material) {
11813
11990
  /* A skin's halves are not bundled: a static list holding one replays draw by draw. */
11814
11991
  if (this.staticRecording !== null) {
@@ -11823,9 +12000,10 @@ export class WebGPURenderer {
11823
12000
  const bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
11824
12001
  /* The light alone, which the blur spreads, then the colour alone, which it is multiplied by. */
11825
12002
  for (const half of SKIN_KEPT_HALVES) {
11826
- const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
11827
- const pipeline = pipelines.peek(key) ??
11828
- flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, false, skinned, morphed, true, layer, false, 'none', this.materialDoubleSided, false, eight, clothed, 'skin', half);
12003
+ const pipeline = this.skinHalfPipeline(half, frameKey, base, skinned, morphed, layer, eight, clothed);
12004
+ /* Both landed before the draw was let through: see `skinHalvesReady`. */
12005
+ if (pipeline === null)
12006
+ return;
11829
12007
  const command = half === 'diffuse' ? skin.take() : skin.takeAlbedo();
11830
12008
  command.pipeline = pipeline;
11831
12009
  command.bindGroup = bindGroup;
@@ -11839,6 +12017,7 @@ export class WebGPURenderer {
11839
12017
  }
11840
12018
  command.vertexCount = mesh.vertexBuffers.length;
11841
12019
  command.indexBuffer = mesh.indexBuffer;
12020
+ command.indexFormat = mesh.indexFormat;
11842
12021
  command.indexed = true;
11843
12022
  command.count = mesh.indexCount;
11844
12023
  command.instances = 1;
@@ -12277,6 +12456,7 @@ export class WebGPURenderer {
12277
12456
  command.offsetCount = 0;
12278
12457
  command.vertexCount = 0;
12279
12458
  command.indexBuffer = null;
12459
+ command.indexFormat = 'uint32';
12280
12460
  command.indexed = false;
12281
12461
  command.instances = 1;
12282
12462
  command.indirect = null;
@@ -12344,7 +12524,7 @@ export class WebGPURenderer {
12344
12524
  pass.setVertexBuffer(i, buffer);
12345
12525
  }
12346
12526
  if (command.indexed && command.indexBuffer !== null) {
12347
- pass.setIndexBuffer(command.indexBuffer, 'uint32');
12527
+ pass.setIndexBuffer(command.indexBuffer, command.indexFormat);
12348
12528
  if (command.indirect !== null)
12349
12529
  pass.drawIndexedIndirect(command.indirect, 0);
12350
12530
  else