@driftengine/core 4.10.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.json +71 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +4 -3
  4. package/dist/index.js +1 -1
  5. package/dist/render/backend/webgl2/renderer.d.ts +99 -2
  6. package/dist/render/backend/webgl2/renderer.js +361 -42
  7. package/dist/render/backend/webgpu/buffers.d.ts +5 -0
  8. package/dist/render/backend/webgpu/buffers.js +98 -48
  9. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  10. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  11. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  12. package/dist/render/backend/webgpu/clothOps.js +78 -0
  13. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  14. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  15. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  16. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  17. package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
  18. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  19. package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
  20. package/dist/render/backend/webgpu/flatPass.js +2 -1
  21. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  22. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  23. package/dist/render/backend/webgpu/latePass.js +1 -0
  24. package/dist/render/backend/webgpu/motionPass.js +15 -6
  25. package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
  26. package/dist/render/backend/webgpu/pipelineCache.js +39 -1
  27. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  28. package/dist/render/backend/webgpu/renderer.d.ts +48 -0
  29. package/dist/render/backend/webgpu/renderer.js +207 -27
  30. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  31. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  32. package/dist/render/backend/webgpu/staticBundles.js +2 -1
  33. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  34. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  35. package/dist/render/globalMediumPass.d.ts +2 -0
  36. package/dist/render/globalMediumPass.js +1 -0
  37. package/dist/render/indexWidth.d.ts +17 -0
  38. package/dist/render/indexWidth.js +23 -0
  39. package/dist/render/lightMatrix.d.ts +11 -0
  40. package/dist/render/lightMatrix.js +14 -0
  41. package/dist/render/mesh.d.ts +9 -0
  42. package/dist/render/mesh.js +38 -14
  43. package/dist/render/renderQuality.d.ts +30 -0
  44. package/dist/render/renderQuality.js +5 -0
  45. package/dist/render/rendererHarness.d.ts +7 -0
  46. package/dist/render/rendererHarness.js +12 -0
  47. package/dist/render/shader.d.ts +15 -0
  48. package/dist/render/shader.js +41 -0
  49. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  50. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  51. package/dist/render/shaders/flat/glassTint.js +3 -0
  52. package/dist/render/shaders/flat/index.d.ts +15 -0
  53. package/dist/render/shaders/flat/index.js +15 -2
  54. package/dist/render/shaders/flat/layered.d.ts +19 -0
  55. package/dist/render/shaders/flat/layered.js +115 -0
  56. package/dist/render/shaders/flat/main.d.ts +1 -1
  57. package/dist/render/shaders/flat/main.js +43 -16
  58. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  59. package/dist/render/shaders/flat/mapReads.js +38 -0
  60. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  61. package/dist/render/shaders/flat/movingSun.js +46 -0
  62. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  63. package/dist/render/shaders/flat/normalMap.js +14 -3
  64. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  65. package/dist/render/shaders/flat/overlay.js +41 -19
  66. package/dist/render/shaders/flat/preamble.js +23 -5
  67. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  68. package/dist/render/shaders/flat/worldUv.js +113 -0
  69. package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
  70. package/dist/render/shaders/generated/flat.wgsl.js +791 -391
  71. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  72. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  73. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  74. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  75. package/dist/render/shaders/globalMedium.js +9 -1
  76. package/dist/render/shaders/lightVolume.js +9 -1
  77. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  78. package/dist/render/shaders/tangentFrame.js +16 -3
  79. package/dist/render/skinnedClothRun.d.ts +28 -0
  80. package/dist/render/skinnedClothRun.js +103 -35
  81. package/dist/render/surfaceLayers.d.ts +39 -0
  82. package/dist/render/surfaceLayers.js +26 -0
  83. package/dist/render/surfaceOverlay.d.ts +16 -3
  84. package/dist/render/surfaceOverlay.js +11 -6
  85. package/dist/render/surfaceProjection.d.ts +34 -0
  86. package/dist/render/surfaceProjection.js +30 -0
  87. package/dist/render/surfaceTexture.d.ts +30 -0
  88. package/dist/render/vertexPacking.d.ts +48 -0
  89. package/dist/render/vertexPacking.js +68 -0
  90. package/package.json +6 -6
  91. package/src/index.ts +4 -2
  92. package/src/render/backend/webgl2/renderer.ts +408 -55
  93. package/src/render/backend/webgpu/buffers.ts +153 -54
  94. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  95. package/src/render/backend/webgpu/clothOps.ts +86 -0
  96. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  97. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  98. package/src/render/backend/webgpu/drawCommand.ts +3 -0
  99. package/src/render/backend/webgpu/flatPass.ts +26 -28
  100. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  101. package/src/render/backend/webgpu/latePass.ts +1 -0
  102. package/src/render/backend/webgpu/motionPass.ts +18 -7
  103. package/src/render/backend/webgpu/pipelineCache.ts +46 -7
  104. package/src/render/backend/webgpu/renderer.ts +274 -48
  105. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  106. package/src/render/backend/webgpu/staticBundles.ts +2 -1
  107. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  108. package/src/render/globalMediumPass.ts +7 -0
  109. package/src/render/indexWidth.ts +27 -0
  110. package/src/render/lightMatrix.ts +15 -0
  111. package/src/render/mesh.ts +66 -14
  112. package/src/render/renderQuality.ts +38 -0
  113. package/src/render/rendererHarness.ts +15 -0
  114. package/src/render/shader.ts +59 -0
  115. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  116. package/src/render/shaders/flat/glassTint.ts +7 -1
  117. package/src/render/shaders/flat/index.ts +30 -2
  118. package/src/render/shaders/flat/layered.ts +115 -0
  119. package/src/render/shaders/flat/main.ts +43 -16
  120. package/src/render/shaders/flat/mapReads.ts +38 -0
  121. package/src/render/shaders/flat/movingSun.ts +46 -0
  122. package/src/render/shaders/flat/normalMap.ts +14 -3
  123. package/src/render/shaders/flat/overlay.ts +41 -19
  124. package/src/render/shaders/flat/preamble.ts +23 -5
  125. package/src/render/shaders/flat/worldUv.ts +113 -0
  126. package/src/render/shaders/generated/flat.wgsl.ts +791 -391
  127. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  128. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  129. package/src/render/shaders/globalMedium.ts +9 -1
  130. package/src/render/shaders/lightVolume.ts +9 -1
  131. package/src/render/shaders/tangentFrame.ts +16 -3
  132. package/src/render/skinnedClothRun.ts +138 -38
  133. package/src/render/surfaceLayers.ts +57 -0
  134. package/src/render/surfaceOverlay.ts +24 -7
  135. package/src/render/surfaceProjection.ts +57 -0
  136. package/src/render/surfaceTexture.ts +30 -0
  137. package/src/render/vertexPacking.ts +109 -0
  138. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  139. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  140. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -7,7 +7,7 @@
7
7
  * each kernel there is an entry point here, one invocation a particle or a constraint, and the loop
8
8
  * over a batch is the dispatch. `clothPose.ts`' `skinParticles` is `pose`. The order of a step —
9
9
  * begin, then per substep predict, the batches, the limits, finish; then the blend — is
10
- * `gpuSkinnedCloth.ts`', taken from the same `ClothControl` the CPU solver is driven by.
10
+ * `gpuClothSet.ts`', taken from the same `ClothControl` the CPU solver is driven by.
11
11
  *
12
12
  * **Where it differs, it says so.** Single precision throughout, where the CPU rounds to single
13
13
  * only on a store. And the bending angle is `atan2(|n1 × n2|, n1 · n2)` rather than `acos(n1 · n2)`
@@ -28,33 +28,44 @@
28
28
  * start; `pose` the two skinned poses a step interpolates between and the latest skinned normals;
29
29
  * `statics` everything per particle that never changes. A section's offset is a multiple of the
30
30
  * particle count, which the constants carry — so no offset is a uniform to keep in step.
31
+ *
32
+ * **One dispatch serves every garment of a set** (`clothSetPack.ts`): the particles, constraints,
33
+ * joints and colliders of all of them in those buffers, and each garment's numbers — its step's
34
+ * fraction and blend, its damping, gravity and limits — a record of its own in `paces`, at the
35
+ * round's place. A kernel finds its garment from the particle, and returns at once where that
36
+ * garment has nothing to do this round: a garment that took fewer steps, or one settling alone after
37
+ * a reset, sits the round out while the rest of the set runs it.
31
38
  */
32
39
  export const CLOTH_WORKGROUP = 64;
33
40
 
34
41
  export const CLOTH_SOLVE_WGSL = /* wgsl */ `
42
+ /* The set's: every garment's particles, records, skin matrices and colliders, and the step length
43
+ they all share. */
35
44
  struct Constants {
36
45
  count: u32,
37
46
  distances: u32,
38
47
  bendings: u32,
39
48
  joints: u32,
40
49
  colliders: u32,
41
- /* 1 a max distance, 2 a backstop, 4 a frontstop: which limits the set-up has at all. */
42
- limits: u32,
43
- width: u32,
44
50
  pad0: u32,
51
+ pad1: u32,
52
+ pad2: u32,
45
53
  h: f32,
46
54
  invH: f32,
47
55
  invH2: f32,
48
- keep: f32,
49
- dragKeep: f32,
50
- maxDistanceScale: f32,
51
- margin: f32,
52
- pad1: f32,
53
- gravity: vec4<f32>,
56
+ pad3: f32,
57
+ }
58
+
59
+ /* Where this dispatch's round of records starts in paces, at a dynamic offset. */
60
+ struct Round {
61
+ at: u32,
62
+ pad0: u32,
63
+ pad1: u32,
64
+ pad2: u32,
54
65
  }
55
66
 
56
- /* One step's numbers, at a dynamic offset: every step of a frame has its own slot. */
57
- struct Step {
67
+ /* One garment's numbers for one round: its step's, and its own that never change. */
68
+ struct Pace {
58
69
  fraction: f32,
59
70
  blend: f32,
60
71
  alpha: f32,
@@ -62,8 +73,10 @@ struct Step {
62
73
  /* Where the pose a step starts from and the one it moves toward sit in pose, in floats. */
63
74
  fromAt: u32,
64
75
  toAt: u32,
65
- pad0: u32,
66
- pad1: u32,
76
+ /* 0 where the garment sits this round out. Not active, which WGSL reserves. */
77
+ live: u32,
78
+ /* 1 a max distance, 2 a backstop, 4 a frontstop: which limits the garment has at all. */
79
+ limits: u32,
67
80
  /* The character's turn since the last step, by columns; origin.w is the share carried. */
68
81
  turn0: vec4<f32>,
69
82
  turn1: vec4<f32>,
@@ -72,9 +85,19 @@ struct Step {
72
85
  shift: vec4<f32>,
73
86
  /* The frame's wind: one wind, sampled once a frame, so a step's rather than the set-up's. */
74
87
  wind: vec4<f32>,
88
+ colliderBase: u32,
89
+ colliders: u32,
90
+ width: u32,
91
+ pad0: u32,
92
+ keep: f32,
93
+ dragKeep: f32,
94
+ maxDistanceScale: f32,
95
+ margin: f32,
96
+ gravity: vec4<f32>,
75
97
  }
76
98
 
77
- /* One batch of one kind of constraint, at a dynamic offset; first resets the multipliers. */
99
+ /* One colour of one kind of constraint across the set, at a dynamic offset; first resets the
100
+ multipliers. Publishing reads one garment's particles as begin to end, and first is the garment. */
78
101
  struct Batch {
79
102
  begin: u32,
80
103
  end: u32,
@@ -83,7 +106,7 @@ struct Batch {
83
106
  }
84
107
 
85
108
  @group(0) @binding(0) var<uniform> c: Constants;
86
- @group(0) @binding(1) var<uniform> pace: Step;
109
+ @group(0) @binding(1) var<uniform> round: Round;
87
110
  @group(0) @binding(2) var<uniform> batch: Batch;
88
111
  @group(0) @binding(3) var<storage, read_write> state: array<f32>;
89
112
  @group(0) @binding(4) var<storage, read_write> pose: array<f32>;
@@ -91,6 +114,12 @@ struct Batch {
91
114
  @group(0) @binding(6) var<storage, read> constraints: array<u32>;
92
115
  @group(0) @binding(7) var<storage, read_write> lambda: array<f32>;
93
116
  @group(0) @binding(8) var<storage, read> frame: array<f32>;
117
+ @group(0) @binding(9) var<storage, read> paces: array<Pace>;
118
+
119
+ /* The record of particle i's garment this round: which garment a particle is, in statics. */
120
+ fn paceAt(i: u32) -> u32 {
121
+ return round.at + u32(statics[29u * c.count + i]);
122
+ }
94
123
 
95
124
  fn stateAt(section: u32, i: u32) -> u32 {
96
125
  return section * 3u * c.count + i * 3u;
@@ -121,10 +150,10 @@ fn inverseMass(i: u32) -> f32 {
121
150
  }
122
151
 
123
152
  /* Where skinning puts particle i, fraction of the way from the last pose to the latest. */
124
- fn skinnedAt(i: u32) -> vec3<f32> {
125
- let a = poseOf(pace.fromAt + i * 3u);
126
- let b = poseOf(pace.toAt + i * 3u);
127
- return a + (b - a) * pace.fraction;
153
+ fn skinnedAt(i: u32, g: u32) -> vec3<f32> {
154
+ let a = poseOf(paces[g].fromAt + i * 3u);
155
+ let b = poseOf(paces[g].toAt + i * 3u);
156
+ return a + (b - a) * paces[g].fraction;
128
157
  }
129
158
 
130
159
  fn skinMatrix(j: u32) -> mat4x4<f32> {
@@ -143,6 +172,10 @@ fn clothPose(@builtin(global_invocation_id) id: vec3<u32>) {
143
172
  if (i >= c.count) {
144
173
  return;
145
174
  }
175
+ let g = paceAt(i);
176
+ if (paces[g].live == 0u) {
177
+ return;
178
+ }
146
179
  let n = c.count;
147
180
  let restAt = 7u * n + i * 3u;
148
181
  let normalAt = 10u * n + i * 3u;
@@ -162,7 +195,7 @@ fn clothPose(@builtin(global_invocation_id) id: vec3<u32>) {
162
195
  q += w * (mat3x3<f32>(m[0].xyz, m[1].xyz, m[2].xyz) * q0);
163
196
  }
164
197
  }
165
- let to = pace.toAt + i * 3u;
198
+ let to = paces[g].toAt + i * 3u;
166
199
  pose[to] = p.x;
167
200
  pose[to + 1u] = p.y;
168
201
  pose[to + 2u] = p.z;
@@ -181,7 +214,11 @@ fn clothRest(@builtin(global_invocation_id) id: vec3<u32>) {
181
214
  if (i >= c.count) {
182
215
  return;
183
216
  }
184
- let p = poseOf(pace.toAt + i * 3u);
217
+ let g = paceAt(i);
218
+ if (paces[g].live == 0u) {
219
+ return;
220
+ }
221
+ let p = poseOf(paces[g].toAt + i * 3u);
185
222
  store(stateAt(POSITION, i), p);
186
223
  store(stateAt(START, i), p);
187
224
  store(stateAt(VELOCITY, i), vec3<f32>(0.0));
@@ -194,21 +231,25 @@ fn clothBegin(@builtin(global_invocation_id) id: vec3<u32>) {
194
231
  if (i >= c.count) {
195
232
  return;
196
233
  }
234
+ let g = paceAt(i);
235
+ if (paces[g].live == 0u) {
236
+ return;
237
+ }
197
238
  let at = stateAt(POSITION, i);
198
239
  let p = load(at);
199
240
  store(stateAt(START, i), p);
200
241
  if (inverseMass(i) == 0.0) {
201
- store(at, skinnedAt(i));
242
+ store(at, skinnedAt(i, g));
202
243
  return;
203
244
  }
204
- if (pace.carry == 0u) {
245
+ if (paces[g].carry == 0u) {
205
246
  return;
206
247
  }
207
248
  /* carryParticles: the share of the character's turn about its old origin, and of its move. */
208
- let turn = mat3x3<f32>(pace.turn0.xyz, pace.turn1.xyz, pace.turn2.xyz);
209
- let share = pace.origin.w;
210
- let x = p - pace.origin.xyz;
211
- store(at, p + (turn * x - x) * share + pace.shift.xyz);
249
+ let turn = mat3x3<f32>(paces[g].turn0.xyz, paces[g].turn1.xyz, paces[g].turn2.xyz);
250
+ let share = paces[g].origin.w;
251
+ let x = p - paces[g].origin.xyz;
252
+ store(at, p + (turn * x - x) * share + paces[g].shift.xyz);
212
253
  let vAt = stateAt(VELOCITY, i);
213
254
  let v = load(vAt);
214
255
  store(vAt, v + (turn * v - v) * share);
@@ -221,6 +262,10 @@ fn clothPredict(@builtin(global_invocation_id) id: vec3<u32>) {
221
262
  if (i >= c.count) {
222
263
  return;
223
264
  }
265
+ let g = paceAt(i);
266
+ if (paces[g].live == 0u) {
267
+ return;
268
+ }
224
269
  let at = stateAt(POSITION, i);
225
270
  let p = load(at);
226
271
  store(stateAt(PREVIOUS, i), p);
@@ -228,8 +273,8 @@ fn clothPredict(@builtin(global_invocation_id) id: vec3<u32>) {
228
273
  return;
229
274
  }
230
275
  let vAt = stateAt(VELOCITY, i);
231
- let air = pace.wind.xyz;
232
- let v = air + (load(vAt) * c.keep - air) * c.dragKeep + c.gravity.xyz * c.h;
276
+ let air = paces[g].wind.xyz;
277
+ let v = air + (load(vAt) * paces[g].keep - air) * paces[g].dragKeep + paces[g].gravity.xyz * c.h;
233
278
  store(vAt, v);
234
279
  store(at, p + v * c.h);
235
280
  }
@@ -241,13 +286,17 @@ fn clothDistance(@builtin(global_invocation_id) id: vec3<u32>) {
241
286
  if (k >= batch.end) {
242
287
  return;
243
288
  }
289
+ let a = constraints[k * 4u];
290
+ let b = constraints[k * 4u + 1u];
291
+ /* A garment sitting the round out solves nothing, and its next step's first pass resets. */
292
+ if (paces[paceAt(a)].live == 0u) {
293
+ return;
294
+ }
244
295
  /* The first pass of a substep starts the multiplier at nothing, as the CPU's fill(0) does —
245
296
  written before any early return, so a skipped constraint does not carry the last one. */
246
297
  if (batch.first == 1u) {
247
298
  lambda[k] = 0.0;
248
299
  }
249
- let a = constraints[k * 4u];
250
- let b = constraints[k * 4u + 1u];
251
300
  let wa = inverseMass(a);
252
301
  let wb = inverseMass(b);
253
302
  let w = wa + wb;
@@ -284,6 +333,9 @@ fn clothBending(@builtin(global_invocation_id) id: vec3<u32>) {
284
333
  }
285
334
  let at = c.distances * 4u + k * 6u;
286
335
  let slot = c.distances + k;
336
+ if (paces[paceAt(constraints[at])].live == 0u) {
337
+ return;
338
+ }
287
339
  if (batch.first == 1u) {
288
340
  lambda[slot] = 0.0;
289
341
  }
@@ -352,6 +404,9 @@ fn clothTether(@builtin(global_invocation_id) id: vec3<u32>) {
352
404
  return;
353
405
  }
354
406
  let at = c.distances * 4u + c.bendings * 6u + k * 3u;
407
+ if (paces[paceAt(constraints[at])].live == 0u) {
408
+ return;
409
+ }
355
410
  let pAt = stateAt(POSITION, constraints[at]);
356
411
  let anchor = load(stateAt(POSITION, constraints[at + 1u]));
357
412
  let d = load(pAt) - anchor;
@@ -380,14 +435,19 @@ fn clothLimit(@builtin(global_invocation_id) id: vec3<u32>) {
380
435
  if (i >= c.count || inverseMass(i) == 0.0) {
381
436
  return;
382
437
  }
438
+ let g = paceAt(i);
439
+ if (paces[g].live == 0u) {
440
+ return;
441
+ }
442
+ let limits = paces[g].limits;
383
443
  let n = c.count;
384
444
  let at = stateAt(POSITION, i);
385
445
  var p = load(at);
386
- let t = skinnedAt(i);
446
+ let t = skinnedAt(i, g);
387
447
  /* A max distance below zero is none: the CPU's Infinity, which a device may not be handed. */
388
448
  let maxDistance = statics[n + i];
389
- if ((c.limits & 1u) != 0u && maxDistance >= 0.0) {
390
- let limit = maxDistance * c.maxDistanceScale;
449
+ if ((limits & 1u) != 0u && maxDistance >= 0.0) {
450
+ let limit = maxDistance * paces[g].maxDistanceScale;
391
451
  let d = p - t;
392
452
  let d2 = dot(d, d);
393
453
  if (d2 > limit * limit) {
@@ -395,22 +455,23 @@ fn clothLimit(@builtin(global_invocation_id) id: vec3<u32>) {
395
455
  }
396
456
  }
397
457
  let normal = poseOf(6u * n + i * 3u);
398
- if ((c.limits & 2u) != 0u) {
458
+ if ((limits & 2u) != 0u) {
399
459
  let radius = statics[2u * n + i * 2u + 1u];
400
460
  if (radius > 0.0) {
401
461
  p = pushOut(p, t - normal * (statics[2u * n + i * 2u] + radius), radius);
402
462
  }
403
463
  }
404
- if ((c.limits & 4u) != 0u) {
464
+ if ((limits & 4u) != 0u) {
405
465
  let radius = statics[4u * n + i * 2u + 1u];
406
466
  if (radius > 0.0) {
407
467
  p = pushOut(p, t + normal * (statics[4u * n + i * 2u] + radius), radius);
408
468
  }
409
469
  }
410
- let pad = statics[6u * n + i] + c.margin;
470
+ let pad = statics[6u * n + i] + paces[g].margin;
411
471
  let ends = c.joints * 16u;
412
472
  let radii = ends + c.colliders * 6u;
413
- for (var k = 0u; k < c.colliders; k++) {
473
+ let first = paces[g].colliderBase;
474
+ for (var k = first; k < first + paces[g].colliders; k++) {
414
475
  let a = vec3<f32>(frame[ends + k * 6u], frame[ends + k * 6u + 1u], frame[ends + k * 6u + 2u]);
415
476
  let e = vec3<f32>(frame[ends + k * 6u + 3u], frame[ends + k * 6u + 4u], frame[ends + k * 6u + 5u]) - a;
416
477
  let e2 = dot(e, e);
@@ -432,6 +493,9 @@ fn clothFinish(@builtin(global_invocation_id) id: vec3<u32>) {
432
493
  if (i >= c.count) {
433
494
  return;
434
495
  }
496
+ if (paces[paceAt(i)].live == 0u) {
497
+ return;
498
+ }
435
499
  let vAt = stateAt(VELOCITY, i);
436
500
  if (inverseMass(i) == 0.0) {
437
501
  store(vAt, vec3<f32>(0.0));
@@ -447,24 +511,34 @@ fn clothBlend(@builtin(global_invocation_id) id: vec3<u32>) {
447
511
  if (i >= c.count) {
448
512
  return;
449
513
  }
450
- let t = skinnedAt(i);
514
+ /* A blend of 0 is none, not a last step of one: see ClothDevice.step. */
515
+ let g = paceAt(i);
516
+ let blend = paces[g].blend;
517
+ if (paces[g].live == 0u || blend <= 0.0) {
518
+ return;
519
+ }
520
+ let t = skinnedAt(i, g);
451
521
  let at = stateAt(POSITION, i);
452
- store(at, t + (load(at) - t) * pace.blend);
522
+ store(at, t + (load(at) - t) * blend);
453
523
  let vAt = stateAt(VELOCITY, i);
454
- store(vAt, load(vAt) * pace.blend);
524
+ store(vAt, load(vAt) * blend);
455
525
  }
456
526
 
457
527
  @group(1) @binding(0) var output: texture_storage_2d<rgba32float, write>;
458
528
 
459
- /* SkinnedCloth.interpolate, into the particle texture the vertex stage reads. */
529
+ /* SkinnedCloth.interpolate, into the particle texture the vertex stage reads: one garment's, whose
530
+ particles are batch.begin to batch.end and whose record is batch.first's. */
460
531
  @compute @workgroup_size(64)
461
532
  fn clothPublish(@builtin(global_invocation_id) id: vec3<u32>) {
462
- let i = id.x;
463
- if (i >= c.count) {
533
+ let i = batch.begin + id.x;
534
+ if (i >= batch.end) {
464
535
  return;
465
536
  }
537
+ let g = round.at + batch.first;
466
538
  let a = load(stateAt(START, i));
467
- let p = a + (load(stateAt(POSITION, i)) - a) * pace.alpha;
468
- textureStore(output, vec2<u32>(i % c.width, i / c.width), vec4<f32>(p, 0.0));
539
+ let p = a + (load(stateAt(POSITION, i)) - a) * paces[g].alpha;
540
+ let local = id.x;
541
+ let width = paces[g].width;
542
+ textureStore(output, vec2<u32>(local % width, local / width), vec4<f32>(p, 0.0));
469
543
  }
470
544
  `;
@@ -287,6 +287,7 @@ export function takeRecorded(pool: CommandPool): DrawCommand {
287
287
  command.offsetCount = 0;
288
288
  command.vertexCount = 0;
289
289
  command.indexBuffer = null;
290
+ command.indexFormat = 'uint32';
290
291
  command.indexed = false;
291
292
  command.instances = 1;
292
293
  command.indirect = null;
@@ -341,7 +342,7 @@ export function encodeBundle(
341
342
  if (buffer != null) encoder.setVertexBuffer(slot, buffer);
342
343
  }
343
344
  if (command.indexed && command.indexBuffer !== null) {
344
- encoder.setIndexBuffer(command.indexBuffer, 'uint32');
345
+ encoder.setIndexBuffer(command.indexBuffer, command.indexFormat);
345
346
  if (command.indirect !== null) encoder.drawIndexedIndirect(command.indirect, 0);
346
347
  else encoder.drawIndexed(command.count, command.instances);
347
348
  } else {
@@ -0,0 +1,113 @@
1
+ import {
2
+ PACKED_ATTRIBUTES,
3
+ toFixed16,
4
+ type AttributePacking,
5
+ type VertexPacking,
6
+ } from '../../vertexPacking.ts';
7
+
8
+ /**
9
+ * A mesh's packed attributes as a WebGPU vertex reads them: each one's format and bytes, the words
10
+ * a pipeline key carries for them, and the writers that put sixteen-bit fixed point into the
11
+ * interleaved rows. What is packed how is `vertexPacking.ts`'s decision; this is only its binding.
12
+ *
13
+ * **Sixteen-bit fields are four wide, never three**, because WebGPU has no three-component
14
+ * sixteen-bit format: a normal or a colour takes `x4` and leaves the fourth zero, and the shader's
15
+ * `vec3` input reads the first three. So a normal costs eight bytes here against six on WebGL2,
16
+ * which reads three. Every field stays a whole number of four-byte words, so a vertex does too.
17
+ */
18
+
19
+ /** The ways an attribute can travel other than as floats, as the key words name them. */
20
+ const PACKINGS = ['snorm16', 'unorm16', 'constant'] as const;
21
+
22
+ /** The pipeline key's word for one attribute's packing. Not a prefix of any other word. */
23
+ function word(name: string, packing: AttributePacking): string {
24
+ return `${name}~${packing}`;
25
+ }
26
+
27
+ /** The key's suffix for this packing: one word each attribute not travelling as floats. */
28
+ export function packingKey(packing: VertexPacking): string {
29
+ let key = '';
30
+ for (const name of PACKED_ATTRIBUTES) {
31
+ if (packing[name] !== 'float') key += `:${word(name, packing[name])}`;
32
+ }
33
+ return key;
34
+ }
35
+
36
+ /** Mark this packing in a pipeline's `present`, which is what `vertexBufferLayouts` reads. */
37
+ export function markPacking(present: Record<string, boolean>, packing: VertexPacking): void {
38
+ for (const name of PACKED_ATTRIBUTES) {
39
+ if (packing[name] !== 'float') present[word(name, packing[name])] = true;
40
+ }
41
+ }
42
+
43
+ /** Mark the packing a mesh's key names, for a pass that has the mesh and not its data. */
44
+ export function markPackingOfKey(present: Record<string, boolean>, key: string): void {
45
+ for (const name of PACKED_ATTRIBUTES) {
46
+ for (const packing of PACKINGS) {
47
+ if (key.includes(`:${word(name, packing)}`)) present[word(name, packing)] = true;
48
+ }
49
+ }
50
+ }
51
+
52
+ /** How `present` says this attribute travels: floats unless a word says otherwise. */
53
+ export function packingIn(
54
+ present: Readonly<Record<string, boolean>>,
55
+ name: string,
56
+ ): AttributePacking {
57
+ for (const packing of PACKINGS) {
58
+ if (present[word(name, packing)] === true) return packing;
59
+ }
60
+ return 'float';
61
+ }
62
+
63
+ /** The format an attribute is read with, packed this way, or `float` as given. */
64
+ export function packedFormat(float: GPUVertexFormat, packing: AttributePacking): GPUVertexFormat {
65
+ if (packing === 'snorm16') return 'snorm16x4';
66
+ if (packing === 'unorm16') return 'unorm16x4';
67
+ return float;
68
+ }
69
+
70
+ /** Bytes a vertex spends on one attribute read with `format`. */
71
+ export function formatBytes(format: GPUVertexFormat): number {
72
+ if (format === 'snorm16x4' || format === 'unorm16x4') return 8;
73
+ if (format === 'float32x4') return 16;
74
+ if (format === 'float32x3') return 12;
75
+ if (format === 'float32x2') return 8;
76
+ return 4;
77
+ }
78
+
79
+ /**
80
+ * One attribute of vertices `from` to `to` into its sixteen-bit field of the interleaved rows,
81
+ * `scale` being `SNORM16_ONE` or `UNORM16_ONE`. Rounded by `toFixed16`, as `fixedArray` rounds
82
+ * for the other backend; the range was checked when the packing was chosen.
83
+ *
84
+ * Unrolled by width, as `interleaveField` is and for its reason: the general loop was most of a
85
+ * heavy load's upload. Three and four are the widths a packed attribute has.
86
+ */
87
+ export function interleaveFixed(
88
+ rows: Int16Array | Uint16Array,
89
+ source: Float32Array,
90
+ width: number,
91
+ step: number,
92
+ fieldOffset: number,
93
+ from: number,
94
+ to: number,
95
+ scale: number,
96
+ ): void {
97
+ let write = from * step + fieldOffset;
98
+ const end = to * width;
99
+ if (width === 3) {
100
+ for (let read = from * 3; read < end; read += 3, write += step) {
101
+ rows[write] = toFixed16(source[read] as number, scale);
102
+ rows[write + 1] = toFixed16(source[read + 1] as number, scale);
103
+ rows[write + 2] = toFixed16(source[read + 2] as number, scale);
104
+ }
105
+ } else {
106
+ for (let read = from * 4; read < end; read += 4, write += step) {
107
+ rows[write] = toFixed16(source[read] as number, scale);
108
+ rows[write + 1] = toFixed16(source[read + 1] as number, scale);
109
+ rows[write + 2] = toFixed16(source[read + 2] as number, scale);
110
+ rows[write + 3] = toFixed16(source[read + 3] as number, scale);
111
+ }
112
+ }
113
+ }
@@ -20,6 +20,8 @@ export interface MediumLight {
20
20
  readonly sunShadow: number;
21
21
  /** World → light clip, **uncorrected**: the shader does its own `* 0.5 + 0.5`. */
22
22
  readonly lightViewProj: ReadonlyMat4;
23
+ /** The same for the sun's moving layer, whose shadow pass may be drawn with its own. */
24
+ readonly movingLightViewProj: ReadonlyMat4;
23
25
  /** The sun's static, moving and peeled maps, as one array. See `shadowMap.ts`. */
24
26
  readonly sunShadows: WebGLTexture;
25
27
  readonly peeledEnabled: boolean;
@@ -240,6 +242,11 @@ export class GlobalMediumPass {
240
242
  gl.uniform1i(u['uSteps'] ?? null, steps);
241
243
  gl.uniform1f(u['uSunShadow'] ?? null, light.sunShadow);
242
244
  gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, light.lightViewProj as Float32Array);
245
+ gl.uniformMatrix4fv(
246
+ u['uMovingLightViewProj'] ?? null,
247
+ false,
248
+ light.movingLightViewProj as Float32Array,
249
+ );
243
250
  gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, light.peeledEnabled ? 1 : 0);
244
251
 
245
252
  gl.drawArrays(gl.TRIANGLES, 0, 3);
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A mesh's indices in the narrowest type that holds them: sixteen bits where every vertex can be
3
+ * named in sixteen, thirty-two otherwise — half the index memory for every mesh of 65,536 vertices
4
+ * or fewer, which is most of them. Lossless because `validateMeshData` has already refused an
5
+ * index past the last vertex.
6
+ *
7
+ * **Padded to an even count**, so the sixteen-bit copy is a whole number of four-byte words: WebGPU
8
+ * writes a buffer only in whole words. The extra index is never drawn, since every draw counts the
9
+ * mesh's own indices. What would make this wrong is a draw that counts the buffer instead.
10
+ *
11
+ * **What it costs** is a copy of the indices while the mesh is built, let go once they are on the
12
+ * device; and every draw of the mesh has to say which width it holds, which is why the width
13
+ * travels with the mesh rather than being worked out where it is drawn.
14
+ */
15
+
16
+ /** The most vertices sixteen-bit indices can name: every index below 65,536. */
17
+ export const MAX_UINT16_VERTICES = 65536;
18
+
19
+ export function compactIndices(
20
+ indices: Uint32Array,
21
+ vertexCount: number,
22
+ ): Uint16Array | Uint32Array {
23
+ if (vertexCount > MAX_UINT16_VERTICES) return indices;
24
+ const out = new Uint16Array(indices.length + (indices.length % 2));
25
+ out.set(indices);
26
+ return out;
27
+ }
@@ -67,3 +67,18 @@ export function computeLightMatrix(
67
67
  // to express its residual depth tolerance in stable world-space metres.
68
68
  return radius * 6;
69
69
  }
70
+
71
+ /**
72
+ * The depth an orthographic light matrix spans, in metres: what `computeLightMatrix` returns, read
73
+ * back off any such matrix. Its depth row is the light's axis scaled by two over the span, so the
74
+ * span is two over that row's length — whatever focus, radius or bearing built it.
75
+ *
76
+ * **For a matrix the renderer is handed rather than one it built**: the sun's moving layer is read
77
+ * through the matrix its shadow pass was drawn with, and its depth tolerance and distance fade are
78
+ * in metres of that matrix. What would make this wrong is a perspective light, whose depth is not
79
+ * linear; the sun's never is.
80
+ */
81
+ export function orthographicDepthSpan(m: ArrayLike<number>): number {
82
+ const length = Math.hypot(m[2] as number, m[6] as number, m[10] as number);
83
+ return length > 0 ? 2 / length : 1;
84
+ }