@driftengine/core 4.4.2 → 4.6.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 +97 -0
- package/README.md +1 -1
- package/dist/geometry/boxCsg.d.ts +28 -0
- package/dist/geometry/boxCsg.js +139 -0
- package/dist/geometry/csg.d.ts +36 -0
- package/dist/geometry/csg.js +329 -0
- package/dist/geometry/meshBuilder.js +13 -0
- package/dist/geometry/meshConcat.js +1 -0
- package/dist/geometry/pixelFont.js +2 -0
- package/dist/geometry/solid.d.ts +61 -0
- package/dist/geometry/solid.js +152 -0
- package/dist/geometry/solidFlat.d.ts +35 -0
- package/dist/geometry/solidFlat.js +428 -0
- package/dist/geometry/solidHarness.d.ts +19 -0
- package/dist/geometry/solidHarness.js +66 -0
- package/dist/geometry/solidNormals.d.ts +17 -0
- package/dist/geometry/solidNormals.js +86 -0
- package/dist/geometry/solidRound.d.ts +41 -0
- package/dist/geometry/solidRound.js +323 -0
- package/dist/geometry/solidWriter.d.ts +16 -0
- package/dist/geometry/solidWriter.js +42 -0
- package/dist/index.d.ts +28 -3
- package/dist/index.js +20 -1
- package/dist/render/backend/api.d.ts +34 -14
- package/dist/render/backend/drawFog.d.ts +26 -0
- package/dist/render/backend/drawFog.js +30 -0
- package/dist/render/backend/materialChanges.d.ts +2 -1
- package/dist/render/backend/materialChanges.js +6 -1
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/instanced.d.ts +24 -5
- package/dist/render/backend/webgl2/instanced.js +72 -15
- package/dist/render/backend/webgl2/renderer.d.ts +201 -16
- package/dist/render/backend/webgl2/renderer.js +596 -224
- package/dist/render/backend/webgpu/buffers.d.ts +29 -1
- package/dist/render/backend/webgpu/buffers.js +61 -4
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +7 -2
- package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +6 -1
- package/dist/render/backend/webgpu/flatPass.js +57 -9
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +206 -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/instanceCullPass.d.ts +34 -0
- package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
- package/dist/render/backend/webgpu/instanced.d.ts +23 -2
- package/dist/render/backend/webgpu/instanced.js +41 -4
- package/dist/render/backend/webgpu/latePass.d.ts +30 -0
- package/dist/render/backend/webgpu/latePass.js +140 -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 +3 -13
- package/dist/render/backend/webgpu/renderer.d.ts +271 -64
- package/dist/render/backend/webgpu/renderer.js +1395 -580
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
- 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 +22 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +78 -39
- package/dist/render/driftLight/bake.d.ts +7 -0
- package/dist/render/driftLight/bake.js +61 -56
- package/dist/render/driftLight/denseField.d.ts +46 -0
- package/dist/render/driftLight/denseField.js +109 -0
- package/dist/render/driftLight/lightField.d.ts +10 -5
- package/dist/render/driftLight/lightField.js +25 -19
- package/dist/render/driftLight/presence.d.ts +50 -0
- package/dist/render/driftLight/presence.js +43 -0
- package/dist/render/driftLight/uniforms.d.ts +3 -3
- package/dist/render/driftLight/uniforms.js +5 -6
- package/dist/render/driftLight/worldLightField.d.ts +41 -0
- package/dist/render/driftLight/worldLightField.js +66 -0
- package/dist/render/fixtureAtlas.d.ts +20 -0
- package/dist/render/fixtureAtlas.js +49 -0
- 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/instanceCull.d.ts +63 -0
- package/dist/render/instanceCull.js +160 -0
- package/dist/render/instances.d.ts +15 -0
- package/dist/render/lightBudget.d.ts +36 -35
- package/dist/render/lightBudget.js +38 -37
- package/dist/render/lightGrid.d.ts +42 -0
- package/dist/render/lightGrid.js +87 -0
- package/dist/render/mesh.d.ts +18 -24
- package/dist/render/mesh.js +51 -68
- package/dist/render/occlusion.d.ts +10 -0
- package/dist/render/occlusion.js +36 -7
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointLightSelection.d.ts +7 -0
- package/dist/render/pointLightSelection.js +29 -4
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/rainField.d.ts +13 -0
- package/dist/render/rainField.js +27 -5
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/lateDepth.d.ts +11 -0
- package/dist/render/recon/lateDepth.js +70 -0
- 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.d.ts +34 -4
- package/dist/render/recon/resolve.js +161 -20
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +16 -0
- package/dist/render/sceneTarget.d.ts +9 -1
- package/dist/render/sceneTarget.js +60 -12
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +94 -15
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/driftLight.d.ts +1 -1
- package/dist/render/shaders/flat/driftLight.js +32 -7
- 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 +9 -3
- 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 +190 -38
- 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 +96 -26
- package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
- package/dist/render/shaders/flat/surfaceEffects.js +152 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +230 -7
- package/dist/render/shaders/generated/depth.wgsl.js +230 -7
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1644 -1596
- package/dist/render/shaders/generated/flat.wgsl.js +1683 -1621
- 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 +21 -17
- 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/rush.wgsl.d.ts +21 -16
- package/dist/render/shaders/generated/rush.wgsl.js +21 -16
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
- package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
- package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/text.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
- 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 +69 -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 +8 -7
- package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
- package/dist/render/shaders/rush.js +11 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/sdfText.js +3 -2
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +18 -9
- 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/surfaceEffects.d.ts +83 -0
- package/dist/render/surfaceEffects.js +75 -0
- package/dist/render/surfaceScene.d.ts +8 -0
- package/dist/render/surfaceScene.js +8 -0
- package/dist/render/surfaceTexture.d.ts +18 -2
- package/dist/render/surfaceTexture.js +68 -15
- package/dist/render/temporalAa.d.ts +25 -6
- package/dist/render/temporalAa.js +45 -15
- package/dist/render/textureSource.d.ts +30 -0
- package/dist/render/textureSource.js +53 -0
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/dist/render/vertexDefaults.js +2 -0
- package/dist/world/hlod.d.ts +104 -0
- package/dist/world/hlod.js +214 -0
- package/package.json +6 -6
- package/src/geometry/boxCsg.ts +186 -0
- package/src/geometry/csg.ts +384 -0
- package/src/geometry/meshBuilder.ts +15 -0
- package/src/geometry/meshConcat.ts +1 -0
- package/src/geometry/pixelFont.ts +2 -0
- package/src/geometry/solid.ts +189 -0
- package/src/geometry/solidFlat.ts +522 -0
- package/src/geometry/solidHarness.ts +83 -0
- package/src/geometry/solidNormals.ts +103 -0
- package/src/geometry/solidRound.ts +443 -0
- package/src/geometry/solidWriter.ts +58 -0
- package/src/index.ts +61 -1
- package/src/render/backend/api.ts +36 -11
- package/src/render/backend/drawFog.ts +31 -0
- package/src/render/backend/materialChanges.ts +9 -2
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/instanced.ts +80 -14
- package/src/render/backend/webgl2/renderer.ts +700 -250
- package/src/render/backend/webgpu/buffers.ts +92 -5
- package/src/render/backend/webgpu/depthPass.ts +7 -2
- package/src/render/backend/webgpu/drawCommand.ts +6 -0
- package/src/render/backend/webgpu/flatPass.ts +62 -11
- package/src/render/backend/webgpu/glassTintPass.ts +245 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
- package/src/render/backend/webgpu/instanced.ts +42 -4
- package/src/render/backend/webgpu/latePass.ts +161 -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/renderer.ts +1671 -670
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
- package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
- 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 +91 -51
- package/src/render/driftLight/bake.ts +85 -62
- package/src/render/driftLight/denseField.ts +149 -0
- package/src/render/driftLight/lightField.ts +33 -18
- package/src/render/driftLight/presence.ts +69 -0
- package/src/render/driftLight/uniforms.ts +8 -9
- package/src/render/driftLight/worldLightField.ts +83 -0
- package/src/render/fixtureAtlas.ts +63 -0
- 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/instanceCull.ts +205 -0
- package/src/render/instances.ts +16 -0
- package/src/render/lightBudget.ts +41 -38
- package/src/render/lightGrid.ts +130 -0
- package/src/render/mesh.ts +59 -72
- package/src/render/occlusion.ts +51 -7
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointLightSelection.ts +72 -4
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/rainField.ts +39 -5
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateDepth.ts +83 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/recon/resolve.ts +206 -20
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +16 -0
- package/src/render/sceneTarget.ts +62 -11
- package/src/render/shaders/depth.ts +118 -15
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/driftLight.ts +32 -7
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +22 -3
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +190 -38
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +96 -26
- package/src/render/shaders/flat/surfaceEffects.ts +152 -0
- package/src/render/shaders/generated/depth.wgsl.ts +237 -7
- package/src/render/shaders/generated/flat.wgsl.ts +1685 -1621
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +23 -17
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/rush.wgsl.ts +21 -16
- package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
- package/src/render/shaders/generated/text.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
- 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 +72 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
- package/src/render/shaders/rush.ts +11 -1
- package/src/render/shaders/sdfText.ts +3 -2
- package/src/render/shaders/text.ts +18 -9
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/surfaceEffects.ts +132 -0
- package/src/render/surfaceScene.ts +16 -0
- package/src/render/surfaceTexture.ts +110 -19
- package/src/render/temporalAa.ts +78 -14
- package/src/render/textureSource.ts +72 -0
- package/src/render/translucentQueue.ts +20 -1
- package/src/render/vertexDefaults.ts +2 -0
- package/src/world/hlod.ts +290 -0
|
@@ -2,19 +2,17 @@ import { mat4 } from 'gl-matrix';
|
|
|
2
2
|
import type { ProbeBakeOptions } from '../../reflectionProbe.ts';
|
|
3
3
|
import { probeFaceRange } from '../../reflectionProbe.ts';
|
|
4
4
|
import {
|
|
5
|
+
TEMPORAL_FLICKER,
|
|
5
6
|
TEMPORAL_HISTORY_BLEND,
|
|
6
7
|
TemporalHistory,
|
|
8
|
+
flickerRule,
|
|
7
9
|
jitterOffset,
|
|
8
10
|
jitterProjection,
|
|
9
11
|
} from '../../temporalAa.ts';
|
|
12
|
+
import type { FlickerRule } from '../../temporalAa.ts';
|
|
10
13
|
import { OIT_MULTISAMPLE_REFUSAL } from '../../orderIndependent.ts';
|
|
11
14
|
import { TranslucentQueue } from '../../translucentQueue.ts';
|
|
12
|
-
import {
|
|
13
|
-
DEPTH_CLEAR,
|
|
14
|
-
DEPTH_COMPARE_EQUAL,
|
|
15
|
-
MAX_DEPTH_LAYER,
|
|
16
|
-
REVERSED_DEPTH,
|
|
17
|
-
} from '../../depthConvention.ts';
|
|
15
|
+
import { DEPTH_CLEAR, MAX_DEPTH_LAYER, REVERSED_DEPTH } from '../../depthConvention.ts';
|
|
18
16
|
import { RECONSTRUCTION_MULTISAMPLE_REFUSAL, reconRenderSize } from '../../recon/frameSizes.ts';
|
|
19
17
|
import { jitterOffset as jitterOffsetFor, reconJitterPhases } from '../../recon/jitter.ts';
|
|
20
18
|
import {
|
|
@@ -23,16 +21,25 @@ import {
|
|
|
23
21
|
RECON_WORKGROUP,
|
|
24
22
|
reconResolveWgsl,
|
|
25
23
|
} from '../../shaders/recon/resolve.wgsl.ts';
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} from '../../
|
|
24
|
+
import { MotionPass } from './motionPass.ts';
|
|
25
|
+
import { LatePass } from './latePass.ts';
|
|
26
|
+
import { SnapshotMips } from './snapshotMips.ts';
|
|
27
|
+
import { resolveGlass } from '../../glass.ts';
|
|
28
|
+
import type { GlassOptions, ResolvedGlass } from '../../glass.ts';
|
|
29
|
+
import { routesLate, tracksMotion } from '../../recon/lateRouting.ts';
|
|
30
|
+
import { frameOfChange, keepsPrevious, pairedInstances } from '../../recon/changeFrames.ts';
|
|
31
|
+
import { INSTANCE_STRIDE } from '../../instances.ts';
|
|
33
32
|
import { DEFAULT_DISOCCLUSION } from '../../recon/disocclusion.ts';
|
|
34
33
|
import { DEFAULT_RECON_QUALITY } from '../../recon/resolve.ts';
|
|
35
34
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
35
|
+
import {
|
|
36
|
+
MOVER_MOTION,
|
|
37
|
+
MOVER_TWICE,
|
|
38
|
+
advanceMover,
|
|
39
|
+
claimRendererId,
|
|
40
|
+
isMover,
|
|
41
|
+
} from '../../recon/mover.ts';
|
|
42
|
+
import type { Mover } from '../../recon/mover.ts';
|
|
36
43
|
|
|
37
44
|
import type { Vec3 } from '../../../math/color.ts';
|
|
38
45
|
import { clamp } from '../../../math/scalar.ts';
|
|
@@ -99,6 +106,8 @@ import { ProbeSweeps } from '../../probeSweeps.ts';
|
|
|
99
106
|
import { ggxMaxLevelFor, irradianceLevelFor, octahedralEdgeFor } from '../../prefilterEnvMap.ts';
|
|
100
107
|
import { environmentTexels } from './environmentTexels.ts';
|
|
101
108
|
import { packIesAtlas } from '../../iesProfile.ts';
|
|
109
|
+
import { fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.ts';
|
|
110
|
+
import { resolveSurfaceScene } from '../../surfaceScene.ts';
|
|
102
111
|
import { COOKIE_TILE } from '../../lightBudget.ts';
|
|
103
112
|
import { FULL_LIGHT_BUDGET } from '../../uniformVectorBudget.ts';
|
|
104
113
|
import { toHalfFloats } from '../../halfFloat.ts';
|
|
@@ -128,6 +137,7 @@ import {
|
|
|
128
137
|
type ShadowLight,
|
|
129
138
|
} from '../../pointShadowSystem.ts';
|
|
130
139
|
import { GpuPointShadowArray, GpuPointShadowMap } from './pointShadowPass.ts';
|
|
140
|
+
import { OCTAHEDRAL_EDGE, strideLayers } from '../../pointShadowArray.ts';
|
|
131
141
|
import {
|
|
132
142
|
createPanelBindGroup,
|
|
133
143
|
createPanelBindGroupLayout,
|
|
@@ -326,6 +336,16 @@ import {
|
|
|
326
336
|
} from '../../globalMedium.ts';
|
|
327
337
|
import type { GlobalMediumOptions } from '../../globalMedium.ts';
|
|
328
338
|
import type { MeshData } from '../../mesh.ts';
|
|
339
|
+
import { castsDepth, glassOf } from '../../shadowCasters.ts';
|
|
340
|
+
import { GlassCasterList, type GlassReplaySink } from '../../glassCasters.ts';
|
|
341
|
+
import { GpuGlassTint } from './glassTintPass.ts';
|
|
342
|
+
import {
|
|
343
|
+
SUN_DYNAMIC_LAYER,
|
|
344
|
+
SUN_GLASS_MOVING_LAYER,
|
|
345
|
+
SUN_GLASS_STATIC_LAYER,
|
|
346
|
+
SUN_PEELED_LAYER,
|
|
347
|
+
SUN_STATIC_LAYER,
|
|
348
|
+
} from '../../shadowMap.ts';
|
|
329
349
|
import type { SceneCasterMaterial, ShadowCasterSink, ShadowCasters } from '../../shadowCasters.ts';
|
|
330
350
|
import { VERTEX_LAYOUT, createGpuMesh, createGpuMeshIncremental, type GpuMesh } from './buffers.ts';
|
|
331
351
|
import type { GpuSurface } from './device.ts';
|
|
@@ -470,6 +490,7 @@ import {
|
|
|
470
490
|
import { aimReflection, createGpuReflection, type GpuReflection } from './reflectionPass.ts';
|
|
471
491
|
import { FrameBudget } from '../budget.ts';
|
|
472
492
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.ts';
|
|
493
|
+
import { FOG_RECEDE, fogModeOf } from '../drawFog.ts';
|
|
473
494
|
import { DYNAMIC_ALIGNMENT as DYNAMIC_UNIFORM_ALIGNMENT, UniformRing } from './uniformRing.ts';
|
|
474
495
|
import { DecalQueue, MAX_DRAWN_DECALS } from '../../decalQueue.ts';
|
|
475
496
|
import { DistanceFieldScene } from '../../gi/fieldScene.ts';
|
|
@@ -525,6 +546,9 @@ import {
|
|
|
525
546
|
import { createFrustum, frustumFromViewProjection } from '../../../math/frustum.ts';
|
|
526
547
|
import type { Frustum } from '../../../math/frustum.ts';
|
|
527
548
|
import type { Bounds } from '../../../math/bounds.ts';
|
|
549
|
+
import { batchBoxVisible, cullsInstances } from '../../instanceCull.ts';
|
|
550
|
+
import { InstanceCullPass, MAX_CULL_SLOTS } from './instanceCullPass.ts';
|
|
551
|
+
import type { CullSlot } from './instanceCullPass.ts';
|
|
528
552
|
import { OcclusionBuffer } from '../../occlusion.ts';
|
|
529
553
|
import { boundsVisible } from '../../visibility.ts';
|
|
530
554
|
import { createPassRegistry, drainRegistry, passAt, registerIn, unregisterIn } from '../../pass.ts';
|
|
@@ -550,11 +574,15 @@ import type {
|
|
|
550
574
|
} from '../../pass.ts';
|
|
551
575
|
import type { Arena, FlushSchedule, ScheduledPass } from '../../frame/index.ts';
|
|
552
576
|
import { GpuInstancedBatch } from './instanced.ts';
|
|
553
|
-
import type { MeshInstances } from '../../instances.ts';
|
|
577
|
+
import type { InstancedOptions, MeshInstances } from '../../instances.ts';
|
|
554
578
|
import { cutoutOf } from '../../cutoutCaster.ts';
|
|
555
579
|
import type { CutoutCaster } from '../../cutoutCaster.ts';
|
|
556
580
|
import { LightField } from '../../driftLight/lightField.ts';
|
|
557
581
|
import type { LightFieldOptions, LightFieldSource } from '../../driftLight/lightField.ts';
|
|
582
|
+
import type { DriftLightVolumes } from '../../driftLight/presence.ts';
|
|
583
|
+
import type { DenseLightVolume } from '../../driftLight/denseField.ts';
|
|
584
|
+
import { WorldLightField } from '../../driftLight/worldLightField.ts';
|
|
585
|
+
import type { WorldLightFieldOptions } from '../../driftLight/worldLightField.ts';
|
|
558
586
|
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.ts';
|
|
559
587
|
|
|
560
588
|
/**
|
|
@@ -1308,6 +1336,49 @@ function padded(direction: ArrayLike<number>): Float32Array {
|
|
|
1308
1336
|
return SCRATCH_VEC4;
|
|
1309
1337
|
}
|
|
1310
1338
|
|
|
1339
|
+
/** The sun texture's usage: attached, sampled, and copied both ways when glass grows it. */
|
|
1340
|
+
const SUN_USAGE = 0x10 | 0x4 | 0x1 | 0x2; // RENDER_ATTACHMENT | TEXTURE_BINDING | COPY_SRC | COPY_DST
|
|
1341
|
+
|
|
1342
|
+
/** Every view of the sun's texture a pass reads or attaches, for `maps` of its layers. */
|
|
1343
|
+
function sunViewsOf(
|
|
1344
|
+
texture: GPUTexture,
|
|
1345
|
+
maps: number,
|
|
1346
|
+
layers: number,
|
|
1347
|
+
): {
|
|
1348
|
+
array: GPUTextureView;
|
|
1349
|
+
staticArray: GPUTextureView;
|
|
1350
|
+
static: GPUTextureView;
|
|
1351
|
+
dynamic: GPUTextureView | null;
|
|
1352
|
+
peel: GPUTextureView | null;
|
|
1353
|
+
glass: readonly [GPUTextureView, GPUTextureView] | null;
|
|
1354
|
+
} {
|
|
1355
|
+
const layer = (index: number): GPUTextureView =>
|
|
1356
|
+
texture.createView({ dimension: '2d', baseArrayLayer: index, arrayLayerCount: 1 });
|
|
1357
|
+
return {
|
|
1358
|
+
array: texture.createView({ dimension: '2d-array' }),
|
|
1359
|
+
staticArray: texture.createView({
|
|
1360
|
+
dimension: '2d-array',
|
|
1361
|
+
baseArrayLayer: SUN_STATIC_LAYER,
|
|
1362
|
+
arrayLayerCount: 1,
|
|
1363
|
+
}),
|
|
1364
|
+
static: layer(SUN_STATIC_LAYER),
|
|
1365
|
+
dynamic: maps > SUN_DYNAMIC_LAYER ? layer(SUN_DYNAMIC_LAYER) : null,
|
|
1366
|
+
peel: maps > SUN_PEELED_LAYER ? layer(SUN_PEELED_LAYER) : null,
|
|
1367
|
+
glass: layers > maps ? [layer(SUN_GLASS_STATIC_LAYER), layer(SUN_GLASS_MOVING_LAYER)] : null,
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/**
|
|
1372
|
+
* What the sun keeps through glass: two `rgba8unorm` layers, attached one at a time at level 0,
|
|
1373
|
+
* sampled whole with their mip chains, which are rebuilt after each layer is drawn.
|
|
1374
|
+
*/
|
|
1375
|
+
interface SunTintTarget {
|
|
1376
|
+
readonly texture: GPUTexture;
|
|
1377
|
+
readonly layers: readonly [GPUTextureView, GPUTextureView];
|
|
1378
|
+
readonly array: GPUTextureView;
|
|
1379
|
+
readonly mips: readonly [SnapshotMips, SnapshotMips];
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1311
1382
|
export class WebGPURenderer implements RendererApi {
|
|
1312
1383
|
/**
|
|
1313
1384
|
* Real timestamp queries, where the adapter offers them.
|
|
@@ -1417,6 +1488,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1417
1488
|
* caller who passed null gets — rather than reading a slot this frame has already re-let.
|
|
1418
1489
|
*/
|
|
1419
1490
|
private skinPaletteSlot = -1;
|
|
1491
|
+
/** The palette `skinPaletteSlot` was uploaded from, for a skinned mover to copy. See `Mover`. */
|
|
1492
|
+
private skinPaletteData: Float32Array | null = null;
|
|
1420
1493
|
|
|
1421
1494
|
private readonly surface: GpuSurface;
|
|
1422
1495
|
/** The encoder for the frame in flight, or null between frames. */
|
|
@@ -1426,6 +1499,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1426
1499
|
private pass: GPURenderPassEncoder | null = null;
|
|
1427
1500
|
|
|
1428
1501
|
private readonly pipelines: PipelineCache;
|
|
1502
|
+
/**
|
|
1503
|
+
* The pipelines a reconstructing frame's blended draws are built for: the reconstructed picture's
|
|
1504
|
+
* format at one sample, which is where `LatePass` draws them. Aliased to `pipelines` when the
|
|
1505
|
+
* world already lands on exactly that, as `overlayPipelines` is.
|
|
1506
|
+
*/
|
|
1507
|
+
private readonly latePipelines: PipelineCache;
|
|
1508
|
+
/** The blended draws of a reconstructing frame, drawn after the upscale. See `LatePass`. */
|
|
1509
|
+
private latePass: LatePass | null = null;
|
|
1510
|
+
/** A late draw asked to refract: `runLate` copies the reconstructed picture before replaying. */
|
|
1511
|
+
private lateRefractWanted = false;
|
|
1512
|
+
/** The refraction copy's mip chain, recorded after each copy: what frosted glass reads. */
|
|
1513
|
+
private refractMips: SnapshotMips | null = null;
|
|
1514
|
+
/**
|
|
1515
|
+
* The refraction copy's sampler: filtering, and **between levels as well**, so a frosted pane's
|
|
1516
|
+
* level falls smoothly between two blurs rather than stepping from one to the next.
|
|
1517
|
+
*/
|
|
1518
|
+
private refractSampler: GPUSampler | null = null;
|
|
1519
|
+
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
1520
|
+
private readonly glassScratch: ResolvedGlass = { transmission: 0, frost: 0, tint: [1, 1, 1] };
|
|
1521
|
+
/** `issueCommand` bound once, so the late replay allocates no closure per frame. */
|
|
1522
|
+
private readonly issueBound: (pass: GPURenderPassEncoder, command: DrawCommand) => void;
|
|
1429
1523
|
/**
|
|
1430
1524
|
* The same pipelines again, built for the canvas, for anything drawn after `endFrame`.
|
|
1431
1525
|
*
|
|
@@ -1818,6 +1912,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1818
1912
|
* what the layout already declares.
|
|
1819
1913
|
*/
|
|
1820
1914
|
private bloomGroups: GPUBindGroup[] = [];
|
|
1915
|
+
/**
|
|
1916
|
+
* The picture the bloom reads, and its size: the scene target, or while reconstructing the
|
|
1917
|
+
* resolved picture at the drawing buffer's size. See `rebuildBloomGroups`.
|
|
1918
|
+
*/
|
|
1919
|
+
private bloomSourceWidth = 1;
|
|
1920
|
+
private bloomSourceHeight = 1;
|
|
1821
1921
|
private frameWidth = 1;
|
|
1822
1922
|
private frameHeight = 1;
|
|
1823
1923
|
/**
|
|
@@ -1971,13 +2071,25 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1971
2071
|
* the *unsharpened* result — sharpening into the history would sharpen a sharpened picture every
|
|
1972
2072
|
* frame, which is the halo the reference's own clamp exists to prevent, compounded.
|
|
1973
2073
|
*
|
|
1974
|
-
* The motion target is render size and carries a drawn object's own motion where
|
|
1975
|
-
* one
|
|
1976
|
-
*
|
|
1977
|
-
* exact
|
|
2074
|
+
* The motion target is render size and carries a drawn object's own motion where the motion pass
|
|
2075
|
+
* wrote one — a draw that named its mover, or passed last frame's matrix. Every other texel is
|
|
2076
|
+
* cleared, and the resolve takes the camera's motion there, derived from the depth and the
|
|
2077
|
+
* reprojection: exact for anything that did not move. See `MotionPass`.
|
|
1978
2078
|
*/
|
|
1979
2079
|
private reconHistories: (GPUTexture | null)[] = [null, null];
|
|
1980
2080
|
private reconHistoryViews: (GPUTextureView | null)[] = [null, null];
|
|
2081
|
+
/**
|
|
2082
|
+
* Each output pixel's flicker record, a pair swapped with the histories: the temporal resolve's
|
|
2083
|
+
* anti-flicker, over the reconstruction's own period (`recon/resolve.ts`).
|
|
2084
|
+
*/
|
|
2085
|
+
private reconFlickers: (GPUTexture | null)[] = [null, null];
|
|
2086
|
+
private reconFlickerViews: (GPUTextureView | null)[] = [null, null];
|
|
2087
|
+
/** Each output pixel's held depth swing, a pair of its own: see `recon/resolve.ts`. */
|
|
2088
|
+
private reconSwings: (GPUTexture | null)[] = [null, null];
|
|
2089
|
+
private reconSwingViews: (GPUTextureView | null)[] = [null, null];
|
|
2090
|
+
/** The anti-flicker's rule for the period last asked for, kept so a frame makes none. */
|
|
2091
|
+
private reconFlickerPhases = 0;
|
|
2092
|
+
private reconFlickerRule: FlickerRule = TEMPORAL_FLICKER;
|
|
1981
2093
|
private reconWrite = 0;
|
|
1982
2094
|
private reconHasHistory = false;
|
|
1983
2095
|
private reconShown: GPUTexture | null = null;
|
|
@@ -1996,40 +2108,24 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1996
2108
|
private reconGroups: (GPUBindGroup | null)[] = [null, null];
|
|
1997
2109
|
private reconSharpenGroups: (GPUBindGroup | null)[] = [null, null];
|
|
1998
2110
|
private reconRushGroup: GPUBindGroup | null = null;
|
|
1999
|
-
/**
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
private
|
|
2007
|
-
private
|
|
2008
|
-
private motionCount = 0;
|
|
2009
|
-
private motionLayout: GPUBindGroupLayout | null = null;
|
|
2010
|
-
/**
|
|
2011
|
-
* One pipeline per vertex stride, because a stride is a property of the *mesh*.
|
|
2012
|
-
*
|
|
2013
|
-
* Which optional attributes a mesh supplied decides how far apart its positions are, and this
|
|
2014
|
-
* pass reads nothing but positions — so two meshes drawn by the same shader still need two
|
|
2015
|
-
* vertex layouts. A map rather than a cache key on the pipeline cache: there is one shader here
|
|
2016
|
-
* and a handful of strides in any scene.
|
|
2017
|
-
*/
|
|
2018
|
-
private readonly motionPipelines = new Map<number, GPURenderPipeline>();
|
|
2019
|
-
private motionModule: GPUShaderModule | null = null;
|
|
2020
|
-
private motionPipelineLayout: GPUPipelineLayout | null = null;
|
|
2021
|
-
private motionFrameBuffer: GPUBuffer | null = null;
|
|
2022
|
-
private motionDrawBuffer: GPUBuffer | null = null;
|
|
2023
|
-
private motionDrawCapacity = 0;
|
|
2024
|
-
private motionGroup: GPUBindGroup | null = null;
|
|
2025
|
-
private readonly motionFrameStaging = new Float32Array(MOTION_FRAME_FLOATS);
|
|
2026
|
-
private motionDrawStaging = new Uint8Array(0);
|
|
2111
|
+
/** The motion pass, made with the reconstruction's targets. See `MotionPass`. */
|
|
2112
|
+
private motionPass: MotionPass | null = null;
|
|
2113
|
+
/** This renderer's identity to a `Mover`. See `claimRendererId`. */
|
|
2114
|
+
private readonly moverRenderer = claimRendererId();
|
|
2115
|
+
/** Frames begun, so a mover can tell last frame from any other. Advanced in `beginFrame`. */
|
|
2116
|
+
private moverFrame = 0;
|
|
2117
|
+
/** Between `beginFrame` and `endFrame`. A change outside it is the next frame's. */
|
|
2118
|
+
private inFrame = false;
|
|
2119
|
+
private warnedMoverTwice = false;
|
|
2027
2120
|
/** The scene's own clip transform with the generated shaders' y negation folded in. */
|
|
2028
2121
|
private readonly motionRaster = mat4.create();
|
|
2029
2122
|
private reconSizeKey = '';
|
|
2030
2123
|
/** Last frame's raw view-projection and its inverse, and the two eyes. */
|
|
2031
2124
|
private readonly reconPreviousRawViewProj = mat4.create();
|
|
2032
2125
|
private readonly reconInverseViewProj = mat4.create();
|
|
2126
|
+
/** The inverse and the reprojection, multiplied in double precision. See `runReconstruction`. */
|
|
2127
|
+
private readonly reconInverse64 = new Float64Array(16);
|
|
2128
|
+
private readonly reconReprojection64 = new Float64Array(16);
|
|
2033
2129
|
private readonly reconPreviousInverseViewProj = mat4.create();
|
|
2034
2130
|
private readonly reconEye = new Float32Array(3);
|
|
2035
2131
|
private readonly reconPreviousEye = new Float32Array(3);
|
|
@@ -2181,6 +2277,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2181
2277
|
/** Said once for the whole renderer. See `refuseOitMultisampled`. */
|
|
2182
2278
|
private oitMultisampleRefused = false;
|
|
2183
2279
|
private oitReplaying = false;
|
|
2280
|
+
/** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
|
|
2281
|
+
private readonly surfaceScene = new Float32Array(4);
|
|
2184
2282
|
/** Which buffer the replay is filling, which the pipeline key must carry. */
|
|
2185
2283
|
private oitMode: OitTarget = 'none';
|
|
2186
2284
|
/**
|
|
@@ -2276,6 +2374,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2276
2374
|
refraction: number;
|
|
2277
2375
|
refractTint: Vec3 | undefined;
|
|
2278
2376
|
thicknessM: number;
|
|
2377
|
+
glass: GlassOptions | undefined;
|
|
2279
2378
|
} = {
|
|
2280
2379
|
lit: true,
|
|
2281
2380
|
fog: true,
|
|
@@ -2286,6 +2385,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2286
2385
|
refraction: 0,
|
|
2287
2386
|
refractTint: undefined,
|
|
2288
2387
|
thicknessM: 0,
|
|
2388
|
+
glass: undefined,
|
|
2289
2389
|
};
|
|
2290
2390
|
|
|
2291
2391
|
private reflectionPassActive = false;
|
|
@@ -2470,6 +2570,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2470
2570
|
}
|
|
2471
2571
|
|
|
2472
2572
|
private flushRings(): void {
|
|
2573
|
+
/* Culls first: a submit that follows may carry a draw reading what they write. */
|
|
2574
|
+
this.instanceCulls?.flush();
|
|
2473
2575
|
for (const ring of this.rings()) ring.flush();
|
|
2474
2576
|
}
|
|
2475
2577
|
|
|
@@ -2591,7 +2693,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2591
2693
|
this.filmFrags.writeBlock(fragmentSlot, this.filmFragInts);
|
|
2592
2694
|
|
|
2593
2695
|
const filmPipe = filmPipeline(
|
|
2594
|
-
this.
|
|
2696
|
+
this.blendedPipelines(),
|
|
2595
2697
|
this.surface.device,
|
|
2596
2698
|
this.filmLayout,
|
|
2597
2699
|
`film|${geometry.key ?? ''}`,
|
|
@@ -2605,10 +2707,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2605
2707
|
* holds the mirror's colour whatever `uReflectionEnabled` says, and WebGPU validates the
|
|
2606
2708
|
* binding rather than the read — the argument `filmBindGroupBlank` exists for.
|
|
2607
2709
|
*/
|
|
2608
|
-
const filmCommand = this.
|
|
2609
|
-
filmGroup === this.filmBindGroup ? MIRROR_COLOR : 0,
|
|
2610
|
-
this.currentTarget(),
|
|
2611
|
-
);
|
|
2710
|
+
const filmCommand = this.recordBlended(filmGroup === this.filmBindGroup ? MIRROR_COLOR : 0);
|
|
2612
2711
|
if (filmCommand !== null) {
|
|
2613
2712
|
filmCommand.pipeline = filmPipe;
|
|
2614
2713
|
filmCommand.bindGroup = filmGroup;
|
|
@@ -2710,6 +2809,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2710
2809
|
return new GpuSurfaceTexture(this.surface.device, this.pipelines, source, options);
|
|
2711
2810
|
}
|
|
2712
2811
|
|
|
2812
|
+
/** Several images of one size as an array's layers. See `RendererApi.createSurfaceTextureArray`. */
|
|
2813
|
+
createSurfaceTextureArray(
|
|
2814
|
+
sources: readonly TexImageSource[],
|
|
2815
|
+
options: SurfaceTextureOptions = {},
|
|
2816
|
+
): GpuSurfaceTexture {
|
|
2817
|
+
return new GpuSurfaceTexture(this.surface.device, this.pipelines, sources, options);
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2713
2820
|
/**
|
|
2714
2821
|
* GPU textures an `updateSurfaceTexture` replaced, destroyed when the next frame begins.
|
|
2715
2822
|
*
|
|
@@ -2799,6 +2906,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2799
2906
|
*/
|
|
2800
2907
|
setSkinPalette(palette: Float32Array | null): void {
|
|
2801
2908
|
if (this.surface.lost) return;
|
|
2909
|
+
this.skinPaletteData = palette;
|
|
2802
2910
|
if (palette === null) {
|
|
2803
2911
|
this.skinPaletteSlot = -1;
|
|
2804
2912
|
return;
|
|
@@ -2987,6 +3095,19 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2987
3095
|
});
|
|
2988
3096
|
}
|
|
2989
3097
|
|
|
3098
|
+
/**
|
|
3099
|
+
* How much of the surfaces drawn next survives a screen-door dither. See `renderer.ts`.
|
|
3100
|
+
*
|
|
3101
|
+
* Written straight into the scratch block rather than through `material()`'s callback, because
|
|
3102
|
+
* this one is called per crossfading region per frame and a closure per call is an allocation.
|
|
3103
|
+
*/
|
|
3104
|
+
setDitherFade(amount: number): void {
|
|
3105
|
+
if (this.surface.lost) return;
|
|
3106
|
+
/* The second half of `uWriteMode`, which the order-independent weight shares. */
|
|
3107
|
+
this.perFrameFloats[this.materialField('uWriteMode') + 1] = Math.min(1, Math.max(-1, amount));
|
|
3108
|
+
this.materials.dirty();
|
|
3109
|
+
}
|
|
3110
|
+
|
|
2990
3111
|
/**
|
|
2991
3112
|
* How strong the microscopic relief on the surfaces drawn next is, and how coarse.
|
|
2992
3113
|
*
|
|
@@ -3167,7 +3288,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3167
3288
|
* no-op before the first `bindMeshPass` of a session, same as every other pass that reads it.
|
|
3168
3289
|
*/
|
|
3169
3290
|
drawSdfText(text: GpuSdfText, model: Float32Array, color: Vec3, opacity: number): void {
|
|
3170
|
-
|
|
3291
|
+
/* Unjittered when it lands after the upscale, like every late draw. See `drawsLate`. */
|
|
3292
|
+
const viewProj = this.drawsLate() ? this.correctedViewProj : this.viewProj;
|
|
3171
3293
|
if (!this.canDraw() || viewProj === null) return;
|
|
3172
3294
|
const font = text.font;
|
|
3173
3295
|
const atlas = text.atlas;
|
|
@@ -3201,12 +3323,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3201
3323
|
this.sdfTextFrags.writeFloat(fragmentSlot, fragAt('uOutputExposure'), this.gradeExposure());
|
|
3202
3324
|
|
|
3203
3325
|
const sdfPipe = sdfTextPipeline(
|
|
3204
|
-
this.
|
|
3326
|
+
this.blendedPipelines(),
|
|
3205
3327
|
this.surface.device,
|
|
3206
3328
|
this.sdfTextLayout,
|
|
3207
3329
|
);
|
|
3208
3330
|
const sdfGroup = this.sdfTextBindGroupFor(atlas);
|
|
3209
|
-
const sdfCommand = this.
|
|
3331
|
+
const sdfCommand = this.recordBlended(0);
|
|
3210
3332
|
if (sdfCommand !== null) {
|
|
3211
3333
|
sdfCommand.pipeline = sdfPipe;
|
|
3212
3334
|
sdfCommand.bindGroup = sdfGroup;
|
|
@@ -3322,8 +3444,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3322
3444
|
|
|
3323
3445
|
const pass = this.openTextPass();
|
|
3324
3446
|
if (pass === null) return;
|
|
3325
|
-
const textPipe = textPipeline(this.
|
|
3326
|
-
const textCommand = this.
|
|
3447
|
+
const textPipe = textPipeline(this.blendedPipelines(), this.surface.device, this.textLayout);
|
|
3448
|
+
const textCommand = this.recordBlended(0);
|
|
3327
3449
|
if (textCommand !== null) {
|
|
3328
3450
|
textCommand.pipeline = textPipe;
|
|
3329
3451
|
textCommand.bindGroup = this.textBindGroup;
|
|
@@ -3773,13 +3895,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3773
3895
|
|
|
3774
3896
|
const layout = this.particleLayoutFor(batch.material).layout;
|
|
3775
3897
|
const particlePipe = particlePipeline(
|
|
3776
|
-
this.
|
|
3898
|
+
this.blendedPipelines(),
|
|
3777
3899
|
device,
|
|
3778
3900
|
layout,
|
|
3779
3901
|
batch.material,
|
|
3780
3902
|
batch.blend,
|
|
3781
3903
|
);
|
|
3782
|
-
const particleCommand = this.
|
|
3904
|
+
const particleCommand = this.recordBlended(0);
|
|
3783
3905
|
if (particleCommand !== null) {
|
|
3784
3906
|
particleCommand.pipeline = particlePipe;
|
|
3785
3907
|
particleCommand.bindGroup = batch.bindGroup;
|
|
@@ -3924,13 +4046,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3924
4046
|
frags.writeFloats(fragmentSlot, atF('uTint'), plumes.tint);
|
|
3925
4047
|
|
|
3926
4048
|
const plumePipe = plumePipeline(
|
|
3927
|
-
this.
|
|
4049
|
+
this.blendedPipelines(),
|
|
3928
4050
|
this.surface.device,
|
|
3929
4051
|
this.plumeLayout,
|
|
3930
4052
|
plumes.material,
|
|
3931
4053
|
plumes.blend,
|
|
3932
4054
|
);
|
|
3933
|
-
const plumeCommand = this.
|
|
4055
|
+
const plumeCommand = this.recordBlended(0);
|
|
3934
4056
|
if (plumeCommand !== null) {
|
|
3935
4057
|
plumeCommand.pipeline = plumePipe;
|
|
3936
4058
|
plumeCommand.bindGroup = this.plumeBindGroup;
|
|
@@ -4485,9 +4607,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4485
4607
|
this.windStreakVerts.writeBlock(vertexSlot, this.windStreakVertBlock);
|
|
4486
4608
|
this.windStreakFrags.writeBlock(fragmentSlot, this.windStreakFragBlock);
|
|
4487
4609
|
|
|
4488
|
-
const windPipe = windStreakPipeline(
|
|
4610
|
+
const windPipe = windStreakPipeline(
|
|
4611
|
+
this.blendedPipelines(),
|
|
4612
|
+
this.surface.device,
|
|
4613
|
+
this.windStreakLayout,
|
|
4614
|
+
);
|
|
4489
4615
|
/* Two uniform buffers and two vertex buffers. Nothing is sampled, so nothing is read. */
|
|
4490
|
-
const windCommand = this.
|
|
4616
|
+
const windCommand = this.recordBlended(0);
|
|
4491
4617
|
if (windCommand !== null) {
|
|
4492
4618
|
windCommand.pipeline = windPipe;
|
|
4493
4619
|
windCommand.bindGroup = this.windStreakBindGroup;
|
|
@@ -4715,8 +4841,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4715
4841
|
this.boltVerts.writeBlock(vertexSlot, this.boltVertBlock);
|
|
4716
4842
|
this.boltFrags.writeBlock(fragmentSlot, this.boltFragInts);
|
|
4717
4843
|
|
|
4718
|
-
const boltPipe = boltPipeline(this.
|
|
4719
|
-
const boltCommand = this.
|
|
4844
|
+
const boltPipe = boltPipeline(this.blendedPipelines(), this.surface.device, this.boltLayout);
|
|
4845
|
+
const boltCommand = this.recordBlended(0);
|
|
4720
4846
|
if (boltCommand !== null) {
|
|
4721
4847
|
boltCommand.pipeline = boltPipe;
|
|
4722
4848
|
boltCommand.bindGroup = this.boltBindGroup;
|
|
@@ -4853,9 +4979,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4853
4979
|
this.lineFrags.writeFloat(fragmentSlot, atF('uFogNear'), medium.fogNear);
|
|
4854
4980
|
this.lineFrags.writeFloat(fragmentSlot, atF('uFogFar'), medium.fogFar);
|
|
4855
4981
|
|
|
4856
|
-
const linePipe = linePipeline(
|
|
4982
|
+
const linePipe = linePipeline(
|
|
4983
|
+
this.blendedPipelines(),
|
|
4984
|
+
this.surface.device,
|
|
4985
|
+
this.lineLayout,
|
|
4986
|
+
additive,
|
|
4987
|
+
);
|
|
4857
4988
|
/* Two uniform blocks and three vertex buffers, and no attachment among them. */
|
|
4858
|
-
const lineCommand = this.
|
|
4989
|
+
const lineCommand = this.recordBlended(0);
|
|
4859
4990
|
if (lineCommand !== null) {
|
|
4860
4991
|
lineCommand.pipeline = linePipe;
|
|
4861
4992
|
lineCommand.bindGroup = this.lineBindGroup;
|
|
@@ -4900,6 +5031,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4900
5031
|
private readonly waterLayout: GPUBindGroupLayout;
|
|
4901
5032
|
/** The one-pixel stand-in, kept so the water bind group can be rebuilt without one. */
|
|
4902
5033
|
private readonly blankView: GPUTextureView;
|
|
5034
|
+
/** The blank texel as a one-layer array: the stand-in every surface-texture binding takes. */
|
|
5035
|
+
private readonly blankSurfaceView: GPUTextureView;
|
|
4903
5036
|
private readonly blankSampler: GPUSampler;
|
|
4904
5037
|
/**
|
|
4905
5038
|
* A slot per body, not one buffer rewritten before each draw.
|
|
@@ -5078,8 +5211,39 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5078
5211
|
|
|
5079
5212
|
/* -- The shadow pass -------------------------------------------------------------- */
|
|
5080
5213
|
|
|
5081
|
-
|
|
5082
|
-
|
|
5214
|
+
/**
|
|
5215
|
+
* The sun's static, moving and peeled maps, as layers of one texture (`shadowMap.ts`). The three
|
|
5216
|
+
* views below are single layers, what each pass attaches; the two here are what is sampled.
|
|
5217
|
+
*/
|
|
5218
|
+
private sunShadowTexture: GPUTexture;
|
|
5219
|
+
/** Every layer, for the lit pass, the light volumes and the medium. */
|
|
5220
|
+
private sunArrayView: GPUTextureView;
|
|
5221
|
+
/**
|
|
5222
|
+
* The static layer alone, as an array, for the peel: it draws into another layer of the same
|
|
5223
|
+
* texture, and a view covering that layer too would put it in two usages in one pass.
|
|
5224
|
+
*/
|
|
5225
|
+
private sunStaticArrayView: GPUTextureView;
|
|
5226
|
+
/** How many layers the sun's texture holds: its maps, and the two glass layers once glass casts. */
|
|
5227
|
+
private sunLayerCount: number;
|
|
5228
|
+
/** The glass depth layers, static then moving, once glass has cast. See `growSunForGlass`. */
|
|
5229
|
+
private sunGlassViews: readonly [GPUTextureView, GPUTextureView] | null = null;
|
|
5230
|
+
/** The texture a growth replaced, destroyed once the encoder that copied out of it is submitted. */
|
|
5231
|
+
private retiredSunTexture: GPUTexture | null = null;
|
|
5232
|
+
/** What the sun's light keeps through glass: two `rgba8unorm` layers, made when glass first casts. */
|
|
5233
|
+
private sunTint: SunTintTarget | null = null;
|
|
5234
|
+
/** The tint pass's layouts, per-draw block, groups and pipelines, made when glass first casts. */
|
|
5235
|
+
private glassTint: GpuGlassTint | null = null;
|
|
5236
|
+
/** The tint pass open while the glass list is replayed into it. */
|
|
5237
|
+
private tintPass: GPURenderPassEncoder | null = null;
|
|
5238
|
+
/** True while the glass list is replayed into a glass depth layer through `casterSink`. */
|
|
5239
|
+
private drawingGlassDepth = false;
|
|
5240
|
+
/** Whether the open sun pass is the movers' layer, whose glass goes to the moving glass layer. */
|
|
5241
|
+
private shadowLayerIsDynamic = false;
|
|
5242
|
+
/** The glass tints' sampler: filtering, and between levels, for frost's coarser reads. */
|
|
5243
|
+
private readonly tintSampler: GPUSampler;
|
|
5244
|
+
/** The light a tint pass is drawn for: xyz a direction (w 0) or a position (w 1). */
|
|
5245
|
+
private readonly glassLight = new Float32Array(4);
|
|
5246
|
+
private shadowView: GPUTextureView;
|
|
5083
5247
|
private readonly shadowLayout: GPUBindGroupLayout;
|
|
5084
5248
|
/** The same layout plus the joint palette. See `createDepthBindGroupLayout`. */
|
|
5085
5249
|
private readonly shadowSkinnedLayout: GPUBindGroupLayout;
|
|
@@ -5105,8 +5269,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5105
5269
|
* read twice — so a profile that switches peeling off gets no map and no pass on either
|
|
5106
5270
|
* backend rather than a map nothing writes.
|
|
5107
5271
|
*/
|
|
5108
|
-
private
|
|
5109
|
-
private readonly peelView: GPUTextureView | null;
|
|
5272
|
+
private peelView: GPUTextureView | null;
|
|
5110
5273
|
/**
|
|
5111
5274
|
* The movers' own layer: the character, the rope it swings on, and anything else that walks.
|
|
5112
5275
|
*
|
|
@@ -5127,10 +5290,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5127
5290
|
* and therefore as lit — so nothing looked broken, only empty, which is why it survived the
|
|
5128
5291
|
* port and every gate in it.
|
|
5129
5292
|
*/
|
|
5130
|
-
private
|
|
5131
|
-
private readonly dynamicView: GPUTextureView | null;
|
|
5293
|
+
private dynamicView: GPUTextureView | null;
|
|
5132
5294
|
/** The peel's own bind groups: `uPeelShadowLayer` at 1, and the static map to peel against. */
|
|
5133
|
-
private
|
|
5295
|
+
private shadowPeelBindGroup: GPUBindGroup | null;
|
|
5134
5296
|
/**
|
|
5135
5297
|
* The cutout casters' layout, and a bind group per cutout map for each layer, built the first
|
|
5136
5298
|
* time a map casts and kept: a `createBindGroup` per draw is the allocation the house rules forbid.
|
|
@@ -5142,7 +5304,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5142
5304
|
private readonly cutoutPeelGroups = new Map<GpuSurfaceTexture, GPUBindGroup>();
|
|
5143
5305
|
private shadowPeelPassUniforms: GPUBuffer | null = null;
|
|
5144
5306
|
private shadowPeelSampler: GPUSampler | null = null;
|
|
5145
|
-
private
|
|
5307
|
+
private scatterDepthPeelBindGroup: GPUBindGroup | null;
|
|
5146
5308
|
/** Which layer the open pass is writing, so the sink picks the bindings that match it. */
|
|
5147
5309
|
private shadowLayerIsPeel = false;
|
|
5148
5310
|
/**
|
|
@@ -5201,7 +5363,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5201
5363
|
const held = groups.get(texture);
|
|
5202
5364
|
if (held !== undefined) return held;
|
|
5203
5365
|
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
5204
|
-
const previousView = peel ? this.
|
|
5366
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
5205
5367
|
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
5206
5368
|
if (passUniforms === null || previousView === null || previousSampler === null) return null;
|
|
5207
5369
|
const group = createDepthBindGroup(
|
|
@@ -5279,8 +5441,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5279
5441
|
* Built once and held, rather than made per call: it closes over `this` and a fresh one
|
|
5280
5442
|
* every frame would be an allocation in a per-frame path.
|
|
5281
5443
|
*/
|
|
5444
|
+
/** The glass the open shadow pass was offered, kept for its own passes: `glassCasters.ts`. */
|
|
5445
|
+
private readonly glassCasters = new GlassCasterList();
|
|
5446
|
+
/** One resolution every glass caster is read through, so recording one allocates nothing. */
|
|
5447
|
+
private readonly glassCasterScratch: ResolvedGlass = {
|
|
5448
|
+
transmission: 0,
|
|
5449
|
+
frost: 0,
|
|
5450
|
+
tint: [1, 1, 1],
|
|
5451
|
+
};
|
|
5282
5452
|
private readonly casterSink: ShadowCasterSink = {
|
|
5283
5453
|
mesh: (mesh, model, material) => {
|
|
5454
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
5455
|
+
/* Glass: kept aside for its own two passes rather than dropped. See `glassCasters.ts`. */
|
|
5456
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
5457
|
+
this.glassCasters.recordMesh(mesh, model, material, this.glassCasterScratch);
|
|
5458
|
+
}
|
|
5459
|
+
return;
|
|
5460
|
+
}
|
|
5284
5461
|
const pass = this.shadowPass;
|
|
5285
5462
|
if (pass === null) return;
|
|
5286
5463
|
/* A cutout caster's group is looked up before a slot is spent, so a miss costs nothing. */
|
|
@@ -5330,6 +5507,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5330
5507
|
* uses — one draw, however many instances, exactly as the colour pass does it.
|
|
5331
5508
|
*/
|
|
5332
5509
|
instanced: (batch, data, material) => {
|
|
5510
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
5511
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
5512
|
+
this.glassCasters.recordInstanced(batch, data, material, this.glassCasterScratch);
|
|
5513
|
+
}
|
|
5514
|
+
return;
|
|
5515
|
+
}
|
|
5333
5516
|
const pass = this.shadowPass;
|
|
5334
5517
|
if (pass === null) return;
|
|
5335
5518
|
const gpuBatch = batch as GpuInstancedBatch;
|
|
@@ -5378,7 +5561,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5378
5561
|
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
5379
5562
|
pass.drawIndexed(geometry.indexCount, count);
|
|
5380
5563
|
},
|
|
5381
|
-
skinnedMesh: (mesh, model, palette) => {
|
|
5564
|
+
skinnedMesh: (mesh, model, palette, material) => {
|
|
5565
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
5566
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
5567
|
+
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
5568
|
+
}
|
|
5569
|
+
return;
|
|
5570
|
+
}
|
|
5382
5571
|
const pass = this.shadowPass;
|
|
5383
5572
|
if (pass === null) return;
|
|
5384
5573
|
/* Its own slot, so two characters casting into one bake do not both take the second one's
|
|
@@ -5762,10 +5951,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5762
5951
|
* nothing at all under the default profile, which is on. The cache carries it for the same
|
|
5763
5952
|
* reason it carries the sample count: no pass can then disagree with the attachment.
|
|
5764
5953
|
*/
|
|
5954
|
+
/* Off compiles none of the glass lookup into a lit pipeline: see `PipelineCache.glassShadows`. */
|
|
5955
|
+
const glassShadows = quality.glassShadows !== 'off';
|
|
5765
5956
|
this.pipelines = new PipelineCache(
|
|
5766
5957
|
device,
|
|
5767
5958
|
quality.screenEffects ? sceneColorFormat(quality.hdrScene) : surface.format,
|
|
5768
5959
|
this.samples,
|
|
5960
|
+
glassShadows,
|
|
5769
5961
|
);
|
|
5770
5962
|
/*
|
|
5771
5963
|
* The canvas' own state, for the pass `ensurePass` reopens after `endFrame`.
|
|
@@ -5778,7 +5970,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5778
5970
|
this.overlayPipelines =
|
|
5779
5971
|
this.pipelines.format === surface.format && this.samples === 1
|
|
5780
5972
|
? this.pipelines
|
|
5781
|
-
: new PipelineCache(device, surface.format, 1);
|
|
5973
|
+
: new PipelineCache(device, surface.format, 1, glassShadows);
|
|
5974
|
+
this.latePipelines =
|
|
5975
|
+
this.pipelines.format === RECON_HISTORY_FORMAT && this.samples === 1
|
|
5976
|
+
? this.pipelines
|
|
5977
|
+
: new PipelineCache(device, RECON_HISTORY_FORMAT, 1, glassShadows);
|
|
5978
|
+
this.issueBound = this.issueCommand.bind(this);
|
|
5782
5979
|
this.pipelineTargets =
|
|
5783
5980
|
this.overlayPipelines === this.pipelines
|
|
5784
5981
|
? [this.pipelines]
|
|
@@ -5866,6 +6063,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5866
6063
|
* `uStaticShadowMap` rather than by anything here changing shape.
|
|
5867
6064
|
*/
|
|
5868
6065
|
const blankView = blank.createView();
|
|
6066
|
+
/* The same texel as a one-layer array, for the four material maps, which are arrays now. */
|
|
6067
|
+
const blankSurfaceView = blank.createView({ dimension: '2d-array' });
|
|
6068
|
+
this.blankSurfaceView = blankSurfaceView;
|
|
5869
6069
|
const blankSampler = device.createSampler({ label: 'flat.sampler' });
|
|
5870
6070
|
|
|
5871
6071
|
/*
|
|
@@ -5929,42 +6129,35 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5929
6129
|
const blankDriftIndexView = blankVolume('flat.emptyDriftLightIndex', 'r32uint');
|
|
5930
6130
|
const blankDriftAtlasView = blankVolume('flat.emptyDriftLightAtlas', 'rgba16float');
|
|
5931
6131
|
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
6132
|
+
/*
|
|
6133
|
+
* The static world, its movers and — where more than one static depth layer is asked for —
|
|
6134
|
+
* the peel, as layers of one texture, on the conditions `renderer.ts` builds its array on: one
|
|
6135
|
+
* decision read twice. Same size and format throughout, because the shader samples every
|
|
6136
|
+
* layer with one \`uShadowMapSize\` and one set of filter offsets.
|
|
6137
|
+
*/
|
|
6138
|
+
const sunLayers = this.quality.directionalShadows
|
|
6139
|
+
? this.quality.directionalShadowDepthLayers > 1
|
|
6140
|
+
? 3
|
|
6141
|
+
: 2
|
|
6142
|
+
: 1;
|
|
6143
|
+
this.sunShadowTexture = device.createTexture({
|
|
6144
|
+
label: 'shadow.sun',
|
|
6145
|
+
size: [
|
|
6146
|
+
this.quality.directionalShadowMapSize,
|
|
6147
|
+
this.quality.directionalShadowMapSize,
|
|
6148
|
+
sunLayers,
|
|
6149
|
+
],
|
|
5935
6150
|
format: SHADOW_FORMAT,
|
|
5936
|
-
|
|
6151
|
+
/* Copyable both ways, because the first glass grows it by copying every layer across. */
|
|
6152
|
+
usage: SUN_USAGE,
|
|
5937
6153
|
});
|
|
5938
|
-
this.
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
this.
|
|
5945
|
-
this.quality.directionalShadowDepthLayers > 1
|
|
5946
|
-
? device.createTexture({
|
|
5947
|
-
label: 'shadow.peel',
|
|
5948
|
-
size: [this.quality.directionalShadowMapSize, this.quality.directionalShadowMapSize],
|
|
5949
|
-
format: SHADOW_FORMAT,
|
|
5950
|
-
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
5951
|
-
})
|
|
5952
|
-
: null;
|
|
5953
|
-
this.peelView = this.peelMap === null ? null : this.peelMap.createView();
|
|
5954
|
-
/*
|
|
5955
|
-
* The movers' layer, on the condition `renderer.ts` builds its own on: directional shadows
|
|
5956
|
-
* at all. Same size and same format as the static map, because the shader samples all three
|
|
5957
|
-
* with one `uShadowMapSize` and one set of PCF offsets.
|
|
5958
|
-
*/
|
|
5959
|
-
this.dynamicMap = this.quality.directionalShadows
|
|
5960
|
-
? device.createTexture({
|
|
5961
|
-
label: 'shadow.dynamic',
|
|
5962
|
-
size: [this.quality.directionalShadowMapSize, this.quality.directionalShadowMapSize],
|
|
5963
|
-
format: SHADOW_FORMAT,
|
|
5964
|
-
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
5965
|
-
})
|
|
5966
|
-
: null;
|
|
5967
|
-
this.dynamicView = this.dynamicMap === null ? null : this.dynamicMap.createView();
|
|
6154
|
+
this.sunLayerCount = sunLayers;
|
|
6155
|
+
const views = sunViewsOf(this.sunShadowTexture, sunLayers, sunLayers);
|
|
6156
|
+
this.sunArrayView = views.array;
|
|
6157
|
+
this.sunStaticArrayView = views.staticArray;
|
|
6158
|
+
this.shadowView = views.static;
|
|
6159
|
+
this.dynamicView = views.dynamic;
|
|
6160
|
+
this.peelView = views.peel;
|
|
5968
6161
|
|
|
5969
6162
|
/*
|
|
5970
6163
|
* **Cleared once, here, because a texture no pass has written reads as zero**, which the
|
|
@@ -6005,6 +6198,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6005
6198
|
* rather than the hardware's.
|
|
6006
6199
|
*/
|
|
6007
6200
|
const shadowSampler = device.createSampler({ label: 'shadow.sampler' });
|
|
6201
|
+
this.tintSampler = device.createSampler({
|
|
6202
|
+
label: 'shadow.tintSampler',
|
|
6203
|
+
magFilter: 'linear',
|
|
6204
|
+
minFilter: 'linear',
|
|
6205
|
+
mipmapFilter: 'linear',
|
|
6206
|
+
addressModeU: 'clamp-to-edge',
|
|
6207
|
+
addressModeV: 'clamp-to-edge',
|
|
6208
|
+
});
|
|
6008
6209
|
/*
|
|
6009
6210
|
* The photometric atlas's own sampler, **created here rather than reusing `postSampler`.**
|
|
6010
6211
|
*
|
|
@@ -6026,14 +6227,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6026
6227
|
});
|
|
6027
6228
|
|
|
6028
6229
|
this.flatTextures = (name) => {
|
|
6029
|
-
if (name === '
|
|
6030
|
-
/*
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
if (name === '
|
|
6036
|
-
return {
|
|
6230
|
+
if (name === 'uSunShadows') return { view: this.sunArrayView, sampler: shadowSampler };
|
|
6231
|
+
/* What the sun keeps through glass, or the one-texel stand-in the lookup reads as none. */
|
|
6232
|
+
if (name === 'uSunGlassTints') {
|
|
6233
|
+
return { view: this.sunTint?.array ?? blankArrayView, sampler: this.tintSampler };
|
|
6234
|
+
}
|
|
6235
|
+
/* What the lamps keep through glass, one layer a light, or the same stand-in. */
|
|
6236
|
+
if (name === 'uPointGlassTints') {
|
|
6237
|
+
return {
|
|
6238
|
+
view: this.pointShadowArray?.glassTint?.view ?? blankArrayView,
|
|
6239
|
+
sampler: this.tintSampler,
|
|
6240
|
+
};
|
|
6037
6241
|
}
|
|
6038
6242
|
/*
|
|
6039
6243
|
* The caller's image, or the stand-in. Read from the field rather than passed in, because
|
|
@@ -6048,7 +6252,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6048
6252
|
if (name === 'uNormalMap') {
|
|
6049
6253
|
const map = this.normalMap;
|
|
6050
6254
|
return map === null
|
|
6051
|
-
? { view:
|
|
6255
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
6052
6256
|
: { view: map.view, sampler: map.sampler };
|
|
6053
6257
|
}
|
|
6054
6258
|
/* The caller's ORM map, or the stand-in, for the reason `uNormalMap` gives just above. */
|
|
@@ -6056,24 +6260,25 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6056
6260
|
if (name === 'uEmissiveMap') {
|
|
6057
6261
|
const map = this.emissiveMap;
|
|
6058
6262
|
return map === null
|
|
6059
|
-
? { view:
|
|
6263
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
6060
6264
|
: { view: map.view, sampler: map.sampler };
|
|
6061
6265
|
}
|
|
6062
6266
|
if (name === 'uOrmMap') {
|
|
6063
6267
|
const map = this.ormMap;
|
|
6064
6268
|
return map === null
|
|
6065
|
-
? { view:
|
|
6269
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
6066
6270
|
: { view: map.view, sampler: map.sampler };
|
|
6067
6271
|
}
|
|
6068
6272
|
if (name === 'uAlbedo') {
|
|
6069
6273
|
const albedo = this.albedo;
|
|
6070
6274
|
return albedo === null
|
|
6071
|
-
? { view:
|
|
6275
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
6072
6276
|
: { view: albedo.view, sampler: albedo.sampler };
|
|
6073
6277
|
}
|
|
6074
|
-
/* The
|
|
6075
|
-
|
|
6076
|
-
|
|
6278
|
+
/* The albedo's effects table, or a one-texel stand-in the shader's size test reads as none.
|
|
6279
|
+
The albedo is the group's key, so its table needs no key of its own. */
|
|
6280
|
+
if (name === 'uSurfaceEffects') {
|
|
6281
|
+
return { view: this.albedo?.effectsView ?? blankView, sampler: blankSampler };
|
|
6077
6282
|
}
|
|
6078
6283
|
/*
|
|
6079
6284
|
* The room, once a bake has filled it. Unbaked reads as the blank cube: nothing to mirror.
|
|
@@ -6106,18 +6311,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6106
6311
|
* this one is a curve sampled between two angles rather than data addressed by index, so
|
|
6107
6312
|
* interpolation across a row is the point. `postSampler` is linear and clamped.
|
|
6108
6313
|
*/
|
|
6109
|
-
if (name === '
|
|
6110
|
-
return { view: this.
|
|
6111
|
-
}
|
|
6112
|
-
/*
|
|
6113
|
-
* The cookie atlas, or the single white texel a scene with no cookie falls back to.
|
|
6114
|
-
*
|
|
6115
|
-
* A filtering sampler, like the photometric atlas and unlike the froxel table: a cookie is
|
|
6116
|
-
* an image and its whole point is that it is soft at the edges. The shader's own half-texel
|
|
6117
|
-
* inset is what keeps one tile out of the next; a clamp cannot, the seam being interior.
|
|
6118
|
-
*/
|
|
6119
|
-
if (name === 'uCookieAtlas') {
|
|
6120
|
-
return { view: this.cookieView ?? blankView, sampler: iesSampler };
|
|
6314
|
+
if (name === 'uFixtureAtlas') {
|
|
6315
|
+
return { view: this.fixtureAtlasView(), sampler: iesSampler };
|
|
6121
6316
|
}
|
|
6122
6317
|
/*
|
|
6123
6318
|
* DriftLight's brick index, read by `texelFetch` alone, so it shares the shadow group's
|
|
@@ -6143,7 +6338,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6143
6338
|
* texels by construction, an offset that snapped to one being no offset at all.
|
|
6144
6339
|
*/
|
|
6145
6340
|
if (name === 'uRefractScene') {
|
|
6146
|
-
|
|
6341
|
+
this.refractSampler ??= device.createSampler({
|
|
6342
|
+
label: 'refract.sampler',
|
|
6343
|
+
magFilter: 'linear',
|
|
6344
|
+
minFilter: 'linear',
|
|
6345
|
+
mipmapFilter: 'linear',
|
|
6346
|
+
addressModeU: 'clamp-to-edge',
|
|
6347
|
+
addressModeV: 'clamp-to-edge',
|
|
6348
|
+
});
|
|
6349
|
+
return { view: this.refractSnapshotView ?? blankView, sampler: this.refractSampler };
|
|
6147
6350
|
}
|
|
6148
6351
|
/*
|
|
6149
6352
|
* Every point light's shadow, in one binding.
|
|
@@ -6369,9 +6572,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6369
6572
|
*/
|
|
6370
6573
|
this.mediumLayout = createPostStageLayout(device, MEDIUM_UNIFORMS, MEDIUM_FRAG_SIZE, [
|
|
6371
6574
|
{ binding: MEDIUM_TEXTURES.uDepth, filterable: false },
|
|
6372
|
-
{ binding: MEDIUM_TEXTURES.
|
|
6373
|
-
{ binding: MEDIUM_TEXTURES.uPeeledShadowMap, filterable: false },
|
|
6374
|
-
{ binding: MEDIUM_TEXTURES.uDynamicShadowMap, filterable: false },
|
|
6575
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows, filterable: false, dimension: '2d-array' },
|
|
6375
6576
|
]);
|
|
6376
6577
|
this.mediumUniforms = device.createBuffer({
|
|
6377
6578
|
label: 'post.mediumUniforms',
|
|
@@ -6603,7 +6804,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6603
6804
|
this.scatterDepthDraws.buffer,
|
|
6604
6805
|
{ binding: DEPTH_FRAG_BINDING, buffer: depthPassUniforms, size: DEPTH_FRAG_SIZE },
|
|
6605
6806
|
DEPTH_PREVIOUS_BINDING,
|
|
6606
|
-
|
|
6807
|
+
blankArrayView,
|
|
6607
6808
|
blankSampler,
|
|
6608
6809
|
);
|
|
6609
6810
|
this.shadowBindGroup = createDepthBindGroup(
|
|
@@ -6611,10 +6812,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6611
6812
|
this.shadowLayout,
|
|
6612
6813
|
this.shadowDraws.buffer,
|
|
6613
6814
|
depthPassUniforms,
|
|
6614
|
-
|
|
6815
|
+
blankArrayView,
|
|
6615
6816
|
blankSampler,
|
|
6616
6817
|
);
|
|
6617
|
-
this.shadowBlankView =
|
|
6818
|
+
this.shadowBlankView = blankArrayView;
|
|
6618
6819
|
this.shadowBlankSampler = blankSampler;
|
|
6619
6820
|
this.shadowPassUniforms = depthPassUniforms;
|
|
6620
6821
|
|
|
@@ -6652,7 +6853,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6652
6853
|
this.shadowLayout,
|
|
6653
6854
|
this.shadowDraws.buffer,
|
|
6654
6855
|
peelPassUniforms,
|
|
6655
|
-
this.
|
|
6856
|
+
this.sunStaticArrayView,
|
|
6656
6857
|
shadowSampler,
|
|
6657
6858
|
);
|
|
6658
6859
|
this.scatterDepthPeelBindGroup =
|
|
@@ -6664,7 +6865,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6664
6865
|
this.scatterDepthDraws.buffer,
|
|
6665
6866
|
{ binding: DEPTH_FRAG_BINDING, buffer: peelPassUniforms, size: DEPTH_FRAG_SIZE },
|
|
6666
6867
|
DEPTH_PREVIOUS_BINDING,
|
|
6667
|
-
this.
|
|
6868
|
+
this.sunStaticArrayView,
|
|
6668
6869
|
shadowSampler,
|
|
6669
6870
|
);
|
|
6670
6871
|
|
|
@@ -6964,6 +7165,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6964
7165
|
if (!attribute.optional) continue;
|
|
6965
7166
|
present[String(attribute.name)] = key.includes(`:${String(attribute.name)}`);
|
|
6966
7167
|
}
|
|
7168
|
+
/* Not a layout entry of its own — it widens `uvs` — so read from the key beside them. */
|
|
7169
|
+
present['layers'] = key.includes(':layers');
|
|
6967
7170
|
return present;
|
|
6968
7171
|
}
|
|
6969
7172
|
|
|
@@ -6976,6 +7179,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6976
7179
|
* uncaught exceptions a second in somebody's error reporting.
|
|
6977
7180
|
*/
|
|
6978
7181
|
beginFrame(clearColor: Vec3): void {
|
|
7182
|
+
this.cullFrame += 1;
|
|
6979
7183
|
/* Whatever an update replaced during the last frame; nothing recorded can read it now. */
|
|
6980
7184
|
for (const texture of this.retiredTextures) texture.destroy();
|
|
6981
7185
|
this.retiredTextures.length = 0;
|
|
@@ -7070,7 +7274,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7070
7274
|
this.perFrame.reset();
|
|
7071
7275
|
this.materials.dirty();
|
|
7072
7276
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
7073
|
-
this.
|
|
7277
|
+
this.motionPass?.reset();
|
|
7278
|
+
this.latePass?.reset();
|
|
7279
|
+
this.moverFrame += 1;
|
|
7280
|
+
this.inFrame = true;
|
|
7074
7281
|
/*
|
|
7075
7282
|
* The canvas rather than the swap chain's texture, and the difference is that reading this
|
|
7076
7283
|
* does not *acquire* anything. See `swapView`.
|
|
@@ -7235,6 +7442,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7235
7442
|
present[String(attribute.name)] = supplied;
|
|
7236
7443
|
key += supplied ? `:${String(attribute.name)}` : '';
|
|
7237
7444
|
}
|
|
7445
|
+
/*
|
|
7446
|
+
* Texture-array layers widen the UV field to three floats (`LAYERED_UVS`), which is a different
|
|
7447
|
+
* vertex layout, so the key has to say so or a layered and a plain mesh would share a pipeline
|
|
7448
|
+
* and one of them would read every field after the UVs from the wrong offset.
|
|
7449
|
+
*/
|
|
7450
|
+
if (data.layers !== undefined) {
|
|
7451
|
+
present['layers'] = true;
|
|
7452
|
+
key += ':layers';
|
|
7453
|
+
}
|
|
7238
7454
|
const fullKey = `${this.variant}|${key}`;
|
|
7239
7455
|
/*
|
|
7240
7456
|
* **For every target this mesh can land on, which is two when the overlay is its own.**
|
|
@@ -7391,7 +7607,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7391
7607
|
'what is kept and what a buffer is hinted as.',
|
|
7392
7608
|
);
|
|
7393
7609
|
}
|
|
7394
|
-
|
|
7610
|
+
/*
|
|
7611
|
+
* **The rows before this rewrite are last frame's**, kept only for a reconstruction and only
|
|
7612
|
+
* on the first rewrite of a frame — a second must not replace where the vertices were last
|
|
7613
|
+
* frame with where they were a moment ago. A rewrite between frames is the next frame's. See
|
|
7614
|
+
* `changeFrames.ts`.
|
|
7615
|
+
*/
|
|
7616
|
+
const motion = mesh.motion;
|
|
7617
|
+
let previous: GPUBuffer | null = null;
|
|
7618
|
+
if (motion !== null && this.reconstructionWanted) {
|
|
7619
|
+
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
7620
|
+
if (keepsPrevious(motion.changed, frame)) {
|
|
7621
|
+
motion.previous ??= this.surface.device.createBuffer({
|
|
7622
|
+
label: 'recon.dynamicPrevious',
|
|
7623
|
+
size: mesh.vertexBuffers[0]?.size ?? 0,
|
|
7624
|
+
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
7625
|
+
});
|
|
7626
|
+
previous = motion.previous;
|
|
7627
|
+
}
|
|
7628
|
+
motion.changed = frame;
|
|
7629
|
+
}
|
|
7630
|
+
mesh.update(this.surface.device, positions, normals, previous);
|
|
7395
7631
|
}
|
|
7396
7632
|
|
|
7397
7633
|
/**
|
|
@@ -7440,62 +7676,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7440
7676
|
* Its sequence is its own: `reconJitterPhases` takes eight positions for every output pixel a
|
|
7441
7677
|
* render pixel covers, where the temporal resolve's eight are enough at one to one.
|
|
7442
7678
|
*/
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
*
|
|
7453
|
-
* **The y offset is negated on the way in, and that is not a preference.** This shifts the
|
|
7454
|
-
* *corrected* matrix, whose y `CLIP_CORRECTION` has already negated so that the negation
|
|
7455
|
-
* every generated vertex stage ends with cancels out — §3 row 56. So an offset added here
|
|
7456
|
-
* arrives on screen with its sign reversed, while x, which nothing negates, arrives as
|
|
7457
|
-
* given. The temporal resolve cannot see this because its shader never reads the offset; the
|
|
7458
|
-
* reconstruction reads it at every texel it unprojects, and a sign error there moves every
|
|
7459
|
-
* sample two jitters away from where the depth says it is.
|
|
7460
|
-
*/
|
|
7461
|
-
this.viewProj = jitterProjection(
|
|
7462
|
-
this.jitteredViewProj,
|
|
7463
|
-
this.correctedViewProj,
|
|
7464
|
-
this.reconJitter[0] as number,
|
|
7465
|
-
-(this.reconJitter[1] as number),
|
|
7466
|
-
this.renderWidth,
|
|
7467
|
-
this.renderHeight,
|
|
7468
|
-
) as Float32Array;
|
|
7469
|
-
this.temporalJittering = false;
|
|
7470
|
-
this.temporalHistoryUsable = false;
|
|
7471
|
-
}
|
|
7472
|
-
this.temporalJittering =
|
|
7473
|
-
!this.reconstructing &&
|
|
7474
|
-
this.quality.temporalAa &&
|
|
7475
|
-
this.quality.screenEffects &&
|
|
7476
|
-
!this.reflectionPassActive;
|
|
7477
|
-
if (this.temporalJittering) {
|
|
7478
|
-
/* The scene's size, not the drawing buffer's: the history is a picture of the render. */
|
|
7479
|
-
this.temporalHistoryUsable = this.temporalHistory.openFrame(
|
|
7480
|
-
this.renderWidth,
|
|
7481
|
-
this.renderHeight,
|
|
7482
|
-
);
|
|
7483
|
-
const [jx, jy] = jitterOffset(this.temporalHistory.frameIndex);
|
|
7484
|
-
this.temporalJitterX = jx;
|
|
7485
|
-
this.temporalJitterY = jy;
|
|
7486
|
-
/* A jitter is half a *render* texel, which is what the sequence is spread over. */
|
|
7487
|
-
this.viewProj = jitterProjection(
|
|
7488
|
-
this.jitteredViewProj,
|
|
7489
|
-
this.correctedViewProj,
|
|
7490
|
-
jx,
|
|
7491
|
-
jy,
|
|
7492
|
-
this.renderWidth,
|
|
7493
|
-
this.renderHeight,
|
|
7494
|
-
) as Float32Array;
|
|
7495
|
-
} else if (!this.reconstructing) {
|
|
7496
|
-
this.temporalHistoryUsable = false;
|
|
7497
|
-
this.viewProj = this.correctedViewProj;
|
|
7498
|
-
}
|
|
7679
|
+
/*
|
|
7680
|
+
* **After the present there is no jitter to add and no temporal frame to open.** A mesh pass
|
|
7681
|
+
* bound then is the interface's, drawn over a picture already resolved: jittered, a map and every
|
|
7682
|
+
* edge of it moved by a fraction of a pixel each frame with nothing to resolve it; and opening a
|
|
7683
|
+
* second temporal frame stepped the sequence twice a frame, so the world saw every other
|
|
7684
|
+
* position and the anti-flicker's period never started.
|
|
7685
|
+
*/
|
|
7686
|
+
if (this.framePresented) this.viewProj = this.correctedViewProj;
|
|
7687
|
+
else this.jitterFrame();
|
|
7499
7688
|
/*
|
|
7500
7689
|
* The frustum, once a frame, and from the **uncorrected** matrix.
|
|
7501
7690
|
*
|
|
@@ -7595,8 +7784,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7595
7784
|
* frame either way, because an unwritten one is four zeroes and `dot(p, 0) < 0` is false,
|
|
7596
7785
|
* so the term would silently pass everything the first time somebody enabled it.
|
|
7597
7786
|
*/
|
|
7598
|
-
|
|
7599
|
-
|
|
7787
|
+
/* The mirror's plane in a reflection pass and zero, which clips nothing, in every other: the lit
|
|
7788
|
+
stage has no flag beside the plane. See `uClipPlane` in `flat/preamble.ts`. */
|
|
7789
|
+
f.set(
|
|
7790
|
+
this.reflectionPassActive ? (this.reflection?.clipPlane ?? NO_CLIP_PLANE) : NO_CLIP_PLANE,
|
|
7791
|
+
at('uClipPlane'),
|
|
7792
|
+
);
|
|
7793
|
+
resolveSurfaceScene(env, this.surfaceScene);
|
|
7794
|
+
f.set(this.surfaceScene, at('uSurfaceScene'));
|
|
7600
7795
|
|
|
7601
7796
|
/*
|
|
7602
7797
|
* The material and grading terms, matching `renderer.ts` value for value.
|
|
@@ -7617,6 +7812,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7617
7812
|
*/
|
|
7618
7813
|
f[at('uGrain')] = 1;
|
|
7619
7814
|
f[at('uReflectivity')] = 0;
|
|
7815
|
+
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
7816
|
+
f[at('uWriteMode')] = 0;
|
|
7817
|
+
f[at('uWriteMode') + 1] = 0;
|
|
7620
7818
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
7621
7819
|
f[at('uEnvironmentGain')] = 1;
|
|
7622
7820
|
/*
|
|
@@ -8050,7 +8248,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8050
8248
|
depthLayer = 0,
|
|
8051
8249
|
tint: Vec3 | null = null,
|
|
8052
8250
|
/** Where it was last frame. See `RendererApi.drawMesh`, and `runMotion` for what reads it. */
|
|
8053
|
-
previousModel: ArrayLike<number> | null = null,
|
|
8251
|
+
previousModel: ArrayLike<number> | Mover | null = null,
|
|
8054
8252
|
): void {
|
|
8055
8253
|
/*
|
|
8056
8254
|
* Skipped where the flag asks and the bounds say so.
|
|
@@ -8075,7 +8273,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8075
8273
|
* depth, so a motion pass drawing it would either be depth-rejected everywhere — wasted — or,
|
|
8076
8274
|
* where it happened to pass, write motion for a surface no pixel of the frame belongs to.
|
|
8077
8275
|
*/
|
|
8078
|
-
if (
|
|
8276
|
+
if (this.tracksMotionNow()) this.recordDrawMotion(mesh, model, previousModel);
|
|
8079
8277
|
this.submitMesh(mesh, model, tint, 1, false, { depthLayer });
|
|
8080
8278
|
}
|
|
8081
8279
|
|
|
@@ -8176,7 +8374,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8176
8374
|
* **Recorded rather than drawn**, when the effect is on and this is not already the replay.
|
|
8177
8375
|
* Everything is copied — see `TranslucentQueue`, and the scratch matrix that made it necessary.
|
|
8178
8376
|
*/
|
|
8179
|
-
if (this.oitActive && !this.oitReplaying) {
|
|
8377
|
+
if (this.oitActive && !this.oitReplaying && options.additive !== true) {
|
|
8180
8378
|
this.translucentQueue.record(mesh, model, opacity, options);
|
|
8181
8379
|
return;
|
|
8182
8380
|
}
|
|
@@ -8238,7 +8436,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8238
8436
|
return;
|
|
8239
8437
|
}
|
|
8240
8438
|
|
|
8241
|
-
|
|
8439
|
+
/* A blended draw landing after the upscale is drawn into an unjittered picture. */
|
|
8440
|
+
/* Unless it moves with what it lies on, which the resolve reconstructs it with. See
|
|
8441
|
+
`TranslucentMeshOptions.reconstructed`. */
|
|
8442
|
+
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
8443
|
+
const late = lands || (blend && this.oitReplaying && this.reconstructing);
|
|
8444
|
+
this.perDraw.writeFloats(
|
|
8445
|
+
slot,
|
|
8446
|
+
FLAT_VERT_FIELDS.uViewProj.offset,
|
|
8447
|
+
late ? this.correctedViewProj : this.viewProj,
|
|
8448
|
+
);
|
|
8242
8449
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
8243
8450
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
8244
8451
|
/* The frame's wind, for a mesh carrying a sway lane. Written on this path and not the
|
|
@@ -8272,17 +8479,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8272
8479
|
this.perFrameFloats[this.materialField('uOpacity')] = opacity;
|
|
8273
8480
|
/* Material state exactly like `uOpacity`, dirtied for the accumulation pass and restored
|
|
8274
8481
|
below — the revealage pass draws with it at zero and its colour multiplied away. */
|
|
8275
|
-
this.perFrameFloats[this.materialField('
|
|
8482
|
+
this.perFrameFloats[this.materialField('uWriteMode')] = this.oitMode === 'accum' ? 1 : 0;
|
|
8276
8483
|
}
|
|
8277
8484
|
/* Same shape as `dimmed`: a slot is only worth taking when either differs from the default
|
|
8278
8485
|
`bindMeshPass` already wrote, and both are put back together once the draw is submitted. */
|
|
8279
8486
|
const lit = options.lit ?? true;
|
|
8280
|
-
|
|
8487
|
+
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
8488
|
+
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
8281
8489
|
const toneMapped = options.toneMapped ?? true;
|
|
8282
|
-
const unlitOrUnfogged = !lit ||
|
|
8490
|
+
const unlitOrUnfogged = !lit || fog !== FOG_RECEDE;
|
|
8283
8491
|
if (unlitOrUnfogged) {
|
|
8284
8492
|
if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
|
|
8285
|
-
|
|
8493
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = fog;
|
|
8286
8494
|
}
|
|
8287
8495
|
/* `1` is sRGB alone: the conversion without the curve. `Math.min` rather than a literal, so
|
|
8288
8496
|
a renderer asked for `none` stays at none. See `TranslucentMeshOptions.toneMapped`. */
|
|
@@ -8298,17 +8506,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8298
8506
|
* shades as an ordinary translucent one rather than sampling a texture holding the frame
|
|
8299
8507
|
* before last. That is the defined state the two-backends rule asks for.
|
|
8300
8508
|
*/
|
|
8301
|
-
const
|
|
8302
|
-
const refracting = refraction > 0 && this.takeRefractSnapshot();
|
|
8303
|
-
if (refracting) {
|
|
8304
|
-
this.perFrameFloats[this.materialField('uRefractStrength')] = refraction;
|
|
8305
|
-
const refractTint = options.refractTint;
|
|
8306
|
-
const tintAt = this.materialField('uRefractTint');
|
|
8307
|
-
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
8308
|
-
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
8309
|
-
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
8310
|
-
this.perFrameFloats[this.materialField('uRefractThickness')] = options.thicknessM ?? 0;
|
|
8311
|
-
}
|
|
8509
|
+
const refracting = this.bindSeeThrough(options);
|
|
8312
8510
|
/* Taken for itself when any of those differs from the pass, and put back below: the rule both
|
|
8313
8511
|
backends count material changes by. See `materialChanges.ts`. */
|
|
8314
8512
|
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
@@ -8337,8 +8535,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8337
8535
|
*/
|
|
8338
8536
|
const depthWrite = options.depthWrite ?? true;
|
|
8339
8537
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
8538
|
+
/* Added rather than blended over: its own blend state, so its own pipeline. */
|
|
8539
|
+
const adds = blend && options.additive === true;
|
|
8340
8540
|
const key =
|
|
8341
|
-
`${keyed}${blend ? '|blend' : ''}` +
|
|
8541
|
+
`${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8342
8542
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8343
8543
|
/* The two order-independent buffers are two formats and two blend states, so a draw into
|
|
8344
8544
|
one must never share a cached pipeline with the same mesh drawn into the other. */
|
|
@@ -8368,7 +8568,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8368
8568
|
* pays the compile it was trying to avoid. That is a slow frame; the alternative is a
|
|
8369
8569
|
* thrown error in the middle of one.
|
|
8370
8570
|
*/
|
|
8371
|
-
const pipelines = this.targetPipelines();
|
|
8571
|
+
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
8372
8572
|
let pipeline = pipelines.peek(key);
|
|
8373
8573
|
if (pipeline === undefined) {
|
|
8374
8574
|
const present = this.meshPresent.get(base);
|
|
@@ -8382,7 +8582,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8382
8582
|
this.variant,
|
|
8383
8583
|
key,
|
|
8384
8584
|
present,
|
|
8385
|
-
blend,
|
|
8585
|
+
adds ? 'additive' : blend,
|
|
8386
8586
|
skinned,
|
|
8387
8587
|
morphed,
|
|
8388
8588
|
depthWrite,
|
|
@@ -8416,7 +8616,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8416
8616
|
* the uniform rings are the state arena this design would otherwise have had to build —
|
|
8417
8617
|
* so the command carries them as its two dynamic offsets.
|
|
8418
8618
|
*/
|
|
8419
|
-
const command =
|
|
8619
|
+
const command = blend
|
|
8620
|
+
? this.recordBlended(0, lands)
|
|
8621
|
+
: this.recordDraw(0, this.currentTarget());
|
|
8420
8622
|
if (command !== null) {
|
|
8421
8623
|
command.pipeline = pipeline;
|
|
8422
8624
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
@@ -8447,17 +8649,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8447
8649
|
whether or not this one found a slot to draw with. */
|
|
8448
8650
|
if (dimmed) {
|
|
8449
8651
|
this.perFrameFloats[this.materialField('uOpacity')] = 1;
|
|
8450
|
-
this.perFrameFloats[this.materialField('
|
|
8652
|
+
this.perFrameFloats[this.materialField('uWriteMode')] = 0;
|
|
8451
8653
|
}
|
|
8452
8654
|
if (unlitOrUnfogged) {
|
|
8453
8655
|
if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
8454
|
-
|
|
8455
|
-
}
|
|
8456
|
-
if (refracting) {
|
|
8457
|
-
/* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
|
|
8458
|
-
shared, so a strength left set is worn by everything drawn after it. */
|
|
8459
|
-
this.perFrameFloats[this.materialField('uRefractStrength')] = 0;
|
|
8656
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = FOG_RECEDE;
|
|
8460
8657
|
}
|
|
8658
|
+
/* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
|
|
8659
|
+
shared, so a strength left set is worn by everything drawn after it. */
|
|
8660
|
+
if (refracting) this.releaseSeeThrough();
|
|
8461
8661
|
if (!toneMapped) {
|
|
8462
8662
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
8463
8663
|
}
|
|
@@ -8479,7 +8679,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8479
8679
|
* anyway, because WebGL2 *cannot* and a capability one backend has and the other does not is
|
|
8480
8680
|
* the shape this repository spent a week removing.
|
|
8481
8681
|
*/
|
|
8482
|
-
createInstanced(
|
|
8682
|
+
createInstanced(
|
|
8683
|
+
mesh: GpuMesh,
|
|
8684
|
+
capacity: number,
|
|
8685
|
+
options: InstancedOptions = {},
|
|
8686
|
+
): GpuInstancedBatch {
|
|
8483
8687
|
/*
|
|
8484
8688
|
* **A skinned mesh cannot be instanced, and the reason is the vertex layout rather than the
|
|
8485
8689
|
* shader.** An instanced pipeline reclaims locations 11 and 12 from the joint indices and
|
|
@@ -8509,34 +8713,53 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8509
8713
|
'instanced pipeline.',
|
|
8510
8714
|
);
|
|
8511
8715
|
}
|
|
8512
|
-
if (this.instanced.has(mesh)) {
|
|
8513
|
-
throw new Error(
|
|
8514
|
-
'WebGPU: this mesh already has an instanced batch — a mesh may have one, because the ' +
|
|
8515
|
-
"other backend binds the attributes to the mesh's own vertex array.",
|
|
8516
|
-
);
|
|
8517
|
-
}
|
|
8518
8716
|
const batch = new GpuInstancedBatch(
|
|
8519
8717
|
this.surface.device,
|
|
8520
8718
|
mesh,
|
|
8521
8719
|
capacity,
|
|
8522
8720
|
`flat.instances:${(mesh as GpuMesh & { key?: string }).key ?? 'mesh'}`,
|
|
8721
|
+
options.cull ?? false,
|
|
8523
8722
|
);
|
|
8524
|
-
this.instanced.set(mesh, batch);
|
|
8525
8723
|
return batch;
|
|
8526
8724
|
}
|
|
8527
8725
|
|
|
8528
|
-
/**
|
|
8529
|
-
private
|
|
8726
|
+
/** The device instance culls, built on the first culling draw. See `InstanceCullPass`. */
|
|
8727
|
+
private instanceCulls: InstanceCullPass | null = null;
|
|
8728
|
+
/** Counts frames, so a batch knows when its cull slots were last taken. */
|
|
8729
|
+
private cullFrame = 0;
|
|
8530
8730
|
|
|
8531
8731
|
/** Push placement and colour. Only the live prefix; see `GpuInstancedBatch.upload`. */
|
|
8532
8732
|
uploadInstanced(batch: GpuInstancedBatch, data: MeshInstances): void {
|
|
8533
8733
|
if (this.surface.lost) return;
|
|
8534
|
-
|
|
8734
|
+
/*
|
|
8735
|
+
* **The placement before this upload is last frame's**, kept only for a reconstruction and only
|
|
8736
|
+
* on a frame's first upload, for the reason `updateMesh` gives. See `changeFrames.ts`.
|
|
8737
|
+
*/
|
|
8738
|
+
let previous: GPUBuffer | null = null;
|
|
8739
|
+
if (this.reconstructionWanted) {
|
|
8740
|
+
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
8741
|
+
if (keepsPrevious(batch.changed, frame)) {
|
|
8742
|
+
batch.previousBuffer ??= this.surface.device.createBuffer({
|
|
8743
|
+
label: 'recon.instancesPrevious',
|
|
8744
|
+
size: batch.capacity * INSTANCE_STRIDE,
|
|
8745
|
+
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
8746
|
+
});
|
|
8747
|
+
previous = batch.previousBuffer;
|
|
8748
|
+
}
|
|
8749
|
+
batch.changed = frame;
|
|
8750
|
+
}
|
|
8751
|
+
batch.upload(this.surface.device.queue, data, previous);
|
|
8535
8752
|
}
|
|
8536
8753
|
|
|
8537
8754
|
/** Draw every live instance, opaque. */
|
|
8538
8755
|
drawInstanced(batch: GpuInstancedBatch, data: MeshInstances): void {
|
|
8539
8756
|
this.submitInstanced(batch, data, 1, false, {});
|
|
8757
|
+
/* A batch that moved this frame pairs each slot with last frame's; the batch is the identity. */
|
|
8758
|
+
const previous = batch.previousBuffer;
|
|
8759
|
+
if (previous !== null && batch.changed === this.moverFrame && this.tracksMotionNow()) {
|
|
8760
|
+
const paired = pairedInstances(batch.previousCount, Math.min(data.count, batch.capacity));
|
|
8761
|
+
if (paired > 0) this.motionPass?.recordInstanced(batch.mesh, batch.buffer, previous, paired);
|
|
8762
|
+
}
|
|
8540
8763
|
}
|
|
8541
8764
|
|
|
8542
8765
|
/** Draw every live instance, blended. See `drawTranslucentMesh` for what the options mean. */
|
|
@@ -8551,10 +8774,48 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8551
8774
|
|
|
8552
8775
|
/** Release the placement. The mesh is the caller's and is not released. */
|
|
8553
8776
|
disposeInstanced(batch: GpuInstancedBatch): void {
|
|
8554
|
-
this.instanced.delete(batch.mesh);
|
|
8555
8777
|
batch.dispose();
|
|
8556
8778
|
}
|
|
8557
8779
|
|
|
8780
|
+
/**
|
|
8781
|
+
* Write what a translucent draw shows through it into the material scratch: refraction's bend and
|
|
8782
|
+
* absorption, and glass. Returns whether the frame's copy is there to read, and so whether
|
|
8783
|
+
* `releaseSeeThrough` owes the scratch back.
|
|
8784
|
+
*
|
|
8785
|
+
* **A glass draw takes the copy even when it does not bend**, since a pane shows what is behind it
|
|
8786
|
+
* either way. A copy that cannot be taken — a mirror, a probe bake — leaves both off, so the pane
|
|
8787
|
+
* shades as ordinary translucency rather than sampling a stand-in. See `glass.ts`.
|
|
8788
|
+
*/
|
|
8789
|
+
private bindSeeThrough(options: TranslucentMeshOptions): boolean {
|
|
8790
|
+
const refraction = options.refraction ?? 0;
|
|
8791
|
+
const glassy = resolveGlass(options.glass, this.glassScratch);
|
|
8792
|
+
if ((refraction <= 0 && !glassy) || !this.takeRefractSnapshot()) return false;
|
|
8793
|
+
const glass = this.glassScratch;
|
|
8794
|
+
/* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
|
|
8795
|
+
const seeAt = this.materialField('uSeeThrough');
|
|
8796
|
+
this.perFrameFloats[seeAt] = Math.max(refraction, 0);
|
|
8797
|
+
this.perFrameFloats[seeAt + 1] = options.thicknessM ?? 0;
|
|
8798
|
+
this.perFrameFloats[seeAt + 2] = glass.transmission;
|
|
8799
|
+
this.perFrameFloats[seeAt + 3] = glass.frost;
|
|
8800
|
+
const refractTint = options.refractTint;
|
|
8801
|
+
const tintAt = this.materialField('uRefractTint');
|
|
8802
|
+
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
8803
|
+
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
8804
|
+
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
8805
|
+
const glassTintAt = this.materialField('uGlassTint');
|
|
8806
|
+
this.perFrameFloats[glassTintAt] = glass.tint[0];
|
|
8807
|
+
this.perFrameFloats[glassTintAt + 1] = glass.tint[1];
|
|
8808
|
+
this.perFrameFloats[glassTintAt + 2] = glass.tint[2];
|
|
8809
|
+
return true;
|
|
8810
|
+
}
|
|
8811
|
+
|
|
8812
|
+
/** Put the scratch back after `bindSeeThrough`: refraction and glass off. */
|
|
8813
|
+
private releaseSeeThrough(): void {
|
|
8814
|
+
const seeAt = this.materialField('uSeeThrough');
|
|
8815
|
+
this.perFrameFloats[seeAt] = 0;
|
|
8816
|
+
this.perFrameFloats[seeAt + 2] = 0;
|
|
8817
|
+
}
|
|
8818
|
+
|
|
8558
8819
|
/**
|
|
8559
8820
|
* One draw, one material slot, one uniform slot, however many instances.
|
|
8560
8821
|
*
|
|
@@ -8567,6 +8828,37 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8567
8828
|
* **What it gives up** against `drawMesh` is skinning and morphing, refused where the variant
|
|
8568
8829
|
* is built, and a per-instance opacity: `opacity` here dims the whole batch.
|
|
8569
8830
|
*/
|
|
8831
|
+
/**
|
|
8832
|
+
* The next of a batch's cull slots this frame, with its cull queued against the current view; null
|
|
8833
|
+
* when the batch has drawn from `MAX_CULL_SLOTS` views already or the frame's job ring is full,
|
|
8834
|
+
* and the draw then takes every instance.
|
|
8835
|
+
*/
|
|
8836
|
+
private takeCullSlot(batch: GpuInstancedBatch, count: number): CullSlot | null {
|
|
8837
|
+
if (batch.slotFrame !== this.cullFrame) {
|
|
8838
|
+
batch.slotFrame = this.cullFrame;
|
|
8839
|
+
batch.slotsTaken = 0;
|
|
8840
|
+
}
|
|
8841
|
+
if (batch.slotsTaken >= MAX_CULL_SLOTS) return null;
|
|
8842
|
+
this.instanceCulls ??= new InstanceCullPass(this.surface.device);
|
|
8843
|
+
let slot = batch.slots[batch.slotsTaken];
|
|
8844
|
+
if (slot === undefined) {
|
|
8845
|
+
slot = this.instanceCulls.createSlot(batch.buffer, batch.capacity, 'flat.instances');
|
|
8846
|
+
batch.slots.push(slot);
|
|
8847
|
+
}
|
|
8848
|
+
const bounds = batch.mesh.bounds;
|
|
8849
|
+
const queued = this.instanceCulls.queue(
|
|
8850
|
+
slot,
|
|
8851
|
+
this.frustum,
|
|
8852
|
+
bounds.centre,
|
|
8853
|
+
bounds.radius,
|
|
8854
|
+
count,
|
|
8855
|
+
batch.mesh.indexCount,
|
|
8856
|
+
);
|
|
8857
|
+
if (!queued) return null;
|
|
8858
|
+
batch.slotsTaken += 1;
|
|
8859
|
+
return slot;
|
|
8860
|
+
}
|
|
8861
|
+
|
|
8570
8862
|
private submitInstanced(
|
|
8571
8863
|
batch: GpuInstancedBatch,
|
|
8572
8864
|
data: MeshInstances,
|
|
@@ -8579,6 +8871,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8579
8871
|
if (!mesh.complete) return;
|
|
8580
8872
|
const count = Math.min(data.count, batch.capacity);
|
|
8581
8873
|
if (count === 0) return;
|
|
8874
|
+
/* A culling batch's camera draw: the whole batch against the view and the occluders on the CPU,
|
|
8875
|
+
then instance by instance on the device into an indirect draw. Blended draws keep every
|
|
8876
|
+
instance and their order. See `InstancedOptions.cull`. */
|
|
8877
|
+
const culling = !blend && batch.cull;
|
|
8878
|
+
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion)) return;
|
|
8879
|
+
const cut =
|
|
8880
|
+
culling && cullsInstances(count, mesh.indexCount) ? this.takeCullSlot(batch, count) : null;
|
|
8881
|
+
const instanceBuffer = cut === null ? batch.buffer : cut.instances;
|
|
8582
8882
|
|
|
8583
8883
|
const slot = this.perDraw.allocate();
|
|
8584
8884
|
if (slot === null) {
|
|
@@ -8591,7 +8891,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8591
8891
|
return;
|
|
8592
8892
|
}
|
|
8593
8893
|
|
|
8594
|
-
|
|
8894
|
+
/* Late unless it moves with what it lies on. See `TranslucentMeshOptions.reconstructed`. */
|
|
8895
|
+
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
8896
|
+
this.perDraw.writeFloats(
|
|
8897
|
+
slot,
|
|
8898
|
+
FLAT_VERT_FIELDS.uViewProj.offset,
|
|
8899
|
+
lands ? this.correctedViewProj : this.viewProj,
|
|
8900
|
+
);
|
|
8595
8901
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
8596
8902
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
8597
8903
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
@@ -8601,12 +8907,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8601
8907
|
this.perFrameFloats[this.materialField('uOpacity')] = opacity;
|
|
8602
8908
|
}
|
|
8603
8909
|
const lit = options.lit ?? true;
|
|
8604
|
-
const fog = options.fog ?? true;
|
|
8910
|
+
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
8605
8911
|
const toneMapped = options.toneMapped ?? true;
|
|
8606
|
-
const unlitOrUnfogged = !lit ||
|
|
8912
|
+
const unlitOrUnfogged = !lit || fog !== FOG_RECEDE;
|
|
8607
8913
|
if (unlitOrUnfogged) {
|
|
8608
8914
|
if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
|
|
8609
|
-
|
|
8915
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = fog;
|
|
8610
8916
|
}
|
|
8611
8917
|
if (!toneMapped) {
|
|
8612
8918
|
this.perFrameInts[this.materialField('uOutputTransform')] = Math.min(this.gradeCode(), 1);
|
|
@@ -8614,19 +8920,21 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8614
8920
|
|
|
8615
8921
|
const depthWrite = options.depthWrite ?? true;
|
|
8616
8922
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
8617
|
-
/*
|
|
8618
|
-
const
|
|
8923
|
+
/* A blended batch shows what is behind it — refraction or glass — as a single draw does. */
|
|
8924
|
+
const refracting = blend && this.bindSeeThrough(options);
|
|
8925
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
8619
8926
|
if (own) this.materials.dirty();
|
|
8620
8927
|
const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
|
|
8621
8928
|
/* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
|
|
8622
8929
|
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
8623
8930
|
plain one would hand a draw the wrong layout. */
|
|
8931
|
+
const adds = blend && options.additive === true;
|
|
8624
8932
|
const key =
|
|
8625
|
-
`${base}|inst${blend ? '|blend' : ''}` +
|
|
8933
|
+
`${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8626
8934
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8627
8935
|
`${this.materialDoubleSided ? '|2s' : ''}`;
|
|
8628
8936
|
|
|
8629
|
-
const pipelines = this.targetPipelines();
|
|
8937
|
+
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
8630
8938
|
let pipeline = pipelines.peek(key);
|
|
8631
8939
|
if (pipeline === undefined) {
|
|
8632
8940
|
const present = this.meshPresent.get(base);
|
|
@@ -8640,7 +8948,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8640
8948
|
this.variant,
|
|
8641
8949
|
key,
|
|
8642
8950
|
present,
|
|
8643
|
-
blend,
|
|
8951
|
+
adds ? 'additive' : blend,
|
|
8644
8952
|
false,
|
|
8645
8953
|
false,
|
|
8646
8954
|
depthWrite,
|
|
@@ -8653,7 +8961,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8653
8961
|
|
|
8654
8962
|
const material = this.materialSlotForDraw();
|
|
8655
8963
|
if (material !== null) {
|
|
8656
|
-
const command =
|
|
8964
|
+
const command = blend
|
|
8965
|
+
? this.recordBlended(0, lands)
|
|
8966
|
+
: this.recordDraw(0, this.currentTarget());
|
|
8657
8967
|
if (command !== null) {
|
|
8658
8968
|
command.pipeline = pipeline;
|
|
8659
8969
|
command.bindGroup = this.flatBindGroupFor(false, false, null);
|
|
@@ -8664,12 +8974,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8664
8974
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
8665
8975
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
8666
8976
|
}
|
|
8667
|
-
command.vertexBuffers[index] =
|
|
8977
|
+
command.vertexBuffers[index] = instanceBuffer;
|
|
8668
8978
|
command.vertexCount = mesh.vertexBuffers.length + 1;
|
|
8669
8979
|
command.indexBuffer = mesh.indexBuffer;
|
|
8670
8980
|
command.indexed = true;
|
|
8671
8981
|
command.count = mesh.indexCount;
|
|
8672
8982
|
command.instances = count;
|
|
8983
|
+
command.indirect = cut === null ? null : cut.args;
|
|
8673
8984
|
} else {
|
|
8674
8985
|
const pass = this.openPass();
|
|
8675
8986
|
if (pass === null) return;
|
|
@@ -8679,9 +8990,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8679
8990
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
8680
8991
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
|
|
8681
8992
|
}
|
|
8682
|
-
pass.setVertexBuffer(index,
|
|
8993
|
+
pass.setVertexBuffer(index, instanceBuffer);
|
|
8683
8994
|
pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
|
|
8684
|
-
pass.drawIndexed(mesh.indexCount, count);
|
|
8995
|
+
if (cut === null) pass.drawIndexed(mesh.indexCount, count);
|
|
8996
|
+
else pass.drawIndexedIndirect(cut.args, 0);
|
|
8685
8997
|
}
|
|
8686
8998
|
}
|
|
8687
8999
|
/* Back to the defaults, unconditionally and for the reason `drawMesh` gives at length: this
|
|
@@ -8692,11 +9004,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8692
9004
|
}
|
|
8693
9005
|
if (unlitOrUnfogged) {
|
|
8694
9006
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
8695
|
-
this.perFrameInts[this.materialField('uFogEnabled')] =
|
|
9007
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = FOG_RECEDE;
|
|
8696
9008
|
}
|
|
8697
9009
|
if (!toneMapped) {
|
|
8698
9010
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
8699
9011
|
}
|
|
9012
|
+
if (refracting) this.releaseSeeThrough();
|
|
8700
9013
|
if (own) this.materials.dirty();
|
|
8701
9014
|
}
|
|
8702
9015
|
|
|
@@ -8890,6 +9203,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8890
9203
|
this.lightViewProj.set(lightViewProj as Float32Array);
|
|
8891
9204
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
8892
9205
|
this.shadowLayerIsPeel = peel;
|
|
9206
|
+
this.shadowLayerIsDynamic = dynamic;
|
|
9207
|
+
this.glassCasters.clear();
|
|
8893
9208
|
/*
|
|
8894
9209
|
* A frame's peel is only as good as the pass that fills it, and the static layer is the one
|
|
8895
9210
|
* that opens first.
|
|
@@ -8997,48 +9312,387 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8997
9312
|
}
|
|
8998
9313
|
|
|
8999
9314
|
/** Close the shadow pass and submit it, so the frame can sample what it wrote. */
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
this.
|
|
9011
|
-
this.
|
|
9315
|
+
/**
|
|
9316
|
+
* Add the sun's two glass layers, once, carrying every layer it holds across on `encoder`.
|
|
9317
|
+
*
|
|
9318
|
+
* **Copied rather than re-baked**, for the reason `SunShadowArray.growForGlass` gives: the static
|
|
9319
|
+
* map is the consumer's to bake and may be baked once, and the glass that grows the texture
|
|
9320
|
+
* arrives in the very pass that drew it. Every view of the old texture is stale afterwards, so
|
|
9321
|
+
* every group holding one is rebuilt here — the lit pass's, the volumes', the medium's and the
|
|
9322
|
+
* peel's — and the old texture is destroyed only once this encoder is submitted.
|
|
9323
|
+
*/
|
|
9324
|
+
private growSunForGlass(encoder: GPUCommandEncoder): void {
|
|
9325
|
+
if (this.sunGlassViews !== null) return;
|
|
9326
|
+
const device = this.surface.device;
|
|
9327
|
+
const size = this.quality.directionalShadowMapSize;
|
|
9328
|
+
const maps = this.sunLayerCount;
|
|
9329
|
+
/* The glass layers at their fixed places after the three maps: see `SUN_GLASS_STATIC_LAYER`. */
|
|
9330
|
+
const layers = SUN_GLASS_MOVING_LAYER + 1;
|
|
9331
|
+
const old = this.sunShadowTexture;
|
|
9332
|
+
const texture = device.createTexture({
|
|
9333
|
+
label: 'shadow.sun',
|
|
9334
|
+
size: [size, size, layers],
|
|
9335
|
+
format: SHADOW_FORMAT,
|
|
9336
|
+
usage: SUN_USAGE,
|
|
9337
|
+
});
|
|
9338
|
+
for (let layer = 0; layer < maps; layer++) {
|
|
9339
|
+
encoder.copyTextureToTexture(
|
|
9340
|
+
{ texture: old, origin: { x: 0, y: 0, z: layer } },
|
|
9341
|
+
{ texture, origin: { x: 0, y: 0, z: layer } },
|
|
9342
|
+
[size, size, 1],
|
|
9343
|
+
);
|
|
9344
|
+
}
|
|
9345
|
+
this.retiredSunTexture = old;
|
|
9346
|
+
this.sunShadowTexture = texture;
|
|
9347
|
+
this.sunLayerCount = layers;
|
|
9348
|
+
const views = sunViewsOf(texture, maps, layers);
|
|
9349
|
+
this.sunArrayView = views.array;
|
|
9350
|
+
this.sunStaticArrayView = views.staticArray;
|
|
9351
|
+
this.shadowView = views.static;
|
|
9352
|
+
this.dynamicView = views.dynamic;
|
|
9353
|
+
this.peelView = views.peel;
|
|
9354
|
+
this.sunGlassViews = views.glass;
|
|
9355
|
+
/* Both glass layers born at the far plane: nothing is nearer the sun than a pane not drawn. */
|
|
9356
|
+
for (const view of views.glass ?? []) {
|
|
9357
|
+
encoder
|
|
9358
|
+
.beginRenderPass({
|
|
9359
|
+
label: 'shadow.glass.clear',
|
|
9360
|
+
colorAttachments: [],
|
|
9361
|
+
depthStencilAttachment: {
|
|
9362
|
+
view,
|
|
9363
|
+
depthClearValue: 1,
|
|
9364
|
+
depthLoadOp: 'clear',
|
|
9365
|
+
depthStoreOp: 'store',
|
|
9366
|
+
},
|
|
9367
|
+
})
|
|
9368
|
+
.end();
|
|
9369
|
+
}
|
|
9370
|
+
this.rebuildSunGroups();
|
|
9012
9371
|
}
|
|
9013
9372
|
|
|
9014
|
-
|
|
9373
|
+
/** Every group that holds a view of the sun's texture, rebuilt after it has been replaced. */
|
|
9374
|
+
private rebuildSunGroups(): void {
|
|
9375
|
+
const device = this.surface.device;
|
|
9376
|
+
const peelUniforms = this.shadowPeelPassUniforms;
|
|
9377
|
+
const peelSampler = this.shadowPeelSampler;
|
|
9378
|
+
if (peelUniforms !== null && peelSampler !== null) {
|
|
9379
|
+
this.shadowPeelBindGroup = createDepthBindGroup(
|
|
9380
|
+
device,
|
|
9381
|
+
this.shadowLayout,
|
|
9382
|
+
this.shadowDraws.buffer,
|
|
9383
|
+
peelUniforms,
|
|
9384
|
+
this.sunStaticArrayView,
|
|
9385
|
+
peelSampler,
|
|
9386
|
+
);
|
|
9387
|
+
this.scatterDepthPeelBindGroup = createScatterDepthBindGroup(
|
|
9388
|
+
device,
|
|
9389
|
+
this.scatterDepthLayout,
|
|
9390
|
+
this.scatterDepthDraws.buffer,
|
|
9391
|
+
{ binding: DEPTH_FRAG_BINDING, buffer: peelUniforms, size: DEPTH_FRAG_SIZE },
|
|
9392
|
+
DEPTH_PREVIOUS_BINDING,
|
|
9393
|
+
this.sunStaticArrayView,
|
|
9394
|
+
peelSampler,
|
|
9395
|
+
);
|
|
9396
|
+
}
|
|
9397
|
+
this.cutoutPeelGroups.clear();
|
|
9398
|
+
this.rebuildFlatGroupsForNewRings();
|
|
9399
|
+
this.lightVolumeBindGroup = this.buildLightVolumeBindGroup();
|
|
9400
|
+
this.mediumBindGroup = this.buildMediumBindGroup();
|
|
9401
|
+
}
|
|
9402
|
+
|
|
9403
|
+
/** The sun's tint target, made the first time glass casts and born clear on `encoder`. */
|
|
9404
|
+
private ensureSunTint(encoder: GPUCommandEncoder): SunTintTarget {
|
|
9405
|
+
if (this.sunTint !== null) return this.sunTint;
|
|
9406
|
+
const size = this.quality.directionalShadowMapSize;
|
|
9407
|
+
const edge = this.quality.glassShadows === 'half' ? Math.max(1, size >> 1) : size;
|
|
9408
|
+
const device = this.surface.device;
|
|
9409
|
+
const texture = device.createTexture({
|
|
9410
|
+
label: 'shadow.sunTint',
|
|
9411
|
+
size: [edge, edge, 2],
|
|
9412
|
+
format: 'rgba8unorm',
|
|
9413
|
+
mipLevelCount: Math.floor(Math.log2(edge)) + 1,
|
|
9414
|
+
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
9415
|
+
});
|
|
9416
|
+
const layer = (index: number): GPUTextureView =>
|
|
9417
|
+
texture.createView({
|
|
9418
|
+
dimension: '2d',
|
|
9419
|
+
baseArrayLayer: index,
|
|
9420
|
+
arrayLayerCount: 1,
|
|
9421
|
+
baseMipLevel: 0,
|
|
9422
|
+
mipLevelCount: 1,
|
|
9423
|
+
});
|
|
9424
|
+
const mips = (index: number): SnapshotMips =>
|
|
9425
|
+
new SnapshotMips(device, this.pipelines, texture, 'rgba8unorm', index, 'shadow.tint.mips');
|
|
9426
|
+
const target: SunTintTarget = {
|
|
9427
|
+
texture,
|
|
9428
|
+
layers: [layer(0), layer(1)],
|
|
9429
|
+
array: texture.createView({ dimension: '2d-array' }),
|
|
9430
|
+
mips: [mips(0), mips(1)],
|
|
9431
|
+
};
|
|
9432
|
+
for (const view of target.layers) {
|
|
9433
|
+
encoder
|
|
9434
|
+
.beginRenderPass({
|
|
9435
|
+
label: 'shadow.tint.clear',
|
|
9436
|
+
colorAttachments: [{ view, loadOp: 'clear', clearValue: [1, 1, 1, 1], storeOp: 'store' }],
|
|
9437
|
+
})
|
|
9438
|
+
.end();
|
|
9439
|
+
}
|
|
9440
|
+
this.sunTint = target;
|
|
9441
|
+
this.rebuildFlatGroupsForNewRings();
|
|
9442
|
+
return target;
|
|
9443
|
+
}
|
|
9015
9444
|
|
|
9016
9445
|
/**
|
|
9017
|
-
*
|
|
9446
|
+
* Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
|
|
9018
9447
|
*
|
|
9019
|
-
*
|
|
9020
|
-
*
|
|
9021
|
-
* `PointShadowSource`.
|
|
9448
|
+
* After the opaque casters, on the pass's own encoder. A layer that has held glass is redrawn
|
|
9449
|
+
* even when this pass offered none, so glass that has gone takes its colour with it.
|
|
9022
9450
|
*/
|
|
9023
|
-
private
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9451
|
+
private flushSunGlass(encoder: GPUCommandEncoder, which: 'static' | 'dynamic'): void {
|
|
9452
|
+
const list = this.glassCasters;
|
|
9453
|
+
if (this.quality.glassShadows === 'off') return;
|
|
9454
|
+
if (list.count === 0 && this.sunGlassViews === null) return;
|
|
9455
|
+
if (list.count > 0) this.growSunForGlass(encoder);
|
|
9456
|
+
const glassViews = this.sunGlassViews;
|
|
9457
|
+
if (glassViews === null) return;
|
|
9458
|
+
|
|
9459
|
+
/* Where the nearest pane is: the depth pipelines, through the sink every opaque caster took. */
|
|
9460
|
+
this.shadowPass = encoder.beginRenderPass({
|
|
9461
|
+
label: 'shadow.glass',
|
|
9462
|
+
colorAttachments: [],
|
|
9463
|
+
depthStencilAttachment: {
|
|
9464
|
+
view: glassViews[which === 'static' ? 0 : 1],
|
|
9465
|
+
depthClearValue: 1,
|
|
9466
|
+
depthLoadOp: 'clear',
|
|
9467
|
+
depthStoreOp: 'store',
|
|
9468
|
+
},
|
|
9469
|
+
});
|
|
9470
|
+
this.drawingGlassDepth = true;
|
|
9471
|
+
list.replay(this.glassDepthSink);
|
|
9472
|
+
this.drawingGlassDepth = false;
|
|
9473
|
+
this.shadowPass.end();
|
|
9474
|
+
this.shadowPass = null;
|
|
9475
|
+
|
|
9476
|
+
/* What the panes let through, every one on a ray, multiplied. */
|
|
9477
|
+
const target = this.ensureSunTint(encoder);
|
|
9478
|
+
const tint = (this.glassTint ??= new GpuGlassTint(this.surface.device));
|
|
9479
|
+
tint.frag.reset();
|
|
9480
|
+
/* The direction the light travels, off the light matrix's depth row; the sign is immaterial,
|
|
9481
|
+
since the tint takes the cosine absolutely, and w 0 says it is a direction. */
|
|
9482
|
+
const m = this.correctedLightViewProj;
|
|
9483
|
+
const length = Math.hypot(m[2] as number, m[6] as number, m[10] as number) || 1;
|
|
9484
|
+
this.glassLight[0] = (m[2] as number) / length;
|
|
9485
|
+
this.glassLight[1] = (m[6] as number) / length;
|
|
9486
|
+
this.glassLight[2] = (m[10] as number) / length;
|
|
9487
|
+
this.glassLight[3] = 0;
|
|
9488
|
+
this.tintPass = encoder.beginRenderPass({
|
|
9489
|
+
label: 'shadow.tint',
|
|
9490
|
+
colorAttachments: [
|
|
9491
|
+
{
|
|
9492
|
+
view: target.layers[which === 'static' ? 0 : 1],
|
|
9493
|
+
loadOp: 'clear',
|
|
9494
|
+
clearValue: [1, 1, 1, 1],
|
|
9495
|
+
storeOp: 'store',
|
|
9496
|
+
},
|
|
9497
|
+
],
|
|
9498
|
+
});
|
|
9499
|
+
list.replay(this.glassTintSink);
|
|
9500
|
+
this.tintPass.end();
|
|
9501
|
+
this.tintPass = null;
|
|
9502
|
+
tint.frag.flush();
|
|
9503
|
+
/* The chain frost reads, for the layer just drawn. */
|
|
9504
|
+
target.mips[which === 'static' ? 0 : 1].record(encoder);
|
|
9505
|
+
}
|
|
9506
|
+
|
|
9507
|
+
/** The glass list into a glass depth layer: `casterSink` itself, with glass let through. */
|
|
9508
|
+
private readonly glassDepthSink: GlassReplaySink = {
|
|
9509
|
+
mesh: (mesh, model, material) => this.casterSink.mesh(mesh, model, material),
|
|
9510
|
+
instanced: (batch, data, material) => this.casterSink.instanced?.(batch, data, material),
|
|
9511
|
+
skinnedMesh: (mesh, model, palette, material) =>
|
|
9512
|
+
this.casterSink.skinnedMesh(mesh, model, palette, material),
|
|
9513
|
+
};
|
|
9514
|
+
|
|
9515
|
+
/**
|
|
9516
|
+
* The glass list into a tint layer, through the tint pipelines, **culling nothing**: every pane on
|
|
9517
|
+
* a ray counts, whichever way it faces. See the other backend's `glassTintSink`.
|
|
9518
|
+
*/
|
|
9519
|
+
private readonly glassTintSink: GlassReplaySink = {
|
|
9520
|
+
mesh: (mesh, model, material, glass) => {
|
|
9521
|
+
const pass = this.tintPass;
|
|
9522
|
+
const tint = this.glassTint;
|
|
9523
|
+
if (pass === null || tint === null) return;
|
|
9524
|
+
const geometry = mesh as GpuMesh & { key?: string };
|
|
9525
|
+
if (geometry.vertexBuffers === undefined) return;
|
|
9526
|
+
const cutout = cutoutOf(material);
|
|
9527
|
+
const albedo = cutout === null ? null : (cutout.albedo as unknown as GpuSurfaceTexture);
|
|
9528
|
+
const slot = this.shadowDraws.allocate();
|
|
9529
|
+
const pane = tint.frag.allocate();
|
|
9530
|
+
if (slot === null || pane === null) return;
|
|
9531
|
+
this.shadowDraws.writeFloats(
|
|
9532
|
+
slot,
|
|
9533
|
+
DEPTH_VERT_FIELDS.uLightViewProj.offset,
|
|
9534
|
+
this.correctedLightViewProj,
|
|
9535
|
+
);
|
|
9536
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uModel.offset, model as Float32Array);
|
|
9537
|
+
this.writeShadowWind(slot);
|
|
9538
|
+
if (cutout !== null) this.writeCutout(slot, cutout, DEPTH_CUTOUT_VERT_FIELDS);
|
|
9539
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
9540
|
+
pass.setPipeline(
|
|
9541
|
+
tint.pipeline(
|
|
9542
|
+
this.pipelines,
|
|
9543
|
+
albedo === null ? 'rigid' : 'cutout',
|
|
9544
|
+
geometry.key ?? '',
|
|
9545
|
+
this.presentOf(geometry),
|
|
9546
|
+
'none',
|
|
9547
|
+
),
|
|
9548
|
+
);
|
|
9549
|
+
pass.setBindGroup(
|
|
9550
|
+
0,
|
|
9551
|
+
albedo === null
|
|
9552
|
+
? tint.group(this.shadowDraws.buffer)
|
|
9553
|
+
: tint.cutoutGroup(this.shadowDraws.buffer, albedo.view, albedo.sampler),
|
|
9554
|
+
[slot, pane],
|
|
9555
|
+
);
|
|
9556
|
+
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
9557
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
9558
|
+
}
|
|
9559
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
9560
|
+
pass.drawIndexed(geometry.indexCount);
|
|
9561
|
+
},
|
|
9562
|
+
instanced: (batch, data, material, glass) => {
|
|
9563
|
+
const pass = this.tintPass;
|
|
9564
|
+
const tint = this.glassTint;
|
|
9565
|
+
if (pass === null || tint === null) return;
|
|
9566
|
+
const gpuBatch = batch as GpuInstancedBatch;
|
|
9567
|
+
const geometry = gpuBatch.mesh as GpuMesh & { key?: string };
|
|
9568
|
+
if (geometry.vertexBuffers === undefined) return;
|
|
9569
|
+
const count = Math.min(data.count, gpuBatch.capacity);
|
|
9570
|
+
if (count === 0) return;
|
|
9571
|
+
const cutout = cutoutOf(material);
|
|
9572
|
+
const albedo = cutout === null ? null : (cutout.albedo as unknown as GpuSurfaceTexture);
|
|
9573
|
+
const slot = this.shadowDraws.allocate();
|
|
9574
|
+
const pane = tint.frag.allocate();
|
|
9575
|
+
if (slot === null || pane === null) return;
|
|
9576
|
+
this.shadowDraws.writeFloats(
|
|
9577
|
+
slot,
|
|
9578
|
+
DEPTH_VERT_FIELDS.uLightViewProj.offset,
|
|
9579
|
+
this.correctedLightViewProj,
|
|
9580
|
+
);
|
|
9581
|
+
if (cutout !== null) this.writeCutout(slot, cutout, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS);
|
|
9582
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
9583
|
+
pass.setPipeline(
|
|
9584
|
+
tint.pipeline(
|
|
9585
|
+
this.pipelines,
|
|
9586
|
+
albedo === null ? 'instanced' : 'instancedCutout',
|
|
9587
|
+
geometry.key ?? '',
|
|
9588
|
+
this.presentOf(geometry),
|
|
9589
|
+
'none',
|
|
9590
|
+
),
|
|
9591
|
+
);
|
|
9592
|
+
pass.setBindGroup(
|
|
9593
|
+
0,
|
|
9594
|
+
albedo === null
|
|
9595
|
+
? tint.group(this.shadowDraws.buffer)
|
|
9596
|
+
: tint.cutoutGroup(this.shadowDraws.buffer, albedo.view, albedo.sampler),
|
|
9597
|
+
[slot, pane],
|
|
9598
|
+
);
|
|
9599
|
+
let index = 0;
|
|
9600
|
+
for (; index < geometry.vertexBuffers.length; index++) {
|
|
9601
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
9602
|
+
}
|
|
9603
|
+
pass.setVertexBuffer(index, gpuBatch.buffer);
|
|
9604
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
9605
|
+
pass.drawIndexed(geometry.indexCount, count);
|
|
9606
|
+
},
|
|
9607
|
+
skinnedMesh: (mesh, model, palette, _material, glass) => {
|
|
9608
|
+
const pass = this.tintPass;
|
|
9609
|
+
const tint = this.glassTint;
|
|
9610
|
+
if (pass === null || tint === null) return;
|
|
9611
|
+
const geometry = mesh as GpuMesh & { key?: string };
|
|
9612
|
+
if (geometry.vertexBuffers === undefined) return;
|
|
9613
|
+
const paletteSlot = this.skinPalettes.take(this.surface.device, palette);
|
|
9614
|
+
const view = paletteSlot === null ? null : this.skinPalettes.view(paletteSlot);
|
|
9615
|
+
if (view === null) return;
|
|
9616
|
+
const slot = this.shadowDraws.allocate();
|
|
9617
|
+
const pane = tint.frag.allocate();
|
|
9618
|
+
if (slot === null || pane === null) return;
|
|
9619
|
+
this.shadowDraws.writeFloats(
|
|
9620
|
+
slot,
|
|
9621
|
+
DEPTH_VERT_FIELDS.uLightViewProj.offset,
|
|
9622
|
+
this.correctedLightViewProj,
|
|
9623
|
+
);
|
|
9624
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uModel.offset, model as Float32Array);
|
|
9625
|
+
this.writeShadowWind(slot);
|
|
9626
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
9627
|
+
pass.setPipeline(
|
|
9628
|
+
tint.pipeline(
|
|
9629
|
+
this.pipelines,
|
|
9630
|
+
'skinned',
|
|
9631
|
+
geometry.key ?? '',
|
|
9632
|
+
this.presentOf(geometry),
|
|
9633
|
+
'none',
|
|
9634
|
+
),
|
|
9635
|
+
);
|
|
9636
|
+
pass.setBindGroup(0, tint.skinnedGroup(this.shadowDraws.buffer, view), [slot, pane]);
|
|
9637
|
+
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
9638
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
9639
|
+
}
|
|
9640
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
9641
|
+
pass.drawIndexed(geometry.indexCount);
|
|
9642
|
+
},
|
|
9643
|
+
};
|
|
9644
|
+
|
|
9645
|
+
endShadowPass(): void {
|
|
9646
|
+
if (this.surface.lost || this.shadowPass === null || this.shadowEncoder === null) return;
|
|
9647
|
+
this.shadowPass.end();
|
|
9648
|
+
/* The peel keeps no glass: its layer is the second opaque occluder and nothing else. */
|
|
9649
|
+
if (!this.shadowLayerIsPeel) {
|
|
9650
|
+
this.flushSunGlass(this.shadowEncoder, this.shadowLayerIsDynamic ? 'dynamic' : 'static');
|
|
9651
|
+
}
|
|
9652
|
+
this.shadowDraws.flush();
|
|
9653
|
+
this.scatterDepthDraws.flush();
|
|
9654
|
+
this.surface.device.queue.submit([this.shadowEncoder.finish()]);
|
|
9655
|
+
/* A growth's old texture, copied out of by what was just submitted and needed no longer. */
|
|
9656
|
+
this.retiredSunTexture?.destroy();
|
|
9657
|
+
this.retiredSunTexture = null;
|
|
9658
|
+
/* The map now holds a rendered peel rather than whatever it was created with. */
|
|
9659
|
+
if (this.shadowLayerIsPeel) this.peelFilled = true;
|
|
9660
|
+
this.shadowLayerIsPeel = false;
|
|
9661
|
+
this.shadowPass = null;
|
|
9662
|
+
this.shadowEncoder = null;
|
|
9663
|
+
this.gpuTimer.end();
|
|
9664
|
+
}
|
|
9665
|
+
|
|
9666
|
+
/* -- Point shadows ------------------------------------------------------------------ */
|
|
9667
|
+
|
|
9668
|
+
/**
|
|
9669
|
+
* The pool, or null where the profile switched cube shadows off.
|
|
9670
|
+
*
|
|
9671
|
+
* `PointShadowSystem` over `GpuPointShadowMap`: the pooling, the staleness and the crossfade
|
|
9672
|
+
* are the same code the WebGL2 path runs, and only the cubemap is this backend's. See
|
|
9673
|
+
* `PointShadowSource`.
|
|
9674
|
+
*/
|
|
9675
|
+
private readonly pointShadows: PointShadowSystem<GpuPointShadowMap> | null;
|
|
9676
|
+
/** A world's casting rectangles, on layers above the pool in the same array. See `renderer.ts`. */
|
|
9677
|
+
private readonly areaShadows: AreaShadowSet<GpuPointShadowMap> | null;
|
|
9678
|
+
private readonly resolvedAreaShadows = createResolvedAreaShadows(MAX_AREA_LIGHTS);
|
|
9679
|
+
private readonly areaBakeScratch = createBakeScratch(MAX_AREA_LIGHTS);
|
|
9680
|
+
private warnedAreaShadowRange = false;
|
|
9681
|
+
private warnedAreaShadowsOff = false;
|
|
9682
|
+
private warnedAreaShadowLayers = false;
|
|
9683
|
+
/** How many point lights the array was last sized for; see the other backend's copy. */
|
|
9684
|
+
private preparedPointLightCount = 0;
|
|
9685
|
+
/** See `renderer.ts`: built when a world reports its lights, and rebuilt only to grow. */
|
|
9686
|
+
private pointShadowArray: GpuPointShadowArray | null = null;
|
|
9687
|
+
/** The array glass replaced, destroyed once the round that copied out of it is submitted. */
|
|
9688
|
+
private retiredPointTexture: GPUTexture | null = null;
|
|
9689
|
+
private readonly bakeScratch = createBakeScratch(POINT_SHADOW_POOL);
|
|
9690
|
+
/** Said once per renderer; see `updatePointShadows`. */
|
|
9691
|
+
private warnedNoPointShadowArray = false;
|
|
9692
|
+
/** Said once, for the retired ambient dial. */
|
|
9693
|
+
private warnedEnvironmentAmbient = false;
|
|
9694
|
+
private readonly castingWorldIndices = new Int32Array(POINT_SHADOW_POOL).fill(-1);
|
|
9695
|
+
private readonly resolvedPointShadows = createResolvedPointShadows(MAX_POINT_LIGHTS);
|
|
9042
9696
|
/** One face's view-projection, rebuilt per face rather than allocated. */
|
|
9043
9697
|
private readonly faceViewProj = mat4.create();
|
|
9044
9698
|
/** The corrected copy the vertex stage is handed. See `SHADOW_CLIP_CORRECTION`. */
|
|
@@ -9217,6 +9871,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9217
9871
|
*/
|
|
9218
9872
|
this.shadowDraws.reset();
|
|
9219
9873
|
this.scatterDepthDraws.reset();
|
|
9874
|
+
this.glassTint?.frag.reset();
|
|
9220
9875
|
this.shadowEncoder = this.surface.device.createCommandEncoder({ label: 'pointShadow' });
|
|
9221
9876
|
/* A cube face is a shadow pass like any other, and the courtyard is where it matters: six of
|
|
9222
9877
|
them a frame is the difference this slot exists to make visible. */
|
|
@@ -9244,7 +9899,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9244
9899
|
light.z,
|
|
9245
9900
|
light.radius,
|
|
9246
9901
|
(view, viewProj) => this.bakeFace(view, viewProj, casters as ShadowCasters),
|
|
9247
|
-
(layer, face, far, near) => this.resolveFace(layer, face, far, near),
|
|
9902
|
+
(layer, face, far, near, x, y, z) => this.resolveFace(layer, face, far, near, x, y, z),
|
|
9248
9903
|
this.faceViewProj,
|
|
9249
9904
|
light.shadowNear,
|
|
9250
9905
|
light.sourceRadius,
|
|
@@ -9258,10 +9913,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9258
9913
|
* encoder, a second submit and a second `shadows` timer slot for work that is the same work.
|
|
9259
9914
|
*/
|
|
9260
9915
|
this.updateAreaShadows(areaLights, frameDt, staticCasters, dynamicCasters);
|
|
9916
|
+
/* The chain frost reads, once for the round, for the lights whose glass it drew or cleared. */
|
|
9917
|
+
this.pointShadowArray?.glassTint?.recordMips(this.shadowEncoder);
|
|
9261
9918
|
} finally {
|
|
9262
9919
|
this.depthCullMode = 'back';
|
|
9263
9920
|
this.shadowDraws.flush();
|
|
9264
9921
|
this.scatterDepthDraws.flush();
|
|
9922
|
+
this.glassTint?.frag.flush();
|
|
9265
9923
|
/*
|
|
9266
9924
|
* **Before the submit, and that ordering is the whole point of the ring.** A write to the
|
|
9267
9925
|
* queue lands ahead of the encoder's commands whenever it is issued, so uploading per
|
|
@@ -9273,6 +9931,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9273
9931
|
this.shadowEncoder = null;
|
|
9274
9932
|
this.shadowPass = null;
|
|
9275
9933
|
this.gpuTimer.end();
|
|
9934
|
+
/* An interleaving's old array, copied out of by what was just submitted. */
|
|
9935
|
+
this.retiredPointTexture?.destroy();
|
|
9936
|
+
this.retiredPointTexture = null;
|
|
9276
9937
|
}
|
|
9277
9938
|
|
|
9278
9939
|
/*
|
|
@@ -9283,6 +9944,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9283
9944
|
* written every frame regardless.
|
|
9284
9945
|
*/
|
|
9285
9946
|
pool.resolve(this.resolvedPointShadows);
|
|
9947
|
+
/* As the array stores them: 2L once glass has interleaved it. See `strideLayers`. */
|
|
9948
|
+
const stride = this.pointShadowArray.stride;
|
|
9949
|
+
strideLayers(this.resolvedPointShadows.layers, stride);
|
|
9950
|
+
strideLayers(this.resolvedPointShadows.liveLayers, stride);
|
|
9286
9951
|
}
|
|
9287
9952
|
|
|
9288
9953
|
/**
|
|
@@ -9327,7 +9992,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9327
9992
|
light.z,
|
|
9328
9993
|
range,
|
|
9329
9994
|
(view, viewProj) => this.bakeFace(view, viewProj, casters as ShadowCasters),
|
|
9330
|
-
(layer, face, far, mapNear
|
|
9995
|
+
(layer, face, far, mapNear, x, y, z) =>
|
|
9996
|
+
this.resolveFace(layer, face, far, mapNear, x, y, z),
|
|
9331
9997
|
this.faceViewProj,
|
|
9332
9998
|
near,
|
|
9333
9999
|
/* The filter reads the emitter's size, never the bake. See `renderer.ts`'s copy. */
|
|
@@ -9336,6 +10002,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9336
10002
|
),
|
|
9337
10003
|
);
|
|
9338
10004
|
set.resolve(areaLights, this.resolvedAreaShadows);
|
|
10005
|
+
const stride = this.pointShadowArray?.stride ?? 1;
|
|
10006
|
+
strideLayers(this.resolvedAreaShadows.layer, stride);
|
|
10007
|
+
strideLayers(this.resolvedAreaShadows.liveLayer, stride);
|
|
9339
10008
|
}
|
|
9340
10009
|
|
|
9341
10010
|
/**
|
|
@@ -9363,9 +10032,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9363
10032
|
depthStoreOp: 'store',
|
|
9364
10033
|
},
|
|
9365
10034
|
});
|
|
10035
|
+
/* A face is an enumeration of its own: its glass is its own. */
|
|
10036
|
+
this.glassCasters.clear();
|
|
9366
10037
|
casters(this.casterSink);
|
|
9367
10038
|
this.shadowPass.end();
|
|
9368
10039
|
this.shadowPass = null;
|
|
10040
|
+
/*
|
|
10041
|
+
* The first face to offer glass interleaves the array, before its opaque depth is resolved:
|
|
10042
|
+
* `PointShadowArray.growForGlass`. The lit pass's groups hold the old array's view, so they
|
|
10043
|
+
* are rebuilt, and the old texture goes once this round's encoder — which copies out of it —
|
|
10044
|
+
* is submitted.
|
|
10045
|
+
*/
|
|
10046
|
+
const array = this.pointShadowArray;
|
|
10047
|
+
if (array !== null && this.glassCasters.count > 0 && this.quality.glassShadows !== 'off') {
|
|
10048
|
+
const tintEdge =
|
|
10049
|
+
this.quality.glassShadows === 'half' ? OCTAHEDRAL_EDGE >> 1 : OCTAHEDRAL_EDGE;
|
|
10050
|
+
const old = array.growForGlass(encoder, tintEdge, this.pipelines);
|
|
10051
|
+
if (old !== null) {
|
|
10052
|
+
this.retiredPointTexture = old;
|
|
10053
|
+
this.rebuildFlatBindGroup();
|
|
10054
|
+
}
|
|
10055
|
+
}
|
|
9369
10056
|
}
|
|
9370
10057
|
|
|
9371
10058
|
/**
|
|
@@ -9375,11 +10062,70 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9375
10062
|
* serve every light: the face is consumed before the next one is drawn. See
|
|
9376
10063
|
* `pointShadowArray.ts` for why the regions are disjoint.
|
|
9377
10064
|
*/
|
|
9378
|
-
private resolveFace(
|
|
10065
|
+
private resolveFace(
|
|
10066
|
+
layer: number,
|
|
10067
|
+
face: number,
|
|
10068
|
+
far: number,
|
|
10069
|
+
near: number,
|
|
10070
|
+
x: number,
|
|
10071
|
+
y: number,
|
|
10072
|
+
z: number,
|
|
10073
|
+
): void {
|
|
9379
10074
|
const encoder = this.shadowEncoder;
|
|
9380
10075
|
const array = this.pointShadowArray;
|
|
9381
10076
|
if (encoder === null || array === null) return;
|
|
9382
10077
|
array.resolve(encoder, layer, face, far, near);
|
|
10078
|
+
const tintTarget = array.glassTint;
|
|
10079
|
+
if (tintTarget === null || this.quality.glassShadows === 'off') return;
|
|
10080
|
+
/*
|
|
10081
|
+
* The face's glass, twice, as `PointShadowMap.bakeGlassFace` draws it on the other backend. A
|
|
10082
|
+
* face that offers none still clears a region a previous owner's glass is in, from scratches
|
|
10083
|
+
* cleared and not drawn into, because a pool slot changes hands with its layer.
|
|
10084
|
+
*/
|
|
10085
|
+
const offered = this.glassCasters.count > 0;
|
|
10086
|
+
if (!offered && !tintTarget.held(layer, face)) return;
|
|
10087
|
+
this.shadowPass = encoder.beginRenderPass({
|
|
10088
|
+
label: 'pointShadow.glass',
|
|
10089
|
+
colorAttachments: [],
|
|
10090
|
+
depthStencilAttachment: {
|
|
10091
|
+
view: array.scratchView,
|
|
10092
|
+
depthClearValue: 1,
|
|
10093
|
+
depthLoadOp: 'clear',
|
|
10094
|
+
depthStoreOp: 'store',
|
|
10095
|
+
},
|
|
10096
|
+
});
|
|
10097
|
+
if (offered) {
|
|
10098
|
+
this.drawingGlassDepth = true;
|
|
10099
|
+
this.glassCasters.replay(this.glassDepthSink);
|
|
10100
|
+
this.drawingGlassDepth = false;
|
|
10101
|
+
}
|
|
10102
|
+
this.shadowPass.end();
|
|
10103
|
+
this.shadowPass = null;
|
|
10104
|
+
array.resolveGlass(encoder, layer, face, far, near);
|
|
10105
|
+
|
|
10106
|
+
this.tintPass = encoder.beginRenderPass({
|
|
10107
|
+
label: 'pointShadow.tint',
|
|
10108
|
+
colorAttachments: [
|
|
10109
|
+
{
|
|
10110
|
+
view: tintTarget.scratchView,
|
|
10111
|
+
loadOp: 'clear',
|
|
10112
|
+
clearValue: [1, 1, 1, 1],
|
|
10113
|
+
storeOp: 'store',
|
|
10114
|
+
},
|
|
10115
|
+
],
|
|
10116
|
+
});
|
|
10117
|
+
if (offered) {
|
|
10118
|
+
this.glassTint ??= new GpuGlassTint(this.surface.device);
|
|
10119
|
+
/* A place, w 1: each pane's angle to the lamp comes from its own position. */
|
|
10120
|
+
this.glassLight[0] = x;
|
|
10121
|
+
this.glassLight[1] = y;
|
|
10122
|
+
this.glassLight[2] = z;
|
|
10123
|
+
this.glassLight[3] = 1;
|
|
10124
|
+
this.glassCasters.replay(this.glassTintSink);
|
|
10125
|
+
}
|
|
10126
|
+
this.tintPass.end();
|
|
10127
|
+
this.tintPass = null;
|
|
10128
|
+
tintTarget.resolve(encoder, layer, face, offered);
|
|
9383
10129
|
}
|
|
9384
10130
|
|
|
9385
10131
|
private buildFlatBindGroup(): GPUBindGroup {
|
|
@@ -9739,8 +10485,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9739
10485
|
* drawn over a reflective floor rather than under it.
|
|
9740
10486
|
*/
|
|
9741
10487
|
this.runReflections(encoder);
|
|
9742
|
-
/* The translucent set, into its two buffers and back over the scene
|
|
9743
|
-
|
|
10488
|
+
/* The translucent set, into its two buffers and back over the scene — after the upscale instead
|
|
10489
|
+
while reconstructing, where `runLate` draws the rest of the blended frame. */
|
|
10490
|
+
if (!this.reconstructing) this.runOit(encoder);
|
|
9744
10491
|
/*
|
|
9745
10492
|
* **The medium, after everything that draws and before anything that composites.**
|
|
9746
10493
|
*
|
|
@@ -9761,6 +10508,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9761
10508
|
*/
|
|
9762
10509
|
this.runMotion(encoder);
|
|
9763
10510
|
this.runReconstruction(encoder);
|
|
10511
|
+
this.runLate(encoder);
|
|
10512
|
+
if (this.reconstructing) this.runOit(encoder);
|
|
9764
10513
|
const aoStrength = this.quality.ambientOcclusion;
|
|
9765
10514
|
if (aoStrength > 0 && this.frameProjection !== null) this.runOcclusion(encoder);
|
|
9766
10515
|
const bloomStrength = this.quality.bloom * this.bloomScale;
|
|
@@ -9793,6 +10542,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9793
10542
|
f[at('uMotionStrength')] = motionStrength;
|
|
9794
10543
|
f[at('uMotionMax')] = MOTION_BLUR_MAX_UV;
|
|
9795
10544
|
f[at('uAoStrength')] = this.frameProjection === null ? 0 : aoStrength;
|
|
10545
|
+
/*
|
|
10546
|
+
* The occlusion read where this frame's jitter put it (`uAoOffset`): measured from the jittered
|
|
10547
|
+
* depth and applied to a picture the jitter has been taken out of. A texel stands at its index
|
|
10548
|
+
* plus a half less the jitter, so the occlusion of a point is a jitter further on. The
|
|
10549
|
+
* reconstruction's y is given upward and negated into the corrected matrix, which this pass's
|
|
10550
|
+
* rows run down from; the temporal resolve's goes in as it is, and arrives as it is.
|
|
10551
|
+
*/
|
|
10552
|
+
f[at('uAoOffset')] = this.reconstructing
|
|
10553
|
+
? (this.reconJitter[0] as number) / this.renderWidth
|
|
10554
|
+
: this.temporalJittering
|
|
10555
|
+
? this.temporalJitterX / this.renderWidth
|
|
10556
|
+
: 0;
|
|
10557
|
+
f[at('uAoOffset') + 1] = this.reconstructing
|
|
10558
|
+
? -(this.reconJitter[1] as number) / this.renderHeight
|
|
10559
|
+
: this.temporalJittering
|
|
10560
|
+
? this.temporalJitterY / this.renderHeight
|
|
10561
|
+
: 0;
|
|
9796
10562
|
f[at('uDofStrength')] = dofStrength;
|
|
9797
10563
|
if (dofStrength > 0 && this.frameProjection !== null) {
|
|
9798
10564
|
mat4.invert(this.dofInvProjection, this.frameProjection);
|
|
@@ -9952,6 +10718,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9952
10718
|
|
|
9953
10719
|
for (const dead of [
|
|
9954
10720
|
...this.reconHistories,
|
|
10721
|
+
...this.reconFlickers,
|
|
10722
|
+
...this.reconSwings,
|
|
9955
10723
|
this.reconShown,
|
|
9956
10724
|
this.reconMotion,
|
|
9957
10725
|
this.reconPreviousDepth,
|
|
@@ -9960,6 +10728,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9960
10728
|
}
|
|
9961
10729
|
this.reconHistories = [null, null];
|
|
9962
10730
|
this.reconHistoryViews = [null, null];
|
|
10731
|
+
this.reconFlickers = [null, null];
|
|
10732
|
+
this.reconFlickerViews = [null, null];
|
|
10733
|
+
this.reconSwings = [null, null];
|
|
10734
|
+
this.reconSwingViews = [null, null];
|
|
9963
10735
|
this.reconShown = null;
|
|
9964
10736
|
this.reconShownView = null;
|
|
9965
10737
|
this.reconMotion = null;
|
|
@@ -9984,24 +10756,44 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9984
10756
|
});
|
|
9985
10757
|
this.reconHistories[i] = texture;
|
|
9986
10758
|
this.reconHistoryViews[i] = texture.createView();
|
|
10759
|
+
const record = device.createTexture({
|
|
10760
|
+
label: `recon.flicker${String(i)}`,
|
|
10761
|
+
size: [outputWidth, outputHeight],
|
|
10762
|
+
format: RECON_HISTORY_FORMAT,
|
|
10763
|
+
usage: HISTORY_USAGE,
|
|
10764
|
+
});
|
|
10765
|
+
this.reconFlickers[i] = record;
|
|
10766
|
+
this.reconFlickerViews[i] = record.createView();
|
|
10767
|
+
const swing = device.createTexture({
|
|
10768
|
+
label: `recon.swing${String(i)}`,
|
|
10769
|
+
size: [outputWidth, outputHeight],
|
|
10770
|
+
format: 'r32float',
|
|
10771
|
+
usage: HISTORY_USAGE,
|
|
10772
|
+
});
|
|
10773
|
+
this.reconSwings[i] = swing;
|
|
10774
|
+
this.reconSwingViews[i] = swing.createView();
|
|
9987
10775
|
}
|
|
9988
10776
|
this.reconShown = device.createTexture({
|
|
9989
10777
|
label: 'recon.shown',
|
|
9990
10778
|
size: [outputWidth, outputHeight],
|
|
9991
10779
|
format: RECON_HISTORY_FORMAT,
|
|
9992
|
-
|
|
10780
|
+
/* An attachment too, because the blended draws land on it after the upscale, and a copy
|
|
10781
|
+
source for the panes that refract it. See `runLate`. */
|
|
10782
|
+
usage: HISTORY_USAGE | 0x10 | 0x1, // RENDER_ATTACHMENT | COPY_SRC
|
|
9993
10783
|
});
|
|
9994
10784
|
this.reconShownView = this.reconShown.createView();
|
|
9995
10785
|
this.reconMotion = device.createTexture({
|
|
9996
10786
|
label: 'recon.motion',
|
|
9997
10787
|
size: [renderWidth, renderHeight],
|
|
9998
10788
|
format: RECON_HISTORY_FORMAT,
|
|
9999
|
-
/* An attachment as well, because the pass that
|
|
10000
|
-
|
|
10001
|
-
|
|
10789
|
+
/* An attachment as well, because the pass that writes it is a draw. It is cleared every
|
|
10790
|
+
frame by that pass, and a zero in the fourth channel is what tells the resolve to derive
|
|
10791
|
+
the camera's motion. */
|
|
10002
10792
|
usage: 0x4 | 0x10,
|
|
10003
10793
|
});
|
|
10004
10794
|
this.reconMotionView = this.reconMotion.createView();
|
|
10795
|
+
this.motionPass ??= new MotionPass(device);
|
|
10796
|
+
this.latePass ??= new LatePass(device);
|
|
10005
10797
|
this.reconPreviousDepth = device.createTexture({
|
|
10006
10798
|
label: 'recon.previousDepth',
|
|
10007
10799
|
size: [renderWidth, renderHeight],
|
|
@@ -10056,6 +10848,26 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10056
10848
|
viewDimension: '2d' as const,
|
|
10057
10849
|
},
|
|
10058
10850
|
},
|
|
10851
|
+
sampled(8, 'float'),
|
|
10852
|
+
{
|
|
10853
|
+
binding: 9,
|
|
10854
|
+
visibility: COMPUTE,
|
|
10855
|
+
storageTexture: {
|
|
10856
|
+
access: 'write-only' as const,
|
|
10857
|
+
format: RECON_HISTORY_FORMAT,
|
|
10858
|
+
viewDimension: '2d' as const,
|
|
10859
|
+
},
|
|
10860
|
+
},
|
|
10861
|
+
sampled(10, 'unfilterable-float'),
|
|
10862
|
+
{
|
|
10863
|
+
binding: 11,
|
|
10864
|
+
visibility: COMPUTE,
|
|
10865
|
+
storageTexture: {
|
|
10866
|
+
access: 'write-only' as const,
|
|
10867
|
+
format: 'r32float' as const,
|
|
10868
|
+
viewDimension: '2d' as const,
|
|
10869
|
+
},
|
|
10870
|
+
},
|
|
10059
10871
|
],
|
|
10060
10872
|
});
|
|
10061
10873
|
const module = device.createShaderModule({ label: 'recon.resolve', code: reconResolveWgsl() });
|
|
@@ -10087,7 +10899,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10087
10899
|
if (this.reconMotionView === null || this.reconPreviousDepthView === null) return;
|
|
10088
10900
|
if (this.reconShownView === null) return;
|
|
10089
10901
|
const { device } = this.surface;
|
|
10090
|
-
const build = (
|
|
10902
|
+
const build = (
|
|
10903
|
+
label: string,
|
|
10904
|
+
history: GPUTextureView,
|
|
10905
|
+
target: GPUTextureView,
|
|
10906
|
+
records: GPUTextureView,
|
|
10907
|
+
nextRecords: GPUTextureView,
|
|
10908
|
+
swings: GPUTextureView,
|
|
10909
|
+
nextSwings: GPUTextureView,
|
|
10910
|
+
) =>
|
|
10091
10911
|
device.createBindGroup({
|
|
10092
10912
|
label,
|
|
10093
10913
|
layout,
|
|
@@ -10100,242 +10920,298 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10100
10920
|
{ binding: 5, resource: this.postSampler },
|
|
10101
10921
|
{ binding: 6, resource: { buffer: params } },
|
|
10102
10922
|
{ binding: 7, resource: target },
|
|
10923
|
+
{ binding: 8, resource: records },
|
|
10924
|
+
{ binding: 9, resource: nextRecords },
|
|
10925
|
+
{ binding: 10, resource: swings },
|
|
10926
|
+
{ binding: 11, resource: nextSwings },
|
|
10103
10927
|
],
|
|
10104
10928
|
});
|
|
10105
10929
|
for (let write = 0; write < 2; write += 1) {
|
|
10106
10930
|
const read = this.reconHistoryViews[1 - write];
|
|
10107
10931
|
const target = this.reconHistoryViews[write];
|
|
10108
|
-
|
|
10109
|
-
this.
|
|
10932
|
+
const records = this.reconFlickerViews[1 - write];
|
|
10933
|
+
const nextRecords = this.reconFlickerViews[write];
|
|
10934
|
+
const swings = this.reconSwingViews[1 - write];
|
|
10935
|
+
const nextSwings = this.reconSwingViews[write];
|
|
10936
|
+
if (read === null || target === null || records === null || nextRecords === null) continue;
|
|
10937
|
+
if (swings === null || nextSwings === null) continue;
|
|
10938
|
+
this.reconGroups[write] = build(
|
|
10939
|
+
`recon.resolve${String(write)}`,
|
|
10940
|
+
read,
|
|
10941
|
+
target,
|
|
10942
|
+
records,
|
|
10943
|
+
nextRecords,
|
|
10944
|
+
swings,
|
|
10945
|
+
nextSwings,
|
|
10946
|
+
);
|
|
10947
|
+
/* The sharpen reads no record, but one layout serves both dispatches: the same pair, so no
|
|
10948
|
+
texture is both read and written. */
|
|
10110
10949
|
this.reconSharpenGroups[write] = build(
|
|
10111
10950
|
`recon.sharpen${String(write)}`,
|
|
10112
10951
|
target,
|
|
10113
10952
|
this.reconShownView,
|
|
10953
|
+
records,
|
|
10954
|
+
nextRecords,
|
|
10955
|
+
swings,
|
|
10956
|
+
nextSwings,
|
|
10114
10957
|
);
|
|
10115
10958
|
}
|
|
10116
10959
|
/* The composite's own group, over the resolved picture instead of the scene target. */
|
|
10117
10960
|
this.reconRushGroup = this.buildRushBindGroup('recon.rushBindGroup', this.reconShownView);
|
|
10118
10961
|
/* And the scene's, because `ensureComposite` built it before this one existed. */
|
|
10119
10962
|
this.rebuildRushBindGroup();
|
|
10963
|
+
/* And the bloom's, which blooms the resolved picture: see `rebuildBloomGroups`. */
|
|
10964
|
+
this.rebuildBloomGroups();
|
|
10965
|
+
}
|
|
10966
|
+
|
|
10967
|
+
/** This frame's jitter and temporal frame, decided where its scene camera is bound. See `bindMeshPass`. */
|
|
10968
|
+
private jitterFrame(): void {
|
|
10969
|
+
this.reconstructing = this.reconstructionWanted && !this.reflectionPassActive;
|
|
10970
|
+
if (this.quality.reconstruction > 0 && this.samples > 1 && !this.reconMultisampleSaid) {
|
|
10971
|
+
this.reconMultisampleSaid = true;
|
|
10972
|
+
console.warn(RECONSTRUCTION_MULTISAMPLE_REFUSAL);
|
|
10973
|
+
}
|
|
10974
|
+
if (this.reconstructing) {
|
|
10975
|
+
/*
|
|
10976
|
+
* The offset `settleReconJitter` chose for the frame at `beginFrame`, applied here and not
|
|
10977
|
+
* advanced here: a frame binding its mesh pass twice is one frame of the sequence.
|
|
10978
|
+
*
|
|
10979
|
+
* **The y offset is negated on the way in, and that is not a preference.** This shifts the
|
|
10980
|
+
* *corrected* matrix, whose y `CLIP_CORRECTION` has already negated so that the negation
|
|
10981
|
+
* every generated vertex stage ends with cancels out — §3 row 56. So an offset added here
|
|
10982
|
+
* arrives on screen with its sign reversed, while x, which nothing negates, arrives as
|
|
10983
|
+
* given. The temporal resolve cannot see this because its shader never reads the offset; the
|
|
10984
|
+
* reconstruction reads it at every texel it unprojects, and a sign error there moves every
|
|
10985
|
+
* sample two jitters away from where the depth says it is.
|
|
10986
|
+
*/
|
|
10987
|
+
this.viewProj = jitterProjection(
|
|
10988
|
+
this.jitteredViewProj,
|
|
10989
|
+
this.correctedViewProj,
|
|
10990
|
+
this.reconJitter[0] as number,
|
|
10991
|
+
-(this.reconJitter[1] as number),
|
|
10992
|
+
this.renderWidth,
|
|
10993
|
+
this.renderHeight,
|
|
10994
|
+
) as Float32Array;
|
|
10995
|
+
this.temporalJittering = false;
|
|
10996
|
+
this.temporalHistoryUsable = false;
|
|
10997
|
+
}
|
|
10998
|
+
this.temporalJittering =
|
|
10999
|
+
!this.reconstructing &&
|
|
11000
|
+
this.quality.temporalAa &&
|
|
11001
|
+
this.quality.screenEffects &&
|
|
11002
|
+
!this.reflectionPassActive;
|
|
11003
|
+
if (this.temporalJittering) {
|
|
11004
|
+
/* The scene's size, not the drawing buffer's: the history is a picture of the render. */
|
|
11005
|
+
this.temporalHistoryUsable = this.temporalHistory.openFrame(
|
|
11006
|
+
this.renderWidth,
|
|
11007
|
+
this.renderHeight,
|
|
11008
|
+
);
|
|
11009
|
+
const [jx, jy] = jitterOffset(this.temporalHistory.frameIndex);
|
|
11010
|
+
this.temporalJitterX = jx;
|
|
11011
|
+
this.temporalJitterY = jy;
|
|
11012
|
+
/* A jitter is half a *render* texel, which is what the sequence is spread over. */
|
|
11013
|
+
this.viewProj = jitterProjection(
|
|
11014
|
+
this.jitteredViewProj,
|
|
11015
|
+
this.correctedViewProj,
|
|
11016
|
+
jx,
|
|
11017
|
+
jy,
|
|
11018
|
+
this.renderWidth,
|
|
11019
|
+
this.renderHeight,
|
|
11020
|
+
) as Float32Array;
|
|
11021
|
+
} else if (!this.reconstructing) {
|
|
11022
|
+
this.temporalHistoryUsable = false;
|
|
11023
|
+
this.viewProj = this.correctedViewProj;
|
|
11024
|
+
}
|
|
10120
11025
|
}
|
|
10121
11026
|
|
|
10122
11027
|
/**
|
|
10123
|
-
*
|
|
11028
|
+
* Whether a blended draw recorded now lands after the upscale. See `lateRouting.ts`.
|
|
10124
11029
|
*
|
|
10125
|
-
*
|
|
10126
|
-
* — which is the right thing for a scene to do, the alternative being to know what the renderer
|
|
10127
|
-
* is configured as — must cost nothing at all when nobody is going to read them.
|
|
11030
|
+
* Not during the order-independent replay, which owns its pass and issues into it directly.
|
|
10128
11031
|
*/
|
|
10129
|
-
private
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
const grown = new Float32Array(Math.max(wanted, this.motionMatrices.length * 2, 64));
|
|
10140
|
-
grown.set(this.motionMatrices);
|
|
10141
|
-
this.motionMatrices = grown;
|
|
10142
|
-
}
|
|
10143
|
-
this.motionMatrices.set(model as ArrayLike<number> & Iterable<number>, at * MOTION_DRAW_FLOATS);
|
|
10144
|
-
this.motionMatrices.set(
|
|
10145
|
-
previousModel as ArrayLike<number> & Iterable<number>,
|
|
10146
|
-
at * MOTION_DRAW_FLOATS + 16,
|
|
11032
|
+
private drawsLate(): boolean {
|
|
11033
|
+
return (
|
|
11034
|
+
this.latePass !== null &&
|
|
11035
|
+
this.oitEncoder === null &&
|
|
11036
|
+
routesLate(
|
|
11037
|
+
this.reconstructing,
|
|
11038
|
+
this.reflectionPassActive,
|
|
11039
|
+
this.probePassActive,
|
|
11040
|
+
this.framePresented,
|
|
11041
|
+
)
|
|
10147
11042
|
);
|
|
10148
|
-
this.motionMeshes[at] = mesh;
|
|
10149
|
-
this.motionCount = at + 1;
|
|
10150
11043
|
}
|
|
10151
11044
|
|
|
10152
11045
|
/**
|
|
10153
|
-
* The
|
|
10154
|
-
*
|
|
10155
|
-
*
|
|
10156
|
-
* an attribute — so this binds buffer zero and nothing else, whatever else the mesh carries.
|
|
11046
|
+
* The command a blended draw records into: the late pool while reconstructing, the frame graph
|
|
11047
|
+
* otherwise. **Late draws record whatever `quality.frameGraph` says**, since the late replay is
|
|
11048
|
+
* the only moment they can land; everything else keeps the path it has.
|
|
10157
11049
|
*/
|
|
10158
|
-
private
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
this.
|
|
10162
|
-
label: 'recon.motion.layout',
|
|
10163
|
-
entries: [
|
|
10164
|
-
{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' as const } },
|
|
10165
|
-
{
|
|
10166
|
-
binding: 1,
|
|
10167
|
-
visibility: 0x1,
|
|
10168
|
-
buffer: { type: 'uniform' as const, hasDynamicOffset: true },
|
|
10169
|
-
},
|
|
10170
|
-
],
|
|
10171
|
-
});
|
|
10172
|
-
this.motionModule = device.createShaderModule({
|
|
10173
|
-
label: 'recon.motion',
|
|
10174
|
-
code: RECON_MOTION_WGSL,
|
|
10175
|
-
});
|
|
10176
|
-
this.motionPipelineLayout = device.createPipelineLayout({
|
|
10177
|
-
bindGroupLayouts: [this.motionLayout],
|
|
10178
|
-
});
|
|
11050
|
+
private recordBlended(reads: number, lands = this.drawsLate()): DrawCommand | null {
|
|
11051
|
+
const late = this.latePass;
|
|
11052
|
+
if (late !== null && lands) return late.take();
|
|
11053
|
+
return this.recordDraw(reads, this.currentTarget());
|
|
10179
11054
|
}
|
|
10180
11055
|
|
|
10181
|
-
/** The
|
|
10182
|
-
private
|
|
10183
|
-
|
|
10184
|
-
if (held !== undefined) return held;
|
|
10185
|
-
const module = this.motionModule;
|
|
10186
|
-
const layout = this.motionPipelineLayout;
|
|
10187
|
-
if (module === null || layout === null) return null;
|
|
10188
|
-
const { device } = this.surface;
|
|
10189
|
-
const pipeline = device.createRenderPipeline({
|
|
10190
|
-
label: `recon.motion.${String(stride)}`,
|
|
10191
|
-
layout,
|
|
10192
|
-
vertex: {
|
|
10193
|
-
module,
|
|
10194
|
-
entryPoint: 'motionVert',
|
|
10195
|
-
/* Positions are the layout's first attribute and are never optional, so they are at offset
|
|
10196
|
-
zero of every stride this can be handed. */
|
|
10197
|
-
buffers: [
|
|
10198
|
-
{
|
|
10199
|
-
arrayStride: stride,
|
|
10200
|
-
attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }],
|
|
10201
|
-
},
|
|
10202
|
-
],
|
|
10203
|
-
},
|
|
10204
|
-
fragment: { module, entryPoint: 'motionFrag', targets: [{ format: MOTION_TARGET_FORMAT }] },
|
|
10205
|
-
/*
|
|
10206
|
-
* **The scene's own winding**, because this draws the scene's geometry through the scene's
|
|
10207
|
-
* clip transform — the product the renderer hands over already carries the negation that
|
|
10208
|
-
* `naga` writes into every generated vertex stage, so the faces come out the way they did
|
|
10209
|
-
* and a `cw` front face here would draw the inside of every mesh. §3 row 57.
|
|
10210
|
-
*/
|
|
10211
|
-
primitive: { topology: 'triangle-list', cullMode: 'back' },
|
|
10212
|
-
depthStencil: {
|
|
10213
|
-
format: DEPTH_FORMAT,
|
|
10214
|
-
/*
|
|
10215
|
-
* **Tested and not written.** The frame's depth is finished; this pass only wants the
|
|
10216
|
-
* pixels the scene actually kept, and `DEPTH_COMPARE_EQUAL` is the comparison that admits
|
|
10217
|
-
* exactly them — the same transform on the same positions gives the same depth, which is
|
|
10218
|
-
* why the rasterisation matrix is handed over whole rather than rebuilt here.
|
|
10219
|
-
*
|
|
10220
|
-
* **And the equality is not fragile here**, which was worth measuring rather than
|
|
10221
|
-
* assuming: this driver contracts multiply-adds (§3 row 107) and two shader modules can
|
|
10222
|
-
* contract a matrix product differently, so an equality across them could have admitted
|
|
10223
|
-
* only some of a surface. Run with `greater-equal` instead, the ghost measured the same
|
|
10224
|
-
* to the pixel — 5,942 either way — so nothing is being dropped.
|
|
10225
|
-
*/
|
|
10226
|
-
depthWriteEnabled: false,
|
|
10227
|
-
depthCompare: DEPTH_COMPARE_EQUAL,
|
|
10228
|
-
},
|
|
10229
|
-
});
|
|
10230
|
-
this.motionPipelines.set(stride, pipeline);
|
|
10231
|
-
return pipeline;
|
|
11056
|
+
/** The pipelines a blended draw is built for: the reconstructed picture's while late. */
|
|
11057
|
+
private blendedPipelines(lands = this.drawsLate()): PipelineCache {
|
|
11058
|
+
return lands ? this.latePipelines : this.targetPipelines();
|
|
10232
11059
|
}
|
|
10233
11060
|
|
|
10234
11061
|
/**
|
|
10235
|
-
*
|
|
10236
|
-
*
|
|
10237
|
-
*
|
|
10238
|
-
* finished for the equality test to mean anything, and the resolve is what consumes the result.
|
|
11062
|
+
* The blended draws of a reconstructing frame, over the reconstructed picture at the output size
|
|
11063
|
+
* and unjittered, against the render's depth taken up to that size. Before occlusion, bloom and
|
|
11064
|
+
* the grade, so they are graded exactly as they were when they were drawn into the scene.
|
|
10239
11065
|
*/
|
|
10240
|
-
private
|
|
10241
|
-
const
|
|
11066
|
+
private runLate(encoder: GPUCommandEncoder): void {
|
|
11067
|
+
const late = this.latePass;
|
|
11068
|
+
const shown = this.reconShownView;
|
|
10242
11069
|
const depth = this.depthView;
|
|
10243
|
-
if (!this.reconstructing ||
|
|
10244
|
-
const
|
|
10245
|
-
|
|
10246
|
-
const
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
11070
|
+
if (!this.reconstructing || late === null || shown === null || depth === null) return;
|
|
11071
|
+
const oitSet = this.oitActive && this.translucentQueue.length > 0;
|
|
11072
|
+
if (late.pending === 0 && !oitSet) return;
|
|
11073
|
+
const width = Math.max(1, this.surface.canvas.width);
|
|
11074
|
+
const height = Math.max(1, this.surface.canvas.height);
|
|
11075
|
+
/* The order-independent set tests against this depth too, so it is made for either. */
|
|
11076
|
+
late.upscale(
|
|
11077
|
+
encoder,
|
|
11078
|
+
depth,
|
|
11079
|
+
this.renderWidth,
|
|
11080
|
+
this.renderHeight,
|
|
11081
|
+
width,
|
|
11082
|
+
height,
|
|
11083
|
+
this.reconJitter[0] as number,
|
|
11084
|
+
this.reconJitter[1] as number,
|
|
11085
|
+
);
|
|
11086
|
+
if (this.lateRefractWanted && this.refractSnapshot !== null && this.reconShown !== null) {
|
|
11087
|
+
encoder.copyTextureToTexture(
|
|
11088
|
+
{ texture: this.reconShown },
|
|
11089
|
+
{ texture: this.refractSnapshot },
|
|
11090
|
+
[this.refractSnapshot.width, this.refractSnapshot.height],
|
|
11091
|
+
);
|
|
11092
|
+
this.refractMips?.record(encoder);
|
|
11093
|
+
this.refractSnapshotTaken = true;
|
|
11094
|
+
}
|
|
11095
|
+
this.lateRefractWanted = false;
|
|
11096
|
+
if (late.pending === 0) return;
|
|
10255
11097
|
const pass = encoder.beginRenderPass({
|
|
10256
|
-
label: 'recon.
|
|
11098
|
+
label: 'recon.late',
|
|
10257
11099
|
timestampWrites: this.gpuTimer.writesFor(),
|
|
10258
|
-
colorAttachments: [
|
|
10259
|
-
{ view: target, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
10260
|
-
],
|
|
11100
|
+
colorAttachments: [{ view: shown, loadOp: 'load', storeOp: 'store' }],
|
|
10261
11101
|
depthStencilAttachment: {
|
|
10262
|
-
view:
|
|
11102
|
+
view: late.depthView(width, height),
|
|
10263
11103
|
depthLoadOp: 'load',
|
|
11104
|
+
/* Kept: the order-independent set replays against it after this pass. */
|
|
10264
11105
|
depthStoreOp: 'store',
|
|
10265
|
-
depthReadOnly: false,
|
|
10266
11106
|
},
|
|
10267
11107
|
});
|
|
11108
|
+
late.replay(pass, this.issueBound);
|
|
11109
|
+
pass.end();
|
|
11110
|
+
}
|
|
10268
11111
|
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
usage: 0x40 | 0x8, // UNIFORM | COPY_DST
|
|
10279
|
-
});
|
|
10280
|
-
this.motionDrawStaging = new Uint8Array(this.motionDrawCapacity * MOTION_DRAW_STRIDE);
|
|
10281
|
-
this.motionGroup = null;
|
|
10282
|
-
}
|
|
10283
|
-
this.motionFrameBuffer ??= device.createBuffer({
|
|
10284
|
-
label: 'recon.motion.frame',
|
|
10285
|
-
size: MOTION_FRAME_FLOATS * 4,
|
|
10286
|
-
usage: 0x40 | 0x8,
|
|
10287
|
-
});
|
|
10288
|
-
this.motionGroup ??= device.createBindGroup({
|
|
10289
|
-
label: 'recon.motion.group',
|
|
10290
|
-
layout,
|
|
10291
|
-
entries: [
|
|
10292
|
-
{ binding: 0, resource: { buffer: this.motionFrameBuffer } },
|
|
10293
|
-
{
|
|
10294
|
-
binding: 1,
|
|
10295
|
-
resource: { buffer: this.motionDrawBuffer as GPUBuffer, size: MOTION_DRAW_STRIDE },
|
|
10296
|
-
},
|
|
10297
|
-
],
|
|
10298
|
-
});
|
|
10299
|
-
|
|
10300
|
-
/* The scene's clip transform, with the generated stages' y negation folded in. */
|
|
10301
|
-
mat4.multiply(this.motionRaster, MOTION_CLIP_FLIP, this.viewProj ?? this.correctedViewProj);
|
|
10302
|
-
this.motionFrameStaging.set(this.motionRaster as Float32Array, 0);
|
|
10303
|
-
this.motionFrameStaging.set(this.frameRawViewProj as Float32Array, 16);
|
|
10304
|
-
this.motionFrameStaging.set(this.reconPreviousRawViewProj as Float32Array, 32);
|
|
10305
|
-
device.queue.writeBuffer(this.motionFrameBuffer, 0, this.motionFrameStaging);
|
|
10306
|
-
|
|
10307
|
-
const draws = new Float32Array(this.motionDrawStaging.buffer);
|
|
10308
|
-
for (let i = 0; i < count; i += 1) {
|
|
10309
|
-
draws.set(
|
|
10310
|
-
this.motionMatrices.subarray(i * MOTION_DRAW_FLOATS, (i + 1) * MOTION_DRAW_FLOATS),
|
|
10311
|
-
(i * MOTION_DRAW_STRIDE) / 4,
|
|
10312
|
-
);
|
|
10313
|
-
}
|
|
10314
|
-
device.queue.writeBuffer(
|
|
10315
|
-
this.motionDrawBuffer as GPUBuffer,
|
|
10316
|
-
0,
|
|
10317
|
-
this.motionDrawStaging,
|
|
10318
|
-
0,
|
|
10319
|
-
bytes,
|
|
10320
|
-
);
|
|
11112
|
+
/** Whether this draw lands in the reconstructed picture. See `lateRouting.ts`. */
|
|
11113
|
+
private tracksMotionNow(): boolean {
|
|
11114
|
+
return tracksMotion(
|
|
11115
|
+
this.reconstructing,
|
|
11116
|
+
this.reflectionPassActive,
|
|
11117
|
+
this.probePassActive,
|
|
11118
|
+
this.framePresented,
|
|
11119
|
+
);
|
|
11120
|
+
}
|
|
10321
11121
|
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
11122
|
+
/**
|
|
11123
|
+
* What this draw's motion is, recorded for the motion pass.
|
|
11124
|
+
*
|
|
11125
|
+
* A mesh rewritten this frame carries its vertex motion whatever else the draw says — under last
|
|
11126
|
+
* frame's model where a mover or a matrix knows it, under this frame's where nothing does. A
|
|
11127
|
+
* mover otherwise carries a rigid draw's last model or a skinned draw's last pose; a matrix
|
|
11128
|
+
* carries a rigid draw's. A draw that says nothing and moved no vertex takes the camera's motion.
|
|
11129
|
+
*/
|
|
11130
|
+
private recordDrawMotion(
|
|
11131
|
+
mesh: GpuMesh,
|
|
11132
|
+
model: ArrayLike<number>,
|
|
11133
|
+
previous: ArrayLike<number> | Mover | null,
|
|
11134
|
+
): void {
|
|
11135
|
+
const dynamic = mesh.motion;
|
|
11136
|
+
const rows = dynamic?.previous ?? null;
|
|
11137
|
+
const rewritten = rows !== null && dynamic !== null && dynamic.changed === this.moverFrame;
|
|
11138
|
+
if (previous === null) {
|
|
11139
|
+
if (rewritten) this.motionPass?.recordDynamic(mesh, model, model, rows);
|
|
11140
|
+
return;
|
|
10337
11141
|
}
|
|
10338
|
-
|
|
11142
|
+
if (!isMover(previous)) {
|
|
11143
|
+
if (rewritten) this.motionPass?.recordDynamic(mesh, model, previous, rows);
|
|
11144
|
+
else this.motionPass?.recordRigid(mesh, model, previous);
|
|
11145
|
+
return;
|
|
11146
|
+
}
|
|
11147
|
+
/* A rig is only followed where the draw really skins: a palette set, a mesh with joints. */
|
|
11148
|
+
const skinned =
|
|
11149
|
+
!rewritten && this.skinPaletteSlot >= 0 && mesh.isSkinned && mesh.skinOffsets !== null;
|
|
11150
|
+
const verdict = advanceMover(
|
|
11151
|
+
previous,
|
|
11152
|
+
this.moverRenderer,
|
|
11153
|
+
this.moverFrame,
|
|
11154
|
+
model,
|
|
11155
|
+
skinned ? this.skinPaletteData : null,
|
|
11156
|
+
);
|
|
11157
|
+
if (verdict === MOVER_TWICE) {
|
|
11158
|
+
this.warnMoverTwice();
|
|
11159
|
+
return;
|
|
11160
|
+
}
|
|
11161
|
+
if (rewritten) {
|
|
11162
|
+
const was = verdict === MOVER_MOTION ? previous.previousModel : model;
|
|
11163
|
+
this.motionPass?.recordDynamic(mesh, model, was, rows);
|
|
11164
|
+
return;
|
|
11165
|
+
}
|
|
11166
|
+
if (verdict !== MOVER_MOTION) return;
|
|
11167
|
+
if (skinned) this.recordSkinnedMotion(mesh, model, previous);
|
|
11168
|
+
else this.motionPass?.recordRigid(mesh, model, previous.previousModel);
|
|
11169
|
+
}
|
|
11170
|
+
|
|
11171
|
+
/**
|
|
11172
|
+
* A skinned mover's motion: this frame's palette is the draw's own slot, and last frame's takes a
|
|
11173
|
+
* slot of its own, like any palette a draw binds — so it is uploaded once and nothing overwrites
|
|
11174
|
+
* it before the motion pass reads it. See `SkinPaletteRing`.
|
|
11175
|
+
*/
|
|
11176
|
+
private recordSkinnedMotion(mesh: GpuMesh, model: ArrayLike<number>, mover: Mover): void {
|
|
11177
|
+
const now = this.skinPalettes.view(this.skinPaletteSlot);
|
|
11178
|
+
const slot = this.skinPalettes.take(this.surface.device, mover.previousPalette);
|
|
11179
|
+
const was = slot === null ? null : this.skinPalettes.view(slot);
|
|
11180
|
+
if (now === null || was === null) return;
|
|
11181
|
+
this.motionPass?.recordSkinned(mesh, model, mover.previousModel, now, was);
|
|
11182
|
+
}
|
|
11183
|
+
|
|
11184
|
+
private warnMoverTwice(): void {
|
|
11185
|
+
if (this.warnedMoverTwice) return;
|
|
11186
|
+
this.warnedMoverTwice = true;
|
|
11187
|
+
console.warn(
|
|
11188
|
+
'WebGPU: a Mover was drawn twice in one frame. A mover is one object; the second draw ' +
|
|
11189
|
+
'records no motion. Give each moving object its own `createMover()`.',
|
|
11190
|
+
);
|
|
11191
|
+
}
|
|
11192
|
+
|
|
11193
|
+
/**
|
|
11194
|
+
* Where every mover was last frame, into the motion target. See `MotionPass`.
|
|
11195
|
+
*
|
|
11196
|
+
* **After everything that draws and before the resolve reads it.** The frame's depth has to be
|
|
11197
|
+
* finished for the pass's equality test to mean anything, and the resolve is what consumes it.
|
|
11198
|
+
*/
|
|
11199
|
+
private runMotion(encoder: GPUCommandEncoder): void {
|
|
11200
|
+
const target = this.reconMotionView;
|
|
11201
|
+
const depth = this.depthView;
|
|
11202
|
+
const motion = this.motionPass;
|
|
11203
|
+
if (!this.reconstructing || target === null || depth === null || motion === null) return;
|
|
11204
|
+
/* The scene's clip transform, with the generated stages' y negation folded in. */
|
|
11205
|
+
mat4.multiply(this.motionRaster, MOTION_CLIP_FLIP, this.viewProj ?? this.correctedViewProj);
|
|
11206
|
+
motion.run(
|
|
11207
|
+
encoder,
|
|
11208
|
+
target,
|
|
11209
|
+
depth,
|
|
11210
|
+
this.motionRaster as Float32Array,
|
|
11211
|
+
this.frameRawViewProj as Float32Array,
|
|
11212
|
+
this.reconPreviousRawViewProj as Float32Array,
|
|
11213
|
+
this.gpuTimer.writesFor(),
|
|
11214
|
+
);
|
|
10339
11215
|
}
|
|
10340
11216
|
|
|
10341
11217
|
/**
|
|
@@ -10365,7 +11241,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10365
11241
|
* OpenGL's depth convention — which is the convention the shader's accessor converts the
|
|
10366
11242
|
* renderer's reversed buffer into.
|
|
10367
11243
|
*/
|
|
10368
|
-
|
|
11244
|
+
/*
|
|
11245
|
+
* The inverse and the reprojection in double precision, rounded once on the way into the
|
|
11246
|
+
* uniform: the product of two single-precision matrices this far apart in depth is not the
|
|
11247
|
+
* identity for a still camera, and the resolve read the difference as motion.
|
|
11248
|
+
*/
|
|
11249
|
+
mat4.invert(this.reconInverse64, this.frameRawViewProj);
|
|
11250
|
+
mat4.multiply(this.reconReprojection64, this.reconPreviousRawViewProj, this.reconInverse64);
|
|
11251
|
+
for (let i = 0; i < 16; i += 1) {
|
|
11252
|
+
this.reconInverseViewProj[i] = this.reconInverse64[i] as number;
|
|
11253
|
+
}
|
|
10369
11254
|
const f = this.reconFloats;
|
|
10370
11255
|
const u = this.reconInts;
|
|
10371
11256
|
u[0] = this.renderWidth;
|
|
@@ -10389,6 +11274,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10389
11274
|
f[69] = DEFAULT_DISOCCLUSION.motionScale;
|
|
10390
11275
|
f[70] = DEFAULT_DISOCCLUSION.normalFloor;
|
|
10391
11276
|
f[71] = DEFAULT_DISOCCLUSION.normalCeiling;
|
|
11277
|
+
/* The anti-flicker over the reconstruction's own period, its rule made once a period. */
|
|
11278
|
+
const phases = reconJitterPhases(this.renderWidth, outputWidth);
|
|
11279
|
+
if (phases !== this.reconFlickerPhases) {
|
|
11280
|
+
this.reconFlickerPhases = phases;
|
|
11281
|
+
this.reconFlickerRule = flickerRule(phases);
|
|
11282
|
+
}
|
|
11283
|
+
u[72] = this.reconFrameIndex % phases === 0 ? 1 : 0;
|
|
11284
|
+
u[73] = 1;
|
|
11285
|
+
f[74] = this.reconFlickerRule.memory;
|
|
11286
|
+
f[75] = this.reconFlickerRule.width;
|
|
11287
|
+
f.set(this.reconReprojection64, 76);
|
|
10392
11288
|
device.queue.writeBuffer(params, 0, this.reconStaging);
|
|
10393
11289
|
|
|
10394
11290
|
const groupsX = Math.ceil(outputWidth / RECON_WORKGROUP);
|
|
@@ -10781,9 +11677,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10781
11677
|
if (!this.oitActive || this.translucentQueue.length === 0) return;
|
|
10782
11678
|
const accum = this.oitAccumView;
|
|
10783
11679
|
const reveal = this.oitRevealView;
|
|
10784
|
-
|
|
11680
|
+
/* After the upscale while reconstructing: over the reconstructed picture, against its depth. */
|
|
11681
|
+
const scene = this.reconstructing ? this.reconShownView : this.sceneColorView;
|
|
10785
11682
|
const group = this.oitResolveGroup;
|
|
10786
|
-
const depth = this.
|
|
11683
|
+
const depth = this.reconstructing
|
|
11684
|
+
? (this.latePass?.depthView(
|
|
11685
|
+
Math.max(1, this.surface.canvas.width),
|
|
11686
|
+
Math.max(1, this.surface.canvas.height),
|
|
11687
|
+
) ?? null)
|
|
11688
|
+
: this.depthView;
|
|
10787
11689
|
if (accum === null || reveal === null || scene === null || group === null || depth === null) {
|
|
10788
11690
|
return;
|
|
10789
11691
|
}
|
|
@@ -10797,12 +11699,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10797
11699
|
*/
|
|
10798
11700
|
if (this.translucentQueue.refracts && !this.refractSnapshotTaken) {
|
|
10799
11701
|
const snapshot = this.refractSnapshot;
|
|
10800
|
-
const source = this.sceneColor;
|
|
11702
|
+
const source = this.reconstructing ? this.reconShown : this.sceneColor;
|
|
10801
11703
|
if (snapshot !== null && source !== null) {
|
|
10802
11704
|
encoder.copyTextureToTexture({ texture: source }, { texture: snapshot }, [
|
|
10803
11705
|
snapshot.width,
|
|
10804
11706
|
snapshot.height,
|
|
10805
11707
|
]);
|
|
11708
|
+
this.refractMips?.record(encoder);
|
|
10806
11709
|
this.refractSnapshotTaken = true;
|
|
10807
11710
|
}
|
|
10808
11711
|
}
|
|
@@ -10843,6 +11746,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10843
11746
|
options.refraction = draw.refraction;
|
|
10844
11747
|
options.refractTint = draw.refractTint as unknown as Vec3 | undefined;
|
|
10845
11748
|
options.thicknessM = draw.thicknessM;
|
|
11749
|
+
options.glass = draw.glass ?? undefined;
|
|
10846
11750
|
this.drawTranslucentMesh(draw.mesh as GpuMesh, draw.model, draw.opacity, options);
|
|
10847
11751
|
});
|
|
10848
11752
|
this.oitReplaying = false;
|
|
@@ -10873,9 +11777,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10873
11777
|
this.pipelines,
|
|
10874
11778
|
device,
|
|
10875
11779
|
this.oitResolveLayout,
|
|
10876
|
-
'post.oitResolve',
|
|
11780
|
+
this.reconstructing ? 'post.oitResolve.recon' : 'post.oitResolve',
|
|
10877
11781
|
OIT_RESOLVE_FRAG_WGSL,
|
|
10878
|
-
sceneColorFormat(this.quality.hdrScene),
|
|
11782
|
+
this.reconstructing ? RECON_HISTORY_FORMAT : sceneColorFormat(this.quality.hdrScene),
|
|
10879
11783
|
OIT_COMPOSITE_BLEND,
|
|
10880
11784
|
),
|
|
10881
11785
|
);
|
|
@@ -11265,18 +12169,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11265
12169
|
this.lightVolumeVertices.buffer,
|
|
11266
12170
|
this.lightVolumeFragments.buffer,
|
|
11267
12171
|
(name) =>
|
|
11268
|
-
name === '
|
|
11269
|
-
? { view: this.
|
|
11270
|
-
:
|
|
11271
|
-
? { view: this.peelView, sampler: this.volumeShadowSampler }
|
|
11272
|
-
: name === 'uDynamicShadowMap' && this.dynamicView !== null
|
|
11273
|
-
? { view: this.dynamicView, sampler: this.volumeShadowSampler }
|
|
11274
|
-
: /* The frame's own depth, snapshotted before the first volume of the frame draws.
|
|
12172
|
+
name === 'uSunShadows'
|
|
12173
|
+
? { view: this.sunArrayView, sampler: this.volumeShadowSampler }
|
|
12174
|
+
: /* The frame's own depth, snapshotted before the first volume of the frame draws.
|
|
11275
12175
|
Null under a profile with no composite, and then `uSceneDepthEnabled` is 0 and
|
|
11276
12176
|
the stand-in is never sampled. */
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
12177
|
+
name === 'uSceneDepth' && this.resolvedDepthView !== null
|
|
12178
|
+
? { view: this.resolvedDepthView, sampler: this.postDepthSampler }
|
|
12179
|
+
: this.volumeBlank,
|
|
11280
12180
|
);
|
|
11281
12181
|
}
|
|
11282
12182
|
|
|
@@ -11302,6 +12202,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11302
12202
|
private takeRefractSnapshot(): boolean {
|
|
11303
12203
|
if (this.reflectionPassActive || this.probePassActive) return false;
|
|
11304
12204
|
if (this.refractSnapshotTaken) return true;
|
|
12205
|
+
/* A late draw refracts the reconstructed picture, which does not exist until the resolve has
|
|
12206
|
+
run; `runLate` takes the copy then, once, before any late draw is replayed. */
|
|
12207
|
+
if (this.drawsLate()) {
|
|
12208
|
+
this.lateRefractWanted = true;
|
|
12209
|
+
return true;
|
|
12210
|
+
}
|
|
11305
12211
|
/* The replay of an order-independent set runs after the scene's pass has ended, and `runOit`
|
|
11306
12212
|
took the copy for it before it began; there is no pass here to end and reopen. */
|
|
11307
12213
|
if (this.oitReplaying) return false;
|
|
@@ -11340,6 +12246,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11340
12246
|
snapshot.width,
|
|
11341
12247
|
snapshot.height,
|
|
11342
12248
|
]);
|
|
12249
|
+
/* The chain, between the passes, where nothing is attached: what frosted glass reads. */
|
|
12250
|
+
this.refractMips?.record(encoder);
|
|
11343
12251
|
const multisampled = this.compositeMsaa() !== null;
|
|
11344
12252
|
this.pass = encoder.beginRenderPass({
|
|
11345
12253
|
label: 'refract.snapshotTaken',
|
|
@@ -11676,6 +12584,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11676
12584
|
command.indexBuffer = null;
|
|
11677
12585
|
command.indexed = false;
|
|
11678
12586
|
command.instances = 1;
|
|
12587
|
+
command.indirect = null;
|
|
11679
12588
|
recordNode(this.arena, VERB_DRAW, reads, writes, at, 0);
|
|
11680
12589
|
return command;
|
|
11681
12590
|
}
|
|
@@ -11701,8 +12610,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11701
12610
|
}
|
|
11702
12611
|
if (verb !== VERB_DRAW) return;
|
|
11703
12612
|
const command = this.commands.commands[nodeState(this.arena, at)];
|
|
11704
|
-
if (command === undefined
|
|
12613
|
+
if (command === undefined) return;
|
|
12614
|
+
this.issueCommand(pass, command);
|
|
12615
|
+
}
|
|
11705
12616
|
|
|
12617
|
+
/** One recorded draw, issued into an open pass. Shared by the graph's replay and `runLate`. */
|
|
12618
|
+
private issueCommand(pass: GPURenderPassEncoder, command: DrawCommand): void {
|
|
12619
|
+
if (command.pipeline === null || command.bindGroup === null) return;
|
|
11706
12620
|
pass.setPipeline(command.pipeline);
|
|
11707
12621
|
if (command.offsetCount === 2) {
|
|
11708
12622
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB]);
|
|
@@ -11717,7 +12631,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11717
12631
|
}
|
|
11718
12632
|
if (command.indexed && command.indexBuffer !== null) {
|
|
11719
12633
|
pass.setIndexBuffer(command.indexBuffer, 'uint32');
|
|
11720
|
-
pass.
|
|
12634
|
+
if (command.indirect !== null) pass.drawIndexedIndirect(command.indirect, 0);
|
|
12635
|
+
else pass.drawIndexed(command.count, command.instances);
|
|
11721
12636
|
} else {
|
|
11722
12637
|
pass.draw(command.count, command.instances);
|
|
11723
12638
|
}
|
|
@@ -11946,6 +12861,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11946
12861
|
}
|
|
11947
12862
|
|
|
11948
12863
|
endFrame(): void {
|
|
12864
|
+
this.inFrame = false;
|
|
11949
12865
|
if (this.surface.lost || this.encoder === null) return;
|
|
11950
12866
|
/*
|
|
11951
12867
|
* Anything still recorded goes now.
|
|
@@ -12373,6 +13289,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12373
13289
|
* declares nothing behaves exactly as it did before. Read it beside `visible`, which answers the
|
|
12374
13290
|
* other half of the same question.
|
|
12375
13291
|
*/
|
|
13292
|
+
/** Whether a world-space box is wholly behind the declared occluders. See `Renderer.occludedBox`. */
|
|
13293
|
+
occludedBox(min: ArrayLike<number>, max: ArrayLike<number>): boolean {
|
|
13294
|
+
return this.occlusion?.occludedBox(min, max) ?? false;
|
|
13295
|
+
}
|
|
13296
|
+
|
|
12376
13297
|
occluded(bounds: Bounds, model: ReadonlyMat4): boolean {
|
|
12377
13298
|
return this.occlusion?.occluded(bounds, model) ?? false;
|
|
12378
13299
|
}
|
|
@@ -12828,12 +13749,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12828
13749
|
const cssHeight = Math.max(canvas.clientHeight, 1);
|
|
12829
13750
|
const origin = canvas.getBoundingClientRect();
|
|
12830
13751
|
/*
|
|
12831
|
-
* **CSS pixels to the
|
|
12832
|
-
*
|
|
12833
|
-
*
|
|
13752
|
+
* **CSS pixels to the pixels of the attachment this pass draws into.** Mid-frame that is the
|
|
13753
|
+
* scene's render size, which a reconstruction makes smaller than the drawing buffer; after the
|
|
13754
|
+
* present it is the swap image, the drawing buffer itself. Measured against the scene's size
|
|
13755
|
+
* there, an interface's inset landed two thirds of the way to where it was asked for.
|
|
12834
13756
|
*/
|
|
12835
|
-
const
|
|
12836
|
-
const
|
|
13757
|
+
const target = this.passSize();
|
|
13758
|
+
const scaleX = target[0] / cssWidth;
|
|
13759
|
+
const scaleY = target[1] / cssHeight;
|
|
12837
13760
|
const left = rect.left - origin.left;
|
|
12838
13761
|
const top = rect.top - origin.top;
|
|
12839
13762
|
const w = Math.max(1, Math.round(rect.width * scaleX));
|
|
@@ -12861,9 +13784,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12861
13784
|
* edge for the frames it is half-off. That asymmetry is the cost of the clamp, it is
|
|
12862
13785
|
* recorded in the parity ledger, and the alternative was an exception.
|
|
12863
13786
|
*/
|
|
12864
|
-
|
|
12865
|
-
const
|
|
12866
|
-
const targetHeight = this.renderHeight;
|
|
13787
|
+
const targetWidth = target[0];
|
|
13788
|
+
const targetHeight = target[1];
|
|
12867
13789
|
const clampedX = Math.max(0, Math.min(x, targetWidth));
|
|
12868
13790
|
const clampedY = Math.max(0, Math.min(y, targetHeight));
|
|
12869
13791
|
const clampedW = Math.max(0, Math.min(x + w, targetWidth) - clampedX);
|
|
@@ -12940,11 +13862,31 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12940
13862
|
if (this.quality.frameGraph) this.flushGraph();
|
|
12941
13863
|
const pass = this.pass;
|
|
12942
13864
|
if (this.surface.lost || pass === null) return;
|
|
12943
|
-
/* The whole of what this pass draws into
|
|
12944
|
-
|
|
12945
|
-
pass.
|
|
13865
|
+
/* The whole of what this pass draws into: the scene's size mid-frame, the canvas after. */
|
|
13866
|
+
const [width, height] = this.passSize();
|
|
13867
|
+
pass.setViewport(0, 0, width, height, 0, 1);
|
|
13868
|
+
pass.setScissorRect(0, 0, width, height);
|
|
12946
13869
|
}
|
|
12947
13870
|
|
|
13871
|
+
/**
|
|
13872
|
+
* The size of the attachment the open pass draws into: the swap image — the drawing buffer —
|
|
13873
|
+
* in the overlay after the present, the scene's render size in the frame's own pass. A tuple
|
|
13874
|
+
* held once rather than a new array a call.
|
|
13875
|
+
*/
|
|
13876
|
+
private passSize(): readonly [number, number] {
|
|
13877
|
+
const size = this.passSizeOut;
|
|
13878
|
+
if (this.overlayActive) {
|
|
13879
|
+
size[0] = Math.max(1, this.surface.canvas.width);
|
|
13880
|
+
size[1] = Math.max(1, this.surface.canvas.height);
|
|
13881
|
+
} else {
|
|
13882
|
+
size[0] = this.renderWidth;
|
|
13883
|
+
size[1] = this.renderHeight;
|
|
13884
|
+
}
|
|
13885
|
+
return size;
|
|
13886
|
+
}
|
|
13887
|
+
|
|
13888
|
+
private readonly passSizeOut: [number, number] = [0, 0];
|
|
13889
|
+
|
|
12948
13890
|
/**
|
|
12949
13891
|
* Copy a rectangle of the frame into a 2D canvas, pixel for pixel.
|
|
12950
13892
|
*
|
|
@@ -13212,6 +14154,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13212
14154
|
*
|
|
13213
14155
|
* What it gives up is one frame of history: the frame after a cut has no blur and no temporal
|
|
13214
14156
|
* blend, as the first frame of a session has none. Nothing else is reset.
|
|
14157
|
+
*
|
|
14158
|
+
* **A reconstructing renderer depends on it**: its history is the last shot's, and without this
|
|
14159
|
+
* call a cut blends that shot into the first frames of the next. Nothing detects a cut on the
|
|
14160
|
+
* caller's behalf, deliberately — a fast pan and a cut look alike from here.
|
|
13215
14161
|
*/
|
|
13216
14162
|
cameraCut(): void {
|
|
13217
14163
|
this.hasPreviousView = false;
|
|
@@ -13421,9 +14367,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13421
14367
|
this.causticsVerts.writeBlock(vertexSlot, this.causticsVertBlock);
|
|
13422
14368
|
this.causticsFrags.writeBlock(fragmentSlot, this.causticsFragInts);
|
|
13423
14369
|
|
|
13424
|
-
const causticsPipe = causticsPipeline(
|
|
14370
|
+
const causticsPipe = causticsPipeline(
|
|
14371
|
+
this.blendedPipelines(),
|
|
14372
|
+
this.surface.device,
|
|
14373
|
+
this.causticsLayout,
|
|
14374
|
+
);
|
|
13425
14375
|
/* The pattern is generated in the shader from the two uniform blocks; nothing is sampled. */
|
|
13426
|
-
const causticsCommand = this.
|
|
14376
|
+
const causticsCommand = this.recordBlended(0);
|
|
13427
14377
|
if (causticsCommand !== null) {
|
|
13428
14378
|
causticsCommand.pipeline = causticsPipe;
|
|
13429
14379
|
causticsCommand.bindGroup = this.causticsBindGroup;
|
|
@@ -13527,10 +14477,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13527
14477
|
private probeView: GPUTextureView | null = null;
|
|
13528
14478
|
private readonly probeSize: number;
|
|
13529
14479
|
private readonly probeMipLayout: GPUBindGroupLayout;
|
|
13530
|
-
/** The
|
|
13531
|
-
private
|
|
14480
|
+
/** The fixture atlas — cookies over profiles — built on first use. See `fixtureAtlas.ts`. */
|
|
14481
|
+
private fixtureTexture: GPUTexture | null = null;
|
|
14482
|
+
private fixtureView: GPUTextureView | null = null;
|
|
14483
|
+
/** The profiles as packed and the cookie images, kept so either can rebuild the atlas. */
|
|
14484
|
+
private iesAtlas = packIesAtlas([]);
|
|
14485
|
+
private cookieImages: readonly TexImageSource[] = [];
|
|
13532
14486
|
/** The scene's DriftLight field, and its two volumes once it is whole. See `createLightField`. */
|
|
13533
|
-
private lightField:
|
|
14487
|
+
private lightField: DriftLightVolumes | null = null;
|
|
13534
14488
|
private driftIndex: GPUTexture | null = null;
|
|
13535
14489
|
private driftAtlas: GPUTexture | null = null;
|
|
13536
14490
|
private driftIndexView: GPUTextureView | null = null;
|
|
@@ -13542,10 +14496,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13542
14496
|
/** The frame's exact lights packed for the traced bake. See `gi/bounceLights.ts`. */
|
|
13543
14497
|
private readonly bounceLights = createBounceLights();
|
|
13544
14498
|
private readonly bakeLamps = { data: this.bounceLights, count: 0, falloff: 0 };
|
|
13545
|
-
|
|
13546
|
-
private cookieTexture: GPUTexture | null = null;
|
|
13547
|
-
private cookieView: GPUTextureView | null = null;
|
|
13548
|
-
/** Tiles in the cookie atlas. 0 is the white placeholder and the whole off path. */
|
|
14499
|
+
/** Cookie tiles in the atlas. 0 is none, and the whole off path. */
|
|
13549
14500
|
private cookieTiles = 0;
|
|
13550
14501
|
/** Rows in that atlas. One until a consumer loads a profile, which is the row of ones. */
|
|
13551
14502
|
private iesRows = 1;
|
|
@@ -14033,42 +14984,58 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14033
14984
|
*/
|
|
14034
14985
|
setSpotCookies(images: readonly TexImageSource[]): void {
|
|
14035
14986
|
if (this.surface.lost) return;
|
|
14987
|
+
this.cookieImages = images.slice();
|
|
14988
|
+
this.forgetFixtureAtlas();
|
|
14989
|
+
this.rebuildFlatBindGroup();
|
|
14990
|
+
}
|
|
14991
|
+
|
|
14992
|
+
/** Let the atlas go, to be built again from what is in hand the next time it is bound. */
|
|
14993
|
+
private forgetFixtureAtlas(): void {
|
|
14994
|
+
this.fixtureTexture?.destroy();
|
|
14995
|
+
this.fixtureTexture = null;
|
|
14996
|
+
this.fixtureView = null;
|
|
14997
|
+
}
|
|
14998
|
+
|
|
14999
|
+
/**
|
|
15000
|
+
* The fixture atlas's view, built from the profiles and cookies in hand if it is not already: the
|
|
15001
|
+
* profiles' rows at the bottom, the cookies' tiles on top. See `renderer.ts` and `fixtureAtlas.ts`.
|
|
15002
|
+
*
|
|
15003
|
+
* **`rgba16float`, not `rgba32float`**: a 32-bit float texture is not filterable in core WebGPU,
|
|
15004
|
+
* and binding one to a filtering sampler takes the whole bind group with it. Both tables want
|
|
15005
|
+
* linear filtering. `RENDER_ATTACHMENT` because `copyExternalImageToTexture` writes through one.
|
|
15006
|
+
*/
|
|
15007
|
+
private fixtureAtlasView(): GPUTextureView {
|
|
15008
|
+
if (this.fixtureView !== null) return this.fixtureView;
|
|
14036
15009
|
const { device } = this.surface;
|
|
14037
|
-
const
|
|
14038
|
-
const
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
}
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
{
|
|
14057
|
-
{ width:
|
|
15010
|
+
const layout = fixtureAtlasLayout(this.iesAtlas.height, this.cookieImages.length);
|
|
15011
|
+
const texture = device.createTexture({
|
|
15012
|
+
label: 'fixture.atlas',
|
|
15013
|
+
size: [layout.width, layout.height],
|
|
15014
|
+
format: 'rgba16float',
|
|
15015
|
+
usage: 0x4 | 0x2 | 0x10,
|
|
15016
|
+
});
|
|
15017
|
+
device.queue.writeTexture(
|
|
15018
|
+
{ texture },
|
|
15019
|
+
/* Converted, because `writeTexture` does not — see `environmentTexels`. */
|
|
15020
|
+
toHalfFloats(fixtureAtlasTexels(layout, this.iesAtlas)),
|
|
15021
|
+
{ bytesPerRow: layout.width * 8, rowsPerImage: layout.height },
|
|
15022
|
+
{ width: layout.width, height: layout.height, depthOrArrayLayers: 1 },
|
|
15023
|
+
);
|
|
15024
|
+
for (let tile = 0; tile < this.cookieImages.length; tile++) {
|
|
15025
|
+
const source = this.cookieImages[tile];
|
|
15026
|
+
if (source === undefined) continue;
|
|
15027
|
+
device.queue.copyExternalImageToTexture(
|
|
15028
|
+
{ source: source as GPUCopyExternalImageSource },
|
|
15029
|
+
{ texture, origin: { x: tile * COOKIE_TILE, y: 0 } },
|
|
15030
|
+
{ width: COOKIE_TILE, height: COOKIE_TILE },
|
|
14058
15031
|
);
|
|
14059
|
-
} else {
|
|
14060
|
-
for (let tile = 0; tile < tiles; tile++) {
|
|
14061
|
-
const source = images[tile];
|
|
14062
|
-
if (source === undefined) continue;
|
|
14063
|
-
device.queue.copyExternalImageToTexture(
|
|
14064
|
-
{ source: source as GPUCopyExternalImageSource },
|
|
14065
|
-
{ texture: this.cookieTexture, origin: { x: tile * COOKIE_TILE, y: 0 } },
|
|
14066
|
-
{ width: COOKIE_TILE, height: COOKIE_TILE },
|
|
14067
|
-
);
|
|
14068
|
-
}
|
|
14069
15032
|
}
|
|
14070
|
-
this.
|
|
14071
|
-
this.
|
|
15033
|
+
this.fixtureTexture = texture;
|
|
15034
|
+
this.fixtureView = texture.createView();
|
|
15035
|
+
this.iesRows = this.iesAtlas.height;
|
|
15036
|
+
this.iesPlanes = this.iesAtlas.planes;
|
|
15037
|
+
this.cookieTiles = this.cookieImages.length;
|
|
15038
|
+
return this.fixtureView;
|
|
14072
15039
|
}
|
|
14073
15040
|
|
|
14074
15041
|
/**
|
|
@@ -14085,6 +15052,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14085
15052
|
return field;
|
|
14086
15053
|
}
|
|
14087
15054
|
|
|
15055
|
+
/**
|
|
15056
|
+
* Hand a world's DriftLight volume to the renderer: every fixed light of a city, summed offline by
|
|
15057
|
+
* `bakeDenseField`, standing in past the frame's exact choice. Replaces any field made before, of
|
|
15058
|
+
* either kind; the scene calls `field.follow(buffer.complete, ...)` each frame as it would for
|
|
15059
|
+
* `createLightField`, and marks the lights the volume summed `inLightField` so none is counted
|
|
15060
|
+
* twice. See `driftLight/worldLightField.ts`.
|
|
15061
|
+
*/
|
|
15062
|
+
createWorldLightField(
|
|
15063
|
+
volume: DenseLightVolume,
|
|
15064
|
+
options: WorldLightFieldOptions = {},
|
|
15065
|
+
): WorldLightField {
|
|
15066
|
+
this.forgetLightFieldVolumes();
|
|
15067
|
+
const field = new WorldLightField(volume, options);
|
|
15068
|
+
this.lightField = field;
|
|
15069
|
+
return field;
|
|
15070
|
+
}
|
|
15071
|
+
|
|
14088
15072
|
/** Let go of the field and its volumes; the scene's lights are all exact again. */
|
|
14089
15073
|
disposeLightField(): void {
|
|
14090
15074
|
this.forgetLightFieldVolumes();
|
|
@@ -14111,12 +15095,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14111
15095
|
*/
|
|
14112
15096
|
private uploadLightField(): void {
|
|
14113
15097
|
const field = this.lightField;
|
|
14114
|
-
if (field === null || this.driftAtlas !== null || !field.ready || field.
|
|
15098
|
+
if (field === null || this.driftAtlas !== null || !field.ready || field.empty) {
|
|
14115
15099
|
return;
|
|
14116
15100
|
}
|
|
14117
15101
|
if (this.surface.lost) return;
|
|
14118
15102
|
const { device } = this.surface;
|
|
14119
|
-
const [ix, iy, iz] = field.
|
|
15103
|
+
const [ix, iy, iz] = field.indexDims;
|
|
14120
15104
|
const index = device.createTexture({
|
|
14121
15105
|
label: 'driftLight.index',
|
|
14122
15106
|
size: [ix, iy, iz],
|
|
@@ -14126,7 +15110,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14126
15110
|
});
|
|
14127
15111
|
device.queue.writeTexture(
|
|
14128
15112
|
{ texture: index },
|
|
14129
|
-
field.
|
|
15113
|
+
field.index,
|
|
14130
15114
|
{ bytesPerRow: ix * 4, rowsPerImage: iy },
|
|
14131
15115
|
[ix, iy, iz],
|
|
14132
15116
|
);
|
|
@@ -14159,45 +15143,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14159
15143
|
|
|
14160
15144
|
setIesProfiles(profiles: readonly PhotometricProfile[]): void {
|
|
14161
15145
|
if (this.surface.lost) return;
|
|
14162
|
-
|
|
14163
|
-
|
|
14164
|
-
/*
|
|
14165
|
-
* Reallocated when the row count changes, because a texture's size is immutable. A set of the
|
|
14166
|
-
* same size overwrites in place, which is the common case of a consumer swapping one fixture.
|
|
14167
|
-
*/
|
|
14168
|
-
if (this.iesTexture === null || this.iesTexture.height !== atlas.height) {
|
|
14169
|
-
this.iesTexture?.destroy();
|
|
14170
|
-
this.iesTexture = device.createTexture({
|
|
14171
|
-
label: 'ies.atlas',
|
|
14172
|
-
size: [atlas.width, atlas.height],
|
|
14173
|
-
/*
|
|
14174
|
-
* **`r16float`, not `r32float`, and that is a filtering constraint rather than a size
|
|
14175
|
-
* choice.** A 32-bit float texture is not filterable in core WebGPU — it needs the
|
|
14176
|
-
* optional `float32-filterable` feature — and binding one to a filtering sampler is a
|
|
14177
|
-
* validation failure that takes the whole bind group with it. This row *wants* linear
|
|
14178
|
-
* filtering: it is a curve sampled between two angles, which is the entire reason it is
|
|
14179
|
-
* not `NEAREST` like the froxel table beside it.
|
|
14180
|
-
*
|
|
14181
|
-
* Half precision holds a normalised intensity to about three decimal digits, which is far
|
|
14182
|
-
* finer than the eight-bit frame it ends up in. **What would make it wrong** is a profile
|
|
14183
|
-
* carrying absolute candela rather than a normalised curve, which would run out of range —
|
|
14184
|
-
* and `packIesAtlas` normalises for its own reasons.
|
|
14185
|
-
*/
|
|
14186
|
-
format: 'r16float',
|
|
14187
|
-
usage: 0x4 | 0x2, // TEXTURE_BINDING | COPY_DST
|
|
14188
|
-
});
|
|
14189
|
-
this.iesView = this.iesTexture.createView();
|
|
14190
|
-
}
|
|
14191
|
-
device.queue.writeTexture(
|
|
14192
|
-
{ texture: this.iesTexture },
|
|
14193
|
-
/* Converted, because `writeTexture` does not — see `environmentTexels` for what handing it
|
|
14194
|
-
the wrong width silently produces. */
|
|
14195
|
-
toHalfFloats(atlas.data),
|
|
14196
|
-
{ bytesPerRow: atlas.width * 2, rowsPerImage: atlas.height },
|
|
14197
|
-
{ width: atlas.width, height: atlas.height, depthOrArrayLayers: 1 },
|
|
14198
|
-
);
|
|
14199
|
-
this.iesRows = atlas.height;
|
|
14200
|
-
this.iesPlanes = atlas.planes;
|
|
15146
|
+
this.iesAtlas = packIesAtlas(profiles);
|
|
15147
|
+
this.forgetFixtureAtlas();
|
|
14201
15148
|
this.rebuildFlatBindGroup();
|
|
14202
15149
|
}
|
|
14203
15150
|
|
|
@@ -14418,6 +15365,33 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14418
15365
|
* The occlusion and bloom targets are half size. Both are blurs read back at full size, so a
|
|
14419
15366
|
* full-resolution intermediate buys nothing a bilinear fetch does not already give.
|
|
14420
15367
|
*/
|
|
15368
|
+
/**
|
|
15369
|
+
* The medium's march group, holding the shadow views it reads — built with the composite, and
|
|
15370
|
+
* again when the sun's texture grows its glass layers and every view of the old one is stale.
|
|
15371
|
+
*
|
|
15372
|
+
* **The stand-in has to read as unoccluded and not as absent**, which is `emptyTexture.ts`'s
|
|
15373
|
+
* point and `createLightVolumeBindGroup`'s: `occlusion()` calls any stored depth below the
|
|
15374
|
+
* receiver's a blocker — so a black stand-in would put the whole medium in shadow, which is a
|
|
15375
|
+
* picture rather than an error. `volumeBlank` is the white one-texel texture built for this.
|
|
15376
|
+
*/
|
|
15377
|
+
private buildMediumBindGroup(): GPUBindGroup | null {
|
|
15378
|
+
if (this.resolvedDepthView === null) return null;
|
|
15379
|
+
return this.surface.device.createBindGroup({
|
|
15380
|
+
label: 'post.mediumBindGroup',
|
|
15381
|
+
layout: this.mediumLayout,
|
|
15382
|
+
entries: [
|
|
15383
|
+
{
|
|
15384
|
+
binding: MEDIUM_UNIFORMS,
|
|
15385
|
+
resource: { buffer: this.mediumUniforms, size: MEDIUM_FRAG_SIZE },
|
|
15386
|
+
},
|
|
15387
|
+
{ binding: MEDIUM_TEXTURES.uDepth.texture, resource: this.resolvedDepthView },
|
|
15388
|
+
{ binding: MEDIUM_TEXTURES.uDepth.sampler, resource: this.postDepthSampler },
|
|
15389
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows.texture, resource: this.sunArrayView },
|
|
15390
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows.sampler, resource: this.volumeShadowSampler },
|
|
15391
|
+
],
|
|
15392
|
+
});
|
|
15393
|
+
}
|
|
15394
|
+
|
|
14421
15395
|
private ensureComposite(width: number, height: number): void {
|
|
14422
15396
|
if (!this.hasComposite) return;
|
|
14423
15397
|
if (this.sceneColor !== null && this.frameWidth === width && this.frameHeight === height)
|
|
@@ -14471,13 +15445,39 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14471
15445
|
* the allocation the house rules are about. A frame that never refracts pays the memory and
|
|
14472
15446
|
* takes no copy.
|
|
14473
15447
|
*/
|
|
15448
|
+
/*
|
|
15449
|
+
* **At the output size and the reconstructed picture's format while reconstructing**, because
|
|
15450
|
+
* every refracting draw is then blended, drawn after the upscale, and reads the reconstructed
|
|
15451
|
+
* picture rather than the render. See `runLate`.
|
|
15452
|
+
*/
|
|
15453
|
+
const reconstructed = this.reconstructionWanted;
|
|
14474
15454
|
this.refractSnapshot = device.createTexture({
|
|
14475
15455
|
label: 'refract.snapshot',
|
|
14476
|
-
size:
|
|
14477
|
-
|
|
14478
|
-
|
|
15456
|
+
size: reconstructed
|
|
15457
|
+
? [Math.max(1, this.surface.canvas.width), Math.max(1, this.surface.canvas.height)]
|
|
15458
|
+
: [width, height],
|
|
15459
|
+
format: reconstructed ? RECON_HISTORY_FORMAT : format,
|
|
15460
|
+
/*
|
|
15461
|
+
* **Every level, so glass can read a blurrier one**, and a render attachment so `SnapshotMips`
|
|
15462
|
+
* can fill them. Plain refraction reads level 0, as it always has. See `glass.ts`.
|
|
15463
|
+
*/
|
|
15464
|
+
mipLevelCount:
|
|
15465
|
+
Math.floor(
|
|
15466
|
+
Math.log2(
|
|
15467
|
+
reconstructed
|
|
15468
|
+
? Math.max(1, this.surface.canvas.width, this.surface.canvas.height)
|
|
15469
|
+
: Math.max(1, width, height),
|
|
15470
|
+
),
|
|
15471
|
+
) + 1,
|
|
15472
|
+
usage: 0x4 | 0x2 | 0x10, // TEXTURE_BINDING | COPY_DST | RENDER_ATTACHMENT
|
|
14479
15473
|
});
|
|
14480
15474
|
this.refractSnapshotView = this.refractSnapshot.createView();
|
|
15475
|
+
this.refractMips = new SnapshotMips(
|
|
15476
|
+
device,
|
|
15477
|
+
this.pipelines,
|
|
15478
|
+
this.refractSnapshot,
|
|
15479
|
+
reconstructed ? RECON_HISTORY_FORMAT : format,
|
|
15480
|
+
);
|
|
14481
15481
|
/*
|
|
14482
15482
|
* **Every cached flat bind group is stale the moment this view changes**, and the failure is
|
|
14483
15483
|
* silent: `flatTextures` resolves `uRefractScene` to the one-texel white stand-in when there is
|
|
@@ -14619,10 +15619,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14619
15619
|
* that range — a step of 1/255 in how much scene shows through, below what a viewer can see
|
|
14620
15620
|
* against a blended surface.
|
|
14621
15621
|
*/
|
|
15622
|
+
/* At the output size while reconstructing, where the set is replayed after the upscale. */
|
|
15623
|
+
const oitSize: [number, number] = this.reconstructionWanted
|
|
15624
|
+
? [Math.max(1, this.surface.canvas.width), Math.max(1, this.surface.canvas.height)]
|
|
15625
|
+
: [width, height];
|
|
14622
15626
|
this.oitAccum?.destroy();
|
|
14623
15627
|
this.oitAccum = device.createTexture({
|
|
14624
15628
|
label: 'post.oitAccum',
|
|
14625
|
-
size:
|
|
15629
|
+
size: oitSize,
|
|
14626
15630
|
format: 'rgba16float',
|
|
14627
15631
|
usage: USAGE,
|
|
14628
15632
|
});
|
|
@@ -14630,7 +15634,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14630
15634
|
this.oitReveal?.destroy();
|
|
14631
15635
|
this.oitReveal = device.createTexture({
|
|
14632
15636
|
label: 'post.oitReveal',
|
|
14633
|
-
size:
|
|
15637
|
+
size: oitSize,
|
|
14634
15638
|
format: 'r8unorm',
|
|
14635
15639
|
usage: USAGE,
|
|
14636
15640
|
});
|
|
@@ -14644,7 +15648,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14644
15648
|
* An empty list is a real answer for a frame too small to hold one, and `runBloom` reads it
|
|
14645
15649
|
* as no bloom rather than as a target it should have had.
|
|
14646
15650
|
*/
|
|
14647
|
-
|
|
15651
|
+
/* The resolved picture's size while reconstructing, as the order-independent pair above is. */
|
|
15652
|
+
this.bloomSourceWidth = oitSize[0];
|
|
15653
|
+
this.bloomSourceHeight = oitSize[1];
|
|
15654
|
+
this.bloomLevels = bloomLevelSizes(oitSize[0], oitSize[1]).map((size, index) => {
|
|
14648
15655
|
const texture = device.createTexture({
|
|
14649
15656
|
label: `post.bloom${index}`,
|
|
14650
15657
|
size: [size.width, size.height],
|
|
@@ -14735,42 +15742,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14735
15742
|
...entry(AO_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
14736
15743
|
],
|
|
14737
15744
|
});
|
|
14738
|
-
|
|
14739
|
-
* The medium's two groups, holding the shadow views the march reads.
|
|
14740
|
-
*
|
|
14741
|
-
* **The stand-in has to read as unoccluded and not as absent**, which is `emptyTexture.ts`'s
|
|
14742
|
-
* point and `createLightVolumeBindGroup`'s: the dynamic layer is sampled unconditionally, and
|
|
14743
|
-
* `occlusion()` calls any stored depth below the receiver's a blocker — so a black stand-in
|
|
14744
|
-
* would put the whole medium in shadow, which is a picture rather than an error. `volumeBlank`
|
|
14745
|
-
* is the white one-texel texture already built for exactly this.
|
|
14746
|
-
*/
|
|
14747
|
-
const shadowPair = (
|
|
14748
|
-
b: { texture: number; sampler: number },
|
|
14749
|
-
view: GPUTextureView | null,
|
|
14750
|
-
): GPUBindGroupEntry[] => [
|
|
14751
|
-
{ binding: b.texture, resource: view ?? this.volumeBlank.view },
|
|
14752
|
-
{
|
|
14753
|
-
binding: b.sampler,
|
|
14754
|
-
resource: view === null ? this.volumeBlank.sampler : this.volumeShadowSampler,
|
|
14755
|
-
},
|
|
14756
|
-
];
|
|
14757
|
-
this.mediumBindGroup =
|
|
14758
|
-
this.resolvedDepthView === null
|
|
14759
|
-
? null
|
|
14760
|
-
: device.createBindGroup({
|
|
14761
|
-
label: 'post.mediumBindGroup',
|
|
14762
|
-
layout: this.mediumLayout,
|
|
14763
|
-
entries: [
|
|
14764
|
-
{
|
|
14765
|
-
binding: MEDIUM_UNIFORMS,
|
|
14766
|
-
resource: { buffer: this.mediumUniforms, size: MEDIUM_FRAG_SIZE },
|
|
14767
|
-
},
|
|
14768
|
-
...entry(MEDIUM_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
14769
|
-
...shadowPair(MEDIUM_TEXTURES.uStaticShadowMap, this.shadowView),
|
|
14770
|
-
...shadowPair(MEDIUM_TEXTURES.uPeeledShadowMap, this.peelView),
|
|
14771
|
-
...shadowPair(MEDIUM_TEXTURES.uDynamicShadowMap, this.dynamicView),
|
|
14772
|
-
],
|
|
14773
|
-
});
|
|
15745
|
+
this.mediumBindGroup = this.buildMediumBindGroup();
|
|
14774
15746
|
this.mediumUpsampleGroup =
|
|
14775
15747
|
this.resolvedDepthView === null || this.mediumTargetView === null
|
|
14776
15748
|
? null
|
|
@@ -14852,13 +15824,31 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14852
15824
|
/* The volume's group holds the depth snapshot, which is one of the targets just replaced.
|
|
14853
15825
|
Keeping the old group would clamp every beam against a destroyed texture. */
|
|
14854
15826
|
this.lightVolumeBindGroup = this.buildLightVolumeBindGroup();
|
|
14855
|
-
|
|
14856
|
-
|
|
14857
|
-
|
|
14858
|
-
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
15827
|
+
this.rebuildBloomGroups();
|
|
15828
|
+
this.writeBloomBlocks();
|
|
15829
|
+
}
|
|
15830
|
+
|
|
15831
|
+
/**
|
|
15832
|
+
* One group per source the bloom chain reads, in the order `runBloom` indexes them: the picture
|
|
15833
|
+
* first, then every level. The prefilter reads the picture; the downsample into level `i` reads
|
|
15834
|
+
* level `i - 1`, which is group `i`; the upsample into level `i - 1` reads level `i`, which is
|
|
15835
|
+
* group `i + 1`.
|
|
15836
|
+
*
|
|
15837
|
+
* **While reconstructing the picture is the resolved one**, which `ensureReconstruction` makes
|
|
15838
|
+
* after this runs and rebuilds these over. The scene target is the jittered render then: a bright
|
|
15839
|
+
* thing smaller than a render texel is caught in some phases and missed in others, and its halo,
|
|
15840
|
+
* wide and over everything round it, flashed with it — a night street's panels breathing a few
|
|
15841
|
+
* levels of luma frame to frame. Built at the drawing buffer's size, which `ensureComposite`
|
|
15842
|
+
* already sized the pyramid for.
|
|
15843
|
+
*/
|
|
15844
|
+
private rebuildBloomGroups(): void {
|
|
15845
|
+
const picture =
|
|
15846
|
+
this.reconstructionWanted && this.reconShownView !== null
|
|
15847
|
+
? this.reconShownView
|
|
15848
|
+
: this.sceneColorView;
|
|
15849
|
+
if (picture === null) return;
|
|
15850
|
+
const { device } = this.surface;
|
|
15851
|
+
this.bloomGroups = [picture, ...this.bloomLevels.map((level) => level.view)].map(
|
|
14862
15852
|
(view, index) =>
|
|
14863
15853
|
device.createBindGroup({
|
|
14864
15854
|
label: `post.bloomGroup${index}`,
|
|
@@ -14868,11 +15858,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14868
15858
|
binding: BLOOM_UNIFORMS,
|
|
14869
15859
|
resource: { buffer: this.bloomUniforms, size: BLOOM_STAGE_SIZE },
|
|
14870
15860
|
},
|
|
14871
|
-
|
|
15861
|
+
{ binding: BLOOM_TEXTURES.uSource.texture, resource: view },
|
|
15862
|
+
{ binding: BLOOM_TEXTURES.uSource.sampler, resource: this.postSampler },
|
|
14872
15863
|
],
|
|
14873
15864
|
}),
|
|
14874
15865
|
);
|
|
14875
|
-
this.writeBloomBlocks();
|
|
14876
15866
|
}
|
|
14877
15867
|
|
|
14878
15868
|
/**
|
|
@@ -14890,12 +15880,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14890
15880
|
if (levels.length === 0) return;
|
|
14891
15881
|
const slot = (index: number): number => (index * BLOOM_SLOT) / 4;
|
|
14892
15882
|
|
|
14893
|
-
/* The prefilter reads the *
|
|
15883
|
+
/* The prefilter reads the *picture*, so its texel is the picture's rather than a level's.
|
|
14894
15884
|
`uTexel`, not `uTexelSize`, and the block carries no strength at all: the composite
|
|
14895
15885
|
applies that when it adds the result back. Both were written wrong here first. */
|
|
14896
15886
|
const prefilter = (name: string): number => this.postField(BLOOM_PREFILTER_FIELDS, name);
|
|
14897
|
-
f[slot(0) + prefilter('uTexel')] = 1 / Math.max(1, this.
|
|
14898
|
-
f[slot(0) + prefilter('uTexel') + 1] = 1 / Math.max(1, this.
|
|
15887
|
+
f[slot(0) + prefilter('uTexel')] = 1 / Math.max(1, this.bloomSourceWidth);
|
|
15888
|
+
f[slot(0) + prefilter('uTexel') + 1] = 1 / Math.max(1, this.bloomSourceHeight);
|
|
14899
15889
|
/* In scene units, which is why an HDR target is what makes a threshold above 1 mean
|
|
14900
15890
|
anything: against a clamped buffer nothing is ever brighter than white. */
|
|
14901
15891
|
f[slot(0) + prefilter('uThreshold')] = this.bloomThresholdSet ?? this.quality.bloomThreshold;
|
|
@@ -15002,6 +15992,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15002
15992
|
* the 2026-08-13 rule asks for — one decision, bound twice.
|
|
15003
15993
|
*/
|
|
15004
15994
|
dispose(): void {
|
|
15995
|
+
this.motionPass?.dispose();
|
|
15996
|
+
this.motionPass = null;
|
|
15997
|
+
this.latePass?.dispose();
|
|
15998
|
+
this.latePass = null;
|
|
15005
15999
|
for (const texture of this.retiredTextures) texture.destroy();
|
|
15006
16000
|
this.retiredTextures.length = 0;
|
|
15007
16001
|
const passDevice = this.passDevice();
|
|
@@ -15018,6 +16012,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15018
16012
|
this.fieldComposer = null;
|
|
15019
16013
|
this.driftIndex?.destroy();
|
|
15020
16014
|
this.driftAtlas?.destroy();
|
|
16015
|
+
this.forgetFixtureAtlas();
|
|
16016
|
+
this.sunTint?.texture.destroy();
|
|
16017
|
+
this.sunTint = null;
|
|
16018
|
+
this.glassTint?.dispose();
|
|
16019
|
+
this.glassTint = null;
|
|
16020
|
+
this.retiredSunTexture?.destroy();
|
|
16021
|
+
this.retiredSunTexture = null;
|
|
15021
16022
|
|
|
15022
16023
|
this.pass = null;
|
|
15023
16024
|
this.encoder = null;
|