@driftengine/core 4.9.0 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +133 -11
- package/README.md +43 -38
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -4
- 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 +189 -4
- package/dist/render/backend/webgl2/renderer.js +614 -66
- 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 +24 -4
- package/dist/render/backend/webgpu/buffers.js +116 -59
- package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
- package/dist/render/backend/webgpu/clothKernels.js +76 -0
- package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
- package/dist/render/backend/webgpu/clothOps.js +78 -0
- package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
- package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
- package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
- package/dist/render/backend/webgpu/clothSetPack.js +144 -0
- package/dist/render/backend/webgpu/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 +20 -4
- package/dist/render/backend/webgpu/drawCommand.js +11 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
- package/dist/render/backend/webgpu/flatPass.js +137 -20
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
- package/dist/render/backend/webgpu/pipelineCache.js +46 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +200 -35
- package/dist/render/backend/webgpu/renderer.js +993 -168
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
- package/dist/render/backend/webgpu/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 +242 -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/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- 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/globalMediumPass.d.ts +2 -0
- package/dist/render/globalMediumPass.js +1 -0
- package/dist/render/indexWidth.d.ts +17 -0
- package/dist/render/indexWidth.js +23 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +14 -0
- package/dist/render/mesh.js +44 -14
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/renderQuality.d.ts +30 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.d.ts +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/screenSpaceReflection.d.ts +2 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- 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/directionalShadow.js +84 -10
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +3 -0
- package/dist/render/shaders/flat/index.d.ts +21 -0
- package/dist/render/shaders/flat/index.js +42 -7
- package/dist/render/shaders/flat/layered.d.ts +19 -0
- package/dist/render/shaders/flat/layered.js +115 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +58 -21
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/movingSun.d.ts +21 -0
- package/dist/render/shaders/flat/movingSun.js +46 -0
- package/dist/render/shaders/flat/normalMap.d.ts +6 -2
- package/dist/render/shaders/flat/normalMap.js +14 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +42 -20
- package/dist/render/shaders/flat/pointShadow.js +2 -2
- package/dist/render/shaders/flat/preamble.js +59 -27
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- 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 +3616 -3042
- package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
- package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
- package/dist/render/shaders/globalMedium.js +9 -1
- package/dist/render/shaders/lightVolume.js +9 -1
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceLayers.d.ts +39 -0
- package/dist/render/surfaceLayers.js +26 -0
- package/dist/render/surfaceOverlay.d.ts +16 -3
- package/dist/render/surfaceOverlay.js +11 -6
- package/dist/render/surfaceProjection.d.ts +34 -0
- package/dist/render/surfaceProjection.js +30 -0
- package/dist/render/surfaceTexture.d.ts +68 -7
- package/dist/render/surfaceTexture.js +56 -17
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/package.json +6 -6
- package/src/index.ts +12 -4
- 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 +723 -79
- 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 +195 -70
- package/src/render/backend/webgpu/clothKernels.ts +86 -0
- package/src/render/backend/webgpu/clothOps.ts +86 -0
- package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
- package/src/render/backend/webgpu/clothSetPack.ts +197 -0
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/drawCommand.ts +25 -4
- package/src/render/backend/webgpu/flatPass.ts +212 -41
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +53 -7
- package/src/render/backend/webgpu/renderer.ts +1201 -202
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- 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 +364 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- 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/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +72 -14
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/screenSpaceReflection.ts +2 -1
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/flat/directionalShadow.ts +84 -10
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +63 -7
- package/src/render/shaders/flat/layered.ts +115 -0
- package/src/render/shaders/flat/main.ts +58 -21
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +14 -3
- package/src/render/shaders/flat/overlay.ts +42 -20
- package/src/render/shaders/flat/pointShadow.ts +2 -2
- package/src/render/shaders/flat/preamble.ts +59 -27
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
- package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
- package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
- package/src/render/shaders/globalMedium.ts +9 -1
- package/src/render/shaders/lightVolume.ts +9 -1
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceLayers.ts +57 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +124 -27
- package/src/render/vertexPacking.ts +109 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { vertexPackingOf } from '../../vertexPacking.js';
|
|
2
|
+
import { markPacking, markPackingOfKey, packingKey } from './vertexFormats.js';
|
|
1
3
|
import { mat4 } from 'gl-matrix';
|
|
2
4
|
import { probeFaceRange } from '../../reflectionProbe.js';
|
|
3
5
|
import { TEMPORAL_FLICKER, TEMPORAL_HISTORY_BLEND, TemporalHistory, flickerRule, jitterOffset, jitterProjection, } from '../../temporalAa.js';
|
|
@@ -60,8 +62,11 @@ import { gpuCompressedFormats } from './compressedUpload.js';
|
|
|
60
62
|
import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTextures.js';
|
|
61
63
|
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel, skinProfileDistance, } from '../../surfaceModel.js';
|
|
62
64
|
import { OVERLAY_FLOATS, overlayLays, packSurfaceOverlay, } from '../../surfaceOverlay.js';
|
|
65
|
+
import { PROJECTION_FLOATS, packSurfaceProjection } from '../../surfaceProjection.js';
|
|
66
|
+
import { orthographicDepthSpan } from '../../lightMatrix.js';
|
|
67
|
+
import { LAYER_FLOATS, packSurfaceLayers } from '../../surfaceLayers.js';
|
|
63
68
|
import { frameReflectionsRefused, resolveFrameReflections, } from '../../frameReflections.js';
|
|
64
|
-
import { ReflectionSurfacePass } from './reflectionSurfacePass.js';
|
|
69
|
+
import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.js';
|
|
65
70
|
import { validateClothBinding } from '../../clothBindingData.js';
|
|
66
71
|
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
72
|
import { createFilmBindGroup, createFilmBindGroupLayout, filmPipeline, FILM_FRAG_FIELDS, FILM_FRAG_SIZE, FILM_VERT_FIELDS, FILM_VERT_SIZE, } from './filmPass.js';
|
|
@@ -97,10 +102,15 @@ import { VIEW_MODEL_DEPTH_SHARE, viewModelDepthRange } from '../../viewModel.js'
|
|
|
97
102
|
import { GlassCasterList } from '../../glassCasters.js';
|
|
98
103
|
import { GpuGlassTint } from './glassTintPass.js';
|
|
99
104
|
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';
|
|
105
|
+
import { VERTEX_LAYOUT, createGpuMesh, createGpuMeshIncremental, } from './buffers.js';
|
|
101
106
|
import { SkinPaletteRing } from './skinPaletteRing.js';
|
|
102
|
-
import { DEPTH_FORMAT, FLAT_VERT_FIELDS, FLAT_VERT_SIZE, createFlatBindGroup, createFlatBindGroupLayout,
|
|
103
|
-
import {
|
|
107
|
+
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';
|
|
108
|
+
import { GpuBoneAnimation } from './boneAnimations.js';
|
|
109
|
+
import { packBoneAnimation } from '../../boneAnimation.js';
|
|
110
|
+
import { MaterialStore, hashWords } from './materialStore.js';
|
|
111
|
+
import { GpuStaticDraws, encodeBundle, takeRecorded } from './staticBundles.js';
|
|
112
|
+
import { captureStaticDraws, replayStaticDraws } from '../../staticDraws.js';
|
|
113
|
+
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
114
|
import { PipelineCache } from './pipelineCache.js';
|
|
105
115
|
import { SKIN_TARGET_FORMAT, SkinScatterPass } from './skinScatterPass.js';
|
|
106
116
|
import { SKY_FIELDS, SKY_UNIFORM_SIZE, createSkyBindGroup, createSkyBindGroupLayout, skyPipeline, } from './skyPass.js';
|
|
@@ -359,6 +369,13 @@ const MAX_WATER_BODIES_PER_FRAME = 16;
|
|
|
359
369
|
* numbers, because that is precisely the defect the ring exists to end.
|
|
360
370
|
*/
|
|
361
371
|
const MAX_BATCHES_PER_FRAME = 8;
|
|
372
|
+
/**
|
|
373
|
+
* Mesh passes a frame may open before the ring grows: the frame's own, a mirror's, a capture's, and
|
|
374
|
+
* a probe face's, which gives its slot back once submitted. A frame past it loses the draws of the
|
|
375
|
+
* passes that found no slot, counted on the `materials` line, and the ring is grown for the next.
|
|
376
|
+
* Each slot is the pass's block, 3,328 to 6,656 bytes by variant, so the ring is 213 to 426 KB.
|
|
377
|
+
*/
|
|
378
|
+
const PASSES_PER_FRAME = 64;
|
|
362
379
|
/** Buffer usage flags, from the specification. See `buffers.ts` for why they are not read off the global. */
|
|
363
380
|
const USAGE_UNIFORM_DST = 0x0040 | 0x0008;
|
|
364
381
|
/**
|
|
@@ -845,7 +862,21 @@ export class WebGPURenderer {
|
|
|
845
862
|
* flight at the time, so a consumer that builds more meshes later can wait again.
|
|
846
863
|
*/
|
|
847
864
|
async ready() {
|
|
848
|
-
await Promise.all(this.
|
|
865
|
+
await Promise.all(this.pipelineCaches().map((cache) => cache.ready()));
|
|
866
|
+
}
|
|
867
|
+
/** Every distinct cache a lit draw reaches: the frame's, the canvas's, the late pass's, skin's. */
|
|
868
|
+
pipelineCaches() {
|
|
869
|
+
const caches = [...this.pipelineTargets];
|
|
870
|
+
for (const cache of [this.latePipelines, this.skinPipelines]) {
|
|
871
|
+
if (cache !== null && !caches.includes(cache))
|
|
872
|
+
caches.push(cache);
|
|
873
|
+
}
|
|
874
|
+
return caches;
|
|
875
|
+
}
|
|
876
|
+
/** Whether a lit draw whose pipeline is not built is left out while it compiles. */
|
|
877
|
+
skipCompiling(skips) {
|
|
878
|
+
for (const cache of this.pipelineCaches())
|
|
879
|
+
cache.skipsCompiling = skips;
|
|
849
880
|
}
|
|
850
881
|
presentedFrameCount = 0;
|
|
851
882
|
/**
|
|
@@ -859,6 +890,8 @@ export class WebGPURenderer {
|
|
|
859
890
|
skinnedBindGroupLayout;
|
|
860
891
|
morphedBindGroupLayout;
|
|
861
892
|
bothBindGroupLayout;
|
|
893
|
+
/** The instanced layout of a batch playing a bone animation. See `animatedGroup`. */
|
|
894
|
+
animatedBindGroupLayout;
|
|
862
895
|
/** The morph weights the following draws use, or null for none. */
|
|
863
896
|
morphWeights = null;
|
|
864
897
|
/**
|
|
@@ -871,14 +904,15 @@ export class WebGPURenderer {
|
|
|
871
904
|
* group per character per frame — an allocation in the frame loop, which `AGENTS.md` forbids.
|
|
872
905
|
* Keyed by slot instead, the groups are built once and reused for the life of the renderer.
|
|
873
906
|
*/
|
|
907
|
+
/*
|
|
908
|
+
* **And by material within a slot**, keyed by the unskinned group each was built beside. One
|
|
909
|
+
* group a slot was rebuilt whenever the material beside it changed, so a character drawn in
|
|
910
|
+
* thirty-odd sections, each its own material, built one a section a frame — about ninety a frame
|
|
911
|
+
* for two characters, reported by a consumer. A slot's map holds one entry a material its rigs
|
|
912
|
+
* are drawn in, and is emptied wherever the unskinned groups are, whose identities it is keyed
|
|
913
|
+
* by.
|
|
914
|
+
*/
|
|
874
915
|
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
916
|
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
883
917
|
cloth = null;
|
|
884
918
|
clothStandCache = null;
|
|
@@ -946,22 +980,51 @@ export class WebGPURenderer {
|
|
|
946
980
|
/** Every distinct cache above, so a mesh's pipelines are built for each. One entry when aliased. */
|
|
947
981
|
pipelineTargets;
|
|
948
982
|
bindGroupLayout;
|
|
949
|
-
/** One slot per draw, uploaded once a frame. See `UniformRing`. */
|
|
983
|
+
/** One slot per draw, uploaded once a frame. See `UniformRing`. Swapped while a static list records. */
|
|
950
984
|
perDraw;
|
|
951
|
-
/** The lights, fog and camera, settled once per frame by `bindMeshPass`. */
|
|
952
|
-
perFrame;
|
|
953
985
|
/**
|
|
954
|
-
* The
|
|
986
|
+
* The lights, fog and camera, a slot per mesh pass a frame opens: `bindMeshPass` writes them, and
|
|
987
|
+
* the first draw after it takes a slot. A ring rather than one buffer because a frame opens more
|
|
988
|
+
* than one pass — a mirror's, a capture's, a probe face's — and a queue write does not interleave
|
|
989
|
+
* with the draws recorded between them. See `UniformRing`.
|
|
990
|
+
*/
|
|
991
|
+
passRing;
|
|
992
|
+
/** Each material's block, kept across frames for as long as it does not change. */
|
|
993
|
+
materialStore;
|
|
994
|
+
/**
|
|
995
|
+
* Both blocks as the CPU holds them, the pass's then the material's, patched by name and copied
|
|
996
|
+
* into a slot by the next draw after something changed them. See `FlatFragmentScratch`.
|
|
955
997
|
*
|
|
956
998
|
* Authoritative between draws: a setter changes one field here and marks the block dirty, and
|
|
957
|
-
* the next `drawMesh`
|
|
958
|
-
*
|
|
999
|
+
* the next `drawMesh` places it. That keeps a setter free of any knowledge of slots, which is
|
|
1000
|
+
* what lets it be called anywhere a scene wants.
|
|
959
1001
|
*/
|
|
960
1002
|
perFrameStaging;
|
|
961
1003
|
perFrameFloats;
|
|
962
1004
|
perFrameInts;
|
|
1005
|
+
/** The two halves of the scratch, as the words a slot is written from. */
|
|
1006
|
+
passInts;
|
|
1007
|
+
materialInts;
|
|
963
1008
|
/** The slot the open material occupies, and whether the next draw must take a new one. */
|
|
964
1009
|
materials = new MaterialChanges();
|
|
1010
|
+
/** The same for the pass's block: reopened by `bindMeshPass` and by nothing a material sets. */
|
|
1011
|
+
passBlock = new MaterialChanges();
|
|
1012
|
+
/**
|
|
1013
|
+
* The vertex stage's view block — the camera, the light's matrix, the wind — a slot a pass, and a
|
|
1014
|
+
* second, unjittered, for a blended draw landing after the upscale. Reopened with the pass, and by
|
|
1015
|
+
* `setWind` and `beginShadowPass`, which change what it holds.
|
|
1016
|
+
*/
|
|
1017
|
+
viewRing;
|
|
1018
|
+
viewBlock = new MaterialChanges();
|
|
1019
|
+
lateViewBlock = new MaterialChanges();
|
|
1020
|
+
/** The view slot the draw being submitted bound, read by the halves it keeps for later passes. */
|
|
1021
|
+
drawViewOffset = 0;
|
|
1022
|
+
/** Pass slots asked for and refused this frame, and materials refused: what grows the two. */
|
|
1023
|
+
passesAsked = 0;
|
|
1024
|
+
passesRefused = 0;
|
|
1025
|
+
viewsAsked = 0;
|
|
1026
|
+
viewsRefused = 0;
|
|
1027
|
+
materialsRefused = 0;
|
|
965
1028
|
/*
|
|
966
1029
|
* The frame graph's recording, and the machinery that replays it.
|
|
967
1030
|
*
|
|
@@ -1214,7 +1277,7 @@ export class WebGPURenderer {
|
|
|
1214
1277
|
variant;
|
|
1215
1278
|
/** Whether this profile compiled the point-shadow path, and so has sentinels to write. */
|
|
1216
1279
|
pointShadowsCompiled;
|
|
1217
|
-
/** What the generator recorded about that permutation
|
|
1280
|
+
/** What the generator recorded about that permutation, as the renderer's scratch lays it out. */
|
|
1218
1281
|
fragment;
|
|
1219
1282
|
depth = null;
|
|
1220
1283
|
depthView = null;
|
|
@@ -1934,7 +1997,7 @@ export class WebGPURenderer {
|
|
|
1934
1997
|
recordNode(this.arena, VERB_SCOPE, 0, MIRROR_TARGET, 0, 0);
|
|
1935
1998
|
this.pass?.end();
|
|
1936
1999
|
this.flushRings();
|
|
1937
|
-
this.
|
|
2000
|
+
this.submit([this.encoder.finish()]);
|
|
1938
2001
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'reflection' });
|
|
1939
2002
|
/*
|
|
1940
2003
|
* **Opened on demand, like the frame's own.** Nothing has been recorded into the mirror yet,
|
|
@@ -1992,7 +2055,8 @@ export class WebGPURenderer {
|
|
|
1992
2055
|
this.windStreakFrags,
|
|
1993
2056
|
this.scatterVerts,
|
|
1994
2057
|
this.scatterFrags,
|
|
1995
|
-
this.
|
|
2058
|
+
this.passRing,
|
|
2059
|
+
this.viewRing,
|
|
1996
2060
|
this.filmVerts,
|
|
1997
2061
|
this.filmFrags,
|
|
1998
2062
|
this.insetUniforms,
|
|
@@ -2013,6 +2077,7 @@ export class WebGPURenderer {
|
|
|
2013
2077
|
this.instanceCulls?.flush();
|
|
2014
2078
|
for (const ring of this.rings())
|
|
2015
2079
|
ring.flush();
|
|
2080
|
+
this.materialStore.flush();
|
|
2016
2081
|
}
|
|
2017
2082
|
/** Where every ring stands before work that submits an encoder of its own. */
|
|
2018
2083
|
ringMarks = [];
|
|
@@ -2032,6 +2097,8 @@ export class WebGPURenderer {
|
|
|
2032
2097
|
const rings = this.rings();
|
|
2033
2098
|
for (let i = 0; i < rings.length; i++)
|
|
2034
2099
|
rings[i].rewind(this.ringMarks[i] ?? 0);
|
|
2100
|
+
/* The open pass may sit in a slot just given back, which the next pass would overwrite. */
|
|
2101
|
+
this.reopenPass();
|
|
2035
2102
|
this.skinPalettes.rewind(skinMark);
|
|
2036
2103
|
if (this.skinPaletteSlot >= skinMark)
|
|
2037
2104
|
this.skinPaletteSlot = -1;
|
|
@@ -2129,6 +2196,7 @@ export class WebGPURenderer {
|
|
|
2129
2196
|
}
|
|
2130
2197
|
filmCommand.vertexCount = geometry.vertexBuffers.length;
|
|
2131
2198
|
filmCommand.indexBuffer = geometry.indexBuffer;
|
|
2199
|
+
filmCommand.indexFormat = geometry.indexFormat;
|
|
2132
2200
|
filmCommand.indexed = true;
|
|
2133
2201
|
filmCommand.count = geometry.indexCount;
|
|
2134
2202
|
}
|
|
@@ -2141,7 +2209,7 @@ export class WebGPURenderer {
|
|
|
2141
2209
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
2142
2210
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
2143
2211
|
}
|
|
2144
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
2212
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
2145
2213
|
pass.drawIndexed(geometry.indexCount);
|
|
2146
2214
|
}
|
|
2147
2215
|
}
|
|
@@ -2175,6 +2243,11 @@ export class WebGPURenderer {
|
|
|
2175
2243
|
modelMap = null;
|
|
2176
2244
|
/** The material's shading model, which picks the lit pipeline: null the standard. */
|
|
2177
2245
|
materialModel = null;
|
|
2246
|
+
/** `uWorldUv`, the material's projection packed by `packSurfaceProjection`. */
|
|
2247
|
+
projection = new Float32Array(PROJECTION_FLOATS);
|
|
2248
|
+
/** `uLayers`, the material's layers packed by `packSurfaceLayers`. */
|
|
2249
|
+
layerFloats = new Float32Array(LAYER_FLOATS);
|
|
2250
|
+
warnedLayersModelMap = false;
|
|
2178
2251
|
/** Its numbers, `uModelParams`, packed by `packModel`. */
|
|
2179
2252
|
modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
2180
2253
|
/**
|
|
@@ -2329,7 +2402,7 @@ export class WebGPURenderer {
|
|
|
2329
2402
|
pass.end();
|
|
2330
2403
|
this.pass = null;
|
|
2331
2404
|
this.flushRings();
|
|
2332
|
-
|
|
2405
|
+
this.submit([encoder.finish()]);
|
|
2333
2406
|
this.rewindRings(skinMark);
|
|
2334
2407
|
this.encoderEpoch = frameEpoch;
|
|
2335
2408
|
this.encoder = savedEncoder;
|
|
@@ -2337,6 +2410,7 @@ export class WebGPURenderer {
|
|
|
2337
2410
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
2338
2411
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
2339
2412
|
this.materials.dirty();
|
|
2413
|
+
this.reopenPass();
|
|
2340
2414
|
this.capturePassActive = false;
|
|
2341
2415
|
}
|
|
2342
2416
|
return true;
|
|
@@ -2352,10 +2426,11 @@ export class WebGPURenderer {
|
|
|
2352
2426
|
updateSurfaceTexture(texture, source) {
|
|
2353
2427
|
if (this.surface.lost)
|
|
2354
2428
|
return;
|
|
2355
|
-
const replaced = texture.update(source);
|
|
2429
|
+
const replaced = texture.update(source, this.compressedFormats);
|
|
2356
2430
|
if (replaced === null)
|
|
2357
2431
|
return;
|
|
2358
|
-
/* A new size is a new view, so every group holding the old one is dropped — see
|
|
2432
|
+
/* A new size or format is a new view, so every group holding the old one is dropped — see
|
|
2433
|
+
below. */
|
|
2359
2434
|
this.retiredTextures.push(replaced);
|
|
2360
2435
|
this.forgetBindingsOf(texture);
|
|
2361
2436
|
}
|
|
@@ -2411,7 +2486,9 @@ export class WebGPURenderer {
|
|
|
2411
2486
|
/* Same reasoning, for whichever SDF labels were bound against this atlas. */
|
|
2412
2487
|
this.sdfTextBindGroups.delete(texture);
|
|
2413
2488
|
this.flatBindGroups.clear();
|
|
2489
|
+
this.flatGroupEpoch += 1;
|
|
2414
2490
|
this.overlayGroups.clear();
|
|
2491
|
+
this.skinnedGroups.length = 0;
|
|
2415
2492
|
/*
|
|
2416
2493
|
* The blank one is built with the albedo held aside, because `buildFlatBindGroup` reads
|
|
2417
2494
|
* `this.albedo` for that slot — leaving it in place would put a real colour map into the
|
|
@@ -2571,11 +2648,21 @@ export class WebGPURenderer {
|
|
|
2571
2648
|
const normal = (material?.normal ?? null);
|
|
2572
2649
|
const orm = (material?.orm ?? null);
|
|
2573
2650
|
const emissiveMap = (material?.emissive ?? null);
|
|
2574
|
-
const
|
|
2651
|
+
const ownModelMap = (material?.modelMap ?? null);
|
|
2652
|
+
/* A layered material's mask goes where a model's map would: see `SurfaceMaterial.layers`. */
|
|
2653
|
+
const layers = this.layersOf(material);
|
|
2654
|
+
const modelMap = ownModelMap ?? (layers?.mask ?? null);
|
|
2575
2655
|
this.materialModel = material?.model ?? null;
|
|
2656
|
+
const reflectivity = material?.reflectivity;
|
|
2657
|
+
const environmentGain = material?.environmentGain;
|
|
2658
|
+
this.materialReflectivity =
|
|
2659
|
+
reflectivity === undefined ? null : Math.min(1, Math.max(0, reflectivity));
|
|
2660
|
+
this.materialEnvironmentGain =
|
|
2661
|
+
environmentGain === undefined ? null : Math.max(0, environmentGain);
|
|
2662
|
+
this.writeEnvironmentDials();
|
|
2576
2663
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
2577
2664
|
const model = material?.model;
|
|
2578
|
-
warnUnpagedLightmap(model,
|
|
2665
|
+
warnUnpagedLightmap(model, ownModelMap);
|
|
2579
2666
|
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
2580
2667
|
this.skinScatter.setProfile(model.profile, skinProfileDistance(model, 0), skinProfileDistance(model, 1), skinProfileDistance(model, 2));
|
|
2581
2668
|
}
|
|
@@ -2592,7 +2679,8 @@ export class WebGPURenderer {
|
|
|
2592
2679
|
const cut = this.materialField('uCutout');
|
|
2593
2680
|
f[cut] = cutoff;
|
|
2594
2681
|
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
2595
|
-
|
|
2682
|
+
/* z unused: the dither's frame is the pass's, in `uHighlightMin.w`. See `uCutout`. */
|
|
2683
|
+
f[cut + 2] = 0;
|
|
2596
2684
|
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
2597
2685
|
f[cut + 3] = lightChannelsOf(material?.lightChannels);
|
|
2598
2686
|
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
@@ -2622,10 +2710,23 @@ export class WebGPURenderer {
|
|
|
2622
2710
|
f[emissive + 1] = asked?.[1] ?? 1;
|
|
2623
2711
|
f[emissive + 2] = asked?.[2] ?? 1;
|
|
2624
2712
|
/* The model's numbers and whether its map is bound. See `packModel`. */
|
|
2625
|
-
packModel(material?.model ?? null,
|
|
2713
|
+
packModel(material?.model ?? null, ownModelMap !== null, this.modelParams, material?.physicalSpecular === true);
|
|
2626
2714
|
/* The first physical highlight asked for is when the lit pipelines start carrying one. */
|
|
2627
2715
|
if (material?.physicalSpecular === true)
|
|
2628
2716
|
this.enableLit('PHYSICAL_SPECULAR');
|
|
2717
|
+
/* Maps placed by the world: the first material that asks is when lit pipelines carry it. */
|
|
2718
|
+
packSurfaceProjection(material?.projection, this.projection);
|
|
2719
|
+
f.set(this.projection, this.materialField('uWorldUv'));
|
|
2720
|
+
/* How much light from behind it lets through, in the glass tint's spare lane. */
|
|
2721
|
+
f[this.materialField('uGlassTint') + 3] =
|
|
2722
|
+
Math.min(Math.max(material?.diffuseTransmission ?? 0, 0), 1) || 0;
|
|
2723
|
+
if (this.projection[0] > 0)
|
|
2724
|
+
this.enableLit('WORLD_UVS');
|
|
2725
|
+
/* And its layers, the first time a material blends any: layered.ts. */
|
|
2726
|
+
packSurfaceLayers(layers, this.layerFloats);
|
|
2727
|
+
f.set(this.layerFloats, this.materialField('uLayers'));
|
|
2728
|
+
if (layers !== null)
|
|
2729
|
+
this.enableLit('LAYERED');
|
|
2629
2730
|
f.set(this.modelParams, this.materialField('uModelParams'));
|
|
2630
2731
|
});
|
|
2631
2732
|
/*
|
|
@@ -2764,14 +2865,28 @@ export class WebGPURenderer {
|
|
|
2764
2865
|
}
|
|
2765
2866
|
/** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
|
|
2766
2867
|
setSurfaceReflectivity(amount) {
|
|
2767
|
-
this.
|
|
2768
|
-
|
|
2769
|
-
});
|
|
2868
|
+
this.passReflectivity = Math.min(1, Math.max(0, amount));
|
|
2869
|
+
this.writeEnvironmentDials();
|
|
2770
2870
|
}
|
|
2771
2871
|
/** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
|
|
2772
2872
|
setEnvironmentGain(gain) {
|
|
2873
|
+
this.passEnvironmentGain = Math.max(0, gain);
|
|
2874
|
+
this.writeEnvironmentDials();
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* The reflectivity and environment gain the setters gave the pass, and the ones the material
|
|
2878
|
+
* set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
|
|
2879
|
+
* material's where it has one and the pass's otherwise, the rule `renderer.ts` keeps too.
|
|
2880
|
+
*/
|
|
2881
|
+
passReflectivity = 0;
|
|
2882
|
+
passEnvironmentGain = 1;
|
|
2883
|
+
materialReflectivity = null;
|
|
2884
|
+
materialEnvironmentGain = null;
|
|
2885
|
+
writeEnvironmentDials() {
|
|
2773
2886
|
this.material((f) => {
|
|
2774
|
-
|
|
2887
|
+
const dials = this.materialField('uEnvironmentDials');
|
|
2888
|
+
f[dials + 1] = this.materialReflectivity ?? this.passReflectivity;
|
|
2889
|
+
f[dials + 2] = this.materialEnvironmentGain ?? this.passEnvironmentGain;
|
|
2775
2890
|
});
|
|
2776
2891
|
}
|
|
2777
2892
|
setSurfaceGrain(amount) {
|
|
@@ -2799,6 +2914,14 @@ export class WebGPURenderer {
|
|
|
2799
2914
|
this.perFrameFloats[this.materialField('uWriteMode') + 1] = Math.min(1, Math.max(-1, amount));
|
|
2800
2915
|
this.materials.dirty();
|
|
2801
2916
|
}
|
|
2917
|
+
/** Whether the draws that follow spend their own opacity on that screen door. See `renderer.ts`. */
|
|
2918
|
+
setDitherOpacity(on) {
|
|
2919
|
+
if (this.surface.lost)
|
|
2920
|
+
return;
|
|
2921
|
+
/* The third lane of `uWriteMode`, beside the fade. */
|
|
2922
|
+
this.perFrameFloats[this.materialField('uWriteMode') + 2] = on ? 1 : 0;
|
|
2923
|
+
this.materials.dirty();
|
|
2924
|
+
}
|
|
2802
2925
|
/**
|
|
2803
2926
|
* How strong the microscopic relief on the surfaces drawn next is, and how coarse.
|
|
2804
2927
|
*
|
|
@@ -3242,7 +3365,7 @@ export class WebGPURenderer {
|
|
|
3242
3365
|
}
|
|
3243
3366
|
this.pass?.end();
|
|
3244
3367
|
this.flushRings();
|
|
3245
|
-
this.
|
|
3368
|
+
this.submit([this.encoder.finish()]);
|
|
3246
3369
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'frame.resumed' });
|
|
3247
3370
|
this.reflectionPassActive = false;
|
|
3248
3371
|
this.reflectionReady = true;
|
|
@@ -4080,6 +4203,7 @@ export class WebGPURenderer {
|
|
|
4080
4203
|
const sunShadow = env === undefined ? 0 : clamp(options.sunShadow ?? 0, 0, 1);
|
|
4081
4204
|
ring.writeFloat(fragmentSlot, at('uSunShadow'), sunShadow);
|
|
4082
4205
|
ring.writeFloats(fragmentSlot, at('uLightViewProj'), env === undefined ? NO_LIGHT_MATRIX : env.lightViewProj);
|
|
4206
|
+
ring.writeFloats(fragmentSlot, at('uMovingLightViewProj'), env === undefined ? NO_LIGHT_MATRIX : this.movingLightFor(env));
|
|
4083
4207
|
ring.writeFloat(fragmentSlot, at('uShadowMapSize'), this.quality.directionalShadowMapSize);
|
|
4084
4208
|
/* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
|
|
4085
4209
|
ring.writeInt(fragmentSlot, at('uPeeledShadowEnabled'), this.peelFilled ? 1 : 0);
|
|
@@ -4108,6 +4232,7 @@ export class WebGPURenderer {
|
|
|
4108
4232
|
}
|
|
4109
4233
|
volumeCommand.vertexCount = mesh.vertexBuffers.length;
|
|
4110
4234
|
volumeCommand.indexBuffer = mesh.indexBuffer;
|
|
4235
|
+
volumeCommand.indexFormat = mesh.indexFormat;
|
|
4111
4236
|
volumeCommand.indexed = true;
|
|
4112
4237
|
volumeCommand.count = mesh.indexCount;
|
|
4113
4238
|
}
|
|
@@ -4117,7 +4242,7 @@ export class WebGPURenderer {
|
|
|
4117
4242
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
4118
4243
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
|
|
4119
4244
|
}
|
|
4120
|
-
pass.setIndexBuffer(mesh.indexBuffer,
|
|
4245
|
+
pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
|
|
4121
4246
|
pass.drawIndexed(mesh.indexCount);
|
|
4122
4247
|
}
|
|
4123
4248
|
}
|
|
@@ -4626,6 +4751,7 @@ export class WebGPURenderer {
|
|
|
4626
4751
|
*/
|
|
4627
4752
|
setWind(windX, windZ, windGust, timeSeconds) {
|
|
4628
4753
|
resolveScatterDeform(windX, windZ, windGust, timeSeconds, null, this.frameWind);
|
|
4754
|
+
this.reopenViews();
|
|
4629
4755
|
}
|
|
4630
4756
|
/**
|
|
4631
4757
|
* The five wind fields of one shadow-draw slot.
|
|
@@ -4643,21 +4769,6 @@ export class WebGPURenderer {
|
|
|
4643
4769
|
this.shadowDraws.writeFloat(slot, DEPTH_VERT_FIELDS.uWindTime.offset, d.time);
|
|
4644
4770
|
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
4645
4771
|
}
|
|
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
4772
|
/** Refilled per frame rather than allocated; see `resolvePointLights`. */
|
|
4662
4773
|
lights = {
|
|
4663
4774
|
count: 0,
|
|
@@ -4807,6 +4918,9 @@ export class WebGPURenderer {
|
|
|
4807
4918
|
* at submit, and a texture's view is new when an update changes its size.
|
|
4808
4919
|
*/
|
|
4809
4920
|
shadowCutoutLayout;
|
|
4921
|
+
/** A crowd's casters: the plain and cutout layouts with a bone animation's three textures. */
|
|
4922
|
+
shadowAnimatedLayout;
|
|
4923
|
+
shadowAnimatedCutoutLayout;
|
|
4810
4924
|
cutoutGroups = new Map();
|
|
4811
4925
|
cutoutPeelGroups = new Map();
|
|
4812
4926
|
shadowPeelPassUniforms = null;
|
|
@@ -4844,6 +4958,22 @@ export class WebGPURenderer {
|
|
|
4844
4958
|
* the texel-for-texel comparison that settled it.
|
|
4845
4959
|
*/
|
|
4846
4960
|
correctedLightViewProj = new Float32Array(16);
|
|
4961
|
+
/**
|
|
4962
|
+
* What a receiver projects through to read the sun's static layers: the environment's
|
|
4963
|
+
* `lightViewProj`, taken at `bindMeshPass` as WebGL2 takes it there.
|
|
4964
|
+
*
|
|
4965
|
+
* **It was the last shadow pass's matrix**, which is the environment's wherever both layers are
|
|
4966
|
+
* drawn with one and the moving layer's wherever they are not: the static layer was then read
|
|
4967
|
+
* through the moving layer's tight square, and a distant pillar lost its shadow on WebGPU and kept
|
|
4968
|
+
* it on WebGL2, measured on demo/dev/sunLayers.html?moving=tight.
|
|
4969
|
+
*/
|
|
4970
|
+
receiverLightViewProj = new Float32Array(16);
|
|
4971
|
+
/**
|
|
4972
|
+
* The matrix the sun's dynamic shadow pass was last drawn with, uncorrected as the lit stage
|
|
4973
|
+
* projects with, and whether one has been. See `movingLightFor`.
|
|
4974
|
+
*/
|
|
4975
|
+
movingLightViewProj = new Float32Array(16);
|
|
4976
|
+
movingLightKnown = false;
|
|
4847
4977
|
/**
|
|
4848
4978
|
* The same matrix *uncorrected*, which is what the flat shader must project with.
|
|
4849
4979
|
*
|
|
@@ -4940,6 +5070,44 @@ export class WebGPURenderer {
|
|
|
4940
5070
|
this.glassSeen = true;
|
|
4941
5071
|
this.enableLit('GLASS_SHADOWS');
|
|
4942
5072
|
}
|
|
5073
|
+
/**
|
|
5074
|
+
* A material's layers where they can be drawn: none asked, or a model's map in the mask's place,
|
|
5075
|
+
* said once, leave one layer.
|
|
5076
|
+
*/
|
|
5077
|
+
layersOf(material) {
|
|
5078
|
+
const layers = material?.layers ?? null;
|
|
5079
|
+
if (layers === null || layers.repeats.length === 0)
|
|
5080
|
+
return null;
|
|
5081
|
+
if ((material?.modelMap ?? null) === null)
|
|
5082
|
+
return layers;
|
|
5083
|
+
if (!this.warnedLayersModelMap) {
|
|
5084
|
+
this.warnedLayersModelMap = true;
|
|
5085
|
+
console.warn("WebGPU: a material's layers are drawn as one: its mask is read where a model's map goes, " +
|
|
5086
|
+
'and this material carries a `modelMap` too.');
|
|
5087
|
+
}
|
|
5088
|
+
return null;
|
|
5089
|
+
}
|
|
5090
|
+
/**
|
|
5091
|
+
* What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
|
|
5092
|
+
* where the lit pipelines carry one (`MOVING_SUN`); the environment's otherwise, which every caller
|
|
5093
|
+
* drawing both layers with one matrix hands it anyway.
|
|
5094
|
+
*/
|
|
5095
|
+
movingLightFor(env) {
|
|
5096
|
+
return this.movingLightKnown && this.pipelines.litSwitches.MOVING_SUN
|
|
5097
|
+
? this.movingLightViewProj
|
|
5098
|
+
: env.lightViewProj;
|
|
5099
|
+
}
|
|
5100
|
+
/** The moving layer's own matrix on, the first time a frame draws that layer with one. */
|
|
5101
|
+
enableMovingSun(env) {
|
|
5102
|
+
if (this.pipelines.litSwitches.MOVING_SUN || !this.movingLightKnown)
|
|
5103
|
+
return;
|
|
5104
|
+
for (let k = 0; k < 16; k++) {
|
|
5105
|
+
if (this.movingLightViewProj[k] !== env.lightViewProj[k]) {
|
|
5106
|
+
this.enableLit('MOVING_SUN');
|
|
5107
|
+
return;
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
4943
5111
|
/** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
|
|
4944
5112
|
enableLit(feature) {
|
|
4945
5113
|
if (this.pipelines.litSwitches[feature])
|
|
@@ -4993,7 +5161,7 @@ export class WebGPURenderer {
|
|
|
4993
5161
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
4994
5162
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
4995
5163
|
}
|
|
4996
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
5164
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
4997
5165
|
pass.drawIndexed(geometry.indexCount);
|
|
4998
5166
|
},
|
|
4999
5167
|
/**
|
|
@@ -5019,26 +5187,54 @@ export class WebGPURenderer {
|
|
|
5019
5187
|
if (count === 0)
|
|
5020
5188
|
return;
|
|
5021
5189
|
const cutout = cutoutOf(material);
|
|
5022
|
-
|
|
5190
|
+
/* A crowd casts where its clip has moved it, through the animated variant. */
|
|
5191
|
+
const animation = gpuBatch.animation === null ? null : this.animationViews(gpuBatch);
|
|
5192
|
+
if (gpuBatch.animation !== null && animation === null)
|
|
5193
|
+
return;
|
|
5194
|
+
const cutoutTexture = cutout === null ? null : cutout.albedo;
|
|
5195
|
+
const cutoutGroup = animation !== null
|
|
5196
|
+
? this.animatedDepthGroup(gpuBatch, animation, cutoutTexture)
|
|
5197
|
+
: cutoutTexture === null
|
|
5198
|
+
? null
|
|
5199
|
+
: this.cutoutGroup(cutoutTexture);
|
|
5200
|
+
if (animation !== null && cutoutGroup === null)
|
|
5201
|
+
return;
|
|
5023
5202
|
const slot = this.shadowDraws.allocate();
|
|
5024
5203
|
if (slot === null)
|
|
5025
5204
|
return;
|
|
5026
5205
|
/* Only the light matrix: the instanced depth variant has no uModel, its placement being
|
|
5027
5206
|
the four attribute columns the batch's buffer supplies. */
|
|
5028
5207
|
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uLightViewProj.offset, this.correctedLightViewProj);
|
|
5029
|
-
|
|
5208
|
+
const cuts = cutoutTexture !== null && cutoutGroup !== null;
|
|
5209
|
+
if (cutout !== null && cuts) {
|
|
5030
5210
|
this.writeCutout(slot, cutout, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS);
|
|
5031
5211
|
}
|
|
5032
|
-
const
|
|
5212
|
+
const clip = gpuBatch.animation;
|
|
5213
|
+
if (animation !== null && clip !== null) {
|
|
5214
|
+
const fields = cuts
|
|
5215
|
+
? DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS
|
|
5216
|
+
: DEPTH_INSTANCED_ANIMATED_VERT_FIELDS;
|
|
5217
|
+
this.shadowDraws.writeFloat(slot, fields.uSceneTime.offset, this.animationTime);
|
|
5218
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset, clip.framesPerSecond);
|
|
5219
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset + 4, clip.boneScale);
|
|
5220
|
+
}
|
|
5033
5221
|
const cull = material?.doubleSided === true ? 'none' : this.depthCullMode;
|
|
5034
|
-
|
|
5222
|
+
const animated = animation !== null;
|
|
5223
|
+
pass.setPipeline(depthPipeline(this.pipelines, this.surface.device, animated
|
|
5224
|
+
? cuts
|
|
5225
|
+
? this.shadowAnimatedCutoutLayout
|
|
5226
|
+
: this.shadowAnimatedLayout
|
|
5227
|
+
: cuts
|
|
5228
|
+
? this.shadowCutoutLayout
|
|
5229
|
+
: this.shadowLayout, `${cuts ? 'depth-instanced-cutout' : 'depth-instanced'}${animated ? '-animated' : ''}` +
|
|
5230
|
+
`|${cull}|${geometry.key ?? ''}`, this.presentOf(geometry), cull, false, true, cuts, false, false, animated));
|
|
5035
5231
|
pass.setBindGroup(0, cutoutGroup ?? this.shadowGroup(), [slot]);
|
|
5036
5232
|
let index = 0;
|
|
5037
5233
|
for (; index < geometry.vertexBuffers.length; index++) {
|
|
5038
5234
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
5039
5235
|
}
|
|
5040
5236
|
pass.setVertexBuffer(index, gpuBatch.buffer);
|
|
5041
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
5237
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
5042
5238
|
pass.drawIndexed(geometry.indexCount, count);
|
|
5043
5239
|
},
|
|
5044
5240
|
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
@@ -5081,7 +5277,7 @@ export class WebGPURenderer {
|
|
|
5081
5277
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
5082
5278
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
5083
5279
|
}
|
|
5084
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
5280
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
5085
5281
|
pass.drawIndexed(geometry.indexCount);
|
|
5086
5282
|
},
|
|
5087
5283
|
/**
|
|
@@ -5452,6 +5648,8 @@ export class WebGPURenderer {
|
|
|
5452
5648
|
this.overlayPipelines === this.pipelines
|
|
5453
5649
|
? [this.pipelines]
|
|
5454
5650
|
: [this.pipelines, this.overlayPipelines];
|
|
5651
|
+
/* A lit draw first needed inside a frame waits for its compile or is left out while it runs. */
|
|
5652
|
+
this.skipCompiling(quality.pipelineCompile === 'skip');
|
|
5455
5653
|
this.variant = flatVariant({
|
|
5456
5654
|
directionalShadows: quality.directionalShadows,
|
|
5457
5655
|
/* No `bakeReflectionProbe` here; see the field. */
|
|
@@ -5472,7 +5670,7 @@ export class WebGPURenderer {
|
|
|
5472
5670
|
pointShadows: quality.pointShadows,
|
|
5473
5671
|
});
|
|
5474
5672
|
this.pointShadowsCompiled = quality.pointShadows;
|
|
5475
|
-
this.fragment =
|
|
5673
|
+
this.fragment = flatFragmentScratch(this.variant);
|
|
5476
5674
|
this.bindGroupLayout = createFlatBindGroupLayout(device, this.variant);
|
|
5477
5675
|
/*
|
|
5478
5676
|
* The skinned twin, built eagerly because it is a descriptor rather than a compile — WebGPU
|
|
@@ -5482,24 +5680,32 @@ export class WebGPURenderer {
|
|
|
5482
5680
|
this.skinnedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true);
|
|
5483
5681
|
this.morphedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, false, true);
|
|
5484
5682
|
this.bothBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true, true);
|
|
5683
|
+
/* And the instanced twin that plays a bone animation: its clip's and clocks' textures. */
|
|
5684
|
+
this.animatedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, false, false, true);
|
|
5485
5685
|
this.perDraw = new UniformRing(device, FLAT_VERT_SIZE, quality.drawsPerFrame, USAGE_UNIFORM_DST, 'flat.vertRing', this.drawBudget);
|
|
5486
|
-
|
|
5686
|
+
const { passSize, materialSize } = this.fragment;
|
|
5687
|
+
this.perFrameStaging = new ArrayBuffer(passSize + materialSize);
|
|
5487
5688
|
this.perFrameFloats = new Float32Array(this.perFrameStaging);
|
|
5488
5689
|
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
|
-
|
|
5690
|
+
this.passInts = new Int32Array(this.perFrameStaging, 0, passSize / 4);
|
|
5691
|
+
this.materialInts = new Int32Array(this.perFrameStaging, passSize, materialSize / 4);
|
|
5692
|
+
/*
|
|
5693
|
+
* **Two blocks, because they change at two rates.** `setSurfaceTexture`, `setSurfaceGrain` and
|
|
5694
|
+
* their siblings are pass state on both backends — a material covers many draws — and on WebGL2
|
|
5695
|
+
* each is a `uniform1f` that lands immediately. Here they live in a block, and
|
|
5696
|
+
* `queue.writeBuffer` does not interleave with recorded commands, so one buffer rewritten between
|
|
5697
|
+
* two draws gives *both* the second value: every surface would wear the last material set, which
|
|
5698
|
+
* is bug 7's shape at pass scale. So each draw binds a slot.
|
|
5699
|
+
*
|
|
5700
|
+
* It was one block until 4.10.0, lights and all, copied whole into a ring slot per material
|
|
5701
|
+
* change and uploaded every frame: a stage of four hundred materials sent 4.8 MB a frame to say
|
|
5702
|
+
* what it had said the frame before. Now the pass's block takes a ring slot per pass, and a
|
|
5703
|
+
* material's block a slot of the store, where an unchanged material stays from frame to frame.
|
|
5704
|
+
*/
|
|
5705
|
+
this.passRing = new UniformRing(device, passSize, PASSES_PER_FRAME, USAGE_UNIFORM_DST, 'flat.passRing');
|
|
5706
|
+
this.materialStore = new MaterialStore(device, materialSize, this.quality.materialChangesPerFrame, USAGE_UNIFORM_DST, 'flat.materials', this.materialBudget);
|
|
5707
|
+
/* Two a pass at most: the jittered view and the unjittered one. */
|
|
5708
|
+
this.viewRing = new UniformRing(device, FLAT_VIEW_SIZE, 2 * PASSES_PER_FRAME, USAGE_UNIFORM_DST, 'flat.viewRing');
|
|
5503
5709
|
/*
|
|
5504
5710
|
* A one-pixel stand-in for the albedo binding. The albedo's flag is zero for untextured
|
|
5505
5711
|
* geometry, which is nearly all of it, so the shader never samples this — but WebGPU
|
|
@@ -5632,7 +5838,7 @@ export class WebGPURenderer {
|
|
|
5632
5838
|
})
|
|
5633
5839
|
.end();
|
|
5634
5840
|
}
|
|
5635
|
-
|
|
5841
|
+
this.submit([clear.finish()]);
|
|
5636
5842
|
/*
|
|
5637
5843
|
* `nearest` on a depth texture sampled as an ordinary float, matching what
|
|
5638
5844
|
* `shadowMap.ts` asks WebGL2 for. A filtering sampler cannot be used on a depth format
|
|
@@ -6120,6 +6326,8 @@ export class WebGPURenderer {
|
|
|
6120
6326
|
this.shadowLayout = createDepthBindGroupLayout(device);
|
|
6121
6327
|
this.shadowSkinnedLayout = createDepthBindGroupLayout(device, true);
|
|
6122
6328
|
this.shadowCutoutLayout = createDepthBindGroupLayout(device, false, true);
|
|
6329
|
+
this.shadowAnimatedLayout = createDepthBindGroupLayout(device, false, false, true);
|
|
6330
|
+
this.shadowAnimatedCutoutLayout = createDepthBindGroupLayout(device, false, true, true);
|
|
6123
6331
|
this.shadowDraws = new UniformRing(device,
|
|
6124
6332
|
/* The cutout block is the plain one and two fields more, and a slot must hold either. */
|
|
6125
6333
|
Math.max(DEPTH_VERT_SIZE, DEPTH_CUTOUT_VERT_SIZE), quality.drawsPerFrame, USAGE_UNIFORM_DST, 'shadow.drawRing', this.shadowDrawBudget);
|
|
@@ -6309,6 +6517,8 @@ export class WebGPURenderer {
|
|
|
6309
6517
|
present['layers'] = key.includes(':layers');
|
|
6310
6518
|
/* Nor is a dynamic mesh's split, which moves positions and normals into buffers of their own. */
|
|
6311
6519
|
present['dynamic'] = key.includes(':dynamic');
|
|
6520
|
+
/* Nor how each attribute travels, which moves every field after a packed one. */
|
|
6521
|
+
markPackingOfKey(present, key);
|
|
6312
6522
|
return present;
|
|
6313
6523
|
}
|
|
6314
6524
|
/**
|
|
@@ -6418,9 +6628,18 @@ export class WebGPURenderer {
|
|
|
6418
6628
|
this.lineVerts.reset();
|
|
6419
6629
|
this.lineFrags.reset();
|
|
6420
6630
|
this.skyUniforms.reset();
|
|
6421
|
-
/* The
|
|
6422
|
-
|
|
6631
|
+
/* The pass ring, and the open slots with it: last frame's pass slots are gone. Its materials
|
|
6632
|
+
stay in the store, which a draw finds them in again. */
|
|
6633
|
+
this.passRing.reset();
|
|
6634
|
+
this.viewRing.reset();
|
|
6635
|
+
this.materialStore.beginFrame();
|
|
6636
|
+
this.passesAsked = 0;
|
|
6637
|
+
this.passesRefused = 0;
|
|
6638
|
+
this.viewsAsked = 0;
|
|
6639
|
+
this.viewsRefused = 0;
|
|
6640
|
+
this.materialsRefused = 0;
|
|
6423
6641
|
this.materials.dirty();
|
|
6642
|
+
this.reopenPass();
|
|
6424
6643
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
6425
6644
|
this.motionPass?.reset();
|
|
6426
6645
|
this.latePass?.reset();
|
|
@@ -6603,6 +6822,13 @@ export class WebGPURenderer {
|
|
|
6603
6822
|
present['dynamic'] = true;
|
|
6604
6823
|
key += ':dynamic';
|
|
6605
6824
|
}
|
|
6825
|
+
/*
|
|
6826
|
+
* And how each attribute travels (`vertexPacking.ts`): a field read as sixteen bits rather than
|
|
6827
|
+
* thirty-two, or not interleaved at all, moves every field after it, so the key says so.
|
|
6828
|
+
*/
|
|
6829
|
+
const packing = vertexPackingOf(data, dynamic);
|
|
6830
|
+
markPacking(present, packing);
|
|
6831
|
+
key += packingKey(packing);
|
|
6606
6832
|
const fullKey = `${this.variant}|${key}`;
|
|
6607
6833
|
/*
|
|
6608
6834
|
* **For every target this mesh can land on, which is two when the overlay is its own.**
|
|
@@ -6718,6 +6944,37 @@ export class WebGPURenderer {
|
|
|
6718
6944
|
},
|
|
6719
6945
|
};
|
|
6720
6946
|
}
|
|
6947
|
+
/**
|
|
6948
|
+
* The copies this frame's mesh rewrites owe (`StagedRewrite`), recorded since the last submit and
|
|
6949
|
+
* sent at the head of the next one: one encoder however many meshes are rewritten. Null while
|
|
6950
|
+
* nothing is owed.
|
|
6951
|
+
*/
|
|
6952
|
+
pendingRewrites = null;
|
|
6953
|
+
/** One record handed to every staged rewrite, refilled each call, so a rewrite allocates nothing. */
|
|
6954
|
+
stagedRewrite = {
|
|
6955
|
+
encoder: null,
|
|
6956
|
+
staging: null,
|
|
6957
|
+
previous: null,
|
|
6958
|
+
};
|
|
6959
|
+
rewriteCopies() {
|
|
6960
|
+
this.pendingRewrites ??= this.surface.device.createCommandEncoder({
|
|
6961
|
+
label: 'mesh.rewriteCopies',
|
|
6962
|
+
});
|
|
6963
|
+
return this.pendingRewrites;
|
|
6964
|
+
}
|
|
6965
|
+
/**
|
|
6966
|
+
* Submit `buffers`, led by any copies the frame's mesh rewrites owe. **Every submit of this
|
|
6967
|
+
* renderer goes through here**, because any of them may draw a rewritten mesh — the frame's own,
|
|
6968
|
+
* a mirror's, a shadow's, a probe's — and the copies must be ahead of the first that does.
|
|
6969
|
+
*/
|
|
6970
|
+
submit(buffers) {
|
|
6971
|
+
const rewrites = this.pendingRewrites;
|
|
6972
|
+
if (rewrites !== null) {
|
|
6973
|
+
this.pendingRewrites = null;
|
|
6974
|
+
buffers.unshift(rewrites.finish());
|
|
6975
|
+
}
|
|
6976
|
+
this.surface.device.queue.submit(buffers);
|
|
6977
|
+
}
|
|
6721
6978
|
/**
|
|
6722
6979
|
* Rewrite a mesh's positions, and its normals where the caller has them.
|
|
6723
6980
|
*
|
|
@@ -6738,20 +6995,35 @@ export class WebGPURenderer {
|
|
|
6738
6995
|
* `changeFrames.ts`.
|
|
6739
6996
|
*/
|
|
6740
6997
|
const motion = mesh.motion;
|
|
6741
|
-
let
|
|
6998
|
+
let staged = null;
|
|
6742
6999
|
if (motion !== null && this.reconstructionWanted) {
|
|
6743
7000
|
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
7001
|
+
const size = mesh.vertexBuffers[0]?.size ?? 0;
|
|
7002
|
+
let previous = null;
|
|
6744
7003
|
if (keepsPrevious(motion.changed, frame)) {
|
|
6745
7004
|
motion.previous ??= this.surface.device.createBuffer({
|
|
6746
7005
|
label: 'recon.dynamicPrevious',
|
|
6747
|
-
size
|
|
7006
|
+
size,
|
|
6748
7007
|
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
6749
7008
|
});
|
|
6750
7009
|
previous = motion.previous;
|
|
6751
7010
|
}
|
|
6752
7011
|
motion.changed = frame;
|
|
7012
|
+
/*
|
|
7013
|
+
* **Staged, and copied in behind the copy aside, in an encoder the frame's next submit leads
|
|
7014
|
+
* with**: no submit of the rewrite's own. See `StagedRewrite` and `submit`.
|
|
7015
|
+
*/
|
|
7016
|
+
motion.staging ??= this.surface.device.createBuffer({
|
|
7017
|
+
label: 'mesh.stagedPositions',
|
|
7018
|
+
size,
|
|
7019
|
+
usage: 0x0004 | 0x0008, // COPY_SRC | COPY_DST
|
|
7020
|
+
});
|
|
7021
|
+
staged = this.stagedRewrite;
|
|
7022
|
+
staged.encoder = this.rewriteCopies();
|
|
7023
|
+
staged.staging = motion.staging;
|
|
7024
|
+
staged.previous = previous;
|
|
6753
7025
|
}
|
|
6754
|
-
mesh.update(this.surface.device, positions, normals,
|
|
7026
|
+
mesh.update(this.surface.device, positions, normals, staged);
|
|
6755
7027
|
}
|
|
6756
7028
|
/**
|
|
6757
7029
|
* Settle everything the frame shares: the camera, the lights and the fog.
|
|
@@ -6764,6 +7036,8 @@ export class WebGPURenderer {
|
|
|
6764
7036
|
bindMeshPass(camera, env) {
|
|
6765
7037
|
if (this.surface.lost)
|
|
6766
7038
|
return;
|
|
7039
|
+
/* The matrix every receiver projects through: the environment's, as WebGL2's. See the field. */
|
|
7040
|
+
this.receiverLightViewProj.set(env.lightViewProj);
|
|
6767
7041
|
/* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
|
|
6768
7042
|
if (!this.warnedAreaOverflow &&
|
|
6769
7043
|
!this.quality.clusteredLights &&
|
|
@@ -6878,6 +7152,12 @@ export class WebGPURenderer {
|
|
|
6878
7152
|
this.surfaceFog = FOG_RECEDE;
|
|
6879
7153
|
/* Every material switch off — albedo, ORM map, emissive map, two-sided — as WebGL2 resets them. */
|
|
6880
7154
|
i.fill(0, at('uMaterialFlags'), at('uMaterialFlags') + 4);
|
|
7155
|
+
/* And no projection: maps follow the mesh's coordinates until a material asks otherwise. */
|
|
7156
|
+
f.fill(0, at('uWorldUv'), at('uWorldUv') + 4);
|
|
7157
|
+
/* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
|
|
7158
|
+
f[at('uGlassTint') + 3] = 0;
|
|
7159
|
+
/* Nor layers: one layer until a material asks for more. */
|
|
7160
|
+
f.fill(0, at('uLayers'), at('uLayers') + LAYER_FLOATS);
|
|
6881
7161
|
f[at('uCutout')] = 0;
|
|
6882
7162
|
f[at('uCutout') + 1] = 0;
|
|
6883
7163
|
this.cutoutResolveStaged = 'hard';
|
|
@@ -6941,10 +7221,16 @@ export class WebGPURenderer {
|
|
|
6941
7221
|
* number, so this is a copy and is marked as one; the two must move together.
|
|
6942
7222
|
*/
|
|
6943
7223
|
f[at('uGrain')] = 1;
|
|
7224
|
+
/* No reflection and an environment at its own brightness, from the pass and from no material. */
|
|
7225
|
+
this.passReflectivity = 0;
|
|
7226
|
+
this.passEnvironmentGain = 1;
|
|
7227
|
+
this.materialReflectivity = null;
|
|
7228
|
+
this.materialEnvironmentGain = null;
|
|
6944
7229
|
f[at('uEnvironmentDials') + 1] = 0;
|
|
6945
7230
|
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
6946
7231
|
f[at('uWriteMode')] = 0;
|
|
6947
7232
|
f[at('uWriteMode') + 1] = 0;
|
|
7233
|
+
f[at('uWriteMode') + 2] = 0;
|
|
6948
7234
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
6949
7235
|
f[at('uEnvironmentDials') + 2] = 1;
|
|
6950
7236
|
/* No overlay until a draw sets one. See `setSurfaceOverlay`. */
|
|
@@ -7051,6 +7337,8 @@ export class WebGPURenderer {
|
|
|
7051
7337
|
if (this.quality.nightEmissive)
|
|
7052
7338
|
f[at('uNightEmissive')] = env.nightEmissive ?? 0;
|
|
7053
7339
|
f.set(env.highlightMin, at('uHighlightMin'));
|
|
7340
|
+
/* The frame the cutout dither moves with, in the near corner's w. See `uHighlightMin`. */
|
|
7341
|
+
f[at('uHighlightMin') + 3] = this.cutoutFrame;
|
|
7054
7342
|
f.set(env.highlightMax, at('uHighlightMax'));
|
|
7055
7343
|
/* The gain in the far corner's w. See `uHighlightMax`. */
|
|
7056
7344
|
f[at('uHighlightMax') + 3] = env.highlightGain;
|
|
@@ -7223,6 +7511,11 @@ export class WebGPURenderer {
|
|
|
7223
7511
|
f[at('uShadowStrength')] = env.shadowStrength;
|
|
7224
7512
|
f[at('uShadowMapSize')] = this.quality.directionalShadowMapSize;
|
|
7225
7513
|
f[at('uShadowDepthSpan')] = env.shadowDepthSpan;
|
|
7514
|
+
/* The moving layer's own matrix and span, where the lit stage reads one: `movingLightFor`. */
|
|
7515
|
+
this.enableMovingSun(env);
|
|
7516
|
+
const moving = this.movingLightFor(env);
|
|
7517
|
+
f.set(moving, at('uMovingLightViewProj'));
|
|
7518
|
+
f[at('uMovingShadowDepthSpan')] = orthographicDepthSpan(moving);
|
|
7226
7519
|
f[at('uShadowMaxDistance')] = this.quality.directionalShadowMaxDistance;
|
|
7227
7520
|
f[at('uShadowMaxSlope')] = this.quality.directionalShadowMaxSlope;
|
|
7228
7521
|
/* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
|
|
@@ -7303,6 +7596,7 @@ export class WebGPURenderer {
|
|
|
7303
7596
|
* same terms here, which is why a pass cannot inherit a material from the one before it.
|
|
7304
7597
|
*/
|
|
7305
7598
|
this.materials.dirty();
|
|
7599
|
+
this.reopenPass();
|
|
7306
7600
|
/*
|
|
7307
7601
|
* A pass starts with no material, exactly as `renderer.ts` resets these terms — **every map,
|
|
7308
7602
|
* not the albedo alone**. `setMaterial` skips the lookup when the maps it is given are the ones
|
|
@@ -7326,29 +7620,104 @@ export class WebGPURenderer {
|
|
|
7326
7620
|
this.uvOffset[1] = 0;
|
|
7327
7621
|
}
|
|
7328
7622
|
/**
|
|
7329
|
-
* The slot the next draw's material lives in,
|
|
7623
|
+
* The slot the next draw's material lives in, placing it when something dirtied it, with the
|
|
7624
|
+
* pass's slot taken first where `bindMeshPass` reopened it: `passOffset` is that one's offset.
|
|
7330
7625
|
*
|
|
7331
|
-
* **A
|
|
7332
|
-
* material
|
|
7333
|
-
*
|
|
7626
|
+
* **A placement per material change, not per draw.** A scene drawing a hundred meshes of one
|
|
7627
|
+
* material places it once; a scene switching material every draw places one each, and a material
|
|
7628
|
+
* the store already holds — the same numbers as a draw before it, this frame or an earlier one —
|
|
7629
|
+
* is bound where it is and uploads nothing.
|
|
7334
7630
|
*/
|
|
7335
7631
|
materialSlotForDraw() {
|
|
7632
|
+
if (!this.passBlock.open) {
|
|
7633
|
+
this.passesAsked += 1;
|
|
7634
|
+
const pass = this.passRing.allocate();
|
|
7635
|
+
if (pass === null) {
|
|
7636
|
+
this.passesRefused += 1;
|
|
7637
|
+
this.materialBudget.ask();
|
|
7638
|
+
this.materialBudget.drop();
|
|
7639
|
+
if (!this.warnedPassesFull) {
|
|
7640
|
+
this.warnedPassesFull = true;
|
|
7641
|
+
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.`);
|
|
7642
|
+
}
|
|
7643
|
+
return null;
|
|
7644
|
+
}
|
|
7645
|
+
this.passRing.writeBlock(pass, this.passInts);
|
|
7646
|
+
this.passBlock.slot = pass;
|
|
7647
|
+
}
|
|
7336
7648
|
if (this.materials.open)
|
|
7337
7649
|
return this.materials.slot;
|
|
7338
|
-
const slot = this.
|
|
7650
|
+
const slot = this.materialStore.place(this.materialInts);
|
|
7339
7651
|
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
|
-
}
|
|
7652
|
+
this.materialsRefused += 1;
|
|
7653
|
+
this.warnMaterialsFull();
|
|
7344
7654
|
return null;
|
|
7345
7655
|
}
|
|
7346
|
-
this.perFrame.writeBlock(slot, this.perFrameInts);
|
|
7347
7656
|
this.materials.slot = slot;
|
|
7348
7657
|
return slot;
|
|
7349
7658
|
}
|
|
7659
|
+
/** The pass slot `materialSlotForDraw` left open, read beside the material's by every lit draw. */
|
|
7660
|
+
get passOffset() {
|
|
7661
|
+
return this.passBlock.slot;
|
|
7662
|
+
}
|
|
7663
|
+
/** A new pass: its block, and both its views, are taken afresh by the next draw. */
|
|
7664
|
+
reopenPass() {
|
|
7665
|
+
this.passBlock.dirty();
|
|
7666
|
+
this.reopenViews();
|
|
7667
|
+
}
|
|
7668
|
+
/** What a view holds changed — the wind, the light's matrix — so the next draw takes a new one. */
|
|
7669
|
+
reopenViews() {
|
|
7670
|
+
this.viewBlock.dirty();
|
|
7671
|
+
this.lateViewBlock.dirty();
|
|
7672
|
+
}
|
|
7673
|
+
/**
|
|
7674
|
+
* The view slot a lit draw reads its camera from: the pass's, or its unjittered twin for a blended
|
|
7675
|
+
* draw landing after the upscale. Taken by the first draw that needs it after the pass, the wind or
|
|
7676
|
+
* the light's matrix changed, and shared by every draw until then. Null when the ring is full, which
|
|
7677
|
+
* skips the draw and grows the ring for the next frame, as the pass ring does.
|
|
7678
|
+
*/
|
|
7679
|
+
viewSlotFor(late, viewProj) {
|
|
7680
|
+
const block = late ? this.lateViewBlock : this.viewBlock;
|
|
7681
|
+
if (block.open)
|
|
7682
|
+
return block.slot;
|
|
7683
|
+
this.viewsAsked += 1;
|
|
7684
|
+
const slot = this.viewRing.allocate();
|
|
7685
|
+
if (slot === null) {
|
|
7686
|
+
this.viewsRefused += 1;
|
|
7687
|
+
this.materialBudget.ask();
|
|
7688
|
+
this.materialBudget.drop();
|
|
7689
|
+
if (!this.warnedPassesFull) {
|
|
7690
|
+
this.warnedPassesFull = true;
|
|
7691
|
+
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.`);
|
|
7692
|
+
}
|
|
7693
|
+
return null;
|
|
7694
|
+
}
|
|
7695
|
+
this.writeView(this.viewRing, slot, viewProj);
|
|
7696
|
+
block.slot = slot;
|
|
7697
|
+
return slot;
|
|
7698
|
+
}
|
|
7699
|
+
/** A view block: the camera, the light's matrix and the frame's wind, into `ring` at `slot`. */
|
|
7700
|
+
writeView(ring, slot, viewProj) {
|
|
7701
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uViewProj.offset, viewProj);
|
|
7702
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.receiverLightViewProj);
|
|
7703
|
+
/* The scene's clock, which a bone animation's instances read their moment from. */
|
|
7704
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uSceneTime.offset, this.animationTime);
|
|
7705
|
+
const d = this.frameWind;
|
|
7706
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindDirection.offset, d.direction);
|
|
7707
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindSpeed.offset, d.bend);
|
|
7708
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindGust.offset, d.gust);
|
|
7709
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindTime.offset, d.time);
|
|
7710
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
7711
|
+
}
|
|
7712
|
+
warnMaterialsFull() {
|
|
7713
|
+
if (this.warnedMaterialsFull)
|
|
7714
|
+
return;
|
|
7715
|
+
this.warnedMaterialsFull = true;
|
|
7716
|
+
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.`);
|
|
7717
|
+
}
|
|
7350
7718
|
/** Said once rather than every frame, for the reason `warnedFull` gives. */
|
|
7351
7719
|
warnedMaterialsFull = false;
|
|
7720
|
+
warnedPassesFull = false;
|
|
7352
7721
|
warnedSkyFull = false;
|
|
7353
7722
|
warnedParticlesTwice = false;
|
|
7354
7723
|
/**
|
|
@@ -7383,8 +7752,12 @@ export class WebGPURenderer {
|
|
|
7383
7752
|
* What this saves is the GPU's share, which for a heavy mesh is most of the cost and for a
|
|
7384
7753
|
* trivial one is very little.
|
|
7385
7754
|
*/
|
|
7386
|
-
|
|
7755
|
+
/* A static list draws every entry in every view, since a bundle cannot leave one out. */
|
|
7756
|
+
if (this.quality.cullDraws &&
|
|
7757
|
+
this.staticRecording === null &&
|
|
7758
|
+
!this.visible(mesh.bounds, model)) {
|
|
7387
7759
|
return;
|
|
7760
|
+
}
|
|
7388
7761
|
/*
|
|
7389
7762
|
* And the other half of the same question, where the profile asked for one. Gated on
|
|
7390
7763
|
* `cullDraws` alongside the frustum test, because both are the renderer doing a consumer's
|
|
@@ -7549,17 +7922,17 @@ export class WebGPURenderer {
|
|
|
7549
7922
|
`TranslucentMeshOptions.reconstructed`. */
|
|
7550
7923
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
7551
7924
|
const late = lands || (blend && this.oitReplaying && this.reconstructing);
|
|
7552
|
-
|
|
7925
|
+
/* The camera, the light's matrix and the wind are the pass's, in its view block. */
|
|
7926
|
+
const view = this.viewSlotFor(late, late ? this.correctedViewProj : this.viewProj);
|
|
7927
|
+
if (view === null)
|
|
7928
|
+
return;
|
|
7929
|
+
this.drawViewOffset = view;
|
|
7553
7930
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
7554
7931
|
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
7932
|
/* The material's repeats, not a constant: geometry authored in metres is textured at
|
|
7559
7933
|
whatever density the material asks for without rebuilding it. */
|
|
7560
7934
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
7561
7935
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
7562
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
7563
7936
|
/*
|
|
7564
7937
|
* Whether this mesh's tangent frame is real. WebGPU has no disabled attribute, so location 10
|
|
7565
7938
|
* is always backed — by the caller's data or by the constant buffer — which makes the binding
|
|
@@ -7696,14 +8069,20 @@ export class WebGPURenderer {
|
|
|
7696
8069
|
* thrown error in the middle of one.
|
|
7697
8070
|
*/
|
|
7698
8071
|
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
7699
|
-
let pipeline = pipelines.peek(key);
|
|
7700
|
-
if (pipeline ===
|
|
8072
|
+
let pipeline = pipelines.peek(key) ?? null;
|
|
8073
|
+
if (pipeline === null) {
|
|
7701
8074
|
const present = this.meshPresent.get(base);
|
|
7702
8075
|
if (present === undefined) {
|
|
7703
8076
|
throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
|
|
7704
8077
|
}
|
|
7705
8078
|
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, adds ? 'additive' : blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided, coverage, eight, clothed, modelKind, splits ? 'scene' : 'whole');
|
|
7706
8079
|
}
|
|
8080
|
+
/* A split skin draws its three halves or none of them. */
|
|
8081
|
+
if (pipeline !== null &&
|
|
8082
|
+
splits &&
|
|
8083
|
+
!this.skinHalvesReady(key, base, skinned, morphed, layer, eight, clothed)) {
|
|
8084
|
+
pipeline = null;
|
|
8085
|
+
}
|
|
7707
8086
|
/*
|
|
7708
8087
|
* Guards the draw call only, not the restore below.
|
|
7709
8088
|
*
|
|
@@ -7720,8 +8099,9 @@ export class WebGPURenderer {
|
|
|
7720
8099
|
* close to the 256-slot budget before the gateway, the play controls, the waveform, the
|
|
7721
8100
|
* label handles and the sparkles are even counted.
|
|
7722
8101
|
*/
|
|
7723
|
-
|
|
7724
|
-
|
|
8102
|
+
/* A draw whose pipeline is still compiling is left out, its state restored all the same. */
|
|
8103
|
+
const material = pipeline === null ? null : this.materialSlotForDraw();
|
|
8104
|
+
if (pipeline !== null && material !== null) {
|
|
7725
8105
|
/*
|
|
7726
8106
|
* Record rather than issue. Every per-draw value is already in `slot` and `material` —
|
|
7727
8107
|
* the uniform rings are the state arena this design would otherwise have had to build —
|
|
@@ -7734,13 +8114,16 @@ export class WebGPURenderer {
|
|
|
7734
8114
|
command.pipeline = pipeline;
|
|
7735
8115
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
7736
8116
|
command.offsetA = slot;
|
|
7737
|
-
command.offsetB =
|
|
7738
|
-
command.
|
|
8117
|
+
command.offsetB = this.passOffset;
|
|
8118
|
+
command.offsetC = material;
|
|
8119
|
+
command.offsetD = this.drawViewOffset;
|
|
8120
|
+
command.offsetCount = 4;
|
|
7739
8121
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
7740
8122
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
7741
8123
|
}
|
|
7742
8124
|
command.vertexCount = mesh.vertexBuffers.length;
|
|
7743
8125
|
command.indexBuffer = mesh.indexBuffer;
|
|
8126
|
+
command.indexFormat = mesh.indexFormat;
|
|
7744
8127
|
command.indexed = true;
|
|
7745
8128
|
command.count = mesh.indexCount;
|
|
7746
8129
|
}
|
|
@@ -7749,11 +8132,16 @@ export class WebGPURenderer {
|
|
|
7749
8132
|
if (pass === null)
|
|
7750
8133
|
return;
|
|
7751
8134
|
pass.setPipeline(pipeline);
|
|
7752
|
-
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
8135
|
+
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
8136
|
+
slot,
|
|
8137
|
+
this.passOffset,
|
|
8138
|
+
material,
|
|
8139
|
+
this.drawViewOffset,
|
|
8140
|
+
]);
|
|
7753
8141
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
7754
8142
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
|
|
7755
8143
|
}
|
|
7756
|
-
pass.setIndexBuffer(mesh.indexBuffer,
|
|
8144
|
+
pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
|
|
7757
8145
|
pass.drawIndexed(mesh.indexCount);
|
|
7758
8146
|
}
|
|
7759
8147
|
if (splits) {
|
|
@@ -7829,9 +8217,87 @@ export class WebGPURenderer {
|
|
|
7829
8217
|
'an ordinary mesh, or drop the channel — the sway, sky and alpha lanes cannot reach an ' +
|
|
7830
8218
|
'instanced pipeline.');
|
|
7831
8219
|
}
|
|
7832
|
-
const
|
|
8220
|
+
const animation = (options.animation ?? null);
|
|
8221
|
+
/* The bone a vertex follows is its second coordinates' u, which a mesh carries in its grain
|
|
8222
|
+
lane; a mesh with none has nothing to name a bone by. See `boneAnimation.ts`. */
|
|
8223
|
+
if (animation !== null && !this.presentOf(mesh)['grain']) {
|
|
8224
|
+
throw new Error('WebGPU: a bone animation reads each vertex’s bone from the mesh’s second coordinates ' +
|
|
8225
|
+
'(`MeshData.lightmapUvs`), and this mesh has none.');
|
|
8226
|
+
}
|
|
8227
|
+
const batch = new GpuInstancedBatch(this.surface.device, mesh, capacity, `flat.instances:${mesh.key ?? 'mesh'}`, options.cull ?? false, animation);
|
|
7833
8228
|
return batch;
|
|
7834
8229
|
}
|
|
8230
|
+
/** See `Renderer.setAnimationTime`. Every view taken after it reads the new clock. */
|
|
8231
|
+
setAnimationTime(seconds) {
|
|
8232
|
+
if (!Number.isFinite(seconds) || seconds === this.animationTime)
|
|
8233
|
+
return;
|
|
8234
|
+
this.animationTime = seconds;
|
|
8235
|
+
this.reopenViews();
|
|
8236
|
+
}
|
|
8237
|
+
/** The clock `setAnimationTime` set, 0 until it is. */
|
|
8238
|
+
animationTime = 0;
|
|
8239
|
+
/** See `Renderer.createBoneAnimation`. */
|
|
8240
|
+
createBoneAnimation(clip) {
|
|
8241
|
+
return new GpuBoneAnimation(this.surface.device, packBoneAnimation(clip));
|
|
8242
|
+
}
|
|
8243
|
+
/** See `Renderer.disposeBoneAnimation`. A batch still playing it draws nothing after. */
|
|
8244
|
+
disposeBoneAnimation(animation) {
|
|
8245
|
+
const clip = animation;
|
|
8246
|
+
if (!clip.disposed)
|
|
8247
|
+
clip.dispose();
|
|
8248
|
+
}
|
|
8249
|
+
/** An animated batch's clip and clocks, as its groups bind them. */
|
|
8250
|
+
animationViews(batch) {
|
|
8251
|
+
const animation = batch.animation;
|
|
8252
|
+
const clocks = batch.clocks;
|
|
8253
|
+
if (animation === null || clocks === null || animation.disposed)
|
|
8254
|
+
return null;
|
|
8255
|
+
return { places: animation.placesView, turns: animation.turnsView, clocks: clocks.view };
|
|
8256
|
+
}
|
|
8257
|
+
/** An animated batch's groups, dropped wherever the renderer drops its own. */
|
|
8258
|
+
freshAnimatedGroups(batch) {
|
|
8259
|
+
if (batch.groupEpoch === this.flatGroupEpoch)
|
|
8260
|
+
return;
|
|
8261
|
+
batch.litGroups.clear();
|
|
8262
|
+
batch.depthGroups.clear();
|
|
8263
|
+
batch.depthPeelGroups.clear();
|
|
8264
|
+
batch.groupEpoch = this.flatGroupEpoch;
|
|
8265
|
+
}
|
|
8266
|
+
/**
|
|
8267
|
+
* The lit group an animated batch draws with: the material's group, or its overlay twin, again
|
|
8268
|
+
* with the clip's and the clocks' textures — kept on the batch by the group it was built beside,
|
|
8269
|
+
* as a skinned twin is kept by its palette slot.
|
|
8270
|
+
*/
|
|
8271
|
+
animatedGroup(batch, views) {
|
|
8272
|
+
this.freshAnimatedGroups(batch);
|
|
8273
|
+
const base = this.overlayGroup();
|
|
8274
|
+
let group = batch.litGroups.get(base);
|
|
8275
|
+
if (group === undefined) {
|
|
8276
|
+
this.overlayBinding = base !== this.bindGroup;
|
|
8277
|
+
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);
|
|
8278
|
+
this.overlayBinding = false;
|
|
8279
|
+
this.bindGroupBudget.ask();
|
|
8280
|
+
batch.litGroups.set(base, group);
|
|
8281
|
+
}
|
|
8282
|
+
return group;
|
|
8283
|
+
}
|
|
8284
|
+
/** An animated caster's shadow group, by what it cuts with and whether this is the peel. */
|
|
8285
|
+
animatedDepthGroup(batch, views, cutout) {
|
|
8286
|
+
this.freshAnimatedGroups(batch);
|
|
8287
|
+
const peel = this.shadowLayerIsPeel && this.shadowPeelPassUniforms !== null;
|
|
8288
|
+
const groups = peel ? batch.depthPeelGroups : batch.depthGroups;
|
|
8289
|
+
const held = groups.get(cutout);
|
|
8290
|
+
if (held !== undefined)
|
|
8291
|
+
return held;
|
|
8292
|
+
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
8293
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
8294
|
+
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
8295
|
+
if (passUniforms === null || previousView === null || previousSampler === null)
|
|
8296
|
+
return null;
|
|
8297
|
+
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);
|
|
8298
|
+
groups.set(cutout, group);
|
|
8299
|
+
return group;
|
|
8300
|
+
}
|
|
7835
8301
|
/** The device instance culls, built on the first culling draw. See `InstanceCullPass`. */
|
|
7836
8302
|
instanceCulls = null;
|
|
7837
8303
|
/** Counts frames, so a batch knows when its cull slots were last taken. */
|
|
@@ -7864,7 +8330,10 @@ export class WebGPURenderer {
|
|
|
7864
8330
|
this.submitInstanced(batch, data, 1, false, {});
|
|
7865
8331
|
/* A batch that moved this frame pairs each slot with last frame's; the batch is the identity. */
|
|
7866
8332
|
const previous = batch.previousBuffer;
|
|
7867
|
-
if (previous !== null &&
|
|
8333
|
+
if (previous !== null &&
|
|
8334
|
+
this.staticRecording === null &&
|
|
8335
|
+
batch.changed === this.moverFrame &&
|
|
8336
|
+
this.tracksMotionNow()) {
|
|
7868
8337
|
const paired = pairedInstances(batch.previousCount, Math.min(data.count, batch.capacity));
|
|
7869
8338
|
if (paired > 0)
|
|
7870
8339
|
this.motionPass?.recordInstanced(batch.mesh, batch.buffer, previous, paired);
|
|
@@ -7905,6 +8374,8 @@ export class WebGPURenderer {
|
|
|
7905
8374
|
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
7906
8375
|
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
7907
8376
|
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
8377
|
+
/* w: this framebuffer's first row is the top, so up the screen is down the rows. */
|
|
8378
|
+
this.perFrameFloats[tintAt + 3] = -1;
|
|
7908
8379
|
const glassTintAt = this.materialField('uGlassTint');
|
|
7909
8380
|
this.perFrameFloats[glassTintAt] = glass.tint[0];
|
|
7910
8381
|
this.perFrameFloats[glassTintAt + 1] = glass.tint[1];
|
|
@@ -7969,10 +8440,17 @@ export class WebGPURenderer {
|
|
|
7969
8440
|
/* A culling batch's camera draw: the whole batch against the view and the occluders on the CPU,
|
|
7970
8441
|
then instance by instance on the device into an indirect draw. Blended draws keep every
|
|
7971
8442
|
instance and their order. See `InstancedOptions.cull`. */
|
|
7972
|
-
const culling = !blend && batch.cull;
|
|
8443
|
+
const culling = !blend && batch.cull && this.staticRecording === null;
|
|
7973
8444
|
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion))
|
|
7974
8445
|
return;
|
|
7975
|
-
|
|
8446
|
+
/* A batch playing a bone animation reads its clocks by instance index, which a compaction
|
|
8447
|
+
would reorder: it is culled whole and never instance by instance. */
|
|
8448
|
+
const animation = batch.animation === null ? null : this.animationViews(batch);
|
|
8449
|
+
if (batch.animation !== null && animation === null)
|
|
8450
|
+
return;
|
|
8451
|
+
const cut = culling && animation === null && cullsInstances(count, mesh.indexCount)
|
|
8452
|
+
? this.takeCullSlot(batch, count)
|
|
8453
|
+
: null;
|
|
7976
8454
|
const instanceBuffer = cut === null ? batch.buffer : cut.instances;
|
|
7977
8455
|
const slot = this.perDraw.allocate();
|
|
7978
8456
|
if (slot === null) {
|
|
@@ -7984,11 +8462,19 @@ export class WebGPURenderer {
|
|
|
7984
8462
|
}
|
|
7985
8463
|
/* Late unless it moves with what it lies on. See `TranslucentMeshOptions.reconstructed`. */
|
|
7986
8464
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
7987
|
-
this.
|
|
8465
|
+
const view = this.viewSlotFor(lands, lands ? this.correctedViewProj : this.viewProj);
|
|
8466
|
+
if (view === null)
|
|
8467
|
+
return;
|
|
8468
|
+
this.drawViewOffset = view;
|
|
7988
8469
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
7989
8470
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
7990
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
7991
8471
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
8472
|
+
/* The clip's speed and bone scale, read by the animated variant's own field. */
|
|
8473
|
+
const clip = batch.animation;
|
|
8474
|
+
if (animation !== null && clip !== null) {
|
|
8475
|
+
this.perDraw.writeFloat(slot, FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset, clip.framesPerSecond);
|
|
8476
|
+
this.perDraw.writeFloat(slot, FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset + 4, clip.boneScale);
|
|
8477
|
+
}
|
|
7992
8478
|
const dimmed = opacity < 1;
|
|
7993
8479
|
if (dimmed) {
|
|
7994
8480
|
this.perFrameFloats[this.materialField('uOpacity')] = opacity;
|
|
@@ -8025,30 +8511,36 @@ export class WebGPURenderer {
|
|
|
8025
8511
|
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
8026
8512
|
plain one would hand a draw the wrong layout. */
|
|
8027
8513
|
const adds = blend && options.additive === true;
|
|
8028
|
-
const key = `${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8514
|
+
const key = `${base}|inst${animation === null ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8029
8515
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8030
8516
|
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
8031
8517
|
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
8032
8518
|
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
8033
|
-
let pipeline = pipelines.peek(key);
|
|
8034
|
-
if (pipeline ===
|
|
8519
|
+
let pipeline = pipelines.peek(key) ?? null;
|
|
8520
|
+
if (pipeline === null) {
|
|
8035
8521
|
const present = this.meshPresent.get(base);
|
|
8036
8522
|
if (present === undefined) {
|
|
8037
8523
|
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
8038
8524
|
}
|
|
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);
|
|
8525
|
+
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
8526
|
}
|
|
8041
|
-
|
|
8042
|
-
|
|
8527
|
+
/* A batch whose pipeline is still compiling is left out, as a mesh is. */
|
|
8528
|
+
const material = pipeline === null ? null : this.materialSlotForDraw();
|
|
8529
|
+
if (pipeline !== null && material !== null) {
|
|
8043
8530
|
const command = blend
|
|
8044
8531
|
? this.recordBlended(0, lands)
|
|
8045
8532
|
: this.recordDraw(0, this.currentTarget());
|
|
8046
8533
|
if (command !== null) {
|
|
8047
8534
|
command.pipeline = pipeline;
|
|
8048
|
-
command.bindGroup =
|
|
8535
|
+
command.bindGroup =
|
|
8536
|
+
animation === null
|
|
8537
|
+
? this.flatBindGroupFor(false, false, null)
|
|
8538
|
+
: this.animatedGroup(batch, animation);
|
|
8049
8539
|
command.offsetA = slot;
|
|
8050
|
-
command.offsetB =
|
|
8051
|
-
command.
|
|
8540
|
+
command.offsetB = this.passOffset;
|
|
8541
|
+
command.offsetC = material;
|
|
8542
|
+
command.offsetD = this.drawViewOffset;
|
|
8543
|
+
command.offsetCount = 4;
|
|
8052
8544
|
let index = 0;
|
|
8053
8545
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
8054
8546
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
@@ -8056,6 +8548,7 @@ export class WebGPURenderer {
|
|
|
8056
8548
|
command.vertexBuffers[index] = instanceBuffer;
|
|
8057
8549
|
command.vertexCount = mesh.vertexBuffers.length + 1;
|
|
8058
8550
|
command.indexBuffer = mesh.indexBuffer;
|
|
8551
|
+
command.indexFormat = mesh.indexFormat;
|
|
8059
8552
|
command.indexed = true;
|
|
8060
8553
|
command.count = mesh.indexCount;
|
|
8061
8554
|
command.instances = count;
|
|
@@ -8066,13 +8559,15 @@ export class WebGPURenderer {
|
|
|
8066
8559
|
if (pass === null)
|
|
8067
8560
|
return;
|
|
8068
8561
|
pass.setPipeline(pipeline);
|
|
8069
|
-
pass.setBindGroup(0,
|
|
8562
|
+
pass.setBindGroup(0, animation === null
|
|
8563
|
+
? this.flatBindGroupFor(false, false, null)
|
|
8564
|
+
: this.animatedGroup(batch, animation), [slot, this.passOffset, material, this.drawViewOffset]);
|
|
8070
8565
|
let index = 0;
|
|
8071
8566
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
8072
8567
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
|
|
8073
8568
|
}
|
|
8074
8569
|
pass.setVertexBuffer(index, instanceBuffer);
|
|
8075
|
-
pass.setIndexBuffer(mesh.indexBuffer,
|
|
8570
|
+
pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
|
|
8076
8571
|
if (cut === null)
|
|
8077
8572
|
pass.drawIndexed(mesh.indexCount, count);
|
|
8078
8573
|
else
|
|
@@ -8080,7 +8575,7 @@ export class WebGPURenderer {
|
|
|
8080
8575
|
}
|
|
8081
8576
|
/* And its surface half, for the frame's materials' reflections, with the same survivors. */
|
|
8082
8577
|
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);
|
|
8578
|
+
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
8579
|
}
|
|
8085
8580
|
}
|
|
8086
8581
|
/* Back to the defaults, unconditionally and for the reason `drawMesh` gives at length: this
|
|
@@ -8273,6 +8768,12 @@ export class WebGPURenderer {
|
|
|
8273
8768
|
if (view === null)
|
|
8274
8769
|
return false;
|
|
8275
8770
|
this.lightViewProj.set(lightViewProj);
|
|
8771
|
+
/* The moving layer is read through the matrix it is drawn with: see `movingLightFor`. */
|
|
8772
|
+
if (dynamic) {
|
|
8773
|
+
this.movingLightViewProj.set(lightViewProj);
|
|
8774
|
+
this.movingLightKnown = true;
|
|
8775
|
+
}
|
|
8776
|
+
this.reopenViews();
|
|
8276
8777
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
8277
8778
|
this.shadowLayerIsPeel = peel;
|
|
8278
8779
|
this.shadowLayerIsDynamic = dynamic;
|
|
@@ -8380,6 +8881,244 @@ export class WebGPURenderer {
|
|
|
8380
8881
|
this.sceneCasterMaterial = undefined;
|
|
8381
8882
|
casters(this.sceneCasterSink);
|
|
8382
8883
|
}
|
|
8884
|
+
/* -- Static draws ---------------------------------------------------------------- */
|
|
8885
|
+
/** See `Renderer.createStaticDraws`. Recorded on its first draw, into each view it is drawn in. */
|
|
8886
|
+
createStaticDraws(casters) {
|
|
8887
|
+
return new GpuStaticDraws(this.surface.device, captureStaticDraws(casters), this.fragment.passSize, FLAT_VIEW_SIZE, FLAT_VERT_SIZE);
|
|
8888
|
+
}
|
|
8889
|
+
/**
|
|
8890
|
+
* See `Renderer.drawStaticDraws`. One bundle executed, after a pass block and a view block are
|
|
8891
|
+
* written for this view; the list is recorded first where it has no recording that is current for
|
|
8892
|
+
* this target and starting state. See `staticBundles.ts`.
|
|
8893
|
+
*/
|
|
8894
|
+
drawStaticDraws(draws) {
|
|
8895
|
+
if (this.surface.lost)
|
|
8896
|
+
return;
|
|
8897
|
+
const list = draws;
|
|
8898
|
+
if (list.disposed) {
|
|
8899
|
+
this.warnStaticDisposed();
|
|
8900
|
+
return;
|
|
8901
|
+
}
|
|
8902
|
+
if (!this.canDraw() || this.viewProj === null)
|
|
8903
|
+
return;
|
|
8904
|
+
/* A list's draws are rigid and wear no overlay: put back to none, as `bindMeshPass` does. */
|
|
8905
|
+
this.setSkinPalette(null);
|
|
8906
|
+
this.setMorphWeights(null);
|
|
8907
|
+
this.setCloth(null);
|
|
8908
|
+
this.setSurfaceOverlay(null);
|
|
8909
|
+
this.setMaterial(null);
|
|
8910
|
+
this.sceneCasterMaterial = undefined;
|
|
8911
|
+
/* A probe face gives its slots back after every face; a list replays there draw by draw. */
|
|
8912
|
+
if (this.probePassActive) {
|
|
8913
|
+
this.replayStaticImmediately(list);
|
|
8914
|
+
return;
|
|
8915
|
+
}
|
|
8916
|
+
/* The copy first: taking one may grow the list's rings, which no recording before can bind. */
|
|
8917
|
+
const copy = list.takeCopy(this.moverFrame);
|
|
8918
|
+
const current = this.staticRecordingFor(list);
|
|
8919
|
+
if (current === null || current.immediate) {
|
|
8920
|
+
this.replayStaticImmediately(list);
|
|
8921
|
+
return;
|
|
8922
|
+
}
|
|
8923
|
+
current.usedIn = this.moverFrame;
|
|
8924
|
+
list.writePass(copy, this.passInts);
|
|
8925
|
+
this.writeView(list.views, list.viewOffset(copy), this.viewProj);
|
|
8926
|
+
list.uploadCopy(copy);
|
|
8927
|
+
const cache = current.cache;
|
|
8928
|
+
let frame = current.frameBundles[copy];
|
|
8929
|
+
if (frame === undefined || frame === null) {
|
|
8930
|
+
frame = encodeBundle(this.surface.device, {
|
|
8931
|
+
colorFormats: [cache.format],
|
|
8932
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
8933
|
+
sampleCount: cache.sampleCount,
|
|
8934
|
+
depthReadOnly: false,
|
|
8935
|
+
}, current.frame, list.passOffset(copy), list.viewOffset(copy), 'static.frame');
|
|
8936
|
+
current.frameBundles[copy] = frame;
|
|
8937
|
+
}
|
|
8938
|
+
const command = this.recordDraw(0, this.currentTarget());
|
|
8939
|
+
if (command !== null) {
|
|
8940
|
+
command.bundles = frame;
|
|
8941
|
+
command.pipeline = null;
|
|
8942
|
+
command.bindGroup = null;
|
|
8943
|
+
}
|
|
8944
|
+
else {
|
|
8945
|
+
this.openPass()?.executeBundles(frame);
|
|
8946
|
+
}
|
|
8947
|
+
const surface = this.reflectionSurface;
|
|
8948
|
+
if (current.surfaceKept && surface !== null && current.surface.taken > 0) {
|
|
8949
|
+
let halves = current.surfaceBundles[copy];
|
|
8950
|
+
if (halves === undefined || halves === null) {
|
|
8951
|
+
halves = encodeBundle(this.surface.device, {
|
|
8952
|
+
colorFormats: [null, REFLECTION_SURFACE_FORMAT, REFLECTION_SURFACE_FORMAT],
|
|
8953
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
8954
|
+
sampleCount: 1,
|
|
8955
|
+
depthReadOnly: true,
|
|
8956
|
+
}, current.surface, list.passOffset(copy), list.viewOffset(copy), 'static.surface');
|
|
8957
|
+
current.surfaceBundles[copy] = halves;
|
|
8958
|
+
}
|
|
8959
|
+
const kept = surface.take();
|
|
8960
|
+
kept.bundles = halves;
|
|
8961
|
+
}
|
|
8962
|
+
/* Counted as the draws would have been, so a frame reports the same on both backends. */
|
|
8963
|
+
for (let draw = 0; draw < current.frame.taken; draw++)
|
|
8964
|
+
this.drawBudget.ask();
|
|
8965
|
+
if (!this.staticJustRecorded) {
|
|
8966
|
+
for (let ask = 0; ask < current.materialAsks; ask++)
|
|
8967
|
+
this.materialBudget.ask();
|
|
8968
|
+
}
|
|
8969
|
+
this.staticJustRecorded = false;
|
|
8970
|
+
}
|
|
8971
|
+
/** A list drawn the way `drawSceneCasters` draws, a draw at a time, for where a bundle cannot be. */
|
|
8972
|
+
replayStaticImmediately(list) {
|
|
8973
|
+
this.staticJustRecorded = false;
|
|
8974
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
8975
|
+
this.setMaterial(null);
|
|
8976
|
+
this.sceneCasterMaterial = undefined;
|
|
8977
|
+
}
|
|
8978
|
+
/** See `Renderer.disposeStaticDraws`. Its pins let go; its meshes and materials are the caller's. */
|
|
8979
|
+
disposeStaticDraws(draws) {
|
|
8980
|
+
const list = draws;
|
|
8981
|
+
if (list.disposed)
|
|
8982
|
+
return;
|
|
8983
|
+
list.dispose(this.unpinFor(list));
|
|
8984
|
+
}
|
|
8985
|
+
/** The recording to replay `list` from here, made now if none is current; null if it cannot be. */
|
|
8986
|
+
staticRecordingFor(list) {
|
|
8987
|
+
/* Geometry still arriving is not drawn yet, so a recording now would leave it out for good. */
|
|
8988
|
+
for (const entry of list.entries) {
|
|
8989
|
+
const mesh = entry.kind === 'mesh' ? entry.mesh : entry.batch.mesh;
|
|
8990
|
+
if (!mesh.complete)
|
|
8991
|
+
return null;
|
|
8992
|
+
}
|
|
8993
|
+
const cache = this.targetPipelines();
|
|
8994
|
+
const start = this.materialInts;
|
|
8995
|
+
const startHash = hashWords(start, start.length);
|
|
8996
|
+
const surfaceKept = this.reflectionSurfaceKeeps();
|
|
8997
|
+
let recording = list.find(cache, start, startHash, this.cutoutResolveStaged, surfaceKept);
|
|
8998
|
+
if (recording !== null &&
|
|
8999
|
+
recording.cacheVersion === cache.version &&
|
|
9000
|
+
recording.groupEpoch === this.flatGroupEpoch &&
|
|
9001
|
+
recording.storeEpoch === this.materialStore.epoch &&
|
|
9002
|
+
recording.ringEpoch === list.ringEpoch) {
|
|
9003
|
+
return recording;
|
|
9004
|
+
}
|
|
9005
|
+
const unpin = this.unpinFor(list);
|
|
9006
|
+
if (recording === null) {
|
|
9007
|
+
recording = list.open(cache, start, startHash, this.cutoutResolveStaged, surfaceKept, unpin);
|
|
9008
|
+
}
|
|
9009
|
+
else {
|
|
9010
|
+
list.clear(recording, recording.storeEpoch === this.materialStore.epoch ? unpin : () => { });
|
|
9011
|
+
}
|
|
9012
|
+
this.recordStatic(list, recording);
|
|
9013
|
+
return recording;
|
|
9014
|
+
}
|
|
9015
|
+
/** How a list's pins are let go: through the store, unless it has forgotten them since. */
|
|
9016
|
+
unpinFor(list) {
|
|
9017
|
+
const epoch = this.materialStore.epoch;
|
|
9018
|
+
return (offset) => {
|
|
9019
|
+
if (this.materialStore.epoch === epoch && list.pinEpoch === epoch) {
|
|
9020
|
+
this.materialStore.unpin(offset);
|
|
9021
|
+
}
|
|
9022
|
+
};
|
|
9023
|
+
}
|
|
9024
|
+
/**
|
|
9025
|
+
* Record `list` into `recording`: its entries through the scene-caster path, with the list's
|
|
9026
|
+
* rings and group cache swapped in for the frame's and the pass and view slots held at the list's
|
|
9027
|
+
* first copy, so every command the path records binds the list's buffers and offsets the bundle
|
|
9028
|
+
* can rewrite per copy. Everything swapped is put back, whatever the path throws.
|
|
9029
|
+
*/
|
|
9030
|
+
recordStatic(list, recording) {
|
|
9031
|
+
if (list.groupEpoch !== this.flatGroupEpoch) {
|
|
9032
|
+
list.groups = new Map();
|
|
9033
|
+
list.blank = null;
|
|
9034
|
+
list.groupEpoch = this.flatGroupEpoch;
|
|
9035
|
+
}
|
|
9036
|
+
list.drawRing.reset();
|
|
9037
|
+
const perDraw = this.perDraw;
|
|
9038
|
+
const passRing = this.passRing;
|
|
9039
|
+
const viewRing = this.viewRing;
|
|
9040
|
+
const groups = this.flatBindGroups;
|
|
9041
|
+
const blank = this.blankAlbedoBindGroup;
|
|
9042
|
+
const bindGroup = this.bindGroup;
|
|
9043
|
+
const passSlot = this.passBlock.slot;
|
|
9044
|
+
const viewSlot = this.viewBlock.slot;
|
|
9045
|
+
const lateSlot = this.lateViewBlock.slot;
|
|
9046
|
+
const asked = this.materialBudget.used;
|
|
9047
|
+
this.perDraw = list.drawRing;
|
|
9048
|
+
this.passRing = list.passes;
|
|
9049
|
+
this.viewRing = list.views;
|
|
9050
|
+
this.flatBindGroups = list.groups;
|
|
9051
|
+
/* The blank group over the list's rings, built as the frame's is: with no maps held. */
|
|
9052
|
+
list.blank ??= this.buildFlatBindGroup();
|
|
9053
|
+
this.blankAlbedoBindGroup = list.blank;
|
|
9054
|
+
this.bindGroup = this.flatGroupForMaps(null, null, null, null, null);
|
|
9055
|
+
this.passBlock.slot = list.passOffset(0);
|
|
9056
|
+
this.viewBlock.slot = list.viewOffset(0);
|
|
9057
|
+
this.lateViewBlock.slot = list.viewOffset(0);
|
|
9058
|
+
this.materials.dirty();
|
|
9059
|
+
this.staticRecording = recording;
|
|
9060
|
+
/* A recorded list keeps what it recorded, so every draw of it waits for its pipeline. */
|
|
9061
|
+
this.skipCompiling(false);
|
|
9062
|
+
try {
|
|
9063
|
+
this.sceneCasterMaterial = undefined;
|
|
9064
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
9065
|
+
}
|
|
9066
|
+
finally {
|
|
9067
|
+
this.skipCompiling(this.quality.pipelineCompile === 'skip');
|
|
9068
|
+
this.staticRecording = null;
|
|
9069
|
+
this.perDraw = perDraw;
|
|
9070
|
+
this.passRing = passRing;
|
|
9071
|
+
this.viewRing = viewRing;
|
|
9072
|
+
this.flatBindGroups = groups;
|
|
9073
|
+
this.blankAlbedoBindGroup = blank;
|
|
9074
|
+
this.bindGroup = bindGroup;
|
|
9075
|
+
this.passBlock.slot = passSlot;
|
|
9076
|
+
this.viewBlock.slot = viewSlot;
|
|
9077
|
+
this.lateViewBlock.slot = lateSlot;
|
|
9078
|
+
this.materials.dirty();
|
|
9079
|
+
this.sceneCasterMaterial = undefined;
|
|
9080
|
+
this.setMaterial(null);
|
|
9081
|
+
}
|
|
9082
|
+
recording.materialAsks = this.materialBudget.used - asked;
|
|
9083
|
+
const store = this.materialStore;
|
|
9084
|
+
recording.cacheVersion = recording.cache.version;
|
|
9085
|
+
recording.groupEpoch = this.flatGroupEpoch;
|
|
9086
|
+
recording.storeEpoch = store.epoch;
|
|
9087
|
+
recording.ringEpoch = list.ringEpoch;
|
|
9088
|
+
/* A skin split into halves is replayed draw by draw, so nothing recorded is kept or pinned. */
|
|
9089
|
+
if (recording.immediate) {
|
|
9090
|
+
resetPool(recording.frame);
|
|
9091
|
+
resetPool(recording.surface);
|
|
9092
|
+
return;
|
|
9093
|
+
}
|
|
9094
|
+
/* Every material the commands bind, pinned once, so nothing displaces what a bundle bakes. */
|
|
9095
|
+
for (const pool of [recording.frame, recording.surface]) {
|
|
9096
|
+
for (let i = 0; i < pool.taken; i++) {
|
|
9097
|
+
const offset = pool.commands[i].offsetC;
|
|
9098
|
+
if (!recording.pins.includes(offset)) {
|
|
9099
|
+
store.pin(offset);
|
|
9100
|
+
recording.pins.push(offset);
|
|
9101
|
+
}
|
|
9102
|
+
}
|
|
9103
|
+
}
|
|
9104
|
+
list.pinEpoch = store.epoch;
|
|
9105
|
+
list.uploadDraws();
|
|
9106
|
+
this.staticJustRecorded = true;
|
|
9107
|
+
}
|
|
9108
|
+
/** The recording under way, which `recordDraw` and the side passes take their commands into. */
|
|
9109
|
+
staticRecording = null;
|
|
9110
|
+
/** Whether the replay about to count its material changes recorded them, and so counted them. */
|
|
9111
|
+
staticJustRecorded = false;
|
|
9112
|
+
/** A disposed list drawn, said once: the frame loop does not throw. */
|
|
9113
|
+
warnStaticDisposed() {
|
|
9114
|
+
if (this.warnedStaticDisposed)
|
|
9115
|
+
return;
|
|
9116
|
+
this.warnedStaticDisposed = true;
|
|
9117
|
+
console.warn('drawStaticDraws: this list was disposed, and draws nothing.');
|
|
9118
|
+
}
|
|
9119
|
+
warnedStaticDisposed = false;
|
|
9120
|
+
/** Bumped wherever the lit groups are dropped, which a static list's own groups follow. */
|
|
9121
|
+
flatGroupEpoch = 0;
|
|
8383
9122
|
drawShadowCasters(casters) {
|
|
8384
9123
|
if (this.surface.lost || this.shadowPass === null)
|
|
8385
9124
|
return;
|
|
@@ -8595,7 +9334,7 @@ export class WebGPURenderer {
|
|
|
8595
9334
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
8596
9335
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
8597
9336
|
}
|
|
8598
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
9337
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
8599
9338
|
pass.drawIndexed(geometry.indexCount);
|
|
8600
9339
|
},
|
|
8601
9340
|
instanced: (batch, data, material, glass) => {
|
|
@@ -8629,7 +9368,7 @@ export class WebGPURenderer {
|
|
|
8629
9368
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
8630
9369
|
}
|
|
8631
9370
|
pass.setVertexBuffer(index, gpuBatch.buffer);
|
|
8632
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
9371
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
8633
9372
|
pass.drawIndexed(geometry.indexCount, count);
|
|
8634
9373
|
},
|
|
8635
9374
|
skinnedMesh: (mesh, model, palette, _material, glass) => {
|
|
@@ -8657,7 +9396,7 @@ export class WebGPURenderer {
|
|
|
8657
9396
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
8658
9397
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
8659
9398
|
}
|
|
8660
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
9399
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
8661
9400
|
pass.drawIndexed(geometry.indexCount);
|
|
8662
9401
|
},
|
|
8663
9402
|
};
|
|
@@ -8671,7 +9410,7 @@ export class WebGPURenderer {
|
|
|
8671
9410
|
}
|
|
8672
9411
|
this.shadowDraws.flush();
|
|
8673
9412
|
this.scatterDepthDraws.flush();
|
|
8674
|
-
this.
|
|
9413
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
8675
9414
|
/* A growth's old texture, copied out of by what was just submitted and needed no longer. */
|
|
8676
9415
|
this.retiredSunTexture?.destroy();
|
|
8677
9416
|
this.retiredSunTexture = null;
|
|
@@ -8905,7 +9644,7 @@ export class WebGPURenderer {
|
|
|
8905
9644
|
* its own slot.
|
|
8906
9645
|
*/
|
|
8907
9646
|
this.pointShadowArray?.flush();
|
|
8908
|
-
this.
|
|
9647
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
8909
9648
|
this.shadowEncoder = null;
|
|
8910
9649
|
this.shadowPass = null;
|
|
8911
9650
|
this.gpuTimer.end();
|
|
@@ -9072,7 +9811,7 @@ export class WebGPURenderer {
|
|
|
9072
9811
|
tintTarget.resolve(encoder, layer, face, offered);
|
|
9073
9812
|
}
|
|
9074
9813
|
buildFlatBindGroup() {
|
|
9075
|
-
return createFlatBindGroup(this.surface.device, this.bindGroupLayout, this.variant, this.perDraw.buffer, this.
|
|
9814
|
+
return createFlatBindGroup(this.surface.device, this.bindGroupLayout, this.variant, this.perDraw.buffer, this.passRing.buffer, this.materialStore.buffer, this.viewRing.buffer, this.flatTextures);
|
|
9076
9815
|
}
|
|
9077
9816
|
/**
|
|
9078
9817
|
* The open material's flat group, or its twin with the surface overlay's atlas in the refraction
|
|
@@ -9112,11 +9851,11 @@ export class WebGPURenderer {
|
|
|
9112
9851
|
/**
|
|
9113
9852
|
* The bind group a draw uses, skinned or not.
|
|
9114
9853
|
*
|
|
9115
|
-
* **The skinned twin is
|
|
9116
|
-
*
|
|
9117
|
-
*
|
|
9118
|
-
*
|
|
9119
|
-
* missed invalidation is not possible rather than merely unlikely, and it is why
|
|
9854
|
+
* **The skinned twin is kept by palette slot and by the unskinned group it was built beside**,
|
|
9855
|
+
* which is one a material, and compared against the views it holds — the palette's, which
|
|
9856
|
+
* changes only when the slot's texture grows, the morph deltas and the cloth's — rather than
|
|
9857
|
+
* against a flag somebody has to remember to clear at six call sites. Comparing object identity
|
|
9858
|
+
* means a missed invalidation is not possible rather than merely unlikely, and it is why
|
|
9120
9859
|
* `SkinPaletteTexture.view()` caches: a fresh view every call never compares equal and would
|
|
9121
9860
|
* rebuild this every draw.
|
|
9122
9861
|
*/
|
|
@@ -9130,23 +9869,31 @@ export class WebGPURenderer {
|
|
|
9130
9869
|
const at = skinned ? this.skinPaletteSlot + 1 : 0;
|
|
9131
9870
|
/* A skinned group carries the cloth's views, or the stand-in's: see `clothViews`. */
|
|
9132
9871
|
const cloth = skinned ? this.clothViews() : null;
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
this.
|
|
9137
|
-
|
|
9138
|
-
|
|
9872
|
+
let byMaterial = this.skinnedGroups[at];
|
|
9873
|
+
if (byMaterial === undefined) {
|
|
9874
|
+
byMaterial = new Map();
|
|
9875
|
+
this.skinnedGroups[at] = byMaterial;
|
|
9876
|
+
}
|
|
9877
|
+
let twin = byMaterial.get(base);
|
|
9878
|
+
if (twin === undefined ||
|
|
9879
|
+
twin.palette !== palette ||
|
|
9880
|
+
twin.deltas !== deltas ||
|
|
9881
|
+
twin.cloth !== (cloth?.particles ?? null) ||
|
|
9882
|
+
twin.clothBinding !== (cloth?.binding ?? null)) {
|
|
9139
9883
|
/* Built from the live material, with the overlay's atlas where `base` is its twin. */
|
|
9140
9884
|
this.overlayBinding = base !== this.bindGroup;
|
|
9141
|
-
|
|
9885
|
+
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
9886
|
this.overlayBinding = false;
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9887
|
+
twin = {
|
|
9888
|
+
group,
|
|
9889
|
+
palette,
|
|
9890
|
+
deltas,
|
|
9891
|
+
cloth: cloth?.particles ?? null,
|
|
9892
|
+
clothBinding: cloth?.binding ?? null,
|
|
9893
|
+
};
|
|
9894
|
+
byMaterial.set(base, twin);
|
|
9148
9895
|
}
|
|
9149
|
-
return
|
|
9896
|
+
return twin.group;
|
|
9150
9897
|
}
|
|
9151
9898
|
/** The layout matching a pair of vertex flags. A pipeline and its bind group must agree. */
|
|
9152
9899
|
flatLayoutFor(skinned, morphed) {
|
|
@@ -9185,8 +9932,14 @@ export class WebGPURenderer {
|
|
|
9185
9932
|
if (this.drawBudget.dropped > 0) {
|
|
9186
9933
|
grew = this.perDraw.growTo(roomFor(this.drawBudget.used)) || grew;
|
|
9187
9934
|
}
|
|
9188
|
-
if (this.
|
|
9189
|
-
grew = this.
|
|
9935
|
+
if (this.materialsRefused > 0) {
|
|
9936
|
+
grew = this.materialStore.growTo(roomFor(this.materialBudget.used)) || grew;
|
|
9937
|
+
}
|
|
9938
|
+
if (this.passesRefused > 0) {
|
|
9939
|
+
grew = this.passRing.growTo(roomFor(this.passesAsked)) || grew;
|
|
9940
|
+
}
|
|
9941
|
+
if (this.viewsRefused > 0) {
|
|
9942
|
+
grew = this.viewRing.growTo(roomFor(this.viewsAsked)) || grew;
|
|
9190
9943
|
}
|
|
9191
9944
|
if (grew)
|
|
9192
9945
|
this.rebuildFlatGroupsForNewRings();
|
|
@@ -9207,9 +9960,10 @@ export class WebGPURenderer {
|
|
|
9207
9960
|
*/
|
|
9208
9961
|
rebuildFlatGroupsForNewRings() {
|
|
9209
9962
|
this.flatBindGroups.clear();
|
|
9963
|
+
this.flatGroupEpoch += 1;
|
|
9210
9964
|
this.overlayGroups.clear();
|
|
9211
|
-
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
9212
9965
|
this.skinnedGroups.length = 0;
|
|
9966
|
+
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
9213
9967
|
const albedo = this.albedo;
|
|
9214
9968
|
const normalMap = this.normalMap;
|
|
9215
9969
|
const ormMap = this.ormMap;
|
|
@@ -9229,6 +9983,7 @@ export class WebGPURenderer {
|
|
|
9229
9983
|
this.bindGroup = this.flatGroupForMaps(this.albedo, this.normalMap, this.ormMap, this.emissiveMap, this.modelMap);
|
|
9230
9984
|
/* The open material lives in a slot of a buffer that no longer exists. */
|
|
9231
9985
|
this.materials.dirty();
|
|
9986
|
+
this.reopenPass();
|
|
9232
9987
|
}
|
|
9233
9988
|
/**
|
|
9234
9989
|
* The environment view the cached flat groups bind: the grid, or the blank stand-in while the
|
|
@@ -9260,7 +10015,9 @@ export class WebGPURenderer {
|
|
|
9260
10015
|
*/
|
|
9261
10016
|
rebuildFlatBindGroup() {
|
|
9262
10017
|
this.flatBindGroups.clear();
|
|
10018
|
+
this.flatGroupEpoch += 1;
|
|
9263
10019
|
this.overlayGroups.clear();
|
|
10020
|
+
this.skinnedGroups.length = 0;
|
|
9264
10021
|
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
9265
10022
|
if (this.albedo === null &&
|
|
9266
10023
|
this.normalMap === null &&
|
|
@@ -10768,6 +11525,7 @@ export class WebGPURenderer {
|
|
|
10768
11525
|
/* Uncorrected, for the reason the shaft's own copy of this line carries: the shader does its
|
|
10769
11526
|
own `* 0.5 + 0.5`, and correcting the matrix as well doubles it. */
|
|
10770
11527
|
f.set(env?.lightViewProj ?? this.mediumIdentity, at('uLightViewProj'));
|
|
11528
|
+
f.set((env === null ? this.mediumIdentity : this.movingLightFor(env)), at('uMovingLightViewProj'));
|
|
10771
11529
|
ints[at('uPeeledShadowEnabled')] = this.peelView === null || !this.peelFilled ? 0 : 1;
|
|
10772
11530
|
device.queue.writeBuffer(this.mediumUniforms, 0, this.mediumStaging);
|
|
10773
11531
|
const g = this.mediumUpsampleFloats;
|
|
@@ -11158,20 +11916,32 @@ export class WebGPURenderer {
|
|
|
11158
11916
|
* `reflectionSurfacePass.ts` holds and tests for the depth its frame draw wrote. Replayed when the
|
|
11159
11917
|
* frame's pass has ended, before the trace reads them. See `shaders/flat/reflectionSurface.ts`.
|
|
11160
11918
|
*/
|
|
11161
|
-
keepReflectionSurface(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, modelKind, slot, material, instanceBuffer, instances, indirect
|
|
11919
|
+
keepReflectionSurface(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, modelKind, slot, material, instanceBuffer, instances, indirect,
|
|
11920
|
+
/** A batch playing a bone animation, whose surface half moves as its draw does. */
|
|
11921
|
+
animated = null) {
|
|
11162
11922
|
const surface = this.reflectionSurface;
|
|
11163
11923
|
const present = this.meshPresent.get(base);
|
|
11164
11924
|
if (surface === null || present === undefined)
|
|
11165
11925
|
return;
|
|
11926
|
+
const animation = animated === null ? null : this.animationViews(animated);
|
|
11166
11927
|
const key = `${frameKey}|rs`;
|
|
11167
11928
|
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
|
-
|
|
11929
|
+
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);
|
|
11930
|
+
/* Still compiling where draws skip: the probe stands in for this surface until it lands. */
|
|
11931
|
+
if (pipeline === null)
|
|
11932
|
+
return;
|
|
11933
|
+
/* A static list's surface halves go into its own pool, beside its draws. */
|
|
11934
|
+
const command = this.staticRecording === null ? surface.take() : takeRecorded(this.staticRecording.surface);
|
|
11170
11935
|
command.pipeline = pipeline;
|
|
11171
|
-
command.bindGroup =
|
|
11936
|
+
command.bindGroup =
|
|
11937
|
+
animated === null || animation === null
|
|
11938
|
+
? this.flatBindGroupFor(skinned, morphed, deltas)
|
|
11939
|
+
: this.animatedGroup(animated, animation);
|
|
11172
11940
|
command.offsetA = slot;
|
|
11173
|
-
command.offsetB =
|
|
11174
|
-
command.
|
|
11941
|
+
command.offsetB = this.passOffset;
|
|
11942
|
+
command.offsetC = material;
|
|
11943
|
+
command.offsetD = this.drawViewOffset;
|
|
11944
|
+
command.offsetCount = 4;
|
|
11175
11945
|
let index = 0;
|
|
11176
11946
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
11177
11947
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
@@ -11180,6 +11950,7 @@ export class WebGPURenderer {
|
|
|
11180
11950
|
command.vertexBuffers[index++] = instanceBuffer;
|
|
11181
11951
|
command.vertexCount = index;
|
|
11182
11952
|
command.indexBuffer = mesh.indexBuffer;
|
|
11953
|
+
command.indexFormat = mesh.indexFormat;
|
|
11183
11954
|
command.indexed = true;
|
|
11184
11955
|
command.count = mesh.indexCount;
|
|
11185
11956
|
command.instances = instances;
|
|
@@ -11191,7 +11962,36 @@ export class WebGPURenderer {
|
|
|
11191
11962
|
* light alone and its colour alone into the blur's targets and test for the depth its frame half
|
|
11192
11963
|
* wrote.
|
|
11193
11964
|
*/
|
|
11965
|
+
/**
|
|
11966
|
+
* Whether a split skin draw's two kept halves have pipelines, starting the compile of either that
|
|
11967
|
+
* has not: a skin drawn without them would lose its diffuse light for the frames they take, so a
|
|
11968
|
+
* split skin draws all three halves or none. See `PipelineCache.lit`.
|
|
11969
|
+
*/
|
|
11970
|
+
skinHalvesReady(frameKey, base, skinned, morphed, layer, eight, clothed) {
|
|
11971
|
+
let ready = true;
|
|
11972
|
+
for (const half of SKIN_KEPT_HALVES) {
|
|
11973
|
+
const pipeline = this.skinHalfPipeline(half, frameKey, base, skinned, morphed, layer, eight, clothed);
|
|
11974
|
+
if (pipeline === null)
|
|
11975
|
+
ready = false;
|
|
11976
|
+
}
|
|
11977
|
+
return ready;
|
|
11978
|
+
}
|
|
11979
|
+
/** One kept half's pipeline, or none where it is still compiling: see `PipelineCache.lit`. */
|
|
11980
|
+
skinHalfPipeline(half, frameKey, base, skinned, morphed, layer, eight, clothed) {
|
|
11981
|
+
const pipelines = this.skinPipelines;
|
|
11982
|
+
const present = this.meshPresent.get(base);
|
|
11983
|
+
if (pipelines === null || present === undefined)
|
|
11984
|
+
return null;
|
|
11985
|
+
const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
|
|
11986
|
+
return (pipelines.peek(key) ??
|
|
11987
|
+
flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, false, skinned, morphed, true, layer, false, 'none', this.materialDoubleSided, false, eight, clothed, 'skin', half));
|
|
11988
|
+
}
|
|
11194
11989
|
keepSkinDiffuse(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, slot, material) {
|
|
11990
|
+
/* A skin's halves are not bundled: a static list holding one replays draw by draw. */
|
|
11991
|
+
if (this.staticRecording !== null) {
|
|
11992
|
+
this.staticRecording.immediate = true;
|
|
11993
|
+
return;
|
|
11994
|
+
}
|
|
11195
11995
|
const skin = this.skinScatter;
|
|
11196
11996
|
const pipelines = this.skinPipelines;
|
|
11197
11997
|
const present = this.meshPresent.get(base);
|
|
@@ -11200,20 +12000,24 @@ export class WebGPURenderer {
|
|
|
11200
12000
|
const bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
11201
12001
|
/* The light alone, which the blur spreads, then the colour alone, which it is multiplied by. */
|
|
11202
12002
|
for (const half of SKIN_KEPT_HALVES) {
|
|
11203
|
-
const
|
|
11204
|
-
|
|
11205
|
-
|
|
12003
|
+
const pipeline = this.skinHalfPipeline(half, frameKey, base, skinned, morphed, layer, eight, clothed);
|
|
12004
|
+
/* Both landed before the draw was let through: see `skinHalvesReady`. */
|
|
12005
|
+
if (pipeline === null)
|
|
12006
|
+
return;
|
|
11206
12007
|
const command = half === 'diffuse' ? skin.take() : skin.takeAlbedo();
|
|
11207
12008
|
command.pipeline = pipeline;
|
|
11208
12009
|
command.bindGroup = bindGroup;
|
|
11209
12010
|
command.offsetA = slot;
|
|
11210
|
-
command.offsetB =
|
|
11211
|
-
command.
|
|
12011
|
+
command.offsetB = this.passOffset;
|
|
12012
|
+
command.offsetC = material;
|
|
12013
|
+
command.offsetD = this.drawViewOffset;
|
|
12014
|
+
command.offsetCount = 4;
|
|
11212
12015
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
11213
12016
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
11214
12017
|
}
|
|
11215
12018
|
command.vertexCount = mesh.vertexBuffers.length;
|
|
11216
12019
|
command.indexBuffer = mesh.indexBuffer;
|
|
12020
|
+
command.indexFormat = mesh.indexFormat;
|
|
11217
12021
|
command.indexed = true;
|
|
11218
12022
|
command.count = mesh.indexCount;
|
|
11219
12023
|
command.instances = 1;
|
|
@@ -11635,6 +12439,9 @@ export class WebGPURenderer {
|
|
|
11635
12439
|
* site reads as one `if` either way.
|
|
11636
12440
|
*/
|
|
11637
12441
|
recordDraw(reads, writes) {
|
|
12442
|
+
/* A static list recording takes its draws into its own pool, never the frame graph's. */
|
|
12443
|
+
if (this.staticRecording !== null)
|
|
12444
|
+
return takeRecorded(this.staticRecording.frame);
|
|
11638
12445
|
/* The order-independent replay owns its pass and issues directly into it; a recorded command
|
|
11639
12446
|
would be replayed into the frame's passes instead, which is not where these belong. */
|
|
11640
12447
|
if (this.oitEncoder !== null)
|
|
@@ -11649,6 +12456,7 @@ export class WebGPURenderer {
|
|
|
11649
12456
|
command.offsetCount = 0;
|
|
11650
12457
|
command.vertexCount = 0;
|
|
11651
12458
|
command.indexBuffer = null;
|
|
12459
|
+
command.indexFormat = 'uint32';
|
|
11652
12460
|
command.indexed = false;
|
|
11653
12461
|
command.instances = 1;
|
|
11654
12462
|
command.indirect = null;
|
|
@@ -11682,10 +12490,26 @@ export class WebGPURenderer {
|
|
|
11682
12490
|
}
|
|
11683
12491
|
/** One recorded draw, issued into an open pass. Shared by the graph's replay and `runLate`. */
|
|
11684
12492
|
issueCommand(pass, command) {
|
|
12493
|
+
/* A static list, recorded once: one call, and the pass's bindings are its to set again. */
|
|
12494
|
+
if (command.bundles !== null) {
|
|
12495
|
+
pass.executeBundles(command.bundles);
|
|
12496
|
+
return;
|
|
12497
|
+
}
|
|
11685
12498
|
if (command.pipeline === null || command.bindGroup === null)
|
|
11686
12499
|
return;
|
|
11687
12500
|
pass.setPipeline(command.pipeline);
|
|
11688
|
-
if (command.offsetCount ===
|
|
12501
|
+
if (command.offsetCount === 4) {
|
|
12502
|
+
pass.setBindGroup(0, command.bindGroup, [
|
|
12503
|
+
command.offsetA,
|
|
12504
|
+
command.offsetB,
|
|
12505
|
+
command.offsetC,
|
|
12506
|
+
command.offsetD,
|
|
12507
|
+
]);
|
|
12508
|
+
}
|
|
12509
|
+
else if (command.offsetCount === 3) {
|
|
12510
|
+
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB, command.offsetC]);
|
|
12511
|
+
}
|
|
12512
|
+
else if (command.offsetCount === 2) {
|
|
11689
12513
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB]);
|
|
11690
12514
|
}
|
|
11691
12515
|
else if (command.offsetCount === 1) {
|
|
@@ -11700,7 +12524,7 @@ export class WebGPURenderer {
|
|
|
11700
12524
|
pass.setVertexBuffer(i, buffer);
|
|
11701
12525
|
}
|
|
11702
12526
|
if (command.indexed && command.indexBuffer !== null) {
|
|
11703
|
-
pass.setIndexBuffer(command.indexBuffer,
|
|
12527
|
+
pass.setIndexBuffer(command.indexBuffer, command.indexFormat);
|
|
11704
12528
|
if (command.indirect !== null)
|
|
11705
12529
|
pass.drawIndexedIndirect(command.indirect, 0);
|
|
11706
12530
|
else
|
|
@@ -11951,7 +12775,7 @@ export class WebGPURenderer {
|
|
|
11951
12775
|
this.pass?.end();
|
|
11952
12776
|
this.pass = null;
|
|
11953
12777
|
if (!this.surface.lost)
|
|
11954
|
-
this.
|
|
12778
|
+
this.submit([this.encoder.finish()]);
|
|
11955
12779
|
this.encoder = null;
|
|
11956
12780
|
}
|
|
11957
12781
|
endFrame() {
|
|
@@ -12015,7 +12839,7 @@ export class WebGPURenderer {
|
|
|
12015
12839
|
* there would have nothing to ride on. See `GpuTimestamps.resolve`.
|
|
12016
12840
|
*/
|
|
12017
12841
|
this.gpuTimer.resolve(this.encoder);
|
|
12018
|
-
this.
|
|
12842
|
+
this.submit([this.encoder.finish()]);
|
|
12019
12843
|
this.encoder = null;
|
|
12020
12844
|
this.framePresented = true;
|
|
12021
12845
|
/* Latched rather than assigned: `framePresented` goes back to false every `beginFrame`. */
|
|
@@ -12676,7 +13500,7 @@ export class WebGPURenderer {
|
|
|
12676
13500
|
definition.dispatch(this.computeContext);
|
|
12677
13501
|
pass.end();
|
|
12678
13502
|
this.flushRings();
|
|
12679
|
-
|
|
13503
|
+
this.submit([encoder.finish()]);
|
|
12680
13504
|
}
|
|
12681
13505
|
/** Let go of a definition. A handle kept past this dispatches nothing; see `ComputeHandle`. */
|
|
12682
13506
|
unregisterCompute(handle) {
|
|
@@ -13730,7 +14554,7 @@ export class WebGPURenderer {
|
|
|
13730
14554
|
.end();
|
|
13731
14555
|
}
|
|
13732
14556
|
}
|
|
13733
|
-
|
|
14557
|
+
this.submit([clear.finish()]);
|
|
13734
14558
|
}
|
|
13735
14559
|
/**
|
|
13736
14560
|
* Declare where a grid's probes stand, and allocate the layers for them.
|
|
@@ -13941,7 +14765,7 @@ export class WebGPURenderer {
|
|
|
13941
14765
|
if (whole)
|
|
13942
14766
|
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
13943
14767
|
this.flushRings();
|
|
13944
|
-
|
|
14768
|
+
this.submit([encoder.finish()]);
|
|
13945
14769
|
this.rewindRings(skinMark);
|
|
13946
14770
|
this.encoderEpoch = frameEpoch;
|
|
13947
14771
|
this.encoder = savedEncoder;
|
|
@@ -13951,6 +14775,7 @@ export class WebGPURenderer {
|
|
|
13951
14775
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
13952
14776
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
13953
14777
|
this.materials.dirty();
|
|
14778
|
+
this.reopenPass();
|
|
13954
14779
|
this.probePassActive = false;
|
|
13955
14780
|
this.probeBounce = false;
|
|
13956
14781
|
this.rebindEnvironment();
|
|
@@ -13998,7 +14823,7 @@ export class WebGPURenderer {
|
|
|
13998
14823
|
this.uploadProbeFaces(probe, image);
|
|
13999
14824
|
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
14000
14825
|
this.buildProbeChain(encoder, probe, 0);
|
|
14001
|
-
|
|
14826
|
+
this.submit([encoder.finish()]);
|
|
14002
14827
|
this.markProbeFilled(0);
|
|
14003
14828
|
this.probeAmbient = options?.irradiance ?? true;
|
|
14004
14829
|
this.rebuildFlatBindGroup();
|
|
@@ -14023,7 +14848,7 @@ export class WebGPURenderer {
|
|
|
14023
14848
|
const { device } = this.surface;
|
|
14024
14849
|
const encoder = device.createCommandEncoder({ label: 'probe.layerImage' });
|
|
14025
14850
|
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
14026
|
-
|
|
14851
|
+
this.submit([encoder.finish()]);
|
|
14027
14852
|
this.markProbeFilled(layer);
|
|
14028
14853
|
this.probeAmbient = options?.irradiance ?? true;
|
|
14029
14854
|
this.rebuildFlatBindGroup();
|