@driftengine/core 4.9.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 (231) hide show
  1. package/CHANGELOG.json +133 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +7 -4
  4. package/dist/index.js +5 -4
  5. package/dist/render/ambientHarmonics.d.ts +1 -1
  6. package/dist/render/ambientHarmonics.js +1 -1
  7. package/dist/render/areaLights.d.ts +4 -4
  8. package/dist/render/areaLights.js +1 -1
  9. package/dist/render/backend/api.d.ts +10 -3
  10. package/dist/render/backend/select.js +11 -5
  11. package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
  12. package/dist/render/backend/webgl2/boneAnimations.js +80 -0
  13. package/dist/render/backend/webgl2/instanced.d.ts +5 -1
  14. package/dist/render/backend/webgl2/instanced.js +13 -1
  15. package/dist/render/backend/webgl2/renderer.d.ts +189 -4
  16. package/dist/render/backend/webgl2/renderer.js +614 -66
  17. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
  18. package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
  19. package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
  20. package/dist/render/backend/webgpu/boneAnimations.js +76 -0
  21. package/dist/render/backend/webgpu/buffers.d.ts +24 -4
  22. package/dist/render/backend/webgpu/buffers.js +116 -59
  23. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  24. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  25. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  26. package/dist/render/backend/webgpu/clothOps.js +78 -0
  27. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  28. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  29. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  30. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  31. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  32. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  33. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  34. package/dist/render/backend/webgpu/depthPass.js +49 -14
  35. package/dist/render/backend/webgpu/drawCommand.d.ts +20 -4
  36. package/dist/render/backend/webgpu/drawCommand.js +11 -3
  37. package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
  38. package/dist/render/backend/webgpu/flatPass.js +137 -20
  39. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  40. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  41. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  42. package/dist/render/backend/webgpu/instanced.js +24 -1
  43. package/dist/render/backend/webgpu/latePass.js +1 -0
  44. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  45. package/dist/render/backend/webgpu/materialStore.js +265 -0
  46. package/dist/render/backend/webgpu/motionPass.js +15 -6
  47. package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
  48. package/dist/render/backend/webgpu/pipelineCache.js +46 -1
  49. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  50. package/dist/render/backend/webgpu/renderer.d.ts +200 -35
  51. package/dist/render/backend/webgpu/renderer.js +993 -168
  52. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  53. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  54. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  55. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  56. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  57. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  58. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  59. package/dist/render/backend/webgpu/staticBundles.js +242 -0
  60. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  61. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  62. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  63. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  64. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  65. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  66. package/dist/render/bloomChain.d.ts +3 -3
  67. package/dist/render/boneAnimation.d.ts +92 -0
  68. package/dist/render/boneAnimation.js +133 -0
  69. package/dist/render/compressedSource.d.ts +33 -13
  70. package/dist/render/compressedSource.js +70 -25
  71. package/dist/render/glCompressed.js +83 -0
  72. package/dist/render/globalMediumPass.d.ts +2 -0
  73. package/dist/render/globalMediumPass.js +1 -0
  74. package/dist/render/indexWidth.d.ts +17 -0
  75. package/dist/render/indexWidth.js +23 -0
  76. package/dist/render/instances.d.ts +26 -0
  77. package/dist/render/lightBudget.d.ts +10 -0
  78. package/dist/render/lightBudget.js +10 -0
  79. package/dist/render/lightMatrix.d.ts +11 -0
  80. package/dist/render/lightMatrix.js +14 -0
  81. package/dist/render/lightmap.d.ts +1 -1
  82. package/dist/render/mesh.d.ts +14 -0
  83. package/dist/render/mesh.js +44 -14
  84. package/dist/render/pointLightSelection.d.ts +4 -4
  85. package/dist/render/renderQuality.d.ts +30 -0
  86. package/dist/render/renderQuality.js +5 -0
  87. package/dist/render/rendererHarness.d.ts +7 -0
  88. package/dist/render/rendererHarness.js +12 -0
  89. package/dist/render/screenSpaceReflection.d.ts +2 -1
  90. package/dist/render/shader.d.ts +15 -0
  91. package/dist/render/shader.js +41 -0
  92. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  93. package/dist/render/shaders/boneAnimation.js +63 -0
  94. package/dist/render/shaders/depth.d.ts +7 -0
  95. package/dist/render/shaders/depth.js +35 -10
  96. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  97. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  98. package/dist/render/shaders/flat/glassTint.js +3 -0
  99. package/dist/render/shaders/flat/index.d.ts +21 -0
  100. package/dist/render/shaders/flat/index.js +42 -7
  101. package/dist/render/shaders/flat/layered.d.ts +19 -0
  102. package/dist/render/shaders/flat/layered.js +115 -0
  103. package/dist/render/shaders/flat/main.d.ts +1 -1
  104. package/dist/render/shaders/flat/main.js +58 -21
  105. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  106. package/dist/render/shaders/flat/mapReads.js +38 -0
  107. package/dist/render/shaders/flat/models.js +1 -1
  108. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  109. package/dist/render/shaders/flat/movingSun.js +46 -0
  110. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  111. package/dist/render/shaders/flat/normalMap.js +14 -3
  112. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  113. package/dist/render/shaders/flat/overlay.js +42 -20
  114. package/dist/render/shaders/flat/pointShadow.js +2 -2
  115. package/dist/render/shaders/flat/preamble.js +59 -27
  116. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  117. package/dist/render/shaders/flat/worldUv.js +113 -0
  118. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  119. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  120. package/dist/render/shaders/generated/flat.wgsl.d.ts +3616 -3042
  121. package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
  122. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  123. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  124. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  125. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  126. package/dist/render/shaders/globalMedium.js +9 -1
  127. package/dist/render/shaders/lightVolume.js +9 -1
  128. package/dist/render/shaders/outputTransform.d.ts +7 -0
  129. package/dist/render/shaders/outputTransform.js +28 -14
  130. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  131. package/dist/render/shaders/tangentFrame.js +16 -3
  132. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  133. package/dist/render/shaders/vertexChannel.js +26 -0
  134. package/dist/render/skinnedClothRun.d.ts +28 -0
  135. package/dist/render/skinnedClothRun.js +103 -35
  136. package/dist/render/staticDraws.d.ts +47 -0
  137. package/dist/render/staticDraws.js +43 -0
  138. package/dist/render/surfaceLayers.d.ts +39 -0
  139. package/dist/render/surfaceLayers.js +26 -0
  140. package/dist/render/surfaceOverlay.d.ts +16 -3
  141. package/dist/render/surfaceOverlay.js +11 -6
  142. package/dist/render/surfaceProjection.d.ts +34 -0
  143. package/dist/render/surfaceProjection.js +30 -0
  144. package/dist/render/surfaceTexture.d.ts +68 -7
  145. package/dist/render/surfaceTexture.js +56 -17
  146. package/dist/render/vertexPacking.d.ts +48 -0
  147. package/dist/render/vertexPacking.js +68 -0
  148. package/package.json +6 -6
  149. package/src/index.ts +12 -4
  150. package/src/render/ambientHarmonics.ts +1 -1
  151. package/src/render/areaLights.ts +4 -4
  152. package/src/render/backend/api.ts +13 -3
  153. package/src/render/backend/select.ts +10 -5
  154. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  155. package/src/render/backend/webgl2/instanced.ts +20 -1
  156. package/src/render/backend/webgl2/renderer.ts +723 -79
  157. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  158. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  159. package/src/render/backend/webgpu/buffers.ts +195 -70
  160. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  161. package/src/render/backend/webgpu/clothOps.ts +86 -0
  162. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  163. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  164. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  165. package/src/render/backend/webgpu/depthPass.ts +51 -10
  166. package/src/render/backend/webgpu/drawCommand.ts +25 -4
  167. package/src/render/backend/webgpu/flatPass.ts +212 -41
  168. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  169. package/src/render/backend/webgpu/instanced.ts +32 -1
  170. package/src/render/backend/webgpu/latePass.ts +1 -0
  171. package/src/render/backend/webgpu/materialStore.ts +276 -0
  172. package/src/render/backend/webgpu/motionPass.ts +18 -7
  173. package/src/render/backend/webgpu/pipelineCache.ts +53 -7
  174. package/src/render/backend/webgpu/renderer.ts +1201 -202
  175. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  176. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  177. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  178. package/src/render/backend/webgpu/staticBundles.ts +364 -0
  179. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  180. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  181. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  182. package/src/render/bloomChain.ts +3 -3
  183. package/src/render/boneAnimation.ts +223 -0
  184. package/src/render/compressedSource.ts +121 -29
  185. package/src/render/glCompressed.ts +83 -0
  186. package/src/render/globalMediumPass.ts +7 -0
  187. package/src/render/indexWidth.ts +27 -0
  188. package/src/render/instances.ts +27 -0
  189. package/src/render/lightBudget.ts +11 -0
  190. package/src/render/lightMatrix.ts +15 -0
  191. package/src/render/lightmap.ts +1 -1
  192. package/src/render/mesh.ts +72 -14
  193. package/src/render/pointLightSelection.ts +4 -4
  194. package/src/render/renderQuality.ts +38 -0
  195. package/src/render/rendererHarness.ts +15 -0
  196. package/src/render/screenSpaceReflection.ts +2 -1
  197. package/src/render/shader.ts +59 -0
  198. package/src/render/shaders/boneAnimation.ts +63 -0
  199. package/src/render/shaders/depth.ts +45 -10
  200. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  201. package/src/render/shaders/flat/glassTint.ts +7 -1
  202. package/src/render/shaders/flat/index.ts +63 -7
  203. package/src/render/shaders/flat/layered.ts +115 -0
  204. package/src/render/shaders/flat/main.ts +58 -21
  205. package/src/render/shaders/flat/mapReads.ts +38 -0
  206. package/src/render/shaders/flat/models.ts +1 -1
  207. package/src/render/shaders/flat/movingSun.ts +46 -0
  208. package/src/render/shaders/flat/normalMap.ts +14 -3
  209. package/src/render/shaders/flat/overlay.ts +42 -20
  210. package/src/render/shaders/flat/pointShadow.ts +2 -2
  211. package/src/render/shaders/flat/preamble.ts +59 -27
  212. package/src/render/shaders/flat/worldUv.ts +113 -0
  213. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  214. package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
  215. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  216. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  217. package/src/render/shaders/globalMedium.ts +9 -1
  218. package/src/render/shaders/lightVolume.ts +9 -1
  219. package/src/render/shaders/outputTransform.ts +30 -14
  220. package/src/render/shaders/tangentFrame.ts +16 -3
  221. package/src/render/shaders/vertexChannel.ts +27 -0
  222. package/src/render/skinnedClothRun.ts +138 -38
  223. package/src/render/staticDraws.ts +95 -0
  224. package/src/render/surfaceLayers.ts +57 -0
  225. package/src/render/surfaceOverlay.ts +24 -7
  226. package/src/render/surfaceProjection.ts +57 -0
  227. package/src/render/surfaceTexture.ts +124 -27
  228. package/src/render/vertexPacking.ts +109 -0
  229. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  230. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  231. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -3,6 +3,8 @@ import {
3
3
  DEPTH_CUTOUT_FRAG_WGSL,
4
4
  DEPTH_CUTOUT_VERT_WGSL,
5
5
  DEPTH_FRAG_WGSL,
6
+ DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_WGSL,
7
+ DEPTH_INSTANCED_ANIMATED_VERT_WGSL,
6
8
  DEPTH_INSTANCED_CUTOUT_VERT_WGSL,
7
9
  DEPTH_INSTANCED_VERT_WGSL,
8
10
  DEPTH_SKINNED_VERT_WGSL,
@@ -13,6 +15,7 @@ import { vertexConstants } from './flatPass.ts';
13
15
  import type { PipelineCache } from './pipelineCache.ts';
14
16
  import { shaderModule } from './shaderModules.ts';
15
17
  import type { ClothViews } from './clothTextures.ts';
18
+ import type { AnimationViews } from './flatPass.ts';
16
19
 
17
20
  /**
18
21
  * The shadow pass: the world drawn from the light, into a depth map.
@@ -52,6 +55,17 @@ const CLOTH_TEXTURES = [
52
55
  DEPTH_BINDINGS.DEPTH_SKINNED_VERT.textures.uClothRest,
53
56
  ] as const;
54
57
 
58
+ /** A bone animation's three textures, at the bindings both animated depth variants declare them. */
59
+ const ANIMATION_TEXTURES = [
60
+ DEPTH_BINDINGS.DEPTH_INSTANCED_ANIMATED_VERT.textures.uBonePlaces,
61
+ DEPTH_BINDINGS.DEPTH_INSTANCED_ANIMATED_VERT.textures.uBoneTurns,
62
+ DEPTH_BINDINGS.DEPTH_INSTANCED_ANIMATED_VERT.textures.uInstanceClocks,
63
+ ] as const;
64
+ export const DEPTH_INSTANCED_ANIMATED_VERT_FIELDS =
65
+ DEPTH_BINDINGS.DEPTH_INSTANCED_ANIMATED_VERT.fields;
66
+ export const DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS =
67
+ DEPTH_BINDINGS.DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT.fields;
68
+
55
69
  const VISIBILITY_VERTEX = 0x1;
56
70
  const VISIBILITY_FRAGMENT = 0x2;
57
71
 
@@ -82,10 +96,19 @@ export function createDepthBindGroupLayout(
82
96
  * no cutout caster pays nothing.
83
97
  */
84
98
  cutout = false,
99
+ /** An instanced caster playing a bone animation: its three vertex textures. */
100
+ animated = false,
85
101
  ): GPUBindGroupLayout {
86
102
  return device.createBindGroupLayout({
87
- label: skinned ? 'depth.layout.skinned' : cutout ? 'depth.layout.cutout' : 'depth.layout',
103
+ label: `${skinned ? 'depth.layout.skinned' : cutout ? 'depth.layout.cutout' : 'depth.layout'}${animated ? '.animated' : ''}`,
88
104
  entries: [
105
+ ...(animated
106
+ ? ANIMATION_TEXTURES.map((binding) => ({
107
+ binding: binding.texture,
108
+ visibility: VISIBILITY_VERTEX,
109
+ texture: { sampleType: 'unfilterable-float' as GPUTextureSampleType },
110
+ }))
111
+ : []),
89
112
  /* The texture and no sampler: the shader reads the palette with `textureLoad`, an integer
90
113
  coordinate with nothing to filter, which is what `flatPass.ts` binds for the same
91
114
  palette. naga declares a sampler for it and nothing ever reads one. */
@@ -174,6 +197,8 @@ export function createDepthBindGroup(
174
197
  cutout: { readonly view: GPUTextureView; readonly sampler: GPUSampler } | null = null,
175
198
  /** A skinned group's cloth views: the caster's, or the stand-in where it has none. */
176
199
  cloth: ClothViews | null = null,
200
+ /** An animated caster's clip and clocks. See `AnimationViews`. */
201
+ animation: AnimationViews | null = null,
177
202
  ): GPUBindGroup {
178
203
  return device.createBindGroup({
179
204
  label:
@@ -208,6 +233,13 @@ export function createDepthBindGroup(
208
233
  { binding: CUTOUT_MAP.sampler, resource: cutout.sampler },
209
234
  ]
210
235
  : []),
236
+ ...(animation !== null
237
+ ? [
238
+ { binding: ANIMATION_TEXTURES[0].texture, resource: animation.places },
239
+ { binding: ANIMATION_TEXTURES[1].texture, resource: animation.turns },
240
+ { binding: ANIMATION_TEXTURES[2].texture, resource: animation.clocks },
241
+ ]
242
+ : []),
211
243
  ],
212
244
  });
213
245
  }
@@ -256,6 +288,8 @@ export function depthPipeline(
256
288
  skinEight = false,
257
289
  /** Whether a skinned caster is placed by a cloth binding, as its visible draw is. The key carries it. */
258
290
  cloth = false,
291
+ /** An instanced caster playing a bone animation. The key must carry it. */
292
+ animated = false,
259
293
  ): GPURenderPipeline {
260
294
  return cache.get(key, () => ({
261
295
  label: key,
@@ -263,15 +297,22 @@ export function depthPipeline(
263
297
  vertex: {
264
298
  module: shaderModule(
265
299
  device,
266
- cutout
267
- ? instanced
268
- ? { label: 'depth.instanced.cutout.vert', code: DEPTH_INSTANCED_CUTOUT_VERT_WGSL }
269
- : { label: 'depth.cutout.vert', code: DEPTH_CUTOUT_VERT_WGSL }
270
- : instanced
271
- ? { label: 'depth.instanced.vert', code: DEPTH_INSTANCED_VERT_WGSL }
272
- : skinned
273
- ? { label: 'depth.skinned.vert', code: DEPTH_SKINNED_VERT_WGSL }
274
- : { label: 'depth.vert', code: DEPTH_VERT_WGSL },
300
+ animated
301
+ ? cutout
302
+ ? {
303
+ label: 'depth.instanced.animated.cutout.vert',
304
+ code: DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_WGSL,
305
+ }
306
+ : { label: 'depth.instanced.animated.vert', code: DEPTH_INSTANCED_ANIMATED_VERT_WGSL }
307
+ : cutout
308
+ ? instanced
309
+ ? { label: 'depth.instanced.cutout.vert', code: DEPTH_INSTANCED_CUTOUT_VERT_WGSL }
310
+ : { label: 'depth.cutout.vert', code: DEPTH_CUTOUT_VERT_WGSL }
311
+ : instanced
312
+ ? { label: 'depth.instanced.vert', code: DEPTH_INSTANCED_VERT_WGSL }
313
+ : skinned
314
+ ? { label: 'depth.skinned.vert', code: DEPTH_SKINNED_VERT_WGSL }
315
+ : { label: 'depth.vert', code: DEPTH_VERT_WGSL },
275
316
  ),
276
317
  entryPoint: 'main',
277
318
  buffers: vertexBufferLayouts(present, instanced),
@@ -2,7 +2,7 @@
2
2
  * One recorded draw, in the only shape this backend ever issues.
3
3
  *
4
4
  * Sampled across three unrelated verbs — panels, particles, wind streaks — the sequence is the
5
- * same every time: a pipeline, a bind group with up to two dynamic offsets, some vertex
5
+ * same every time: a pipeline, a bind group with up to four dynamic offsets, some vertex
6
6
  * buffers, perhaps an index buffer, then a draw. Nothing in it is specific to what is being
7
7
  * drawn except the values.
8
8
  *
@@ -21,14 +21,21 @@ const VERTEX_BUFFER_SLOTS = 8;
21
21
  export interface DrawCommand {
22
22
  pipeline: GPURenderPipeline | null;
23
23
  bindGroup: GPUBindGroup | null;
24
- /** Dynamic offsets, held as two numbers rather than an array so a reset stays free. */
24
+ /**
25
+ * Dynamic offsets, held as numbers rather than an array so a reset stays free. Four only for a
26
+ * lit mesh: its draw's slot, its pass's, its material's and its view's, in binding order.
27
+ */
25
28
  offsetA: number;
26
29
  offsetB: number;
30
+ offsetC: number;
31
+ offsetD: number;
27
32
  offsetCount: number;
28
33
  /** Fixed length; `vertexCount` says how many slots are live. */
29
34
  readonly vertexBuffers: (GPUBuffer | null)[];
30
35
  vertexCount: number;
31
36
  indexBuffer: GPUBuffer | null;
37
+ /** How wide an index of `indexBuffer` is: a mesh's own, thirty-two bits for everything else. */
38
+ indexFormat: GPUIndexFormat;
32
39
  indexed: boolean;
33
40
  count: number;
34
41
  instances: number;
@@ -37,6 +44,12 @@ export interface DrawCommand {
37
44
  * cull's survivors, counted on the device. Null for every other draw.
38
45
  */
39
46
  indirect: GPUBuffer | null;
47
+ /**
48
+ * Render bundles to execute instead of everything above: a static list recorded once, replayed
49
+ * as one command. Held as the array `executeBundles` takes, kept by the list, so issuing it
50
+ * allocates nothing. Null for every other command, and put back to null by every take.
51
+ */
52
+ bundles: readonly GPURenderBundle[] | null;
40
53
  }
41
54
 
42
55
  export interface CommandPool {
@@ -52,14 +65,18 @@ function emptyCommand(): DrawCommand {
52
65
  bindGroup: null,
53
66
  offsetA: 0,
54
67
  offsetB: 0,
68
+ offsetC: 0,
69
+ offsetD: 0,
55
70
  offsetCount: 0,
56
71
  vertexBuffers: new Array<GPUBuffer | null>(VERTEX_BUFFER_SLOTS).fill(null),
57
72
  vertexCount: 0,
58
73
  indexBuffer: null,
74
+ indexFormat: 'uint32',
59
75
  indexed: false,
60
76
  count: 0,
61
77
  instances: 1,
62
78
  indirect: null,
79
+ bundles: null,
63
80
  };
64
81
  }
65
82
 
@@ -72,14 +89,18 @@ export function createCommandPool(capacity: number): CommandPool {
72
89
  /**
73
90
  * Hand out the next command, growing if a frame overruns.
74
91
  *
75
- * The entry is **not cleared**. Every caller fills every field it uses, and clearing here would
76
- * be a write per field per draw for no reader — the same reasoning the node arena uses.
92
+ * The entry is **not cleared**, but for `bundles`. Every caller fills every field it uses, and
93
+ * clearing here would be a write per field per draw for no reader — the same reasoning the node
94
+ * arena uses. `bundles` is the exception because almost no caller knows it exists: a pooled command
95
+ * that once carried a static list's bundles and was then taken for an ordinary draw would replay the
96
+ * list instead, so the one field nobody fills is the one field put back here.
77
97
  */
78
98
  export function takeCommand(pool: CommandPool): number {
79
99
  const at = pool.taken;
80
100
  while (at >= pool.commands.length) pool.commands.push(emptyCommand());
81
101
  pool.taken = at + 1;
82
102
  if (pool.taken > pool.highWater) pool.highWater = pool.taken;
103
+ (pool.commands[at] as DrawCommand).bundles = null;
83
104
  return at;
84
105
  }
85
106
 
@@ -99,6 +99,20 @@ export interface StageBindings {
99
99
  readonly textures: Readonly<Record<string, TextureBinding>>;
100
100
  /** Each pipeline-overridable constant's id, by the GLSL name it was declared under. */
101
101
  readonly overrides?: Readonly<Record<string, number>>;
102
+ /**
103
+ * The second block, of the uniforms marked `// wgsl:material`: its binding, size and fields.
104
+ * Absent from a stage that marks none, which is every stage but the lit fragment's.
105
+ */
106
+ readonly materialUniforms?: number;
107
+ readonly materialSize?: number;
108
+ readonly materialFields?: StageBindings['fields'];
109
+ /**
110
+ * The vertex stage's view block, of the uniforms marked `// wgsl:view`: the camera, the light's
111
+ * matrix and the wind, which a pass settles. Absent from every stage but the lit vertex one.
112
+ */
113
+ readonly viewUniforms?: number;
114
+ readonly viewSize?: number;
115
+ readonly viewFields?: StageBindings['fields'];
102
116
  }
103
117
 
104
118
  /*
@@ -130,9 +144,16 @@ const VERTEX_BINDINGS = FLAT_BINDINGS.flatVert as unknown as Readonly<
130
144
  >;
131
145
 
132
146
  /** The key for a set of vertex flags: the same rule `variantKey` uses, flags on, sorted, joined. */
133
- export function flatVertexKey(skinned: boolean, morphed: boolean, instanced = false): string {
147
+ export function flatVertexKey(
148
+ skinned: boolean,
149
+ morphed: boolean,
150
+ instanced = false,
151
+ /** Whether an instanced variant plays a bone animation. See `shaders/boneAnimation.ts`. */
152
+ animated = false,
153
+ ): string {
134
154
  const on: string[] = [];
135
155
  /* Alphabetical, because the generator's own `variantKey` sorts and the two keys must agree. */
156
+ if (animated) on.push('animated');
136
157
  if (instanced) on.push('instanced');
137
158
  if (morphed) on.push('morphed');
138
159
  if (skinned) on.push('skinned');
@@ -144,8 +165,9 @@ export function flatVertexBindings(
144
165
  skinned: boolean,
145
166
  morphed = false,
146
167
  instanced = false,
168
+ animated = false,
147
169
  ): StageBindings {
148
- const key = flatVertexKey(skinned, morphed, instanced);
170
+ const key = flatVertexKey(skinned, morphed, instanced, animated);
149
171
  const found = VERTEX_BINDINGS[key];
150
172
  if (found === undefined) throw new Error(`flatPass: no vertex bindings for variant "${key}"`);
151
173
  return found;
@@ -221,6 +243,8 @@ for (const [key, bindings] of Object.entries(VERTEX_BINDINGS)) {
221
243
  }
222
244
 
223
245
  export const FLAT_VERT_FIELDS = FLAT_BINDINGS.flatVert.none.fields;
246
+ /** The animated instanced variant's block: the instanced fields, and the clip's `uBoneClip`. */
247
+ export const FLAT_ANIMATED_VERT_FIELDS = FLAT_BINDINGS.flatVert['animated+instanced'].fields;
224
248
  /**
225
249
  * The **largest** variant's block, because one ring serves every draw.
226
250
  *
@@ -233,6 +257,35 @@ export const FLAT_VERT_SIZE = Math.max(
233
257
  );
234
258
  const FLAT_VERT_BINDING = PLAIN_VERTEX.uniforms;
235
259
 
260
+ /*
261
+ * **The view block may be shorter in a variant, never different.** One slot serves every lit draw
262
+ * of a pass, plain and instanced alike, so every field a variant's view block declares must sit
263
+ * where the plain variant has it. The instanced variant declares no wind, and its block is the plain
264
+ * one's first two matrices.
265
+ */
266
+ const PLAIN_VIEW_FIELDS = PLAIN_VERTEX.viewFields ?? {};
267
+ for (const [key, bindings] of Object.entries(VERTEX_BINDINGS)) {
268
+ if (bindings.viewUniforms !== PLAIN_VERTEX.viewUniforms) {
269
+ throw new Error(`flatPass: vertex variant "${key}" binds its view block elsewhere`);
270
+ }
271
+ for (const [field, here] of Object.entries(bindings.viewFields ?? {})) {
272
+ if (PLAIN_VIEW_FIELDS[field]?.offset !== here.offset) {
273
+ throw new Error(
274
+ `flatPass: vertex variant "${key}" puts view field ${field} at ${here.offset} where the ` +
275
+ `plain variant has it at ${PLAIN_VIEW_FIELDS[field]?.offset}.`,
276
+ );
277
+ }
278
+ }
279
+ }
280
+
281
+ /** The view block's fields, binding and size: the largest variant's, as `FLAT_VERT_SIZE` is. */
282
+ export const FLAT_VIEW_FIELDS = PLAIN_VIEW_FIELDS;
283
+ export const FLAT_VIEW_SIZE = Math.max(
284
+ ...Object.values(VERTEX_BINDINGS).map((bindings) => bindings.viewSize ?? 0),
285
+ );
286
+ const FLAT_VIEW_BINDING = PLAIN_VERTEX.viewUniforms ?? -1;
287
+ if (FLAT_VIEW_BINDING < 0) throw new Error('flatPass: the lit vertex stage declares no view block');
288
+
236
289
  /**
237
290
  * The most sampled textures and distinct samplers any lit variant carrying the environment probe
238
291
  * binds in its fragment stage, counted from the generated bindings: what a device must allow per
@@ -260,6 +313,46 @@ export function flatFragmentBindings(variant: FlatVariant): StageBindings {
260
313
  return found;
261
314
  }
262
315
 
316
+ /**
317
+ * The lit fragment stage's two blocks as the renderer keeps them on the CPU: the pass's, then the
318
+ * material's straight after it, in one scratch, with every field at its offset in that scratch.
319
+ *
320
+ * **One scratch rather than two, so no setter has to know which block its field is in.** A setter
321
+ * writes by name, as it always has; the draw copies the material's bytes — `passSize` onwards — into
322
+ * the store and the pass's bytes into a ring slot, each only when something has changed it.
323
+ */
324
+ export interface FlatFragmentScratch {
325
+ readonly fields: StageBindings['fields'];
326
+ /** Bytes of the pass's block, which is where the material's starts in the scratch. */
327
+ readonly passSize: number;
328
+ readonly passBinding: number;
329
+ readonly materialSize: number;
330
+ readonly materialBinding: number;
331
+ }
332
+
333
+ export function flatFragmentScratch(variant: FlatVariant): FlatFragmentScratch {
334
+ const bindings = flatFragmentBindings(variant);
335
+ const { materialUniforms, materialSize, materialFields } = bindings;
336
+ if (
337
+ materialUniforms === undefined ||
338
+ materialSize === undefined ||
339
+ materialFields === undefined
340
+ ) {
341
+ throw new Error(`flatPass: variant "${variant}" has no material block`);
342
+ }
343
+ const fields: Record<string, StageBindings['fields'][string]> = { ...bindings.fields };
344
+ for (const [name, field] of Object.entries(materialFields)) {
345
+ fields[name] = { ...field, offset: bindings.uniformSize + field.offset };
346
+ }
347
+ return {
348
+ fields,
349
+ passSize: bindings.uniformSize,
350
+ passBinding: bindings.uniforms,
351
+ materialSize,
352
+ materialBinding: materialUniforms,
353
+ };
354
+ }
355
+
263
356
  /**
264
357
  * The lit fragment stage's constants, set from the cache's switches, keyed by each one's **id**.
265
358
  *
@@ -306,6 +399,14 @@ function flatFragmentConstants(
306
399
  return constants;
307
400
  }
308
401
 
402
+ /** The material block's binding, which every lit variant declares. */
403
+ function materialBinding(fragment: StageBindings): number {
404
+ if (fragment.materialUniforms === undefined) {
405
+ throw new Error('flatPass: the lit fragment stage declares no material block');
406
+ }
407
+ return fragment.materialUniforms;
408
+ }
409
+
309
410
  /** The depth format both backends compare with. */
310
411
  /* Re-exported so the twenty passes that already import it from here do not each learn a new
311
412
  module; `depthConvention.ts` is where the decision lives and why. */
@@ -317,15 +418,19 @@ const VISIBILITY_FRAGMENT = 0x2;
317
418
  /**
318
419
  * The layout this variant binds against.
319
420
  *
320
- * The vertex block is **dynamic**, because every draw supplies its own model matrix and tint
321
- * from its own slot of one buffer. The fragment block is not: it holds the lights, the fog
322
- * and the camera, settled once by `bindMeshPass` and unchanged between draws.
421
+ * All four blocks are **dynamic**: the vertex block because every draw supplies its own model
422
+ * matrix and tint from its own slot of one buffer; the fragment stage's pass block — the lights, the
423
+ * fog, the camera — and the vertex stage's view block because a frame binds more than one pass; and
424
+ * the material block because a material changes between draws. Their dynamic offsets go in binding
425
+ * order: the draw's, the pass's, the material's, the view's.
323
426
  */
324
427
  export function createFlatBindGroupLayout(
325
428
  device: GPUDevice,
326
429
  variant: FlatVariant,
327
430
  skinned = false,
328
431
  morphed = false,
432
+ /** The instanced variant that plays a bone animation: its three vertex textures. */
433
+ animated = false,
329
434
  ): GPUBindGroupLayout {
330
435
  const fragment = flatFragmentBindings(variant);
331
436
  const entries: GPUBindGroupLayoutEntry[] = [
@@ -334,15 +439,31 @@ export function createFlatBindGroupLayout(
334
439
  visibility: VISIBILITY_VERTEX,
335
440
  buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: FLAT_VERT_SIZE },
336
441
  },
442
+ {
443
+ binding: FLAT_VIEW_BINDING,
444
+ visibility: VISIBILITY_VERTEX,
445
+ /* Dynamic, a slot a pass, for the reason the fragment stage's pass block is. */
446
+ buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: FLAT_VIEW_SIZE },
447
+ },
337
448
  {
338
449
  binding: fragment.uniforms,
339
450
  visibility: VISIBILITY_FRAGMENT,
340
451
  /*
341
- * Dynamic, because this block carries the material and a material changes between draws.
342
- * See `WebGPURenderer.perFrame` for why one buffer cannot hold two answers.
452
+ * Dynamic, because a pass's lights and camera change between passes of one frame — a
453
+ * mirror's, a capture's — and one buffer cannot hold two answers. See `WebGPURenderer.passRing`.
343
454
  */
344
455
  buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: fragment.uniformSize },
345
456
  },
457
+ {
458
+ binding: materialBinding(fragment),
459
+ visibility: VISIBILITY_FRAGMENT,
460
+ /* Dynamic, because a material changes between draws. See `MaterialStore`. */
461
+ buffer: {
462
+ type: 'uniform',
463
+ hasDynamicOffset: true,
464
+ minBindingSize: fragment.materialSize ?? 0,
465
+ },
466
+ },
346
467
  ];
347
468
 
348
469
  /** What each sampler binding was declared as, so a shared one is declared once and agrees. */
@@ -489,12 +610,43 @@ export function createFlatBindGroupLayout(
489
610
  }
490
611
  }
491
612
 
613
+ /* A bone animation's places, turns and the batch's clocks, read by index as the palette is. */
614
+ if (animated) {
615
+ for (const binding of animationBindings()) {
616
+ entries.push({
617
+ binding,
618
+ visibility: VISIBILITY_VERTEX,
619
+ texture: { sampleType: 'unfilterable-float' },
620
+ });
621
+ }
622
+ }
623
+
492
624
  return device.createBindGroupLayout({
493
- label: `flat.layout:${variant}|${flatVertexKey(skinned, morphed)}`,
625
+ label: `flat.layout:${variant}|${flatVertexKey(skinned, morphed, animated, animated)}`,
494
626
  entries,
495
627
  });
496
628
  }
497
629
 
630
+ /** The three textures a bone animation binds, in the order `AnimationViews` holds them. */
631
+ function animationBindings(): [number, number, number] {
632
+ const textures = flatVertexBindings(false, false, true, true).textures;
633
+ const at = (name: string): number => {
634
+ const found = textures[name];
635
+ if (found === undefined) {
636
+ throw new Error(`flatPass: the animated vertex shader declares no ${name}`);
637
+ }
638
+ return found.texture;
639
+ };
640
+ return [at('uBonePlaces'), at('uBoneTurns'), at('uInstanceClocks')];
641
+ }
642
+
643
+ /** What an animated draw binds besides its material: the clip's two textures and its batch's clocks. */
644
+ export interface AnimationViews {
645
+ readonly places: GPUTextureView;
646
+ readonly turns: GPUTextureView;
647
+ readonly clocks: GPUTextureView;
648
+ }
649
+
498
650
  /**
499
651
  * Bind the buffers and one view per declared texture.
500
652
  *
@@ -509,7 +661,9 @@ export function createFlatBindGroup(
509
661
  layout: GPUBindGroupLayout,
510
662
  variant: FlatVariant,
511
663
  perDraw: GPUBuffer,
512
- perFrame: GPUBuffer,
664
+ passes: GPUBuffer,
665
+ materials: GPUBuffer,
666
+ views: GPUBuffer,
513
667
  resolve: (name: string) => { view: GPUTextureView; sampler: GPUSampler },
514
668
  /** The palette view for a skinned bind group, or null for an unskinned one. */
515
669
  palette: GPUTextureView | null = null,
@@ -517,6 +671,8 @@ export function createFlatBindGroup(
517
671
  deltas: GPUTextureView | null = null,
518
672
  /** A skinned bind group's cloth views — the draw's, or the stand-in where it sets none. */
519
673
  cloth: ClothViews | null = null,
674
+ /** An animated instanced group's views. See `AnimationViews`. */
675
+ animation: AnimationViews | null = null,
520
676
  ): GPUBindGroup {
521
677
  const fragment = flatFragmentBindings(variant);
522
678
  const entries: GPUBindGroupEntry[] = [
@@ -524,7 +680,12 @@ export function createFlatBindGroup(
524
680
  binding: FLAT_VERT_BINDING,
525
681
  resource: { buffer: perDraw, size: FLAT_VERT_SIZE },
526
682
  },
527
- { binding: fragment.uniforms, resource: { buffer: perFrame, size: fragment.uniformSize } },
683
+ { binding: FLAT_VIEW_BINDING, resource: { buffer: views, size: FLAT_VIEW_SIZE } },
684
+ { binding: fragment.uniforms, resource: { buffer: passes, size: fragment.uniformSize } },
685
+ {
686
+ binding: materialBinding(fragment),
687
+ resource: { buffer: materials, size: fragment.materialSize ?? 0 },
688
+ },
528
689
  ];
529
690
 
530
691
  /* The same dedupe the layout does, and for the same reason: the shadow bindings share one
@@ -563,8 +724,15 @@ export function createFlatBindGroup(
563
724
  entries.push({ binding: restAt, resource: cloth.rest });
564
725
  }
565
726
 
727
+ if (animation !== null) {
728
+ const [places, turns, clocks] = animationBindings();
729
+ entries.push({ binding: places, resource: animation.places });
730
+ entries.push({ binding: turns, resource: animation.turns });
731
+ entries.push({ binding: clocks, resource: animation.clocks });
732
+ }
733
+
566
734
  return device.createBindGroup({
567
- label: `flat.bindGroup:${variant}|${flatVertexKey(palette !== null, deltas !== null)}`,
735
+ label: `flat.bindGroup:${variant}|${flatVertexKey(palette !== null, deltas !== null, animation !== null, animation !== null)}`,
568
736
  layout,
569
737
  entries,
570
738
  });
@@ -641,33 +809,34 @@ export function flatPipeline(
641
809
  * targets `reflectionSurfacePass.ts` holds. The key must carry it: `|rs`.
642
810
  */
643
811
  reflectionSurface = false,
644
- ): GPURenderPipeline {
645
- return cache.get(
646
- key,
647
- () =>
648
- flatDescriptor(
649
- cache,
650
- device,
651
- layout,
652
- variant,
653
- key,
654
- present,
655
- translucent,
656
- skinned,
657
- morphed,
658
- depthWrite,
659
- depthLayer,
660
- instanced,
661
- oit,
662
- doubleSided,
663
- alphaToCoverage,
664
- skinEight,
665
- cloth,
666
- model,
667
- skinPass,
668
- reflectionSurface,
669
- ),
670
- true,
812
+ /** An instanced pipeline that plays a bone animation. The key must carry it: `|anim`. */
813
+ animated = false,
814
+ ): GPURenderPipeline | null {
815
+ /* None where draws skip a compile and this one has not landed: see `PipelineCache.lit`. */
816
+ return cache.lit(key, () =>
817
+ flatDescriptor(
818
+ cache,
819
+ device,
820
+ layout,
821
+ variant,
822
+ key,
823
+ present,
824
+ translucent,
825
+ skinned,
826
+ morphed,
827
+ depthWrite,
828
+ depthLayer,
829
+ instanced,
830
+ oit,
831
+ doubleSided,
832
+ alphaToCoverage,
833
+ skinEight,
834
+ cloth,
835
+ model,
836
+ skinPass,
837
+ reflectionSurface,
838
+ animated,
839
+ ),
671
840
  );
672
841
  }
673
842
 
@@ -799,6 +968,8 @@ function flatDescriptor(
799
968
  model: SurfaceModelKind | null = null,
800
969
  skinPass: SkinHalf = 'whole',
801
970
  reflectionSurface = false,
971
+ /** An instanced pipeline that plays a bone animation. See `shaders/boneAnimation.ts`. */
972
+ animated = false,
802
973
  ): GPURenderPipelineDescriptor {
803
974
  const offset = depthOffsetForLayer(depthLayer);
804
975
  /* The diffuse and colour halves are the same surface again, into the blur's targets; and so is
@@ -814,13 +985,13 @@ function flatDescriptor(
814
985
  * layout validates, draws, and is wrong, exactly as this file's header says.
815
986
  */
816
987
  module: shaderModule(device, {
817
- label: `flat.vert:${flatVertexKey(skinned, morphed, instanced)}`,
818
- code: FLAT_VERT_WGSL[flatVertexKey(skinned, morphed, instanced)] ?? '',
988
+ label: `flat.vert:${flatVertexKey(skinned, morphed, instanced, animated)}`,
989
+ code: FLAT_VERT_WGSL[flatVertexKey(skinned, morphed, instanced, animated)] ?? '',
819
990
  }),
820
991
  entryPoint: 'main',
821
992
  buffers: vertexBufferLayouts(present, instanced),
822
993
  constants: vertexConstants(
823
- flatVertexBindings(skinned, morphed, instanced),
994
+ flatVertexBindings(skinned, morphed, instanced, animated),
824
995
  skinEight,
825
996
  cloth,
826
997
  model === 'lightmap',