@driftengine/core 4.4.1 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +72 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +82 -8
- package/dist/render/backend/webgl2/renderer.js +373 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +21 -15
- package/dist/render/backend/webgpu/postPass.js +17 -4
- package/dist/render/backend/webgpu/renderer.d.ts +181 -54
- package/dist/render/backend/webgpu/renderer.js +1011 -394
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- package/dist/render/flameLight.js +15 -2
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowBudget.js +51 -41
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/disocclusion.d.ts +36 -3
- package/dist/render/recon/disocclusion.js +56 -4
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/recon/resolve.js +95 -12
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +9 -1
- package/dist/render/sceneTarget.js +59 -13
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +4 -0
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +115 -15
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
- package/dist/render/shaders/temporalResolve.js +133 -5
- package/dist/render/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/temporalAa.d.ts +145 -0
- package/dist/render/temporalAa.js +190 -0
- package/dist/render/temporalPass.d.ts +16 -14
- package/dist/render/temporalPass.js +59 -14
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +422 -109
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/postPass.ts +34 -4
- package/src/render/backend/webgpu/renderer.ts +1178 -425
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
- package/src/render/flameLight.ts +15 -2
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowBudget.ts +49 -39
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/disocclusion.ts +54 -3
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/recon/resolve.ts +137 -10
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +66 -12
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
- package/src/render/shaders/temporalResolve.ts +141 -5
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/temporalAa.ts +227 -0
- package/src/render/temporalPass.ts +88 -12
- package/src/render/translucentQueue.ts +20 -1
|
@@ -9,12 +9,7 @@ import {
|
|
|
9
9
|
} from '../../temporalAa.ts';
|
|
10
10
|
import { OIT_MULTISAMPLE_REFUSAL } from '../../orderIndependent.ts';
|
|
11
11
|
import { TranslucentQueue } from '../../translucentQueue.ts';
|
|
12
|
-
import {
|
|
13
|
-
DEPTH_CLEAR,
|
|
14
|
-
DEPTH_COMPARE_EQUAL,
|
|
15
|
-
MAX_DEPTH_LAYER,
|
|
16
|
-
REVERSED_DEPTH,
|
|
17
|
-
} from '../../depthConvention.ts';
|
|
12
|
+
import { DEPTH_CLEAR, MAX_DEPTH_LAYER, REVERSED_DEPTH } from '../../depthConvention.ts';
|
|
18
13
|
import { RECONSTRUCTION_MULTISAMPLE_REFUSAL, reconRenderSize } from '../../recon/frameSizes.ts';
|
|
19
14
|
import { jitterOffset as jitterOffsetFor, reconJitterPhases } from '../../recon/jitter.ts';
|
|
20
15
|
import {
|
|
@@ -23,16 +18,25 @@ import {
|
|
|
23
18
|
RECON_WORKGROUP,
|
|
24
19
|
reconResolveWgsl,
|
|
25
20
|
} from '../../shaders/recon/resolve.wgsl.ts';
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} from '../../
|
|
21
|
+
import { MotionPass } from './motionPass.ts';
|
|
22
|
+
import { LatePass } from './latePass.ts';
|
|
23
|
+
import { SnapshotMips } from './snapshotMips.ts';
|
|
24
|
+
import { resolveGlass } from '../../glass.ts';
|
|
25
|
+
import type { GlassOptions, ResolvedGlass } from '../../glass.ts';
|
|
26
|
+
import { routesLate, tracksMotion } from '../../recon/lateRouting.ts';
|
|
27
|
+
import { frameOfChange, keepsPrevious, pairedInstances } from '../../recon/changeFrames.ts';
|
|
28
|
+
import { INSTANCE_STRIDE } from '../../instances.ts';
|
|
33
29
|
import { DEFAULT_DISOCCLUSION } from '../../recon/disocclusion.ts';
|
|
34
30
|
import { DEFAULT_RECON_QUALITY } from '../../recon/resolve.ts';
|
|
35
31
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
32
|
+
import {
|
|
33
|
+
MOVER_MOTION,
|
|
34
|
+
MOVER_TWICE,
|
|
35
|
+
advanceMover,
|
|
36
|
+
claimRendererId,
|
|
37
|
+
isMover,
|
|
38
|
+
} from '../../recon/mover.ts';
|
|
39
|
+
import type { Mover } from '../../recon/mover.ts';
|
|
36
40
|
|
|
37
41
|
import type { Vec3 } from '../../../math/color.ts';
|
|
38
42
|
import { clamp } from '../../../math/scalar.ts';
|
|
@@ -128,6 +132,7 @@ import {
|
|
|
128
132
|
type ShadowLight,
|
|
129
133
|
} from '../../pointShadowSystem.ts';
|
|
130
134
|
import { GpuPointShadowArray, GpuPointShadowMap } from './pointShadowPass.ts';
|
|
135
|
+
import { OCTAHEDRAL_EDGE, strideLayers } from '../../pointShadowArray.ts';
|
|
131
136
|
import {
|
|
132
137
|
createPanelBindGroup,
|
|
133
138
|
createPanelBindGroupLayout,
|
|
@@ -190,6 +195,7 @@ import {
|
|
|
190
195
|
createPostStageLayout,
|
|
191
196
|
depthResolvePipeline,
|
|
192
197
|
postPipeline,
|
|
198
|
+
postPipelineTargets,
|
|
193
199
|
sceneColorFormat,
|
|
194
200
|
AO_BLUR_FIELDS,
|
|
195
201
|
AO_BLUR_FRAG_WGSL,
|
|
@@ -325,6 +331,16 @@ import {
|
|
|
325
331
|
} from '../../globalMedium.ts';
|
|
326
332
|
import type { GlobalMediumOptions } from '../../globalMedium.ts';
|
|
327
333
|
import type { MeshData } from '../../mesh.ts';
|
|
334
|
+
import { castsDepth, glassOf } from '../../shadowCasters.ts';
|
|
335
|
+
import { GlassCasterList, type GlassReplaySink } from '../../glassCasters.ts';
|
|
336
|
+
import { GpuGlassTint } from './glassTintPass.ts';
|
|
337
|
+
import {
|
|
338
|
+
SUN_DYNAMIC_LAYER,
|
|
339
|
+
SUN_GLASS_MOVING_LAYER,
|
|
340
|
+
SUN_GLASS_STATIC_LAYER,
|
|
341
|
+
SUN_PEELED_LAYER,
|
|
342
|
+
SUN_STATIC_LAYER,
|
|
343
|
+
} from '../../shadowMap.ts';
|
|
328
344
|
import type { SceneCasterMaterial, ShadowCasterSink, ShadowCasters } from '../../shadowCasters.ts';
|
|
329
345
|
import { VERTEX_LAYOUT, createGpuMesh, createGpuMeshIncremental, type GpuMesh } from './buffers.ts';
|
|
330
346
|
import type { GpuSurface } from './device.ts';
|
|
@@ -1307,6 +1323,49 @@ function padded(direction: ArrayLike<number>): Float32Array {
|
|
|
1307
1323
|
return SCRATCH_VEC4;
|
|
1308
1324
|
}
|
|
1309
1325
|
|
|
1326
|
+
/** The sun texture's usage: attached, sampled, and copied both ways when glass grows it. */
|
|
1327
|
+
const SUN_USAGE = 0x10 | 0x4 | 0x1 | 0x2; // RENDER_ATTACHMENT | TEXTURE_BINDING | COPY_SRC | COPY_DST
|
|
1328
|
+
|
|
1329
|
+
/** Every view of the sun's texture a pass reads or attaches, for `maps` of its layers. */
|
|
1330
|
+
function sunViewsOf(
|
|
1331
|
+
texture: GPUTexture,
|
|
1332
|
+
maps: number,
|
|
1333
|
+
layers: number,
|
|
1334
|
+
): {
|
|
1335
|
+
array: GPUTextureView;
|
|
1336
|
+
staticArray: GPUTextureView;
|
|
1337
|
+
static: GPUTextureView;
|
|
1338
|
+
dynamic: GPUTextureView | null;
|
|
1339
|
+
peel: GPUTextureView | null;
|
|
1340
|
+
glass: readonly [GPUTextureView, GPUTextureView] | null;
|
|
1341
|
+
} {
|
|
1342
|
+
const layer = (index: number): GPUTextureView =>
|
|
1343
|
+
texture.createView({ dimension: '2d', baseArrayLayer: index, arrayLayerCount: 1 });
|
|
1344
|
+
return {
|
|
1345
|
+
array: texture.createView({ dimension: '2d-array' }),
|
|
1346
|
+
staticArray: texture.createView({
|
|
1347
|
+
dimension: '2d-array',
|
|
1348
|
+
baseArrayLayer: SUN_STATIC_LAYER,
|
|
1349
|
+
arrayLayerCount: 1,
|
|
1350
|
+
}),
|
|
1351
|
+
static: layer(SUN_STATIC_LAYER),
|
|
1352
|
+
dynamic: maps > SUN_DYNAMIC_LAYER ? layer(SUN_DYNAMIC_LAYER) : null,
|
|
1353
|
+
peel: maps > SUN_PEELED_LAYER ? layer(SUN_PEELED_LAYER) : null,
|
|
1354
|
+
glass: layers > maps ? [layer(SUN_GLASS_STATIC_LAYER), layer(SUN_GLASS_MOVING_LAYER)] : null,
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* What the sun keeps through glass: two `rgba8unorm` layers, attached one at a time at level 0,
|
|
1360
|
+
* sampled whole with their mip chains, which are rebuilt after each layer is drawn.
|
|
1361
|
+
*/
|
|
1362
|
+
interface SunTintTarget {
|
|
1363
|
+
readonly texture: GPUTexture;
|
|
1364
|
+
readonly layers: readonly [GPUTextureView, GPUTextureView];
|
|
1365
|
+
readonly array: GPUTextureView;
|
|
1366
|
+
readonly mips: readonly [SnapshotMips, SnapshotMips];
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1310
1369
|
export class WebGPURenderer implements RendererApi {
|
|
1311
1370
|
/**
|
|
1312
1371
|
* Real timestamp queries, where the adapter offers them.
|
|
@@ -1416,6 +1475,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1416
1475
|
* caller who passed null gets — rather than reading a slot this frame has already re-let.
|
|
1417
1476
|
*/
|
|
1418
1477
|
private skinPaletteSlot = -1;
|
|
1478
|
+
/** The palette `skinPaletteSlot` was uploaded from, for a skinned mover to copy. See `Mover`. */
|
|
1479
|
+
private skinPaletteData: Float32Array | null = null;
|
|
1419
1480
|
|
|
1420
1481
|
private readonly surface: GpuSurface;
|
|
1421
1482
|
/** The encoder for the frame in flight, or null between frames. */
|
|
@@ -1425,6 +1486,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1425
1486
|
private pass: GPURenderPassEncoder | null = null;
|
|
1426
1487
|
|
|
1427
1488
|
private readonly pipelines: PipelineCache;
|
|
1489
|
+
/**
|
|
1490
|
+
* The pipelines a reconstructing frame's blended draws are built for: the reconstructed picture's
|
|
1491
|
+
* format at one sample, which is where `LatePass` draws them. Aliased to `pipelines` when the
|
|
1492
|
+
* world already lands on exactly that, as `overlayPipelines` is.
|
|
1493
|
+
*/
|
|
1494
|
+
private readonly latePipelines: PipelineCache;
|
|
1495
|
+
/** The blended draws of a reconstructing frame, drawn after the upscale. See `LatePass`. */
|
|
1496
|
+
private latePass: LatePass | null = null;
|
|
1497
|
+
/** A late draw asked to refract: `runLate` copies the reconstructed picture before replaying. */
|
|
1498
|
+
private lateRefractWanted = false;
|
|
1499
|
+
/** The refraction copy's mip chain, recorded after each copy: what frosted glass reads. */
|
|
1500
|
+
private refractMips: SnapshotMips | null = null;
|
|
1501
|
+
/**
|
|
1502
|
+
* The refraction copy's sampler: filtering, and **between levels as well**, so a frosted pane's
|
|
1503
|
+
* level falls smoothly between two blurs rather than stepping from one to the next.
|
|
1504
|
+
*/
|
|
1505
|
+
private refractSampler: GPUSampler | null = null;
|
|
1506
|
+
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
1507
|
+
private readonly glassScratch: ResolvedGlass = { transmission: 0, frost: 0, tint: [1, 1, 1] };
|
|
1508
|
+
/** `issueCommand` bound once, so the late replay allocates no closure per frame. */
|
|
1509
|
+
private readonly issueBound: (pass: GPURenderPassEncoder, command: DrawCommand) => void;
|
|
1428
1510
|
/**
|
|
1429
1511
|
* The same pipelines again, built for the canvas, for anything drawn after `endFrame`.
|
|
1430
1512
|
*
|
|
@@ -1926,6 +2008,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1926
2008
|
private taaUniforms!: GPUBuffer;
|
|
1927
2009
|
private taaTextures: (GPUTexture | null)[] = [null, null];
|
|
1928
2010
|
private taaViews: (GPUTextureView | null)[] = [null, null];
|
|
2011
|
+
/** Each pixel's flicker record, paired with the history and swapped with it: see `temporalAa.ts`. */
|
|
2012
|
+
private taaRecords: (GPUTexture | null)[] = [null, null];
|
|
2013
|
+
private taaRecordViews: (GPUTextureView | null)[] = [null, null];
|
|
2014
|
+
/** The rest of each pixel's record — depth, pull and recent swing — beside the first. */
|
|
2015
|
+
private taaMotions: (GPUTexture | null)[] = [null, null];
|
|
2016
|
+
private taaMotionViews: (GPUTextureView | null)[] = [null, null];
|
|
2017
|
+
/** The resolve's depth-to-metres row, from the inverse of `frameProjection`. */
|
|
2018
|
+
private readonly taaInvProjection = new Float32Array(16);
|
|
2019
|
+
/** The resolve's two targets, the picture and the record, held for the pipeline cache's sake. */
|
|
2020
|
+
private taaTargets: GPUColorTargetState[] = [];
|
|
1929
2021
|
/** Group `i` reads history `i` and the pass writes into the other one. */
|
|
1930
2022
|
private taaBindGroups: (GPUBindGroup | null)[] = [null, null];
|
|
1931
2023
|
private taaWrite = 0;
|
|
@@ -1960,10 +2052,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1960
2052
|
* the *unsharpened* result — sharpening into the history would sharpen a sharpened picture every
|
|
1961
2053
|
* frame, which is the halo the reference's own clamp exists to prevent, compounded.
|
|
1962
2054
|
*
|
|
1963
|
-
* The motion target is render size and carries a drawn object's own motion where
|
|
1964
|
-
* one
|
|
1965
|
-
*
|
|
1966
|
-
* exact
|
|
2055
|
+
* The motion target is render size and carries a drawn object's own motion where the motion pass
|
|
2056
|
+
* wrote one — a draw that named its mover, or passed last frame's matrix. Every other texel is
|
|
2057
|
+
* cleared, and the resolve takes the camera's motion there, derived from the depth and the
|
|
2058
|
+
* reprojection: exact for anything that did not move. See `MotionPass`.
|
|
1967
2059
|
*/
|
|
1968
2060
|
private reconHistories: (GPUTexture | null)[] = [null, null];
|
|
1969
2061
|
private reconHistoryViews: (GPUTextureView | null)[] = [null, null];
|
|
@@ -1985,34 +2077,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1985
2077
|
private reconGroups: (GPUBindGroup | null)[] = [null, null];
|
|
1986
2078
|
private reconSharpenGroups: (GPUBindGroup | null)[] = [null, null];
|
|
1987
2079
|
private reconRushGroup: GPUBindGroup | null = null;
|
|
1988
|
-
/**
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
private
|
|
1996
|
-
private
|
|
1997
|
-
private motionCount = 0;
|
|
1998
|
-
private motionLayout: GPUBindGroupLayout | null = null;
|
|
1999
|
-
/**
|
|
2000
|
-
* One pipeline per vertex stride, because a stride is a property of the *mesh*.
|
|
2001
|
-
*
|
|
2002
|
-
* Which optional attributes a mesh supplied decides how far apart its positions are, and this
|
|
2003
|
-
* pass reads nothing but positions — so two meshes drawn by the same shader still need two
|
|
2004
|
-
* vertex layouts. A map rather than a cache key on the pipeline cache: there is one shader here
|
|
2005
|
-
* and a handful of strides in any scene.
|
|
2006
|
-
*/
|
|
2007
|
-
private readonly motionPipelines = new Map<number, GPURenderPipeline>();
|
|
2008
|
-
private motionModule: GPUShaderModule | null = null;
|
|
2009
|
-
private motionPipelineLayout: GPUPipelineLayout | null = null;
|
|
2010
|
-
private motionFrameBuffer: GPUBuffer | null = null;
|
|
2011
|
-
private motionDrawBuffer: GPUBuffer | null = null;
|
|
2012
|
-
private motionDrawCapacity = 0;
|
|
2013
|
-
private motionGroup: GPUBindGroup | null = null;
|
|
2014
|
-
private readonly motionFrameStaging = new Float32Array(MOTION_FRAME_FLOATS);
|
|
2015
|
-
private motionDrawStaging = new Uint8Array(0);
|
|
2080
|
+
/** The motion pass, made with the reconstruction's targets. See `MotionPass`. */
|
|
2081
|
+
private motionPass: MotionPass | null = null;
|
|
2082
|
+
/** This renderer's identity to a `Mover`. See `claimRendererId`. */
|
|
2083
|
+
private readonly moverRenderer = claimRendererId();
|
|
2084
|
+
/** Frames begun, so a mover can tell last frame from any other. Advanced in `beginFrame`. */
|
|
2085
|
+
private moverFrame = 0;
|
|
2086
|
+
/** Between `beginFrame` and `endFrame`. A change outside it is the next frame's. */
|
|
2087
|
+
private inFrame = false;
|
|
2088
|
+
private warnedMoverTwice = false;
|
|
2016
2089
|
/** The scene's own clip transform with the generated shaders' y negation folded in. */
|
|
2017
2090
|
private readonly motionRaster = mat4.create();
|
|
2018
2091
|
private reconSizeKey = '';
|
|
@@ -2265,6 +2338,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2265
2338
|
refraction: number;
|
|
2266
2339
|
refractTint: Vec3 | undefined;
|
|
2267
2340
|
thicknessM: number;
|
|
2341
|
+
glass: GlassOptions | undefined;
|
|
2268
2342
|
} = {
|
|
2269
2343
|
lit: true,
|
|
2270
2344
|
fog: true,
|
|
@@ -2275,6 +2349,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2275
2349
|
refraction: 0,
|
|
2276
2350
|
refractTint: undefined,
|
|
2277
2351
|
thicknessM: 0,
|
|
2352
|
+
glass: undefined,
|
|
2278
2353
|
};
|
|
2279
2354
|
|
|
2280
2355
|
private reflectionPassActive = false;
|
|
@@ -2580,7 +2655,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2580
2655
|
this.filmFrags.writeBlock(fragmentSlot, this.filmFragInts);
|
|
2581
2656
|
|
|
2582
2657
|
const filmPipe = filmPipeline(
|
|
2583
|
-
this.
|
|
2658
|
+
this.blendedPipelines(),
|
|
2584
2659
|
this.surface.device,
|
|
2585
2660
|
this.filmLayout,
|
|
2586
2661
|
`film|${geometry.key ?? ''}`,
|
|
@@ -2594,10 +2669,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2594
2669
|
* holds the mirror's colour whatever `uReflectionEnabled` says, and WebGPU validates the
|
|
2595
2670
|
* binding rather than the read — the argument `filmBindGroupBlank` exists for.
|
|
2596
2671
|
*/
|
|
2597
|
-
const filmCommand = this.
|
|
2598
|
-
filmGroup === this.filmBindGroup ? MIRROR_COLOR : 0,
|
|
2599
|
-
this.currentTarget(),
|
|
2600
|
-
);
|
|
2672
|
+
const filmCommand = this.recordBlended(filmGroup === this.filmBindGroup ? MIRROR_COLOR : 0);
|
|
2601
2673
|
if (filmCommand !== null) {
|
|
2602
2674
|
filmCommand.pipeline = filmPipe;
|
|
2603
2675
|
filmCommand.bindGroup = filmGroup;
|
|
@@ -2788,6 +2860,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2788
2860
|
*/
|
|
2789
2861
|
setSkinPalette(palette: Float32Array | null): void {
|
|
2790
2862
|
if (this.surface.lost) return;
|
|
2863
|
+
this.skinPaletteData = palette;
|
|
2791
2864
|
if (palette === null) {
|
|
2792
2865
|
this.skinPaletteSlot = -1;
|
|
2793
2866
|
return;
|
|
@@ -3156,7 +3229,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3156
3229
|
* no-op before the first `bindMeshPass` of a session, same as every other pass that reads it.
|
|
3157
3230
|
*/
|
|
3158
3231
|
drawSdfText(text: GpuSdfText, model: Float32Array, color: Vec3, opacity: number): void {
|
|
3159
|
-
|
|
3232
|
+
/* Unjittered when it lands after the upscale, like every late draw. See `drawsLate`. */
|
|
3233
|
+
const viewProj = this.drawsLate() ? this.correctedViewProj : this.viewProj;
|
|
3160
3234
|
if (!this.canDraw() || viewProj === null) return;
|
|
3161
3235
|
const font = text.font;
|
|
3162
3236
|
const atlas = text.atlas;
|
|
@@ -3190,12 +3264,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3190
3264
|
this.sdfTextFrags.writeFloat(fragmentSlot, fragAt('uOutputExposure'), this.gradeExposure());
|
|
3191
3265
|
|
|
3192
3266
|
const sdfPipe = sdfTextPipeline(
|
|
3193
|
-
this.
|
|
3267
|
+
this.blendedPipelines(),
|
|
3194
3268
|
this.surface.device,
|
|
3195
3269
|
this.sdfTextLayout,
|
|
3196
3270
|
);
|
|
3197
3271
|
const sdfGroup = this.sdfTextBindGroupFor(atlas);
|
|
3198
|
-
const sdfCommand = this.
|
|
3272
|
+
const sdfCommand = this.recordBlended(0);
|
|
3199
3273
|
if (sdfCommand !== null) {
|
|
3200
3274
|
sdfCommand.pipeline = sdfPipe;
|
|
3201
3275
|
sdfCommand.bindGroup = sdfGroup;
|
|
@@ -3311,8 +3385,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3311
3385
|
|
|
3312
3386
|
const pass = this.openTextPass();
|
|
3313
3387
|
if (pass === null) return;
|
|
3314
|
-
const textPipe = textPipeline(this.
|
|
3315
|
-
const textCommand = this.
|
|
3388
|
+
const textPipe = textPipeline(this.blendedPipelines(), this.surface.device, this.textLayout);
|
|
3389
|
+
const textCommand = this.recordBlended(0);
|
|
3316
3390
|
if (textCommand !== null) {
|
|
3317
3391
|
textCommand.pipeline = textPipe;
|
|
3318
3392
|
textCommand.bindGroup = this.textBindGroup;
|
|
@@ -3762,13 +3836,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3762
3836
|
|
|
3763
3837
|
const layout = this.particleLayoutFor(batch.material).layout;
|
|
3764
3838
|
const particlePipe = particlePipeline(
|
|
3765
|
-
this.
|
|
3839
|
+
this.blendedPipelines(),
|
|
3766
3840
|
device,
|
|
3767
3841
|
layout,
|
|
3768
3842
|
batch.material,
|
|
3769
3843
|
batch.blend,
|
|
3770
3844
|
);
|
|
3771
|
-
const particleCommand = this.
|
|
3845
|
+
const particleCommand = this.recordBlended(0);
|
|
3772
3846
|
if (particleCommand !== null) {
|
|
3773
3847
|
particleCommand.pipeline = particlePipe;
|
|
3774
3848
|
particleCommand.bindGroup = batch.bindGroup;
|
|
@@ -3913,13 +3987,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3913
3987
|
frags.writeFloats(fragmentSlot, atF('uTint'), plumes.tint);
|
|
3914
3988
|
|
|
3915
3989
|
const plumePipe = plumePipeline(
|
|
3916
|
-
this.
|
|
3990
|
+
this.blendedPipelines(),
|
|
3917
3991
|
this.surface.device,
|
|
3918
3992
|
this.plumeLayout,
|
|
3919
3993
|
plumes.material,
|
|
3920
3994
|
plumes.blend,
|
|
3921
3995
|
);
|
|
3922
|
-
const plumeCommand = this.
|
|
3996
|
+
const plumeCommand = this.recordBlended(0);
|
|
3923
3997
|
if (plumeCommand !== null) {
|
|
3924
3998
|
plumeCommand.pipeline = plumePipe;
|
|
3925
3999
|
plumeCommand.bindGroup = this.plumeBindGroup;
|
|
@@ -4474,9 +4548,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4474
4548
|
this.windStreakVerts.writeBlock(vertexSlot, this.windStreakVertBlock);
|
|
4475
4549
|
this.windStreakFrags.writeBlock(fragmentSlot, this.windStreakFragBlock);
|
|
4476
4550
|
|
|
4477
|
-
const windPipe = windStreakPipeline(
|
|
4551
|
+
const windPipe = windStreakPipeline(
|
|
4552
|
+
this.blendedPipelines(),
|
|
4553
|
+
this.surface.device,
|
|
4554
|
+
this.windStreakLayout,
|
|
4555
|
+
);
|
|
4478
4556
|
/* Two uniform buffers and two vertex buffers. Nothing is sampled, so nothing is read. */
|
|
4479
|
-
const windCommand = this.
|
|
4557
|
+
const windCommand = this.recordBlended(0);
|
|
4480
4558
|
if (windCommand !== null) {
|
|
4481
4559
|
windCommand.pipeline = windPipe;
|
|
4482
4560
|
windCommand.bindGroup = this.windStreakBindGroup;
|
|
@@ -4704,8 +4782,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4704
4782
|
this.boltVerts.writeBlock(vertexSlot, this.boltVertBlock);
|
|
4705
4783
|
this.boltFrags.writeBlock(fragmentSlot, this.boltFragInts);
|
|
4706
4784
|
|
|
4707
|
-
const boltPipe = boltPipeline(this.
|
|
4708
|
-
const boltCommand = this.
|
|
4785
|
+
const boltPipe = boltPipeline(this.blendedPipelines(), this.surface.device, this.boltLayout);
|
|
4786
|
+
const boltCommand = this.recordBlended(0);
|
|
4709
4787
|
if (boltCommand !== null) {
|
|
4710
4788
|
boltCommand.pipeline = boltPipe;
|
|
4711
4789
|
boltCommand.bindGroup = this.boltBindGroup;
|
|
@@ -4842,9 +4920,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4842
4920
|
this.lineFrags.writeFloat(fragmentSlot, atF('uFogNear'), medium.fogNear);
|
|
4843
4921
|
this.lineFrags.writeFloat(fragmentSlot, atF('uFogFar'), medium.fogFar);
|
|
4844
4922
|
|
|
4845
|
-
const linePipe = linePipeline(
|
|
4923
|
+
const linePipe = linePipeline(
|
|
4924
|
+
this.blendedPipelines(),
|
|
4925
|
+
this.surface.device,
|
|
4926
|
+
this.lineLayout,
|
|
4927
|
+
additive,
|
|
4928
|
+
);
|
|
4846
4929
|
/* Two uniform blocks and three vertex buffers, and no attachment among them. */
|
|
4847
|
-
const lineCommand = this.
|
|
4930
|
+
const lineCommand = this.recordBlended(0);
|
|
4848
4931
|
if (lineCommand !== null) {
|
|
4849
4932
|
lineCommand.pipeline = linePipe;
|
|
4850
4933
|
lineCommand.bindGroup = this.lineBindGroup;
|
|
@@ -5067,8 +5150,39 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5067
5150
|
|
|
5068
5151
|
/* -- The shadow pass -------------------------------------------------------------- */
|
|
5069
5152
|
|
|
5070
|
-
|
|
5071
|
-
|
|
5153
|
+
/**
|
|
5154
|
+
* The sun's static, moving and peeled maps, as layers of one texture (`shadowMap.ts`). The three
|
|
5155
|
+
* views below are single layers, what each pass attaches; the two here are what is sampled.
|
|
5156
|
+
*/
|
|
5157
|
+
private sunShadowTexture: GPUTexture;
|
|
5158
|
+
/** Every layer, for the lit pass, the light volumes and the medium. */
|
|
5159
|
+
private sunArrayView: GPUTextureView;
|
|
5160
|
+
/**
|
|
5161
|
+
* The static layer alone, as an array, for the peel: it draws into another layer of the same
|
|
5162
|
+
* texture, and a view covering that layer too would put it in two usages in one pass.
|
|
5163
|
+
*/
|
|
5164
|
+
private sunStaticArrayView: GPUTextureView;
|
|
5165
|
+
/** How many layers the sun's texture holds: its maps, and the two glass layers once glass casts. */
|
|
5166
|
+
private sunLayerCount: number;
|
|
5167
|
+
/** The glass depth layers, static then moving, once glass has cast. See `growSunForGlass`. */
|
|
5168
|
+
private sunGlassViews: readonly [GPUTextureView, GPUTextureView] | null = null;
|
|
5169
|
+
/** The texture a growth replaced, destroyed once the encoder that copied out of it is submitted. */
|
|
5170
|
+
private retiredSunTexture: GPUTexture | null = null;
|
|
5171
|
+
/** What the sun's light keeps through glass: two `rgba8unorm` layers, made when glass first casts. */
|
|
5172
|
+
private sunTint: SunTintTarget | null = null;
|
|
5173
|
+
/** The tint pass's layouts, per-draw block, groups and pipelines, made when glass first casts. */
|
|
5174
|
+
private glassTint: GpuGlassTint | null = null;
|
|
5175
|
+
/** The tint pass open while the glass list is replayed into it. */
|
|
5176
|
+
private tintPass: GPURenderPassEncoder | null = null;
|
|
5177
|
+
/** True while the glass list is replayed into a glass depth layer through `casterSink`. */
|
|
5178
|
+
private drawingGlassDepth = false;
|
|
5179
|
+
/** Whether the open sun pass is the movers' layer, whose glass goes to the moving glass layer. */
|
|
5180
|
+
private shadowLayerIsDynamic = false;
|
|
5181
|
+
/** The glass tints' sampler: filtering, and between levels, for frost's coarser reads. */
|
|
5182
|
+
private readonly tintSampler: GPUSampler;
|
|
5183
|
+
/** The light a tint pass is drawn for: xyz a direction (w 0) or a position (w 1). */
|
|
5184
|
+
private readonly glassLight = new Float32Array(4);
|
|
5185
|
+
private shadowView: GPUTextureView;
|
|
5072
5186
|
private readonly shadowLayout: GPUBindGroupLayout;
|
|
5073
5187
|
/** The same layout plus the joint palette. See `createDepthBindGroupLayout`. */
|
|
5074
5188
|
private readonly shadowSkinnedLayout: GPUBindGroupLayout;
|
|
@@ -5094,8 +5208,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5094
5208
|
* read twice — so a profile that switches peeling off gets no map and no pass on either
|
|
5095
5209
|
* backend rather than a map nothing writes.
|
|
5096
5210
|
*/
|
|
5097
|
-
private
|
|
5098
|
-
private readonly peelView: GPUTextureView | null;
|
|
5211
|
+
private peelView: GPUTextureView | null;
|
|
5099
5212
|
/**
|
|
5100
5213
|
* The movers' own layer: the character, the rope it swings on, and anything else that walks.
|
|
5101
5214
|
*
|
|
@@ -5116,10 +5229,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5116
5229
|
* and therefore as lit — so nothing looked broken, only empty, which is why it survived the
|
|
5117
5230
|
* port and every gate in it.
|
|
5118
5231
|
*/
|
|
5119
|
-
private
|
|
5120
|
-
private readonly dynamicView: GPUTextureView | null;
|
|
5232
|
+
private dynamicView: GPUTextureView | null;
|
|
5121
5233
|
/** The peel's own bind groups: `uPeelShadowLayer` at 1, and the static map to peel against. */
|
|
5122
|
-
private
|
|
5234
|
+
private shadowPeelBindGroup: GPUBindGroup | null;
|
|
5123
5235
|
/**
|
|
5124
5236
|
* The cutout casters' layout, and a bind group per cutout map for each layer, built the first
|
|
5125
5237
|
* time a map casts and kept: a `createBindGroup` per draw is the allocation the house rules forbid.
|
|
@@ -5131,7 +5243,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5131
5243
|
private readonly cutoutPeelGroups = new Map<GpuSurfaceTexture, GPUBindGroup>();
|
|
5132
5244
|
private shadowPeelPassUniforms: GPUBuffer | null = null;
|
|
5133
5245
|
private shadowPeelSampler: GPUSampler | null = null;
|
|
5134
|
-
private
|
|
5246
|
+
private scatterDepthPeelBindGroup: GPUBindGroup | null;
|
|
5135
5247
|
/** Which layer the open pass is writing, so the sink picks the bindings that match it. */
|
|
5136
5248
|
private shadowLayerIsPeel = false;
|
|
5137
5249
|
/**
|
|
@@ -5190,7 +5302,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5190
5302
|
const held = groups.get(texture);
|
|
5191
5303
|
if (held !== undefined) return held;
|
|
5192
5304
|
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
5193
|
-
const previousView = peel ? this.
|
|
5305
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
5194
5306
|
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
5195
5307
|
if (passUniforms === null || previousView === null || previousSampler === null) return null;
|
|
5196
5308
|
const group = createDepthBindGroup(
|
|
@@ -5268,8 +5380,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5268
5380
|
* Built once and held, rather than made per call: it closes over `this` and a fresh one
|
|
5269
5381
|
* every frame would be an allocation in a per-frame path.
|
|
5270
5382
|
*/
|
|
5383
|
+
/** The glass the open shadow pass was offered, kept for its own passes: `glassCasters.ts`. */
|
|
5384
|
+
private readonly glassCasters = new GlassCasterList();
|
|
5385
|
+
/** One resolution every glass caster is read through, so recording one allocates nothing. */
|
|
5386
|
+
private readonly glassCasterScratch: ResolvedGlass = {
|
|
5387
|
+
transmission: 0,
|
|
5388
|
+
frost: 0,
|
|
5389
|
+
tint: [1, 1, 1],
|
|
5390
|
+
};
|
|
5271
5391
|
private readonly casterSink: ShadowCasterSink = {
|
|
5272
5392
|
mesh: (mesh, model, material) => {
|
|
5393
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
5394
|
+
/* Glass: kept aside for its own two passes rather than dropped. See `glassCasters.ts`. */
|
|
5395
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
5396
|
+
this.glassCasters.recordMesh(mesh, model, material, this.glassCasterScratch);
|
|
5397
|
+
}
|
|
5398
|
+
return;
|
|
5399
|
+
}
|
|
5273
5400
|
const pass = this.shadowPass;
|
|
5274
5401
|
if (pass === null) return;
|
|
5275
5402
|
/* A cutout caster's group is looked up before a slot is spent, so a miss costs nothing. */
|
|
@@ -5319,6 +5446,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5319
5446
|
* uses — one draw, however many instances, exactly as the colour pass does it.
|
|
5320
5447
|
*/
|
|
5321
5448
|
instanced: (batch, data, material) => {
|
|
5449
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
5450
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
5451
|
+
this.glassCasters.recordInstanced(batch, data, material, this.glassCasterScratch);
|
|
5452
|
+
}
|
|
5453
|
+
return;
|
|
5454
|
+
}
|
|
5322
5455
|
const pass = this.shadowPass;
|
|
5323
5456
|
if (pass === null) return;
|
|
5324
5457
|
const gpuBatch = batch as GpuInstancedBatch;
|
|
@@ -5367,7 +5500,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5367
5500
|
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
5368
5501
|
pass.drawIndexed(geometry.indexCount, count);
|
|
5369
5502
|
},
|
|
5370
|
-
skinnedMesh: (mesh, model, palette) => {
|
|
5503
|
+
skinnedMesh: (mesh, model, palette, material) => {
|
|
5504
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
5505
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
5506
|
+
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
5507
|
+
}
|
|
5508
|
+
return;
|
|
5509
|
+
}
|
|
5371
5510
|
const pass = this.shadowPass;
|
|
5372
5511
|
if (pass === null) return;
|
|
5373
5512
|
/* Its own slot, so two characters casting into one bake do not both take the second one's
|
|
@@ -5751,10 +5890,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5751
5890
|
* nothing at all under the default profile, which is on. The cache carries it for the same
|
|
5752
5891
|
* reason it carries the sample count: no pass can then disagree with the attachment.
|
|
5753
5892
|
*/
|
|
5893
|
+
/* Off compiles none of the glass lookup into a lit pipeline: see `PipelineCache.glassShadows`. */
|
|
5894
|
+
const glassShadows = quality.glassShadows !== 'off';
|
|
5754
5895
|
this.pipelines = new PipelineCache(
|
|
5755
5896
|
device,
|
|
5756
5897
|
quality.screenEffects ? sceneColorFormat(quality.hdrScene) : surface.format,
|
|
5757
5898
|
this.samples,
|
|
5899
|
+
glassShadows,
|
|
5758
5900
|
);
|
|
5759
5901
|
/*
|
|
5760
5902
|
* The canvas' own state, for the pass `ensurePass` reopens after `endFrame`.
|
|
@@ -5767,7 +5909,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5767
5909
|
this.overlayPipelines =
|
|
5768
5910
|
this.pipelines.format === surface.format && this.samples === 1
|
|
5769
5911
|
? this.pipelines
|
|
5770
|
-
: new PipelineCache(device, surface.format, 1);
|
|
5912
|
+
: new PipelineCache(device, surface.format, 1, glassShadows);
|
|
5913
|
+
this.latePipelines =
|
|
5914
|
+
this.pipelines.format === RECON_HISTORY_FORMAT && this.samples === 1
|
|
5915
|
+
? this.pipelines
|
|
5916
|
+
: new PipelineCache(device, RECON_HISTORY_FORMAT, 1, glassShadows);
|
|
5917
|
+
this.issueBound = this.issueCommand.bind(this);
|
|
5771
5918
|
this.pipelineTargets =
|
|
5772
5919
|
this.overlayPipelines === this.pipelines
|
|
5773
5920
|
? [this.pipelines]
|
|
@@ -5918,42 +6065,35 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5918
6065
|
const blankDriftIndexView = blankVolume('flat.emptyDriftLightIndex', 'r32uint');
|
|
5919
6066
|
const blankDriftAtlasView = blankVolume('flat.emptyDriftLightAtlas', 'rgba16float');
|
|
5920
6067
|
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
6068
|
+
/*
|
|
6069
|
+
* The static world, its movers and — where more than one static depth layer is asked for —
|
|
6070
|
+
* the peel, as layers of one texture, on the conditions `renderer.ts` builds its array on: one
|
|
6071
|
+
* decision read twice. Same size and format throughout, because the shader samples every
|
|
6072
|
+
* layer with one \`uShadowMapSize\` and one set of filter offsets.
|
|
6073
|
+
*/
|
|
6074
|
+
const sunLayers = this.quality.directionalShadows
|
|
6075
|
+
? this.quality.directionalShadowDepthLayers > 1
|
|
6076
|
+
? 3
|
|
6077
|
+
: 2
|
|
6078
|
+
: 1;
|
|
6079
|
+
this.sunShadowTexture = device.createTexture({
|
|
6080
|
+
label: 'shadow.sun',
|
|
6081
|
+
size: [
|
|
6082
|
+
this.quality.directionalShadowMapSize,
|
|
6083
|
+
this.quality.directionalShadowMapSize,
|
|
6084
|
+
sunLayers,
|
|
6085
|
+
],
|
|
5924
6086
|
format: SHADOW_FORMAT,
|
|
5925
|
-
|
|
6087
|
+
/* Copyable both ways, because the first glass grows it by copying every layer across. */
|
|
6088
|
+
usage: SUN_USAGE,
|
|
5926
6089
|
});
|
|
5927
|
-
this.
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
this.
|
|
5934
|
-
this.quality.directionalShadowDepthLayers > 1
|
|
5935
|
-
? device.createTexture({
|
|
5936
|
-
label: 'shadow.peel',
|
|
5937
|
-
size: [this.quality.directionalShadowMapSize, this.quality.directionalShadowMapSize],
|
|
5938
|
-
format: SHADOW_FORMAT,
|
|
5939
|
-
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
5940
|
-
})
|
|
5941
|
-
: null;
|
|
5942
|
-
this.peelView = this.peelMap === null ? null : this.peelMap.createView();
|
|
5943
|
-
/*
|
|
5944
|
-
* The movers' layer, on the condition `renderer.ts` builds its own on: directional shadows
|
|
5945
|
-
* at all. Same size and same format as the static map, because the shader samples all three
|
|
5946
|
-
* with one `uShadowMapSize` and one set of PCF offsets.
|
|
5947
|
-
*/
|
|
5948
|
-
this.dynamicMap = this.quality.directionalShadows
|
|
5949
|
-
? device.createTexture({
|
|
5950
|
-
label: 'shadow.dynamic',
|
|
5951
|
-
size: [this.quality.directionalShadowMapSize, this.quality.directionalShadowMapSize],
|
|
5952
|
-
format: SHADOW_FORMAT,
|
|
5953
|
-
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
5954
|
-
})
|
|
5955
|
-
: null;
|
|
5956
|
-
this.dynamicView = this.dynamicMap === null ? null : this.dynamicMap.createView();
|
|
6090
|
+
this.sunLayerCount = sunLayers;
|
|
6091
|
+
const views = sunViewsOf(this.sunShadowTexture, sunLayers, sunLayers);
|
|
6092
|
+
this.sunArrayView = views.array;
|
|
6093
|
+
this.sunStaticArrayView = views.staticArray;
|
|
6094
|
+
this.shadowView = views.static;
|
|
6095
|
+
this.dynamicView = views.dynamic;
|
|
6096
|
+
this.peelView = views.peel;
|
|
5957
6097
|
|
|
5958
6098
|
/*
|
|
5959
6099
|
* **Cleared once, here, because a texture no pass has written reads as zero**, which the
|
|
@@ -5994,6 +6134,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5994
6134
|
* rather than the hardware's.
|
|
5995
6135
|
*/
|
|
5996
6136
|
const shadowSampler = device.createSampler({ label: 'shadow.sampler' });
|
|
6137
|
+
this.tintSampler = device.createSampler({
|
|
6138
|
+
label: 'shadow.tintSampler',
|
|
6139
|
+
magFilter: 'linear',
|
|
6140
|
+
minFilter: 'linear',
|
|
6141
|
+
mipmapFilter: 'linear',
|
|
6142
|
+
addressModeU: 'clamp-to-edge',
|
|
6143
|
+
addressModeV: 'clamp-to-edge',
|
|
6144
|
+
});
|
|
5997
6145
|
/*
|
|
5998
6146
|
* The photometric atlas's own sampler, **created here rather than reusing `postSampler`.**
|
|
5999
6147
|
*
|
|
@@ -6015,14 +6163,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6015
6163
|
});
|
|
6016
6164
|
|
|
6017
6165
|
this.flatTextures = (name) => {
|
|
6018
|
-
if (name === '
|
|
6019
|
-
/*
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
if (name === '
|
|
6025
|
-
return {
|
|
6166
|
+
if (name === 'uSunShadows') return { view: this.sunArrayView, sampler: shadowSampler };
|
|
6167
|
+
/* What the sun keeps through glass, or the one-texel stand-in the lookup reads as none. */
|
|
6168
|
+
if (name === 'uSunGlassTints') {
|
|
6169
|
+
return { view: this.sunTint?.array ?? blankArrayView, sampler: this.tintSampler };
|
|
6170
|
+
}
|
|
6171
|
+
/* What the lamps keep through glass, one layer a light, or the same stand-in. */
|
|
6172
|
+
if (name === 'uPointGlassTints') {
|
|
6173
|
+
return {
|
|
6174
|
+
view: this.pointShadowArray?.glassTint?.view ?? blankArrayView,
|
|
6175
|
+
sampler: this.tintSampler,
|
|
6176
|
+
};
|
|
6026
6177
|
}
|
|
6027
6178
|
/*
|
|
6028
6179
|
* The caller's image, or the stand-in. Read from the field rather than passed in, because
|
|
@@ -6060,10 +6211,6 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6060
6211
|
? { view: blankView, sampler: blankSampler }
|
|
6061
6212
|
: { view: albedo.view, sampler: albedo.sampler };
|
|
6062
6213
|
}
|
|
6063
|
-
/* The peel, where the profile asked for one. `uPeeledShadowEnabled` gates the sample. */
|
|
6064
|
-
if (name === 'uPeeledShadowMap' && this.peelView !== null) {
|
|
6065
|
-
return { view: this.peelView, sampler: shadowSampler };
|
|
6066
|
-
}
|
|
6067
6214
|
/*
|
|
6068
6215
|
* The room, once a bake has filled it. Unbaked reads as the blank cube: nothing to mirror.
|
|
6069
6216
|
*
|
|
@@ -6132,7 +6279,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6132
6279
|
* texels by construction, an offset that snapped to one being no offset at all.
|
|
6133
6280
|
*/
|
|
6134
6281
|
if (name === 'uRefractScene') {
|
|
6135
|
-
|
|
6282
|
+
this.refractSampler ??= device.createSampler({
|
|
6283
|
+
label: 'refract.sampler',
|
|
6284
|
+
magFilter: 'linear',
|
|
6285
|
+
minFilter: 'linear',
|
|
6286
|
+
mipmapFilter: 'linear',
|
|
6287
|
+
addressModeU: 'clamp-to-edge',
|
|
6288
|
+
addressModeV: 'clamp-to-edge',
|
|
6289
|
+
});
|
|
6290
|
+
return { view: this.refractSnapshotView ?? blankView, sampler: this.refractSampler };
|
|
6136
6291
|
}
|
|
6137
6292
|
/*
|
|
6138
6293
|
* Every point light's shadow, in one binding.
|
|
@@ -6358,9 +6513,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6358
6513
|
*/
|
|
6359
6514
|
this.mediumLayout = createPostStageLayout(device, MEDIUM_UNIFORMS, MEDIUM_FRAG_SIZE, [
|
|
6360
6515
|
{ binding: MEDIUM_TEXTURES.uDepth, filterable: false },
|
|
6361
|
-
{ binding: MEDIUM_TEXTURES.
|
|
6362
|
-
{ binding: MEDIUM_TEXTURES.uPeeledShadowMap, filterable: false },
|
|
6363
|
-
{ binding: MEDIUM_TEXTURES.uDynamicShadowMap, filterable: false },
|
|
6516
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows, filterable: false, dimension: '2d-array' },
|
|
6364
6517
|
]);
|
|
6365
6518
|
this.mediumUniforms = device.createBuffer({
|
|
6366
6519
|
label: 'post.mediumUniforms',
|
|
@@ -6395,6 +6548,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6395
6548
|
this.taaLayout = createPostStageLayout(device, TAA_UNIFORMS, TAA_FRAG_SIZE, [
|
|
6396
6549
|
{ binding: TAA_TEXTURES.uScene },
|
|
6397
6550
|
{ binding: TAA_TEXTURES.uHistory },
|
|
6551
|
+
{ binding: TAA_TEXTURES.uFlicker },
|
|
6552
|
+
{ binding: TAA_TEXTURES.uFlickerMotion },
|
|
6398
6553
|
{ binding: TAA_TEXTURES.uDepth, filterable: false },
|
|
6399
6554
|
]);
|
|
6400
6555
|
this.taaUniforms = device.createBuffer({
|
|
@@ -6590,7 +6745,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6590
6745
|
this.scatterDepthDraws.buffer,
|
|
6591
6746
|
{ binding: DEPTH_FRAG_BINDING, buffer: depthPassUniforms, size: DEPTH_FRAG_SIZE },
|
|
6592
6747
|
DEPTH_PREVIOUS_BINDING,
|
|
6593
|
-
|
|
6748
|
+
blankArrayView,
|
|
6594
6749
|
blankSampler,
|
|
6595
6750
|
);
|
|
6596
6751
|
this.shadowBindGroup = createDepthBindGroup(
|
|
@@ -6598,10 +6753,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6598
6753
|
this.shadowLayout,
|
|
6599
6754
|
this.shadowDraws.buffer,
|
|
6600
6755
|
depthPassUniforms,
|
|
6601
|
-
|
|
6756
|
+
blankArrayView,
|
|
6602
6757
|
blankSampler,
|
|
6603
6758
|
);
|
|
6604
|
-
this.shadowBlankView =
|
|
6759
|
+
this.shadowBlankView = blankArrayView;
|
|
6605
6760
|
this.shadowBlankSampler = blankSampler;
|
|
6606
6761
|
this.shadowPassUniforms = depthPassUniforms;
|
|
6607
6762
|
|
|
@@ -6639,7 +6794,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6639
6794
|
this.shadowLayout,
|
|
6640
6795
|
this.shadowDraws.buffer,
|
|
6641
6796
|
peelPassUniforms,
|
|
6642
|
-
this.
|
|
6797
|
+
this.sunStaticArrayView,
|
|
6643
6798
|
shadowSampler,
|
|
6644
6799
|
);
|
|
6645
6800
|
this.scatterDepthPeelBindGroup =
|
|
@@ -6651,7 +6806,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6651
6806
|
this.scatterDepthDraws.buffer,
|
|
6652
6807
|
{ binding: DEPTH_FRAG_BINDING, buffer: peelPassUniforms, size: DEPTH_FRAG_SIZE },
|
|
6653
6808
|
DEPTH_PREVIOUS_BINDING,
|
|
6654
|
-
this.
|
|
6809
|
+
this.sunStaticArrayView,
|
|
6655
6810
|
shadowSampler,
|
|
6656
6811
|
);
|
|
6657
6812
|
|
|
@@ -7057,7 +7212,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7057
7212
|
this.perFrame.reset();
|
|
7058
7213
|
this.materials.dirty();
|
|
7059
7214
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
7060
|
-
this.
|
|
7215
|
+
this.motionPass?.reset();
|
|
7216
|
+
this.latePass?.reset();
|
|
7217
|
+
this.moverFrame += 1;
|
|
7218
|
+
this.inFrame = true;
|
|
7061
7219
|
/*
|
|
7062
7220
|
* The canvas rather than the swap chain's texture, and the difference is that reading this
|
|
7063
7221
|
* does not *acquire* anything. See `swapView`.
|
|
@@ -7378,7 +7536,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7378
7536
|
'what is kept and what a buffer is hinted as.',
|
|
7379
7537
|
);
|
|
7380
7538
|
}
|
|
7381
|
-
|
|
7539
|
+
/*
|
|
7540
|
+
* **The rows before this rewrite are last frame's**, kept only for a reconstruction and only
|
|
7541
|
+
* on the first rewrite of a frame — a second must not replace where the vertices were last
|
|
7542
|
+
* frame with where they were a moment ago. A rewrite between frames is the next frame's. See
|
|
7543
|
+
* `changeFrames.ts`.
|
|
7544
|
+
*/
|
|
7545
|
+
const motion = mesh.motion;
|
|
7546
|
+
let previous: GPUBuffer | null = null;
|
|
7547
|
+
if (motion !== null && this.reconstructionWanted) {
|
|
7548
|
+
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
7549
|
+
if (keepsPrevious(motion.changed, frame)) {
|
|
7550
|
+
motion.previous ??= this.surface.device.createBuffer({
|
|
7551
|
+
label: 'recon.dynamicPrevious',
|
|
7552
|
+
size: mesh.vertexBuffers[0]?.size ?? 0,
|
|
7553
|
+
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
7554
|
+
});
|
|
7555
|
+
previous = motion.previous;
|
|
7556
|
+
}
|
|
7557
|
+
motion.changed = frame;
|
|
7558
|
+
}
|
|
7559
|
+
mesh.update(this.surface.device, positions, normals, previous);
|
|
7382
7560
|
}
|
|
7383
7561
|
|
|
7384
7562
|
/**
|
|
@@ -8037,7 +8215,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8037
8215
|
depthLayer = 0,
|
|
8038
8216
|
tint: Vec3 | null = null,
|
|
8039
8217
|
/** Where it was last frame. See `RendererApi.drawMesh`, and `runMotion` for what reads it. */
|
|
8040
|
-
previousModel: ArrayLike<number> | null = null,
|
|
8218
|
+
previousModel: ArrayLike<number> | Mover | null = null,
|
|
8041
8219
|
): void {
|
|
8042
8220
|
/*
|
|
8043
8221
|
* Skipped where the flag asks and the bounds say so.
|
|
@@ -8062,7 +8240,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8062
8240
|
* depth, so a motion pass drawing it would either be depth-rejected everywhere — wasted — or,
|
|
8063
8241
|
* where it happened to pass, write motion for a surface no pixel of the frame belongs to.
|
|
8064
8242
|
*/
|
|
8065
|
-
if (
|
|
8243
|
+
if (this.tracksMotionNow()) this.recordDrawMotion(mesh, model, previousModel);
|
|
8066
8244
|
this.submitMesh(mesh, model, tint, 1, false, { depthLayer });
|
|
8067
8245
|
}
|
|
8068
8246
|
|
|
@@ -8225,7 +8403,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8225
8403
|
return;
|
|
8226
8404
|
}
|
|
8227
8405
|
|
|
8228
|
-
|
|
8406
|
+
/* A blended draw landing after the upscale is drawn into an unjittered picture. */
|
|
8407
|
+
const late = blend && (this.drawsLate() || (this.oitReplaying && this.reconstructing));
|
|
8408
|
+
this.perDraw.writeFloats(
|
|
8409
|
+
slot,
|
|
8410
|
+
FLAT_VERT_FIELDS.uViewProj.offset,
|
|
8411
|
+
late ? this.correctedViewProj : this.viewProj,
|
|
8412
|
+
);
|
|
8229
8413
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
8230
8414
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
8231
8415
|
/* The frame's wind, for a mesh carrying a sway lane. Written on this path and not the
|
|
@@ -8285,17 +8469,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8285
8469
|
* shades as an ordinary translucent one rather than sampling a texture holding the frame
|
|
8286
8470
|
* before last. That is the defined state the two-backends rule asks for.
|
|
8287
8471
|
*/
|
|
8288
|
-
const
|
|
8289
|
-
const refracting = refraction > 0 && this.takeRefractSnapshot();
|
|
8290
|
-
if (refracting) {
|
|
8291
|
-
this.perFrameFloats[this.materialField('uRefractStrength')] = refraction;
|
|
8292
|
-
const refractTint = options.refractTint;
|
|
8293
|
-
const tintAt = this.materialField('uRefractTint');
|
|
8294
|
-
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
8295
|
-
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
8296
|
-
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
8297
|
-
this.perFrameFloats[this.materialField('uRefractThickness')] = options.thicknessM ?? 0;
|
|
8298
|
-
}
|
|
8472
|
+
const refracting = this.bindSeeThrough(options);
|
|
8299
8473
|
/* Taken for itself when any of those differs from the pass, and put back below: the rule both
|
|
8300
8474
|
backends count material changes by. See `materialChanges.ts`. */
|
|
8301
8475
|
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
@@ -8355,7 +8529,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8355
8529
|
* pays the compile it was trying to avoid. That is a slow frame; the alternative is a
|
|
8356
8530
|
* thrown error in the middle of one.
|
|
8357
8531
|
*/
|
|
8358
|
-
const pipelines = this.targetPipelines();
|
|
8532
|
+
const pipelines = blend ? this.blendedPipelines() : this.targetPipelines();
|
|
8359
8533
|
let pipeline = pipelines.peek(key);
|
|
8360
8534
|
if (pipeline === undefined) {
|
|
8361
8535
|
const present = this.meshPresent.get(base);
|
|
@@ -8403,7 +8577,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8403
8577
|
* the uniform rings are the state arena this design would otherwise have had to build —
|
|
8404
8578
|
* so the command carries them as its two dynamic offsets.
|
|
8405
8579
|
*/
|
|
8406
|
-
const command = this.recordDraw(0, this.currentTarget());
|
|
8580
|
+
const command = blend ? this.recordBlended(0) : this.recordDraw(0, this.currentTarget());
|
|
8407
8581
|
if (command !== null) {
|
|
8408
8582
|
command.pipeline = pipeline;
|
|
8409
8583
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
@@ -8440,11 +8614,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8440
8614
|
if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
8441
8615
|
if (!fog) this.perFrameInts[this.materialField('uFogEnabled')] = 1;
|
|
8442
8616
|
}
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
this.perFrameFloats[this.materialField('uRefractStrength')] = 0;
|
|
8447
|
-
}
|
|
8617
|
+
/* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
|
|
8618
|
+
shared, so a strength left set is worn by everything drawn after it. */
|
|
8619
|
+
if (refracting) this.releaseSeeThrough();
|
|
8448
8620
|
if (!toneMapped) {
|
|
8449
8621
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
8450
8622
|
}
|
|
@@ -8518,12 +8690,35 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8518
8690
|
/** Push placement and colour. Only the live prefix; see `GpuInstancedBatch.upload`. */
|
|
8519
8691
|
uploadInstanced(batch: GpuInstancedBatch, data: MeshInstances): void {
|
|
8520
8692
|
if (this.surface.lost) return;
|
|
8521
|
-
|
|
8693
|
+
/*
|
|
8694
|
+
* **The placement before this upload is last frame's**, kept only for a reconstruction and only
|
|
8695
|
+
* on a frame's first upload, for the reason `updateMesh` gives. See `changeFrames.ts`.
|
|
8696
|
+
*/
|
|
8697
|
+
let previous: GPUBuffer | null = null;
|
|
8698
|
+
if (this.reconstructionWanted) {
|
|
8699
|
+
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
8700
|
+
if (keepsPrevious(batch.changed, frame)) {
|
|
8701
|
+
batch.previousBuffer ??= this.surface.device.createBuffer({
|
|
8702
|
+
label: 'recon.instancesPrevious',
|
|
8703
|
+
size: batch.capacity * INSTANCE_STRIDE,
|
|
8704
|
+
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
8705
|
+
});
|
|
8706
|
+
previous = batch.previousBuffer;
|
|
8707
|
+
}
|
|
8708
|
+
batch.changed = frame;
|
|
8709
|
+
}
|
|
8710
|
+
batch.upload(this.surface.device.queue, data, previous);
|
|
8522
8711
|
}
|
|
8523
8712
|
|
|
8524
8713
|
/** Draw every live instance, opaque. */
|
|
8525
8714
|
drawInstanced(batch: GpuInstancedBatch, data: MeshInstances): void {
|
|
8526
8715
|
this.submitInstanced(batch, data, 1, false, {});
|
|
8716
|
+
/* A batch that moved this frame pairs each slot with last frame's; the batch is the identity. */
|
|
8717
|
+
const previous = batch.previousBuffer;
|
|
8718
|
+
if (previous !== null && batch.changed === this.moverFrame && this.tracksMotionNow()) {
|
|
8719
|
+
const paired = pairedInstances(batch.previousCount, Math.min(data.count, batch.capacity));
|
|
8720
|
+
if (paired > 0) this.motionPass?.recordInstanced(batch.mesh, batch.buffer, previous, paired);
|
|
8721
|
+
}
|
|
8527
8722
|
}
|
|
8528
8723
|
|
|
8529
8724
|
/** Draw every live instance, blended. See `drawTranslucentMesh` for what the options mean. */
|
|
@@ -8542,6 +8737,45 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8542
8737
|
batch.dispose();
|
|
8543
8738
|
}
|
|
8544
8739
|
|
|
8740
|
+
/**
|
|
8741
|
+
* Write what a translucent draw shows through it into the material scratch: refraction's bend and
|
|
8742
|
+
* absorption, and glass. Returns whether the frame's copy is there to read, and so whether
|
|
8743
|
+
* `releaseSeeThrough` owes the scratch back.
|
|
8744
|
+
*
|
|
8745
|
+
* **A glass draw takes the copy even when it does not bend**, since a pane shows what is behind it
|
|
8746
|
+
* either way. A copy that cannot be taken — a mirror, a probe bake — leaves both off, so the pane
|
|
8747
|
+
* shades as ordinary translucency rather than sampling a stand-in. See `glass.ts`.
|
|
8748
|
+
*/
|
|
8749
|
+
private bindSeeThrough(options: TranslucentMeshOptions): boolean {
|
|
8750
|
+
const refraction = options.refraction ?? 0;
|
|
8751
|
+
const glassy = resolveGlass(options.glass, this.glassScratch);
|
|
8752
|
+
if ((refraction <= 0 && !glassy) || !this.takeRefractSnapshot()) return false;
|
|
8753
|
+
const glass = this.glassScratch;
|
|
8754
|
+
/* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
|
|
8755
|
+
const seeAt = this.materialField('uSeeThrough');
|
|
8756
|
+
this.perFrameFloats[seeAt] = Math.max(refraction, 0);
|
|
8757
|
+
this.perFrameFloats[seeAt + 1] = options.thicknessM ?? 0;
|
|
8758
|
+
this.perFrameFloats[seeAt + 2] = glass.transmission;
|
|
8759
|
+
this.perFrameFloats[seeAt + 3] = glass.frost;
|
|
8760
|
+
const refractTint = options.refractTint;
|
|
8761
|
+
const tintAt = this.materialField('uRefractTint');
|
|
8762
|
+
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
8763
|
+
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
8764
|
+
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
8765
|
+
const glassTintAt = this.materialField('uGlassTint');
|
|
8766
|
+
this.perFrameFloats[glassTintAt] = glass.tint[0];
|
|
8767
|
+
this.perFrameFloats[glassTintAt + 1] = glass.tint[1];
|
|
8768
|
+
this.perFrameFloats[glassTintAt + 2] = glass.tint[2];
|
|
8769
|
+
return true;
|
|
8770
|
+
}
|
|
8771
|
+
|
|
8772
|
+
/** Put the scratch back after `bindSeeThrough`: refraction and glass off. */
|
|
8773
|
+
private releaseSeeThrough(): void {
|
|
8774
|
+
const seeAt = this.materialField('uSeeThrough');
|
|
8775
|
+
this.perFrameFloats[seeAt] = 0;
|
|
8776
|
+
this.perFrameFloats[seeAt + 2] = 0;
|
|
8777
|
+
}
|
|
8778
|
+
|
|
8545
8779
|
/**
|
|
8546
8780
|
* One draw, one material slot, one uniform slot, however many instances.
|
|
8547
8781
|
*
|
|
@@ -8578,7 +8812,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8578
8812
|
return;
|
|
8579
8813
|
}
|
|
8580
8814
|
|
|
8581
|
-
this.perDraw.writeFloats(
|
|
8815
|
+
this.perDraw.writeFloats(
|
|
8816
|
+
slot,
|
|
8817
|
+
FLAT_VERT_FIELDS.uViewProj.offset,
|
|
8818
|
+
blend && this.drawsLate() ? this.correctedViewProj : this.viewProj,
|
|
8819
|
+
);
|
|
8582
8820
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
8583
8821
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
8584
8822
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
@@ -8601,8 +8839,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8601
8839
|
|
|
8602
8840
|
const depthWrite = options.depthWrite ?? true;
|
|
8603
8841
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
8604
|
-
/*
|
|
8605
|
-
const
|
|
8842
|
+
/* A blended batch shows what is behind it — refraction or glass — as a single draw does. */
|
|
8843
|
+
const refracting = blend && this.bindSeeThrough(options);
|
|
8844
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
8606
8845
|
if (own) this.materials.dirty();
|
|
8607
8846
|
const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
|
|
8608
8847
|
/* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
|
|
@@ -8613,7 +8852,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8613
8852
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8614
8853
|
`${this.materialDoubleSided ? '|2s' : ''}`;
|
|
8615
8854
|
|
|
8616
|
-
const pipelines = this.targetPipelines();
|
|
8855
|
+
const pipelines = blend ? this.blendedPipelines() : this.targetPipelines();
|
|
8617
8856
|
let pipeline = pipelines.peek(key);
|
|
8618
8857
|
if (pipeline === undefined) {
|
|
8619
8858
|
const present = this.meshPresent.get(base);
|
|
@@ -8640,7 +8879,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8640
8879
|
|
|
8641
8880
|
const material = this.materialSlotForDraw();
|
|
8642
8881
|
if (material !== null) {
|
|
8643
|
-
const command = this.recordDraw(0, this.currentTarget());
|
|
8882
|
+
const command = blend ? this.recordBlended(0) : this.recordDraw(0, this.currentTarget());
|
|
8644
8883
|
if (command !== null) {
|
|
8645
8884
|
command.pipeline = pipeline;
|
|
8646
8885
|
command.bindGroup = this.flatBindGroupFor(false, false, null);
|
|
@@ -8684,6 +8923,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8684
8923
|
if (!toneMapped) {
|
|
8685
8924
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
8686
8925
|
}
|
|
8926
|
+
if (refracting) this.releaseSeeThrough();
|
|
8687
8927
|
if (own) this.materials.dirty();
|
|
8688
8928
|
}
|
|
8689
8929
|
|
|
@@ -8877,6 +9117,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8877
9117
|
this.lightViewProj.set(lightViewProj as Float32Array);
|
|
8878
9118
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
8879
9119
|
this.shadowLayerIsPeel = peel;
|
|
9120
|
+
this.shadowLayerIsDynamic = dynamic;
|
|
9121
|
+
this.glassCasters.clear();
|
|
8880
9122
|
/*
|
|
8881
9123
|
* A frame's peel is only as good as the pass that fills it, and the static layer is the one
|
|
8882
9124
|
* that opens first.
|
|
@@ -8984,12 +9226,349 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8984
9226
|
}
|
|
8985
9227
|
|
|
8986
9228
|
/** Close the shadow pass and submit it, so the frame can sample what it wrote. */
|
|
9229
|
+
/**
|
|
9230
|
+
* Add the sun's two glass layers, once, carrying every layer it holds across on `encoder`.
|
|
9231
|
+
*
|
|
9232
|
+
* **Copied rather than re-baked**, for the reason `SunShadowArray.growForGlass` gives: the static
|
|
9233
|
+
* map is the consumer's to bake and may be baked once, and the glass that grows the texture
|
|
9234
|
+
* arrives in the very pass that drew it. Every view of the old texture is stale afterwards, so
|
|
9235
|
+
* every group holding one is rebuilt here — the lit pass's, the volumes', the medium's and the
|
|
9236
|
+
* peel's — and the old texture is destroyed only once this encoder is submitted.
|
|
9237
|
+
*/
|
|
9238
|
+
private growSunForGlass(encoder: GPUCommandEncoder): void {
|
|
9239
|
+
if (this.sunGlassViews !== null) return;
|
|
9240
|
+
const device = this.surface.device;
|
|
9241
|
+
const size = this.quality.directionalShadowMapSize;
|
|
9242
|
+
const maps = this.sunLayerCount;
|
|
9243
|
+
/* The glass layers at their fixed places after the three maps: see `SUN_GLASS_STATIC_LAYER`. */
|
|
9244
|
+
const layers = SUN_GLASS_MOVING_LAYER + 1;
|
|
9245
|
+
const old = this.sunShadowTexture;
|
|
9246
|
+
const texture = device.createTexture({
|
|
9247
|
+
label: 'shadow.sun',
|
|
9248
|
+
size: [size, size, layers],
|
|
9249
|
+
format: SHADOW_FORMAT,
|
|
9250
|
+
usage: SUN_USAGE,
|
|
9251
|
+
});
|
|
9252
|
+
for (let layer = 0; layer < maps; layer++) {
|
|
9253
|
+
encoder.copyTextureToTexture(
|
|
9254
|
+
{ texture: old, origin: { x: 0, y: 0, z: layer } },
|
|
9255
|
+
{ texture, origin: { x: 0, y: 0, z: layer } },
|
|
9256
|
+
[size, size, 1],
|
|
9257
|
+
);
|
|
9258
|
+
}
|
|
9259
|
+
this.retiredSunTexture = old;
|
|
9260
|
+
this.sunShadowTexture = texture;
|
|
9261
|
+
this.sunLayerCount = layers;
|
|
9262
|
+
const views = sunViewsOf(texture, maps, layers);
|
|
9263
|
+
this.sunArrayView = views.array;
|
|
9264
|
+
this.sunStaticArrayView = views.staticArray;
|
|
9265
|
+
this.shadowView = views.static;
|
|
9266
|
+
this.dynamicView = views.dynamic;
|
|
9267
|
+
this.peelView = views.peel;
|
|
9268
|
+
this.sunGlassViews = views.glass;
|
|
9269
|
+
/* Both glass layers born at the far plane: nothing is nearer the sun than a pane not drawn. */
|
|
9270
|
+
for (const view of views.glass ?? []) {
|
|
9271
|
+
encoder
|
|
9272
|
+
.beginRenderPass({
|
|
9273
|
+
label: 'shadow.glass.clear',
|
|
9274
|
+
colorAttachments: [],
|
|
9275
|
+
depthStencilAttachment: {
|
|
9276
|
+
view,
|
|
9277
|
+
depthClearValue: 1,
|
|
9278
|
+
depthLoadOp: 'clear',
|
|
9279
|
+
depthStoreOp: 'store',
|
|
9280
|
+
},
|
|
9281
|
+
})
|
|
9282
|
+
.end();
|
|
9283
|
+
}
|
|
9284
|
+
this.rebuildSunGroups();
|
|
9285
|
+
}
|
|
9286
|
+
|
|
9287
|
+
/** Every group that holds a view of the sun's texture, rebuilt after it has been replaced. */
|
|
9288
|
+
private rebuildSunGroups(): void {
|
|
9289
|
+
const device = this.surface.device;
|
|
9290
|
+
const peelUniforms = this.shadowPeelPassUniforms;
|
|
9291
|
+
const peelSampler = this.shadowPeelSampler;
|
|
9292
|
+
if (peelUniforms !== null && peelSampler !== null) {
|
|
9293
|
+
this.shadowPeelBindGroup = createDepthBindGroup(
|
|
9294
|
+
device,
|
|
9295
|
+
this.shadowLayout,
|
|
9296
|
+
this.shadowDraws.buffer,
|
|
9297
|
+
peelUniforms,
|
|
9298
|
+
this.sunStaticArrayView,
|
|
9299
|
+
peelSampler,
|
|
9300
|
+
);
|
|
9301
|
+
this.scatterDepthPeelBindGroup = createScatterDepthBindGroup(
|
|
9302
|
+
device,
|
|
9303
|
+
this.scatterDepthLayout,
|
|
9304
|
+
this.scatterDepthDraws.buffer,
|
|
9305
|
+
{ binding: DEPTH_FRAG_BINDING, buffer: peelUniforms, size: DEPTH_FRAG_SIZE },
|
|
9306
|
+
DEPTH_PREVIOUS_BINDING,
|
|
9307
|
+
this.sunStaticArrayView,
|
|
9308
|
+
peelSampler,
|
|
9309
|
+
);
|
|
9310
|
+
}
|
|
9311
|
+
this.cutoutPeelGroups.clear();
|
|
9312
|
+
this.rebuildFlatGroupsForNewRings();
|
|
9313
|
+
this.lightVolumeBindGroup = this.buildLightVolumeBindGroup();
|
|
9314
|
+
this.mediumBindGroup = this.buildMediumBindGroup();
|
|
9315
|
+
}
|
|
9316
|
+
|
|
9317
|
+
/** The sun's tint target, made the first time glass casts and born clear on `encoder`. */
|
|
9318
|
+
private ensureSunTint(encoder: GPUCommandEncoder): SunTintTarget {
|
|
9319
|
+
if (this.sunTint !== null) return this.sunTint;
|
|
9320
|
+
const size = this.quality.directionalShadowMapSize;
|
|
9321
|
+
const edge = this.quality.glassShadows === 'half' ? Math.max(1, size >> 1) : size;
|
|
9322
|
+
const device = this.surface.device;
|
|
9323
|
+
const texture = device.createTexture({
|
|
9324
|
+
label: 'shadow.sunTint',
|
|
9325
|
+
size: [edge, edge, 2],
|
|
9326
|
+
format: 'rgba8unorm',
|
|
9327
|
+
mipLevelCount: Math.floor(Math.log2(edge)) + 1,
|
|
9328
|
+
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
9329
|
+
});
|
|
9330
|
+
const layer = (index: number): GPUTextureView =>
|
|
9331
|
+
texture.createView({
|
|
9332
|
+
dimension: '2d',
|
|
9333
|
+
baseArrayLayer: index,
|
|
9334
|
+
arrayLayerCount: 1,
|
|
9335
|
+
baseMipLevel: 0,
|
|
9336
|
+
mipLevelCount: 1,
|
|
9337
|
+
});
|
|
9338
|
+
const mips = (index: number): SnapshotMips =>
|
|
9339
|
+
new SnapshotMips(device, this.pipelines, texture, 'rgba8unorm', index, 'shadow.tint.mips');
|
|
9340
|
+
const target: SunTintTarget = {
|
|
9341
|
+
texture,
|
|
9342
|
+
layers: [layer(0), layer(1)],
|
|
9343
|
+
array: texture.createView({ dimension: '2d-array' }),
|
|
9344
|
+
mips: [mips(0), mips(1)],
|
|
9345
|
+
};
|
|
9346
|
+
for (const view of target.layers) {
|
|
9347
|
+
encoder
|
|
9348
|
+
.beginRenderPass({
|
|
9349
|
+
label: 'shadow.tint.clear',
|
|
9350
|
+
colorAttachments: [{ view, loadOp: 'clear', clearValue: [1, 1, 1, 1], storeOp: 'store' }],
|
|
9351
|
+
})
|
|
9352
|
+
.end();
|
|
9353
|
+
}
|
|
9354
|
+
this.sunTint = target;
|
|
9355
|
+
this.rebuildFlatGroupsForNewRings();
|
|
9356
|
+
return target;
|
|
9357
|
+
}
|
|
9358
|
+
|
|
9359
|
+
/**
|
|
9360
|
+
* Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
|
|
9361
|
+
*
|
|
9362
|
+
* After the opaque casters, on the pass's own encoder. A layer that has held glass is redrawn
|
|
9363
|
+
* even when this pass offered none, so glass that has gone takes its colour with it.
|
|
9364
|
+
*/
|
|
9365
|
+
private flushSunGlass(encoder: GPUCommandEncoder, which: 'static' | 'dynamic'): void {
|
|
9366
|
+
const list = this.glassCasters;
|
|
9367
|
+
if (this.quality.glassShadows === 'off') return;
|
|
9368
|
+
if (list.count === 0 && this.sunGlassViews === null) return;
|
|
9369
|
+
if (list.count > 0) this.growSunForGlass(encoder);
|
|
9370
|
+
const glassViews = this.sunGlassViews;
|
|
9371
|
+
if (glassViews === null) return;
|
|
9372
|
+
|
|
9373
|
+
/* Where the nearest pane is: the depth pipelines, through the sink every opaque caster took. */
|
|
9374
|
+
this.shadowPass = encoder.beginRenderPass({
|
|
9375
|
+
label: 'shadow.glass',
|
|
9376
|
+
colorAttachments: [],
|
|
9377
|
+
depthStencilAttachment: {
|
|
9378
|
+
view: glassViews[which === 'static' ? 0 : 1],
|
|
9379
|
+
depthClearValue: 1,
|
|
9380
|
+
depthLoadOp: 'clear',
|
|
9381
|
+
depthStoreOp: 'store',
|
|
9382
|
+
},
|
|
9383
|
+
});
|
|
9384
|
+
this.drawingGlassDepth = true;
|
|
9385
|
+
list.replay(this.glassDepthSink);
|
|
9386
|
+
this.drawingGlassDepth = false;
|
|
9387
|
+
this.shadowPass.end();
|
|
9388
|
+
this.shadowPass = null;
|
|
9389
|
+
|
|
9390
|
+
/* What the panes let through, every one on a ray, multiplied. */
|
|
9391
|
+
const target = this.ensureSunTint(encoder);
|
|
9392
|
+
const tint = (this.glassTint ??= new GpuGlassTint(this.surface.device));
|
|
9393
|
+
tint.frag.reset();
|
|
9394
|
+
/* The direction the light travels, off the light matrix's depth row; the sign is immaterial,
|
|
9395
|
+
since the tint takes the cosine absolutely, and w 0 says it is a direction. */
|
|
9396
|
+
const m = this.correctedLightViewProj;
|
|
9397
|
+
const length = Math.hypot(m[2] as number, m[6] as number, m[10] as number) || 1;
|
|
9398
|
+
this.glassLight[0] = (m[2] as number) / length;
|
|
9399
|
+
this.glassLight[1] = (m[6] as number) / length;
|
|
9400
|
+
this.glassLight[2] = (m[10] as number) / length;
|
|
9401
|
+
this.glassLight[3] = 0;
|
|
9402
|
+
this.tintPass = encoder.beginRenderPass({
|
|
9403
|
+
label: 'shadow.tint',
|
|
9404
|
+
colorAttachments: [
|
|
9405
|
+
{
|
|
9406
|
+
view: target.layers[which === 'static' ? 0 : 1],
|
|
9407
|
+
loadOp: 'clear',
|
|
9408
|
+
clearValue: [1, 1, 1, 1],
|
|
9409
|
+
storeOp: 'store',
|
|
9410
|
+
},
|
|
9411
|
+
],
|
|
9412
|
+
});
|
|
9413
|
+
list.replay(this.glassTintSink);
|
|
9414
|
+
this.tintPass.end();
|
|
9415
|
+
this.tintPass = null;
|
|
9416
|
+
tint.frag.flush();
|
|
9417
|
+
/* The chain frost reads, for the layer just drawn. */
|
|
9418
|
+
target.mips[which === 'static' ? 0 : 1].record(encoder);
|
|
9419
|
+
}
|
|
9420
|
+
|
|
9421
|
+
/** The glass list into a glass depth layer: `casterSink` itself, with glass let through. */
|
|
9422
|
+
private readonly glassDepthSink: GlassReplaySink = {
|
|
9423
|
+
mesh: (mesh, model, material) => this.casterSink.mesh(mesh, model, material),
|
|
9424
|
+
instanced: (batch, data, material) => this.casterSink.instanced?.(batch, data, material),
|
|
9425
|
+
skinnedMesh: (mesh, model, palette, material) =>
|
|
9426
|
+
this.casterSink.skinnedMesh(mesh, model, palette, material),
|
|
9427
|
+
};
|
|
9428
|
+
|
|
9429
|
+
/**
|
|
9430
|
+
* The glass list into a tint layer, through the tint pipelines, **culling nothing**: every pane on
|
|
9431
|
+
* a ray counts, whichever way it faces. See the other backend's `glassTintSink`.
|
|
9432
|
+
*/
|
|
9433
|
+
private readonly glassTintSink: GlassReplaySink = {
|
|
9434
|
+
mesh: (mesh, model, material, glass) => {
|
|
9435
|
+
const pass = this.tintPass;
|
|
9436
|
+
const tint = this.glassTint;
|
|
9437
|
+
if (pass === null || tint === null) return;
|
|
9438
|
+
const geometry = mesh as GpuMesh & { key?: string };
|
|
9439
|
+
if (geometry.vertexBuffers === undefined) return;
|
|
9440
|
+
const cutout = cutoutOf(material);
|
|
9441
|
+
const albedo = cutout === null ? null : (cutout.albedo as unknown as GpuSurfaceTexture);
|
|
9442
|
+
const slot = this.shadowDraws.allocate();
|
|
9443
|
+
const pane = tint.frag.allocate();
|
|
9444
|
+
if (slot === null || pane === null) return;
|
|
9445
|
+
this.shadowDraws.writeFloats(
|
|
9446
|
+
slot,
|
|
9447
|
+
DEPTH_VERT_FIELDS.uLightViewProj.offset,
|
|
9448
|
+
this.correctedLightViewProj,
|
|
9449
|
+
);
|
|
9450
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uModel.offset, model as Float32Array);
|
|
9451
|
+
this.writeShadowWind(slot);
|
|
9452
|
+
if (cutout !== null) this.writeCutout(slot, cutout, DEPTH_CUTOUT_VERT_FIELDS);
|
|
9453
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
9454
|
+
pass.setPipeline(
|
|
9455
|
+
tint.pipeline(
|
|
9456
|
+
this.pipelines,
|
|
9457
|
+
albedo === null ? 'rigid' : 'cutout',
|
|
9458
|
+
geometry.key ?? '',
|
|
9459
|
+
this.presentOf(geometry),
|
|
9460
|
+
'none',
|
|
9461
|
+
),
|
|
9462
|
+
);
|
|
9463
|
+
pass.setBindGroup(
|
|
9464
|
+
0,
|
|
9465
|
+
albedo === null
|
|
9466
|
+
? tint.group(this.shadowDraws.buffer)
|
|
9467
|
+
: tint.cutoutGroup(this.shadowDraws.buffer, albedo.view, albedo.sampler),
|
|
9468
|
+
[slot, pane],
|
|
9469
|
+
);
|
|
9470
|
+
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
9471
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
9472
|
+
}
|
|
9473
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
9474
|
+
pass.drawIndexed(geometry.indexCount);
|
|
9475
|
+
},
|
|
9476
|
+
instanced: (batch, data, material, glass) => {
|
|
9477
|
+
const pass = this.tintPass;
|
|
9478
|
+
const tint = this.glassTint;
|
|
9479
|
+
if (pass === null || tint === null) return;
|
|
9480
|
+
const gpuBatch = batch as GpuInstancedBatch;
|
|
9481
|
+
const geometry = gpuBatch.mesh as GpuMesh & { key?: string };
|
|
9482
|
+
if (geometry.vertexBuffers === undefined) return;
|
|
9483
|
+
const count = Math.min(data.count, gpuBatch.capacity);
|
|
9484
|
+
if (count === 0) return;
|
|
9485
|
+
const cutout = cutoutOf(material);
|
|
9486
|
+
const albedo = cutout === null ? null : (cutout.albedo as unknown as GpuSurfaceTexture);
|
|
9487
|
+
const slot = this.shadowDraws.allocate();
|
|
9488
|
+
const pane = tint.frag.allocate();
|
|
9489
|
+
if (slot === null || pane === null) return;
|
|
9490
|
+
this.shadowDraws.writeFloats(
|
|
9491
|
+
slot,
|
|
9492
|
+
DEPTH_VERT_FIELDS.uLightViewProj.offset,
|
|
9493
|
+
this.correctedLightViewProj,
|
|
9494
|
+
);
|
|
9495
|
+
if (cutout !== null) this.writeCutout(slot, cutout, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS);
|
|
9496
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
9497
|
+
pass.setPipeline(
|
|
9498
|
+
tint.pipeline(
|
|
9499
|
+
this.pipelines,
|
|
9500
|
+
albedo === null ? 'instanced' : 'instancedCutout',
|
|
9501
|
+
geometry.key ?? '',
|
|
9502
|
+
this.presentOf(geometry),
|
|
9503
|
+
'none',
|
|
9504
|
+
),
|
|
9505
|
+
);
|
|
9506
|
+
pass.setBindGroup(
|
|
9507
|
+
0,
|
|
9508
|
+
albedo === null
|
|
9509
|
+
? tint.group(this.shadowDraws.buffer)
|
|
9510
|
+
: tint.cutoutGroup(this.shadowDraws.buffer, albedo.view, albedo.sampler),
|
|
9511
|
+
[slot, pane],
|
|
9512
|
+
);
|
|
9513
|
+
let index = 0;
|
|
9514
|
+
for (; index < geometry.vertexBuffers.length; index++) {
|
|
9515
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
9516
|
+
}
|
|
9517
|
+
pass.setVertexBuffer(index, gpuBatch.buffer);
|
|
9518
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
9519
|
+
pass.drawIndexed(geometry.indexCount, count);
|
|
9520
|
+
},
|
|
9521
|
+
skinnedMesh: (mesh, model, palette, _material, glass) => {
|
|
9522
|
+
const pass = this.tintPass;
|
|
9523
|
+
const tint = this.glassTint;
|
|
9524
|
+
if (pass === null || tint === null) return;
|
|
9525
|
+
const geometry = mesh as GpuMesh & { key?: string };
|
|
9526
|
+
if (geometry.vertexBuffers === undefined) return;
|
|
9527
|
+
const paletteSlot = this.skinPalettes.take(this.surface.device, palette);
|
|
9528
|
+
const view = paletteSlot === null ? null : this.skinPalettes.view(paletteSlot);
|
|
9529
|
+
if (view === null) return;
|
|
9530
|
+
const slot = this.shadowDraws.allocate();
|
|
9531
|
+
const pane = tint.frag.allocate();
|
|
9532
|
+
if (slot === null || pane === null) return;
|
|
9533
|
+
this.shadowDraws.writeFloats(
|
|
9534
|
+
slot,
|
|
9535
|
+
DEPTH_VERT_FIELDS.uLightViewProj.offset,
|
|
9536
|
+
this.correctedLightViewProj,
|
|
9537
|
+
);
|
|
9538
|
+
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uModel.offset, model as Float32Array);
|
|
9539
|
+
this.writeShadowWind(slot);
|
|
9540
|
+
tint.writePane(pane, glass, this.glassLight);
|
|
9541
|
+
pass.setPipeline(
|
|
9542
|
+
tint.pipeline(
|
|
9543
|
+
this.pipelines,
|
|
9544
|
+
'skinned',
|
|
9545
|
+
geometry.key ?? '',
|
|
9546
|
+
this.presentOf(geometry),
|
|
9547
|
+
'none',
|
|
9548
|
+
),
|
|
9549
|
+
);
|
|
9550
|
+
pass.setBindGroup(0, tint.skinnedGroup(this.shadowDraws.buffer, view), [slot, pane]);
|
|
9551
|
+
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
9552
|
+
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
9553
|
+
}
|
|
9554
|
+
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
9555
|
+
pass.drawIndexed(geometry.indexCount);
|
|
9556
|
+
},
|
|
9557
|
+
};
|
|
9558
|
+
|
|
8987
9559
|
endShadowPass(): void {
|
|
8988
9560
|
if (this.surface.lost || this.shadowPass === null || this.shadowEncoder === null) return;
|
|
9561
|
+
this.shadowPass.end();
|
|
9562
|
+
/* The peel keeps no glass: its layer is the second opaque occluder and nothing else. */
|
|
9563
|
+
if (!this.shadowLayerIsPeel) {
|
|
9564
|
+
this.flushSunGlass(this.shadowEncoder, this.shadowLayerIsDynamic ? 'dynamic' : 'static');
|
|
9565
|
+
}
|
|
8989
9566
|
this.shadowDraws.flush();
|
|
8990
9567
|
this.scatterDepthDraws.flush();
|
|
8991
|
-
this.shadowPass.end();
|
|
8992
9568
|
this.surface.device.queue.submit([this.shadowEncoder.finish()]);
|
|
9569
|
+
/* A growth's old texture, copied out of by what was just submitted and needed no longer. */
|
|
9570
|
+
this.retiredSunTexture?.destroy();
|
|
9571
|
+
this.retiredSunTexture = null;
|
|
8993
9572
|
/* The map now holds a rendered peel rather than whatever it was created with. */
|
|
8994
9573
|
if (this.shadowLayerIsPeel) this.peelFilled = true;
|
|
8995
9574
|
this.shadowLayerIsPeel = false;
|
|
@@ -9019,6 +9598,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9019
9598
|
private preparedPointLightCount = 0;
|
|
9020
9599
|
/** See `renderer.ts`: built when a world reports its lights, and rebuilt only to grow. */
|
|
9021
9600
|
private pointShadowArray: GpuPointShadowArray | null = null;
|
|
9601
|
+
/** The array glass replaced, destroyed once the round that copied out of it is submitted. */
|
|
9602
|
+
private retiredPointTexture: GPUTexture | null = null;
|
|
9022
9603
|
private readonly bakeScratch = createBakeScratch(POINT_SHADOW_POOL);
|
|
9023
9604
|
/** Said once per renderer; see `updatePointShadows`. */
|
|
9024
9605
|
private warnedNoPointShadowArray = false;
|
|
@@ -9204,6 +9785,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9204
9785
|
*/
|
|
9205
9786
|
this.shadowDraws.reset();
|
|
9206
9787
|
this.scatterDepthDraws.reset();
|
|
9788
|
+
this.glassTint?.frag.reset();
|
|
9207
9789
|
this.shadowEncoder = this.surface.device.createCommandEncoder({ label: 'pointShadow' });
|
|
9208
9790
|
/* A cube face is a shadow pass like any other, and the courtyard is where it matters: six of
|
|
9209
9791
|
them a frame is the difference this slot exists to make visible. */
|
|
@@ -9231,7 +9813,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9231
9813
|
light.z,
|
|
9232
9814
|
light.radius,
|
|
9233
9815
|
(view, viewProj) => this.bakeFace(view, viewProj, casters as ShadowCasters),
|
|
9234
|
-
(layer, face, far, near) => this.resolveFace(layer, face, far, near),
|
|
9816
|
+
(layer, face, far, near, x, y, z) => this.resolveFace(layer, face, far, near, x, y, z),
|
|
9235
9817
|
this.faceViewProj,
|
|
9236
9818
|
light.shadowNear,
|
|
9237
9819
|
light.sourceRadius,
|
|
@@ -9245,10 +9827,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9245
9827
|
* encoder, a second submit and a second `shadows` timer slot for work that is the same work.
|
|
9246
9828
|
*/
|
|
9247
9829
|
this.updateAreaShadows(areaLights, frameDt, staticCasters, dynamicCasters);
|
|
9830
|
+
/* The chain frost reads, once for the round, for the lights whose glass it drew or cleared. */
|
|
9831
|
+
this.pointShadowArray?.glassTint?.recordMips(this.shadowEncoder);
|
|
9248
9832
|
} finally {
|
|
9249
9833
|
this.depthCullMode = 'back';
|
|
9250
9834
|
this.shadowDraws.flush();
|
|
9251
9835
|
this.scatterDepthDraws.flush();
|
|
9836
|
+
this.glassTint?.frag.flush();
|
|
9252
9837
|
/*
|
|
9253
9838
|
* **Before the submit, and that ordering is the whole point of the ring.** A write to the
|
|
9254
9839
|
* queue lands ahead of the encoder's commands whenever it is issued, so uploading per
|
|
@@ -9260,6 +9845,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9260
9845
|
this.shadowEncoder = null;
|
|
9261
9846
|
this.shadowPass = null;
|
|
9262
9847
|
this.gpuTimer.end();
|
|
9848
|
+
/* An interleaving's old array, copied out of by what was just submitted. */
|
|
9849
|
+
this.retiredPointTexture?.destroy();
|
|
9850
|
+
this.retiredPointTexture = null;
|
|
9263
9851
|
}
|
|
9264
9852
|
|
|
9265
9853
|
/*
|
|
@@ -9270,6 +9858,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9270
9858
|
* written every frame regardless.
|
|
9271
9859
|
*/
|
|
9272
9860
|
pool.resolve(this.resolvedPointShadows);
|
|
9861
|
+
/* As the array stores them: 2L once glass has interleaved it. See `strideLayers`. */
|
|
9862
|
+
const stride = this.pointShadowArray.stride;
|
|
9863
|
+
strideLayers(this.resolvedPointShadows.layers, stride);
|
|
9864
|
+
strideLayers(this.resolvedPointShadows.liveLayers, stride);
|
|
9273
9865
|
}
|
|
9274
9866
|
|
|
9275
9867
|
/**
|
|
@@ -9314,7 +9906,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9314
9906
|
light.z,
|
|
9315
9907
|
range,
|
|
9316
9908
|
(view, viewProj) => this.bakeFace(view, viewProj, casters as ShadowCasters),
|
|
9317
|
-
(layer, face, far, mapNear
|
|
9909
|
+
(layer, face, far, mapNear, x, y, z) =>
|
|
9910
|
+
this.resolveFace(layer, face, far, mapNear, x, y, z),
|
|
9318
9911
|
this.faceViewProj,
|
|
9319
9912
|
near,
|
|
9320
9913
|
/* The filter reads the emitter's size, never the bake. See `renderer.ts`'s copy. */
|
|
@@ -9323,6 +9916,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9323
9916
|
),
|
|
9324
9917
|
);
|
|
9325
9918
|
set.resolve(areaLights, this.resolvedAreaShadows);
|
|
9919
|
+
const stride = this.pointShadowArray?.stride ?? 1;
|
|
9920
|
+
strideLayers(this.resolvedAreaShadows.layer, stride);
|
|
9921
|
+
strideLayers(this.resolvedAreaShadows.liveLayer, stride);
|
|
9326
9922
|
}
|
|
9327
9923
|
|
|
9328
9924
|
/**
|
|
@@ -9350,9 +9946,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9350
9946
|
depthStoreOp: 'store',
|
|
9351
9947
|
},
|
|
9352
9948
|
});
|
|
9949
|
+
/* A face is an enumeration of its own: its glass is its own. */
|
|
9950
|
+
this.glassCasters.clear();
|
|
9353
9951
|
casters(this.casterSink);
|
|
9354
9952
|
this.shadowPass.end();
|
|
9355
9953
|
this.shadowPass = null;
|
|
9954
|
+
/*
|
|
9955
|
+
* The first face to offer glass interleaves the array, before its opaque depth is resolved:
|
|
9956
|
+
* `PointShadowArray.growForGlass`. The lit pass's groups hold the old array's view, so they
|
|
9957
|
+
* are rebuilt, and the old texture goes once this round's encoder — which copies out of it —
|
|
9958
|
+
* is submitted.
|
|
9959
|
+
*/
|
|
9960
|
+
const array = this.pointShadowArray;
|
|
9961
|
+
if (array !== null && this.glassCasters.count > 0 && this.quality.glassShadows !== 'off') {
|
|
9962
|
+
const tintEdge =
|
|
9963
|
+
this.quality.glassShadows === 'half' ? OCTAHEDRAL_EDGE >> 1 : OCTAHEDRAL_EDGE;
|
|
9964
|
+
const old = array.growForGlass(encoder, tintEdge, this.pipelines);
|
|
9965
|
+
if (old !== null) {
|
|
9966
|
+
this.retiredPointTexture = old;
|
|
9967
|
+
this.rebuildFlatBindGroup();
|
|
9968
|
+
}
|
|
9969
|
+
}
|
|
9356
9970
|
}
|
|
9357
9971
|
|
|
9358
9972
|
/**
|
|
@@ -9362,11 +9976,70 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9362
9976
|
* serve every light: the face is consumed before the next one is drawn. See
|
|
9363
9977
|
* `pointShadowArray.ts` for why the regions are disjoint.
|
|
9364
9978
|
*/
|
|
9365
|
-
private resolveFace(
|
|
9979
|
+
private resolveFace(
|
|
9980
|
+
layer: number,
|
|
9981
|
+
face: number,
|
|
9982
|
+
far: number,
|
|
9983
|
+
near: number,
|
|
9984
|
+
x: number,
|
|
9985
|
+
y: number,
|
|
9986
|
+
z: number,
|
|
9987
|
+
): void {
|
|
9366
9988
|
const encoder = this.shadowEncoder;
|
|
9367
9989
|
const array = this.pointShadowArray;
|
|
9368
9990
|
if (encoder === null || array === null) return;
|
|
9369
9991
|
array.resolve(encoder, layer, face, far, near);
|
|
9992
|
+
const tintTarget = array.glassTint;
|
|
9993
|
+
if (tintTarget === null || this.quality.glassShadows === 'off') return;
|
|
9994
|
+
/*
|
|
9995
|
+
* The face's glass, twice, as `PointShadowMap.bakeGlassFace` draws it on the other backend. A
|
|
9996
|
+
* face that offers none still clears a region a previous owner's glass is in, from scratches
|
|
9997
|
+
* cleared and not drawn into, because a pool slot changes hands with its layer.
|
|
9998
|
+
*/
|
|
9999
|
+
const offered = this.glassCasters.count > 0;
|
|
10000
|
+
if (!offered && !tintTarget.held(layer, face)) return;
|
|
10001
|
+
this.shadowPass = encoder.beginRenderPass({
|
|
10002
|
+
label: 'pointShadow.glass',
|
|
10003
|
+
colorAttachments: [],
|
|
10004
|
+
depthStencilAttachment: {
|
|
10005
|
+
view: array.scratchView,
|
|
10006
|
+
depthClearValue: 1,
|
|
10007
|
+
depthLoadOp: 'clear',
|
|
10008
|
+
depthStoreOp: 'store',
|
|
10009
|
+
},
|
|
10010
|
+
});
|
|
10011
|
+
if (offered) {
|
|
10012
|
+
this.drawingGlassDepth = true;
|
|
10013
|
+
this.glassCasters.replay(this.glassDepthSink);
|
|
10014
|
+
this.drawingGlassDepth = false;
|
|
10015
|
+
}
|
|
10016
|
+
this.shadowPass.end();
|
|
10017
|
+
this.shadowPass = null;
|
|
10018
|
+
array.resolveGlass(encoder, layer, face, far, near);
|
|
10019
|
+
|
|
10020
|
+
this.tintPass = encoder.beginRenderPass({
|
|
10021
|
+
label: 'pointShadow.tint',
|
|
10022
|
+
colorAttachments: [
|
|
10023
|
+
{
|
|
10024
|
+
view: tintTarget.scratchView,
|
|
10025
|
+
loadOp: 'clear',
|
|
10026
|
+
clearValue: [1, 1, 1, 1],
|
|
10027
|
+
storeOp: 'store',
|
|
10028
|
+
},
|
|
10029
|
+
],
|
|
10030
|
+
});
|
|
10031
|
+
if (offered) {
|
|
10032
|
+
this.glassTint ??= new GpuGlassTint(this.surface.device);
|
|
10033
|
+
/* A place, w 1: each pane's angle to the lamp comes from its own position. */
|
|
10034
|
+
this.glassLight[0] = x;
|
|
10035
|
+
this.glassLight[1] = y;
|
|
10036
|
+
this.glassLight[2] = z;
|
|
10037
|
+
this.glassLight[3] = 1;
|
|
10038
|
+
this.glassCasters.replay(this.glassTintSink);
|
|
10039
|
+
}
|
|
10040
|
+
this.tintPass.end();
|
|
10041
|
+
this.tintPass = null;
|
|
10042
|
+
tintTarget.resolve(encoder, layer, face, offered);
|
|
9370
10043
|
}
|
|
9371
10044
|
|
|
9372
10045
|
private buildFlatBindGroup(): GPUBindGroup {
|
|
@@ -9726,8 +10399,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9726
10399
|
* drawn over a reflective floor rather than under it.
|
|
9727
10400
|
*/
|
|
9728
10401
|
this.runReflections(encoder);
|
|
9729
|
-
/* The translucent set, into its two buffers and back over the scene
|
|
9730
|
-
|
|
10402
|
+
/* The translucent set, into its two buffers and back over the scene — after the upscale instead
|
|
10403
|
+
while reconstructing, where `runLate` draws the rest of the blended frame. */
|
|
10404
|
+
if (!this.reconstructing) this.runOit(encoder);
|
|
9731
10405
|
/*
|
|
9732
10406
|
* **The medium, after everything that draws and before anything that composites.**
|
|
9733
10407
|
*
|
|
@@ -9748,6 +10422,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9748
10422
|
*/
|
|
9749
10423
|
this.runMotion(encoder);
|
|
9750
10424
|
this.runReconstruction(encoder);
|
|
10425
|
+
this.runLate(encoder);
|
|
10426
|
+
if (this.reconstructing) this.runOit(encoder);
|
|
9751
10427
|
const aoStrength = this.quality.ambientOcclusion;
|
|
9752
10428
|
if (aoStrength > 0 && this.frameProjection !== null) this.runOcclusion(encoder);
|
|
9753
10429
|
const bloomStrength = this.quality.bloom * this.bloomScale;
|
|
@@ -9976,19 +10652,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9976
10652
|
label: 'recon.shown',
|
|
9977
10653
|
size: [outputWidth, outputHeight],
|
|
9978
10654
|
format: RECON_HISTORY_FORMAT,
|
|
9979
|
-
|
|
10655
|
+
/* An attachment too, because the blended draws land on it after the upscale, and a copy
|
|
10656
|
+
source for the panes that refract it. See `runLate`. */
|
|
10657
|
+
usage: HISTORY_USAGE | 0x10 | 0x1, // RENDER_ATTACHMENT | COPY_SRC
|
|
9980
10658
|
});
|
|
9981
10659
|
this.reconShownView = this.reconShown.createView();
|
|
9982
10660
|
this.reconMotion = device.createTexture({
|
|
9983
10661
|
label: 'recon.motion',
|
|
9984
10662
|
size: [renderWidth, renderHeight],
|
|
9985
10663
|
format: RECON_HISTORY_FORMAT,
|
|
9986
|
-
/* An attachment as well, because the pass that
|
|
9987
|
-
|
|
9988
|
-
|
|
10664
|
+
/* An attachment as well, because the pass that writes it is a draw. It is cleared every
|
|
10665
|
+
frame by that pass, and a zero in the fourth channel is what tells the resolve to derive
|
|
10666
|
+
the camera's motion. */
|
|
9989
10667
|
usage: 0x4 | 0x10,
|
|
9990
10668
|
});
|
|
9991
10669
|
this.reconMotionView = this.reconMotion.createView();
|
|
10670
|
+
this.motionPass ??= new MotionPass(device);
|
|
10671
|
+
this.latePass ??= new LatePass(device);
|
|
9992
10672
|
this.reconPreviousDepth = device.createTexture({
|
|
9993
10673
|
label: 'recon.previousDepth',
|
|
9994
10674
|
size: [renderWidth, renderHeight],
|
|
@@ -10107,222 +10787,184 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10107
10787
|
}
|
|
10108
10788
|
|
|
10109
10789
|
/**
|
|
10110
|
-
*
|
|
10790
|
+
* Whether a blended draw recorded now lands after the upscale. See `lateRouting.ts`.
|
|
10111
10791
|
*
|
|
10112
|
-
*
|
|
10113
|
-
* — which is the right thing for a scene to do, the alternative being to know what the renderer
|
|
10114
|
-
* is configured as — must cost nothing at all when nobody is going to read them.
|
|
10792
|
+
* Not during the order-independent replay, which owns its pass and issues into it directly.
|
|
10115
10793
|
*/
|
|
10116
|
-
private
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
const grown = new Float32Array(Math.max(wanted, this.motionMatrices.length * 2, 64));
|
|
10127
|
-
grown.set(this.motionMatrices);
|
|
10128
|
-
this.motionMatrices = grown;
|
|
10129
|
-
}
|
|
10130
|
-
this.motionMatrices.set(model as ArrayLike<number> & Iterable<number>, at * MOTION_DRAW_FLOATS);
|
|
10131
|
-
this.motionMatrices.set(
|
|
10132
|
-
previousModel as ArrayLike<number> & Iterable<number>,
|
|
10133
|
-
at * MOTION_DRAW_FLOATS + 16,
|
|
10794
|
+
private drawsLate(): boolean {
|
|
10795
|
+
return (
|
|
10796
|
+
this.latePass !== null &&
|
|
10797
|
+
this.oitEncoder === null &&
|
|
10798
|
+
routesLate(
|
|
10799
|
+
this.reconstructing,
|
|
10800
|
+
this.reflectionPassActive,
|
|
10801
|
+
this.probePassActive,
|
|
10802
|
+
this.framePresented,
|
|
10803
|
+
)
|
|
10134
10804
|
);
|
|
10135
|
-
this.motionMeshes[at] = mesh;
|
|
10136
|
-
this.motionCount = at + 1;
|
|
10137
10805
|
}
|
|
10138
10806
|
|
|
10139
10807
|
/**
|
|
10140
|
-
* The
|
|
10141
|
-
*
|
|
10142
|
-
*
|
|
10143
|
-
* an attribute — so this binds buffer zero and nothing else, whatever else the mesh carries.
|
|
10808
|
+
* The command a blended draw records into: the late pool while reconstructing, the frame graph
|
|
10809
|
+
* otherwise. **Late draws record whatever `quality.frameGraph` says**, since the late replay is
|
|
10810
|
+
* the only moment they can land; everything else keeps the path it has.
|
|
10144
10811
|
*/
|
|
10145
|
-
private
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
this.
|
|
10149
|
-
label: 'recon.motion.layout',
|
|
10150
|
-
entries: [
|
|
10151
|
-
{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' as const } },
|
|
10152
|
-
{
|
|
10153
|
-
binding: 1,
|
|
10154
|
-
visibility: 0x1,
|
|
10155
|
-
buffer: { type: 'uniform' as const, hasDynamicOffset: true },
|
|
10156
|
-
},
|
|
10157
|
-
],
|
|
10158
|
-
});
|
|
10159
|
-
this.motionModule = device.createShaderModule({
|
|
10160
|
-
label: 'recon.motion',
|
|
10161
|
-
code: RECON_MOTION_WGSL,
|
|
10162
|
-
});
|
|
10163
|
-
this.motionPipelineLayout = device.createPipelineLayout({
|
|
10164
|
-
bindGroupLayouts: [this.motionLayout],
|
|
10165
|
-
});
|
|
10812
|
+
private recordBlended(reads: number): DrawCommand | null {
|
|
10813
|
+
const late = this.latePass;
|
|
10814
|
+
if (late !== null && this.drawsLate()) return late.take();
|
|
10815
|
+
return this.recordDraw(reads, this.currentTarget());
|
|
10166
10816
|
}
|
|
10167
10817
|
|
|
10168
|
-
/** The
|
|
10169
|
-
private
|
|
10170
|
-
|
|
10171
|
-
if (held !== undefined) return held;
|
|
10172
|
-
const module = this.motionModule;
|
|
10173
|
-
const layout = this.motionPipelineLayout;
|
|
10174
|
-
if (module === null || layout === null) return null;
|
|
10175
|
-
const { device } = this.surface;
|
|
10176
|
-
const pipeline = device.createRenderPipeline({
|
|
10177
|
-
label: `recon.motion.${String(stride)}`,
|
|
10178
|
-
layout,
|
|
10179
|
-
vertex: {
|
|
10180
|
-
module,
|
|
10181
|
-
entryPoint: 'motionVert',
|
|
10182
|
-
/* Positions are the layout's first attribute and are never optional, so they are at offset
|
|
10183
|
-
zero of every stride this can be handed. */
|
|
10184
|
-
buffers: [
|
|
10185
|
-
{
|
|
10186
|
-
arrayStride: stride,
|
|
10187
|
-
attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }],
|
|
10188
|
-
},
|
|
10189
|
-
],
|
|
10190
|
-
},
|
|
10191
|
-
fragment: { module, entryPoint: 'motionFrag', targets: [{ format: MOTION_TARGET_FORMAT }] },
|
|
10192
|
-
/*
|
|
10193
|
-
* **The scene's own winding**, because this draws the scene's geometry through the scene's
|
|
10194
|
-
* clip transform — the product the renderer hands over already carries the negation that
|
|
10195
|
-
* `naga` writes into every generated vertex stage, so the faces come out the way they did
|
|
10196
|
-
* and a `cw` front face here would draw the inside of every mesh. §3 row 57.
|
|
10197
|
-
*/
|
|
10198
|
-
primitive: { topology: 'triangle-list', cullMode: 'back' },
|
|
10199
|
-
depthStencil: {
|
|
10200
|
-
format: DEPTH_FORMAT,
|
|
10201
|
-
/*
|
|
10202
|
-
* **Tested and not written.** The frame's depth is finished; this pass only wants the
|
|
10203
|
-
* pixels the scene actually kept, and `DEPTH_COMPARE_EQUAL` is the comparison that admits
|
|
10204
|
-
* exactly them — the same transform on the same positions gives the same depth, which is
|
|
10205
|
-
* why the rasterisation matrix is handed over whole rather than rebuilt here.
|
|
10206
|
-
*
|
|
10207
|
-
* **And the equality is not fragile here**, which was worth measuring rather than
|
|
10208
|
-
* assuming: this driver contracts multiply-adds (§3 row 107) and two shader modules can
|
|
10209
|
-
* contract a matrix product differently, so an equality across them could have admitted
|
|
10210
|
-
* only some of a surface. Run with `greater-equal` instead, the ghost measured the same
|
|
10211
|
-
* to the pixel — 5,942 either way — so nothing is being dropped.
|
|
10212
|
-
*/
|
|
10213
|
-
depthWriteEnabled: false,
|
|
10214
|
-
depthCompare: DEPTH_COMPARE_EQUAL,
|
|
10215
|
-
},
|
|
10216
|
-
});
|
|
10217
|
-
this.motionPipelines.set(stride, pipeline);
|
|
10218
|
-
return pipeline;
|
|
10818
|
+
/** The pipelines a blended draw is built for: the reconstructed picture's while late. */
|
|
10819
|
+
private blendedPipelines(): PipelineCache {
|
|
10820
|
+
return this.drawsLate() ? this.latePipelines : this.targetPipelines();
|
|
10219
10821
|
}
|
|
10220
10822
|
|
|
10221
10823
|
/**
|
|
10222
|
-
*
|
|
10223
|
-
*
|
|
10224
|
-
*
|
|
10225
|
-
* finished for the equality test to mean anything, and the resolve is what consumes the result.
|
|
10824
|
+
* The blended draws of a reconstructing frame, over the reconstructed picture at the output size
|
|
10825
|
+
* and unjittered, against the render's depth taken up to that size. Before occlusion, bloom and
|
|
10826
|
+
* the grade, so they are graded exactly as they were when they were drawn into the scene.
|
|
10226
10827
|
*/
|
|
10227
|
-
private
|
|
10228
|
-
const
|
|
10828
|
+
private runLate(encoder: GPUCommandEncoder): void {
|
|
10829
|
+
const late = this.latePass;
|
|
10830
|
+
const shown = this.reconShownView;
|
|
10229
10831
|
const depth = this.depthView;
|
|
10230
|
-
if (!this.reconstructing ||
|
|
10231
|
-
const
|
|
10232
|
-
|
|
10233
|
-
const
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10832
|
+
if (!this.reconstructing || late === null || shown === null || depth === null) return;
|
|
10833
|
+
const oitSet = this.oitActive && this.translucentQueue.length > 0;
|
|
10834
|
+
if (late.pending === 0 && !oitSet) return;
|
|
10835
|
+
const width = Math.max(1, this.surface.canvas.width);
|
|
10836
|
+
const height = Math.max(1, this.surface.canvas.height);
|
|
10837
|
+
/* The order-independent set tests against this depth too, so it is made for either. */
|
|
10838
|
+
late.upscale(encoder, depth, this.renderWidth, this.renderHeight, width, height);
|
|
10839
|
+
if (this.lateRefractWanted && this.refractSnapshot !== null && this.reconShown !== null) {
|
|
10840
|
+
encoder.copyTextureToTexture(
|
|
10841
|
+
{ texture: this.reconShown },
|
|
10842
|
+
{ texture: this.refractSnapshot },
|
|
10843
|
+
[this.refractSnapshot.width, this.refractSnapshot.height],
|
|
10844
|
+
);
|
|
10845
|
+
this.refractMips?.record(encoder);
|
|
10846
|
+
this.refractSnapshotTaken = true;
|
|
10847
|
+
}
|
|
10848
|
+
this.lateRefractWanted = false;
|
|
10849
|
+
if (late.pending === 0) return;
|
|
10242
10850
|
const pass = encoder.beginRenderPass({
|
|
10243
|
-
label: 'recon.
|
|
10851
|
+
label: 'recon.late',
|
|
10244
10852
|
timestampWrites: this.gpuTimer.writesFor(),
|
|
10245
|
-
colorAttachments: [
|
|
10246
|
-
{ view: target, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
10247
|
-
],
|
|
10853
|
+
colorAttachments: [{ view: shown, loadOp: 'load', storeOp: 'store' }],
|
|
10248
10854
|
depthStencilAttachment: {
|
|
10249
|
-
view:
|
|
10855
|
+
view: late.depthView(width, height),
|
|
10250
10856
|
depthLoadOp: 'load',
|
|
10857
|
+
/* Kept: the order-independent set replays against it after this pass. */
|
|
10251
10858
|
depthStoreOp: 'store',
|
|
10252
|
-
depthReadOnly: false,
|
|
10253
10859
|
},
|
|
10254
10860
|
});
|
|
10861
|
+
late.replay(pass, this.issueBound);
|
|
10862
|
+
pass.end();
|
|
10863
|
+
}
|
|
10255
10864
|
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
usage: 0x40 | 0x8, // UNIFORM | COPY_DST
|
|
10266
|
-
});
|
|
10267
|
-
this.motionDrawStaging = new Uint8Array(this.motionDrawCapacity * MOTION_DRAW_STRIDE);
|
|
10268
|
-
this.motionGroup = null;
|
|
10269
|
-
}
|
|
10270
|
-
this.motionFrameBuffer ??= device.createBuffer({
|
|
10271
|
-
label: 'recon.motion.frame',
|
|
10272
|
-
size: MOTION_FRAME_FLOATS * 4,
|
|
10273
|
-
usage: 0x40 | 0x8,
|
|
10274
|
-
});
|
|
10275
|
-
this.motionGroup ??= device.createBindGroup({
|
|
10276
|
-
label: 'recon.motion.group',
|
|
10277
|
-
layout,
|
|
10278
|
-
entries: [
|
|
10279
|
-
{ binding: 0, resource: { buffer: this.motionFrameBuffer } },
|
|
10280
|
-
{
|
|
10281
|
-
binding: 1,
|
|
10282
|
-
resource: { buffer: this.motionDrawBuffer as GPUBuffer, size: MOTION_DRAW_STRIDE },
|
|
10283
|
-
},
|
|
10284
|
-
],
|
|
10285
|
-
});
|
|
10286
|
-
|
|
10287
|
-
/* The scene's clip transform, with the generated stages' y negation folded in. */
|
|
10288
|
-
mat4.multiply(this.motionRaster, MOTION_CLIP_FLIP, this.viewProj ?? this.correctedViewProj);
|
|
10289
|
-
this.motionFrameStaging.set(this.motionRaster as Float32Array, 0);
|
|
10290
|
-
this.motionFrameStaging.set(this.frameRawViewProj as Float32Array, 16);
|
|
10291
|
-
this.motionFrameStaging.set(this.reconPreviousRawViewProj as Float32Array, 32);
|
|
10292
|
-
device.queue.writeBuffer(this.motionFrameBuffer, 0, this.motionFrameStaging);
|
|
10293
|
-
|
|
10294
|
-
const draws = new Float32Array(this.motionDrawStaging.buffer);
|
|
10295
|
-
for (let i = 0; i < count; i += 1) {
|
|
10296
|
-
draws.set(
|
|
10297
|
-
this.motionMatrices.subarray(i * MOTION_DRAW_FLOATS, (i + 1) * MOTION_DRAW_FLOATS),
|
|
10298
|
-
(i * MOTION_DRAW_STRIDE) / 4,
|
|
10299
|
-
);
|
|
10300
|
-
}
|
|
10301
|
-
device.queue.writeBuffer(
|
|
10302
|
-
this.motionDrawBuffer as GPUBuffer,
|
|
10303
|
-
0,
|
|
10304
|
-
this.motionDrawStaging,
|
|
10305
|
-
0,
|
|
10306
|
-
bytes,
|
|
10307
|
-
);
|
|
10865
|
+
/** Whether this draw lands in the reconstructed picture. See `lateRouting.ts`. */
|
|
10866
|
+
private tracksMotionNow(): boolean {
|
|
10867
|
+
return tracksMotion(
|
|
10868
|
+
this.reconstructing,
|
|
10869
|
+
this.reflectionPassActive,
|
|
10870
|
+
this.probePassActive,
|
|
10871
|
+
this.framePresented,
|
|
10872
|
+
);
|
|
10873
|
+
}
|
|
10308
10874
|
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10875
|
+
/**
|
|
10876
|
+
* What this draw's motion is, recorded for the motion pass.
|
|
10877
|
+
*
|
|
10878
|
+
* A mesh rewritten this frame carries its vertex motion whatever else the draw says — under last
|
|
10879
|
+
* frame's model where a mover or a matrix knows it, under this frame's where nothing does. A
|
|
10880
|
+
* mover otherwise carries a rigid draw's last model or a skinned draw's last pose; a matrix
|
|
10881
|
+
* carries a rigid draw's. A draw that says nothing and moved no vertex takes the camera's motion.
|
|
10882
|
+
*/
|
|
10883
|
+
private recordDrawMotion(
|
|
10884
|
+
mesh: GpuMesh,
|
|
10885
|
+
model: ArrayLike<number>,
|
|
10886
|
+
previous: ArrayLike<number> | Mover | null,
|
|
10887
|
+
): void {
|
|
10888
|
+
const dynamic = mesh.motion;
|
|
10889
|
+
const rows = dynamic?.previous ?? null;
|
|
10890
|
+
const rewritten = rows !== null && dynamic !== null && dynamic.changed === this.moverFrame;
|
|
10891
|
+
if (previous === null) {
|
|
10892
|
+
if (rewritten) this.motionPass?.recordDynamic(mesh, model, model, rows);
|
|
10893
|
+
return;
|
|
10324
10894
|
}
|
|
10325
|
-
|
|
10895
|
+
if (!isMover(previous)) {
|
|
10896
|
+
if (rewritten) this.motionPass?.recordDynamic(mesh, model, previous, rows);
|
|
10897
|
+
else this.motionPass?.recordRigid(mesh, model, previous);
|
|
10898
|
+
return;
|
|
10899
|
+
}
|
|
10900
|
+
/* A rig is only followed where the draw really skins: a palette set, a mesh with joints. */
|
|
10901
|
+
const skinned =
|
|
10902
|
+
!rewritten && this.skinPaletteSlot >= 0 && mesh.isSkinned && mesh.skinOffsets !== null;
|
|
10903
|
+
const verdict = advanceMover(
|
|
10904
|
+
previous,
|
|
10905
|
+
this.moverRenderer,
|
|
10906
|
+
this.moverFrame,
|
|
10907
|
+
model,
|
|
10908
|
+
skinned ? this.skinPaletteData : null,
|
|
10909
|
+
);
|
|
10910
|
+
if (verdict === MOVER_TWICE) {
|
|
10911
|
+
this.warnMoverTwice();
|
|
10912
|
+
return;
|
|
10913
|
+
}
|
|
10914
|
+
if (rewritten) {
|
|
10915
|
+
const was = verdict === MOVER_MOTION ? previous.previousModel : model;
|
|
10916
|
+
this.motionPass?.recordDynamic(mesh, model, was, rows);
|
|
10917
|
+
return;
|
|
10918
|
+
}
|
|
10919
|
+
if (verdict !== MOVER_MOTION) return;
|
|
10920
|
+
if (skinned) this.recordSkinnedMotion(mesh, model, previous);
|
|
10921
|
+
else this.motionPass?.recordRigid(mesh, model, previous.previousModel);
|
|
10922
|
+
}
|
|
10923
|
+
|
|
10924
|
+
/**
|
|
10925
|
+
* A skinned mover's motion: this frame's palette is the draw's own slot, and last frame's takes a
|
|
10926
|
+
* slot of its own, like any palette a draw binds — so it is uploaded once and nothing overwrites
|
|
10927
|
+
* it before the motion pass reads it. See `SkinPaletteRing`.
|
|
10928
|
+
*/
|
|
10929
|
+
private recordSkinnedMotion(mesh: GpuMesh, model: ArrayLike<number>, mover: Mover): void {
|
|
10930
|
+
const now = this.skinPalettes.view(this.skinPaletteSlot);
|
|
10931
|
+
const slot = this.skinPalettes.take(this.surface.device, mover.previousPalette);
|
|
10932
|
+
const was = slot === null ? null : this.skinPalettes.view(slot);
|
|
10933
|
+
if (now === null || was === null) return;
|
|
10934
|
+
this.motionPass?.recordSkinned(mesh, model, mover.previousModel, now, was);
|
|
10935
|
+
}
|
|
10936
|
+
|
|
10937
|
+
private warnMoverTwice(): void {
|
|
10938
|
+
if (this.warnedMoverTwice) return;
|
|
10939
|
+
this.warnedMoverTwice = true;
|
|
10940
|
+
console.warn(
|
|
10941
|
+
'WebGPU: a Mover was drawn twice in one frame. A mover is one object; the second draw ' +
|
|
10942
|
+
'records no motion. Give each moving object its own `createMover()`.',
|
|
10943
|
+
);
|
|
10944
|
+
}
|
|
10945
|
+
|
|
10946
|
+
/**
|
|
10947
|
+
* Where every mover was last frame, into the motion target. See `MotionPass`.
|
|
10948
|
+
*
|
|
10949
|
+
* **After everything that draws and before the resolve reads it.** The frame's depth has to be
|
|
10950
|
+
* finished for the pass's equality test to mean anything, and the resolve is what consumes it.
|
|
10951
|
+
*/
|
|
10952
|
+
private runMotion(encoder: GPUCommandEncoder): void {
|
|
10953
|
+
const target = this.reconMotionView;
|
|
10954
|
+
const depth = this.depthView;
|
|
10955
|
+
const motion = this.motionPass;
|
|
10956
|
+
if (!this.reconstructing || target === null || depth === null || motion === null) return;
|
|
10957
|
+
/* The scene's clip transform, with the generated stages' y negation folded in. */
|
|
10958
|
+
mat4.multiply(this.motionRaster, MOTION_CLIP_FLIP, this.viewProj ?? this.correctedViewProj);
|
|
10959
|
+
motion.run(
|
|
10960
|
+
encoder,
|
|
10961
|
+
target,
|
|
10962
|
+
depth,
|
|
10963
|
+
this.motionRaster as Float32Array,
|
|
10964
|
+
this.frameRawViewProj as Float32Array,
|
|
10965
|
+
this.reconPreviousRawViewProj as Float32Array,
|
|
10966
|
+
this.gpuTimer.writesFor(),
|
|
10967
|
+
);
|
|
10326
10968
|
}
|
|
10327
10969
|
|
|
10328
10970
|
/**
|
|
@@ -10468,8 +11110,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10468
11110
|
if (!this.temporalJittering) return;
|
|
10469
11111
|
const write = this.taaWrite;
|
|
10470
11112
|
const target = this.taaViews[write];
|
|
11113
|
+
const record = this.taaRecordViews[write];
|
|
11114
|
+
const motion = this.taaMotionViews[write];
|
|
10471
11115
|
const group = this.taaBindGroups[1 - write];
|
|
10472
|
-
if (target === null || group === null) return;
|
|
11116
|
+
if (target === null || record === null || motion === null || group === null) return;
|
|
10473
11117
|
const { device } = this.surface;
|
|
10474
11118
|
|
|
10475
11119
|
const f = this.taaFloats;
|
|
@@ -10478,6 +11122,22 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10478
11122
|
f[at('uTexel')] = 1 / this.renderWidth;
|
|
10479
11123
|
f[at('uTexel') + 1] = 1 / this.renderHeight;
|
|
10480
11124
|
f[at('uHistoryBlend')] = this.temporalHistoryUsable ? TEMPORAL_HISTORY_BLEND : 0;
|
|
11125
|
+
f[at('uPeriodStart')] = this.temporalHistory.periodStart ? 1 : 0;
|
|
11126
|
+
/* The anti-flicker's same-surface test reads depths in metres, as the occlusion does. */
|
|
11127
|
+
const depthRow = at('uDepthToViewZ');
|
|
11128
|
+
if (this.frameProjection !== null) {
|
|
11129
|
+
mat4.invert(this.taaInvProjection, this.frameProjection);
|
|
11130
|
+
f[depthRow] = this.taaInvProjection[10] ?? 0;
|
|
11131
|
+
f[depthRow + 1] = this.taaInvProjection[14] ?? 0;
|
|
11132
|
+
f[depthRow + 2] = this.taaInvProjection[11] ?? 0;
|
|
11133
|
+
f[depthRow + 3] = this.taaInvProjection[15] ?? 1;
|
|
11134
|
+
} else {
|
|
11135
|
+
f[depthRow] = 0;
|
|
11136
|
+
f[depthRow + 1] = 0;
|
|
11137
|
+
f[depthRow + 2] = 0;
|
|
11138
|
+
f[depthRow + 3] = 1;
|
|
11139
|
+
}
|
|
11140
|
+
f[at('uAntiFlicker')] = 1;
|
|
10481
11141
|
device.queue.writeBuffer(this.taaUniforms, 0, this.taaStaging);
|
|
10482
11142
|
|
|
10483
11143
|
const pass = encoder.beginRenderPass({
|
|
@@ -10485,16 +11145,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10485
11145
|
timestampWrites: this.gpuTimer.writesFor(),
|
|
10486
11146
|
colorAttachments: [
|
|
10487
11147
|
{ view: target, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 1] },
|
|
11148
|
+
{ view: record, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
11149
|
+
{ view: motion, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
10488
11150
|
],
|
|
10489
11151
|
});
|
|
10490
11152
|
pass.setPipeline(
|
|
10491
|
-
|
|
11153
|
+
postPipelineTargets(
|
|
10492
11154
|
this.pipelines,
|
|
10493
11155
|
device,
|
|
10494
11156
|
this.taaLayout,
|
|
10495
11157
|
'post.taa',
|
|
10496
11158
|
TEMPORAL_RESOLVE_FRAG_WGSL,
|
|
10497
|
-
|
|
11159
|
+
this.taaTargets,
|
|
10498
11160
|
),
|
|
10499
11161
|
);
|
|
10500
11162
|
pass.setBindGroup(0, group, [0]);
|
|
@@ -10748,9 +11410,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10748
11410
|
if (!this.oitActive || this.translucentQueue.length === 0) return;
|
|
10749
11411
|
const accum = this.oitAccumView;
|
|
10750
11412
|
const reveal = this.oitRevealView;
|
|
10751
|
-
|
|
11413
|
+
/* After the upscale while reconstructing: over the reconstructed picture, against its depth. */
|
|
11414
|
+
const scene = this.reconstructing ? this.reconShownView : this.sceneColorView;
|
|
10752
11415
|
const group = this.oitResolveGroup;
|
|
10753
|
-
const depth = this.
|
|
11416
|
+
const depth = this.reconstructing
|
|
11417
|
+
? (this.latePass?.depthView(
|
|
11418
|
+
Math.max(1, this.surface.canvas.width),
|
|
11419
|
+
Math.max(1, this.surface.canvas.height),
|
|
11420
|
+
) ?? null)
|
|
11421
|
+
: this.depthView;
|
|
10754
11422
|
if (accum === null || reveal === null || scene === null || group === null || depth === null) {
|
|
10755
11423
|
return;
|
|
10756
11424
|
}
|
|
@@ -10764,12 +11432,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10764
11432
|
*/
|
|
10765
11433
|
if (this.translucentQueue.refracts && !this.refractSnapshotTaken) {
|
|
10766
11434
|
const snapshot = this.refractSnapshot;
|
|
10767
|
-
const source = this.sceneColor;
|
|
11435
|
+
const source = this.reconstructing ? this.reconShown : this.sceneColor;
|
|
10768
11436
|
if (snapshot !== null && source !== null) {
|
|
10769
11437
|
encoder.copyTextureToTexture({ texture: source }, { texture: snapshot }, [
|
|
10770
11438
|
snapshot.width,
|
|
10771
11439
|
snapshot.height,
|
|
10772
11440
|
]);
|
|
11441
|
+
this.refractMips?.record(encoder);
|
|
10773
11442
|
this.refractSnapshotTaken = true;
|
|
10774
11443
|
}
|
|
10775
11444
|
}
|
|
@@ -10810,6 +11479,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10810
11479
|
options.refraction = draw.refraction;
|
|
10811
11480
|
options.refractTint = draw.refractTint as unknown as Vec3 | undefined;
|
|
10812
11481
|
options.thicknessM = draw.thicknessM;
|
|
11482
|
+
options.glass = draw.glass ?? undefined;
|
|
10813
11483
|
this.drawTranslucentMesh(draw.mesh as GpuMesh, draw.model, draw.opacity, options);
|
|
10814
11484
|
});
|
|
10815
11485
|
this.oitReplaying = false;
|
|
@@ -10840,9 +11510,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10840
11510
|
this.pipelines,
|
|
10841
11511
|
device,
|
|
10842
11512
|
this.oitResolveLayout,
|
|
10843
|
-
'post.oitResolve',
|
|
11513
|
+
this.reconstructing ? 'post.oitResolve.recon' : 'post.oitResolve',
|
|
10844
11514
|
OIT_RESOLVE_FRAG_WGSL,
|
|
10845
|
-
sceneColorFormat(this.quality.hdrScene),
|
|
11515
|
+
this.reconstructing ? RECON_HISTORY_FORMAT : sceneColorFormat(this.quality.hdrScene),
|
|
10846
11516
|
OIT_COMPOSITE_BLEND,
|
|
10847
11517
|
),
|
|
10848
11518
|
);
|
|
@@ -11232,18 +11902,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11232
11902
|
this.lightVolumeVertices.buffer,
|
|
11233
11903
|
this.lightVolumeFragments.buffer,
|
|
11234
11904
|
(name) =>
|
|
11235
|
-
name === '
|
|
11236
|
-
? { view: this.
|
|
11237
|
-
:
|
|
11238
|
-
? { view: this.peelView, sampler: this.volumeShadowSampler }
|
|
11239
|
-
: name === 'uDynamicShadowMap' && this.dynamicView !== null
|
|
11240
|
-
? { view: this.dynamicView, sampler: this.volumeShadowSampler }
|
|
11241
|
-
: /* The frame's own depth, snapshotted before the first volume of the frame draws.
|
|
11905
|
+
name === 'uSunShadows'
|
|
11906
|
+
? { view: this.sunArrayView, sampler: this.volumeShadowSampler }
|
|
11907
|
+
: /* The frame's own depth, snapshotted before the first volume of the frame draws.
|
|
11242
11908
|
Null under a profile with no composite, and then `uSceneDepthEnabled` is 0 and
|
|
11243
11909
|
the stand-in is never sampled. */
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11910
|
+
name === 'uSceneDepth' && this.resolvedDepthView !== null
|
|
11911
|
+
? { view: this.resolvedDepthView, sampler: this.postDepthSampler }
|
|
11912
|
+
: this.volumeBlank,
|
|
11247
11913
|
);
|
|
11248
11914
|
}
|
|
11249
11915
|
|
|
@@ -11269,6 +11935,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11269
11935
|
private takeRefractSnapshot(): boolean {
|
|
11270
11936
|
if (this.reflectionPassActive || this.probePassActive) return false;
|
|
11271
11937
|
if (this.refractSnapshotTaken) return true;
|
|
11938
|
+
/* A late draw refracts the reconstructed picture, which does not exist until the resolve has
|
|
11939
|
+
run; `runLate` takes the copy then, once, before any late draw is replayed. */
|
|
11940
|
+
if (this.drawsLate()) {
|
|
11941
|
+
this.lateRefractWanted = true;
|
|
11942
|
+
return true;
|
|
11943
|
+
}
|
|
11272
11944
|
/* The replay of an order-independent set runs after the scene's pass has ended, and `runOit`
|
|
11273
11945
|
took the copy for it before it began; there is no pass here to end and reopen. */
|
|
11274
11946
|
if (this.oitReplaying) return false;
|
|
@@ -11307,6 +11979,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11307
11979
|
snapshot.width,
|
|
11308
11980
|
snapshot.height,
|
|
11309
11981
|
]);
|
|
11982
|
+
/* The chain, between the passes, where nothing is attached: what frosted glass reads. */
|
|
11983
|
+
this.refractMips?.record(encoder);
|
|
11310
11984
|
const multisampled = this.compositeMsaa() !== null;
|
|
11311
11985
|
this.pass = encoder.beginRenderPass({
|
|
11312
11986
|
label: 'refract.snapshotTaken',
|
|
@@ -11668,8 +12342,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11668
12342
|
}
|
|
11669
12343
|
if (verb !== VERB_DRAW) return;
|
|
11670
12344
|
const command = this.commands.commands[nodeState(this.arena, at)];
|
|
11671
|
-
if (command === undefined
|
|
12345
|
+
if (command === undefined) return;
|
|
12346
|
+
this.issueCommand(pass, command);
|
|
12347
|
+
}
|
|
11672
12348
|
|
|
12349
|
+
/** One recorded draw, issued into an open pass. Shared by the graph's replay and `runLate`. */
|
|
12350
|
+
private issueCommand(pass: GPURenderPassEncoder, command: DrawCommand): void {
|
|
12351
|
+
if (command.pipeline === null || command.bindGroup === null) return;
|
|
11673
12352
|
pass.setPipeline(command.pipeline);
|
|
11674
12353
|
if (command.offsetCount === 2) {
|
|
11675
12354
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB]);
|
|
@@ -11913,6 +12592,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11913
12592
|
}
|
|
11914
12593
|
|
|
11915
12594
|
endFrame(): void {
|
|
12595
|
+
this.inFrame = false;
|
|
11916
12596
|
if (this.surface.lost || this.encoder === null) return;
|
|
11917
12597
|
/*
|
|
11918
12598
|
* Anything still recorded goes now.
|
|
@@ -13179,6 +13859,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13179
13859
|
*
|
|
13180
13860
|
* What it gives up is one frame of history: the frame after a cut has no blur and no temporal
|
|
13181
13861
|
* blend, as the first frame of a session has none. Nothing else is reset.
|
|
13862
|
+
*
|
|
13863
|
+
* **A reconstructing renderer depends on it**: its history is the last shot's, and without this
|
|
13864
|
+
* call a cut blends that shot into the first frames of the next. Nothing detects a cut on the
|
|
13865
|
+
* caller's behalf, deliberately — a fast pan and a cut look alike from here.
|
|
13182
13866
|
*/
|
|
13183
13867
|
cameraCut(): void {
|
|
13184
13868
|
this.hasPreviousView = false;
|
|
@@ -13388,9 +14072,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13388
14072
|
this.causticsVerts.writeBlock(vertexSlot, this.causticsVertBlock);
|
|
13389
14073
|
this.causticsFrags.writeBlock(fragmentSlot, this.causticsFragInts);
|
|
13390
14074
|
|
|
13391
|
-
const causticsPipe = causticsPipeline(
|
|
14075
|
+
const causticsPipe = causticsPipeline(
|
|
14076
|
+
this.blendedPipelines(),
|
|
14077
|
+
this.surface.device,
|
|
14078
|
+
this.causticsLayout,
|
|
14079
|
+
);
|
|
13392
14080
|
/* The pattern is generated in the shader from the two uniform blocks; nothing is sampled. */
|
|
13393
|
-
const causticsCommand = this.
|
|
14081
|
+
const causticsCommand = this.recordBlended(0);
|
|
13394
14082
|
if (causticsCommand !== null) {
|
|
13395
14083
|
causticsCommand.pipeline = causticsPipe;
|
|
13396
14084
|
causticsCommand.bindGroup = this.causticsBindGroup;
|
|
@@ -14385,6 +15073,33 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14385
15073
|
* The occlusion and bloom targets are half size. Both are blurs read back at full size, so a
|
|
14386
15074
|
* full-resolution intermediate buys nothing a bilinear fetch does not already give.
|
|
14387
15075
|
*/
|
|
15076
|
+
/**
|
|
15077
|
+
* The medium's march group, holding the shadow views it reads — built with the composite, and
|
|
15078
|
+
* again when the sun's texture grows its glass layers and every view of the old one is stale.
|
|
15079
|
+
*
|
|
15080
|
+
* **The stand-in has to read as unoccluded and not as absent**, which is `emptyTexture.ts`'s
|
|
15081
|
+
* point and `createLightVolumeBindGroup`'s: `occlusion()` calls any stored depth below the
|
|
15082
|
+
* receiver's a blocker — so a black stand-in would put the whole medium in shadow, which is a
|
|
15083
|
+
* picture rather than an error. `volumeBlank` is the white one-texel texture built for this.
|
|
15084
|
+
*/
|
|
15085
|
+
private buildMediumBindGroup(): GPUBindGroup | null {
|
|
15086
|
+
if (this.resolvedDepthView === null) return null;
|
|
15087
|
+
return this.surface.device.createBindGroup({
|
|
15088
|
+
label: 'post.mediumBindGroup',
|
|
15089
|
+
layout: this.mediumLayout,
|
|
15090
|
+
entries: [
|
|
15091
|
+
{
|
|
15092
|
+
binding: MEDIUM_UNIFORMS,
|
|
15093
|
+
resource: { buffer: this.mediumUniforms, size: MEDIUM_FRAG_SIZE },
|
|
15094
|
+
},
|
|
15095
|
+
{ binding: MEDIUM_TEXTURES.uDepth.texture, resource: this.resolvedDepthView },
|
|
15096
|
+
{ binding: MEDIUM_TEXTURES.uDepth.sampler, resource: this.postDepthSampler },
|
|
15097
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows.texture, resource: this.sunArrayView },
|
|
15098
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows.sampler, resource: this.volumeShadowSampler },
|
|
15099
|
+
],
|
|
15100
|
+
});
|
|
15101
|
+
}
|
|
15102
|
+
|
|
14388
15103
|
private ensureComposite(width: number, height: number): void {
|
|
14389
15104
|
if (!this.hasComposite) return;
|
|
14390
15105
|
if (this.sceneColor !== null && this.frameWidth === width && this.frameHeight === height)
|
|
@@ -14438,13 +15153,39 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14438
15153
|
* the allocation the house rules are about. A frame that never refracts pays the memory and
|
|
14439
15154
|
* takes no copy.
|
|
14440
15155
|
*/
|
|
15156
|
+
/*
|
|
15157
|
+
* **At the output size and the reconstructed picture's format while reconstructing**, because
|
|
15158
|
+
* every refracting draw is then blended, drawn after the upscale, and reads the reconstructed
|
|
15159
|
+
* picture rather than the render. See `runLate`.
|
|
15160
|
+
*/
|
|
15161
|
+
const reconstructed = this.reconstructionWanted;
|
|
14441
15162
|
this.refractSnapshot = device.createTexture({
|
|
14442
15163
|
label: 'refract.snapshot',
|
|
14443
|
-
size:
|
|
14444
|
-
|
|
14445
|
-
|
|
15164
|
+
size: reconstructed
|
|
15165
|
+
? [Math.max(1, this.surface.canvas.width), Math.max(1, this.surface.canvas.height)]
|
|
15166
|
+
: [width, height],
|
|
15167
|
+
format: reconstructed ? RECON_HISTORY_FORMAT : format,
|
|
15168
|
+
/*
|
|
15169
|
+
* **Every level, so glass can read a blurrier one**, and a render attachment so `SnapshotMips`
|
|
15170
|
+
* can fill them. Plain refraction reads level 0, as it always has. See `glass.ts`.
|
|
15171
|
+
*/
|
|
15172
|
+
mipLevelCount:
|
|
15173
|
+
Math.floor(
|
|
15174
|
+
Math.log2(
|
|
15175
|
+
reconstructed
|
|
15176
|
+
? Math.max(1, this.surface.canvas.width, this.surface.canvas.height)
|
|
15177
|
+
: Math.max(1, width, height),
|
|
15178
|
+
),
|
|
15179
|
+
) + 1,
|
|
15180
|
+
usage: 0x4 | 0x2 | 0x10, // TEXTURE_BINDING | COPY_DST | RENDER_ATTACHMENT
|
|
14446
15181
|
});
|
|
14447
15182
|
this.refractSnapshotView = this.refractSnapshot.createView();
|
|
15183
|
+
this.refractMips = new SnapshotMips(
|
|
15184
|
+
device,
|
|
15185
|
+
this.pipelines,
|
|
15186
|
+
this.refractSnapshot,
|
|
15187
|
+
reconstructed ? RECON_HISTORY_FORMAT : format,
|
|
15188
|
+
);
|
|
14448
15189
|
/*
|
|
14449
15190
|
* **Every cached flat bind group is stale the moment this view changes**, and the failure is
|
|
14450
15191
|
* silent: `flatTextures` resolves `uRefractScene` to the one-texel white stand-in when there is
|
|
@@ -14551,7 +15292,28 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14551
15292
|
});
|
|
14552
15293
|
this.taaTextures[i] = texture;
|
|
14553
15294
|
this.taaViews[i] = texture.createView();
|
|
15295
|
+
/* The records in half float, whatever the picture's format: one of them holds metres. */
|
|
15296
|
+
this.taaRecords[i]?.destroy();
|
|
15297
|
+
const record = device.createTexture({
|
|
15298
|
+
label: `post.taaRecord${i}`,
|
|
15299
|
+
size: [width, height],
|
|
15300
|
+
format: 'rgba16float',
|
|
15301
|
+
usage: USAGE,
|
|
15302
|
+
});
|
|
15303
|
+
this.taaRecords[i] = record;
|
|
15304
|
+
this.taaRecordViews[i] = record.createView();
|
|
15305
|
+
this.taaMotions[i]?.destroy();
|
|
15306
|
+
/* Two numbers a pixel: the depth its sample stood at, in metres, and how far that swings. */
|
|
15307
|
+
const motion = device.createTexture({
|
|
15308
|
+
label: `post.taaMotion${i}`,
|
|
15309
|
+
size: [width, height],
|
|
15310
|
+
format: 'rg16float',
|
|
15311
|
+
usage: USAGE,
|
|
15312
|
+
});
|
|
15313
|
+
this.taaMotions[i] = motion;
|
|
15314
|
+
this.taaMotionViews[i] = motion.createView();
|
|
14554
15315
|
}
|
|
15316
|
+
this.taaTargets = [{ format }, { format: 'rgba16float' }, { format: 'rg16float' }];
|
|
14555
15317
|
this.taaWrite = 0;
|
|
14556
15318
|
/* A resized target is a new texture, so whatever the history held is gone. */
|
|
14557
15319
|
this.temporalHistory.invalidate();
|
|
@@ -14565,10 +15327,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14565
15327
|
* that range — a step of 1/255 in how much scene shows through, below what a viewer can see
|
|
14566
15328
|
* against a blended surface.
|
|
14567
15329
|
*/
|
|
15330
|
+
/* At the output size while reconstructing, where the set is replayed after the upscale. */
|
|
15331
|
+
const oitSize: [number, number] = this.reconstructionWanted
|
|
15332
|
+
? [Math.max(1, this.surface.canvas.width), Math.max(1, this.surface.canvas.height)]
|
|
15333
|
+
: [width, height];
|
|
14568
15334
|
this.oitAccum?.destroy();
|
|
14569
15335
|
this.oitAccum = device.createTexture({
|
|
14570
15336
|
label: 'post.oitAccum',
|
|
14571
|
-
size:
|
|
15337
|
+
size: oitSize,
|
|
14572
15338
|
format: 'rgba16float',
|
|
14573
15339
|
usage: USAGE,
|
|
14574
15340
|
});
|
|
@@ -14576,7 +15342,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14576
15342
|
this.oitReveal?.destroy();
|
|
14577
15343
|
this.oitReveal = device.createTexture({
|
|
14578
15344
|
label: 'post.oitReveal',
|
|
14579
|
-
size:
|
|
15345
|
+
size: oitSize,
|
|
14580
15346
|
format: 'r8unorm',
|
|
14581
15347
|
usage: USAGE,
|
|
14582
15348
|
});
|
|
@@ -14681,42 +15447,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14681
15447
|
...entry(AO_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
14682
15448
|
],
|
|
14683
15449
|
});
|
|
14684
|
-
|
|
14685
|
-
* The medium's two groups, holding the shadow views the march reads.
|
|
14686
|
-
*
|
|
14687
|
-
* **The stand-in has to read as unoccluded and not as absent**, which is `emptyTexture.ts`'s
|
|
14688
|
-
* point and `createLightVolumeBindGroup`'s: the dynamic layer is sampled unconditionally, and
|
|
14689
|
-
* `occlusion()` calls any stored depth below the receiver's a blocker — so a black stand-in
|
|
14690
|
-
* would put the whole medium in shadow, which is a picture rather than an error. `volumeBlank`
|
|
14691
|
-
* is the white one-texel texture already built for exactly this.
|
|
14692
|
-
*/
|
|
14693
|
-
const shadowPair = (
|
|
14694
|
-
b: { texture: number; sampler: number },
|
|
14695
|
-
view: GPUTextureView | null,
|
|
14696
|
-
): GPUBindGroupEntry[] => [
|
|
14697
|
-
{ binding: b.texture, resource: view ?? this.volumeBlank.view },
|
|
14698
|
-
{
|
|
14699
|
-
binding: b.sampler,
|
|
14700
|
-
resource: view === null ? this.volumeBlank.sampler : this.volumeShadowSampler,
|
|
14701
|
-
},
|
|
14702
|
-
];
|
|
14703
|
-
this.mediumBindGroup =
|
|
14704
|
-
this.resolvedDepthView === null
|
|
14705
|
-
? null
|
|
14706
|
-
: device.createBindGroup({
|
|
14707
|
-
label: 'post.mediumBindGroup',
|
|
14708
|
-
layout: this.mediumLayout,
|
|
14709
|
-
entries: [
|
|
14710
|
-
{
|
|
14711
|
-
binding: MEDIUM_UNIFORMS,
|
|
14712
|
-
resource: { buffer: this.mediumUniforms, size: MEDIUM_FRAG_SIZE },
|
|
14713
|
-
},
|
|
14714
|
-
...entry(MEDIUM_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
14715
|
-
...shadowPair(MEDIUM_TEXTURES.uStaticShadowMap, this.shadowView),
|
|
14716
|
-
...shadowPair(MEDIUM_TEXTURES.uPeeledShadowMap, this.peelView),
|
|
14717
|
-
...shadowPair(MEDIUM_TEXTURES.uDynamicShadowMap, this.dynamicView),
|
|
14718
|
-
],
|
|
14719
|
-
});
|
|
15450
|
+
this.mediumBindGroup = this.buildMediumBindGroup();
|
|
14720
15451
|
this.mediumUpsampleGroup =
|
|
14721
15452
|
this.resolvedDepthView === null || this.mediumTargetView === null
|
|
14722
15453
|
? null
|
|
@@ -14747,7 +15478,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14747
15478
|
const scene = this.sceneColorView;
|
|
14748
15479
|
const history = this.taaViews[i];
|
|
14749
15480
|
const resolved = this.taaViews[1 - i];
|
|
14750
|
-
|
|
15481
|
+
const record = this.taaRecordViews[i];
|
|
15482
|
+
const motion = this.taaMotionViews[i];
|
|
15483
|
+
if (
|
|
15484
|
+
scene === null ||
|
|
15485
|
+
history === null ||
|
|
15486
|
+
resolved === null ||
|
|
15487
|
+
record === null ||
|
|
15488
|
+
motion === null
|
|
15489
|
+
) {
|
|
15490
|
+
continue;
|
|
15491
|
+
}
|
|
14751
15492
|
this.taaBindGroups[i] = device.createBindGroup({
|
|
14752
15493
|
label: `post.taaBindGroup${i}`,
|
|
14753
15494
|
layout: this.taaLayout,
|
|
@@ -14755,6 +15496,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14755
15496
|
{ binding: TAA_UNIFORMS, resource: { buffer: this.taaUniforms, size: TAA_FRAG_SIZE } },
|
|
14756
15497
|
...entry(TAA_TEXTURES.uScene, scene),
|
|
14757
15498
|
...entry(TAA_TEXTURES.uHistory, history),
|
|
15499
|
+
...entry(TAA_TEXTURES.uFlicker, record),
|
|
15500
|
+
...entry(TAA_TEXTURES.uFlickerMotion, motion),
|
|
14758
15501
|
...entry(TAA_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
14759
15502
|
],
|
|
14760
15503
|
});
|
|
@@ -14936,6 +15679,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14936
15679
|
* the 2026-08-13 rule asks for — one decision, bound twice.
|
|
14937
15680
|
*/
|
|
14938
15681
|
dispose(): void {
|
|
15682
|
+
this.motionPass?.dispose();
|
|
15683
|
+
this.motionPass = null;
|
|
15684
|
+
this.latePass?.dispose();
|
|
15685
|
+
this.latePass = null;
|
|
14939
15686
|
for (const texture of this.retiredTextures) texture.destroy();
|
|
14940
15687
|
this.retiredTextures.length = 0;
|
|
14941
15688
|
const passDevice = this.passDevice();
|
|
@@ -14952,6 +15699,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14952
15699
|
this.fieldComposer = null;
|
|
14953
15700
|
this.driftIndex?.destroy();
|
|
14954
15701
|
this.driftAtlas?.destroy();
|
|
15702
|
+
this.sunTint?.texture.destroy();
|
|
15703
|
+
this.sunTint = null;
|
|
15704
|
+
this.glassTint?.dispose();
|
|
15705
|
+
this.glassTint = null;
|
|
15706
|
+
this.retiredSunTexture?.destroy();
|
|
15707
|
+
this.retiredSunTexture = null;
|
|
14955
15708
|
|
|
14956
15709
|
this.pass = null;
|
|
14957
15710
|
this.encoder = null;
|