@driftengine/core 4.9.0 → 4.10.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 +62 -11
- package/README.md +43 -38
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -3
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/backend/api.d.ts +10 -3
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +90 -2
- package/dist/render/backend/webgl2/renderer.js +253 -24
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +19 -4
- package/dist/render/backend/webgpu/buffers.js +18 -11
- package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
- package/dist/render/backend/webgpu/drawCommand.js +10 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
- package/dist/render/backend/webgpu/flatPass.js +136 -20
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
- package/dist/render/backend/webgpu/pipelineCache.js +7 -0
- package/dist/render/backend/webgpu/renderer.d.ts +152 -35
- package/dist/render/backend/webgpu/renderer.js +787 -142
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +241 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/glCompressed.js +83 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +5 -0
- package/dist/render/mesh.js +6 -0
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/screenSpaceReflection.d.ts +2 -1
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/flat/index.d.ts +6 -0
- package/dist/render/shaders/flat/index.js +27 -5
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +15 -5
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/overlay.js +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -2
- package/dist/render/shaders/flat/preamble.js +38 -24
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3077 -2903
- package/dist/render/shaders/generated/flat.wgsl.js +3100 -2925
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceTexture.d.ts +38 -7
- package/dist/render/surfaceTexture.js +56 -17
- package/package.json +6 -6
- package/src/index.ts +8 -2
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/backend/api.ts +13 -3
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +315 -24
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +42 -16
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/drawCommand.ts +22 -4
- package/src/render/backend/webgpu/flatPass.ts +187 -14
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/pipelineCache.ts +7 -0
- package/src/render/backend/webgpu/renderer.ts +928 -155
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +363 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/glCompressed.ts +83 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +6 -0
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/screenSpaceReflection.ts +2 -1
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/flat/index.ts +33 -5
- package/src/render/shaders/flat/main.ts +15 -5
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/overlay.ts +1 -1
- package/src/render/shaders/flat/pointShadow.ts +2 -2
- package/src/render/shaders/flat/preamble.ts +38 -24
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/flat.wgsl.ts +3100 -2925
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceTexture.ts +94 -27
|
@@ -199,10 +199,14 @@ import {
|
|
|
199
199
|
resolveFrameReflections,
|
|
200
200
|
type ResolvedFrameReflections,
|
|
201
201
|
} from '../../frameReflections.ts';
|
|
202
|
-
import { ReflectionSurfacePass } from './reflectionSurfacePass.ts';
|
|
202
|
+
import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.ts';
|
|
203
203
|
import { validateClothBinding } from '../../clothBindingData.ts';
|
|
204
204
|
import type { ClothBindingData } from '../../clothBindingData.ts';
|
|
205
|
-
import type {
|
|
205
|
+
import type {
|
|
206
|
+
CompressedTextureFormat,
|
|
207
|
+
CompressedTextureSource,
|
|
208
|
+
SurfaceSource,
|
|
209
|
+
} from '../../compressedSource.ts';
|
|
206
210
|
import {
|
|
207
211
|
createSdfTextBindGroup,
|
|
208
212
|
createSdfTextBindGroupLayout,
|
|
@@ -412,16 +416,25 @@ import {
|
|
|
412
416
|
SUN_STATIC_LAYER,
|
|
413
417
|
} from '../../shadowMap.ts';
|
|
414
418
|
import type { SceneCasterMaterial, ShadowCasterSink, ShadowCasters } from '../../shadowCasters.ts';
|
|
415
|
-
import {
|
|
419
|
+
import {
|
|
420
|
+
VERTEX_LAYOUT,
|
|
421
|
+
createGpuMesh,
|
|
422
|
+
createGpuMeshIncremental,
|
|
423
|
+
type GpuMesh,
|
|
424
|
+
type StagedRewrite,
|
|
425
|
+
} from './buffers.ts';
|
|
416
426
|
import type { GpuSurface } from './device.ts';
|
|
417
427
|
import { SkinPaletteRing } from './skinPaletteRing.ts';
|
|
418
428
|
import {
|
|
419
429
|
DEPTH_FORMAT,
|
|
430
|
+
FLAT_ANIMATED_VERT_FIELDS,
|
|
420
431
|
FLAT_VERT_FIELDS,
|
|
421
432
|
FLAT_VERT_SIZE,
|
|
433
|
+
FLAT_VIEW_FIELDS,
|
|
434
|
+
FLAT_VIEW_SIZE,
|
|
422
435
|
createFlatBindGroup,
|
|
423
436
|
createFlatBindGroupLayout,
|
|
424
|
-
|
|
437
|
+
flatFragmentScratch,
|
|
425
438
|
flatVariant,
|
|
426
439
|
flatVertexBindings,
|
|
427
440
|
type FlatVariant,
|
|
@@ -429,7 +442,15 @@ import {
|
|
|
429
442
|
flatPipelineAsync,
|
|
430
443
|
probeStageCeilings,
|
|
431
444
|
} from './flatPass.ts';
|
|
432
|
-
import type { OitTarget } from './flatPass.ts';
|
|
445
|
+
import type { AnimationViews, FlatFragmentScratch, OitTarget } from './flatPass.ts';
|
|
446
|
+
import { GpuBoneAnimation } from './boneAnimations.ts';
|
|
447
|
+
import { packBoneAnimation } from '../../boneAnimation.ts';
|
|
448
|
+
import type { BoneAnimationClip, BoneAnimationHandle } from '../../boneAnimation.ts';
|
|
449
|
+
import { MaterialStore, hashWords } from './materialStore.ts';
|
|
450
|
+
import { GpuStaticDraws, encodeBundle, takeRecorded } from './staticBundles.ts';
|
|
451
|
+
import type { StaticRecording } from './staticBundles.ts';
|
|
452
|
+
import { captureStaticDraws, replayStaticDraws } from '../../staticDraws.ts';
|
|
453
|
+
import type { StaticDrawsHandle } from '../../staticDraws.ts';
|
|
433
454
|
import {
|
|
434
455
|
DEPTH_FRAG_BINDING,
|
|
435
456
|
DEPTH_FRAG_SIZE,
|
|
@@ -437,6 +458,8 @@ import {
|
|
|
437
458
|
DEPTH_PREVIOUS_BINDING,
|
|
438
459
|
DEPTH_CUTOUT_VERT_FIELDS,
|
|
439
460
|
DEPTH_CUTOUT_VERT_SIZE,
|
|
461
|
+
DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS,
|
|
462
|
+
DEPTH_INSTANCED_ANIMATED_VERT_FIELDS,
|
|
440
463
|
DEPTH_INSTANCED_CUTOUT_VERT_FIELDS,
|
|
441
464
|
DEPTH_VERT_FIELDS,
|
|
442
465
|
DEPTH_VERT_SIZE,
|
|
@@ -909,6 +932,14 @@ const MAX_WATER_BODIES_PER_FRAME = 16;
|
|
|
909
932
|
*/
|
|
910
933
|
const MAX_BATCHES_PER_FRAME = 8;
|
|
911
934
|
|
|
935
|
+
/**
|
|
936
|
+
* Mesh passes a frame may open before the ring grows: the frame's own, a mirror's, a capture's, and
|
|
937
|
+
* a probe face's, which gives its slot back once submitted. A frame past it loses the draws of the
|
|
938
|
+
* passes that found no slot, counted on the `materials` line, and the ring is grown for the next.
|
|
939
|
+
* Each slot is the pass's block, 3,328 to 6,656 bytes by variant, so the ring is 213 to 426 KB.
|
|
940
|
+
*/
|
|
941
|
+
const PASSES_PER_FRAME = 64;
|
|
942
|
+
|
|
912
943
|
/** Buffer usage flags, from the specification. See `buffers.ts` for why they are not read off the global. */
|
|
913
944
|
const USAGE_UNIFORM_DST = 0x0040 | 0x0008;
|
|
914
945
|
|
|
@@ -1423,6 +1454,15 @@ interface SunTintTarget {
|
|
|
1423
1454
|
readonly mips: readonly [SnapshotMips, SnapshotMips];
|
|
1424
1455
|
}
|
|
1425
1456
|
|
|
1457
|
+
/** A skinned or morphed flat group, and what it was built with, compared by identity. */
|
|
1458
|
+
interface SkinnedTwin {
|
|
1459
|
+
readonly group: GPUBindGroup;
|
|
1460
|
+
readonly palette: GPUTextureView | null;
|
|
1461
|
+
readonly deltas: GPUTextureView | null;
|
|
1462
|
+
readonly cloth: GPUTextureView | null;
|
|
1463
|
+
readonly clothBinding: GPUTextureView | null;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1426
1466
|
export class WebGPURenderer implements RendererApi {
|
|
1427
1467
|
/**
|
|
1428
1468
|
* Real timestamp queries, where the adapter offers them.
|
|
@@ -1506,6 +1546,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1506
1546
|
private readonly skinnedBindGroupLayout: GPUBindGroupLayout;
|
|
1507
1547
|
private readonly morphedBindGroupLayout: GPUBindGroupLayout;
|
|
1508
1548
|
private readonly bothBindGroupLayout: GPUBindGroupLayout;
|
|
1549
|
+
/** The instanced layout of a batch playing a bone animation. See `animatedGroup`. */
|
|
1550
|
+
private readonly animatedBindGroupLayout: GPUBindGroupLayout;
|
|
1509
1551
|
/** The morph weights the following draws use, or null for none. */
|
|
1510
1552
|
private morphWeights: Float32Array | null = null;
|
|
1511
1553
|
/**
|
|
@@ -1518,14 +1560,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1518
1560
|
* group per character per frame — an allocation in the frame loop, which `AGENTS.md` forbids.
|
|
1519
1561
|
* Keyed by slot instead, the groups are built once and reused for the life of the renderer.
|
|
1520
1562
|
*/
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1563
|
+
/*
|
|
1564
|
+
* **And by material within a slot**, keyed by the unskinned group each was built beside. One
|
|
1565
|
+
* group a slot was rebuilt whenever the material beside it changed, so a character drawn in
|
|
1566
|
+
* thirty-odd sections, each its own material, built one a section a frame — about ninety a frame
|
|
1567
|
+
* for two characters, reported by a consumer. A slot's map holds one entry a material its rigs
|
|
1568
|
+
* are drawn in, and is emptied wherever the unskinned groups are, whose identities it is keyed
|
|
1569
|
+
* by.
|
|
1570
|
+
*/
|
|
1571
|
+
private readonly skinnedGroups: (Map<GPUBindGroup, SkinnedTwin> | undefined)[] = [];
|
|
1529
1572
|
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
1530
1573
|
private cloth: { binding: GpuClothBinding; particles: GpuClothParticles } | null = null;
|
|
1531
1574
|
private clothStandCache: { texture: GPUTexture; views: ClothViews } | null = null;
|
|
@@ -1595,22 +1638,51 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1595
1638
|
/** Every distinct cache above, so a mesh's pipelines are built for each. One entry when aliased. */
|
|
1596
1639
|
private readonly pipelineTargets: readonly PipelineCache[];
|
|
1597
1640
|
private readonly bindGroupLayout: GPUBindGroupLayout;
|
|
1598
|
-
/** One slot per draw, uploaded once a frame. See `UniformRing`. */
|
|
1599
|
-
private
|
|
1600
|
-
/** The lights, fog and camera, settled once per frame by `bindMeshPass`. */
|
|
1601
|
-
private readonly perFrame: UniformRing;
|
|
1641
|
+
/** One slot per draw, uploaded once a frame. See `UniformRing`. Swapped while a static list records. */
|
|
1642
|
+
private perDraw: UniformRing;
|
|
1602
1643
|
/**
|
|
1603
|
-
* The
|
|
1644
|
+
* The lights, fog and camera, a slot per mesh pass a frame opens: `bindMeshPass` writes them, and
|
|
1645
|
+
* the first draw after it takes a slot. A ring rather than one buffer because a frame opens more
|
|
1646
|
+
* than one pass — a mirror's, a capture's, a probe face's — and a queue write does not interleave
|
|
1647
|
+
* with the draws recorded between them. See `UniformRing`.
|
|
1648
|
+
*/
|
|
1649
|
+
private passRing: UniformRing;
|
|
1650
|
+
/** Each material's block, kept across frames for as long as it does not change. */
|
|
1651
|
+
private readonly materialStore: MaterialStore;
|
|
1652
|
+
/**
|
|
1653
|
+
* Both blocks as the CPU holds them, the pass's then the material's, patched by name and copied
|
|
1654
|
+
* into a slot by the next draw after something changed them. See `FlatFragmentScratch`.
|
|
1604
1655
|
*
|
|
1605
1656
|
* Authoritative between draws: a setter changes one field here and marks the block dirty, and
|
|
1606
|
-
* the next `drawMesh`
|
|
1607
|
-
*
|
|
1657
|
+
* the next `drawMesh` places it. That keeps a setter free of any knowledge of slots, which is
|
|
1658
|
+
* what lets it be called anywhere a scene wants.
|
|
1608
1659
|
*/
|
|
1609
1660
|
private readonly perFrameStaging: ArrayBuffer;
|
|
1610
1661
|
private readonly perFrameFloats: Float32Array;
|
|
1611
1662
|
private readonly perFrameInts: Int32Array;
|
|
1663
|
+
/** The two halves of the scratch, as the words a slot is written from. */
|
|
1664
|
+
private readonly passInts: Int32Array;
|
|
1665
|
+
private readonly materialInts: Int32Array;
|
|
1612
1666
|
/** The slot the open material occupies, and whether the next draw must take a new one. */
|
|
1613
1667
|
private readonly materials = new MaterialChanges();
|
|
1668
|
+
/** The same for the pass's block: reopened by `bindMeshPass` and by nothing a material sets. */
|
|
1669
|
+
private readonly passBlock = new MaterialChanges();
|
|
1670
|
+
/**
|
|
1671
|
+
* The vertex stage's view block — the camera, the light's matrix, the wind — a slot a pass, and a
|
|
1672
|
+
* second, unjittered, for a blended draw landing after the upscale. Reopened with the pass, and by
|
|
1673
|
+
* `setWind` and `beginShadowPass`, which change what it holds.
|
|
1674
|
+
*/
|
|
1675
|
+
private viewRing: UniformRing;
|
|
1676
|
+
private readonly viewBlock = new MaterialChanges();
|
|
1677
|
+
private readonly lateViewBlock = new MaterialChanges();
|
|
1678
|
+
/** The view slot the draw being submitted bound, read by the halves it keeps for later passes. */
|
|
1679
|
+
private drawViewOffset = 0;
|
|
1680
|
+
/** Pass slots asked for and refused this frame, and materials refused: what grows the two. */
|
|
1681
|
+
private passesAsked = 0;
|
|
1682
|
+
private passesRefused = 0;
|
|
1683
|
+
private viewsAsked = 0;
|
|
1684
|
+
private viewsRefused = 0;
|
|
1685
|
+
private materialsRefused = 0;
|
|
1614
1686
|
|
|
1615
1687
|
/*
|
|
1616
1688
|
* The frame graph's recording, and the machinery that replays it.
|
|
@@ -1879,8 +1951,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1879
1951
|
private readonly variant: FlatVariant;
|
|
1880
1952
|
/** Whether this profile compiled the point-shadow path, and so has sentinels to write. */
|
|
1881
1953
|
private readonly pointShadowsCompiled: boolean;
|
|
1882
|
-
/** What the generator recorded about that permutation
|
|
1883
|
-
private readonly fragment:
|
|
1954
|
+
/** What the generator recorded about that permutation, as the renderer's scratch lays it out. */
|
|
1955
|
+
private readonly fragment: FlatFragmentScratch;
|
|
1884
1956
|
|
|
1885
1957
|
private depth: GPUTexture | null = null;
|
|
1886
1958
|
private depthView: GPUTextureView | null = null;
|
|
@@ -2624,7 +2696,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2624
2696
|
if (this.quality.frameGraph) recordNode(this.arena, VERB_SCOPE, 0, MIRROR_TARGET, 0, 0);
|
|
2625
2697
|
this.pass?.end();
|
|
2626
2698
|
this.flushRings();
|
|
2627
|
-
this.
|
|
2699
|
+
this.submit([this.encoder.finish()]);
|
|
2628
2700
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'reflection' });
|
|
2629
2701
|
/*
|
|
2630
2702
|
* **Opened on demand, like the frame's own.** Nothing has been recorded into the mirror yet,
|
|
@@ -2684,7 +2756,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2684
2756
|
this.windStreakFrags,
|
|
2685
2757
|
this.scatterVerts,
|
|
2686
2758
|
this.scatterFrags,
|
|
2687
|
-
this.
|
|
2759
|
+
this.passRing,
|
|
2760
|
+
this.viewRing,
|
|
2688
2761
|
this.filmVerts,
|
|
2689
2762
|
this.filmFrags,
|
|
2690
2763
|
this.insetUniforms,
|
|
@@ -2705,6 +2778,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2705
2778
|
/* Culls first: a submit that follows may carry a draw reading what they write. */
|
|
2706
2779
|
this.instanceCulls?.flush();
|
|
2707
2780
|
for (const ring of this.rings()) ring.flush();
|
|
2781
|
+
this.materialStore.flush();
|
|
2708
2782
|
}
|
|
2709
2783
|
|
|
2710
2784
|
/** Where every ring stands before work that submits an encoder of its own. */
|
|
@@ -2724,6 +2798,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2724
2798
|
private rewindRings(skinMark: number): void {
|
|
2725
2799
|
const rings = this.rings();
|
|
2726
2800
|
for (let i = 0; i < rings.length; i++) (rings[i] as UniformRing).rewind(this.ringMarks[i] ?? 0);
|
|
2801
|
+
/* The open pass may sit in a slot just given back, which the next pass would overwrite. */
|
|
2802
|
+
this.reopenPass();
|
|
2727
2803
|
this.skinPalettes.rewind(skinMark);
|
|
2728
2804
|
if (this.skinPaletteSlot >= skinMark) this.skinPaletteSlot = -1;
|
|
2729
2805
|
}
|
|
@@ -2926,7 +3002,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2926
3002
|
* ones that move something *every* group holds: the shadow cubemaps, the probe fence, the cookie
|
|
2927
3003
|
* atlas and the IES rows. None of those is a frame path.
|
|
2928
3004
|
*/
|
|
2929
|
-
private
|
|
3005
|
+
private flatBindGroups = new Map<
|
|
2930
3006
|
GpuSurfaceTexture | null,
|
|
2931
3007
|
Map<
|
|
2932
3008
|
GpuSurfaceTexture | null,
|
|
@@ -3097,7 +3173,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3097
3173
|
pass.end();
|
|
3098
3174
|
this.pass = null;
|
|
3099
3175
|
this.flushRings();
|
|
3100
|
-
|
|
3176
|
+
this.submit([encoder.finish()]);
|
|
3101
3177
|
this.rewindRings(skinMark);
|
|
3102
3178
|
this.encoderEpoch = frameEpoch;
|
|
3103
3179
|
this.encoder = savedEncoder;
|
|
@@ -3105,6 +3181,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3105
3181
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
3106
3182
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
3107
3183
|
this.materials.dirty();
|
|
3184
|
+
this.reopenPass();
|
|
3108
3185
|
this.capturePassActive = false;
|
|
3109
3186
|
}
|
|
3110
3187
|
return true;
|
|
@@ -3119,11 +3196,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3119
3196
|
*/
|
|
3120
3197
|
private readonly retiredTextures: GPUTexture[] = [];
|
|
3121
3198
|
|
|
3122
|
-
updateSurfaceTexture(
|
|
3199
|
+
updateSurfaceTexture(
|
|
3200
|
+
texture: GpuSurfaceTexture,
|
|
3201
|
+
source: TexImageSource | CompressedTextureSource,
|
|
3202
|
+
): void {
|
|
3123
3203
|
if (this.surface.lost) return;
|
|
3124
|
-
const replaced = texture.update(source);
|
|
3204
|
+
const replaced = texture.update(source, this.compressedFormats);
|
|
3125
3205
|
if (replaced === null) return;
|
|
3126
|
-
/* A new size is a new view, so every group holding the old one is dropped — see
|
|
3206
|
+
/* A new size or format is a new view, so every group holding the old one is dropped — see
|
|
3207
|
+
below. */
|
|
3127
3208
|
this.retiredTextures.push(replaced);
|
|
3128
3209
|
this.forgetBindingsOf(texture);
|
|
3129
3210
|
}
|
|
@@ -3175,7 +3256,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3175
3256
|
/* Same reasoning, for whichever SDF labels were bound against this atlas. */
|
|
3176
3257
|
this.sdfTextBindGroups.delete(texture);
|
|
3177
3258
|
this.flatBindGroups.clear();
|
|
3259
|
+
this.flatGroupEpoch += 1;
|
|
3178
3260
|
this.overlayGroups.clear();
|
|
3261
|
+
this.skinnedGroups.length = 0;
|
|
3179
3262
|
/*
|
|
3180
3263
|
* The blank one is built with the albedo held aside, because `buildFlatBindGroup` reads
|
|
3181
3264
|
* `this.albedo` for that slot — leaving it in place would put a real colour map into the
|
|
@@ -3367,6 +3450,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3367
3450
|
const emissiveMap = (material?.emissive ?? null) as GpuSurfaceTexture | null;
|
|
3368
3451
|
const modelMap = (material?.modelMap ?? null) as GpuSurfaceTexture | null;
|
|
3369
3452
|
this.materialModel = material?.model ?? null;
|
|
3453
|
+
const reflectivity = material?.reflectivity;
|
|
3454
|
+
const environmentGain = material?.environmentGain;
|
|
3455
|
+
this.materialReflectivity =
|
|
3456
|
+
reflectivity === undefined ? null : Math.min(1, Math.max(0, reflectivity));
|
|
3457
|
+
this.materialEnvironmentGain =
|
|
3458
|
+
environmentGain === undefined ? null : Math.max(0, environmentGain);
|
|
3459
|
+
this.writeEnvironmentDials();
|
|
3370
3460
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
3371
3461
|
const model = material?.model;
|
|
3372
3462
|
warnUnpagedLightmap(model, modelMap);
|
|
@@ -3395,7 +3485,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3395
3485
|
const cut = this.materialField('uCutout');
|
|
3396
3486
|
f[cut] = cutoff;
|
|
3397
3487
|
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
3398
|
-
|
|
3488
|
+
/* z unused: the dither's frame is the pass's, in `uHighlightMin.w`. See `uCutout`. */
|
|
3489
|
+
f[cut + 2] = 0;
|
|
3399
3490
|
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
3400
3491
|
f[cut + 3] = lightChannelsOf(material?.lightChannels);
|
|
3401
3492
|
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
@@ -3585,15 +3676,31 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3585
3676
|
|
|
3586
3677
|
/** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
|
|
3587
3678
|
setSurfaceReflectivity(amount: number): void {
|
|
3588
|
-
this.
|
|
3589
|
-
|
|
3590
|
-
});
|
|
3679
|
+
this.passReflectivity = Math.min(1, Math.max(0, amount));
|
|
3680
|
+
this.writeEnvironmentDials();
|
|
3591
3681
|
}
|
|
3592
3682
|
|
|
3593
3683
|
/** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
|
|
3594
3684
|
setEnvironmentGain(gain: number): void {
|
|
3685
|
+
this.passEnvironmentGain = Math.max(0, gain);
|
|
3686
|
+
this.writeEnvironmentDials();
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
/**
|
|
3690
|
+
* The reflectivity and environment gain the setters gave the pass, and the ones the material
|
|
3691
|
+
* set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
|
|
3692
|
+
* material's where it has one and the pass's otherwise, the rule `renderer.ts` keeps too.
|
|
3693
|
+
*/
|
|
3694
|
+
private passReflectivity = 0;
|
|
3695
|
+
private passEnvironmentGain = 1;
|
|
3696
|
+
private materialReflectivity: number | null = null;
|
|
3697
|
+
private materialEnvironmentGain: number | null = null;
|
|
3698
|
+
|
|
3699
|
+
private writeEnvironmentDials(): void {
|
|
3595
3700
|
this.material((f) => {
|
|
3596
|
-
|
|
3701
|
+
const dials = this.materialField('uEnvironmentDials');
|
|
3702
|
+
f[dials + 1] = this.materialReflectivity ?? this.passReflectivity;
|
|
3703
|
+
f[dials + 2] = this.materialEnvironmentGain ?? this.passEnvironmentGain;
|
|
3597
3704
|
});
|
|
3598
3705
|
}
|
|
3599
3706
|
|
|
@@ -4112,7 +4219,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4112
4219
|
}
|
|
4113
4220
|
this.pass?.end();
|
|
4114
4221
|
this.flushRings();
|
|
4115
|
-
this.
|
|
4222
|
+
this.submit([this.encoder.finish()]);
|
|
4116
4223
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'frame.resumed' });
|
|
4117
4224
|
this.reflectionPassActive = false;
|
|
4118
4225
|
this.reflectionReady = true;
|
|
@@ -5795,6 +5902,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5795
5902
|
*/
|
|
5796
5903
|
setWind(windX: number, windZ: number, windGust: number, timeSeconds: number): void {
|
|
5797
5904
|
resolveScatterDeform(windX, windZ, windGust, timeSeconds, null, this.frameWind);
|
|
5905
|
+
this.reopenViews();
|
|
5798
5906
|
}
|
|
5799
5907
|
|
|
5800
5908
|
/**
|
|
@@ -5814,22 +5922,6 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5814
5922
|
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
5815
5923
|
}
|
|
5816
5924
|
|
|
5817
|
-
/**
|
|
5818
|
-
* The five wind fields of one per-draw slot.
|
|
5819
|
-
*
|
|
5820
|
-
* **Never called for an instanced draw.** That variant declares none of these, and
|
|
5821
|
-
* `flatPass.ts` records what writing a field a variant does not declare costs: the block is
|
|
5822
|
-
* shorter, so the write lands on whatever occupies that offset instead.
|
|
5823
|
-
*/
|
|
5824
|
-
private writeWind(slot: number): void {
|
|
5825
|
-
const d = this.frameWind;
|
|
5826
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uWindDirection.offset, d.direction);
|
|
5827
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindSpeed.offset, d.bend);
|
|
5828
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindGust.offset, d.gust);
|
|
5829
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindTime.offset, d.time);
|
|
5830
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
5831
|
-
}
|
|
5832
|
-
|
|
5833
5925
|
/** Refilled per frame rather than allocated; see `resolvePointLights`. */
|
|
5834
5926
|
private readonly lights: ResolvedPointLights = {
|
|
5835
5927
|
count: 0,
|
|
@@ -5984,6 +6076,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5984
6076
|
* at submit, and a texture's view is new when an update changes its size.
|
|
5985
6077
|
*/
|
|
5986
6078
|
private readonly shadowCutoutLayout: GPUBindGroupLayout;
|
|
6079
|
+
/** A crowd's casters: the plain and cutout layouts with a bone animation's three textures. */
|
|
6080
|
+
private readonly shadowAnimatedLayout: GPUBindGroupLayout;
|
|
6081
|
+
private readonly shadowAnimatedCutoutLayout: GPUBindGroupLayout;
|
|
5987
6082
|
private readonly cutoutGroups = new Map<GpuSurfaceTexture, GPUBindGroup>();
|
|
5988
6083
|
private readonly cutoutPeelGroups = new Map<GpuSurfaceTexture, GPUBindGroup>();
|
|
5989
6084
|
private shadowPeelPassUniforms: GPUBuffer | null = null;
|
|
@@ -6239,8 +6334,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6239
6334
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
6240
6335
|
if (count === 0) return;
|
|
6241
6336
|
const cutout = cutoutOf(material);
|
|
6337
|
+
/* A crowd casts where its clip has moved it, through the animated variant. */
|
|
6338
|
+
const animation = gpuBatch.animation === null ? null : this.animationViews(gpuBatch);
|
|
6339
|
+
if (gpuBatch.animation !== null && animation === null) return;
|
|
6340
|
+
const cutoutTexture =
|
|
6341
|
+
cutout === null ? null : (cutout.albedo as unknown as GpuSurfaceTexture);
|
|
6242
6342
|
const cutoutGroup =
|
|
6243
|
-
|
|
6343
|
+
animation !== null
|
|
6344
|
+
? this.animatedDepthGroup(gpuBatch, animation, cutoutTexture)
|
|
6345
|
+
: cutoutTexture === null
|
|
6346
|
+
? null
|
|
6347
|
+
: this.cutoutGroup(cutoutTexture);
|
|
6348
|
+
if (animation !== null && cutoutGroup === null) return;
|
|
6244
6349
|
|
|
6245
6350
|
const slot = this.shadowDraws.allocate();
|
|
6246
6351
|
if (slot === null) return;
|
|
@@ -6251,23 +6356,43 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6251
6356
|
DEPTH_VERT_FIELDS.uLightViewProj.offset,
|
|
6252
6357
|
this.correctedLightViewProj,
|
|
6253
6358
|
);
|
|
6254
|
-
|
|
6359
|
+
const cuts = cutoutTexture !== null && cutoutGroup !== null;
|
|
6360
|
+
if (cutout !== null && cuts) {
|
|
6255
6361
|
this.writeCutout(slot, cutout, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS);
|
|
6256
6362
|
}
|
|
6363
|
+
const clip = gpuBatch.animation;
|
|
6364
|
+
if (animation !== null && clip !== null) {
|
|
6365
|
+
const fields = cuts
|
|
6366
|
+
? DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS
|
|
6367
|
+
: DEPTH_INSTANCED_ANIMATED_VERT_FIELDS;
|
|
6368
|
+
this.shadowDraws.writeFloat(slot, fields.uSceneTime.offset, this.animationTime);
|
|
6369
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset, clip.framesPerSecond);
|
|
6370
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset + 4, clip.boneScale);
|
|
6371
|
+
}
|
|
6257
6372
|
|
|
6258
|
-
const cuts = cutoutGroup !== null;
|
|
6259
6373
|
const cull = material?.doubleSided === true ? 'none' : this.depthCullMode;
|
|
6374
|
+
const animated = animation !== null;
|
|
6260
6375
|
pass.setPipeline(
|
|
6261
6376
|
depthPipeline(
|
|
6262
6377
|
this.pipelines,
|
|
6263
6378
|
this.surface.device,
|
|
6264
|
-
|
|
6265
|
-
|
|
6379
|
+
animated
|
|
6380
|
+
? cuts
|
|
6381
|
+
? this.shadowAnimatedCutoutLayout
|
|
6382
|
+
: this.shadowAnimatedLayout
|
|
6383
|
+
: cuts
|
|
6384
|
+
? this.shadowCutoutLayout
|
|
6385
|
+
: this.shadowLayout,
|
|
6386
|
+
`${cuts ? 'depth-instanced-cutout' : 'depth-instanced'}${animated ? '-animated' : ''}` +
|
|
6387
|
+
`|${cull}|${geometry.key ?? ''}`,
|
|
6266
6388
|
this.presentOf(geometry),
|
|
6267
6389
|
cull,
|
|
6268
6390
|
false,
|
|
6269
6391
|
true,
|
|
6270
6392
|
cuts,
|
|
6393
|
+
false,
|
|
6394
|
+
false,
|
|
6395
|
+
animated,
|
|
6271
6396
|
),
|
|
6272
6397
|
);
|
|
6273
6398
|
pass.setBindGroup(0, cutoutGroup ?? this.shadowGroup(), [slot]);
|
|
@@ -6781,7 +6906,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6781
6906
|
pointShadows: quality.pointShadows,
|
|
6782
6907
|
});
|
|
6783
6908
|
this.pointShadowsCompiled = quality.pointShadows;
|
|
6784
|
-
this.fragment =
|
|
6909
|
+
this.fragment = flatFragmentScratch(this.variant);
|
|
6785
6910
|
this.bindGroupLayout = createFlatBindGroupLayout(device, this.variant);
|
|
6786
6911
|
/*
|
|
6787
6912
|
* The skinned twin, built eagerly because it is a descriptor rather than a compile — WebGPU
|
|
@@ -6791,6 +6916,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6791
6916
|
this.skinnedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true);
|
|
6792
6917
|
this.morphedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, false, true);
|
|
6793
6918
|
this.bothBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true, true);
|
|
6919
|
+
/* And the instanced twin that plays a bone animation: its clip's and clocks' textures. */
|
|
6920
|
+
this.animatedBindGroupLayout = createFlatBindGroupLayout(
|
|
6921
|
+
device,
|
|
6922
|
+
this.variant,
|
|
6923
|
+
false,
|
|
6924
|
+
false,
|
|
6925
|
+
true,
|
|
6926
|
+
);
|
|
6794
6927
|
this.perDraw = new UniformRing(
|
|
6795
6928
|
device,
|
|
6796
6929
|
FLAT_VERT_SIZE,
|
|
@@ -6799,30 +6932,48 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6799
6932
|
'flat.vertRing',
|
|
6800
6933
|
this.drawBudget,
|
|
6801
6934
|
);
|
|
6802
|
-
|
|
6935
|
+
const { passSize, materialSize } = this.fragment;
|
|
6936
|
+
this.perFrameStaging = new ArrayBuffer(passSize + materialSize);
|
|
6803
6937
|
this.perFrameFloats = new Float32Array(this.perFrameStaging);
|
|
6804
6938
|
this.perFrameInts = new Int32Array(this.perFrameStaging);
|
|
6939
|
+
this.passInts = new Int32Array(this.perFrameStaging, 0, passSize / 4);
|
|
6940
|
+
this.materialInts = new Int32Array(this.perFrameStaging, passSize, materialSize / 4);
|
|
6805
6941
|
/*
|
|
6806
|
-
* **
|
|
6942
|
+
* **Two blocks, because they change at two rates.** `setSurfaceTexture`, `setSurfaceGrain` and
|
|
6943
|
+
* their siblings are pass state on both backends — a material covers many draws — and on WebGL2
|
|
6944
|
+
* each is a `uniform1f` that lands immediately. Here they live in a block, and
|
|
6945
|
+
* `queue.writeBuffer` does not interleave with recorded commands, so one buffer rewritten between
|
|
6946
|
+
* two draws gives *both* the second value: every surface would wear the last material set, which
|
|
6947
|
+
* is bug 7's shape at pass scale. So each draw binds a slot.
|
|
6807
6948
|
*
|
|
6808
|
-
*
|
|
6809
|
-
*
|
|
6810
|
-
*
|
|
6811
|
-
*
|
|
6812
|
-
* commands, so one buffer rewritten between two draws gives *both* the second value. Every
|
|
6813
|
-
* surface in a scene would wear the last material set, which is bug 7's shape at pass scale.
|
|
6814
|
-
*
|
|
6815
|
-
* A slot per *material change* rather than per draw: `drawMesh` reuses the open slot until
|
|
6816
|
-
* something dirties it, so a scene drawing a hundred meshes of one material spends one.
|
|
6949
|
+
* It was one block until 4.10.0, lights and all, copied whole into a ring slot per material
|
|
6950
|
+
* change and uploaded every frame: a stage of four hundred materials sent 4.8 MB a frame to say
|
|
6951
|
+
* what it had said the frame before. Now the pass's block takes a ring slot per pass, and a
|
|
6952
|
+
* material's block a slot of the store, where an unchanged material stays from frame to frame.
|
|
6817
6953
|
*/
|
|
6818
|
-
this.
|
|
6954
|
+
this.passRing = new UniformRing(
|
|
6955
|
+
device,
|
|
6956
|
+
passSize,
|
|
6957
|
+
PASSES_PER_FRAME,
|
|
6958
|
+
USAGE_UNIFORM_DST,
|
|
6959
|
+
'flat.passRing',
|
|
6960
|
+
);
|
|
6961
|
+
this.materialStore = new MaterialStore(
|
|
6819
6962
|
device,
|
|
6820
|
-
|
|
6963
|
+
materialSize,
|
|
6821
6964
|
this.quality.materialChangesPerFrame,
|
|
6822
6965
|
USAGE_UNIFORM_DST,
|
|
6823
|
-
'flat.
|
|
6966
|
+
'flat.materials',
|
|
6824
6967
|
this.materialBudget,
|
|
6825
6968
|
);
|
|
6969
|
+
/* Two a pass at most: the jittered view and the unjittered one. */
|
|
6970
|
+
this.viewRing = new UniformRing(
|
|
6971
|
+
device,
|
|
6972
|
+
FLAT_VIEW_SIZE,
|
|
6973
|
+
2 * PASSES_PER_FRAME,
|
|
6974
|
+
USAGE_UNIFORM_DST,
|
|
6975
|
+
'flat.viewRing',
|
|
6976
|
+
);
|
|
6826
6977
|
|
|
6827
6978
|
/*
|
|
6828
6979
|
* A one-pixel stand-in for the albedo binding. The albedo's flag is zero for untextured
|
|
@@ -6971,7 +7122,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6971
7122
|
})
|
|
6972
7123
|
.end();
|
|
6973
7124
|
}
|
|
6974
|
-
|
|
7125
|
+
this.submit([clear.finish()]);
|
|
6975
7126
|
/*
|
|
6976
7127
|
* `nearest` on a depth texture sampled as an ordinary float, matching what
|
|
6977
7128
|
* `shadowMap.ts` asks WebGL2 for. A filtering sampler cannot be used on a depth format
|
|
@@ -7584,6 +7735,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7584
7735
|
this.shadowLayout = createDepthBindGroupLayout(device);
|
|
7585
7736
|
this.shadowSkinnedLayout = createDepthBindGroupLayout(device, true);
|
|
7586
7737
|
this.shadowCutoutLayout = createDepthBindGroupLayout(device, false, true);
|
|
7738
|
+
this.shadowAnimatedLayout = createDepthBindGroupLayout(device, false, false, true);
|
|
7739
|
+
this.shadowAnimatedCutoutLayout = createDepthBindGroupLayout(device, false, true, true);
|
|
7587
7740
|
this.shadowDraws = new UniformRing(
|
|
7588
7741
|
device,
|
|
7589
7742
|
/* The cutout block is the plain one and two fields more, and a slot must hold either. */
|
|
@@ -8095,9 +8248,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8095
8248
|
this.lineVerts.reset();
|
|
8096
8249
|
this.lineFrags.reset();
|
|
8097
8250
|
this.skyUniforms.reset();
|
|
8098
|
-
/* The
|
|
8099
|
-
|
|
8251
|
+
/* The pass ring, and the open slots with it: last frame's pass slots are gone. Its materials
|
|
8252
|
+
stay in the store, which a draw finds them in again. */
|
|
8253
|
+
this.passRing.reset();
|
|
8254
|
+
this.viewRing.reset();
|
|
8255
|
+
this.materialStore.beginFrame();
|
|
8256
|
+
this.passesAsked = 0;
|
|
8257
|
+
this.passesRefused = 0;
|
|
8258
|
+
this.viewsAsked = 0;
|
|
8259
|
+
this.viewsRefused = 0;
|
|
8260
|
+
this.materialsRefused = 0;
|
|
8100
8261
|
this.materials.dirty();
|
|
8262
|
+
this.reopenPass();
|
|
8101
8263
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
8102
8264
|
this.motionPass?.reset();
|
|
8103
8265
|
this.latePass?.reset();
|
|
@@ -8432,6 +8594,40 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8432
8594
|
};
|
|
8433
8595
|
}
|
|
8434
8596
|
|
|
8597
|
+
/**
|
|
8598
|
+
* The copies this frame's mesh rewrites owe (`StagedRewrite`), recorded since the last submit and
|
|
8599
|
+
* sent at the head of the next one: one encoder however many meshes are rewritten. Null while
|
|
8600
|
+
* nothing is owed.
|
|
8601
|
+
*/
|
|
8602
|
+
private pendingRewrites: GPUCommandEncoder | null = null;
|
|
8603
|
+
/** One record handed to every staged rewrite, refilled each call, so a rewrite allocates nothing. */
|
|
8604
|
+
private readonly stagedRewrite: StagedRewrite = {
|
|
8605
|
+
encoder: null as unknown as GPUCommandEncoder,
|
|
8606
|
+
staging: null as unknown as GPUBuffer,
|
|
8607
|
+
previous: null,
|
|
8608
|
+
};
|
|
8609
|
+
|
|
8610
|
+
private rewriteCopies(): GPUCommandEncoder {
|
|
8611
|
+
this.pendingRewrites ??= this.surface.device.createCommandEncoder({
|
|
8612
|
+
label: 'mesh.rewriteCopies',
|
|
8613
|
+
});
|
|
8614
|
+
return this.pendingRewrites;
|
|
8615
|
+
}
|
|
8616
|
+
|
|
8617
|
+
/**
|
|
8618
|
+
* Submit `buffers`, led by any copies the frame's mesh rewrites owe. **Every submit of this
|
|
8619
|
+
* renderer goes through here**, because any of them may draw a rewritten mesh — the frame's own,
|
|
8620
|
+
* a mirror's, a shadow's, a probe's — and the copies must be ahead of the first that does.
|
|
8621
|
+
*/
|
|
8622
|
+
private submit(buffers: GPUCommandBuffer[]): void {
|
|
8623
|
+
const rewrites = this.pendingRewrites;
|
|
8624
|
+
if (rewrites !== null) {
|
|
8625
|
+
this.pendingRewrites = null;
|
|
8626
|
+
buffers.unshift(rewrites.finish());
|
|
8627
|
+
}
|
|
8628
|
+
this.surface.device.queue.submit(buffers);
|
|
8629
|
+
}
|
|
8630
|
+
|
|
8435
8631
|
/**
|
|
8436
8632
|
* Rewrite a mesh's positions, and its normals where the caller has them.
|
|
8437
8633
|
*
|
|
@@ -8454,20 +8650,35 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8454
8650
|
* `changeFrames.ts`.
|
|
8455
8651
|
*/
|
|
8456
8652
|
const motion = mesh.motion;
|
|
8457
|
-
let
|
|
8653
|
+
let staged: StagedRewrite | null = null;
|
|
8458
8654
|
if (motion !== null && this.reconstructionWanted) {
|
|
8459
8655
|
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
8656
|
+
const size = mesh.vertexBuffers[0]?.size ?? 0;
|
|
8657
|
+
let previous: GPUBuffer | null = null;
|
|
8460
8658
|
if (keepsPrevious(motion.changed, frame)) {
|
|
8461
8659
|
motion.previous ??= this.surface.device.createBuffer({
|
|
8462
8660
|
label: 'recon.dynamicPrevious',
|
|
8463
|
-
size
|
|
8661
|
+
size,
|
|
8464
8662
|
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
8465
8663
|
});
|
|
8466
8664
|
previous = motion.previous;
|
|
8467
8665
|
}
|
|
8468
8666
|
motion.changed = frame;
|
|
8667
|
+
/*
|
|
8668
|
+
* **Staged, and copied in behind the copy aside, in an encoder the frame's next submit leads
|
|
8669
|
+
* with**: no submit of the rewrite's own. See `StagedRewrite` and `submit`.
|
|
8670
|
+
*/
|
|
8671
|
+
motion.staging ??= this.surface.device.createBuffer({
|
|
8672
|
+
label: 'mesh.stagedPositions',
|
|
8673
|
+
size,
|
|
8674
|
+
usage: 0x0004 | 0x0008, // COPY_SRC | COPY_DST
|
|
8675
|
+
});
|
|
8676
|
+
staged = this.stagedRewrite;
|
|
8677
|
+
staged.encoder = this.rewriteCopies();
|
|
8678
|
+
staged.staging = motion.staging;
|
|
8679
|
+
staged.previous = previous;
|
|
8469
8680
|
}
|
|
8470
|
-
mesh.update(this.surface.device, positions, normals,
|
|
8681
|
+
mesh.update(this.surface.device, positions, normals, staged);
|
|
8471
8682
|
}
|
|
8472
8683
|
|
|
8473
8684
|
/**
|
|
@@ -8671,6 +8882,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8671
8882
|
* number, so this is a copy and is marked as one; the two must move together.
|
|
8672
8883
|
*/
|
|
8673
8884
|
f[at('uGrain')] = 1;
|
|
8885
|
+
/* No reflection and an environment at its own brightness, from the pass and from no material. */
|
|
8886
|
+
this.passReflectivity = 0;
|
|
8887
|
+
this.passEnvironmentGain = 1;
|
|
8888
|
+
this.materialReflectivity = null;
|
|
8889
|
+
this.materialEnvironmentGain = null;
|
|
8674
8890
|
f[at('uEnvironmentDials') + 1] = 0;
|
|
8675
8891
|
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
8676
8892
|
f[at('uWriteMode')] = 0;
|
|
@@ -8785,6 +9001,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8785
9001
|
*/
|
|
8786
9002
|
if (this.quality.nightEmissive) f[at('uNightEmissive')] = env.nightEmissive ?? 0;
|
|
8787
9003
|
f.set(env.highlightMin, at('uHighlightMin'));
|
|
9004
|
+
/* The frame the cutout dither moves with, in the near corner's w. See `uHighlightMin`. */
|
|
9005
|
+
f[at('uHighlightMin') + 3] = this.cutoutFrame;
|
|
8788
9006
|
f.set(env.highlightMax, at('uHighlightMax'));
|
|
8789
9007
|
/* The gain in the far corner's w. See `uHighlightMax`. */
|
|
8790
9008
|
f[at('uHighlightMax') + 3] = env.highlightGain;
|
|
@@ -9057,6 +9275,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9057
9275
|
* same terms here, which is why a pass cannot inherit a material from the one before it.
|
|
9058
9276
|
*/
|
|
9059
9277
|
this.materials.dirty();
|
|
9278
|
+
this.reopenPass();
|
|
9060
9279
|
/*
|
|
9061
9280
|
* A pass starts with no material, exactly as `renderer.ts` resets these terms — **every map,
|
|
9062
9281
|
* not the albedo alone**. `setMaterial` skips the lookup when the maps it is given are the ones
|
|
@@ -9081,31 +9300,114 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9081
9300
|
}
|
|
9082
9301
|
|
|
9083
9302
|
/**
|
|
9084
|
-
* The slot the next draw's material lives in,
|
|
9303
|
+
* The slot the next draw's material lives in, placing it when something dirtied it, with the
|
|
9304
|
+
* pass's slot taken first where `bindMeshPass` reopened it: `passOffset` is that one's offset.
|
|
9085
9305
|
*
|
|
9086
|
-
* **A
|
|
9087
|
-
* material
|
|
9088
|
-
*
|
|
9306
|
+
* **A placement per material change, not per draw.** A scene drawing a hundred meshes of one
|
|
9307
|
+
* material places it once; a scene switching material every draw places one each, and a material
|
|
9308
|
+
* the store already holds — the same numbers as a draw before it, this frame or an earlier one —
|
|
9309
|
+
* is bound where it is and uploads nothing.
|
|
9089
9310
|
*/
|
|
9090
9311
|
private materialSlotForDraw(): number | null {
|
|
9312
|
+
if (!this.passBlock.open) {
|
|
9313
|
+
this.passesAsked += 1;
|
|
9314
|
+
const pass = this.passRing.allocate();
|
|
9315
|
+
if (pass === null) {
|
|
9316
|
+
this.passesRefused += 1;
|
|
9317
|
+
this.materialBudget.ask();
|
|
9318
|
+
this.materialBudget.drop();
|
|
9319
|
+
if (!this.warnedPassesFull) {
|
|
9320
|
+
this.warnedPassesFull = true;
|
|
9321
|
+
console.warn(
|
|
9322
|
+
`WebGPU: more than ${this.passRing.slots} mesh passes drew in one frame; the draws of the rest are skipped this frame and the ring grows for the next.`,
|
|
9323
|
+
);
|
|
9324
|
+
}
|
|
9325
|
+
return null;
|
|
9326
|
+
}
|
|
9327
|
+
this.passRing.writeBlock(pass, this.passInts);
|
|
9328
|
+
this.passBlock.slot = pass;
|
|
9329
|
+
}
|
|
9091
9330
|
if (this.materials.open) return this.materials.slot;
|
|
9092
|
-
const slot = this.
|
|
9331
|
+
const slot = this.materialStore.place(this.materialInts);
|
|
9332
|
+
if (slot === null) {
|
|
9333
|
+
this.materialsRefused += 1;
|
|
9334
|
+
this.warnMaterialsFull();
|
|
9335
|
+
return null;
|
|
9336
|
+
}
|
|
9337
|
+
this.materials.slot = slot;
|
|
9338
|
+
return slot;
|
|
9339
|
+
}
|
|
9340
|
+
|
|
9341
|
+
/** The pass slot `materialSlotForDraw` left open, read beside the material's by every lit draw. */
|
|
9342
|
+
private get passOffset(): number {
|
|
9343
|
+
return this.passBlock.slot;
|
|
9344
|
+
}
|
|
9345
|
+
|
|
9346
|
+
/** A new pass: its block, and both its views, are taken afresh by the next draw. */
|
|
9347
|
+
private reopenPass(): void {
|
|
9348
|
+
this.passBlock.dirty();
|
|
9349
|
+
this.reopenViews();
|
|
9350
|
+
}
|
|
9351
|
+
|
|
9352
|
+
/** What a view holds changed — the wind, the light's matrix — so the next draw takes a new one. */
|
|
9353
|
+
private reopenViews(): void {
|
|
9354
|
+
this.viewBlock.dirty();
|
|
9355
|
+
this.lateViewBlock.dirty();
|
|
9356
|
+
}
|
|
9357
|
+
|
|
9358
|
+
/**
|
|
9359
|
+
* The view slot a lit draw reads its camera from: the pass's, or its unjittered twin for a blended
|
|
9360
|
+
* draw landing after the upscale. Taken by the first draw that needs it after the pass, the wind or
|
|
9361
|
+
* the light's matrix changed, and shared by every draw until then. Null when the ring is full, which
|
|
9362
|
+
* skips the draw and grows the ring for the next frame, as the pass ring does.
|
|
9363
|
+
*/
|
|
9364
|
+
private viewSlotFor(late: boolean, viewProj: Float32Array): number | null {
|
|
9365
|
+
const block = late ? this.lateViewBlock : this.viewBlock;
|
|
9366
|
+
if (block.open) return block.slot;
|
|
9367
|
+
this.viewsAsked += 1;
|
|
9368
|
+
const slot = this.viewRing.allocate();
|
|
9093
9369
|
if (slot === null) {
|
|
9094
|
-
|
|
9095
|
-
|
|
9370
|
+
this.viewsRefused += 1;
|
|
9371
|
+
this.materialBudget.ask();
|
|
9372
|
+
this.materialBudget.drop();
|
|
9373
|
+
if (!this.warnedPassesFull) {
|
|
9374
|
+
this.warnedPassesFull = true;
|
|
9096
9375
|
console.warn(
|
|
9097
|
-
`WebGPU: more than ${this.
|
|
9376
|
+
`WebGPU: more than ${this.viewRing.slots} views drew in one frame; the draws of the rest are skipped this frame and the ring grows for the next.`,
|
|
9098
9377
|
);
|
|
9099
9378
|
}
|
|
9100
9379
|
return null;
|
|
9101
9380
|
}
|
|
9102
|
-
this.
|
|
9103
|
-
|
|
9381
|
+
this.writeView(this.viewRing, slot, viewProj);
|
|
9382
|
+
block.slot = slot;
|
|
9104
9383
|
return slot;
|
|
9105
9384
|
}
|
|
9106
9385
|
|
|
9386
|
+
/** A view block: the camera, the light's matrix and the frame's wind, into `ring` at `slot`. */
|
|
9387
|
+
private writeView(ring: UniformRing, slot: number, viewProj: Float32Array): void {
|
|
9388
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uViewProj.offset, viewProj);
|
|
9389
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
9390
|
+
/* The scene's clock, which a bone animation's instances read their moment from. */
|
|
9391
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uSceneTime.offset, this.animationTime);
|
|
9392
|
+
const d = this.frameWind;
|
|
9393
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindDirection.offset, d.direction);
|
|
9394
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindSpeed.offset, d.bend);
|
|
9395
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindGust.offset, d.gust);
|
|
9396
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindTime.offset, d.time);
|
|
9397
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
9398
|
+
}
|
|
9399
|
+
|
|
9400
|
+
private warnMaterialsFull(): void {
|
|
9401
|
+
if (this.warnedMaterialsFull) return;
|
|
9402
|
+
this.warnedMaterialsFull = true;
|
|
9403
|
+
console.warn(
|
|
9404
|
+
`WebGPU: more than ${this.quality.materialChangesPerFrame} distinct materials in a frame; the draws asking for the rest are skipped. renderer.frameBudget names the line and the count.`,
|
|
9405
|
+
);
|
|
9406
|
+
}
|
|
9407
|
+
|
|
9107
9408
|
/** Said once rather than every frame, for the reason `warnedFull` gives. */
|
|
9108
9409
|
private warnedMaterialsFull = false;
|
|
9410
|
+
private warnedPassesFull = false;
|
|
9109
9411
|
private warnedSkyFull = false;
|
|
9110
9412
|
private warnedParticlesTwice = false;
|
|
9111
9413
|
/**
|
|
@@ -9146,7 +9448,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9146
9448
|
* What this saves is the GPU's share, which for a heavy mesh is most of the cost and for a
|
|
9147
9449
|
* trivial one is very little.
|
|
9148
9450
|
*/
|
|
9149
|
-
|
|
9451
|
+
/* A static list draws every entry in every view, since a bundle cannot leave one out. */
|
|
9452
|
+
if (
|
|
9453
|
+
this.quality.cullDraws &&
|
|
9454
|
+
this.staticRecording === null &&
|
|
9455
|
+
!this.visible(mesh.bounds, model as ReadonlyMat4)
|
|
9456
|
+
) {
|
|
9457
|
+
return;
|
|
9458
|
+
}
|
|
9150
9459
|
/*
|
|
9151
9460
|
* And the other half of the same question, where the profile asked for one. Gated on
|
|
9152
9461
|
* `cullDraws` alongside the frustum test, because both are the renderer doing a consumer's
|
|
@@ -9337,21 +9646,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9337
9646
|
`TranslucentMeshOptions.reconstructed`. */
|
|
9338
9647
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
9339
9648
|
const late = lands || (blend && this.oitReplaying && this.reconstructing);
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
);
|
|
9649
|
+
/* The camera, the light's matrix and the wind are the pass's, in its view block. */
|
|
9650
|
+
const view = this.viewSlotFor(late, late ? this.correctedViewProj : this.viewProj);
|
|
9651
|
+
if (view === null) return;
|
|
9652
|
+
this.drawViewOffset = view;
|
|
9345
9653
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
9346
9654
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
9347
|
-
/* The frame's wind, for a mesh carrying a sway lane. Written on this path and not the
|
|
9348
|
-
instanced one, which declares none of these fields. */
|
|
9349
|
-
this.writeWind(slot);
|
|
9350
9655
|
/* The material's repeats, not a constant: geometry authored in metres is textured at
|
|
9351
9656
|
whatever density the material asks for without rebuilding it. */
|
|
9352
9657
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
9353
9658
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
9354
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
9355
9659
|
/*
|
|
9356
9660
|
* Whether this mesh's tangent frame is real. WebGPU has no disabled attribute, so location 10
|
|
9357
9661
|
* is always backed — by the caller's data or by the constant buffer — which makes the binding
|
|
@@ -9548,8 +9852,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9548
9852
|
command.pipeline = pipeline;
|
|
9549
9853
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
9550
9854
|
command.offsetA = slot;
|
|
9551
|
-
command.offsetB =
|
|
9552
|
-
command.
|
|
9855
|
+
command.offsetB = this.passOffset;
|
|
9856
|
+
command.offsetC = material;
|
|
9857
|
+
command.offsetD = this.drawViewOffset;
|
|
9858
|
+
command.offsetCount = 4;
|
|
9553
9859
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
9554
9860
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
9555
9861
|
}
|
|
@@ -9561,7 +9867,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9561
9867
|
const pass = this.openPass();
|
|
9562
9868
|
if (pass === null) return;
|
|
9563
9869
|
pass.setPipeline(pipeline);
|
|
9564
|
-
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
9870
|
+
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
9871
|
+
slot,
|
|
9872
|
+
this.passOffset,
|
|
9873
|
+
material,
|
|
9874
|
+
this.drawViewOffset,
|
|
9875
|
+
]);
|
|
9565
9876
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
9566
9877
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
|
|
9567
9878
|
}
|
|
@@ -9677,16 +9988,127 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9677
9988
|
'instanced pipeline.',
|
|
9678
9989
|
);
|
|
9679
9990
|
}
|
|
9991
|
+
const animation = (options.animation ?? null) as GpuBoneAnimation | null;
|
|
9992
|
+
/* The bone a vertex follows is its second coordinates' u, which a mesh carries in its grain
|
|
9993
|
+
lane; a mesh with none has nothing to name a bone by. See `boneAnimation.ts`. */
|
|
9994
|
+
if (animation !== null && !this.presentOf(mesh as GpuMesh & { key?: string })['grain']) {
|
|
9995
|
+
throw new Error(
|
|
9996
|
+
'WebGPU: a bone animation reads each vertex’s bone from the mesh’s second coordinates ' +
|
|
9997
|
+
'(`MeshData.lightmapUvs`), and this mesh has none.',
|
|
9998
|
+
);
|
|
9999
|
+
}
|
|
9680
10000
|
const batch = new GpuInstancedBatch(
|
|
9681
10001
|
this.surface.device,
|
|
9682
10002
|
mesh,
|
|
9683
10003
|
capacity,
|
|
9684
10004
|
`flat.instances:${(mesh as GpuMesh & { key?: string }).key ?? 'mesh'}`,
|
|
9685
10005
|
options.cull ?? false,
|
|
10006
|
+
animation,
|
|
9686
10007
|
);
|
|
9687
10008
|
return batch;
|
|
9688
10009
|
}
|
|
9689
10010
|
|
|
10011
|
+
/** See `Renderer.setAnimationTime`. Every view taken after it reads the new clock. */
|
|
10012
|
+
setAnimationTime(seconds: number): void {
|
|
10013
|
+
if (!Number.isFinite(seconds) || seconds === this.animationTime) return;
|
|
10014
|
+
this.animationTime = seconds;
|
|
10015
|
+
this.reopenViews();
|
|
10016
|
+
}
|
|
10017
|
+
|
|
10018
|
+
/** The clock `setAnimationTime` set, 0 until it is. */
|
|
10019
|
+
private animationTime = 0;
|
|
10020
|
+
|
|
10021
|
+
/** See `Renderer.createBoneAnimation`. */
|
|
10022
|
+
createBoneAnimation(clip: BoneAnimationClip): BoneAnimationHandle {
|
|
10023
|
+
return new GpuBoneAnimation(this.surface.device, packBoneAnimation(clip));
|
|
10024
|
+
}
|
|
10025
|
+
|
|
10026
|
+
/** See `Renderer.disposeBoneAnimation`. A batch still playing it draws nothing after. */
|
|
10027
|
+
disposeBoneAnimation(animation: BoneAnimationHandle): void {
|
|
10028
|
+
const clip = animation as GpuBoneAnimation;
|
|
10029
|
+
if (!clip.disposed) clip.dispose();
|
|
10030
|
+
}
|
|
10031
|
+
|
|
10032
|
+
/** An animated batch's clip and clocks, as its groups bind them. */
|
|
10033
|
+
private animationViews(batch: GpuInstancedBatch): AnimationViews | null {
|
|
10034
|
+
const animation = batch.animation;
|
|
10035
|
+
const clocks = batch.clocks;
|
|
10036
|
+
if (animation === null || clocks === null || animation.disposed) return null;
|
|
10037
|
+
return { places: animation.placesView, turns: animation.turnsView, clocks: clocks.view };
|
|
10038
|
+
}
|
|
10039
|
+
|
|
10040
|
+
/** An animated batch's groups, dropped wherever the renderer drops its own. */
|
|
10041
|
+
private freshAnimatedGroups(batch: GpuInstancedBatch): void {
|
|
10042
|
+
if (batch.groupEpoch === this.flatGroupEpoch) return;
|
|
10043
|
+
batch.litGroups.clear();
|
|
10044
|
+
batch.depthGroups.clear();
|
|
10045
|
+
batch.depthPeelGroups.clear();
|
|
10046
|
+
batch.groupEpoch = this.flatGroupEpoch;
|
|
10047
|
+
}
|
|
10048
|
+
|
|
10049
|
+
/**
|
|
10050
|
+
* The lit group an animated batch draws with: the material's group, or its overlay twin, again
|
|
10051
|
+
* with the clip's and the clocks' textures — kept on the batch by the group it was built beside,
|
|
10052
|
+
* as a skinned twin is kept by its palette slot.
|
|
10053
|
+
*/
|
|
10054
|
+
private animatedGroup(batch: GpuInstancedBatch, views: AnimationViews): GPUBindGroup {
|
|
10055
|
+
this.freshAnimatedGroups(batch);
|
|
10056
|
+
const base = this.overlayGroup();
|
|
10057
|
+
let group = batch.litGroups.get(base);
|
|
10058
|
+
if (group === undefined) {
|
|
10059
|
+
this.overlayBinding = base !== this.bindGroup;
|
|
10060
|
+
group = createFlatBindGroup(
|
|
10061
|
+
this.surface.device,
|
|
10062
|
+
this.animatedBindGroupLayout,
|
|
10063
|
+
this.variant,
|
|
10064
|
+
this.perDraw.buffer,
|
|
10065
|
+
this.passRing.buffer,
|
|
10066
|
+
this.materialStore.buffer,
|
|
10067
|
+
this.viewRing.buffer,
|
|
10068
|
+
this.flatTextures,
|
|
10069
|
+
null,
|
|
10070
|
+
null,
|
|
10071
|
+
null,
|
|
10072
|
+
views,
|
|
10073
|
+
);
|
|
10074
|
+
this.overlayBinding = false;
|
|
10075
|
+
this.bindGroupBudget.ask();
|
|
10076
|
+
batch.litGroups.set(base, group);
|
|
10077
|
+
}
|
|
10078
|
+
return group;
|
|
10079
|
+
}
|
|
10080
|
+
|
|
10081
|
+
/** An animated caster's shadow group, by what it cuts with and whether this is the peel. */
|
|
10082
|
+
private animatedDepthGroup(
|
|
10083
|
+
batch: GpuInstancedBatch,
|
|
10084
|
+
views: AnimationViews,
|
|
10085
|
+
cutout: GpuSurfaceTexture | null,
|
|
10086
|
+
): GPUBindGroup | null {
|
|
10087
|
+
this.freshAnimatedGroups(batch);
|
|
10088
|
+
const peel = this.shadowLayerIsPeel && this.shadowPeelPassUniforms !== null;
|
|
10089
|
+
const groups = peel ? batch.depthPeelGroups : batch.depthGroups;
|
|
10090
|
+
const held = groups.get(cutout);
|
|
10091
|
+
if (held !== undefined) return held;
|
|
10092
|
+
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
10093
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
10094
|
+
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
10095
|
+
if (passUniforms === null || previousView === null || previousSampler === null) return null;
|
|
10096
|
+
const group = createDepthBindGroup(
|
|
10097
|
+
this.surface.device,
|
|
10098
|
+
cutout === null ? this.shadowAnimatedLayout : this.shadowAnimatedCutoutLayout,
|
|
10099
|
+
this.shadowDraws.buffer,
|
|
10100
|
+
passUniforms,
|
|
10101
|
+
previousView,
|
|
10102
|
+
previousSampler,
|
|
10103
|
+
null,
|
|
10104
|
+
cutout === null ? null : { view: cutout.view, sampler: cutout.sampler },
|
|
10105
|
+
null,
|
|
10106
|
+
views,
|
|
10107
|
+
);
|
|
10108
|
+
groups.set(cutout, group);
|
|
10109
|
+
return group;
|
|
10110
|
+
}
|
|
10111
|
+
|
|
9690
10112
|
/** The device instance culls, built on the first culling draw. See `InstanceCullPass`. */
|
|
9691
10113
|
private instanceCulls: InstanceCullPass | null = null;
|
|
9692
10114
|
/** Counts frames, so a batch knows when its cull slots were last taken. */
|
|
@@ -9720,7 +10142,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9720
10142
|
this.submitInstanced(batch, data, 1, false, {});
|
|
9721
10143
|
/* A batch that moved this frame pairs each slot with last frame's; the batch is the identity. */
|
|
9722
10144
|
const previous = batch.previousBuffer;
|
|
9723
|
-
if (
|
|
10145
|
+
if (
|
|
10146
|
+
previous !== null &&
|
|
10147
|
+
this.staticRecording === null &&
|
|
10148
|
+
batch.changed === this.moverFrame &&
|
|
10149
|
+
this.tracksMotionNow()
|
|
10150
|
+
) {
|
|
9724
10151
|
const paired = pairedInstances(batch.previousCount, Math.min(data.count, batch.capacity));
|
|
9725
10152
|
if (paired > 0) this.motionPass?.recordInstanced(batch.mesh, batch.buffer, previous, paired);
|
|
9726
10153
|
}
|
|
@@ -9767,6 +10194,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9767
10194
|
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
9768
10195
|
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
9769
10196
|
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
10197
|
+
/* w: this framebuffer's first row is the top, so up the screen is down the rows. */
|
|
10198
|
+
this.perFrameFloats[tintAt + 3] = -1;
|
|
9770
10199
|
const glassTintAt = this.materialField('uGlassTint');
|
|
9771
10200
|
this.perFrameFloats[glassTintAt] = glass.tint[0];
|
|
9772
10201
|
this.perFrameFloats[glassTintAt + 1] = glass.tint[1];
|
|
@@ -9842,10 +10271,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9842
10271
|
/* A culling batch's camera draw: the whole batch against the view and the occluders on the CPU,
|
|
9843
10272
|
then instance by instance on the device into an indirect draw. Blended draws keep every
|
|
9844
10273
|
instance and their order. See `InstancedOptions.cull`. */
|
|
9845
|
-
const culling = !blend && batch.cull;
|
|
10274
|
+
const culling = !blend && batch.cull && this.staticRecording === null;
|
|
9846
10275
|
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion)) return;
|
|
10276
|
+
/* A batch playing a bone animation reads its clocks by instance index, which a compaction
|
|
10277
|
+
would reorder: it is culled whole and never instance by instance. */
|
|
10278
|
+
const animation = batch.animation === null ? null : this.animationViews(batch);
|
|
10279
|
+
if (batch.animation !== null && animation === null) return;
|
|
9847
10280
|
const cut =
|
|
9848
|
-
culling && cullsInstances(count, mesh.indexCount)
|
|
10281
|
+
culling && animation === null && cullsInstances(count, mesh.indexCount)
|
|
10282
|
+
? this.takeCullSlot(batch, count)
|
|
10283
|
+
: null;
|
|
9849
10284
|
const instanceBuffer = cut === null ? batch.buffer : cut.instances;
|
|
9850
10285
|
|
|
9851
10286
|
const slot = this.perDraw.allocate();
|
|
@@ -9861,15 +10296,22 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9861
10296
|
|
|
9862
10297
|
/* Late unless it moves with what it lies on. See `TranslucentMeshOptions.reconstructed`. */
|
|
9863
10298
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
9864
|
-
this.
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
lands ? this.correctedViewProj : this.viewProj,
|
|
9868
|
-
);
|
|
10299
|
+
const view = this.viewSlotFor(lands, lands ? this.correctedViewProj : this.viewProj);
|
|
10300
|
+
if (view === null) return;
|
|
10301
|
+
this.drawViewOffset = view;
|
|
9869
10302
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
9870
10303
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
9871
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
9872
10304
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
10305
|
+
/* The clip's speed and bone scale, read by the animated variant's own field. */
|
|
10306
|
+
const clip = batch.animation;
|
|
10307
|
+
if (animation !== null && clip !== null) {
|
|
10308
|
+
this.perDraw.writeFloat(
|
|
10309
|
+
slot,
|
|
10310
|
+
FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset,
|
|
10311
|
+
clip.framesPerSecond,
|
|
10312
|
+
);
|
|
10313
|
+
this.perDraw.writeFloat(slot, FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset + 4, clip.boneScale);
|
|
10314
|
+
}
|
|
9873
10315
|
|
|
9874
10316
|
const dimmed = opacity < 1;
|
|
9875
10317
|
if (dimmed) {
|
|
@@ -9907,7 +10349,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9907
10349
|
plain one would hand a draw the wrong layout. */
|
|
9908
10350
|
const adds = blend && options.additive === true;
|
|
9909
10351
|
const key =
|
|
9910
|
-
`${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
10352
|
+
`${base}|inst${animation === null ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
9911
10353
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
9912
10354
|
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
9913
10355
|
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
@@ -9922,7 +10364,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9922
10364
|
pipeline = flatPipeline(
|
|
9923
10365
|
pipelines,
|
|
9924
10366
|
this.surface.device,
|
|
9925
|
-
this.flatLayoutFor(false, false),
|
|
10367
|
+
animation === null ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout,
|
|
9926
10368
|
this.variant,
|
|
9927
10369
|
key,
|
|
9928
10370
|
present,
|
|
@@ -9938,6 +10380,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9938
10380
|
false,
|
|
9939
10381
|
false,
|
|
9940
10382
|
modelKind,
|
|
10383
|
+
'whole',
|
|
10384
|
+
false,
|
|
10385
|
+
animation !== null,
|
|
9941
10386
|
);
|
|
9942
10387
|
}
|
|
9943
10388
|
|
|
@@ -9948,10 +10393,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9948
10393
|
: this.recordDraw(0, this.currentTarget());
|
|
9949
10394
|
if (command !== null) {
|
|
9950
10395
|
command.pipeline = pipeline;
|
|
9951
|
-
command.bindGroup =
|
|
10396
|
+
command.bindGroup =
|
|
10397
|
+
animation === null
|
|
10398
|
+
? this.flatBindGroupFor(false, false, null)
|
|
10399
|
+
: this.animatedGroup(batch, animation);
|
|
9952
10400
|
command.offsetA = slot;
|
|
9953
|
-
command.offsetB =
|
|
9954
|
-
command.
|
|
10401
|
+
command.offsetB = this.passOffset;
|
|
10402
|
+
command.offsetC = material;
|
|
10403
|
+
command.offsetD = this.drawViewOffset;
|
|
10404
|
+
command.offsetCount = 4;
|
|
9955
10405
|
let index = 0;
|
|
9956
10406
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
9957
10407
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
@@ -9967,7 +10417,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9967
10417
|
const pass = this.openPass();
|
|
9968
10418
|
if (pass === null) return;
|
|
9969
10419
|
pass.setPipeline(pipeline);
|
|
9970
|
-
pass.setBindGroup(
|
|
10420
|
+
pass.setBindGroup(
|
|
10421
|
+
0,
|
|
10422
|
+
animation === null
|
|
10423
|
+
? this.flatBindGroupFor(false, false, null)
|
|
10424
|
+
: this.animatedGroup(batch, animation),
|
|
10425
|
+
[slot, this.passOffset, material, this.drawViewOffset],
|
|
10426
|
+
);
|
|
9971
10427
|
let index = 0;
|
|
9972
10428
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
9973
10429
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
|
|
@@ -9995,6 +10451,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9995
10451
|
instanceBuffer,
|
|
9996
10452
|
count,
|
|
9997
10453
|
cut === null ? null : cut.args,
|
|
10454
|
+
animation === null ? null : batch,
|
|
9998
10455
|
);
|
|
9999
10456
|
}
|
|
10000
10457
|
}
|
|
@@ -10207,6 +10664,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10207
10664
|
if (view === null) return false;
|
|
10208
10665
|
|
|
10209
10666
|
this.lightViewProj.set(lightViewProj as Float32Array);
|
|
10667
|
+
this.reopenViews();
|
|
10210
10668
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
10211
10669
|
this.shadowLayerIsPeel = peel;
|
|
10212
10670
|
this.shadowLayerIsDynamic = dynamic;
|
|
@@ -10319,6 +10777,264 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10319
10777
|
casters(this.sceneCasterSink);
|
|
10320
10778
|
}
|
|
10321
10779
|
|
|
10780
|
+
/* -- Static draws ---------------------------------------------------------------- */
|
|
10781
|
+
|
|
10782
|
+
/** See `Renderer.createStaticDraws`. Recorded on its first draw, into each view it is drawn in. */
|
|
10783
|
+
createStaticDraws(casters: ShadowCasters): StaticDrawsHandle {
|
|
10784
|
+
return new GpuStaticDraws(
|
|
10785
|
+
this.surface.device,
|
|
10786
|
+
captureStaticDraws(casters),
|
|
10787
|
+
this.fragment.passSize,
|
|
10788
|
+
FLAT_VIEW_SIZE,
|
|
10789
|
+
FLAT_VERT_SIZE,
|
|
10790
|
+
);
|
|
10791
|
+
}
|
|
10792
|
+
|
|
10793
|
+
/**
|
|
10794
|
+
* See `Renderer.drawStaticDraws`. One bundle executed, after a pass block and a view block are
|
|
10795
|
+
* written for this view; the list is recorded first where it has no recording that is current for
|
|
10796
|
+
* this target and starting state. See `staticBundles.ts`.
|
|
10797
|
+
*/
|
|
10798
|
+
drawStaticDraws(draws: StaticDrawsHandle): void {
|
|
10799
|
+
if (this.surface.lost) return;
|
|
10800
|
+
const list = draws as GpuStaticDraws;
|
|
10801
|
+
if (list.disposed) {
|
|
10802
|
+
this.warnStaticDisposed();
|
|
10803
|
+
return;
|
|
10804
|
+
}
|
|
10805
|
+
if (!this.canDraw() || this.viewProj === null) return;
|
|
10806
|
+
/* A list's draws are rigid and wear no overlay: put back to none, as `bindMeshPass` does. */
|
|
10807
|
+
this.setSkinPalette(null);
|
|
10808
|
+
this.setMorphWeights(null);
|
|
10809
|
+
this.setCloth(null);
|
|
10810
|
+
this.setSurfaceOverlay(null);
|
|
10811
|
+
this.setMaterial(null);
|
|
10812
|
+
this.sceneCasterMaterial = undefined;
|
|
10813
|
+
/* A probe face gives its slots back after every face; a list replays there draw by draw. */
|
|
10814
|
+
if (this.probePassActive) {
|
|
10815
|
+
this.replayStaticImmediately(list);
|
|
10816
|
+
return;
|
|
10817
|
+
}
|
|
10818
|
+
/* The copy first: taking one may grow the list's rings, which no recording before can bind. */
|
|
10819
|
+
const copy = list.takeCopy(this.moverFrame);
|
|
10820
|
+
const current = this.staticRecordingFor(list);
|
|
10821
|
+
if (current === null || current.immediate) {
|
|
10822
|
+
this.replayStaticImmediately(list);
|
|
10823
|
+
return;
|
|
10824
|
+
}
|
|
10825
|
+
current.usedIn = this.moverFrame;
|
|
10826
|
+
list.writePass(copy, this.passInts);
|
|
10827
|
+
this.writeView(list.views, list.viewOffset(copy), this.viewProj);
|
|
10828
|
+
list.uploadCopy(copy);
|
|
10829
|
+
|
|
10830
|
+
const cache = current.cache;
|
|
10831
|
+
let frame = current.frameBundles[copy];
|
|
10832
|
+
if (frame === undefined || frame === null) {
|
|
10833
|
+
frame = encodeBundle(
|
|
10834
|
+
this.surface.device,
|
|
10835
|
+
{
|
|
10836
|
+
colorFormats: [cache.format],
|
|
10837
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
10838
|
+
sampleCount: cache.sampleCount,
|
|
10839
|
+
depthReadOnly: false,
|
|
10840
|
+
},
|
|
10841
|
+
current.frame,
|
|
10842
|
+
list.passOffset(copy),
|
|
10843
|
+
list.viewOffset(copy),
|
|
10844
|
+
'static.frame',
|
|
10845
|
+
);
|
|
10846
|
+
current.frameBundles[copy] = frame;
|
|
10847
|
+
}
|
|
10848
|
+
const command = this.recordDraw(0, this.currentTarget());
|
|
10849
|
+
if (command !== null) {
|
|
10850
|
+
command.bundles = frame;
|
|
10851
|
+
command.pipeline = null;
|
|
10852
|
+
command.bindGroup = null;
|
|
10853
|
+
} else {
|
|
10854
|
+
this.openPass()?.executeBundles(frame);
|
|
10855
|
+
}
|
|
10856
|
+
const surface = this.reflectionSurface;
|
|
10857
|
+
if (current.surfaceKept && surface !== null && current.surface.taken > 0) {
|
|
10858
|
+
let halves = current.surfaceBundles[copy];
|
|
10859
|
+
if (halves === undefined || halves === null) {
|
|
10860
|
+
halves = encodeBundle(
|
|
10861
|
+
this.surface.device,
|
|
10862
|
+
{
|
|
10863
|
+
colorFormats: [null, REFLECTION_SURFACE_FORMAT, REFLECTION_SURFACE_FORMAT],
|
|
10864
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
10865
|
+
sampleCount: 1,
|
|
10866
|
+
depthReadOnly: true,
|
|
10867
|
+
},
|
|
10868
|
+
current.surface,
|
|
10869
|
+
list.passOffset(copy),
|
|
10870
|
+
list.viewOffset(copy),
|
|
10871
|
+
'static.surface',
|
|
10872
|
+
);
|
|
10873
|
+
current.surfaceBundles[copy] = halves;
|
|
10874
|
+
}
|
|
10875
|
+
const kept = surface.take();
|
|
10876
|
+
kept.bundles = halves;
|
|
10877
|
+
}
|
|
10878
|
+
/* Counted as the draws would have been, so a frame reports the same on both backends. */
|
|
10879
|
+
for (let draw = 0; draw < current.frame.taken; draw++) this.drawBudget.ask();
|
|
10880
|
+
if (!this.staticJustRecorded) {
|
|
10881
|
+
for (let ask = 0; ask < current.materialAsks; ask++) this.materialBudget.ask();
|
|
10882
|
+
}
|
|
10883
|
+
this.staticJustRecorded = false;
|
|
10884
|
+
}
|
|
10885
|
+
|
|
10886
|
+
/** A list drawn the way `drawSceneCasters` draws, a draw at a time, for where a bundle cannot be. */
|
|
10887
|
+
private replayStaticImmediately(list: GpuStaticDraws): void {
|
|
10888
|
+
this.staticJustRecorded = false;
|
|
10889
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
10890
|
+
this.setMaterial(null);
|
|
10891
|
+
this.sceneCasterMaterial = undefined;
|
|
10892
|
+
}
|
|
10893
|
+
|
|
10894
|
+
/** See `Renderer.disposeStaticDraws`. Its pins let go; its meshes and materials are the caller's. */
|
|
10895
|
+
disposeStaticDraws(draws: StaticDrawsHandle): void {
|
|
10896
|
+
const list = draws as GpuStaticDraws;
|
|
10897
|
+
if (list.disposed) return;
|
|
10898
|
+
list.dispose(this.unpinFor(list));
|
|
10899
|
+
}
|
|
10900
|
+
|
|
10901
|
+
/** The recording to replay `list` from here, made now if none is current; null if it cannot be. */
|
|
10902
|
+
private staticRecordingFor(list: GpuStaticDraws): StaticRecording | null {
|
|
10903
|
+
/* Geometry still arriving is not drawn yet, so a recording now would leave it out for good. */
|
|
10904
|
+
for (const entry of list.entries) {
|
|
10905
|
+
const mesh = entry.kind === 'mesh' ? entry.mesh : (entry.batch as GpuInstancedBatch).mesh;
|
|
10906
|
+
if (!mesh.complete) return null;
|
|
10907
|
+
}
|
|
10908
|
+
const cache = this.targetPipelines();
|
|
10909
|
+
const start = this.materialInts;
|
|
10910
|
+
const startHash = hashWords(start, start.length);
|
|
10911
|
+
const surfaceKept = this.reflectionSurfaceKeeps();
|
|
10912
|
+
let recording = list.find(cache, start, startHash, this.cutoutResolveStaged, surfaceKept);
|
|
10913
|
+
if (
|
|
10914
|
+
recording !== null &&
|
|
10915
|
+
recording.cacheVersion === cache.version &&
|
|
10916
|
+
recording.groupEpoch === this.flatGroupEpoch &&
|
|
10917
|
+
recording.storeEpoch === this.materialStore.epoch &&
|
|
10918
|
+
recording.ringEpoch === list.ringEpoch
|
|
10919
|
+
) {
|
|
10920
|
+
return recording;
|
|
10921
|
+
}
|
|
10922
|
+
const unpin = this.unpinFor(list);
|
|
10923
|
+
if (recording === null) {
|
|
10924
|
+
recording = list.open(cache, start, startHash, this.cutoutResolveStaged, surfaceKept, unpin);
|
|
10925
|
+
} else {
|
|
10926
|
+
list.clear(recording, recording.storeEpoch === this.materialStore.epoch ? unpin : () => {});
|
|
10927
|
+
}
|
|
10928
|
+
this.recordStatic(list, recording);
|
|
10929
|
+
return recording;
|
|
10930
|
+
}
|
|
10931
|
+
|
|
10932
|
+
/** How a list's pins are let go: through the store, unless it has forgotten them since. */
|
|
10933
|
+
private unpinFor(list: GpuStaticDraws): (offset: number) => void {
|
|
10934
|
+
const epoch = this.materialStore.epoch;
|
|
10935
|
+
return (offset) => {
|
|
10936
|
+
if (this.materialStore.epoch === epoch && list.pinEpoch === epoch) {
|
|
10937
|
+
this.materialStore.unpin(offset);
|
|
10938
|
+
}
|
|
10939
|
+
};
|
|
10940
|
+
}
|
|
10941
|
+
|
|
10942
|
+
/**
|
|
10943
|
+
* Record `list` into `recording`: its entries through the scene-caster path, with the list's
|
|
10944
|
+
* rings and group cache swapped in for the frame's and the pass and view slots held at the list's
|
|
10945
|
+
* first copy, so every command the path records binds the list's buffers and offsets the bundle
|
|
10946
|
+
* can rewrite per copy. Everything swapped is put back, whatever the path throws.
|
|
10947
|
+
*/
|
|
10948
|
+
private recordStatic(list: GpuStaticDraws, recording: StaticRecording): void {
|
|
10949
|
+
if (list.groupEpoch !== this.flatGroupEpoch) {
|
|
10950
|
+
list.groups = new Map();
|
|
10951
|
+
list.blank = null;
|
|
10952
|
+
list.groupEpoch = this.flatGroupEpoch;
|
|
10953
|
+
}
|
|
10954
|
+
list.drawRing.reset();
|
|
10955
|
+
const perDraw = this.perDraw;
|
|
10956
|
+
const passRing = this.passRing;
|
|
10957
|
+
const viewRing = this.viewRing;
|
|
10958
|
+
const groups = this.flatBindGroups;
|
|
10959
|
+
const blank = this.blankAlbedoBindGroup;
|
|
10960
|
+
const bindGroup = this.bindGroup;
|
|
10961
|
+
const passSlot = this.passBlock.slot;
|
|
10962
|
+
const viewSlot = this.viewBlock.slot;
|
|
10963
|
+
const lateSlot = this.lateViewBlock.slot;
|
|
10964
|
+
const asked = this.materialBudget.used;
|
|
10965
|
+
this.perDraw = list.drawRing;
|
|
10966
|
+
this.passRing = list.passes;
|
|
10967
|
+
this.viewRing = list.views;
|
|
10968
|
+
this.flatBindGroups = list.groups as typeof groups;
|
|
10969
|
+
/* The blank group over the list's rings, built as the frame's is: with no maps held. */
|
|
10970
|
+
list.blank ??= this.buildFlatBindGroup();
|
|
10971
|
+
this.blankAlbedoBindGroup = list.blank;
|
|
10972
|
+
this.bindGroup = this.flatGroupForMaps(null, null, null, null, null);
|
|
10973
|
+
this.passBlock.slot = list.passOffset(0);
|
|
10974
|
+
this.viewBlock.slot = list.viewOffset(0);
|
|
10975
|
+
this.lateViewBlock.slot = list.viewOffset(0);
|
|
10976
|
+
this.materials.dirty();
|
|
10977
|
+
this.staticRecording = recording;
|
|
10978
|
+
try {
|
|
10979
|
+
this.sceneCasterMaterial = undefined;
|
|
10980
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
10981
|
+
} finally {
|
|
10982
|
+
this.staticRecording = null;
|
|
10983
|
+
this.perDraw = perDraw;
|
|
10984
|
+
this.passRing = passRing;
|
|
10985
|
+
this.viewRing = viewRing;
|
|
10986
|
+
this.flatBindGroups = groups;
|
|
10987
|
+
this.blankAlbedoBindGroup = blank;
|
|
10988
|
+
this.bindGroup = bindGroup;
|
|
10989
|
+
this.passBlock.slot = passSlot;
|
|
10990
|
+
this.viewBlock.slot = viewSlot;
|
|
10991
|
+
this.lateViewBlock.slot = lateSlot;
|
|
10992
|
+
this.materials.dirty();
|
|
10993
|
+
this.sceneCasterMaterial = undefined;
|
|
10994
|
+
this.setMaterial(null);
|
|
10995
|
+
}
|
|
10996
|
+
recording.materialAsks = this.materialBudget.used - asked;
|
|
10997
|
+
const store = this.materialStore;
|
|
10998
|
+
recording.cacheVersion = recording.cache.version;
|
|
10999
|
+
recording.groupEpoch = this.flatGroupEpoch;
|
|
11000
|
+
recording.storeEpoch = store.epoch;
|
|
11001
|
+
recording.ringEpoch = list.ringEpoch;
|
|
11002
|
+
/* A skin split into halves is replayed draw by draw, so nothing recorded is kept or pinned. */
|
|
11003
|
+
if (recording.immediate) {
|
|
11004
|
+
resetPool(recording.frame);
|
|
11005
|
+
resetPool(recording.surface);
|
|
11006
|
+
return;
|
|
11007
|
+
}
|
|
11008
|
+
/* Every material the commands bind, pinned once, so nothing displaces what a bundle bakes. */
|
|
11009
|
+
for (const pool of [recording.frame, recording.surface]) {
|
|
11010
|
+
for (let i = 0; i < pool.taken; i++) {
|
|
11011
|
+
const offset = (pool.commands[i] as DrawCommand).offsetC;
|
|
11012
|
+
if (!recording.pins.includes(offset)) {
|
|
11013
|
+
store.pin(offset);
|
|
11014
|
+
recording.pins.push(offset);
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
}
|
|
11018
|
+
list.pinEpoch = store.epoch;
|
|
11019
|
+
list.uploadDraws();
|
|
11020
|
+
this.staticJustRecorded = true;
|
|
11021
|
+
}
|
|
11022
|
+
|
|
11023
|
+
/** The recording under way, which `recordDraw` and the side passes take their commands into. */
|
|
11024
|
+
private staticRecording: StaticRecording | null = null;
|
|
11025
|
+
/** Whether the replay about to count its material changes recorded them, and so counted them. */
|
|
11026
|
+
private staticJustRecorded = false;
|
|
11027
|
+
|
|
11028
|
+
/** A disposed list drawn, said once: the frame loop does not throw. */
|
|
11029
|
+
private warnStaticDisposed(): void {
|
|
11030
|
+
if (this.warnedStaticDisposed) return;
|
|
11031
|
+
this.warnedStaticDisposed = true;
|
|
11032
|
+
console.warn('drawStaticDraws: this list was disposed, and draws nothing.');
|
|
11033
|
+
}
|
|
11034
|
+
private warnedStaticDisposed = false;
|
|
11035
|
+
/** Bumped wherever the lit groups are dropped, which a static list's own groups follow. */
|
|
11036
|
+
private flatGroupEpoch = 0;
|
|
11037
|
+
|
|
10322
11038
|
drawShadowCasters(casters: ShadowCasters): void {
|
|
10323
11039
|
if (this.surface.lost || this.shadowPass === null) return;
|
|
10324
11040
|
casters(this.casterSink);
|
|
@@ -10664,7 +11380,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10664
11380
|
}
|
|
10665
11381
|
this.shadowDraws.flush();
|
|
10666
11382
|
this.scatterDepthDraws.flush();
|
|
10667
|
-
this.
|
|
11383
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
10668
11384
|
/* A growth's old texture, copied out of by what was just submitted and needed no longer. */
|
|
10669
11385
|
this.retiredSunTexture?.destroy();
|
|
10670
11386
|
this.retiredSunTexture = null;
|
|
@@ -10954,7 +11670,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10954
11670
|
* its own slot.
|
|
10955
11671
|
*/
|
|
10956
11672
|
this.pointShadowArray?.flush();
|
|
10957
|
-
this.
|
|
11673
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
10958
11674
|
this.shadowEncoder = null;
|
|
10959
11675
|
this.shadowPass = null;
|
|
10960
11676
|
this.gpuTimer.end();
|
|
@@ -11161,7 +11877,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11161
11877
|
this.bindGroupLayout,
|
|
11162
11878
|
this.variant,
|
|
11163
11879
|
this.perDraw.buffer,
|
|
11164
|
-
this.
|
|
11880
|
+
this.passRing.buffer,
|
|
11881
|
+
this.materialStore.buffer,
|
|
11882
|
+
this.viewRing.buffer,
|
|
11165
11883
|
this.flatTextures,
|
|
11166
11884
|
);
|
|
11167
11885
|
}
|
|
@@ -11206,11 +11924,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11206
11924
|
/**
|
|
11207
11925
|
* The bind group a draw uses, skinned or not.
|
|
11208
11926
|
*
|
|
11209
|
-
* **The skinned twin is
|
|
11210
|
-
*
|
|
11211
|
-
*
|
|
11212
|
-
*
|
|
11213
|
-
* missed invalidation is not possible rather than merely unlikely, and it is why
|
|
11927
|
+
* **The skinned twin is kept by palette slot and by the unskinned group it was built beside**,
|
|
11928
|
+
* which is one a material, and compared against the views it holds — the palette's, which
|
|
11929
|
+
* changes only when the slot's texture grows, the morph deltas and the cloth's — rather than
|
|
11930
|
+
* against a flag somebody has to remember to clear at six call sites. Comparing object identity
|
|
11931
|
+
* means a missed invalidation is not possible rather than merely unlikely, and it is why
|
|
11214
11932
|
* `SkinPaletteTexture.view()` caches: a fresh view every call never compares equal and would
|
|
11215
11933
|
* rebuild this every draw.
|
|
11216
11934
|
*/
|
|
@@ -11227,35 +11945,45 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11227
11945
|
const at = skinned ? this.skinPaletteSlot + 1 : 0;
|
|
11228
11946
|
/* A skinned group carries the cloth's views, or the stand-in's: see `clothViews`. */
|
|
11229
11947
|
const cloth = skinned ? this.clothViews() : null;
|
|
11948
|
+
let byMaterial = this.skinnedGroups[at];
|
|
11949
|
+
if (byMaterial === undefined) {
|
|
11950
|
+
byMaterial = new Map();
|
|
11951
|
+
this.skinnedGroups[at] = byMaterial;
|
|
11952
|
+
}
|
|
11953
|
+
let twin = byMaterial.get(base);
|
|
11230
11954
|
if (
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
this.skinnedGroupsClothBinding[at] !== (cloth?.binding ?? null)
|
|
11955
|
+
twin === undefined ||
|
|
11956
|
+
twin.palette !== palette ||
|
|
11957
|
+
twin.deltas !== deltas ||
|
|
11958
|
+
twin.cloth !== (cloth?.particles ?? null) ||
|
|
11959
|
+
twin.clothBinding !== (cloth?.binding ?? null)
|
|
11237
11960
|
) {
|
|
11238
11961
|
/* Built from the live material, with the overlay's atlas where `base` is its twin. */
|
|
11239
11962
|
this.overlayBinding = base !== this.bindGroup;
|
|
11240
|
-
|
|
11963
|
+
const group = createFlatBindGroup(
|
|
11241
11964
|
this.surface.device,
|
|
11242
11965
|
this.flatLayoutFor(skinned, morphed),
|
|
11243
11966
|
this.variant,
|
|
11244
11967
|
this.perDraw.buffer,
|
|
11245
|
-
this.
|
|
11968
|
+
this.passRing.buffer,
|
|
11969
|
+
this.materialStore.buffer,
|
|
11970
|
+
this.viewRing.buffer,
|
|
11246
11971
|
this.flatTextures,
|
|
11247
11972
|
palette,
|
|
11248
11973
|
deltas,
|
|
11249
11974
|
cloth,
|
|
11250
11975
|
);
|
|
11251
11976
|
this.overlayBinding = false;
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11977
|
+
twin = {
|
|
11978
|
+
group,
|
|
11979
|
+
palette,
|
|
11980
|
+
deltas,
|
|
11981
|
+
cloth: cloth?.particles ?? null,
|
|
11982
|
+
clothBinding: cloth?.binding ?? null,
|
|
11983
|
+
};
|
|
11984
|
+
byMaterial.set(base, twin);
|
|
11257
11985
|
}
|
|
11258
|
-
return
|
|
11986
|
+
return twin.group;
|
|
11259
11987
|
}
|
|
11260
11988
|
|
|
11261
11989
|
/** The layout matching a pair of vertex flags. A pipeline and its bind group must agree. */
|
|
@@ -11293,8 +12021,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11293
12021
|
if (this.drawBudget.dropped > 0) {
|
|
11294
12022
|
grew = this.perDraw.growTo(roomFor(this.drawBudget.used)) || grew;
|
|
11295
12023
|
}
|
|
11296
|
-
if (this.
|
|
11297
|
-
grew = this.
|
|
12024
|
+
if (this.materialsRefused > 0) {
|
|
12025
|
+
grew = this.materialStore.growTo(roomFor(this.materialBudget.used)) || grew;
|
|
12026
|
+
}
|
|
12027
|
+
if (this.passesRefused > 0) {
|
|
12028
|
+
grew = this.passRing.growTo(roomFor(this.passesAsked)) || grew;
|
|
12029
|
+
}
|
|
12030
|
+
if (this.viewsRefused > 0) {
|
|
12031
|
+
grew = this.viewRing.growTo(roomFor(this.viewsAsked)) || grew;
|
|
11298
12032
|
}
|
|
11299
12033
|
if (grew) this.rebuildFlatGroupsForNewRings();
|
|
11300
12034
|
}
|
|
@@ -11315,9 +12049,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11315
12049
|
*/
|
|
11316
12050
|
private rebuildFlatGroupsForNewRings(): void {
|
|
11317
12051
|
this.flatBindGroups.clear();
|
|
12052
|
+
this.flatGroupEpoch += 1;
|
|
11318
12053
|
this.overlayGroups.clear();
|
|
11319
|
-
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
11320
12054
|
this.skinnedGroups.length = 0;
|
|
12055
|
+
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
11321
12056
|
|
|
11322
12057
|
const albedo = this.albedo;
|
|
11323
12058
|
const normalMap = this.normalMap;
|
|
@@ -11345,6 +12080,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11345
12080
|
);
|
|
11346
12081
|
/* The open material lives in a slot of a buffer that no longer exists. */
|
|
11347
12082
|
this.materials.dirty();
|
|
12083
|
+
this.reopenPass();
|
|
11348
12084
|
}
|
|
11349
12085
|
|
|
11350
12086
|
/**
|
|
@@ -11379,7 +12115,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11379
12115
|
*/
|
|
11380
12116
|
private rebuildFlatBindGroup(): void {
|
|
11381
12117
|
this.flatBindGroups.clear();
|
|
12118
|
+
this.flatGroupEpoch += 1;
|
|
11382
12119
|
this.overlayGroups.clear();
|
|
12120
|
+
this.skinnedGroups.length = 0;
|
|
11383
12121
|
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
11384
12122
|
if (
|
|
11385
12123
|
this.albedo === null &&
|
|
@@ -13618,17 +14356,20 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13618
14356
|
instanceBuffer: GPUBuffer | null,
|
|
13619
14357
|
instances: number,
|
|
13620
14358
|
indirect: GPUBuffer | null,
|
|
14359
|
+
/** A batch playing a bone animation, whose surface half moves as its draw does. */
|
|
14360
|
+
animated: GpuInstancedBatch | null = null,
|
|
13621
14361
|
): void {
|
|
13622
14362
|
const surface = this.reflectionSurface;
|
|
13623
14363
|
const present = this.meshPresent.get(base);
|
|
13624
14364
|
if (surface === null || present === undefined) return;
|
|
14365
|
+
const animation = animated === null ? null : this.animationViews(animated);
|
|
13625
14366
|
const key = `${frameKey}|rs`;
|
|
13626
14367
|
const pipeline =
|
|
13627
14368
|
this.pipelines.peek(key) ??
|
|
13628
14369
|
flatPipeline(
|
|
13629
14370
|
this.pipelines,
|
|
13630
14371
|
this.surface.device,
|
|
13631
|
-
this.flatLayoutFor(skinned, morphed),
|
|
14372
|
+
animation === null ? this.flatLayoutFor(skinned, morphed) : this.animatedBindGroupLayout,
|
|
13632
14373
|
this.variant,
|
|
13633
14374
|
key,
|
|
13634
14375
|
present,
|
|
@@ -13646,13 +14387,21 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13646
14387
|
modelKind,
|
|
13647
14388
|
'whole',
|
|
13648
14389
|
true,
|
|
14390
|
+
animation !== null,
|
|
13649
14391
|
);
|
|
13650
|
-
|
|
14392
|
+
/* A static list's surface halves go into its own pool, beside its draws. */
|
|
14393
|
+
const command =
|
|
14394
|
+
this.staticRecording === null ? surface.take() : takeRecorded(this.staticRecording.surface);
|
|
13651
14395
|
command.pipeline = pipeline;
|
|
13652
|
-
command.bindGroup =
|
|
14396
|
+
command.bindGroup =
|
|
14397
|
+
animated === null || animation === null
|
|
14398
|
+
? this.flatBindGroupFor(skinned, morphed, deltas)
|
|
14399
|
+
: this.animatedGroup(animated, animation);
|
|
13653
14400
|
command.offsetA = slot;
|
|
13654
|
-
command.offsetB =
|
|
13655
|
-
command.
|
|
14401
|
+
command.offsetB = this.passOffset;
|
|
14402
|
+
command.offsetC = material;
|
|
14403
|
+
command.offsetD = this.drawViewOffset;
|
|
14404
|
+
command.offsetCount = 4;
|
|
13656
14405
|
let index = 0;
|
|
13657
14406
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
13658
14407
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
@@ -13685,6 +14434,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13685
14434
|
slot: number,
|
|
13686
14435
|
material: number,
|
|
13687
14436
|
): void {
|
|
14437
|
+
/* A skin's halves are not bundled: a static list holding one replays draw by draw. */
|
|
14438
|
+
if (this.staticRecording !== null) {
|
|
14439
|
+
this.staticRecording.immediate = true;
|
|
14440
|
+
return;
|
|
14441
|
+
}
|
|
13688
14442
|
const skin = this.skinScatter;
|
|
13689
14443
|
const pipelines = this.skinPipelines;
|
|
13690
14444
|
const present = this.meshPresent.get(base);
|
|
@@ -13720,8 +14474,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13720
14474
|
command.pipeline = pipeline;
|
|
13721
14475
|
command.bindGroup = bindGroup;
|
|
13722
14476
|
command.offsetA = slot;
|
|
13723
|
-
command.offsetB =
|
|
13724
|
-
command.
|
|
14477
|
+
command.offsetB = this.passOffset;
|
|
14478
|
+
command.offsetC = material;
|
|
14479
|
+
command.offsetD = this.drawViewOffset;
|
|
14480
|
+
command.offsetCount = 4;
|
|
13725
14481
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
13726
14482
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
13727
14483
|
}
|
|
@@ -14167,6 +14923,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14167
14923
|
* site reads as one `if` either way.
|
|
14168
14924
|
*/
|
|
14169
14925
|
private recordDraw(reads: number, writes: number): DrawCommand | null {
|
|
14926
|
+
/* A static list recording takes its draws into its own pool, never the frame graph's. */
|
|
14927
|
+
if (this.staticRecording !== null) return takeRecorded(this.staticRecording.frame);
|
|
14170
14928
|
/* The order-independent replay owns its pass and issues directly into it; a recorded command
|
|
14171
14929
|
would be replayed into the frame's passes instead, which is not where these belong. */
|
|
14172
14930
|
if (this.oitEncoder !== null) return null;
|
|
@@ -14209,9 +14967,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14209
14967
|
|
|
14210
14968
|
/** One recorded draw, issued into an open pass. Shared by the graph's replay and `runLate`. */
|
|
14211
14969
|
private issueCommand(pass: GPURenderPassEncoder, command: DrawCommand): void {
|
|
14970
|
+
/* A static list, recorded once: one call, and the pass's bindings are its to set again. */
|
|
14971
|
+
if (command.bundles !== null) {
|
|
14972
|
+
pass.executeBundles(command.bundles);
|
|
14973
|
+
return;
|
|
14974
|
+
}
|
|
14212
14975
|
if (command.pipeline === null || command.bindGroup === null) return;
|
|
14213
14976
|
pass.setPipeline(command.pipeline);
|
|
14214
|
-
if (command.offsetCount ===
|
|
14977
|
+
if (command.offsetCount === 4) {
|
|
14978
|
+
pass.setBindGroup(0, command.bindGroup, [
|
|
14979
|
+
command.offsetA,
|
|
14980
|
+
command.offsetB,
|
|
14981
|
+
command.offsetC,
|
|
14982
|
+
command.offsetD,
|
|
14983
|
+
]);
|
|
14984
|
+
} else if (command.offsetCount === 3) {
|
|
14985
|
+
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB, command.offsetC]);
|
|
14986
|
+
} else if (command.offsetCount === 2) {
|
|
14215
14987
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB]);
|
|
14216
14988
|
} else if (command.offsetCount === 1) {
|
|
14217
14989
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA]);
|
|
@@ -14473,7 +15245,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14473
15245
|
this.flushRings();
|
|
14474
15246
|
this.pass?.end();
|
|
14475
15247
|
this.pass = null;
|
|
14476
|
-
if (!this.surface.lost) this.
|
|
15248
|
+
if (!this.surface.lost) this.submit([this.encoder.finish()]);
|
|
14477
15249
|
this.encoder = null;
|
|
14478
15250
|
}
|
|
14479
15251
|
|
|
@@ -14533,7 +15305,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14533
15305
|
* there would have nothing to ride on. See `GpuTimestamps.resolve`.
|
|
14534
15306
|
*/
|
|
14535
15307
|
this.gpuTimer.resolve(this.encoder);
|
|
14536
|
-
this.
|
|
15308
|
+
this.submit([this.encoder.finish()]);
|
|
14537
15309
|
this.encoder = null;
|
|
14538
15310
|
this.framePresented = true;
|
|
14539
15311
|
/* Latched rather than assigned: `framePresented` goes back to false every `beginFrame`. */
|
|
@@ -15275,7 +16047,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15275
16047
|
definition.dispatch(this.computeContext as ComputeContext);
|
|
15276
16048
|
pass.end();
|
|
15277
16049
|
this.flushRings();
|
|
15278
|
-
|
|
16050
|
+
this.submit([encoder.finish()]);
|
|
15279
16051
|
}
|
|
15280
16052
|
|
|
15281
16053
|
/** Let go of a definition. A handle kept past this dispatches nothing; see `ComputeHandle`. */
|
|
@@ -16434,7 +17206,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16434
17206
|
.end();
|
|
16435
17207
|
}
|
|
16436
17208
|
}
|
|
16437
|
-
|
|
17209
|
+
this.submit([clear.finish()]);
|
|
16438
17210
|
}
|
|
16439
17211
|
|
|
16440
17212
|
/**
|
|
@@ -16665,7 +17437,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16665
17437
|
const whole = endFace === PROBE_FACES.length;
|
|
16666
17438
|
if (whole) this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
16667
17439
|
this.flushRings();
|
|
16668
|
-
|
|
17440
|
+
this.submit([encoder.finish()]);
|
|
16669
17441
|
this.rewindRings(skinMark);
|
|
16670
17442
|
this.encoderEpoch = frameEpoch;
|
|
16671
17443
|
|
|
@@ -16675,6 +17447,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16675
17447
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
16676
17448
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
16677
17449
|
this.materials.dirty();
|
|
17450
|
+
this.reopenPass();
|
|
16678
17451
|
this.probePassActive = false;
|
|
16679
17452
|
this.probeBounce = false;
|
|
16680
17453
|
this.rebindEnvironment();
|
|
@@ -16732,7 +17505,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16732
17505
|
this.uploadProbeFaces(probe, image);
|
|
16733
17506
|
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
16734
17507
|
this.buildProbeChain(encoder, probe, 0);
|
|
16735
|
-
|
|
17508
|
+
this.submit([encoder.finish()]);
|
|
16736
17509
|
|
|
16737
17510
|
this.markProbeFilled(0);
|
|
16738
17511
|
this.probeAmbient = options?.irradiance ?? true;
|
|
@@ -16770,7 +17543,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16770
17543
|
const { device } = this.surface;
|
|
16771
17544
|
const encoder = device.createCommandEncoder({ label: 'probe.layerImage' });
|
|
16772
17545
|
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
16773
|
-
|
|
17546
|
+
this.submit([encoder.finish()]);
|
|
16774
17547
|
this.markProbeFilled(layer);
|
|
16775
17548
|
this.probeAmbient = options?.irradiance ?? true;
|
|
16776
17549
|
this.rebuildFlatBindGroup();
|