@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
|
@@ -61,7 +61,7 @@ import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTexture
|
|
|
61
61
|
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel, skinProfileDistance, } from '../../surfaceModel.js';
|
|
62
62
|
import { OVERLAY_FLOATS, overlayLays, packSurfaceOverlay, } from '../../surfaceOverlay.js';
|
|
63
63
|
import { frameReflectionsRefused, resolveFrameReflections, } from '../../frameReflections.js';
|
|
64
|
-
import { ReflectionSurfacePass } from './reflectionSurfacePass.js';
|
|
64
|
+
import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.js';
|
|
65
65
|
import { validateClothBinding } from '../../clothBindingData.js';
|
|
66
66
|
import { createSdfTextBindGroup, createSdfTextBindGroupLayout, GpuSdfText, sdfTextPipeline, SDF_TEXT_FRAG_FIELDS, SDF_TEXT_FRAG_SIZE, SDF_TEXT_VERT_FIELDS, SDF_TEXT_VERT_SIZE, } from './sdfTextPass.js';
|
|
67
67
|
import { createFilmBindGroup, createFilmBindGroupLayout, filmPipeline, FILM_FRAG_FIELDS, FILM_FRAG_SIZE, FILM_VERT_FIELDS, FILM_VERT_SIZE, } from './filmPass.js';
|
|
@@ -97,10 +97,15 @@ import { VIEW_MODEL_DEPTH_SHARE, viewModelDepthRange } from '../../viewModel.js'
|
|
|
97
97
|
import { GlassCasterList } from '../../glassCasters.js';
|
|
98
98
|
import { GpuGlassTint } from './glassTintPass.js';
|
|
99
99
|
import { SUN_DYNAMIC_LAYER, SUN_GLASS_MOVING_LAYER, SUN_GLASS_STATIC_LAYER, SUN_PEELED_LAYER, SUN_STATIC_LAYER, } from '../../shadowMap.js';
|
|
100
|
-
import { VERTEX_LAYOUT, createGpuMesh, createGpuMeshIncremental } from './buffers.js';
|
|
100
|
+
import { VERTEX_LAYOUT, createGpuMesh, createGpuMeshIncremental, } from './buffers.js';
|
|
101
101
|
import { SkinPaletteRing } from './skinPaletteRing.js';
|
|
102
|
-
import { DEPTH_FORMAT, FLAT_VERT_FIELDS, FLAT_VERT_SIZE, createFlatBindGroup, createFlatBindGroupLayout,
|
|
103
|
-
import {
|
|
102
|
+
import { DEPTH_FORMAT, FLAT_ANIMATED_VERT_FIELDS, FLAT_VERT_FIELDS, FLAT_VERT_SIZE, FLAT_VIEW_FIELDS, FLAT_VIEW_SIZE, createFlatBindGroup, createFlatBindGroupLayout, flatFragmentScratch, flatVariant, flatVertexBindings, flatPipeline, flatPipelineAsync, probeStageCeilings, } from './flatPass.js';
|
|
103
|
+
import { GpuBoneAnimation } from './boneAnimations.js';
|
|
104
|
+
import { packBoneAnimation } from '../../boneAnimation.js';
|
|
105
|
+
import { MaterialStore, hashWords } from './materialStore.js';
|
|
106
|
+
import { GpuStaticDraws, encodeBundle, takeRecorded } from './staticBundles.js';
|
|
107
|
+
import { captureStaticDraws, replayStaticDraws } from '../../staticDraws.js';
|
|
108
|
+
import { DEPTH_FRAG_BINDING, DEPTH_FRAG_SIZE, DEPTH_FRAG_WGSL, DEPTH_PREVIOUS_BINDING, DEPTH_CUTOUT_VERT_FIELDS, DEPTH_CUTOUT_VERT_SIZE, DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS, DEPTH_INSTANCED_ANIMATED_VERT_FIELDS, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS, DEPTH_VERT_FIELDS, DEPTH_VERT_SIZE, SHADOW_FORMAT, createDepthBindGroup, createDepthBindGroupLayout, depthPipeline, } from './depthPass.js';
|
|
104
109
|
import { PipelineCache } from './pipelineCache.js';
|
|
105
110
|
import { SKIN_TARGET_FORMAT, SkinScatterPass } from './skinScatterPass.js';
|
|
106
111
|
import { SKY_FIELDS, SKY_UNIFORM_SIZE, createSkyBindGroup, createSkyBindGroupLayout, skyPipeline, } from './skyPass.js';
|
|
@@ -359,6 +364,13 @@ const MAX_WATER_BODIES_PER_FRAME = 16;
|
|
|
359
364
|
* numbers, because that is precisely the defect the ring exists to end.
|
|
360
365
|
*/
|
|
361
366
|
const MAX_BATCHES_PER_FRAME = 8;
|
|
367
|
+
/**
|
|
368
|
+
* Mesh passes a frame may open before the ring grows: the frame's own, a mirror's, a capture's, and
|
|
369
|
+
* a probe face's, which gives its slot back once submitted. A frame past it loses the draws of the
|
|
370
|
+
* passes that found no slot, counted on the `materials` line, and the ring is grown for the next.
|
|
371
|
+
* Each slot is the pass's block, 3,328 to 6,656 bytes by variant, so the ring is 213 to 426 KB.
|
|
372
|
+
*/
|
|
373
|
+
const PASSES_PER_FRAME = 64;
|
|
362
374
|
/** Buffer usage flags, from the specification. See `buffers.ts` for why they are not read off the global. */
|
|
363
375
|
const USAGE_UNIFORM_DST = 0x0040 | 0x0008;
|
|
364
376
|
/**
|
|
@@ -859,6 +871,8 @@ export class WebGPURenderer {
|
|
|
859
871
|
skinnedBindGroupLayout;
|
|
860
872
|
morphedBindGroupLayout;
|
|
861
873
|
bothBindGroupLayout;
|
|
874
|
+
/** The instanced layout of a batch playing a bone animation. See `animatedGroup`. */
|
|
875
|
+
animatedBindGroupLayout;
|
|
862
876
|
/** The morph weights the following draws use, or null for none. */
|
|
863
877
|
morphWeights = null;
|
|
864
878
|
/**
|
|
@@ -871,14 +885,15 @@ export class WebGPURenderer {
|
|
|
871
885
|
* group per character per frame — an allocation in the frame loop, which `AGENTS.md` forbids.
|
|
872
886
|
* Keyed by slot instead, the groups are built once and reused for the life of the renderer.
|
|
873
887
|
*/
|
|
888
|
+
/*
|
|
889
|
+
* **And by material within a slot**, keyed by the unskinned group each was built beside. One
|
|
890
|
+
* group a slot was rebuilt whenever the material beside it changed, so a character drawn in
|
|
891
|
+
* thirty-odd sections, each its own material, built one a section a frame — about ninety a frame
|
|
892
|
+
* for two characters, reported by a consumer. A slot's map holds one entry a material its rigs
|
|
893
|
+
* are drawn in, and is emptied wherever the unskinned groups are, whose identities it is keyed
|
|
894
|
+
* by.
|
|
895
|
+
*/
|
|
874
896
|
skinnedGroups = [];
|
|
875
|
-
/** The unskinned group each was built beside, for identity-based invalidation. */
|
|
876
|
-
skinnedGroupsBeside = [];
|
|
877
|
-
skinnedGroupsPalette = [];
|
|
878
|
-
skinnedGroupsDeltas = [];
|
|
879
|
-
/** The cloth views each skinned twin was built with, compared by identity like the palette. */
|
|
880
|
-
skinnedGroupsCloth = [];
|
|
881
|
-
skinnedGroupsClothBinding = [];
|
|
882
897
|
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
883
898
|
cloth = null;
|
|
884
899
|
clothStandCache = null;
|
|
@@ -946,22 +961,51 @@ export class WebGPURenderer {
|
|
|
946
961
|
/** Every distinct cache above, so a mesh's pipelines are built for each. One entry when aliased. */
|
|
947
962
|
pipelineTargets;
|
|
948
963
|
bindGroupLayout;
|
|
949
|
-
/** One slot per draw, uploaded once a frame. See `UniformRing`. */
|
|
964
|
+
/** One slot per draw, uploaded once a frame. See `UniformRing`. Swapped while a static list records. */
|
|
950
965
|
perDraw;
|
|
951
|
-
/** The lights, fog and camera, settled once per frame by `bindMeshPass`. */
|
|
952
|
-
perFrame;
|
|
953
966
|
/**
|
|
954
|
-
* The
|
|
967
|
+
* The lights, fog and camera, a slot per mesh pass a frame opens: `bindMeshPass` writes them, and
|
|
968
|
+
* the first draw after it takes a slot. A ring rather than one buffer because a frame opens more
|
|
969
|
+
* than one pass — a mirror's, a capture's, a probe face's — and a queue write does not interleave
|
|
970
|
+
* with the draws recorded between them. See `UniformRing`.
|
|
971
|
+
*/
|
|
972
|
+
passRing;
|
|
973
|
+
/** Each material's block, kept across frames for as long as it does not change. */
|
|
974
|
+
materialStore;
|
|
975
|
+
/**
|
|
976
|
+
* Both blocks as the CPU holds them, the pass's then the material's, patched by name and copied
|
|
977
|
+
* into a slot by the next draw after something changed them. See `FlatFragmentScratch`.
|
|
955
978
|
*
|
|
956
979
|
* Authoritative between draws: a setter changes one field here and marks the block dirty, and
|
|
957
|
-
* the next `drawMesh`
|
|
958
|
-
*
|
|
980
|
+
* the next `drawMesh` places it. That keeps a setter free of any knowledge of slots, which is
|
|
981
|
+
* what lets it be called anywhere a scene wants.
|
|
959
982
|
*/
|
|
960
983
|
perFrameStaging;
|
|
961
984
|
perFrameFloats;
|
|
962
985
|
perFrameInts;
|
|
986
|
+
/** The two halves of the scratch, as the words a slot is written from. */
|
|
987
|
+
passInts;
|
|
988
|
+
materialInts;
|
|
963
989
|
/** The slot the open material occupies, and whether the next draw must take a new one. */
|
|
964
990
|
materials = new MaterialChanges();
|
|
991
|
+
/** The same for the pass's block: reopened by `bindMeshPass` and by nothing a material sets. */
|
|
992
|
+
passBlock = new MaterialChanges();
|
|
993
|
+
/**
|
|
994
|
+
* The vertex stage's view block — the camera, the light's matrix, the wind — a slot a pass, and a
|
|
995
|
+
* second, unjittered, for a blended draw landing after the upscale. Reopened with the pass, and by
|
|
996
|
+
* `setWind` and `beginShadowPass`, which change what it holds.
|
|
997
|
+
*/
|
|
998
|
+
viewRing;
|
|
999
|
+
viewBlock = new MaterialChanges();
|
|
1000
|
+
lateViewBlock = new MaterialChanges();
|
|
1001
|
+
/** The view slot the draw being submitted bound, read by the halves it keeps for later passes. */
|
|
1002
|
+
drawViewOffset = 0;
|
|
1003
|
+
/** Pass slots asked for and refused this frame, and materials refused: what grows the two. */
|
|
1004
|
+
passesAsked = 0;
|
|
1005
|
+
passesRefused = 0;
|
|
1006
|
+
viewsAsked = 0;
|
|
1007
|
+
viewsRefused = 0;
|
|
1008
|
+
materialsRefused = 0;
|
|
965
1009
|
/*
|
|
966
1010
|
* The frame graph's recording, and the machinery that replays it.
|
|
967
1011
|
*
|
|
@@ -1214,7 +1258,7 @@ export class WebGPURenderer {
|
|
|
1214
1258
|
variant;
|
|
1215
1259
|
/** Whether this profile compiled the point-shadow path, and so has sentinels to write. */
|
|
1216
1260
|
pointShadowsCompiled;
|
|
1217
|
-
/** What the generator recorded about that permutation
|
|
1261
|
+
/** What the generator recorded about that permutation, as the renderer's scratch lays it out. */
|
|
1218
1262
|
fragment;
|
|
1219
1263
|
depth = null;
|
|
1220
1264
|
depthView = null;
|
|
@@ -1934,7 +1978,7 @@ export class WebGPURenderer {
|
|
|
1934
1978
|
recordNode(this.arena, VERB_SCOPE, 0, MIRROR_TARGET, 0, 0);
|
|
1935
1979
|
this.pass?.end();
|
|
1936
1980
|
this.flushRings();
|
|
1937
|
-
this.
|
|
1981
|
+
this.submit([this.encoder.finish()]);
|
|
1938
1982
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'reflection' });
|
|
1939
1983
|
/*
|
|
1940
1984
|
* **Opened on demand, like the frame's own.** Nothing has been recorded into the mirror yet,
|
|
@@ -1992,7 +2036,8 @@ export class WebGPURenderer {
|
|
|
1992
2036
|
this.windStreakFrags,
|
|
1993
2037
|
this.scatterVerts,
|
|
1994
2038
|
this.scatterFrags,
|
|
1995
|
-
this.
|
|
2039
|
+
this.passRing,
|
|
2040
|
+
this.viewRing,
|
|
1996
2041
|
this.filmVerts,
|
|
1997
2042
|
this.filmFrags,
|
|
1998
2043
|
this.insetUniforms,
|
|
@@ -2013,6 +2058,7 @@ export class WebGPURenderer {
|
|
|
2013
2058
|
this.instanceCulls?.flush();
|
|
2014
2059
|
for (const ring of this.rings())
|
|
2015
2060
|
ring.flush();
|
|
2061
|
+
this.materialStore.flush();
|
|
2016
2062
|
}
|
|
2017
2063
|
/** Where every ring stands before work that submits an encoder of its own. */
|
|
2018
2064
|
ringMarks = [];
|
|
@@ -2032,6 +2078,8 @@ export class WebGPURenderer {
|
|
|
2032
2078
|
const rings = this.rings();
|
|
2033
2079
|
for (let i = 0; i < rings.length; i++)
|
|
2034
2080
|
rings[i].rewind(this.ringMarks[i] ?? 0);
|
|
2081
|
+
/* The open pass may sit in a slot just given back, which the next pass would overwrite. */
|
|
2082
|
+
this.reopenPass();
|
|
2035
2083
|
this.skinPalettes.rewind(skinMark);
|
|
2036
2084
|
if (this.skinPaletteSlot >= skinMark)
|
|
2037
2085
|
this.skinPaletteSlot = -1;
|
|
@@ -2329,7 +2377,7 @@ export class WebGPURenderer {
|
|
|
2329
2377
|
pass.end();
|
|
2330
2378
|
this.pass = null;
|
|
2331
2379
|
this.flushRings();
|
|
2332
|
-
|
|
2380
|
+
this.submit([encoder.finish()]);
|
|
2333
2381
|
this.rewindRings(skinMark);
|
|
2334
2382
|
this.encoderEpoch = frameEpoch;
|
|
2335
2383
|
this.encoder = savedEncoder;
|
|
@@ -2337,6 +2385,7 @@ export class WebGPURenderer {
|
|
|
2337
2385
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
2338
2386
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
2339
2387
|
this.materials.dirty();
|
|
2388
|
+
this.reopenPass();
|
|
2340
2389
|
this.capturePassActive = false;
|
|
2341
2390
|
}
|
|
2342
2391
|
return true;
|
|
@@ -2352,10 +2401,11 @@ export class WebGPURenderer {
|
|
|
2352
2401
|
updateSurfaceTexture(texture, source) {
|
|
2353
2402
|
if (this.surface.lost)
|
|
2354
2403
|
return;
|
|
2355
|
-
const replaced = texture.update(source);
|
|
2404
|
+
const replaced = texture.update(source, this.compressedFormats);
|
|
2356
2405
|
if (replaced === null)
|
|
2357
2406
|
return;
|
|
2358
|
-
/* A new size is a new view, so every group holding the old one is dropped — see
|
|
2407
|
+
/* A new size or format is a new view, so every group holding the old one is dropped — see
|
|
2408
|
+
below. */
|
|
2359
2409
|
this.retiredTextures.push(replaced);
|
|
2360
2410
|
this.forgetBindingsOf(texture);
|
|
2361
2411
|
}
|
|
@@ -2411,7 +2461,9 @@ export class WebGPURenderer {
|
|
|
2411
2461
|
/* Same reasoning, for whichever SDF labels were bound against this atlas. */
|
|
2412
2462
|
this.sdfTextBindGroups.delete(texture);
|
|
2413
2463
|
this.flatBindGroups.clear();
|
|
2464
|
+
this.flatGroupEpoch += 1;
|
|
2414
2465
|
this.overlayGroups.clear();
|
|
2466
|
+
this.skinnedGroups.length = 0;
|
|
2415
2467
|
/*
|
|
2416
2468
|
* The blank one is built with the albedo held aside, because `buildFlatBindGroup` reads
|
|
2417
2469
|
* `this.albedo` for that slot — leaving it in place would put a real colour map into the
|
|
@@ -2573,6 +2625,13 @@ export class WebGPURenderer {
|
|
|
2573
2625
|
const emissiveMap = (material?.emissive ?? null);
|
|
2574
2626
|
const modelMap = (material?.modelMap ?? null);
|
|
2575
2627
|
this.materialModel = material?.model ?? null;
|
|
2628
|
+
const reflectivity = material?.reflectivity;
|
|
2629
|
+
const environmentGain = material?.environmentGain;
|
|
2630
|
+
this.materialReflectivity =
|
|
2631
|
+
reflectivity === undefined ? null : Math.min(1, Math.max(0, reflectivity));
|
|
2632
|
+
this.materialEnvironmentGain =
|
|
2633
|
+
environmentGain === undefined ? null : Math.max(0, environmentGain);
|
|
2634
|
+
this.writeEnvironmentDials();
|
|
2576
2635
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
2577
2636
|
const model = material?.model;
|
|
2578
2637
|
warnUnpagedLightmap(model, modelMap);
|
|
@@ -2592,7 +2651,8 @@ export class WebGPURenderer {
|
|
|
2592
2651
|
const cut = this.materialField('uCutout');
|
|
2593
2652
|
f[cut] = cutoff;
|
|
2594
2653
|
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
2595
|
-
|
|
2654
|
+
/* z unused: the dither's frame is the pass's, in `uHighlightMin.w`. See `uCutout`. */
|
|
2655
|
+
f[cut + 2] = 0;
|
|
2596
2656
|
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
2597
2657
|
f[cut + 3] = lightChannelsOf(material?.lightChannels);
|
|
2598
2658
|
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
@@ -2764,14 +2824,28 @@ export class WebGPURenderer {
|
|
|
2764
2824
|
}
|
|
2765
2825
|
/** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
|
|
2766
2826
|
setSurfaceReflectivity(amount) {
|
|
2767
|
-
this.
|
|
2768
|
-
|
|
2769
|
-
});
|
|
2827
|
+
this.passReflectivity = Math.min(1, Math.max(0, amount));
|
|
2828
|
+
this.writeEnvironmentDials();
|
|
2770
2829
|
}
|
|
2771
2830
|
/** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
|
|
2772
2831
|
setEnvironmentGain(gain) {
|
|
2832
|
+
this.passEnvironmentGain = Math.max(0, gain);
|
|
2833
|
+
this.writeEnvironmentDials();
|
|
2834
|
+
}
|
|
2835
|
+
/**
|
|
2836
|
+
* The reflectivity and environment gain the setters gave the pass, and the ones the material
|
|
2837
|
+
* set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
|
|
2838
|
+
* material's where it has one and the pass's otherwise, the rule `renderer.ts` keeps too.
|
|
2839
|
+
*/
|
|
2840
|
+
passReflectivity = 0;
|
|
2841
|
+
passEnvironmentGain = 1;
|
|
2842
|
+
materialReflectivity = null;
|
|
2843
|
+
materialEnvironmentGain = null;
|
|
2844
|
+
writeEnvironmentDials() {
|
|
2773
2845
|
this.material((f) => {
|
|
2774
|
-
|
|
2846
|
+
const dials = this.materialField('uEnvironmentDials');
|
|
2847
|
+
f[dials + 1] = this.materialReflectivity ?? this.passReflectivity;
|
|
2848
|
+
f[dials + 2] = this.materialEnvironmentGain ?? this.passEnvironmentGain;
|
|
2775
2849
|
});
|
|
2776
2850
|
}
|
|
2777
2851
|
setSurfaceGrain(amount) {
|
|
@@ -3242,7 +3316,7 @@ export class WebGPURenderer {
|
|
|
3242
3316
|
}
|
|
3243
3317
|
this.pass?.end();
|
|
3244
3318
|
this.flushRings();
|
|
3245
|
-
this.
|
|
3319
|
+
this.submit([this.encoder.finish()]);
|
|
3246
3320
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'frame.resumed' });
|
|
3247
3321
|
this.reflectionPassActive = false;
|
|
3248
3322
|
this.reflectionReady = true;
|
|
@@ -4626,6 +4700,7 @@ export class WebGPURenderer {
|
|
|
4626
4700
|
*/
|
|
4627
4701
|
setWind(windX, windZ, windGust, timeSeconds) {
|
|
4628
4702
|
resolveScatterDeform(windX, windZ, windGust, timeSeconds, null, this.frameWind);
|
|
4703
|
+
this.reopenViews();
|
|
4629
4704
|
}
|
|
4630
4705
|
/**
|
|
4631
4706
|
* The five wind fields of one shadow-draw slot.
|
|
@@ -4643,21 +4718,6 @@ export class WebGPURenderer {
|
|
|
4643
4718
|
this.shadowDraws.writeFloat(slot, DEPTH_VERT_FIELDS.uWindTime.offset, d.time);
|
|
4644
4719
|
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
4645
4720
|
}
|
|
4646
|
-
/**
|
|
4647
|
-
* The five wind fields of one per-draw slot.
|
|
4648
|
-
*
|
|
4649
|
-
* **Never called for an instanced draw.** That variant declares none of these, and
|
|
4650
|
-
* `flatPass.ts` records what writing a field a variant does not declare costs: the block is
|
|
4651
|
-
* shorter, so the write lands on whatever occupies that offset instead.
|
|
4652
|
-
*/
|
|
4653
|
-
writeWind(slot) {
|
|
4654
|
-
const d = this.frameWind;
|
|
4655
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uWindDirection.offset, d.direction);
|
|
4656
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindSpeed.offset, d.bend);
|
|
4657
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindGust.offset, d.gust);
|
|
4658
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindTime.offset, d.time);
|
|
4659
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
4660
|
-
}
|
|
4661
4721
|
/** Refilled per frame rather than allocated; see `resolvePointLights`. */
|
|
4662
4722
|
lights = {
|
|
4663
4723
|
count: 0,
|
|
@@ -4807,6 +4867,9 @@ export class WebGPURenderer {
|
|
|
4807
4867
|
* at submit, and a texture's view is new when an update changes its size.
|
|
4808
4868
|
*/
|
|
4809
4869
|
shadowCutoutLayout;
|
|
4870
|
+
/** A crowd's casters: the plain and cutout layouts with a bone animation's three textures. */
|
|
4871
|
+
shadowAnimatedLayout;
|
|
4872
|
+
shadowAnimatedCutoutLayout;
|
|
4810
4873
|
cutoutGroups = new Map();
|
|
4811
4874
|
cutoutPeelGroups = new Map();
|
|
4812
4875
|
shadowPeelPassUniforms = null;
|
|
@@ -5019,19 +5082,47 @@ export class WebGPURenderer {
|
|
|
5019
5082
|
if (count === 0)
|
|
5020
5083
|
return;
|
|
5021
5084
|
const cutout = cutoutOf(material);
|
|
5022
|
-
|
|
5085
|
+
/* A crowd casts where its clip has moved it, through the animated variant. */
|
|
5086
|
+
const animation = gpuBatch.animation === null ? null : this.animationViews(gpuBatch);
|
|
5087
|
+
if (gpuBatch.animation !== null && animation === null)
|
|
5088
|
+
return;
|
|
5089
|
+
const cutoutTexture = cutout === null ? null : cutout.albedo;
|
|
5090
|
+
const cutoutGroup = animation !== null
|
|
5091
|
+
? this.animatedDepthGroup(gpuBatch, animation, cutoutTexture)
|
|
5092
|
+
: cutoutTexture === null
|
|
5093
|
+
? null
|
|
5094
|
+
: this.cutoutGroup(cutoutTexture);
|
|
5095
|
+
if (animation !== null && cutoutGroup === null)
|
|
5096
|
+
return;
|
|
5023
5097
|
const slot = this.shadowDraws.allocate();
|
|
5024
5098
|
if (slot === null)
|
|
5025
5099
|
return;
|
|
5026
5100
|
/* Only the light matrix: the instanced depth variant has no uModel, its placement being
|
|
5027
5101
|
the four attribute columns the batch's buffer supplies. */
|
|
5028
5102
|
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uLightViewProj.offset, this.correctedLightViewProj);
|
|
5029
|
-
|
|
5103
|
+
const cuts = cutoutTexture !== null && cutoutGroup !== null;
|
|
5104
|
+
if (cutout !== null && cuts) {
|
|
5030
5105
|
this.writeCutout(slot, cutout, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS);
|
|
5031
5106
|
}
|
|
5032
|
-
const
|
|
5107
|
+
const clip = gpuBatch.animation;
|
|
5108
|
+
if (animation !== null && clip !== null) {
|
|
5109
|
+
const fields = cuts
|
|
5110
|
+
? DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS
|
|
5111
|
+
: DEPTH_INSTANCED_ANIMATED_VERT_FIELDS;
|
|
5112
|
+
this.shadowDraws.writeFloat(slot, fields.uSceneTime.offset, this.animationTime);
|
|
5113
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset, clip.framesPerSecond);
|
|
5114
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset + 4, clip.boneScale);
|
|
5115
|
+
}
|
|
5033
5116
|
const cull = material?.doubleSided === true ? 'none' : this.depthCullMode;
|
|
5034
|
-
|
|
5117
|
+
const animated = animation !== null;
|
|
5118
|
+
pass.setPipeline(depthPipeline(this.pipelines, this.surface.device, animated
|
|
5119
|
+
? cuts
|
|
5120
|
+
? this.shadowAnimatedCutoutLayout
|
|
5121
|
+
: this.shadowAnimatedLayout
|
|
5122
|
+
: cuts
|
|
5123
|
+
? this.shadowCutoutLayout
|
|
5124
|
+
: this.shadowLayout, `${cuts ? 'depth-instanced-cutout' : 'depth-instanced'}${animated ? '-animated' : ''}` +
|
|
5125
|
+
`|${cull}|${geometry.key ?? ''}`, this.presentOf(geometry), cull, false, true, cuts, false, false, animated));
|
|
5035
5126
|
pass.setBindGroup(0, cutoutGroup ?? this.shadowGroup(), [slot]);
|
|
5036
5127
|
let index = 0;
|
|
5037
5128
|
for (; index < geometry.vertexBuffers.length; index++) {
|
|
@@ -5472,7 +5563,7 @@ export class WebGPURenderer {
|
|
|
5472
5563
|
pointShadows: quality.pointShadows,
|
|
5473
5564
|
});
|
|
5474
5565
|
this.pointShadowsCompiled = quality.pointShadows;
|
|
5475
|
-
this.fragment =
|
|
5566
|
+
this.fragment = flatFragmentScratch(this.variant);
|
|
5476
5567
|
this.bindGroupLayout = createFlatBindGroupLayout(device, this.variant);
|
|
5477
5568
|
/*
|
|
5478
5569
|
* The skinned twin, built eagerly because it is a descriptor rather than a compile — WebGPU
|
|
@@ -5482,24 +5573,32 @@ export class WebGPURenderer {
|
|
|
5482
5573
|
this.skinnedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true);
|
|
5483
5574
|
this.morphedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, false, true);
|
|
5484
5575
|
this.bothBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true, true);
|
|
5576
|
+
/* And the instanced twin that plays a bone animation: its clip's and clocks' textures. */
|
|
5577
|
+
this.animatedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, false, false, true);
|
|
5485
5578
|
this.perDraw = new UniformRing(device, FLAT_VERT_SIZE, quality.drawsPerFrame, USAGE_UNIFORM_DST, 'flat.vertRing', this.drawBudget);
|
|
5486
|
-
|
|
5579
|
+
const { passSize, materialSize } = this.fragment;
|
|
5580
|
+
this.perFrameStaging = new ArrayBuffer(passSize + materialSize);
|
|
5487
5581
|
this.perFrameFloats = new Float32Array(this.perFrameStaging);
|
|
5488
5582
|
this.perFrameInts = new Int32Array(this.perFrameStaging);
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
*
|
|
5493
|
-
* — a material covers many draws
|
|
5494
|
-
*
|
|
5495
|
-
*
|
|
5496
|
-
*
|
|
5497
|
-
*
|
|
5498
|
-
*
|
|
5499
|
-
*
|
|
5500
|
-
*
|
|
5501
|
-
|
|
5502
|
-
|
|
5583
|
+
this.passInts = new Int32Array(this.perFrameStaging, 0, passSize / 4);
|
|
5584
|
+
this.materialInts = new Int32Array(this.perFrameStaging, passSize, materialSize / 4);
|
|
5585
|
+
/*
|
|
5586
|
+
* **Two blocks, because they change at two rates.** `setSurfaceTexture`, `setSurfaceGrain` and
|
|
5587
|
+
* their siblings are pass state on both backends — a material covers many draws — and on WebGL2
|
|
5588
|
+
* each is a `uniform1f` that lands immediately. Here they live in a block, and
|
|
5589
|
+
* `queue.writeBuffer` does not interleave with recorded commands, so one buffer rewritten between
|
|
5590
|
+
* two draws gives *both* the second value: every surface would wear the last material set, which
|
|
5591
|
+
* is bug 7's shape at pass scale. So each draw binds a slot.
|
|
5592
|
+
*
|
|
5593
|
+
* It was one block until 4.10.0, lights and all, copied whole into a ring slot per material
|
|
5594
|
+
* change and uploaded every frame: a stage of four hundred materials sent 4.8 MB a frame to say
|
|
5595
|
+
* what it had said the frame before. Now the pass's block takes a ring slot per pass, and a
|
|
5596
|
+
* material's block a slot of the store, where an unchanged material stays from frame to frame.
|
|
5597
|
+
*/
|
|
5598
|
+
this.passRing = new UniformRing(device, passSize, PASSES_PER_FRAME, USAGE_UNIFORM_DST, 'flat.passRing');
|
|
5599
|
+
this.materialStore = new MaterialStore(device, materialSize, this.quality.materialChangesPerFrame, USAGE_UNIFORM_DST, 'flat.materials', this.materialBudget);
|
|
5600
|
+
/* Two a pass at most: the jittered view and the unjittered one. */
|
|
5601
|
+
this.viewRing = new UniformRing(device, FLAT_VIEW_SIZE, 2 * PASSES_PER_FRAME, USAGE_UNIFORM_DST, 'flat.viewRing');
|
|
5503
5602
|
/*
|
|
5504
5603
|
* A one-pixel stand-in for the albedo binding. The albedo's flag is zero for untextured
|
|
5505
5604
|
* geometry, which is nearly all of it, so the shader never samples this — but WebGPU
|
|
@@ -5632,7 +5731,7 @@ export class WebGPURenderer {
|
|
|
5632
5731
|
})
|
|
5633
5732
|
.end();
|
|
5634
5733
|
}
|
|
5635
|
-
|
|
5734
|
+
this.submit([clear.finish()]);
|
|
5636
5735
|
/*
|
|
5637
5736
|
* `nearest` on a depth texture sampled as an ordinary float, matching what
|
|
5638
5737
|
* `shadowMap.ts` asks WebGL2 for. A filtering sampler cannot be used on a depth format
|
|
@@ -6120,6 +6219,8 @@ export class WebGPURenderer {
|
|
|
6120
6219
|
this.shadowLayout = createDepthBindGroupLayout(device);
|
|
6121
6220
|
this.shadowSkinnedLayout = createDepthBindGroupLayout(device, true);
|
|
6122
6221
|
this.shadowCutoutLayout = createDepthBindGroupLayout(device, false, true);
|
|
6222
|
+
this.shadowAnimatedLayout = createDepthBindGroupLayout(device, false, false, true);
|
|
6223
|
+
this.shadowAnimatedCutoutLayout = createDepthBindGroupLayout(device, false, true, true);
|
|
6123
6224
|
this.shadowDraws = new UniformRing(device,
|
|
6124
6225
|
/* The cutout block is the plain one and two fields more, and a slot must hold either. */
|
|
6125
6226
|
Math.max(DEPTH_VERT_SIZE, DEPTH_CUTOUT_VERT_SIZE), quality.drawsPerFrame, USAGE_UNIFORM_DST, 'shadow.drawRing', this.shadowDrawBudget);
|
|
@@ -6418,9 +6519,18 @@ export class WebGPURenderer {
|
|
|
6418
6519
|
this.lineVerts.reset();
|
|
6419
6520
|
this.lineFrags.reset();
|
|
6420
6521
|
this.skyUniforms.reset();
|
|
6421
|
-
/* The
|
|
6422
|
-
|
|
6522
|
+
/* The pass ring, and the open slots with it: last frame's pass slots are gone. Its materials
|
|
6523
|
+
stay in the store, which a draw finds them in again. */
|
|
6524
|
+
this.passRing.reset();
|
|
6525
|
+
this.viewRing.reset();
|
|
6526
|
+
this.materialStore.beginFrame();
|
|
6527
|
+
this.passesAsked = 0;
|
|
6528
|
+
this.passesRefused = 0;
|
|
6529
|
+
this.viewsAsked = 0;
|
|
6530
|
+
this.viewsRefused = 0;
|
|
6531
|
+
this.materialsRefused = 0;
|
|
6423
6532
|
this.materials.dirty();
|
|
6533
|
+
this.reopenPass();
|
|
6424
6534
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
6425
6535
|
this.motionPass?.reset();
|
|
6426
6536
|
this.latePass?.reset();
|
|
@@ -6718,6 +6828,37 @@ export class WebGPURenderer {
|
|
|
6718
6828
|
},
|
|
6719
6829
|
};
|
|
6720
6830
|
}
|
|
6831
|
+
/**
|
|
6832
|
+
* The copies this frame's mesh rewrites owe (`StagedRewrite`), recorded since the last submit and
|
|
6833
|
+
* sent at the head of the next one: one encoder however many meshes are rewritten. Null while
|
|
6834
|
+
* nothing is owed.
|
|
6835
|
+
*/
|
|
6836
|
+
pendingRewrites = null;
|
|
6837
|
+
/** One record handed to every staged rewrite, refilled each call, so a rewrite allocates nothing. */
|
|
6838
|
+
stagedRewrite = {
|
|
6839
|
+
encoder: null,
|
|
6840
|
+
staging: null,
|
|
6841
|
+
previous: null,
|
|
6842
|
+
};
|
|
6843
|
+
rewriteCopies() {
|
|
6844
|
+
this.pendingRewrites ??= this.surface.device.createCommandEncoder({
|
|
6845
|
+
label: 'mesh.rewriteCopies',
|
|
6846
|
+
});
|
|
6847
|
+
return this.pendingRewrites;
|
|
6848
|
+
}
|
|
6849
|
+
/**
|
|
6850
|
+
* Submit `buffers`, led by any copies the frame's mesh rewrites owe. **Every submit of this
|
|
6851
|
+
* renderer goes through here**, because any of them may draw a rewritten mesh — the frame's own,
|
|
6852
|
+
* a mirror's, a shadow's, a probe's — and the copies must be ahead of the first that does.
|
|
6853
|
+
*/
|
|
6854
|
+
submit(buffers) {
|
|
6855
|
+
const rewrites = this.pendingRewrites;
|
|
6856
|
+
if (rewrites !== null) {
|
|
6857
|
+
this.pendingRewrites = null;
|
|
6858
|
+
buffers.unshift(rewrites.finish());
|
|
6859
|
+
}
|
|
6860
|
+
this.surface.device.queue.submit(buffers);
|
|
6861
|
+
}
|
|
6721
6862
|
/**
|
|
6722
6863
|
* Rewrite a mesh's positions, and its normals where the caller has them.
|
|
6723
6864
|
*
|
|
@@ -6738,20 +6879,35 @@ export class WebGPURenderer {
|
|
|
6738
6879
|
* `changeFrames.ts`.
|
|
6739
6880
|
*/
|
|
6740
6881
|
const motion = mesh.motion;
|
|
6741
|
-
let
|
|
6882
|
+
let staged = null;
|
|
6742
6883
|
if (motion !== null && this.reconstructionWanted) {
|
|
6743
6884
|
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
6885
|
+
const size = mesh.vertexBuffers[0]?.size ?? 0;
|
|
6886
|
+
let previous = null;
|
|
6744
6887
|
if (keepsPrevious(motion.changed, frame)) {
|
|
6745
6888
|
motion.previous ??= this.surface.device.createBuffer({
|
|
6746
6889
|
label: 'recon.dynamicPrevious',
|
|
6747
|
-
size
|
|
6890
|
+
size,
|
|
6748
6891
|
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
6749
6892
|
});
|
|
6750
6893
|
previous = motion.previous;
|
|
6751
6894
|
}
|
|
6752
6895
|
motion.changed = frame;
|
|
6896
|
+
/*
|
|
6897
|
+
* **Staged, and copied in behind the copy aside, in an encoder the frame's next submit leads
|
|
6898
|
+
* with**: no submit of the rewrite's own. See `StagedRewrite` and `submit`.
|
|
6899
|
+
*/
|
|
6900
|
+
motion.staging ??= this.surface.device.createBuffer({
|
|
6901
|
+
label: 'mesh.stagedPositions',
|
|
6902
|
+
size,
|
|
6903
|
+
usage: 0x0004 | 0x0008, // COPY_SRC | COPY_DST
|
|
6904
|
+
});
|
|
6905
|
+
staged = this.stagedRewrite;
|
|
6906
|
+
staged.encoder = this.rewriteCopies();
|
|
6907
|
+
staged.staging = motion.staging;
|
|
6908
|
+
staged.previous = previous;
|
|
6753
6909
|
}
|
|
6754
|
-
mesh.update(this.surface.device, positions, normals,
|
|
6910
|
+
mesh.update(this.surface.device, positions, normals, staged);
|
|
6755
6911
|
}
|
|
6756
6912
|
/**
|
|
6757
6913
|
* Settle everything the frame shares: the camera, the lights and the fog.
|
|
@@ -6941,6 +7097,11 @@ export class WebGPURenderer {
|
|
|
6941
7097
|
* number, so this is a copy and is marked as one; the two must move together.
|
|
6942
7098
|
*/
|
|
6943
7099
|
f[at('uGrain')] = 1;
|
|
7100
|
+
/* No reflection and an environment at its own brightness, from the pass and from no material. */
|
|
7101
|
+
this.passReflectivity = 0;
|
|
7102
|
+
this.passEnvironmentGain = 1;
|
|
7103
|
+
this.materialReflectivity = null;
|
|
7104
|
+
this.materialEnvironmentGain = null;
|
|
6944
7105
|
f[at('uEnvironmentDials') + 1] = 0;
|
|
6945
7106
|
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
6946
7107
|
f[at('uWriteMode')] = 0;
|
|
@@ -7051,6 +7212,8 @@ export class WebGPURenderer {
|
|
|
7051
7212
|
if (this.quality.nightEmissive)
|
|
7052
7213
|
f[at('uNightEmissive')] = env.nightEmissive ?? 0;
|
|
7053
7214
|
f.set(env.highlightMin, at('uHighlightMin'));
|
|
7215
|
+
/* The frame the cutout dither moves with, in the near corner's w. See `uHighlightMin`. */
|
|
7216
|
+
f[at('uHighlightMin') + 3] = this.cutoutFrame;
|
|
7054
7217
|
f.set(env.highlightMax, at('uHighlightMax'));
|
|
7055
7218
|
/* The gain in the far corner's w. See `uHighlightMax`. */
|
|
7056
7219
|
f[at('uHighlightMax') + 3] = env.highlightGain;
|
|
@@ -7303,6 +7466,7 @@ export class WebGPURenderer {
|
|
|
7303
7466
|
* same terms here, which is why a pass cannot inherit a material from the one before it.
|
|
7304
7467
|
*/
|
|
7305
7468
|
this.materials.dirty();
|
|
7469
|
+
this.reopenPass();
|
|
7306
7470
|
/*
|
|
7307
7471
|
* A pass starts with no material, exactly as `renderer.ts` resets these terms — **every map,
|
|
7308
7472
|
* not the albedo alone**. `setMaterial` skips the lookup when the maps it is given are the ones
|
|
@@ -7326,29 +7490,104 @@ export class WebGPURenderer {
|
|
|
7326
7490
|
this.uvOffset[1] = 0;
|
|
7327
7491
|
}
|
|
7328
7492
|
/**
|
|
7329
|
-
* The slot the next draw's material lives in,
|
|
7493
|
+
* The slot the next draw's material lives in, placing it when something dirtied it, with the
|
|
7494
|
+
* pass's slot taken first where `bindMeshPass` reopened it: `passOffset` is that one's offset.
|
|
7330
7495
|
*
|
|
7331
|
-
* **A
|
|
7332
|
-
* material
|
|
7333
|
-
*
|
|
7496
|
+
* **A placement per material change, not per draw.** A scene drawing a hundred meshes of one
|
|
7497
|
+
* material places it once; a scene switching material every draw places one each, and a material
|
|
7498
|
+
* the store already holds — the same numbers as a draw before it, this frame or an earlier one —
|
|
7499
|
+
* is bound where it is and uploads nothing.
|
|
7334
7500
|
*/
|
|
7335
7501
|
materialSlotForDraw() {
|
|
7502
|
+
if (!this.passBlock.open) {
|
|
7503
|
+
this.passesAsked += 1;
|
|
7504
|
+
const pass = this.passRing.allocate();
|
|
7505
|
+
if (pass === null) {
|
|
7506
|
+
this.passesRefused += 1;
|
|
7507
|
+
this.materialBudget.ask();
|
|
7508
|
+
this.materialBudget.drop();
|
|
7509
|
+
if (!this.warnedPassesFull) {
|
|
7510
|
+
this.warnedPassesFull = true;
|
|
7511
|
+
console.warn(`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.`);
|
|
7512
|
+
}
|
|
7513
|
+
return null;
|
|
7514
|
+
}
|
|
7515
|
+
this.passRing.writeBlock(pass, this.passInts);
|
|
7516
|
+
this.passBlock.slot = pass;
|
|
7517
|
+
}
|
|
7336
7518
|
if (this.materials.open)
|
|
7337
7519
|
return this.materials.slot;
|
|
7338
|
-
const slot = this.
|
|
7520
|
+
const slot = this.materialStore.place(this.materialInts);
|
|
7339
7521
|
if (slot === null) {
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
console.warn(`WebGPU: more than ${this.quality.materialChangesPerFrame} material changes in a frame; the draws asking for the rest are skipped. renderer.frameBudget names the line and the count.`);
|
|
7343
|
-
}
|
|
7522
|
+
this.materialsRefused += 1;
|
|
7523
|
+
this.warnMaterialsFull();
|
|
7344
7524
|
return null;
|
|
7345
7525
|
}
|
|
7346
|
-
this.perFrame.writeBlock(slot, this.perFrameInts);
|
|
7347
7526
|
this.materials.slot = slot;
|
|
7348
7527
|
return slot;
|
|
7349
7528
|
}
|
|
7529
|
+
/** The pass slot `materialSlotForDraw` left open, read beside the material's by every lit draw. */
|
|
7530
|
+
get passOffset() {
|
|
7531
|
+
return this.passBlock.slot;
|
|
7532
|
+
}
|
|
7533
|
+
/** A new pass: its block, and both its views, are taken afresh by the next draw. */
|
|
7534
|
+
reopenPass() {
|
|
7535
|
+
this.passBlock.dirty();
|
|
7536
|
+
this.reopenViews();
|
|
7537
|
+
}
|
|
7538
|
+
/** What a view holds changed — the wind, the light's matrix — so the next draw takes a new one. */
|
|
7539
|
+
reopenViews() {
|
|
7540
|
+
this.viewBlock.dirty();
|
|
7541
|
+
this.lateViewBlock.dirty();
|
|
7542
|
+
}
|
|
7543
|
+
/**
|
|
7544
|
+
* The view slot a lit draw reads its camera from: the pass's, or its unjittered twin for a blended
|
|
7545
|
+
* draw landing after the upscale. Taken by the first draw that needs it after the pass, the wind or
|
|
7546
|
+
* the light's matrix changed, and shared by every draw until then. Null when the ring is full, which
|
|
7547
|
+
* skips the draw and grows the ring for the next frame, as the pass ring does.
|
|
7548
|
+
*/
|
|
7549
|
+
viewSlotFor(late, viewProj) {
|
|
7550
|
+
const block = late ? this.lateViewBlock : this.viewBlock;
|
|
7551
|
+
if (block.open)
|
|
7552
|
+
return block.slot;
|
|
7553
|
+
this.viewsAsked += 1;
|
|
7554
|
+
const slot = this.viewRing.allocate();
|
|
7555
|
+
if (slot === null) {
|
|
7556
|
+
this.viewsRefused += 1;
|
|
7557
|
+
this.materialBudget.ask();
|
|
7558
|
+
this.materialBudget.drop();
|
|
7559
|
+
if (!this.warnedPassesFull) {
|
|
7560
|
+
this.warnedPassesFull = true;
|
|
7561
|
+
console.warn(`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.`);
|
|
7562
|
+
}
|
|
7563
|
+
return null;
|
|
7564
|
+
}
|
|
7565
|
+
this.writeView(this.viewRing, slot, viewProj);
|
|
7566
|
+
block.slot = slot;
|
|
7567
|
+
return slot;
|
|
7568
|
+
}
|
|
7569
|
+
/** A view block: the camera, the light's matrix and the frame's wind, into `ring` at `slot`. */
|
|
7570
|
+
writeView(ring, slot, viewProj) {
|
|
7571
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uViewProj.offset, viewProj);
|
|
7572
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
7573
|
+
/* The scene's clock, which a bone animation's instances read their moment from. */
|
|
7574
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uSceneTime.offset, this.animationTime);
|
|
7575
|
+
const d = this.frameWind;
|
|
7576
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindDirection.offset, d.direction);
|
|
7577
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindSpeed.offset, d.bend);
|
|
7578
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindGust.offset, d.gust);
|
|
7579
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindTime.offset, d.time);
|
|
7580
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
7581
|
+
}
|
|
7582
|
+
warnMaterialsFull() {
|
|
7583
|
+
if (this.warnedMaterialsFull)
|
|
7584
|
+
return;
|
|
7585
|
+
this.warnedMaterialsFull = true;
|
|
7586
|
+
console.warn(`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.`);
|
|
7587
|
+
}
|
|
7350
7588
|
/** Said once rather than every frame, for the reason `warnedFull` gives. */
|
|
7351
7589
|
warnedMaterialsFull = false;
|
|
7590
|
+
warnedPassesFull = false;
|
|
7352
7591
|
warnedSkyFull = false;
|
|
7353
7592
|
warnedParticlesTwice = false;
|
|
7354
7593
|
/**
|
|
@@ -7383,8 +7622,12 @@ export class WebGPURenderer {
|
|
|
7383
7622
|
* What this saves is the GPU's share, which for a heavy mesh is most of the cost and for a
|
|
7384
7623
|
* trivial one is very little.
|
|
7385
7624
|
*/
|
|
7386
|
-
|
|
7625
|
+
/* A static list draws every entry in every view, since a bundle cannot leave one out. */
|
|
7626
|
+
if (this.quality.cullDraws &&
|
|
7627
|
+
this.staticRecording === null &&
|
|
7628
|
+
!this.visible(mesh.bounds, model)) {
|
|
7387
7629
|
return;
|
|
7630
|
+
}
|
|
7388
7631
|
/*
|
|
7389
7632
|
* And the other half of the same question, where the profile asked for one. Gated on
|
|
7390
7633
|
* `cullDraws` alongside the frustum test, because both are the renderer doing a consumer's
|
|
@@ -7549,17 +7792,17 @@ export class WebGPURenderer {
|
|
|
7549
7792
|
`TranslucentMeshOptions.reconstructed`. */
|
|
7550
7793
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
7551
7794
|
const late = lands || (blend && this.oitReplaying && this.reconstructing);
|
|
7552
|
-
|
|
7795
|
+
/* The camera, the light's matrix and the wind are the pass's, in its view block. */
|
|
7796
|
+
const view = this.viewSlotFor(late, late ? this.correctedViewProj : this.viewProj);
|
|
7797
|
+
if (view === null)
|
|
7798
|
+
return;
|
|
7799
|
+
this.drawViewOffset = view;
|
|
7553
7800
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
7554
7801
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
7555
|
-
/* The frame's wind, for a mesh carrying a sway lane. Written on this path and not the
|
|
7556
|
-
instanced one, which declares none of these fields. */
|
|
7557
|
-
this.writeWind(slot);
|
|
7558
7802
|
/* The material's repeats, not a constant: geometry authored in metres is textured at
|
|
7559
7803
|
whatever density the material asks for without rebuilding it. */
|
|
7560
7804
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
7561
7805
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
7562
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
7563
7806
|
/*
|
|
7564
7807
|
* Whether this mesh's tangent frame is real. WebGPU has no disabled attribute, so location 10
|
|
7565
7808
|
* is always backed — by the caller's data or by the constant buffer — which makes the binding
|
|
@@ -7734,8 +7977,10 @@ export class WebGPURenderer {
|
|
|
7734
7977
|
command.pipeline = pipeline;
|
|
7735
7978
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
7736
7979
|
command.offsetA = slot;
|
|
7737
|
-
command.offsetB =
|
|
7738
|
-
command.
|
|
7980
|
+
command.offsetB = this.passOffset;
|
|
7981
|
+
command.offsetC = material;
|
|
7982
|
+
command.offsetD = this.drawViewOffset;
|
|
7983
|
+
command.offsetCount = 4;
|
|
7739
7984
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
7740
7985
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
7741
7986
|
}
|
|
@@ -7749,7 +7994,12 @@ export class WebGPURenderer {
|
|
|
7749
7994
|
if (pass === null)
|
|
7750
7995
|
return;
|
|
7751
7996
|
pass.setPipeline(pipeline);
|
|
7752
|
-
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
7997
|
+
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
7998
|
+
slot,
|
|
7999
|
+
this.passOffset,
|
|
8000
|
+
material,
|
|
8001
|
+
this.drawViewOffset,
|
|
8002
|
+
]);
|
|
7753
8003
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
7754
8004
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
|
|
7755
8005
|
}
|
|
@@ -7829,9 +8079,87 @@ export class WebGPURenderer {
|
|
|
7829
8079
|
'an ordinary mesh, or drop the channel — the sway, sky and alpha lanes cannot reach an ' +
|
|
7830
8080
|
'instanced pipeline.');
|
|
7831
8081
|
}
|
|
7832
|
-
const
|
|
8082
|
+
const animation = (options.animation ?? null);
|
|
8083
|
+
/* The bone a vertex follows is its second coordinates' u, which a mesh carries in its grain
|
|
8084
|
+
lane; a mesh with none has nothing to name a bone by. See `boneAnimation.ts`. */
|
|
8085
|
+
if (animation !== null && !this.presentOf(mesh)['grain']) {
|
|
8086
|
+
throw new Error('WebGPU: a bone animation reads each vertex’s bone from the mesh’s second coordinates ' +
|
|
8087
|
+
'(`MeshData.lightmapUvs`), and this mesh has none.');
|
|
8088
|
+
}
|
|
8089
|
+
const batch = new GpuInstancedBatch(this.surface.device, mesh, capacity, `flat.instances:${mesh.key ?? 'mesh'}`, options.cull ?? false, animation);
|
|
7833
8090
|
return batch;
|
|
7834
8091
|
}
|
|
8092
|
+
/** See `Renderer.setAnimationTime`. Every view taken after it reads the new clock. */
|
|
8093
|
+
setAnimationTime(seconds) {
|
|
8094
|
+
if (!Number.isFinite(seconds) || seconds === this.animationTime)
|
|
8095
|
+
return;
|
|
8096
|
+
this.animationTime = seconds;
|
|
8097
|
+
this.reopenViews();
|
|
8098
|
+
}
|
|
8099
|
+
/** The clock `setAnimationTime` set, 0 until it is. */
|
|
8100
|
+
animationTime = 0;
|
|
8101
|
+
/** See `Renderer.createBoneAnimation`. */
|
|
8102
|
+
createBoneAnimation(clip) {
|
|
8103
|
+
return new GpuBoneAnimation(this.surface.device, packBoneAnimation(clip));
|
|
8104
|
+
}
|
|
8105
|
+
/** See `Renderer.disposeBoneAnimation`. A batch still playing it draws nothing after. */
|
|
8106
|
+
disposeBoneAnimation(animation) {
|
|
8107
|
+
const clip = animation;
|
|
8108
|
+
if (!clip.disposed)
|
|
8109
|
+
clip.dispose();
|
|
8110
|
+
}
|
|
8111
|
+
/** An animated batch's clip and clocks, as its groups bind them. */
|
|
8112
|
+
animationViews(batch) {
|
|
8113
|
+
const animation = batch.animation;
|
|
8114
|
+
const clocks = batch.clocks;
|
|
8115
|
+
if (animation === null || clocks === null || animation.disposed)
|
|
8116
|
+
return null;
|
|
8117
|
+
return { places: animation.placesView, turns: animation.turnsView, clocks: clocks.view };
|
|
8118
|
+
}
|
|
8119
|
+
/** An animated batch's groups, dropped wherever the renderer drops its own. */
|
|
8120
|
+
freshAnimatedGroups(batch) {
|
|
8121
|
+
if (batch.groupEpoch === this.flatGroupEpoch)
|
|
8122
|
+
return;
|
|
8123
|
+
batch.litGroups.clear();
|
|
8124
|
+
batch.depthGroups.clear();
|
|
8125
|
+
batch.depthPeelGroups.clear();
|
|
8126
|
+
batch.groupEpoch = this.flatGroupEpoch;
|
|
8127
|
+
}
|
|
8128
|
+
/**
|
|
8129
|
+
* The lit group an animated batch draws with: the material's group, or its overlay twin, again
|
|
8130
|
+
* with the clip's and the clocks' textures — kept on the batch by the group it was built beside,
|
|
8131
|
+
* as a skinned twin is kept by its palette slot.
|
|
8132
|
+
*/
|
|
8133
|
+
animatedGroup(batch, views) {
|
|
8134
|
+
this.freshAnimatedGroups(batch);
|
|
8135
|
+
const base = this.overlayGroup();
|
|
8136
|
+
let group = batch.litGroups.get(base);
|
|
8137
|
+
if (group === undefined) {
|
|
8138
|
+
this.overlayBinding = base !== this.bindGroup;
|
|
8139
|
+
group = createFlatBindGroup(this.surface.device, this.animatedBindGroupLayout, this.variant, this.perDraw.buffer, this.passRing.buffer, this.materialStore.buffer, this.viewRing.buffer, this.flatTextures, null, null, null, views);
|
|
8140
|
+
this.overlayBinding = false;
|
|
8141
|
+
this.bindGroupBudget.ask();
|
|
8142
|
+
batch.litGroups.set(base, group);
|
|
8143
|
+
}
|
|
8144
|
+
return group;
|
|
8145
|
+
}
|
|
8146
|
+
/** An animated caster's shadow group, by what it cuts with and whether this is the peel. */
|
|
8147
|
+
animatedDepthGroup(batch, views, cutout) {
|
|
8148
|
+
this.freshAnimatedGroups(batch);
|
|
8149
|
+
const peel = this.shadowLayerIsPeel && this.shadowPeelPassUniforms !== null;
|
|
8150
|
+
const groups = peel ? batch.depthPeelGroups : batch.depthGroups;
|
|
8151
|
+
const held = groups.get(cutout);
|
|
8152
|
+
if (held !== undefined)
|
|
8153
|
+
return held;
|
|
8154
|
+
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
8155
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
8156
|
+
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
8157
|
+
if (passUniforms === null || previousView === null || previousSampler === null)
|
|
8158
|
+
return null;
|
|
8159
|
+
const group = createDepthBindGroup(this.surface.device, cutout === null ? this.shadowAnimatedLayout : this.shadowAnimatedCutoutLayout, this.shadowDraws.buffer, passUniforms, previousView, previousSampler, null, cutout === null ? null : { view: cutout.view, sampler: cutout.sampler }, null, views);
|
|
8160
|
+
groups.set(cutout, group);
|
|
8161
|
+
return group;
|
|
8162
|
+
}
|
|
7835
8163
|
/** The device instance culls, built on the first culling draw. See `InstanceCullPass`. */
|
|
7836
8164
|
instanceCulls = null;
|
|
7837
8165
|
/** Counts frames, so a batch knows when its cull slots were last taken. */
|
|
@@ -7864,7 +8192,10 @@ export class WebGPURenderer {
|
|
|
7864
8192
|
this.submitInstanced(batch, data, 1, false, {});
|
|
7865
8193
|
/* A batch that moved this frame pairs each slot with last frame's; the batch is the identity. */
|
|
7866
8194
|
const previous = batch.previousBuffer;
|
|
7867
|
-
if (previous !== null &&
|
|
8195
|
+
if (previous !== null &&
|
|
8196
|
+
this.staticRecording === null &&
|
|
8197
|
+
batch.changed === this.moverFrame &&
|
|
8198
|
+
this.tracksMotionNow()) {
|
|
7868
8199
|
const paired = pairedInstances(batch.previousCount, Math.min(data.count, batch.capacity));
|
|
7869
8200
|
if (paired > 0)
|
|
7870
8201
|
this.motionPass?.recordInstanced(batch.mesh, batch.buffer, previous, paired);
|
|
@@ -7905,6 +8236,8 @@ export class WebGPURenderer {
|
|
|
7905
8236
|
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
7906
8237
|
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
7907
8238
|
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
8239
|
+
/* w: this framebuffer's first row is the top, so up the screen is down the rows. */
|
|
8240
|
+
this.perFrameFloats[tintAt + 3] = -1;
|
|
7908
8241
|
const glassTintAt = this.materialField('uGlassTint');
|
|
7909
8242
|
this.perFrameFloats[glassTintAt] = glass.tint[0];
|
|
7910
8243
|
this.perFrameFloats[glassTintAt + 1] = glass.tint[1];
|
|
@@ -7969,10 +8302,17 @@ export class WebGPURenderer {
|
|
|
7969
8302
|
/* A culling batch's camera draw: the whole batch against the view and the occluders on the CPU,
|
|
7970
8303
|
then instance by instance on the device into an indirect draw. Blended draws keep every
|
|
7971
8304
|
instance and their order. See `InstancedOptions.cull`. */
|
|
7972
|
-
const culling = !blend && batch.cull;
|
|
8305
|
+
const culling = !blend && batch.cull && this.staticRecording === null;
|
|
7973
8306
|
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion))
|
|
7974
8307
|
return;
|
|
7975
|
-
|
|
8308
|
+
/* A batch playing a bone animation reads its clocks by instance index, which a compaction
|
|
8309
|
+
would reorder: it is culled whole and never instance by instance. */
|
|
8310
|
+
const animation = batch.animation === null ? null : this.animationViews(batch);
|
|
8311
|
+
if (batch.animation !== null && animation === null)
|
|
8312
|
+
return;
|
|
8313
|
+
const cut = culling && animation === null && cullsInstances(count, mesh.indexCount)
|
|
8314
|
+
? this.takeCullSlot(batch, count)
|
|
8315
|
+
: null;
|
|
7976
8316
|
const instanceBuffer = cut === null ? batch.buffer : cut.instances;
|
|
7977
8317
|
const slot = this.perDraw.allocate();
|
|
7978
8318
|
if (slot === null) {
|
|
@@ -7984,11 +8324,19 @@ export class WebGPURenderer {
|
|
|
7984
8324
|
}
|
|
7985
8325
|
/* Late unless it moves with what it lies on. See `TranslucentMeshOptions.reconstructed`. */
|
|
7986
8326
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
7987
|
-
this.
|
|
8327
|
+
const view = this.viewSlotFor(lands, lands ? this.correctedViewProj : this.viewProj);
|
|
8328
|
+
if (view === null)
|
|
8329
|
+
return;
|
|
8330
|
+
this.drawViewOffset = view;
|
|
7988
8331
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
7989
8332
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
7990
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
7991
8333
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
8334
|
+
/* The clip's speed and bone scale, read by the animated variant's own field. */
|
|
8335
|
+
const clip = batch.animation;
|
|
8336
|
+
if (animation !== null && clip !== null) {
|
|
8337
|
+
this.perDraw.writeFloat(slot, FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset, clip.framesPerSecond);
|
|
8338
|
+
this.perDraw.writeFloat(slot, FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset + 4, clip.boneScale);
|
|
8339
|
+
}
|
|
7992
8340
|
const dimmed = opacity < 1;
|
|
7993
8341
|
if (dimmed) {
|
|
7994
8342
|
this.perFrameFloats[this.materialField('uOpacity')] = opacity;
|
|
@@ -8025,7 +8373,7 @@ export class WebGPURenderer {
|
|
|
8025
8373
|
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
8026
8374
|
plain one would hand a draw the wrong layout. */
|
|
8027
8375
|
const adds = blend && options.additive === true;
|
|
8028
|
-
const key = `${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8376
|
+
const key = `${base}|inst${animation === null ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8029
8377
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8030
8378
|
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
8031
8379
|
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
@@ -8036,7 +8384,7 @@ export class WebGPURenderer {
|
|
|
8036
8384
|
if (present === undefined) {
|
|
8037
8385
|
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
8038
8386
|
}
|
|
8039
|
-
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(false, false), this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided, coverage, false, false, modelKind);
|
|
8387
|
+
pipeline = flatPipeline(pipelines, this.surface.device, animation === null ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout, this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided, coverage, false, false, modelKind, 'whole', false, animation !== null);
|
|
8040
8388
|
}
|
|
8041
8389
|
const material = this.materialSlotForDraw();
|
|
8042
8390
|
if (material !== null) {
|
|
@@ -8045,10 +8393,15 @@ export class WebGPURenderer {
|
|
|
8045
8393
|
: this.recordDraw(0, this.currentTarget());
|
|
8046
8394
|
if (command !== null) {
|
|
8047
8395
|
command.pipeline = pipeline;
|
|
8048
|
-
command.bindGroup =
|
|
8396
|
+
command.bindGroup =
|
|
8397
|
+
animation === null
|
|
8398
|
+
? this.flatBindGroupFor(false, false, null)
|
|
8399
|
+
: this.animatedGroup(batch, animation);
|
|
8049
8400
|
command.offsetA = slot;
|
|
8050
|
-
command.offsetB =
|
|
8051
|
-
command.
|
|
8401
|
+
command.offsetB = this.passOffset;
|
|
8402
|
+
command.offsetC = material;
|
|
8403
|
+
command.offsetD = this.drawViewOffset;
|
|
8404
|
+
command.offsetCount = 4;
|
|
8052
8405
|
let index = 0;
|
|
8053
8406
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
8054
8407
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
@@ -8066,7 +8419,9 @@ export class WebGPURenderer {
|
|
|
8066
8419
|
if (pass === null)
|
|
8067
8420
|
return;
|
|
8068
8421
|
pass.setPipeline(pipeline);
|
|
8069
|
-
pass.setBindGroup(0,
|
|
8422
|
+
pass.setBindGroup(0, animation === null
|
|
8423
|
+
? this.flatBindGroupFor(false, false, null)
|
|
8424
|
+
: this.animatedGroup(batch, animation), [slot, this.passOffset, material, this.drawViewOffset]);
|
|
8070
8425
|
let index = 0;
|
|
8071
8426
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
8072
8427
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
|
|
@@ -8080,7 +8435,7 @@ export class WebGPURenderer {
|
|
|
8080
8435
|
}
|
|
8081
8436
|
/* And its surface half, for the frame's materials' reflections, with the same survivors. */
|
|
8082
8437
|
if (!blend && lit && depthWrite && this.reflectionSurfaceKeeps()) {
|
|
8083
|
-
this.keepReflectionSurface(mesh, key, base, false, false, null, layer, false, false, modelKind, slot, material, instanceBuffer, count, cut === null ? null : cut.args);
|
|
8438
|
+
this.keepReflectionSurface(mesh, key, base, false, false, null, layer, false, false, modelKind, slot, material, instanceBuffer, count, cut === null ? null : cut.args, animation === null ? null : batch);
|
|
8084
8439
|
}
|
|
8085
8440
|
}
|
|
8086
8441
|
/* Back to the defaults, unconditionally and for the reason `drawMesh` gives at length: this
|
|
@@ -8273,6 +8628,7 @@ export class WebGPURenderer {
|
|
|
8273
8628
|
if (view === null)
|
|
8274
8629
|
return false;
|
|
8275
8630
|
this.lightViewProj.set(lightViewProj);
|
|
8631
|
+
this.reopenViews();
|
|
8276
8632
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
8277
8633
|
this.shadowLayerIsPeel = peel;
|
|
8278
8634
|
this.shadowLayerIsDynamic = dynamic;
|
|
@@ -8380,6 +8736,241 @@ export class WebGPURenderer {
|
|
|
8380
8736
|
this.sceneCasterMaterial = undefined;
|
|
8381
8737
|
casters(this.sceneCasterSink);
|
|
8382
8738
|
}
|
|
8739
|
+
/* -- Static draws ---------------------------------------------------------------- */
|
|
8740
|
+
/** See `Renderer.createStaticDraws`. Recorded on its first draw, into each view it is drawn in. */
|
|
8741
|
+
createStaticDraws(casters) {
|
|
8742
|
+
return new GpuStaticDraws(this.surface.device, captureStaticDraws(casters), this.fragment.passSize, FLAT_VIEW_SIZE, FLAT_VERT_SIZE);
|
|
8743
|
+
}
|
|
8744
|
+
/**
|
|
8745
|
+
* See `Renderer.drawStaticDraws`. One bundle executed, after a pass block and a view block are
|
|
8746
|
+
* written for this view; the list is recorded first where it has no recording that is current for
|
|
8747
|
+
* this target and starting state. See `staticBundles.ts`.
|
|
8748
|
+
*/
|
|
8749
|
+
drawStaticDraws(draws) {
|
|
8750
|
+
if (this.surface.lost)
|
|
8751
|
+
return;
|
|
8752
|
+
const list = draws;
|
|
8753
|
+
if (list.disposed) {
|
|
8754
|
+
this.warnStaticDisposed();
|
|
8755
|
+
return;
|
|
8756
|
+
}
|
|
8757
|
+
if (!this.canDraw() || this.viewProj === null)
|
|
8758
|
+
return;
|
|
8759
|
+
/* A list's draws are rigid and wear no overlay: put back to none, as `bindMeshPass` does. */
|
|
8760
|
+
this.setSkinPalette(null);
|
|
8761
|
+
this.setMorphWeights(null);
|
|
8762
|
+
this.setCloth(null);
|
|
8763
|
+
this.setSurfaceOverlay(null);
|
|
8764
|
+
this.setMaterial(null);
|
|
8765
|
+
this.sceneCasterMaterial = undefined;
|
|
8766
|
+
/* A probe face gives its slots back after every face; a list replays there draw by draw. */
|
|
8767
|
+
if (this.probePassActive) {
|
|
8768
|
+
this.replayStaticImmediately(list);
|
|
8769
|
+
return;
|
|
8770
|
+
}
|
|
8771
|
+
/* The copy first: taking one may grow the list's rings, which no recording before can bind. */
|
|
8772
|
+
const copy = list.takeCopy(this.moverFrame);
|
|
8773
|
+
const current = this.staticRecordingFor(list);
|
|
8774
|
+
if (current === null || current.immediate) {
|
|
8775
|
+
this.replayStaticImmediately(list);
|
|
8776
|
+
return;
|
|
8777
|
+
}
|
|
8778
|
+
current.usedIn = this.moverFrame;
|
|
8779
|
+
list.writePass(copy, this.passInts);
|
|
8780
|
+
this.writeView(list.views, list.viewOffset(copy), this.viewProj);
|
|
8781
|
+
list.uploadCopy(copy);
|
|
8782
|
+
const cache = current.cache;
|
|
8783
|
+
let frame = current.frameBundles[copy];
|
|
8784
|
+
if (frame === undefined || frame === null) {
|
|
8785
|
+
frame = encodeBundle(this.surface.device, {
|
|
8786
|
+
colorFormats: [cache.format],
|
|
8787
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
8788
|
+
sampleCount: cache.sampleCount,
|
|
8789
|
+
depthReadOnly: false,
|
|
8790
|
+
}, current.frame, list.passOffset(copy), list.viewOffset(copy), 'static.frame');
|
|
8791
|
+
current.frameBundles[copy] = frame;
|
|
8792
|
+
}
|
|
8793
|
+
const command = this.recordDraw(0, this.currentTarget());
|
|
8794
|
+
if (command !== null) {
|
|
8795
|
+
command.bundles = frame;
|
|
8796
|
+
command.pipeline = null;
|
|
8797
|
+
command.bindGroup = null;
|
|
8798
|
+
}
|
|
8799
|
+
else {
|
|
8800
|
+
this.openPass()?.executeBundles(frame);
|
|
8801
|
+
}
|
|
8802
|
+
const surface = this.reflectionSurface;
|
|
8803
|
+
if (current.surfaceKept && surface !== null && current.surface.taken > 0) {
|
|
8804
|
+
let halves = current.surfaceBundles[copy];
|
|
8805
|
+
if (halves === undefined || halves === null) {
|
|
8806
|
+
halves = encodeBundle(this.surface.device, {
|
|
8807
|
+
colorFormats: [null, REFLECTION_SURFACE_FORMAT, REFLECTION_SURFACE_FORMAT],
|
|
8808
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
8809
|
+
sampleCount: 1,
|
|
8810
|
+
depthReadOnly: true,
|
|
8811
|
+
}, current.surface, list.passOffset(copy), list.viewOffset(copy), 'static.surface');
|
|
8812
|
+
current.surfaceBundles[copy] = halves;
|
|
8813
|
+
}
|
|
8814
|
+
const kept = surface.take();
|
|
8815
|
+
kept.bundles = halves;
|
|
8816
|
+
}
|
|
8817
|
+
/* Counted as the draws would have been, so a frame reports the same on both backends. */
|
|
8818
|
+
for (let draw = 0; draw < current.frame.taken; draw++)
|
|
8819
|
+
this.drawBudget.ask();
|
|
8820
|
+
if (!this.staticJustRecorded) {
|
|
8821
|
+
for (let ask = 0; ask < current.materialAsks; ask++)
|
|
8822
|
+
this.materialBudget.ask();
|
|
8823
|
+
}
|
|
8824
|
+
this.staticJustRecorded = false;
|
|
8825
|
+
}
|
|
8826
|
+
/** A list drawn the way `drawSceneCasters` draws, a draw at a time, for where a bundle cannot be. */
|
|
8827
|
+
replayStaticImmediately(list) {
|
|
8828
|
+
this.staticJustRecorded = false;
|
|
8829
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
8830
|
+
this.setMaterial(null);
|
|
8831
|
+
this.sceneCasterMaterial = undefined;
|
|
8832
|
+
}
|
|
8833
|
+
/** See `Renderer.disposeStaticDraws`. Its pins let go; its meshes and materials are the caller's. */
|
|
8834
|
+
disposeStaticDraws(draws) {
|
|
8835
|
+
const list = draws;
|
|
8836
|
+
if (list.disposed)
|
|
8837
|
+
return;
|
|
8838
|
+
list.dispose(this.unpinFor(list));
|
|
8839
|
+
}
|
|
8840
|
+
/** The recording to replay `list` from here, made now if none is current; null if it cannot be. */
|
|
8841
|
+
staticRecordingFor(list) {
|
|
8842
|
+
/* Geometry still arriving is not drawn yet, so a recording now would leave it out for good. */
|
|
8843
|
+
for (const entry of list.entries) {
|
|
8844
|
+
const mesh = entry.kind === 'mesh' ? entry.mesh : entry.batch.mesh;
|
|
8845
|
+
if (!mesh.complete)
|
|
8846
|
+
return null;
|
|
8847
|
+
}
|
|
8848
|
+
const cache = this.targetPipelines();
|
|
8849
|
+
const start = this.materialInts;
|
|
8850
|
+
const startHash = hashWords(start, start.length);
|
|
8851
|
+
const surfaceKept = this.reflectionSurfaceKeeps();
|
|
8852
|
+
let recording = list.find(cache, start, startHash, this.cutoutResolveStaged, surfaceKept);
|
|
8853
|
+
if (recording !== null &&
|
|
8854
|
+
recording.cacheVersion === cache.version &&
|
|
8855
|
+
recording.groupEpoch === this.flatGroupEpoch &&
|
|
8856
|
+
recording.storeEpoch === this.materialStore.epoch &&
|
|
8857
|
+
recording.ringEpoch === list.ringEpoch) {
|
|
8858
|
+
return recording;
|
|
8859
|
+
}
|
|
8860
|
+
const unpin = this.unpinFor(list);
|
|
8861
|
+
if (recording === null) {
|
|
8862
|
+
recording = list.open(cache, start, startHash, this.cutoutResolveStaged, surfaceKept, unpin);
|
|
8863
|
+
}
|
|
8864
|
+
else {
|
|
8865
|
+
list.clear(recording, recording.storeEpoch === this.materialStore.epoch ? unpin : () => { });
|
|
8866
|
+
}
|
|
8867
|
+
this.recordStatic(list, recording);
|
|
8868
|
+
return recording;
|
|
8869
|
+
}
|
|
8870
|
+
/** How a list's pins are let go: through the store, unless it has forgotten them since. */
|
|
8871
|
+
unpinFor(list) {
|
|
8872
|
+
const epoch = this.materialStore.epoch;
|
|
8873
|
+
return (offset) => {
|
|
8874
|
+
if (this.materialStore.epoch === epoch && list.pinEpoch === epoch) {
|
|
8875
|
+
this.materialStore.unpin(offset);
|
|
8876
|
+
}
|
|
8877
|
+
};
|
|
8878
|
+
}
|
|
8879
|
+
/**
|
|
8880
|
+
* Record `list` into `recording`: its entries through the scene-caster path, with the list's
|
|
8881
|
+
* rings and group cache swapped in for the frame's and the pass and view slots held at the list's
|
|
8882
|
+
* first copy, so every command the path records binds the list's buffers and offsets the bundle
|
|
8883
|
+
* can rewrite per copy. Everything swapped is put back, whatever the path throws.
|
|
8884
|
+
*/
|
|
8885
|
+
recordStatic(list, recording) {
|
|
8886
|
+
if (list.groupEpoch !== this.flatGroupEpoch) {
|
|
8887
|
+
list.groups = new Map();
|
|
8888
|
+
list.blank = null;
|
|
8889
|
+
list.groupEpoch = this.flatGroupEpoch;
|
|
8890
|
+
}
|
|
8891
|
+
list.drawRing.reset();
|
|
8892
|
+
const perDraw = this.perDraw;
|
|
8893
|
+
const passRing = this.passRing;
|
|
8894
|
+
const viewRing = this.viewRing;
|
|
8895
|
+
const groups = this.flatBindGroups;
|
|
8896
|
+
const blank = this.blankAlbedoBindGroup;
|
|
8897
|
+
const bindGroup = this.bindGroup;
|
|
8898
|
+
const passSlot = this.passBlock.slot;
|
|
8899
|
+
const viewSlot = this.viewBlock.slot;
|
|
8900
|
+
const lateSlot = this.lateViewBlock.slot;
|
|
8901
|
+
const asked = this.materialBudget.used;
|
|
8902
|
+
this.perDraw = list.drawRing;
|
|
8903
|
+
this.passRing = list.passes;
|
|
8904
|
+
this.viewRing = list.views;
|
|
8905
|
+
this.flatBindGroups = list.groups;
|
|
8906
|
+
/* The blank group over the list's rings, built as the frame's is: with no maps held. */
|
|
8907
|
+
list.blank ??= this.buildFlatBindGroup();
|
|
8908
|
+
this.blankAlbedoBindGroup = list.blank;
|
|
8909
|
+
this.bindGroup = this.flatGroupForMaps(null, null, null, null, null);
|
|
8910
|
+
this.passBlock.slot = list.passOffset(0);
|
|
8911
|
+
this.viewBlock.slot = list.viewOffset(0);
|
|
8912
|
+
this.lateViewBlock.slot = list.viewOffset(0);
|
|
8913
|
+
this.materials.dirty();
|
|
8914
|
+
this.staticRecording = recording;
|
|
8915
|
+
try {
|
|
8916
|
+
this.sceneCasterMaterial = undefined;
|
|
8917
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
8918
|
+
}
|
|
8919
|
+
finally {
|
|
8920
|
+
this.staticRecording = null;
|
|
8921
|
+
this.perDraw = perDraw;
|
|
8922
|
+
this.passRing = passRing;
|
|
8923
|
+
this.viewRing = viewRing;
|
|
8924
|
+
this.flatBindGroups = groups;
|
|
8925
|
+
this.blankAlbedoBindGroup = blank;
|
|
8926
|
+
this.bindGroup = bindGroup;
|
|
8927
|
+
this.passBlock.slot = passSlot;
|
|
8928
|
+
this.viewBlock.slot = viewSlot;
|
|
8929
|
+
this.lateViewBlock.slot = lateSlot;
|
|
8930
|
+
this.materials.dirty();
|
|
8931
|
+
this.sceneCasterMaterial = undefined;
|
|
8932
|
+
this.setMaterial(null);
|
|
8933
|
+
}
|
|
8934
|
+
recording.materialAsks = this.materialBudget.used - asked;
|
|
8935
|
+
const store = this.materialStore;
|
|
8936
|
+
recording.cacheVersion = recording.cache.version;
|
|
8937
|
+
recording.groupEpoch = this.flatGroupEpoch;
|
|
8938
|
+
recording.storeEpoch = store.epoch;
|
|
8939
|
+
recording.ringEpoch = list.ringEpoch;
|
|
8940
|
+
/* A skin split into halves is replayed draw by draw, so nothing recorded is kept or pinned. */
|
|
8941
|
+
if (recording.immediate) {
|
|
8942
|
+
resetPool(recording.frame);
|
|
8943
|
+
resetPool(recording.surface);
|
|
8944
|
+
return;
|
|
8945
|
+
}
|
|
8946
|
+
/* Every material the commands bind, pinned once, so nothing displaces what a bundle bakes. */
|
|
8947
|
+
for (const pool of [recording.frame, recording.surface]) {
|
|
8948
|
+
for (let i = 0; i < pool.taken; i++) {
|
|
8949
|
+
const offset = pool.commands[i].offsetC;
|
|
8950
|
+
if (!recording.pins.includes(offset)) {
|
|
8951
|
+
store.pin(offset);
|
|
8952
|
+
recording.pins.push(offset);
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
list.pinEpoch = store.epoch;
|
|
8957
|
+
list.uploadDraws();
|
|
8958
|
+
this.staticJustRecorded = true;
|
|
8959
|
+
}
|
|
8960
|
+
/** The recording under way, which `recordDraw` and the side passes take their commands into. */
|
|
8961
|
+
staticRecording = null;
|
|
8962
|
+
/** Whether the replay about to count its material changes recorded them, and so counted them. */
|
|
8963
|
+
staticJustRecorded = false;
|
|
8964
|
+
/** A disposed list drawn, said once: the frame loop does not throw. */
|
|
8965
|
+
warnStaticDisposed() {
|
|
8966
|
+
if (this.warnedStaticDisposed)
|
|
8967
|
+
return;
|
|
8968
|
+
this.warnedStaticDisposed = true;
|
|
8969
|
+
console.warn('drawStaticDraws: this list was disposed, and draws nothing.');
|
|
8970
|
+
}
|
|
8971
|
+
warnedStaticDisposed = false;
|
|
8972
|
+
/** Bumped wherever the lit groups are dropped, which a static list's own groups follow. */
|
|
8973
|
+
flatGroupEpoch = 0;
|
|
8383
8974
|
drawShadowCasters(casters) {
|
|
8384
8975
|
if (this.surface.lost || this.shadowPass === null)
|
|
8385
8976
|
return;
|
|
@@ -8671,7 +9262,7 @@ export class WebGPURenderer {
|
|
|
8671
9262
|
}
|
|
8672
9263
|
this.shadowDraws.flush();
|
|
8673
9264
|
this.scatterDepthDraws.flush();
|
|
8674
|
-
this.
|
|
9265
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
8675
9266
|
/* A growth's old texture, copied out of by what was just submitted and needed no longer. */
|
|
8676
9267
|
this.retiredSunTexture?.destroy();
|
|
8677
9268
|
this.retiredSunTexture = null;
|
|
@@ -8905,7 +9496,7 @@ export class WebGPURenderer {
|
|
|
8905
9496
|
* its own slot.
|
|
8906
9497
|
*/
|
|
8907
9498
|
this.pointShadowArray?.flush();
|
|
8908
|
-
this.
|
|
9499
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
8909
9500
|
this.shadowEncoder = null;
|
|
8910
9501
|
this.shadowPass = null;
|
|
8911
9502
|
this.gpuTimer.end();
|
|
@@ -9072,7 +9663,7 @@ export class WebGPURenderer {
|
|
|
9072
9663
|
tintTarget.resolve(encoder, layer, face, offered);
|
|
9073
9664
|
}
|
|
9074
9665
|
buildFlatBindGroup() {
|
|
9075
|
-
return createFlatBindGroup(this.surface.device, this.bindGroupLayout, this.variant, this.perDraw.buffer, this.
|
|
9666
|
+
return createFlatBindGroup(this.surface.device, this.bindGroupLayout, this.variant, this.perDraw.buffer, this.passRing.buffer, this.materialStore.buffer, this.viewRing.buffer, this.flatTextures);
|
|
9076
9667
|
}
|
|
9077
9668
|
/**
|
|
9078
9669
|
* The open material's flat group, or its twin with the surface overlay's atlas in the refraction
|
|
@@ -9112,11 +9703,11 @@ export class WebGPURenderer {
|
|
|
9112
9703
|
/**
|
|
9113
9704
|
* The bind group a draw uses, skinned or not.
|
|
9114
9705
|
*
|
|
9115
|
-
* **The skinned twin is
|
|
9116
|
-
*
|
|
9117
|
-
*
|
|
9118
|
-
*
|
|
9119
|
-
* missed invalidation is not possible rather than merely unlikely, and it is why
|
|
9706
|
+
* **The skinned twin is kept by palette slot and by the unskinned group it was built beside**,
|
|
9707
|
+
* which is one a material, and compared against the views it holds — the palette's, which
|
|
9708
|
+
* changes only when the slot's texture grows, the morph deltas and the cloth's — rather than
|
|
9709
|
+
* against a flag somebody has to remember to clear at six call sites. Comparing object identity
|
|
9710
|
+
* means a missed invalidation is not possible rather than merely unlikely, and it is why
|
|
9120
9711
|
* `SkinPaletteTexture.view()` caches: a fresh view every call never compares equal and would
|
|
9121
9712
|
* rebuild this every draw.
|
|
9122
9713
|
*/
|
|
@@ -9130,23 +9721,31 @@ export class WebGPURenderer {
|
|
|
9130
9721
|
const at = skinned ? this.skinPaletteSlot + 1 : 0;
|
|
9131
9722
|
/* A skinned group carries the cloth's views, or the stand-in's: see `clothViews`. */
|
|
9132
9723
|
const cloth = skinned ? this.clothViews() : null;
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
this.
|
|
9137
|
-
|
|
9138
|
-
|
|
9724
|
+
let byMaterial = this.skinnedGroups[at];
|
|
9725
|
+
if (byMaterial === undefined) {
|
|
9726
|
+
byMaterial = new Map();
|
|
9727
|
+
this.skinnedGroups[at] = byMaterial;
|
|
9728
|
+
}
|
|
9729
|
+
let twin = byMaterial.get(base);
|
|
9730
|
+
if (twin === undefined ||
|
|
9731
|
+
twin.palette !== palette ||
|
|
9732
|
+
twin.deltas !== deltas ||
|
|
9733
|
+
twin.cloth !== (cloth?.particles ?? null) ||
|
|
9734
|
+
twin.clothBinding !== (cloth?.binding ?? null)) {
|
|
9139
9735
|
/* Built from the live material, with the overlay's atlas where `base` is its twin. */
|
|
9140
9736
|
this.overlayBinding = base !== this.bindGroup;
|
|
9141
|
-
|
|
9737
|
+
const group = createFlatBindGroup(this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, this.perDraw.buffer, this.passRing.buffer, this.materialStore.buffer, this.viewRing.buffer, this.flatTextures, palette, deltas, cloth);
|
|
9142
9738
|
this.overlayBinding = false;
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9739
|
+
twin = {
|
|
9740
|
+
group,
|
|
9741
|
+
palette,
|
|
9742
|
+
deltas,
|
|
9743
|
+
cloth: cloth?.particles ?? null,
|
|
9744
|
+
clothBinding: cloth?.binding ?? null,
|
|
9745
|
+
};
|
|
9746
|
+
byMaterial.set(base, twin);
|
|
9148
9747
|
}
|
|
9149
|
-
return
|
|
9748
|
+
return twin.group;
|
|
9150
9749
|
}
|
|
9151
9750
|
/** The layout matching a pair of vertex flags. A pipeline and its bind group must agree. */
|
|
9152
9751
|
flatLayoutFor(skinned, morphed) {
|
|
@@ -9185,8 +9784,14 @@ export class WebGPURenderer {
|
|
|
9185
9784
|
if (this.drawBudget.dropped > 0) {
|
|
9186
9785
|
grew = this.perDraw.growTo(roomFor(this.drawBudget.used)) || grew;
|
|
9187
9786
|
}
|
|
9188
|
-
if (this.
|
|
9189
|
-
grew = this.
|
|
9787
|
+
if (this.materialsRefused > 0) {
|
|
9788
|
+
grew = this.materialStore.growTo(roomFor(this.materialBudget.used)) || grew;
|
|
9789
|
+
}
|
|
9790
|
+
if (this.passesRefused > 0) {
|
|
9791
|
+
grew = this.passRing.growTo(roomFor(this.passesAsked)) || grew;
|
|
9792
|
+
}
|
|
9793
|
+
if (this.viewsRefused > 0) {
|
|
9794
|
+
grew = this.viewRing.growTo(roomFor(this.viewsAsked)) || grew;
|
|
9190
9795
|
}
|
|
9191
9796
|
if (grew)
|
|
9192
9797
|
this.rebuildFlatGroupsForNewRings();
|
|
@@ -9207,9 +9812,10 @@ export class WebGPURenderer {
|
|
|
9207
9812
|
*/
|
|
9208
9813
|
rebuildFlatGroupsForNewRings() {
|
|
9209
9814
|
this.flatBindGroups.clear();
|
|
9815
|
+
this.flatGroupEpoch += 1;
|
|
9210
9816
|
this.overlayGroups.clear();
|
|
9211
|
-
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
9212
9817
|
this.skinnedGroups.length = 0;
|
|
9818
|
+
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
9213
9819
|
const albedo = this.albedo;
|
|
9214
9820
|
const normalMap = this.normalMap;
|
|
9215
9821
|
const ormMap = this.ormMap;
|
|
@@ -9229,6 +9835,7 @@ export class WebGPURenderer {
|
|
|
9229
9835
|
this.bindGroup = this.flatGroupForMaps(this.albedo, this.normalMap, this.ormMap, this.emissiveMap, this.modelMap);
|
|
9230
9836
|
/* The open material lives in a slot of a buffer that no longer exists. */
|
|
9231
9837
|
this.materials.dirty();
|
|
9838
|
+
this.reopenPass();
|
|
9232
9839
|
}
|
|
9233
9840
|
/**
|
|
9234
9841
|
* The environment view the cached flat groups bind: the grid, or the blank stand-in while the
|
|
@@ -9260,7 +9867,9 @@ export class WebGPURenderer {
|
|
|
9260
9867
|
*/
|
|
9261
9868
|
rebuildFlatBindGroup() {
|
|
9262
9869
|
this.flatBindGroups.clear();
|
|
9870
|
+
this.flatGroupEpoch += 1;
|
|
9263
9871
|
this.overlayGroups.clear();
|
|
9872
|
+
this.skinnedGroups.length = 0;
|
|
9264
9873
|
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
9265
9874
|
if (this.albedo === null &&
|
|
9266
9875
|
this.normalMap === null &&
|
|
@@ -11158,20 +11767,29 @@ export class WebGPURenderer {
|
|
|
11158
11767
|
* `reflectionSurfacePass.ts` holds and tests for the depth its frame draw wrote. Replayed when the
|
|
11159
11768
|
* frame's pass has ended, before the trace reads them. See `shaders/flat/reflectionSurface.ts`.
|
|
11160
11769
|
*/
|
|
11161
|
-
keepReflectionSurface(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, modelKind, slot, material, instanceBuffer, instances, indirect
|
|
11770
|
+
keepReflectionSurface(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, modelKind, slot, material, instanceBuffer, instances, indirect,
|
|
11771
|
+
/** A batch playing a bone animation, whose surface half moves as its draw does. */
|
|
11772
|
+
animated = null) {
|
|
11162
11773
|
const surface = this.reflectionSurface;
|
|
11163
11774
|
const present = this.meshPresent.get(base);
|
|
11164
11775
|
if (surface === null || present === undefined)
|
|
11165
11776
|
return;
|
|
11777
|
+
const animation = animated === null ? null : this.animationViews(animated);
|
|
11166
11778
|
const key = `${frameKey}|rs`;
|
|
11167
11779
|
const pipeline = this.pipelines.peek(key) ??
|
|
11168
|
-
flatPipeline(this.pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, false, skinned, morphed, true, layer, instanceBuffer !== null, 'none', this.materialDoubleSided, false, eight, clothed, modelKind, 'whole', true);
|
|
11169
|
-
|
|
11780
|
+
flatPipeline(this.pipelines, this.surface.device, animation === null ? this.flatLayoutFor(skinned, morphed) : this.animatedBindGroupLayout, this.variant, key, present, false, skinned, morphed, true, layer, instanceBuffer !== null, 'none', this.materialDoubleSided, false, eight, clothed, modelKind, 'whole', true, animation !== null);
|
|
11781
|
+
/* A static list's surface halves go into its own pool, beside its draws. */
|
|
11782
|
+
const command = this.staticRecording === null ? surface.take() : takeRecorded(this.staticRecording.surface);
|
|
11170
11783
|
command.pipeline = pipeline;
|
|
11171
|
-
command.bindGroup =
|
|
11784
|
+
command.bindGroup =
|
|
11785
|
+
animated === null || animation === null
|
|
11786
|
+
? this.flatBindGroupFor(skinned, morphed, deltas)
|
|
11787
|
+
: this.animatedGroup(animated, animation);
|
|
11172
11788
|
command.offsetA = slot;
|
|
11173
|
-
command.offsetB =
|
|
11174
|
-
command.
|
|
11789
|
+
command.offsetB = this.passOffset;
|
|
11790
|
+
command.offsetC = material;
|
|
11791
|
+
command.offsetD = this.drawViewOffset;
|
|
11792
|
+
command.offsetCount = 4;
|
|
11175
11793
|
let index = 0;
|
|
11176
11794
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
11177
11795
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
@@ -11192,6 +11810,11 @@ export class WebGPURenderer {
|
|
|
11192
11810
|
* wrote.
|
|
11193
11811
|
*/
|
|
11194
11812
|
keepSkinDiffuse(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, slot, material) {
|
|
11813
|
+
/* A skin's halves are not bundled: a static list holding one replays draw by draw. */
|
|
11814
|
+
if (this.staticRecording !== null) {
|
|
11815
|
+
this.staticRecording.immediate = true;
|
|
11816
|
+
return;
|
|
11817
|
+
}
|
|
11195
11818
|
const skin = this.skinScatter;
|
|
11196
11819
|
const pipelines = this.skinPipelines;
|
|
11197
11820
|
const present = this.meshPresent.get(base);
|
|
@@ -11207,8 +11830,10 @@ export class WebGPURenderer {
|
|
|
11207
11830
|
command.pipeline = pipeline;
|
|
11208
11831
|
command.bindGroup = bindGroup;
|
|
11209
11832
|
command.offsetA = slot;
|
|
11210
|
-
command.offsetB =
|
|
11211
|
-
command.
|
|
11833
|
+
command.offsetB = this.passOffset;
|
|
11834
|
+
command.offsetC = material;
|
|
11835
|
+
command.offsetD = this.drawViewOffset;
|
|
11836
|
+
command.offsetCount = 4;
|
|
11212
11837
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
11213
11838
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
11214
11839
|
}
|
|
@@ -11635,6 +12260,9 @@ export class WebGPURenderer {
|
|
|
11635
12260
|
* site reads as one `if` either way.
|
|
11636
12261
|
*/
|
|
11637
12262
|
recordDraw(reads, writes) {
|
|
12263
|
+
/* A static list recording takes its draws into its own pool, never the frame graph's. */
|
|
12264
|
+
if (this.staticRecording !== null)
|
|
12265
|
+
return takeRecorded(this.staticRecording.frame);
|
|
11638
12266
|
/* The order-independent replay owns its pass and issues directly into it; a recorded command
|
|
11639
12267
|
would be replayed into the frame's passes instead, which is not where these belong. */
|
|
11640
12268
|
if (this.oitEncoder !== null)
|
|
@@ -11682,10 +12310,26 @@ export class WebGPURenderer {
|
|
|
11682
12310
|
}
|
|
11683
12311
|
/** One recorded draw, issued into an open pass. Shared by the graph's replay and `runLate`. */
|
|
11684
12312
|
issueCommand(pass, command) {
|
|
12313
|
+
/* A static list, recorded once: one call, and the pass's bindings are its to set again. */
|
|
12314
|
+
if (command.bundles !== null) {
|
|
12315
|
+
pass.executeBundles(command.bundles);
|
|
12316
|
+
return;
|
|
12317
|
+
}
|
|
11685
12318
|
if (command.pipeline === null || command.bindGroup === null)
|
|
11686
12319
|
return;
|
|
11687
12320
|
pass.setPipeline(command.pipeline);
|
|
11688
|
-
if (command.offsetCount ===
|
|
12321
|
+
if (command.offsetCount === 4) {
|
|
12322
|
+
pass.setBindGroup(0, command.bindGroup, [
|
|
12323
|
+
command.offsetA,
|
|
12324
|
+
command.offsetB,
|
|
12325
|
+
command.offsetC,
|
|
12326
|
+
command.offsetD,
|
|
12327
|
+
]);
|
|
12328
|
+
}
|
|
12329
|
+
else if (command.offsetCount === 3) {
|
|
12330
|
+
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB, command.offsetC]);
|
|
12331
|
+
}
|
|
12332
|
+
else if (command.offsetCount === 2) {
|
|
11689
12333
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB]);
|
|
11690
12334
|
}
|
|
11691
12335
|
else if (command.offsetCount === 1) {
|
|
@@ -11951,7 +12595,7 @@ export class WebGPURenderer {
|
|
|
11951
12595
|
this.pass?.end();
|
|
11952
12596
|
this.pass = null;
|
|
11953
12597
|
if (!this.surface.lost)
|
|
11954
|
-
this.
|
|
12598
|
+
this.submit([this.encoder.finish()]);
|
|
11955
12599
|
this.encoder = null;
|
|
11956
12600
|
}
|
|
11957
12601
|
endFrame() {
|
|
@@ -12015,7 +12659,7 @@ export class WebGPURenderer {
|
|
|
12015
12659
|
* there would have nothing to ride on. See `GpuTimestamps.resolve`.
|
|
12016
12660
|
*/
|
|
12017
12661
|
this.gpuTimer.resolve(this.encoder);
|
|
12018
|
-
this.
|
|
12662
|
+
this.submit([this.encoder.finish()]);
|
|
12019
12663
|
this.encoder = null;
|
|
12020
12664
|
this.framePresented = true;
|
|
12021
12665
|
/* Latched rather than assigned: `framePresented` goes back to false every `beginFrame`. */
|
|
@@ -12676,7 +13320,7 @@ export class WebGPURenderer {
|
|
|
12676
13320
|
definition.dispatch(this.computeContext);
|
|
12677
13321
|
pass.end();
|
|
12678
13322
|
this.flushRings();
|
|
12679
|
-
|
|
13323
|
+
this.submit([encoder.finish()]);
|
|
12680
13324
|
}
|
|
12681
13325
|
/** Let go of a definition. A handle kept past this dispatches nothing; see `ComputeHandle`. */
|
|
12682
13326
|
unregisterCompute(handle) {
|
|
@@ -13730,7 +14374,7 @@ export class WebGPURenderer {
|
|
|
13730
14374
|
.end();
|
|
13731
14375
|
}
|
|
13732
14376
|
}
|
|
13733
|
-
|
|
14377
|
+
this.submit([clear.finish()]);
|
|
13734
14378
|
}
|
|
13735
14379
|
/**
|
|
13736
14380
|
* Declare where a grid's probes stand, and allocate the layers for them.
|
|
@@ -13941,7 +14585,7 @@ export class WebGPURenderer {
|
|
|
13941
14585
|
if (whole)
|
|
13942
14586
|
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
13943
14587
|
this.flushRings();
|
|
13944
|
-
|
|
14588
|
+
this.submit([encoder.finish()]);
|
|
13945
14589
|
this.rewindRings(skinMark);
|
|
13946
14590
|
this.encoderEpoch = frameEpoch;
|
|
13947
14591
|
this.encoder = savedEncoder;
|
|
@@ -13951,6 +14595,7 @@ export class WebGPURenderer {
|
|
|
13951
14595
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
13952
14596
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
13953
14597
|
this.materials.dirty();
|
|
14598
|
+
this.reopenPass();
|
|
13954
14599
|
this.probePassActive = false;
|
|
13955
14600
|
this.probeBounce = false;
|
|
13956
14601
|
this.rebindEnvironment();
|
|
@@ -13998,7 +14643,7 @@ export class WebGPURenderer {
|
|
|
13998
14643
|
this.uploadProbeFaces(probe, image);
|
|
13999
14644
|
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
14000
14645
|
this.buildProbeChain(encoder, probe, 0);
|
|
14001
|
-
|
|
14646
|
+
this.submit([encoder.finish()]);
|
|
14002
14647
|
this.markProbeFilled(0);
|
|
14003
14648
|
this.probeAmbient = options?.irradiance ?? true;
|
|
14004
14649
|
this.rebuildFlatBindGroup();
|
|
@@ -14023,7 +14668,7 @@ export class WebGPURenderer {
|
|
|
14023
14668
|
const { device } = this.surface;
|
|
14024
14669
|
const encoder = device.createCommandEncoder({ label: 'probe.layerImage' });
|
|
14025
14670
|
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
14026
|
-
|
|
14671
|
+
this.submit([encoder.finish()]);
|
|
14027
14672
|
this.markProbeFilled(layer);
|
|
14028
14673
|
this.probeAmbient = options?.irradiance ?? true;
|
|
14029
14674
|
this.rebuildFlatBindGroup();
|