@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
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { mat4 } from 'gl-matrix';
|
|
2
2
|
import { probeFaceRange } from '../../reflectionProbe.js';
|
|
3
|
-
import { TEMPORAL_HISTORY_BLEND, TemporalHistory, jitterOffset, jitterProjection, } from '../../temporalAa.js';
|
|
3
|
+
import { TEMPORAL_FLICKER, TEMPORAL_HISTORY_BLEND, TemporalHistory, flickerRule, jitterOffset, jitterProjection, } from '../../temporalAa.js';
|
|
4
4
|
import { OIT_MULTISAMPLE_REFUSAL } from '../../orderIndependent.js';
|
|
5
5
|
import { TranslucentQueue } from '../../translucentQueue.js';
|
|
6
|
-
import { DEPTH_CLEAR,
|
|
6
|
+
import { DEPTH_CLEAR, MAX_DEPTH_LAYER, REVERSED_DEPTH } from '../../depthConvention.js';
|
|
7
7
|
import { RECONSTRUCTION_MULTISAMPLE_REFUSAL, reconRenderSize } from '../../recon/frameSizes.js';
|
|
8
8
|
import { jitterOffset as jitterOffsetFor, reconJitterPhases } from '../../recon/jitter.js';
|
|
9
9
|
import { RECON_HISTORY_FORMAT, RECON_PARAM_FLOATS, RECON_WORKGROUP, reconResolveWgsl, } from '../../shaders/recon/resolve.wgsl.js';
|
|
10
|
-
import {
|
|
10
|
+
import { MotionPass } from './motionPass.js';
|
|
11
|
+
import { LatePass } from './latePass.js';
|
|
12
|
+
import { SnapshotMips } from './snapshotMips.js';
|
|
13
|
+
import { resolveGlass } from '../../glass.js';
|
|
14
|
+
import { routesLate, tracksMotion } from '../../recon/lateRouting.js';
|
|
15
|
+
import { frameOfChange, keepsPrevious, pairedInstances } from '../../recon/changeFrames.js';
|
|
16
|
+
import { INSTANCE_STRIDE } from '../../instances.js';
|
|
11
17
|
import { DEFAULT_DISOCCLUSION } from '../../recon/disocclusion.js';
|
|
12
18
|
import { DEFAULT_RECON_QUALITY } from '../../recon/resolve.js';
|
|
19
|
+
import { MOVER_MOTION, MOVER_TWICE, advanceMover, claimRendererId, isMover, } from '../../recon/mover.js';
|
|
13
20
|
import { clamp } from '../../../math/scalar.js';
|
|
14
21
|
import { Camera } from '../../camera.js';
|
|
15
22
|
import { PickableSet } from '../../pickable.js';
|
|
@@ -29,6 +36,8 @@ import { ProbeSweeps } from '../../probeSweeps.js';
|
|
|
29
36
|
import { ggxMaxLevelFor, irradianceLevelFor, octahedralEdgeFor } from '../../prefilterEnvMap.js';
|
|
30
37
|
import { environmentTexels } from './environmentTexels.js';
|
|
31
38
|
import { packIesAtlas } from '../../iesProfile.js';
|
|
39
|
+
import { fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.js';
|
|
40
|
+
import { resolveSurfaceScene } from '../../surfaceScene.js';
|
|
32
41
|
import { COOKIE_TILE } from '../../lightBudget.js';
|
|
33
42
|
import { FULL_LIGHT_BUDGET } from '../../uniformVectorBudget.js';
|
|
34
43
|
import { toHalfFloats } from '../../halfFloat.js';
|
|
@@ -38,6 +47,7 @@ import { areaShadowLayerCount, castingRange, createResolvedAreaShadows, firstAre
|
|
|
38
47
|
import { createBakeScratch, runPointShadowBakes, selectCastingLights, } from '../../pointShadowBudget.js';
|
|
39
48
|
import { POINT_SHADOW_SAMPLER, PointShadowSystem, } from '../../pointShadowSystem.js';
|
|
40
49
|
import { GpuPointShadowArray, GpuPointShadowMap } from './pointShadowPass.js';
|
|
50
|
+
import { OCTAHEDRAL_EDGE, strideLayers } from '../../pointShadowArray.js';
|
|
41
51
|
import { createPanelBindGroup, createPanelBindGroupLayout, createPanelCorners, panelPipeline, PANEL_FRAG_FIELDS, PANEL_FRAG_SIZE, PANEL_VERTEX_COUNT, PANEL_VERT_FIELDS, PANEL_VERT_SIZE, } from './panelPass.js';
|
|
42
52
|
import { createGpuTextCube, createTextBindGroup, createTextBindGroupLayout, GpuText, textPipeline, TEXT_FRAG_FIELDS, TEXT_FRAG_SIZE, TEXT_VERT_FIELDS, TEXT_VERT_SIZE, } from './textPass.js';
|
|
43
53
|
import { deviceSnappedCellSize, deviceSnappedOrigin } from '../../textLayout.js';
|
|
@@ -66,6 +76,10 @@ import { createScatterDeform, resolveScatterDeform, } from '../../scatterDeform.
|
|
|
66
76
|
import { resolvePointLights } from '../../lightBudget.js';
|
|
67
77
|
import { resolveRenderQuality } from '../../renderQuality.js';
|
|
68
78
|
import { DEFAULT_GLOBAL_MEDIUM, mediumActive, mediumTargetSize, WEAK_GPU_MEDIUM_STEPS, resolveGlobalMedium, } from '../../globalMedium.js';
|
|
79
|
+
import { castsDepth, glassOf } from '../../shadowCasters.js';
|
|
80
|
+
import { GlassCasterList } from '../../glassCasters.js';
|
|
81
|
+
import { GpuGlassTint } from './glassTintPass.js';
|
|
82
|
+
import { SUN_DYNAMIC_LAYER, SUN_GLASS_MOVING_LAYER, SUN_GLASS_STATIC_LAYER, SUN_PEELED_LAYER, SUN_STATIC_LAYER, } from '../../shadowMap.js';
|
|
69
83
|
import { VERTEX_LAYOUT, createGpuMesh, createGpuMeshIncremental } from './buffers.js';
|
|
70
84
|
import { SkinPaletteRing } from './skinPaletteRing.js';
|
|
71
85
|
import { DEPTH_FORMAT, FLAT_VERT_FIELDS, FLAT_VERT_SIZE, createFlatBindGroup, createFlatBindGroupLayout, flatFragmentBindings, flatVariant, flatVertexBindings, flatPipeline, flatPipelineAsync, } from './flatPass.js';
|
|
@@ -84,6 +98,7 @@ import { LIGHT_VOLUME_VERT_FIELDS, LIGHT_VOLUME_VERT_SIZE, createLightVolumeBind
|
|
|
84
98
|
import { aimReflection, createGpuReflection } from './reflectionPass.js';
|
|
85
99
|
import { FrameBudget } from '../budget.js';
|
|
86
100
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.js';
|
|
101
|
+
import { FOG_RECEDE, fogModeOf } from '../drawFog.js';
|
|
87
102
|
import { DYNAMIC_ALIGNMENT as DYNAMIC_UNIFORM_ALIGNMENT, UniformRing } from './uniformRing.js';
|
|
88
103
|
import { DecalQueue, MAX_DRAWN_DECALS } from '../../decalQueue.js';
|
|
89
104
|
import { DistanceFieldScene } from '../../gi/fieldScene.js';
|
|
@@ -110,6 +125,8 @@ import { decalScissor } from '../../decalProjector.js';
|
|
|
110
125
|
import { createCommandPool, resetPool, takeCommand } from './drawCommand.js';
|
|
111
126
|
import { RESOURCE_COUNT, createArena, createFlushSchedule, keptNodes, maskOf, nodeCount, nodeState, nodeVerb, recordNode, resetArena, schedule, scheduleFlush, scratchFor, } from '../../frame/index.js';
|
|
112
127
|
import { createFrustum, frustumFromViewProjection } from '../../../math/frustum.js';
|
|
128
|
+
import { batchBoxVisible, cullsInstances } from '../../instanceCull.js';
|
|
129
|
+
import { InstanceCullPass, MAX_CULL_SLOTS } from './instanceCullPass.js';
|
|
113
130
|
import { OcclusionBuffer } from '../../occlusion.js';
|
|
114
131
|
import { boundsVisible } from '../../visibility.js';
|
|
115
132
|
import { createPassRegistry, drainRegistry, passAt, registerIn, unregisterIn } from '../../pass.js';
|
|
@@ -119,6 +136,7 @@ import { computeAt, createComputeRegistry, } from '../../compute.js';
|
|
|
119
136
|
import { GpuInstancedBatch } from './instanced.js';
|
|
120
137
|
import { cutoutOf } from '../../cutoutCaster.js';
|
|
121
138
|
import { LightField } from '../../driftLight/lightField.js';
|
|
139
|
+
import { WorldLightField } from '../../driftLight/worldLightField.js';
|
|
122
140
|
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.js';
|
|
123
141
|
/**
|
|
124
142
|
* How many draws one frame may make before the ring is full.
|
|
@@ -762,6 +780,24 @@ function padded(direction) {
|
|
|
762
780
|
SCRATCH_VEC4[3] = 0;
|
|
763
781
|
return SCRATCH_VEC4;
|
|
764
782
|
}
|
|
783
|
+
/** The sun texture's usage: attached, sampled, and copied both ways when glass grows it. */
|
|
784
|
+
const SUN_USAGE = 0x10 | 0x4 | 0x1 | 0x2; // RENDER_ATTACHMENT | TEXTURE_BINDING | COPY_SRC | COPY_DST
|
|
785
|
+
/** Every view of the sun's texture a pass reads or attaches, for `maps` of its layers. */
|
|
786
|
+
function sunViewsOf(texture, maps, layers) {
|
|
787
|
+
const layer = (index) => texture.createView({ dimension: '2d', baseArrayLayer: index, arrayLayerCount: 1 });
|
|
788
|
+
return {
|
|
789
|
+
array: texture.createView({ dimension: '2d-array' }),
|
|
790
|
+
staticArray: texture.createView({
|
|
791
|
+
dimension: '2d-array',
|
|
792
|
+
baseArrayLayer: SUN_STATIC_LAYER,
|
|
793
|
+
arrayLayerCount: 1,
|
|
794
|
+
}),
|
|
795
|
+
static: layer(SUN_STATIC_LAYER),
|
|
796
|
+
dynamic: maps > SUN_DYNAMIC_LAYER ? layer(SUN_DYNAMIC_LAYER) : null,
|
|
797
|
+
peel: maps > SUN_PEELED_LAYER ? layer(SUN_PEELED_LAYER) : null,
|
|
798
|
+
glass: layers > maps ? [layer(SUN_GLASS_STATIC_LAYER), layer(SUN_GLASS_MOVING_LAYER)] : null,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
765
801
|
export class WebGPURenderer {
|
|
766
802
|
/**
|
|
767
803
|
* Real timestamp queries, where the adapter offers them.
|
|
@@ -863,6 +899,8 @@ export class WebGPURenderer {
|
|
|
863
899
|
* caller who passed null gets — rather than reading a slot this frame has already re-let.
|
|
864
900
|
*/
|
|
865
901
|
skinPaletteSlot = -1;
|
|
902
|
+
/** The palette `skinPaletteSlot` was uploaded from, for a skinned mover to copy. See `Mover`. */
|
|
903
|
+
skinPaletteData = null;
|
|
866
904
|
surface;
|
|
867
905
|
/** The encoder for the frame in flight, or null between frames. */
|
|
868
906
|
encoder = null;
|
|
@@ -870,6 +908,27 @@ export class WebGPURenderer {
|
|
|
870
908
|
meshPresent = new Map();
|
|
871
909
|
pass = null;
|
|
872
910
|
pipelines;
|
|
911
|
+
/**
|
|
912
|
+
* The pipelines a reconstructing frame's blended draws are built for: the reconstructed picture's
|
|
913
|
+
* format at one sample, which is where `LatePass` draws them. Aliased to `pipelines` when the
|
|
914
|
+
* world already lands on exactly that, as `overlayPipelines` is.
|
|
915
|
+
*/
|
|
916
|
+
latePipelines;
|
|
917
|
+
/** The blended draws of a reconstructing frame, drawn after the upscale. See `LatePass`. */
|
|
918
|
+
latePass = null;
|
|
919
|
+
/** A late draw asked to refract: `runLate` copies the reconstructed picture before replaying. */
|
|
920
|
+
lateRefractWanted = false;
|
|
921
|
+
/** The refraction copy's mip chain, recorded after each copy: what frosted glass reads. */
|
|
922
|
+
refractMips = null;
|
|
923
|
+
/**
|
|
924
|
+
* The refraction copy's sampler: filtering, and **between levels as well**, so a frosted pane's
|
|
925
|
+
* level falls smoothly between two blurs rather than stepping from one to the next.
|
|
926
|
+
*/
|
|
927
|
+
refractSampler = null;
|
|
928
|
+
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
929
|
+
glassScratch = { transmission: 0, frost: 0, tint: [1, 1, 1] };
|
|
930
|
+
/** `issueCommand` bound once, so the late replay allocates no closure per frame. */
|
|
931
|
+
issueBound;
|
|
873
932
|
/**
|
|
874
933
|
* The same pipelines again, built for the canvas, for anything drawn after `endFrame`.
|
|
875
934
|
*
|
|
@@ -1242,6 +1301,12 @@ export class WebGPURenderer {
|
|
|
1242
1301
|
* what the layout already declares.
|
|
1243
1302
|
*/
|
|
1244
1303
|
bloomGroups = [];
|
|
1304
|
+
/**
|
|
1305
|
+
* The picture the bloom reads, and its size: the scene target, or while reconstructing the
|
|
1306
|
+
* resolved picture at the drawing buffer's size. See `rebuildBloomGroups`.
|
|
1307
|
+
*/
|
|
1308
|
+
bloomSourceWidth = 1;
|
|
1309
|
+
bloomSourceHeight = 1;
|
|
1245
1310
|
frameWidth = 1;
|
|
1246
1311
|
frameHeight = 1;
|
|
1247
1312
|
/**
|
|
@@ -1393,13 +1458,25 @@ export class WebGPURenderer {
|
|
|
1393
1458
|
* the *unsharpened* result — sharpening into the history would sharpen a sharpened picture every
|
|
1394
1459
|
* frame, which is the halo the reference's own clamp exists to prevent, compounded.
|
|
1395
1460
|
*
|
|
1396
|
-
* The motion target is render size and carries a drawn object's own motion where
|
|
1397
|
-
* one
|
|
1398
|
-
*
|
|
1399
|
-
* exact
|
|
1461
|
+
* The motion target is render size and carries a drawn object's own motion where the motion pass
|
|
1462
|
+
* wrote one — a draw that named its mover, or passed last frame's matrix. Every other texel is
|
|
1463
|
+
* cleared, and the resolve takes the camera's motion there, derived from the depth and the
|
|
1464
|
+
* reprojection: exact for anything that did not move. See `MotionPass`.
|
|
1400
1465
|
*/
|
|
1401
1466
|
reconHistories = [null, null];
|
|
1402
1467
|
reconHistoryViews = [null, null];
|
|
1468
|
+
/**
|
|
1469
|
+
* Each output pixel's flicker record, a pair swapped with the histories: the temporal resolve's
|
|
1470
|
+
* anti-flicker, over the reconstruction's own period (`recon/resolve.ts`).
|
|
1471
|
+
*/
|
|
1472
|
+
reconFlickers = [null, null];
|
|
1473
|
+
reconFlickerViews = [null, null];
|
|
1474
|
+
/** Each output pixel's held depth swing, a pair of its own: see `recon/resolve.ts`. */
|
|
1475
|
+
reconSwings = [null, null];
|
|
1476
|
+
reconSwingViews = [null, null];
|
|
1477
|
+
/** The anti-flicker's rule for the period last asked for, kept so a frame makes none. */
|
|
1478
|
+
reconFlickerPhases = 0;
|
|
1479
|
+
reconFlickerRule = TEMPORAL_FLICKER;
|
|
1403
1480
|
reconWrite = 0;
|
|
1404
1481
|
reconHasHistory = false;
|
|
1405
1482
|
reconShown = null;
|
|
@@ -1418,40 +1495,24 @@ export class WebGPURenderer {
|
|
|
1418
1495
|
reconGroups = [null, null];
|
|
1419
1496
|
reconSharpenGroups = [null, null];
|
|
1420
1497
|
reconRushGroup = null;
|
|
1421
|
-
/**
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
motionCount = 0;
|
|
1431
|
-
motionLayout = null;
|
|
1432
|
-
/**
|
|
1433
|
-
* One pipeline per vertex stride, because a stride is a property of the *mesh*.
|
|
1434
|
-
*
|
|
1435
|
-
* Which optional attributes a mesh supplied decides how far apart its positions are, and this
|
|
1436
|
-
* pass reads nothing but positions — so two meshes drawn by the same shader still need two
|
|
1437
|
-
* vertex layouts. A map rather than a cache key on the pipeline cache: there is one shader here
|
|
1438
|
-
* and a handful of strides in any scene.
|
|
1439
|
-
*/
|
|
1440
|
-
motionPipelines = new Map();
|
|
1441
|
-
motionModule = null;
|
|
1442
|
-
motionPipelineLayout = null;
|
|
1443
|
-
motionFrameBuffer = null;
|
|
1444
|
-
motionDrawBuffer = null;
|
|
1445
|
-
motionDrawCapacity = 0;
|
|
1446
|
-
motionGroup = null;
|
|
1447
|
-
motionFrameStaging = new Float32Array(MOTION_FRAME_FLOATS);
|
|
1448
|
-
motionDrawStaging = new Uint8Array(0);
|
|
1498
|
+
/** The motion pass, made with the reconstruction's targets. See `MotionPass`. */
|
|
1499
|
+
motionPass = null;
|
|
1500
|
+
/** This renderer's identity to a `Mover`. See `claimRendererId`. */
|
|
1501
|
+
moverRenderer = claimRendererId();
|
|
1502
|
+
/** Frames begun, so a mover can tell last frame from any other. Advanced in `beginFrame`. */
|
|
1503
|
+
moverFrame = 0;
|
|
1504
|
+
/** Between `beginFrame` and `endFrame`. A change outside it is the next frame's. */
|
|
1505
|
+
inFrame = false;
|
|
1506
|
+
warnedMoverTwice = false;
|
|
1449
1507
|
/** The scene's own clip transform with the generated shaders' y negation folded in. */
|
|
1450
1508
|
motionRaster = mat4.create();
|
|
1451
1509
|
reconSizeKey = '';
|
|
1452
1510
|
/** Last frame's raw view-projection and its inverse, and the two eyes. */
|
|
1453
1511
|
reconPreviousRawViewProj = mat4.create();
|
|
1454
1512
|
reconInverseViewProj = mat4.create();
|
|
1513
|
+
/** The inverse and the reprojection, multiplied in double precision. See `runReconstruction`. */
|
|
1514
|
+
reconInverse64 = new Float64Array(16);
|
|
1515
|
+
reconReprojection64 = new Float64Array(16);
|
|
1455
1516
|
reconPreviousInverseViewProj = mat4.create();
|
|
1456
1517
|
reconEye = new Float32Array(3);
|
|
1457
1518
|
reconPreviousEye = new Float32Array(3);
|
|
@@ -1597,6 +1658,8 @@ export class WebGPURenderer {
|
|
|
1597
1658
|
/** Said once for the whole renderer. See `refuseOitMultisampled`. */
|
|
1598
1659
|
oitMultisampleRefused = false;
|
|
1599
1660
|
oitReplaying = false;
|
|
1661
|
+
/** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
|
|
1662
|
+
surfaceScene = new Float32Array(4);
|
|
1600
1663
|
/** Which buffer the replay is filling, which the pipeline key must carry. */
|
|
1601
1664
|
oitMode = 'none';
|
|
1602
1665
|
/**
|
|
@@ -1692,6 +1755,7 @@ export class WebGPURenderer {
|
|
|
1692
1755
|
refraction: 0,
|
|
1693
1756
|
refractTint: undefined,
|
|
1694
1757
|
thicknessM: 0,
|
|
1758
|
+
glass: undefined,
|
|
1695
1759
|
};
|
|
1696
1760
|
reflectionPassActive = false;
|
|
1697
1761
|
/** Said once: a mirror asked for outside a frame is a call-order fault, not a device limit. */
|
|
@@ -1871,6 +1935,8 @@ export class WebGPURenderer {
|
|
|
1871
1935
|
return this.ringList;
|
|
1872
1936
|
}
|
|
1873
1937
|
flushRings() {
|
|
1938
|
+
/* Culls first: a submit that follows may carry a draw reading what they write. */
|
|
1939
|
+
this.instanceCulls?.flush();
|
|
1874
1940
|
for (const ring of this.rings())
|
|
1875
1941
|
ring.flush();
|
|
1876
1942
|
}
|
|
@@ -1968,7 +2034,7 @@ export class WebGPURenderer {
|
|
|
1968
2034
|
f[atF('uFogNear')] = medium.fogNear;
|
|
1969
2035
|
f[atF('uFogFar')] = medium.fogFar;
|
|
1970
2036
|
this.filmFrags.writeBlock(fragmentSlot, this.filmFragInts);
|
|
1971
|
-
const filmPipe = filmPipeline(this.
|
|
2037
|
+
const filmPipe = filmPipeline(this.blendedPipelines(), this.surface.device, this.filmLayout, `film|${geometry.key ?? ''}`, this.presentOf(geometry));
|
|
1972
2038
|
/* The mirror is an attachment right now, so the group that does not hold it. */
|
|
1973
2039
|
const filmGroup = this.reflectionPassActive ? this.filmBindGroupBlank : this.filmBindGroup;
|
|
1974
2040
|
/*
|
|
@@ -1977,7 +2043,7 @@ export class WebGPURenderer {
|
|
|
1977
2043
|
* holds the mirror's colour whatever `uReflectionEnabled` says, and WebGPU validates the
|
|
1978
2044
|
* binding rather than the read — the argument `filmBindGroupBlank` exists for.
|
|
1979
2045
|
*/
|
|
1980
|
-
const filmCommand = this.
|
|
2046
|
+
const filmCommand = this.recordBlended(filmGroup === this.filmBindGroup ? MIRROR_COLOR : 0);
|
|
1981
2047
|
if (filmCommand !== null) {
|
|
1982
2048
|
filmCommand.pipeline = filmPipe;
|
|
1983
2049
|
filmCommand.bindGroup = filmGroup;
|
|
@@ -2067,6 +2133,10 @@ export class WebGPURenderer {
|
|
|
2067
2133
|
createSurfaceTexture(source, options = {}) {
|
|
2068
2134
|
return new GpuSurfaceTexture(this.surface.device, this.pipelines, source, options);
|
|
2069
2135
|
}
|
|
2136
|
+
/** Several images of one size as an array's layers. See `RendererApi.createSurfaceTextureArray`. */
|
|
2137
|
+
createSurfaceTextureArray(sources, options = {}) {
|
|
2138
|
+
return new GpuSurfaceTexture(this.surface.device, this.pipelines, sources, options);
|
|
2139
|
+
}
|
|
2070
2140
|
/**
|
|
2071
2141
|
* GPU textures an `updateSurfaceTexture` replaced, destroyed when the next frame begins.
|
|
2072
2142
|
*
|
|
@@ -2159,6 +2229,7 @@ export class WebGPURenderer {
|
|
|
2159
2229
|
setSkinPalette(palette) {
|
|
2160
2230
|
if (this.surface.lost)
|
|
2161
2231
|
return;
|
|
2232
|
+
this.skinPaletteData = palette;
|
|
2162
2233
|
if (palette === null) {
|
|
2163
2234
|
this.skinPaletteSlot = -1;
|
|
2164
2235
|
return;
|
|
@@ -2331,6 +2402,19 @@ export class WebGPURenderer {
|
|
|
2331
2402
|
f[this.materialField('uGrain')] = Math.min(1, Math.max(0, amount));
|
|
2332
2403
|
});
|
|
2333
2404
|
}
|
|
2405
|
+
/**
|
|
2406
|
+
* How much of the surfaces drawn next survives a screen-door dither. See `renderer.ts`.
|
|
2407
|
+
*
|
|
2408
|
+
* Written straight into the scratch block rather than through `material()`'s callback, because
|
|
2409
|
+
* this one is called per crossfading region per frame and a closure per call is an allocation.
|
|
2410
|
+
*/
|
|
2411
|
+
setDitherFade(amount) {
|
|
2412
|
+
if (this.surface.lost)
|
|
2413
|
+
return;
|
|
2414
|
+
/* The second half of `uWriteMode`, which the order-independent weight shares. */
|
|
2415
|
+
this.perFrameFloats[this.materialField('uWriteMode') + 1] = Math.min(1, Math.max(-1, amount));
|
|
2416
|
+
this.materials.dirty();
|
|
2417
|
+
}
|
|
2334
2418
|
/**
|
|
2335
2419
|
* How strong the microscopic relief on the surfaces drawn next is, and how coarse.
|
|
2336
2420
|
*
|
|
@@ -2482,7 +2566,8 @@ export class WebGPURenderer {
|
|
|
2482
2566
|
* no-op before the first `bindMeshPass` of a session, same as every other pass that reads it.
|
|
2483
2567
|
*/
|
|
2484
2568
|
drawSdfText(text, model, color, opacity) {
|
|
2485
|
-
|
|
2569
|
+
/* Unjittered when it lands after the upscale, like every late draw. See `drawsLate`. */
|
|
2570
|
+
const viewProj = this.drawsLate() ? this.correctedViewProj : this.viewProj;
|
|
2486
2571
|
if (!this.canDraw() || viewProj === null)
|
|
2487
2572
|
return;
|
|
2488
2573
|
const font = text.font;
|
|
@@ -2511,9 +2596,9 @@ export class WebGPURenderer {
|
|
|
2511
2596
|
]);
|
|
2512
2597
|
this.sdfTextFrags.writeInt(fragmentSlot, fragAt('uOutputTransform'), this.gradeCode());
|
|
2513
2598
|
this.sdfTextFrags.writeFloat(fragmentSlot, fragAt('uOutputExposure'), this.gradeExposure());
|
|
2514
|
-
const sdfPipe = sdfTextPipeline(this.
|
|
2599
|
+
const sdfPipe = sdfTextPipeline(this.blendedPipelines(), this.surface.device, this.sdfTextLayout);
|
|
2515
2600
|
const sdfGroup = this.sdfTextBindGroupFor(atlas);
|
|
2516
|
-
const sdfCommand = this.
|
|
2601
|
+
const sdfCommand = this.recordBlended(0);
|
|
2517
2602
|
if (sdfCommand !== null) {
|
|
2518
2603
|
sdfCommand.pipeline = sdfPipe;
|
|
2519
2604
|
sdfCommand.bindGroup = sdfGroup;
|
|
@@ -2615,8 +2700,8 @@ export class WebGPURenderer {
|
|
|
2615
2700
|
const pass = this.openTextPass();
|
|
2616
2701
|
if (pass === null)
|
|
2617
2702
|
return;
|
|
2618
|
-
const textPipe = textPipeline(this.
|
|
2619
|
-
const textCommand = this.
|
|
2703
|
+
const textPipe = textPipeline(this.blendedPipelines(), this.surface.device, this.textLayout);
|
|
2704
|
+
const textCommand = this.recordBlended(0);
|
|
2620
2705
|
if (textCommand !== null) {
|
|
2621
2706
|
textCommand.pipeline = textPipe;
|
|
2622
2707
|
textCommand.bindGroup = this.textBindGroup;
|
|
@@ -3035,8 +3120,8 @@ export class WebGPURenderer {
|
|
|
3035
3120
|
device.queue.writeBuffer(batch.vertUniforms, 0, batch.vertStaging);
|
|
3036
3121
|
device.queue.writeBuffer(batch.fragUniforms, 0, batch.fragStaging);
|
|
3037
3122
|
const layout = this.particleLayoutFor(batch.material).layout;
|
|
3038
|
-
const particlePipe = particlePipeline(this.
|
|
3039
|
-
const particleCommand = this.
|
|
3123
|
+
const particlePipe = particlePipeline(this.blendedPipelines(), device, layout, batch.material, batch.blend);
|
|
3124
|
+
const particleCommand = this.recordBlended(0);
|
|
3040
3125
|
if (particleCommand !== null) {
|
|
3041
3126
|
particleCommand.pipeline = particlePipe;
|
|
3042
3127
|
particleCommand.bindGroup = batch.bindGroup;
|
|
@@ -3154,8 +3239,8 @@ export class WebGPURenderer {
|
|
|
3154
3239
|
* with `PlumeRenderer`, which has set `uTint` on every draw since before this backend.
|
|
3155
3240
|
*/
|
|
3156
3241
|
frags.writeFloats(fragmentSlot, atF('uTint'), plumes.tint);
|
|
3157
|
-
const plumePipe = plumePipeline(this.
|
|
3158
|
-
const plumeCommand = this.
|
|
3242
|
+
const plumePipe = plumePipeline(this.blendedPipelines(), this.surface.device, this.plumeLayout, plumes.material, plumes.blend);
|
|
3243
|
+
const plumeCommand = this.recordBlended(0);
|
|
3159
3244
|
if (plumeCommand !== null) {
|
|
3160
3245
|
plumeCommand.pipeline = plumePipe;
|
|
3161
3246
|
plumeCommand.bindGroup = this.plumeBindGroup;
|
|
@@ -3598,9 +3683,9 @@ export class WebGPURenderer {
|
|
|
3598
3683
|
this.windStreakFragFloats.set(tint, (WIND_STREAK_FRAG_FIELDS.uTint.offset ?? 0) / 4);
|
|
3599
3684
|
this.windStreakVerts.writeBlock(vertexSlot, this.windStreakVertBlock);
|
|
3600
3685
|
this.windStreakFrags.writeBlock(fragmentSlot, this.windStreakFragBlock);
|
|
3601
|
-
const windPipe = windStreakPipeline(this.
|
|
3686
|
+
const windPipe = windStreakPipeline(this.blendedPipelines(), this.surface.device, this.windStreakLayout);
|
|
3602
3687
|
/* Two uniform buffers and two vertex buffers. Nothing is sampled, so nothing is read. */
|
|
3603
|
-
const windCommand = this.
|
|
3688
|
+
const windCommand = this.recordBlended(0);
|
|
3604
3689
|
if (windCommand !== null) {
|
|
3605
3690
|
windCommand.pipeline = windPipe;
|
|
3606
3691
|
windCommand.bindGroup = this.windStreakBindGroup;
|
|
@@ -3788,8 +3873,8 @@ export class WebGPURenderer {
|
|
|
3788
3873
|
f[atF('uFogFar')] = medium.fogFar;
|
|
3789
3874
|
this.boltVerts.writeBlock(vertexSlot, this.boltVertBlock);
|
|
3790
3875
|
this.boltFrags.writeBlock(fragmentSlot, this.boltFragInts);
|
|
3791
|
-
const boltPipe = boltPipeline(this.
|
|
3792
|
-
const boltCommand = this.
|
|
3876
|
+
const boltPipe = boltPipeline(this.blendedPipelines(), this.surface.device, this.boltLayout);
|
|
3877
|
+
const boltCommand = this.recordBlended(0);
|
|
3793
3878
|
if (boltCommand !== null) {
|
|
3794
3879
|
boltCommand.pipeline = boltPipe;
|
|
3795
3880
|
boltCommand.bindGroup = this.boltBindGroup;
|
|
@@ -3898,9 +3983,9 @@ export class WebGPURenderer {
|
|
|
3898
3983
|
this.lineFrags.writeInt(fragmentSlot, atF('uFogMode'), medium.fogMode);
|
|
3899
3984
|
this.lineFrags.writeFloat(fragmentSlot, atF('uFogNear'), medium.fogNear);
|
|
3900
3985
|
this.lineFrags.writeFloat(fragmentSlot, atF('uFogFar'), medium.fogFar);
|
|
3901
|
-
const linePipe = linePipeline(this.
|
|
3986
|
+
const linePipe = linePipeline(this.blendedPipelines(), this.surface.device, this.lineLayout, additive);
|
|
3902
3987
|
/* Two uniform blocks and three vertex buffers, and no attachment among them. */
|
|
3903
|
-
const lineCommand = this.
|
|
3988
|
+
const lineCommand = this.recordBlended(0);
|
|
3904
3989
|
if (lineCommand !== null) {
|
|
3905
3990
|
lineCommand.pipeline = linePipe;
|
|
3906
3991
|
lineCommand.bindGroup = this.lineBindGroup;
|
|
@@ -3941,6 +4026,8 @@ export class WebGPURenderer {
|
|
|
3941
4026
|
waterLayout;
|
|
3942
4027
|
/** The one-pixel stand-in, kept so the water bind group can be rebuilt without one. */
|
|
3943
4028
|
blankView;
|
|
4029
|
+
/** The blank texel as a one-layer array: the stand-in every surface-texture binding takes. */
|
|
4030
|
+
blankSurfaceView;
|
|
3944
4031
|
blankSampler;
|
|
3945
4032
|
/**
|
|
3946
4033
|
* A slot per body, not one buffer rewritten before each draw.
|
|
@@ -4107,7 +4194,38 @@ export class WebGPURenderer {
|
|
|
4107
4194
|
sdfTextFrags;
|
|
4108
4195
|
sdfTextBindGroups = new Map();
|
|
4109
4196
|
/* -- The shadow pass -------------------------------------------------------------- */
|
|
4110
|
-
|
|
4197
|
+
/**
|
|
4198
|
+
* The sun's static, moving and peeled maps, as layers of one texture (`shadowMap.ts`). The three
|
|
4199
|
+
* views below are single layers, what each pass attaches; the two here are what is sampled.
|
|
4200
|
+
*/
|
|
4201
|
+
sunShadowTexture;
|
|
4202
|
+
/** Every layer, for the lit pass, the light volumes and the medium. */
|
|
4203
|
+
sunArrayView;
|
|
4204
|
+
/**
|
|
4205
|
+
* The static layer alone, as an array, for the peel: it draws into another layer of the same
|
|
4206
|
+
* texture, and a view covering that layer too would put it in two usages in one pass.
|
|
4207
|
+
*/
|
|
4208
|
+
sunStaticArrayView;
|
|
4209
|
+
/** How many layers the sun's texture holds: its maps, and the two glass layers once glass casts. */
|
|
4210
|
+
sunLayerCount;
|
|
4211
|
+
/** The glass depth layers, static then moving, once glass has cast. See `growSunForGlass`. */
|
|
4212
|
+
sunGlassViews = null;
|
|
4213
|
+
/** The texture a growth replaced, destroyed once the encoder that copied out of it is submitted. */
|
|
4214
|
+
retiredSunTexture = null;
|
|
4215
|
+
/** What the sun's light keeps through glass: two `rgba8unorm` layers, made when glass first casts. */
|
|
4216
|
+
sunTint = null;
|
|
4217
|
+
/** The tint pass's layouts, per-draw block, groups and pipelines, made when glass first casts. */
|
|
4218
|
+
glassTint = null;
|
|
4219
|
+
/** The tint pass open while the glass list is replayed into it. */
|
|
4220
|
+
tintPass = null;
|
|
4221
|
+
/** True while the glass list is replayed into a glass depth layer through `casterSink`. */
|
|
4222
|
+
drawingGlassDepth = false;
|
|
4223
|
+
/** Whether the open sun pass is the movers' layer, whose glass goes to the moving glass layer. */
|
|
4224
|
+
shadowLayerIsDynamic = false;
|
|
4225
|
+
/** The glass tints' sampler: filtering, and between levels, for frost's coarser reads. */
|
|
4226
|
+
tintSampler;
|
|
4227
|
+
/** The light a tint pass is drawn for: xyz a direction (w 0) or a position (w 1). */
|
|
4228
|
+
glassLight = new Float32Array(4);
|
|
4111
4229
|
shadowView;
|
|
4112
4230
|
shadowLayout;
|
|
4113
4231
|
/** The same layout plus the joint palette. See `createDepthBindGroupLayout`. */
|
|
@@ -4134,7 +4252,6 @@ export class WebGPURenderer {
|
|
|
4134
4252
|
* read twice — so a profile that switches peeling off gets no map and no pass on either
|
|
4135
4253
|
* backend rather than a map nothing writes.
|
|
4136
4254
|
*/
|
|
4137
|
-
peelMap;
|
|
4138
4255
|
peelView;
|
|
4139
4256
|
/**
|
|
4140
4257
|
* The movers' own layer: the character, the rope it swings on, and anything else that walks.
|
|
@@ -4156,7 +4273,6 @@ export class WebGPURenderer {
|
|
|
4156
4273
|
* and therefore as lit — so nothing looked broken, only empty, which is why it survived the
|
|
4157
4274
|
* port and every gate in it.
|
|
4158
4275
|
*/
|
|
4159
|
-
dynamicMap;
|
|
4160
4276
|
dynamicView;
|
|
4161
4277
|
/** The peel's own bind groups: `uPeelShadowLayer` at 1, and the static map to peel against. */
|
|
4162
4278
|
shadowPeelBindGroup;
|
|
@@ -4230,7 +4346,7 @@ export class WebGPURenderer {
|
|
|
4230
4346
|
if (held !== undefined)
|
|
4231
4347
|
return held;
|
|
4232
4348
|
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
4233
|
-
const previousView = peel ? this.
|
|
4349
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
4234
4350
|
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
4235
4351
|
if (passUniforms === null || previousView === null || previousSampler === null)
|
|
4236
4352
|
return null;
|
|
@@ -4278,8 +4394,23 @@ export class WebGPURenderer {
|
|
|
4278
4394
|
* Built once and held, rather than made per call: it closes over `this` and a fresh one
|
|
4279
4395
|
* every frame would be an allocation in a per-frame path.
|
|
4280
4396
|
*/
|
|
4397
|
+
/** The glass the open shadow pass was offered, kept for its own passes: `glassCasters.ts`. */
|
|
4398
|
+
glassCasters = new GlassCasterList();
|
|
4399
|
+
/** One resolution every glass caster is read through, so recording one allocates nothing. */
|
|
4400
|
+
glassCasterScratch = {
|
|
4401
|
+
transmission: 0,
|
|
4402
|
+
frost: 0,
|
|
4403
|
+
tint: [1, 1, 1],
|
|
4404
|
+
};
|
|
4281
4405
|
casterSink = {
|
|
4282
4406
|
mesh: (mesh, model, material) => {
|
|
4407
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
4408
|
+
/* Glass: kept aside for its own two passes rather than dropped. See `glassCasters.ts`. */
|
|
4409
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
4410
|
+
this.glassCasters.recordMesh(mesh, model, material, this.glassCasterScratch);
|
|
4411
|
+
}
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4283
4414
|
const pass = this.shadowPass;
|
|
4284
4415
|
if (pass === null)
|
|
4285
4416
|
return;
|
|
@@ -4313,6 +4444,12 @@ export class WebGPURenderer {
|
|
|
4313
4444
|
* uses — one draw, however many instances, exactly as the colour pass does it.
|
|
4314
4445
|
*/
|
|
4315
4446
|
instanced: (batch, data, material) => {
|
|
4447
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
4448
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
4449
|
+
this.glassCasters.recordInstanced(batch, data, material, this.glassCasterScratch);
|
|
4450
|
+
}
|
|
4451
|
+
return;
|
|
4452
|
+
}
|
|
4316
4453
|
const pass = this.shadowPass;
|
|
4317
4454
|
if (pass === null)
|
|
4318
4455
|
return;
|
|
@@ -4346,7 +4483,13 @@ export class WebGPURenderer {
|
|
|
4346
4483
|
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
4347
4484
|
pass.drawIndexed(geometry.indexCount, count);
|
|
4348
4485
|
},
|
|
4349
|
-
skinnedMesh: (mesh, model, palette) => {
|
|
4486
|
+
skinnedMesh: (mesh, model, palette, material) => {
|
|
4487
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
4488
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
4489
|
+
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
4490
|
+
}
|
|
4491
|
+
return;
|
|
4492
|
+
}
|
|
4350
4493
|
const pass = this.shadowPass;
|
|
4351
4494
|
if (pass === null)
|
|
4352
4495
|
return;
|
|
@@ -4681,7 +4824,9 @@ export class WebGPURenderer {
|
|
|
4681
4824
|
* nothing at all under the default profile, which is on. The cache carries it for the same
|
|
4682
4825
|
* reason it carries the sample count: no pass can then disagree with the attachment.
|
|
4683
4826
|
*/
|
|
4684
|
-
|
|
4827
|
+
/* Off compiles none of the glass lookup into a lit pipeline: see `PipelineCache.glassShadows`. */
|
|
4828
|
+
const glassShadows = quality.glassShadows !== 'off';
|
|
4829
|
+
this.pipelines = new PipelineCache(device, quality.screenEffects ? sceneColorFormat(quality.hdrScene) : surface.format, this.samples, glassShadows);
|
|
4685
4830
|
/*
|
|
4686
4831
|
* The canvas' own state, for the pass `ensurePass` reopens after `endFrame`.
|
|
4687
4832
|
*
|
|
@@ -4693,7 +4838,12 @@ export class WebGPURenderer {
|
|
|
4693
4838
|
this.overlayPipelines =
|
|
4694
4839
|
this.pipelines.format === surface.format && this.samples === 1
|
|
4695
4840
|
? this.pipelines
|
|
4696
|
-
: new PipelineCache(device, surface.format, 1);
|
|
4841
|
+
: new PipelineCache(device, surface.format, 1, glassShadows);
|
|
4842
|
+
this.latePipelines =
|
|
4843
|
+
this.pipelines.format === RECON_HISTORY_FORMAT && this.samples === 1
|
|
4844
|
+
? this.pipelines
|
|
4845
|
+
: new PipelineCache(device, RECON_HISTORY_FORMAT, 1, glassShadows);
|
|
4846
|
+
this.issueBound = this.issueCommand.bind(this);
|
|
4697
4847
|
this.pipelineTargets =
|
|
4698
4848
|
this.overlayPipelines === this.pipelines
|
|
4699
4849
|
? [this.pipelines]
|
|
@@ -4765,6 +4915,9 @@ export class WebGPURenderer {
|
|
|
4765
4915
|
* `uStaticShadowMap` rather than by anything here changing shape.
|
|
4766
4916
|
*/
|
|
4767
4917
|
const blankView = blank.createView();
|
|
4918
|
+
/* The same texel as a one-layer array, for the four material maps, which are arrays now. */
|
|
4919
|
+
const blankSurfaceView = blank.createView({ dimension: '2d-array' });
|
|
4920
|
+
this.blankSurfaceView = blankSurfaceView;
|
|
4768
4921
|
const blankSampler = device.createSampler({ label: 'flat.sampler' });
|
|
4769
4922
|
/*
|
|
4770
4923
|
* A one-texel integer stand-in for the froxel table.
|
|
@@ -4814,42 +4967,35 @@ export class WebGPURenderer {
|
|
|
4814
4967
|
.createView({ dimension: '3d' });
|
|
4815
4968
|
const blankDriftIndexView = blankVolume('flat.emptyDriftLightIndex', 'r32uint');
|
|
4816
4969
|
const blankDriftAtlasView = blankVolume('flat.emptyDriftLightAtlas', 'rgba16float');
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4970
|
+
/*
|
|
4971
|
+
* The static world, its movers and — where more than one static depth layer is asked for —
|
|
4972
|
+
* the peel, as layers of one texture, on the conditions `renderer.ts` builds its array on: one
|
|
4973
|
+
* decision read twice. Same size and format throughout, because the shader samples every
|
|
4974
|
+
* layer with one \`uShadowMapSize\` and one set of filter offsets.
|
|
4975
|
+
*/
|
|
4976
|
+
const sunLayers = this.quality.directionalShadows
|
|
4977
|
+
? this.quality.directionalShadowDepthLayers > 1
|
|
4978
|
+
? 3
|
|
4979
|
+
: 2
|
|
4980
|
+
: 1;
|
|
4981
|
+
this.sunShadowTexture = device.createTexture({
|
|
4982
|
+
label: 'shadow.sun',
|
|
4983
|
+
size: [
|
|
4984
|
+
this.quality.directionalShadowMapSize,
|
|
4985
|
+
this.quality.directionalShadowMapSize,
|
|
4986
|
+
sunLayers,
|
|
4987
|
+
],
|
|
4820
4988
|
format: SHADOW_FORMAT,
|
|
4821
|
-
|
|
4989
|
+
/* Copyable both ways, because the first glass grows it by copying every layer across. */
|
|
4990
|
+
usage: SUN_USAGE,
|
|
4822
4991
|
});
|
|
4823
|
-
this.
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
this.
|
|
4830
|
-
this.quality.directionalShadowDepthLayers > 1
|
|
4831
|
-
? device.createTexture({
|
|
4832
|
-
label: 'shadow.peel',
|
|
4833
|
-
size: [this.quality.directionalShadowMapSize, this.quality.directionalShadowMapSize],
|
|
4834
|
-
format: SHADOW_FORMAT,
|
|
4835
|
-
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
4836
|
-
})
|
|
4837
|
-
: null;
|
|
4838
|
-
this.peelView = this.peelMap === null ? null : this.peelMap.createView();
|
|
4839
|
-
/*
|
|
4840
|
-
* The movers' layer, on the condition `renderer.ts` builds its own on: directional shadows
|
|
4841
|
-
* at all. Same size and same format as the static map, because the shader samples all three
|
|
4842
|
-
* with one `uShadowMapSize` and one set of PCF offsets.
|
|
4843
|
-
*/
|
|
4844
|
-
this.dynamicMap = this.quality.directionalShadows
|
|
4845
|
-
? device.createTexture({
|
|
4846
|
-
label: 'shadow.dynamic',
|
|
4847
|
-
size: [this.quality.directionalShadowMapSize, this.quality.directionalShadowMapSize],
|
|
4848
|
-
format: SHADOW_FORMAT,
|
|
4849
|
-
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
4850
|
-
})
|
|
4851
|
-
: null;
|
|
4852
|
-
this.dynamicView = this.dynamicMap === null ? null : this.dynamicMap.createView();
|
|
4992
|
+
this.sunLayerCount = sunLayers;
|
|
4993
|
+
const views = sunViewsOf(this.sunShadowTexture, sunLayers, sunLayers);
|
|
4994
|
+
this.sunArrayView = views.array;
|
|
4995
|
+
this.sunStaticArrayView = views.staticArray;
|
|
4996
|
+
this.shadowView = views.static;
|
|
4997
|
+
this.dynamicView = views.dynamic;
|
|
4998
|
+
this.peelView = views.peel;
|
|
4853
4999
|
/*
|
|
4854
5000
|
* **Cleared once, here, because a texture no pass has written reads as zero**, which the
|
|
4855
5001
|
* specification requires, and a zero in a map compared with `LEQUAL` is an occluder at the
|
|
@@ -4890,6 +5036,14 @@ export class WebGPURenderer {
|
|
|
4890
5036
|
* rather than the hardware's.
|
|
4891
5037
|
*/
|
|
4892
5038
|
const shadowSampler = device.createSampler({ label: 'shadow.sampler' });
|
|
5039
|
+
this.tintSampler = device.createSampler({
|
|
5040
|
+
label: 'shadow.tintSampler',
|
|
5041
|
+
magFilter: 'linear',
|
|
5042
|
+
minFilter: 'linear',
|
|
5043
|
+
mipmapFilter: 'linear',
|
|
5044
|
+
addressModeU: 'clamp-to-edge',
|
|
5045
|
+
addressModeV: 'clamp-to-edge',
|
|
5046
|
+
});
|
|
4893
5047
|
/*
|
|
4894
5048
|
* The photometric atlas's own sampler, **created here rather than reusing `postSampler`.**
|
|
4895
5049
|
*
|
|
@@ -4910,15 +5064,18 @@ export class WebGPURenderer {
|
|
|
4910
5064
|
addressModeV: 'clamp-to-edge',
|
|
4911
5065
|
});
|
|
4912
5066
|
this.flatTextures = (name) => {
|
|
4913
|
-
if (name === '
|
|
4914
|
-
return { view: this.
|
|
4915
|
-
/*
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
if (name === '
|
|
4921
|
-
return {
|
|
5067
|
+
if (name === 'uSunShadows')
|
|
5068
|
+
return { view: this.sunArrayView, sampler: shadowSampler };
|
|
5069
|
+
/* What the sun keeps through glass, or the one-texel stand-in the lookup reads as none. */
|
|
5070
|
+
if (name === 'uSunGlassTints') {
|
|
5071
|
+
return { view: this.sunTint?.array ?? blankArrayView, sampler: this.tintSampler };
|
|
5072
|
+
}
|
|
5073
|
+
/* What the lamps keep through glass, one layer a light, or the same stand-in. */
|
|
5074
|
+
if (name === 'uPointGlassTints') {
|
|
5075
|
+
return {
|
|
5076
|
+
view: this.pointShadowArray?.glassTint?.view ?? blankArrayView,
|
|
5077
|
+
sampler: this.tintSampler,
|
|
5078
|
+
};
|
|
4922
5079
|
}
|
|
4923
5080
|
/*
|
|
4924
5081
|
* The caller's image, or the stand-in. Read from the field rather than passed in, because
|
|
@@ -4933,7 +5090,7 @@ export class WebGPURenderer {
|
|
|
4933
5090
|
if (name === 'uNormalMap') {
|
|
4934
5091
|
const map = this.normalMap;
|
|
4935
5092
|
return map === null
|
|
4936
|
-
? { view:
|
|
5093
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
4937
5094
|
: { view: map.view, sampler: map.sampler };
|
|
4938
5095
|
}
|
|
4939
5096
|
/* The caller's ORM map, or the stand-in, for the reason `uNormalMap` gives just above. */
|
|
@@ -4941,24 +5098,25 @@ export class WebGPURenderer {
|
|
|
4941
5098
|
if (name === 'uEmissiveMap') {
|
|
4942
5099
|
const map = this.emissiveMap;
|
|
4943
5100
|
return map === null
|
|
4944
|
-
? { view:
|
|
5101
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
4945
5102
|
: { view: map.view, sampler: map.sampler };
|
|
4946
5103
|
}
|
|
4947
5104
|
if (name === 'uOrmMap') {
|
|
4948
5105
|
const map = this.ormMap;
|
|
4949
5106
|
return map === null
|
|
4950
|
-
? { view:
|
|
5107
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
4951
5108
|
: { view: map.view, sampler: map.sampler };
|
|
4952
5109
|
}
|
|
4953
5110
|
if (name === 'uAlbedo') {
|
|
4954
5111
|
const albedo = this.albedo;
|
|
4955
5112
|
return albedo === null
|
|
4956
|
-
? { view:
|
|
5113
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
4957
5114
|
: { view: albedo.view, sampler: albedo.sampler };
|
|
4958
5115
|
}
|
|
4959
|
-
/* The
|
|
4960
|
-
|
|
4961
|
-
|
|
5116
|
+
/* The albedo's effects table, or a one-texel stand-in the shader's size test reads as none.
|
|
5117
|
+
The albedo is the group's key, so its table needs no key of its own. */
|
|
5118
|
+
if (name === 'uSurfaceEffects') {
|
|
5119
|
+
return { view: this.albedo?.effectsView ?? blankView, sampler: blankSampler };
|
|
4962
5120
|
}
|
|
4963
5121
|
/*
|
|
4964
5122
|
* The room, once a bake has filled it. Unbaked reads as the blank cube: nothing to mirror.
|
|
@@ -4991,18 +5149,8 @@ export class WebGPURenderer {
|
|
|
4991
5149
|
* this one is a curve sampled between two angles rather than data addressed by index, so
|
|
4992
5150
|
* interpolation across a row is the point. `postSampler` is linear and clamped.
|
|
4993
5151
|
*/
|
|
4994
|
-
if (name === '
|
|
4995
|
-
return { view: this.
|
|
4996
|
-
}
|
|
4997
|
-
/*
|
|
4998
|
-
* The cookie atlas, or the single white texel a scene with no cookie falls back to.
|
|
4999
|
-
*
|
|
5000
|
-
* A filtering sampler, like the photometric atlas and unlike the froxel table: a cookie is
|
|
5001
|
-
* an image and its whole point is that it is soft at the edges. The shader's own half-texel
|
|
5002
|
-
* inset is what keeps one tile out of the next; a clamp cannot, the seam being interior.
|
|
5003
|
-
*/
|
|
5004
|
-
if (name === 'uCookieAtlas') {
|
|
5005
|
-
return { view: this.cookieView ?? blankView, sampler: iesSampler };
|
|
5152
|
+
if (name === 'uFixtureAtlas') {
|
|
5153
|
+
return { view: this.fixtureAtlasView(), sampler: iesSampler };
|
|
5006
5154
|
}
|
|
5007
5155
|
/*
|
|
5008
5156
|
* DriftLight's brick index, read by `texelFetch` alone, so it shares the shadow group's
|
|
@@ -5028,7 +5176,15 @@ export class WebGPURenderer {
|
|
|
5028
5176
|
* texels by construction, an offset that snapped to one being no offset at all.
|
|
5029
5177
|
*/
|
|
5030
5178
|
if (name === 'uRefractScene') {
|
|
5031
|
-
|
|
5179
|
+
this.refractSampler ??= device.createSampler({
|
|
5180
|
+
label: 'refract.sampler',
|
|
5181
|
+
magFilter: 'linear',
|
|
5182
|
+
minFilter: 'linear',
|
|
5183
|
+
mipmapFilter: 'linear',
|
|
5184
|
+
addressModeU: 'clamp-to-edge',
|
|
5185
|
+
addressModeV: 'clamp-to-edge',
|
|
5186
|
+
});
|
|
5187
|
+
return { view: this.refractSnapshotView ?? blankView, sampler: this.refractSampler };
|
|
5032
5188
|
}
|
|
5033
5189
|
/*
|
|
5034
5190
|
* Every point light's shadow, in one binding.
|
|
@@ -5208,9 +5364,7 @@ export class WebGPURenderer {
|
|
|
5208
5364
|
*/
|
|
5209
5365
|
this.mediumLayout = createPostStageLayout(device, MEDIUM_UNIFORMS, MEDIUM_FRAG_SIZE, [
|
|
5210
5366
|
{ binding: MEDIUM_TEXTURES.uDepth, filterable: false },
|
|
5211
|
-
{ binding: MEDIUM_TEXTURES.
|
|
5212
|
-
{ binding: MEDIUM_TEXTURES.uPeeledShadowMap, filterable: false },
|
|
5213
|
-
{ binding: MEDIUM_TEXTURES.uDynamicShadowMap, filterable: false },
|
|
5367
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows, filterable: false, dimension: '2d-array' },
|
|
5214
5368
|
]);
|
|
5215
5369
|
this.mediumUniforms = device.createBuffer({
|
|
5216
5370
|
label: 'post.mediumUniforms',
|
|
@@ -5343,9 +5497,9 @@ export class WebGPURenderer {
|
|
|
5343
5497
|
device.queue.writeBuffer(depthPassUniforms, 0, new Int32Array([0]));
|
|
5344
5498
|
this.scatterDepthLayout = createScatterDepthBindGroupLayout(device, DEPTH_FRAG_BINDING, DEPTH_FRAG_SIZE, DEPTH_PREVIOUS_BINDING);
|
|
5345
5499
|
this.scatterDepthDraws = new UniformRing(device, SCATTER_DEPTH_SIZE, MAX_SCATTER_DEPTH_DRAWS, USAGE_UNIFORM_DST, 'scatter.depthDrawRing', this.scatterDepthBudget);
|
|
5346
|
-
this.scatterDepthBindGroup = createScatterDepthBindGroup(device, this.scatterDepthLayout, this.scatterDepthDraws.buffer, { binding: DEPTH_FRAG_BINDING, buffer: depthPassUniforms, size: DEPTH_FRAG_SIZE }, DEPTH_PREVIOUS_BINDING,
|
|
5347
|
-
this.shadowBindGroup = createDepthBindGroup(device, this.shadowLayout, this.shadowDraws.buffer, depthPassUniforms,
|
|
5348
|
-
this.shadowBlankView =
|
|
5500
|
+
this.scatterDepthBindGroup = createScatterDepthBindGroup(device, this.scatterDepthLayout, this.scatterDepthDraws.buffer, { binding: DEPTH_FRAG_BINDING, buffer: depthPassUniforms, size: DEPTH_FRAG_SIZE }, DEPTH_PREVIOUS_BINDING, blankArrayView, blankSampler);
|
|
5501
|
+
this.shadowBindGroup = createDepthBindGroup(device, this.shadowLayout, this.shadowDraws.buffer, depthPassUniforms, blankArrayView, blankSampler);
|
|
5502
|
+
this.shadowBlankView = blankArrayView;
|
|
5349
5503
|
this.shadowBlankSampler = blankSampler;
|
|
5350
5504
|
this.shadowPassUniforms = depthPassUniforms;
|
|
5351
5505
|
/*
|
|
@@ -5376,11 +5530,11 @@ export class WebGPURenderer {
|
|
|
5376
5530
|
this.shadowPeelBindGroup =
|
|
5377
5531
|
peelPassUniforms === null
|
|
5378
5532
|
? null
|
|
5379
|
-
: createDepthBindGroup(device, this.shadowLayout, this.shadowDraws.buffer, peelPassUniforms, this.
|
|
5533
|
+
: createDepthBindGroup(device, this.shadowLayout, this.shadowDraws.buffer, peelPassUniforms, this.sunStaticArrayView, shadowSampler);
|
|
5380
5534
|
this.scatterDepthPeelBindGroup =
|
|
5381
5535
|
peelPassUniforms === null
|
|
5382
5536
|
? null
|
|
5383
|
-
: createScatterDepthBindGroup(device, this.scatterDepthLayout, this.scatterDepthDraws.buffer, { binding: DEPTH_FRAG_BINDING, buffer: peelPassUniforms, size: DEPTH_FRAG_SIZE }, DEPTH_PREVIOUS_BINDING, this.
|
|
5537
|
+
: createScatterDepthBindGroup(device, this.scatterDepthLayout, this.scatterDepthDraws.buffer, { binding: DEPTH_FRAG_BINDING, buffer: peelPassUniforms, size: DEPTH_FRAG_SIZE }, DEPTH_PREVIOUS_BINDING, this.sunStaticArrayView, shadowSampler);
|
|
5384
5538
|
/*
|
|
5385
5539
|
* The overlays. Rings rather than single blocks because a frame draws several of each —
|
|
5386
5540
|
* the loader alone is two panels and two strings — and `queue.writeBuffer` does not
|
|
@@ -5516,6 +5670,8 @@ export class WebGPURenderer {
|
|
|
5516
5670
|
continue;
|
|
5517
5671
|
present[String(attribute.name)] = key.includes(`:${String(attribute.name)}`);
|
|
5518
5672
|
}
|
|
5673
|
+
/* Not a layout entry of its own — it widens `uvs` — so read from the key beside them. */
|
|
5674
|
+
present['layers'] = key.includes(':layers');
|
|
5519
5675
|
return present;
|
|
5520
5676
|
}
|
|
5521
5677
|
/**
|
|
@@ -5527,6 +5683,7 @@ export class WebGPURenderer {
|
|
|
5527
5683
|
* uncaught exceptions a second in somebody's error reporting.
|
|
5528
5684
|
*/
|
|
5529
5685
|
beginFrame(clearColor) {
|
|
5686
|
+
this.cullFrame += 1;
|
|
5530
5687
|
/* Whatever an update replaced during the last frame; nothing recorded can read it now. */
|
|
5531
5688
|
for (const texture of this.retiredTextures)
|
|
5532
5689
|
texture.destroy();
|
|
@@ -5621,7 +5778,10 @@ export class WebGPURenderer {
|
|
|
5621
5778
|
this.perFrame.reset();
|
|
5622
5779
|
this.materials.dirty();
|
|
5623
5780
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
5624
|
-
this.
|
|
5781
|
+
this.motionPass?.reset();
|
|
5782
|
+
this.latePass?.reset();
|
|
5783
|
+
this.moverFrame += 1;
|
|
5784
|
+
this.inFrame = true;
|
|
5625
5785
|
/*
|
|
5626
5786
|
* The canvas rather than the swap chain's texture, and the difference is that reading this
|
|
5627
5787
|
* does not *acquire* anything. See `swapView`.
|
|
@@ -5781,6 +5941,15 @@ export class WebGPURenderer {
|
|
|
5781
5941
|
present[String(attribute.name)] = supplied;
|
|
5782
5942
|
key += supplied ? `:${String(attribute.name)}` : '';
|
|
5783
5943
|
}
|
|
5944
|
+
/*
|
|
5945
|
+
* Texture-array layers widen the UV field to three floats (`LAYERED_UVS`), which is a different
|
|
5946
|
+
* vertex layout, so the key has to say so or a layered and a plain mesh would share a pipeline
|
|
5947
|
+
* and one of them would read every field after the UVs from the wrong offset.
|
|
5948
|
+
*/
|
|
5949
|
+
if (data.layers !== undefined) {
|
|
5950
|
+
present['layers'] = true;
|
|
5951
|
+
key += ':layers';
|
|
5952
|
+
}
|
|
5784
5953
|
const fullKey = `${this.variant}|${key}`;
|
|
5785
5954
|
/*
|
|
5786
5955
|
* **For every target this mesh can land on, which is two when the overlay is its own.**
|
|
@@ -5904,7 +6073,27 @@ export class WebGPURenderer {
|
|
|
5904
6073
|
'would be patched from was not kept. Say so at `createMesh` — the flag is what decides ' +
|
|
5905
6074
|
'what is kept and what a buffer is hinted as.');
|
|
5906
6075
|
}
|
|
5907
|
-
|
|
6076
|
+
/*
|
|
6077
|
+
* **The rows before this rewrite are last frame's**, kept only for a reconstruction and only
|
|
6078
|
+
* on the first rewrite of a frame — a second must not replace where the vertices were last
|
|
6079
|
+
* frame with where they were a moment ago. A rewrite between frames is the next frame's. See
|
|
6080
|
+
* `changeFrames.ts`.
|
|
6081
|
+
*/
|
|
6082
|
+
const motion = mesh.motion;
|
|
6083
|
+
let previous = null;
|
|
6084
|
+
if (motion !== null && this.reconstructionWanted) {
|
|
6085
|
+
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
6086
|
+
if (keepsPrevious(motion.changed, frame)) {
|
|
6087
|
+
motion.previous ??= this.surface.device.createBuffer({
|
|
6088
|
+
label: 'recon.dynamicPrevious',
|
|
6089
|
+
size: mesh.vertexBuffers[0]?.size ?? 0,
|
|
6090
|
+
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
6091
|
+
});
|
|
6092
|
+
previous = motion.previous;
|
|
6093
|
+
}
|
|
6094
|
+
motion.changed = frame;
|
|
6095
|
+
}
|
|
6096
|
+
mesh.update(this.surface.device, positions, normals, previous);
|
|
5908
6097
|
}
|
|
5909
6098
|
/**
|
|
5910
6099
|
* Settle everything the frame shares: the camera, the lights and the fog.
|
|
@@ -5952,46 +6141,17 @@ export class WebGPURenderer {
|
|
|
5952
6141
|
* Its sequence is its own: `reconJitterPhases` takes eight positions for every output pixel a
|
|
5953
6142
|
* render pixel covers, where the temporal resolve's eight are enough at one to one.
|
|
5954
6143
|
*/
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
* advanced here: a frame binding its mesh pass twice is one frame of the sequence.
|
|
5964
|
-
*
|
|
5965
|
-
* **The y offset is negated on the way in, and that is not a preference.** This shifts the
|
|
5966
|
-
* *corrected* matrix, whose y `CLIP_CORRECTION` has already negated so that the negation
|
|
5967
|
-
* every generated vertex stage ends with cancels out — §3 row 56. So an offset added here
|
|
5968
|
-
* arrives on screen with its sign reversed, while x, which nothing negates, arrives as
|
|
5969
|
-
* given. The temporal resolve cannot see this because its shader never reads the offset; the
|
|
5970
|
-
* reconstruction reads it at every texel it unprojects, and a sign error there moves every
|
|
5971
|
-
* sample two jitters away from where the depth says it is.
|
|
5972
|
-
*/
|
|
5973
|
-
this.viewProj = jitterProjection(this.jitteredViewProj, this.correctedViewProj, this.reconJitter[0], -this.reconJitter[1], this.renderWidth, this.renderHeight);
|
|
5974
|
-
this.temporalJittering = false;
|
|
5975
|
-
this.temporalHistoryUsable = false;
|
|
5976
|
-
}
|
|
5977
|
-
this.temporalJittering =
|
|
5978
|
-
!this.reconstructing &&
|
|
5979
|
-
this.quality.temporalAa &&
|
|
5980
|
-
this.quality.screenEffects &&
|
|
5981
|
-
!this.reflectionPassActive;
|
|
5982
|
-
if (this.temporalJittering) {
|
|
5983
|
-
/* The scene's size, not the drawing buffer's: the history is a picture of the render. */
|
|
5984
|
-
this.temporalHistoryUsable = this.temporalHistory.openFrame(this.renderWidth, this.renderHeight);
|
|
5985
|
-
const [jx, jy] = jitterOffset(this.temporalHistory.frameIndex);
|
|
5986
|
-
this.temporalJitterX = jx;
|
|
5987
|
-
this.temporalJitterY = jy;
|
|
5988
|
-
/* A jitter is half a *render* texel, which is what the sequence is spread over. */
|
|
5989
|
-
this.viewProj = jitterProjection(this.jitteredViewProj, this.correctedViewProj, jx, jy, this.renderWidth, this.renderHeight);
|
|
5990
|
-
}
|
|
5991
|
-
else if (!this.reconstructing) {
|
|
5992
|
-
this.temporalHistoryUsable = false;
|
|
6144
|
+
/*
|
|
6145
|
+
* **After the present there is no jitter to add and no temporal frame to open.** A mesh pass
|
|
6146
|
+
* bound then is the interface's, drawn over a picture already resolved: jittered, a map and every
|
|
6147
|
+
* edge of it moved by a fraction of a pixel each frame with nothing to resolve it; and opening a
|
|
6148
|
+
* second temporal frame stepped the sequence twice a frame, so the world saw every other
|
|
6149
|
+
* position and the anti-flicker's period never started.
|
|
6150
|
+
*/
|
|
6151
|
+
if (this.framePresented)
|
|
5993
6152
|
this.viewProj = this.correctedViewProj;
|
|
5994
|
-
|
|
6153
|
+
else
|
|
6154
|
+
this.jitterFrame();
|
|
5995
6155
|
/*
|
|
5996
6156
|
* The frustum, once a frame, and from the **uncorrected** matrix.
|
|
5997
6157
|
*
|
|
@@ -6084,8 +6244,11 @@ export class WebGPURenderer {
|
|
|
6084
6244
|
* frame either way, because an unwritten one is four zeroes and `dot(p, 0) < 0` is false,
|
|
6085
6245
|
* so the term would silently pass everything the first time somebody enabled it.
|
|
6086
6246
|
*/
|
|
6087
|
-
|
|
6088
|
-
|
|
6247
|
+
/* The mirror's plane in a reflection pass and zero, which clips nothing, in every other: the lit
|
|
6248
|
+
stage has no flag beside the plane. See `uClipPlane` in `flat/preamble.ts`. */
|
|
6249
|
+
f.set(this.reflectionPassActive ? (this.reflection?.clipPlane ?? NO_CLIP_PLANE) : NO_CLIP_PLANE, at('uClipPlane'));
|
|
6250
|
+
resolveSurfaceScene(env, this.surfaceScene);
|
|
6251
|
+
f.set(this.surfaceScene, at('uSurfaceScene'));
|
|
6089
6252
|
/*
|
|
6090
6253
|
* The material and grading terms, matching `renderer.ts` value for value.
|
|
6091
6254
|
*
|
|
@@ -6105,6 +6268,9 @@ export class WebGPURenderer {
|
|
|
6105
6268
|
*/
|
|
6106
6269
|
f[at('uGrain')] = 1;
|
|
6107
6270
|
f[at('uReflectivity')] = 0;
|
|
6271
|
+
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
6272
|
+
f[at('uWriteMode')] = 0;
|
|
6273
|
+
f[at('uWriteMode') + 1] = 0;
|
|
6108
6274
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
6109
6275
|
f[at('uEnvironmentGain')] = 1;
|
|
6110
6276
|
/*
|
|
@@ -6532,8 +6698,8 @@ export class WebGPURenderer {
|
|
|
6532
6698
|
* depth, so a motion pass drawing it would either be depth-rejected everywhere — wasted — or,
|
|
6533
6699
|
* where it happened to pass, write motion for a surface no pixel of the frame belongs to.
|
|
6534
6700
|
*/
|
|
6535
|
-
if (
|
|
6536
|
-
this.
|
|
6701
|
+
if (this.tracksMotionNow())
|
|
6702
|
+
this.recordDrawMotion(mesh, model, previousModel);
|
|
6537
6703
|
this.submitMesh(mesh, model, tint, 1, false, { depthLayer });
|
|
6538
6704
|
}
|
|
6539
6705
|
/**
|
|
@@ -6624,7 +6790,7 @@ export class WebGPURenderer {
|
|
|
6624
6790
|
* **Recorded rather than drawn**, when the effect is on and this is not already the replay.
|
|
6625
6791
|
* Everything is copied — see `TranslucentQueue`, and the scratch matrix that made it necessary.
|
|
6626
6792
|
*/
|
|
6627
|
-
if (this.oitActive && !this.oitReplaying) {
|
|
6793
|
+
if (this.oitActive && !this.oitReplaying && options.additive !== true) {
|
|
6628
6794
|
this.translucentQueue.record(mesh, model, opacity, options);
|
|
6629
6795
|
return;
|
|
6630
6796
|
}
|
|
@@ -6668,7 +6834,12 @@ export class WebGPURenderer {
|
|
|
6668
6834
|
}
|
|
6669
6835
|
return;
|
|
6670
6836
|
}
|
|
6671
|
-
|
|
6837
|
+
/* A blended draw landing after the upscale is drawn into an unjittered picture. */
|
|
6838
|
+
/* Unless it moves with what it lies on, which the resolve reconstructs it with. See
|
|
6839
|
+
`TranslucentMeshOptions.reconstructed`. */
|
|
6840
|
+
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
6841
|
+
const late = lands || (blend && this.oitReplaying && this.reconstructing);
|
|
6842
|
+
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uViewProj.offset, late ? this.correctedViewProj : this.viewProj);
|
|
6672
6843
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
6673
6844
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
6674
6845
|
/* The frame's wind, for a mesh carrying a sway lane. Written on this path and not the
|
|
@@ -6701,19 +6872,19 @@ export class WebGPURenderer {
|
|
|
6701
6872
|
this.perFrameFloats[this.materialField('uOpacity')] = opacity;
|
|
6702
6873
|
/* Material state exactly like `uOpacity`, dirtied for the accumulation pass and restored
|
|
6703
6874
|
below — the revealage pass draws with it at zero and its colour multiplied away. */
|
|
6704
|
-
this.perFrameFloats[this.materialField('
|
|
6875
|
+
this.perFrameFloats[this.materialField('uWriteMode')] = this.oitMode === 'accum' ? 1 : 0;
|
|
6705
6876
|
}
|
|
6706
6877
|
/* Same shape as `dimmed`: a slot is only worth taking when either differs from the default
|
|
6707
6878
|
`bindMeshPass` already wrote, and both are put back together once the draw is submitted. */
|
|
6708
6879
|
const lit = options.lit ?? true;
|
|
6709
|
-
|
|
6880
|
+
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
6881
|
+
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
6710
6882
|
const toneMapped = options.toneMapped ?? true;
|
|
6711
|
-
const unlitOrUnfogged = !lit ||
|
|
6883
|
+
const unlitOrUnfogged = !lit || fog !== FOG_RECEDE;
|
|
6712
6884
|
if (unlitOrUnfogged) {
|
|
6713
6885
|
if (!lit)
|
|
6714
6886
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
|
|
6715
|
-
|
|
6716
|
-
this.perFrameInts[this.materialField('uFogEnabled')] = 0;
|
|
6887
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = fog;
|
|
6717
6888
|
}
|
|
6718
6889
|
/* `1` is sRGB alone: the conversion without the curve. `Math.min` rather than a literal, so
|
|
6719
6890
|
a renderer asked for `none` stays at none. See `TranslucentMeshOptions.toneMapped`. */
|
|
@@ -6729,17 +6900,7 @@ export class WebGPURenderer {
|
|
|
6729
6900
|
* shades as an ordinary translucent one rather than sampling a texture holding the frame
|
|
6730
6901
|
* before last. That is the defined state the two-backends rule asks for.
|
|
6731
6902
|
*/
|
|
6732
|
-
const
|
|
6733
|
-
const refracting = refraction > 0 && this.takeRefractSnapshot();
|
|
6734
|
-
if (refracting) {
|
|
6735
|
-
this.perFrameFloats[this.materialField('uRefractStrength')] = refraction;
|
|
6736
|
-
const refractTint = options.refractTint;
|
|
6737
|
-
const tintAt = this.materialField('uRefractTint');
|
|
6738
|
-
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
6739
|
-
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
6740
|
-
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
6741
|
-
this.perFrameFloats[this.materialField('uRefractThickness')] = options.thicknessM ?? 0;
|
|
6742
|
-
}
|
|
6903
|
+
const refracting = this.bindSeeThrough(options);
|
|
6743
6904
|
/* Taken for itself when any of those differs from the pass, and put back below: the rule both
|
|
6744
6905
|
backends count material changes by. See `materialChanges.ts`. */
|
|
6745
6906
|
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
@@ -6769,7 +6930,9 @@ export class WebGPURenderer {
|
|
|
6769
6930
|
*/
|
|
6770
6931
|
const depthWrite = options.depthWrite ?? true;
|
|
6771
6932
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
6772
|
-
|
|
6933
|
+
/* Added rather than blended over: its own blend state, so its own pipeline. */
|
|
6934
|
+
const adds = blend && options.additive === true;
|
|
6935
|
+
const key = `${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
6773
6936
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
6774
6937
|
/* The two order-independent buffers are two formats and two blend states, so a draw into
|
|
6775
6938
|
one must never share a cached pipeline with the same mesh drawn into the other. */
|
|
@@ -6798,14 +6961,14 @@ export class WebGPURenderer {
|
|
|
6798
6961
|
* pays the compile it was trying to avoid. That is a slow frame; the alternative is a
|
|
6799
6962
|
* thrown error in the middle of one.
|
|
6800
6963
|
*/
|
|
6801
|
-
const pipelines = this.targetPipelines();
|
|
6964
|
+
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
6802
6965
|
let pipeline = pipelines.peek(key);
|
|
6803
6966
|
if (pipeline === undefined) {
|
|
6804
6967
|
const present = this.meshPresent.get(base);
|
|
6805
6968
|
if (present === undefined) {
|
|
6806
6969
|
throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
|
|
6807
6970
|
}
|
|
6808
|
-
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided);
|
|
6971
|
+
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, adds ? 'additive' : blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided);
|
|
6809
6972
|
}
|
|
6810
6973
|
/*
|
|
6811
6974
|
* Guards the draw call only, not the restore below.
|
|
@@ -6830,7 +6993,9 @@ export class WebGPURenderer {
|
|
|
6830
6993
|
* the uniform rings are the state arena this design would otherwise have had to build —
|
|
6831
6994
|
* so the command carries them as its two dynamic offsets.
|
|
6832
6995
|
*/
|
|
6833
|
-
const command =
|
|
6996
|
+
const command = blend
|
|
6997
|
+
? this.recordBlended(0, lands)
|
|
6998
|
+
: this.recordDraw(0, this.currentTarget());
|
|
6834
6999
|
if (command !== null) {
|
|
6835
7000
|
command.pipeline = pipeline;
|
|
6836
7001
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
@@ -6863,19 +7028,17 @@ export class WebGPURenderer {
|
|
|
6863
7028
|
whether or not this one found a slot to draw with. */
|
|
6864
7029
|
if (dimmed) {
|
|
6865
7030
|
this.perFrameFloats[this.materialField('uOpacity')] = 1;
|
|
6866
|
-
this.perFrameFloats[this.materialField('
|
|
7031
|
+
this.perFrameFloats[this.materialField('uWriteMode')] = 0;
|
|
6867
7032
|
}
|
|
6868
7033
|
if (unlitOrUnfogged) {
|
|
6869
7034
|
if (!lit)
|
|
6870
7035
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
6871
|
-
|
|
6872
|
-
this.perFrameInts[this.materialField('uFogEnabled')] = 1;
|
|
6873
|
-
}
|
|
6874
|
-
if (refracting) {
|
|
6875
|
-
/* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
|
|
6876
|
-
shared, so a strength left set is worn by everything drawn after it. */
|
|
6877
|
-
this.perFrameFloats[this.materialField('uRefractStrength')] = 0;
|
|
7036
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = FOG_RECEDE;
|
|
6878
7037
|
}
|
|
7038
|
+
/* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
|
|
7039
|
+
shared, so a strength left set is worn by everything drawn after it. */
|
|
7040
|
+
if (refracting)
|
|
7041
|
+
this.releaseSeeThrough();
|
|
6879
7042
|
if (!toneMapped) {
|
|
6880
7043
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
6881
7044
|
}
|
|
@@ -6895,7 +7058,7 @@ export class WebGPURenderer {
|
|
|
6895
7058
|
* anyway, because WebGL2 *cannot* and a capability one backend has and the other does not is
|
|
6896
7059
|
* the shape this repository spent a week removing.
|
|
6897
7060
|
*/
|
|
6898
|
-
createInstanced(mesh, capacity) {
|
|
7061
|
+
createInstanced(mesh, capacity, options = {}) {
|
|
6899
7062
|
/*
|
|
6900
7063
|
* **A skinned mesh cannot be instanced, and the reason is the vertex layout rather than the
|
|
6901
7064
|
* shader.** An instanced pipeline reclaims locations 11 and 12 from the joint indices and
|
|
@@ -6921,25 +7084,46 @@ export class WebGPURenderer {
|
|
|
6921
7084
|
'an ordinary mesh, or drop the channel — the sway, sky and alpha lanes cannot reach an ' +
|
|
6922
7085
|
'instanced pipeline.');
|
|
6923
7086
|
}
|
|
6924
|
-
|
|
6925
|
-
throw new Error('WebGPU: this mesh already has an instanced batch — a mesh may have one, because the ' +
|
|
6926
|
-
"other backend binds the attributes to the mesh's own vertex array.");
|
|
6927
|
-
}
|
|
6928
|
-
const batch = new GpuInstancedBatch(this.surface.device, mesh, capacity, `flat.instances:${mesh.key ?? 'mesh'}`);
|
|
6929
|
-
this.instanced.set(mesh, batch);
|
|
7087
|
+
const batch = new GpuInstancedBatch(this.surface.device, mesh, capacity, `flat.instances:${mesh.key ?? 'mesh'}`, options.cull ?? false);
|
|
6930
7088
|
return batch;
|
|
6931
7089
|
}
|
|
6932
|
-
/**
|
|
6933
|
-
|
|
7090
|
+
/** The device instance culls, built on the first culling draw. See `InstanceCullPass`. */
|
|
7091
|
+
instanceCulls = null;
|
|
7092
|
+
/** Counts frames, so a batch knows when its cull slots were last taken. */
|
|
7093
|
+
cullFrame = 0;
|
|
6934
7094
|
/** Push placement and colour. Only the live prefix; see `GpuInstancedBatch.upload`. */
|
|
6935
7095
|
uploadInstanced(batch, data) {
|
|
6936
7096
|
if (this.surface.lost)
|
|
6937
7097
|
return;
|
|
6938
|
-
|
|
7098
|
+
/*
|
|
7099
|
+
* **The placement before this upload is last frame's**, kept only for a reconstruction and only
|
|
7100
|
+
* on a frame's first upload, for the reason `updateMesh` gives. See `changeFrames.ts`.
|
|
7101
|
+
*/
|
|
7102
|
+
let previous = null;
|
|
7103
|
+
if (this.reconstructionWanted) {
|
|
7104
|
+
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
7105
|
+
if (keepsPrevious(batch.changed, frame)) {
|
|
7106
|
+
batch.previousBuffer ??= this.surface.device.createBuffer({
|
|
7107
|
+
label: 'recon.instancesPrevious',
|
|
7108
|
+
size: batch.capacity * INSTANCE_STRIDE,
|
|
7109
|
+
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
7110
|
+
});
|
|
7111
|
+
previous = batch.previousBuffer;
|
|
7112
|
+
}
|
|
7113
|
+
batch.changed = frame;
|
|
7114
|
+
}
|
|
7115
|
+
batch.upload(this.surface.device.queue, data, previous);
|
|
6939
7116
|
}
|
|
6940
7117
|
/** Draw every live instance, opaque. */
|
|
6941
7118
|
drawInstanced(batch, data) {
|
|
6942
7119
|
this.submitInstanced(batch, data, 1, false, {});
|
|
7120
|
+
/* A batch that moved this frame pairs each slot with last frame's; the batch is the identity. */
|
|
7121
|
+
const previous = batch.previousBuffer;
|
|
7122
|
+
if (previous !== null && batch.changed === this.moverFrame && this.tracksMotionNow()) {
|
|
7123
|
+
const paired = pairedInstances(batch.previousCount, Math.min(data.count, batch.capacity));
|
|
7124
|
+
if (paired > 0)
|
|
7125
|
+
this.motionPass?.recordInstanced(batch.mesh, batch.buffer, previous, paired);
|
|
7126
|
+
}
|
|
6943
7127
|
}
|
|
6944
7128
|
/** Draw every live instance, blended. See `drawTranslucentMesh` for what the options mean. */
|
|
6945
7129
|
drawTranslucentInstanced(batch, data, opacity, options = {}) {
|
|
@@ -6947,9 +7131,46 @@ export class WebGPURenderer {
|
|
|
6947
7131
|
}
|
|
6948
7132
|
/** Release the placement. The mesh is the caller's and is not released. */
|
|
6949
7133
|
disposeInstanced(batch) {
|
|
6950
|
-
this.instanced.delete(batch.mesh);
|
|
6951
7134
|
batch.dispose();
|
|
6952
7135
|
}
|
|
7136
|
+
/**
|
|
7137
|
+
* Write what a translucent draw shows through it into the material scratch: refraction's bend and
|
|
7138
|
+
* absorption, and glass. Returns whether the frame's copy is there to read, and so whether
|
|
7139
|
+
* `releaseSeeThrough` owes the scratch back.
|
|
7140
|
+
*
|
|
7141
|
+
* **A glass draw takes the copy even when it does not bend**, since a pane shows what is behind it
|
|
7142
|
+
* either way. A copy that cannot be taken — a mirror, a probe bake — leaves both off, so the pane
|
|
7143
|
+
* shades as ordinary translucency rather than sampling a stand-in. See `glass.ts`.
|
|
7144
|
+
*/
|
|
7145
|
+
bindSeeThrough(options) {
|
|
7146
|
+
const refraction = options.refraction ?? 0;
|
|
7147
|
+
const glassy = resolveGlass(options.glass, this.glassScratch);
|
|
7148
|
+
if ((refraction <= 0 && !glassy) || !this.takeRefractSnapshot())
|
|
7149
|
+
return false;
|
|
7150
|
+
const glass = this.glassScratch;
|
|
7151
|
+
/* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
|
|
7152
|
+
const seeAt = this.materialField('uSeeThrough');
|
|
7153
|
+
this.perFrameFloats[seeAt] = Math.max(refraction, 0);
|
|
7154
|
+
this.perFrameFloats[seeAt + 1] = options.thicknessM ?? 0;
|
|
7155
|
+
this.perFrameFloats[seeAt + 2] = glass.transmission;
|
|
7156
|
+
this.perFrameFloats[seeAt + 3] = glass.frost;
|
|
7157
|
+
const refractTint = options.refractTint;
|
|
7158
|
+
const tintAt = this.materialField('uRefractTint');
|
|
7159
|
+
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
7160
|
+
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
7161
|
+
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
7162
|
+
const glassTintAt = this.materialField('uGlassTint');
|
|
7163
|
+
this.perFrameFloats[glassTintAt] = glass.tint[0];
|
|
7164
|
+
this.perFrameFloats[glassTintAt + 1] = glass.tint[1];
|
|
7165
|
+
this.perFrameFloats[glassTintAt + 2] = glass.tint[2];
|
|
7166
|
+
return true;
|
|
7167
|
+
}
|
|
7168
|
+
/** Put the scratch back after `bindSeeThrough`: refraction and glass off. */
|
|
7169
|
+
releaseSeeThrough() {
|
|
7170
|
+
const seeAt = this.materialField('uSeeThrough');
|
|
7171
|
+
this.perFrameFloats[seeAt] = 0;
|
|
7172
|
+
this.perFrameFloats[seeAt + 2] = 0;
|
|
7173
|
+
}
|
|
6953
7174
|
/**
|
|
6954
7175
|
* One draw, one material slot, one uniform slot, however many instances.
|
|
6955
7176
|
*
|
|
@@ -6962,6 +7183,31 @@ export class WebGPURenderer {
|
|
|
6962
7183
|
* **What it gives up** against `drawMesh` is skinning and morphing, refused where the variant
|
|
6963
7184
|
* is built, and a per-instance opacity: `opacity` here dims the whole batch.
|
|
6964
7185
|
*/
|
|
7186
|
+
/**
|
|
7187
|
+
* The next of a batch's cull slots this frame, with its cull queued against the current view; null
|
|
7188
|
+
* when the batch has drawn from `MAX_CULL_SLOTS` views already or the frame's job ring is full,
|
|
7189
|
+
* and the draw then takes every instance.
|
|
7190
|
+
*/
|
|
7191
|
+
takeCullSlot(batch, count) {
|
|
7192
|
+
if (batch.slotFrame !== this.cullFrame) {
|
|
7193
|
+
batch.slotFrame = this.cullFrame;
|
|
7194
|
+
batch.slotsTaken = 0;
|
|
7195
|
+
}
|
|
7196
|
+
if (batch.slotsTaken >= MAX_CULL_SLOTS)
|
|
7197
|
+
return null;
|
|
7198
|
+
this.instanceCulls ??= new InstanceCullPass(this.surface.device);
|
|
7199
|
+
let slot = batch.slots[batch.slotsTaken];
|
|
7200
|
+
if (slot === undefined) {
|
|
7201
|
+
slot = this.instanceCulls.createSlot(batch.buffer, batch.capacity, 'flat.instances');
|
|
7202
|
+
batch.slots.push(slot);
|
|
7203
|
+
}
|
|
7204
|
+
const bounds = batch.mesh.bounds;
|
|
7205
|
+
const queued = this.instanceCulls.queue(slot, this.frustum, bounds.centre, bounds.radius, count, batch.mesh.indexCount);
|
|
7206
|
+
if (!queued)
|
|
7207
|
+
return null;
|
|
7208
|
+
batch.slotsTaken += 1;
|
|
7209
|
+
return slot;
|
|
7210
|
+
}
|
|
6965
7211
|
submitInstanced(batch, data, opacity, blend, options) {
|
|
6966
7212
|
if (!this.canDraw() || this.viewProj === null)
|
|
6967
7213
|
return;
|
|
@@ -6971,6 +7217,14 @@ export class WebGPURenderer {
|
|
|
6971
7217
|
const count = Math.min(data.count, batch.capacity);
|
|
6972
7218
|
if (count === 0)
|
|
6973
7219
|
return;
|
|
7220
|
+
/* A culling batch's camera draw: the whole batch against the view and the occluders on the CPU,
|
|
7221
|
+
then instance by instance on the device into an indirect draw. Blended draws keep every
|
|
7222
|
+
instance and their order. See `InstancedOptions.cull`. */
|
|
7223
|
+
const culling = !blend && batch.cull;
|
|
7224
|
+
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion))
|
|
7225
|
+
return;
|
|
7226
|
+
const cut = culling && cullsInstances(count, mesh.indexCount) ? this.takeCullSlot(batch, count) : null;
|
|
7227
|
+
const instanceBuffer = cut === null ? batch.buffer : cut.instances;
|
|
6974
7228
|
const slot = this.perDraw.allocate();
|
|
6975
7229
|
if (slot === null) {
|
|
6976
7230
|
if (!this.warnedFull) {
|
|
@@ -6979,7 +7233,9 @@ export class WebGPURenderer {
|
|
|
6979
7233
|
}
|
|
6980
7234
|
return;
|
|
6981
7235
|
}
|
|
6982
|
-
|
|
7236
|
+
/* Late unless it moves with what it lies on. See `TranslucentMeshOptions.reconstructed`. */
|
|
7237
|
+
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
7238
|
+
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uViewProj.offset, lands ? this.correctedViewProj : this.viewProj);
|
|
6983
7239
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
6984
7240
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
6985
7241
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
@@ -6988,43 +7244,46 @@ export class WebGPURenderer {
|
|
|
6988
7244
|
this.perFrameFloats[this.materialField('uOpacity')] = opacity;
|
|
6989
7245
|
}
|
|
6990
7246
|
const lit = options.lit ?? true;
|
|
6991
|
-
const fog = options.fog ?? true;
|
|
7247
|
+
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
6992
7248
|
const toneMapped = options.toneMapped ?? true;
|
|
6993
|
-
const unlitOrUnfogged = !lit ||
|
|
7249
|
+
const unlitOrUnfogged = !lit || fog !== FOG_RECEDE;
|
|
6994
7250
|
if (unlitOrUnfogged) {
|
|
6995
7251
|
if (!lit)
|
|
6996
7252
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
|
|
6997
|
-
|
|
6998
|
-
this.perFrameInts[this.materialField('uFogEnabled')] = 0;
|
|
7253
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = fog;
|
|
6999
7254
|
}
|
|
7000
7255
|
if (!toneMapped) {
|
|
7001
7256
|
this.perFrameInts[this.materialField('uOutputTransform')] = Math.min(this.gradeCode(), 1);
|
|
7002
7257
|
}
|
|
7003
7258
|
const depthWrite = options.depthWrite ?? true;
|
|
7004
7259
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
7005
|
-
/*
|
|
7006
|
-
const
|
|
7260
|
+
/* A blended batch shows what is behind it — refraction or glass — as a single draw does. */
|
|
7261
|
+
const refracting = blend && this.bindSeeThrough(options);
|
|
7262
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
7007
7263
|
if (own)
|
|
7008
7264
|
this.materials.dirty();
|
|
7009
7265
|
const base = mesh.key ?? 'flat:s0:u0';
|
|
7010
7266
|
/* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
|
|
7011
7267
|
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
7012
7268
|
plain one would hand a draw the wrong layout. */
|
|
7013
|
-
const
|
|
7269
|
+
const adds = blend && options.additive === true;
|
|
7270
|
+
const key = `${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
7014
7271
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
7015
7272
|
`${this.materialDoubleSided ? '|2s' : ''}`;
|
|
7016
|
-
const pipelines = this.targetPipelines();
|
|
7273
|
+
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
7017
7274
|
let pipeline = pipelines.peek(key);
|
|
7018
7275
|
if (pipeline === undefined) {
|
|
7019
7276
|
const present = this.meshPresent.get(base);
|
|
7020
7277
|
if (present === undefined) {
|
|
7021
7278
|
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
7022
7279
|
}
|
|
7023
|
-
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(false, false), this.variant, key, present, blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided);
|
|
7280
|
+
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(false, false), this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided);
|
|
7024
7281
|
}
|
|
7025
7282
|
const material = this.materialSlotForDraw();
|
|
7026
7283
|
if (material !== null) {
|
|
7027
|
-
const command =
|
|
7284
|
+
const command = blend
|
|
7285
|
+
? this.recordBlended(0, lands)
|
|
7286
|
+
: this.recordDraw(0, this.currentTarget());
|
|
7028
7287
|
if (command !== null) {
|
|
7029
7288
|
command.pipeline = pipeline;
|
|
7030
7289
|
command.bindGroup = this.flatBindGroupFor(false, false, null);
|
|
@@ -7035,12 +7294,13 @@ export class WebGPURenderer {
|
|
|
7035
7294
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
7036
7295
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
7037
7296
|
}
|
|
7038
|
-
command.vertexBuffers[index] =
|
|
7297
|
+
command.vertexBuffers[index] = instanceBuffer;
|
|
7039
7298
|
command.vertexCount = mesh.vertexBuffers.length + 1;
|
|
7040
7299
|
command.indexBuffer = mesh.indexBuffer;
|
|
7041
7300
|
command.indexed = true;
|
|
7042
7301
|
command.count = mesh.indexCount;
|
|
7043
7302
|
command.instances = count;
|
|
7303
|
+
command.indirect = cut === null ? null : cut.args;
|
|
7044
7304
|
}
|
|
7045
7305
|
else {
|
|
7046
7306
|
const pass = this.openPass();
|
|
@@ -7052,9 +7312,12 @@ export class WebGPURenderer {
|
|
|
7052
7312
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
7053
7313
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
|
|
7054
7314
|
}
|
|
7055
|
-
pass.setVertexBuffer(index,
|
|
7315
|
+
pass.setVertexBuffer(index, instanceBuffer);
|
|
7056
7316
|
pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
|
|
7057
|
-
|
|
7317
|
+
if (cut === null)
|
|
7318
|
+
pass.drawIndexed(mesh.indexCount, count);
|
|
7319
|
+
else
|
|
7320
|
+
pass.drawIndexedIndirect(cut.args, 0);
|
|
7058
7321
|
}
|
|
7059
7322
|
}
|
|
7060
7323
|
/* Back to the defaults, unconditionally and for the reason `drawMesh` gives at length: this
|
|
@@ -7065,11 +7328,13 @@ export class WebGPURenderer {
|
|
|
7065
7328
|
}
|
|
7066
7329
|
if (unlitOrUnfogged) {
|
|
7067
7330
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
7068
|
-
this.perFrameInts[this.materialField('uFogEnabled')] =
|
|
7331
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = FOG_RECEDE;
|
|
7069
7332
|
}
|
|
7070
7333
|
if (!toneMapped) {
|
|
7071
7334
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
7072
7335
|
}
|
|
7336
|
+
if (refracting)
|
|
7337
|
+
this.releaseSeeThrough();
|
|
7073
7338
|
if (own)
|
|
7074
7339
|
this.materials.dirty();
|
|
7075
7340
|
}
|
|
@@ -7243,6 +7508,8 @@ export class WebGPURenderer {
|
|
|
7243
7508
|
this.lightViewProj.set(lightViewProj);
|
|
7244
7509
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
7245
7510
|
this.shadowLayerIsPeel = peel;
|
|
7511
|
+
this.shadowLayerIsDynamic = dynamic;
|
|
7512
|
+
this.glassCasters.clear();
|
|
7246
7513
|
/*
|
|
7247
7514
|
* A frame's peel is only as good as the pass that fills it, and the static layer is the one
|
|
7248
7515
|
* that opens first.
|
|
@@ -7348,13 +7615,295 @@ export class WebGPURenderer {
|
|
|
7348
7615
|
casters(this.casterSink);
|
|
7349
7616
|
}
|
|
7350
7617
|
/** Close the shadow pass and submit it, so the frame can sample what it wrote. */
|
|
7618
|
+
/**
|
|
7619
|
+
* Add the sun's two glass layers, once, carrying every layer it holds across on `encoder`.
|
|
7620
|
+
*
|
|
7621
|
+
* **Copied rather than re-baked**, for the reason `SunShadowArray.growForGlass` gives: the static
|
|
7622
|
+
* map is the consumer's to bake and may be baked once, and the glass that grows the texture
|
|
7623
|
+
* arrives in the very pass that drew it. Every view of the old texture is stale afterwards, so
|
|
7624
|
+
* every group holding one is rebuilt here — the lit pass's, the volumes', the medium's and the
|
|
7625
|
+
* peel's — and the old texture is destroyed only once this encoder is submitted.
|
|
7626
|
+
*/
|
|
7627
|
+
growSunForGlass(encoder) {
|
|
7628
|
+
if (this.sunGlassViews !== null)
|
|
7629
|
+
return;
|
|
7630
|
+
const device = this.surface.device;
|
|
7631
|
+
const size = this.quality.directionalShadowMapSize;
|
|
7632
|
+
const maps = this.sunLayerCount;
|
|
7633
|
+
/* The glass layers at their fixed places after the three maps: see `SUN_GLASS_STATIC_LAYER`. */
|
|
7634
|
+
const layers = SUN_GLASS_MOVING_LAYER + 1;
|
|
7635
|
+
const old = this.sunShadowTexture;
|
|
7636
|
+
const texture = device.createTexture({
|
|
7637
|
+
label: 'shadow.sun',
|
|
7638
|
+
size: [size, size, layers],
|
|
7639
|
+
format: SHADOW_FORMAT,
|
|
7640
|
+
usage: SUN_USAGE,
|
|
7641
|
+
});
|
|
7642
|
+
for (let layer = 0; layer < maps; layer++) {
|
|
7643
|
+
encoder.copyTextureToTexture({ texture: old, origin: { x: 0, y: 0, z: layer } }, { texture, origin: { x: 0, y: 0, z: layer } }, [size, size, 1]);
|
|
7644
|
+
}
|
|
7645
|
+
this.retiredSunTexture = old;
|
|
7646
|
+
this.sunShadowTexture = texture;
|
|
7647
|
+
this.sunLayerCount = layers;
|
|
7648
|
+
const views = sunViewsOf(texture, maps, layers);
|
|
7649
|
+
this.sunArrayView = views.array;
|
|
7650
|
+
this.sunStaticArrayView = views.staticArray;
|
|
7651
|
+
this.shadowView = views.static;
|
|
7652
|
+
this.dynamicView = views.dynamic;
|
|
7653
|
+
this.peelView = views.peel;
|
|
7654
|
+
this.sunGlassViews = views.glass;
|
|
7655
|
+
/* Both glass layers born at the far plane: nothing is nearer the sun than a pane not drawn. */
|
|
7656
|
+
for (const view of views.glass ?? []) {
|
|
7657
|
+
encoder
|
|
7658
|
+
.beginRenderPass({
|
|
7659
|
+
label: 'shadow.glass.clear',
|
|
7660
|
+
colorAttachments: [],
|
|
7661
|
+
depthStencilAttachment: {
|
|
7662
|
+
view,
|
|
7663
|
+
depthClearValue: 1,
|
|
7664
|
+
depthLoadOp: 'clear',
|
|
7665
|
+
depthStoreOp: 'store',
|
|
7666
|
+
},
|
|
7667
|
+
})
|
|
7668
|
+
.end();
|
|
7669
|
+
}
|
|
7670
|
+
this.rebuildSunGroups();
|
|
7671
|
+
}
|
|
7672
|
+
/** Every group that holds a view of the sun's texture, rebuilt after it has been replaced. */
|
|
7673
|
+
rebuildSunGroups() {
|
|
7674
|
+
const device = this.surface.device;
|
|
7675
|
+
const peelUniforms = this.shadowPeelPassUniforms;
|
|
7676
|
+
const peelSampler = this.shadowPeelSampler;
|
|
7677
|
+
if (peelUniforms !== null && peelSampler !== null) {
|
|
7678
|
+
this.shadowPeelBindGroup = createDepthBindGroup(device, this.shadowLayout, this.shadowDraws.buffer, peelUniforms, this.sunStaticArrayView, peelSampler);
|
|
7679
|
+
this.scatterDepthPeelBindGroup = createScatterDepthBindGroup(device, this.scatterDepthLayout, this.scatterDepthDraws.buffer, { binding: DEPTH_FRAG_BINDING, buffer: peelUniforms, size: DEPTH_FRAG_SIZE }, DEPTH_PREVIOUS_BINDING, this.sunStaticArrayView, peelSampler);
|
|
7680
|
+
}
|
|
7681
|
+
this.cutoutPeelGroups.clear();
|
|
7682
|
+
this.rebuildFlatGroupsForNewRings();
|
|
7683
|
+
this.lightVolumeBindGroup = this.buildLightVolumeBindGroup();
|
|
7684
|
+
this.mediumBindGroup = this.buildMediumBindGroup();
|
|
7685
|
+
}
|
|
7686
|
+
/** The sun's tint target, made the first time glass casts and born clear on `encoder`. */
|
|
7687
|
+
ensureSunTint(encoder) {
|
|
7688
|
+
if (this.sunTint !== null)
|
|
7689
|
+
return this.sunTint;
|
|
7690
|
+
const size = this.quality.directionalShadowMapSize;
|
|
7691
|
+
const edge = this.quality.glassShadows === 'half' ? Math.max(1, size >> 1) : size;
|
|
7692
|
+
const device = this.surface.device;
|
|
7693
|
+
const texture = device.createTexture({
|
|
7694
|
+
label: 'shadow.sunTint',
|
|
7695
|
+
size: [edge, edge, 2],
|
|
7696
|
+
format: 'rgba8unorm',
|
|
7697
|
+
mipLevelCount: Math.floor(Math.log2(edge)) + 1,
|
|
7698
|
+
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
7699
|
+
});
|
|
7700
|
+
const layer = (index) => texture.createView({
|
|
7701
|
+
dimension: '2d',
|
|
7702
|
+
baseArrayLayer: index,
|
|
7703
|
+
arrayLayerCount: 1,
|
|
7704
|
+
baseMipLevel: 0,
|
|
7705
|
+
mipLevelCount: 1,
|
|
7706
|
+
});
|
|
7707
|
+
const mips = (index) => new SnapshotMips(device, this.pipelines, texture, 'rgba8unorm', index, 'shadow.tint.mips');
|
|
7708
|
+
const target = {
|
|
7709
|
+
texture,
|
|
7710
|
+
layers: [layer(0), layer(1)],
|
|
7711
|
+
array: texture.createView({ dimension: '2d-array' }),
|
|
7712
|
+
mips: [mips(0), mips(1)],
|
|
7713
|
+
};
|
|
7714
|
+
for (const view of target.layers) {
|
|
7715
|
+
encoder
|
|
7716
|
+
.beginRenderPass({
|
|
7717
|
+
label: 'shadow.tint.clear',
|
|
7718
|
+
colorAttachments: [{ view, loadOp: 'clear', clearValue: [1, 1, 1, 1], storeOp: 'store' }],
|
|
7719
|
+
})
|
|
7720
|
+
.end();
|
|
7721
|
+
}
|
|
7722
|
+
this.sunTint = target;
|
|
7723
|
+
this.rebuildFlatGroupsForNewRings();
|
|
7724
|
+
return target;
|
|
7725
|
+
}
|
|
7726
|
+
/**
|
|
7727
|
+
* Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
|
|
7728
|
+
*
|
|
7729
|
+
* After the opaque casters, on the pass's own encoder. A layer that has held glass is redrawn
|
|
7730
|
+
* even when this pass offered none, so glass that has gone takes its colour with it.
|
|
7731
|
+
*/
|
|
7732
|
+
flushSunGlass(encoder, which) {
|
|
7733
|
+
const list = this.glassCasters;
|
|
7734
|
+
if (this.quality.glassShadows === 'off')
|
|
7735
|
+
return;
|
|
7736
|
+
if (list.count === 0 && this.sunGlassViews === null)
|
|
7737
|
+
return;
|
|
7738
|
+
if (list.count > 0)
|
|
7739
|
+
this.growSunForGlass(encoder);
|
|
7740
|
+
const glassViews = this.sunGlassViews;
|
|
7741
|
+
if (glassViews === null)
|
|
7742
|
+
return;
|
|
7743
|
+
/* Where the nearest pane is: the depth pipelines, through the sink every opaque caster took. */
|
|
7744
|
+
this.shadowPass = encoder.beginRenderPass({
|
|
7745
|
+
label: 'shadow.glass',
|
|
7746
|
+
colorAttachments: [],
|
|
7747
|
+
depthStencilAttachment: {
|
|
7748
|
+
view: glassViews[which === 'static' ? 0 : 1],
|
|
7749
|
+
depthClearValue: 1,
|
|
7750
|
+
depthLoadOp: 'clear',
|
|
7751
|
+
depthStoreOp: 'store',
|
|
7752
|
+
},
|
|
7753
|
+
});
|
|
7754
|
+
this.drawingGlassDepth = true;
|
|
7755
|
+
list.replay(this.glassDepthSink);
|
|
7756
|
+
this.drawingGlassDepth = false;
|
|
7757
|
+
this.shadowPass.end();
|
|
7758
|
+
this.shadowPass = null;
|
|
7759
|
+
/* What the panes let through, every one on a ray, multiplied. */
|
|
7760
|
+
const target = this.ensureSunTint(encoder);
|
|
7761
|
+
const tint = (this.glassTint ??= new GpuGlassTint(this.surface.device));
|
|
7762
|
+
tint.frag.reset();
|
|
7763
|
+
/* The direction the light travels, off the light matrix's depth row; the sign is immaterial,
|
|
7764
|
+
since the tint takes the cosine absolutely, and w 0 says it is a direction. */
|
|
7765
|
+
const m = this.correctedLightViewProj;
|
|
7766
|
+
const length = Math.hypot(m[2], m[6], m[10]) || 1;
|
|
7767
|
+
this.glassLight[0] = m[2] / length;
|
|
7768
|
+
this.glassLight[1] = m[6] / length;
|
|
7769
|
+
this.glassLight[2] = m[10] / length;
|
|
7770
|
+
this.glassLight[3] = 0;
|
|
7771
|
+
this.tintPass = encoder.beginRenderPass({
|
|
7772
|
+
label: 'shadow.tint',
|
|
7773
|
+
colorAttachments: [
|
|
7774
|
+
{
|
|
7775
|
+
view: target.layers[which === 'static' ? 0 : 1],
|
|
7776
|
+
loadOp: 'clear',
|
|
7777
|
+
clearValue: [1, 1, 1, 1],
|
|
7778
|
+
storeOp: 'store',
|
|
7779
|
+
},
|
|
7780
|
+
],
|
|
7781
|
+
});
|
|
7782
|
+
list.replay(this.glassTintSink);
|
|
7783
|
+
this.tintPass.end();
|
|
7784
|
+
this.tintPass = null;
|
|
7785
|
+
tint.frag.flush();
|
|
7786
|
+
/* The chain frost reads, for the layer just drawn. */
|
|
7787
|
+
target.mips[which === 'static' ? 0 : 1].record(encoder);
|
|
7788
|
+
}
|
|
7789
|
+
/** The glass list into a glass depth layer: `casterSink` itself, with glass let through. */
|
|
7790
|
+
glassDepthSink = {
|
|
7791
|
+
mesh: (mesh, model, material) => this.casterSink.mesh(mesh, model, material),
|
|
7792
|
+
instanced: (batch, data, material) => this.casterSink.instanced?.(batch, data, material),
|
|
7793
|
+
skinnedMesh: (mesh, model, palette, material) => this.casterSink.skinnedMesh(mesh, model, palette, material),
|
|
7794
|
+
};
|
|
7795
|
+
/**
|
|
7796
|
+
* The glass list into a tint layer, through the tint pipelines, **culling nothing**: every pane on
|
|
7797
|
+
* a ray counts, whichever way it faces. See the other backend's `glassTintSink`.
|
|
7798
|
+
*/
|
|
7799
|
+
glassTintSink = {
|
|
7800
|
+
mesh: (mesh, model, material, glass) => {
|
|
7801
|
+
const pass = this.tintPass;
|
|
7802
|
+
const tint = this.glassTint;
|
|
7803
|
+
if (pass === null || tint === null)
|
|
7804
|
+
return;
|
|
7805
|
+
const geometry = mesh;
|
|
7806
|
+
if (geometry.vertexBuffers === undefined)
|
|
7807
|
+
return;
|
|
7808
|
+
const cutout = cutoutOf(material);
|
|
7809
|
+
const albedo = cutout === null ? null : cutout.albedo;
|
|
7810
|
+
const slot = this.shadowDraws.allocate();
|
|
7811
|
+
const pane = tint.frag.allocate();
|
|
7812
|
+
if (slot === null || pane === null)
|
|
7813
|
+
return;
|
|
7814
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uLightViewProj.offset, this.correctedLightViewProj);
|
|
7815
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uModel.offset, model);
|
|
7816
|
+
this.writeShadowWind(slot);
|
|
7817
|
+
if (cutout !== null)
|
|
7818
|
+
this.writeCutout(slot, cutout, DEPTH_CUTOUT_VERT_FIELDS);
|
|
7819
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
7820
|
+
pass.setPipeline(tint.pipeline(this.pipelines, albedo === null ? 'rigid' : 'cutout', geometry.key ?? '', this.presentOf(geometry), 'none'));
|
|
7821
|
+
pass.setBindGroup(0, albedo === null
|
|
7822
|
+
? tint.group(this.shadowDraws.buffer)
|
|
7823
|
+
: tint.cutoutGroup(this.shadowDraws.buffer, albedo.view, albedo.sampler), [slot, pane]);
|
|
7824
|
+
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
7825
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
7826
|
+
}
|
|
7827
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
7828
|
+
pass.drawIndexed(geometry.indexCount);
|
|
7829
|
+
},
|
|
7830
|
+
instanced: (batch, data, material, glass) => {
|
|
7831
|
+
const pass = this.tintPass;
|
|
7832
|
+
const tint = this.glassTint;
|
|
7833
|
+
if (pass === null || tint === null)
|
|
7834
|
+
return;
|
|
7835
|
+
const gpuBatch = batch;
|
|
7836
|
+
const geometry = gpuBatch.mesh;
|
|
7837
|
+
if (geometry.vertexBuffers === undefined)
|
|
7838
|
+
return;
|
|
7839
|
+
const count = Math.min(data.count, gpuBatch.capacity);
|
|
7840
|
+
if (count === 0)
|
|
7841
|
+
return;
|
|
7842
|
+
const cutout = cutoutOf(material);
|
|
7843
|
+
const albedo = cutout === null ? null : cutout.albedo;
|
|
7844
|
+
const slot = this.shadowDraws.allocate();
|
|
7845
|
+
const pane = tint.frag.allocate();
|
|
7846
|
+
if (slot === null || pane === null)
|
|
7847
|
+
return;
|
|
7848
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uLightViewProj.offset, this.correctedLightViewProj);
|
|
7849
|
+
if (cutout !== null)
|
|
7850
|
+
this.writeCutout(slot, cutout, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS);
|
|
7851
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
7852
|
+
pass.setPipeline(tint.pipeline(this.pipelines, albedo === null ? 'instanced' : 'instancedCutout', geometry.key ?? '', this.presentOf(geometry), 'none'));
|
|
7853
|
+
pass.setBindGroup(0, albedo === null
|
|
7854
|
+
? tint.group(this.shadowDraws.buffer)
|
|
7855
|
+
: tint.cutoutGroup(this.shadowDraws.buffer, albedo.view, albedo.sampler), [slot, pane]);
|
|
7856
|
+
let index = 0;
|
|
7857
|
+
for (; index < geometry.vertexBuffers.length; index++) {
|
|
7858
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
7859
|
+
}
|
|
7860
|
+
pass.setVertexBuffer(index, gpuBatch.buffer);
|
|
7861
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
7862
|
+
pass.drawIndexed(geometry.indexCount, count);
|
|
7863
|
+
},
|
|
7864
|
+
skinnedMesh: (mesh, model, palette, _material, glass) => {
|
|
7865
|
+
const pass = this.tintPass;
|
|
7866
|
+
const tint = this.glassTint;
|
|
7867
|
+
if (pass === null || tint === null)
|
|
7868
|
+
return;
|
|
7869
|
+
const geometry = mesh;
|
|
7870
|
+
if (geometry.vertexBuffers === undefined)
|
|
7871
|
+
return;
|
|
7872
|
+
const paletteSlot = this.skinPalettes.take(this.surface.device, palette);
|
|
7873
|
+
const view = paletteSlot === null ? null : this.skinPalettes.view(paletteSlot);
|
|
7874
|
+
if (view === null)
|
|
7875
|
+
return;
|
|
7876
|
+
const slot = this.shadowDraws.allocate();
|
|
7877
|
+
const pane = tint.frag.allocate();
|
|
7878
|
+
if (slot === null || pane === null)
|
|
7879
|
+
return;
|
|
7880
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uLightViewProj.offset, this.correctedLightViewProj);
|
|
7881
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uModel.offset, model);
|
|
7882
|
+
this.writeShadowWind(slot);
|
|
7883
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
7884
|
+
pass.setPipeline(tint.pipeline(this.pipelines, 'skinned', geometry.key ?? '', this.presentOf(geometry), 'none'));
|
|
7885
|
+
pass.setBindGroup(0, tint.skinnedGroup(this.shadowDraws.buffer, view), [slot, pane]);
|
|
7886
|
+
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
7887
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
7888
|
+
}
|
|
7889
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
7890
|
+
pass.drawIndexed(geometry.indexCount);
|
|
7891
|
+
},
|
|
7892
|
+
};
|
|
7351
7893
|
endShadowPass() {
|
|
7352
7894
|
if (this.surface.lost || this.shadowPass === null || this.shadowEncoder === null)
|
|
7353
7895
|
return;
|
|
7896
|
+
this.shadowPass.end();
|
|
7897
|
+
/* The peel keeps no glass: its layer is the second opaque occluder and nothing else. */
|
|
7898
|
+
if (!this.shadowLayerIsPeel) {
|
|
7899
|
+
this.flushSunGlass(this.shadowEncoder, this.shadowLayerIsDynamic ? 'dynamic' : 'static');
|
|
7900
|
+
}
|
|
7354
7901
|
this.shadowDraws.flush();
|
|
7355
7902
|
this.scatterDepthDraws.flush();
|
|
7356
|
-
this.shadowPass.end();
|
|
7357
7903
|
this.surface.device.queue.submit([this.shadowEncoder.finish()]);
|
|
7904
|
+
/* A growth's old texture, copied out of by what was just submitted and needed no longer. */
|
|
7905
|
+
this.retiredSunTexture?.destroy();
|
|
7906
|
+
this.retiredSunTexture = null;
|
|
7358
7907
|
/* The map now holds a rendered peel rather than whatever it was created with. */
|
|
7359
7908
|
if (this.shadowLayerIsPeel)
|
|
7360
7909
|
this.peelFilled = true;
|
|
@@ -7383,6 +7932,8 @@ export class WebGPURenderer {
|
|
|
7383
7932
|
preparedPointLightCount = 0;
|
|
7384
7933
|
/** See `renderer.ts`: built when a world reports its lights, and rebuilt only to grow. */
|
|
7385
7934
|
pointShadowArray = null;
|
|
7935
|
+
/** The array glass replaced, destroyed once the round that copied out of it is submitted. */
|
|
7936
|
+
retiredPointTexture = null;
|
|
7386
7937
|
bakeScratch = createBakeScratch(POINT_SHADOW_POOL);
|
|
7387
7938
|
/** Said once per renderer; see `updatePointShadows`. */
|
|
7388
7939
|
warnedNoPointShadowArray = false;
|
|
@@ -7535,6 +8086,7 @@ export class WebGPURenderer {
|
|
|
7535
8086
|
*/
|
|
7536
8087
|
this.shadowDraws.reset();
|
|
7537
8088
|
this.scatterDepthDraws.reset();
|
|
8089
|
+
this.glassTint?.frag.reset();
|
|
7538
8090
|
this.shadowEncoder = this.surface.device.createCommandEncoder({ label: 'pointShadow' });
|
|
7539
8091
|
/* A cube face is a shadow pass like any other, and the courtyard is where it matters: six of
|
|
7540
8092
|
them a frame is the difference this slot exists to make visible. */
|
|
@@ -7545,7 +8097,7 @@ export class WebGPURenderer {
|
|
|
7545
8097
|
/* A frame's resolves take slots in the array's ring; see it for why they cannot share one. */
|
|
7546
8098
|
this.pointShadowArray?.beginFrame();
|
|
7547
8099
|
try {
|
|
7548
|
-
runPointShadowBakes(pool, lights, this.quality.pointShadowFacesPerFrame, this.quality.liveShadowFacesPerFrame, this.quality.pointShadowRebakeDistance, FACE_COUNT, this.bakeScratch, staticCasters, dynamicCasters, (map, light, casters, maxFaces) => map.bake(light.x, light.y, light.z, light.radius, (view, viewProj) => this.bakeFace(view, viewProj, casters), (layer, face, far, near) => this.resolveFace(layer, face, far, near), this.faceViewProj, light.shadowNear, light.sourceRadius, maxFaces));
|
|
8100
|
+
runPointShadowBakes(pool, lights, this.quality.pointShadowFacesPerFrame, this.quality.liveShadowFacesPerFrame, this.quality.pointShadowRebakeDistance, FACE_COUNT, this.bakeScratch, staticCasters, dynamicCasters, (map, light, casters, maxFaces) => map.bake(light.x, light.y, light.z, light.radius, (view, viewProj) => this.bakeFace(view, viewProj, casters), (layer, face, far, near, x, y, z) => this.resolveFace(layer, face, far, near, x, y, z), this.faceViewProj, light.shadowNear, light.sourceRadius, maxFaces));
|
|
7549
8101
|
/*
|
|
7550
8102
|
* **Inside the same encoder and the same try, which is the whole reason the rectangles' bakes
|
|
7551
8103
|
* ride this call.** Every face is its own render pass, they share one submission, and the
|
|
@@ -7553,11 +8105,14 @@ export class WebGPURenderer {
|
|
|
7553
8105
|
* encoder, a second submit and a second `shadows` timer slot for work that is the same work.
|
|
7554
8106
|
*/
|
|
7555
8107
|
this.updateAreaShadows(areaLights, frameDt, staticCasters, dynamicCasters);
|
|
8108
|
+
/* The chain frost reads, once for the round, for the lights whose glass it drew or cleared. */
|
|
8109
|
+
this.pointShadowArray?.glassTint?.recordMips(this.shadowEncoder);
|
|
7556
8110
|
}
|
|
7557
8111
|
finally {
|
|
7558
8112
|
this.depthCullMode = 'back';
|
|
7559
8113
|
this.shadowDraws.flush();
|
|
7560
8114
|
this.scatterDepthDraws.flush();
|
|
8115
|
+
this.glassTint?.frag.flush();
|
|
7561
8116
|
/*
|
|
7562
8117
|
* **Before the submit, and that ordering is the whole point of the ring.** A write to the
|
|
7563
8118
|
* queue lands ahead of the encoder's commands whenever it is issued, so uploading per
|
|
@@ -7569,6 +8124,9 @@ export class WebGPURenderer {
|
|
|
7569
8124
|
this.shadowEncoder = null;
|
|
7570
8125
|
this.shadowPass = null;
|
|
7571
8126
|
this.gpuTimer.end();
|
|
8127
|
+
/* An interleaving's old array, copied out of by what was just submitted. */
|
|
8128
|
+
this.retiredPointTexture?.destroy();
|
|
8129
|
+
this.retiredPointTexture = null;
|
|
7572
8130
|
}
|
|
7573
8131
|
/*
|
|
7574
8132
|
* **No rebind here any more.** Twelve cube views meant the bind group named twelve textures,
|
|
@@ -7578,6 +8136,10 @@ export class WebGPURenderer {
|
|
|
7578
8136
|
* written every frame regardless.
|
|
7579
8137
|
*/
|
|
7580
8138
|
pool.resolve(this.resolvedPointShadows);
|
|
8139
|
+
/* As the array stores them: 2L once glass has interleaved it. See `strideLayers`. */
|
|
8140
|
+
const stride = this.pointShadowArray.stride;
|
|
8141
|
+
strideLayers(this.resolvedPointShadows.layers, stride);
|
|
8142
|
+
strideLayers(this.resolvedPointShadows.liveLayers, stride);
|
|
7581
8143
|
}
|
|
7582
8144
|
/**
|
|
7583
8145
|
* The rectangles' round, inside the shadow encoder this backend already opened.
|
|
@@ -7603,10 +8165,13 @@ export class WebGPURenderer {
|
|
|
7603
8165
|
set.release();
|
|
7604
8166
|
}
|
|
7605
8167
|
}
|
|
7606
|
-
set.update(areaLights, frameDt, this.quality.pointShadowFacesPerFrame, this.quality.liveShadowFacesPerFrame, this.quality.pointShadowRebakeDistance, FACE_COUNT, this.areaBakeScratch, staticCasters, dynamicCasters, (map, light, range, near, casters, maxFaces) => map.bake(light.x, light.y, light.z, range, (view, viewProj) => this.bakeFace(view, viewProj, casters), (layer, face, far, mapNear) => this.resolveFace(layer, face, far, mapNear), this.faceViewProj, near,
|
|
8168
|
+
set.update(areaLights, frameDt, this.quality.pointShadowFacesPerFrame, this.quality.liveShadowFacesPerFrame, this.quality.pointShadowRebakeDistance, FACE_COUNT, this.areaBakeScratch, staticCasters, dynamicCasters, (map, light, range, near, casters, maxFaces) => map.bake(light.x, light.y, light.z, range, (view, viewProj) => this.bakeFace(view, viewProj, casters), (layer, face, far, mapNear, x, y, z) => this.resolveFace(layer, face, far, mapNear, x, y, z), this.faceViewProj, near,
|
|
7607
8169
|
/* The filter reads the emitter's size, never the bake. See `renderer.ts`'s copy. */
|
|
7608
8170
|
DEFAULT_SOURCE_RADIUS, maxFaces));
|
|
7609
8171
|
set.resolve(areaLights, this.resolvedAreaShadows);
|
|
8172
|
+
const stride = this.pointShadowArray?.stride ?? 1;
|
|
8173
|
+
strideLayers(this.resolvedAreaShadows.layer, stride);
|
|
8174
|
+
strideLayers(this.resolvedAreaShadows.liveLayer, stride);
|
|
7610
8175
|
}
|
|
7611
8176
|
/**
|
|
7612
8177
|
* One cubemap face: a depth pass, the casters, done.
|
|
@@ -7634,10 +8199,27 @@ export class WebGPURenderer {
|
|
|
7634
8199
|
depthStoreOp: 'store',
|
|
7635
8200
|
},
|
|
7636
8201
|
});
|
|
8202
|
+
/* A face is an enumeration of its own: its glass is its own. */
|
|
8203
|
+
this.glassCasters.clear();
|
|
7637
8204
|
casters(this.casterSink);
|
|
7638
8205
|
this.shadowPass.end();
|
|
7639
8206
|
this.shadowPass = null;
|
|
7640
|
-
|
|
8207
|
+
/*
|
|
8208
|
+
* The first face to offer glass interleaves the array, before its opaque depth is resolved:
|
|
8209
|
+
* `PointShadowArray.growForGlass`. The lit pass's groups hold the old array's view, so they
|
|
8210
|
+
* are rebuilt, and the old texture goes once this round's encoder — which copies out of it —
|
|
8211
|
+
* is submitted.
|
|
8212
|
+
*/
|
|
8213
|
+
const array = this.pointShadowArray;
|
|
8214
|
+
if (array !== null && this.glassCasters.count > 0 && this.quality.glassShadows !== 'off') {
|
|
8215
|
+
const tintEdge = this.quality.glassShadows === 'half' ? OCTAHEDRAL_EDGE >> 1 : OCTAHEDRAL_EDGE;
|
|
8216
|
+
const old = array.growForGlass(encoder, tintEdge, this.pipelines);
|
|
8217
|
+
if (old !== null) {
|
|
8218
|
+
this.retiredPointTexture = old;
|
|
8219
|
+
this.rebuildFlatBindGroup();
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8222
|
+
}
|
|
7641
8223
|
/**
|
|
7642
8224
|
* One rendered face into its region of the light's octahedral layer.
|
|
7643
8225
|
*
|
|
@@ -7645,12 +8227,64 @@ export class WebGPURenderer {
|
|
|
7645
8227
|
* serve every light: the face is consumed before the next one is drawn. See
|
|
7646
8228
|
* `pointShadowArray.ts` for why the regions are disjoint.
|
|
7647
8229
|
*/
|
|
7648
|
-
resolveFace(layer, face, far, near) {
|
|
8230
|
+
resolveFace(layer, face, far, near, x, y, z) {
|
|
7649
8231
|
const encoder = this.shadowEncoder;
|
|
7650
8232
|
const array = this.pointShadowArray;
|
|
7651
8233
|
if (encoder === null || array === null)
|
|
7652
8234
|
return;
|
|
7653
8235
|
array.resolve(encoder, layer, face, far, near);
|
|
8236
|
+
const tintTarget = array.glassTint;
|
|
8237
|
+
if (tintTarget === null || this.quality.glassShadows === 'off')
|
|
8238
|
+
return;
|
|
8239
|
+
/*
|
|
8240
|
+
* The face's glass, twice, as `PointShadowMap.bakeGlassFace` draws it on the other backend. A
|
|
8241
|
+
* face that offers none still clears a region a previous owner's glass is in, from scratches
|
|
8242
|
+
* cleared and not drawn into, because a pool slot changes hands with its layer.
|
|
8243
|
+
*/
|
|
8244
|
+
const offered = this.glassCasters.count > 0;
|
|
8245
|
+
if (!offered && !tintTarget.held(layer, face))
|
|
8246
|
+
return;
|
|
8247
|
+
this.shadowPass = encoder.beginRenderPass({
|
|
8248
|
+
label: 'pointShadow.glass',
|
|
8249
|
+
colorAttachments: [],
|
|
8250
|
+
depthStencilAttachment: {
|
|
8251
|
+
view: array.scratchView,
|
|
8252
|
+
depthClearValue: 1,
|
|
8253
|
+
depthLoadOp: 'clear',
|
|
8254
|
+
depthStoreOp: 'store',
|
|
8255
|
+
},
|
|
8256
|
+
});
|
|
8257
|
+
if (offered) {
|
|
8258
|
+
this.drawingGlassDepth = true;
|
|
8259
|
+
this.glassCasters.replay(this.glassDepthSink);
|
|
8260
|
+
this.drawingGlassDepth = false;
|
|
8261
|
+
}
|
|
8262
|
+
this.shadowPass.end();
|
|
8263
|
+
this.shadowPass = null;
|
|
8264
|
+
array.resolveGlass(encoder, layer, face, far, near);
|
|
8265
|
+
this.tintPass = encoder.beginRenderPass({
|
|
8266
|
+
label: 'pointShadow.tint',
|
|
8267
|
+
colorAttachments: [
|
|
8268
|
+
{
|
|
8269
|
+
view: tintTarget.scratchView,
|
|
8270
|
+
loadOp: 'clear',
|
|
8271
|
+
clearValue: [1, 1, 1, 1],
|
|
8272
|
+
storeOp: 'store',
|
|
8273
|
+
},
|
|
8274
|
+
],
|
|
8275
|
+
});
|
|
8276
|
+
if (offered) {
|
|
8277
|
+
this.glassTint ??= new GpuGlassTint(this.surface.device);
|
|
8278
|
+
/* A place, w 1: each pane's angle to the lamp comes from its own position. */
|
|
8279
|
+
this.glassLight[0] = x;
|
|
8280
|
+
this.glassLight[1] = y;
|
|
8281
|
+
this.glassLight[2] = z;
|
|
8282
|
+
this.glassLight[3] = 1;
|
|
8283
|
+
this.glassCasters.replay(this.glassTintSink);
|
|
8284
|
+
}
|
|
8285
|
+
this.tintPass.end();
|
|
8286
|
+
this.tintPass = null;
|
|
8287
|
+
tintTarget.resolve(encoder, layer, face, offered);
|
|
7654
8288
|
}
|
|
7655
8289
|
buildFlatBindGroup() {
|
|
7656
8290
|
return createFlatBindGroup(this.surface.device, this.bindGroupLayout, this.variant, this.perDraw.buffer, this.perFrame.buffer, this.flatTextures);
|
|
@@ -7959,8 +8593,10 @@ export class WebGPURenderer {
|
|
|
7959
8593
|
* drawn over a reflective floor rather than under it.
|
|
7960
8594
|
*/
|
|
7961
8595
|
this.runReflections(encoder);
|
|
7962
|
-
/* The translucent set, into its two buffers and back over the scene
|
|
7963
|
-
|
|
8596
|
+
/* The translucent set, into its two buffers and back over the scene — after the upscale instead
|
|
8597
|
+
while reconstructing, where `runLate` draws the rest of the blended frame. */
|
|
8598
|
+
if (!this.reconstructing)
|
|
8599
|
+
this.runOit(encoder);
|
|
7964
8600
|
/*
|
|
7965
8601
|
* **The medium, after everything that draws and before anything that composites.**
|
|
7966
8602
|
*
|
|
@@ -7982,6 +8618,9 @@ export class WebGPURenderer {
|
|
|
7982
8618
|
*/
|
|
7983
8619
|
this.runMotion(encoder);
|
|
7984
8620
|
this.runReconstruction(encoder);
|
|
8621
|
+
this.runLate(encoder);
|
|
8622
|
+
if (this.reconstructing)
|
|
8623
|
+
this.runOit(encoder);
|
|
7985
8624
|
const aoStrength = this.quality.ambientOcclusion;
|
|
7986
8625
|
if (aoStrength > 0 && this.frameProjection !== null)
|
|
7987
8626
|
this.runOcclusion(encoder);
|
|
@@ -8015,6 +8654,23 @@ export class WebGPURenderer {
|
|
|
8015
8654
|
f[at('uMotionStrength')] = motionStrength;
|
|
8016
8655
|
f[at('uMotionMax')] = MOTION_BLUR_MAX_UV;
|
|
8017
8656
|
f[at('uAoStrength')] = this.frameProjection === null ? 0 : aoStrength;
|
|
8657
|
+
/*
|
|
8658
|
+
* The occlusion read where this frame's jitter put it (`uAoOffset`): measured from the jittered
|
|
8659
|
+
* depth and applied to a picture the jitter has been taken out of. A texel stands at its index
|
|
8660
|
+
* plus a half less the jitter, so the occlusion of a point is a jitter further on. The
|
|
8661
|
+
* reconstruction's y is given upward and negated into the corrected matrix, which this pass's
|
|
8662
|
+
* rows run down from; the temporal resolve's goes in as it is, and arrives as it is.
|
|
8663
|
+
*/
|
|
8664
|
+
f[at('uAoOffset')] = this.reconstructing
|
|
8665
|
+
? this.reconJitter[0] / this.renderWidth
|
|
8666
|
+
: this.temporalJittering
|
|
8667
|
+
? this.temporalJitterX / this.renderWidth
|
|
8668
|
+
: 0;
|
|
8669
|
+
f[at('uAoOffset') + 1] = this.reconstructing
|
|
8670
|
+
? -this.reconJitter[1] / this.renderHeight
|
|
8671
|
+
: this.temporalJittering
|
|
8672
|
+
? this.temporalJitterY / this.renderHeight
|
|
8673
|
+
: 0;
|
|
8018
8674
|
f[at('uDofStrength')] = dofStrength;
|
|
8019
8675
|
if (dofStrength > 0 && this.frameProjection !== null) {
|
|
8020
8676
|
mat4.invert(this.dofInvProjection, this.frameProjection);
|
|
@@ -8156,6 +8812,8 @@ export class WebGPURenderer {
|
|
|
8156
8812
|
this.reconSizeKey = key;
|
|
8157
8813
|
for (const dead of [
|
|
8158
8814
|
...this.reconHistories,
|
|
8815
|
+
...this.reconFlickers,
|
|
8816
|
+
...this.reconSwings,
|
|
8159
8817
|
this.reconShown,
|
|
8160
8818
|
this.reconMotion,
|
|
8161
8819
|
this.reconPreviousDepth,
|
|
@@ -8164,6 +8822,10 @@ export class WebGPURenderer {
|
|
|
8164
8822
|
}
|
|
8165
8823
|
this.reconHistories = [null, null];
|
|
8166
8824
|
this.reconHistoryViews = [null, null];
|
|
8825
|
+
this.reconFlickers = [null, null];
|
|
8826
|
+
this.reconFlickerViews = [null, null];
|
|
8827
|
+
this.reconSwings = [null, null];
|
|
8828
|
+
this.reconSwingViews = [null, null];
|
|
8167
8829
|
this.reconShown = null;
|
|
8168
8830
|
this.reconShownView = null;
|
|
8169
8831
|
this.reconMotion = null;
|
|
@@ -8188,24 +8850,44 @@ export class WebGPURenderer {
|
|
|
8188
8850
|
});
|
|
8189
8851
|
this.reconHistories[i] = texture;
|
|
8190
8852
|
this.reconHistoryViews[i] = texture.createView();
|
|
8853
|
+
const record = device.createTexture({
|
|
8854
|
+
label: `recon.flicker${String(i)}`,
|
|
8855
|
+
size: [outputWidth, outputHeight],
|
|
8856
|
+
format: RECON_HISTORY_FORMAT,
|
|
8857
|
+
usage: HISTORY_USAGE,
|
|
8858
|
+
});
|
|
8859
|
+
this.reconFlickers[i] = record;
|
|
8860
|
+
this.reconFlickerViews[i] = record.createView();
|
|
8861
|
+
const swing = device.createTexture({
|
|
8862
|
+
label: `recon.swing${String(i)}`,
|
|
8863
|
+
size: [outputWidth, outputHeight],
|
|
8864
|
+
format: 'r32float',
|
|
8865
|
+
usage: HISTORY_USAGE,
|
|
8866
|
+
});
|
|
8867
|
+
this.reconSwings[i] = swing;
|
|
8868
|
+
this.reconSwingViews[i] = swing.createView();
|
|
8191
8869
|
}
|
|
8192
8870
|
this.reconShown = device.createTexture({
|
|
8193
8871
|
label: 'recon.shown',
|
|
8194
8872
|
size: [outputWidth, outputHeight],
|
|
8195
8873
|
format: RECON_HISTORY_FORMAT,
|
|
8196
|
-
|
|
8874
|
+
/* An attachment too, because the blended draws land on it after the upscale, and a copy
|
|
8875
|
+
source for the panes that refract it. See `runLate`. */
|
|
8876
|
+
usage: HISTORY_USAGE | 0x10 | 0x1, // RENDER_ATTACHMENT | COPY_SRC
|
|
8197
8877
|
});
|
|
8198
8878
|
this.reconShownView = this.reconShown.createView();
|
|
8199
8879
|
this.reconMotion = device.createTexture({
|
|
8200
8880
|
label: 'recon.motion',
|
|
8201
8881
|
size: [renderWidth, renderHeight],
|
|
8202
8882
|
format: RECON_HISTORY_FORMAT,
|
|
8203
|
-
/* An attachment as well, because the pass that
|
|
8204
|
-
|
|
8205
|
-
|
|
8883
|
+
/* An attachment as well, because the pass that writes it is a draw. It is cleared every
|
|
8884
|
+
frame by that pass, and a zero in the fourth channel is what tells the resolve to derive
|
|
8885
|
+
the camera's motion. */
|
|
8206
8886
|
usage: 0x4 | 0x10,
|
|
8207
8887
|
});
|
|
8208
8888
|
this.reconMotionView = this.reconMotion.createView();
|
|
8889
|
+
this.motionPass ??= new MotionPass(device);
|
|
8890
|
+
this.latePass ??= new LatePass(device);
|
|
8209
8891
|
this.reconPreviousDepth = device.createTexture({
|
|
8210
8892
|
label: 'recon.previousDepth',
|
|
8211
8893
|
size: [renderWidth, renderHeight],
|
|
@@ -8259,6 +8941,26 @@ export class WebGPURenderer {
|
|
|
8259
8941
|
viewDimension: '2d',
|
|
8260
8942
|
},
|
|
8261
8943
|
},
|
|
8944
|
+
sampled(8, 'float'),
|
|
8945
|
+
{
|
|
8946
|
+
binding: 9,
|
|
8947
|
+
visibility: COMPUTE,
|
|
8948
|
+
storageTexture: {
|
|
8949
|
+
access: 'write-only',
|
|
8950
|
+
format: RECON_HISTORY_FORMAT,
|
|
8951
|
+
viewDimension: '2d',
|
|
8952
|
+
},
|
|
8953
|
+
},
|
|
8954
|
+
sampled(10, 'unfilterable-float'),
|
|
8955
|
+
{
|
|
8956
|
+
binding: 11,
|
|
8957
|
+
visibility: COMPUTE,
|
|
8958
|
+
storageTexture: {
|
|
8959
|
+
access: 'write-only',
|
|
8960
|
+
format: 'r32float',
|
|
8961
|
+
viewDimension: '2d',
|
|
8962
|
+
},
|
|
8963
|
+
},
|
|
8262
8964
|
],
|
|
8263
8965
|
});
|
|
8264
8966
|
const module = device.createShaderModule({ label: 'recon.resolve', code: reconResolveWgsl() });
|
|
@@ -8293,7 +8995,7 @@ export class WebGPURenderer {
|
|
|
8293
8995
|
if (this.reconShownView === null)
|
|
8294
8996
|
return;
|
|
8295
8997
|
const { device } = this.surface;
|
|
8296
|
-
const build = (label, history, target) => device.createBindGroup({
|
|
8998
|
+
const build = (label, history, target, records, nextRecords, swings, nextSwings) => device.createBindGroup({
|
|
8297
8999
|
label,
|
|
8298
9000
|
layout,
|
|
8299
9001
|
entries: [
|
|
@@ -8305,223 +9007,226 @@ export class WebGPURenderer {
|
|
|
8305
9007
|
{ binding: 5, resource: this.postSampler },
|
|
8306
9008
|
{ binding: 6, resource: { buffer: params } },
|
|
8307
9009
|
{ binding: 7, resource: target },
|
|
9010
|
+
{ binding: 8, resource: records },
|
|
9011
|
+
{ binding: 9, resource: nextRecords },
|
|
9012
|
+
{ binding: 10, resource: swings },
|
|
9013
|
+
{ binding: 11, resource: nextSwings },
|
|
8308
9014
|
],
|
|
8309
9015
|
});
|
|
8310
9016
|
for (let write = 0; write < 2; write += 1) {
|
|
8311
9017
|
const read = this.reconHistoryViews[1 - write];
|
|
8312
9018
|
const target = this.reconHistoryViews[write];
|
|
8313
|
-
|
|
9019
|
+
const records = this.reconFlickerViews[1 - write];
|
|
9020
|
+
const nextRecords = this.reconFlickerViews[write];
|
|
9021
|
+
const swings = this.reconSwingViews[1 - write];
|
|
9022
|
+
const nextSwings = this.reconSwingViews[write];
|
|
9023
|
+
if (read === null || target === null || records === null || nextRecords === null)
|
|
9024
|
+
continue;
|
|
9025
|
+
if (swings === null || nextSwings === null)
|
|
8314
9026
|
continue;
|
|
8315
|
-
this.reconGroups[write] = build(`recon.resolve${String(write)}`, read, target);
|
|
8316
|
-
|
|
9027
|
+
this.reconGroups[write] = build(`recon.resolve${String(write)}`, read, target, records, nextRecords, swings, nextSwings);
|
|
9028
|
+
/* The sharpen reads no record, but one layout serves both dispatches: the same pair, so no
|
|
9029
|
+
texture is both read and written. */
|
|
9030
|
+
this.reconSharpenGroups[write] = build(`recon.sharpen${String(write)}`, target, this.reconShownView, records, nextRecords, swings, nextSwings);
|
|
8317
9031
|
}
|
|
8318
9032
|
/* The composite's own group, over the resolved picture instead of the scene target. */
|
|
8319
9033
|
this.reconRushGroup = this.buildRushBindGroup('recon.rushBindGroup', this.reconShownView);
|
|
8320
9034
|
/* And the scene's, because `ensureComposite` built it before this one existed. */
|
|
8321
9035
|
this.rebuildRushBindGroup();
|
|
9036
|
+
/* And the bloom's, which blooms the resolved picture: see `rebuildBloomGroups`. */
|
|
9037
|
+
this.rebuildBloomGroups();
|
|
9038
|
+
}
|
|
9039
|
+
/** This frame's jitter and temporal frame, decided where its scene camera is bound. See `bindMeshPass`. */
|
|
9040
|
+
jitterFrame() {
|
|
9041
|
+
this.reconstructing = this.reconstructionWanted && !this.reflectionPassActive;
|
|
9042
|
+
if (this.quality.reconstruction > 0 && this.samples > 1 && !this.reconMultisampleSaid) {
|
|
9043
|
+
this.reconMultisampleSaid = true;
|
|
9044
|
+
console.warn(RECONSTRUCTION_MULTISAMPLE_REFUSAL);
|
|
9045
|
+
}
|
|
9046
|
+
if (this.reconstructing) {
|
|
9047
|
+
/*
|
|
9048
|
+
* The offset `settleReconJitter` chose for the frame at `beginFrame`, applied here and not
|
|
9049
|
+
* advanced here: a frame binding its mesh pass twice is one frame of the sequence.
|
|
9050
|
+
*
|
|
9051
|
+
* **The y offset is negated on the way in, and that is not a preference.** This shifts the
|
|
9052
|
+
* *corrected* matrix, whose y `CLIP_CORRECTION` has already negated so that the negation
|
|
9053
|
+
* every generated vertex stage ends with cancels out — §3 row 56. So an offset added here
|
|
9054
|
+
* arrives on screen with its sign reversed, while x, which nothing negates, arrives as
|
|
9055
|
+
* given. The temporal resolve cannot see this because its shader never reads the offset; the
|
|
9056
|
+
* reconstruction reads it at every texel it unprojects, and a sign error there moves every
|
|
9057
|
+
* sample two jitters away from where the depth says it is.
|
|
9058
|
+
*/
|
|
9059
|
+
this.viewProj = jitterProjection(this.jitteredViewProj, this.correctedViewProj, this.reconJitter[0], -this.reconJitter[1], this.renderWidth, this.renderHeight);
|
|
9060
|
+
this.temporalJittering = false;
|
|
9061
|
+
this.temporalHistoryUsable = false;
|
|
9062
|
+
}
|
|
9063
|
+
this.temporalJittering =
|
|
9064
|
+
!this.reconstructing &&
|
|
9065
|
+
this.quality.temporalAa &&
|
|
9066
|
+
this.quality.screenEffects &&
|
|
9067
|
+
!this.reflectionPassActive;
|
|
9068
|
+
if (this.temporalJittering) {
|
|
9069
|
+
/* The scene's size, not the drawing buffer's: the history is a picture of the render. */
|
|
9070
|
+
this.temporalHistoryUsable = this.temporalHistory.openFrame(this.renderWidth, this.renderHeight);
|
|
9071
|
+
const [jx, jy] = jitterOffset(this.temporalHistory.frameIndex);
|
|
9072
|
+
this.temporalJitterX = jx;
|
|
9073
|
+
this.temporalJitterY = jy;
|
|
9074
|
+
/* A jitter is half a *render* texel, which is what the sequence is spread over. */
|
|
9075
|
+
this.viewProj = jitterProjection(this.jitteredViewProj, this.correctedViewProj, jx, jy, this.renderWidth, this.renderHeight);
|
|
9076
|
+
}
|
|
9077
|
+
else if (!this.reconstructing) {
|
|
9078
|
+
this.temporalHistoryUsable = false;
|
|
9079
|
+
this.viewProj = this.correctedViewProj;
|
|
9080
|
+
}
|
|
8322
9081
|
}
|
|
8323
9082
|
/**
|
|
8324
|
-
*
|
|
9083
|
+
* Whether a blended draw recorded now lands after the upscale. See `lateRouting.ts`.
|
|
8325
9084
|
*
|
|
8326
|
-
*
|
|
8327
|
-
* — which is the right thing for a scene to do, the alternative being to know what the renderer
|
|
8328
|
-
* is configured as — must cost nothing at all when nobody is going to read them.
|
|
9085
|
+
* Not during the order-independent replay, which owns its pass and issues into it directly.
|
|
8329
9086
|
*/
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
const wanted = (at + 1) * MOTION_DRAW_FLOATS;
|
|
8335
|
-
if (this.motionMatrices.length < wanted) {
|
|
8336
|
-
/* Doubling, so a scene settles on one allocation rather than one a draw. */
|
|
8337
|
-
const grown = new Float32Array(Math.max(wanted, this.motionMatrices.length * 2, 64));
|
|
8338
|
-
grown.set(this.motionMatrices);
|
|
8339
|
-
this.motionMatrices = grown;
|
|
8340
|
-
}
|
|
8341
|
-
this.motionMatrices.set(model, at * MOTION_DRAW_FLOATS);
|
|
8342
|
-
this.motionMatrices.set(previousModel, at * MOTION_DRAW_FLOATS + 16);
|
|
8343
|
-
this.motionMeshes[at] = mesh;
|
|
8344
|
-
this.motionCount = at + 1;
|
|
9087
|
+
drawsLate() {
|
|
9088
|
+
return (this.latePass !== null &&
|
|
9089
|
+
this.oitEncoder === null &&
|
|
9090
|
+
routesLate(this.reconstructing, this.reflectionPassActive, this.probePassActive, this.framePresented));
|
|
8345
9091
|
}
|
|
8346
9092
|
/**
|
|
8347
|
-
* The
|
|
8348
|
-
*
|
|
8349
|
-
*
|
|
8350
|
-
* an attribute — so this binds buffer zero and nothing else, whatever else the mesh carries.
|
|
9093
|
+
* The command a blended draw records into: the late pool while reconstructing, the frame graph
|
|
9094
|
+
* otherwise. **Late draws record whatever `quality.frameGraph` says**, since the late replay is
|
|
9095
|
+
* the only moment they can land; everything else keeps the path it has.
|
|
8351
9096
|
*/
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
this.
|
|
8357
|
-
label: 'recon.motion.layout',
|
|
8358
|
-
entries: [
|
|
8359
|
-
{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } },
|
|
8360
|
-
{
|
|
8361
|
-
binding: 1,
|
|
8362
|
-
visibility: 0x1,
|
|
8363
|
-
buffer: { type: 'uniform', hasDynamicOffset: true },
|
|
8364
|
-
},
|
|
8365
|
-
],
|
|
8366
|
-
});
|
|
8367
|
-
this.motionModule = device.createShaderModule({
|
|
8368
|
-
label: 'recon.motion',
|
|
8369
|
-
code: RECON_MOTION_WGSL,
|
|
8370
|
-
});
|
|
8371
|
-
this.motionPipelineLayout = device.createPipelineLayout({
|
|
8372
|
-
bindGroupLayouts: [this.motionLayout],
|
|
8373
|
-
});
|
|
9097
|
+
recordBlended(reads, lands = this.drawsLate()) {
|
|
9098
|
+
const late = this.latePass;
|
|
9099
|
+
if (late !== null && lands)
|
|
9100
|
+
return late.take();
|
|
9101
|
+
return this.recordDraw(reads, this.currentTarget());
|
|
8374
9102
|
}
|
|
8375
|
-
/** The
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
if (held !== undefined)
|
|
8379
|
-
return held;
|
|
8380
|
-
const module = this.motionModule;
|
|
8381
|
-
const layout = this.motionPipelineLayout;
|
|
8382
|
-
if (module === null || layout === null)
|
|
8383
|
-
return null;
|
|
8384
|
-
const { device } = this.surface;
|
|
8385
|
-
const pipeline = device.createRenderPipeline({
|
|
8386
|
-
label: `recon.motion.${String(stride)}`,
|
|
8387
|
-
layout,
|
|
8388
|
-
vertex: {
|
|
8389
|
-
module,
|
|
8390
|
-
entryPoint: 'motionVert',
|
|
8391
|
-
/* Positions are the layout's first attribute and are never optional, so they are at offset
|
|
8392
|
-
zero of every stride this can be handed. */
|
|
8393
|
-
buffers: [
|
|
8394
|
-
{
|
|
8395
|
-
arrayStride: stride,
|
|
8396
|
-
attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }],
|
|
8397
|
-
},
|
|
8398
|
-
],
|
|
8399
|
-
},
|
|
8400
|
-
fragment: { module, entryPoint: 'motionFrag', targets: [{ format: MOTION_TARGET_FORMAT }] },
|
|
8401
|
-
/*
|
|
8402
|
-
* **The scene's own winding**, because this draws the scene's geometry through the scene's
|
|
8403
|
-
* clip transform — the product the renderer hands over already carries the negation that
|
|
8404
|
-
* `naga` writes into every generated vertex stage, so the faces come out the way they did
|
|
8405
|
-
* and a `cw` front face here would draw the inside of every mesh. §3 row 57.
|
|
8406
|
-
*/
|
|
8407
|
-
primitive: { topology: 'triangle-list', cullMode: 'back' },
|
|
8408
|
-
depthStencil: {
|
|
8409
|
-
format: DEPTH_FORMAT,
|
|
8410
|
-
/*
|
|
8411
|
-
* **Tested and not written.** The frame's depth is finished; this pass only wants the
|
|
8412
|
-
* pixels the scene actually kept, and `DEPTH_COMPARE_EQUAL` is the comparison that admits
|
|
8413
|
-
* exactly them — the same transform on the same positions gives the same depth, which is
|
|
8414
|
-
* why the rasterisation matrix is handed over whole rather than rebuilt here.
|
|
8415
|
-
*
|
|
8416
|
-
* **And the equality is not fragile here**, which was worth measuring rather than
|
|
8417
|
-
* assuming: this driver contracts multiply-adds (§3 row 107) and two shader modules can
|
|
8418
|
-
* contract a matrix product differently, so an equality across them could have admitted
|
|
8419
|
-
* only some of a surface. Run with `greater-equal` instead, the ghost measured the same
|
|
8420
|
-
* to the pixel — 5,942 either way — so nothing is being dropped.
|
|
8421
|
-
*/
|
|
8422
|
-
depthWriteEnabled: false,
|
|
8423
|
-
depthCompare: DEPTH_COMPARE_EQUAL,
|
|
8424
|
-
},
|
|
8425
|
-
});
|
|
8426
|
-
this.motionPipelines.set(stride, pipeline);
|
|
8427
|
-
return pipeline;
|
|
9103
|
+
/** The pipelines a blended draw is built for: the reconstructed picture's while late. */
|
|
9104
|
+
blendedPipelines(lands = this.drawsLate()) {
|
|
9105
|
+
return lands ? this.latePipelines : this.targetPipelines();
|
|
8428
9106
|
}
|
|
8429
9107
|
/**
|
|
8430
|
-
*
|
|
8431
|
-
*
|
|
8432
|
-
*
|
|
8433
|
-
* finished for the equality test to mean anything, and the resolve is what consumes the result.
|
|
9108
|
+
* The blended draws of a reconstructing frame, over the reconstructed picture at the output size
|
|
9109
|
+
* and unjittered, against the render's depth taken up to that size. Before occlusion, bloom and
|
|
9110
|
+
* the grade, so they are graded exactly as they were when they were drawn into the scene.
|
|
8434
9111
|
*/
|
|
8435
|
-
|
|
8436
|
-
const
|
|
9112
|
+
runLate(encoder) {
|
|
9113
|
+
const late = this.latePass;
|
|
9114
|
+
const shown = this.reconShownView;
|
|
8437
9115
|
const depth = this.depthView;
|
|
8438
|
-
if (!this.reconstructing ||
|
|
9116
|
+
if (!this.reconstructing || late === null || shown === null || depth === null)
|
|
8439
9117
|
return;
|
|
8440
|
-
const
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
9118
|
+
const oitSet = this.oitActive && this.translucentQueue.length > 0;
|
|
9119
|
+
if (late.pending === 0 && !oitSet)
|
|
9120
|
+
return;
|
|
9121
|
+
const width = Math.max(1, this.surface.canvas.width);
|
|
9122
|
+
const height = Math.max(1, this.surface.canvas.height);
|
|
9123
|
+
/* The order-independent set tests against this depth too, so it is made for either. */
|
|
9124
|
+
late.upscale(encoder, depth, this.renderWidth, this.renderHeight, width, height, this.reconJitter[0], this.reconJitter[1]);
|
|
9125
|
+
if (this.lateRefractWanted && this.refractSnapshot !== null && this.reconShown !== null) {
|
|
9126
|
+
encoder.copyTextureToTexture({ texture: this.reconShown }, { texture: this.refractSnapshot }, [this.refractSnapshot.width, this.refractSnapshot.height]);
|
|
9127
|
+
this.refractMips?.record(encoder);
|
|
9128
|
+
this.refractSnapshotTaken = true;
|
|
9129
|
+
}
|
|
9130
|
+
this.lateRefractWanted = false;
|
|
9131
|
+
if (late.pending === 0)
|
|
8444
9132
|
return;
|
|
8445
|
-
/*
|
|
8446
|
-
* **Cleared every frame, even when nothing moved.** A texel left from last frame is a flag
|
|
8447
|
-
* saying "a draw wrote this", and the resolve would take a motion belonging to a surface that
|
|
8448
|
-
* is no longer there. The clear is the pass's own load operation, so an empty queue still runs
|
|
8449
|
-
* it — which is why this is not returned from before the pass is opened.
|
|
8450
|
-
*/
|
|
8451
9133
|
const pass = encoder.beginRenderPass({
|
|
8452
|
-
label: 'recon.
|
|
9134
|
+
label: 'recon.late',
|
|
8453
9135
|
timestampWrites: this.gpuTimer.writesFor(),
|
|
8454
|
-
colorAttachments: [
|
|
8455
|
-
{ view: target, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
8456
|
-
],
|
|
9136
|
+
colorAttachments: [{ view: shown, loadOp: 'load', storeOp: 'store' }],
|
|
8457
9137
|
depthStencilAttachment: {
|
|
8458
|
-
view:
|
|
9138
|
+
view: late.depthView(width, height),
|
|
8459
9139
|
depthLoadOp: 'load',
|
|
9140
|
+
/* Kept: the order-independent set replays against it after this pass. */
|
|
8460
9141
|
depthStoreOp: 'store',
|
|
8461
|
-
depthReadOnly: false,
|
|
8462
9142
|
},
|
|
8463
9143
|
});
|
|
8464
|
-
|
|
8465
|
-
if (count > 0) {
|
|
8466
|
-
const bytes = count * MOTION_DRAW_STRIDE;
|
|
8467
|
-
if (this.motionDrawCapacity < count) {
|
|
8468
|
-
this.motionDrawBuffer?.destroy();
|
|
8469
|
-
this.motionDrawCapacity = Math.max(count, this.motionDrawCapacity * 2, 16);
|
|
8470
|
-
this.motionDrawBuffer = device.createBuffer({
|
|
8471
|
-
label: 'recon.motion.draws',
|
|
8472
|
-
size: this.motionDrawCapacity * MOTION_DRAW_STRIDE,
|
|
8473
|
-
usage: 0x40 | 0x8, // UNIFORM | COPY_DST
|
|
8474
|
-
});
|
|
8475
|
-
this.motionDrawStaging = new Uint8Array(this.motionDrawCapacity * MOTION_DRAW_STRIDE);
|
|
8476
|
-
this.motionGroup = null;
|
|
8477
|
-
}
|
|
8478
|
-
this.motionFrameBuffer ??= device.createBuffer({
|
|
8479
|
-
label: 'recon.motion.frame',
|
|
8480
|
-
size: MOTION_FRAME_FLOATS * 4,
|
|
8481
|
-
usage: 0x40 | 0x8,
|
|
8482
|
-
});
|
|
8483
|
-
this.motionGroup ??= device.createBindGroup({
|
|
8484
|
-
label: 'recon.motion.group',
|
|
8485
|
-
layout,
|
|
8486
|
-
entries: [
|
|
8487
|
-
{ binding: 0, resource: { buffer: this.motionFrameBuffer } },
|
|
8488
|
-
{
|
|
8489
|
-
binding: 1,
|
|
8490
|
-
resource: { buffer: this.motionDrawBuffer, size: MOTION_DRAW_STRIDE },
|
|
8491
|
-
},
|
|
8492
|
-
],
|
|
8493
|
-
});
|
|
8494
|
-
/* The scene's clip transform, with the generated stages' y negation folded in. */
|
|
8495
|
-
mat4.multiply(this.motionRaster, MOTION_CLIP_FLIP, this.viewProj ?? this.correctedViewProj);
|
|
8496
|
-
this.motionFrameStaging.set(this.motionRaster, 0);
|
|
8497
|
-
this.motionFrameStaging.set(this.frameRawViewProj, 16);
|
|
8498
|
-
this.motionFrameStaging.set(this.reconPreviousRawViewProj, 32);
|
|
8499
|
-
device.queue.writeBuffer(this.motionFrameBuffer, 0, this.motionFrameStaging);
|
|
8500
|
-
const draws = new Float32Array(this.motionDrawStaging.buffer);
|
|
8501
|
-
for (let i = 0; i < count; i += 1) {
|
|
8502
|
-
draws.set(this.motionMatrices.subarray(i * MOTION_DRAW_FLOATS, (i + 1) * MOTION_DRAW_FLOATS), (i * MOTION_DRAW_STRIDE) / 4);
|
|
8503
|
-
}
|
|
8504
|
-
device.queue.writeBuffer(this.motionDrawBuffer, 0, this.motionDrawStaging, 0, bytes);
|
|
8505
|
-
let bound = null;
|
|
8506
|
-
for (let i = 0; i < count; i += 1) {
|
|
8507
|
-
const mesh = this.motionMeshes[i];
|
|
8508
|
-
if (mesh === undefined)
|
|
8509
|
-
continue;
|
|
8510
|
-
const pipeline = this.motionPipelineFor(mesh.vertexStride);
|
|
8511
|
-
if (pipeline === null)
|
|
8512
|
-
continue;
|
|
8513
|
-
if (pipeline !== bound) {
|
|
8514
|
-
pass.setPipeline(pipeline);
|
|
8515
|
-
bound = pipeline;
|
|
8516
|
-
}
|
|
8517
|
-
pass.setBindGroup(0, this.motionGroup, [i * MOTION_DRAW_STRIDE]);
|
|
8518
|
-
pass.setVertexBuffer(0, mesh.vertexBuffers[0]);
|
|
8519
|
-
pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
|
|
8520
|
-
pass.drawIndexed(mesh.indexCount);
|
|
8521
|
-
}
|
|
8522
|
-
}
|
|
9144
|
+
late.replay(pass, this.issueBound);
|
|
8523
9145
|
pass.end();
|
|
8524
9146
|
}
|
|
9147
|
+
/** Whether this draw lands in the reconstructed picture. See `lateRouting.ts`. */
|
|
9148
|
+
tracksMotionNow() {
|
|
9149
|
+
return tracksMotion(this.reconstructing, this.reflectionPassActive, this.probePassActive, this.framePresented);
|
|
9150
|
+
}
|
|
9151
|
+
/**
|
|
9152
|
+
* What this draw's motion is, recorded for the motion pass.
|
|
9153
|
+
*
|
|
9154
|
+
* A mesh rewritten this frame carries its vertex motion whatever else the draw says — under last
|
|
9155
|
+
* frame's model where a mover or a matrix knows it, under this frame's where nothing does. A
|
|
9156
|
+
* mover otherwise carries a rigid draw's last model or a skinned draw's last pose; a matrix
|
|
9157
|
+
* carries a rigid draw's. A draw that says nothing and moved no vertex takes the camera's motion.
|
|
9158
|
+
*/
|
|
9159
|
+
recordDrawMotion(mesh, model, previous) {
|
|
9160
|
+
const dynamic = mesh.motion;
|
|
9161
|
+
const rows = dynamic?.previous ?? null;
|
|
9162
|
+
const rewritten = rows !== null && dynamic !== null && dynamic.changed === this.moverFrame;
|
|
9163
|
+
if (previous === null) {
|
|
9164
|
+
if (rewritten)
|
|
9165
|
+
this.motionPass?.recordDynamic(mesh, model, model, rows);
|
|
9166
|
+
return;
|
|
9167
|
+
}
|
|
9168
|
+
if (!isMover(previous)) {
|
|
9169
|
+
if (rewritten)
|
|
9170
|
+
this.motionPass?.recordDynamic(mesh, model, previous, rows);
|
|
9171
|
+
else
|
|
9172
|
+
this.motionPass?.recordRigid(mesh, model, previous);
|
|
9173
|
+
return;
|
|
9174
|
+
}
|
|
9175
|
+
/* A rig is only followed where the draw really skins: a palette set, a mesh with joints. */
|
|
9176
|
+
const skinned = !rewritten && this.skinPaletteSlot >= 0 && mesh.isSkinned && mesh.skinOffsets !== null;
|
|
9177
|
+
const verdict = advanceMover(previous, this.moverRenderer, this.moverFrame, model, skinned ? this.skinPaletteData : null);
|
|
9178
|
+
if (verdict === MOVER_TWICE) {
|
|
9179
|
+
this.warnMoverTwice();
|
|
9180
|
+
return;
|
|
9181
|
+
}
|
|
9182
|
+
if (rewritten) {
|
|
9183
|
+
const was = verdict === MOVER_MOTION ? previous.previousModel : model;
|
|
9184
|
+
this.motionPass?.recordDynamic(mesh, model, was, rows);
|
|
9185
|
+
return;
|
|
9186
|
+
}
|
|
9187
|
+
if (verdict !== MOVER_MOTION)
|
|
9188
|
+
return;
|
|
9189
|
+
if (skinned)
|
|
9190
|
+
this.recordSkinnedMotion(mesh, model, previous);
|
|
9191
|
+
else
|
|
9192
|
+
this.motionPass?.recordRigid(mesh, model, previous.previousModel);
|
|
9193
|
+
}
|
|
9194
|
+
/**
|
|
9195
|
+
* A skinned mover's motion: this frame's palette is the draw's own slot, and last frame's takes a
|
|
9196
|
+
* slot of its own, like any palette a draw binds — so it is uploaded once and nothing overwrites
|
|
9197
|
+
* it before the motion pass reads it. See `SkinPaletteRing`.
|
|
9198
|
+
*/
|
|
9199
|
+
recordSkinnedMotion(mesh, model, mover) {
|
|
9200
|
+
const now = this.skinPalettes.view(this.skinPaletteSlot);
|
|
9201
|
+
const slot = this.skinPalettes.take(this.surface.device, mover.previousPalette);
|
|
9202
|
+
const was = slot === null ? null : this.skinPalettes.view(slot);
|
|
9203
|
+
if (now === null || was === null)
|
|
9204
|
+
return;
|
|
9205
|
+
this.motionPass?.recordSkinned(mesh, model, mover.previousModel, now, was);
|
|
9206
|
+
}
|
|
9207
|
+
warnMoverTwice() {
|
|
9208
|
+
if (this.warnedMoverTwice)
|
|
9209
|
+
return;
|
|
9210
|
+
this.warnedMoverTwice = true;
|
|
9211
|
+
console.warn('WebGPU: a Mover was drawn twice in one frame. A mover is one object; the second draw ' +
|
|
9212
|
+
'records no motion. Give each moving object its own `createMover()`.');
|
|
9213
|
+
}
|
|
9214
|
+
/**
|
|
9215
|
+
* Where every mover was last frame, into the motion target. See `MotionPass`.
|
|
9216
|
+
*
|
|
9217
|
+
* **After everything that draws and before the resolve reads it.** The frame's depth has to be
|
|
9218
|
+
* finished for the pass's equality test to mean anything, and the resolve is what consumes it.
|
|
9219
|
+
*/
|
|
9220
|
+
runMotion(encoder) {
|
|
9221
|
+
const target = this.reconMotionView;
|
|
9222
|
+
const depth = this.depthView;
|
|
9223
|
+
const motion = this.motionPass;
|
|
9224
|
+
if (!this.reconstructing || target === null || depth === null || motion === null)
|
|
9225
|
+
return;
|
|
9226
|
+
/* The scene's clip transform, with the generated stages' y negation folded in. */
|
|
9227
|
+
mat4.multiply(this.motionRaster, MOTION_CLIP_FLIP, this.viewProj ?? this.correctedViewProj);
|
|
9228
|
+
motion.run(encoder, target, depth, this.motionRaster, this.frameRawViewProj, this.reconPreviousRawViewProj, this.gpuTimer.writesFor());
|
|
9229
|
+
}
|
|
8525
9230
|
/**
|
|
8526
9231
|
* DriftTR tier 0: the jittered render at the render size into the picture at the output size.
|
|
8527
9232
|
*
|
|
@@ -8551,7 +9256,16 @@ export class WebGPURenderer {
|
|
|
8551
9256
|
* OpenGL's depth convention — which is the convention the shader's accessor converts the
|
|
8552
9257
|
* renderer's reversed buffer into.
|
|
8553
9258
|
*/
|
|
8554
|
-
|
|
9259
|
+
/*
|
|
9260
|
+
* The inverse and the reprojection in double precision, rounded once on the way into the
|
|
9261
|
+
* uniform: the product of two single-precision matrices this far apart in depth is not the
|
|
9262
|
+
* identity for a still camera, and the resolve read the difference as motion.
|
|
9263
|
+
*/
|
|
9264
|
+
mat4.invert(this.reconInverse64, this.frameRawViewProj);
|
|
9265
|
+
mat4.multiply(this.reconReprojection64, this.reconPreviousRawViewProj, this.reconInverse64);
|
|
9266
|
+
for (let i = 0; i < 16; i += 1) {
|
|
9267
|
+
this.reconInverseViewProj[i] = this.reconInverse64[i];
|
|
9268
|
+
}
|
|
8555
9269
|
const f = this.reconFloats;
|
|
8556
9270
|
const u = this.reconInts;
|
|
8557
9271
|
u[0] = this.renderWidth;
|
|
@@ -8575,6 +9289,17 @@ export class WebGPURenderer {
|
|
|
8575
9289
|
f[69] = DEFAULT_DISOCCLUSION.motionScale;
|
|
8576
9290
|
f[70] = DEFAULT_DISOCCLUSION.normalFloor;
|
|
8577
9291
|
f[71] = DEFAULT_DISOCCLUSION.normalCeiling;
|
|
9292
|
+
/* The anti-flicker over the reconstruction's own period, its rule made once a period. */
|
|
9293
|
+
const phases = reconJitterPhases(this.renderWidth, outputWidth);
|
|
9294
|
+
if (phases !== this.reconFlickerPhases) {
|
|
9295
|
+
this.reconFlickerPhases = phases;
|
|
9296
|
+
this.reconFlickerRule = flickerRule(phases);
|
|
9297
|
+
}
|
|
9298
|
+
u[72] = this.reconFrameIndex % phases === 0 ? 1 : 0;
|
|
9299
|
+
u[73] = 1;
|
|
9300
|
+
f[74] = this.reconFlickerRule.memory;
|
|
9301
|
+
f[75] = this.reconFlickerRule.width;
|
|
9302
|
+
f.set(this.reconReprojection64, 76);
|
|
8578
9303
|
device.queue.writeBuffer(params, 0, this.reconStaging);
|
|
8579
9304
|
const groupsX = Math.ceil(outputWidth / RECON_WORKGROUP);
|
|
8580
9305
|
const groupsY = Math.ceil(outputHeight / RECON_WORKGROUP);
|
|
@@ -8904,9 +9629,12 @@ export class WebGPURenderer {
|
|
|
8904
9629
|
return;
|
|
8905
9630
|
const accum = this.oitAccumView;
|
|
8906
9631
|
const reveal = this.oitRevealView;
|
|
8907
|
-
|
|
9632
|
+
/* After the upscale while reconstructing: over the reconstructed picture, against its depth. */
|
|
9633
|
+
const scene = this.reconstructing ? this.reconShownView : this.sceneColorView;
|
|
8908
9634
|
const group = this.oitResolveGroup;
|
|
8909
|
-
const depth = this.
|
|
9635
|
+
const depth = this.reconstructing
|
|
9636
|
+
? (this.latePass?.depthView(Math.max(1, this.surface.canvas.width), Math.max(1, this.surface.canvas.height)) ?? null)
|
|
9637
|
+
: this.depthView;
|
|
8910
9638
|
if (accum === null || reveal === null || scene === null || group === null || depth === null) {
|
|
8911
9639
|
return;
|
|
8912
9640
|
}
|
|
@@ -8920,12 +9648,13 @@ export class WebGPURenderer {
|
|
|
8920
9648
|
*/
|
|
8921
9649
|
if (this.translucentQueue.refracts && !this.refractSnapshotTaken) {
|
|
8922
9650
|
const snapshot = this.refractSnapshot;
|
|
8923
|
-
const source = this.sceneColor;
|
|
9651
|
+
const source = this.reconstructing ? this.reconShown : this.sceneColor;
|
|
8924
9652
|
if (snapshot !== null && source !== null) {
|
|
8925
9653
|
encoder.copyTextureToTexture({ texture: source }, { texture: snapshot }, [
|
|
8926
9654
|
snapshot.width,
|
|
8927
9655
|
snapshot.height,
|
|
8928
9656
|
]);
|
|
9657
|
+
this.refractMips?.record(encoder);
|
|
8929
9658
|
this.refractSnapshotTaken = true;
|
|
8930
9659
|
}
|
|
8931
9660
|
}
|
|
@@ -8964,6 +9693,7 @@ export class WebGPURenderer {
|
|
|
8964
9693
|
options.refraction = draw.refraction;
|
|
8965
9694
|
options.refractTint = draw.refractTint;
|
|
8966
9695
|
options.thicknessM = draw.thicknessM;
|
|
9696
|
+
options.glass = draw.glass ?? undefined;
|
|
8967
9697
|
this.drawTranslucentMesh(draw.mesh, draw.model, draw.opacity, options);
|
|
8968
9698
|
});
|
|
8969
9699
|
this.oitReplaying = false;
|
|
@@ -8987,7 +9717,7 @@ export class WebGPURenderer {
|
|
|
8987
9717
|
label: 'post.oit.resolve',
|
|
8988
9718
|
colorAttachments: [{ view: scene, loadOp: 'load', storeOp: 'store' }],
|
|
8989
9719
|
});
|
|
8990
|
-
resolve.setPipeline(postPipeline(this.pipelines, device, this.oitResolveLayout, 'post.oitResolve', OIT_RESOLVE_FRAG_WGSL, sceneColorFormat(this.quality.hdrScene), OIT_COMPOSITE_BLEND));
|
|
9720
|
+
resolve.setPipeline(postPipeline(this.pipelines, device, this.oitResolveLayout, this.reconstructing ? 'post.oitResolve.recon' : 'post.oitResolve', OIT_RESOLVE_FRAG_WGSL, this.reconstructing ? RECON_HISTORY_FORMAT : sceneColorFormat(this.quality.hdrScene), OIT_COMPOSITE_BLEND));
|
|
8991
9721
|
resolve.setBindGroup(0, group);
|
|
8992
9722
|
resolve.draw(3);
|
|
8993
9723
|
resolve.end();
|
|
@@ -9294,18 +10024,14 @@ export class WebGPURenderer {
|
|
|
9294
10024
|
* the drawing buffer: `ensureComposite` calls this again whenever it rebuilds that target.
|
|
9295
10025
|
*/
|
|
9296
10026
|
buildLightVolumeBindGroup() {
|
|
9297
|
-
return createLightVolumeBindGroup(this.surface.device, this.lightVolumeLayout, this.lightVolumeVariant, this.lightVolumeVertices.buffer, this.lightVolumeFragments.buffer, (name) => name === '
|
|
9298
|
-
? { view: this.
|
|
9299
|
-
:
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
the stand-in is never sampled. */
|
|
9306
|
-
name === 'uSceneDepth' && this.resolvedDepthView !== null
|
|
9307
|
-
? { view: this.resolvedDepthView, sampler: this.postDepthSampler }
|
|
9308
|
-
: this.volumeBlank);
|
|
10027
|
+
return createLightVolumeBindGroup(this.surface.device, this.lightVolumeLayout, this.lightVolumeVariant, this.lightVolumeVertices.buffer, this.lightVolumeFragments.buffer, (name) => name === 'uSunShadows'
|
|
10028
|
+
? { view: this.sunArrayView, sampler: this.volumeShadowSampler }
|
|
10029
|
+
: /* The frame's own depth, snapshotted before the first volume of the frame draws.
|
|
10030
|
+
Null under a profile with no composite, and then `uSceneDepthEnabled` is 0 and
|
|
10031
|
+
the stand-in is never sampled. */
|
|
10032
|
+
name === 'uSceneDepth' && this.resolvedDepthView !== null
|
|
10033
|
+
? { view: this.resolvedDepthView, sampler: this.postDepthSampler }
|
|
10034
|
+
: this.volumeBlank);
|
|
9309
10035
|
}
|
|
9310
10036
|
/**
|
|
9311
10037
|
* Copy the colour the frame has drawn so far, so a refracting draw can read it.
|
|
@@ -9331,6 +10057,12 @@ export class WebGPURenderer {
|
|
|
9331
10057
|
return false;
|
|
9332
10058
|
if (this.refractSnapshotTaken)
|
|
9333
10059
|
return true;
|
|
10060
|
+
/* A late draw refracts the reconstructed picture, which does not exist until the resolve has
|
|
10061
|
+
run; `runLate` takes the copy then, once, before any late draw is replayed. */
|
|
10062
|
+
if (this.drawsLate()) {
|
|
10063
|
+
this.lateRefractWanted = true;
|
|
10064
|
+
return true;
|
|
10065
|
+
}
|
|
9334
10066
|
/* The replay of an order-independent set runs after the scene's pass has ended, and `runOit`
|
|
9335
10067
|
took the copy for it before it began; there is no pass here to end and reopen. */
|
|
9336
10068
|
if (this.oitReplaying)
|
|
@@ -9372,6 +10104,8 @@ export class WebGPURenderer {
|
|
|
9372
10104
|
snapshot.width,
|
|
9373
10105
|
snapshot.height,
|
|
9374
10106
|
]);
|
|
10107
|
+
/* The chain, between the passes, where nothing is attached: what frosted glass reads. */
|
|
10108
|
+
this.refractMips?.record(encoder);
|
|
9375
10109
|
const multisampled = this.compositeMsaa() !== null;
|
|
9376
10110
|
this.pass = encoder.beginRenderPass({
|
|
9377
10111
|
label: 'refract.snapshotTaken',
|
|
@@ -9697,6 +10431,7 @@ export class WebGPURenderer {
|
|
|
9697
10431
|
command.indexBuffer = null;
|
|
9698
10432
|
command.indexed = false;
|
|
9699
10433
|
command.instances = 1;
|
|
10434
|
+
command.indirect = null;
|
|
9700
10435
|
recordNode(this.arena, VERB_DRAW, reads, writes, at, 0);
|
|
9701
10436
|
return command;
|
|
9702
10437
|
}
|
|
@@ -9723,7 +10458,13 @@ export class WebGPURenderer {
|
|
|
9723
10458
|
if (verb !== VERB_DRAW)
|
|
9724
10459
|
return;
|
|
9725
10460
|
const command = this.commands.commands[nodeState(this.arena, at)];
|
|
9726
|
-
if (command === undefined
|
|
10461
|
+
if (command === undefined)
|
|
10462
|
+
return;
|
|
10463
|
+
this.issueCommand(pass, command);
|
|
10464
|
+
}
|
|
10465
|
+
/** One recorded draw, issued into an open pass. Shared by the graph's replay and `runLate`. */
|
|
10466
|
+
issueCommand(pass, command) {
|
|
10467
|
+
if (command.pipeline === null || command.bindGroup === null)
|
|
9727
10468
|
return;
|
|
9728
10469
|
pass.setPipeline(command.pipeline);
|
|
9729
10470
|
if (command.offsetCount === 2) {
|
|
@@ -9742,7 +10483,10 @@ export class WebGPURenderer {
|
|
|
9742
10483
|
}
|
|
9743
10484
|
if (command.indexed && command.indexBuffer !== null) {
|
|
9744
10485
|
pass.setIndexBuffer(command.indexBuffer, 'uint32');
|
|
9745
|
-
|
|
10486
|
+
if (command.indirect !== null)
|
|
10487
|
+
pass.drawIndexedIndirect(command.indirect, 0);
|
|
10488
|
+
else
|
|
10489
|
+
pass.drawIndexed(command.count, command.instances);
|
|
9746
10490
|
}
|
|
9747
10491
|
else {
|
|
9748
10492
|
pass.draw(command.count, command.instances);
|
|
@@ -9972,6 +10716,7 @@ export class WebGPURenderer {
|
|
|
9972
10716
|
this.encoder = null;
|
|
9973
10717
|
}
|
|
9974
10718
|
endFrame() {
|
|
10719
|
+
this.inFrame = false;
|
|
9975
10720
|
if (this.surface.lost || this.encoder === null)
|
|
9976
10721
|
return;
|
|
9977
10722
|
/*
|
|
@@ -10357,6 +11102,10 @@ export class WebGPURenderer {
|
|
|
10357
11102
|
* declares nothing behaves exactly as it did before. Read it beside `visible`, which answers the
|
|
10358
11103
|
* other half of the same question.
|
|
10359
11104
|
*/
|
|
11105
|
+
/** Whether a world-space box is wholly behind the declared occluders. See `Renderer.occludedBox`. */
|
|
11106
|
+
occludedBox(min, max) {
|
|
11107
|
+
return this.occlusion?.occludedBox(min, max) ?? false;
|
|
11108
|
+
}
|
|
10360
11109
|
occluded(bounds, model) {
|
|
10361
11110
|
return this.occlusion?.occluded(bounds, model) ?? false;
|
|
10362
11111
|
}
|
|
@@ -10755,12 +11504,14 @@ export class WebGPURenderer {
|
|
|
10755
11504
|
const cssHeight = Math.max(canvas.clientHeight, 1);
|
|
10756
11505
|
const origin = canvas.getBoundingClientRect();
|
|
10757
11506
|
/*
|
|
10758
|
-
* **CSS pixels to the
|
|
10759
|
-
*
|
|
10760
|
-
*
|
|
11507
|
+
* **CSS pixels to the pixels of the attachment this pass draws into.** Mid-frame that is the
|
|
11508
|
+
* scene's render size, which a reconstruction makes smaller than the drawing buffer; after the
|
|
11509
|
+
* present it is the swap image, the drawing buffer itself. Measured against the scene's size
|
|
11510
|
+
* there, an interface's inset landed two thirds of the way to where it was asked for.
|
|
10761
11511
|
*/
|
|
10762
|
-
const
|
|
10763
|
-
const
|
|
11512
|
+
const target = this.passSize();
|
|
11513
|
+
const scaleX = target[0] / cssWidth;
|
|
11514
|
+
const scaleY = target[1] / cssHeight;
|
|
10764
11515
|
const left = rect.left - origin.left;
|
|
10765
11516
|
const top = rect.top - origin.top;
|
|
10766
11517
|
const w = Math.max(1, Math.round(rect.width * scaleX));
|
|
@@ -10787,9 +11538,8 @@ export class WebGPURenderer {
|
|
|
10787
11538
|
* edge for the frames it is half-off. That asymmetry is the cost of the clamp, it is
|
|
10788
11539
|
* recorded in the parity ledger, and the alternative was an exception.
|
|
10789
11540
|
*/
|
|
10790
|
-
|
|
10791
|
-
const
|
|
10792
|
-
const targetHeight = this.renderHeight;
|
|
11541
|
+
const targetWidth = target[0];
|
|
11542
|
+
const targetHeight = target[1];
|
|
10793
11543
|
const clampedX = Math.max(0, Math.min(x, targetWidth));
|
|
10794
11544
|
const clampedY = Math.max(0, Math.min(y, targetHeight));
|
|
10795
11545
|
const clampedW = Math.max(0, Math.min(x + w, targetWidth) - clampedX);
|
|
@@ -10861,10 +11611,29 @@ export class WebGPURenderer {
|
|
|
10861
11611
|
const pass = this.pass;
|
|
10862
11612
|
if (this.surface.lost || pass === null)
|
|
10863
11613
|
return;
|
|
10864
|
-
/* The whole of what this pass draws into
|
|
10865
|
-
|
|
10866
|
-
pass.
|
|
11614
|
+
/* The whole of what this pass draws into: the scene's size mid-frame, the canvas after. */
|
|
11615
|
+
const [width, height] = this.passSize();
|
|
11616
|
+
pass.setViewport(0, 0, width, height, 0, 1);
|
|
11617
|
+
pass.setScissorRect(0, 0, width, height);
|
|
10867
11618
|
}
|
|
11619
|
+
/**
|
|
11620
|
+
* The size of the attachment the open pass draws into: the swap image — the drawing buffer —
|
|
11621
|
+
* in the overlay after the present, the scene's render size in the frame's own pass. A tuple
|
|
11622
|
+
* held once rather than a new array a call.
|
|
11623
|
+
*/
|
|
11624
|
+
passSize() {
|
|
11625
|
+
const size = this.passSizeOut;
|
|
11626
|
+
if (this.overlayActive) {
|
|
11627
|
+
size[0] = Math.max(1, this.surface.canvas.width);
|
|
11628
|
+
size[1] = Math.max(1, this.surface.canvas.height);
|
|
11629
|
+
}
|
|
11630
|
+
else {
|
|
11631
|
+
size[0] = this.renderWidth;
|
|
11632
|
+
size[1] = this.renderHeight;
|
|
11633
|
+
}
|
|
11634
|
+
return size;
|
|
11635
|
+
}
|
|
11636
|
+
passSizeOut = [0, 0];
|
|
10868
11637
|
/**
|
|
10869
11638
|
* Copy a rectangle of the frame into a 2D canvas, pixel for pixel.
|
|
10870
11639
|
*
|
|
@@ -11102,6 +11871,10 @@ export class WebGPURenderer {
|
|
|
11102
11871
|
*
|
|
11103
11872
|
* What it gives up is one frame of history: the frame after a cut has no blur and no temporal
|
|
11104
11873
|
* blend, as the first frame of a session has none. Nothing else is reset.
|
|
11874
|
+
*
|
|
11875
|
+
* **A reconstructing renderer depends on it**: its history is the last shot's, and without this
|
|
11876
|
+
* call a cut blends that shot into the first frames of the next. Nothing detects a cut on the
|
|
11877
|
+
* caller's behalf, deliberately — a fast pan and a cut look alike from here.
|
|
11105
11878
|
*/
|
|
11106
11879
|
cameraCut() {
|
|
11107
11880
|
this.hasPreviousView = false;
|
|
@@ -11277,9 +12050,9 @@ export class WebGPURenderer {
|
|
|
11277
12050
|
f[atF('uFogFar')] = medium.fogFar;
|
|
11278
12051
|
this.causticsVerts.writeBlock(vertexSlot, this.causticsVertBlock);
|
|
11279
12052
|
this.causticsFrags.writeBlock(fragmentSlot, this.causticsFragInts);
|
|
11280
|
-
const causticsPipe = causticsPipeline(this.
|
|
12053
|
+
const causticsPipe = causticsPipeline(this.blendedPipelines(), this.surface.device, this.causticsLayout);
|
|
11281
12054
|
/* The pattern is generated in the shader from the two uniform blocks; nothing is sampled. */
|
|
11282
|
-
const causticsCommand = this.
|
|
12055
|
+
const causticsCommand = this.recordBlended(0);
|
|
11283
12056
|
if (causticsCommand !== null) {
|
|
11284
12057
|
causticsCommand.pipeline = causticsPipe;
|
|
11285
12058
|
causticsCommand.bindGroup = this.causticsBindGroup;
|
|
@@ -11382,8 +12155,12 @@ export class WebGPURenderer {
|
|
|
11382
12155
|
probeView = null;
|
|
11383
12156
|
probeSize;
|
|
11384
12157
|
probeMipLayout;
|
|
11385
|
-
/** The
|
|
11386
|
-
|
|
12158
|
+
/** The fixture atlas — cookies over profiles — built on first use. See `fixtureAtlas.ts`. */
|
|
12159
|
+
fixtureTexture = null;
|
|
12160
|
+
fixtureView = null;
|
|
12161
|
+
/** The profiles as packed and the cookie images, kept so either can rebuild the atlas. */
|
|
12162
|
+
iesAtlas = packIesAtlas([]);
|
|
12163
|
+
cookieImages = [];
|
|
11387
12164
|
/** The scene's DriftLight field, and its two volumes once it is whole. See `createLightField`. */
|
|
11388
12165
|
lightField = null;
|
|
11389
12166
|
driftIndex = null;
|
|
@@ -11397,10 +12174,7 @@ export class WebGPURenderer {
|
|
|
11397
12174
|
/** The frame's exact lights packed for the traced bake. See `gi/bounceLights.ts`. */
|
|
11398
12175
|
bounceLights = createBounceLights();
|
|
11399
12176
|
bakeLamps = { data: this.bounceLights, count: 0, falloff: 0 };
|
|
11400
|
-
|
|
11401
|
-
cookieTexture = null;
|
|
11402
|
-
cookieView = null;
|
|
11403
|
-
/** Tiles in the cookie atlas. 0 is the white placeholder and the whole off path. */
|
|
12177
|
+
/** Cookie tiles in the atlas. 0 is none, and the whole off path. */
|
|
11404
12178
|
cookieTiles = 0;
|
|
11405
12179
|
/** Rows in that atlas. One until a consumer loads a profile, which is the row of ones. */
|
|
11406
12180
|
iesRows = 1;
|
|
@@ -11847,35 +12621,50 @@ export class WebGPURenderer {
|
|
|
11847
12621
|
setSpotCookies(images) {
|
|
11848
12622
|
if (this.surface.lost)
|
|
11849
12623
|
return;
|
|
12624
|
+
this.cookieImages = images.slice();
|
|
12625
|
+
this.forgetFixtureAtlas();
|
|
12626
|
+
this.rebuildFlatBindGroup();
|
|
12627
|
+
}
|
|
12628
|
+
/** Let the atlas go, to be built again from what is in hand the next time it is bound. */
|
|
12629
|
+
forgetFixtureAtlas() {
|
|
12630
|
+
this.fixtureTexture?.destroy();
|
|
12631
|
+
this.fixtureTexture = null;
|
|
12632
|
+
this.fixtureView = null;
|
|
12633
|
+
}
|
|
12634
|
+
/**
|
|
12635
|
+
* The fixture atlas's view, built from the profiles and cookies in hand if it is not already: the
|
|
12636
|
+
* profiles' rows at the bottom, the cookies' tiles on top. See `renderer.ts` and `fixtureAtlas.ts`.
|
|
12637
|
+
*
|
|
12638
|
+
* **`rgba16float`, not `rgba32float`**: a 32-bit float texture is not filterable in core WebGPU,
|
|
12639
|
+
* and binding one to a filtering sampler takes the whole bind group with it. Both tables want
|
|
12640
|
+
* linear filtering. `RENDER_ATTACHMENT` because `copyExternalImageToTexture` writes through one.
|
|
12641
|
+
*/
|
|
12642
|
+
fixtureAtlasView() {
|
|
12643
|
+
if (this.fixtureView !== null)
|
|
12644
|
+
return this.fixtureView;
|
|
11850
12645
|
const { device } = this.surface;
|
|
11851
|
-
const
|
|
11852
|
-
const
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
/* White, not black: this multiplies a light's colour. */
|
|
11867
|
-
device.queue.writeTexture({ texture: this.cookieTexture }, new Uint8Array([255, 255, 255, 255]), { bytesPerRow: 4, rowsPerImage: 1 }, { width: 1, height: 1, depthOrArrayLayers: 1 });
|
|
11868
|
-
}
|
|
11869
|
-
else {
|
|
11870
|
-
for (let tile = 0; tile < tiles; tile++) {
|
|
11871
|
-
const source = images[tile];
|
|
11872
|
-
if (source === undefined)
|
|
11873
|
-
continue;
|
|
11874
|
-
device.queue.copyExternalImageToTexture({ source: source }, { texture: this.cookieTexture, origin: { x: tile * COOKIE_TILE, y: 0 } }, { width: COOKIE_TILE, height: COOKIE_TILE });
|
|
11875
|
-
}
|
|
12646
|
+
const layout = fixtureAtlasLayout(this.iesAtlas.height, this.cookieImages.length);
|
|
12647
|
+
const texture = device.createTexture({
|
|
12648
|
+
label: 'fixture.atlas',
|
|
12649
|
+
size: [layout.width, layout.height],
|
|
12650
|
+
format: 'rgba16float',
|
|
12651
|
+
usage: 0x4 | 0x2 | 0x10,
|
|
12652
|
+
});
|
|
12653
|
+
device.queue.writeTexture({ texture },
|
|
12654
|
+
/* Converted, because `writeTexture` does not — see `environmentTexels`. */
|
|
12655
|
+
toHalfFloats(fixtureAtlasTexels(layout, this.iesAtlas)), { bytesPerRow: layout.width * 8, rowsPerImage: layout.height }, { width: layout.width, height: layout.height, depthOrArrayLayers: 1 });
|
|
12656
|
+
for (let tile = 0; tile < this.cookieImages.length; tile++) {
|
|
12657
|
+
const source = this.cookieImages[tile];
|
|
12658
|
+
if (source === undefined)
|
|
12659
|
+
continue;
|
|
12660
|
+
device.queue.copyExternalImageToTexture({ source: source }, { texture, origin: { x: tile * COOKIE_TILE, y: 0 } }, { width: COOKIE_TILE, height: COOKIE_TILE });
|
|
11876
12661
|
}
|
|
11877
|
-
this.
|
|
11878
|
-
this.
|
|
12662
|
+
this.fixtureTexture = texture;
|
|
12663
|
+
this.fixtureView = texture.createView();
|
|
12664
|
+
this.iesRows = this.iesAtlas.height;
|
|
12665
|
+
this.iesPlanes = this.iesAtlas.planes;
|
|
12666
|
+
this.cookieTiles = this.cookieImages.length;
|
|
12667
|
+
return this.fixtureView;
|
|
11879
12668
|
}
|
|
11880
12669
|
/**
|
|
11881
12670
|
* Sum a scene's many fixed lights into a DriftLight field. The same contract as
|
|
@@ -11887,6 +12676,19 @@ export class WebGPURenderer {
|
|
|
11887
12676
|
this.lightField = field;
|
|
11888
12677
|
return field;
|
|
11889
12678
|
}
|
|
12679
|
+
/**
|
|
12680
|
+
* Hand a world's DriftLight volume to the renderer: every fixed light of a city, summed offline by
|
|
12681
|
+
* `bakeDenseField`, standing in past the frame's exact choice. Replaces any field made before, of
|
|
12682
|
+
* either kind; the scene calls `field.follow(buffer.complete, ...)` each frame as it would for
|
|
12683
|
+
* `createLightField`, and marks the lights the volume summed `inLightField` so none is counted
|
|
12684
|
+
* twice. See `driftLight/worldLightField.ts`.
|
|
12685
|
+
*/
|
|
12686
|
+
createWorldLightField(volume, options = {}) {
|
|
12687
|
+
this.forgetLightFieldVolumes();
|
|
12688
|
+
const field = new WorldLightField(volume, options);
|
|
12689
|
+
this.lightField = field;
|
|
12690
|
+
return field;
|
|
12691
|
+
}
|
|
11890
12692
|
/** Let go of the field and its volumes; the scene's lights are all exact again. */
|
|
11891
12693
|
disposeLightField() {
|
|
11892
12694
|
this.forgetLightFieldVolumes();
|
|
@@ -11912,13 +12714,13 @@ export class WebGPURenderer {
|
|
|
11912
12714
|
*/
|
|
11913
12715
|
uploadLightField() {
|
|
11914
12716
|
const field = this.lightField;
|
|
11915
|
-
if (field === null || this.driftAtlas !== null || !field.ready || field.
|
|
12717
|
+
if (field === null || this.driftAtlas !== null || !field.ready || field.empty) {
|
|
11916
12718
|
return;
|
|
11917
12719
|
}
|
|
11918
12720
|
if (this.surface.lost)
|
|
11919
12721
|
return;
|
|
11920
12722
|
const { device } = this.surface;
|
|
11921
|
-
const [ix, iy, iz] = field.
|
|
12723
|
+
const [ix, iy, iz] = field.indexDims;
|
|
11922
12724
|
const index = device.createTexture({
|
|
11923
12725
|
label: 'driftLight.index',
|
|
11924
12726
|
size: [ix, iy, iz],
|
|
@@ -11926,7 +12728,7 @@ export class WebGPURenderer {
|
|
|
11926
12728
|
format: 'r32uint',
|
|
11927
12729
|
usage: 0x2 | 0x4, // COPY_DST | TEXTURE_BINDING
|
|
11928
12730
|
});
|
|
11929
|
-
device.queue.writeTexture({ texture: index }, field.
|
|
12731
|
+
device.queue.writeTexture({ texture: index }, field.index, { bytesPerRow: ix * 4, rowsPerImage: iy }, [ix, iy, iz]);
|
|
11930
12732
|
const [aw, ah, ad] = field.atlasSize;
|
|
11931
12733
|
const atlas = device.createTexture({
|
|
11932
12734
|
label: 'driftLight.atlas',
|
|
@@ -11951,41 +12753,8 @@ export class WebGPURenderer {
|
|
|
11951
12753
|
setIesProfiles(profiles) {
|
|
11952
12754
|
if (this.surface.lost)
|
|
11953
12755
|
return;
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
/*
|
|
11957
|
-
* Reallocated when the row count changes, because a texture's size is immutable. A set of the
|
|
11958
|
-
* same size overwrites in place, which is the common case of a consumer swapping one fixture.
|
|
11959
|
-
*/
|
|
11960
|
-
if (this.iesTexture === null || this.iesTexture.height !== atlas.height) {
|
|
11961
|
-
this.iesTexture?.destroy();
|
|
11962
|
-
this.iesTexture = device.createTexture({
|
|
11963
|
-
label: 'ies.atlas',
|
|
11964
|
-
size: [atlas.width, atlas.height],
|
|
11965
|
-
/*
|
|
11966
|
-
* **`r16float`, not `r32float`, and that is a filtering constraint rather than a size
|
|
11967
|
-
* choice.** A 32-bit float texture is not filterable in core WebGPU — it needs the
|
|
11968
|
-
* optional `float32-filterable` feature — and binding one to a filtering sampler is a
|
|
11969
|
-
* validation failure that takes the whole bind group with it. This row *wants* linear
|
|
11970
|
-
* filtering: it is a curve sampled between two angles, which is the entire reason it is
|
|
11971
|
-
* not `NEAREST` like the froxel table beside it.
|
|
11972
|
-
*
|
|
11973
|
-
* Half precision holds a normalised intensity to about three decimal digits, which is far
|
|
11974
|
-
* finer than the eight-bit frame it ends up in. **What would make it wrong** is a profile
|
|
11975
|
-
* carrying absolute candela rather than a normalised curve, which would run out of range —
|
|
11976
|
-
* and `packIesAtlas` normalises for its own reasons.
|
|
11977
|
-
*/
|
|
11978
|
-
format: 'r16float',
|
|
11979
|
-
usage: 0x4 | 0x2, // TEXTURE_BINDING | COPY_DST
|
|
11980
|
-
});
|
|
11981
|
-
this.iesView = this.iesTexture.createView();
|
|
11982
|
-
}
|
|
11983
|
-
device.queue.writeTexture({ texture: this.iesTexture },
|
|
11984
|
-
/* Converted, because `writeTexture` does not — see `environmentTexels` for what handing it
|
|
11985
|
-
the wrong width silently produces. */
|
|
11986
|
-
toHalfFloats(atlas.data), { bytesPerRow: atlas.width * 2, rowsPerImage: atlas.height }, { width: atlas.width, height: atlas.height, depthOrArrayLayers: 1 });
|
|
11987
|
-
this.iesRows = atlas.height;
|
|
11988
|
-
this.iesPlanes = atlas.planes;
|
|
12756
|
+
this.iesAtlas = packIesAtlas(profiles);
|
|
12757
|
+
this.forgetFixtureAtlas();
|
|
11989
12758
|
this.rebuildFlatBindGroup();
|
|
11990
12759
|
}
|
|
11991
12760
|
/** Each level from the one above it, six faces at a time. See `probePass.ts`. */
|
|
@@ -12158,6 +12927,33 @@ export class WebGPURenderer {
|
|
|
12158
12927
|
* The occlusion and bloom targets are half size. Both are blurs read back at full size, so a
|
|
12159
12928
|
* full-resolution intermediate buys nothing a bilinear fetch does not already give.
|
|
12160
12929
|
*/
|
|
12930
|
+
/**
|
|
12931
|
+
* The medium's march group, holding the shadow views it reads — built with the composite, and
|
|
12932
|
+
* again when the sun's texture grows its glass layers and every view of the old one is stale.
|
|
12933
|
+
*
|
|
12934
|
+
* **The stand-in has to read as unoccluded and not as absent**, which is `emptyTexture.ts`'s
|
|
12935
|
+
* point and `createLightVolumeBindGroup`'s: `occlusion()` calls any stored depth below the
|
|
12936
|
+
* receiver's a blocker — so a black stand-in would put the whole medium in shadow, which is a
|
|
12937
|
+
* picture rather than an error. `volumeBlank` is the white one-texel texture built for this.
|
|
12938
|
+
*/
|
|
12939
|
+
buildMediumBindGroup() {
|
|
12940
|
+
if (this.resolvedDepthView === null)
|
|
12941
|
+
return null;
|
|
12942
|
+
return this.surface.device.createBindGroup({
|
|
12943
|
+
label: 'post.mediumBindGroup',
|
|
12944
|
+
layout: this.mediumLayout,
|
|
12945
|
+
entries: [
|
|
12946
|
+
{
|
|
12947
|
+
binding: MEDIUM_UNIFORMS,
|
|
12948
|
+
resource: { buffer: this.mediumUniforms, size: MEDIUM_FRAG_SIZE },
|
|
12949
|
+
},
|
|
12950
|
+
{ binding: MEDIUM_TEXTURES.uDepth.texture, resource: this.resolvedDepthView },
|
|
12951
|
+
{ binding: MEDIUM_TEXTURES.uDepth.sampler, resource: this.postDepthSampler },
|
|
12952
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows.texture, resource: this.sunArrayView },
|
|
12953
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows.sampler, resource: this.volumeShadowSampler },
|
|
12954
|
+
],
|
|
12955
|
+
});
|
|
12956
|
+
}
|
|
12161
12957
|
ensureComposite(width, height) {
|
|
12162
12958
|
if (!this.hasComposite)
|
|
12163
12959
|
return;
|
|
@@ -12213,13 +13009,29 @@ export class WebGPURenderer {
|
|
|
12213
13009
|
* the allocation the house rules are about. A frame that never refracts pays the memory and
|
|
12214
13010
|
* takes no copy.
|
|
12215
13011
|
*/
|
|
13012
|
+
/*
|
|
13013
|
+
* **At the output size and the reconstructed picture's format while reconstructing**, because
|
|
13014
|
+
* every refracting draw is then blended, drawn after the upscale, and reads the reconstructed
|
|
13015
|
+
* picture rather than the render. See `runLate`.
|
|
13016
|
+
*/
|
|
13017
|
+
const reconstructed = this.reconstructionWanted;
|
|
12216
13018
|
this.refractSnapshot = device.createTexture({
|
|
12217
13019
|
label: 'refract.snapshot',
|
|
12218
|
-
size:
|
|
12219
|
-
|
|
12220
|
-
|
|
13020
|
+
size: reconstructed
|
|
13021
|
+
? [Math.max(1, this.surface.canvas.width), Math.max(1, this.surface.canvas.height)]
|
|
13022
|
+
: [width, height],
|
|
13023
|
+
format: reconstructed ? RECON_HISTORY_FORMAT : format,
|
|
13024
|
+
/*
|
|
13025
|
+
* **Every level, so glass can read a blurrier one**, and a render attachment so `SnapshotMips`
|
|
13026
|
+
* can fill them. Plain refraction reads level 0, as it always has. See `glass.ts`.
|
|
13027
|
+
*/
|
|
13028
|
+
mipLevelCount: Math.floor(Math.log2(reconstructed
|
|
13029
|
+
? Math.max(1, this.surface.canvas.width, this.surface.canvas.height)
|
|
13030
|
+
: Math.max(1, width, height))) + 1,
|
|
13031
|
+
usage: 0x4 | 0x2 | 0x10, // TEXTURE_BINDING | COPY_DST | RENDER_ATTACHMENT
|
|
12221
13032
|
});
|
|
12222
13033
|
this.refractSnapshotView = this.refractSnapshot.createView();
|
|
13034
|
+
this.refractMips = new SnapshotMips(device, this.pipelines, this.refractSnapshot, reconstructed ? RECON_HISTORY_FORMAT : format);
|
|
12223
13035
|
/*
|
|
12224
13036
|
* **Every cached flat bind group is stale the moment this view changes**, and the failure is
|
|
12225
13037
|
* silent: `flatTextures` resolves `uRefractScene` to the one-texel white stand-in when there is
|
|
@@ -12359,10 +13171,14 @@ export class WebGPURenderer {
|
|
|
12359
13171
|
* that range — a step of 1/255 in how much scene shows through, below what a viewer can see
|
|
12360
13172
|
* against a blended surface.
|
|
12361
13173
|
*/
|
|
13174
|
+
/* At the output size while reconstructing, where the set is replayed after the upscale. */
|
|
13175
|
+
const oitSize = this.reconstructionWanted
|
|
13176
|
+
? [Math.max(1, this.surface.canvas.width), Math.max(1, this.surface.canvas.height)]
|
|
13177
|
+
: [width, height];
|
|
12362
13178
|
this.oitAccum?.destroy();
|
|
12363
13179
|
this.oitAccum = device.createTexture({
|
|
12364
13180
|
label: 'post.oitAccum',
|
|
12365
|
-
size:
|
|
13181
|
+
size: oitSize,
|
|
12366
13182
|
format: 'rgba16float',
|
|
12367
13183
|
usage: USAGE,
|
|
12368
13184
|
});
|
|
@@ -12370,7 +13186,7 @@ export class WebGPURenderer {
|
|
|
12370
13186
|
this.oitReveal?.destroy();
|
|
12371
13187
|
this.oitReveal = device.createTexture({
|
|
12372
13188
|
label: 'post.oitReveal',
|
|
12373
|
-
size:
|
|
13189
|
+
size: oitSize,
|
|
12374
13190
|
format: 'r8unorm',
|
|
12375
13191
|
usage: USAGE,
|
|
12376
13192
|
});
|
|
@@ -12383,7 +13199,10 @@ export class WebGPURenderer {
|
|
|
12383
13199
|
* An empty list is a real answer for a frame too small to hold one, and `runBloom` reads it
|
|
12384
13200
|
* as no bloom rather than as a target it should have had.
|
|
12385
13201
|
*/
|
|
12386
|
-
|
|
13202
|
+
/* The resolved picture's size while reconstructing, as the order-independent pair above is. */
|
|
13203
|
+
this.bloomSourceWidth = oitSize[0];
|
|
13204
|
+
this.bloomSourceHeight = oitSize[1];
|
|
13205
|
+
this.bloomLevels = bloomLevelSizes(oitSize[0], oitSize[1]).map((size, index) => {
|
|
12387
13206
|
const texture = device.createTexture({
|
|
12388
13207
|
label: `post.bloom${index}`,
|
|
12389
13208
|
size: [size.width, size.height],
|
|
@@ -12469,39 +13288,7 @@ export class WebGPURenderer {
|
|
|
12469
13288
|
...entry(AO_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
12470
13289
|
],
|
|
12471
13290
|
});
|
|
12472
|
-
|
|
12473
|
-
* The medium's two groups, holding the shadow views the march reads.
|
|
12474
|
-
*
|
|
12475
|
-
* **The stand-in has to read as unoccluded and not as absent**, which is `emptyTexture.ts`'s
|
|
12476
|
-
* point and `createLightVolumeBindGroup`'s: the dynamic layer is sampled unconditionally, and
|
|
12477
|
-
* `occlusion()` calls any stored depth below the receiver's a blocker — so a black stand-in
|
|
12478
|
-
* would put the whole medium in shadow, which is a picture rather than an error. `volumeBlank`
|
|
12479
|
-
* is the white one-texel texture already built for exactly this.
|
|
12480
|
-
*/
|
|
12481
|
-
const shadowPair = (b, view) => [
|
|
12482
|
-
{ binding: b.texture, resource: view ?? this.volumeBlank.view },
|
|
12483
|
-
{
|
|
12484
|
-
binding: b.sampler,
|
|
12485
|
-
resource: view === null ? this.volumeBlank.sampler : this.volumeShadowSampler,
|
|
12486
|
-
},
|
|
12487
|
-
];
|
|
12488
|
-
this.mediumBindGroup =
|
|
12489
|
-
this.resolvedDepthView === null
|
|
12490
|
-
? null
|
|
12491
|
-
: device.createBindGroup({
|
|
12492
|
-
label: 'post.mediumBindGroup',
|
|
12493
|
-
layout: this.mediumLayout,
|
|
12494
|
-
entries: [
|
|
12495
|
-
{
|
|
12496
|
-
binding: MEDIUM_UNIFORMS,
|
|
12497
|
-
resource: { buffer: this.mediumUniforms, size: MEDIUM_FRAG_SIZE },
|
|
12498
|
-
},
|
|
12499
|
-
...entry(MEDIUM_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
12500
|
-
...shadowPair(MEDIUM_TEXTURES.uStaticShadowMap, this.shadowView),
|
|
12501
|
-
...shadowPair(MEDIUM_TEXTURES.uPeeledShadowMap, this.peelView),
|
|
12502
|
-
...shadowPair(MEDIUM_TEXTURES.uDynamicShadowMap, this.dynamicView),
|
|
12503
|
-
],
|
|
12504
|
-
});
|
|
13291
|
+
this.mediumBindGroup = this.buildMediumBindGroup();
|
|
12505
13292
|
this.mediumUpsampleGroup =
|
|
12506
13293
|
this.resolvedDepthView === null || this.mediumTargetView === null
|
|
12507
13294
|
? null
|
|
@@ -12580,13 +13367,30 @@ export class WebGPURenderer {
|
|
|
12580
13367
|
/* The volume's group holds the depth snapshot, which is one of the targets just replaced.
|
|
12581
13368
|
Keeping the old group would clamp every beam against a destroyed texture. */
|
|
12582
13369
|
this.lightVolumeBindGroup = this.buildLightVolumeBindGroup();
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
|
|
12588
|
-
|
|
12589
|
-
|
|
13370
|
+
this.rebuildBloomGroups();
|
|
13371
|
+
this.writeBloomBlocks();
|
|
13372
|
+
}
|
|
13373
|
+
/**
|
|
13374
|
+
* One group per source the bloom chain reads, in the order `runBloom` indexes them: the picture
|
|
13375
|
+
* first, then every level. The prefilter reads the picture; the downsample into level `i` reads
|
|
13376
|
+
* level `i - 1`, which is group `i`; the upsample into level `i - 1` reads level `i`, which is
|
|
13377
|
+
* group `i + 1`.
|
|
13378
|
+
*
|
|
13379
|
+
* **While reconstructing the picture is the resolved one**, which `ensureReconstruction` makes
|
|
13380
|
+
* after this runs and rebuilds these over. The scene target is the jittered render then: a bright
|
|
13381
|
+
* thing smaller than a render texel is caught in some phases and missed in others, and its halo,
|
|
13382
|
+
* wide and over everything round it, flashed with it — a night street's panels breathing a few
|
|
13383
|
+
* levels of luma frame to frame. Built at the drawing buffer's size, which `ensureComposite`
|
|
13384
|
+
* already sized the pyramid for.
|
|
13385
|
+
*/
|
|
13386
|
+
rebuildBloomGroups() {
|
|
13387
|
+
const picture = this.reconstructionWanted && this.reconShownView !== null
|
|
13388
|
+
? this.reconShownView
|
|
13389
|
+
: this.sceneColorView;
|
|
13390
|
+
if (picture === null)
|
|
13391
|
+
return;
|
|
13392
|
+
const { device } = this.surface;
|
|
13393
|
+
this.bloomGroups = [picture, ...this.bloomLevels.map((level) => level.view)].map((view, index) => device.createBindGroup({
|
|
12590
13394
|
label: `post.bloomGroup${index}`,
|
|
12591
13395
|
layout: this.bloomLayout,
|
|
12592
13396
|
entries: [
|
|
@@ -12594,10 +13398,10 @@ export class WebGPURenderer {
|
|
|
12594
13398
|
binding: BLOOM_UNIFORMS,
|
|
12595
13399
|
resource: { buffer: this.bloomUniforms, size: BLOOM_STAGE_SIZE },
|
|
12596
13400
|
},
|
|
12597
|
-
|
|
13401
|
+
{ binding: BLOOM_TEXTURES.uSource.texture, resource: view },
|
|
13402
|
+
{ binding: BLOOM_TEXTURES.uSource.sampler, resource: this.postSampler },
|
|
12598
13403
|
],
|
|
12599
13404
|
}));
|
|
12600
|
-
this.writeBloomBlocks();
|
|
12601
13405
|
}
|
|
12602
13406
|
/**
|
|
12603
13407
|
* Every bloom stage's uniform block, written once per size rather than once per frame.
|
|
@@ -12614,12 +13418,12 @@ export class WebGPURenderer {
|
|
|
12614
13418
|
if (levels.length === 0)
|
|
12615
13419
|
return;
|
|
12616
13420
|
const slot = (index) => (index * BLOOM_SLOT) / 4;
|
|
12617
|
-
/* The prefilter reads the *
|
|
13421
|
+
/* The prefilter reads the *picture*, so its texel is the picture's rather than a level's.
|
|
12618
13422
|
`uTexel`, not `uTexelSize`, and the block carries no strength at all: the composite
|
|
12619
13423
|
applies that when it adds the result back. Both were written wrong here first. */
|
|
12620
13424
|
const prefilter = (name) => this.postField(BLOOM_PREFILTER_FIELDS, name);
|
|
12621
|
-
f[slot(0) + prefilter('uTexel')] = 1 / Math.max(1, this.
|
|
12622
|
-
f[slot(0) + prefilter('uTexel') + 1] = 1 / Math.max(1, this.
|
|
13425
|
+
f[slot(0) + prefilter('uTexel')] = 1 / Math.max(1, this.bloomSourceWidth);
|
|
13426
|
+
f[slot(0) + prefilter('uTexel') + 1] = 1 / Math.max(1, this.bloomSourceHeight);
|
|
12623
13427
|
/* In scene units, which is why an HDR target is what makes a threshold above 1 mean
|
|
12624
13428
|
anything: against a clamped buffer nothing is ever brighter than white. */
|
|
12625
13429
|
f[slot(0) + prefilter('uThreshold')] = this.bloomThresholdSet ?? this.quality.bloomThreshold;
|
|
@@ -12684,6 +13488,10 @@ export class WebGPURenderer {
|
|
|
12684
13488
|
* the 2026-08-13 rule asks for — one decision, bound twice.
|
|
12685
13489
|
*/
|
|
12686
13490
|
dispose() {
|
|
13491
|
+
this.motionPass?.dispose();
|
|
13492
|
+
this.motionPass = null;
|
|
13493
|
+
this.latePass?.dispose();
|
|
13494
|
+
this.latePass = null;
|
|
12687
13495
|
for (const texture of this.retiredTextures)
|
|
12688
13496
|
texture.destroy();
|
|
12689
13497
|
this.retiredTextures.length = 0;
|
|
@@ -12700,6 +13508,13 @@ export class WebGPURenderer {
|
|
|
12700
13508
|
this.fieldComposer = null;
|
|
12701
13509
|
this.driftIndex?.destroy();
|
|
12702
13510
|
this.driftAtlas?.destroy();
|
|
13511
|
+
this.forgetFixtureAtlas();
|
|
13512
|
+
this.sunTint?.texture.destroy();
|
|
13513
|
+
this.sunTint = null;
|
|
13514
|
+
this.glassTint?.dispose();
|
|
13515
|
+
this.glassTint = null;
|
|
13516
|
+
this.retiredSunTexture?.destroy();
|
|
13517
|
+
this.retiredSunTexture = null;
|
|
12703
13518
|
this.pass = null;
|
|
12704
13519
|
this.encoder = null;
|
|
12705
13520
|
this.skinPalettes.dispose();
|