@driftengine/core 4.8.3 → 4.8.5
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 +82 -0
- package/README.md +1 -1
- package/dist/build/shaderComments.js +0 -0
- package/dist/geometry/meshConcat.js +2 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +16 -1
- package/dist/math/scalar.js +1 -1
- package/dist/render/anisotropicLobe.d.ts +7 -0
- package/dist/render/anisotropicLobe.js +33 -0
- package/dist/render/backend/api.d.ts +41 -4
- package/dist/render/backend/select.js +8 -0
- package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
- package/dist/render/backend/webgl2/clothTextures.js +93 -0
- package/dist/render/backend/webgl2/renderer.d.ts +136 -3
- package/dist/render/backend/webgl2/renderer.js +604 -63
- package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
- package/dist/render/backend/webgl2/skinScatter.js +256 -0
- package/dist/render/backend/webgpu/buffers.d.ts +25 -11
- package/dist/render/backend/webgpu/buffers.js +98 -49
- package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
- package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
- package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
- package/dist/render/backend/webgpu/clothTextures.js +145 -0
- package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
- package/dist/render/backend/webgpu/compressedUpload.js +38 -0
- package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
- package/dist/render/backend/webgpu/computeHost.js +5 -0
- package/dist/render/backend/webgpu/depthPass.d.ts +16 -6
- package/dist/render/backend/webgpu/depthPass.js +39 -9
- package/dist/render/backend/webgpu/drawCommand.js +6 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +38 -4
- package/dist/render/backend/webgpu/flatPass.js +109 -12
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
- package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
- package/dist/render/backend/webgpu/motionPass.js +106 -9
- package/dist/render/backend/webgpu/postPass.d.ts +3 -1
- package/dist/render/backend/webgpu/postPass.js +6 -3
- package/dist/render/backend/webgpu/renderer.d.ts +113 -5
- package/dist/render/backend/webgpu/renderer.js +498 -48
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
- package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
- package/dist/render/clothBindingData.d.ts +41 -0
- package/dist/render/clothBindingData.js +72 -0
- package/dist/render/clothPlacement.d.ts +25 -0
- package/dist/render/clothPlacement.js +85 -0
- package/dist/render/compressedSource.d.ts +66 -0
- package/dist/render/compressedSource.js +138 -0
- package/dist/render/cutoutCaster.d.ts +2 -0
- package/dist/render/cutoutCaster.js +2 -0
- package/dist/render/cutoutDither.d.ts +51 -0
- package/dist/render/cutoutDither.js +90 -0
- package/dist/render/eyeRefraction.d.ts +35 -0
- package/dist/render/eyeRefraction.js +40 -0
- package/dist/render/glCompressed.d.ts +27 -0
- package/dist/render/glCompressed.js +53 -0
- package/dist/render/hairLobes.d.ts +70 -0
- package/dist/render/hairLobes.js +120 -0
- package/dist/render/lightBudget.d.ts +22 -0
- package/dist/render/lightBudget.js +22 -0
- package/dist/render/mesh.d.ts +7 -0
- package/dist/render/mesh.js +24 -0
- package/dist/render/recon/motionVectors.d.ts +4 -2
- package/dist/render/recon/motionVectors.js +6 -3
- package/dist/render/renderQuality.d.ts +20 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +12 -0
- package/dist/render/sceneTarget.js +14 -0
- package/dist/render/shaders/clothBinding.d.ts +22 -0
- package/dist/render/shaders/clothBinding.js +80 -0
- package/dist/render/shaders/depth.js +42 -6
- package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
- package/dist/render/shaders/flat/anisotropic.js +68 -0
- package/dist/render/shaders/flat/eye.d.ts +1 -0
- package/dist/render/shaders/flat/eye.js +81 -0
- package/dist/render/shaders/flat/hair.d.ts +1 -0
- package/dist/render/shaders/flat/hair.js +101 -0
- package/dist/render/shaders/flat/index.js +36 -2
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +113 -17
- package/dist/render/shaders/flat/models.d.ts +14 -0
- package/dist/render/shaders/flat/models.js +273 -0
- package/dist/render/shaders/flat/preamble.js +26 -24
- package/dist/render/shaders/flat/skin.d.ts +1 -0
- package/dist/render/shaders/flat/skin.js +114 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +38 -16
- package/dist/render/shaders/generated/depth.wgsl.js +38 -16
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1790 -1704
- package/dist/render/shaders/generated/flat.wgsl.js +1828 -1742
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
- package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +102 -0
- package/dist/render/shaders/skinBlur.d.ts +1 -0
- package/dist/render/shaders/skinBlur.js +103 -0
- package/dist/render/shaders/skinning.d.ts +3 -1
- package/dist/render/shaders/skinning.js +31 -4
- package/dist/render/shadowCasters.d.ts +13 -1
- package/dist/render/skinBlur.d.ts +41 -0
- package/dist/render/skinBlur.js +49 -0
- package/dist/render/skinScatter.d.ts +67 -0
- package/dist/render/skinScatter.js +80 -0
- package/dist/render/skinnedClothRun.d.ts +26 -0
- package/dist/render/skinnedClothRun.js +84 -0
- package/dist/render/surfaceModel.d.ts +129 -0
- package/dist/render/surfaceModel.js +152 -0
- package/dist/render/surfaceTexture.d.ts +44 -4
- package/dist/render/surfaceTexture.js +60 -46
- package/dist/render/vertexDefaults.js +7 -0
- package/package.json +6 -6
- package/src/build/shaderComments.ts +0 -0
- package/src/geometry/meshConcat.ts +2 -0
- package/src/index.ts +40 -1
- package/src/math/scalar.ts +1 -1
- package/src/render/anisotropicLobe.ts +41 -0
- package/src/render/backend/api.ts +52 -3
- package/src/render/backend/select.ts +8 -0
- package/src/render/backend/webgl2/clothTextures.ts +120 -0
- package/src/render/backend/webgl2/renderer.ts +693 -64
- package/src/render/backend/webgl2/skinScatter.ts +309 -0
- package/src/render/backend/webgpu/buffers.ts +137 -63
- package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
- package/src/render/backend/webgpu/clothTextures.ts +194 -0
- package/src/render/backend/webgpu/compressedUpload.ts +65 -0
- package/src/render/backend/webgpu/computeHost.ts +25 -0
- package/src/render/backend/webgpu/depthPass.ts +39 -7
- package/src/render/backend/webgpu/drawCommand.ts +6 -2
- package/src/render/backend/webgpu/flatPass.ts +145 -7
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
- package/src/render/backend/webgpu/motionPass.ts +128 -9
- package/src/render/backend/webgpu/postPass.ts +5 -1
- package/src/render/backend/webgpu/renderer.ts +663 -43
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
- package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
- package/src/render/clothBindingData.ts +89 -0
- package/src/render/clothPlacement.ts +114 -0
- package/src/render/compressedSource.ts +198 -0
- package/src/render/cutoutCaster.ts +5 -0
- package/src/render/cutoutDither.ts +99 -0
- package/src/render/eyeRefraction.ts +72 -0
- package/src/render/glCompressed.ts +86 -0
- package/src/render/hairLobes.ts +188 -0
- package/src/render/lightBudget.ts +25 -0
- package/src/render/mesh.ts +23 -0
- package/src/render/recon/motionVectors.ts +6 -3
- package/src/render/renderQuality.ts +28 -0
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +21 -0
- package/src/render/shaders/clothBinding.ts +81 -0
- package/src/render/shaders/depth.ts +42 -6
- package/src/render/shaders/flat/anisotropic.ts +68 -0
- package/src/render/shaders/flat/eye.ts +82 -0
- package/src/render/shaders/flat/hair.ts +102 -0
- package/src/render/shaders/flat/index.ts +36 -2
- package/src/render/shaders/flat/main.ts +113 -17
- package/src/render/shaders/flat/models.ts +286 -0
- package/src/render/shaders/flat/preamble.ts +26 -24
- package/src/render/shaders/flat/skin.ts +116 -0
- package/src/render/shaders/generated/depth.wgsl.ts +38 -16
- package/src/render/shaders/generated/flat.wgsl.ts +1828 -1742
- package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
- package/src/render/shaders/recon/motion.wgsl.ts +102 -0
- package/src/render/shaders/skinBlur.ts +105 -0
- package/src/render/shaders/skinning.ts +31 -4
- package/src/render/shadowCasters.ts +13 -0
- package/src/render/skinBlur.ts +53 -0
- package/src/render/skinScatter.ts +92 -0
- package/src/render/skinnedClothRun.ts +128 -0
- package/src/render/surfaceModel.ts +274 -0
- package/src/render/surfaceTexture.ts +123 -63
- package/src/render/vertexDefaults.ts +7 -0
- package/dist/math/exact.d.ts +0 -76
- package/dist/math/exact.js +0 -327
- package/src/math/exact.ts +0 -340
|
@@ -24,6 +24,7 @@ import { INSTANCE_STRIDE } from '../../instances.ts';
|
|
|
24
24
|
* without one.
|
|
25
25
|
*/
|
|
26
26
|
const USAGE = {
|
|
27
|
+
COPY_SRC: 0x0004,
|
|
27
28
|
COPY_DST: 0x0008,
|
|
28
29
|
INDEX: 0x0010,
|
|
29
30
|
VERTEX: 0x0020,
|
|
@@ -100,6 +101,13 @@ export const VERTEX_LAYOUT: readonly VertexAttribute[] = [
|
|
|
100
101
|
* three lanes and a spare live here instead of three attributes that do not fit.
|
|
101
102
|
*/
|
|
102
103
|
{ name: 'channel', shaderLocation: 13, format: 'float32x4', components: 4, optional: true },
|
|
104
|
+
/*
|
|
105
|
+
* The fifth to eighth influences, at the last two locations. Only a skinned variant reads them,
|
|
106
|
+
* and only with `SKIN_EIGHT` on; every other mesh is fed zeros by the constants buffer. An
|
|
107
|
+
* instanced pipeline reclaims 14 and 15 with the rest of 11 to 15, as `INSTANCE_LOCATIONS` says.
|
|
108
|
+
*/
|
|
109
|
+
{ name: 'joints2', shaderLocation: 14, format: 'float32x4', components: 4, optional: true },
|
|
110
|
+
{ name: 'weights2', shaderLocation: 15, format: 'float32x4', components: 4, optional: true },
|
|
103
111
|
];
|
|
104
112
|
|
|
105
113
|
/** Geometry on the device, with the buffers bound in layout order. */
|
|
@@ -131,14 +139,16 @@ export interface GpuMesh {
|
|
|
131
139
|
*
|
|
132
140
|
* **Two, and the first is interleaved** — every attribute this mesh actually supplied, woven
|
|
133
141
|
* into one stride — while the second holds one value per *absent* optional attribute at a stride
|
|
134
|
-
* of zero.
|
|
142
|
+
* of zero. **A dynamic mesh has four**: its positions and its normals, each a buffer of its own so
|
|
143
|
+
* `update` writes them alone, then the interleaved rest and the constants. It is emphatically not one buffer per attribute, which this comment said until
|
|
135
144
|
* 2026-09-17 and which cost the motion pass an afternoon: a pipeline built to that description
|
|
136
145
|
* reads a position out of every twelve bytes of an interleaved vertex, draws geometry that is
|
|
137
146
|
* not the mesh, and fails no validation at all.
|
|
138
147
|
*/
|
|
139
148
|
readonly vertexBuffers: readonly GPUBuffer[];
|
|
140
149
|
/**
|
|
141
|
-
* Bytes a vertex takes in the first of those buffers
|
|
150
|
+
* Bytes a vertex takes in the first of those buffers: the interleaved stride, or twelve for a
|
|
151
|
+
* dynamic mesh, whose first buffer is its positions.
|
|
142
152
|
*
|
|
143
153
|
* Kept because it is a function of which *optional* attributes this mesh supplied, so it differs
|
|
144
154
|
* between two meshes drawn by the same pipeline — and a pass that reads only the position still
|
|
@@ -151,8 +161,18 @@ export interface GpuMesh {
|
|
|
151
161
|
* Byte offsets of the joint indices and weights in the interleaved vertex, or null for a mesh
|
|
152
162
|
* that supplied neither. For a pass that skins through a shader of its own — the motion pass —
|
|
153
163
|
* which has to find them without the `present` map the generated pipelines are keyed by.
|
|
164
|
+
*
|
|
165
|
+
* **Null on a dynamic mesh too**, whose first buffer holds positions alone: the motion pass reads
|
|
166
|
+
* joints from that buffer, so a dynamic skinned mesh's motion between rewrites is its model's
|
|
167
|
+
* rather than its pose's. A rewrite carries its own motion, from last frame's positions.
|
|
154
168
|
*/
|
|
155
|
-
readonly skinOffsets: {
|
|
169
|
+
readonly skinOffsets: {
|
|
170
|
+
readonly joints: number;
|
|
171
|
+
readonly weights: number;
|
|
172
|
+
/** The second four influences' offsets, or -1 for a mesh with four. */
|
|
173
|
+
readonly joints2: number;
|
|
174
|
+
readonly weights2: number;
|
|
175
|
+
} | null;
|
|
156
176
|
readonly indexBuffer: GPUBuffer;
|
|
157
177
|
/**
|
|
158
178
|
* How many indices the mesh draws. **Zero is a mesh with nothing to draw, and no pass draws it**:
|
|
@@ -200,6 +220,10 @@ export interface GpuMesh {
|
|
|
200
220
|
* the key is built before the present map is consulted.
|
|
201
221
|
*/
|
|
202
222
|
readonly isSkinned: boolean;
|
|
223
|
+
/** Whether its rig moves a vertex by eight influences, which keys a pipeline with `SKIN_EIGHT`. */
|
|
224
|
+
readonly isSkinnedEight: boolean;
|
|
225
|
+
/** How many vertices it has, which a cloth binding is checked against. */
|
|
226
|
+
readonly vertexCount: number;
|
|
203
227
|
/** Whether it has texture coordinates, without which a material's maps read one texel. */
|
|
204
228
|
readonly hasUvs: boolean;
|
|
205
229
|
/**
|
|
@@ -221,12 +245,13 @@ export interface GpuMesh {
|
|
|
221
245
|
/**
|
|
222
246
|
* Rewrite this mesh's positions, and its normals where the caller has them.
|
|
223
247
|
*
|
|
224
|
-
* Absent unless the mesh was created `dynamic`. **
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
248
|
+
* Absent unless the mesh was created `dynamic`. **A dynamic mesh's positions and normals are
|
|
249
|
+
* buffers of their own**, so this is one `writeBuffer` of the caller's positions and one of its
|
|
250
|
+
* normals, and nothing else moves. Until 4.8.4 every attribute was interleaved into one buffer,
|
|
251
|
+
* and a dynamic mesh kept a CPU copy of it to patch and upload whole — a garment's colours,
|
|
252
|
+
* coordinates, tangents and joints re-sent every frame for the twenty-four bytes a vertex that
|
|
253
|
+
* changed, reported at 2.8 MB a frame for one character. What the split costs is a pipeline per
|
|
254
|
+
* layout, keyed by `:dynamic`, and two more buffers bound per draw of such a mesh.
|
|
230
255
|
*/
|
|
231
256
|
readonly update:
|
|
232
257
|
| ((
|
|
@@ -237,9 +262,9 @@ export interface GpuMesh {
|
|
|
237
262
|
) => void)
|
|
238
263
|
| null;
|
|
239
264
|
/**
|
|
240
|
-
* A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's
|
|
241
|
-
*
|
|
242
|
-
*
|
|
265
|
+
* A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's positions,
|
|
266
|
+
* made the first time a reconstructing renderer rewrites the mesh, and the frame of the last
|
|
267
|
+
* rewrite. Null on a mesh created without `{ dynamic: true }`, which never moves a vertex.
|
|
243
268
|
*/
|
|
244
269
|
readonly motion: { previous: GPUBuffer | null; changed: number } | null;
|
|
245
270
|
dispose(): void;
|
|
@@ -314,10 +339,13 @@ export function createGpuMeshIncremental(
|
|
|
314
339
|
const vertexCount = data.positions.length / 3;
|
|
315
340
|
const layered = data.layers !== undefined;
|
|
316
341
|
const supplied = VERTEX_LAYOUT.filter(
|
|
317
|
-
(attribute) =>
|
|
342
|
+
(attribute) =>
|
|
343
|
+
(!attribute.optional || data[attribute.name as keyof MeshData] !== undefined) &&
|
|
344
|
+
!(dynamic && isStreamed(attribute)),
|
|
318
345
|
).map((attribute) => laidOut(attribute, layered));
|
|
319
346
|
|
|
320
|
-
/* Interleaved: one stride holding every attribute the mesh actually supplied
|
|
347
|
+
/* Interleaved: one stride holding every attribute the mesh actually supplied — less a dynamic
|
|
348
|
+
mesh's positions and normals, which are buffers of their own. */
|
|
321
349
|
const stride = supplied.reduce((sum, attribute) => sum + attribute.components * 4, 0);
|
|
322
350
|
const step = stride / 4;
|
|
323
351
|
const interleaved = new Float32Array(step * vertexCount);
|
|
@@ -381,26 +409,31 @@ export function createGpuMeshIncremental(
|
|
|
381
409
|
usage: USAGE.INDEX | USAGE.COPY_DST,
|
|
382
410
|
});
|
|
383
411
|
|
|
384
|
-
/*
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
412
|
+
/* A dynamic mesh's two moving streams: copied from as well as written, for last frame's positions. */
|
|
413
|
+
const streamBytes = align4(vertexCount * 12);
|
|
414
|
+
const streams = dynamic
|
|
415
|
+
? {
|
|
416
|
+
positions: device.createBuffer({
|
|
417
|
+
label: 'mesh.positions',
|
|
418
|
+
size: streamBytes,
|
|
419
|
+
usage: USAGE.VERTEX | USAGE.COPY_DST | USAGE.COPY_SRC,
|
|
420
|
+
}),
|
|
421
|
+
normals: device.createBuffer({
|
|
422
|
+
label: 'mesh.normals',
|
|
423
|
+
size: streamBytes,
|
|
424
|
+
usage: USAGE.VERTEX | USAGE.COPY_DST,
|
|
425
|
+
}),
|
|
426
|
+
}
|
|
427
|
+
: null;
|
|
398
428
|
|
|
399
429
|
/*
|
|
400
430
|
* The chunks, in whole vertices and whole indices: a step never writes half of either, so the
|
|
401
431
|
* buffer never holds a torn one even for the frame between two steps.
|
|
402
432
|
*/
|
|
403
|
-
const verticesPerStep = Math.max(
|
|
433
|
+
const verticesPerStep = Math.max(
|
|
434
|
+
1,
|
|
435
|
+
Math.floor(UPLOAD_BYTES_PER_STEP / (stride + (dynamic ? 24 : 0))),
|
|
436
|
+
);
|
|
404
437
|
const indexWidth = data.indices.BYTES_PER_ELEMENT;
|
|
405
438
|
const indicesPerStep = Math.max(1, Math.floor(UPLOAD_BYTES_PER_STEP / indexWidth));
|
|
406
439
|
const totalSteps =
|
|
@@ -420,15 +453,18 @@ export function createGpuMeshIncremental(
|
|
|
420
453
|
const mesh = gpuMeshOf({
|
|
421
454
|
vertices,
|
|
422
455
|
constants,
|
|
456
|
+
streams,
|
|
423
457
|
indexBuffer,
|
|
424
|
-
stride,
|
|
458
|
+
stride: streams === null ? stride : 12,
|
|
425
459
|
indexCount: data.indices.length,
|
|
426
460
|
bounds,
|
|
427
461
|
progress,
|
|
428
462
|
hasTangents: data.tangents !== undefined,
|
|
429
463
|
isSkinned: data.joints !== undefined,
|
|
464
|
+
isSkinnedEight: data.joints !== undefined && data.joints2 !== undefined,
|
|
465
|
+
vertexCount,
|
|
430
466
|
hasUvs: data.uvs !== undefined,
|
|
431
|
-
skinOffsets: skinOffsetsOf(supplied),
|
|
467
|
+
skinOffsets: streams === null ? skinOffsetsOf(supplied) : null,
|
|
432
468
|
hasChannel: data.channel !== undefined,
|
|
433
469
|
morph:
|
|
434
470
|
data.morphTargets === undefined || data.morphTargetCount === undefined
|
|
@@ -439,9 +475,10 @@ export function createGpuMeshIncremental(
|
|
|
439
475
|
data.positions.length / 3,
|
|
440
476
|
data.morphTargetCount,
|
|
441
477
|
),
|
|
442
|
-
update:
|
|
443
|
-
|
|
444
|
-
|
|
478
|
+
update:
|
|
479
|
+
streams === null
|
|
480
|
+
? null
|
|
481
|
+
: streamUpdate(streams.positions, streams.normals, streamBytes, bounds, vertexCount),
|
|
445
482
|
});
|
|
446
483
|
|
|
447
484
|
const job: UploadJob = {
|
|
@@ -449,6 +486,7 @@ export function createGpuMeshIncremental(
|
|
|
449
486
|
interleaved,
|
|
450
487
|
device,
|
|
451
488
|
supplied,
|
|
489
|
+
streams,
|
|
452
490
|
vertices,
|
|
453
491
|
indexBuffer,
|
|
454
492
|
vertexCount,
|
|
@@ -471,6 +509,7 @@ interface UploadJob {
|
|
|
471
509
|
interleaved: Float32Array | null;
|
|
472
510
|
readonly device: GPUDevice;
|
|
473
511
|
readonly supplied: readonly VertexAttribute[];
|
|
512
|
+
readonly streams: { readonly positions: GPUBuffer; readonly normals: GPUBuffer } | null;
|
|
474
513
|
readonly vertices: GPUBuffer;
|
|
475
514
|
readonly indexBuffer: GPUBuffer;
|
|
476
515
|
readonly vertexCount: number;
|
|
@@ -510,6 +549,11 @@ function* uploadSteps(job: UploadJob): Generator<void, void, void> {
|
|
|
510
549
|
fieldOffset += attribute.components;
|
|
511
550
|
}
|
|
512
551
|
device.queue.writeBuffer(vertices, from * stride, interleaved, from * step, (to - from) * step);
|
|
552
|
+
if (job.streams !== null) {
|
|
553
|
+
const count = (to - from) * 3;
|
|
554
|
+
device.queue.writeBuffer(job.streams.positions, from * 12, data.positions, from * 3, count);
|
|
555
|
+
device.queue.writeBuffer(job.streams.normals, from * 12, data.normals, from * 3, count);
|
|
556
|
+
}
|
|
513
557
|
taken += 1;
|
|
514
558
|
if (taken < job.totalSteps) yield;
|
|
515
559
|
}
|
|
@@ -579,18 +623,26 @@ function skinOffsetsOf(supplied: readonly VertexAttribute[]): GpuMesh['skinOffse
|
|
|
579
623
|
let floats = 0;
|
|
580
624
|
let joints = -1;
|
|
581
625
|
let weights = -1;
|
|
626
|
+
let joints2 = -1;
|
|
627
|
+
let weights2 = -1;
|
|
582
628
|
for (const attribute of supplied) {
|
|
583
629
|
if (attribute.name === 'joints') joints = floats * 4;
|
|
584
630
|
if (attribute.name === 'weights') weights = floats * 4;
|
|
631
|
+
if (attribute.name === 'joints2') joints2 = floats * 4;
|
|
632
|
+
if (attribute.name === 'weights2') weights2 = floats * 4;
|
|
585
633
|
floats += attribute.components;
|
|
586
634
|
}
|
|
587
|
-
|
|
635
|
+
if (joints < 0 || weights < 0) return null;
|
|
636
|
+
return joints2 >= 0 && weights2 >= 0
|
|
637
|
+
? { joints, weights, joints2, weights2 }
|
|
638
|
+
: { joints, weights, joints2: -1, weights2: -1 };
|
|
588
639
|
}
|
|
589
640
|
|
|
590
641
|
/** The handle, made where it can see only what it hands out. */
|
|
591
642
|
function gpuMeshOf(parts: {
|
|
592
643
|
readonly vertices: GPUBuffer;
|
|
593
644
|
readonly constants: GPUBuffer;
|
|
645
|
+
readonly streams: { readonly positions: GPUBuffer; readonly normals: GPUBuffer } | null;
|
|
594
646
|
readonly indexBuffer: GPUBuffer;
|
|
595
647
|
readonly stride: number;
|
|
596
648
|
readonly indexCount: number;
|
|
@@ -598,6 +650,8 @@ function gpuMeshOf(parts: {
|
|
|
598
650
|
readonly progress: { readonly uploaded: boolean };
|
|
599
651
|
readonly hasTangents: boolean;
|
|
600
652
|
readonly isSkinned: boolean;
|
|
653
|
+
readonly isSkinnedEight: boolean;
|
|
654
|
+
readonly vertexCount: number;
|
|
601
655
|
readonly hasUvs: boolean;
|
|
602
656
|
readonly skinOffsets: GpuMesh['skinOffsets'];
|
|
603
657
|
readonly hasChannel: boolean;
|
|
@@ -605,9 +659,12 @@ function gpuMeshOf(parts: {
|
|
|
605
659
|
readonly update: GpuMesh['update'];
|
|
606
660
|
}): GpuMesh {
|
|
607
661
|
const motion = parts.update === null ? null : { previous: null as GPUBuffer | null, changed: -1 };
|
|
608
|
-
const { vertices, constants, indexBuffer, progress } = parts;
|
|
662
|
+
const { vertices, constants, streams, indexBuffer, progress } = parts;
|
|
609
663
|
return {
|
|
610
|
-
vertexBuffers:
|
|
664
|
+
vertexBuffers:
|
|
665
|
+
streams === null
|
|
666
|
+
? [vertices, constants]
|
|
667
|
+
: [streams.positions, streams.normals, vertices, constants],
|
|
611
668
|
vertexStride: parts.stride,
|
|
612
669
|
skinOffsets: parts.skinOffsets,
|
|
613
670
|
indexBuffer,
|
|
@@ -618,6 +675,8 @@ function gpuMeshOf(parts: {
|
|
|
618
675
|
},
|
|
619
676
|
hasTangents: parts.hasTangents,
|
|
620
677
|
isSkinned: parts.isSkinned,
|
|
678
|
+
isSkinnedEight: parts.isSkinnedEight,
|
|
679
|
+
vertexCount: parts.vertexCount,
|
|
621
680
|
hasUvs: parts.hasUvs,
|
|
622
681
|
hasChannel: parts.hasChannel,
|
|
623
682
|
morph: parts.morph,
|
|
@@ -625,6 +684,8 @@ function gpuMeshOf(parts: {
|
|
|
625
684
|
motion,
|
|
626
685
|
dispose(): void {
|
|
627
686
|
motion?.previous?.destroy();
|
|
687
|
+
streams?.positions.destroy();
|
|
688
|
+
streams?.normals.destroy();
|
|
628
689
|
vertices.destroy();
|
|
629
690
|
constants.destroy();
|
|
630
691
|
indexBuffer.destroy();
|
|
@@ -632,19 +693,23 @@ function gpuMeshOf(parts: {
|
|
|
632
693
|
};
|
|
633
694
|
}
|
|
634
695
|
|
|
696
|
+
/** Whether an attribute is one a dynamic mesh keeps in a buffer of its own. */
|
|
697
|
+
function isStreamed(attribute: VertexAttribute): boolean {
|
|
698
|
+
return attribute.name === 'positions' || attribute.name === 'normals';
|
|
699
|
+
}
|
|
700
|
+
|
|
635
701
|
/**
|
|
636
|
-
* A dynamic mesh's `update
|
|
637
|
-
*
|
|
638
|
-
*
|
|
702
|
+
* A dynamic mesh's `update`: the caller's positions, and its normals where it has them, each
|
|
703
|
+
* written to its own buffer as it came. Last frame's positions, where a reconstruction wants them,
|
|
704
|
+
* are copied on the device before the write — a copy submitted ahead of a `writeBuffer` runs ahead
|
|
705
|
+
* of it on the queue — so no CPU copy of anything is kept.
|
|
639
706
|
*/
|
|
640
|
-
function
|
|
641
|
-
|
|
642
|
-
|
|
707
|
+
function streamUpdate(
|
|
708
|
+
positionsBuffer: GPUBuffer,
|
|
709
|
+
normalsBuffer: GPUBuffer,
|
|
710
|
+
streamBytes: number,
|
|
643
711
|
bounds: Bounds,
|
|
644
712
|
vertexCount: number,
|
|
645
|
-
step: number,
|
|
646
|
-
positionField: number,
|
|
647
|
-
normalField: number,
|
|
648
713
|
): NonNullable<GpuMesh['update']> {
|
|
649
714
|
return (
|
|
650
715
|
target: GPUDevice,
|
|
@@ -652,30 +717,27 @@ function dynamicUpdate(
|
|
|
652
717
|
normals?: Float32Array,
|
|
653
718
|
previous: GPUBuffer | null = null,
|
|
654
719
|
): void => {
|
|
655
|
-
|
|
720
|
+
for (const [name, values] of [
|
|
721
|
+
['update', positions],
|
|
722
|
+
['normals', normals],
|
|
723
|
+
] as const) {
|
|
724
|
+
if (values === undefined || values.length === vertexCount * 3) continue;
|
|
656
725
|
throw new Error(
|
|
657
|
-
`this mesh has ${vertexCount} vertices and the
|
|
726
|
+
`this mesh has ${vertexCount} vertices and the ${name} has ${values.length / 3}. ` +
|
|
658
727
|
"A mesh's vertex count is fixed at creation: the index buffer, the pipeline and " +
|
|
659
728
|
'every other attribute are sized against it.',
|
|
660
729
|
);
|
|
661
730
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
731
|
+
if (previous !== null) {
|
|
732
|
+
const encoder = target.createCommandEncoder({ label: 'mesh.previousPositions' });
|
|
733
|
+
encoder.copyBufferToBuffer(positionsBuffer, 0, previous, 0, streamBytes);
|
|
734
|
+
target.queue.submit([encoder.finish()]);
|
|
735
|
+
}
|
|
665
736
|
/* Everything that decides whether this is on screen starts from the bounds, so a cloth
|
|
666
737
|
that blew sideways out of its original box would be culled while still visible. */
|
|
667
738
|
boundsOfPositions(positions, bounds);
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
interleaved[at + positionField] = positions[vertex * 3] ?? 0;
|
|
671
|
-
interleaved[at + positionField + 1] = positions[vertex * 3 + 1] ?? 0;
|
|
672
|
-
interleaved[at + positionField + 2] = positions[vertex * 3 + 2] ?? 0;
|
|
673
|
-
if (normals === undefined) continue;
|
|
674
|
-
interleaved[at + normalField] = normals[vertex * 3] ?? 0;
|
|
675
|
-
interleaved[at + normalField + 1] = normals[vertex * 3 + 1] ?? 0;
|
|
676
|
-
interleaved[at + normalField + 2] = normals[vertex * 3 + 2] ?? 0;
|
|
677
|
-
}
|
|
678
|
-
target.queue.writeBuffer(vertices, 0, interleaved);
|
|
739
|
+
target.queue.writeBuffer(positionsBuffer, 0, positions);
|
|
740
|
+
if (normals !== undefined) target.queue.writeBuffer(normalsBuffer, 0, normals);
|
|
679
741
|
};
|
|
680
742
|
}
|
|
681
743
|
|
|
@@ -728,8 +790,20 @@ export function vertexBufferLayouts(
|
|
|
728
790
|
let offset = 0;
|
|
729
791
|
|
|
730
792
|
const layered = present['layers'] === true;
|
|
793
|
+
/* A dynamic mesh's positions and normals: buffers of their own, ahead of the rest. */
|
|
794
|
+
const dynamic = present['dynamic'] === true;
|
|
795
|
+
const layouts: GPUVertexBufferLayout[] = [];
|
|
731
796
|
VERTEX_LAYOUT.forEach((declared, index) => {
|
|
732
797
|
const attribute = laidOut(declared, layered);
|
|
798
|
+
if (dynamic && isStreamed(attribute)) {
|
|
799
|
+
layouts.push({
|
|
800
|
+
arrayStride: attribute.components * 4,
|
|
801
|
+
attributes: [
|
|
802
|
+
{ shaderLocation: attribute.shaderLocation, offset: 0, format: attribute.format },
|
|
803
|
+
],
|
|
804
|
+
});
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
733
807
|
/*
|
|
734
808
|
* **The instanced pipeline reclaims the two skinning locations, and it is allowed to because
|
|
735
809
|
* it cannot skin.** Locations 11 and 12 are the joint indices and weights; the INSTANCED
|
|
@@ -780,7 +854,7 @@ export function vertexBufferLayouts(
|
|
|
780
854
|
});
|
|
781
855
|
});
|
|
782
856
|
|
|
783
|
-
|
|
857
|
+
layouts.push({ arrayStride: offset, attributes: interleaved });
|
|
784
858
|
/*
|
|
785
859
|
* The constants buffer is always bound, even with nothing in it: the pipeline layout has
|
|
786
860
|
* to match the buffers a draw sets, and a mesh supplying every attribute would otherwise
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A skinned cloth's set-up as the buffers `shaders/clothSolve.wgsl.ts` reads: per-particle
|
|
3
|
+
* statics in sections, and every constraint as a record sorted by batch.
|
|
4
|
+
*
|
|
5
|
+
* **Sorted by batch so a batch is a range**: a dispatch runs records `begin` to `end`, and a
|
|
6
|
+
* multiplier sits at its record's index. The batches are `batchesOf`'s — the set-up's own where it
|
|
7
|
+
* gives them, coloured otherwise — the same ones the CPU solver walks, so the two solve each batch
|
|
8
|
+
* in the same order.
|
|
9
|
+
*
|
|
10
|
+
* **What a device cannot be handed is said another way here.** An infinite max distance is −1, an
|
|
11
|
+
* infinite stop distance is a stop of radius 0: shader compilers may assume no infinity, and a
|
|
12
|
+
* comparison against one is then whatever the optimiser made of it. Tethers are coloured too, with
|
|
13
|
+
* every particle counted as movable, so two tethers on one particle never run in one dispatch —
|
|
14
|
+
* which keeps the CPU's order for that particle while the rest run at once.
|
|
15
|
+
*
|
|
16
|
+
* A cloth with no rig is skinned by its model matrix alone: one joint, weight 1, whose matrix the
|
|
17
|
+
* solver uploads as the model.
|
|
18
|
+
*/
|
|
19
|
+
import { batchesOf } from '@driftengine/physics';
|
|
20
|
+
import type { ClothBatches, SkinnedClothSetup } from '@driftengine/physics';
|
|
21
|
+
|
|
22
|
+
/** Floats of statics a particle, across its sections. */
|
|
23
|
+
export const CLOTH_STATIC_FLOATS = 29;
|
|
24
|
+
|
|
25
|
+
/** Bit flags of the limits a set-up has: a max distance, a backstop, a frontstop. */
|
|
26
|
+
export const CLOTH_LIMIT_MAX = 1;
|
|
27
|
+
export const CLOTH_LIMIT_BACK = 2;
|
|
28
|
+
export const CLOTH_LIMIT_FRONT = 4;
|
|
29
|
+
|
|
30
|
+
export interface PackedCloth {
|
|
31
|
+
readonly count: number;
|
|
32
|
+
/** inverse mass, max distance, backstop ×2, frontstop ×2, thickness, rest ×3, normal ×3, influences ×16. */
|
|
33
|
+
readonly statics: Float32Array;
|
|
34
|
+
/** Distance records (a, b, rest, compliance), bending (four ids, rest, compliance), tethers (particle, anchor, length). */
|
|
35
|
+
readonly constraints: Uint32Array;
|
|
36
|
+
readonly distances: number;
|
|
37
|
+
readonly bendings: number;
|
|
38
|
+
readonly tethers: number;
|
|
39
|
+
/** Each kind's batches as offsets into its sorted records: batch b is `[b]` up to `[b + 1]`. */
|
|
40
|
+
readonly distanceBatches: Uint32Array;
|
|
41
|
+
readonly bendingBatches: Uint32Array;
|
|
42
|
+
readonly tetherBatches: Uint32Array;
|
|
43
|
+
readonly limits: number;
|
|
44
|
+
/** Skin matrices the solver uploads a pose: the rig's joints, or one for the model alone. */
|
|
45
|
+
readonly joints: number;
|
|
46
|
+
readonly rigged: boolean;
|
|
47
|
+
readonly colliders: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Whether a set-up is skinned by a rig, as `ClothTargets` decides it. */
|
|
51
|
+
function riggedOf(setup: SkinnedClothSetup): boolean {
|
|
52
|
+
return (
|
|
53
|
+
setup.joints !== undefined &&
|
|
54
|
+
setup.weights !== undefined &&
|
|
55
|
+
(setup.inverseBind?.length ?? 0) >= 16
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function packCloth(setup: SkinnedClothSetup): PackedCloth {
|
|
60
|
+
const n = setup.positions.length / 3;
|
|
61
|
+
const inverseMass = setup.inverseMass;
|
|
62
|
+
const limits = setup.limits ?? {};
|
|
63
|
+
const statics = new Float32Array(n * CLOTH_STATIC_FLOATS);
|
|
64
|
+
statics.set(inverseMass, 0);
|
|
65
|
+
const rigged = riggedOf(setup);
|
|
66
|
+
for (let i = 0; i < n; i++) {
|
|
67
|
+
const max = limits.maxDistance?.[i] ?? Infinity;
|
|
68
|
+
statics[n + i] = max < Infinity ? max : -1;
|
|
69
|
+
for (const [stops, at] of [
|
|
70
|
+
[limits.backstop, 2 * n],
|
|
71
|
+
[limits.frontstop, 4 * n],
|
|
72
|
+
] as const) {
|
|
73
|
+
const distance = stops?.[i * 2] ?? Infinity;
|
|
74
|
+
const radius = stops?.[i * 2 + 1] ?? 0;
|
|
75
|
+
const real = distance < Infinity && radius > 0;
|
|
76
|
+
statics[at + i * 2] = real ? distance : 0;
|
|
77
|
+
statics[at + i * 2 + 1] = real ? radius : 0;
|
|
78
|
+
}
|
|
79
|
+
statics[6 * n + i] = limits.thickness?.[i] ?? 0;
|
|
80
|
+
for (let k = 0; k < 3; k++) {
|
|
81
|
+
statics[7 * n + i * 3 + k] = setup.positions[i * 3 + k] as number;
|
|
82
|
+
statics[10 * n + i * 3 + k] = setup.normals?.[i * 3 + k] ?? (k === 2 ? 1 : 0);
|
|
83
|
+
}
|
|
84
|
+
const at = 13 * n + i * 16;
|
|
85
|
+
if (!rigged) {
|
|
86
|
+
statics[at + 4] = 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (let k = 0; k < 4; k++) {
|
|
90
|
+
statics[at + k] = setup.joints?.[i * 4 + k] ?? 0;
|
|
91
|
+
statics[at + 4 + k] = setup.weights?.[i * 4 + k] ?? 0;
|
|
92
|
+
statics[at + 8 + k] = setup.joints2?.[i * 4 + k] ?? 0;
|
|
93
|
+
statics[at + 12 + k] = setup.weights2?.[i * 4 + k] ?? 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { distance, bending, tethers } = setup;
|
|
98
|
+
const distances = distance.rest.length;
|
|
99
|
+
const bendings = bending?.rest.length ?? 0;
|
|
100
|
+
const tetherCount = tethers?.lengths.length ?? 0;
|
|
101
|
+
const words = new Uint32Array(distances * 4 + bendings * 6 + tetherCount * 3);
|
|
102
|
+
const floats = new Float32Array(words.buffer);
|
|
103
|
+
|
|
104
|
+
const distanceBatches = batchesOf('distance', distance.pairs, 2, inverseMass, distance.batches);
|
|
105
|
+
sorted(distanceBatches, (k, r) => {
|
|
106
|
+
const at = r * 4;
|
|
107
|
+
words[at] = distance.pairs[k * 2] as number;
|
|
108
|
+
words[at + 1] = distance.pairs[k * 2 + 1] as number;
|
|
109
|
+
floats[at + 2] = distance.rest[k] as number;
|
|
110
|
+
floats[at + 3] = distance.compliance[k] as number;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
let bendingBatches: Uint32Array = new Uint32Array([0]);
|
|
114
|
+
if (bending !== undefined) {
|
|
115
|
+
const batches = batchesOf('bending', bending.quads, 4, inverseMass, bending.batches);
|
|
116
|
+
bendingBatches = batches.batches;
|
|
117
|
+
sorted(batches, (k, r) => {
|
|
118
|
+
const at = distances * 4 + r * 6;
|
|
119
|
+
for (let j = 0; j < 4; j++) words[at + j] = bending.quads[k * 4 + j] as number;
|
|
120
|
+
floats[at + 4] = bending.rest[k] as number;
|
|
121
|
+
floats[at + 5] = bending.compliance[k] as number;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let tetherBatches: Uint32Array = new Uint32Array([0]);
|
|
126
|
+
if (tethers !== undefined) {
|
|
127
|
+
const movable = new Float32Array(n).fill(1);
|
|
128
|
+
const batches = batchesOf('tethers', tethers.particles, 1, movable, undefined);
|
|
129
|
+
tetherBatches = batches.batches;
|
|
130
|
+
sorted(batches, (k, r) => {
|
|
131
|
+
const at = distances * 4 + bendings * 6 + r * 3;
|
|
132
|
+
words[at] = tethers.particles[k] as number;
|
|
133
|
+
words[at + 1] = tethers.anchors[k] as number;
|
|
134
|
+
floats[at + 2] = tethers.lengths[k] as number;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
count: n,
|
|
140
|
+
statics,
|
|
141
|
+
constraints: words,
|
|
142
|
+
distances,
|
|
143
|
+
bendings,
|
|
144
|
+
tethers: tetherCount,
|
|
145
|
+
distanceBatches: distanceBatches.batches,
|
|
146
|
+
bendingBatches,
|
|
147
|
+
tetherBatches,
|
|
148
|
+
limits:
|
|
149
|
+
(limits.maxDistance !== undefined ? CLOTH_LIMIT_MAX : 0) |
|
|
150
|
+
(limits.backstop !== undefined ? CLOTH_LIMIT_BACK : 0) |
|
|
151
|
+
(limits.frontstop !== undefined ? CLOTH_LIMIT_FRONT : 0),
|
|
152
|
+
joints: rigged ? (setup.inverseBind?.length ?? 0) / 16 : 1,
|
|
153
|
+
rigged,
|
|
154
|
+
colliders: setup.colliders?.length ?? 0,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Each constraint `k` with the place `r` it takes in batch order. */
|
|
159
|
+
function sorted(batches: ClothBatches, write: (k: number, r: number) => void): void {
|
|
160
|
+
for (let r = 0; r < batches.order.length; r++) write(batches.order[r] as number, r);
|
|
161
|
+
}
|