@driftengine/core 4.8.7 → 4.10.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 (263) hide show
  1. package/CHANGELOG.json +123 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +8 -1
  4. package/dist/index.js +11 -3
  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/atmosphere.d.ts +13 -0
  10. package/dist/render/atmosphere.js +9 -1
  11. package/dist/render/backend/api.d.ts +19 -4
  12. package/dist/render/backend/createRenderer.d.ts +6 -0
  13. package/dist/render/backend/createRenderer.js +4 -1
  14. package/dist/render/backend/select.js +11 -5
  15. package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
  16. package/dist/render/backend/webgl2/boneAnimations.js +80 -0
  17. package/dist/render/backend/webgl2/instanced.d.ts +5 -1
  18. package/dist/render/backend/webgl2/instanced.js +13 -1
  19. package/dist/render/backend/webgl2/renderer.d.ts +180 -4
  20. package/dist/render/backend/webgl2/renderer.js +550 -40
  21. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
  22. package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
  23. package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
  24. package/dist/render/backend/webgpu/boneAnimations.js +76 -0
  25. package/dist/render/backend/webgpu/buffers.d.ts +19 -4
  26. package/dist/render/backend/webgpu/buffers.js +18 -11
  27. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  28. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  29. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  30. package/dist/render/backend/webgpu/depthPass.js +49 -14
  31. package/dist/render/backend/webgpu/device.d.ts +7 -1
  32. package/dist/render/backend/webgpu/device.js +31 -3
  33. package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
  34. package/dist/render/backend/webgpu/drawCommand.js +10 -3
  35. package/dist/render/backend/webgpu/flatPass.d.ts +100 -42
  36. package/dist/render/backend/webgpu/flatPass.js +191 -43
  37. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  38. package/dist/render/backend/webgpu/instanced.js +24 -1
  39. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  40. package/dist/render/backend/webgpu/materialStore.js +265 -0
  41. package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
  42. package/dist/render/backend/webgpu/pipelineCache.js +7 -0
  43. package/dist/render/backend/webgpu/postPass.d.ts +51 -9
  44. package/dist/render/backend/webgpu/postPass.js +16 -2
  45. package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
  46. package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
  47. package/dist/render/backend/webgpu/renderer.d.ts +283 -37
  48. package/dist/render/backend/webgpu/renderer.js +1363 -202
  49. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  50. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  51. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  52. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  53. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  54. package/dist/render/backend/webgpu/staticBundles.js +241 -0
  55. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  56. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  57. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  58. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  59. package/dist/render/bloomChain.d.ts +3 -3
  60. package/dist/render/boneAnimation.d.ts +92 -0
  61. package/dist/render/boneAnimation.js +133 -0
  62. package/dist/render/compressedSource.d.ts +33 -13
  63. package/dist/render/compressedSource.js +70 -25
  64. package/dist/render/displayRange.d.ts +31 -0
  65. package/dist/render/displayRange.js +47 -0
  66. package/dist/render/filmicCurve.d.ts +74 -0
  67. package/dist/render/filmicCurve.js +203 -0
  68. package/dist/render/fog.d.ts +4 -1
  69. package/dist/render/fog.js +12 -2
  70. package/dist/render/frame/resources.d.ts +2 -2
  71. package/dist/render/frame/resources.js +6 -0
  72. package/dist/render/frameReflections.d.ts +40 -0
  73. package/dist/render/frameReflections.js +48 -0
  74. package/dist/render/glCompressed.js +83 -0
  75. package/dist/render/instances.d.ts +26 -0
  76. package/dist/render/lightBudget.d.ts +10 -0
  77. package/dist/render/lightBudget.js +10 -0
  78. package/dist/render/lightmap.d.ts +1 -1
  79. package/dist/render/mesh.d.ts +5 -0
  80. package/dist/render/mesh.js +6 -0
  81. package/dist/render/pass.d.ts +49 -0
  82. package/dist/render/pointLightSelection.d.ts +4 -4
  83. package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
  84. package/dist/render/reflectionSurfaceTargets.js +99 -0
  85. package/dist/render/renderQuality.d.ts +39 -8
  86. package/dist/render/renderQuality.js +4 -0
  87. package/dist/render/sceneTarget.d.ts +5 -0
  88. package/dist/render/sceneTarget.js +24 -1
  89. package/dist/render/screenSpaceReflection.d.ts +7 -1
  90. package/dist/render/screenSpaceReflection.js +5 -0
  91. package/dist/render/shaders/arcane.d.ts +1 -1
  92. package/dist/render/shaders/bolt.d.ts +1 -1
  93. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  94. package/dist/render/shaders/boneAnimation.js +63 -0
  95. package/dist/render/shaders/caustics.d.ts +1 -1
  96. package/dist/render/shaders/depth.d.ts +7 -0
  97. package/dist/render/shaders/depth.js +35 -10
  98. package/dist/render/shaders/film.d.ts +1 -1
  99. package/dist/render/shaders/filmLook.js +2 -1
  100. package/dist/render/shaders/fire.d.ts +1 -1
  101. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  102. package/dist/render/shaders/flat/glassTint.js +2 -0
  103. package/dist/render/shaders/flat/index.d.ts +16 -0
  104. package/dist/render/shaders/flat/index.js +33 -5
  105. package/dist/render/shaders/flat/main.d.ts +1 -1
  106. package/dist/render/shaders/flat/main.js +43 -5
  107. package/dist/render/shaders/flat/models.js +1 -1
  108. package/dist/render/shaders/flat/overlay.d.ts +16 -0
  109. package/dist/render/shaders/flat/overlay.js +117 -0
  110. package/dist/render/shaders/flat/pointShadow.js +3 -3
  111. package/dist/render/shaders/flat/preamble.js +38 -24
  112. package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
  113. package/dist/render/shaders/flat/reflectionSurface.js +61 -0
  114. package/dist/render/shaders/fog.d.ts +1 -1
  115. package/dist/render/shaders/fog.js +9 -2
  116. package/dist/render/shaders/fringe.d.ts +29 -0
  117. package/dist/render/shaders/fringe.js +55 -0
  118. package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  121. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  122. package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
  123. package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
  124. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  125. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  126. package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
  127. package/dist/render/shaders/generated/film.wgsl.js +1 -1
  128. package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
  129. package/dist/render/shaders/generated/fire.wgsl.js +1 -1
  130. package/dist/render/shaders/generated/flat.wgsl.d.ts +3254 -2920
  131. package/dist/render/shaders/generated/flat.wgsl.js +3292 -2957
  132. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  133. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  134. package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
  135. package/dist/render/shaders/generated/particle.wgsl.js +4 -4
  136. package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
  137. package/dist/render/shaders/generated/rush.wgsl.js +56 -26
  138. package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
  139. package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
  140. package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
  141. package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
  142. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
  143. package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
  144. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
  145. package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
  146. package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
  147. package/dist/render/shaders/generated/water.wgsl.js +1 -1
  148. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
  149. package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
  150. package/dist/render/shaders/line.d.ts +1 -1
  151. package/dist/render/shaders/outputTransform.d.ts +7 -0
  152. package/dist/render/shaders/outputTransform.js +28 -14
  153. package/dist/render/shaders/particle.d.ts +3 -3
  154. package/dist/render/shaders/rush.js +54 -11
  155. package/dist/render/shaders/scatter.d.ts +1 -1
  156. package/dist/render/shaders/smoke.d.ts +1 -1
  157. package/dist/render/shaders/ssrResolve.d.ts +17 -0
  158. package/dist/render/shaders/ssrResolve.js +51 -0
  159. package/dist/render/shaders/ssrTrace.js +26 -0
  160. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  161. package/dist/render/shaders/vertexChannel.js +26 -0
  162. package/dist/render/shaders/water.d.ts +1 -1
  163. package/dist/render/ssrPass.d.ts +16 -0
  164. package/dist/render/ssrPass.js +106 -1
  165. package/dist/render/staticDraws.d.ts +47 -0
  166. package/dist/render/staticDraws.js +43 -0
  167. package/dist/render/surfaceOverlay.d.ts +132 -0
  168. package/dist/render/surfaceOverlay.js +85 -0
  169. package/dist/render/surfaceTexture.d.ts +57 -7
  170. package/dist/render/surfaceTexture.js +125 -17
  171. package/dist/render/temporalAa.d.ts +14 -0
  172. package/dist/render/temporalAa.js +20 -0
  173. package/dist/render/vertexDefaults.d.ts +17 -0
  174. package/dist/render/vertexDefaults.js +24 -0
  175. package/package.json +6 -6
  176. package/src/index.ts +24 -2
  177. package/src/render/ambientHarmonics.ts +1 -1
  178. package/src/render/areaLights.ts +4 -4
  179. package/src/render/atmosphere.ts +22 -1
  180. package/src/render/backend/api.ts +22 -3
  181. package/src/render/backend/createRenderer.ts +18 -1
  182. package/src/render/backend/select.ts +10 -5
  183. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  184. package/src/render/backend/webgl2/instanced.ts +20 -1
  185. package/src/render/backend/webgl2/renderer.ts +672 -37
  186. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  187. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  188. package/src/render/backend/webgpu/buffers.ts +42 -16
  189. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  190. package/src/render/backend/webgpu/depthPass.ts +51 -10
  191. package/src/render/backend/webgpu/device.ts +42 -3
  192. package/src/render/backend/webgpu/drawCommand.ts +22 -4
  193. package/src/render/backend/webgpu/flatPass.ts +250 -40
  194. package/src/render/backend/webgpu/instanced.ts +32 -1
  195. package/src/render/backend/webgpu/materialStore.ts +276 -0
  196. package/src/render/backend/webgpu/pipelineCache.ts +7 -0
  197. package/src/render/backend/webgpu/postPass.ts +18 -0
  198. package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
  199. package/src/render/backend/webgpu/renderer.ts +1655 -230
  200. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  201. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  202. package/src/render/backend/webgpu/staticBundles.ts +363 -0
  203. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  204. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  205. package/src/render/bloomChain.ts +3 -3
  206. package/src/render/boneAnimation.ts +223 -0
  207. package/src/render/compressedSource.ts +121 -29
  208. package/src/render/displayRange.ts +61 -0
  209. package/src/render/filmicCurve.ts +229 -0
  210. package/src/render/fog.ts +19 -3
  211. package/src/render/frame/resources.ts +6 -0
  212. package/src/render/frameReflections.ts +72 -0
  213. package/src/render/glCompressed.ts +83 -0
  214. package/src/render/instances.ts +27 -0
  215. package/src/render/lightBudget.ts +11 -0
  216. package/src/render/lightmap.ts +1 -1
  217. package/src/render/mesh.ts +6 -0
  218. package/src/render/pass.ts +53 -1
  219. package/src/render/pointLightSelection.ts +4 -4
  220. package/src/render/reflectionSurfaceTargets.ts +105 -0
  221. package/src/render/renderQuality.ts +44 -8
  222. package/src/render/sceneTarget.ts +30 -2
  223. package/src/render/screenSpaceReflection.ts +7 -1
  224. package/src/render/shaders/boneAnimation.ts +63 -0
  225. package/src/render/shaders/depth.ts +45 -10
  226. package/src/render/shaders/filmLook.ts +2 -1
  227. package/src/render/shaders/flat/glassTint.ts +5 -1
  228. package/src/render/shaders/flat/index.ts +49 -5
  229. package/src/render/shaders/flat/main.ts +43 -5
  230. package/src/render/shaders/flat/models.ts +1 -1
  231. package/src/render/shaders/flat/overlay.ts +117 -0
  232. package/src/render/shaders/flat/pointShadow.ts +3 -3
  233. package/src/render/shaders/flat/preamble.ts +38 -24
  234. package/src/render/shaders/flat/reflectionSurface.ts +61 -0
  235. package/src/render/shaders/fog.ts +9 -2
  236. package/src/render/shaders/fringe.ts +59 -0
  237. package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
  238. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  239. package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
  240. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  241. package/src/render/shaders/generated/film.wgsl.ts +1 -1
  242. package/src/render/shaders/generated/fire.wgsl.ts +1 -1
  243. package/src/render/shaders/generated/flat.wgsl.ts +3292 -2957
  244. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  245. package/src/render/shaders/generated/particle.wgsl.ts +4 -4
  246. package/src/render/shaders/generated/rush.wgsl.ts +56 -26
  247. package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
  248. package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
  249. package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
  250. package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
  251. package/src/render/shaders/generated/water.wgsl.ts +1 -1
  252. package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
  253. package/src/render/shaders/outputTransform.ts +30 -14
  254. package/src/render/shaders/rush.ts +54 -11
  255. package/src/render/shaders/ssrResolve.ts +52 -0
  256. package/src/render/shaders/ssrTrace.ts +26 -0
  257. package/src/render/shaders/vertexChannel.ts +27 -0
  258. package/src/render/ssrPass.ts +138 -1
  259. package/src/render/staticDraws.ts +95 -0
  260. package/src/render/surfaceOverlay.ts +195 -0
  261. package/src/render/surfaceTexture.ts +161 -27
  262. package/src/render/temporalAa.ts +20 -0
  263. package/src/render/vertexDefaults.ts +28 -0
@@ -12,6 +12,7 @@ import type { LitSwitch, PipelineCache } from './pipelineCache.ts';
12
12
  import type { ClothViews } from './clothTextures.ts';
13
13
  import { shaderModule } from './shaderModules.ts';
14
14
  import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
15
+ import { REFLECTION_SURFACE_FORMAT } from './reflectionSurfacePass.ts';
15
16
 
16
17
  /** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
17
18
  const SURFACE_MAPS: ReadonlySet<string> = new Set([
@@ -98,6 +99,20 @@ export interface StageBindings {
98
99
  readonly textures: Readonly<Record<string, TextureBinding>>;
99
100
  /** Each pipeline-overridable constant's id, by the GLSL name it was declared under. */
100
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'];
101
116
  }
102
117
 
103
118
  /*
@@ -129,9 +144,16 @@ const VERTEX_BINDINGS = FLAT_BINDINGS.flatVert as unknown as Readonly<
129
144
  >;
130
145
 
131
146
  /** The key for a set of vertex flags: the same rule `variantKey` uses, flags on, sorted, joined. */
132
- 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 {
133
154
  const on: string[] = [];
134
155
  /* Alphabetical, because the generator's own `variantKey` sorts and the two keys must agree. */
156
+ if (animated) on.push('animated');
135
157
  if (instanced) on.push('instanced');
136
158
  if (morphed) on.push('morphed');
137
159
  if (skinned) on.push('skinned');
@@ -143,8 +165,9 @@ export function flatVertexBindings(
143
165
  skinned: boolean,
144
166
  morphed = false,
145
167
  instanced = false,
168
+ animated = false,
146
169
  ): StageBindings {
147
- const key = flatVertexKey(skinned, morphed, instanced);
170
+ const key = flatVertexKey(skinned, morphed, instanced, animated);
148
171
  const found = VERTEX_BINDINGS[key];
149
172
  if (found === undefined) throw new Error(`flatPass: no vertex bindings for variant "${key}"`);
150
173
  return found;
@@ -220,6 +243,8 @@ for (const [key, bindings] of Object.entries(VERTEX_BINDINGS)) {
220
243
  }
221
244
 
222
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;
223
248
  /**
224
249
  * The **largest** variant's block, because one ring serves every draw.
225
250
  *
@@ -232,6 +257,55 @@ export const FLAT_VERT_SIZE = Math.max(
232
257
  );
233
258
  const FLAT_VERT_BINDING = PLAIN_VERTEX.uniforms;
234
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
+
289
+ /**
290
+ * The most sampled textures and distinct samplers any lit variant carrying the environment probe
291
+ * binds in its fragment stage, counted from the generated bindings: what a device must allow per
292
+ * stage before the probe's permutation can be built. A sampler several textures share counts once,
293
+ * which is what `// wgsl:share` buys.
294
+ */
295
+ export function probeStageCeilings(): { readonly textures: number; readonly samplers: number } {
296
+ let textures = 0;
297
+ let samplers = 0;
298
+ for (const [variant, bindings] of Object.entries(FLAT_BINDINGS.flatFrag)) {
299
+ if (!variant.includes('environmentProbe')) continue;
300
+ const listed = Object.values(
301
+ (bindings as { textures?: Record<string, { sampler: number }> }).textures ?? {},
302
+ );
303
+ textures = Math.max(textures, listed.length);
304
+ samplers = Math.max(samplers, new Set(listed.map((entry) => entry.sampler)).size);
305
+ }
306
+ return { textures, samplers };
307
+ }
308
+
235
309
  /** Everything the generator recorded about one permutation of the fragment stage. */
236
310
  export function flatFragmentBindings(variant: FlatVariant): StageBindings {
237
311
  const found = FRAGMENT_BINDINGS[variant];
@@ -239,6 +313,46 @@ export function flatFragmentBindings(variant: FlatVariant): StageBindings {
239
313
  return found;
240
314
  }
241
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
+
242
356
  /**
243
357
  * The lit fragment stage's constants, set from the cache's switches, keyed by each one's **id**.
244
358
  *
@@ -253,6 +367,7 @@ function flatFragmentConstants(
253
367
  cache: PipelineCache,
254
368
  model: SurfaceModelKind | null,
255
369
  skinPass: SkinHalf = 'whole',
370
+ reflectionSurface = false,
256
371
  ): Record<string, number> {
257
372
  const overrides = flatFragmentBindings(variant).overrides;
258
373
  if (overrides?.['GLASS_SHADOWS'] === undefined) {
@@ -271,6 +386,11 @@ function flatFragmentConstants(
271
386
  constants[String(id)] = skinPass !== 'whole' && name === SKIN_HALF_SWITCH[skinPass] ? 1 : 0;
272
387
  continue;
273
388
  }
389
+ /* The reflection pass's surface half, set from the key too. See `reflectionSurface.ts`. */
390
+ if (name === 'REFLECTION_SURFACE') {
391
+ constants[String(id)] = reflectionSurface ? 1 : 0;
392
+ continue;
393
+ }
274
394
  if (!(name in cache.litSwitches)) {
275
395
  throw new Error(`flatPass: variant "${variant}" declares a switch "${name}" no cache sets`);
276
396
  }
@@ -279,6 +399,14 @@ function flatFragmentConstants(
279
399
  return constants;
280
400
  }
281
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
+
282
410
  /** The depth format both backends compare with. */
283
411
  /* Re-exported so the twenty passes that already import it from here do not each learn a new
284
412
  module; `depthConvention.ts` is where the decision lives and why. */
@@ -290,15 +418,19 @@ const VISIBILITY_FRAGMENT = 0x2;
290
418
  /**
291
419
  * The layout this variant binds against.
292
420
  *
293
- * The vertex block is **dynamic**, because every draw supplies its own model matrix and tint
294
- * from its own slot of one buffer. The fragment block is not: it holds the lights, the fog
295
- * 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.
296
426
  */
297
427
  export function createFlatBindGroupLayout(
298
428
  device: GPUDevice,
299
429
  variant: FlatVariant,
300
430
  skinned = false,
301
431
  morphed = false,
432
+ /** The instanced variant that plays a bone animation: its three vertex textures. */
433
+ animated = false,
302
434
  ): GPUBindGroupLayout {
303
435
  const fragment = flatFragmentBindings(variant);
304
436
  const entries: GPUBindGroupLayoutEntry[] = [
@@ -307,15 +439,31 @@ export function createFlatBindGroupLayout(
307
439
  visibility: VISIBILITY_VERTEX,
308
440
  buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: FLAT_VERT_SIZE },
309
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
+ },
310
448
  {
311
449
  binding: fragment.uniforms,
312
450
  visibility: VISIBILITY_FRAGMENT,
313
451
  /*
314
- * Dynamic, because this block carries the material and a material changes between draws.
315
- * 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`.
316
454
  */
317
455
  buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: fragment.uniformSize },
318
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
+ },
319
467
  ];
320
468
 
321
469
  /** What each sampler binding was declared as, so a shared one is declared once and agrees. */
@@ -462,12 +610,43 @@ export function createFlatBindGroupLayout(
462
610
  }
463
611
  }
464
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
+
465
624
  return device.createBindGroupLayout({
466
- label: `flat.layout:${variant}|${flatVertexKey(skinned, morphed)}`,
625
+ label: `flat.layout:${variant}|${flatVertexKey(skinned, morphed, animated, animated)}`,
467
626
  entries,
468
627
  });
469
628
  }
470
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
+
471
650
  /**
472
651
  * Bind the buffers and one view per declared texture.
473
652
  *
@@ -482,7 +661,9 @@ export function createFlatBindGroup(
482
661
  layout: GPUBindGroupLayout,
483
662
  variant: FlatVariant,
484
663
  perDraw: GPUBuffer,
485
- perFrame: GPUBuffer,
664
+ passes: GPUBuffer,
665
+ materials: GPUBuffer,
666
+ views: GPUBuffer,
486
667
  resolve: (name: string) => { view: GPUTextureView; sampler: GPUSampler },
487
668
  /** The palette view for a skinned bind group, or null for an unskinned one. */
488
669
  palette: GPUTextureView | null = null,
@@ -490,6 +671,8 @@ export function createFlatBindGroup(
490
671
  deltas: GPUTextureView | null = null,
491
672
  /** A skinned bind group's cloth views — the draw's, or the stand-in where it sets none. */
492
673
  cloth: ClothViews | null = null,
674
+ /** An animated instanced group's views. See `AnimationViews`. */
675
+ animation: AnimationViews | null = null,
493
676
  ): GPUBindGroup {
494
677
  const fragment = flatFragmentBindings(variant);
495
678
  const entries: GPUBindGroupEntry[] = [
@@ -497,7 +680,12 @@ export function createFlatBindGroup(
497
680
  binding: FLAT_VERT_BINDING,
498
681
  resource: { buffer: perDraw, size: FLAT_VERT_SIZE },
499
682
  },
500
- { 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
+ },
501
689
  ];
502
690
 
503
691
  /* The same dedupe the layout does, and for the same reason: the shadow bindings share one
@@ -536,8 +724,15 @@ export function createFlatBindGroup(
536
724
  entries.push({ binding: restAt, resource: cloth.rest });
537
725
  }
538
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
+
539
734
  return device.createBindGroup({
540
- label: `flat.bindGroup:${variant}|${flatVertexKey(palette !== null, deltas !== null)}`,
735
+ label: `flat.bindGroup:${variant}|${flatVertexKey(palette !== null, deltas !== null, animation !== null, animation !== null)}`,
541
736
  layout,
542
737
  entries,
543
738
  });
@@ -609,6 +804,13 @@ export function flatPipeline(
609
804
  model: SurfaceModelKind | null = null,
610
805
  /** Which half of a skin it draws. The key must carry it: `|ss`, `|sd` or `|sa`. See `SkinHalf`. */
611
806
  skinPass: SkinHalf = 'whole',
807
+ /**
808
+ * The reflection pass's surface half: the draw again, against its own depth, into the two
809
+ * targets `reflectionSurfacePass.ts` holds. The key must carry it: `|rs`.
810
+ */
811
+ reflectionSurface = false,
812
+ /** An instanced pipeline that plays a bone animation. The key must carry it: `|anim`. */
813
+ animated = false,
612
814
  ): GPURenderPipeline {
613
815
  return cache.get(
614
816
  key,
@@ -633,6 +835,8 @@ export function flatPipeline(
633
835
  cloth,
634
836
  model,
635
837
  skinPass,
838
+ reflectionSurface,
839
+ animated,
636
840
  ),
637
841
  true,
638
842
  );
@@ -765,10 +969,14 @@ function flatDescriptor(
765
969
  cloth = false,
766
970
  model: SurfaceModelKind | null = null,
767
971
  skinPass: SkinHalf = 'whole',
972
+ reflectionSurface = false,
973
+ /** An instanced pipeline that plays a bone animation. See `shaders/boneAnimation.ts`. */
974
+ animated = false,
768
975
  ): GPURenderPipelineDescriptor {
769
976
  const offset = depthOffsetForLayer(depthLayer);
770
- /* The diffuse and colour halves are the same surface again, into the blur's targets. */
771
- const diffuseAlone = skinPass === 'diffuse' || skinPass === 'albedo';
977
+ /* The diffuse and colour halves are the same surface again, into the blur's targets; and so is
978
+ the reflection pass's surface half, into its two. */
979
+ const diffuseAlone = skinPass === 'diffuse' || skinPass === 'albedo' || reflectionSurface;
772
980
  return {
773
981
  label: key,
774
982
  layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
@@ -779,13 +987,13 @@ function flatDescriptor(
779
987
  * layout validates, draws, and is wrong, exactly as this file's header says.
780
988
  */
781
989
  module: shaderModule(device, {
782
- label: `flat.vert:${flatVertexKey(skinned, morphed, instanced)}`,
783
- code: FLAT_VERT_WGSL[flatVertexKey(skinned, morphed, instanced)] ?? '',
990
+ label: `flat.vert:${flatVertexKey(skinned, morphed, instanced, animated)}`,
991
+ code: FLAT_VERT_WGSL[flatVertexKey(skinned, morphed, instanced, animated)] ?? '',
784
992
  }),
785
993
  entryPoint: 'main',
786
994
  buffers: vertexBufferLayouts(present, instanced),
787
995
  constants: vertexConstants(
788
- flatVertexBindings(skinned, morphed, instanced),
996
+ flatVertexBindings(skinned, morphed, instanced, animated),
789
997
  skinEight,
790
998
  cloth,
791
999
  model === 'lightmap',
@@ -798,31 +1006,33 @@ function flatDescriptor(
798
1006
  }),
799
1007
  entryPoint: 'main',
800
1008
  /* Every variant declares it, so every lit pipeline may set it: see `PipelineCache`. */
801
- constants: flatFragmentConstants(variant, cache, model, skinPass),
802
- targets: [
803
- oitTarget(oit) ??
804
- (translucent === 'additive'
805
- ? {
806
- format: cache.format,
807
- blend: {
808
- color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
809
- alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
810
- },
811
- }
812
- : translucent
813
- ? {
814
- format: cache.format,
815
- blend: {
816
- color: {
817
- srcFactor: 'src-alpha',
818
- dstFactor: 'one-minus-src-alpha',
819
- operation: 'add',
1009
+ constants: flatFragmentConstants(variant, cache, model, skinPass, reflectionSurface),
1010
+ targets: reflectionSurface
1011
+ ? [null, { format: REFLECTION_SURFACE_FORMAT }, { format: REFLECTION_SURFACE_FORMAT }]
1012
+ : [
1013
+ oitTarget(oit) ??
1014
+ (translucent === 'additive'
1015
+ ? {
1016
+ format: cache.format,
1017
+ blend: {
1018
+ color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
1019
+ alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
820
1020
  },
821
- alpha: SCENE_ALPHA_COVERS,
822
- },
823
- }
824
- : { format: cache.format }),
825
- ],
1021
+ }
1022
+ : translucent
1023
+ ? {
1024
+ format: cache.format,
1025
+ blend: {
1026
+ color: {
1027
+ srcFactor: 'src-alpha',
1028
+ dstFactor: 'one-minus-src-alpha',
1029
+ operation: 'add',
1030
+ },
1031
+ alpha: SCENE_ALPHA_COVERS,
1032
+ },
1033
+ }
1034
+ : { format: cache.format }),
1035
+ ],
826
1036
  },
827
1037
  primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
828
1038
  /* Coverage only where a sample count can carry it: WebGPU refuses it at one sample. */
@@ -4,6 +4,8 @@ import { destroySlot } from './instanceCullPass.ts';
4
4
  import type { CullSlot } from './instanceCullPass.ts';
5
5
  import type { MeshInstances } from '../../instances.ts';
6
6
  import type { GpuMesh } from './buffers.ts';
7
+ import { packInstanceClocks } from '../../boneAnimation.ts';
8
+ import { GpuInstanceClocks, type GpuBoneAnimation } from './boneAnimations.ts';
7
9
 
8
10
  /** VERTEX | COPY_DST, spelled the way every other pass here spells it. */
9
11
  const USAGE_VERTEX = 0x0020 | 0x0008;
@@ -36,10 +38,34 @@ export class GpuInstancedBatch {
36
38
  slotFrame = -1;
37
39
  slotsTaken = 0;
38
40
 
39
- constructor(device: GPUDevice, mesh: GpuMesh, capacity: number, label: string, cull = false) {
41
+ /** The bone animation every instance plays, and its clocks; null for a batch that plays none. */
42
+ readonly animation: GpuBoneAnimation | null;
43
+ readonly clocks: GpuInstanceClocks | null;
44
+ /**
45
+ * An animated batch's own groups — its lit twins by the material group they were built beside, its
46
+ * shadow groups by what they cut with — and the renderer's group epoch they were built in, after
47
+ * which every one is dropped. See `WebGPURenderer.animatedGroup`.
48
+ */
49
+ readonly litGroups = new Map<GPUBindGroup, GPUBindGroup>();
50
+ readonly depthGroups = new Map<unknown, GPUBindGroup>();
51
+ readonly depthPeelGroups = new Map<unknown, GPUBindGroup>();
52
+ groupEpoch = -1;
53
+
54
+ constructor(
55
+ device: GPUDevice,
56
+ mesh: GpuMesh,
57
+ capacity: number,
58
+ label: string,
59
+ cull = false,
60
+ animation: GpuBoneAnimation | null = null,
61
+ ) {
40
62
  this.mesh = mesh;
41
63
  this.capacity = capacity;
64
+ /* An animated batch is culled whole and never by instance: see `submitInstanced`. */
42
65
  this.cull = cull;
66
+ this.animation = animation;
67
+ this.clocks =
68
+ animation === null ? null : new GpuInstanceClocks(device, capacity, `${label}.clocks`);
43
69
  this.staging = new Float32Array(capacity * INSTANCE_FLOATS);
44
70
  this.buffer = device.createBuffer({
45
71
  /*
@@ -83,11 +109,16 @@ export class GpuInstancedBatch {
83
109
  if (count === 0) return;
84
110
  packInstances(data, this.staging);
85
111
  queue.writeBuffer(this.buffer, 0, this.staging, 0, count * INSTANCE_FLOATS);
112
+ if (this.clocks !== null) {
113
+ packInstanceClocks(data, this.clocks.staging);
114
+ this.clocks.upload(queue, count);
115
+ }
86
116
  }
87
117
 
88
118
  dispose(): void {
89
119
  this.previousBuffer?.destroy();
90
120
  this.buffer.destroy();
121
+ this.clocks?.dispose();
91
122
  for (const slot of this.slots) destroySlot(slot);
92
123
  }
93
124
  }