@driftengine/core 4.10.0 → 4.11.1
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 +107 -0
- package/README.md +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +2 -2
- package/dist/render/ambientOcclusionPass.d.ts +3 -2
- package/dist/render/ambientOcclusionPass.js +4 -2
- package/dist/render/backend/api.d.ts +21 -1
- package/dist/render/backend/webgl2/renderer.d.ts +141 -3
- package/dist/render/backend/webgl2/renderer.js +454 -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 +100 -1
- package/dist/render/backend/webgpu/renderer.js +423 -95
- 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/instances.d.ts +13 -1
- package/dist/render/instances.js +10 -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/occlusionFade.d.ts +21 -0
- package/dist/render/occlusionFade.js +27 -0
- 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/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/ambientOcclusion.js +12 -1
- 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 +24 -4
- package/dist/render/shaders/flat/layered.d.ts +27 -0
- package/dist/render/shaders/flat/layered.js +198 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +50 -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 +23 -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/ambientOcclusion.wgsl.d.ts +6 -1
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
- 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 +107 -0
- package/dist/render/surfaceLayers.js +82 -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 +39 -0
- package/dist/render/transmission.d.ts +21 -0
- package/dist/render/transmission.js +21 -0
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/dist/ui/splash.d.ts +42 -2
- package/dist/ui/splash.js +78 -13
- package/package.json +6 -6
- package/src/index.ts +6 -2
- package/src/render/ambientOcclusionPass.ts +4 -1
- package/src/render/backend/api.ts +31 -0
- package/src/render/backend/webgl2/renderer.ts +519 -56
- 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 +622 -164
- 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/instances.ts +18 -1
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/mesh.ts +66 -14
- package/src/render/occlusionFade.ts +29 -0
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/ambientOcclusion.ts +12 -1
- 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 +39 -4
- package/src/render/shaders/flat/layered.ts +198 -0
- package/src/render/shaders/flat/main.ts +50 -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 +23 -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/ambientOcclusion.wgsl.ts +6 -1
- 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 +176 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +39 -0
- package/src/render/transmission.ts +27 -0
- package/src/render/vertexPacking.ts +109 -0
- package/src/ui/splash.ts +83 -14
- 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
|
@@ -6,6 +6,16 @@ import type { MeshData } from '../../mesh.ts';
|
|
|
6
6
|
import { ABSENT_ATTRIBUTE } from '../../vertexDefaults.ts';
|
|
7
7
|
import { UPLOAD_BYTES_PER_STEP } from '../../uploadStep.ts';
|
|
8
8
|
import { INSTANCE_STRIDE } from '../../instances.ts';
|
|
9
|
+
import { compactIndices } from '../../indexWidth.ts';
|
|
10
|
+
import {
|
|
11
|
+
SNORM16_ONE,
|
|
12
|
+
UNORM16_ONE,
|
|
13
|
+
packingFor,
|
|
14
|
+
sharedValue,
|
|
15
|
+
vertexPackingOf,
|
|
16
|
+
type AttributePacking,
|
|
17
|
+
} from '../../vertexPacking.ts';
|
|
18
|
+
import { formatBytes, interleaveFixed, packedFormat, packingIn } from './vertexFormats.ts';
|
|
9
19
|
|
|
10
20
|
/**
|
|
11
21
|
* Mesh geometry as WebGPU buffers.
|
|
@@ -172,8 +182,13 @@ export interface GpuMesh {
|
|
|
172
182
|
/** The second four influences' offsets, or -1 for a mesh with four. */
|
|
173
183
|
readonly joints2: number;
|
|
174
184
|
readonly weights2: number;
|
|
185
|
+
/** How the weights are read: floats, or sixteen-bit fixed point (`vertexPacking.ts`). */
|
|
186
|
+
readonly weightsFormat: GPUVertexFormat;
|
|
187
|
+
readonly weights2Format: GPUVertexFormat;
|
|
175
188
|
} | null;
|
|
176
189
|
readonly indexBuffer: GPUBuffer;
|
|
190
|
+
/** Sixteen or thirty-two bits an index: every pass that binds `indexBuffer` says which. */
|
|
191
|
+
readonly indexFormat: GPUIndexFormat;
|
|
177
192
|
/**
|
|
178
193
|
* How many indices the mesh draws. **Zero is a mesh with nothing to draw, and no pass draws it**:
|
|
179
194
|
* a level can hold none of some kind of geometry, and a draw of zero indices does nothing but
|
|
@@ -357,17 +372,31 @@ export function createGpuMeshIncremental(
|
|
|
357
372
|
const bounds = boundsOfPositions(data.positions, createBounds());
|
|
358
373
|
const vertexCount = data.positions.length / 3;
|
|
359
374
|
const layered = data.layers !== undefined;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
(!attribute.optional || data[attribute.name as keyof MeshData] !== undefined) &&
|
|
363
|
-
!(dynamic && isStreamed(attribute)),
|
|
364
|
-
).map((attribute) => laidOut(attribute, layered));
|
|
375
|
+
/* How each attribute travels, which the pipeline key says too: `vertexPacking.ts`. */
|
|
376
|
+
const packing = vertexPackingOf(data, dynamic);
|
|
365
377
|
|
|
366
378
|
/* Interleaved: one stride holding every attribute the mesh actually supplied — less a dynamic
|
|
367
|
-
mesh's positions and normals, which are buffers of their own
|
|
368
|
-
|
|
369
|
-
const
|
|
370
|
-
|
|
379
|
+
mesh's positions and normals, which are buffers of their own, and any attribute every vertex
|
|
380
|
+
shares, which is a constant. */
|
|
381
|
+
const fields: Field[] = [];
|
|
382
|
+
let stride = 0;
|
|
383
|
+
for (const declared of VERTEX_LAYOUT) {
|
|
384
|
+
if (declared.optional && data[declared.name as keyof MeshData] === undefined) continue;
|
|
385
|
+
if (dynamic && isStreamed(declared)) continue;
|
|
386
|
+
const attribute = laidOut(declared, layered);
|
|
387
|
+
const how = packingFor(packing, attribute.name);
|
|
388
|
+
if (how === 'constant') continue;
|
|
389
|
+
const format = packedFormat(attribute.format, how);
|
|
390
|
+
fields.push({ attribute, packing: how, format, offset: stride });
|
|
391
|
+
stride += formatBytes(format);
|
|
392
|
+
}
|
|
393
|
+
const buffer = new ArrayBuffer(stride * vertexCount);
|
|
394
|
+
const rows: Rows = {
|
|
395
|
+
floats: new Float32Array(buffer),
|
|
396
|
+
signed: new Int16Array(buffer),
|
|
397
|
+
unsigned: new Uint16Array(buffer),
|
|
398
|
+
bytes: new Uint8Array(buffer),
|
|
399
|
+
};
|
|
371
400
|
|
|
372
401
|
/*
|
|
373
402
|
* **Refused here, in one sentence, rather than by the driver a hundred times.**
|
|
@@ -383,7 +412,7 @@ export function createGpuMeshIncremental(
|
|
|
383
412
|
* whose adapter caps at the default, and it deserves a sentence naming the model's size and
|
|
384
413
|
* the device's rather than a wall of validation.
|
|
385
414
|
*/
|
|
386
|
-
const byteLength = align4(
|
|
415
|
+
const byteLength = align4(buffer.byteLength);
|
|
387
416
|
const ceiling = device.limits.maxBufferSize;
|
|
388
417
|
if (typeof ceiling === 'number' && byteLength > ceiling) {
|
|
389
418
|
throw new Error(
|
|
@@ -420,11 +449,20 @@ export function createGpuMeshIncremental(
|
|
|
420
449
|
if (value === undefined) continue;
|
|
421
450
|
absent.set(value, slot * (CONSTANT_SLOT / 4));
|
|
422
451
|
}
|
|
452
|
+
/* And what every vertex shares, where a mesh's attribute is one value: `vertexPacking.ts`. */
|
|
453
|
+
for (let slot = 0; slot < VERTEX_LAYOUT.length; slot++) {
|
|
454
|
+
const attribute = VERTEX_LAYOUT[slot] as VertexAttribute;
|
|
455
|
+
if (packingFor(packing, attribute.name) !== 'constant') continue;
|
|
456
|
+
const values = data[attribute.name as keyof MeshData] as Float32Array;
|
|
457
|
+
absent.set(sharedValue(values, attribute.components), slot * (CONSTANT_SLOT / 4));
|
|
458
|
+
}
|
|
423
459
|
device.queue.writeBuffer(constants, 0, absent);
|
|
424
460
|
|
|
461
|
+
/* Sixteen bits where every vertex fits, which every draw of this mesh says: indexWidth.ts. */
|
|
462
|
+
const indices = compactIndices(data.indices, vertexCount);
|
|
425
463
|
const indexBuffer = device.createBuffer({
|
|
426
464
|
label: 'mesh.indices',
|
|
427
|
-
size: align4(
|
|
465
|
+
size: align4(indices.byteLength),
|
|
428
466
|
usage: USAGE.INDEX | USAGE.COPY_DST,
|
|
429
467
|
});
|
|
430
468
|
|
|
@@ -453,11 +491,12 @@ export function createGpuMeshIncremental(
|
|
|
453
491
|
1,
|
|
454
492
|
Math.floor(UPLOAD_BYTES_PER_STEP / (stride + (dynamic ? 24 : 0))),
|
|
455
493
|
);
|
|
456
|
-
|
|
457
|
-
const
|
|
494
|
+
/* Whole four-byte words a step, which is all a queue writes: two sixteen-bit indices to one. */
|
|
495
|
+
const indexWidth = indices.BYTES_PER_ELEMENT;
|
|
496
|
+
const indicesPerStep = Math.max(1, Math.floor(UPLOAD_BYTES_PER_STEP / 4)) * (4 / indexWidth);
|
|
458
497
|
const totalSteps =
|
|
459
498
|
Math.max(1, Math.ceil(vertexCount / verticesPerStep)) +
|
|
460
|
-
Math.max(1, Math.ceil(
|
|
499
|
+
Math.max(1, Math.ceil(indices.length / indicesPerStep));
|
|
461
500
|
|
|
462
501
|
/*
|
|
463
502
|
* **The mesh is assembled out here, by functions of its own, and that is the fix for a copy
|
|
@@ -474,6 +513,7 @@ export function createGpuMeshIncremental(
|
|
|
474
513
|
constants,
|
|
475
514
|
streams,
|
|
476
515
|
indexBuffer,
|
|
516
|
+
indexFormat: indices instanceof Uint16Array ? 'uint16' : 'uint32',
|
|
477
517
|
stride: streams === null ? stride : 12,
|
|
478
518
|
indexCount: data.indices.length,
|
|
479
519
|
bounds,
|
|
@@ -483,7 +523,7 @@ export function createGpuMeshIncremental(
|
|
|
483
523
|
isSkinnedEight: data.joints !== undefined && data.joints2 !== undefined,
|
|
484
524
|
vertexCount,
|
|
485
525
|
hasUvs: data.uvs !== undefined,
|
|
486
|
-
skinOffsets: streams === null ? skinOffsetsOf(
|
|
526
|
+
skinOffsets: streams === null ? skinOffsetsOf(fields) : null,
|
|
487
527
|
hasChannel: data.channel !== undefined,
|
|
488
528
|
morph:
|
|
489
529
|
data.morphTargets === undefined || data.morphTargetCount === undefined
|
|
@@ -502,14 +542,14 @@ export function createGpuMeshIncremental(
|
|
|
502
542
|
|
|
503
543
|
const job: UploadJob = {
|
|
504
544
|
data,
|
|
505
|
-
|
|
545
|
+
indices,
|
|
546
|
+
rows,
|
|
506
547
|
device,
|
|
507
|
-
|
|
548
|
+
fields,
|
|
508
549
|
streams,
|
|
509
550
|
vertices,
|
|
510
551
|
indexBuffer,
|
|
511
552
|
vertexCount,
|
|
512
|
-
step,
|
|
513
553
|
stride,
|
|
514
554
|
verticesPerStep,
|
|
515
555
|
indicesPerStep,
|
|
@@ -519,20 +559,38 @@ export function createGpuMeshIncremental(
|
|
|
519
559
|
return { mesh, upload: uploadSteps(job) };
|
|
520
560
|
}
|
|
521
561
|
|
|
562
|
+
/** One attribute as a mesh interleaves it: how it travels, the format it is read with, where. */
|
|
563
|
+
interface Field {
|
|
564
|
+
readonly attribute: VertexAttribute;
|
|
565
|
+
readonly packing: AttributePacking;
|
|
566
|
+
readonly format: GPUVertexFormat;
|
|
567
|
+
/** Bytes into the vertex. */
|
|
568
|
+
readonly offset: number;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** The interleaved rows, viewed as each field's type writes them. */
|
|
572
|
+
interface Rows {
|
|
573
|
+
readonly floats: Float32Array;
|
|
574
|
+
readonly signed: Int16Array;
|
|
575
|
+
readonly unsigned: Uint16Array;
|
|
576
|
+
readonly bytes: Uint8Array;
|
|
577
|
+
}
|
|
578
|
+
|
|
522
579
|
/**
|
|
523
580
|
* What an upload reads, held by the upload alone and let go when it finishes — so a caller that
|
|
524
581
|
* keeps its iterator, as a streamer keeps the handle it was given, keeps nothing through it.
|
|
525
582
|
*/
|
|
526
583
|
interface UploadJob {
|
|
527
584
|
data: MeshData | null;
|
|
528
|
-
|
|
585
|
+
/** The mesh's indices at the width its buffer holds them: `compactIndices`. */
|
|
586
|
+
indices: Uint16Array | Uint32Array | null;
|
|
587
|
+
rows: Rows | null;
|
|
529
588
|
readonly device: GPUDevice;
|
|
530
|
-
readonly
|
|
589
|
+
readonly fields: readonly Field[];
|
|
531
590
|
readonly streams: { readonly positions: GPUBuffer; readonly normals: GPUBuffer } | null;
|
|
532
591
|
readonly vertices: GPUBuffer;
|
|
533
592
|
readonly indexBuffer: GPUBuffer;
|
|
534
593
|
readonly vertexCount: number;
|
|
535
|
-
readonly step: number;
|
|
536
594
|
readonly stride: number;
|
|
537
595
|
readonly verticesPerStep: number;
|
|
538
596
|
readonly indicesPerStep: number;
|
|
@@ -541,33 +599,64 @@ interface UploadJob {
|
|
|
541
599
|
}
|
|
542
600
|
|
|
543
601
|
function* uploadSteps(job: UploadJob): Generator<void, void, void> {
|
|
544
|
-
const { device,
|
|
602
|
+
const { device, fields, vertices, indexBuffer, vertexCount, stride } = job;
|
|
603
|
+
/* Floats and halves a vertex: every field is whole words, so both divide. */
|
|
604
|
+
const step = stride / 4;
|
|
605
|
+
const halves = stride / 2;
|
|
545
606
|
let taken = 0;
|
|
546
607
|
for (let from = 0; from < vertexCount; from += job.verticesPerStep) {
|
|
547
608
|
const data = job.data as MeshData;
|
|
548
|
-
const
|
|
609
|
+
const rows = job.rows as Rows;
|
|
549
610
|
const to = Math.min(from + job.verticesPerStep, vertexCount);
|
|
550
|
-
|
|
551
|
-
|
|
611
|
+
for (const field of fields) {
|
|
612
|
+
const attribute = field.attribute;
|
|
552
613
|
const source = data[attribute.name as keyof MeshData] as Float32Array;
|
|
553
|
-
if (
|
|
614
|
+
if (field.packing === 'snorm16') {
|
|
615
|
+
interleaveFixed(
|
|
616
|
+
rows.signed,
|
|
617
|
+
source,
|
|
618
|
+
attribute.components,
|
|
619
|
+
halves,
|
|
620
|
+
field.offset / 2,
|
|
621
|
+
from,
|
|
622
|
+
to,
|
|
623
|
+
SNORM16_ONE,
|
|
624
|
+
);
|
|
625
|
+
} else if (field.packing === 'unorm16') {
|
|
626
|
+
interleaveFixed(
|
|
627
|
+
rows.unsigned,
|
|
628
|
+
source,
|
|
629
|
+
attribute.components,
|
|
630
|
+
halves,
|
|
631
|
+
field.offset / 2,
|
|
632
|
+
from,
|
|
633
|
+
to,
|
|
634
|
+
UNORM16_ONE,
|
|
635
|
+
);
|
|
636
|
+
} else if (attribute === LAYERED_UVS) {
|
|
554
637
|
/* (u, v) from the coordinates and the layer after them, one field of three. */
|
|
555
|
-
|
|
638
|
+
const at = field.offset / 4;
|
|
639
|
+
interleaveField(rows.floats, source, 2, step, at, from, to);
|
|
640
|
+
interleaveField(rows.floats, data.layers as Float32Array, 1, step, at + 2, from, to);
|
|
641
|
+
} else {
|
|
556
642
|
interleaveField(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
643
|
+
rows.floats,
|
|
644
|
+
source,
|
|
645
|
+
attribute.components,
|
|
560
646
|
step,
|
|
561
|
-
|
|
647
|
+
field.offset / 4,
|
|
562
648
|
from,
|
|
563
649
|
to,
|
|
564
650
|
);
|
|
565
|
-
} else {
|
|
566
|
-
interleaveField(interleaved, source, attribute.components, step, fieldOffset, from, to);
|
|
567
651
|
}
|
|
568
|
-
fieldOffset += attribute.components;
|
|
569
652
|
}
|
|
570
|
-
device.queue.writeBuffer(
|
|
653
|
+
device.queue.writeBuffer(
|
|
654
|
+
vertices,
|
|
655
|
+
from * stride,
|
|
656
|
+
rows.bytes,
|
|
657
|
+
from * stride,
|
|
658
|
+
(to - from) * stride,
|
|
659
|
+
);
|
|
571
660
|
if (job.streams !== null) {
|
|
572
661
|
const count = (to - from) * 3;
|
|
573
662
|
device.queue.writeBuffer(job.streams.positions, from * 12, data.positions, from * 3, count);
|
|
@@ -577,7 +666,7 @@ function* uploadSteps(job: UploadJob): Generator<void, void, void> {
|
|
|
577
666
|
if (taken < job.totalSteps) yield;
|
|
578
667
|
}
|
|
579
668
|
|
|
580
|
-
const indices =
|
|
669
|
+
const indices = job.indices as Uint16Array | Uint32Array;
|
|
581
670
|
const indexWidth = indices.BYTES_PER_ELEMENT;
|
|
582
671
|
for (let from = 0; from < indices.length; from += job.indicesPerStep) {
|
|
583
672
|
const to = Math.min(from + job.indicesPerStep, indices.length);
|
|
@@ -587,7 +676,8 @@ function* uploadSteps(job: UploadJob): Generator<void, void, void> {
|
|
|
587
676
|
}
|
|
588
677
|
|
|
589
678
|
job.data = null;
|
|
590
|
-
job.
|
|
679
|
+
job.indices = null;
|
|
680
|
+
job.rows = null;
|
|
591
681
|
job.progress.uploaded = true;
|
|
592
682
|
}
|
|
593
683
|
|
|
@@ -638,23 +728,30 @@ function interleaveField(
|
|
|
638
728
|
* Where the joints and weights sit in a vertex of `supplied`, the attributes a mesh interleaves in
|
|
639
729
|
* layout order; null unless it supplied both.
|
|
640
730
|
*/
|
|
641
|
-
function skinOffsetsOf(
|
|
642
|
-
let floats = 0;
|
|
731
|
+
function skinOffsetsOf(fields: readonly Field[]): GpuMesh['skinOffsets'] {
|
|
643
732
|
let joints = -1;
|
|
644
733
|
let weights = -1;
|
|
645
734
|
let joints2 = -1;
|
|
646
735
|
let weights2 = -1;
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (
|
|
652
|
-
|
|
736
|
+
let weightsFormat: GPUVertexFormat = 'float32x4';
|
|
737
|
+
let weights2Format: GPUVertexFormat = 'float32x4';
|
|
738
|
+
for (const field of fields) {
|
|
739
|
+
const name = field.attribute.name;
|
|
740
|
+
if (name === 'joints') joints = field.offset;
|
|
741
|
+
if (name === 'joints2') joints2 = field.offset;
|
|
742
|
+
if (name === 'weights') {
|
|
743
|
+
weights = field.offset;
|
|
744
|
+
weightsFormat = field.format;
|
|
745
|
+
}
|
|
746
|
+
if (name === 'weights2') {
|
|
747
|
+
weights2 = field.offset;
|
|
748
|
+
weights2Format = field.format;
|
|
749
|
+
}
|
|
653
750
|
}
|
|
654
751
|
if (joints < 0 || weights < 0) return null;
|
|
655
752
|
return joints2 >= 0 && weights2 >= 0
|
|
656
|
-
? { joints, weights, joints2, weights2 }
|
|
657
|
-
: { joints, weights, joints2: -1, weights2: -1 };
|
|
753
|
+
? { joints, weights, joints2, weights2, weightsFormat, weights2Format }
|
|
754
|
+
: { joints, weights, joints2: -1, weights2: -1, weightsFormat, weights2Format };
|
|
658
755
|
}
|
|
659
756
|
|
|
660
757
|
/** The handle, made where it can see only what it hands out. */
|
|
@@ -663,6 +760,7 @@ function gpuMeshOf(parts: {
|
|
|
663
760
|
readonly constants: GPUBuffer;
|
|
664
761
|
readonly streams: { readonly positions: GPUBuffer; readonly normals: GPUBuffer } | null;
|
|
665
762
|
readonly indexBuffer: GPUBuffer;
|
|
763
|
+
readonly indexFormat: GPUIndexFormat;
|
|
666
764
|
readonly stride: number;
|
|
667
765
|
readonly indexCount: number;
|
|
668
766
|
readonly bounds: Bounds;
|
|
@@ -690,6 +788,7 @@ function gpuMeshOf(parts: {
|
|
|
690
788
|
vertexStride: parts.stride,
|
|
691
789
|
skinOffsets: parts.skinOffsets,
|
|
692
790
|
indexBuffer,
|
|
791
|
+
indexFormat: parts.indexFormat,
|
|
693
792
|
indexCount: parts.indexCount,
|
|
694
793
|
bounds: parts.bounds,
|
|
695
794
|
get complete(): boolean {
|
|
@@ -844,7 +943,10 @@ export function vertexBufferLayouts(
|
|
|
844
943
|
* in the interleaved one. A *skinned* mesh interleaves them, so removing them would shift
|
|
845
944
|
* the stride under every other attribute — which is why `createInstanced` refuses one.
|
|
846
945
|
*/
|
|
847
|
-
|
|
946
|
+
/* One value every vertex shares sits with the absent ones: `vertexPacking.ts`. */
|
|
947
|
+
const packing = packingIn(present, String(attribute.name));
|
|
948
|
+
const supplied =
|
|
949
|
+
(!attribute.optional || present[String(attribute.name)] === true) && packing !== 'constant';
|
|
848
950
|
if (instanced && INSTANCE_LOCATIONS.has(attribute.shaderLocation)) {
|
|
849
951
|
/*
|
|
850
952
|
* **Free to drop only because the mesh does not supply it.** An absent optional attribute
|
|
@@ -866,12 +968,9 @@ export function vertexBufferLayouts(
|
|
|
866
968
|
return;
|
|
867
969
|
}
|
|
868
970
|
if (supplied) {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
format: attribute.format,
|
|
873
|
-
});
|
|
874
|
-
offset += attribute.components * 4;
|
|
971
|
+
const format = packedFormat(attribute.format, packing);
|
|
972
|
+
interleaved.push({ shaderLocation: attribute.shaderLocation, offset, format });
|
|
973
|
+
offset += formatBytes(format);
|
|
875
974
|
return;
|
|
876
975
|
}
|
|
877
976
|
constants.push({
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cloth solver's layouts and one compute pipeline an entry point of `shaders/clothSolve.wgsl.ts`,
|
|
3
|
+
* built once a device and shared by every set of garments on it.
|
|
4
|
+
*/
|
|
5
|
+
import { CLOTH_SOLVE_WGSL } from './shaders/clothSolve.wgsl.ts';
|
|
6
|
+
import { shaderModule } from './shaderModules.ts';
|
|
7
|
+
|
|
8
|
+
const COMPUTE = 0x4;
|
|
9
|
+
|
|
10
|
+
export const CLOTH_ENTRIES = [
|
|
11
|
+
'clothPose',
|
|
12
|
+
'clothRest',
|
|
13
|
+
'clothBegin',
|
|
14
|
+
'clothPredict',
|
|
15
|
+
'clothDistance',
|
|
16
|
+
'clothBending',
|
|
17
|
+
'clothTether',
|
|
18
|
+
'clothLimit',
|
|
19
|
+
'clothFinish',
|
|
20
|
+
'clothBlend',
|
|
21
|
+
'clothPublish',
|
|
22
|
+
] as const;
|
|
23
|
+
export type ClothEntry = (typeof CLOTH_ENTRIES)[number];
|
|
24
|
+
|
|
25
|
+
export interface ClothKernels {
|
|
26
|
+
readonly layout: GPUBindGroupLayout;
|
|
27
|
+
readonly outputLayout: GPUBindGroupLayout;
|
|
28
|
+
readonly pipelines: Readonly<Record<ClothEntry, GPUComputePipeline>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const kernelsByDevice = new WeakMap<GPUDevice, ClothKernels>();
|
|
32
|
+
|
|
33
|
+
export function clothKernelsFor(device: GPUDevice): ClothKernels {
|
|
34
|
+
const held = kernelsByDevice.get(device);
|
|
35
|
+
if (held !== undefined) return held;
|
|
36
|
+
const uniform = (binding: number, dynamic: boolean): GPUBindGroupLayoutEntry => ({
|
|
37
|
+
binding,
|
|
38
|
+
visibility: COMPUTE,
|
|
39
|
+
buffer: { type: 'uniform', hasDynamicOffset: dynamic },
|
|
40
|
+
});
|
|
41
|
+
const storage = (binding: number, type: GPUBufferBindingType): GPUBindGroupLayoutEntry => ({
|
|
42
|
+
binding,
|
|
43
|
+
visibility: COMPUTE,
|
|
44
|
+
buffer: { type },
|
|
45
|
+
});
|
|
46
|
+
/* Seven storage buffers of the eight a stage is granted: the shader's header says why so few. */
|
|
47
|
+
const layout = device.createBindGroupLayout({
|
|
48
|
+
label: 'cloth.solve',
|
|
49
|
+
entries: [
|
|
50
|
+
uniform(0, false),
|
|
51
|
+
uniform(1, true),
|
|
52
|
+
uniform(2, true),
|
|
53
|
+
storage(3, 'storage'),
|
|
54
|
+
storage(4, 'storage'),
|
|
55
|
+
storage(5, 'read-only-storage'),
|
|
56
|
+
storage(6, 'read-only-storage'),
|
|
57
|
+
storage(7, 'storage'),
|
|
58
|
+
storage(8, 'read-only-storage'),
|
|
59
|
+
storage(9, 'read-only-storage'),
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
const outputLayout = device.createBindGroupLayout({
|
|
63
|
+
label: 'cloth.publish',
|
|
64
|
+
entries: [
|
|
65
|
+
{
|
|
66
|
+
binding: 0,
|
|
67
|
+
visibility: COMPUTE,
|
|
68
|
+
storageTexture: { access: 'write-only', format: 'rgba32float' },
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
const module = shaderModule(device, { label: 'cloth.solve', code: CLOTH_SOLVE_WGSL });
|
|
73
|
+
const solve = device.createPipelineLayout({ bindGroupLayouts: [layout] });
|
|
74
|
+
const publish = device.createPipelineLayout({ bindGroupLayouts: [layout, outputLayout] });
|
|
75
|
+
const pipelines = {} as Record<ClothEntry, GPUComputePipeline>;
|
|
76
|
+
for (const entry of CLOTH_ENTRIES) {
|
|
77
|
+
pipelines[entry] = device.createComputePipeline({
|
|
78
|
+
label: `cloth.${entry}`,
|
|
79
|
+
layout: entry === 'clothPublish' ? publish : solve,
|
|
80
|
+
compute: { module, entryPoint: entry },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const kernels = { layout, outputLayout, pipelines };
|
|
84
|
+
kernelsByDevice.set(device, kernels);
|
|
85
|
+
return kernels;
|
|
86
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one garment of a set has asked the device for and the device has not yet recorded: a pose, a
|
|
3
|
+
* reset to rest, steps — each with the numbers its round reads, taken when it was asked for.
|
|
4
|
+
*
|
|
5
|
+
* **Taken when asked for, because they change after.** `ClothControl` moves its targets to a step's
|
|
6
|
+
* fraction and calls the device; by the time the set records the step, the next step has moved them
|
|
7
|
+
* again. So each op keeps the head of its record — fraction, blend, the poses it reads, the carry and
|
|
8
|
+
* the wind — as it stood, and the set copies it into the round that runs it.
|
|
9
|
+
*
|
|
10
|
+
* Grows by doubling and never shrinks, so a settled set allocates nothing a frame.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Words of a garment's record in a round: the shader's `Pace`. */
|
|
14
|
+
export const PACE_WORDS = 44;
|
|
15
|
+
/** The head of it, which an op carries: everything before the garment's own constants. */
|
|
16
|
+
export const PACE_HEAD_WORDS = 32;
|
|
17
|
+
|
|
18
|
+
export const CLOTH_OP_POSE = 0;
|
|
19
|
+
export const CLOTH_OP_REST = 1;
|
|
20
|
+
export const CLOTH_OP_STEP = 2;
|
|
21
|
+
|
|
22
|
+
export class ClothOps {
|
|
23
|
+
private kinds = new Uint8Array(8);
|
|
24
|
+
private heads = new ArrayBuffer(8 * PACE_HEAD_WORDS * 4);
|
|
25
|
+
private floats = new Float32Array(this.heads);
|
|
26
|
+
private words = new Uint32Array(this.heads);
|
|
27
|
+
private first = 0;
|
|
28
|
+
private end = 0;
|
|
29
|
+
|
|
30
|
+
get pending(): number {
|
|
31
|
+
return this.end - this.first;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The kind of the op to run next, or -1 where there is none. */
|
|
35
|
+
get next(): number {
|
|
36
|
+
return this.first < this.end ? (this.kinds[this.first] as number) : -1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A new op of `kind`, its head zeroed, for the caller to fill: the float and word offset of it. */
|
|
40
|
+
push(kind: number): number {
|
|
41
|
+
if (this.end === this.kinds.length) this.grow();
|
|
42
|
+
const at = this.end * PACE_HEAD_WORDS;
|
|
43
|
+
this.floats.fill(0, at, at + PACE_HEAD_WORDS);
|
|
44
|
+
this.kinds[this.end] = kind;
|
|
45
|
+
this.end++;
|
|
46
|
+
return at;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get headFloats(): Float32Array {
|
|
50
|
+
return this.floats;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get headWords(): Uint32Array {
|
|
54
|
+
return this.words;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The next op's head into `out` at `at`, and the op gone. Copied as words, since a head holds
|
|
59
|
+
* integers too, and a word at a time, since a view a call would be an allocation a round.
|
|
60
|
+
*/
|
|
61
|
+
take(out: Uint32Array, at: number): void {
|
|
62
|
+
const from = this.first * PACE_HEAD_WORDS;
|
|
63
|
+
for (let w = 0; w < PACE_HEAD_WORDS; w++) out[at + w] = this.words[from + w] as number;
|
|
64
|
+
this.first++;
|
|
65
|
+
if (this.first === this.end) {
|
|
66
|
+
this.first = 0;
|
|
67
|
+
this.end = 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private grow(): void {
|
|
72
|
+
const live = this.end - this.first;
|
|
73
|
+
const capacity = this.kinds.length * 2;
|
|
74
|
+
const kinds = new Uint8Array(capacity);
|
|
75
|
+
kinds.set(this.kinds.subarray(this.first, this.end));
|
|
76
|
+
const heads = new ArrayBuffer(capacity * PACE_HEAD_WORDS * 4);
|
|
77
|
+
const floats = new Float32Array(heads);
|
|
78
|
+
floats.set(this.floats.subarray(this.first * PACE_HEAD_WORDS, this.end * PACE_HEAD_WORDS));
|
|
79
|
+
this.kinds = kinds;
|
|
80
|
+
this.heads = heads;
|
|
81
|
+
this.floats = floats;
|
|
82
|
+
this.words = new Uint32Array(heads);
|
|
83
|
+
this.first = 0;
|
|
84
|
+
this.end = live;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a set of garments writes once into the buffers its rounds read, and the one per-step number
|
|
3
|
+
* worked out on the CPU: the constants, each round slot's first record, each colour's and each
|
|
4
|
+
* garment's batch slot, and the carry a step hands a garment that keeps less than all of its
|
|
5
|
+
* character's motion. Plain functions over the packed set, apart from the solver that schedules.
|
|
6
|
+
*/
|
|
7
|
+
import type { ResolvedClothParameters } from '@driftengine/physics';
|
|
8
|
+
|
|
9
|
+
import type { PackedClothSet } from './clothSetPack.ts';
|
|
10
|
+
import { DYNAMIC_ALIGNMENT } from './uniformRing.ts';
|
|
11
|
+
|
|
12
|
+
/** Bytes of the shader's `Constants`. */
|
|
13
|
+
export const CLOTH_CONSTANT_BYTES = 48;
|
|
14
|
+
|
|
15
|
+
/** The set's `Constants`: its totals, and the substep length every garment of it shares. */
|
|
16
|
+
export function clothConstants(packed: PackedClothSet, h: number): ArrayBuffer {
|
|
17
|
+
const bytes = new ArrayBuffer(CLOTH_CONSTANT_BYTES);
|
|
18
|
+
const u = new Uint32Array(bytes);
|
|
19
|
+
const f = new Float32Array(bytes);
|
|
20
|
+
u[0] = packed.count;
|
|
21
|
+
u[1] = packed.distances;
|
|
22
|
+
u[2] = packed.bendings;
|
|
23
|
+
u[3] = packed.joints;
|
|
24
|
+
u[4] = packed.colliders;
|
|
25
|
+
f[8] = h;
|
|
26
|
+
f[9] = 1 / h;
|
|
27
|
+
f[10] = 1 / (h * h);
|
|
28
|
+
return bytes;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Each of `rounds` round slots' first record: the round's index times the garments. */
|
|
32
|
+
export function clothRoundOffsets(rounds: number, garments: number): Uint32Array<ArrayBuffer> {
|
|
33
|
+
const words = new Uint32Array((rounds * DYNAMIC_ALIGNMENT) / 4);
|
|
34
|
+
for (let r = 0; r < rounds; r++) words[(r * DYNAMIC_ALIGNMENT) / 4] = r * garments;
|
|
35
|
+
return words;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Each colour's range, twice — opening a substep's iterations and not — at a slot each, kind by
|
|
40
|
+
* kind; then one slot a garment for publishing, its particles' range and its index.
|
|
41
|
+
*/
|
|
42
|
+
export function clothBatchSlots(packed: PackedClothSet): Uint32Array<ArrayBuffer> {
|
|
43
|
+
const { distanceBatches, bendingBatches, tetherBatches, garments } = packed;
|
|
44
|
+
const kinds = [distanceBatches, bendingBatches, tetherBatches];
|
|
45
|
+
let slots = garments.length;
|
|
46
|
+
for (const batches of kinds) slots += (batches.length - 1) * 2;
|
|
47
|
+
const words = new Uint32Array((slots * DYNAMIC_ALIGNMENT) / 4);
|
|
48
|
+
let slot = 0;
|
|
49
|
+
for (const batches of kinds) {
|
|
50
|
+
for (let b = 0; b + 1 < batches.length; b++) {
|
|
51
|
+
for (let first = 0; first < 2; first++) {
|
|
52
|
+
const at = (slot * DYNAMIC_ALIGNMENT) / 4;
|
|
53
|
+
words[at] = batches[b] as number;
|
|
54
|
+
words[at + 1] = batches[b + 1] as number;
|
|
55
|
+
words[at + 2] = first;
|
|
56
|
+
slot++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
garments.forEach((range, g) => {
|
|
61
|
+
const at = (slot * DYNAMIC_ALIGNMENT) / 4;
|
|
62
|
+
words[at] = range.particleBase;
|
|
63
|
+
words[at + 1] = range.particleBase + range.count;
|
|
64
|
+
words[at + 2] = g;
|
|
65
|
+
slot++;
|
|
66
|
+
});
|
|
67
|
+
return words;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* `carryParticles`' rotation, origin and move, into a record's head at `at`, between the model
|
|
72
|
+
* matrices before and after the step; nothing where the garment keeps all of its character's
|
|
73
|
+
* motion, which leaves the head's carry at 0.
|
|
74
|
+
*/
|
|
75
|
+
export function writeClothCarry(
|
|
76
|
+
parameters: ResolvedClothParameters,
|
|
77
|
+
previous: Float32Array,
|
|
78
|
+
next: Float32Array,
|
|
79
|
+
words: Uint32Array,
|
|
80
|
+
f: Float32Array,
|
|
81
|
+
at: number,
|
|
82
|
+
): void {
|
|
83
|
+
const turn = 1 - parameters.angularInertia;
|
|
84
|
+
const move = 1 - parameters.linearInertia;
|
|
85
|
+
if (turn === 0 && move === 0) return;
|
|
86
|
+
words[at + 3] = 1;
|
|
87
|
+
/* Column c of R = R_next × R_previousᵀ, at turn0 … turn2. */
|
|
88
|
+
for (let col = 0; col < 3; col++) {
|
|
89
|
+
for (let row = 0; row < 3; row++) {
|
|
90
|
+
let sum = 0;
|
|
91
|
+
for (let k = 0; k < 3; k++) {
|
|
92
|
+
sum += (next[k * 4 + row] as number) * (previous[k * 4 + col] as number);
|
|
93
|
+
}
|
|
94
|
+
f[at + 8 + col * 4 + row] = sum;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (let k = 0; k < 3; k++) {
|
|
98
|
+
f[at + 20 + k] = previous[12 + k] as number;
|
|
99
|
+
f[at + 24 + k] = ((next[12 + k] as number) - (previous[12 + k] as number)) * move;
|
|
100
|
+
}
|
|
101
|
+
f[at + 23] = turn;
|
|
102
|
+
}
|