@driftengine/core 4.4.1 → 4.5.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 +72 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +82 -8
- package/dist/render/backend/webgl2/renderer.js +373 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +21 -15
- package/dist/render/backend/webgpu/postPass.js +17 -4
- package/dist/render/backend/webgpu/renderer.d.ts +181 -54
- package/dist/render/backend/webgpu/renderer.js +1011 -394
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- package/dist/render/flameLight.js +15 -2
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowBudget.js +51 -41
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/disocclusion.d.ts +36 -3
- package/dist/render/recon/disocclusion.js +56 -4
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/recon/resolve.js +95 -12
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +9 -1
- package/dist/render/sceneTarget.js +59 -13
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +4 -0
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +115 -15
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
- package/dist/render/shaders/temporalResolve.js +133 -5
- package/dist/render/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/temporalAa.d.ts +145 -0
- package/dist/render/temporalAa.js +190 -0
- package/dist/render/temporalPass.d.ts +16 -14
- package/dist/render/temporalPass.js +59 -14
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +422 -109
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/postPass.ts +34 -4
- package/src/render/backend/webgpu/renderer.ts +1178 -425
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
- package/src/render/flameLight.ts +15 -2
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowBudget.ts +49 -39
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/disocclusion.ts +54 -3
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/recon/resolve.ts +137 -10
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +66 -12
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
- package/src/render/shaders/temporalResolve.ts +141 -5
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/temporalAa.ts +227 -0
- package/src/render/temporalPass.ts +88 -12
- package/src/render/translucentQueue.ts +20 -1
|
@@ -3,13 +3,20 @@ import { probeFaceRange } from '../../reflectionProbe.js';
|
|
|
3
3
|
import { TEMPORAL_HISTORY_BLEND, TemporalHistory, jitterOffset, jitterProjection, } from '../../temporalAa.js';
|
|
4
4
|
import { OIT_MULTISAMPLE_REFUSAL } from '../../orderIndependent.js';
|
|
5
5
|
import { TranslucentQueue } from '../../translucentQueue.js';
|
|
6
|
-
import { DEPTH_CLEAR,
|
|
6
|
+
import { DEPTH_CLEAR, MAX_DEPTH_LAYER, REVERSED_DEPTH } from '../../depthConvention.js';
|
|
7
7
|
import { RECONSTRUCTION_MULTISAMPLE_REFUSAL, reconRenderSize } from '../../recon/frameSizes.js';
|
|
8
8
|
import { jitterOffset as jitterOffsetFor, reconJitterPhases } from '../../recon/jitter.js';
|
|
9
9
|
import { RECON_HISTORY_FORMAT, RECON_PARAM_FLOATS, RECON_WORKGROUP, reconResolveWgsl, } from '../../shaders/recon/resolve.wgsl.js';
|
|
10
|
-
import {
|
|
10
|
+
import { MotionPass } from './motionPass.js';
|
|
11
|
+
import { LatePass } from './latePass.js';
|
|
12
|
+
import { SnapshotMips } from './snapshotMips.js';
|
|
13
|
+
import { resolveGlass } from '../../glass.js';
|
|
14
|
+
import { routesLate, tracksMotion } from '../../recon/lateRouting.js';
|
|
15
|
+
import { frameOfChange, keepsPrevious, pairedInstances } from '../../recon/changeFrames.js';
|
|
16
|
+
import { INSTANCE_STRIDE } from '../../instances.js';
|
|
11
17
|
import { DEFAULT_DISOCCLUSION } from '../../recon/disocclusion.js';
|
|
12
18
|
import { DEFAULT_RECON_QUALITY } from '../../recon/resolve.js';
|
|
19
|
+
import { MOVER_MOTION, MOVER_TWICE, advanceMover, claimRendererId, isMover, } from '../../recon/mover.js';
|
|
13
20
|
import { clamp } from '../../../math/scalar.js';
|
|
14
21
|
import { Camera } from '../../camera.js';
|
|
15
22
|
import { PickableSet } from '../../pickable.js';
|
|
@@ -38,6 +45,7 @@ import { areaShadowLayerCount, castingRange, createResolvedAreaShadows, firstAre
|
|
|
38
45
|
import { createBakeScratch, runPointShadowBakes, selectCastingLights, } from '../../pointShadowBudget.js';
|
|
39
46
|
import { POINT_SHADOW_SAMPLER, PointShadowSystem, } from '../../pointShadowSystem.js';
|
|
40
47
|
import { GpuPointShadowArray, GpuPointShadowMap } from './pointShadowPass.js';
|
|
48
|
+
import { OCTAHEDRAL_EDGE, strideLayers } from '../../pointShadowArray.js';
|
|
41
49
|
import { createPanelBindGroup, createPanelBindGroupLayout, createPanelCorners, panelPipeline, PANEL_FRAG_FIELDS, PANEL_FRAG_SIZE, PANEL_VERTEX_COUNT, PANEL_VERT_FIELDS, PANEL_VERT_SIZE, } from './panelPass.js';
|
|
42
50
|
import { createGpuTextCube, createTextBindGroup, createTextBindGroupLayout, GpuText, textPipeline, TEXT_FRAG_FIELDS, TEXT_FRAG_SIZE, TEXT_VERT_FIELDS, TEXT_VERT_SIZE, } from './textPass.js';
|
|
43
51
|
import { deviceSnappedCellSize, deviceSnappedOrigin } from '../../textLayout.js';
|
|
@@ -51,7 +59,7 @@ import { ExposurePass } from './exposurePass.js';
|
|
|
51
59
|
import { AO_STORE } from '../../shaders/ambientOcclusion.js';
|
|
52
60
|
import { clampAutoExposure } from '../../shaders/exposure.js';
|
|
53
61
|
import { clampLocalExposure } from '../../shaders/localExposure.js';
|
|
54
|
-
import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS, ADDITIVE_BLEND, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, BLOOM_UPSAMPLE_FRAG_WGSL, RESOLVED_DEPTH_FORMAT, RUSH_FRAG_FIELDS, RUSH_FRAG_SIZE, RUSH_FRAG_WGSL, RUSH_TEXTURES, RUSH_UNIFORMS, TAA_FRAG_FIELDS, TAA_FRAG_SIZE, TAA_TEXTURES, TAA_UNIFORMS, TEMPORAL_RESOLVE_FRAG_WGSL, MEDIUM_FRAG_FIELDS, MEDIUM_FRAG_SIZE, MEDIUM_FRAG_WGSL, MEDIUM_TEXTURES, MEDIUM_TRANSMIT_BLEND, MEDIUM_UNIFORMS, MEDIUM_UPSAMPLE_FIELDS, MEDIUM_UPSAMPLE_FRAG_WGSL, MEDIUM_UPSAMPLE_SIZE, MEDIUM_UPSAMPLE_TEXTURES, MEDIUM_UPSAMPLE_UNIFORMS, OIT_COMPOSITE_BLEND, OIT_RESOLVE_FRAG_WGSL, OIT_TEXTURES, DECAL_FRAG_FIELDS, DECAL_FRAG_SIZE, DECAL_MULTIPLY_BLEND, DECAL_PROJECT_FRAG_WGSL, DECAL_TEXTURES, DECAL_UNIFORMS, PREMULTIPLIED_OVER_BLEND, SSR_FRAG_FIELDS, SSR_FRAG_SIZE, SSR_RESOLVE_FRAG_WGSL, SSR_RESOLVE_TEXTURES, SSR_TEXTURES, SSR_TRACE_FRAG_WGSL, SSR_UNIFORMS, createOitResolveLayout, createSsrResolveLayout, } from './postPass.js';
|
|
62
|
+
import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, postPipelineTargets, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS, ADDITIVE_BLEND, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, BLOOM_UPSAMPLE_FRAG_WGSL, RESOLVED_DEPTH_FORMAT, RUSH_FRAG_FIELDS, RUSH_FRAG_SIZE, RUSH_FRAG_WGSL, RUSH_TEXTURES, RUSH_UNIFORMS, TAA_FRAG_FIELDS, TAA_FRAG_SIZE, TAA_TEXTURES, TAA_UNIFORMS, TEMPORAL_RESOLVE_FRAG_WGSL, MEDIUM_FRAG_FIELDS, MEDIUM_FRAG_SIZE, MEDIUM_FRAG_WGSL, MEDIUM_TEXTURES, MEDIUM_TRANSMIT_BLEND, MEDIUM_UNIFORMS, MEDIUM_UPSAMPLE_FIELDS, MEDIUM_UPSAMPLE_FRAG_WGSL, MEDIUM_UPSAMPLE_SIZE, MEDIUM_UPSAMPLE_TEXTURES, MEDIUM_UPSAMPLE_UNIFORMS, OIT_COMPOSITE_BLEND, OIT_RESOLVE_FRAG_WGSL, OIT_TEXTURES, DECAL_FRAG_FIELDS, DECAL_FRAG_SIZE, DECAL_MULTIPLY_BLEND, DECAL_PROJECT_FRAG_WGSL, DECAL_TEXTURES, DECAL_UNIFORMS, PREMULTIPLIED_OVER_BLEND, SSR_FRAG_FIELDS, SSR_FRAG_SIZE, SSR_RESOLVE_FRAG_WGSL, SSR_RESOLVE_TEXTURES, SSR_TEXTURES, SSR_TRACE_FRAG_WGSL, SSR_UNIFORMS, createOitResolveLayout, createSsrResolveLayout, } from './postPass.js';
|
|
55
63
|
import { GRADE_PLACEHOLDER_SIZE, identityGradeLut, validateGradeLut, } from '../../colourGrade.js';
|
|
56
64
|
import { FILM_LOOK_WITHOUT_COMPOSITE, clampGrain, clampVignette, grainSeed, } from '../../shaders/filmLook.js';
|
|
57
65
|
import { createProbeMipBindGroupLayout, createProbePrefilterBindGroupLayout, PREFILTER_UNIFORM_OFFSETS, probeLevels, probeMipPipeline, probePrefilterPipeline, PROBE_FACES, PROBE_FACE_BASIS, PROBE_FAR_M, PROBE_FOV_DEG, PROBE_MIP_UNIFORM_SIZE, PROBE_NEAR_M, } from './probePass.js';
|
|
@@ -66,6 +74,10 @@ import { createScatterDeform, resolveScatterDeform, } from '../../scatterDeform.
|
|
|
66
74
|
import { resolvePointLights } from '../../lightBudget.js';
|
|
67
75
|
import { resolveRenderQuality } from '../../renderQuality.js';
|
|
68
76
|
import { DEFAULT_GLOBAL_MEDIUM, mediumActive, mediumTargetSize, WEAK_GPU_MEDIUM_STEPS, resolveGlobalMedium, } from '../../globalMedium.js';
|
|
77
|
+
import { castsDepth, glassOf } from '../../shadowCasters.js';
|
|
78
|
+
import { GlassCasterList } from '../../glassCasters.js';
|
|
79
|
+
import { GpuGlassTint } from './glassTintPass.js';
|
|
80
|
+
import { SUN_DYNAMIC_LAYER, SUN_GLASS_MOVING_LAYER, SUN_GLASS_STATIC_LAYER, SUN_PEELED_LAYER, SUN_STATIC_LAYER, } from '../../shadowMap.js';
|
|
69
81
|
import { VERTEX_LAYOUT, createGpuMesh, createGpuMeshIncremental } from './buffers.js';
|
|
70
82
|
import { SkinPaletteRing } from './skinPaletteRing.js';
|
|
71
83
|
import { DEPTH_FORMAT, FLAT_VERT_FIELDS, FLAT_VERT_SIZE, createFlatBindGroup, createFlatBindGroupLayout, flatFragmentBindings, flatVariant, flatVertexBindings, flatPipeline, flatPipelineAsync, } from './flatPass.js';
|
|
@@ -762,6 +774,24 @@ function padded(direction) {
|
|
|
762
774
|
SCRATCH_VEC4[3] = 0;
|
|
763
775
|
return SCRATCH_VEC4;
|
|
764
776
|
}
|
|
777
|
+
/** The sun texture's usage: attached, sampled, and copied both ways when glass grows it. */
|
|
778
|
+
const SUN_USAGE = 0x10 | 0x4 | 0x1 | 0x2; // RENDER_ATTACHMENT | TEXTURE_BINDING | COPY_SRC | COPY_DST
|
|
779
|
+
/** Every view of the sun's texture a pass reads or attaches, for `maps` of its layers. */
|
|
780
|
+
function sunViewsOf(texture, maps, layers) {
|
|
781
|
+
const layer = (index) => texture.createView({ dimension: '2d', baseArrayLayer: index, arrayLayerCount: 1 });
|
|
782
|
+
return {
|
|
783
|
+
array: texture.createView({ dimension: '2d-array' }),
|
|
784
|
+
staticArray: texture.createView({
|
|
785
|
+
dimension: '2d-array',
|
|
786
|
+
baseArrayLayer: SUN_STATIC_LAYER,
|
|
787
|
+
arrayLayerCount: 1,
|
|
788
|
+
}),
|
|
789
|
+
static: layer(SUN_STATIC_LAYER),
|
|
790
|
+
dynamic: maps > SUN_DYNAMIC_LAYER ? layer(SUN_DYNAMIC_LAYER) : null,
|
|
791
|
+
peel: maps > SUN_PEELED_LAYER ? layer(SUN_PEELED_LAYER) : null,
|
|
792
|
+
glass: layers > maps ? [layer(SUN_GLASS_STATIC_LAYER), layer(SUN_GLASS_MOVING_LAYER)] : null,
|
|
793
|
+
};
|
|
794
|
+
}
|
|
765
795
|
export class WebGPURenderer {
|
|
766
796
|
/**
|
|
767
797
|
* Real timestamp queries, where the adapter offers them.
|
|
@@ -863,6 +893,8 @@ export class WebGPURenderer {
|
|
|
863
893
|
* caller who passed null gets — rather than reading a slot this frame has already re-let.
|
|
864
894
|
*/
|
|
865
895
|
skinPaletteSlot = -1;
|
|
896
|
+
/** The palette `skinPaletteSlot` was uploaded from, for a skinned mover to copy. See `Mover`. */
|
|
897
|
+
skinPaletteData = null;
|
|
866
898
|
surface;
|
|
867
899
|
/** The encoder for the frame in flight, or null between frames. */
|
|
868
900
|
encoder = null;
|
|
@@ -870,6 +902,27 @@ export class WebGPURenderer {
|
|
|
870
902
|
meshPresent = new Map();
|
|
871
903
|
pass = null;
|
|
872
904
|
pipelines;
|
|
905
|
+
/**
|
|
906
|
+
* The pipelines a reconstructing frame's blended draws are built for: the reconstructed picture's
|
|
907
|
+
* format at one sample, which is where `LatePass` draws them. Aliased to `pipelines` when the
|
|
908
|
+
* world already lands on exactly that, as `overlayPipelines` is.
|
|
909
|
+
*/
|
|
910
|
+
latePipelines;
|
|
911
|
+
/** The blended draws of a reconstructing frame, drawn after the upscale. See `LatePass`. */
|
|
912
|
+
latePass = null;
|
|
913
|
+
/** A late draw asked to refract: `runLate` copies the reconstructed picture before replaying. */
|
|
914
|
+
lateRefractWanted = false;
|
|
915
|
+
/** The refraction copy's mip chain, recorded after each copy: what frosted glass reads. */
|
|
916
|
+
refractMips = null;
|
|
917
|
+
/**
|
|
918
|
+
* The refraction copy's sampler: filtering, and **between levels as well**, so a frosted pane's
|
|
919
|
+
* level falls smoothly between two blurs rather than stepping from one to the next.
|
|
920
|
+
*/
|
|
921
|
+
refractSampler = null;
|
|
922
|
+
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
923
|
+
glassScratch = { transmission: 0, frost: 0, tint: [1, 1, 1] };
|
|
924
|
+
/** `issueCommand` bound once, so the late replay allocates no closure per frame. */
|
|
925
|
+
issueBound;
|
|
873
926
|
/**
|
|
874
927
|
* The same pipelines again, built for the canvas, for anything drawn after `endFrame`.
|
|
875
928
|
*
|
|
@@ -1349,6 +1402,16 @@ export class WebGPURenderer {
|
|
|
1349
1402
|
taaUniforms;
|
|
1350
1403
|
taaTextures = [null, null];
|
|
1351
1404
|
taaViews = [null, null];
|
|
1405
|
+
/** Each pixel's flicker record, paired with the history and swapped with it: see `temporalAa.ts`. */
|
|
1406
|
+
taaRecords = [null, null];
|
|
1407
|
+
taaRecordViews = [null, null];
|
|
1408
|
+
/** The rest of each pixel's record — depth, pull and recent swing — beside the first. */
|
|
1409
|
+
taaMotions = [null, null];
|
|
1410
|
+
taaMotionViews = [null, null];
|
|
1411
|
+
/** The resolve's depth-to-metres row, from the inverse of `frameProjection`. */
|
|
1412
|
+
taaInvProjection = new Float32Array(16);
|
|
1413
|
+
/** The resolve's two targets, the picture and the record, held for the pipeline cache's sake. */
|
|
1414
|
+
taaTargets = [];
|
|
1352
1415
|
/** Group `i` reads history `i` and the pass writes into the other one. */
|
|
1353
1416
|
taaBindGroups = [null, null];
|
|
1354
1417
|
taaWrite = 0;
|
|
@@ -1383,10 +1446,10 @@ export class WebGPURenderer {
|
|
|
1383
1446
|
* the *unsharpened* result — sharpening into the history would sharpen a sharpened picture every
|
|
1384
1447
|
* frame, which is the halo the reference's own clamp exists to prevent, compounded.
|
|
1385
1448
|
*
|
|
1386
|
-
* The motion target is render size and carries a drawn object's own motion where
|
|
1387
|
-
* one
|
|
1388
|
-
*
|
|
1389
|
-
* exact
|
|
1449
|
+
* The motion target is render size and carries a drawn object's own motion where the motion pass
|
|
1450
|
+
* wrote one — a draw that named its mover, or passed last frame's matrix. Every other texel is
|
|
1451
|
+
* cleared, and the resolve takes the camera's motion there, derived from the depth and the
|
|
1452
|
+
* reprojection: exact for anything that did not move. See `MotionPass`.
|
|
1390
1453
|
*/
|
|
1391
1454
|
reconHistories = [null, null];
|
|
1392
1455
|
reconHistoryViews = [null, null];
|
|
@@ -1408,34 +1471,15 @@ export class WebGPURenderer {
|
|
|
1408
1471
|
reconGroups = [null, null];
|
|
1409
1472
|
reconSharpenGroups = [null, null];
|
|
1410
1473
|
reconRushGroup = null;
|
|
1411
|
-
/**
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
motionCount = 0;
|
|
1421
|
-
motionLayout = null;
|
|
1422
|
-
/**
|
|
1423
|
-
* One pipeline per vertex stride, because a stride is a property of the *mesh*.
|
|
1424
|
-
*
|
|
1425
|
-
* Which optional attributes a mesh supplied decides how far apart its positions are, and this
|
|
1426
|
-
* pass reads nothing but positions — so two meshes drawn by the same shader still need two
|
|
1427
|
-
* vertex layouts. A map rather than a cache key on the pipeline cache: there is one shader here
|
|
1428
|
-
* and a handful of strides in any scene.
|
|
1429
|
-
*/
|
|
1430
|
-
motionPipelines = new Map();
|
|
1431
|
-
motionModule = null;
|
|
1432
|
-
motionPipelineLayout = null;
|
|
1433
|
-
motionFrameBuffer = null;
|
|
1434
|
-
motionDrawBuffer = null;
|
|
1435
|
-
motionDrawCapacity = 0;
|
|
1436
|
-
motionGroup = null;
|
|
1437
|
-
motionFrameStaging = new Float32Array(MOTION_FRAME_FLOATS);
|
|
1438
|
-
motionDrawStaging = new Uint8Array(0);
|
|
1474
|
+
/** The motion pass, made with the reconstruction's targets. See `MotionPass`. */
|
|
1475
|
+
motionPass = null;
|
|
1476
|
+
/** This renderer's identity to a `Mover`. See `claimRendererId`. */
|
|
1477
|
+
moverRenderer = claimRendererId();
|
|
1478
|
+
/** Frames begun, so a mover can tell last frame from any other. Advanced in `beginFrame`. */
|
|
1479
|
+
moverFrame = 0;
|
|
1480
|
+
/** Between `beginFrame` and `endFrame`. A change outside it is the next frame's. */
|
|
1481
|
+
inFrame = false;
|
|
1482
|
+
warnedMoverTwice = false;
|
|
1439
1483
|
/** The scene's own clip transform with the generated shaders' y negation folded in. */
|
|
1440
1484
|
motionRaster = mat4.create();
|
|
1441
1485
|
reconSizeKey = '';
|
|
@@ -1682,6 +1726,7 @@ export class WebGPURenderer {
|
|
|
1682
1726
|
refraction: 0,
|
|
1683
1727
|
refractTint: undefined,
|
|
1684
1728
|
thicknessM: 0,
|
|
1729
|
+
glass: undefined,
|
|
1685
1730
|
};
|
|
1686
1731
|
reflectionPassActive = false;
|
|
1687
1732
|
/** Said once: a mirror asked for outside a frame is a call-order fault, not a device limit. */
|
|
@@ -1958,7 +2003,7 @@ export class WebGPURenderer {
|
|
|
1958
2003
|
f[atF('uFogNear')] = medium.fogNear;
|
|
1959
2004
|
f[atF('uFogFar')] = medium.fogFar;
|
|
1960
2005
|
this.filmFrags.writeBlock(fragmentSlot, this.filmFragInts);
|
|
1961
|
-
const filmPipe = filmPipeline(this.
|
|
2006
|
+
const filmPipe = filmPipeline(this.blendedPipelines(), this.surface.device, this.filmLayout, `film|${geometry.key ?? ''}`, this.presentOf(geometry));
|
|
1962
2007
|
/* The mirror is an attachment right now, so the group that does not hold it. */
|
|
1963
2008
|
const filmGroup = this.reflectionPassActive ? this.filmBindGroupBlank : this.filmBindGroup;
|
|
1964
2009
|
/*
|
|
@@ -1967,7 +2012,7 @@ export class WebGPURenderer {
|
|
|
1967
2012
|
* holds the mirror's colour whatever `uReflectionEnabled` says, and WebGPU validates the
|
|
1968
2013
|
* binding rather than the read — the argument `filmBindGroupBlank` exists for.
|
|
1969
2014
|
*/
|
|
1970
|
-
const filmCommand = this.
|
|
2015
|
+
const filmCommand = this.recordBlended(filmGroup === this.filmBindGroup ? MIRROR_COLOR : 0);
|
|
1971
2016
|
if (filmCommand !== null) {
|
|
1972
2017
|
filmCommand.pipeline = filmPipe;
|
|
1973
2018
|
filmCommand.bindGroup = filmGroup;
|
|
@@ -2149,6 +2194,7 @@ export class WebGPURenderer {
|
|
|
2149
2194
|
setSkinPalette(palette) {
|
|
2150
2195
|
if (this.surface.lost)
|
|
2151
2196
|
return;
|
|
2197
|
+
this.skinPaletteData = palette;
|
|
2152
2198
|
if (palette === null) {
|
|
2153
2199
|
this.skinPaletteSlot = -1;
|
|
2154
2200
|
return;
|
|
@@ -2472,7 +2518,8 @@ export class WebGPURenderer {
|
|
|
2472
2518
|
* no-op before the first `bindMeshPass` of a session, same as every other pass that reads it.
|
|
2473
2519
|
*/
|
|
2474
2520
|
drawSdfText(text, model, color, opacity) {
|
|
2475
|
-
|
|
2521
|
+
/* Unjittered when it lands after the upscale, like every late draw. See `drawsLate`. */
|
|
2522
|
+
const viewProj = this.drawsLate() ? this.correctedViewProj : this.viewProj;
|
|
2476
2523
|
if (!this.canDraw() || viewProj === null)
|
|
2477
2524
|
return;
|
|
2478
2525
|
const font = text.font;
|
|
@@ -2501,9 +2548,9 @@ export class WebGPURenderer {
|
|
|
2501
2548
|
]);
|
|
2502
2549
|
this.sdfTextFrags.writeInt(fragmentSlot, fragAt('uOutputTransform'), this.gradeCode());
|
|
2503
2550
|
this.sdfTextFrags.writeFloat(fragmentSlot, fragAt('uOutputExposure'), this.gradeExposure());
|
|
2504
|
-
const sdfPipe = sdfTextPipeline(this.
|
|
2551
|
+
const sdfPipe = sdfTextPipeline(this.blendedPipelines(), this.surface.device, this.sdfTextLayout);
|
|
2505
2552
|
const sdfGroup = this.sdfTextBindGroupFor(atlas);
|
|
2506
|
-
const sdfCommand = this.
|
|
2553
|
+
const sdfCommand = this.recordBlended(0);
|
|
2507
2554
|
if (sdfCommand !== null) {
|
|
2508
2555
|
sdfCommand.pipeline = sdfPipe;
|
|
2509
2556
|
sdfCommand.bindGroup = sdfGroup;
|
|
@@ -2605,8 +2652,8 @@ export class WebGPURenderer {
|
|
|
2605
2652
|
const pass = this.openTextPass();
|
|
2606
2653
|
if (pass === null)
|
|
2607
2654
|
return;
|
|
2608
|
-
const textPipe = textPipeline(this.
|
|
2609
|
-
const textCommand = this.
|
|
2655
|
+
const textPipe = textPipeline(this.blendedPipelines(), this.surface.device, this.textLayout);
|
|
2656
|
+
const textCommand = this.recordBlended(0);
|
|
2610
2657
|
if (textCommand !== null) {
|
|
2611
2658
|
textCommand.pipeline = textPipe;
|
|
2612
2659
|
textCommand.bindGroup = this.textBindGroup;
|
|
@@ -3025,8 +3072,8 @@ export class WebGPURenderer {
|
|
|
3025
3072
|
device.queue.writeBuffer(batch.vertUniforms, 0, batch.vertStaging);
|
|
3026
3073
|
device.queue.writeBuffer(batch.fragUniforms, 0, batch.fragStaging);
|
|
3027
3074
|
const layout = this.particleLayoutFor(batch.material).layout;
|
|
3028
|
-
const particlePipe = particlePipeline(this.
|
|
3029
|
-
const particleCommand = this.
|
|
3075
|
+
const particlePipe = particlePipeline(this.blendedPipelines(), device, layout, batch.material, batch.blend);
|
|
3076
|
+
const particleCommand = this.recordBlended(0);
|
|
3030
3077
|
if (particleCommand !== null) {
|
|
3031
3078
|
particleCommand.pipeline = particlePipe;
|
|
3032
3079
|
particleCommand.bindGroup = batch.bindGroup;
|
|
@@ -3144,8 +3191,8 @@ export class WebGPURenderer {
|
|
|
3144
3191
|
* with `PlumeRenderer`, which has set `uTint` on every draw since before this backend.
|
|
3145
3192
|
*/
|
|
3146
3193
|
frags.writeFloats(fragmentSlot, atF('uTint'), plumes.tint);
|
|
3147
|
-
const plumePipe = plumePipeline(this.
|
|
3148
|
-
const plumeCommand = this.
|
|
3194
|
+
const plumePipe = plumePipeline(this.blendedPipelines(), this.surface.device, this.plumeLayout, plumes.material, plumes.blend);
|
|
3195
|
+
const plumeCommand = this.recordBlended(0);
|
|
3149
3196
|
if (plumeCommand !== null) {
|
|
3150
3197
|
plumeCommand.pipeline = plumePipe;
|
|
3151
3198
|
plumeCommand.bindGroup = this.plumeBindGroup;
|
|
@@ -3588,9 +3635,9 @@ export class WebGPURenderer {
|
|
|
3588
3635
|
this.windStreakFragFloats.set(tint, (WIND_STREAK_FRAG_FIELDS.uTint.offset ?? 0) / 4);
|
|
3589
3636
|
this.windStreakVerts.writeBlock(vertexSlot, this.windStreakVertBlock);
|
|
3590
3637
|
this.windStreakFrags.writeBlock(fragmentSlot, this.windStreakFragBlock);
|
|
3591
|
-
const windPipe = windStreakPipeline(this.
|
|
3638
|
+
const windPipe = windStreakPipeline(this.blendedPipelines(), this.surface.device, this.windStreakLayout);
|
|
3592
3639
|
/* Two uniform buffers and two vertex buffers. Nothing is sampled, so nothing is read. */
|
|
3593
|
-
const windCommand = this.
|
|
3640
|
+
const windCommand = this.recordBlended(0);
|
|
3594
3641
|
if (windCommand !== null) {
|
|
3595
3642
|
windCommand.pipeline = windPipe;
|
|
3596
3643
|
windCommand.bindGroup = this.windStreakBindGroup;
|
|
@@ -3778,8 +3825,8 @@ export class WebGPURenderer {
|
|
|
3778
3825
|
f[atF('uFogFar')] = medium.fogFar;
|
|
3779
3826
|
this.boltVerts.writeBlock(vertexSlot, this.boltVertBlock);
|
|
3780
3827
|
this.boltFrags.writeBlock(fragmentSlot, this.boltFragInts);
|
|
3781
|
-
const boltPipe = boltPipeline(this.
|
|
3782
|
-
const boltCommand = this.
|
|
3828
|
+
const boltPipe = boltPipeline(this.blendedPipelines(), this.surface.device, this.boltLayout);
|
|
3829
|
+
const boltCommand = this.recordBlended(0);
|
|
3783
3830
|
if (boltCommand !== null) {
|
|
3784
3831
|
boltCommand.pipeline = boltPipe;
|
|
3785
3832
|
boltCommand.bindGroup = this.boltBindGroup;
|
|
@@ -3888,9 +3935,9 @@ export class WebGPURenderer {
|
|
|
3888
3935
|
this.lineFrags.writeInt(fragmentSlot, atF('uFogMode'), medium.fogMode);
|
|
3889
3936
|
this.lineFrags.writeFloat(fragmentSlot, atF('uFogNear'), medium.fogNear);
|
|
3890
3937
|
this.lineFrags.writeFloat(fragmentSlot, atF('uFogFar'), medium.fogFar);
|
|
3891
|
-
const linePipe = linePipeline(this.
|
|
3938
|
+
const linePipe = linePipeline(this.blendedPipelines(), this.surface.device, this.lineLayout, additive);
|
|
3892
3939
|
/* Two uniform blocks and three vertex buffers, and no attachment among them. */
|
|
3893
|
-
const lineCommand = this.
|
|
3940
|
+
const lineCommand = this.recordBlended(0);
|
|
3894
3941
|
if (lineCommand !== null) {
|
|
3895
3942
|
lineCommand.pipeline = linePipe;
|
|
3896
3943
|
lineCommand.bindGroup = this.lineBindGroup;
|
|
@@ -4097,7 +4144,38 @@ export class WebGPURenderer {
|
|
|
4097
4144
|
sdfTextFrags;
|
|
4098
4145
|
sdfTextBindGroups = new Map();
|
|
4099
4146
|
/* -- The shadow pass -------------------------------------------------------------- */
|
|
4100
|
-
|
|
4147
|
+
/**
|
|
4148
|
+
* The sun's static, moving and peeled maps, as layers of one texture (`shadowMap.ts`). The three
|
|
4149
|
+
* views below are single layers, what each pass attaches; the two here are what is sampled.
|
|
4150
|
+
*/
|
|
4151
|
+
sunShadowTexture;
|
|
4152
|
+
/** Every layer, for the lit pass, the light volumes and the medium. */
|
|
4153
|
+
sunArrayView;
|
|
4154
|
+
/**
|
|
4155
|
+
* The static layer alone, as an array, for the peel: it draws into another layer of the same
|
|
4156
|
+
* texture, and a view covering that layer too would put it in two usages in one pass.
|
|
4157
|
+
*/
|
|
4158
|
+
sunStaticArrayView;
|
|
4159
|
+
/** How many layers the sun's texture holds: its maps, and the two glass layers once glass casts. */
|
|
4160
|
+
sunLayerCount;
|
|
4161
|
+
/** The glass depth layers, static then moving, once glass has cast. See `growSunForGlass`. */
|
|
4162
|
+
sunGlassViews = null;
|
|
4163
|
+
/** The texture a growth replaced, destroyed once the encoder that copied out of it is submitted. */
|
|
4164
|
+
retiredSunTexture = null;
|
|
4165
|
+
/** What the sun's light keeps through glass: two `rgba8unorm` layers, made when glass first casts. */
|
|
4166
|
+
sunTint = null;
|
|
4167
|
+
/** The tint pass's layouts, per-draw block, groups and pipelines, made when glass first casts. */
|
|
4168
|
+
glassTint = null;
|
|
4169
|
+
/** The tint pass open while the glass list is replayed into it. */
|
|
4170
|
+
tintPass = null;
|
|
4171
|
+
/** True while the glass list is replayed into a glass depth layer through `casterSink`. */
|
|
4172
|
+
drawingGlassDepth = false;
|
|
4173
|
+
/** Whether the open sun pass is the movers' layer, whose glass goes to the moving glass layer. */
|
|
4174
|
+
shadowLayerIsDynamic = false;
|
|
4175
|
+
/** The glass tints' sampler: filtering, and between levels, for frost's coarser reads. */
|
|
4176
|
+
tintSampler;
|
|
4177
|
+
/** The light a tint pass is drawn for: xyz a direction (w 0) or a position (w 1). */
|
|
4178
|
+
glassLight = new Float32Array(4);
|
|
4101
4179
|
shadowView;
|
|
4102
4180
|
shadowLayout;
|
|
4103
4181
|
/** The same layout plus the joint palette. See `createDepthBindGroupLayout`. */
|
|
@@ -4124,7 +4202,6 @@ export class WebGPURenderer {
|
|
|
4124
4202
|
* read twice — so a profile that switches peeling off gets no map and no pass on either
|
|
4125
4203
|
* backend rather than a map nothing writes.
|
|
4126
4204
|
*/
|
|
4127
|
-
peelMap;
|
|
4128
4205
|
peelView;
|
|
4129
4206
|
/**
|
|
4130
4207
|
* The movers' own layer: the character, the rope it swings on, and anything else that walks.
|
|
@@ -4146,7 +4223,6 @@ export class WebGPURenderer {
|
|
|
4146
4223
|
* and therefore as lit — so nothing looked broken, only empty, which is why it survived the
|
|
4147
4224
|
* port and every gate in it.
|
|
4148
4225
|
*/
|
|
4149
|
-
dynamicMap;
|
|
4150
4226
|
dynamicView;
|
|
4151
4227
|
/** The peel's own bind groups: `uPeelShadowLayer` at 1, and the static map to peel against. */
|
|
4152
4228
|
shadowPeelBindGroup;
|
|
@@ -4220,7 +4296,7 @@ export class WebGPURenderer {
|
|
|
4220
4296
|
if (held !== undefined)
|
|
4221
4297
|
return held;
|
|
4222
4298
|
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
4223
|
-
const previousView = peel ? this.
|
|
4299
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
4224
4300
|
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
4225
4301
|
if (passUniforms === null || previousView === null || previousSampler === null)
|
|
4226
4302
|
return null;
|
|
@@ -4268,8 +4344,23 @@ export class WebGPURenderer {
|
|
|
4268
4344
|
* Built once and held, rather than made per call: it closes over `this` and a fresh one
|
|
4269
4345
|
* every frame would be an allocation in a per-frame path.
|
|
4270
4346
|
*/
|
|
4347
|
+
/** The glass the open shadow pass was offered, kept for its own passes: `glassCasters.ts`. */
|
|
4348
|
+
glassCasters = new GlassCasterList();
|
|
4349
|
+
/** One resolution every glass caster is read through, so recording one allocates nothing. */
|
|
4350
|
+
glassCasterScratch = {
|
|
4351
|
+
transmission: 0,
|
|
4352
|
+
frost: 0,
|
|
4353
|
+
tint: [1, 1, 1],
|
|
4354
|
+
};
|
|
4271
4355
|
casterSink = {
|
|
4272
4356
|
mesh: (mesh, model, material) => {
|
|
4357
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
4358
|
+
/* Glass: kept aside for its own two passes rather than dropped. See `glassCasters.ts`. */
|
|
4359
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
4360
|
+
this.glassCasters.recordMesh(mesh, model, material, this.glassCasterScratch);
|
|
4361
|
+
}
|
|
4362
|
+
return;
|
|
4363
|
+
}
|
|
4273
4364
|
const pass = this.shadowPass;
|
|
4274
4365
|
if (pass === null)
|
|
4275
4366
|
return;
|
|
@@ -4303,6 +4394,12 @@ export class WebGPURenderer {
|
|
|
4303
4394
|
* uses — one draw, however many instances, exactly as the colour pass does it.
|
|
4304
4395
|
*/
|
|
4305
4396
|
instanced: (batch, data, material) => {
|
|
4397
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
4398
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
4399
|
+
this.glassCasters.recordInstanced(batch, data, material, this.glassCasterScratch);
|
|
4400
|
+
}
|
|
4401
|
+
return;
|
|
4402
|
+
}
|
|
4306
4403
|
const pass = this.shadowPass;
|
|
4307
4404
|
if (pass === null)
|
|
4308
4405
|
return;
|
|
@@ -4336,7 +4433,13 @@ export class WebGPURenderer {
|
|
|
4336
4433
|
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
4337
4434
|
pass.drawIndexed(geometry.indexCount, count);
|
|
4338
4435
|
},
|
|
4339
|
-
skinnedMesh: (mesh, model, palette) => {
|
|
4436
|
+
skinnedMesh: (mesh, model, palette, material) => {
|
|
4437
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
4438
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
4439
|
+
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
4440
|
+
}
|
|
4441
|
+
return;
|
|
4442
|
+
}
|
|
4340
4443
|
const pass = this.shadowPass;
|
|
4341
4444
|
if (pass === null)
|
|
4342
4445
|
return;
|
|
@@ -4671,7 +4774,9 @@ export class WebGPURenderer {
|
|
|
4671
4774
|
* nothing at all under the default profile, which is on. The cache carries it for the same
|
|
4672
4775
|
* reason it carries the sample count: no pass can then disagree with the attachment.
|
|
4673
4776
|
*/
|
|
4674
|
-
|
|
4777
|
+
/* Off compiles none of the glass lookup into a lit pipeline: see `PipelineCache.glassShadows`. */
|
|
4778
|
+
const glassShadows = quality.glassShadows !== 'off';
|
|
4779
|
+
this.pipelines = new PipelineCache(device, quality.screenEffects ? sceneColorFormat(quality.hdrScene) : surface.format, this.samples, glassShadows);
|
|
4675
4780
|
/*
|
|
4676
4781
|
* The canvas' own state, for the pass `ensurePass` reopens after `endFrame`.
|
|
4677
4782
|
*
|
|
@@ -4683,7 +4788,12 @@ export class WebGPURenderer {
|
|
|
4683
4788
|
this.overlayPipelines =
|
|
4684
4789
|
this.pipelines.format === surface.format && this.samples === 1
|
|
4685
4790
|
? this.pipelines
|
|
4686
|
-
: new PipelineCache(device, surface.format, 1);
|
|
4791
|
+
: new PipelineCache(device, surface.format, 1, glassShadows);
|
|
4792
|
+
this.latePipelines =
|
|
4793
|
+
this.pipelines.format === RECON_HISTORY_FORMAT && this.samples === 1
|
|
4794
|
+
? this.pipelines
|
|
4795
|
+
: new PipelineCache(device, RECON_HISTORY_FORMAT, 1, glassShadows);
|
|
4796
|
+
this.issueBound = this.issueCommand.bind(this);
|
|
4687
4797
|
this.pipelineTargets =
|
|
4688
4798
|
this.overlayPipelines === this.pipelines
|
|
4689
4799
|
? [this.pipelines]
|
|
@@ -4804,42 +4914,35 @@ export class WebGPURenderer {
|
|
|
4804
4914
|
.createView({ dimension: '3d' });
|
|
4805
4915
|
const blankDriftIndexView = blankVolume('flat.emptyDriftLightIndex', 'r32uint');
|
|
4806
4916
|
const blankDriftAtlasView = blankVolume('flat.emptyDriftLightAtlas', 'rgba16float');
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4917
|
+
/*
|
|
4918
|
+
* The static world, its movers and — where more than one static depth layer is asked for —
|
|
4919
|
+
* the peel, as layers of one texture, on the conditions `renderer.ts` builds its array on: one
|
|
4920
|
+
* decision read twice. Same size and format throughout, because the shader samples every
|
|
4921
|
+
* layer with one \`uShadowMapSize\` and one set of filter offsets.
|
|
4922
|
+
*/
|
|
4923
|
+
const sunLayers = this.quality.directionalShadows
|
|
4924
|
+
? this.quality.directionalShadowDepthLayers > 1
|
|
4925
|
+
? 3
|
|
4926
|
+
: 2
|
|
4927
|
+
: 1;
|
|
4928
|
+
this.sunShadowTexture = device.createTexture({
|
|
4929
|
+
label: 'shadow.sun',
|
|
4930
|
+
size: [
|
|
4931
|
+
this.quality.directionalShadowMapSize,
|
|
4932
|
+
this.quality.directionalShadowMapSize,
|
|
4933
|
+
sunLayers,
|
|
4934
|
+
],
|
|
4810
4935
|
format: SHADOW_FORMAT,
|
|
4811
|
-
|
|
4936
|
+
/* Copyable both ways, because the first glass grows it by copying every layer across. */
|
|
4937
|
+
usage: SUN_USAGE,
|
|
4812
4938
|
});
|
|
4813
|
-
this.
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
this.
|
|
4820
|
-
this.quality.directionalShadowDepthLayers > 1
|
|
4821
|
-
? device.createTexture({
|
|
4822
|
-
label: 'shadow.peel',
|
|
4823
|
-
size: [this.quality.directionalShadowMapSize, this.quality.directionalShadowMapSize],
|
|
4824
|
-
format: SHADOW_FORMAT,
|
|
4825
|
-
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
4826
|
-
})
|
|
4827
|
-
: null;
|
|
4828
|
-
this.peelView = this.peelMap === null ? null : this.peelMap.createView();
|
|
4829
|
-
/*
|
|
4830
|
-
* The movers' layer, on the condition `renderer.ts` builds its own on: directional shadows
|
|
4831
|
-
* at all. Same size and same format as the static map, because the shader samples all three
|
|
4832
|
-
* with one `uShadowMapSize` and one set of PCF offsets.
|
|
4833
|
-
*/
|
|
4834
|
-
this.dynamicMap = this.quality.directionalShadows
|
|
4835
|
-
? device.createTexture({
|
|
4836
|
-
label: 'shadow.dynamic',
|
|
4837
|
-
size: [this.quality.directionalShadowMapSize, this.quality.directionalShadowMapSize],
|
|
4838
|
-
format: SHADOW_FORMAT,
|
|
4839
|
-
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
4840
|
-
})
|
|
4841
|
-
: null;
|
|
4842
|
-
this.dynamicView = this.dynamicMap === null ? null : this.dynamicMap.createView();
|
|
4939
|
+
this.sunLayerCount = sunLayers;
|
|
4940
|
+
const views = sunViewsOf(this.sunShadowTexture, sunLayers, sunLayers);
|
|
4941
|
+
this.sunArrayView = views.array;
|
|
4942
|
+
this.sunStaticArrayView = views.staticArray;
|
|
4943
|
+
this.shadowView = views.static;
|
|
4944
|
+
this.dynamicView = views.dynamic;
|
|
4945
|
+
this.peelView = views.peel;
|
|
4843
4946
|
/*
|
|
4844
4947
|
* **Cleared once, here, because a texture no pass has written reads as zero**, which the
|
|
4845
4948
|
* specification requires, and a zero in a map compared with `LEQUAL` is an occluder at the
|
|
@@ -4880,6 +4983,14 @@ export class WebGPURenderer {
|
|
|
4880
4983
|
* rather than the hardware's.
|
|
4881
4984
|
*/
|
|
4882
4985
|
const shadowSampler = device.createSampler({ label: 'shadow.sampler' });
|
|
4986
|
+
this.tintSampler = device.createSampler({
|
|
4987
|
+
label: 'shadow.tintSampler',
|
|
4988
|
+
magFilter: 'linear',
|
|
4989
|
+
minFilter: 'linear',
|
|
4990
|
+
mipmapFilter: 'linear',
|
|
4991
|
+
addressModeU: 'clamp-to-edge',
|
|
4992
|
+
addressModeV: 'clamp-to-edge',
|
|
4993
|
+
});
|
|
4883
4994
|
/*
|
|
4884
4995
|
* The photometric atlas's own sampler, **created here rather than reusing `postSampler`.**
|
|
4885
4996
|
*
|
|
@@ -4900,15 +5011,18 @@ export class WebGPURenderer {
|
|
|
4900
5011
|
addressModeV: 'clamp-to-edge',
|
|
4901
5012
|
});
|
|
4902
5013
|
this.flatTextures = (name) => {
|
|
4903
|
-
if (name === '
|
|
4904
|
-
return { view: this.
|
|
4905
|
-
/*
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
if (name === '
|
|
4911
|
-
return {
|
|
5014
|
+
if (name === 'uSunShadows')
|
|
5015
|
+
return { view: this.sunArrayView, sampler: shadowSampler };
|
|
5016
|
+
/* What the sun keeps through glass, or the one-texel stand-in the lookup reads as none. */
|
|
5017
|
+
if (name === 'uSunGlassTints') {
|
|
5018
|
+
return { view: this.sunTint?.array ?? blankArrayView, sampler: this.tintSampler };
|
|
5019
|
+
}
|
|
5020
|
+
/* What the lamps keep through glass, one layer a light, or the same stand-in. */
|
|
5021
|
+
if (name === 'uPointGlassTints') {
|
|
5022
|
+
return {
|
|
5023
|
+
view: this.pointShadowArray?.glassTint?.view ?? blankArrayView,
|
|
5024
|
+
sampler: this.tintSampler,
|
|
5025
|
+
};
|
|
4912
5026
|
}
|
|
4913
5027
|
/*
|
|
4914
5028
|
* The caller's image, or the stand-in. Read from the field rather than passed in, because
|
|
@@ -4946,10 +5060,6 @@ export class WebGPURenderer {
|
|
|
4946
5060
|
? { view: blankView, sampler: blankSampler }
|
|
4947
5061
|
: { view: albedo.view, sampler: albedo.sampler };
|
|
4948
5062
|
}
|
|
4949
|
-
/* The peel, where the profile asked for one. `uPeeledShadowEnabled` gates the sample. */
|
|
4950
|
-
if (name === 'uPeeledShadowMap' && this.peelView !== null) {
|
|
4951
|
-
return { view: this.peelView, sampler: shadowSampler };
|
|
4952
|
-
}
|
|
4953
5063
|
/*
|
|
4954
5064
|
* The room, once a bake has filled it. Unbaked reads as the blank cube: nothing to mirror.
|
|
4955
5065
|
*
|
|
@@ -5018,7 +5128,15 @@ export class WebGPURenderer {
|
|
|
5018
5128
|
* texels by construction, an offset that snapped to one being no offset at all.
|
|
5019
5129
|
*/
|
|
5020
5130
|
if (name === 'uRefractScene') {
|
|
5021
|
-
|
|
5131
|
+
this.refractSampler ??= device.createSampler({
|
|
5132
|
+
label: 'refract.sampler',
|
|
5133
|
+
magFilter: 'linear',
|
|
5134
|
+
minFilter: 'linear',
|
|
5135
|
+
mipmapFilter: 'linear',
|
|
5136
|
+
addressModeU: 'clamp-to-edge',
|
|
5137
|
+
addressModeV: 'clamp-to-edge',
|
|
5138
|
+
});
|
|
5139
|
+
return { view: this.refractSnapshotView ?? blankView, sampler: this.refractSampler };
|
|
5022
5140
|
}
|
|
5023
5141
|
/*
|
|
5024
5142
|
* Every point light's shadow, in one binding.
|
|
@@ -5198,9 +5316,7 @@ export class WebGPURenderer {
|
|
|
5198
5316
|
*/
|
|
5199
5317
|
this.mediumLayout = createPostStageLayout(device, MEDIUM_UNIFORMS, MEDIUM_FRAG_SIZE, [
|
|
5200
5318
|
{ binding: MEDIUM_TEXTURES.uDepth, filterable: false },
|
|
5201
|
-
{ binding: MEDIUM_TEXTURES.
|
|
5202
|
-
{ binding: MEDIUM_TEXTURES.uPeeledShadowMap, filterable: false },
|
|
5203
|
-
{ binding: MEDIUM_TEXTURES.uDynamicShadowMap, filterable: false },
|
|
5319
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows, filterable: false, dimension: '2d-array' },
|
|
5204
5320
|
]);
|
|
5205
5321
|
this.mediumUniforms = device.createBuffer({
|
|
5206
5322
|
label: 'post.mediumUniforms',
|
|
@@ -5230,6 +5346,8 @@ export class WebGPURenderer {
|
|
|
5230
5346
|
this.taaLayout = createPostStageLayout(device, TAA_UNIFORMS, TAA_FRAG_SIZE, [
|
|
5231
5347
|
{ binding: TAA_TEXTURES.uScene },
|
|
5232
5348
|
{ binding: TAA_TEXTURES.uHistory },
|
|
5349
|
+
{ binding: TAA_TEXTURES.uFlicker },
|
|
5350
|
+
{ binding: TAA_TEXTURES.uFlickerMotion },
|
|
5233
5351
|
{ binding: TAA_TEXTURES.uDepth, filterable: false },
|
|
5234
5352
|
]);
|
|
5235
5353
|
this.taaUniforms = device.createBuffer({
|
|
@@ -5331,9 +5449,9 @@ export class WebGPURenderer {
|
|
|
5331
5449
|
device.queue.writeBuffer(depthPassUniforms, 0, new Int32Array([0]));
|
|
5332
5450
|
this.scatterDepthLayout = createScatterDepthBindGroupLayout(device, DEPTH_FRAG_BINDING, DEPTH_FRAG_SIZE, DEPTH_PREVIOUS_BINDING);
|
|
5333
5451
|
this.scatterDepthDraws = new UniformRing(device, SCATTER_DEPTH_SIZE, MAX_SCATTER_DEPTH_DRAWS, USAGE_UNIFORM_DST, 'scatter.depthDrawRing', this.scatterDepthBudget);
|
|
5334
|
-
this.scatterDepthBindGroup = createScatterDepthBindGroup(device, this.scatterDepthLayout, this.scatterDepthDraws.buffer, { binding: DEPTH_FRAG_BINDING, buffer: depthPassUniforms, size: DEPTH_FRAG_SIZE }, DEPTH_PREVIOUS_BINDING,
|
|
5335
|
-
this.shadowBindGroup = createDepthBindGroup(device, this.shadowLayout, this.shadowDraws.buffer, depthPassUniforms,
|
|
5336
|
-
this.shadowBlankView =
|
|
5452
|
+
this.scatterDepthBindGroup = createScatterDepthBindGroup(device, this.scatterDepthLayout, this.scatterDepthDraws.buffer, { binding: DEPTH_FRAG_BINDING, buffer: depthPassUniforms, size: DEPTH_FRAG_SIZE }, DEPTH_PREVIOUS_BINDING, blankArrayView, blankSampler);
|
|
5453
|
+
this.shadowBindGroup = createDepthBindGroup(device, this.shadowLayout, this.shadowDraws.buffer, depthPassUniforms, blankArrayView, blankSampler);
|
|
5454
|
+
this.shadowBlankView = blankArrayView;
|
|
5337
5455
|
this.shadowBlankSampler = blankSampler;
|
|
5338
5456
|
this.shadowPassUniforms = depthPassUniforms;
|
|
5339
5457
|
/*
|
|
@@ -5364,11 +5482,11 @@ export class WebGPURenderer {
|
|
|
5364
5482
|
this.shadowPeelBindGroup =
|
|
5365
5483
|
peelPassUniforms === null
|
|
5366
5484
|
? null
|
|
5367
|
-
: createDepthBindGroup(device, this.shadowLayout, this.shadowDraws.buffer, peelPassUniforms, this.
|
|
5485
|
+
: createDepthBindGroup(device, this.shadowLayout, this.shadowDraws.buffer, peelPassUniforms, this.sunStaticArrayView, shadowSampler);
|
|
5368
5486
|
this.scatterDepthPeelBindGroup =
|
|
5369
5487
|
peelPassUniforms === null
|
|
5370
5488
|
? null
|
|
5371
|
-
: createScatterDepthBindGroup(device, this.scatterDepthLayout, this.scatterDepthDraws.buffer, { binding: DEPTH_FRAG_BINDING, buffer: peelPassUniforms, size: DEPTH_FRAG_SIZE }, DEPTH_PREVIOUS_BINDING, this.
|
|
5489
|
+
: createScatterDepthBindGroup(device, this.scatterDepthLayout, this.scatterDepthDraws.buffer, { binding: DEPTH_FRAG_BINDING, buffer: peelPassUniforms, size: DEPTH_FRAG_SIZE }, DEPTH_PREVIOUS_BINDING, this.sunStaticArrayView, shadowSampler);
|
|
5372
5490
|
/*
|
|
5373
5491
|
* The overlays. Rings rather than single blocks because a frame draws several of each —
|
|
5374
5492
|
* the loader alone is two panels and two strings — and `queue.writeBuffer` does not
|
|
@@ -5609,7 +5727,10 @@ export class WebGPURenderer {
|
|
|
5609
5727
|
this.perFrame.reset();
|
|
5610
5728
|
this.materials.dirty();
|
|
5611
5729
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
5612
|
-
this.
|
|
5730
|
+
this.motionPass?.reset();
|
|
5731
|
+
this.latePass?.reset();
|
|
5732
|
+
this.moverFrame += 1;
|
|
5733
|
+
this.inFrame = true;
|
|
5613
5734
|
/*
|
|
5614
5735
|
* The canvas rather than the swap chain's texture, and the difference is that reading this
|
|
5615
5736
|
* does not *acquire* anything. See `swapView`.
|
|
@@ -5892,7 +6013,27 @@ export class WebGPURenderer {
|
|
|
5892
6013
|
'would be patched from was not kept. Say so at `createMesh` — the flag is what decides ' +
|
|
5893
6014
|
'what is kept and what a buffer is hinted as.');
|
|
5894
6015
|
}
|
|
5895
|
-
|
|
6016
|
+
/*
|
|
6017
|
+
* **The rows before this rewrite are last frame's**, kept only for a reconstruction and only
|
|
6018
|
+
* on the first rewrite of a frame — a second must not replace where the vertices were last
|
|
6019
|
+
* frame with where they were a moment ago. A rewrite between frames is the next frame's. See
|
|
6020
|
+
* `changeFrames.ts`.
|
|
6021
|
+
*/
|
|
6022
|
+
const motion = mesh.motion;
|
|
6023
|
+
let previous = null;
|
|
6024
|
+
if (motion !== null && this.reconstructionWanted) {
|
|
6025
|
+
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
6026
|
+
if (keepsPrevious(motion.changed, frame)) {
|
|
6027
|
+
motion.previous ??= this.surface.device.createBuffer({
|
|
6028
|
+
label: 'recon.dynamicPrevious',
|
|
6029
|
+
size: mesh.vertexBuffers[0]?.size ?? 0,
|
|
6030
|
+
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
6031
|
+
});
|
|
6032
|
+
previous = motion.previous;
|
|
6033
|
+
}
|
|
6034
|
+
motion.changed = frame;
|
|
6035
|
+
}
|
|
6036
|
+
mesh.update(this.surface.device, positions, normals, previous);
|
|
5896
6037
|
}
|
|
5897
6038
|
/**
|
|
5898
6039
|
* Settle everything the frame shares: the camera, the lights and the fog.
|
|
@@ -6520,8 +6661,8 @@ export class WebGPURenderer {
|
|
|
6520
6661
|
* depth, so a motion pass drawing it would either be depth-rejected everywhere — wasted — or,
|
|
6521
6662
|
* where it happened to pass, write motion for a surface no pixel of the frame belongs to.
|
|
6522
6663
|
*/
|
|
6523
|
-
if (
|
|
6524
|
-
this.
|
|
6664
|
+
if (this.tracksMotionNow())
|
|
6665
|
+
this.recordDrawMotion(mesh, model, previousModel);
|
|
6525
6666
|
this.submitMesh(mesh, model, tint, 1, false, { depthLayer });
|
|
6526
6667
|
}
|
|
6527
6668
|
/**
|
|
@@ -6656,7 +6797,9 @@ export class WebGPURenderer {
|
|
|
6656
6797
|
}
|
|
6657
6798
|
return;
|
|
6658
6799
|
}
|
|
6659
|
-
|
|
6800
|
+
/* A blended draw landing after the upscale is drawn into an unjittered picture. */
|
|
6801
|
+
const late = blend && (this.drawsLate() || (this.oitReplaying && this.reconstructing));
|
|
6802
|
+
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uViewProj.offset, late ? this.correctedViewProj : this.viewProj);
|
|
6660
6803
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
6661
6804
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
6662
6805
|
/* The frame's wind, for a mesh carrying a sway lane. Written on this path and not the
|
|
@@ -6717,17 +6860,7 @@ export class WebGPURenderer {
|
|
|
6717
6860
|
* shades as an ordinary translucent one rather than sampling a texture holding the frame
|
|
6718
6861
|
* before last. That is the defined state the two-backends rule asks for.
|
|
6719
6862
|
*/
|
|
6720
|
-
const
|
|
6721
|
-
const refracting = refraction > 0 && this.takeRefractSnapshot();
|
|
6722
|
-
if (refracting) {
|
|
6723
|
-
this.perFrameFloats[this.materialField('uRefractStrength')] = refraction;
|
|
6724
|
-
const refractTint = options.refractTint;
|
|
6725
|
-
const tintAt = this.materialField('uRefractTint');
|
|
6726
|
-
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
6727
|
-
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
6728
|
-
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
6729
|
-
this.perFrameFloats[this.materialField('uRefractThickness')] = options.thicknessM ?? 0;
|
|
6730
|
-
}
|
|
6863
|
+
const refracting = this.bindSeeThrough(options);
|
|
6731
6864
|
/* Taken for itself when any of those differs from the pass, and put back below: the rule both
|
|
6732
6865
|
backends count material changes by. See `materialChanges.ts`. */
|
|
6733
6866
|
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
@@ -6786,7 +6919,7 @@ export class WebGPURenderer {
|
|
|
6786
6919
|
* pays the compile it was trying to avoid. That is a slow frame; the alternative is a
|
|
6787
6920
|
* thrown error in the middle of one.
|
|
6788
6921
|
*/
|
|
6789
|
-
const pipelines = this.targetPipelines();
|
|
6922
|
+
const pipelines = blend ? this.blendedPipelines() : this.targetPipelines();
|
|
6790
6923
|
let pipeline = pipelines.peek(key);
|
|
6791
6924
|
if (pipeline === undefined) {
|
|
6792
6925
|
const present = this.meshPresent.get(base);
|
|
@@ -6818,7 +6951,7 @@ export class WebGPURenderer {
|
|
|
6818
6951
|
* the uniform rings are the state arena this design would otherwise have had to build —
|
|
6819
6952
|
* so the command carries them as its two dynamic offsets.
|
|
6820
6953
|
*/
|
|
6821
|
-
const command = this.recordDraw(0, this.currentTarget());
|
|
6954
|
+
const command = blend ? this.recordBlended(0) : this.recordDraw(0, this.currentTarget());
|
|
6822
6955
|
if (command !== null) {
|
|
6823
6956
|
command.pipeline = pipeline;
|
|
6824
6957
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
@@ -6859,11 +6992,10 @@ export class WebGPURenderer {
|
|
|
6859
6992
|
if (!fog)
|
|
6860
6993
|
this.perFrameInts[this.materialField('uFogEnabled')] = 1;
|
|
6861
6994
|
}
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
this.
|
|
6866
|
-
}
|
|
6995
|
+
/* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
|
|
6996
|
+
shared, so a strength left set is worn by everything drawn after it. */
|
|
6997
|
+
if (refracting)
|
|
6998
|
+
this.releaseSeeThrough();
|
|
6867
6999
|
if (!toneMapped) {
|
|
6868
7000
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
6869
7001
|
}
|
|
@@ -6923,11 +7055,35 @@ export class WebGPURenderer {
|
|
|
6923
7055
|
uploadInstanced(batch, data) {
|
|
6924
7056
|
if (this.surface.lost)
|
|
6925
7057
|
return;
|
|
6926
|
-
|
|
7058
|
+
/*
|
|
7059
|
+
* **The placement before this upload is last frame's**, kept only for a reconstruction and only
|
|
7060
|
+
* on a frame's first upload, for the reason `updateMesh` gives. See `changeFrames.ts`.
|
|
7061
|
+
*/
|
|
7062
|
+
let previous = null;
|
|
7063
|
+
if (this.reconstructionWanted) {
|
|
7064
|
+
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
7065
|
+
if (keepsPrevious(batch.changed, frame)) {
|
|
7066
|
+
batch.previousBuffer ??= this.surface.device.createBuffer({
|
|
7067
|
+
label: 'recon.instancesPrevious',
|
|
7068
|
+
size: batch.capacity * INSTANCE_STRIDE,
|
|
7069
|
+
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
7070
|
+
});
|
|
7071
|
+
previous = batch.previousBuffer;
|
|
7072
|
+
}
|
|
7073
|
+
batch.changed = frame;
|
|
7074
|
+
}
|
|
7075
|
+
batch.upload(this.surface.device.queue, data, previous);
|
|
6927
7076
|
}
|
|
6928
7077
|
/** Draw every live instance, opaque. */
|
|
6929
7078
|
drawInstanced(batch, data) {
|
|
6930
7079
|
this.submitInstanced(batch, data, 1, false, {});
|
|
7080
|
+
/* A batch that moved this frame pairs each slot with last frame's; the batch is the identity. */
|
|
7081
|
+
const previous = batch.previousBuffer;
|
|
7082
|
+
if (previous !== null && batch.changed === this.moverFrame && this.tracksMotionNow()) {
|
|
7083
|
+
const paired = pairedInstances(batch.previousCount, Math.min(data.count, batch.capacity));
|
|
7084
|
+
if (paired > 0)
|
|
7085
|
+
this.motionPass?.recordInstanced(batch.mesh, batch.buffer, previous, paired);
|
|
7086
|
+
}
|
|
6931
7087
|
}
|
|
6932
7088
|
/** Draw every live instance, blended. See `drawTranslucentMesh` for what the options mean. */
|
|
6933
7089
|
drawTranslucentInstanced(batch, data, opacity, options = {}) {
|
|
@@ -6938,6 +7094,44 @@ export class WebGPURenderer {
|
|
|
6938
7094
|
this.instanced.delete(batch.mesh);
|
|
6939
7095
|
batch.dispose();
|
|
6940
7096
|
}
|
|
7097
|
+
/**
|
|
7098
|
+
* Write what a translucent draw shows through it into the material scratch: refraction's bend and
|
|
7099
|
+
* absorption, and glass. Returns whether the frame's copy is there to read, and so whether
|
|
7100
|
+
* `releaseSeeThrough` owes the scratch back.
|
|
7101
|
+
*
|
|
7102
|
+
* **A glass draw takes the copy even when it does not bend**, since a pane shows what is behind it
|
|
7103
|
+
* either way. A copy that cannot be taken — a mirror, a probe bake — leaves both off, so the pane
|
|
7104
|
+
* shades as ordinary translucency rather than sampling a stand-in. See `glass.ts`.
|
|
7105
|
+
*/
|
|
7106
|
+
bindSeeThrough(options) {
|
|
7107
|
+
const refraction = options.refraction ?? 0;
|
|
7108
|
+
const glassy = resolveGlass(options.glass, this.glassScratch);
|
|
7109
|
+
if ((refraction <= 0 && !glassy) || !this.takeRefractSnapshot())
|
|
7110
|
+
return false;
|
|
7111
|
+
const glass = this.glassScratch;
|
|
7112
|
+
/* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
|
|
7113
|
+
const seeAt = this.materialField('uSeeThrough');
|
|
7114
|
+
this.perFrameFloats[seeAt] = Math.max(refraction, 0);
|
|
7115
|
+
this.perFrameFloats[seeAt + 1] = options.thicknessM ?? 0;
|
|
7116
|
+
this.perFrameFloats[seeAt + 2] = glass.transmission;
|
|
7117
|
+
this.perFrameFloats[seeAt + 3] = glass.frost;
|
|
7118
|
+
const refractTint = options.refractTint;
|
|
7119
|
+
const tintAt = this.materialField('uRefractTint');
|
|
7120
|
+
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
7121
|
+
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
7122
|
+
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
7123
|
+
const glassTintAt = this.materialField('uGlassTint');
|
|
7124
|
+
this.perFrameFloats[glassTintAt] = glass.tint[0];
|
|
7125
|
+
this.perFrameFloats[glassTintAt + 1] = glass.tint[1];
|
|
7126
|
+
this.perFrameFloats[glassTintAt + 2] = glass.tint[2];
|
|
7127
|
+
return true;
|
|
7128
|
+
}
|
|
7129
|
+
/** Put the scratch back after `bindSeeThrough`: refraction and glass off. */
|
|
7130
|
+
releaseSeeThrough() {
|
|
7131
|
+
const seeAt = this.materialField('uSeeThrough');
|
|
7132
|
+
this.perFrameFloats[seeAt] = 0;
|
|
7133
|
+
this.perFrameFloats[seeAt + 2] = 0;
|
|
7134
|
+
}
|
|
6941
7135
|
/**
|
|
6942
7136
|
* One draw, one material slot, one uniform slot, however many instances.
|
|
6943
7137
|
*
|
|
@@ -6967,7 +7161,7 @@ export class WebGPURenderer {
|
|
|
6967
7161
|
}
|
|
6968
7162
|
return;
|
|
6969
7163
|
}
|
|
6970
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uViewProj.offset, this.viewProj);
|
|
7164
|
+
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uViewProj.offset, blend && this.drawsLate() ? this.correctedViewProj : this.viewProj);
|
|
6971
7165
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
6972
7166
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
6973
7167
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
@@ -6990,8 +7184,9 @@ export class WebGPURenderer {
|
|
|
6990
7184
|
}
|
|
6991
7185
|
const depthWrite = options.depthWrite ?? true;
|
|
6992
7186
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
6993
|
-
/*
|
|
6994
|
-
const
|
|
7187
|
+
/* A blended batch shows what is behind it — refraction or glass — as a single draw does. */
|
|
7188
|
+
const refracting = blend && this.bindSeeThrough(options);
|
|
7189
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
6995
7190
|
if (own)
|
|
6996
7191
|
this.materials.dirty();
|
|
6997
7192
|
const base = mesh.key ?? 'flat:s0:u0';
|
|
@@ -7001,7 +7196,7 @@ export class WebGPURenderer {
|
|
|
7001
7196
|
const key = `${base}|inst${blend ? '|blend' : ''}` +
|
|
7002
7197
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
7003
7198
|
`${this.materialDoubleSided ? '|2s' : ''}`;
|
|
7004
|
-
const pipelines = this.targetPipelines();
|
|
7199
|
+
const pipelines = blend ? this.blendedPipelines() : this.targetPipelines();
|
|
7005
7200
|
let pipeline = pipelines.peek(key);
|
|
7006
7201
|
if (pipeline === undefined) {
|
|
7007
7202
|
const present = this.meshPresent.get(base);
|
|
@@ -7012,7 +7207,7 @@ export class WebGPURenderer {
|
|
|
7012
7207
|
}
|
|
7013
7208
|
const material = this.materialSlotForDraw();
|
|
7014
7209
|
if (material !== null) {
|
|
7015
|
-
const command = this.recordDraw(0, this.currentTarget());
|
|
7210
|
+
const command = blend ? this.recordBlended(0) : this.recordDraw(0, this.currentTarget());
|
|
7016
7211
|
if (command !== null) {
|
|
7017
7212
|
command.pipeline = pipeline;
|
|
7018
7213
|
command.bindGroup = this.flatBindGroupFor(false, false, null);
|
|
@@ -7058,6 +7253,8 @@ export class WebGPURenderer {
|
|
|
7058
7253
|
if (!toneMapped) {
|
|
7059
7254
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
7060
7255
|
}
|
|
7256
|
+
if (refracting)
|
|
7257
|
+
this.releaseSeeThrough();
|
|
7061
7258
|
if (own)
|
|
7062
7259
|
this.materials.dirty();
|
|
7063
7260
|
}
|
|
@@ -7231,6 +7428,8 @@ export class WebGPURenderer {
|
|
|
7231
7428
|
this.lightViewProj.set(lightViewProj);
|
|
7232
7429
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
7233
7430
|
this.shadowLayerIsPeel = peel;
|
|
7431
|
+
this.shadowLayerIsDynamic = dynamic;
|
|
7432
|
+
this.glassCasters.clear();
|
|
7234
7433
|
/*
|
|
7235
7434
|
* A frame's peel is only as good as the pass that fills it, and the static layer is the one
|
|
7236
7435
|
* that opens first.
|
|
@@ -7336,13 +7535,295 @@ export class WebGPURenderer {
|
|
|
7336
7535
|
casters(this.casterSink);
|
|
7337
7536
|
}
|
|
7338
7537
|
/** Close the shadow pass and submit it, so the frame can sample what it wrote. */
|
|
7538
|
+
/**
|
|
7539
|
+
* Add the sun's two glass layers, once, carrying every layer it holds across on `encoder`.
|
|
7540
|
+
*
|
|
7541
|
+
* **Copied rather than re-baked**, for the reason `SunShadowArray.growForGlass` gives: the static
|
|
7542
|
+
* map is the consumer's to bake and may be baked once, and the glass that grows the texture
|
|
7543
|
+
* arrives in the very pass that drew it. Every view of the old texture is stale afterwards, so
|
|
7544
|
+
* every group holding one is rebuilt here — the lit pass's, the volumes', the medium's and the
|
|
7545
|
+
* peel's — and the old texture is destroyed only once this encoder is submitted.
|
|
7546
|
+
*/
|
|
7547
|
+
growSunForGlass(encoder) {
|
|
7548
|
+
if (this.sunGlassViews !== null)
|
|
7549
|
+
return;
|
|
7550
|
+
const device = this.surface.device;
|
|
7551
|
+
const size = this.quality.directionalShadowMapSize;
|
|
7552
|
+
const maps = this.sunLayerCount;
|
|
7553
|
+
/* The glass layers at their fixed places after the three maps: see `SUN_GLASS_STATIC_LAYER`. */
|
|
7554
|
+
const layers = SUN_GLASS_MOVING_LAYER + 1;
|
|
7555
|
+
const old = this.sunShadowTexture;
|
|
7556
|
+
const texture = device.createTexture({
|
|
7557
|
+
label: 'shadow.sun',
|
|
7558
|
+
size: [size, size, layers],
|
|
7559
|
+
format: SHADOW_FORMAT,
|
|
7560
|
+
usage: SUN_USAGE,
|
|
7561
|
+
});
|
|
7562
|
+
for (let layer = 0; layer < maps; layer++) {
|
|
7563
|
+
encoder.copyTextureToTexture({ texture: old, origin: { x: 0, y: 0, z: layer } }, { texture, origin: { x: 0, y: 0, z: layer } }, [size, size, 1]);
|
|
7564
|
+
}
|
|
7565
|
+
this.retiredSunTexture = old;
|
|
7566
|
+
this.sunShadowTexture = texture;
|
|
7567
|
+
this.sunLayerCount = layers;
|
|
7568
|
+
const views = sunViewsOf(texture, maps, layers);
|
|
7569
|
+
this.sunArrayView = views.array;
|
|
7570
|
+
this.sunStaticArrayView = views.staticArray;
|
|
7571
|
+
this.shadowView = views.static;
|
|
7572
|
+
this.dynamicView = views.dynamic;
|
|
7573
|
+
this.peelView = views.peel;
|
|
7574
|
+
this.sunGlassViews = views.glass;
|
|
7575
|
+
/* Both glass layers born at the far plane: nothing is nearer the sun than a pane not drawn. */
|
|
7576
|
+
for (const view of views.glass ?? []) {
|
|
7577
|
+
encoder
|
|
7578
|
+
.beginRenderPass({
|
|
7579
|
+
label: 'shadow.glass.clear',
|
|
7580
|
+
colorAttachments: [],
|
|
7581
|
+
depthStencilAttachment: {
|
|
7582
|
+
view,
|
|
7583
|
+
depthClearValue: 1,
|
|
7584
|
+
depthLoadOp: 'clear',
|
|
7585
|
+
depthStoreOp: 'store',
|
|
7586
|
+
},
|
|
7587
|
+
})
|
|
7588
|
+
.end();
|
|
7589
|
+
}
|
|
7590
|
+
this.rebuildSunGroups();
|
|
7591
|
+
}
|
|
7592
|
+
/** Every group that holds a view of the sun's texture, rebuilt after it has been replaced. */
|
|
7593
|
+
rebuildSunGroups() {
|
|
7594
|
+
const device = this.surface.device;
|
|
7595
|
+
const peelUniforms = this.shadowPeelPassUniforms;
|
|
7596
|
+
const peelSampler = this.shadowPeelSampler;
|
|
7597
|
+
if (peelUniforms !== null && peelSampler !== null) {
|
|
7598
|
+
this.shadowPeelBindGroup = createDepthBindGroup(device, this.shadowLayout, this.shadowDraws.buffer, peelUniforms, this.sunStaticArrayView, peelSampler);
|
|
7599
|
+
this.scatterDepthPeelBindGroup = createScatterDepthBindGroup(device, this.scatterDepthLayout, this.scatterDepthDraws.buffer, { binding: DEPTH_FRAG_BINDING, buffer: peelUniforms, size: DEPTH_FRAG_SIZE }, DEPTH_PREVIOUS_BINDING, this.sunStaticArrayView, peelSampler);
|
|
7600
|
+
}
|
|
7601
|
+
this.cutoutPeelGroups.clear();
|
|
7602
|
+
this.rebuildFlatGroupsForNewRings();
|
|
7603
|
+
this.lightVolumeBindGroup = this.buildLightVolumeBindGroup();
|
|
7604
|
+
this.mediumBindGroup = this.buildMediumBindGroup();
|
|
7605
|
+
}
|
|
7606
|
+
/** The sun's tint target, made the first time glass casts and born clear on `encoder`. */
|
|
7607
|
+
ensureSunTint(encoder) {
|
|
7608
|
+
if (this.sunTint !== null)
|
|
7609
|
+
return this.sunTint;
|
|
7610
|
+
const size = this.quality.directionalShadowMapSize;
|
|
7611
|
+
const edge = this.quality.glassShadows === 'half' ? Math.max(1, size >> 1) : size;
|
|
7612
|
+
const device = this.surface.device;
|
|
7613
|
+
const texture = device.createTexture({
|
|
7614
|
+
label: 'shadow.sunTint',
|
|
7615
|
+
size: [edge, edge, 2],
|
|
7616
|
+
format: 'rgba8unorm',
|
|
7617
|
+
mipLevelCount: Math.floor(Math.log2(edge)) + 1,
|
|
7618
|
+
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
7619
|
+
});
|
|
7620
|
+
const layer = (index) => texture.createView({
|
|
7621
|
+
dimension: '2d',
|
|
7622
|
+
baseArrayLayer: index,
|
|
7623
|
+
arrayLayerCount: 1,
|
|
7624
|
+
baseMipLevel: 0,
|
|
7625
|
+
mipLevelCount: 1,
|
|
7626
|
+
});
|
|
7627
|
+
const mips = (index) => new SnapshotMips(device, this.pipelines, texture, 'rgba8unorm', index, 'shadow.tint.mips');
|
|
7628
|
+
const target = {
|
|
7629
|
+
texture,
|
|
7630
|
+
layers: [layer(0), layer(1)],
|
|
7631
|
+
array: texture.createView({ dimension: '2d-array' }),
|
|
7632
|
+
mips: [mips(0), mips(1)],
|
|
7633
|
+
};
|
|
7634
|
+
for (const view of target.layers) {
|
|
7635
|
+
encoder
|
|
7636
|
+
.beginRenderPass({
|
|
7637
|
+
label: 'shadow.tint.clear',
|
|
7638
|
+
colorAttachments: [{ view, loadOp: 'clear', clearValue: [1, 1, 1, 1], storeOp: 'store' }],
|
|
7639
|
+
})
|
|
7640
|
+
.end();
|
|
7641
|
+
}
|
|
7642
|
+
this.sunTint = target;
|
|
7643
|
+
this.rebuildFlatGroupsForNewRings();
|
|
7644
|
+
return target;
|
|
7645
|
+
}
|
|
7646
|
+
/**
|
|
7647
|
+
* Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
|
|
7648
|
+
*
|
|
7649
|
+
* After the opaque casters, on the pass's own encoder. A layer that has held glass is redrawn
|
|
7650
|
+
* even when this pass offered none, so glass that has gone takes its colour with it.
|
|
7651
|
+
*/
|
|
7652
|
+
flushSunGlass(encoder, which) {
|
|
7653
|
+
const list = this.glassCasters;
|
|
7654
|
+
if (this.quality.glassShadows === 'off')
|
|
7655
|
+
return;
|
|
7656
|
+
if (list.count === 0 && this.sunGlassViews === null)
|
|
7657
|
+
return;
|
|
7658
|
+
if (list.count > 0)
|
|
7659
|
+
this.growSunForGlass(encoder);
|
|
7660
|
+
const glassViews = this.sunGlassViews;
|
|
7661
|
+
if (glassViews === null)
|
|
7662
|
+
return;
|
|
7663
|
+
/* Where the nearest pane is: the depth pipelines, through the sink every opaque caster took. */
|
|
7664
|
+
this.shadowPass = encoder.beginRenderPass({
|
|
7665
|
+
label: 'shadow.glass',
|
|
7666
|
+
colorAttachments: [],
|
|
7667
|
+
depthStencilAttachment: {
|
|
7668
|
+
view: glassViews[which === 'static' ? 0 : 1],
|
|
7669
|
+
depthClearValue: 1,
|
|
7670
|
+
depthLoadOp: 'clear',
|
|
7671
|
+
depthStoreOp: 'store',
|
|
7672
|
+
},
|
|
7673
|
+
});
|
|
7674
|
+
this.drawingGlassDepth = true;
|
|
7675
|
+
list.replay(this.glassDepthSink);
|
|
7676
|
+
this.drawingGlassDepth = false;
|
|
7677
|
+
this.shadowPass.end();
|
|
7678
|
+
this.shadowPass = null;
|
|
7679
|
+
/* What the panes let through, every one on a ray, multiplied. */
|
|
7680
|
+
const target = this.ensureSunTint(encoder);
|
|
7681
|
+
const tint = (this.glassTint ??= new GpuGlassTint(this.surface.device));
|
|
7682
|
+
tint.frag.reset();
|
|
7683
|
+
/* The direction the light travels, off the light matrix's depth row; the sign is immaterial,
|
|
7684
|
+
since the tint takes the cosine absolutely, and w 0 says it is a direction. */
|
|
7685
|
+
const m = this.correctedLightViewProj;
|
|
7686
|
+
const length = Math.hypot(m[2], m[6], m[10]) || 1;
|
|
7687
|
+
this.glassLight[0] = m[2] / length;
|
|
7688
|
+
this.glassLight[1] = m[6] / length;
|
|
7689
|
+
this.glassLight[2] = m[10] / length;
|
|
7690
|
+
this.glassLight[3] = 0;
|
|
7691
|
+
this.tintPass = encoder.beginRenderPass({
|
|
7692
|
+
label: 'shadow.tint',
|
|
7693
|
+
colorAttachments: [
|
|
7694
|
+
{
|
|
7695
|
+
view: target.layers[which === 'static' ? 0 : 1],
|
|
7696
|
+
loadOp: 'clear',
|
|
7697
|
+
clearValue: [1, 1, 1, 1],
|
|
7698
|
+
storeOp: 'store',
|
|
7699
|
+
},
|
|
7700
|
+
],
|
|
7701
|
+
});
|
|
7702
|
+
list.replay(this.glassTintSink);
|
|
7703
|
+
this.tintPass.end();
|
|
7704
|
+
this.tintPass = null;
|
|
7705
|
+
tint.frag.flush();
|
|
7706
|
+
/* The chain frost reads, for the layer just drawn. */
|
|
7707
|
+
target.mips[which === 'static' ? 0 : 1].record(encoder);
|
|
7708
|
+
}
|
|
7709
|
+
/** The glass list into a glass depth layer: `casterSink` itself, with glass let through. */
|
|
7710
|
+
glassDepthSink = {
|
|
7711
|
+
mesh: (mesh, model, material) => this.casterSink.mesh(mesh, model, material),
|
|
7712
|
+
instanced: (batch, data, material) => this.casterSink.instanced?.(batch, data, material),
|
|
7713
|
+
skinnedMesh: (mesh, model, palette, material) => this.casterSink.skinnedMesh(mesh, model, palette, material),
|
|
7714
|
+
};
|
|
7715
|
+
/**
|
|
7716
|
+
* The glass list into a tint layer, through the tint pipelines, **culling nothing**: every pane on
|
|
7717
|
+
* a ray counts, whichever way it faces. See the other backend's `glassTintSink`.
|
|
7718
|
+
*/
|
|
7719
|
+
glassTintSink = {
|
|
7720
|
+
mesh: (mesh, model, material, glass) => {
|
|
7721
|
+
const pass = this.tintPass;
|
|
7722
|
+
const tint = this.glassTint;
|
|
7723
|
+
if (pass === null || tint === null)
|
|
7724
|
+
return;
|
|
7725
|
+
const geometry = mesh;
|
|
7726
|
+
if (geometry.vertexBuffers === undefined)
|
|
7727
|
+
return;
|
|
7728
|
+
const cutout = cutoutOf(material);
|
|
7729
|
+
const albedo = cutout === null ? null : cutout.albedo;
|
|
7730
|
+
const slot = this.shadowDraws.allocate();
|
|
7731
|
+
const pane = tint.frag.allocate();
|
|
7732
|
+
if (slot === null || pane === null)
|
|
7733
|
+
return;
|
|
7734
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uLightViewProj.offset, this.correctedLightViewProj);
|
|
7735
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uModel.offset, model);
|
|
7736
|
+
this.writeShadowWind(slot);
|
|
7737
|
+
if (cutout !== null)
|
|
7738
|
+
this.writeCutout(slot, cutout, DEPTH_CUTOUT_VERT_FIELDS);
|
|
7739
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
7740
|
+
pass.setPipeline(tint.pipeline(this.pipelines, albedo === null ? 'rigid' : 'cutout', geometry.key ?? '', this.presentOf(geometry), 'none'));
|
|
7741
|
+
pass.setBindGroup(0, albedo === null
|
|
7742
|
+
? tint.group(this.shadowDraws.buffer)
|
|
7743
|
+
: tint.cutoutGroup(this.shadowDraws.buffer, albedo.view, albedo.sampler), [slot, pane]);
|
|
7744
|
+
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
7745
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
7746
|
+
}
|
|
7747
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
7748
|
+
pass.drawIndexed(geometry.indexCount);
|
|
7749
|
+
},
|
|
7750
|
+
instanced: (batch, data, material, glass) => {
|
|
7751
|
+
const pass = this.tintPass;
|
|
7752
|
+
const tint = this.glassTint;
|
|
7753
|
+
if (pass === null || tint === null)
|
|
7754
|
+
return;
|
|
7755
|
+
const gpuBatch = batch;
|
|
7756
|
+
const geometry = gpuBatch.mesh;
|
|
7757
|
+
if (geometry.vertexBuffers === undefined)
|
|
7758
|
+
return;
|
|
7759
|
+
const count = Math.min(data.count, gpuBatch.capacity);
|
|
7760
|
+
if (count === 0)
|
|
7761
|
+
return;
|
|
7762
|
+
const cutout = cutoutOf(material);
|
|
7763
|
+
const albedo = cutout === null ? null : cutout.albedo;
|
|
7764
|
+
const slot = this.shadowDraws.allocate();
|
|
7765
|
+
const pane = tint.frag.allocate();
|
|
7766
|
+
if (slot === null || pane === null)
|
|
7767
|
+
return;
|
|
7768
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uLightViewProj.offset, this.correctedLightViewProj);
|
|
7769
|
+
if (cutout !== null)
|
|
7770
|
+
this.writeCutout(slot, cutout, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS);
|
|
7771
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
7772
|
+
pass.setPipeline(tint.pipeline(this.pipelines, albedo === null ? 'instanced' : 'instancedCutout', geometry.key ?? '', this.presentOf(geometry), 'none'));
|
|
7773
|
+
pass.setBindGroup(0, albedo === null
|
|
7774
|
+
? tint.group(this.shadowDraws.buffer)
|
|
7775
|
+
: tint.cutoutGroup(this.shadowDraws.buffer, albedo.view, albedo.sampler), [slot, pane]);
|
|
7776
|
+
let index = 0;
|
|
7777
|
+
for (; index < geometry.vertexBuffers.length; index++) {
|
|
7778
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
7779
|
+
}
|
|
7780
|
+
pass.setVertexBuffer(index, gpuBatch.buffer);
|
|
7781
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
7782
|
+
pass.drawIndexed(geometry.indexCount, count);
|
|
7783
|
+
},
|
|
7784
|
+
skinnedMesh: (mesh, model, palette, _material, glass) => {
|
|
7785
|
+
const pass = this.tintPass;
|
|
7786
|
+
const tint = this.glassTint;
|
|
7787
|
+
if (pass === null || tint === null)
|
|
7788
|
+
return;
|
|
7789
|
+
const geometry = mesh;
|
|
7790
|
+
if (geometry.vertexBuffers === undefined)
|
|
7791
|
+
return;
|
|
7792
|
+
const paletteSlot = this.skinPalettes.take(this.surface.device, palette);
|
|
7793
|
+
const view = paletteSlot === null ? null : this.skinPalettes.view(paletteSlot);
|
|
7794
|
+
if (view === null)
|
|
7795
|
+
return;
|
|
7796
|
+
const slot = this.shadowDraws.allocate();
|
|
7797
|
+
const pane = tint.frag.allocate();
|
|
7798
|
+
if (slot === null || pane === null)
|
|
7799
|
+
return;
|
|
7800
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uLightViewProj.offset, this.correctedLightViewProj);
|
|
7801
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uModel.offset, model);
|
|
7802
|
+
this.writeShadowWind(slot);
|
|
7803
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
7804
|
+
pass.setPipeline(tint.pipeline(this.pipelines, 'skinned', geometry.key ?? '', this.presentOf(geometry), 'none'));
|
|
7805
|
+
pass.setBindGroup(0, tint.skinnedGroup(this.shadowDraws.buffer, view), [slot, pane]);
|
|
7806
|
+
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
7807
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
7808
|
+
}
|
|
7809
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
7810
|
+
pass.drawIndexed(geometry.indexCount);
|
|
7811
|
+
},
|
|
7812
|
+
};
|
|
7339
7813
|
endShadowPass() {
|
|
7340
7814
|
if (this.surface.lost || this.shadowPass === null || this.shadowEncoder === null)
|
|
7341
7815
|
return;
|
|
7816
|
+
this.shadowPass.end();
|
|
7817
|
+
/* The peel keeps no glass: its layer is the second opaque occluder and nothing else. */
|
|
7818
|
+
if (!this.shadowLayerIsPeel) {
|
|
7819
|
+
this.flushSunGlass(this.shadowEncoder, this.shadowLayerIsDynamic ? 'dynamic' : 'static');
|
|
7820
|
+
}
|
|
7342
7821
|
this.shadowDraws.flush();
|
|
7343
7822
|
this.scatterDepthDraws.flush();
|
|
7344
|
-
this.shadowPass.end();
|
|
7345
7823
|
this.surface.device.queue.submit([this.shadowEncoder.finish()]);
|
|
7824
|
+
/* A growth's old texture, copied out of by what was just submitted and needed no longer. */
|
|
7825
|
+
this.retiredSunTexture?.destroy();
|
|
7826
|
+
this.retiredSunTexture = null;
|
|
7346
7827
|
/* The map now holds a rendered peel rather than whatever it was created with. */
|
|
7347
7828
|
if (this.shadowLayerIsPeel)
|
|
7348
7829
|
this.peelFilled = true;
|
|
@@ -7371,6 +7852,8 @@ export class WebGPURenderer {
|
|
|
7371
7852
|
preparedPointLightCount = 0;
|
|
7372
7853
|
/** See `renderer.ts`: built when a world reports its lights, and rebuilt only to grow. */
|
|
7373
7854
|
pointShadowArray = null;
|
|
7855
|
+
/** The array glass replaced, destroyed once the round that copied out of it is submitted. */
|
|
7856
|
+
retiredPointTexture = null;
|
|
7374
7857
|
bakeScratch = createBakeScratch(POINT_SHADOW_POOL);
|
|
7375
7858
|
/** Said once per renderer; see `updatePointShadows`. */
|
|
7376
7859
|
warnedNoPointShadowArray = false;
|
|
@@ -7523,6 +8006,7 @@ export class WebGPURenderer {
|
|
|
7523
8006
|
*/
|
|
7524
8007
|
this.shadowDraws.reset();
|
|
7525
8008
|
this.scatterDepthDraws.reset();
|
|
8009
|
+
this.glassTint?.frag.reset();
|
|
7526
8010
|
this.shadowEncoder = this.surface.device.createCommandEncoder({ label: 'pointShadow' });
|
|
7527
8011
|
/* A cube face is a shadow pass like any other, and the courtyard is where it matters: six of
|
|
7528
8012
|
them a frame is the difference this slot exists to make visible. */
|
|
@@ -7533,7 +8017,7 @@ export class WebGPURenderer {
|
|
|
7533
8017
|
/* A frame's resolves take slots in the array's ring; see it for why they cannot share one. */
|
|
7534
8018
|
this.pointShadowArray?.beginFrame();
|
|
7535
8019
|
try {
|
|
7536
|
-
runPointShadowBakes(pool, lights, this.quality.pointShadowFacesPerFrame, this.quality.liveShadowFacesPerFrame, this.quality.pointShadowRebakeDistance, FACE_COUNT, this.bakeScratch, staticCasters, dynamicCasters, (map, light, casters, maxFaces) => map.bake(light.x, light.y, light.z, light.radius, (view, viewProj) => this.bakeFace(view, viewProj, casters), (layer, face, far, near) => this.resolveFace(layer, face, far, near), this.faceViewProj, light.shadowNear, light.sourceRadius, maxFaces));
|
|
8020
|
+
runPointShadowBakes(pool, lights, this.quality.pointShadowFacesPerFrame, this.quality.liveShadowFacesPerFrame, this.quality.pointShadowRebakeDistance, FACE_COUNT, this.bakeScratch, staticCasters, dynamicCasters, (map, light, casters, maxFaces) => map.bake(light.x, light.y, light.z, light.radius, (view, viewProj) => this.bakeFace(view, viewProj, casters), (layer, face, far, near, x, y, z) => this.resolveFace(layer, face, far, near, x, y, z), this.faceViewProj, light.shadowNear, light.sourceRadius, maxFaces));
|
|
7537
8021
|
/*
|
|
7538
8022
|
* **Inside the same encoder and the same try, which is the whole reason the rectangles' bakes
|
|
7539
8023
|
* ride this call.** Every face is its own render pass, they share one submission, and the
|
|
@@ -7541,11 +8025,14 @@ export class WebGPURenderer {
|
|
|
7541
8025
|
* encoder, a second submit and a second `shadows` timer slot for work that is the same work.
|
|
7542
8026
|
*/
|
|
7543
8027
|
this.updateAreaShadows(areaLights, frameDt, staticCasters, dynamicCasters);
|
|
8028
|
+
/* The chain frost reads, once for the round, for the lights whose glass it drew or cleared. */
|
|
8029
|
+
this.pointShadowArray?.glassTint?.recordMips(this.shadowEncoder);
|
|
7544
8030
|
}
|
|
7545
8031
|
finally {
|
|
7546
8032
|
this.depthCullMode = 'back';
|
|
7547
8033
|
this.shadowDraws.flush();
|
|
7548
8034
|
this.scatterDepthDraws.flush();
|
|
8035
|
+
this.glassTint?.frag.flush();
|
|
7549
8036
|
/*
|
|
7550
8037
|
* **Before the submit, and that ordering is the whole point of the ring.** A write to the
|
|
7551
8038
|
* queue lands ahead of the encoder's commands whenever it is issued, so uploading per
|
|
@@ -7557,6 +8044,9 @@ export class WebGPURenderer {
|
|
|
7557
8044
|
this.shadowEncoder = null;
|
|
7558
8045
|
this.shadowPass = null;
|
|
7559
8046
|
this.gpuTimer.end();
|
|
8047
|
+
/* An interleaving's old array, copied out of by what was just submitted. */
|
|
8048
|
+
this.retiredPointTexture?.destroy();
|
|
8049
|
+
this.retiredPointTexture = null;
|
|
7560
8050
|
}
|
|
7561
8051
|
/*
|
|
7562
8052
|
* **No rebind here any more.** Twelve cube views meant the bind group named twelve textures,
|
|
@@ -7566,6 +8056,10 @@ export class WebGPURenderer {
|
|
|
7566
8056
|
* written every frame regardless.
|
|
7567
8057
|
*/
|
|
7568
8058
|
pool.resolve(this.resolvedPointShadows);
|
|
8059
|
+
/* As the array stores them: 2L once glass has interleaved it. See `strideLayers`. */
|
|
8060
|
+
const stride = this.pointShadowArray.stride;
|
|
8061
|
+
strideLayers(this.resolvedPointShadows.layers, stride);
|
|
8062
|
+
strideLayers(this.resolvedPointShadows.liveLayers, stride);
|
|
7569
8063
|
}
|
|
7570
8064
|
/**
|
|
7571
8065
|
* The rectangles' round, inside the shadow encoder this backend already opened.
|
|
@@ -7591,10 +8085,13 @@ export class WebGPURenderer {
|
|
|
7591
8085
|
set.release();
|
|
7592
8086
|
}
|
|
7593
8087
|
}
|
|
7594
|
-
set.update(areaLights, frameDt, this.quality.pointShadowFacesPerFrame, this.quality.liveShadowFacesPerFrame, this.quality.pointShadowRebakeDistance, FACE_COUNT, this.areaBakeScratch, staticCasters, dynamicCasters, (map, light, range, near, casters, maxFaces) => map.bake(light.x, light.y, light.z, range, (view, viewProj) => this.bakeFace(view, viewProj, casters), (layer, face, far, mapNear) => this.resolveFace(layer, face, far, mapNear), this.faceViewProj, near,
|
|
8088
|
+
set.update(areaLights, frameDt, this.quality.pointShadowFacesPerFrame, this.quality.liveShadowFacesPerFrame, this.quality.pointShadowRebakeDistance, FACE_COUNT, this.areaBakeScratch, staticCasters, dynamicCasters, (map, light, range, near, casters, maxFaces) => map.bake(light.x, light.y, light.z, range, (view, viewProj) => this.bakeFace(view, viewProj, casters), (layer, face, far, mapNear, x, y, z) => this.resolveFace(layer, face, far, mapNear, x, y, z), this.faceViewProj, near,
|
|
7595
8089
|
/* The filter reads the emitter's size, never the bake. See `renderer.ts`'s copy. */
|
|
7596
8090
|
DEFAULT_SOURCE_RADIUS, maxFaces));
|
|
7597
8091
|
set.resolve(areaLights, this.resolvedAreaShadows);
|
|
8092
|
+
const stride = this.pointShadowArray?.stride ?? 1;
|
|
8093
|
+
strideLayers(this.resolvedAreaShadows.layer, stride);
|
|
8094
|
+
strideLayers(this.resolvedAreaShadows.liveLayer, stride);
|
|
7598
8095
|
}
|
|
7599
8096
|
/**
|
|
7600
8097
|
* One cubemap face: a depth pass, the casters, done.
|
|
@@ -7622,9 +8119,26 @@ export class WebGPURenderer {
|
|
|
7622
8119
|
depthStoreOp: 'store',
|
|
7623
8120
|
},
|
|
7624
8121
|
});
|
|
8122
|
+
/* A face is an enumeration of its own: its glass is its own. */
|
|
8123
|
+
this.glassCasters.clear();
|
|
7625
8124
|
casters(this.casterSink);
|
|
7626
8125
|
this.shadowPass.end();
|
|
7627
8126
|
this.shadowPass = null;
|
|
8127
|
+
/*
|
|
8128
|
+
* The first face to offer glass interleaves the array, before its opaque depth is resolved:
|
|
8129
|
+
* `PointShadowArray.growForGlass`. The lit pass's groups hold the old array's view, so they
|
|
8130
|
+
* are rebuilt, and the old texture goes once this round's encoder — which copies out of it —
|
|
8131
|
+
* is submitted.
|
|
8132
|
+
*/
|
|
8133
|
+
const array = this.pointShadowArray;
|
|
8134
|
+
if (array !== null && this.glassCasters.count > 0 && this.quality.glassShadows !== 'off') {
|
|
8135
|
+
const tintEdge = this.quality.glassShadows === 'half' ? OCTAHEDRAL_EDGE >> 1 : OCTAHEDRAL_EDGE;
|
|
8136
|
+
const old = array.growForGlass(encoder, tintEdge, this.pipelines);
|
|
8137
|
+
if (old !== null) {
|
|
8138
|
+
this.retiredPointTexture = old;
|
|
8139
|
+
this.rebuildFlatBindGroup();
|
|
8140
|
+
}
|
|
8141
|
+
}
|
|
7628
8142
|
}
|
|
7629
8143
|
/**
|
|
7630
8144
|
* One rendered face into its region of the light's octahedral layer.
|
|
@@ -7633,12 +8147,64 @@ export class WebGPURenderer {
|
|
|
7633
8147
|
* serve every light: the face is consumed before the next one is drawn. See
|
|
7634
8148
|
* `pointShadowArray.ts` for why the regions are disjoint.
|
|
7635
8149
|
*/
|
|
7636
|
-
resolveFace(layer, face, far, near) {
|
|
8150
|
+
resolveFace(layer, face, far, near, x, y, z) {
|
|
7637
8151
|
const encoder = this.shadowEncoder;
|
|
7638
8152
|
const array = this.pointShadowArray;
|
|
7639
8153
|
if (encoder === null || array === null)
|
|
7640
8154
|
return;
|
|
7641
8155
|
array.resolve(encoder, layer, face, far, near);
|
|
8156
|
+
const tintTarget = array.glassTint;
|
|
8157
|
+
if (tintTarget === null || this.quality.glassShadows === 'off')
|
|
8158
|
+
return;
|
|
8159
|
+
/*
|
|
8160
|
+
* The face's glass, twice, as `PointShadowMap.bakeGlassFace` draws it on the other backend. A
|
|
8161
|
+
* face that offers none still clears a region a previous owner's glass is in, from scratches
|
|
8162
|
+
* cleared and not drawn into, because a pool slot changes hands with its layer.
|
|
8163
|
+
*/
|
|
8164
|
+
const offered = this.glassCasters.count > 0;
|
|
8165
|
+
if (!offered && !tintTarget.held(layer, face))
|
|
8166
|
+
return;
|
|
8167
|
+
this.shadowPass = encoder.beginRenderPass({
|
|
8168
|
+
label: 'pointShadow.glass',
|
|
8169
|
+
colorAttachments: [],
|
|
8170
|
+
depthStencilAttachment: {
|
|
8171
|
+
view: array.scratchView,
|
|
8172
|
+
depthClearValue: 1,
|
|
8173
|
+
depthLoadOp: 'clear',
|
|
8174
|
+
depthStoreOp: 'store',
|
|
8175
|
+
},
|
|
8176
|
+
});
|
|
8177
|
+
if (offered) {
|
|
8178
|
+
this.drawingGlassDepth = true;
|
|
8179
|
+
this.glassCasters.replay(this.glassDepthSink);
|
|
8180
|
+
this.drawingGlassDepth = false;
|
|
8181
|
+
}
|
|
8182
|
+
this.shadowPass.end();
|
|
8183
|
+
this.shadowPass = null;
|
|
8184
|
+
array.resolveGlass(encoder, layer, face, far, near);
|
|
8185
|
+
this.tintPass = encoder.beginRenderPass({
|
|
8186
|
+
label: 'pointShadow.tint',
|
|
8187
|
+
colorAttachments: [
|
|
8188
|
+
{
|
|
8189
|
+
view: tintTarget.scratchView,
|
|
8190
|
+
loadOp: 'clear',
|
|
8191
|
+
clearValue: [1, 1, 1, 1],
|
|
8192
|
+
storeOp: 'store',
|
|
8193
|
+
},
|
|
8194
|
+
],
|
|
8195
|
+
});
|
|
8196
|
+
if (offered) {
|
|
8197
|
+
this.glassTint ??= new GpuGlassTint(this.surface.device);
|
|
8198
|
+
/* A place, w 1: each pane's angle to the lamp comes from its own position. */
|
|
8199
|
+
this.glassLight[0] = x;
|
|
8200
|
+
this.glassLight[1] = y;
|
|
8201
|
+
this.glassLight[2] = z;
|
|
8202
|
+
this.glassLight[3] = 1;
|
|
8203
|
+
this.glassCasters.replay(this.glassTintSink);
|
|
8204
|
+
}
|
|
8205
|
+
this.tintPass.end();
|
|
8206
|
+
this.tintPass = null;
|
|
8207
|
+
tintTarget.resolve(encoder, layer, face, offered);
|
|
7642
8208
|
}
|
|
7643
8209
|
buildFlatBindGroup() {
|
|
7644
8210
|
return createFlatBindGroup(this.surface.device, this.bindGroupLayout, this.variant, this.perDraw.buffer, this.perFrame.buffer, this.flatTextures);
|
|
@@ -7947,8 +8513,10 @@ export class WebGPURenderer {
|
|
|
7947
8513
|
* drawn over a reflective floor rather than under it.
|
|
7948
8514
|
*/
|
|
7949
8515
|
this.runReflections(encoder);
|
|
7950
|
-
/* The translucent set, into its two buffers and back over the scene
|
|
7951
|
-
|
|
8516
|
+
/* The translucent set, into its two buffers and back over the scene — after the upscale instead
|
|
8517
|
+
while reconstructing, where `runLate` draws the rest of the blended frame. */
|
|
8518
|
+
if (!this.reconstructing)
|
|
8519
|
+
this.runOit(encoder);
|
|
7952
8520
|
/*
|
|
7953
8521
|
* **The medium, after everything that draws and before anything that composites.**
|
|
7954
8522
|
*
|
|
@@ -7970,6 +8538,9 @@ export class WebGPURenderer {
|
|
|
7970
8538
|
*/
|
|
7971
8539
|
this.runMotion(encoder);
|
|
7972
8540
|
this.runReconstruction(encoder);
|
|
8541
|
+
this.runLate(encoder);
|
|
8542
|
+
if (this.reconstructing)
|
|
8543
|
+
this.runOit(encoder);
|
|
7973
8544
|
const aoStrength = this.quality.ambientOcclusion;
|
|
7974
8545
|
if (aoStrength > 0 && this.frameProjection !== null)
|
|
7975
8546
|
this.runOcclusion(encoder);
|
|
@@ -8181,19 +8752,23 @@ export class WebGPURenderer {
|
|
|
8181
8752
|
label: 'recon.shown',
|
|
8182
8753
|
size: [outputWidth, outputHeight],
|
|
8183
8754
|
format: RECON_HISTORY_FORMAT,
|
|
8184
|
-
|
|
8755
|
+
/* An attachment too, because the blended draws land on it after the upscale, and a copy
|
|
8756
|
+
source for the panes that refract it. See `runLate`. */
|
|
8757
|
+
usage: HISTORY_USAGE | 0x10 | 0x1, // RENDER_ATTACHMENT | COPY_SRC
|
|
8185
8758
|
});
|
|
8186
8759
|
this.reconShownView = this.reconShown.createView();
|
|
8187
8760
|
this.reconMotion = device.createTexture({
|
|
8188
8761
|
label: 'recon.motion',
|
|
8189
8762
|
size: [renderWidth, renderHeight],
|
|
8190
8763
|
format: RECON_HISTORY_FORMAT,
|
|
8191
|
-
/* An attachment as well, because the pass that
|
|
8192
|
-
|
|
8193
|
-
|
|
8764
|
+
/* An attachment as well, because the pass that writes it is a draw. It is cleared every
|
|
8765
|
+
frame by that pass, and a zero in the fourth channel is what tells the resolve to derive
|
|
8766
|
+
the camera's motion. */
|
|
8194
8767
|
usage: 0x4 | 0x10,
|
|
8195
8768
|
});
|
|
8196
8769
|
this.reconMotionView = this.reconMotion.createView();
|
|
8770
|
+
this.motionPass ??= new MotionPass(device);
|
|
8771
|
+
this.latePass ??= new LatePass(device);
|
|
8197
8772
|
this.reconPreviousDepth = device.createTexture({
|
|
8198
8773
|
label: 'recon.previousDepth',
|
|
8199
8774
|
size: [renderWidth, renderHeight],
|
|
@@ -8309,207 +8884,153 @@ export class WebGPURenderer {
|
|
|
8309
8884
|
this.rebuildRushBindGroup();
|
|
8310
8885
|
}
|
|
8311
8886
|
/**
|
|
8312
|
-
*
|
|
8887
|
+
* Whether a blended draw recorded now lands after the upscale. See `lateRouting.ts`.
|
|
8313
8888
|
*
|
|
8314
|
-
*
|
|
8315
|
-
* — which is the right thing for a scene to do, the alternative being to know what the renderer
|
|
8316
|
-
* is configured as — must cost nothing at all when nobody is going to read them.
|
|
8889
|
+
* Not during the order-independent replay, which owns its pass and issues into it directly.
|
|
8317
8890
|
*/
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
const wanted = (at + 1) * MOTION_DRAW_FLOATS;
|
|
8323
|
-
if (this.motionMatrices.length < wanted) {
|
|
8324
|
-
/* Doubling, so a scene settles on one allocation rather than one a draw. */
|
|
8325
|
-
const grown = new Float32Array(Math.max(wanted, this.motionMatrices.length * 2, 64));
|
|
8326
|
-
grown.set(this.motionMatrices);
|
|
8327
|
-
this.motionMatrices = grown;
|
|
8328
|
-
}
|
|
8329
|
-
this.motionMatrices.set(model, at * MOTION_DRAW_FLOATS);
|
|
8330
|
-
this.motionMatrices.set(previousModel, at * MOTION_DRAW_FLOATS + 16);
|
|
8331
|
-
this.motionMeshes[at] = mesh;
|
|
8332
|
-
this.motionCount = at + 1;
|
|
8891
|
+
drawsLate() {
|
|
8892
|
+
return (this.latePass !== null &&
|
|
8893
|
+
this.oitEncoder === null &&
|
|
8894
|
+
routesLate(this.reconstructing, this.reflectionPassActive, this.probePassActive, this.framePresented));
|
|
8333
8895
|
}
|
|
8334
8896
|
/**
|
|
8335
|
-
* The
|
|
8336
|
-
*
|
|
8337
|
-
*
|
|
8338
|
-
* an attribute — so this binds buffer zero and nothing else, whatever else the mesh carries.
|
|
8897
|
+
* The command a blended draw records into: the late pool while reconstructing, the frame graph
|
|
8898
|
+
* otherwise. **Late draws record whatever `quality.frameGraph` says**, since the late replay is
|
|
8899
|
+
* the only moment they can land; everything else keeps the path it has.
|
|
8339
8900
|
*/
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
this.
|
|
8345
|
-
label: 'recon.motion.layout',
|
|
8346
|
-
entries: [
|
|
8347
|
-
{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } },
|
|
8348
|
-
{
|
|
8349
|
-
binding: 1,
|
|
8350
|
-
visibility: 0x1,
|
|
8351
|
-
buffer: { type: 'uniform', hasDynamicOffset: true },
|
|
8352
|
-
},
|
|
8353
|
-
],
|
|
8354
|
-
});
|
|
8355
|
-
this.motionModule = device.createShaderModule({
|
|
8356
|
-
label: 'recon.motion',
|
|
8357
|
-
code: RECON_MOTION_WGSL,
|
|
8358
|
-
});
|
|
8359
|
-
this.motionPipelineLayout = device.createPipelineLayout({
|
|
8360
|
-
bindGroupLayouts: [this.motionLayout],
|
|
8361
|
-
});
|
|
8901
|
+
recordBlended(reads) {
|
|
8902
|
+
const late = this.latePass;
|
|
8903
|
+
if (late !== null && this.drawsLate())
|
|
8904
|
+
return late.take();
|
|
8905
|
+
return this.recordDraw(reads, this.currentTarget());
|
|
8362
8906
|
}
|
|
8363
|
-
/** The
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
if (held !== undefined)
|
|
8367
|
-
return held;
|
|
8368
|
-
const module = this.motionModule;
|
|
8369
|
-
const layout = this.motionPipelineLayout;
|
|
8370
|
-
if (module === null || layout === null)
|
|
8371
|
-
return null;
|
|
8372
|
-
const { device } = this.surface;
|
|
8373
|
-
const pipeline = device.createRenderPipeline({
|
|
8374
|
-
label: `recon.motion.${String(stride)}`,
|
|
8375
|
-
layout,
|
|
8376
|
-
vertex: {
|
|
8377
|
-
module,
|
|
8378
|
-
entryPoint: 'motionVert',
|
|
8379
|
-
/* Positions are the layout's first attribute and are never optional, so they are at offset
|
|
8380
|
-
zero of every stride this can be handed. */
|
|
8381
|
-
buffers: [
|
|
8382
|
-
{
|
|
8383
|
-
arrayStride: stride,
|
|
8384
|
-
attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }],
|
|
8385
|
-
},
|
|
8386
|
-
],
|
|
8387
|
-
},
|
|
8388
|
-
fragment: { module, entryPoint: 'motionFrag', targets: [{ format: MOTION_TARGET_FORMAT }] },
|
|
8389
|
-
/*
|
|
8390
|
-
* **The scene's own winding**, because this draws the scene's geometry through the scene's
|
|
8391
|
-
* clip transform — the product the renderer hands over already carries the negation that
|
|
8392
|
-
* `naga` writes into every generated vertex stage, so the faces come out the way they did
|
|
8393
|
-
* and a `cw` front face here would draw the inside of every mesh. §3 row 57.
|
|
8394
|
-
*/
|
|
8395
|
-
primitive: { topology: 'triangle-list', cullMode: 'back' },
|
|
8396
|
-
depthStencil: {
|
|
8397
|
-
format: DEPTH_FORMAT,
|
|
8398
|
-
/*
|
|
8399
|
-
* **Tested and not written.** The frame's depth is finished; this pass only wants the
|
|
8400
|
-
* pixels the scene actually kept, and `DEPTH_COMPARE_EQUAL` is the comparison that admits
|
|
8401
|
-
* exactly them — the same transform on the same positions gives the same depth, which is
|
|
8402
|
-
* why the rasterisation matrix is handed over whole rather than rebuilt here.
|
|
8403
|
-
*
|
|
8404
|
-
* **And the equality is not fragile here**, which was worth measuring rather than
|
|
8405
|
-
* assuming: this driver contracts multiply-adds (§3 row 107) and two shader modules can
|
|
8406
|
-
* contract a matrix product differently, so an equality across them could have admitted
|
|
8407
|
-
* only some of a surface. Run with `greater-equal` instead, the ghost measured the same
|
|
8408
|
-
* to the pixel — 5,942 either way — so nothing is being dropped.
|
|
8409
|
-
*/
|
|
8410
|
-
depthWriteEnabled: false,
|
|
8411
|
-
depthCompare: DEPTH_COMPARE_EQUAL,
|
|
8412
|
-
},
|
|
8413
|
-
});
|
|
8414
|
-
this.motionPipelines.set(stride, pipeline);
|
|
8415
|
-
return pipeline;
|
|
8907
|
+
/** The pipelines a blended draw is built for: the reconstructed picture's while late. */
|
|
8908
|
+
blendedPipelines() {
|
|
8909
|
+
return this.drawsLate() ? this.latePipelines : this.targetPipelines();
|
|
8416
8910
|
}
|
|
8417
8911
|
/**
|
|
8418
|
-
*
|
|
8419
|
-
*
|
|
8420
|
-
*
|
|
8421
|
-
* finished for the equality test to mean anything, and the resolve is what consumes the result.
|
|
8912
|
+
* The blended draws of a reconstructing frame, over the reconstructed picture at the output size
|
|
8913
|
+
* and unjittered, against the render's depth taken up to that size. Before occlusion, bloom and
|
|
8914
|
+
* the grade, so they are graded exactly as they were when they were drawn into the scene.
|
|
8422
8915
|
*/
|
|
8423
|
-
|
|
8424
|
-
const
|
|
8916
|
+
runLate(encoder) {
|
|
8917
|
+
const late = this.latePass;
|
|
8918
|
+
const shown = this.reconShownView;
|
|
8425
8919
|
const depth = this.depthView;
|
|
8426
|
-
if (!this.reconstructing ||
|
|
8920
|
+
if (!this.reconstructing || late === null || shown === null || depth === null)
|
|
8427
8921
|
return;
|
|
8428
|
-
const
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8922
|
+
const oitSet = this.oitActive && this.translucentQueue.length > 0;
|
|
8923
|
+
if (late.pending === 0 && !oitSet)
|
|
8924
|
+
return;
|
|
8925
|
+
const width = Math.max(1, this.surface.canvas.width);
|
|
8926
|
+
const height = Math.max(1, this.surface.canvas.height);
|
|
8927
|
+
/* The order-independent set tests against this depth too, so it is made for either. */
|
|
8928
|
+
late.upscale(encoder, depth, this.renderWidth, this.renderHeight, width, height);
|
|
8929
|
+
if (this.lateRefractWanted && this.refractSnapshot !== null && this.reconShown !== null) {
|
|
8930
|
+
encoder.copyTextureToTexture({ texture: this.reconShown }, { texture: this.refractSnapshot }, [this.refractSnapshot.width, this.refractSnapshot.height]);
|
|
8931
|
+
this.refractMips?.record(encoder);
|
|
8932
|
+
this.refractSnapshotTaken = true;
|
|
8933
|
+
}
|
|
8934
|
+
this.lateRefractWanted = false;
|
|
8935
|
+
if (late.pending === 0)
|
|
8432
8936
|
return;
|
|
8433
|
-
/*
|
|
8434
|
-
* **Cleared every frame, even when nothing moved.** A texel left from last frame is a flag
|
|
8435
|
-
* saying "a draw wrote this", and the resolve would take a motion belonging to a surface that
|
|
8436
|
-
* is no longer there. The clear is the pass's own load operation, so an empty queue still runs
|
|
8437
|
-
* it — which is why this is not returned from before the pass is opened.
|
|
8438
|
-
*/
|
|
8439
8937
|
const pass = encoder.beginRenderPass({
|
|
8440
|
-
label: 'recon.
|
|
8938
|
+
label: 'recon.late',
|
|
8441
8939
|
timestampWrites: this.gpuTimer.writesFor(),
|
|
8442
|
-
colorAttachments: [
|
|
8443
|
-
{ view: target, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
8444
|
-
],
|
|
8940
|
+
colorAttachments: [{ view: shown, loadOp: 'load', storeOp: 'store' }],
|
|
8445
8941
|
depthStencilAttachment: {
|
|
8446
|
-
view:
|
|
8942
|
+
view: late.depthView(width, height),
|
|
8447
8943
|
depthLoadOp: 'load',
|
|
8944
|
+
/* Kept: the order-independent set replays against it after this pass. */
|
|
8448
8945
|
depthStoreOp: 'store',
|
|
8449
|
-
depthReadOnly: false,
|
|
8450
8946
|
},
|
|
8451
8947
|
});
|
|
8452
|
-
|
|
8453
|
-
if (count > 0) {
|
|
8454
|
-
const bytes = count * MOTION_DRAW_STRIDE;
|
|
8455
|
-
if (this.motionDrawCapacity < count) {
|
|
8456
|
-
this.motionDrawBuffer?.destroy();
|
|
8457
|
-
this.motionDrawCapacity = Math.max(count, this.motionDrawCapacity * 2, 16);
|
|
8458
|
-
this.motionDrawBuffer = device.createBuffer({
|
|
8459
|
-
label: 'recon.motion.draws',
|
|
8460
|
-
size: this.motionDrawCapacity * MOTION_DRAW_STRIDE,
|
|
8461
|
-
usage: 0x40 | 0x8, // UNIFORM | COPY_DST
|
|
8462
|
-
});
|
|
8463
|
-
this.motionDrawStaging = new Uint8Array(this.motionDrawCapacity * MOTION_DRAW_STRIDE);
|
|
8464
|
-
this.motionGroup = null;
|
|
8465
|
-
}
|
|
8466
|
-
this.motionFrameBuffer ??= device.createBuffer({
|
|
8467
|
-
label: 'recon.motion.frame',
|
|
8468
|
-
size: MOTION_FRAME_FLOATS * 4,
|
|
8469
|
-
usage: 0x40 | 0x8,
|
|
8470
|
-
});
|
|
8471
|
-
this.motionGroup ??= device.createBindGroup({
|
|
8472
|
-
label: 'recon.motion.group',
|
|
8473
|
-
layout,
|
|
8474
|
-
entries: [
|
|
8475
|
-
{ binding: 0, resource: { buffer: this.motionFrameBuffer } },
|
|
8476
|
-
{
|
|
8477
|
-
binding: 1,
|
|
8478
|
-
resource: { buffer: this.motionDrawBuffer, size: MOTION_DRAW_STRIDE },
|
|
8479
|
-
},
|
|
8480
|
-
],
|
|
8481
|
-
});
|
|
8482
|
-
/* The scene's clip transform, with the generated stages' y negation folded in. */
|
|
8483
|
-
mat4.multiply(this.motionRaster, MOTION_CLIP_FLIP, this.viewProj ?? this.correctedViewProj);
|
|
8484
|
-
this.motionFrameStaging.set(this.motionRaster, 0);
|
|
8485
|
-
this.motionFrameStaging.set(this.frameRawViewProj, 16);
|
|
8486
|
-
this.motionFrameStaging.set(this.reconPreviousRawViewProj, 32);
|
|
8487
|
-
device.queue.writeBuffer(this.motionFrameBuffer, 0, this.motionFrameStaging);
|
|
8488
|
-
const draws = new Float32Array(this.motionDrawStaging.buffer);
|
|
8489
|
-
for (let i = 0; i < count; i += 1) {
|
|
8490
|
-
draws.set(this.motionMatrices.subarray(i * MOTION_DRAW_FLOATS, (i + 1) * MOTION_DRAW_FLOATS), (i * MOTION_DRAW_STRIDE) / 4);
|
|
8491
|
-
}
|
|
8492
|
-
device.queue.writeBuffer(this.motionDrawBuffer, 0, this.motionDrawStaging, 0, bytes);
|
|
8493
|
-
let bound = null;
|
|
8494
|
-
for (let i = 0; i < count; i += 1) {
|
|
8495
|
-
const mesh = this.motionMeshes[i];
|
|
8496
|
-
if (mesh === undefined)
|
|
8497
|
-
continue;
|
|
8498
|
-
const pipeline = this.motionPipelineFor(mesh.vertexStride);
|
|
8499
|
-
if (pipeline === null)
|
|
8500
|
-
continue;
|
|
8501
|
-
if (pipeline !== bound) {
|
|
8502
|
-
pass.setPipeline(pipeline);
|
|
8503
|
-
bound = pipeline;
|
|
8504
|
-
}
|
|
8505
|
-
pass.setBindGroup(0, this.motionGroup, [i * MOTION_DRAW_STRIDE]);
|
|
8506
|
-
pass.setVertexBuffer(0, mesh.vertexBuffers[0]);
|
|
8507
|
-
pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
|
|
8508
|
-
pass.drawIndexed(mesh.indexCount);
|
|
8509
|
-
}
|
|
8510
|
-
}
|
|
8948
|
+
late.replay(pass, this.issueBound);
|
|
8511
8949
|
pass.end();
|
|
8512
8950
|
}
|
|
8951
|
+
/** Whether this draw lands in the reconstructed picture. See `lateRouting.ts`. */
|
|
8952
|
+
tracksMotionNow() {
|
|
8953
|
+
return tracksMotion(this.reconstructing, this.reflectionPassActive, this.probePassActive, this.framePresented);
|
|
8954
|
+
}
|
|
8955
|
+
/**
|
|
8956
|
+
* What this draw's motion is, recorded for the motion pass.
|
|
8957
|
+
*
|
|
8958
|
+
* A mesh rewritten this frame carries its vertex motion whatever else the draw says — under last
|
|
8959
|
+
* frame's model where a mover or a matrix knows it, under this frame's where nothing does. A
|
|
8960
|
+
* mover otherwise carries a rigid draw's last model or a skinned draw's last pose; a matrix
|
|
8961
|
+
* carries a rigid draw's. A draw that says nothing and moved no vertex takes the camera's motion.
|
|
8962
|
+
*/
|
|
8963
|
+
recordDrawMotion(mesh, model, previous) {
|
|
8964
|
+
const dynamic = mesh.motion;
|
|
8965
|
+
const rows = dynamic?.previous ?? null;
|
|
8966
|
+
const rewritten = rows !== null && dynamic !== null && dynamic.changed === this.moverFrame;
|
|
8967
|
+
if (previous === null) {
|
|
8968
|
+
if (rewritten)
|
|
8969
|
+
this.motionPass?.recordDynamic(mesh, model, model, rows);
|
|
8970
|
+
return;
|
|
8971
|
+
}
|
|
8972
|
+
if (!isMover(previous)) {
|
|
8973
|
+
if (rewritten)
|
|
8974
|
+
this.motionPass?.recordDynamic(mesh, model, previous, rows);
|
|
8975
|
+
else
|
|
8976
|
+
this.motionPass?.recordRigid(mesh, model, previous);
|
|
8977
|
+
return;
|
|
8978
|
+
}
|
|
8979
|
+
/* A rig is only followed where the draw really skins: a palette set, a mesh with joints. */
|
|
8980
|
+
const skinned = !rewritten && this.skinPaletteSlot >= 0 && mesh.isSkinned && mesh.skinOffsets !== null;
|
|
8981
|
+
const verdict = advanceMover(previous, this.moverRenderer, this.moverFrame, model, skinned ? this.skinPaletteData : null);
|
|
8982
|
+
if (verdict === MOVER_TWICE) {
|
|
8983
|
+
this.warnMoverTwice();
|
|
8984
|
+
return;
|
|
8985
|
+
}
|
|
8986
|
+
if (rewritten) {
|
|
8987
|
+
const was = verdict === MOVER_MOTION ? previous.previousModel : model;
|
|
8988
|
+
this.motionPass?.recordDynamic(mesh, model, was, rows);
|
|
8989
|
+
return;
|
|
8990
|
+
}
|
|
8991
|
+
if (verdict !== MOVER_MOTION)
|
|
8992
|
+
return;
|
|
8993
|
+
if (skinned)
|
|
8994
|
+
this.recordSkinnedMotion(mesh, model, previous);
|
|
8995
|
+
else
|
|
8996
|
+
this.motionPass?.recordRigid(mesh, model, previous.previousModel);
|
|
8997
|
+
}
|
|
8998
|
+
/**
|
|
8999
|
+
* A skinned mover's motion: this frame's palette is the draw's own slot, and last frame's takes a
|
|
9000
|
+
* slot of its own, like any palette a draw binds — so it is uploaded once and nothing overwrites
|
|
9001
|
+
* it before the motion pass reads it. See `SkinPaletteRing`.
|
|
9002
|
+
*/
|
|
9003
|
+
recordSkinnedMotion(mesh, model, mover) {
|
|
9004
|
+
const now = this.skinPalettes.view(this.skinPaletteSlot);
|
|
9005
|
+
const slot = this.skinPalettes.take(this.surface.device, mover.previousPalette);
|
|
9006
|
+
const was = slot === null ? null : this.skinPalettes.view(slot);
|
|
9007
|
+
if (now === null || was === null)
|
|
9008
|
+
return;
|
|
9009
|
+
this.motionPass?.recordSkinned(mesh, model, mover.previousModel, now, was);
|
|
9010
|
+
}
|
|
9011
|
+
warnMoverTwice() {
|
|
9012
|
+
if (this.warnedMoverTwice)
|
|
9013
|
+
return;
|
|
9014
|
+
this.warnedMoverTwice = true;
|
|
9015
|
+
console.warn('WebGPU: a Mover was drawn twice in one frame. A mover is one object; the second draw ' +
|
|
9016
|
+
'records no motion. Give each moving object its own `createMover()`.');
|
|
9017
|
+
}
|
|
9018
|
+
/**
|
|
9019
|
+
* Where every mover was last frame, into the motion target. See `MotionPass`.
|
|
9020
|
+
*
|
|
9021
|
+
* **After everything that draws and before the resolve reads it.** The frame's depth has to be
|
|
9022
|
+
* finished for the pass's equality test to mean anything, and the resolve is what consumes it.
|
|
9023
|
+
*/
|
|
9024
|
+
runMotion(encoder) {
|
|
9025
|
+
const target = this.reconMotionView;
|
|
9026
|
+
const depth = this.depthView;
|
|
9027
|
+
const motion = this.motionPass;
|
|
9028
|
+
if (!this.reconstructing || target === null || depth === null || motion === null)
|
|
9029
|
+
return;
|
|
9030
|
+
/* The scene's clip transform, with the generated stages' y negation folded in. */
|
|
9031
|
+
mat4.multiply(this.motionRaster, MOTION_CLIP_FLIP, this.viewProj ?? this.correctedViewProj);
|
|
9032
|
+
motion.run(encoder, target, depth, this.motionRaster, this.frameRawViewProj, this.reconPreviousRawViewProj, this.gpuTimer.writesFor());
|
|
9033
|
+
}
|
|
8513
9034
|
/**
|
|
8514
9035
|
* DriftTR tier 0: the jittered render at the render size into the picture at the output size.
|
|
8515
9036
|
*
|
|
@@ -8647,8 +9168,10 @@ export class WebGPURenderer {
|
|
|
8647
9168
|
return;
|
|
8648
9169
|
const write = this.taaWrite;
|
|
8649
9170
|
const target = this.taaViews[write];
|
|
9171
|
+
const record = this.taaRecordViews[write];
|
|
9172
|
+
const motion = this.taaMotionViews[write];
|
|
8650
9173
|
const group = this.taaBindGroups[1 - write];
|
|
8651
|
-
if (target === null || group === null)
|
|
9174
|
+
if (target === null || record === null || motion === null || group === null)
|
|
8652
9175
|
return;
|
|
8653
9176
|
const { device } = this.surface;
|
|
8654
9177
|
const f = this.taaFloats;
|
|
@@ -8657,15 +9180,34 @@ export class WebGPURenderer {
|
|
|
8657
9180
|
f[at('uTexel')] = 1 / this.renderWidth;
|
|
8658
9181
|
f[at('uTexel') + 1] = 1 / this.renderHeight;
|
|
8659
9182
|
f[at('uHistoryBlend')] = this.temporalHistoryUsable ? TEMPORAL_HISTORY_BLEND : 0;
|
|
9183
|
+
f[at('uPeriodStart')] = this.temporalHistory.periodStart ? 1 : 0;
|
|
9184
|
+
/* The anti-flicker's same-surface test reads depths in metres, as the occlusion does. */
|
|
9185
|
+
const depthRow = at('uDepthToViewZ');
|
|
9186
|
+
if (this.frameProjection !== null) {
|
|
9187
|
+
mat4.invert(this.taaInvProjection, this.frameProjection);
|
|
9188
|
+
f[depthRow] = this.taaInvProjection[10] ?? 0;
|
|
9189
|
+
f[depthRow + 1] = this.taaInvProjection[14] ?? 0;
|
|
9190
|
+
f[depthRow + 2] = this.taaInvProjection[11] ?? 0;
|
|
9191
|
+
f[depthRow + 3] = this.taaInvProjection[15] ?? 1;
|
|
9192
|
+
}
|
|
9193
|
+
else {
|
|
9194
|
+
f[depthRow] = 0;
|
|
9195
|
+
f[depthRow + 1] = 0;
|
|
9196
|
+
f[depthRow + 2] = 0;
|
|
9197
|
+
f[depthRow + 3] = 1;
|
|
9198
|
+
}
|
|
9199
|
+
f[at('uAntiFlicker')] = 1;
|
|
8660
9200
|
device.queue.writeBuffer(this.taaUniforms, 0, this.taaStaging);
|
|
8661
9201
|
const pass = encoder.beginRenderPass({
|
|
8662
9202
|
label: 'post.taa',
|
|
8663
9203
|
timestampWrites: this.gpuTimer.writesFor(),
|
|
8664
9204
|
colorAttachments: [
|
|
8665
9205
|
{ view: target, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 1] },
|
|
9206
|
+
{ view: record, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
9207
|
+
{ view: motion, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
8666
9208
|
],
|
|
8667
9209
|
});
|
|
8668
|
-
pass.setPipeline(
|
|
9210
|
+
pass.setPipeline(postPipelineTargets(this.pipelines, device, this.taaLayout, 'post.taa', TEMPORAL_RESOLVE_FRAG_WGSL, this.taaTargets));
|
|
8669
9211
|
pass.setBindGroup(0, group, [0]);
|
|
8670
9212
|
pass.draw(3);
|
|
8671
9213
|
pass.end();
|
|
@@ -8871,9 +9413,12 @@ export class WebGPURenderer {
|
|
|
8871
9413
|
return;
|
|
8872
9414
|
const accum = this.oitAccumView;
|
|
8873
9415
|
const reveal = this.oitRevealView;
|
|
8874
|
-
|
|
9416
|
+
/* After the upscale while reconstructing: over the reconstructed picture, against its depth. */
|
|
9417
|
+
const scene = this.reconstructing ? this.reconShownView : this.sceneColorView;
|
|
8875
9418
|
const group = this.oitResolveGroup;
|
|
8876
|
-
const depth = this.
|
|
9419
|
+
const depth = this.reconstructing
|
|
9420
|
+
? (this.latePass?.depthView(Math.max(1, this.surface.canvas.width), Math.max(1, this.surface.canvas.height)) ?? null)
|
|
9421
|
+
: this.depthView;
|
|
8877
9422
|
if (accum === null || reveal === null || scene === null || group === null || depth === null) {
|
|
8878
9423
|
return;
|
|
8879
9424
|
}
|
|
@@ -8887,12 +9432,13 @@ export class WebGPURenderer {
|
|
|
8887
9432
|
*/
|
|
8888
9433
|
if (this.translucentQueue.refracts && !this.refractSnapshotTaken) {
|
|
8889
9434
|
const snapshot = this.refractSnapshot;
|
|
8890
|
-
const source = this.sceneColor;
|
|
9435
|
+
const source = this.reconstructing ? this.reconShown : this.sceneColor;
|
|
8891
9436
|
if (snapshot !== null && source !== null) {
|
|
8892
9437
|
encoder.copyTextureToTexture({ texture: source }, { texture: snapshot }, [
|
|
8893
9438
|
snapshot.width,
|
|
8894
9439
|
snapshot.height,
|
|
8895
9440
|
]);
|
|
9441
|
+
this.refractMips?.record(encoder);
|
|
8896
9442
|
this.refractSnapshotTaken = true;
|
|
8897
9443
|
}
|
|
8898
9444
|
}
|
|
@@ -8931,6 +9477,7 @@ export class WebGPURenderer {
|
|
|
8931
9477
|
options.refraction = draw.refraction;
|
|
8932
9478
|
options.refractTint = draw.refractTint;
|
|
8933
9479
|
options.thicknessM = draw.thicknessM;
|
|
9480
|
+
options.glass = draw.glass ?? undefined;
|
|
8934
9481
|
this.drawTranslucentMesh(draw.mesh, draw.model, draw.opacity, options);
|
|
8935
9482
|
});
|
|
8936
9483
|
this.oitReplaying = false;
|
|
@@ -8954,7 +9501,7 @@ export class WebGPURenderer {
|
|
|
8954
9501
|
label: 'post.oit.resolve',
|
|
8955
9502
|
colorAttachments: [{ view: scene, loadOp: 'load', storeOp: 'store' }],
|
|
8956
9503
|
});
|
|
8957
|
-
resolve.setPipeline(postPipeline(this.pipelines, device, this.oitResolveLayout, 'post.oitResolve', OIT_RESOLVE_FRAG_WGSL, sceneColorFormat(this.quality.hdrScene), OIT_COMPOSITE_BLEND));
|
|
9504
|
+
resolve.setPipeline(postPipeline(this.pipelines, device, this.oitResolveLayout, this.reconstructing ? 'post.oitResolve.recon' : 'post.oitResolve', OIT_RESOLVE_FRAG_WGSL, this.reconstructing ? RECON_HISTORY_FORMAT : sceneColorFormat(this.quality.hdrScene), OIT_COMPOSITE_BLEND));
|
|
8958
9505
|
resolve.setBindGroup(0, group);
|
|
8959
9506
|
resolve.draw(3);
|
|
8960
9507
|
resolve.end();
|
|
@@ -9261,18 +9808,14 @@ export class WebGPURenderer {
|
|
|
9261
9808
|
* the drawing buffer: `ensureComposite` calls this again whenever it rebuilds that target.
|
|
9262
9809
|
*/
|
|
9263
9810
|
buildLightVolumeBindGroup() {
|
|
9264
|
-
return createLightVolumeBindGroup(this.surface.device, this.lightVolumeLayout, this.lightVolumeVariant, this.lightVolumeVertices.buffer, this.lightVolumeFragments.buffer, (name) => name === '
|
|
9265
|
-
? { view: this.
|
|
9266
|
-
:
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
the stand-in is never sampled. */
|
|
9273
|
-
name === 'uSceneDepth' && this.resolvedDepthView !== null
|
|
9274
|
-
? { view: this.resolvedDepthView, sampler: this.postDepthSampler }
|
|
9275
|
-
: this.volumeBlank);
|
|
9811
|
+
return createLightVolumeBindGroup(this.surface.device, this.lightVolumeLayout, this.lightVolumeVariant, this.lightVolumeVertices.buffer, this.lightVolumeFragments.buffer, (name) => name === 'uSunShadows'
|
|
9812
|
+
? { view: this.sunArrayView, sampler: this.volumeShadowSampler }
|
|
9813
|
+
: /* The frame's own depth, snapshotted before the first volume of the frame draws.
|
|
9814
|
+
Null under a profile with no composite, and then `uSceneDepthEnabled` is 0 and
|
|
9815
|
+
the stand-in is never sampled. */
|
|
9816
|
+
name === 'uSceneDepth' && this.resolvedDepthView !== null
|
|
9817
|
+
? { view: this.resolvedDepthView, sampler: this.postDepthSampler }
|
|
9818
|
+
: this.volumeBlank);
|
|
9276
9819
|
}
|
|
9277
9820
|
/**
|
|
9278
9821
|
* Copy the colour the frame has drawn so far, so a refracting draw can read it.
|
|
@@ -9298,6 +9841,12 @@ export class WebGPURenderer {
|
|
|
9298
9841
|
return false;
|
|
9299
9842
|
if (this.refractSnapshotTaken)
|
|
9300
9843
|
return true;
|
|
9844
|
+
/* A late draw refracts the reconstructed picture, which does not exist until the resolve has
|
|
9845
|
+
run; `runLate` takes the copy then, once, before any late draw is replayed. */
|
|
9846
|
+
if (this.drawsLate()) {
|
|
9847
|
+
this.lateRefractWanted = true;
|
|
9848
|
+
return true;
|
|
9849
|
+
}
|
|
9301
9850
|
/* The replay of an order-independent set runs after the scene's pass has ended, and `runOit`
|
|
9302
9851
|
took the copy for it before it began; there is no pass here to end and reopen. */
|
|
9303
9852
|
if (this.oitReplaying)
|
|
@@ -9339,6 +9888,8 @@ export class WebGPURenderer {
|
|
|
9339
9888
|
snapshot.width,
|
|
9340
9889
|
snapshot.height,
|
|
9341
9890
|
]);
|
|
9891
|
+
/* The chain, between the passes, where nothing is attached: what frosted glass reads. */
|
|
9892
|
+
this.refractMips?.record(encoder);
|
|
9342
9893
|
const multisampled = this.compositeMsaa() !== null;
|
|
9343
9894
|
this.pass = encoder.beginRenderPass({
|
|
9344
9895
|
label: 'refract.snapshotTaken',
|
|
@@ -9690,7 +10241,13 @@ export class WebGPURenderer {
|
|
|
9690
10241
|
if (verb !== VERB_DRAW)
|
|
9691
10242
|
return;
|
|
9692
10243
|
const command = this.commands.commands[nodeState(this.arena, at)];
|
|
9693
|
-
if (command === undefined
|
|
10244
|
+
if (command === undefined)
|
|
10245
|
+
return;
|
|
10246
|
+
this.issueCommand(pass, command);
|
|
10247
|
+
}
|
|
10248
|
+
/** One recorded draw, issued into an open pass. Shared by the graph's replay and `runLate`. */
|
|
10249
|
+
issueCommand(pass, command) {
|
|
10250
|
+
if (command.pipeline === null || command.bindGroup === null)
|
|
9694
10251
|
return;
|
|
9695
10252
|
pass.setPipeline(command.pipeline);
|
|
9696
10253
|
if (command.offsetCount === 2) {
|
|
@@ -9939,6 +10496,7 @@ export class WebGPURenderer {
|
|
|
9939
10496
|
this.encoder = null;
|
|
9940
10497
|
}
|
|
9941
10498
|
endFrame() {
|
|
10499
|
+
this.inFrame = false;
|
|
9942
10500
|
if (this.surface.lost || this.encoder === null)
|
|
9943
10501
|
return;
|
|
9944
10502
|
/*
|
|
@@ -11069,6 +11627,10 @@ export class WebGPURenderer {
|
|
|
11069
11627
|
*
|
|
11070
11628
|
* What it gives up is one frame of history: the frame after a cut has no blur and no temporal
|
|
11071
11629
|
* blend, as the first frame of a session has none. Nothing else is reset.
|
|
11630
|
+
*
|
|
11631
|
+
* **A reconstructing renderer depends on it**: its history is the last shot's, and without this
|
|
11632
|
+
* call a cut blends that shot into the first frames of the next. Nothing detects a cut on the
|
|
11633
|
+
* caller's behalf, deliberately — a fast pan and a cut look alike from here.
|
|
11072
11634
|
*/
|
|
11073
11635
|
cameraCut() {
|
|
11074
11636
|
this.hasPreviousView = false;
|
|
@@ -11244,9 +11806,9 @@ export class WebGPURenderer {
|
|
|
11244
11806
|
f[atF('uFogFar')] = medium.fogFar;
|
|
11245
11807
|
this.causticsVerts.writeBlock(vertexSlot, this.causticsVertBlock);
|
|
11246
11808
|
this.causticsFrags.writeBlock(fragmentSlot, this.causticsFragInts);
|
|
11247
|
-
const causticsPipe = causticsPipeline(this.
|
|
11809
|
+
const causticsPipe = causticsPipeline(this.blendedPipelines(), this.surface.device, this.causticsLayout);
|
|
11248
11810
|
/* The pattern is generated in the shader from the two uniform blocks; nothing is sampled. */
|
|
11249
|
-
const causticsCommand = this.
|
|
11811
|
+
const causticsCommand = this.recordBlended(0);
|
|
11250
11812
|
if (causticsCommand !== null) {
|
|
11251
11813
|
causticsCommand.pipeline = causticsPipe;
|
|
11252
11814
|
causticsCommand.bindGroup = this.causticsBindGroup;
|
|
@@ -12125,6 +12687,33 @@ export class WebGPURenderer {
|
|
|
12125
12687
|
* The occlusion and bloom targets are half size. Both are blurs read back at full size, so a
|
|
12126
12688
|
* full-resolution intermediate buys nothing a bilinear fetch does not already give.
|
|
12127
12689
|
*/
|
|
12690
|
+
/**
|
|
12691
|
+
* The medium's march group, holding the shadow views it reads — built with the composite, and
|
|
12692
|
+
* again when the sun's texture grows its glass layers and every view of the old one is stale.
|
|
12693
|
+
*
|
|
12694
|
+
* **The stand-in has to read as unoccluded and not as absent**, which is `emptyTexture.ts`'s
|
|
12695
|
+
* point and `createLightVolumeBindGroup`'s: `occlusion()` calls any stored depth below the
|
|
12696
|
+
* receiver's a blocker — so a black stand-in would put the whole medium in shadow, which is a
|
|
12697
|
+
* picture rather than an error. `volumeBlank` is the white one-texel texture built for this.
|
|
12698
|
+
*/
|
|
12699
|
+
buildMediumBindGroup() {
|
|
12700
|
+
if (this.resolvedDepthView === null)
|
|
12701
|
+
return null;
|
|
12702
|
+
return this.surface.device.createBindGroup({
|
|
12703
|
+
label: 'post.mediumBindGroup',
|
|
12704
|
+
layout: this.mediumLayout,
|
|
12705
|
+
entries: [
|
|
12706
|
+
{
|
|
12707
|
+
binding: MEDIUM_UNIFORMS,
|
|
12708
|
+
resource: { buffer: this.mediumUniforms, size: MEDIUM_FRAG_SIZE },
|
|
12709
|
+
},
|
|
12710
|
+
{ binding: MEDIUM_TEXTURES.uDepth.texture, resource: this.resolvedDepthView },
|
|
12711
|
+
{ binding: MEDIUM_TEXTURES.uDepth.sampler, resource: this.postDepthSampler },
|
|
12712
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows.texture, resource: this.sunArrayView },
|
|
12713
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows.sampler, resource: this.volumeShadowSampler },
|
|
12714
|
+
],
|
|
12715
|
+
});
|
|
12716
|
+
}
|
|
12128
12717
|
ensureComposite(width, height) {
|
|
12129
12718
|
if (!this.hasComposite)
|
|
12130
12719
|
return;
|
|
@@ -12180,13 +12769,29 @@ export class WebGPURenderer {
|
|
|
12180
12769
|
* the allocation the house rules are about. A frame that never refracts pays the memory and
|
|
12181
12770
|
* takes no copy.
|
|
12182
12771
|
*/
|
|
12772
|
+
/*
|
|
12773
|
+
* **At the output size and the reconstructed picture's format while reconstructing**, because
|
|
12774
|
+
* every refracting draw is then blended, drawn after the upscale, and reads the reconstructed
|
|
12775
|
+
* picture rather than the render. See `runLate`.
|
|
12776
|
+
*/
|
|
12777
|
+
const reconstructed = this.reconstructionWanted;
|
|
12183
12778
|
this.refractSnapshot = device.createTexture({
|
|
12184
12779
|
label: 'refract.snapshot',
|
|
12185
|
-
size:
|
|
12186
|
-
|
|
12187
|
-
|
|
12780
|
+
size: reconstructed
|
|
12781
|
+
? [Math.max(1, this.surface.canvas.width), Math.max(1, this.surface.canvas.height)]
|
|
12782
|
+
: [width, height],
|
|
12783
|
+
format: reconstructed ? RECON_HISTORY_FORMAT : format,
|
|
12784
|
+
/*
|
|
12785
|
+
* **Every level, so glass can read a blurrier one**, and a render attachment so `SnapshotMips`
|
|
12786
|
+
* can fill them. Plain refraction reads level 0, as it always has. See `glass.ts`.
|
|
12787
|
+
*/
|
|
12788
|
+
mipLevelCount: Math.floor(Math.log2(reconstructed
|
|
12789
|
+
? Math.max(1, this.surface.canvas.width, this.surface.canvas.height)
|
|
12790
|
+
: Math.max(1, width, height))) + 1,
|
|
12791
|
+
usage: 0x4 | 0x2 | 0x10, // TEXTURE_BINDING | COPY_DST | RENDER_ATTACHMENT
|
|
12188
12792
|
});
|
|
12189
12793
|
this.refractSnapshotView = this.refractSnapshot.createView();
|
|
12794
|
+
this.refractMips = new SnapshotMips(device, this.pipelines, this.refractSnapshot, reconstructed ? RECON_HISTORY_FORMAT : format);
|
|
12190
12795
|
/*
|
|
12191
12796
|
* **Every cached flat bind group is stale the moment this view changes**, and the failure is
|
|
12192
12797
|
* silent: `flatTextures` resolves `uRefractScene` to the one-texel white stand-in when there is
|
|
@@ -12292,7 +12897,28 @@ export class WebGPURenderer {
|
|
|
12292
12897
|
});
|
|
12293
12898
|
this.taaTextures[i] = texture;
|
|
12294
12899
|
this.taaViews[i] = texture.createView();
|
|
12900
|
+
/* The records in half float, whatever the picture's format: one of them holds metres. */
|
|
12901
|
+
this.taaRecords[i]?.destroy();
|
|
12902
|
+
const record = device.createTexture({
|
|
12903
|
+
label: `post.taaRecord${i}`,
|
|
12904
|
+
size: [width, height],
|
|
12905
|
+
format: 'rgba16float',
|
|
12906
|
+
usage: USAGE,
|
|
12907
|
+
});
|
|
12908
|
+
this.taaRecords[i] = record;
|
|
12909
|
+
this.taaRecordViews[i] = record.createView();
|
|
12910
|
+
this.taaMotions[i]?.destroy();
|
|
12911
|
+
/* Two numbers a pixel: the depth its sample stood at, in metres, and how far that swings. */
|
|
12912
|
+
const motion = device.createTexture({
|
|
12913
|
+
label: `post.taaMotion${i}`,
|
|
12914
|
+
size: [width, height],
|
|
12915
|
+
format: 'rg16float',
|
|
12916
|
+
usage: USAGE,
|
|
12917
|
+
});
|
|
12918
|
+
this.taaMotions[i] = motion;
|
|
12919
|
+
this.taaMotionViews[i] = motion.createView();
|
|
12295
12920
|
}
|
|
12921
|
+
this.taaTargets = [{ format }, { format: 'rgba16float' }, { format: 'rg16float' }];
|
|
12296
12922
|
this.taaWrite = 0;
|
|
12297
12923
|
/* A resized target is a new texture, so whatever the history held is gone. */
|
|
12298
12924
|
this.temporalHistory.invalidate();
|
|
@@ -12305,10 +12931,14 @@ export class WebGPURenderer {
|
|
|
12305
12931
|
* that range — a step of 1/255 in how much scene shows through, below what a viewer can see
|
|
12306
12932
|
* against a blended surface.
|
|
12307
12933
|
*/
|
|
12934
|
+
/* At the output size while reconstructing, where the set is replayed after the upscale. */
|
|
12935
|
+
const oitSize = this.reconstructionWanted
|
|
12936
|
+
? [Math.max(1, this.surface.canvas.width), Math.max(1, this.surface.canvas.height)]
|
|
12937
|
+
: [width, height];
|
|
12308
12938
|
this.oitAccum?.destroy();
|
|
12309
12939
|
this.oitAccum = device.createTexture({
|
|
12310
12940
|
label: 'post.oitAccum',
|
|
12311
|
-
size:
|
|
12941
|
+
size: oitSize,
|
|
12312
12942
|
format: 'rgba16float',
|
|
12313
12943
|
usage: USAGE,
|
|
12314
12944
|
});
|
|
@@ -12316,7 +12946,7 @@ export class WebGPURenderer {
|
|
|
12316
12946
|
this.oitReveal?.destroy();
|
|
12317
12947
|
this.oitReveal = device.createTexture({
|
|
12318
12948
|
label: 'post.oitReveal',
|
|
12319
|
-
size:
|
|
12949
|
+
size: oitSize,
|
|
12320
12950
|
format: 'r8unorm',
|
|
12321
12951
|
usage: USAGE,
|
|
12322
12952
|
});
|
|
@@ -12415,39 +13045,7 @@ export class WebGPURenderer {
|
|
|
12415
13045
|
...entry(AO_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
12416
13046
|
],
|
|
12417
13047
|
});
|
|
12418
|
-
|
|
12419
|
-
* The medium's two groups, holding the shadow views the march reads.
|
|
12420
|
-
*
|
|
12421
|
-
* **The stand-in has to read as unoccluded and not as absent**, which is `emptyTexture.ts`'s
|
|
12422
|
-
* point and `createLightVolumeBindGroup`'s: the dynamic layer is sampled unconditionally, and
|
|
12423
|
-
* `occlusion()` calls any stored depth below the receiver's a blocker — so a black stand-in
|
|
12424
|
-
* would put the whole medium in shadow, which is a picture rather than an error. `volumeBlank`
|
|
12425
|
-
* is the white one-texel texture already built for exactly this.
|
|
12426
|
-
*/
|
|
12427
|
-
const shadowPair = (b, view) => [
|
|
12428
|
-
{ binding: b.texture, resource: view ?? this.volumeBlank.view },
|
|
12429
|
-
{
|
|
12430
|
-
binding: b.sampler,
|
|
12431
|
-
resource: view === null ? this.volumeBlank.sampler : this.volumeShadowSampler,
|
|
12432
|
-
},
|
|
12433
|
-
];
|
|
12434
|
-
this.mediumBindGroup =
|
|
12435
|
-
this.resolvedDepthView === null
|
|
12436
|
-
? null
|
|
12437
|
-
: device.createBindGroup({
|
|
12438
|
-
label: 'post.mediumBindGroup',
|
|
12439
|
-
layout: this.mediumLayout,
|
|
12440
|
-
entries: [
|
|
12441
|
-
{
|
|
12442
|
-
binding: MEDIUM_UNIFORMS,
|
|
12443
|
-
resource: { buffer: this.mediumUniforms, size: MEDIUM_FRAG_SIZE },
|
|
12444
|
-
},
|
|
12445
|
-
...entry(MEDIUM_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
12446
|
-
...shadowPair(MEDIUM_TEXTURES.uStaticShadowMap, this.shadowView),
|
|
12447
|
-
...shadowPair(MEDIUM_TEXTURES.uPeeledShadowMap, this.peelView),
|
|
12448
|
-
...shadowPair(MEDIUM_TEXTURES.uDynamicShadowMap, this.dynamicView),
|
|
12449
|
-
],
|
|
12450
|
-
});
|
|
13048
|
+
this.mediumBindGroup = this.buildMediumBindGroup();
|
|
12451
13049
|
this.mediumUpsampleGroup =
|
|
12452
13050
|
this.resolvedDepthView === null || this.mediumTargetView === null
|
|
12453
13051
|
? null
|
|
@@ -12478,8 +13076,15 @@ export class WebGPURenderer {
|
|
|
12478
13076
|
const scene = this.sceneColorView;
|
|
12479
13077
|
const history = this.taaViews[i];
|
|
12480
13078
|
const resolved = this.taaViews[1 - i];
|
|
12481
|
-
|
|
13079
|
+
const record = this.taaRecordViews[i];
|
|
13080
|
+
const motion = this.taaMotionViews[i];
|
|
13081
|
+
if (scene === null ||
|
|
13082
|
+
history === null ||
|
|
13083
|
+
resolved === null ||
|
|
13084
|
+
record === null ||
|
|
13085
|
+
motion === null) {
|
|
12482
13086
|
continue;
|
|
13087
|
+
}
|
|
12483
13088
|
this.taaBindGroups[i] = device.createBindGroup({
|
|
12484
13089
|
label: `post.taaBindGroup${i}`,
|
|
12485
13090
|
layout: this.taaLayout,
|
|
@@ -12487,6 +13092,8 @@ export class WebGPURenderer {
|
|
|
12487
13092
|
{ binding: TAA_UNIFORMS, resource: { buffer: this.taaUniforms, size: TAA_FRAG_SIZE } },
|
|
12488
13093
|
...entry(TAA_TEXTURES.uScene, scene),
|
|
12489
13094
|
...entry(TAA_TEXTURES.uHistory, history),
|
|
13095
|
+
...entry(TAA_TEXTURES.uFlicker, record),
|
|
13096
|
+
...entry(TAA_TEXTURES.uFlickerMotion, motion),
|
|
12490
13097
|
...entry(TAA_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
12491
13098
|
],
|
|
12492
13099
|
});
|
|
@@ -12621,6 +13228,10 @@ export class WebGPURenderer {
|
|
|
12621
13228
|
* the 2026-08-13 rule asks for — one decision, bound twice.
|
|
12622
13229
|
*/
|
|
12623
13230
|
dispose() {
|
|
13231
|
+
this.motionPass?.dispose();
|
|
13232
|
+
this.motionPass = null;
|
|
13233
|
+
this.latePass?.dispose();
|
|
13234
|
+
this.latePass = null;
|
|
12624
13235
|
for (const texture of this.retiredTextures)
|
|
12625
13236
|
texture.destroy();
|
|
12626
13237
|
this.retiredTextures.length = 0;
|
|
@@ -12637,6 +13248,12 @@ export class WebGPURenderer {
|
|
|
12637
13248
|
this.fieldComposer = null;
|
|
12638
13249
|
this.driftIndex?.destroy();
|
|
12639
13250
|
this.driftAtlas?.destroy();
|
|
13251
|
+
this.sunTint?.texture.destroy();
|
|
13252
|
+
this.sunTint = null;
|
|
13253
|
+
this.glassTint?.dispose();
|
|
13254
|
+
this.glassTint = null;
|
|
13255
|
+
this.retiredSunTexture?.destroy();
|
|
13256
|
+
this.retiredSunTexture = null;
|
|
12640
13257
|
this.pass = null;
|
|
12641
13258
|
this.encoder = null;
|
|
12642
13259
|
this.skinPalettes.dispose();
|