@driftengine/core 4.10.0 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +71 -0
- package/README.md +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/render/backend/webgl2/renderer.d.ts +99 -2
- package/dist/render/backend/webgl2/renderer.js +361 -42
- package/dist/render/backend/webgpu/buffers.d.ts +5 -0
- package/dist/render/backend/webgpu/buffers.js +98 -48
- 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/drawCommand.d.ts +2 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
- package/dist/render/backend/webgpu/flatPass.js +2 -1
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
- package/dist/render/backend/webgpu/pipelineCache.js +39 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +48 -0
- package/dist/render/backend/webgpu/renderer.js +207 -27
- 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/staticBundles.js +2 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -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/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/mesh.d.ts +9 -0
- package/dist/render/mesh.js +38 -14
- 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/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- 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 +15 -0
- package/dist/render/shaders/flat/index.js +15 -2
- 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 +43 -16
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- 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 +41 -19
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
- package/dist/render/shaders/generated/flat.wgsl.js +791 -391
- 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/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- 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 +30 -0
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/package.json +6 -6
- package/src/index.ts +4 -2
- package/src/render/backend/webgl2/renderer.ts +408 -55
- package/src/render/backend/webgpu/buffers.ts +153 -54
- 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/drawCommand.ts +3 -0
- package/src/render/backend/webgpu/flatPass.ts +26 -28
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +46 -7
- package/src/render/backend/webgpu/renderer.ts +274 -48
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/staticBundles.ts +2 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/mesh.ts +66 -14
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/shader.ts +59 -0
- 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 +30 -2
- package/src/render/shaders/flat/layered.ts +115 -0
- package/src/render/shaders/flat/main.ts +43 -16
- package/src/render/shaders/flat/mapReads.ts +38 -0
- 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 +41 -19
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/flat.wgsl.ts +791 -391
- 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/tangentFrame.ts +16 -3
- package/src/render/skinnedClothRun.ts +138 -38
- 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 +30 -0
- 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
|
`;
|
|
@@ -175,6 +175,7 @@ export function takeRecorded(pool) {
|
|
|
175
175
|
command.offsetCount = 0;
|
|
176
176
|
command.vertexCount = 0;
|
|
177
177
|
command.indexBuffer = null;
|
|
178
|
+
command.indexFormat = 'uint32';
|
|
178
179
|
command.indexed = false;
|
|
179
180
|
command.instances = 1;
|
|
180
181
|
command.indirect = null;
|
|
@@ -213,7 +214,7 @@ export function encodeBundle(device, target, pool, passOffset, viewOffset, label
|
|
|
213
214
|
encoder.setVertexBuffer(slot, buffer);
|
|
214
215
|
}
|
|
215
216
|
if (command.indexed && command.indexBuffer !== null) {
|
|
216
|
-
encoder.setIndexBuffer(command.indexBuffer,
|
|
217
|
+
encoder.setIndexBuffer(command.indexBuffer, command.indexFormat);
|
|
217
218
|
if (command.indirect !== null)
|
|
218
219
|
encoder.drawIndexedIndirect(command.indirect, 0);
|
|
219
220
|
else
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type AttributePacking, type VertexPacking } from '../../vertexPacking.ts';
|
|
2
|
+
/** The key's suffix for this packing: one word each attribute not travelling as floats. */
|
|
3
|
+
export declare function packingKey(packing: VertexPacking): string;
|
|
4
|
+
/** Mark this packing in a pipeline's `present`, which is what `vertexBufferLayouts` reads. */
|
|
5
|
+
export declare function markPacking(present: Record<string, boolean>, packing: VertexPacking): void;
|
|
6
|
+
/** Mark the packing a mesh's key names, for a pass that has the mesh and not its data. */
|
|
7
|
+
export declare function markPackingOfKey(present: Record<string, boolean>, key: string): void;
|
|
8
|
+
/** How `present` says this attribute travels: floats unless a word says otherwise. */
|
|
9
|
+
export declare function packingIn(present: Readonly<Record<string, boolean>>, name: string): AttributePacking;
|
|
10
|
+
/** The format an attribute is read with, packed this way, or `float` as given. */
|
|
11
|
+
export declare function packedFormat(float: GPUVertexFormat, packing: AttributePacking): GPUVertexFormat;
|
|
12
|
+
/** Bytes a vertex spends on one attribute read with `format`. */
|
|
13
|
+
export declare function formatBytes(format: GPUVertexFormat): number;
|
|
14
|
+
/**
|
|
15
|
+
* One attribute of vertices `from` to `to` into its sixteen-bit field of the interleaved rows,
|
|
16
|
+
* `scale` being `SNORM16_ONE` or `UNORM16_ONE`. Rounded by `toFixed16`, as `fixedArray` rounds
|
|
17
|
+
* for the other backend; the range was checked when the packing was chosen.
|
|
18
|
+
*
|
|
19
|
+
* Unrolled by width, as `interleaveField` is and for its reason: the general loop was most of a
|
|
20
|
+
* heavy load's upload. Three and four are the widths a packed attribute has.
|
|
21
|
+
*/
|
|
22
|
+
export declare function interleaveFixed(rows: Int16Array | Uint16Array, source: Float32Array, width: number, step: number, fieldOffset: number, from: number, to: number, scale: number): void;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { PACKED_ATTRIBUTES, toFixed16, } from '../../vertexPacking.js';
|
|
2
|
+
/**
|
|
3
|
+
* A mesh's packed attributes as a WebGPU vertex reads them: each one's format and bytes, the words
|
|
4
|
+
* a pipeline key carries for them, and the writers that put sixteen-bit fixed point into the
|
|
5
|
+
* interleaved rows. What is packed how is `vertexPacking.ts`'s decision; this is only its binding.
|
|
6
|
+
*
|
|
7
|
+
* **Sixteen-bit fields are four wide, never three**, because WebGPU has no three-component
|
|
8
|
+
* sixteen-bit format: a normal or a colour takes `x4` and leaves the fourth zero, and the shader's
|
|
9
|
+
* `vec3` input reads the first three. So a normal costs eight bytes here against six on WebGL2,
|
|
10
|
+
* which reads three. Every field stays a whole number of four-byte words, so a vertex does too.
|
|
11
|
+
*/
|
|
12
|
+
/** The ways an attribute can travel other than as floats, as the key words name them. */
|
|
13
|
+
const PACKINGS = ['snorm16', 'unorm16', 'constant'];
|
|
14
|
+
/** The pipeline key's word for one attribute's packing. Not a prefix of any other word. */
|
|
15
|
+
function word(name, packing) {
|
|
16
|
+
return `${name}~${packing}`;
|
|
17
|
+
}
|
|
18
|
+
/** The key's suffix for this packing: one word each attribute not travelling as floats. */
|
|
19
|
+
export function packingKey(packing) {
|
|
20
|
+
let key = '';
|
|
21
|
+
for (const name of PACKED_ATTRIBUTES) {
|
|
22
|
+
if (packing[name] !== 'float')
|
|
23
|
+
key += `:${word(name, packing[name])}`;
|
|
24
|
+
}
|
|
25
|
+
return key;
|
|
26
|
+
}
|
|
27
|
+
/** Mark this packing in a pipeline's `present`, which is what `vertexBufferLayouts` reads. */
|
|
28
|
+
export function markPacking(present, packing) {
|
|
29
|
+
for (const name of PACKED_ATTRIBUTES) {
|
|
30
|
+
if (packing[name] !== 'float')
|
|
31
|
+
present[word(name, packing[name])] = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Mark the packing a mesh's key names, for a pass that has the mesh and not its data. */
|
|
35
|
+
export function markPackingOfKey(present, key) {
|
|
36
|
+
for (const name of PACKED_ATTRIBUTES) {
|
|
37
|
+
for (const packing of PACKINGS) {
|
|
38
|
+
if (key.includes(`:${word(name, packing)}`))
|
|
39
|
+
present[word(name, packing)] = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** How `present` says this attribute travels: floats unless a word says otherwise. */
|
|
44
|
+
export function packingIn(present, name) {
|
|
45
|
+
for (const packing of PACKINGS) {
|
|
46
|
+
if (present[word(name, packing)] === true)
|
|
47
|
+
return packing;
|
|
48
|
+
}
|
|
49
|
+
return 'float';
|
|
50
|
+
}
|
|
51
|
+
/** The format an attribute is read with, packed this way, or `float` as given. */
|
|
52
|
+
export function packedFormat(float, packing) {
|
|
53
|
+
if (packing === 'snorm16')
|
|
54
|
+
return 'snorm16x4';
|
|
55
|
+
if (packing === 'unorm16')
|
|
56
|
+
return 'unorm16x4';
|
|
57
|
+
return float;
|
|
58
|
+
}
|
|
59
|
+
/** Bytes a vertex spends on one attribute read with `format`. */
|
|
60
|
+
export function formatBytes(format) {
|
|
61
|
+
if (format === 'snorm16x4' || format === 'unorm16x4')
|
|
62
|
+
return 8;
|
|
63
|
+
if (format === 'float32x4')
|
|
64
|
+
return 16;
|
|
65
|
+
if (format === 'float32x3')
|
|
66
|
+
return 12;
|
|
67
|
+
if (format === 'float32x2')
|
|
68
|
+
return 8;
|
|
69
|
+
return 4;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One attribute of vertices `from` to `to` into its sixteen-bit field of the interleaved rows,
|
|
73
|
+
* `scale` being `SNORM16_ONE` or `UNORM16_ONE`. Rounded by `toFixed16`, as `fixedArray` rounds
|
|
74
|
+
* for the other backend; the range was checked when the packing was chosen.
|
|
75
|
+
*
|
|
76
|
+
* Unrolled by width, as `interleaveField` is and for its reason: the general loop was most of a
|
|
77
|
+
* heavy load's upload. Three and four are the widths a packed attribute has.
|
|
78
|
+
*/
|
|
79
|
+
export function interleaveFixed(rows, source, width, step, fieldOffset, from, to, scale) {
|
|
80
|
+
let write = from * step + fieldOffset;
|
|
81
|
+
const end = to * width;
|
|
82
|
+
if (width === 3) {
|
|
83
|
+
for (let read = from * 3; read < end; read += 3, write += step) {
|
|
84
|
+
rows[write] = toFixed16(source[read], scale);
|
|
85
|
+
rows[write + 1] = toFixed16(source[read + 1], scale);
|
|
86
|
+
rows[write + 2] = toFixed16(source[read + 2], scale);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
for (let read = from * 4; read < end; read += 4, write += step) {
|
|
91
|
+
rows[write] = toFixed16(source[read], scale);
|
|
92
|
+
rows[write + 1] = toFixed16(source[read + 1], scale);
|
|
93
|
+
rows[write + 2] = toFixed16(source[read + 2], scale);
|
|
94
|
+
rows[write + 3] = toFixed16(source[read + 3], scale);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -11,6 +11,8 @@ export interface MediumLight {
|
|
|
11
11
|
readonly sunShadow: number;
|
|
12
12
|
/** World → light clip, **uncorrected**: the shader does its own `* 0.5 + 0.5`. */
|
|
13
13
|
readonly lightViewProj: ReadonlyMat4;
|
|
14
|
+
/** The same for the sun's moving layer, whose shadow pass may be drawn with its own. */
|
|
15
|
+
readonly movingLightViewProj: ReadonlyMat4;
|
|
14
16
|
/** The sun's static, moving and peeled maps, as one array. See `shadowMap.ts`. */
|
|
15
17
|
readonly sunShadows: WebGLTexture;
|
|
16
18
|
readonly peeledEnabled: boolean;
|
|
@@ -173,6 +173,7 @@ export class GlobalMediumPass {
|
|
|
173
173
|
gl.uniform1i(u['uSteps'] ?? null, steps);
|
|
174
174
|
gl.uniform1f(u['uSunShadow'] ?? null, light.sunShadow);
|
|
175
175
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, light.lightViewProj);
|
|
176
|
+
gl.uniformMatrix4fv(u['uMovingLightViewProj'] ?? null, false, light.movingLightViewProj);
|
|
176
177
|
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, light.peeledEnabled ? 1 : 0);
|
|
177
178
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
178
179
|
gl.bindVertexArray(null);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mesh's indices in the narrowest type that holds them: sixteen bits where every vertex can be
|
|
3
|
+
* named in sixteen, thirty-two otherwise — half the index memory for every mesh of 65,536 vertices
|
|
4
|
+
* or fewer, which is most of them. Lossless because `validateMeshData` has already refused an
|
|
5
|
+
* index past the last vertex.
|
|
6
|
+
*
|
|
7
|
+
* **Padded to an even count**, so the sixteen-bit copy is a whole number of four-byte words: WebGPU
|
|
8
|
+
* writes a buffer only in whole words. The extra index is never drawn, since every draw counts the
|
|
9
|
+
* mesh's own indices. What would make this wrong is a draw that counts the buffer instead.
|
|
10
|
+
*
|
|
11
|
+
* **What it costs** is a copy of the indices while the mesh is built, let go once they are on the
|
|
12
|
+
* device; and every draw of the mesh has to say which width it holds, which is why the width
|
|
13
|
+
* travels with the mesh rather than being worked out where it is drawn.
|
|
14
|
+
*/
|
|
15
|
+
/** The most vertices sixteen-bit indices can name: every index below 65,536. */
|
|
16
|
+
export declare const MAX_UINT16_VERTICES = 65536;
|
|
17
|
+
export declare function compactIndices(indices: Uint32Array, vertexCount: number): Uint16Array | Uint32Array;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mesh's indices in the narrowest type that holds them: sixteen bits where every vertex can be
|
|
3
|
+
* named in sixteen, thirty-two otherwise — half the index memory for every mesh of 65,536 vertices
|
|
4
|
+
* or fewer, which is most of them. Lossless because `validateMeshData` has already refused an
|
|
5
|
+
* index past the last vertex.
|
|
6
|
+
*
|
|
7
|
+
* **Padded to an even count**, so the sixteen-bit copy is a whole number of four-byte words: WebGPU
|
|
8
|
+
* writes a buffer only in whole words. The extra index is never drawn, since every draw counts the
|
|
9
|
+
* mesh's own indices. What would make this wrong is a draw that counts the buffer instead.
|
|
10
|
+
*
|
|
11
|
+
* **What it costs** is a copy of the indices while the mesh is built, let go once they are on the
|
|
12
|
+
* device; and every draw of the mesh has to say which width it holds, which is why the width
|
|
13
|
+
* travels with the mesh rather than being worked out where it is drawn.
|
|
14
|
+
*/
|
|
15
|
+
/** The most vertices sixteen-bit indices can name: every index below 65,536. */
|
|
16
|
+
export const MAX_UINT16_VERTICES = 65536;
|
|
17
|
+
export function compactIndices(indices, vertexCount) {
|
|
18
|
+
if (vertexCount > MAX_UINT16_VERTICES)
|
|
19
|
+
return indices;
|
|
20
|
+
const out = new Uint16Array(indices.length + (indices.length % 2));
|
|
21
|
+
out.set(indices);
|
|
22
|
+
return out;
|
|
23
|
+
}
|