@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
@@ -22,16 +22,18 @@ export class SkinPaletteTexture {
22
22
  /**
23
23
  * Upload a palette, reallocating only when the joint count changes.
24
24
  *
25
- * A rig's joint count is fixed for its life, so the reallocation path runs once per skeleton and
26
- * the steady state is one `texSubImage2D`. Reallocating per frame would be a GPU allocation in
27
- * the frame loop, which is the same class of mistake as allocating on the CPU there.
25
+ * **The texture grows and never shrinks**, so the steady state is one `texSubImage2D` whatever
26
+ * mix of rigs a frame draws. It was rebuilt whenever the joint count changed, which with one
27
+ * texture for every skinned draw meant a character and a prop drawn in turn reallocated it at
28
+ * every draw: a GPU allocation in the frame loop. The shader reads joints by index, so a texture
29
+ * wider than the rig is read exactly as one that fits.
28
30
  */
29
31
  update(gl: WebGL2RenderingContext, palette: Float32Array): void {
30
32
  validateSkinPalette(palette);
31
33
  const joints = palette.length / 16;
32
34
  const width = paletteTextureWidth(joints);
33
35
 
34
- if (this.texture === null || this.joints !== joints) {
36
+ if (this.texture === null || joints > this.joints) {
35
37
  if (this.texture !== null) gl.deleteTexture(this.texture);
36
38
  const created = gl.createTexture();
37
39
  if (created === null) throw new Error('skin palette: failed to create the palette texture');
@@ -0,0 +1,97 @@
1
+ /**
2
+ * A bone animation's textures on this backend, and a batch's clocks: what
3
+ * `shaders/boneAnimation.ts` reads by index, made once and never filtered.
4
+ *
5
+ * **`rgba32float` places and turns, and `rg32float` clocks**, all read with `textureLoad` and bound
6
+ * `unfilterable-float`, as the joint palette is: a turn in half floats moves a crowd's hands by a
7
+ * thousandth of their reach, a jitter at the frame's rate, and the bytes are a clip's, once.
8
+ */
9
+ import {
10
+ CLOCK_TEXTURE_WIDTH,
11
+ type BoneAnimationHandle,
12
+ type BoneAnimationTexels,
13
+ } from '../../boneAnimation.ts';
14
+
15
+ const USAGE_TEXTURE = 0x4 | 0x2; // TEXTURE_BINDING | COPY_DST
16
+
17
+ /** A clip on the device, from `createBoneAnimation`. */
18
+ export class GpuBoneAnimation implements BoneAnimationHandle {
19
+ readonly bones: number;
20
+ readonly frames: number;
21
+ readonly framesPerSecond: number;
22
+ readonly boneScale: number;
23
+ readonly placesView: GPUTextureView;
24
+ readonly turnsView: GPUTextureView;
25
+ disposed = false;
26
+ private readonly places: GPUTexture;
27
+ private readonly turns: GPUTexture;
28
+
29
+ constructor(device: GPUDevice, texels: BoneAnimationTexels) {
30
+ this.bones = texels.bones;
31
+ this.frames = texels.frames;
32
+ this.framesPerSecond = texels.framesPerSecond;
33
+ this.boneScale = texels.boneScale;
34
+ const make = (label: string, data: Float32Array): GPUTexture => {
35
+ const texture = device.createTexture({
36
+ label,
37
+ size: [texels.bones, texels.frames],
38
+ format: 'rgba32float',
39
+ usage: USAGE_TEXTURE,
40
+ });
41
+ device.queue.writeTexture(
42
+ { texture },
43
+ data,
44
+ { bytesPerRow: texels.bones * 16, rowsPerImage: texels.frames },
45
+ [texels.bones, texels.frames],
46
+ );
47
+ return texture;
48
+ };
49
+ this.places = make('bones.places', texels.places);
50
+ this.turns = make('bones.turns', texels.turns);
51
+ this.placesView = this.places.createView();
52
+ this.turnsView = this.turns.createView();
53
+ }
54
+
55
+ dispose(): void {
56
+ this.disposed = true;
57
+ this.places.destroy();
58
+ this.turns.destroy();
59
+ }
60
+ }
61
+
62
+ /** A batch's clocks: two floats an instance, wrapped onto rows `CLOCK_TEXTURE_WIDTH` wide. */
63
+ export class GpuInstanceClocks {
64
+ readonly view: GPUTextureView;
65
+ readonly staging: Float32Array;
66
+ private readonly texture: GPUTexture;
67
+ private readonly width: number;
68
+
69
+ constructor(device: GPUDevice, capacity: number, label: string) {
70
+ this.width = Math.max(1, Math.min(capacity, CLOCK_TEXTURE_WIDTH));
71
+ const rows = Math.max(1, Math.ceil(capacity / this.width));
72
+ this.staging = new Float32Array(this.width * rows * 2);
73
+ this.texture = device.createTexture({
74
+ label,
75
+ size: [this.width, rows],
76
+ format: 'rg32float',
77
+ usage: USAGE_TEXTURE,
78
+ });
79
+ this.view = this.texture.createView();
80
+ }
81
+
82
+ /** The rows that hold the first `count` instances, from `staging`. */
83
+ upload(queue: GPUQueue, count: number): void {
84
+ if (count <= 0) return;
85
+ const rows = Math.ceil(count / this.width);
86
+ queue.writeTexture(
87
+ { texture: this.texture },
88
+ this.staging,
89
+ { bytesPerRow: this.width * 8, rowsPerImage: rows },
90
+ [this.width, rows],
91
+ );
92
+ }
93
+
94
+ dispose(): void {
95
+ this.texture.destroy();
96
+ }
97
+ }
@@ -6,6 +6,16 @@ import type { MeshData } from '../../mesh.ts';
6
6
  import { ABSENT_ATTRIBUTE } from '../../vertexDefaults.ts';
7
7
  import { UPLOAD_BYTES_PER_STEP } from '../../uploadStep.ts';
8
8
  import { INSTANCE_STRIDE } from '../../instances.ts';
9
+ import { compactIndices } from '../../indexWidth.ts';
10
+ import {
11
+ SNORM16_ONE,
12
+ UNORM16_ONE,
13
+ packingFor,
14
+ sharedValue,
15
+ vertexPackingOf,
16
+ type AttributePacking,
17
+ } from '../../vertexPacking.ts';
18
+ import { formatBytes, interleaveFixed, packedFormat, packingIn } from './vertexFormats.ts';
9
19
 
10
20
  /**
11
21
  * Mesh geometry as WebGPU buffers.
@@ -172,8 +182,13 @@ export interface GpuMesh {
172
182
  /** The second four influences' offsets, or -1 for a mesh with four. */
173
183
  readonly joints2: number;
174
184
  readonly weights2: number;
185
+ /** How the weights are read: floats, or sixteen-bit fixed point (`vertexPacking.ts`). */
186
+ readonly weightsFormat: GPUVertexFormat;
187
+ readonly weights2Format: GPUVertexFormat;
175
188
  } | null;
176
189
  readonly indexBuffer: GPUBuffer;
190
+ /** Sixteen or thirty-two bits an index: every pass that binds `indexBuffer` says which. */
191
+ readonly indexFormat: GPUIndexFormat;
177
192
  /**
178
193
  * How many indices the mesh draws. **Zero is a mesh with nothing to draw, and no pass draws it**:
179
194
  * a level can hold none of some kind of geometry, and a draw of zero indices does nothing but
@@ -258,18 +273,37 @@ export interface GpuMesh {
258
273
  device: GPUDevice,
259
274
  positions: Float32Array,
260
275
  normals?: Float32Array,
261
- previous?: GPUBuffer | null,
276
+ staged?: StagedRewrite | null,
262
277
  ) => void)
263
278
  | null;
264
279
  /**
265
- * A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's positions,
266
- * made the first time a reconstructing renderer rewrites the mesh, and the frame of the last
267
- * rewrite. Null on a mesh created without `{ dynamic: true }`, which never moves a vertex.
280
+ * A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's positions and
281
+ * one the new positions are staged in, each made the first time a reconstructing renderer
282
+ * rewrites the mesh, and the frame of the last rewrite. Null on a mesh created without
283
+ * `{ dynamic: true }`, which never moves a vertex.
268
284
  */
269
- readonly motion: { previous: GPUBuffer | null; changed: number } | null;
285
+ readonly motion: {
286
+ previous: GPUBuffer | null;
287
+ staging: GPUBuffer | null;
288
+ changed: number;
289
+ } | null;
270
290
  dispose(): void;
271
291
  }
272
292
 
293
+ /**
294
+ * A rewrite a reconstructing renderer stages rather than writes, so last frame's positions can be
295
+ * kept with no submit of the rewrite's own: the new positions are written into `staging`, and two
296
+ * copies are recorded into `encoder` — the live positions into `previous` first, where the frame's
297
+ * first rewrite keeps them, then the staged ones in. The renderer submits that encoder at the head
298
+ * of the frame's next submit, which is what keeps both copies ahead of every draw that reads the
299
+ * mesh. A `writeBuffer` straight to the live positions would land before the copy aside could run.
300
+ */
301
+ export interface StagedRewrite {
302
+ encoder: GPUCommandEncoder;
303
+ staging: GPUBuffer;
304
+ previous: GPUBuffer | null;
305
+ }
306
+
273
307
  /** A mesh on its way to the device, and the iterator that gets it there. */
274
308
  export interface IncrementalGpuMesh {
275
309
  /**
@@ -338,17 +372,31 @@ export function createGpuMeshIncremental(
338
372
  const bounds = boundsOfPositions(data.positions, createBounds());
339
373
  const vertexCount = data.positions.length / 3;
340
374
  const layered = data.layers !== undefined;
341
- const supplied = VERTEX_LAYOUT.filter(
342
- (attribute) =>
343
- (!attribute.optional || data[attribute.name as keyof MeshData] !== undefined) &&
344
- !(dynamic && isStreamed(attribute)),
345
- ).map((attribute) => laidOut(attribute, layered));
375
+ /* How each attribute travels, which the pipeline key says too: `vertexPacking.ts`. */
376
+ const packing = vertexPackingOf(data, dynamic);
346
377
 
347
378
  /* Interleaved: one stride holding every attribute the mesh actually supplied — less a dynamic
348
- mesh's positions and normals, which are buffers of their own. */
349
- const stride = supplied.reduce((sum, attribute) => sum + attribute.components * 4, 0);
350
- const step = stride / 4;
351
- const interleaved = new Float32Array(step * vertexCount);
379
+ mesh's positions and normals, which are buffers of their own, and any attribute every vertex
380
+ shares, which is a constant. */
381
+ const fields: Field[] = [];
382
+ let stride = 0;
383
+ for (const declared of VERTEX_LAYOUT) {
384
+ if (declared.optional && data[declared.name as keyof MeshData] === undefined) continue;
385
+ if (dynamic && isStreamed(declared)) continue;
386
+ const attribute = laidOut(declared, layered);
387
+ const how = packingFor(packing, attribute.name);
388
+ if (how === 'constant') continue;
389
+ const format = packedFormat(attribute.format, how);
390
+ fields.push({ attribute, packing: how, format, offset: stride });
391
+ stride += formatBytes(format);
392
+ }
393
+ const buffer = new ArrayBuffer(stride * vertexCount);
394
+ const rows: Rows = {
395
+ floats: new Float32Array(buffer),
396
+ signed: new Int16Array(buffer),
397
+ unsigned: new Uint16Array(buffer),
398
+ bytes: new Uint8Array(buffer),
399
+ };
352
400
 
353
401
  /*
354
402
  * **Refused here, in one sentence, rather than by the driver a hundred times.**
@@ -364,7 +412,7 @@ export function createGpuMeshIncremental(
364
412
  * whose adapter caps at the default, and it deserves a sentence naming the model's size and
365
413
  * the device's rather than a wall of validation.
366
414
  */
367
- const byteLength = align4(interleaved.byteLength);
415
+ const byteLength = align4(buffer.byteLength);
368
416
  const ceiling = device.limits.maxBufferSize;
369
417
  if (typeof ceiling === 'number' && byteLength > ceiling) {
370
418
  throw new Error(
@@ -401,11 +449,20 @@ export function createGpuMeshIncremental(
401
449
  if (value === undefined) continue;
402
450
  absent.set(value, slot * (CONSTANT_SLOT / 4));
403
451
  }
452
+ /* And what every vertex shares, where a mesh's attribute is one value: `vertexPacking.ts`. */
453
+ for (let slot = 0; slot < VERTEX_LAYOUT.length; slot++) {
454
+ const attribute = VERTEX_LAYOUT[slot] as VertexAttribute;
455
+ if (packingFor(packing, attribute.name) !== 'constant') continue;
456
+ const values = data[attribute.name as keyof MeshData] as Float32Array;
457
+ absent.set(sharedValue(values, attribute.components), slot * (CONSTANT_SLOT / 4));
458
+ }
404
459
  device.queue.writeBuffer(constants, 0, absent);
405
460
 
461
+ /* Sixteen bits where every vertex fits, which every draw of this mesh says: indexWidth.ts. */
462
+ const indices = compactIndices(data.indices, vertexCount);
406
463
  const indexBuffer = device.createBuffer({
407
464
  label: 'mesh.indices',
408
- size: align4(data.indices.byteLength),
465
+ size: align4(indices.byteLength),
409
466
  usage: USAGE.INDEX | USAGE.COPY_DST,
410
467
  });
411
468
 
@@ -434,11 +491,12 @@ export function createGpuMeshIncremental(
434
491
  1,
435
492
  Math.floor(UPLOAD_BYTES_PER_STEP / (stride + (dynamic ? 24 : 0))),
436
493
  );
437
- const indexWidth = data.indices.BYTES_PER_ELEMENT;
438
- const indicesPerStep = Math.max(1, Math.floor(UPLOAD_BYTES_PER_STEP / indexWidth));
494
+ /* Whole four-byte words a step, which is all a queue writes: two sixteen-bit indices to one. */
495
+ const indexWidth = indices.BYTES_PER_ELEMENT;
496
+ const indicesPerStep = Math.max(1, Math.floor(UPLOAD_BYTES_PER_STEP / 4)) * (4 / indexWidth);
439
497
  const totalSteps =
440
498
  Math.max(1, Math.ceil(vertexCount / verticesPerStep)) +
441
- Math.max(1, Math.ceil(data.indices.length / indicesPerStep));
499
+ Math.max(1, Math.ceil(indices.length / indicesPerStep));
442
500
 
443
501
  /*
444
502
  * **The mesh is assembled out here, by functions of its own, and that is the fix for a copy
@@ -455,6 +513,7 @@ export function createGpuMeshIncremental(
455
513
  constants,
456
514
  streams,
457
515
  indexBuffer,
516
+ indexFormat: indices instanceof Uint16Array ? 'uint16' : 'uint32',
458
517
  stride: streams === null ? stride : 12,
459
518
  indexCount: data.indices.length,
460
519
  bounds,
@@ -464,7 +523,7 @@ export function createGpuMeshIncremental(
464
523
  isSkinnedEight: data.joints !== undefined && data.joints2 !== undefined,
465
524
  vertexCount,
466
525
  hasUvs: data.uvs !== undefined,
467
- skinOffsets: streams === null ? skinOffsetsOf(supplied) : null,
526
+ skinOffsets: streams === null ? skinOffsetsOf(fields) : null,
468
527
  hasChannel: data.channel !== undefined,
469
528
  morph:
470
529
  data.morphTargets === undefined || data.morphTargetCount === undefined
@@ -483,14 +542,14 @@ export function createGpuMeshIncremental(
483
542
 
484
543
  const job: UploadJob = {
485
544
  data,
486
- interleaved,
545
+ indices,
546
+ rows,
487
547
  device,
488
- supplied,
548
+ fields,
489
549
  streams,
490
550
  vertices,
491
551
  indexBuffer,
492
552
  vertexCount,
493
- step,
494
553
  stride,
495
554
  verticesPerStep,
496
555
  indicesPerStep,
@@ -500,20 +559,38 @@ export function createGpuMeshIncremental(
500
559
  return { mesh, upload: uploadSteps(job) };
501
560
  }
502
561
 
562
+ /** One attribute as a mesh interleaves it: how it travels, the format it is read with, where. */
563
+ interface Field {
564
+ readonly attribute: VertexAttribute;
565
+ readonly packing: AttributePacking;
566
+ readonly format: GPUVertexFormat;
567
+ /** Bytes into the vertex. */
568
+ readonly offset: number;
569
+ }
570
+
571
+ /** The interleaved rows, viewed as each field's type writes them. */
572
+ interface Rows {
573
+ readonly floats: Float32Array;
574
+ readonly signed: Int16Array;
575
+ readonly unsigned: Uint16Array;
576
+ readonly bytes: Uint8Array;
577
+ }
578
+
503
579
  /**
504
580
  * What an upload reads, held by the upload alone and let go when it finishes — so a caller that
505
581
  * keeps its iterator, as a streamer keeps the handle it was given, keeps nothing through it.
506
582
  */
507
583
  interface UploadJob {
508
584
  data: MeshData | null;
509
- interleaved: Float32Array | null;
585
+ /** The mesh's indices at the width its buffer holds them: `compactIndices`. */
586
+ indices: Uint16Array | Uint32Array | null;
587
+ rows: Rows | null;
510
588
  readonly device: GPUDevice;
511
- readonly supplied: readonly VertexAttribute[];
589
+ readonly fields: readonly Field[];
512
590
  readonly streams: { readonly positions: GPUBuffer; readonly normals: GPUBuffer } | null;
513
591
  readonly vertices: GPUBuffer;
514
592
  readonly indexBuffer: GPUBuffer;
515
593
  readonly vertexCount: number;
516
- readonly step: number;
517
594
  readonly stride: number;
518
595
  readonly verticesPerStep: number;
519
596
  readonly indicesPerStep: number;
@@ -522,33 +599,64 @@ interface UploadJob {
522
599
  }
523
600
 
524
601
  function* uploadSteps(job: UploadJob): Generator<void, void, void> {
525
- const { device, supplied, vertices, indexBuffer, vertexCount, step, stride } = job;
602
+ const { device, fields, vertices, indexBuffer, vertexCount, stride } = job;
603
+ /* Floats and halves a vertex: every field is whole words, so both divide. */
604
+ const step = stride / 4;
605
+ const halves = stride / 2;
526
606
  let taken = 0;
527
607
  for (let from = 0; from < vertexCount; from += job.verticesPerStep) {
528
608
  const data = job.data as MeshData;
529
- const interleaved = job.interleaved as Float32Array;
609
+ const rows = job.rows as Rows;
530
610
  const to = Math.min(from + job.verticesPerStep, vertexCount);
531
- let fieldOffset = 0;
532
- for (const attribute of supplied) {
611
+ for (const field of fields) {
612
+ const attribute = field.attribute;
533
613
  const source = data[attribute.name as keyof MeshData] as Float32Array;
534
- if (attribute === LAYERED_UVS) {
614
+ if (field.packing === 'snorm16') {
615
+ interleaveFixed(
616
+ rows.signed,
617
+ source,
618
+ attribute.components,
619
+ halves,
620
+ field.offset / 2,
621
+ from,
622
+ to,
623
+ SNORM16_ONE,
624
+ );
625
+ } else if (field.packing === 'unorm16') {
626
+ interleaveFixed(
627
+ rows.unsigned,
628
+ source,
629
+ attribute.components,
630
+ halves,
631
+ field.offset / 2,
632
+ from,
633
+ to,
634
+ UNORM16_ONE,
635
+ );
636
+ } else if (attribute === LAYERED_UVS) {
535
637
  /* (u, v) from the coordinates and the layer after them, one field of three. */
536
- interleaveField(interleaved, source, 2, step, fieldOffset, from, to);
638
+ const at = field.offset / 4;
639
+ interleaveField(rows.floats, source, 2, step, at, from, to);
640
+ interleaveField(rows.floats, data.layers as Float32Array, 1, step, at + 2, from, to);
641
+ } else {
537
642
  interleaveField(
538
- interleaved,
539
- data.layers as Float32Array,
540
- 1,
643
+ rows.floats,
644
+ source,
645
+ attribute.components,
541
646
  step,
542
- fieldOffset + 2,
647
+ field.offset / 4,
543
648
  from,
544
649
  to,
545
650
  );
546
- } else {
547
- interleaveField(interleaved, source, attribute.components, step, fieldOffset, from, to);
548
651
  }
549
- fieldOffset += attribute.components;
550
652
  }
551
- device.queue.writeBuffer(vertices, from * stride, interleaved, from * step, (to - from) * step);
653
+ device.queue.writeBuffer(
654
+ vertices,
655
+ from * stride,
656
+ rows.bytes,
657
+ from * stride,
658
+ (to - from) * stride,
659
+ );
552
660
  if (job.streams !== null) {
553
661
  const count = (to - from) * 3;
554
662
  device.queue.writeBuffer(job.streams.positions, from * 12, data.positions, from * 3, count);
@@ -558,7 +666,7 @@ function* uploadSteps(job: UploadJob): Generator<void, void, void> {
558
666
  if (taken < job.totalSteps) yield;
559
667
  }
560
668
 
561
- const indices = (job.data as MeshData).indices;
669
+ const indices = job.indices as Uint16Array | Uint32Array;
562
670
  const indexWidth = indices.BYTES_PER_ELEMENT;
563
671
  for (let from = 0; from < indices.length; from += job.indicesPerStep) {
564
672
  const to = Math.min(from + job.indicesPerStep, indices.length);
@@ -568,7 +676,8 @@ function* uploadSteps(job: UploadJob): Generator<void, void, void> {
568
676
  }
569
677
 
570
678
  job.data = null;
571
- job.interleaved = null;
679
+ job.indices = null;
680
+ job.rows = null;
572
681
  job.progress.uploaded = true;
573
682
  }
574
683
 
@@ -619,23 +728,30 @@ function interleaveField(
619
728
  * Where the joints and weights sit in a vertex of `supplied`, the attributes a mesh interleaves in
620
729
  * layout order; null unless it supplied both.
621
730
  */
622
- function skinOffsetsOf(supplied: readonly VertexAttribute[]): GpuMesh['skinOffsets'] {
623
- let floats = 0;
731
+ function skinOffsetsOf(fields: readonly Field[]): GpuMesh['skinOffsets'] {
624
732
  let joints = -1;
625
733
  let weights = -1;
626
734
  let joints2 = -1;
627
735
  let weights2 = -1;
628
- for (const attribute of supplied) {
629
- if (attribute.name === 'joints') joints = floats * 4;
630
- if (attribute.name === 'weights') weights = floats * 4;
631
- if (attribute.name === 'joints2') joints2 = floats * 4;
632
- if (attribute.name === 'weights2') weights2 = floats * 4;
633
- floats += attribute.components;
736
+ let weightsFormat: GPUVertexFormat = 'float32x4';
737
+ let weights2Format: GPUVertexFormat = 'float32x4';
738
+ for (const field of fields) {
739
+ const name = field.attribute.name;
740
+ if (name === 'joints') joints = field.offset;
741
+ if (name === 'joints2') joints2 = field.offset;
742
+ if (name === 'weights') {
743
+ weights = field.offset;
744
+ weightsFormat = field.format;
745
+ }
746
+ if (name === 'weights2') {
747
+ weights2 = field.offset;
748
+ weights2Format = field.format;
749
+ }
634
750
  }
635
751
  if (joints < 0 || weights < 0) return null;
636
752
  return joints2 >= 0 && weights2 >= 0
637
- ? { joints, weights, joints2, weights2 }
638
- : { joints, weights, joints2: -1, weights2: -1 };
753
+ ? { joints, weights, joints2, weights2, weightsFormat, weights2Format }
754
+ : { joints, weights, joints2: -1, weights2: -1, weightsFormat, weights2Format };
639
755
  }
640
756
 
641
757
  /** The handle, made where it can see only what it hands out. */
@@ -644,6 +760,7 @@ function gpuMeshOf(parts: {
644
760
  readonly constants: GPUBuffer;
645
761
  readonly streams: { readonly positions: GPUBuffer; readonly normals: GPUBuffer } | null;
646
762
  readonly indexBuffer: GPUBuffer;
763
+ readonly indexFormat: GPUIndexFormat;
647
764
  readonly stride: number;
648
765
  readonly indexCount: number;
649
766
  readonly bounds: Bounds;
@@ -658,7 +775,10 @@ function gpuMeshOf(parts: {
658
775
  readonly morph: MorphTexture | null;
659
776
  readonly update: GpuMesh['update'];
660
777
  }): GpuMesh {
661
- const motion = parts.update === null ? null : { previous: null as GPUBuffer | null, changed: -1 };
778
+ const motion =
779
+ parts.update === null
780
+ ? null
781
+ : { previous: null as GPUBuffer | null, staging: null as GPUBuffer | null, changed: -1 };
662
782
  const { vertices, constants, streams, indexBuffer, progress } = parts;
663
783
  return {
664
784
  vertexBuffers:
@@ -668,6 +788,7 @@ function gpuMeshOf(parts: {
668
788
  vertexStride: parts.stride,
669
789
  skinOffsets: parts.skinOffsets,
670
790
  indexBuffer,
791
+ indexFormat: parts.indexFormat,
671
792
  indexCount: parts.indexCount,
672
793
  bounds: parts.bounds,
673
794
  get complete(): boolean {
@@ -684,6 +805,7 @@ function gpuMeshOf(parts: {
684
805
  motion,
685
806
  dispose(): void {
686
807
  motion?.previous?.destroy();
808
+ motion?.staging?.destroy();
687
809
  streams?.positions.destroy();
688
810
  streams?.normals.destroy();
689
811
  vertices.destroy();
@@ -700,9 +822,9 @@ function isStreamed(attribute: VertexAttribute): boolean {
700
822
 
701
823
  /**
702
824
  * A dynamic mesh's `update`: the caller's positions, and its normals where it has them, each
703
- * written to its own buffer as it came. Last frame's positions, where a reconstruction wants them,
704
- * are copied on the device before the write — a copy submitted ahead of a `writeBuffer` runs ahead
705
- * of it on the queue — so no CPU copy of anything is kept.
825
+ * written to its own buffer as it came — or, where a reconstruction keeps last frame's positions,
826
+ * the positions staged and copied in on the device behind the copy aside (`StagedRewrite`), so no
827
+ * CPU copy of anything is kept and the rewrite submits nothing of its own.
706
828
  */
707
829
  function streamUpdate(
708
830
  positionsBuffer: GPUBuffer,
@@ -715,7 +837,7 @@ function streamUpdate(
715
837
  target: GPUDevice,
716
838
  positions: Float32Array,
717
839
  normals?: Float32Array,
718
- previous: GPUBuffer | null = null,
840
+ staged: StagedRewrite | null = null,
719
841
  ): void => {
720
842
  for (const [name, values] of [
721
843
  ['update', positions],
@@ -728,15 +850,18 @@ function streamUpdate(
728
850
  'every other attribute are sized against it.',
729
851
  );
730
852
  }
731
- if (previous !== null) {
732
- const encoder = target.createCommandEncoder({ label: 'mesh.previousPositions' });
733
- encoder.copyBufferToBuffer(positionsBuffer, 0, previous, 0, streamBytes);
734
- target.queue.submit([encoder.finish()]);
735
- }
736
853
  /* Everything that decides whether this is on screen starts from the bounds, so a cloth
737
854
  that blew sideways out of its original box would be culled while still visible. */
738
855
  boundsOfPositions(positions, bounds);
739
- target.queue.writeBuffer(positionsBuffer, 0, positions);
856
+ if (staged === null) {
857
+ target.queue.writeBuffer(positionsBuffer, 0, positions);
858
+ } else {
859
+ target.queue.writeBuffer(staged.staging, 0, positions);
860
+ if (staged.previous !== null) {
861
+ staged.encoder.copyBufferToBuffer(positionsBuffer, 0, staged.previous, 0, streamBytes);
862
+ }
863
+ staged.encoder.copyBufferToBuffer(staged.staging, 0, positionsBuffer, 0, streamBytes);
864
+ }
740
865
  if (normals !== undefined) target.queue.writeBuffer(normalsBuffer, 0, normals);
741
866
  };
742
867
  }
@@ -818,7 +943,10 @@ export function vertexBufferLayouts(
818
943
  * in the interleaved one. A *skinned* mesh interleaves them, so removing them would shift
819
944
  * the stride under every other attribute — which is why `createInstanced` refuses one.
820
945
  */
821
- const supplied = !attribute.optional || present[String(attribute.name)] === true;
946
+ /* One value every vertex shares sits with the absent ones: `vertexPacking.ts`. */
947
+ const packing = packingIn(present, String(attribute.name));
948
+ const supplied =
949
+ (!attribute.optional || present[String(attribute.name)] === true) && packing !== 'constant';
822
950
  if (instanced && INSTANCE_LOCATIONS.has(attribute.shaderLocation)) {
823
951
  /*
824
952
  * **Free to drop only because the mesh does not supply it.** An absent optional attribute
@@ -840,12 +968,9 @@ export function vertexBufferLayouts(
840
968
  return;
841
969
  }
842
970
  if (supplied) {
843
- interleaved.push({
844
- shaderLocation: attribute.shaderLocation,
845
- offset,
846
- format: attribute.format,
847
- });
848
- offset += attribute.components * 4;
971
+ const format = packedFormat(attribute.format, packing);
972
+ interleaved.push({ shaderLocation: attribute.shaderLocation, offset, format });
973
+ offset += formatBytes(format);
849
974
  return;
850
975
  }
851
976
  constants.push({