@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
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skinned cloth solved on the device, a set of garments at a time: `ClothControl`'s schedule for
|
|
3
|
+
* each garment, over one set of buffers, in one compute pass and one submit a frame.
|
|
4
|
+
*
|
|
5
|
+
* **The CPU solver's steps, as dispatches.** A step is `clothBegin`, then per substep
|
|
6
|
+
* `clothPredict`, every colour of every kind of constraint per iteration, `clothLimit` and
|
|
7
|
+
* `clothFinish`; then `clothBlend` while a reset blends back in. The particles never leave the
|
|
8
|
+
* device: a frame ends with `clothPublish` writing each garment's, at the frame's `alpha`, into the
|
|
9
|
+
* particle texture its bound meshes' vertex stage reads — so a garment costs no upload a frame.
|
|
10
|
+
*
|
|
11
|
+
* **A round is one kind of work for every garment that has it next.** Each garment's control asks
|
|
12
|
+
* for poses, resets and steps as it always did; they wait in its queue (`clothOps.ts`) until the
|
|
13
|
+
* set records them a round at a time — the poses of every garment with a pose next, in one
|
|
14
|
+
* dispatch, then the rests, then a step for every garment with a step next, each colour of each kind
|
|
15
|
+
* one dispatch across the set (`clothSetPack.ts`). A garment with nothing of that kind sits the round
|
|
16
|
+
* out. So a step costs one garment's dispatches whatever the set holds: fifteen garments of nine
|
|
17
|
+
* colours at four iterations are about a hundred a step, where a pass a garment was some fourteen
|
|
18
|
+
* hundred.
|
|
19
|
+
*
|
|
20
|
+
* **One submit a frame, everything it needs written before it.** `queue.writeBuffer` lands ahead of
|
|
21
|
+
* the whole submit rather than between two dispatches, which is the trap `AGENTS.md` (2026-08-27)
|
|
22
|
+
* records for draws and is no different here. So every round's records take a slot of their own, a
|
|
23
|
+
* frame that runs out of slots submits what it has and goes on, and a garment posed while work
|
|
24
|
+
* against its last pose is still unsent sends that work first, since its matrices are one region.
|
|
25
|
+
*
|
|
26
|
+
* **What it gives up**: single precision; a colour dispatched over the most records any garment has
|
|
27
|
+
* in it; and garments of one set step together, so they share a step, substeps, iterations and most
|
|
28
|
+
* steps a frame, refused by name otherwise. Self-collision is accepted and not simulated, as on the
|
|
29
|
+
* CPU.
|
|
30
|
+
*/
|
|
31
|
+
import { ClothControl, exactExp, validateClothSetup } from '@driftengine/physics';
|
|
32
|
+
import { clothKernelsFor } from './clothKernels.js';
|
|
33
|
+
import { CLOTH_OP_POSE, CLOTH_OP_REST, CLOTH_OP_STEP, ClothOps, PACE_HEAD_WORDS, PACE_WORDS, } from './clothOps.js';
|
|
34
|
+
import { CLOTH_CONSTANT_BYTES, clothBatchSlots, clothConstants, clothRoundOffsets, writeClothCarry, } from './clothSetLayout.js';
|
|
35
|
+
import { packClothSet } from './clothSetPack.js';
|
|
36
|
+
import { packCloth } from './clothSolvePack.js';
|
|
37
|
+
import { GpuClothParticles } from './clothTextures.js';
|
|
38
|
+
import { CLOTH_WORKGROUP } from './shaders/clothSolve.wgsl.js';
|
|
39
|
+
import { DYNAMIC_ALIGNMENT } from './uniformRing.js';
|
|
40
|
+
const STORAGE = 0x80;
|
|
41
|
+
const UNIFORM = 0x40;
|
|
42
|
+
const COPY_DST = 0x8;
|
|
43
|
+
const COPY_SRC = 0x4;
|
|
44
|
+
const MAP_READ = 0x1;
|
|
45
|
+
/** Rounds a submit holds before it is sent and the slots start again. */
|
|
46
|
+
const ROUNDS = 64;
|
|
47
|
+
const KINDS = [CLOTH_OP_POSE, CLOTH_OP_REST, CLOTH_OP_STEP];
|
|
48
|
+
export class GpuClothSet {
|
|
49
|
+
device;
|
|
50
|
+
garments = [];
|
|
51
|
+
packed;
|
|
52
|
+
kernels;
|
|
53
|
+
buffers = [];
|
|
54
|
+
state;
|
|
55
|
+
frameData;
|
|
56
|
+
paces;
|
|
57
|
+
group;
|
|
58
|
+
/** One round's records, every garment's: the head an op's, the rest each garment's own. */
|
|
59
|
+
staging;
|
|
60
|
+
stagingWords;
|
|
61
|
+
stagingFloats;
|
|
62
|
+
/** Each kind's first batch slot, and the first garment's publishing slot. */
|
|
63
|
+
batchSlot;
|
|
64
|
+
groups;
|
|
65
|
+
substeps;
|
|
66
|
+
iterations;
|
|
67
|
+
encoder = null;
|
|
68
|
+
pass = null;
|
|
69
|
+
round = 0;
|
|
70
|
+
constructor(device, setups) {
|
|
71
|
+
this.device = device;
|
|
72
|
+
if (setups.length === 0)
|
|
73
|
+
throw new Error('skinned cloth set: no garments');
|
|
74
|
+
for (const setup of setups)
|
|
75
|
+
validateClothSetup(setup);
|
|
76
|
+
const packed = packClothSet(setups.map(packCloth));
|
|
77
|
+
this.packed = packed;
|
|
78
|
+
const n = packed.count;
|
|
79
|
+
const count = setups.length;
|
|
80
|
+
this.kernels = clothKernelsFor(device);
|
|
81
|
+
this.groups = Math.ceil(n / CLOTH_WORKGROUP);
|
|
82
|
+
const buffer = (label, bytes, usage) => {
|
|
83
|
+
const made = device.createBuffer({
|
|
84
|
+
label: `cloth.${label}`,
|
|
85
|
+
size: Math.max(16, bytes),
|
|
86
|
+
usage,
|
|
87
|
+
});
|
|
88
|
+
this.buffers.push(made);
|
|
89
|
+
return made;
|
|
90
|
+
};
|
|
91
|
+
this.staging = new ArrayBuffer(count * PACE_WORDS * 4);
|
|
92
|
+
this.stagingWords = new Uint32Array(this.staging);
|
|
93
|
+
this.stagingFloats = new Float32Array(this.staging);
|
|
94
|
+
setups.forEach((setup, g) => this.garments.push(this.garment(setup, g)));
|
|
95
|
+
const first = this.garments[0].control.parameters;
|
|
96
|
+
this.substeps = first.substeps;
|
|
97
|
+
this.iterations = first.iterations;
|
|
98
|
+
this.refuseStrangers(first);
|
|
99
|
+
const constants = buffer('constants', CLOTH_CONSTANT_BYTES, UNIFORM | COPY_DST);
|
|
100
|
+
const rounds = buffer('rounds', ROUNDS * DYNAMIC_ALIGNMENT, UNIFORM | COPY_DST);
|
|
101
|
+
this.paces = buffer('paces', ROUNDS * this.staging.byteLength, STORAGE | COPY_DST);
|
|
102
|
+
const batchWords = clothBatchSlots(packed);
|
|
103
|
+
const batches = buffer('batches', batchWords.byteLength, UNIFORM | COPY_DST);
|
|
104
|
+
this.state = buffer('state', n * 12 * 4, STORAGE | COPY_DST | COPY_SRC);
|
|
105
|
+
const pose = buffer('pose', n * 9 * 4, STORAGE);
|
|
106
|
+
const statics = buffer('statics', packed.statics.byteLength, STORAGE | COPY_DST);
|
|
107
|
+
const constraints = buffer('constraints', packed.constraints.byteLength, STORAGE | COPY_DST);
|
|
108
|
+
const lambda = buffer('lambda', (packed.distances + packed.bendings) * 4, STORAGE);
|
|
109
|
+
this.frameData = buffer('frame', (packed.joints * 16 + packed.colliders * 8) * 4, STORAGE | COPY_DST);
|
|
110
|
+
const queue = device.queue;
|
|
111
|
+
queue.writeBuffer(constants, 0, clothConstants(packed, first.step / first.substeps));
|
|
112
|
+
queue.writeBuffer(rounds, 0, clothRoundOffsets(ROUNDS, count));
|
|
113
|
+
queue.writeBuffer(batches, 0, batchWords);
|
|
114
|
+
queue.writeBuffer(statics, 0, packed.statics);
|
|
115
|
+
if (packed.constraints.byteLength > 0) {
|
|
116
|
+
queue.writeBuffer(constraints, 0, packed.constraints);
|
|
117
|
+
}
|
|
118
|
+
/* The rest pose until the first pose, which resets each garment to its own. */
|
|
119
|
+
const state = new Float32Array(n * 12);
|
|
120
|
+
setups.forEach((setup, g) => {
|
|
121
|
+
const base = this.garments[g].range.particleBase * 3;
|
|
122
|
+
state.set(setup.positions, base);
|
|
123
|
+
state.set(setup.positions, n * 9 + base);
|
|
124
|
+
});
|
|
125
|
+
queue.writeBuffer(this.state, 0, state);
|
|
126
|
+
const slot = (of) => ({ buffer: of, size: DYNAMIC_ALIGNMENT });
|
|
127
|
+
this.group = device.createBindGroup({
|
|
128
|
+
label: 'cloth.solve',
|
|
129
|
+
layout: this.kernels.layout,
|
|
130
|
+
entries: [
|
|
131
|
+
{ binding: 0, resource: { buffer: constants } },
|
|
132
|
+
{ binding: 1, resource: slot(rounds) },
|
|
133
|
+
{ binding: 2, resource: slot(batches) },
|
|
134
|
+
{ binding: 3, resource: { buffer: this.state } },
|
|
135
|
+
{ binding: 4, resource: { buffer: pose } },
|
|
136
|
+
{ binding: 5, resource: { buffer: statics } },
|
|
137
|
+
{ binding: 6, resource: { buffer: constraints } },
|
|
138
|
+
{ binding: 7, resource: { buffer: lambda } },
|
|
139
|
+
{ binding: 8, resource: { buffer: this.frameData } },
|
|
140
|
+
{ binding: 9, resource: { buffer: this.paces } },
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
const colours = (batches) => (batches.length - 1) * 2;
|
|
144
|
+
this.batchSlot = {
|
|
145
|
+
distance: 0,
|
|
146
|
+
bending: colours(packed.distanceBatches),
|
|
147
|
+
tether: colours(packed.distanceBatches) + colours(packed.bendingBatches),
|
|
148
|
+
publish: colours(packed.distanceBatches) +
|
|
149
|
+
colours(packed.bendingBatches) +
|
|
150
|
+
colours(packed.tetherBatches),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** How many garments the set holds. */
|
|
154
|
+
get size() {
|
|
155
|
+
return this.garments.length;
|
|
156
|
+
}
|
|
157
|
+
/** Garment `g`'s particles, what `setCloth` draws its bound meshes by. */
|
|
158
|
+
particles(g) {
|
|
159
|
+
return this.at(g).particles;
|
|
160
|
+
}
|
|
161
|
+
/** Garment `g`'s rig for the steps to come; see `ClothControl.setPose`. */
|
|
162
|
+
setPose(g, globals, model) {
|
|
163
|
+
this.at(g).control.setPose(globals, model);
|
|
164
|
+
}
|
|
165
|
+
/** The frame's sample of the scene's wind for garment `g`. See `SkinnedCloth.setWind`. */
|
|
166
|
+
setWind(g, x, y, z) {
|
|
167
|
+
const wind = this.at(g).wind;
|
|
168
|
+
wind[0] = x;
|
|
169
|
+
wind[1] = y;
|
|
170
|
+
wind[2] = z;
|
|
171
|
+
}
|
|
172
|
+
/** Garment `g` back to its skinned pose, settled; the next `step` publishes it. */
|
|
173
|
+
reset(g) {
|
|
174
|
+
this.at(g).control.reset();
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* `dt` of the caller's time in whole fixed steps for every garment, and each garment's particles
|
|
178
|
+
* at its `alpha` published for the draws that follow, as the particles of `frame`.
|
|
179
|
+
*/
|
|
180
|
+
step(dt, frame) {
|
|
181
|
+
const garments = this.garments;
|
|
182
|
+
for (let g = 0; g < garments.length; g++)
|
|
183
|
+
garments[g].control.advance(dt);
|
|
184
|
+
this.run();
|
|
185
|
+
const at = this.takeRound();
|
|
186
|
+
for (let g = 0; g < garments.length; g++) {
|
|
187
|
+
const base = g * PACE_WORDS;
|
|
188
|
+
const garment = garments[g];
|
|
189
|
+
this.stagingWords.fill(0, base, base + PACE_HEAD_WORDS);
|
|
190
|
+
this.stagingFloats[base + 2] = garment.control.alpha;
|
|
191
|
+
this.stagingFloats.set(garment.wind, base + 28);
|
|
192
|
+
}
|
|
193
|
+
this.device.queue.writeBuffer(this.paces, this.paceOffset(at), this.staging);
|
|
194
|
+
for (let g = 0; g < garments.length; g++) {
|
|
195
|
+
const garment = garments[g];
|
|
196
|
+
const view = garment.particles.publish(frame);
|
|
197
|
+
const slot = (this.batchSlot.publish + g) * DYNAMIC_ALIGNMENT;
|
|
198
|
+
const groups = Math.ceil(garment.range.count / CLOTH_WORKGROUP);
|
|
199
|
+
this.dispatch('clothPublish', at, slot, garment.outputs[view], groups);
|
|
200
|
+
}
|
|
201
|
+
this.flush();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Garment `g`'s particles where its last step left them, three floats each: for the parity check,
|
|
205
|
+
* which compares them with the CPU solver's positions. Not for a frame — it waits on the device.
|
|
206
|
+
*/
|
|
207
|
+
async read(g) {
|
|
208
|
+
this.run();
|
|
209
|
+
this.flush();
|
|
210
|
+
const { particleBase, count } = this.at(g).range;
|
|
211
|
+
const bytes = count * 12;
|
|
212
|
+
const readback = this.device.createBuffer({
|
|
213
|
+
label: 'cloth.readback',
|
|
214
|
+
size: bytes,
|
|
215
|
+
usage: MAP_READ | COPY_DST,
|
|
216
|
+
});
|
|
217
|
+
const encoder = this.device.createCommandEncoder({ label: 'cloth.read' });
|
|
218
|
+
encoder.copyBufferToBuffer(this.state, particleBase * 12, readback, 0, bytes);
|
|
219
|
+
this.device.queue.submit([encoder.finish()]);
|
|
220
|
+
await readback.mapAsync(MAP_READ);
|
|
221
|
+
const out = new Float32Array(readback.getMappedRange().slice(0));
|
|
222
|
+
readback.unmap();
|
|
223
|
+
readback.destroy();
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
/** Its buffers; the particle textures are the renderer's to retire with the frame. */
|
|
227
|
+
dispose() {
|
|
228
|
+
this.flush();
|
|
229
|
+
for (const made of this.buffers)
|
|
230
|
+
made.destroy();
|
|
231
|
+
}
|
|
232
|
+
at(g) {
|
|
233
|
+
const garment = this.garments[g];
|
|
234
|
+
if (garment === undefined)
|
|
235
|
+
throw new Error(`skinned cloth set: no garment ${g}`);
|
|
236
|
+
return garment;
|
|
237
|
+
}
|
|
238
|
+
/** Garment `g`: its schedule over a device that queues, its particles and its own numbers. */
|
|
239
|
+
garment(setup, g) {
|
|
240
|
+
const range = this.packed.garments[g];
|
|
241
|
+
const particles = new GpuClothParticles(this.device, range.count, true);
|
|
242
|
+
const ops = new ClothOps();
|
|
243
|
+
const solver = {
|
|
244
|
+
targetsChanged: (change) => this.targetsChanged(garment, change),
|
|
245
|
+
rest: () => this.queueOp(garment, CLOTH_OP_REST),
|
|
246
|
+
step: (fraction, blend) => this.queueStep(garment, fraction, blend),
|
|
247
|
+
};
|
|
248
|
+
const control = new ClothControl(setup, solver, false);
|
|
249
|
+
const output = (k) => this.device.createBindGroup({
|
|
250
|
+
label: 'cloth.publish',
|
|
251
|
+
layout: this.kernels.outputLayout,
|
|
252
|
+
entries: [{ binding: 0, resource: particles.viewOf(k) }],
|
|
253
|
+
});
|
|
254
|
+
const garment = {
|
|
255
|
+
range,
|
|
256
|
+
control,
|
|
257
|
+
particles,
|
|
258
|
+
ops,
|
|
259
|
+
wind: new Float32Array(control.parameters.wind),
|
|
260
|
+
outputs: [output(0), output(1)],
|
|
261
|
+
fromAt: 0,
|
|
262
|
+
toAt: 0,
|
|
263
|
+
recorded: false,
|
|
264
|
+
};
|
|
265
|
+
this.writeOwn(garment, g);
|
|
266
|
+
return garment;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* A garment's own words of its record, which every round carries: its colliders, its texture's
|
|
270
|
+
* width and its constants. Its limits ride in the head beside `live`, written as it joins a round.
|
|
271
|
+
*/
|
|
272
|
+
writeOwn(garment, g) {
|
|
273
|
+
const p = garment.control.parameters;
|
|
274
|
+
const h = p.step / p.substeps;
|
|
275
|
+
const base = g * PACE_WORDS;
|
|
276
|
+
const u = this.stagingWords;
|
|
277
|
+
const f = this.stagingFloats;
|
|
278
|
+
u[base + 32] = garment.range.colliderBase;
|
|
279
|
+
u[base + 33] = garment.range.colliders;
|
|
280
|
+
u[base + 34] = garment.particles.width;
|
|
281
|
+
f[base + 36] = exactExp(p.damping * h);
|
|
282
|
+
f[base + 37] = exactExp(p.drag * h);
|
|
283
|
+
f[base + 38] = p.maxDistanceScale;
|
|
284
|
+
f[base + 39] = p.margin;
|
|
285
|
+
f.set(p.gravity, base + 40);
|
|
286
|
+
}
|
|
287
|
+
/** Garments that cannot step together, refused by the first number they disagree on. */
|
|
288
|
+
refuseStrangers(first) {
|
|
289
|
+
this.garments.forEach((garment, g) => {
|
|
290
|
+
const p = garment.control.parameters;
|
|
291
|
+
for (const name of ['step', 'substeps', 'iterations', 'maxSteps']) {
|
|
292
|
+
if (p[name] !== first[name]) {
|
|
293
|
+
throw new Error(`skinned cloth set: garment ${g} has ${name} ${p[name]} where garment 0 has ` +
|
|
294
|
+
`${first[name]}; a set steps its garments together, so they must agree`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
targetsChanged(garment, change) {
|
|
300
|
+
if (change !== 'posed') {
|
|
301
|
+
garment.fromAt = garment.toAt;
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
/*
|
|
305
|
+
* The pose's matrices and colliders are written ahead of the next submit, so any work of this
|
|
306
|
+
* garment's still waiting or recorded — a reset's settle steps, which read the colliders — is
|
|
307
|
+
* sent first, or it would run against this pose rather than its own.
|
|
308
|
+
*/
|
|
309
|
+
if (garment.ops.pending > 0 || garment.recorded) {
|
|
310
|
+
this.run();
|
|
311
|
+
this.flush();
|
|
312
|
+
}
|
|
313
|
+
this.writeFrameData(garment);
|
|
314
|
+
garment.fromAt = garment.toAt;
|
|
315
|
+
garment.toAt = garment.toAt === 0 ? this.packed.count * 3 : 0;
|
|
316
|
+
this.queueOp(garment, CLOTH_OP_POSE);
|
|
317
|
+
}
|
|
318
|
+
queueOp(garment, kind) {
|
|
319
|
+
const at = garment.ops.push(kind);
|
|
320
|
+
const words = garment.ops.headWords;
|
|
321
|
+
words[at + 4] = garment.fromAt;
|
|
322
|
+
words[at + 5] = garment.toAt;
|
|
323
|
+
garment.ops.headFloats.set(garment.wind, at + 28);
|
|
324
|
+
return at;
|
|
325
|
+
}
|
|
326
|
+
queueStep(garment, fraction, blend) {
|
|
327
|
+
const at = this.queueOp(garment, CLOTH_OP_STEP);
|
|
328
|
+
const f = garment.ops.headFloats;
|
|
329
|
+
f[at] = fraction;
|
|
330
|
+
f[at + 1] = blend;
|
|
331
|
+
const { parameters, targets } = garment.control;
|
|
332
|
+
writeClothCarry(parameters, targets.modelBefore, targets.modelNow, garment.ops.headWords, f, at);
|
|
333
|
+
}
|
|
334
|
+
/** Every op every garment is waiting on, a round of one kind at a time, in each garment's order. */
|
|
335
|
+
run() {
|
|
336
|
+
for (;;) {
|
|
337
|
+
let ran = false;
|
|
338
|
+
for (let k = 0; k < KINDS.length; k++) {
|
|
339
|
+
const kind = KINDS[k];
|
|
340
|
+
if (this.waiting(kind)) {
|
|
341
|
+
this.runRound(kind);
|
|
342
|
+
ran = true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (!ran)
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/** Whether any garment has `kind` next. */
|
|
350
|
+
waiting(kind) {
|
|
351
|
+
for (let g = 0; g < this.garments.length; g++) {
|
|
352
|
+
if (this.garments[g].ops.next === kind)
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
/** One round: every garment with `kind` next takes part, and the rest sit it out. */
|
|
358
|
+
runRound(kind) {
|
|
359
|
+
const at = this.takeRound();
|
|
360
|
+
const words = this.stagingWords;
|
|
361
|
+
let blends = false;
|
|
362
|
+
for (let g = 0; g < this.garments.length; g++) {
|
|
363
|
+
const garment = this.garments[g];
|
|
364
|
+
const base = g * PACE_WORDS;
|
|
365
|
+
words.fill(0, base, base + PACE_HEAD_WORDS);
|
|
366
|
+
if (garment.ops.next !== kind)
|
|
367
|
+
continue;
|
|
368
|
+
garment.ops.take(words, base);
|
|
369
|
+
words[base + 6] = 1;
|
|
370
|
+
words[base + 7] = garment.range.limits;
|
|
371
|
+
garment.recorded = true;
|
|
372
|
+
if (this.stagingFloats[base + 1] > 0)
|
|
373
|
+
blends = true;
|
|
374
|
+
}
|
|
375
|
+
this.device.queue.writeBuffer(this.paces, this.paceOffset(at), this.staging);
|
|
376
|
+
if (kind === CLOTH_OP_POSE)
|
|
377
|
+
this.dispatch('clothPose', at, 0, null);
|
|
378
|
+
else if (kind === CLOTH_OP_REST)
|
|
379
|
+
this.dispatch('clothRest', at, 0, null);
|
|
380
|
+
else
|
|
381
|
+
this.stepRound(at, blends);
|
|
382
|
+
}
|
|
383
|
+
stepRound(at, blends) {
|
|
384
|
+
this.dispatch('clothBegin', at, 0, null);
|
|
385
|
+
const { distanceBatches, bendingBatches, tetherBatches } = this.packed;
|
|
386
|
+
for (let s = 0; s < this.substeps; s++) {
|
|
387
|
+
this.dispatch('clothPredict', at, 0, null);
|
|
388
|
+
for (let pass = 0; pass < this.iterations; pass++) {
|
|
389
|
+
const first = pass === 0 ? 1 : 0;
|
|
390
|
+
this.batches('clothDistance', distanceBatches, this.batchSlot.distance, first, at);
|
|
391
|
+
this.batches('clothBending', bendingBatches, this.batchSlot.bending, first, at);
|
|
392
|
+
this.batches('clothTether', tetherBatches, this.batchSlot.tether, first, at);
|
|
393
|
+
}
|
|
394
|
+
this.dispatch('clothLimit', at, 0, null);
|
|
395
|
+
this.dispatch('clothFinish', at, 0, null);
|
|
396
|
+
}
|
|
397
|
+
if (blends)
|
|
398
|
+
this.dispatch('clothBlend', at, 0, null);
|
|
399
|
+
}
|
|
400
|
+
/** Every colour of one kind across the set, in colour order, each a dispatch over its range. */
|
|
401
|
+
batches(entry, batches, firstSlot, first, roundAt) {
|
|
402
|
+
for (let b = 0; b + 1 < batches.length; b++) {
|
|
403
|
+
const size = batches[b + 1] - batches[b];
|
|
404
|
+
if (size === 0)
|
|
405
|
+
continue;
|
|
406
|
+
const slot = firstSlot + b * 2 + first;
|
|
407
|
+
this.dispatch(entry, roundAt, slot * DYNAMIC_ALIGNMENT, null, Math.ceil(size / CLOTH_WORKGROUP));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
dispatch(entry, roundAt, batchAt, output, groups = this.groups) {
|
|
411
|
+
const pass = this.passFor();
|
|
412
|
+
pass.setPipeline(this.kernels.pipelines[entry]);
|
|
413
|
+
pass.setBindGroup(0, this.group, [roundAt, batchAt]);
|
|
414
|
+
if (output !== null)
|
|
415
|
+
pass.setBindGroup(1, output);
|
|
416
|
+
pass.dispatchWorkgroups(groups);
|
|
417
|
+
}
|
|
418
|
+
passFor() {
|
|
419
|
+
if (this.pass === null) {
|
|
420
|
+
this.encoder = this.device.createCommandEncoder({ label: 'cloth' });
|
|
421
|
+
this.pass = this.encoder.beginComputePass({ label: 'cloth' });
|
|
422
|
+
}
|
|
423
|
+
return this.pass;
|
|
424
|
+
}
|
|
425
|
+
/** A round's slot, sending what is recorded first where the slots are spent: its byte offset. */
|
|
426
|
+
takeRound() {
|
|
427
|
+
if (this.round === ROUNDS)
|
|
428
|
+
this.flush();
|
|
429
|
+
return this.round++ * DYNAMIC_ALIGNMENT;
|
|
430
|
+
}
|
|
431
|
+
/** Where the records of the round at `roundAt` sit in `paces`. */
|
|
432
|
+
paceOffset(roundAt) {
|
|
433
|
+
return (roundAt / DYNAMIC_ALIGNMENT) * this.staging.byteLength;
|
|
434
|
+
}
|
|
435
|
+
/** Send what has been recorded: every slot it reads was written ahead of it on the queue. */
|
|
436
|
+
flush() {
|
|
437
|
+
const { pass, encoder } = this;
|
|
438
|
+
if (pass === null || encoder === null)
|
|
439
|
+
return;
|
|
440
|
+
pass.end();
|
|
441
|
+
this.device.queue.submit([encoder.finish()]);
|
|
442
|
+
this.pass = null;
|
|
443
|
+
this.encoder = null;
|
|
444
|
+
this.round = 0;
|
|
445
|
+
for (let g = 0; g < this.garments.length; g++)
|
|
446
|
+
this.garments[g].recorded = false;
|
|
447
|
+
}
|
|
448
|
+
/** A garment's skin matrices — or its model, with no rig — and its colliders, into the set's. */
|
|
449
|
+
writeFrameData(garment) {
|
|
450
|
+
const targets = garment.control.targets;
|
|
451
|
+
const { jointBase, joints, colliderBase, colliders, rigged } = garment.range;
|
|
452
|
+
const queue = this.device.queue;
|
|
453
|
+
const matrices = (rigged && targets.skin !== null ? targets.skin : targets.model);
|
|
454
|
+
queue.writeBuffer(this.frameData, jointBase * 64, matrices, 0, joints * 16);
|
|
455
|
+
if (colliders === 0)
|
|
456
|
+
return;
|
|
457
|
+
const ends = this.packed.joints * 16;
|
|
458
|
+
const radii = ends + this.packed.colliders * 6;
|
|
459
|
+
queue.writeBuffer(this.frameData, (ends + colliderBase * 6) * 4, targets.colliderEnds, 0, colliders * 6);
|
|
460
|
+
queue.writeBuffer(this.frameData, (radii + colliderBase * 2) * 4, targets.colliderRadii, 0, colliders * 2);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
@@ -197,12 +197,17 @@ export class MotionPass {
|
|
|
197
197
|
const eight = offsets.joints2 >= 0 && offsets.weights2 >= 0;
|
|
198
198
|
/*
|
|
199
199
|
* Five byte counts, each under 256 because a vertex is at most 172 bytes, as five base-256
|
|
200
|
-
* digits: exact below 2^
|
|
200
|
+
* digits, and two bits for the weights' formats: exact below 2^42, so two layouts can never
|
|
201
|
+
* share a key.
|
|
201
202
|
*/
|
|
202
|
-
const key = (((stride * 256 + offsets.joints) * 256 + offsets.weights) * 256 + offsets.joints2 + 1) *
|
|
203
|
+
const key = ((((stride * 256 + offsets.joints) * 256 + offsets.weights) * 256 + offsets.joints2 + 1) *
|
|
203
204
|
256 +
|
|
204
205
|
offsets.weights2 +
|
|
205
|
-
1
|
|
206
|
+
1) *
|
|
207
|
+
4 +
|
|
208
|
+
/* And whether each four weights are floats or fixed point, two bits: `vertexPacking.ts`. */
|
|
209
|
+
(offsets.weightsFormat === 'float32x4' ? 0 : 2) +
|
|
210
|
+
(offsets.weights2Format === 'float32x4' ? 0 : 1);
|
|
206
211
|
const pipelines = cloth ? this.clothPipelines : this.skinnedPipelines;
|
|
207
212
|
const held = pipelines.get(key);
|
|
208
213
|
if (held !== undefined)
|
|
@@ -224,11 +229,15 @@ export class MotionPass {
|
|
|
224
229
|
attributes: [
|
|
225
230
|
{ shaderLocation: 0, offset: 0, format: 'float32x3' },
|
|
226
231
|
{ shaderLocation: 11, offset: offsets.joints, format: 'float32x4' },
|
|
227
|
-
{ shaderLocation: 12, offset: offsets.weights, format:
|
|
232
|
+
{ shaderLocation: 12, offset: offsets.weights, format: offsets.weightsFormat },
|
|
228
233
|
...(eight
|
|
229
234
|
? [
|
|
230
235
|
{ shaderLocation: 14, offset: offsets.joints2, format: 'float32x4' },
|
|
231
|
-
{
|
|
236
|
+
{
|
|
237
|
+
shaderLocation: 15,
|
|
238
|
+
offset: offsets.weights2,
|
|
239
|
+
format: offsets.weights2Format,
|
|
240
|
+
},
|
|
232
241
|
]
|
|
233
242
|
: []),
|
|
234
243
|
],
|
|
@@ -570,7 +579,7 @@ export class MotionPass {
|
|
|
570
579
|
}
|
|
571
580
|
pass.setBindGroup(0, this.group, [i * MOTION_DRAW_STRIDE]);
|
|
572
581
|
pass.setVertexBuffer(0, mesh.vertexBuffers[0]);
|
|
573
|
-
pass.setIndexBuffer(mesh.indexBuffer,
|
|
582
|
+
pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
|
|
574
583
|
pass.drawIndexed(mesh.indexCount, instances);
|
|
575
584
|
}
|
|
576
585
|
}
|
|
@@ -79,6 +79,15 @@ export declare class PipelineCache {
|
|
|
79
79
|
* wait for readiness rather than discovering it mid-frame.
|
|
80
80
|
*/
|
|
81
81
|
private readonly compiling;
|
|
82
|
+
/** Keys whose compile off the main thread failed, which `lit` then builds where asked for. */
|
|
83
|
+
private readonly failed;
|
|
84
|
+
/**
|
|
85
|
+
* Whether a lit pipeline first asked for inside a frame is compiled off the main thread while
|
|
86
|
+
* its draw is left out — `RenderQuality.pipelineCompile` `'skip'` — rather than built where it is
|
|
87
|
+
* asked for, which holds the GPU process for the compile. The renderer turns it off while it
|
|
88
|
+
* records static draws, which must hold every draw they were given.
|
|
89
|
+
*/
|
|
90
|
+
skipsCompiling: boolean;
|
|
82
91
|
constructor(device: GPUDevice, format: GPUTextureFormat, sampleCount?: number, glassShadows?: boolean,
|
|
83
92
|
/** The profile's `clusteredLights`, which compiles the clustered arm in or leaves it out. */
|
|
84
93
|
clusteredLights?: boolean);
|
|
@@ -96,7 +105,8 @@ export declare class PipelineCache {
|
|
|
96
105
|
/** Whether this is a lit pipeline, which `enable` rebuilds. See `litSwitches`. */
|
|
97
106
|
lit?: boolean): Promise<GPURenderPipeline>;
|
|
98
107
|
/**
|
|
99
|
-
* Resolve once every pipeline asked for so far has finished compiling
|
|
108
|
+
* Resolve once every pipeline asked for so far has finished compiling, a switch's rebuild
|
|
109
|
+
* among them.
|
|
100
110
|
*
|
|
101
111
|
* A renderer awaits this before its first frame, which is what moves the compile out of the
|
|
102
112
|
* queue and in front of it. A failure is not rethrown: a pipeline that cannot be built will
|
|
@@ -118,6 +128,17 @@ export declare class PipelineCache {
|
|
|
118
128
|
get(key: string, describe: () => GPURenderPipelineDescriptor,
|
|
119
129
|
/** Whether this is a lit pipeline, which `enable` rebuilds. See `litSwitches`. */
|
|
120
130
|
lit?: boolean): GPURenderPipeline;
|
|
131
|
+
/**
|
|
132
|
+
* A lit pipeline for a draw inside a frame: the one built, or one built here — or, where draws
|
|
133
|
+
* skip (`skipsCompiling`), none, with its compile started off the main thread for a later frame to
|
|
134
|
+
* find. A draw handed none is left out.
|
|
135
|
+
*
|
|
136
|
+
* **What skipping gives up** is the draw for the frames its compile takes, a few to a few dozen on
|
|
137
|
+
* a phone, against a frame held for the compile where it is asked for: 130 to 400 ms, reported on
|
|
138
|
+
* a phone for a material first drawn mid-scene. A compile that fails is built here the next time,
|
|
139
|
+
* so the device reports it rather than the draw vanishing for good.
|
|
140
|
+
*/
|
|
141
|
+
lit(key: string, describe: () => GPURenderPipelineDescriptor): GPURenderPipeline | null;
|
|
121
142
|
/**
|
|
122
143
|
* Turn one of the lit stage's switches on, for every lit pipeline built from now on and every one
|
|
123
144
|
* already built. See `litSwitches`.
|
|
@@ -78,6 +78,15 @@ export class PipelineCache {
|
|
|
78
78
|
* wait for readiness rather than discovering it mid-frame.
|
|
79
79
|
*/
|
|
80
80
|
compiling = new Map();
|
|
81
|
+
/** Keys whose compile off the main thread failed, which `lit` then builds where asked for. */
|
|
82
|
+
failed = new Set();
|
|
83
|
+
/**
|
|
84
|
+
* Whether a lit pipeline first asked for inside a frame is compiled off the main thread while
|
|
85
|
+
* its draw is left out — `RenderQuality.pipelineCompile` `'skip'` — rather than built where it is
|
|
86
|
+
* asked for, which holds the GPU process for the compile. The renderer turns it off while it
|
|
87
|
+
* records static draws, which must hold every draw they were given.
|
|
88
|
+
*/
|
|
89
|
+
skipsCompiling = false;
|
|
81
90
|
constructor(device, format, sampleCount = 1, glassShadows = true,
|
|
82
91
|
/** The profile's `clusteredLights`, which compiles the clustered arm in or leaves it out. */
|
|
83
92
|
clusteredLights = false) {
|
|
@@ -127,12 +136,19 @@ export class PipelineCache {
|
|
|
127
136
|
this.pipelines.set(key, built);
|
|
128
137
|
this.compiling.delete(key);
|
|
129
138
|
return built;
|
|
139
|
+
},
|
|
140
|
+
/* Out of the list, or `ready` waits on it for ever; `lit` builds it where asked next. */
|
|
141
|
+
(error) => {
|
|
142
|
+
this.compiling.delete(key);
|
|
143
|
+
this.failed.add(key);
|
|
144
|
+
throw error;
|
|
130
145
|
});
|
|
131
146
|
this.compiling.set(key, promise);
|
|
132
147
|
return promise;
|
|
133
148
|
}
|
|
134
149
|
/**
|
|
135
|
-
* Resolve once every pipeline asked for so far has finished compiling
|
|
150
|
+
* Resolve once every pipeline asked for so far has finished compiling, a switch's rebuild
|
|
151
|
+
* among them.
|
|
136
152
|
*
|
|
137
153
|
* A renderer awaits this before its first frame, which is what moves the compile out of the
|
|
138
154
|
* queue and in front of it. A failure is not rethrown: a pipeline that cannot be built will
|
|
@@ -143,6 +159,8 @@ export class PipelineCache {
|
|
|
143
159
|
while (this.compiling.size > 0) {
|
|
144
160
|
await Promise.allSettled([...this.compiling.values()]);
|
|
145
161
|
}
|
|
162
|
+
/* And a switch's rebuild, whose set replaces the pipelines in one swap: see `enable`. */
|
|
163
|
+
await this.litRebuild;
|
|
146
164
|
}
|
|
147
165
|
/** The pipeline for this key if it is already built, without building one. */
|
|
148
166
|
peek(key) {
|
|
@@ -171,6 +189,26 @@ export class PipelineCache {
|
|
|
171
189
|
this.pipelines.set(key, built);
|
|
172
190
|
return built;
|
|
173
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* A lit pipeline for a draw inside a frame: the one built, or one built here — or, where draws
|
|
194
|
+
* skip (`skipsCompiling`), none, with its compile started off the main thread for a later frame to
|
|
195
|
+
* find. A draw handed none is left out.
|
|
196
|
+
*
|
|
197
|
+
* **What skipping gives up** is the draw for the frames its compile takes, a few to a few dozen on
|
|
198
|
+
* a phone, against a frame held for the compile where it is asked for: 130 to 400 ms, reported on
|
|
199
|
+
* a phone for a material first drawn mid-scene. A compile that fails is built here the next time,
|
|
200
|
+
* so the device reports it rather than the draw vanishing for good.
|
|
201
|
+
*/
|
|
202
|
+
lit(key, describe) {
|
|
203
|
+
const existing = this.pipelines.get(key);
|
|
204
|
+
if (existing !== undefined)
|
|
205
|
+
return existing;
|
|
206
|
+
if (!this.skipsCompiling || this.failed.has(key))
|
|
207
|
+
return this.get(key, describe, true);
|
|
208
|
+
if (!this.compiling.has(key))
|
|
209
|
+
void this.getAsync(key, describe, true).catch(() => { });
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
174
212
|
/**
|
|
175
213
|
* Turn one of the lit stage's switches on, for every lit pipeline built from now on and every one
|
|
176
214
|
* already built. See `litSwitches`.
|
|
@@ -75,7 +75,7 @@ export declare const AO_FRAG_SIZE: 96;
|
|
|
75
75
|
export declare const AO_BLUR_FIELDS: UniformFields;
|
|
76
76
|
export declare const AO_BLUR_SIZE: 48;
|
|
77
77
|
export declare const MEDIUM_FRAG_FIELDS: UniformFields;
|
|
78
|
-
export declare const MEDIUM_FRAG_SIZE:
|
|
78
|
+
export declare const MEDIUM_FRAG_SIZE: 304;
|
|
79
79
|
export declare const MEDIUM_UPSAMPLE_FIELDS: UniformFields;
|
|
80
80
|
export declare const MEDIUM_UPSAMPLE_SIZE: 32;
|
|
81
81
|
export declare const BLOOM_PREFILTER_FIELDS: UniformFields;
|