@driftengine/core 4.9.0 → 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 (135) hide show
  1. package/CHANGELOG.json +62 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +4 -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/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 +90 -2
  16. package/dist/render/backend/webgl2/renderer.js +253 -24
  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 +19 -4
  22. package/dist/render/backend/webgpu/buffers.js +18 -11
  23. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  24. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  25. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  26. package/dist/render/backend/webgpu/depthPass.js +49 -14
  27. package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
  28. package/dist/render/backend/webgpu/drawCommand.js +10 -3
  29. package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
  30. package/dist/render/backend/webgpu/flatPass.js +136 -20
  31. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  32. package/dist/render/backend/webgpu/instanced.js +24 -1
  33. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  34. package/dist/render/backend/webgpu/materialStore.js +265 -0
  35. package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
  36. package/dist/render/backend/webgpu/pipelineCache.js +7 -0
  37. package/dist/render/backend/webgpu/renderer.d.ts +152 -35
  38. package/dist/render/backend/webgpu/renderer.js +787 -142
  39. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  40. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  41. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  42. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  43. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  44. package/dist/render/backend/webgpu/staticBundles.js +241 -0
  45. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  46. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  47. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  48. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  49. package/dist/render/bloomChain.d.ts +3 -3
  50. package/dist/render/boneAnimation.d.ts +92 -0
  51. package/dist/render/boneAnimation.js +133 -0
  52. package/dist/render/compressedSource.d.ts +33 -13
  53. package/dist/render/compressedSource.js +70 -25
  54. package/dist/render/glCompressed.js +83 -0
  55. package/dist/render/instances.d.ts +26 -0
  56. package/dist/render/lightBudget.d.ts +10 -0
  57. package/dist/render/lightBudget.js +10 -0
  58. package/dist/render/lightmap.d.ts +1 -1
  59. package/dist/render/mesh.d.ts +5 -0
  60. package/dist/render/mesh.js +6 -0
  61. package/dist/render/pointLightSelection.d.ts +4 -4
  62. package/dist/render/screenSpaceReflection.d.ts +2 -1
  63. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  64. package/dist/render/shaders/boneAnimation.js +63 -0
  65. package/dist/render/shaders/depth.d.ts +7 -0
  66. package/dist/render/shaders/depth.js +35 -10
  67. package/dist/render/shaders/flat/index.d.ts +6 -0
  68. package/dist/render/shaders/flat/index.js +27 -5
  69. package/dist/render/shaders/flat/main.d.ts +1 -1
  70. package/dist/render/shaders/flat/main.js +15 -5
  71. package/dist/render/shaders/flat/models.js +1 -1
  72. package/dist/render/shaders/flat/overlay.js +1 -1
  73. package/dist/render/shaders/flat/pointShadow.js +2 -2
  74. package/dist/render/shaders/flat/preamble.js +38 -24
  75. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  76. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  77. package/dist/render/shaders/generated/flat.wgsl.d.ts +3077 -2903
  78. package/dist/render/shaders/generated/flat.wgsl.js +3100 -2925
  79. package/dist/render/shaders/outputTransform.d.ts +7 -0
  80. package/dist/render/shaders/outputTransform.js +28 -14
  81. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  82. package/dist/render/shaders/vertexChannel.js +26 -0
  83. package/dist/render/staticDraws.d.ts +47 -0
  84. package/dist/render/staticDraws.js +43 -0
  85. package/dist/render/surfaceTexture.d.ts +38 -7
  86. package/dist/render/surfaceTexture.js +56 -17
  87. package/package.json +6 -6
  88. package/src/index.ts +8 -2
  89. package/src/render/ambientHarmonics.ts +1 -1
  90. package/src/render/areaLights.ts +4 -4
  91. package/src/render/backend/api.ts +13 -3
  92. package/src/render/backend/select.ts +10 -5
  93. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  94. package/src/render/backend/webgl2/instanced.ts +20 -1
  95. package/src/render/backend/webgl2/renderer.ts +315 -24
  96. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  97. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  98. package/src/render/backend/webgpu/buffers.ts +42 -16
  99. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  100. package/src/render/backend/webgpu/depthPass.ts +51 -10
  101. package/src/render/backend/webgpu/drawCommand.ts +22 -4
  102. package/src/render/backend/webgpu/flatPass.ts +187 -14
  103. package/src/render/backend/webgpu/instanced.ts +32 -1
  104. package/src/render/backend/webgpu/materialStore.ts +276 -0
  105. package/src/render/backend/webgpu/pipelineCache.ts +7 -0
  106. package/src/render/backend/webgpu/renderer.ts +928 -155
  107. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  108. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  109. package/src/render/backend/webgpu/staticBundles.ts +363 -0
  110. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  111. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  112. package/src/render/bloomChain.ts +3 -3
  113. package/src/render/boneAnimation.ts +223 -0
  114. package/src/render/compressedSource.ts +121 -29
  115. package/src/render/glCompressed.ts +83 -0
  116. package/src/render/instances.ts +27 -0
  117. package/src/render/lightBudget.ts +11 -0
  118. package/src/render/lightmap.ts +1 -1
  119. package/src/render/mesh.ts +6 -0
  120. package/src/render/pointLightSelection.ts +4 -4
  121. package/src/render/screenSpaceReflection.ts +2 -1
  122. package/src/render/shaders/boneAnimation.ts +63 -0
  123. package/src/render/shaders/depth.ts +45 -10
  124. package/src/render/shaders/flat/index.ts +33 -5
  125. package/src/render/shaders/flat/main.ts +15 -5
  126. package/src/render/shaders/flat/models.ts +1 -1
  127. package/src/render/shaders/flat/overlay.ts +1 -1
  128. package/src/render/shaders/flat/pointShadow.ts +2 -2
  129. package/src/render/shaders/flat/preamble.ts +38 -24
  130. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  131. package/src/render/shaders/generated/flat.wgsl.ts +3100 -2925
  132. package/src/render/shaders/outputTransform.ts +30 -14
  133. package/src/render/shaders/vertexChannel.ts +27 -0
  134. package/src/render/staticDraws.ts +95 -0
  135. package/src/render/surfaceTexture.ts +94 -27
@@ -156,18 +156,33 @@ export interface GpuMesh {
156
156
  * changed, reported at 2.8 MB a frame for one character. What the split costs is a pipeline per
157
157
  * layout, keyed by `:dynamic`, and two more buffers bound per draw of such a mesh.
158
158
  */
159
- readonly update: ((device: GPUDevice, positions: Float32Array, normals?: Float32Array, previous?: GPUBuffer | null) => void) | null;
159
+ readonly update: ((device: GPUDevice, positions: Float32Array, normals?: Float32Array, staged?: StagedRewrite | null) => void) | null;
160
160
  /**
161
- * A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's positions,
162
- * made the first time a reconstructing renderer rewrites the mesh, and the frame of the last
163
- * rewrite. Null on a mesh created without `{ dynamic: true }`, which never moves a vertex.
161
+ * A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's positions and
162
+ * one the new positions are staged in, each made the first time a reconstructing renderer
163
+ * rewrites the mesh, and the frame of the last rewrite. Null on a mesh created without
164
+ * `{ dynamic: true }`, which never moves a vertex.
164
165
  */
165
166
  readonly motion: {
166
167
  previous: GPUBuffer | null;
168
+ staging: GPUBuffer | null;
167
169
  changed: number;
168
170
  } | null;
169
171
  dispose(): void;
170
172
  }
173
+ /**
174
+ * A rewrite a reconstructing renderer stages rather than writes, so last frame's positions can be
175
+ * kept with no submit of the rewrite's own: the new positions are written into `staging`, and two
176
+ * copies are recorded into `encoder` — the live positions into `previous` first, where the frame's
177
+ * first rewrite keeps them, then the staged ones in. The renderer submits that encoder at the head
178
+ * of the frame's next submit, which is what keeps both copies ahead of every draw that reads the
179
+ * mesh. A `writeBuffer` straight to the live positions would land before the copy aside could run.
180
+ */
181
+ export interface StagedRewrite {
182
+ encoder: GPUCommandEncoder;
183
+ staging: GPUBuffer;
184
+ previous: GPUBuffer | null;
185
+ }
171
186
  /** A mesh on its way to the device, and the iterator that gets it there. */
172
187
  export interface IncrementalGpuMesh {
173
188
  /**
@@ -408,7 +408,9 @@ function skinOffsetsOf(supplied) {
408
408
  }
409
409
  /** The handle, made where it can see only what it hands out. */
410
410
  function gpuMeshOf(parts) {
411
- const motion = parts.update === null ? null : { previous: null, changed: -1 };
411
+ const motion = parts.update === null
412
+ ? null
413
+ : { previous: null, staging: null, changed: -1 };
412
414
  const { vertices, constants, streams, indexBuffer, progress } = parts;
413
415
  return {
414
416
  vertexBuffers: streams === null
@@ -433,6 +435,7 @@ function gpuMeshOf(parts) {
433
435
  motion,
434
436
  dispose() {
435
437
  motion?.previous?.destroy();
438
+ motion?.staging?.destroy();
436
439
  streams?.positions.destroy();
437
440
  streams?.normals.destroy();
438
441
  vertices.destroy();
@@ -447,12 +450,12 @@ function isStreamed(attribute) {
447
450
  }
448
451
  /**
449
452
  * A dynamic mesh's `update`: the caller's positions, and its normals where it has them, each
450
- * written to its own buffer as it came. Last frame's positions, where a reconstruction wants them,
451
- * are copied on the device before the write — a copy submitted ahead of a `writeBuffer` runs ahead
452
- * of it on the queue — so no CPU copy of anything is kept.
453
+ * written to its own buffer as it came — or, where a reconstruction keeps last frame's positions,
454
+ * the positions staged and copied in on the device behind the copy aside (`StagedRewrite`), so no
455
+ * CPU copy of anything is kept and the rewrite submits nothing of its own.
453
456
  */
454
457
  function streamUpdate(positionsBuffer, normalsBuffer, streamBytes, bounds, vertexCount) {
455
- return (target, positions, normals, previous = null) => {
458
+ return (target, positions, normals, staged = null) => {
456
459
  for (const [name, values] of [
457
460
  ['update', positions],
458
461
  ['normals', normals],
@@ -463,15 +466,19 @@ function streamUpdate(positionsBuffer, normalsBuffer, streamBytes, bounds, verte
463
466
  "A mesh's vertex count is fixed at creation: the index buffer, the pipeline and " +
464
467
  'every other attribute are sized against it.');
465
468
  }
466
- if (previous !== null) {
467
- const encoder = target.createCommandEncoder({ label: 'mesh.previousPositions' });
468
- encoder.copyBufferToBuffer(positionsBuffer, 0, previous, 0, streamBytes);
469
- target.queue.submit([encoder.finish()]);
470
- }
471
469
  /* Everything that decides whether this is on screen starts from the bounds, so a cloth
472
470
  that blew sideways out of its original box would be culled while still visible. */
473
471
  boundsOfPositions(positions, bounds);
474
- target.queue.writeBuffer(positionsBuffer, 0, positions);
472
+ if (staged === null) {
473
+ target.queue.writeBuffer(positionsBuffer, 0, positions);
474
+ }
475
+ else {
476
+ target.queue.writeBuffer(staged.staging, 0, positions);
477
+ if (staged.previous !== null) {
478
+ staged.encoder.copyBufferToBuffer(positionsBuffer, 0, staged.previous, 0, streamBytes);
479
+ }
480
+ staged.encoder.copyBufferToBuffer(staged.staging, 0, positionsBuffer, 0, streamBytes);
481
+ }
475
482
  if (normals !== undefined)
476
483
  target.queue.writeBuffer(normalsBuffer, 0, normals);
477
484
  };
@@ -12,7 +12,6 @@
12
12
  * the level nominally has.
13
13
  */
14
14
  import type { CompressedTextureFormat, CompressedTextureSource } from '../../compressedSource.ts';
15
- /** Every BC format, where the device carries the feature; none where it does not. */
16
15
  export declare function gpuCompressedFormats(device: GPUDevice): CompressedTextureFormat[];
17
16
  /** A texture holding `layers` as `name`, `levels` levels of each, written as they are. */
18
17
  export declare function createCompressedTexture(device: GPUDevice, name: CompressedTextureFormat, layers: readonly CompressedTextureSource[], levels: number): GPUTexture;
@@ -1,4 +1,4 @@
1
- import { BLOCK_BYTES } from '../../compressedSource.js';
1
+ import { BLOCK_SHAPES, blockFamily } from '../../compressedSource.js';
2
2
  const GPU_FORMAT = {
3
3
  bc1: 'bc1-rgba-unorm',
4
4
  'bc1-srgb': 'bc1-rgba-unorm-srgb',
@@ -10,28 +10,73 @@ const GPU_FORMAT = {
10
10
  bc5: 'bc5-rg-unorm',
11
11
  bc7: 'bc7-rgba-unorm',
12
12
  'bc7-srgb': 'bc7-rgba-unorm-srgb',
13
+ 'etc2-rgb8': 'etc2-rgb8unorm',
14
+ 'etc2-rgb8-srgb': 'etc2-rgb8unorm-srgb',
15
+ 'etc2-rgb8a1': 'etc2-rgb8a1unorm',
16
+ 'etc2-rgb8a1-srgb': 'etc2-rgb8a1unorm-srgb',
17
+ 'etc2-rgba8': 'etc2-rgba8unorm',
18
+ 'etc2-rgba8-srgb': 'etc2-rgba8unorm-srgb',
19
+ 'eac-r11': 'eac-r11unorm',
20
+ 'eac-rg11': 'eac-rg11unorm',
21
+ 'astc-4x4': 'astc-4x4-unorm',
22
+ 'astc-4x4-srgb': 'astc-4x4-unorm-srgb',
23
+ 'astc-5x4': 'astc-5x4-unorm',
24
+ 'astc-5x4-srgb': 'astc-5x4-unorm-srgb',
25
+ 'astc-5x5': 'astc-5x5-unorm',
26
+ 'astc-5x5-srgb': 'astc-5x5-unorm-srgb',
27
+ 'astc-6x5': 'astc-6x5-unorm',
28
+ 'astc-6x5-srgb': 'astc-6x5-unorm-srgb',
29
+ 'astc-6x6': 'astc-6x6-unorm',
30
+ 'astc-6x6-srgb': 'astc-6x6-unorm-srgb',
31
+ 'astc-8x5': 'astc-8x5-unorm',
32
+ 'astc-8x5-srgb': 'astc-8x5-unorm-srgb',
33
+ 'astc-8x6': 'astc-8x6-unorm',
34
+ 'astc-8x6-srgb': 'astc-8x6-unorm-srgb',
35
+ 'astc-8x8': 'astc-8x8-unorm',
36
+ 'astc-8x8-srgb': 'astc-8x8-unorm-srgb',
37
+ 'astc-10x5': 'astc-10x5-unorm',
38
+ 'astc-10x5-srgb': 'astc-10x5-unorm-srgb',
39
+ 'astc-10x6': 'astc-10x6-unorm',
40
+ 'astc-10x6-srgb': 'astc-10x6-unorm-srgb',
41
+ 'astc-10x8': 'astc-10x8-unorm',
42
+ 'astc-10x8-srgb': 'astc-10x8-unorm-srgb',
43
+ 'astc-10x10': 'astc-10x10-unorm',
44
+ 'astc-10x10-srgb': 'astc-10x10-unorm-srgb',
45
+ 'astc-12x10': 'astc-12x10-unorm',
46
+ 'astc-12x10-srgb': 'astc-12x10-unorm-srgb',
47
+ 'astc-12x12': 'astc-12x12-unorm',
48
+ 'astc-12x12-srgb': 'astc-12x12-unorm-srgb',
13
49
  };
14
50
  /** Every BC format, where the device carries the feature; none where it does not. */
51
+ /** The feature that grants each family, which `select.ts` asks for wherever the adapter offers it. */
52
+ const FEATURE = {
53
+ bc: 'texture-compression-bc',
54
+ etc2: 'texture-compression-etc2',
55
+ astc: 'texture-compression-astc',
56
+ };
15
57
  export function gpuCompressedFormats(device) {
16
- return device.features.has('texture-compression-bc')
17
- ? Object.keys(GPU_FORMAT)
18
- : [];
58
+ return Object.keys(GPU_FORMAT).filter((name) => device.features.has(FEATURE[blockFamily(baseOf(name))]));
59
+ }
60
+ /** The format a name is of, without its colour space. */
61
+ function baseOf(name) {
62
+ return (name.endsWith('-srgb') ? name.slice(0, -'-srgb'.length) : name);
19
63
  }
20
64
  /** A texture holding `layers` as `name`, `levels` levels of each, written as they are. */
21
65
  export function createCompressedTexture(device, name, layers, levels) {
22
66
  const { format, width, height } = layers[0];
23
67
  const texture = device.createTexture({
24
- label: 'surface.texture.bc',
68
+ label: `surface.texture.${format}`,
25
69
  size: [width, height, layers.length],
26
70
  format: GPU_FORMAT[name],
27
71
  mipLevelCount: levels,
28
72
  usage: 0x2 | 0x4, // COPY_DST | TEXTURE_BINDING
29
73
  });
74
+ const block = BLOCK_SHAPES[format];
30
75
  for (let level = 0; level < levels; level++) {
31
- const across = Math.ceil(Math.max(1, width >> level) / 4);
32
- const down = Math.ceil(Math.max(1, height >> level) / 4);
76
+ const across = Math.ceil(Math.max(1, width >> level) / block.width);
77
+ const down = Math.ceil(Math.max(1, height >> level) / block.height);
33
78
  for (let layer = 0; layer < layers.length; layer++) {
34
- device.queue.writeTexture({ texture, mipLevel: level, origin: [0, 0, layer] }, layers[layer].levels[level], { bytesPerRow: across * BLOCK_BYTES[format], rowsPerImage: down }, { width: across * 4, height: down * 4, depthOrArrayLayers: 1 });
79
+ device.queue.writeTexture({ texture, mipLevel: level, origin: [0, 0, layer] }, layers[layer].levels[level], { bytesPerRow: across * block.bytes, rowsPerImage: down }, { width: across * block.width, height: down * block.height, depthOrArrayLayers: 1 });
35
80
  }
36
81
  }
37
82
  return texture;
@@ -1,6 +1,7 @@
1
1
  import { DEPTH_FRAG_WGSL } from '../../shaders/generated/depth.wgsl.ts';
2
2
  import type { PipelineCache } from './pipelineCache.ts';
3
3
  import type { ClothViews } from './clothTextures.ts';
4
+ import type { AnimationViews } from './flatPass.ts';
4
5
  /**
5
6
  * The shadow pass: the world drawn from the light, into a depth map.
6
7
  *
@@ -136,6 +137,55 @@ export declare const DEPTH_PALETTE_BINDING: {
136
137
  readonly sampler: 17;
137
138
  readonly type: "sampler2D";
138
139
  };
140
+ export declare const DEPTH_INSTANCED_ANIMATED_VERT_FIELDS: {
141
+ readonly uLightViewProj: {
142
+ readonly offset: 0;
143
+ readonly size: 64;
144
+ readonly type: "mat4";
145
+ };
146
+ readonly uSceneTime: {
147
+ readonly offset: 64;
148
+ readonly size: 4;
149
+ readonly type: "float";
150
+ };
151
+ readonly uBoneClip: {
152
+ readonly offset: 72;
153
+ readonly size: 8;
154
+ readonly type: "vec2";
155
+ };
156
+ };
157
+ export declare const DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS: {
158
+ readonly uLightViewProj: {
159
+ readonly offset: 0;
160
+ readonly size: 64;
161
+ readonly type: "mat4";
162
+ };
163
+ readonly uUvScale: {
164
+ readonly offset: 64;
165
+ readonly size: 8;
166
+ readonly type: "vec2";
167
+ };
168
+ readonly uUvOffset: {
169
+ readonly offset: 72;
170
+ readonly size: 8;
171
+ readonly type: "vec2";
172
+ };
173
+ readonly uAlphaCutout: {
174
+ readonly offset: 80;
175
+ readonly size: 8;
176
+ readonly type: "vec2";
177
+ };
178
+ readonly uSceneTime: {
179
+ readonly offset: 88;
180
+ readonly size: 4;
181
+ readonly type: "float";
182
+ };
183
+ readonly uBoneClip: {
184
+ readonly offset: 96;
185
+ readonly size: 8;
186
+ readonly type: "vec2";
187
+ };
188
+ };
139
189
  /**
140
190
  * The format the shadow map is stored in.
141
191
  *
@@ -160,7 +210,9 @@ skinned?: boolean,
160
210
  * Separate for the palette's reason: the plain layout stays exactly what it was, and a frame with
161
211
  * no cutout caster pays nothing.
162
212
  */
163
- cutout?: boolean): GPUBindGroupLayout;
213
+ cutout?: boolean,
214
+ /** An instanced caster playing a bone animation: its three vertex textures. */
215
+ animated?: boolean): GPUBindGroupLayout;
164
216
  export declare function createDepthBindGroup(device: GPUDevice, layout: GPUBindGroupLayout, perDraw: GPUBuffer, perPass: GPUBuffer, previous: GPUTextureView, sampler: GPUSampler,
165
217
  /** The joint palette, for a layout built with `skinned`. Omit for the rigid layout. */
166
218
  palette?: GPUTextureView | null,
@@ -170,7 +222,9 @@ cutout?: {
170
222
  readonly sampler: GPUSampler;
171
223
  } | null,
172
224
  /** A skinned group's cloth views: the caster's, or the stand-in where it has none. */
173
- cloth?: ClothViews | null): GPUBindGroup;
225
+ cloth?: ClothViews | null,
226
+ /** An animated caster's clip and clocks. See `AnimationViews`. */
227
+ animation?: AnimationViews | null): GPUBindGroup;
174
228
  /**
175
229
  * The depth pipeline.
176
230
  *
@@ -209,4 +263,6 @@ cutout?: boolean,
209
263
  */
210
264
  skinEight?: boolean,
211
265
  /** Whether a skinned caster is placed by a cloth binding, as its visible draw is. The key carries it. */
212
- cloth?: boolean): GPURenderPipeline;
266
+ cloth?: boolean,
267
+ /** An instanced caster playing a bone animation. The key must carry it. */
268
+ animated?: boolean): GPURenderPipeline;
@@ -1,4 +1,4 @@
1
- import { DEPTH_BINDINGS, DEPTH_CUTOUT_FRAG_WGSL, DEPTH_CUTOUT_VERT_WGSL, DEPTH_FRAG_WGSL, DEPTH_INSTANCED_CUTOUT_VERT_WGSL, DEPTH_INSTANCED_VERT_WGSL, DEPTH_SKINNED_VERT_WGSL, DEPTH_VERT_WGSL, } from '../../shaders/generated/depth.wgsl.js';
1
+ import { DEPTH_BINDINGS, DEPTH_CUTOUT_FRAG_WGSL, DEPTH_CUTOUT_VERT_WGSL, DEPTH_FRAG_WGSL, DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_WGSL, DEPTH_INSTANCED_ANIMATED_VERT_WGSL, DEPTH_INSTANCED_CUTOUT_VERT_WGSL, DEPTH_INSTANCED_VERT_WGSL, DEPTH_SKINNED_VERT_WGSL, DEPTH_VERT_WGSL, } from '../../shaders/generated/depth.wgsl.js';
2
2
  import { vertexBufferLayouts } from './buffers.js';
3
3
  import { vertexConstants } from './flatPass.js';
4
4
  import { shaderModule } from './shaderModules.js';
@@ -37,6 +37,14 @@ const CLOTH_TEXTURES = [
37
37
  DEPTH_BINDINGS.DEPTH_SKINNED_VERT.textures.uClothParticles,
38
38
  DEPTH_BINDINGS.DEPTH_SKINNED_VERT.textures.uClothRest,
39
39
  ];
40
+ /** A bone animation's three textures, at the bindings both animated depth variants declare them. */
41
+ const ANIMATION_TEXTURES = [
42
+ DEPTH_BINDINGS.DEPTH_INSTANCED_ANIMATED_VERT.textures.uBonePlaces,
43
+ DEPTH_BINDINGS.DEPTH_INSTANCED_ANIMATED_VERT.textures.uBoneTurns,
44
+ DEPTH_BINDINGS.DEPTH_INSTANCED_ANIMATED_VERT.textures.uInstanceClocks,
45
+ ];
46
+ export const DEPTH_INSTANCED_ANIMATED_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_INSTANCED_ANIMATED_VERT.fields;
47
+ export const DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT.fields;
40
48
  const VISIBILITY_VERTEX = 0x1;
41
49
  const VISIBILITY_FRAGMENT = 0x2;
42
50
  /**
@@ -63,10 +71,19 @@ skinned = false,
63
71
  * Separate for the palette's reason: the plain layout stays exactly what it was, and a frame with
64
72
  * no cutout caster pays nothing.
65
73
  */
66
- cutout = false) {
74
+ cutout = false,
75
+ /** An instanced caster playing a bone animation: its three vertex textures. */
76
+ animated = false) {
67
77
  return device.createBindGroupLayout({
68
- label: skinned ? 'depth.layout.skinned' : cutout ? 'depth.layout.cutout' : 'depth.layout',
78
+ label: `${skinned ? 'depth.layout.skinned' : cutout ? 'depth.layout.cutout' : 'depth.layout'}${animated ? '.animated' : ''}`,
69
79
  entries: [
80
+ ...(animated
81
+ ? ANIMATION_TEXTURES.map((binding) => ({
82
+ binding: binding.texture,
83
+ visibility: VISIBILITY_VERTEX,
84
+ texture: { sampleType: 'unfilterable-float' },
85
+ }))
86
+ : []),
70
87
  /* The texture and no sampler: the shader reads the palette with `textureLoad`, an integer
71
88
  coordinate with nothing to filter, which is what `flatPass.ts` binds for the same
72
89
  palette. naga declares a sampler for it and nothing ever reads one. */
@@ -147,7 +164,9 @@ palette = null,
147
164
  /** The cutout map and its sampler, for a layout built with `cutout`. */
148
165
  cutout = null,
149
166
  /** A skinned group's cloth views: the caster's, or the stand-in where it has none. */
150
- cloth = null) {
167
+ cloth = null,
168
+ /** An animated caster's clip and clocks. See `AnimationViews`. */
169
+ animation = null) {
151
170
  return device.createBindGroup({
152
171
  label: palette !== null
153
172
  ? 'depth.bindGroup.skinned'
@@ -180,6 +199,13 @@ cloth = null) {
180
199
  { binding: CUTOUT_MAP.sampler, resource: cutout.sampler },
181
200
  ]
182
201
  : []),
202
+ ...(animation !== null
203
+ ? [
204
+ { binding: ANIMATION_TEXTURES[0].texture, resource: animation.places },
205
+ { binding: ANIMATION_TEXTURES[1].texture, resource: animation.turns },
206
+ { binding: ANIMATION_TEXTURES[2].texture, resource: animation.clocks },
207
+ ]
208
+ : []),
183
209
  ],
184
210
  });
185
211
  }
@@ -221,20 +247,29 @@ cutout = false,
221
247
  */
222
248
  skinEight = false,
223
249
  /** Whether a skinned caster is placed by a cloth binding, as its visible draw is. The key carries it. */
224
- cloth = false) {
250
+ cloth = false,
251
+ /** An instanced caster playing a bone animation. The key must carry it. */
252
+ animated = false) {
225
253
  return cache.get(key, () => ({
226
254
  label: key,
227
255
  layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
228
256
  vertex: {
229
- module: shaderModule(device, cutout
230
- ? instanced
231
- ? { label: 'depth.instanced.cutout.vert', code: DEPTH_INSTANCED_CUTOUT_VERT_WGSL }
232
- : { label: 'depth.cutout.vert', code: DEPTH_CUTOUT_VERT_WGSL }
233
- : instanced
234
- ? { label: 'depth.instanced.vert', code: DEPTH_INSTANCED_VERT_WGSL }
235
- : skinned
236
- ? { label: 'depth.skinned.vert', code: DEPTH_SKINNED_VERT_WGSL }
237
- : { label: 'depth.vert', code: DEPTH_VERT_WGSL }),
257
+ module: shaderModule(device, animated
258
+ ? cutout
259
+ ? {
260
+ label: 'depth.instanced.animated.cutout.vert',
261
+ code: DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_WGSL,
262
+ }
263
+ : { label: 'depth.instanced.animated.vert', code: DEPTH_INSTANCED_ANIMATED_VERT_WGSL }
264
+ : cutout
265
+ ? instanced
266
+ ? { label: 'depth.instanced.cutout.vert', code: DEPTH_INSTANCED_CUTOUT_VERT_WGSL }
267
+ : { label: 'depth.cutout.vert', code: DEPTH_CUTOUT_VERT_WGSL }
268
+ : instanced
269
+ ? { label: 'depth.instanced.vert', code: DEPTH_INSTANCED_VERT_WGSL }
270
+ : skinned
271
+ ? { label: 'depth.skinned.vert', code: DEPTH_SKINNED_VERT_WGSL }
272
+ : { label: 'depth.vert', code: DEPTH_VERT_WGSL }),
238
273
  entryPoint: 'main',
239
274
  buffers: vertexBufferLayouts(present, instanced),
240
275
  ...(skinned && !instanced && !cutout
@@ -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
  *
@@ -13,9 +13,14 @@
13
13
  export interface DrawCommand {
14
14
  pipeline: GPURenderPipeline | null;
15
15
  bindGroup: GPUBindGroup | null;
16
- /** Dynamic offsets, held as two numbers rather than an array so a reset stays free. */
16
+ /**
17
+ * Dynamic offsets, held as numbers rather than an array so a reset stays free. Four only for a
18
+ * lit mesh: its draw's slot, its pass's, its material's and its view's, in binding order.
19
+ */
17
20
  offsetA: number;
18
21
  offsetB: number;
22
+ offsetC: number;
23
+ offsetD: number;
19
24
  offsetCount: number;
20
25
  /** Fixed length; `vertexCount` says how many slots are live. */
21
26
  readonly vertexBuffers: (GPUBuffer | null)[];
@@ -29,6 +34,12 @@ export interface DrawCommand {
29
34
  * cull's survivors, counted on the device. Null for every other draw.
30
35
  */
31
36
  indirect: GPUBuffer | null;
37
+ /**
38
+ * Render bundles to execute instead of everything above: a static list recorded once, replayed
39
+ * as one command. Held as the array `executeBundles` takes, kept by the list, so issuing it
40
+ * allocates nothing. Null for every other command, and put back to null by every take.
41
+ */
42
+ bundles: readonly GPURenderBundle[] | null;
32
43
  }
33
44
  export interface CommandPool {
34
45
  commands: DrawCommand[];
@@ -40,8 +51,11 @@ export declare function createCommandPool(capacity: number): CommandPool;
40
51
  /**
41
52
  * Hand out the next command, growing if a frame overruns.
42
53
  *
43
- * The entry is **not cleared**. Every caller fills every field it uses, and clearing here would
44
- * be a write per field per draw for no reader — the same reasoning the node arena uses.
54
+ * The entry is **not cleared**, but for `bundles`. Every caller fills every field it uses, and
55
+ * clearing here would be a write per field per draw for no reader — the same reasoning the node
56
+ * arena uses. `bundles` is the exception because almost no caller knows it exists: a pooled command
57
+ * that once carried a static list's bundles and was then taken for an ordinary draw would replay the
58
+ * list instead, so the one field nobody fills is the one field put back here.
45
59
  */
46
60
  export declare function takeCommand(pool: CommandPool): number;
47
61
  /** Empty the pool for a new frame. Keeps every entry; that is the whole point. */
@@ -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
  *
@@ -22,6 +22,8 @@ function emptyCommand() {
22
22
  bindGroup: null,
23
23
  offsetA: 0,
24
24
  offsetB: 0,
25
+ offsetC: 0,
26
+ offsetD: 0,
25
27
  offsetCount: 0,
26
28
  vertexBuffers: new Array(VERTEX_BUFFER_SLOTS).fill(null),
27
29
  vertexCount: 0,
@@ -30,6 +32,7 @@ function emptyCommand() {
30
32
  count: 0,
31
33
  instances: 1,
32
34
  indirect: null,
35
+ bundles: null,
33
36
  };
34
37
  }
35
38
  export function createCommandPool(capacity) {
@@ -41,8 +44,11 @@ export function createCommandPool(capacity) {
41
44
  /**
42
45
  * Hand out the next command, growing if a frame overruns.
43
46
  *
44
- * The entry is **not cleared**. Every caller fills every field it uses, and clearing here would
45
- * be a write per field per draw for no reader — the same reasoning the node arena uses.
47
+ * The entry is **not cleared**, but for `bundles`. Every caller fills every field it uses, and
48
+ * clearing here would be a write per field per draw for no reader — the same reasoning the node
49
+ * arena uses. `bundles` is the exception because almost no caller knows it exists: a pooled command
50
+ * that once carried a static list's bundles and was then taken for an ordinary draw would replay the
51
+ * list instead, so the one field nobody fills is the one field put back here.
46
52
  */
47
53
  export function takeCommand(pool) {
48
54
  const at = pool.taken;
@@ -51,6 +57,7 @@ export function takeCommand(pool) {
51
57
  pool.taken = at + 1;
52
58
  if (pool.taken > pool.highWater)
53
59
  pool.highWater = pool.taken;
60
+ pool.commands[at].bundles = null;
54
61
  return at;
55
62
  }
56
63
  /** Empty the pool for a new frame. Keeps every entry; that is the whole point. */