@driftengine/core 4.8.2 → 4.8.4

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 (259) hide show
  1. package/CHANGELOG.json +127 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/meshConcat.js +2 -0
  4. package/dist/index.d.ts +11 -4
  5. package/dist/index.js +22 -1
  6. package/dist/math/scalar.js +1 -1
  7. package/dist/render/anisotropicLobe.d.ts +7 -0
  8. package/dist/render/anisotropicLobe.js +33 -0
  9. package/dist/render/backend/api.d.ts +41 -4
  10. package/dist/render/backend/materialChanges.d.ts +7 -2
  11. package/dist/render/backend/materialChanges.js +8 -3
  12. package/dist/render/backend/select.js +8 -0
  13. package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
  14. package/dist/render/backend/webgl2/clothTextures.js +93 -0
  15. package/dist/render/backend/webgl2/renderer.d.ts +159 -3
  16. package/dist/render/backend/webgl2/renderer.js +662 -80
  17. package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
  18. package/dist/render/backend/webgl2/skinScatter.js +256 -0
  19. package/dist/render/backend/webgpu/buffers.d.ts +25 -11
  20. package/dist/render/backend/webgpu/buffers.js +98 -49
  21. package/dist/render/backend/webgpu/causticsPass.js +2 -1
  22. package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
  23. package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
  24. package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
  25. package/dist/render/backend/webgpu/clothTextures.js +145 -0
  26. package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
  27. package/dist/render/backend/webgpu/compressedUpload.js +38 -0
  28. package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
  29. package/dist/render/backend/webgpu/computeHost.js +5 -0
  30. package/dist/render/backend/webgpu/depthPass.d.ts +29 -9
  31. package/dist/render/backend/webgpu/depthPass.js +43 -12
  32. package/dist/render/backend/webgpu/drawCommand.js +6 -2
  33. package/dist/render/backend/webgpu/filmPass.js +2 -1
  34. package/dist/render/backend/webgpu/flatPass.d.ts +43 -4
  35. package/dist/render/backend/webgpu/flatPass.js +111 -13
  36. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
  37. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
  38. package/dist/render/backend/webgpu/linePass.js +2 -1
  39. package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
  40. package/dist/render/backend/webgpu/motionPass.js +106 -9
  41. package/dist/render/backend/webgpu/panelPass.js +2 -1
  42. package/dist/render/backend/webgpu/particlePass.js +2 -1
  43. package/dist/render/backend/webgpu/plumePass.js +2 -1
  44. package/dist/render/backend/webgpu/postPass.d.ts +15 -3
  45. package/dist/render/backend/webgpu/postPass.js +25 -8
  46. package/dist/render/backend/webgpu/renderer.d.ts +123 -6
  47. package/dist/render/backend/webgpu/renderer.js +534 -58
  48. package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
  49. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
  50. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
  51. package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
  52. package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
  53. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
  54. package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
  55. package/dist/render/backend/webgpu/textPass.js +2 -1
  56. package/dist/render/backend/webgpu/waterPass.js +2 -1
  57. package/dist/render/backend/webgpu/windStreakPass.js +2 -1
  58. package/dist/render/boltBatch.js +2 -1
  59. package/dist/render/causticsRenderer.js +2 -1
  60. package/dist/render/clothBindingData.d.ts +41 -0
  61. package/dist/render/clothBindingData.js +72 -0
  62. package/dist/render/clothPlacement.d.ts +25 -0
  63. package/dist/render/clothPlacement.js +85 -0
  64. package/dist/render/compressedSource.d.ts +66 -0
  65. package/dist/render/compressedSource.js +138 -0
  66. package/dist/render/cutoutCaster.d.ts +5 -1
  67. package/dist/render/cutoutCaster.js +6 -0
  68. package/dist/render/cutoutDither.d.ts +51 -0
  69. package/dist/render/cutoutDither.js +90 -0
  70. package/dist/render/decalPass.js +2 -1
  71. package/dist/render/eyeRefraction.d.ts +35 -0
  72. package/dist/render/eyeRefraction.js +40 -0
  73. package/dist/render/glCompressed.d.ts +27 -0
  74. package/dist/render/glCompressed.js +53 -0
  75. package/dist/render/globalMediumPass.js +3 -2
  76. package/dist/render/hairLobes.d.ts +70 -0
  77. package/dist/render/hairLobes.js +120 -0
  78. package/dist/render/lightBudget.d.ts +22 -0
  79. package/dist/render/lightBudget.js +22 -0
  80. package/dist/render/lineBatch.js +5 -1
  81. package/dist/render/mesh.d.ts +7 -0
  82. package/dist/render/mesh.js +24 -0
  83. package/dist/render/oitPass.js +2 -1
  84. package/dist/render/particleBatch.js +3 -2
  85. package/dist/render/particlePool.d.ts +6 -2
  86. package/dist/render/plumeRenderer.js +3 -2
  87. package/dist/render/recon/motionVectors.d.ts +4 -2
  88. package/dist/render/recon/motionVectors.js +6 -3
  89. package/dist/render/renderQuality.d.ts +29 -2
  90. package/dist/render/renderQuality.js +5 -0
  91. package/dist/render/rendererHarness.js +4 -0
  92. package/dist/render/sceneCoverage.d.ts +54 -0
  93. package/dist/render/sceneCoverage.js +72 -0
  94. package/dist/render/sceneTarget.d.ts +12 -0
  95. package/dist/render/sceneTarget.js +14 -0
  96. package/dist/render/sdfTextRenderer.js +2 -1
  97. package/dist/render/shaders/clothBinding.d.ts +22 -0
  98. package/dist/render/shaders/clothBinding.js +80 -0
  99. package/dist/render/shaders/depth.js +45 -8
  100. package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
  101. package/dist/render/shaders/flat/anisotropic.js +68 -0
  102. package/dist/render/shaders/flat/eye.d.ts +1 -0
  103. package/dist/render/shaders/flat/eye.js +81 -0
  104. package/dist/render/shaders/flat/hair.d.ts +1 -0
  105. package/dist/render/shaders/flat/hair.js +101 -0
  106. package/dist/render/shaders/flat/index.js +42 -3
  107. package/dist/render/shaders/flat/main.d.ts +1 -1
  108. package/dist/render/shaders/flat/main.js +113 -17
  109. package/dist/render/shaders/flat/models.d.ts +14 -0
  110. package/dist/render/shaders/flat/models.js +273 -0
  111. package/dist/render/shaders/flat/preamble.js +26 -24
  112. package/dist/render/shaders/flat/skin.d.ts +1 -0
  113. package/dist/render/shaders/flat/skin.js +114 -0
  114. package/dist/render/shaders/generated/depth.wgsl.d.ts +66 -24
  115. package/dist/render/shaders/generated/depth.wgsl.js +66 -24
  116. package/dist/render/shaders/generated/flat.wgsl.d.ts +1815 -1704
  117. package/dist/render/shaders/generated/flat.wgsl.js +1853 -1742
  118. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
  121. package/dist/render/shaders/generated/particle.wgsl.js +3 -3
  122. package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
  123. package/dist/render/shaders/generated/rush.wgsl.js +1 -1
  124. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
  125. package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
  126. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
  127. package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
  128. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
  129. package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
  130. package/dist/render/shaders/line.d.ts +1 -1
  131. package/dist/render/shaders/outputTransform.d.ts +14 -1
  132. package/dist/render/shaders/outputTransform.js +23 -1
  133. package/dist/render/shaders/particle.d.ts +3 -3
  134. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  135. package/dist/render/shaders/recon/motion.wgsl.js +102 -0
  136. package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
  137. package/dist/render/shaders/rush.js +13 -2
  138. package/dist/render/shaders/sdfText.d.ts +1 -1
  139. package/dist/render/shaders/skinBlur.d.ts +1 -0
  140. package/dist/render/shaders/skinBlur.js +103 -0
  141. package/dist/render/shaders/skinning.d.ts +3 -1
  142. package/dist/render/shaders/skinning.js +31 -4
  143. package/dist/render/shaders/temporalResolve.js +12 -5
  144. package/dist/render/shadowCasters.d.ts +13 -1
  145. package/dist/render/skinBlur.d.ts +41 -0
  146. package/dist/render/skinBlur.js +49 -0
  147. package/dist/render/skinScatter.d.ts +67 -0
  148. package/dist/render/skinScatter.js +80 -0
  149. package/dist/render/skinnedClothRun.d.ts +26 -0
  150. package/dist/render/skinnedClothRun.js +84 -0
  151. package/dist/render/ssrPass.js +2 -1
  152. package/dist/render/surfaceModel.d.ts +129 -0
  153. package/dist/render/surfaceModel.js +152 -0
  154. package/dist/render/surfaceTexture.d.ts +55 -4
  155. package/dist/render/surfaceTexture.js +60 -46
  156. package/dist/render/textRenderer.js +2 -1
  157. package/dist/render/vertexDefaults.js +8 -0
  158. package/dist/render/waterRenderer.js +2 -1
  159. package/dist/render/windStreakRenderer.js +2 -1
  160. package/package.json +6 -6
  161. package/src/geometry/meshConcat.ts +2 -0
  162. package/src/index.ts +53 -1
  163. package/src/math/scalar.ts +1 -1
  164. package/src/render/anisotropicLobe.ts +41 -0
  165. package/src/render/backend/api.ts +52 -3
  166. package/src/render/backend/materialChanges.ts +8 -3
  167. package/src/render/backend/select.ts +8 -0
  168. package/src/render/backend/webgl2/clothTextures.ts +120 -0
  169. package/src/render/backend/webgl2/renderer.ts +750 -80
  170. package/src/render/backend/webgl2/skinScatter.ts +309 -0
  171. package/src/render/backend/webgpu/buffers.ts +137 -63
  172. package/src/render/backend/webgpu/causticsPass.ts +2 -1
  173. package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
  174. package/src/render/backend/webgpu/clothTextures.ts +194 -0
  175. package/src/render/backend/webgpu/compressedUpload.ts +65 -0
  176. package/src/render/backend/webgpu/computeHost.ts +25 -0
  177. package/src/render/backend/webgpu/depthPass.ts +43 -10
  178. package/src/render/backend/webgpu/drawCommand.ts +6 -2
  179. package/src/render/backend/webgpu/filmPass.ts +2 -1
  180. package/src/render/backend/webgpu/flatPass.ts +147 -8
  181. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
  182. package/src/render/backend/webgpu/linePass.ts +2 -1
  183. package/src/render/backend/webgpu/motionPass.ts +128 -9
  184. package/src/render/backend/webgpu/panelPass.ts +2 -1
  185. package/src/render/backend/webgpu/particlePass.ts +2 -1
  186. package/src/render/backend/webgpu/plumePass.ts +2 -1
  187. package/src/render/backend/webgpu/postPass.ts +25 -6
  188. package/src/render/backend/webgpu/renderer.ts +701 -52
  189. package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
  190. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
  191. package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
  192. package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
  193. package/src/render/backend/webgpu/textPass.ts +2 -1
  194. package/src/render/backend/webgpu/waterPass.ts +2 -1
  195. package/src/render/backend/webgpu/windStreakPass.ts +2 -1
  196. package/src/render/boltBatch.ts +2 -1
  197. package/src/render/causticsRenderer.ts +2 -1
  198. package/src/render/clothBindingData.ts +89 -0
  199. package/src/render/clothPlacement.ts +114 -0
  200. package/src/render/compressedSource.ts +198 -0
  201. package/src/render/cutoutCaster.ts +20 -2
  202. package/src/render/cutoutDither.ts +99 -0
  203. package/src/render/decalPass.ts +2 -1
  204. package/src/render/eyeRefraction.ts +72 -0
  205. package/src/render/glCompressed.ts +86 -0
  206. package/src/render/globalMediumPass.ts +3 -2
  207. package/src/render/hairLobes.ts +188 -0
  208. package/src/render/lightBudget.ts +25 -0
  209. package/src/render/lineBatch.ts +3 -1
  210. package/src/render/mesh.ts +23 -0
  211. package/src/render/oitPass.ts +2 -1
  212. package/src/render/particleBatch.ts +3 -2
  213. package/src/render/particlePool.ts +6 -2
  214. package/src/render/plumeRenderer.ts +3 -2
  215. package/src/render/recon/motionVectors.ts +6 -3
  216. package/src/render/renderQuality.ts +37 -2
  217. package/src/render/rendererHarness.ts +4 -0
  218. package/src/render/sceneCoverage.ts +78 -0
  219. package/src/render/sceneTarget.ts +21 -0
  220. package/src/render/sdfTextRenderer.ts +2 -1
  221. package/src/render/shaders/clothBinding.ts +81 -0
  222. package/src/render/shaders/depth.ts +45 -8
  223. package/src/render/shaders/flat/anisotropic.ts +68 -0
  224. package/src/render/shaders/flat/eye.ts +82 -0
  225. package/src/render/shaders/flat/hair.ts +102 -0
  226. package/src/render/shaders/flat/index.ts +42 -3
  227. package/src/render/shaders/flat/main.ts +113 -17
  228. package/src/render/shaders/flat/models.ts +286 -0
  229. package/src/render/shaders/flat/preamble.ts +26 -24
  230. package/src/render/shaders/flat/skin.ts +116 -0
  231. package/src/render/shaders/generated/depth.wgsl.ts +66 -24
  232. package/src/render/shaders/generated/flat.wgsl.ts +1853 -1742
  233. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  234. package/src/render/shaders/generated/particle.wgsl.ts +3 -3
  235. package/src/render/shaders/generated/rush.wgsl.ts +1 -1
  236. package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
  237. package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
  238. package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
  239. package/src/render/shaders/outputTransform.ts +24 -1
  240. package/src/render/shaders/recon/motion.wgsl.ts +102 -0
  241. package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
  242. package/src/render/shaders/rush.ts +13 -2
  243. package/src/render/shaders/skinBlur.ts +105 -0
  244. package/src/render/shaders/skinning.ts +31 -4
  245. package/src/render/shaders/temporalResolve.ts +12 -5
  246. package/src/render/shadowCasters.ts +13 -0
  247. package/src/render/skinBlur.ts +53 -0
  248. package/src/render/skinScatter.ts +92 -0
  249. package/src/render/skinnedClothRun.ts +128 -0
  250. package/src/render/ssrPass.ts +2 -1
  251. package/src/render/surfaceModel.ts +274 -0
  252. package/src/render/surfaceTexture.ts +134 -63
  253. package/src/render/textRenderer.ts +2 -1
  254. package/src/render/vertexDefaults.ts +8 -0
  255. package/src/render/waterRenderer.ts +2 -1
  256. package/src/render/windStreakRenderer.ts +2 -1
  257. package/dist/math/exact.d.ts +0 -76
  258. package/dist/math/exact.js +0 -327
  259. package/src/math/exact.ts +0 -340
@@ -0,0 +1,502 @@
1
+ /**
2
+ * A skinned cloth solved on the device: `ClothControl`'s schedule, over buffers rather than arrays.
3
+ *
4
+ * **The CPU solver's steps, as dispatches.** A step is `clothBegin`, then per substep
5
+ * `clothPredict`, every batch of every kind of constraint per iteration, `clothLimit` and
6
+ * `clothFinish`; then `clothBlend` while a reset blends back in. The particles never leave the
7
+ * device: a frame ends with `clothPublish` writing them, at the frame's `alpha`, into the particle
8
+ * texture the bound mesh's vertex stage reads — so a garment costs no upload a frame at all.
9
+ *
10
+ * **One submit a frame, everything it needs written before it.** `queue.writeBuffer` lands ahead of
11
+ * the whole submit rather than between two dispatches, which is the trap `AGENTS.md` (2026-08-27)
12
+ * records for draws and is no different for dispatches. So every step takes a slot of its own, at a
13
+ * dynamic offset, and the batches' ranges are slots written once; a frame that runs out of slots
14
+ * submits what it has first and goes on, and so does a pose, whose matrices are one buffer.
15
+ *
16
+ * **What it gives up**: single precision, and a dispatch per batch — a garment of nine colours and
17
+ * four iterations is about forty dispatches a step, which is cheap on a desktop part and is what the
18
+ * budget in `demo/dev/skinnedCloth.html` measures. Self-collision is accepted and not simulated, as on
19
+ * the CPU.
20
+ */
21
+ import { ClothControl, exactExp, validateClothSetup } from '@driftengine/physics';
22
+ import type { ClothDevice, SkinnedClothSetup } from '@driftengine/physics';
23
+
24
+ import { packCloth } from './clothSolvePack.ts';
25
+ import type { PackedCloth } from './clothSolvePack.ts';
26
+ import { GpuClothParticles } from './clothTextures.ts';
27
+ import { CLOTH_SOLVE_WGSL, CLOTH_WORKGROUP } from './shaders/clothSolve.wgsl.ts';
28
+ import { shaderModule } from './shaderModules.ts';
29
+ import { DYNAMIC_ALIGNMENT } from './uniformRing.ts';
30
+
31
+ const STORAGE = 0x80;
32
+ const UNIFORM = 0x40;
33
+ const COPY_DST = 0x8;
34
+ const COPY_SRC = 0x4;
35
+ const MAP_READ = 0x1;
36
+ const COMPUTE = 0x4;
37
+
38
+ /** Step slots a submit holds before it is sent and the ring starts again. */
39
+ const STEP_SLOTS = 64;
40
+ /** Floats in a step's slot that are written: see `Step` in the shader. */
41
+ const STEP_FLOATS = 32;
42
+ const CONSTANT_BYTES = 80;
43
+
44
+ const ENTRIES = [
45
+ 'clothPose',
46
+ 'clothRest',
47
+ 'clothBegin',
48
+ 'clothPredict',
49
+ 'clothDistance',
50
+ 'clothBending',
51
+ 'clothTether',
52
+ 'clothLimit',
53
+ 'clothFinish',
54
+ 'clothBlend',
55
+ 'clothPublish',
56
+ ] as const;
57
+ type Entry = (typeof ENTRIES)[number];
58
+
59
+ /** The layouts and one pipeline an entry point, built once a device and shared by every cloth. */
60
+ interface ClothKernels {
61
+ readonly layout: GPUBindGroupLayout;
62
+ readonly outputLayout: GPUBindGroupLayout;
63
+ readonly pipelines: Readonly<Record<Entry, GPUComputePipeline>>;
64
+ }
65
+
66
+ const kernelsByDevice = new WeakMap<GPUDevice, ClothKernels>();
67
+
68
+ function kernelsFor(device: GPUDevice): ClothKernels {
69
+ const held = kernelsByDevice.get(device);
70
+ if (held !== undefined) return held;
71
+ const uniform = (binding: number, dynamic: boolean): GPUBindGroupLayoutEntry => ({
72
+ binding,
73
+ visibility: COMPUTE,
74
+ buffer: { type: 'uniform', hasDynamicOffset: dynamic },
75
+ });
76
+ const storage = (binding: number, type: GPUBufferBindingType): GPUBindGroupLayoutEntry => ({
77
+ binding,
78
+ visibility: COMPUTE,
79
+ buffer: { type },
80
+ });
81
+ const layout = device.createBindGroupLayout({
82
+ label: 'cloth.solve',
83
+ entries: [
84
+ uniform(0, false),
85
+ uniform(1, true),
86
+ uniform(2, true),
87
+ storage(3, 'storage'),
88
+ storage(4, 'storage'),
89
+ storage(5, 'read-only-storage'),
90
+ storage(6, 'read-only-storage'),
91
+ storage(7, 'storage'),
92
+ storage(8, 'read-only-storage'),
93
+ ],
94
+ });
95
+ const outputLayout = device.createBindGroupLayout({
96
+ label: 'cloth.publish',
97
+ entries: [
98
+ {
99
+ binding: 0,
100
+ visibility: COMPUTE,
101
+ storageTexture: { access: 'write-only', format: 'rgba32float' },
102
+ },
103
+ ],
104
+ });
105
+ const module = shaderModule(device, { label: 'cloth.solve', code: CLOTH_SOLVE_WGSL });
106
+ const solve = device.createPipelineLayout({ bindGroupLayouts: [layout] });
107
+ const publish = device.createPipelineLayout({ bindGroupLayouts: [layout, outputLayout] });
108
+ const pipelines = {} as Record<Entry, GPUComputePipeline>;
109
+ for (const entry of ENTRIES) {
110
+ pipelines[entry] = device.createComputePipeline({
111
+ label: `cloth.${entry}`,
112
+ layout: entry === 'clothPublish' ? publish : solve,
113
+ compute: { module, entryPoint: entry },
114
+ });
115
+ }
116
+ const kernels = { layout, outputLayout, pipelines };
117
+ kernelsByDevice.set(device, kernels);
118
+ return kernels;
119
+ }
120
+
121
+ /** One garment's solver on the device, and the particle textures it publishes into. */
122
+ export class GpuSkinnedCloth {
123
+ /** The particles a bound mesh draws by: see `RendererApi.setCloth`. */
124
+ readonly particles: GpuClothParticles;
125
+ readonly count: number;
126
+
127
+ private readonly control: ClothControl;
128
+ private readonly packed: PackedCloth;
129
+ private readonly kernels: ClothKernels;
130
+ private readonly buffers: GPUBuffer[] = [];
131
+ private readonly state: GPUBuffer;
132
+ private readonly frameData: GPUBuffer;
133
+ private readonly stepSlots: GPUBuffer;
134
+ private readonly group: GPUBindGroup;
135
+ private readonly outputs: readonly [GPUBindGroup, GPUBindGroup];
136
+ private readonly frameStaging: Float32Array<ArrayBuffer>;
137
+ private readonly stepStaging = new ArrayBuffer(STEP_FLOATS * 4);
138
+ private readonly stepFloats = new Float32Array(this.stepStaging);
139
+ private readonly stepWords = new Uint32Array(this.stepStaging);
140
+ /** Each batch's slot, by kind and by whether it opens a substep's iterations. */
141
+ private readonly batchSlot: { distance: number; bending: number; tether: number };
142
+ /** The air the steps drag toward: the set-up's until `setWind`, written into every step. */
143
+ private readonly wind = new Float32Array(3);
144
+ private readonly groups: number;
145
+ private readonly substeps: number;
146
+ private readonly iterations: number;
147
+
148
+ private encoder: GPUCommandEncoder | null = null;
149
+ private pass: GPUComputePassEncoder | null = null;
150
+ private slot = 0;
151
+ /** Where the pose a step starts from and the one it moves toward sit in `pose`, in floats. */
152
+ private fromAt = 0;
153
+ private toAt = 0;
154
+
155
+ constructor(
156
+ private readonly device: GPUDevice,
157
+ setup: SkinnedClothSetup,
158
+ ) {
159
+ validateClothSetup(setup);
160
+ const packed = packCloth(setup);
161
+ this.packed = packed;
162
+ const n = packed.count;
163
+ this.count = n;
164
+ this.kernels = kernelsFor(device);
165
+ this.particles = new GpuClothParticles(device, n, true);
166
+ this.groups = Math.ceil(n / CLOTH_WORKGROUP);
167
+
168
+ const solver: ClothDevice = {
169
+ targetsChanged: (change) => {
170
+ this.targetsChanged(change);
171
+ },
172
+ rest: () => {
173
+ this.rest();
174
+ },
175
+ step: (fraction, blend) => {
176
+ this.step(fraction, blend);
177
+ },
178
+ };
179
+ this.control = new ClothControl(setup, solver, false);
180
+ const parameters = this.control.parameters;
181
+ this.wind.set(parameters.wind);
182
+ this.substeps = parameters.substeps;
183
+ this.iterations = parameters.iterations;
184
+
185
+ const buffer = (label: string, bytes: number, usage: number): GPUBuffer => {
186
+ const made = device.createBuffer({
187
+ label: `cloth.${label}`,
188
+ size: Math.max(16, bytes),
189
+ usage,
190
+ });
191
+ this.buffers.push(made);
192
+ return made;
193
+ };
194
+ const constants = buffer('constants', CONSTANT_BYTES, UNIFORM | COPY_DST);
195
+ this.stepSlots = buffer('steps', STEP_SLOTS * DYNAMIC_ALIGNMENT, UNIFORM | COPY_DST);
196
+ const batchFloats = this.writeBatches();
197
+ const batches = buffer('batches', batchFloats.byteLength, UNIFORM | COPY_DST);
198
+ this.state = buffer('state', n * 12 * 4, STORAGE | COPY_DST | COPY_SRC);
199
+ const pose = buffer('pose', n * 9 * 4, STORAGE);
200
+ const statics = buffer('statics', packed.statics.byteLength, STORAGE | COPY_DST);
201
+ const constraints = buffer('constraints', packed.constraints.byteLength, STORAGE | COPY_DST);
202
+ const lambda = buffer('lambda', (packed.distances + packed.bendings) * 4, STORAGE);
203
+ this.frameStaging = new Float32Array(packed.joints * 16 + packed.colliders * 8);
204
+ this.frameData = buffer('frame', this.frameStaging.byteLength, STORAGE | COPY_DST);
205
+
206
+ const queue = device.queue;
207
+ queue.writeBuffer(constants, 0, this.constants());
208
+ queue.writeBuffer(batches, 0, batchFloats);
209
+ queue.writeBuffer(statics, 0, packed.statics as Float32Array<ArrayBuffer>);
210
+ if (packed.constraints.byteLength > 0) {
211
+ queue.writeBuffer(constraints, 0, packed.constraints as Uint32Array<ArrayBuffer>);
212
+ }
213
+ /* The rest pose until the first `setPose`, which resets the cloth to its own. */
214
+ const state = new Float32Array(n * 12);
215
+ state.set(setup.positions, 0);
216
+ state.set(setup.positions, n * 9);
217
+ queue.writeBuffer(this.state, 0, state);
218
+
219
+ const slot = (bufferOf: GPUBuffer): GPUBufferBinding => ({
220
+ buffer: bufferOf,
221
+ size: DYNAMIC_ALIGNMENT,
222
+ });
223
+ this.group = device.createBindGroup({
224
+ label: 'cloth.solve',
225
+ layout: this.kernels.layout,
226
+ entries: [
227
+ { binding: 0, resource: { buffer: constants } },
228
+ { binding: 1, resource: slot(this.stepSlots) },
229
+ { binding: 2, resource: slot(batches) },
230
+ { binding: 3, resource: { buffer: this.state } },
231
+ { binding: 4, resource: { buffer: pose } },
232
+ { binding: 5, resource: { buffer: statics } },
233
+ { binding: 6, resource: { buffer: constraints } },
234
+ { binding: 7, resource: { buffer: lambda } },
235
+ { binding: 8, resource: { buffer: this.frameData } },
236
+ ],
237
+ });
238
+ const output = (k: number): GPUBindGroup =>
239
+ device.createBindGroup({
240
+ label: 'cloth.publish',
241
+ layout: this.kernels.outputLayout,
242
+ entries: [{ binding: 0, resource: this.particles.viewOf(k) }],
243
+ });
244
+ this.outputs = [output(0), output(1)];
245
+ this.batchSlot = {
246
+ distance: 0,
247
+ bending: (packed.distanceBatches.length - 1) * 2,
248
+ tether: (packed.distanceBatches.length - 1 + packed.bendingBatches.length - 1) * 2,
249
+ };
250
+ }
251
+
252
+ /**
253
+ * One frame: the rig's pose, `dt` of the caller's time in whole fixed steps, and the particles at
254
+ * the frame's `alpha` published for the draws that follow, as the particles of `frame`.
255
+ */
256
+ advance(globals: Float32Array, model: Float32Array, dt: number, frame: number): void {
257
+ this.control.setPose(globals, model);
258
+ this.control.advance(dt);
259
+ const view = this.particles.publish(frame);
260
+ this.dispatch('clothPublish', this.takeSlot(0, 0, this.control.alpha, null), 0, view);
261
+ this.flush();
262
+ }
263
+
264
+ /** The frame's sample of the scene's wind, for the steps to come. See `SkinnedCloth.setWind`. */
265
+ setWind(x: number, y: number, z: number): void {
266
+ this.wind[0] = x;
267
+ this.wind[1] = y;
268
+ this.wind[2] = z;
269
+ }
270
+
271
+ /** Back to the skinned pose, settled; the next `advance` publishes it. */
272
+ reset(): void {
273
+ this.control.reset();
274
+ }
275
+
276
+ /**
277
+ * The particles where the last step left them, read back: for the parity check, which compares
278
+ * them with the CPU solver's. Not for a frame — it waits on the device.
279
+ */
280
+ async read(): Promise<Float32Array> {
281
+ this.flush();
282
+ const bytes = this.count * 12;
283
+ const readback = this.device.createBuffer({
284
+ label: 'cloth.readback',
285
+ size: bytes,
286
+ usage: MAP_READ | COPY_DST,
287
+ });
288
+ const encoder = this.device.createCommandEncoder({ label: 'cloth.read' });
289
+ encoder.copyBufferToBuffer(this.state, 0, readback, 0, bytes);
290
+ this.device.queue.submit([encoder.finish()]);
291
+ await readback.mapAsync(MAP_READ);
292
+ const out = new Float32Array(readback.getMappedRange().slice(0));
293
+ readback.unmap();
294
+ readback.destroy();
295
+ return out;
296
+ }
297
+
298
+ /** Its buffers; the particle textures are the renderer's to retire with the frame. */
299
+ dispose(): void {
300
+ this.flush();
301
+ for (const made of this.buffers) made.destroy();
302
+ }
303
+
304
+ private targetsChanged(change: 'posed' | 'settled' | 'consumed'): void {
305
+ if (change !== 'posed') {
306
+ this.fromAt = this.toAt;
307
+ return;
308
+ }
309
+ /*
310
+ * The pose's matrices and colliders are written ahead of the next submit, so anything already
311
+ * recorded — a reset's settle steps, which read the colliders — is sent first, or it would run
312
+ * against this pose rather than its own.
313
+ */
314
+ this.flush();
315
+ this.writeFrameData();
316
+ this.fromAt = this.toAt;
317
+ this.toAt = this.toAt === 0 ? this.count * 3 : 0;
318
+ this.dispatch('clothPose', this.takeSlot(0, 0, 0, null), 0, -1);
319
+ }
320
+
321
+ private rest(): void {
322
+ this.dispatch('clothRest', this.takeSlot(1, 0, 0, null), 0, -1);
323
+ }
324
+
325
+ private step(fraction: number, blend: number): void {
326
+ const at = this.takeSlot(fraction, blend, 0, this.control.targets);
327
+ this.dispatch('clothBegin', at, 0, -1);
328
+ const { distanceBatches, bendingBatches, tetherBatches } = this.packed;
329
+ for (let s = 0; s < this.substeps; s++) {
330
+ this.dispatch('clothPredict', at, 0, -1);
331
+ for (let pass = 0; pass < this.iterations; pass++) {
332
+ const first = pass === 0 ? 1 : 0;
333
+ this.batches('clothDistance', distanceBatches, this.batchSlot.distance, first, at);
334
+ this.batches('clothBending', bendingBatches, this.batchSlot.bending, first, at);
335
+ this.batches('clothTether', tetherBatches, this.batchSlot.tether, first, at);
336
+ }
337
+ this.dispatch('clothLimit', at, 0, -1);
338
+ this.dispatch('clothFinish', at, 0, -1);
339
+ }
340
+ if (blend > 0) this.dispatch('clothBlend', at, 0, -1);
341
+ }
342
+
343
+ /** Every batch of one kind, in colour order, each a dispatch over its range. */
344
+ private batches(
345
+ entry: Entry,
346
+ batches: Uint32Array,
347
+ firstSlot: number,
348
+ first: number,
349
+ stepAt: number,
350
+ ): void {
351
+ for (let b = 0; b + 1 < batches.length; b++) {
352
+ const size = (batches[b + 1] as number) - (batches[b] as number);
353
+ if (size === 0) continue;
354
+ const slot = firstSlot + b * 2 + first;
355
+ this.dispatch(entry, stepAt, slot * DYNAMIC_ALIGNMENT, -1, Math.ceil(size / CLOTH_WORKGROUP));
356
+ }
357
+ }
358
+
359
+ private dispatch(
360
+ entry: Entry,
361
+ stepAt: number,
362
+ batchAt: number,
363
+ output: number,
364
+ groups = this.groups,
365
+ ): void {
366
+ const pass = this.passFor();
367
+ pass.setPipeline(this.kernels.pipelines[entry]);
368
+ pass.setBindGroup(0, this.group, [stepAt, batchAt]);
369
+ if (output >= 0) pass.setBindGroup(1, this.outputs[output] as GPUBindGroup);
370
+ pass.dispatchWorkgroups(groups);
371
+ }
372
+
373
+ private passFor(): GPUComputePassEncoder {
374
+ if (this.pass === null) {
375
+ this.encoder = this.device.createCommandEncoder({ label: 'cloth' });
376
+ this.pass = this.encoder.beginComputePass({ label: 'cloth' });
377
+ }
378
+ return this.pass;
379
+ }
380
+
381
+ /** Send what has been recorded: every slot it reads was written ahead of it on the queue. */
382
+ private flush(): void {
383
+ const { pass, encoder } = this;
384
+ if (pass === null || encoder === null) return;
385
+ pass.end();
386
+ this.device.queue.submit([encoder.finish()]);
387
+ this.pass = null;
388
+ this.encoder = null;
389
+ this.slot = 0;
390
+ }
391
+
392
+ /**
393
+ * A step's slot: its fraction, blend and alpha, the poses it reads, and — where `targets` is
394
+ * given — the share of the character's motion the cloth is carried by, as `carryParticles` takes
395
+ * it. Returns the slot's byte offset.
396
+ */
397
+ private takeSlot(
398
+ fraction: number,
399
+ blend: number,
400
+ alpha: number,
401
+ targets: { readonly modelBefore: Float32Array; readonly modelNow: Float32Array } | null,
402
+ ): number {
403
+ if (this.slot === STEP_SLOTS) this.flush();
404
+ const f = this.stepFloats;
405
+ const u = this.stepWords;
406
+ f.fill(0);
407
+ f[0] = fraction;
408
+ f[1] = blend;
409
+ f[2] = alpha;
410
+ u[4] = this.fromAt;
411
+ u[5] = this.toAt;
412
+ f.set(this.wind, 28);
413
+ if (targets !== null) this.writeCarry(targets.modelBefore, targets.modelNow);
414
+ const at = this.slot * DYNAMIC_ALIGNMENT;
415
+ this.device.queue.writeBuffer(this.stepSlots, at, this.stepStaging);
416
+ this.slot++;
417
+ return at;
418
+ }
419
+
420
+ /** `carryParticles`' rotation, origin and move, into the step's slot; carry 0 where none. */
421
+ private writeCarry(previous: Float32Array, next: Float32Array): void {
422
+ const { linearInertia, angularInertia } = this.control.parameters;
423
+ const turn = 1 - angularInertia;
424
+ const move = 1 - linearInertia;
425
+ if (turn === 0 && move === 0) return;
426
+ const f = this.stepFloats;
427
+ this.stepWords[3] = 1;
428
+ /* Column c of R = R_next × R_previousᵀ, at turn0 … turn2. */
429
+ for (let col = 0; col < 3; col++) {
430
+ for (let row = 0; row < 3; row++) {
431
+ let sum = 0;
432
+ for (let k = 0; k < 3; k++) {
433
+ sum += (next[k * 4 + row] as number) * (previous[k * 4 + col] as number);
434
+ }
435
+ f[8 + col * 4 + row] = sum;
436
+ }
437
+ }
438
+ for (let k = 0; k < 3; k++) {
439
+ f[20 + k] = previous[12 + k] as number;
440
+ f[24 + k] = ((next[12 + k] as number) - (previous[12 + k] as number)) * move;
441
+ }
442
+ f[23] = turn;
443
+ }
444
+
445
+ /** The pose's skin matrices — or the model, for a cloth with no rig — and its colliders. */
446
+ private writeFrameData(): void {
447
+ const targets = this.control.targets;
448
+ const out = this.frameStaging;
449
+ const joints = this.packed.joints;
450
+ out.set(this.packed.rigged && targets.skin !== null ? targets.skin : targets.model, 0);
451
+ const colliders = this.packed.colliders;
452
+ out.set(targets.colliderEnds, joints * 16);
453
+ out.set(targets.colliderRadii, joints * 16 + colliders * 6);
454
+ if (out.byteLength > 0) this.device.queue.writeBuffer(this.frameData, 0, out);
455
+ }
456
+
457
+ private constants(): ArrayBuffer {
458
+ const p = this.control.parameters;
459
+ const packed = this.packed;
460
+ const bytes = new ArrayBuffer(CONSTANT_BYTES);
461
+ const u = new Uint32Array(bytes);
462
+ const f = new Float32Array(bytes);
463
+ const h = p.step / p.substeps;
464
+ u[0] = packed.count;
465
+ u[1] = packed.distances;
466
+ u[2] = packed.bendings;
467
+ u[3] = packed.joints;
468
+ u[4] = packed.colliders;
469
+ u[5] = packed.limits;
470
+ u[6] = this.particles.width;
471
+ f[8] = h;
472
+ f[9] = 1 / h;
473
+ f[10] = 1 / (h * h);
474
+ f[11] = exactExp(p.damping * h);
475
+ f[12] = exactExp(p.drag * h);
476
+ f[13] = p.maxDistanceScale;
477
+ f[14] = p.margin;
478
+ f.set(p.gravity, 16);
479
+ return bytes;
480
+ }
481
+
482
+ /** Each batch's range, twice — opening a substep's iterations and not — at a slot each. */
483
+ private writeBatches(): Uint32Array<ArrayBuffer> {
484
+ const { distanceBatches, bendingBatches, tetherBatches } = this.packed;
485
+ const kinds = [distanceBatches, bendingBatches, tetherBatches];
486
+ const slots = kinds.reduce((sum, batches) => sum + (batches.length - 1) * 2, 0);
487
+ const words = new Uint32Array((Math.max(1, slots) * DYNAMIC_ALIGNMENT) / 4);
488
+ let slot = 0;
489
+ for (const batches of kinds) {
490
+ for (let b = 0; b + 1 < batches.length; b++) {
491
+ for (let first = 0; first < 2; first++) {
492
+ const at = (slot * DYNAMIC_ALIGNMENT) / 4;
493
+ words[at] = batches[b] as number;
494
+ words[at + 1] = batches[b + 1] as number;
495
+ words[at + 2] = first;
496
+ slot++;
497
+ }
498
+ }
499
+ }
500
+ return words;
501
+ }
502
+ }
@@ -9,6 +9,7 @@ import type { UniformFields } from './scatterPass.ts';
9
9
  import { DEPTH_FORMAT } from './flatPass.ts';
10
10
  import type { PipelineCache } from './pipelineCache.ts';
11
11
  import { shaderModule } from './shaderModules.ts';
12
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
12
13
 
13
14
  /**
14
15
  * The line pass: a polyline with a real width, four vertices a segment.
@@ -222,7 +223,7 @@ export function linePipeline(
222
223
  dstFactor: 'one-minus-src-alpha',
223
224
  operation: 'add',
224
225
  },
225
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
226
+ alpha: SCENE_ALPHA_COVERS,
226
227
  },
227
228
  },
228
229
  ],