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