@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.
- package/CHANGELOG.json +133 -11
- package/README.md +43 -38
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -4
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/backend/api.d.ts +10 -3
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +189 -4
- package/dist/render/backend/webgl2/renderer.js +614 -66
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +24 -4
- package/dist/render/backend/webgpu/buffers.js +116 -59
- package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
- package/dist/render/backend/webgpu/clothKernels.js +76 -0
- package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
- package/dist/render/backend/webgpu/clothOps.js +78 -0
- package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
- package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
- package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
- package/dist/render/backend/webgpu/clothSetPack.js +144 -0
- package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/drawCommand.d.ts +20 -4
- package/dist/render/backend/webgpu/drawCommand.js +11 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
- package/dist/render/backend/webgpu/flatPass.js +137 -20
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
- package/dist/render/backend/webgpu/pipelineCache.js +46 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +200 -35
- package/dist/render/backend/webgpu/renderer.js +993 -168
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +242 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/glCompressed.js +83 -0
- package/dist/render/globalMediumPass.d.ts +2 -0
- package/dist/render/globalMediumPass.js +1 -0
- package/dist/render/indexWidth.d.ts +17 -0
- package/dist/render/indexWidth.js +23 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +14 -0
- package/dist/render/mesh.js +44 -14
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/renderQuality.d.ts +30 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.d.ts +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/screenSpaceReflection.d.ts +2 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/flat/directionalShadow.js +84 -10
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +3 -0
- package/dist/render/shaders/flat/index.d.ts +21 -0
- package/dist/render/shaders/flat/index.js +42 -7
- package/dist/render/shaders/flat/layered.d.ts +19 -0
- package/dist/render/shaders/flat/layered.js +115 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +58 -21
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/movingSun.d.ts +21 -0
- package/dist/render/shaders/flat/movingSun.js +46 -0
- package/dist/render/shaders/flat/normalMap.d.ts +6 -2
- package/dist/render/shaders/flat/normalMap.js +14 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +42 -20
- package/dist/render/shaders/flat/pointShadow.js +2 -2
- package/dist/render/shaders/flat/preamble.js +59 -27
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3616 -3042
- package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
- package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
- package/dist/render/shaders/globalMedium.js +9 -1
- package/dist/render/shaders/lightVolume.js +9 -1
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceLayers.d.ts +39 -0
- package/dist/render/surfaceLayers.js +26 -0
- package/dist/render/surfaceOverlay.d.ts +16 -3
- package/dist/render/surfaceOverlay.js +11 -6
- package/dist/render/surfaceProjection.d.ts +34 -0
- package/dist/render/surfaceProjection.js +30 -0
- package/dist/render/surfaceTexture.d.ts +68 -7
- package/dist/render/surfaceTexture.js +56 -17
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/package.json +6 -6
- package/src/index.ts +12 -4
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/backend/api.ts +13 -3
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +723 -79
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +195 -70
- package/src/render/backend/webgpu/clothKernels.ts +86 -0
- package/src/render/backend/webgpu/clothOps.ts +86 -0
- package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
- package/src/render/backend/webgpu/clothSetPack.ts +197 -0
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/drawCommand.ts +25 -4
- package/src/render/backend/webgpu/flatPass.ts +212 -41
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +53 -7
- package/src/render/backend/webgpu/renderer.ts +1201 -202
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +364 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/glCompressed.ts +83 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +72 -14
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/screenSpaceReflection.ts +2 -1
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/flat/directionalShadow.ts +84 -10
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +63 -7
- package/src/render/shaders/flat/layered.ts +115 -0
- package/src/render/shaders/flat/main.ts +58 -21
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +14 -3
- package/src/render/shaders/flat/overlay.ts +42 -20
- package/src/render/shaders/flat/pointShadow.ts +2 -2
- package/src/render/shaders/flat/preamble.ts +59 -27
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
- package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
- package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
- package/src/render/shaders/globalMedium.ts +9 -1
- package/src/render/shaders/lightVolume.ts +9 -1
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceLayers.ts +57 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +124 -27
- package/src/render/vertexPacking.ts +109 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
- 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
|
-
* `
|
|
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,6 +28,13 @@
|
|
|
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 declare const CLOTH_WORKGROUP = 64;
|
|
33
|
-
export declare const CLOTH_SOLVE_WGSL = "\nstruct Constants {\n count: u32,\n distances: u32,\n bendings: u32,\n joints: u32,\n colliders: u32,\n /* 1 a max distance, 2 a backstop, 4 a frontstop: which limits the set-up has at all. */\n limits: u32,\n width: u32,\n pad0: u32,\n h: f32,\n invH: f32,\n invH2: f32,\n keep: f32,\n dragKeep: f32,\n maxDistanceScale: f32,\n margin: f32,\n pad1: f32,\n gravity: vec4<f32>,\n}\n\n/* One step's numbers, at a dynamic offset: every step of a frame has its own slot. */\nstruct Step {\n fraction: f32,\n blend: f32,\n alpha: f32,\n carry: u32,\n /* Where the pose a step starts from and the one it moves toward sit in pose, in floats. */\n fromAt: u32,\n toAt: u32,\n pad0: u32,\n pad1: u32,\n /* The character's turn since the last step, by columns; origin.w is the share carried. */\n turn0: vec4<f32>,\n turn1: vec4<f32>,\n turn2: vec4<f32>,\n origin: vec4<f32>,\n shift: vec4<f32>,\n /* The frame's wind: one wind, sampled once a frame, so a step's rather than the set-up's. */\n wind: vec4<f32>,\n}\n\n/* One batch of one kind of constraint, at a dynamic offset; first resets the multipliers. */\nstruct Batch {\n begin: u32,\n end: u32,\n first: u32,\n pad: u32,\n}\n\n@group(0) @binding(0) var<uniform> c: Constants;\n@group(0) @binding(1) var<uniform> pace: Step;\n@group(0) @binding(2) var<uniform> batch: Batch;\n@group(0) @binding(3) var<storage, read_write> state: array<f32>;\n@group(0) @binding(4) var<storage, read_write> pose: array<f32>;\n@group(0) @binding(5) var<storage, read> statics: array<f32>;\n@group(0) @binding(6) var<storage, read> constraints: array<u32>;\n@group(0) @binding(7) var<storage, read_write> lambda: array<f32>;\n@group(0) @binding(8) var<storage, read> frame: array<f32>;\n\nfn stateAt(section: u32, i: u32) -> u32 {\n return section * 3u * c.count + i * 3u;\n}\n\nfn load(at: u32) -> vec3<f32> {\n return vec3<f32>(state[at], state[at + 1u], state[at + 2u]);\n}\n\nfn store(at: u32, v: vec3<f32>) {\n state[at] = v.x;\n state[at + 1u] = v.y;\n state[at + 2u] = v.z;\n}\n\nfn poseOf(at: u32) -> vec3<f32> {\n return vec3<f32>(pose[at], pose[at + 1u], pose[at + 2u]);\n}\n\n/* Sections of state. */\nconst POSITION = 0u;\nconst PREVIOUS = 1u;\nconst VELOCITY = 2u;\nconst START = 3u;\n\nfn inverseMass(i: u32) -> f32 {\n return statics[i];\n}\n\n/* Where skinning puts particle i, fraction of the way from the last pose to the latest. */\nfn skinnedAt(i: u32) -> vec3<f32> {\n let a = poseOf(pace.fromAt + i * 3u);\n let b = poseOf(pace.toAt + i * 3u);\n return a + (b - a) * pace.fraction;\n}\n\nfn skinMatrix(j: u32) -> mat4x4<f32> {\n let at = j * 16u;\n return mat4x4<f32>(\n vec4<f32>(frame[at], frame[at + 1u], frame[at + 2u], frame[at + 3u]),\n vec4<f32>(frame[at + 4u], frame[at + 5u], frame[at + 6u], frame[at + 7u]),\n vec4<f32>(frame[at + 8u], frame[at + 9u], frame[at + 10u], frame[at + 11u]),\n vec4<f32>(frame[at + 12u], frame[at + 13u], frame[at + 14u], frame[at + 15u]));\n}\n\n/* skinParticles: each particle's skinned position and normal by up to eight influences. */\n@compute @workgroup_size(64)\nfn clothPose(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let n = c.count;\n let restAt = 7u * n + i * 3u;\n let normalAt = 10u * n + i * 3u;\n let x = vec4<f32>(statics[restAt], statics[restAt + 1u], statics[restAt + 2u], 1.0);\n let q0 = vec3<f32>(statics[normalAt], statics[normalAt + 1u], statics[normalAt + 2u]);\n var p = vec3<f32>(0.0);\n var q = vec3<f32>(0.0);\n for (var half = 0u; half < 2u; half++) {\n for (var k = 0u; k < 4u; k++) {\n let at = 13u * n + i * 16u + half * 8u;\n let w = statics[at + 4u + k];\n if (w == 0.0) {\n continue;\n }\n let m = skinMatrix(u32(statics[at + k]));\n p += w * (m * x).xyz;\n q += w * (mat3x3<f32>(m[0].xyz, m[1].xyz, m[2].xyz) * q0);\n }\n }\n let to = pace.toAt + i * 3u;\n pose[to] = p.x;\n pose[to + 1u] = p.y;\n pose[to + 2u] = p.z;\n let len = sqrt(dot(q, q));\n let inv = select(0.0, 1.0 / len, len > 1e-12);\n let normal = 6u * n + i * 3u;\n pose[normal] = q.x * inv;\n pose[normal + 1u] = q.y * inv;\n pose[normal + 2u] = q.z * inv;\n}\n\n/* SkinnedCloth.rest: every particle at the latest skinned position, at rest. */\n@compute @workgroup_size(64)\nfn clothRest(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let p = poseOf(pace.toAt + i * 3u);\n store(stateAt(POSITION, i), p);\n store(stateAt(START, i), p);\n store(stateAt(VELOCITY, i), vec3<f32>(0.0));\n}\n\n/* The step's start: the step start kept, a free particle carried, a kinematic one placed. */\n@compute @workgroup_size(64)\nfn clothBegin(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let at = stateAt(POSITION, i);\n let p = load(at);\n store(stateAt(START, i), p);\n if (inverseMass(i) == 0.0) {\n store(at, skinnedAt(i));\n return;\n }\n if (pace.carry == 0u) {\n return;\n }\n /* carryParticles: the share of the character's turn about its old origin, and of its move. */\n let turn = mat3x3<f32>(pace.turn0.xyz, pace.turn1.xyz, pace.turn2.xyz);\n let share = pace.origin.w;\n let x = p - pace.origin.xyz;\n store(at, p + (turn * x - x) * share + pace.shift.xyz);\n let vAt = stateAt(VELOCITY, i);\n let v = load(vAt);\n store(vAt, v + (turn * v - v) * share);\n}\n\n/* predictParticles. */\n@compute @workgroup_size(64)\nfn clothPredict(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let at = stateAt(POSITION, i);\n let p = load(at);\n store(stateAt(PREVIOUS, i), p);\n if (inverseMass(i) == 0.0) {\n return;\n }\n let vAt = stateAt(VELOCITY, i);\n let air = pace.wind.xyz;\n let v = air + (load(vAt) * c.keep - air) * c.dragKeep + c.gravity.xyz * c.h;\n store(vAt, v);\n store(at, p + v * c.h);\n}\n\n/* solveDistanceBatch: one invocation a constraint of the batch, constraints sorted by batch. */\n@compute @workgroup_size(64)\nfn clothDistance(@builtin(global_invocation_id) id: vec3<u32>) {\n let k = batch.begin + id.x;\n if (k >= batch.end) {\n return;\n }\n /* The first pass of a substep starts the multiplier at nothing, as the CPU's fill(0) does \u2014\n written before any early return, so a skipped constraint does not carry the last one. */\n if (batch.first == 1u) {\n lambda[k] = 0.0;\n }\n let a = constraints[k * 4u];\n let b = constraints[k * 4u + 1u];\n let wa = inverseMass(a);\n let wb = inverseMass(b);\n let w = wa + wb;\n if (w == 0.0) {\n return;\n }\n let aAt = stateAt(POSITION, a);\n let bAt = stateAt(POSITION, b);\n let d = load(bAt) - load(aAt);\n let len = sqrt(dot(d, d));\n if (len < 1e-9) {\n return;\n }\n let alpha = bitcast<f32>(constraints[k * 4u + 3u]) * c.invH2;\n let old = lambda[k];\n let delta = (-(len - bitcast<f32>(constraints[k * 4u + 2u])) - alpha * old) / (w + alpha);\n lambda[k] = old + delta;\n let s = delta / len;\n /* A pinned particle is shared across a batch, so it is never written, even with itself. */\n if (wa > 0.0) {\n store(aAt, load(aAt) - d * s * wa);\n }\n if (wb > 0.0) {\n store(bAt, load(bAt) + d * s * wb);\n }\n}\n\n/* solveBendingBatch, with the angle from atan2 (see the header). */\n@compute @workgroup_size(64)\nfn clothBending(@builtin(global_invocation_id) id: vec3<u32>) {\n let k = batch.begin + id.x;\n if (k >= batch.end) {\n return;\n }\n let at = c.distances * 4u + k * 6u;\n let slot = c.distances + k;\n if (batch.first == 1u) {\n lambda[slot] = 0.0;\n }\n let ids = vec4<u32>(constraints[at], constraints[at + 1u], constraints[at + 2u], constraints[at + 3u]);\n let w = vec4<f32>(inverseMass(ids.x), inverseMass(ids.y), inverseMass(ids.z), inverseMass(ids.w));\n if (w.x + w.y + w.z + w.w == 0.0) {\n return;\n }\n let origin = load(stateAt(POSITION, ids.x));\n let e = load(stateAt(POSITION, ids.y)) - origin;\n let p3 = load(stateAt(POSITION, ids.z)) - origin;\n let p4 = load(stateAt(POSITION, ids.w)) - origin;\n var n1 = cross(e, p3);\n var n2 = cross(e, p4);\n let l1 = sqrt(dot(n1, n1));\n let l2 = sqrt(dot(n2, n2));\n if (l1 < 1e-12 || l2 < 1e-12) {\n return;\n }\n n1 = n1 / l1;\n n2 = n2 / l2;\n let d = clamp(dot(n1, n2), -1.0, 1.0);\n let sine = sqrt(dot(cross(n1, n2), cross(n1, n2)));\n if (sine < 1e-6) {\n return;\n }\n /* The q vectors: the negative of the derivative of n1 \u00B7 n2. See solveBendingBatch. */\n let q3 = (cross(e, n2) + cross(n1, e) * d) / l1;\n let q4 = (cross(e, n1) + cross(n2, e) * d) / l2;\n let q2 = -((cross(p3, n2) + cross(n1, p3) * d) / l1 + (cross(p4, n1) + cross(n2, p4) * d) / l2);\n let q1 = -(q2 + q3 + q4);\n var sum = w.x * dot(q1, q1) + w.y * dot(q2, q2) + w.z * dot(q3, q3) + w.w * dot(q4, q4);\n sum = sum / (sine * sine);\n if (sum < 1e-12) {\n return;\n }\n let alpha = bitcast<f32>(constraints[at + 5u]) * c.invH2;\n let old = lambda[slot];\n let angle = atan2(sine, d);\n let delta = (-(angle - bitcast<f32>(constraints[at + 4u])) - alpha * old) / (sum + alpha);\n lambda[slot] = old + delta;\n let scale = delta / sine;\n if (w.x > 0.0) {\n let p = stateAt(POSITION, ids.x);\n store(p, load(p) + w.x * scale * q1);\n }\n if (w.y > 0.0) {\n let p = stateAt(POSITION, ids.y);\n store(p, load(p) + w.y * scale * q2);\n }\n if (w.z > 0.0) {\n let p = stateAt(POSITION, ids.z);\n store(p, load(p) + w.z * scale * q3);\n }\n if (w.w > 0.0) {\n let p = stateAt(POSITION, ids.w);\n store(p, load(p) + w.w * scale * q4);\n }\n}\n\n/* solveTethers, one invocation a tether of the batch: a batch holds a particle once. */\n@compute @workgroup_size(64)\nfn clothTether(@builtin(global_invocation_id) id: vec3<u32>) {\n let k = batch.begin + id.x;\n if (k >= batch.end) {\n return;\n }\n let at = c.distances * 4u + c.bendings * 6u + k * 3u;\n let pAt = stateAt(POSITION, constraints[at]);\n let anchor = load(stateAt(POSITION, constraints[at + 1u]));\n let d = load(pAt) - anchor;\n let dist = sqrt(dot(d, d));\n let reach = bitcast<f32>(constraints[at + 2u]);\n if (dist <= reach || dist < 1e-9) {\n return;\n }\n store(pAt, anchor + d * (reach / dist));\n}\n\n/* pushOut: the point onto the sphere's surface if it is inside. A point at the centre stays. */\nfn pushOut(p: vec3<f32>, centre: vec3<f32>, radius: f32) -> vec3<f32> {\n let d = p - centre;\n let d2 = dot(d, d);\n if (d2 >= radius * radius || d2 < 1e-18) {\n return p;\n }\n return centre + d * (radius / sqrt(d2));\n}\n\n/* applyMaxDistance, applyStops (back, then front) and applyColliders, in that order. */\n@compute @workgroup_size(64)\nfn clothLimit(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count || inverseMass(i) == 0.0) {\n return;\n }\n let n = c.count;\n let at = stateAt(POSITION, i);\n var p = load(at);\n let t = skinnedAt(i);\n /* A max distance below zero is none: the CPU's Infinity, which a device may not be handed. */\n let maxDistance = statics[n + i];\n if ((c.limits & 1u) != 0u && maxDistance >= 0.0) {\n let limit = maxDistance * c.maxDistanceScale;\n let d = p - t;\n let d2 = dot(d, d);\n if (d2 > limit * limit) {\n p = t + d * (limit / sqrt(d2));\n }\n }\n let normal = poseOf(6u * n + i * 3u);\n if ((c.limits & 2u) != 0u) {\n let radius = statics[2u * n + i * 2u + 1u];\n if (radius > 0.0) {\n p = pushOut(p, t - normal * (statics[2u * n + i * 2u] + radius), radius);\n }\n }\n if ((c.limits & 4u) != 0u) {\n let radius = statics[4u * n + i * 2u + 1u];\n if (radius > 0.0) {\n p = pushOut(p, t + normal * (statics[4u * n + i * 2u] + radius), radius);\n }\n }\n let pad = statics[6u * n + i] + c.margin;\n let ends = c.joints * 16u;\n let radii = ends + c.colliders * 6u;\n for (var k = 0u; k < c.colliders; k++) {\n let a = vec3<f32>(frame[ends + k * 6u], frame[ends + k * 6u + 1u], frame[ends + k * 6u + 2u]);\n let e = vec3<f32>(frame[ends + k * 6u + 3u], frame[ends + k * 6u + 4u], frame[ends + k * 6u + 5u]) - a;\n let e2 = dot(e, e);\n var s = 0.0;\n if (e2 > 1e-18) {\n s = clamp(dot(p - a, e) / e2, 0.0, 1.0);\n }\n let r0 = frame[radii + k * 2u];\n let r1 = frame[radii + k * 2u + 1u];\n p = pushOut(p, a + e * s, r0 + (r1 - r0) * s + pad);\n }\n store(at, p);\n}\n\n/* finishParticles. */\n@compute @workgroup_size(64)\nfn clothFinish(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let vAt = stateAt(VELOCITY, i);\n if (inverseMass(i) == 0.0) {\n store(vAt, vec3<f32>(0.0));\n return;\n }\n store(vAt, (load(stateAt(POSITION, i)) - load(stateAt(PREVIOUS, i))) * c.invH);\n}\n\n/* SkinnedCloth.blend: after a reset, the simulation's share grows over the blend's steps. */\n@compute @workgroup_size(64)\nfn clothBlend(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let t = skinnedAt(i);\n let at = stateAt(POSITION, i);\n store(at, t + (load(at) - t) * pace.blend);\n let vAt = stateAt(VELOCITY, i);\n store(vAt, load(vAt) * pace.blend);\n}\n\n@group(1) @binding(0) var output: texture_storage_2d<rgba32float, write>;\n\n/* SkinnedCloth.interpolate, into the particle texture the vertex stage reads. */\n@compute @workgroup_size(64)\nfn clothPublish(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let a = load(stateAt(START, i));\n let p = a + (load(stateAt(POSITION, i)) - a) * pace.alpha;\n textureStore(output, vec2<u32>(i % c.width, i / c.width), vec4<f32>(p, 0.0));\n}\n";
|
|
40
|
+
export declare const CLOTH_SOLVE_WGSL = "\n/* The set's: every garment's particles, records, skin matrices and colliders, and the step length\n they all share. */\nstruct Constants {\n count: u32,\n distances: u32,\n bendings: u32,\n joints: u32,\n colliders: u32,\n pad0: u32,\n pad1: u32,\n pad2: u32,\n h: f32,\n invH: f32,\n invH2: f32,\n pad3: f32,\n}\n\n/* Where this dispatch's round of records starts in paces, at a dynamic offset. */\nstruct Round {\n at: u32,\n pad0: u32,\n pad1: u32,\n pad2: u32,\n}\n\n/* One garment's numbers for one round: its step's, and its own that never change. */\nstruct Pace {\n fraction: f32,\n blend: f32,\n alpha: f32,\n carry: u32,\n /* Where the pose a step starts from and the one it moves toward sit in pose, in floats. */\n fromAt: u32,\n toAt: u32,\n /* 0 where the garment sits this round out. Not active, which WGSL reserves. */\n live: u32,\n /* 1 a max distance, 2 a backstop, 4 a frontstop: which limits the garment has at all. */\n limits: u32,\n /* The character's turn since the last step, by columns; origin.w is the share carried. */\n turn0: vec4<f32>,\n turn1: vec4<f32>,\n turn2: vec4<f32>,\n origin: vec4<f32>,\n shift: vec4<f32>,\n /* The frame's wind: one wind, sampled once a frame, so a step's rather than the set-up's. */\n wind: vec4<f32>,\n colliderBase: u32,\n colliders: u32,\n width: u32,\n pad0: u32,\n keep: f32,\n dragKeep: f32,\n maxDistanceScale: f32,\n margin: f32,\n gravity: vec4<f32>,\n}\n\n/* One colour of one kind of constraint across the set, at a dynamic offset; first resets the\n multipliers. Publishing reads one garment's particles as begin to end, and first is the garment. */\nstruct Batch {\n begin: u32,\n end: u32,\n first: u32,\n pad: u32,\n}\n\n@group(0) @binding(0) var<uniform> c: Constants;\n@group(0) @binding(1) var<uniform> round: Round;\n@group(0) @binding(2) var<uniform> batch: Batch;\n@group(0) @binding(3) var<storage, read_write> state: array<f32>;\n@group(0) @binding(4) var<storage, read_write> pose: array<f32>;\n@group(0) @binding(5) var<storage, read> statics: array<f32>;\n@group(0) @binding(6) var<storage, read> constraints: array<u32>;\n@group(0) @binding(7) var<storage, read_write> lambda: array<f32>;\n@group(0) @binding(8) var<storage, read> frame: array<f32>;\n@group(0) @binding(9) var<storage, read> paces: array<Pace>;\n\n/* The record of particle i's garment this round: which garment a particle is, in statics. */\nfn paceAt(i: u32) -> u32 {\n return round.at + u32(statics[29u * c.count + i]);\n}\n\nfn stateAt(section: u32, i: u32) -> u32 {\n return section * 3u * c.count + i * 3u;\n}\n\nfn load(at: u32) -> vec3<f32> {\n return vec3<f32>(state[at], state[at + 1u], state[at + 2u]);\n}\n\nfn store(at: u32, v: vec3<f32>) {\n state[at] = v.x;\n state[at + 1u] = v.y;\n state[at + 2u] = v.z;\n}\n\nfn poseOf(at: u32) -> vec3<f32> {\n return vec3<f32>(pose[at], pose[at + 1u], pose[at + 2u]);\n}\n\n/* Sections of state. */\nconst POSITION = 0u;\nconst PREVIOUS = 1u;\nconst VELOCITY = 2u;\nconst START = 3u;\n\nfn inverseMass(i: u32) -> f32 {\n return statics[i];\n}\n\n/* Where skinning puts particle i, fraction of the way from the last pose to the latest. */\nfn skinnedAt(i: u32, g: u32) -> vec3<f32> {\n let a = poseOf(paces[g].fromAt + i * 3u);\n let b = poseOf(paces[g].toAt + i * 3u);\n return a + (b - a) * paces[g].fraction;\n}\n\nfn skinMatrix(j: u32) -> mat4x4<f32> {\n let at = j * 16u;\n return mat4x4<f32>(\n vec4<f32>(frame[at], frame[at + 1u], frame[at + 2u], frame[at + 3u]),\n vec4<f32>(frame[at + 4u], frame[at + 5u], frame[at + 6u], frame[at + 7u]),\n vec4<f32>(frame[at + 8u], frame[at + 9u], frame[at + 10u], frame[at + 11u]),\n vec4<f32>(frame[at + 12u], frame[at + 13u], frame[at + 14u], frame[at + 15u]));\n}\n\n/* skinParticles: each particle's skinned position and normal by up to eight influences. */\n@compute @workgroup_size(64)\nfn clothPose(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let g = paceAt(i);\n if (paces[g].live == 0u) {\n return;\n }\n let n = c.count;\n let restAt = 7u * n + i * 3u;\n let normalAt = 10u * n + i * 3u;\n let x = vec4<f32>(statics[restAt], statics[restAt + 1u], statics[restAt + 2u], 1.0);\n let q0 = vec3<f32>(statics[normalAt], statics[normalAt + 1u], statics[normalAt + 2u]);\n var p = vec3<f32>(0.0);\n var q = vec3<f32>(0.0);\n for (var half = 0u; half < 2u; half++) {\n for (var k = 0u; k < 4u; k++) {\n let at = 13u * n + i * 16u + half * 8u;\n let w = statics[at + 4u + k];\n if (w == 0.0) {\n continue;\n }\n let m = skinMatrix(u32(statics[at + k]));\n p += w * (m * x).xyz;\n q += w * (mat3x3<f32>(m[0].xyz, m[1].xyz, m[2].xyz) * q0);\n }\n }\n let to = paces[g].toAt + i * 3u;\n pose[to] = p.x;\n pose[to + 1u] = p.y;\n pose[to + 2u] = p.z;\n let len = sqrt(dot(q, q));\n let inv = select(0.0, 1.0 / len, len > 1e-12);\n let normal = 6u * n + i * 3u;\n pose[normal] = q.x * inv;\n pose[normal + 1u] = q.y * inv;\n pose[normal + 2u] = q.z * inv;\n}\n\n/* SkinnedCloth.rest: every particle at the latest skinned position, at rest. */\n@compute @workgroup_size(64)\nfn clothRest(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let g = paceAt(i);\n if (paces[g].live == 0u) {\n return;\n }\n let p = poseOf(paces[g].toAt + i * 3u);\n store(stateAt(POSITION, i), p);\n store(stateAt(START, i), p);\n store(stateAt(VELOCITY, i), vec3<f32>(0.0));\n}\n\n/* The step's start: the step start kept, a free particle carried, a kinematic one placed. */\n@compute @workgroup_size(64)\nfn clothBegin(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let g = paceAt(i);\n if (paces[g].live == 0u) {\n return;\n }\n let at = stateAt(POSITION, i);\n let p = load(at);\n store(stateAt(START, i), p);\n if (inverseMass(i) == 0.0) {\n store(at, skinnedAt(i, g));\n return;\n }\n if (paces[g].carry == 0u) {\n return;\n }\n /* carryParticles: the share of the character's turn about its old origin, and of its move. */\n let turn = mat3x3<f32>(paces[g].turn0.xyz, paces[g].turn1.xyz, paces[g].turn2.xyz);\n let share = paces[g].origin.w;\n let x = p - paces[g].origin.xyz;\n store(at, p + (turn * x - x) * share + paces[g].shift.xyz);\n let vAt = stateAt(VELOCITY, i);\n let v = load(vAt);\n store(vAt, v + (turn * v - v) * share);\n}\n\n/* predictParticles. */\n@compute @workgroup_size(64)\nfn clothPredict(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n let g = paceAt(i);\n if (paces[g].live == 0u) {\n return;\n }\n let at = stateAt(POSITION, i);\n let p = load(at);\n store(stateAt(PREVIOUS, i), p);\n if (inverseMass(i) == 0.0) {\n return;\n }\n let vAt = stateAt(VELOCITY, i);\n let air = paces[g].wind.xyz;\n let v = air + (load(vAt) * paces[g].keep - air) * paces[g].dragKeep + paces[g].gravity.xyz * c.h;\n store(vAt, v);\n store(at, p + v * c.h);\n}\n\n/* solveDistanceBatch: one invocation a constraint of the batch, constraints sorted by batch. */\n@compute @workgroup_size(64)\nfn clothDistance(@builtin(global_invocation_id) id: vec3<u32>) {\n let k = batch.begin + id.x;\n if (k >= batch.end) {\n return;\n }\n let a = constraints[k * 4u];\n let b = constraints[k * 4u + 1u];\n /* A garment sitting the round out solves nothing, and its next step's first pass resets. */\n if (paces[paceAt(a)].live == 0u) {\n return;\n }\n /* The first pass of a substep starts the multiplier at nothing, as the CPU's fill(0) does \u2014\n written before any early return, so a skipped constraint does not carry the last one. */\n if (batch.first == 1u) {\n lambda[k] = 0.0;\n }\n let wa = inverseMass(a);\n let wb = inverseMass(b);\n let w = wa + wb;\n if (w == 0.0) {\n return;\n }\n let aAt = stateAt(POSITION, a);\n let bAt = stateAt(POSITION, b);\n let d = load(bAt) - load(aAt);\n let len = sqrt(dot(d, d));\n if (len < 1e-9) {\n return;\n }\n let alpha = bitcast<f32>(constraints[k * 4u + 3u]) * c.invH2;\n let old = lambda[k];\n let delta = (-(len - bitcast<f32>(constraints[k * 4u + 2u])) - alpha * old) / (w + alpha);\n lambda[k] = old + delta;\n let s = delta / len;\n /* A pinned particle is shared across a batch, so it is never written, even with itself. */\n if (wa > 0.0) {\n store(aAt, load(aAt) - d * s * wa);\n }\n if (wb > 0.0) {\n store(bAt, load(bAt) + d * s * wb);\n }\n}\n\n/* solveBendingBatch, with the angle from atan2 (see the header). */\n@compute @workgroup_size(64)\nfn clothBending(@builtin(global_invocation_id) id: vec3<u32>) {\n let k = batch.begin + id.x;\n if (k >= batch.end) {\n return;\n }\n let at = c.distances * 4u + k * 6u;\n let slot = c.distances + k;\n if (paces[paceAt(constraints[at])].live == 0u) {\n return;\n }\n if (batch.first == 1u) {\n lambda[slot] = 0.0;\n }\n let ids = vec4<u32>(constraints[at], constraints[at + 1u], constraints[at + 2u], constraints[at + 3u]);\n let w = vec4<f32>(inverseMass(ids.x), inverseMass(ids.y), inverseMass(ids.z), inverseMass(ids.w));\n if (w.x + w.y + w.z + w.w == 0.0) {\n return;\n }\n let origin = load(stateAt(POSITION, ids.x));\n let e = load(stateAt(POSITION, ids.y)) - origin;\n let p3 = load(stateAt(POSITION, ids.z)) - origin;\n let p4 = load(stateAt(POSITION, ids.w)) - origin;\n var n1 = cross(e, p3);\n var n2 = cross(e, p4);\n let l1 = sqrt(dot(n1, n1));\n let l2 = sqrt(dot(n2, n2));\n if (l1 < 1e-12 || l2 < 1e-12) {\n return;\n }\n n1 = n1 / l1;\n n2 = n2 / l2;\n let d = clamp(dot(n1, n2), -1.0, 1.0);\n let sine = sqrt(dot(cross(n1, n2), cross(n1, n2)));\n if (sine < 1e-6) {\n return;\n }\n /* The q vectors: the negative of the derivative of n1 \u00B7 n2. See solveBendingBatch. */\n let q3 = (cross(e, n2) + cross(n1, e) * d) / l1;\n let q4 = (cross(e, n1) + cross(n2, e) * d) / l2;\n let q2 = -((cross(p3, n2) + cross(n1, p3) * d) / l1 + (cross(p4, n1) + cross(n2, p4) * d) / l2);\n let q1 = -(q2 + q3 + q4);\n var sum = w.x * dot(q1, q1) + w.y * dot(q2, q2) + w.z * dot(q3, q3) + w.w * dot(q4, q4);\n sum = sum / (sine * sine);\n if (sum < 1e-12) {\n return;\n }\n let alpha = bitcast<f32>(constraints[at + 5u]) * c.invH2;\n let old = lambda[slot];\n let angle = atan2(sine, d);\n let delta = (-(angle - bitcast<f32>(constraints[at + 4u])) - alpha * old) / (sum + alpha);\n lambda[slot] = old + delta;\n let scale = delta / sine;\n if (w.x > 0.0) {\n let p = stateAt(POSITION, ids.x);\n store(p, load(p) + w.x * scale * q1);\n }\n if (w.y > 0.0) {\n let p = stateAt(POSITION, ids.y);\n store(p, load(p) + w.y * scale * q2);\n }\n if (w.z > 0.0) {\n let p = stateAt(POSITION, ids.z);\n store(p, load(p) + w.z * scale * q3);\n }\n if (w.w > 0.0) {\n let p = stateAt(POSITION, ids.w);\n store(p, load(p) + w.w * scale * q4);\n }\n}\n\n/* solveTethers, one invocation a tether of the batch: a batch holds a particle once. */\n@compute @workgroup_size(64)\nfn clothTether(@builtin(global_invocation_id) id: vec3<u32>) {\n let k = batch.begin + id.x;\n if (k >= batch.end) {\n return;\n }\n let at = c.distances * 4u + c.bendings * 6u + k * 3u;\n if (paces[paceAt(constraints[at])].live == 0u) {\n return;\n }\n let pAt = stateAt(POSITION, constraints[at]);\n let anchor = load(stateAt(POSITION, constraints[at + 1u]));\n let d = load(pAt) - anchor;\n let dist = sqrt(dot(d, d));\n let reach = bitcast<f32>(constraints[at + 2u]);\n if (dist <= reach || dist < 1e-9) {\n return;\n }\n store(pAt, anchor + d * (reach / dist));\n}\n\n/* pushOut: the point onto the sphere's surface if it is inside. A point at the centre stays. */\nfn pushOut(p: vec3<f32>, centre: vec3<f32>, radius: f32) -> vec3<f32> {\n let d = p - centre;\n let d2 = dot(d, d);\n if (d2 >= radius * radius || d2 < 1e-18) {\n return p;\n }\n return centre + d * (radius / sqrt(d2));\n}\n\n/* applyMaxDistance, applyStops (back, then front) and applyColliders, in that order. */\n@compute @workgroup_size(64)\nfn clothLimit(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count || inverseMass(i) == 0.0) {\n return;\n }\n let g = paceAt(i);\n if (paces[g].live == 0u) {\n return;\n }\n let limits = paces[g].limits;\n let n = c.count;\n let at = stateAt(POSITION, i);\n var p = load(at);\n let t = skinnedAt(i, g);\n /* A max distance below zero is none: the CPU's Infinity, which a device may not be handed. */\n let maxDistance = statics[n + i];\n if ((limits & 1u) != 0u && maxDistance >= 0.0) {\n let limit = maxDistance * paces[g].maxDistanceScale;\n let d = p - t;\n let d2 = dot(d, d);\n if (d2 > limit * limit) {\n p = t + d * (limit / sqrt(d2));\n }\n }\n let normal = poseOf(6u * n + i * 3u);\n if ((limits & 2u) != 0u) {\n let radius = statics[2u * n + i * 2u + 1u];\n if (radius > 0.0) {\n p = pushOut(p, t - normal * (statics[2u * n + i * 2u] + radius), radius);\n }\n }\n if ((limits & 4u) != 0u) {\n let radius = statics[4u * n + i * 2u + 1u];\n if (radius > 0.0) {\n p = pushOut(p, t + normal * (statics[4u * n + i * 2u] + radius), radius);\n }\n }\n let pad = statics[6u * n + i] + paces[g].margin;\n let ends = c.joints * 16u;\n let radii = ends + c.colliders * 6u;\n let first = paces[g].colliderBase;\n for (var k = first; k < first + paces[g].colliders; k++) {\n let a = vec3<f32>(frame[ends + k * 6u], frame[ends + k * 6u + 1u], frame[ends + k * 6u + 2u]);\n let e = vec3<f32>(frame[ends + k * 6u + 3u], frame[ends + k * 6u + 4u], frame[ends + k * 6u + 5u]) - a;\n let e2 = dot(e, e);\n var s = 0.0;\n if (e2 > 1e-18) {\n s = clamp(dot(p - a, e) / e2, 0.0, 1.0);\n }\n let r0 = frame[radii + k * 2u];\n let r1 = frame[radii + k * 2u + 1u];\n p = pushOut(p, a + e * s, r0 + (r1 - r0) * s + pad);\n }\n store(at, p);\n}\n\n/* finishParticles. */\n@compute @workgroup_size(64)\nfn clothFinish(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n if (paces[paceAt(i)].live == 0u) {\n return;\n }\n let vAt = stateAt(VELOCITY, i);\n if (inverseMass(i) == 0.0) {\n store(vAt, vec3<f32>(0.0));\n return;\n }\n store(vAt, (load(stateAt(POSITION, i)) - load(stateAt(PREVIOUS, i))) * c.invH);\n}\n\n/* SkinnedCloth.blend: after a reset, the simulation's share grows over the blend's steps. */\n@compute @workgroup_size(64)\nfn clothBlend(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= c.count) {\n return;\n }\n /* A blend of 0 is none, not a last step of one: see ClothDevice.step. */\n let g = paceAt(i);\n let blend = paces[g].blend;\n if (paces[g].live == 0u || blend <= 0.0) {\n return;\n }\n let t = skinnedAt(i, g);\n let at = stateAt(POSITION, i);\n store(at, t + (load(at) - t) * blend);\n let vAt = stateAt(VELOCITY, i);\n store(vAt, load(vAt) * blend);\n}\n\n@group(1) @binding(0) var output: texture_storage_2d<rgba32float, write>;\n\n/* SkinnedCloth.interpolate, into the particle texture the vertex stage reads: one garment's, whose\n particles are batch.begin to batch.end and whose record is batch.first's. */\n@compute @workgroup_size(64)\nfn clothPublish(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = batch.begin + id.x;\n if (i >= batch.end) {\n return;\n }\n let g = round.at + batch.first;\n let a = load(stateAt(START, i));\n let p = a + (load(stateAt(POSITION, i)) - a) * paces[g].alpha;\n let local = id.x;\n let width = paces[g].width;\n textureStore(output, vec2<u32>(local % width, local / width), vec4<f32>(p, 0.0));\n}\n";
|
|
@@ -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
|
-
* `
|
|
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,32 +28,43 @@
|
|
|
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
|
export const CLOTH_SOLVE_WGSL = /* wgsl */ `
|
|
41
|
+
/* The set's: every garment's particles, records, skin matrices and colliders, and the step length
|
|
42
|
+
they all share. */
|
|
34
43
|
struct Constants {
|
|
35
44
|
count: u32,
|
|
36
45
|
distances: u32,
|
|
37
46
|
bendings: u32,
|
|
38
47
|
joints: u32,
|
|
39
48
|
colliders: u32,
|
|
40
|
-
/* 1 a max distance, 2 a backstop, 4 a frontstop: which limits the set-up has at all. */
|
|
41
|
-
limits: u32,
|
|
42
|
-
width: u32,
|
|
43
49
|
pad0: u32,
|
|
50
|
+
pad1: u32,
|
|
51
|
+
pad2: u32,
|
|
44
52
|
h: f32,
|
|
45
53
|
invH: f32,
|
|
46
54
|
invH2: f32,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
pad3: f32,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Where this dispatch's round of records starts in paces, at a dynamic offset. */
|
|
59
|
+
struct Round {
|
|
60
|
+
at: u32,
|
|
61
|
+
pad0: u32,
|
|
62
|
+
pad1: u32,
|
|
63
|
+
pad2: u32,
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
/* One
|
|
56
|
-
struct
|
|
66
|
+
/* One garment's numbers for one round: its step's, and its own that never change. */
|
|
67
|
+
struct Pace {
|
|
57
68
|
fraction: f32,
|
|
58
69
|
blend: f32,
|
|
59
70
|
alpha: f32,
|
|
@@ -61,8 +72,10 @@ struct Step {
|
|
|
61
72
|
/* Where the pose a step starts from and the one it moves toward sit in pose, in floats. */
|
|
62
73
|
fromAt: u32,
|
|
63
74
|
toAt: u32,
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
/* 0 where the garment sits this round out. Not active, which WGSL reserves. */
|
|
76
|
+
live: u32,
|
|
77
|
+
/* 1 a max distance, 2 a backstop, 4 a frontstop: which limits the garment has at all. */
|
|
78
|
+
limits: u32,
|
|
66
79
|
/* The character's turn since the last step, by columns; origin.w is the share carried. */
|
|
67
80
|
turn0: vec4<f32>,
|
|
68
81
|
turn1: vec4<f32>,
|
|
@@ -71,9 +84,19 @@ struct Step {
|
|
|
71
84
|
shift: vec4<f32>,
|
|
72
85
|
/* The frame's wind: one wind, sampled once a frame, so a step's rather than the set-up's. */
|
|
73
86
|
wind: vec4<f32>,
|
|
87
|
+
colliderBase: u32,
|
|
88
|
+
colliders: u32,
|
|
89
|
+
width: u32,
|
|
90
|
+
pad0: u32,
|
|
91
|
+
keep: f32,
|
|
92
|
+
dragKeep: f32,
|
|
93
|
+
maxDistanceScale: f32,
|
|
94
|
+
margin: f32,
|
|
95
|
+
gravity: vec4<f32>,
|
|
74
96
|
}
|
|
75
97
|
|
|
76
|
-
/* One
|
|
98
|
+
/* One colour of one kind of constraint across the set, at a dynamic offset; first resets the
|
|
99
|
+
multipliers. Publishing reads one garment's particles as begin to end, and first is the garment. */
|
|
77
100
|
struct Batch {
|
|
78
101
|
begin: u32,
|
|
79
102
|
end: u32,
|
|
@@ -82,7 +105,7 @@ struct Batch {
|
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
@group(0) @binding(0) var<uniform> c: Constants;
|
|
85
|
-
@group(0) @binding(1) var<uniform>
|
|
108
|
+
@group(0) @binding(1) var<uniform> round: Round;
|
|
86
109
|
@group(0) @binding(2) var<uniform> batch: Batch;
|
|
87
110
|
@group(0) @binding(3) var<storage, read_write> state: array<f32>;
|
|
88
111
|
@group(0) @binding(4) var<storage, read_write> pose: array<f32>;
|
|
@@ -90,6 +113,12 @@ struct Batch {
|
|
|
90
113
|
@group(0) @binding(6) var<storage, read> constraints: array<u32>;
|
|
91
114
|
@group(0) @binding(7) var<storage, read_write> lambda: array<f32>;
|
|
92
115
|
@group(0) @binding(8) var<storage, read> frame: array<f32>;
|
|
116
|
+
@group(0) @binding(9) var<storage, read> paces: array<Pace>;
|
|
117
|
+
|
|
118
|
+
/* The record of particle i's garment this round: which garment a particle is, in statics. */
|
|
119
|
+
fn paceAt(i: u32) -> u32 {
|
|
120
|
+
return round.at + u32(statics[29u * c.count + i]);
|
|
121
|
+
}
|
|
93
122
|
|
|
94
123
|
fn stateAt(section: u32, i: u32) -> u32 {
|
|
95
124
|
return section * 3u * c.count + i * 3u;
|
|
@@ -120,10 +149,10 @@ fn inverseMass(i: u32) -> f32 {
|
|
|
120
149
|
}
|
|
121
150
|
|
|
122
151
|
/* Where skinning puts particle i, fraction of the way from the last pose to the latest. */
|
|
123
|
-
fn skinnedAt(i: u32) -> vec3<f32> {
|
|
124
|
-
let a = poseOf(
|
|
125
|
-
let b = poseOf(
|
|
126
|
-
return a + (b - a) *
|
|
152
|
+
fn skinnedAt(i: u32, g: u32) -> vec3<f32> {
|
|
153
|
+
let a = poseOf(paces[g].fromAt + i * 3u);
|
|
154
|
+
let b = poseOf(paces[g].toAt + i * 3u);
|
|
155
|
+
return a + (b - a) * paces[g].fraction;
|
|
127
156
|
}
|
|
128
157
|
|
|
129
158
|
fn skinMatrix(j: u32) -> mat4x4<f32> {
|
|
@@ -142,6 +171,10 @@ fn clothPose(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
142
171
|
if (i >= c.count) {
|
|
143
172
|
return;
|
|
144
173
|
}
|
|
174
|
+
let g = paceAt(i);
|
|
175
|
+
if (paces[g].live == 0u) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
145
178
|
let n = c.count;
|
|
146
179
|
let restAt = 7u * n + i * 3u;
|
|
147
180
|
let normalAt = 10u * n + i * 3u;
|
|
@@ -161,7 +194,7 @@ fn clothPose(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
161
194
|
q += w * (mat3x3<f32>(m[0].xyz, m[1].xyz, m[2].xyz) * q0);
|
|
162
195
|
}
|
|
163
196
|
}
|
|
164
|
-
let to =
|
|
197
|
+
let to = paces[g].toAt + i * 3u;
|
|
165
198
|
pose[to] = p.x;
|
|
166
199
|
pose[to + 1u] = p.y;
|
|
167
200
|
pose[to + 2u] = p.z;
|
|
@@ -180,7 +213,11 @@ fn clothRest(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
180
213
|
if (i >= c.count) {
|
|
181
214
|
return;
|
|
182
215
|
}
|
|
183
|
-
let
|
|
216
|
+
let g = paceAt(i);
|
|
217
|
+
if (paces[g].live == 0u) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
let p = poseOf(paces[g].toAt + i * 3u);
|
|
184
221
|
store(stateAt(POSITION, i), p);
|
|
185
222
|
store(stateAt(START, i), p);
|
|
186
223
|
store(stateAt(VELOCITY, i), vec3<f32>(0.0));
|
|
@@ -193,21 +230,25 @@ fn clothBegin(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
193
230
|
if (i >= c.count) {
|
|
194
231
|
return;
|
|
195
232
|
}
|
|
233
|
+
let g = paceAt(i);
|
|
234
|
+
if (paces[g].live == 0u) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
196
237
|
let at = stateAt(POSITION, i);
|
|
197
238
|
let p = load(at);
|
|
198
239
|
store(stateAt(START, i), p);
|
|
199
240
|
if (inverseMass(i) == 0.0) {
|
|
200
|
-
store(at, skinnedAt(i));
|
|
241
|
+
store(at, skinnedAt(i, g));
|
|
201
242
|
return;
|
|
202
243
|
}
|
|
203
|
-
if (
|
|
244
|
+
if (paces[g].carry == 0u) {
|
|
204
245
|
return;
|
|
205
246
|
}
|
|
206
247
|
/* carryParticles: the share of the character's turn about its old origin, and of its move. */
|
|
207
|
-
let turn = mat3x3<f32>(
|
|
208
|
-
let share =
|
|
209
|
-
let x = p -
|
|
210
|
-
store(at, p + (turn * x - x) * share +
|
|
248
|
+
let turn = mat3x3<f32>(paces[g].turn0.xyz, paces[g].turn1.xyz, paces[g].turn2.xyz);
|
|
249
|
+
let share = paces[g].origin.w;
|
|
250
|
+
let x = p - paces[g].origin.xyz;
|
|
251
|
+
store(at, p + (turn * x - x) * share + paces[g].shift.xyz);
|
|
211
252
|
let vAt = stateAt(VELOCITY, i);
|
|
212
253
|
let v = load(vAt);
|
|
213
254
|
store(vAt, v + (turn * v - v) * share);
|
|
@@ -220,6 +261,10 @@ fn clothPredict(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
220
261
|
if (i >= c.count) {
|
|
221
262
|
return;
|
|
222
263
|
}
|
|
264
|
+
let g = paceAt(i);
|
|
265
|
+
if (paces[g].live == 0u) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
223
268
|
let at = stateAt(POSITION, i);
|
|
224
269
|
let p = load(at);
|
|
225
270
|
store(stateAt(PREVIOUS, i), p);
|
|
@@ -227,8 +272,8 @@ fn clothPredict(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
227
272
|
return;
|
|
228
273
|
}
|
|
229
274
|
let vAt = stateAt(VELOCITY, i);
|
|
230
|
-
let air =
|
|
231
|
-
let v = air + (load(vAt) *
|
|
275
|
+
let air = paces[g].wind.xyz;
|
|
276
|
+
let v = air + (load(vAt) * paces[g].keep - air) * paces[g].dragKeep + paces[g].gravity.xyz * c.h;
|
|
232
277
|
store(vAt, v);
|
|
233
278
|
store(at, p + v * c.h);
|
|
234
279
|
}
|
|
@@ -240,13 +285,17 @@ fn clothDistance(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
240
285
|
if (k >= batch.end) {
|
|
241
286
|
return;
|
|
242
287
|
}
|
|
288
|
+
let a = constraints[k * 4u];
|
|
289
|
+
let b = constraints[k * 4u + 1u];
|
|
290
|
+
/* A garment sitting the round out solves nothing, and its next step's first pass resets. */
|
|
291
|
+
if (paces[paceAt(a)].live == 0u) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
243
294
|
/* The first pass of a substep starts the multiplier at nothing, as the CPU's fill(0) does —
|
|
244
295
|
written before any early return, so a skipped constraint does not carry the last one. */
|
|
245
296
|
if (batch.first == 1u) {
|
|
246
297
|
lambda[k] = 0.0;
|
|
247
298
|
}
|
|
248
|
-
let a = constraints[k * 4u];
|
|
249
|
-
let b = constraints[k * 4u + 1u];
|
|
250
299
|
let wa = inverseMass(a);
|
|
251
300
|
let wb = inverseMass(b);
|
|
252
301
|
let w = wa + wb;
|
|
@@ -283,6 +332,9 @@ fn clothBending(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
283
332
|
}
|
|
284
333
|
let at = c.distances * 4u + k * 6u;
|
|
285
334
|
let slot = c.distances + k;
|
|
335
|
+
if (paces[paceAt(constraints[at])].live == 0u) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
286
338
|
if (batch.first == 1u) {
|
|
287
339
|
lambda[slot] = 0.0;
|
|
288
340
|
}
|
|
@@ -351,6 +403,9 @@ fn clothTether(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
351
403
|
return;
|
|
352
404
|
}
|
|
353
405
|
let at = c.distances * 4u + c.bendings * 6u + k * 3u;
|
|
406
|
+
if (paces[paceAt(constraints[at])].live == 0u) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
354
409
|
let pAt = stateAt(POSITION, constraints[at]);
|
|
355
410
|
let anchor = load(stateAt(POSITION, constraints[at + 1u]));
|
|
356
411
|
let d = load(pAt) - anchor;
|
|
@@ -379,14 +434,19 @@ fn clothLimit(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
379
434
|
if (i >= c.count || inverseMass(i) == 0.0) {
|
|
380
435
|
return;
|
|
381
436
|
}
|
|
437
|
+
let g = paceAt(i);
|
|
438
|
+
if (paces[g].live == 0u) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
let limits = paces[g].limits;
|
|
382
442
|
let n = c.count;
|
|
383
443
|
let at = stateAt(POSITION, i);
|
|
384
444
|
var p = load(at);
|
|
385
|
-
let t = skinnedAt(i);
|
|
445
|
+
let t = skinnedAt(i, g);
|
|
386
446
|
/* A max distance below zero is none: the CPU's Infinity, which a device may not be handed. */
|
|
387
447
|
let maxDistance = statics[n + i];
|
|
388
|
-
if ((
|
|
389
|
-
let limit = maxDistance *
|
|
448
|
+
if ((limits & 1u) != 0u && maxDistance >= 0.0) {
|
|
449
|
+
let limit = maxDistance * paces[g].maxDistanceScale;
|
|
390
450
|
let d = p - t;
|
|
391
451
|
let d2 = dot(d, d);
|
|
392
452
|
if (d2 > limit * limit) {
|
|
@@ -394,22 +454,23 @@ fn clothLimit(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
394
454
|
}
|
|
395
455
|
}
|
|
396
456
|
let normal = poseOf(6u * n + i * 3u);
|
|
397
|
-
if ((
|
|
457
|
+
if ((limits & 2u) != 0u) {
|
|
398
458
|
let radius = statics[2u * n + i * 2u + 1u];
|
|
399
459
|
if (radius > 0.0) {
|
|
400
460
|
p = pushOut(p, t - normal * (statics[2u * n + i * 2u] + radius), radius);
|
|
401
461
|
}
|
|
402
462
|
}
|
|
403
|
-
if ((
|
|
463
|
+
if ((limits & 4u) != 0u) {
|
|
404
464
|
let radius = statics[4u * n + i * 2u + 1u];
|
|
405
465
|
if (radius > 0.0) {
|
|
406
466
|
p = pushOut(p, t + normal * (statics[4u * n + i * 2u] + radius), radius);
|
|
407
467
|
}
|
|
408
468
|
}
|
|
409
|
-
let pad = statics[6u * n + i] +
|
|
469
|
+
let pad = statics[6u * n + i] + paces[g].margin;
|
|
410
470
|
let ends = c.joints * 16u;
|
|
411
471
|
let radii = ends + c.colliders * 6u;
|
|
412
|
-
|
|
472
|
+
let first = paces[g].colliderBase;
|
|
473
|
+
for (var k = first; k < first + paces[g].colliders; k++) {
|
|
413
474
|
let a = vec3<f32>(frame[ends + k * 6u], frame[ends + k * 6u + 1u], frame[ends + k * 6u + 2u]);
|
|
414
475
|
let e = vec3<f32>(frame[ends + k * 6u + 3u], frame[ends + k * 6u + 4u], frame[ends + k * 6u + 5u]) - a;
|
|
415
476
|
let e2 = dot(e, e);
|
|
@@ -431,6 +492,9 @@ fn clothFinish(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
431
492
|
if (i >= c.count) {
|
|
432
493
|
return;
|
|
433
494
|
}
|
|
495
|
+
if (paces[paceAt(i)].live == 0u) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
434
498
|
let vAt = stateAt(VELOCITY, i);
|
|
435
499
|
if (inverseMass(i) == 0.0) {
|
|
436
500
|
store(vAt, vec3<f32>(0.0));
|
|
@@ -446,24 +510,34 @@ fn clothBlend(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
446
510
|
if (i >= c.count) {
|
|
447
511
|
return;
|
|
448
512
|
}
|
|
449
|
-
|
|
513
|
+
/* A blend of 0 is none, not a last step of one: see ClothDevice.step. */
|
|
514
|
+
let g = paceAt(i);
|
|
515
|
+
let blend = paces[g].blend;
|
|
516
|
+
if (paces[g].live == 0u || blend <= 0.0) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
let t = skinnedAt(i, g);
|
|
450
520
|
let at = stateAt(POSITION, i);
|
|
451
|
-
store(at, t + (load(at) - t) *
|
|
521
|
+
store(at, t + (load(at) - t) * blend);
|
|
452
522
|
let vAt = stateAt(VELOCITY, i);
|
|
453
|
-
store(vAt, load(vAt) *
|
|
523
|
+
store(vAt, load(vAt) * blend);
|
|
454
524
|
}
|
|
455
525
|
|
|
456
526
|
@group(1) @binding(0) var output: texture_storage_2d<rgba32float, write>;
|
|
457
527
|
|
|
458
|
-
/* SkinnedCloth.interpolate, into the particle texture the vertex stage reads
|
|
528
|
+
/* SkinnedCloth.interpolate, into the particle texture the vertex stage reads: one garment's, whose
|
|
529
|
+
particles are batch.begin to batch.end and whose record is batch.first's. */
|
|
459
530
|
@compute @workgroup_size(64)
|
|
460
531
|
fn clothPublish(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
461
|
-
let i = id.x;
|
|
462
|
-
if (i >=
|
|
532
|
+
let i = batch.begin + id.x;
|
|
533
|
+
if (i >= batch.end) {
|
|
463
534
|
return;
|
|
464
535
|
}
|
|
536
|
+
let g = round.at + batch.first;
|
|
465
537
|
let a = load(stateAt(START, i));
|
|
466
|
-
let p = a + (load(stateAt(POSITION, i)) - a) *
|
|
467
|
-
|
|
538
|
+
let p = a + (load(stateAt(POSITION, i)) - a) * paces[g].alpha;
|
|
539
|
+
let local = id.x;
|
|
540
|
+
let width = paces[g].width;
|
|
541
|
+
textureStore(output, vec2<u32>(local % width, local / width), vec4<f32>(p, 0.0));
|
|
468
542
|
}
|
|
469
543
|
`;
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
* different rig at every set — the comparison is what tells the two apart.
|
|
28
28
|
*
|
|
29
29
|
* **It was capped at 128 until 4.8.6, and the cap declined draws.** A stage of three dozen idling
|
|
30
|
-
* extras beside two
|
|
31
|
-
* the consumer stood its crowd in the bind pose. The ring now holds as many palettes as a frame
|
|
30
|
+
* extras beside two characters filled it in one frame and every skinned draw past it was refused,
|
|
31
|
+
* so the consumer stood its crowd in the bind pose. The ring now holds as many palettes as a frame
|
|
32
32
|
* holds draws (`RenderQuality.drawsPerFrame`), since no frame can bind more palettes than it draws;
|
|
33
33
|
* a slot still allocates its texture only the first time a frame reaches it, so a scene with two
|
|
34
34
|
* characters holds two. **What it costs** is a texture and a cached bind group a slot, 1.5 KB for a
|
|
@@ -45,8 +45,12 @@
|
|
|
45
45
|
export declare class SkinPaletteRing {
|
|
46
46
|
private readonly capacity;
|
|
47
47
|
private readonly slots;
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* What each slot was last written with, for the comparison that lets a pass reuse it: a copy
|
|
50
|
+
* that grows with the largest rig the slot has held, and how much of it the last rig filled.
|
|
51
|
+
*/
|
|
49
52
|
private readonly held;
|
|
53
|
+
private readonly heldLength;
|
|
50
54
|
/** The slot each palette array last went into, by identity. Weak, so a caller's array can go. */
|
|
51
55
|
private readonly lastSlot;
|
|
52
56
|
private used;
|
|
@@ -28,8 +28,8 @@ import { SkinPaletteTexture } from './skinPaletteTexture.js';
|
|
|
28
28
|
* different rig at every set — the comparison is what tells the two apart.
|
|
29
29
|
*
|
|
30
30
|
* **It was capped at 128 until 4.8.6, and the cap declined draws.** A stage of three dozen idling
|
|
31
|
-
* extras beside two
|
|
32
|
-
* the consumer stood its crowd in the bind pose. The ring now holds as many palettes as a frame
|
|
31
|
+
* extras beside two characters filled it in one frame and every skinned draw past it was refused,
|
|
32
|
+
* so the consumer stood its crowd in the bind pose. The ring now holds as many palettes as a frame
|
|
33
33
|
* holds draws (`RenderQuality.drawsPerFrame`), since no frame can bind more palettes than it draws;
|
|
34
34
|
* a slot still allocates its texture only the first time a frame reaches it, so a scene with two
|
|
35
35
|
* 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.js';
|
|
|
46
46
|
export class SkinPaletteRing {
|
|
47
47
|
capacity;
|
|
48
48
|
slots = [];
|
|
49
|
-
/**
|
|
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
|
held = [];
|
|
54
|
+
heldLength = [];
|
|
51
55
|
/** The slot each palette array last went into, by identity. Weak, so a caller's array can go. */
|
|
52
56
|
lastSlot = new WeakMap();
|
|
53
57
|
used = 0;
|
|
@@ -114,12 +118,13 @@ export class SkinPaletteRing {
|
|
|
114
118
|
slot.update(device, palette);
|
|
115
119
|
this.uploads += 1;
|
|
116
120
|
let copy = this.held[at];
|
|
117
|
-
if (copy === undefined || copy.length
|
|
118
|
-
/* Once per slot and
|
|
121
|
+
if (copy === undefined || copy.length < palette.length) {
|
|
122
|
+
/* Once per slot and larger rig, the way the slot's own texture is: never per frame. */
|
|
119
123
|
copy = new Float32Array(palette.length);
|
|
120
124
|
this.held[at] = copy;
|
|
121
125
|
}
|
|
122
126
|
copy.set(palette);
|
|
127
|
+
this.heldLength[at] = palette.length;
|
|
123
128
|
this.lastSlot.set(palette, at);
|
|
124
129
|
return at;
|
|
125
130
|
}
|
|
@@ -132,14 +137,15 @@ export class SkinPaletteRing {
|
|
|
132
137
|
slot.dispose();
|
|
133
138
|
this.slots.length = 0;
|
|
134
139
|
this.held.length = 0;
|
|
140
|
+
this.heldLength.length = 0;
|
|
135
141
|
this.used = 0;
|
|
136
142
|
}
|
|
137
143
|
/** Whether a slot was last written with exactly these numbers. */
|
|
138
144
|
holds(slot, palette) {
|
|
139
145
|
const copy = this.held[slot];
|
|
140
|
-
if (copy === undefined ||
|
|
146
|
+
if (copy === undefined || this.heldLength[slot] !== palette.length)
|
|
141
147
|
return false;
|
|
142
|
-
for (let i = 0; i <
|
|
148
|
+
for (let i = 0; i < palette.length; i++)
|
|
143
149
|
if (copy[i] !== palette[i])
|
|
144
150
|
return false;
|
|
145
151
|
return true;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export declare class SkinPaletteTexture {
|
|
2
2
|
private texture;
|
|
3
|
+
/**
|
|
4
|
+
* How many joints the texture has room for, which is the most any rig in this slot has had.
|
|
5
|
+
* **It grows and never shrinks**: the shader reads a joint by index, so a texture wider than the
|
|
6
|
+
* rig is read exactly as one that fits, and a slot that a 10-joint prop and a 296-joint character
|
|
7
|
+
* take in turn keeps one texture rather than making one at every change of rig.
|
|
8
|
+
*/
|
|
3
9
|
private joints;
|
|
4
10
|
/**
|
|
5
11
|
* The view, cached rather than created per call.
|
|
@@ -20,6 +20,12 @@ const TEXTURE_USAGE = {
|
|
|
20
20
|
};
|
|
21
21
|
export class SkinPaletteTexture {
|
|
22
22
|
texture = null;
|
|
23
|
+
/**
|
|
24
|
+
* How many joints the texture has room for, which is the most any rig in this slot has had.
|
|
25
|
+
* **It grows and never shrinks**: the shader reads a joint by index, so a texture wider than the
|
|
26
|
+
* rig is read exactly as one that fits, and a slot that a 10-joint prop and a 296-joint character
|
|
27
|
+
* take in turn keeps one texture rather than making one at every change of rig.
|
|
28
|
+
*/
|
|
23
29
|
joints = 0;
|
|
24
30
|
/**
|
|
25
31
|
* The view, cached rather than created per call.
|
|
@@ -34,7 +40,7 @@ export class SkinPaletteTexture {
|
|
|
34
40
|
validateSkinPalette(palette);
|
|
35
41
|
const joints = palette.length / 16;
|
|
36
42
|
const width = paletteTextureWidth(joints);
|
|
37
|
-
if (this.texture === null ||
|
|
43
|
+
if (this.texture === null || joints > this.joints) {
|
|
38
44
|
this.texture?.destroy();
|
|
39
45
|
this.cachedView = null;
|
|
40
46
|
this.texture = device.createTexture({
|