@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
@@ -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
  `;
@@ -29,8 +29,8 @@ import { SkinPaletteTexture } from './skinPaletteTexture.ts';
29
29
  * different rig at every set — the comparison is what tells the two apart.
30
30
  *
31
31
  * **It was capped at 128 until 4.8.6, and the cap declined draws.** A stage of three dozen idling
32
- * extras beside two fighters filled it in one frame and every skinned draw past it was refused, so
33
- * the consumer stood its crowd in the bind pose. The ring now holds as many palettes as a frame
32
+ * extras beside two characters filled it in one frame and every skinned draw past it was refused,
33
+ * so the consumer stood its crowd in the bind pose. The ring now holds as many palettes as a frame
34
34
  * holds draws (`RenderQuality.drawsPerFrame`), since no frame can bind more palettes than it draws;
35
35
  * a slot still allocates its texture only the first time a frame reaches it, so a scene with two
36
36
  * characters holds two. **What it costs** is a texture and a cached bind group a slot, 1.5 KB for a
@@ -46,8 +46,12 @@ import { SkinPaletteTexture } from './skinPaletteTexture.ts';
46
46
  */
47
47
  export class SkinPaletteRing {
48
48
  private readonly slots: SkinPaletteTexture[] = [];
49
- /** What each slot was last written with, for the comparison that lets a pass reuse it. */
49
+ /**
50
+ * What each slot was last written with, for the comparison that lets a pass reuse it: a copy
51
+ * that grows with the largest rig the slot has held, and how much of it the last rig filled.
52
+ */
50
53
  private readonly held: Float32Array[] = [];
54
+ private readonly heldLength: number[] = [];
51
55
  /** The slot each palette array last went into, by identity. Weak, so a caller's array can go. */
52
56
  private readonly lastSlot = new WeakMap<Float32Array, number>();
53
57
  private used = 0;
@@ -119,12 +123,13 @@ export class SkinPaletteRing {
119
123
  slot.update(device, palette);
120
124
  this.uploads += 1;
121
125
  let copy = this.held[at];
122
- if (copy === undefined || copy.length !== palette.length) {
123
- /* Once per slot and joint count, the way the slot's own texture is: never per frame. */
126
+ if (copy === undefined || copy.length < palette.length) {
127
+ /* Once per slot and larger rig, the way the slot's own texture is: never per frame. */
124
128
  copy = new Float32Array(palette.length);
125
129
  this.held[at] = copy;
126
130
  }
127
131
  copy.set(palette);
132
+ this.heldLength[at] = palette.length;
128
133
  this.lastSlot.set(palette, at);
129
134
  return at;
130
135
  }
@@ -138,14 +143,15 @@ export class SkinPaletteRing {
138
143
  for (const slot of this.slots) slot.dispose();
139
144
  this.slots.length = 0;
140
145
  this.held.length = 0;
146
+ this.heldLength.length = 0;
141
147
  this.used = 0;
142
148
  }
143
149
 
144
150
  /** Whether a slot was last written with exactly these numbers. */
145
151
  private holds(slot: number, palette: Float32Array): boolean {
146
152
  const copy = this.held[slot];
147
- if (copy === undefined || copy.length !== palette.length) return false;
148
- for (let i = 0; i < copy.length; i++) if (copy[i] !== palette[i]) return false;
153
+ if (copy === undefined || this.heldLength[slot] !== palette.length) return false;
154
+ for (let i = 0; i < palette.length; i++) if (copy[i] !== palette[i]) return false;
149
155
  return true;
150
156
  }
151
157
  }
@@ -22,6 +22,12 @@ const TEXTURE_USAGE = {
22
22
 
23
23
  export class SkinPaletteTexture {
24
24
  private texture: GPUTexture | null = null;
25
+ /**
26
+ * How many joints the texture has room for, which is the most any rig in this slot has had.
27
+ * **It grows and never shrinks**: the shader reads a joint by index, so a texture wider than the
28
+ * rig is read exactly as one that fits, and a slot that a 10-joint prop and a 296-joint character
29
+ * take in turn keeps one texture rather than making one at every change of rig.
30
+ */
25
31
  private joints = 0;
26
32
  /**
27
33
  * The view, cached rather than created per call.
@@ -38,7 +44,7 @@ export class SkinPaletteTexture {
38
44
  const joints = palette.length / 16;
39
45
  const width = paletteTextureWidth(joints);
40
46
 
41
- if (this.texture === null || this.joints !== joints) {
47
+ if (this.texture === null || joints > this.joints) {
42
48
  this.texture?.destroy();
43
49
  this.cachedView = null;
44
50
  this.texture = device.createTexture({