@driftengine/core 4.4.2 → 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 +46 -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 +79 -8
- package/dist/render/backend/webgl2/renderer.js +360 -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 +2 -12
- package/dist/render/backend/webgpu/renderer.d.ts +171 -54
- package/dist/render/backend/webgpu/renderer.js +944 -390
- 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/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/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/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/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 +7 -1
- package/dist/render/sceneTarget.js +58 -12
- 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/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/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/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 +401 -108
- 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/renderer.ts +1108 -421
- 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/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/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +58 -11
- 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/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/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- 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,
|
|
@@ -326,6 +331,16 @@ import {
|
|
|
326
331
|
} from '../../globalMedium.ts';
|
|
327
332
|
import type { GlobalMediumOptions } from '../../globalMedium.ts';
|
|
328
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';
|
|
329
344
|
import type { SceneCasterMaterial, ShadowCasterSink, ShadowCasters } from '../../shadowCasters.ts';
|
|
330
345
|
import { VERTEX_LAYOUT, createGpuMesh, createGpuMeshIncremental, type GpuMesh } from './buffers.ts';
|
|
331
346
|
import type { GpuSurface } from './device.ts';
|
|
@@ -1308,6 +1323,49 @@ function padded(direction: ArrayLike<number>): Float32Array {
|
|
|
1308
1323
|
return SCRATCH_VEC4;
|
|
1309
1324
|
}
|
|
1310
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
|
+
|
|
1311
1369
|
export class WebGPURenderer implements RendererApi {
|
|
1312
1370
|
/**
|
|
1313
1371
|
* Real timestamp queries, where the adapter offers them.
|
|
@@ -1417,6 +1475,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1417
1475
|
* caller who passed null gets — rather than reading a slot this frame has already re-let.
|
|
1418
1476
|
*/
|
|
1419
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;
|
|
1420
1480
|
|
|
1421
1481
|
private readonly surface: GpuSurface;
|
|
1422
1482
|
/** The encoder for the frame in flight, or null between frames. */
|
|
@@ -1426,6 +1486,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1426
1486
|
private pass: GPURenderPassEncoder | null = null;
|
|
1427
1487
|
|
|
1428
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;
|
|
1429
1510
|
/**
|
|
1430
1511
|
* The same pipelines again, built for the canvas, for anything drawn after `endFrame`.
|
|
1431
1512
|
*
|
|
@@ -1971,10 +2052,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1971
2052
|
* the *unsharpened* result — sharpening into the history would sharpen a sharpened picture every
|
|
1972
2053
|
* frame, which is the halo the reference's own clamp exists to prevent, compounded.
|
|
1973
2054
|
*
|
|
1974
|
-
* The motion target is render size and carries a drawn object's own motion where
|
|
1975
|
-
* one
|
|
1976
|
-
*
|
|
1977
|
-
* 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`.
|
|
1978
2059
|
*/
|
|
1979
2060
|
private reconHistories: (GPUTexture | null)[] = [null, null];
|
|
1980
2061
|
private reconHistoryViews: (GPUTextureView | null)[] = [null, null];
|
|
@@ -1996,34 +2077,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1996
2077
|
private reconGroups: (GPUBindGroup | null)[] = [null, null];
|
|
1997
2078
|
private reconSharpenGroups: (GPUBindGroup | null)[] = [null, null];
|
|
1998
2079
|
private reconRushGroup: GPUBindGroup | null = null;
|
|
1999
|
-
/**
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
private
|
|
2007
|
-
private
|
|
2008
|
-
private motionCount = 0;
|
|
2009
|
-
private motionLayout: GPUBindGroupLayout | null = null;
|
|
2010
|
-
/**
|
|
2011
|
-
* One pipeline per vertex stride, because a stride is a property of the *mesh*.
|
|
2012
|
-
*
|
|
2013
|
-
* Which optional attributes a mesh supplied decides how far apart its positions are, and this
|
|
2014
|
-
* pass reads nothing but positions — so two meshes drawn by the same shader still need two
|
|
2015
|
-
* vertex layouts. A map rather than a cache key on the pipeline cache: there is one shader here
|
|
2016
|
-
* and a handful of strides in any scene.
|
|
2017
|
-
*/
|
|
2018
|
-
private readonly motionPipelines = new Map<number, GPURenderPipeline>();
|
|
2019
|
-
private motionModule: GPUShaderModule | null = null;
|
|
2020
|
-
private motionPipelineLayout: GPUPipelineLayout | null = null;
|
|
2021
|
-
private motionFrameBuffer: GPUBuffer | null = null;
|
|
2022
|
-
private motionDrawBuffer: GPUBuffer | null = null;
|
|
2023
|
-
private motionDrawCapacity = 0;
|
|
2024
|
-
private motionGroup: GPUBindGroup | null = null;
|
|
2025
|
-
private readonly motionFrameStaging = new Float32Array(MOTION_FRAME_FLOATS);
|
|
2026
|
-
private motionDrawStaging = new Uint8Array(0);
|
|
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;
|
|
2027
2089
|
/** The scene's own clip transform with the generated shaders' y negation folded in. */
|
|
2028
2090
|
private readonly motionRaster = mat4.create();
|
|
2029
2091
|
private reconSizeKey = '';
|
|
@@ -2276,6 +2338,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2276
2338
|
refraction: number;
|
|
2277
2339
|
refractTint: Vec3 | undefined;
|
|
2278
2340
|
thicknessM: number;
|
|
2341
|
+
glass: GlassOptions | undefined;
|
|
2279
2342
|
} = {
|
|
2280
2343
|
lit: true,
|
|
2281
2344
|
fog: true,
|
|
@@ -2286,6 +2349,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2286
2349
|
refraction: 0,
|
|
2287
2350
|
refractTint: undefined,
|
|
2288
2351
|
thicknessM: 0,
|
|
2352
|
+
glass: undefined,
|
|
2289
2353
|
};
|
|
2290
2354
|
|
|
2291
2355
|
private reflectionPassActive = false;
|
|
@@ -2591,7 +2655,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2591
2655
|
this.filmFrags.writeBlock(fragmentSlot, this.filmFragInts);
|
|
2592
2656
|
|
|
2593
2657
|
const filmPipe = filmPipeline(
|
|
2594
|
-
this.
|
|
2658
|
+
this.blendedPipelines(),
|
|
2595
2659
|
this.surface.device,
|
|
2596
2660
|
this.filmLayout,
|
|
2597
2661
|
`film|${geometry.key ?? ''}`,
|
|
@@ -2605,10 +2669,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2605
2669
|
* holds the mirror's colour whatever `uReflectionEnabled` says, and WebGPU validates the
|
|
2606
2670
|
* binding rather than the read — the argument `filmBindGroupBlank` exists for.
|
|
2607
2671
|
*/
|
|
2608
|
-
const filmCommand = this.
|
|
2609
|
-
filmGroup === this.filmBindGroup ? MIRROR_COLOR : 0,
|
|
2610
|
-
this.currentTarget(),
|
|
2611
|
-
);
|
|
2672
|
+
const filmCommand = this.recordBlended(filmGroup === this.filmBindGroup ? MIRROR_COLOR : 0);
|
|
2612
2673
|
if (filmCommand !== null) {
|
|
2613
2674
|
filmCommand.pipeline = filmPipe;
|
|
2614
2675
|
filmCommand.bindGroup = filmGroup;
|
|
@@ -2799,6 +2860,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2799
2860
|
*/
|
|
2800
2861
|
setSkinPalette(palette: Float32Array | null): void {
|
|
2801
2862
|
if (this.surface.lost) return;
|
|
2863
|
+
this.skinPaletteData = palette;
|
|
2802
2864
|
if (palette === null) {
|
|
2803
2865
|
this.skinPaletteSlot = -1;
|
|
2804
2866
|
return;
|
|
@@ -3167,7 +3229,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3167
3229
|
* no-op before the first `bindMeshPass` of a session, same as every other pass that reads it.
|
|
3168
3230
|
*/
|
|
3169
3231
|
drawSdfText(text: GpuSdfText, model: Float32Array, color: Vec3, opacity: number): void {
|
|
3170
|
-
|
|
3232
|
+
/* Unjittered when it lands after the upscale, like every late draw. See `drawsLate`. */
|
|
3233
|
+
const viewProj = this.drawsLate() ? this.correctedViewProj : this.viewProj;
|
|
3171
3234
|
if (!this.canDraw() || viewProj === null) return;
|
|
3172
3235
|
const font = text.font;
|
|
3173
3236
|
const atlas = text.atlas;
|
|
@@ -3201,12 +3264,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3201
3264
|
this.sdfTextFrags.writeFloat(fragmentSlot, fragAt('uOutputExposure'), this.gradeExposure());
|
|
3202
3265
|
|
|
3203
3266
|
const sdfPipe = sdfTextPipeline(
|
|
3204
|
-
this.
|
|
3267
|
+
this.blendedPipelines(),
|
|
3205
3268
|
this.surface.device,
|
|
3206
3269
|
this.sdfTextLayout,
|
|
3207
3270
|
);
|
|
3208
3271
|
const sdfGroup = this.sdfTextBindGroupFor(atlas);
|
|
3209
|
-
const sdfCommand = this.
|
|
3272
|
+
const sdfCommand = this.recordBlended(0);
|
|
3210
3273
|
if (sdfCommand !== null) {
|
|
3211
3274
|
sdfCommand.pipeline = sdfPipe;
|
|
3212
3275
|
sdfCommand.bindGroup = sdfGroup;
|
|
@@ -3322,8 +3385,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3322
3385
|
|
|
3323
3386
|
const pass = this.openTextPass();
|
|
3324
3387
|
if (pass === null) return;
|
|
3325
|
-
const textPipe = textPipeline(this.
|
|
3326
|
-
const textCommand = this.
|
|
3388
|
+
const textPipe = textPipeline(this.blendedPipelines(), this.surface.device, this.textLayout);
|
|
3389
|
+
const textCommand = this.recordBlended(0);
|
|
3327
3390
|
if (textCommand !== null) {
|
|
3328
3391
|
textCommand.pipeline = textPipe;
|
|
3329
3392
|
textCommand.bindGroup = this.textBindGroup;
|
|
@@ -3773,13 +3836,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3773
3836
|
|
|
3774
3837
|
const layout = this.particleLayoutFor(batch.material).layout;
|
|
3775
3838
|
const particlePipe = particlePipeline(
|
|
3776
|
-
this.
|
|
3839
|
+
this.blendedPipelines(),
|
|
3777
3840
|
device,
|
|
3778
3841
|
layout,
|
|
3779
3842
|
batch.material,
|
|
3780
3843
|
batch.blend,
|
|
3781
3844
|
);
|
|
3782
|
-
const particleCommand = this.
|
|
3845
|
+
const particleCommand = this.recordBlended(0);
|
|
3783
3846
|
if (particleCommand !== null) {
|
|
3784
3847
|
particleCommand.pipeline = particlePipe;
|
|
3785
3848
|
particleCommand.bindGroup = batch.bindGroup;
|
|
@@ -3924,13 +3987,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3924
3987
|
frags.writeFloats(fragmentSlot, atF('uTint'), plumes.tint);
|
|
3925
3988
|
|
|
3926
3989
|
const plumePipe = plumePipeline(
|
|
3927
|
-
this.
|
|
3990
|
+
this.blendedPipelines(),
|
|
3928
3991
|
this.surface.device,
|
|
3929
3992
|
this.plumeLayout,
|
|
3930
3993
|
plumes.material,
|
|
3931
3994
|
plumes.blend,
|
|
3932
3995
|
);
|
|
3933
|
-
const plumeCommand = this.
|
|
3996
|
+
const plumeCommand = this.recordBlended(0);
|
|
3934
3997
|
if (plumeCommand !== null) {
|
|
3935
3998
|
plumeCommand.pipeline = plumePipe;
|
|
3936
3999
|
plumeCommand.bindGroup = this.plumeBindGroup;
|
|
@@ -4485,9 +4548,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4485
4548
|
this.windStreakVerts.writeBlock(vertexSlot, this.windStreakVertBlock);
|
|
4486
4549
|
this.windStreakFrags.writeBlock(fragmentSlot, this.windStreakFragBlock);
|
|
4487
4550
|
|
|
4488
|
-
const windPipe = windStreakPipeline(
|
|
4551
|
+
const windPipe = windStreakPipeline(
|
|
4552
|
+
this.blendedPipelines(),
|
|
4553
|
+
this.surface.device,
|
|
4554
|
+
this.windStreakLayout,
|
|
4555
|
+
);
|
|
4489
4556
|
/* Two uniform buffers and two vertex buffers. Nothing is sampled, so nothing is read. */
|
|
4490
|
-
const windCommand = this.
|
|
4557
|
+
const windCommand = this.recordBlended(0);
|
|
4491
4558
|
if (windCommand !== null) {
|
|
4492
4559
|
windCommand.pipeline = windPipe;
|
|
4493
4560
|
windCommand.bindGroup = this.windStreakBindGroup;
|
|
@@ -4715,8 +4782,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4715
4782
|
this.boltVerts.writeBlock(vertexSlot, this.boltVertBlock);
|
|
4716
4783
|
this.boltFrags.writeBlock(fragmentSlot, this.boltFragInts);
|
|
4717
4784
|
|
|
4718
|
-
const boltPipe = boltPipeline(this.
|
|
4719
|
-
const boltCommand = this.
|
|
4785
|
+
const boltPipe = boltPipeline(this.blendedPipelines(), this.surface.device, this.boltLayout);
|
|
4786
|
+
const boltCommand = this.recordBlended(0);
|
|
4720
4787
|
if (boltCommand !== null) {
|
|
4721
4788
|
boltCommand.pipeline = boltPipe;
|
|
4722
4789
|
boltCommand.bindGroup = this.boltBindGroup;
|
|
@@ -4853,9 +4920,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4853
4920
|
this.lineFrags.writeFloat(fragmentSlot, atF('uFogNear'), medium.fogNear);
|
|
4854
4921
|
this.lineFrags.writeFloat(fragmentSlot, atF('uFogFar'), medium.fogFar);
|
|
4855
4922
|
|
|
4856
|
-
const linePipe = linePipeline(
|
|
4923
|
+
const linePipe = linePipeline(
|
|
4924
|
+
this.blendedPipelines(),
|
|
4925
|
+
this.surface.device,
|
|
4926
|
+
this.lineLayout,
|
|
4927
|
+
additive,
|
|
4928
|
+
);
|
|
4857
4929
|
/* Two uniform blocks and three vertex buffers, and no attachment among them. */
|
|
4858
|
-
const lineCommand = this.
|
|
4930
|
+
const lineCommand = this.recordBlended(0);
|
|
4859
4931
|
if (lineCommand !== null) {
|
|
4860
4932
|
lineCommand.pipeline = linePipe;
|
|
4861
4933
|
lineCommand.bindGroup = this.lineBindGroup;
|
|
@@ -5078,8 +5150,39 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5078
5150
|
|
|
5079
5151
|
/* -- The shadow pass -------------------------------------------------------------- */
|
|
5080
5152
|
|
|
5081
|
-
|
|
5082
|
-
|
|
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;
|
|
5083
5186
|
private readonly shadowLayout: GPUBindGroupLayout;
|
|
5084
5187
|
/** The same layout plus the joint palette. See `createDepthBindGroupLayout`. */
|
|
5085
5188
|
private readonly shadowSkinnedLayout: GPUBindGroupLayout;
|
|
@@ -5105,8 +5208,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5105
5208
|
* read twice — so a profile that switches peeling off gets no map and no pass on either
|
|
5106
5209
|
* backend rather than a map nothing writes.
|
|
5107
5210
|
*/
|
|
5108
|
-
private
|
|
5109
|
-
private readonly peelView: GPUTextureView | null;
|
|
5211
|
+
private peelView: GPUTextureView | null;
|
|
5110
5212
|
/**
|
|
5111
5213
|
* The movers' own layer: the character, the rope it swings on, and anything else that walks.
|
|
5112
5214
|
*
|
|
@@ -5127,10 +5229,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5127
5229
|
* and therefore as lit — so nothing looked broken, only empty, which is why it survived the
|
|
5128
5230
|
* port and every gate in it.
|
|
5129
5231
|
*/
|
|
5130
|
-
private
|
|
5131
|
-
private readonly dynamicView: GPUTextureView | null;
|
|
5232
|
+
private dynamicView: GPUTextureView | null;
|
|
5132
5233
|
/** The peel's own bind groups: `uPeelShadowLayer` at 1, and the static map to peel against. */
|
|
5133
|
-
private
|
|
5234
|
+
private shadowPeelBindGroup: GPUBindGroup | null;
|
|
5134
5235
|
/**
|
|
5135
5236
|
* The cutout casters' layout, and a bind group per cutout map for each layer, built the first
|
|
5136
5237
|
* time a map casts and kept: a `createBindGroup` per draw is the allocation the house rules forbid.
|
|
@@ -5142,7 +5243,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5142
5243
|
private readonly cutoutPeelGroups = new Map<GpuSurfaceTexture, GPUBindGroup>();
|
|
5143
5244
|
private shadowPeelPassUniforms: GPUBuffer | null = null;
|
|
5144
5245
|
private shadowPeelSampler: GPUSampler | null = null;
|
|
5145
|
-
private
|
|
5246
|
+
private scatterDepthPeelBindGroup: GPUBindGroup | null;
|
|
5146
5247
|
/** Which layer the open pass is writing, so the sink picks the bindings that match it. */
|
|
5147
5248
|
private shadowLayerIsPeel = false;
|
|
5148
5249
|
/**
|
|
@@ -5201,7 +5302,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5201
5302
|
const held = groups.get(texture);
|
|
5202
5303
|
if (held !== undefined) return held;
|
|
5203
5304
|
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
5204
|
-
const previousView = peel ? this.
|
|
5305
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
5205
5306
|
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
5206
5307
|
if (passUniforms === null || previousView === null || previousSampler === null) return null;
|
|
5207
5308
|
const group = createDepthBindGroup(
|
|
@@ -5279,8 +5380,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5279
5380
|
* Built once and held, rather than made per call: it closes over `this` and a fresh one
|
|
5280
5381
|
* every frame would be an allocation in a per-frame path.
|
|
5281
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
|
+
};
|
|
5282
5391
|
private readonly casterSink: ShadowCasterSink = {
|
|
5283
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
|
+
}
|
|
5284
5400
|
const pass = this.shadowPass;
|
|
5285
5401
|
if (pass === null) return;
|
|
5286
5402
|
/* A cutout caster's group is looked up before a slot is spent, so a miss costs nothing. */
|
|
@@ -5330,6 +5446,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5330
5446
|
* uses — one draw, however many instances, exactly as the colour pass does it.
|
|
5331
5447
|
*/
|
|
5332
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
|
+
}
|
|
5333
5455
|
const pass = this.shadowPass;
|
|
5334
5456
|
if (pass === null) return;
|
|
5335
5457
|
const gpuBatch = batch as GpuInstancedBatch;
|
|
@@ -5378,7 +5500,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5378
5500
|
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
5379
5501
|
pass.drawIndexed(geometry.indexCount, count);
|
|
5380
5502
|
},
|
|
5381
|
-
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
|
+
}
|
|
5382
5510
|
const pass = this.shadowPass;
|
|
5383
5511
|
if (pass === null) return;
|
|
5384
5512
|
/* Its own slot, so two characters casting into one bake do not both take the second one's
|
|
@@ -5762,10 +5890,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5762
5890
|
* nothing at all under the default profile, which is on. The cache carries it for the same
|
|
5763
5891
|
* reason it carries the sample count: no pass can then disagree with the attachment.
|
|
5764
5892
|
*/
|
|
5893
|
+
/* Off compiles none of the glass lookup into a lit pipeline: see `PipelineCache.glassShadows`. */
|
|
5894
|
+
const glassShadows = quality.glassShadows !== 'off';
|
|
5765
5895
|
this.pipelines = new PipelineCache(
|
|
5766
5896
|
device,
|
|
5767
5897
|
quality.screenEffects ? sceneColorFormat(quality.hdrScene) : surface.format,
|
|
5768
5898
|
this.samples,
|
|
5899
|
+
glassShadows,
|
|
5769
5900
|
);
|
|
5770
5901
|
/*
|
|
5771
5902
|
* The canvas' own state, for the pass `ensurePass` reopens after `endFrame`.
|
|
@@ -5778,7 +5909,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5778
5909
|
this.overlayPipelines =
|
|
5779
5910
|
this.pipelines.format === surface.format && this.samples === 1
|
|
5780
5911
|
? this.pipelines
|
|
5781
|
-
: 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);
|
|
5782
5918
|
this.pipelineTargets =
|
|
5783
5919
|
this.overlayPipelines === this.pipelines
|
|
5784
5920
|
? [this.pipelines]
|
|
@@ -5929,42 +6065,35 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5929
6065
|
const blankDriftIndexView = blankVolume('flat.emptyDriftLightIndex', 'r32uint');
|
|
5930
6066
|
const blankDriftAtlasView = blankVolume('flat.emptyDriftLightAtlas', 'rgba16float');
|
|
5931
6067
|
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
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
|
+
],
|
|
5935
6086
|
format: SHADOW_FORMAT,
|
|
5936
|
-
|
|
6087
|
+
/* Copyable both ways, because the first glass grows it by copying every layer across. */
|
|
6088
|
+
usage: SUN_USAGE,
|
|
5937
6089
|
});
|
|
5938
|
-
this.
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
this.
|
|
5945
|
-
this.quality.directionalShadowDepthLayers > 1
|
|
5946
|
-
? device.createTexture({
|
|
5947
|
-
label: 'shadow.peel',
|
|
5948
|
-
size: [this.quality.directionalShadowMapSize, this.quality.directionalShadowMapSize],
|
|
5949
|
-
format: SHADOW_FORMAT,
|
|
5950
|
-
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
5951
|
-
})
|
|
5952
|
-
: null;
|
|
5953
|
-
this.peelView = this.peelMap === null ? null : this.peelMap.createView();
|
|
5954
|
-
/*
|
|
5955
|
-
* The movers' layer, on the condition `renderer.ts` builds its own on: directional shadows
|
|
5956
|
-
* at all. Same size and same format as the static map, because the shader samples all three
|
|
5957
|
-
* with one `uShadowMapSize` and one set of PCF offsets.
|
|
5958
|
-
*/
|
|
5959
|
-
this.dynamicMap = this.quality.directionalShadows
|
|
5960
|
-
? device.createTexture({
|
|
5961
|
-
label: 'shadow.dynamic',
|
|
5962
|
-
size: [this.quality.directionalShadowMapSize, this.quality.directionalShadowMapSize],
|
|
5963
|
-
format: SHADOW_FORMAT,
|
|
5964
|
-
usage: 0x10 | 0x4, // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
5965
|
-
})
|
|
5966
|
-
: null;
|
|
5967
|
-
this.dynamicView = this.dynamicMap === null ? null : this.dynamicMap.createView();
|
|
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;
|
|
5968
6097
|
|
|
5969
6098
|
/*
|
|
5970
6099
|
* **Cleared once, here, because a texture no pass has written reads as zero**, which the
|
|
@@ -6005,6 +6134,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6005
6134
|
* rather than the hardware's.
|
|
6006
6135
|
*/
|
|
6007
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
|
+
});
|
|
6008
6145
|
/*
|
|
6009
6146
|
* The photometric atlas's own sampler, **created here rather than reusing `postSampler`.**
|
|
6010
6147
|
*
|
|
@@ -6026,14 +6163,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6026
6163
|
});
|
|
6027
6164
|
|
|
6028
6165
|
this.flatTextures = (name) => {
|
|
6029
|
-
if (name === '
|
|
6030
|
-
/*
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
if (name === '
|
|
6036
|
-
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
|
+
};
|
|
6037
6177
|
}
|
|
6038
6178
|
/*
|
|
6039
6179
|
* The caller's image, or the stand-in. Read from the field rather than passed in, because
|
|
@@ -6071,10 +6211,6 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6071
6211
|
? { view: blankView, sampler: blankSampler }
|
|
6072
6212
|
: { view: albedo.view, sampler: albedo.sampler };
|
|
6073
6213
|
}
|
|
6074
|
-
/* The peel, where the profile asked for one. `uPeeledShadowEnabled` gates the sample. */
|
|
6075
|
-
if (name === 'uPeeledShadowMap' && this.peelView !== null) {
|
|
6076
|
-
return { view: this.peelView, sampler: shadowSampler };
|
|
6077
|
-
}
|
|
6078
6214
|
/*
|
|
6079
6215
|
* The room, once a bake has filled it. Unbaked reads as the blank cube: nothing to mirror.
|
|
6080
6216
|
*
|
|
@@ -6143,7 +6279,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6143
6279
|
* texels by construction, an offset that snapped to one being no offset at all.
|
|
6144
6280
|
*/
|
|
6145
6281
|
if (name === 'uRefractScene') {
|
|
6146
|
-
|
|
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 };
|
|
6147
6291
|
}
|
|
6148
6292
|
/*
|
|
6149
6293
|
* Every point light's shadow, in one binding.
|
|
@@ -6369,9 +6513,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6369
6513
|
*/
|
|
6370
6514
|
this.mediumLayout = createPostStageLayout(device, MEDIUM_UNIFORMS, MEDIUM_FRAG_SIZE, [
|
|
6371
6515
|
{ binding: MEDIUM_TEXTURES.uDepth, filterable: false },
|
|
6372
|
-
{ binding: MEDIUM_TEXTURES.
|
|
6373
|
-
{ binding: MEDIUM_TEXTURES.uPeeledShadowMap, filterable: false },
|
|
6374
|
-
{ binding: MEDIUM_TEXTURES.uDynamicShadowMap, filterable: false },
|
|
6516
|
+
{ binding: MEDIUM_TEXTURES.uSunShadows, filterable: false, dimension: '2d-array' },
|
|
6375
6517
|
]);
|
|
6376
6518
|
this.mediumUniforms = device.createBuffer({
|
|
6377
6519
|
label: 'post.mediumUniforms',
|
|
@@ -6603,7 +6745,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6603
6745
|
this.scatterDepthDraws.buffer,
|
|
6604
6746
|
{ binding: DEPTH_FRAG_BINDING, buffer: depthPassUniforms, size: DEPTH_FRAG_SIZE },
|
|
6605
6747
|
DEPTH_PREVIOUS_BINDING,
|
|
6606
|
-
|
|
6748
|
+
blankArrayView,
|
|
6607
6749
|
blankSampler,
|
|
6608
6750
|
);
|
|
6609
6751
|
this.shadowBindGroup = createDepthBindGroup(
|
|
@@ -6611,10 +6753,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6611
6753
|
this.shadowLayout,
|
|
6612
6754
|
this.shadowDraws.buffer,
|
|
6613
6755
|
depthPassUniforms,
|
|
6614
|
-
|
|
6756
|
+
blankArrayView,
|
|
6615
6757
|
blankSampler,
|
|
6616
6758
|
);
|
|
6617
|
-
this.shadowBlankView =
|
|
6759
|
+
this.shadowBlankView = blankArrayView;
|
|
6618
6760
|
this.shadowBlankSampler = blankSampler;
|
|
6619
6761
|
this.shadowPassUniforms = depthPassUniforms;
|
|
6620
6762
|
|
|
@@ -6652,7 +6794,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6652
6794
|
this.shadowLayout,
|
|
6653
6795
|
this.shadowDraws.buffer,
|
|
6654
6796
|
peelPassUniforms,
|
|
6655
|
-
this.
|
|
6797
|
+
this.sunStaticArrayView,
|
|
6656
6798
|
shadowSampler,
|
|
6657
6799
|
);
|
|
6658
6800
|
this.scatterDepthPeelBindGroup =
|
|
@@ -6664,7 +6806,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6664
6806
|
this.scatterDepthDraws.buffer,
|
|
6665
6807
|
{ binding: DEPTH_FRAG_BINDING, buffer: peelPassUniforms, size: DEPTH_FRAG_SIZE },
|
|
6666
6808
|
DEPTH_PREVIOUS_BINDING,
|
|
6667
|
-
this.
|
|
6809
|
+
this.sunStaticArrayView,
|
|
6668
6810
|
shadowSampler,
|
|
6669
6811
|
);
|
|
6670
6812
|
|
|
@@ -7070,7 +7212,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7070
7212
|
this.perFrame.reset();
|
|
7071
7213
|
this.materials.dirty();
|
|
7072
7214
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
7073
|
-
this.
|
|
7215
|
+
this.motionPass?.reset();
|
|
7216
|
+
this.latePass?.reset();
|
|
7217
|
+
this.moverFrame += 1;
|
|
7218
|
+
this.inFrame = true;
|
|
7074
7219
|
/*
|
|
7075
7220
|
* The canvas rather than the swap chain's texture, and the difference is that reading this
|
|
7076
7221
|
* does not *acquire* anything. See `swapView`.
|
|
@@ -7391,7 +7536,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7391
7536
|
'what is kept and what a buffer is hinted as.',
|
|
7392
7537
|
);
|
|
7393
7538
|
}
|
|
7394
|
-
|
|
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);
|
|
7395
7560
|
}
|
|
7396
7561
|
|
|
7397
7562
|
/**
|
|
@@ -8050,7 +8215,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8050
8215
|
depthLayer = 0,
|
|
8051
8216
|
tint: Vec3 | null = null,
|
|
8052
8217
|
/** Where it was last frame. See `RendererApi.drawMesh`, and `runMotion` for what reads it. */
|
|
8053
|
-
previousModel: ArrayLike<number> | null = null,
|
|
8218
|
+
previousModel: ArrayLike<number> | Mover | null = null,
|
|
8054
8219
|
): void {
|
|
8055
8220
|
/*
|
|
8056
8221
|
* Skipped where the flag asks and the bounds say so.
|
|
@@ -8075,7 +8240,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8075
8240
|
* depth, so a motion pass drawing it would either be depth-rejected everywhere — wasted — or,
|
|
8076
8241
|
* where it happened to pass, write motion for a surface no pixel of the frame belongs to.
|
|
8077
8242
|
*/
|
|
8078
|
-
if (
|
|
8243
|
+
if (this.tracksMotionNow()) this.recordDrawMotion(mesh, model, previousModel);
|
|
8079
8244
|
this.submitMesh(mesh, model, tint, 1, false, { depthLayer });
|
|
8080
8245
|
}
|
|
8081
8246
|
|
|
@@ -8238,7 +8403,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8238
8403
|
return;
|
|
8239
8404
|
}
|
|
8240
8405
|
|
|
8241
|
-
|
|
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
|
+
);
|
|
8242
8413
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
8243
8414
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
8244
8415
|
/* The frame's wind, for a mesh carrying a sway lane. Written on this path and not the
|
|
@@ -8298,17 +8469,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8298
8469
|
* shades as an ordinary translucent one rather than sampling a texture holding the frame
|
|
8299
8470
|
* before last. That is the defined state the two-backends rule asks for.
|
|
8300
8471
|
*/
|
|
8301
|
-
const
|
|
8302
|
-
const refracting = refraction > 0 && this.takeRefractSnapshot();
|
|
8303
|
-
if (refracting) {
|
|
8304
|
-
this.perFrameFloats[this.materialField('uRefractStrength')] = refraction;
|
|
8305
|
-
const refractTint = options.refractTint;
|
|
8306
|
-
const tintAt = this.materialField('uRefractTint');
|
|
8307
|
-
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
8308
|
-
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
8309
|
-
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
8310
|
-
this.perFrameFloats[this.materialField('uRefractThickness')] = options.thicknessM ?? 0;
|
|
8311
|
-
}
|
|
8472
|
+
const refracting = this.bindSeeThrough(options);
|
|
8312
8473
|
/* Taken for itself when any of those differs from the pass, and put back below: the rule both
|
|
8313
8474
|
backends count material changes by. See `materialChanges.ts`. */
|
|
8314
8475
|
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
@@ -8368,7 +8529,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8368
8529
|
* pays the compile it was trying to avoid. That is a slow frame; the alternative is a
|
|
8369
8530
|
* thrown error in the middle of one.
|
|
8370
8531
|
*/
|
|
8371
|
-
const pipelines = this.targetPipelines();
|
|
8532
|
+
const pipelines = blend ? this.blendedPipelines() : this.targetPipelines();
|
|
8372
8533
|
let pipeline = pipelines.peek(key);
|
|
8373
8534
|
if (pipeline === undefined) {
|
|
8374
8535
|
const present = this.meshPresent.get(base);
|
|
@@ -8416,7 +8577,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8416
8577
|
* the uniform rings are the state arena this design would otherwise have had to build —
|
|
8417
8578
|
* so the command carries them as its two dynamic offsets.
|
|
8418
8579
|
*/
|
|
8419
|
-
const command = this.recordDraw(0, this.currentTarget());
|
|
8580
|
+
const command = blend ? this.recordBlended(0) : this.recordDraw(0, this.currentTarget());
|
|
8420
8581
|
if (command !== null) {
|
|
8421
8582
|
command.pipeline = pipeline;
|
|
8422
8583
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
@@ -8453,11 +8614,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8453
8614
|
if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
8454
8615
|
if (!fog) this.perFrameInts[this.materialField('uFogEnabled')] = 1;
|
|
8455
8616
|
}
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
this.perFrameFloats[this.materialField('uRefractStrength')] = 0;
|
|
8460
|
-
}
|
|
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();
|
|
8461
8620
|
if (!toneMapped) {
|
|
8462
8621
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
8463
8622
|
}
|
|
@@ -8531,12 +8690,35 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8531
8690
|
/** Push placement and colour. Only the live prefix; see `GpuInstancedBatch.upload`. */
|
|
8532
8691
|
uploadInstanced(batch: GpuInstancedBatch, data: MeshInstances): void {
|
|
8533
8692
|
if (this.surface.lost) return;
|
|
8534
|
-
|
|
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);
|
|
8535
8711
|
}
|
|
8536
8712
|
|
|
8537
8713
|
/** Draw every live instance, opaque. */
|
|
8538
8714
|
drawInstanced(batch: GpuInstancedBatch, data: MeshInstances): void {
|
|
8539
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
|
+
}
|
|
8540
8722
|
}
|
|
8541
8723
|
|
|
8542
8724
|
/** Draw every live instance, blended. See `drawTranslucentMesh` for what the options mean. */
|
|
@@ -8555,6 +8737,45 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8555
8737
|
batch.dispose();
|
|
8556
8738
|
}
|
|
8557
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
|
+
|
|
8558
8779
|
/**
|
|
8559
8780
|
* One draw, one material slot, one uniform slot, however many instances.
|
|
8560
8781
|
*
|
|
@@ -8591,7 +8812,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8591
8812
|
return;
|
|
8592
8813
|
}
|
|
8593
8814
|
|
|
8594
|
-
this.perDraw.writeFloats(
|
|
8815
|
+
this.perDraw.writeFloats(
|
|
8816
|
+
slot,
|
|
8817
|
+
FLAT_VERT_FIELDS.uViewProj.offset,
|
|
8818
|
+
blend && this.drawsLate() ? this.correctedViewProj : this.viewProj,
|
|
8819
|
+
);
|
|
8595
8820
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
8596
8821
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
8597
8822
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
@@ -8614,8 +8839,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8614
8839
|
|
|
8615
8840
|
const depthWrite = options.depthWrite ?? true;
|
|
8616
8841
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
8617
|
-
/*
|
|
8618
|
-
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 });
|
|
8619
8845
|
if (own) this.materials.dirty();
|
|
8620
8846
|
const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
|
|
8621
8847
|
/* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
|
|
@@ -8626,7 +8852,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8626
8852
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8627
8853
|
`${this.materialDoubleSided ? '|2s' : ''}`;
|
|
8628
8854
|
|
|
8629
|
-
const pipelines = this.targetPipelines();
|
|
8855
|
+
const pipelines = blend ? this.blendedPipelines() : this.targetPipelines();
|
|
8630
8856
|
let pipeline = pipelines.peek(key);
|
|
8631
8857
|
if (pipeline === undefined) {
|
|
8632
8858
|
const present = this.meshPresent.get(base);
|
|
@@ -8653,7 +8879,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8653
8879
|
|
|
8654
8880
|
const material = this.materialSlotForDraw();
|
|
8655
8881
|
if (material !== null) {
|
|
8656
|
-
const command = this.recordDraw(0, this.currentTarget());
|
|
8882
|
+
const command = blend ? this.recordBlended(0) : this.recordDraw(0, this.currentTarget());
|
|
8657
8883
|
if (command !== null) {
|
|
8658
8884
|
command.pipeline = pipeline;
|
|
8659
8885
|
command.bindGroup = this.flatBindGroupFor(false, false, null);
|
|
@@ -8697,6 +8923,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8697
8923
|
if (!toneMapped) {
|
|
8698
8924
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
8699
8925
|
}
|
|
8926
|
+
if (refracting) this.releaseSeeThrough();
|
|
8700
8927
|
if (own) this.materials.dirty();
|
|
8701
8928
|
}
|
|
8702
8929
|
|
|
@@ -8890,6 +9117,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8890
9117
|
this.lightViewProj.set(lightViewProj as Float32Array);
|
|
8891
9118
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
8892
9119
|
this.shadowLayerIsPeel = peel;
|
|
9120
|
+
this.shadowLayerIsDynamic = dynamic;
|
|
9121
|
+
this.glassCasters.clear();
|
|
8893
9122
|
/*
|
|
8894
9123
|
* A frame's peel is only as good as the pass that fills it, and the static layer is the one
|
|
8895
9124
|
* that opens first.
|
|
@@ -8997,12 +9226,349 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8997
9226
|
}
|
|
8998
9227
|
|
|
8999
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
|
+
|
|
9000
9559
|
endShadowPass(): void {
|
|
9001
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
|
+
}
|
|
9002
9566
|
this.shadowDraws.flush();
|
|
9003
9567
|
this.scatterDepthDraws.flush();
|
|
9004
|
-
this.shadowPass.end();
|
|
9005
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;
|
|
9006
9572
|
/* The map now holds a rendered peel rather than whatever it was created with. */
|
|
9007
9573
|
if (this.shadowLayerIsPeel) this.peelFilled = true;
|
|
9008
9574
|
this.shadowLayerIsPeel = false;
|
|
@@ -9032,6 +9598,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9032
9598
|
private preparedPointLightCount = 0;
|
|
9033
9599
|
/** See `renderer.ts`: built when a world reports its lights, and rebuilt only to grow. */
|
|
9034
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;
|
|
9035
9603
|
private readonly bakeScratch = createBakeScratch(POINT_SHADOW_POOL);
|
|
9036
9604
|
/** Said once per renderer; see `updatePointShadows`. */
|
|
9037
9605
|
private warnedNoPointShadowArray = false;
|
|
@@ -9217,6 +9785,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9217
9785
|
*/
|
|
9218
9786
|
this.shadowDraws.reset();
|
|
9219
9787
|
this.scatterDepthDraws.reset();
|
|
9788
|
+
this.glassTint?.frag.reset();
|
|
9220
9789
|
this.shadowEncoder = this.surface.device.createCommandEncoder({ label: 'pointShadow' });
|
|
9221
9790
|
/* A cube face is a shadow pass like any other, and the courtyard is where it matters: six of
|
|
9222
9791
|
them a frame is the difference this slot exists to make visible. */
|
|
@@ -9244,7 +9813,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9244
9813
|
light.z,
|
|
9245
9814
|
light.radius,
|
|
9246
9815
|
(view, viewProj) => this.bakeFace(view, viewProj, casters as ShadowCasters),
|
|
9247
|
-
(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),
|
|
9248
9817
|
this.faceViewProj,
|
|
9249
9818
|
light.shadowNear,
|
|
9250
9819
|
light.sourceRadius,
|
|
@@ -9258,10 +9827,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9258
9827
|
* encoder, a second submit and a second `shadows` timer slot for work that is the same work.
|
|
9259
9828
|
*/
|
|
9260
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);
|
|
9261
9832
|
} finally {
|
|
9262
9833
|
this.depthCullMode = 'back';
|
|
9263
9834
|
this.shadowDraws.flush();
|
|
9264
9835
|
this.scatterDepthDraws.flush();
|
|
9836
|
+
this.glassTint?.frag.flush();
|
|
9265
9837
|
/*
|
|
9266
9838
|
* **Before the submit, and that ordering is the whole point of the ring.** A write to the
|
|
9267
9839
|
* queue lands ahead of the encoder's commands whenever it is issued, so uploading per
|
|
@@ -9273,6 +9845,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9273
9845
|
this.shadowEncoder = null;
|
|
9274
9846
|
this.shadowPass = null;
|
|
9275
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;
|
|
9276
9851
|
}
|
|
9277
9852
|
|
|
9278
9853
|
/*
|
|
@@ -9283,6 +9858,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9283
9858
|
* written every frame regardless.
|
|
9284
9859
|
*/
|
|
9285
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);
|
|
9286
9865
|
}
|
|
9287
9866
|
|
|
9288
9867
|
/**
|
|
@@ -9327,7 +9906,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9327
9906
|
light.z,
|
|
9328
9907
|
range,
|
|
9329
9908
|
(view, viewProj) => this.bakeFace(view, viewProj, casters as ShadowCasters),
|
|
9330
|
-
(layer, face, far, mapNear
|
|
9909
|
+
(layer, face, far, mapNear, x, y, z) =>
|
|
9910
|
+
this.resolveFace(layer, face, far, mapNear, x, y, z),
|
|
9331
9911
|
this.faceViewProj,
|
|
9332
9912
|
near,
|
|
9333
9913
|
/* The filter reads the emitter's size, never the bake. See `renderer.ts`'s copy. */
|
|
@@ -9336,6 +9916,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9336
9916
|
),
|
|
9337
9917
|
);
|
|
9338
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);
|
|
9339
9922
|
}
|
|
9340
9923
|
|
|
9341
9924
|
/**
|
|
@@ -9363,9 +9946,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9363
9946
|
depthStoreOp: 'store',
|
|
9364
9947
|
},
|
|
9365
9948
|
});
|
|
9949
|
+
/* A face is an enumeration of its own: its glass is its own. */
|
|
9950
|
+
this.glassCasters.clear();
|
|
9366
9951
|
casters(this.casterSink);
|
|
9367
9952
|
this.shadowPass.end();
|
|
9368
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
|
+
}
|
|
9369
9970
|
}
|
|
9370
9971
|
|
|
9371
9972
|
/**
|
|
@@ -9375,11 +9976,70 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9375
9976
|
* serve every light: the face is consumed before the next one is drawn. See
|
|
9376
9977
|
* `pointShadowArray.ts` for why the regions are disjoint.
|
|
9377
9978
|
*/
|
|
9378
|
-
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 {
|
|
9379
9988
|
const encoder = this.shadowEncoder;
|
|
9380
9989
|
const array = this.pointShadowArray;
|
|
9381
9990
|
if (encoder === null || array === null) return;
|
|
9382
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);
|
|
9383
10043
|
}
|
|
9384
10044
|
|
|
9385
10045
|
private buildFlatBindGroup(): GPUBindGroup {
|
|
@@ -9739,8 +10399,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9739
10399
|
* drawn over a reflective floor rather than under it.
|
|
9740
10400
|
*/
|
|
9741
10401
|
this.runReflections(encoder);
|
|
9742
|
-
/* The translucent set, into its two buffers and back over the scene
|
|
9743
|
-
|
|
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);
|
|
9744
10405
|
/*
|
|
9745
10406
|
* **The medium, after everything that draws and before anything that composites.**
|
|
9746
10407
|
*
|
|
@@ -9761,6 +10422,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9761
10422
|
*/
|
|
9762
10423
|
this.runMotion(encoder);
|
|
9763
10424
|
this.runReconstruction(encoder);
|
|
10425
|
+
this.runLate(encoder);
|
|
10426
|
+
if (this.reconstructing) this.runOit(encoder);
|
|
9764
10427
|
const aoStrength = this.quality.ambientOcclusion;
|
|
9765
10428
|
if (aoStrength > 0 && this.frameProjection !== null) this.runOcclusion(encoder);
|
|
9766
10429
|
const bloomStrength = this.quality.bloom * this.bloomScale;
|
|
@@ -9989,19 +10652,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9989
10652
|
label: 'recon.shown',
|
|
9990
10653
|
size: [outputWidth, outputHeight],
|
|
9991
10654
|
format: RECON_HISTORY_FORMAT,
|
|
9992
|
-
|
|
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
|
|
9993
10658
|
});
|
|
9994
10659
|
this.reconShownView = this.reconShown.createView();
|
|
9995
10660
|
this.reconMotion = device.createTexture({
|
|
9996
10661
|
label: 'recon.motion',
|
|
9997
10662
|
size: [renderWidth, renderHeight],
|
|
9998
10663
|
format: RECON_HISTORY_FORMAT,
|
|
9999
|
-
/* An attachment as well, because the pass that
|
|
10000
|
-
|
|
10001
|
-
|
|
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. */
|
|
10002
10667
|
usage: 0x4 | 0x10,
|
|
10003
10668
|
});
|
|
10004
10669
|
this.reconMotionView = this.reconMotion.createView();
|
|
10670
|
+
this.motionPass ??= new MotionPass(device);
|
|
10671
|
+
this.latePass ??= new LatePass(device);
|
|
10005
10672
|
this.reconPreviousDepth = device.createTexture({
|
|
10006
10673
|
label: 'recon.previousDepth',
|
|
10007
10674
|
size: [renderWidth, renderHeight],
|
|
@@ -10120,222 +10787,184 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10120
10787
|
}
|
|
10121
10788
|
|
|
10122
10789
|
/**
|
|
10123
|
-
*
|
|
10790
|
+
* Whether a blended draw recorded now lands after the upscale. See `lateRouting.ts`.
|
|
10124
10791
|
*
|
|
10125
|
-
*
|
|
10126
|
-
* — which is the right thing for a scene to do, the alternative being to know what the renderer
|
|
10127
|
-
* is configured as — must cost nothing at all when nobody is going to read them.
|
|
10792
|
+
* Not during the order-independent replay, which owns its pass and issues into it directly.
|
|
10128
10793
|
*/
|
|
10129
|
-
private
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
const grown = new Float32Array(Math.max(wanted, this.motionMatrices.length * 2, 64));
|
|
10140
|
-
grown.set(this.motionMatrices);
|
|
10141
|
-
this.motionMatrices = grown;
|
|
10142
|
-
}
|
|
10143
|
-
this.motionMatrices.set(model as ArrayLike<number> & Iterable<number>, at * MOTION_DRAW_FLOATS);
|
|
10144
|
-
this.motionMatrices.set(
|
|
10145
|
-
previousModel as ArrayLike<number> & Iterable<number>,
|
|
10146
|
-
at * MOTION_DRAW_FLOATS + 16,
|
|
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
|
+
)
|
|
10147
10804
|
);
|
|
10148
|
-
this.motionMeshes[at] = mesh;
|
|
10149
|
-
this.motionCount = at + 1;
|
|
10150
10805
|
}
|
|
10151
10806
|
|
|
10152
10807
|
/**
|
|
10153
|
-
* The
|
|
10154
|
-
*
|
|
10155
|
-
*
|
|
10156
|
-
* 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.
|
|
10157
10811
|
*/
|
|
10158
|
-
private
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
this.
|
|
10162
|
-
label: 'recon.motion.layout',
|
|
10163
|
-
entries: [
|
|
10164
|
-
{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' as const } },
|
|
10165
|
-
{
|
|
10166
|
-
binding: 1,
|
|
10167
|
-
visibility: 0x1,
|
|
10168
|
-
buffer: { type: 'uniform' as const, hasDynamicOffset: true },
|
|
10169
|
-
},
|
|
10170
|
-
],
|
|
10171
|
-
});
|
|
10172
|
-
this.motionModule = device.createShaderModule({
|
|
10173
|
-
label: 'recon.motion',
|
|
10174
|
-
code: RECON_MOTION_WGSL,
|
|
10175
|
-
});
|
|
10176
|
-
this.motionPipelineLayout = device.createPipelineLayout({
|
|
10177
|
-
bindGroupLayouts: [this.motionLayout],
|
|
10178
|
-
});
|
|
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());
|
|
10179
10816
|
}
|
|
10180
10817
|
|
|
10181
|
-
/** The
|
|
10182
|
-
private
|
|
10183
|
-
|
|
10184
|
-
if (held !== undefined) return held;
|
|
10185
|
-
const module = this.motionModule;
|
|
10186
|
-
const layout = this.motionPipelineLayout;
|
|
10187
|
-
if (module === null || layout === null) return null;
|
|
10188
|
-
const { device } = this.surface;
|
|
10189
|
-
const pipeline = device.createRenderPipeline({
|
|
10190
|
-
label: `recon.motion.${String(stride)}`,
|
|
10191
|
-
layout,
|
|
10192
|
-
vertex: {
|
|
10193
|
-
module,
|
|
10194
|
-
entryPoint: 'motionVert',
|
|
10195
|
-
/* Positions are the layout's first attribute and are never optional, so they are at offset
|
|
10196
|
-
zero of every stride this can be handed. */
|
|
10197
|
-
buffers: [
|
|
10198
|
-
{
|
|
10199
|
-
arrayStride: stride,
|
|
10200
|
-
attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }],
|
|
10201
|
-
},
|
|
10202
|
-
],
|
|
10203
|
-
},
|
|
10204
|
-
fragment: { module, entryPoint: 'motionFrag', targets: [{ format: MOTION_TARGET_FORMAT }] },
|
|
10205
|
-
/*
|
|
10206
|
-
* **The scene's own winding**, because this draws the scene's geometry through the scene's
|
|
10207
|
-
* clip transform — the product the renderer hands over already carries the negation that
|
|
10208
|
-
* `naga` writes into every generated vertex stage, so the faces come out the way they did
|
|
10209
|
-
* and a `cw` front face here would draw the inside of every mesh. §3 row 57.
|
|
10210
|
-
*/
|
|
10211
|
-
primitive: { topology: 'triangle-list', cullMode: 'back' },
|
|
10212
|
-
depthStencil: {
|
|
10213
|
-
format: DEPTH_FORMAT,
|
|
10214
|
-
/*
|
|
10215
|
-
* **Tested and not written.** The frame's depth is finished; this pass only wants the
|
|
10216
|
-
* pixels the scene actually kept, and `DEPTH_COMPARE_EQUAL` is the comparison that admits
|
|
10217
|
-
* exactly them — the same transform on the same positions gives the same depth, which is
|
|
10218
|
-
* why the rasterisation matrix is handed over whole rather than rebuilt here.
|
|
10219
|
-
*
|
|
10220
|
-
* **And the equality is not fragile here**, which was worth measuring rather than
|
|
10221
|
-
* assuming: this driver contracts multiply-adds (§3 row 107) and two shader modules can
|
|
10222
|
-
* contract a matrix product differently, so an equality across them could have admitted
|
|
10223
|
-
* only some of a surface. Run with `greater-equal` instead, the ghost measured the same
|
|
10224
|
-
* to the pixel — 5,942 either way — so nothing is being dropped.
|
|
10225
|
-
*/
|
|
10226
|
-
depthWriteEnabled: false,
|
|
10227
|
-
depthCompare: DEPTH_COMPARE_EQUAL,
|
|
10228
|
-
},
|
|
10229
|
-
});
|
|
10230
|
-
this.motionPipelines.set(stride, pipeline);
|
|
10231
|
-
return pipeline;
|
|
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();
|
|
10232
10821
|
}
|
|
10233
10822
|
|
|
10234
10823
|
/**
|
|
10235
|
-
*
|
|
10236
|
-
*
|
|
10237
|
-
*
|
|
10238
|
-
* 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.
|
|
10239
10827
|
*/
|
|
10240
|
-
private
|
|
10241
|
-
const
|
|
10828
|
+
private runLate(encoder: GPUCommandEncoder): void {
|
|
10829
|
+
const late = this.latePass;
|
|
10830
|
+
const shown = this.reconShownView;
|
|
10242
10831
|
const depth = this.depthView;
|
|
10243
|
-
if (!this.reconstructing ||
|
|
10244
|
-
const
|
|
10245
|
-
|
|
10246
|
-
const
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
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;
|
|
10255
10850
|
const pass = encoder.beginRenderPass({
|
|
10256
|
-
label: 'recon.
|
|
10851
|
+
label: 'recon.late',
|
|
10257
10852
|
timestampWrites: this.gpuTimer.writesFor(),
|
|
10258
|
-
colorAttachments: [
|
|
10259
|
-
{ view: target, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
10260
|
-
],
|
|
10853
|
+
colorAttachments: [{ view: shown, loadOp: 'load', storeOp: 'store' }],
|
|
10261
10854
|
depthStencilAttachment: {
|
|
10262
|
-
view:
|
|
10855
|
+
view: late.depthView(width, height),
|
|
10263
10856
|
depthLoadOp: 'load',
|
|
10857
|
+
/* Kept: the order-independent set replays against it after this pass. */
|
|
10264
10858
|
depthStoreOp: 'store',
|
|
10265
|
-
depthReadOnly: false,
|
|
10266
10859
|
},
|
|
10267
10860
|
});
|
|
10861
|
+
late.replay(pass, this.issueBound);
|
|
10862
|
+
pass.end();
|
|
10863
|
+
}
|
|
10268
10864
|
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
usage: 0x40 | 0x8, // UNIFORM | COPY_DST
|
|
10279
|
-
});
|
|
10280
|
-
this.motionDrawStaging = new Uint8Array(this.motionDrawCapacity * MOTION_DRAW_STRIDE);
|
|
10281
|
-
this.motionGroup = null;
|
|
10282
|
-
}
|
|
10283
|
-
this.motionFrameBuffer ??= device.createBuffer({
|
|
10284
|
-
label: 'recon.motion.frame',
|
|
10285
|
-
size: MOTION_FRAME_FLOATS * 4,
|
|
10286
|
-
usage: 0x40 | 0x8,
|
|
10287
|
-
});
|
|
10288
|
-
this.motionGroup ??= device.createBindGroup({
|
|
10289
|
-
label: 'recon.motion.group',
|
|
10290
|
-
layout,
|
|
10291
|
-
entries: [
|
|
10292
|
-
{ binding: 0, resource: { buffer: this.motionFrameBuffer } },
|
|
10293
|
-
{
|
|
10294
|
-
binding: 1,
|
|
10295
|
-
resource: { buffer: this.motionDrawBuffer as GPUBuffer, size: MOTION_DRAW_STRIDE },
|
|
10296
|
-
},
|
|
10297
|
-
],
|
|
10298
|
-
});
|
|
10299
|
-
|
|
10300
|
-
/* The scene's clip transform, with the generated stages' y negation folded in. */
|
|
10301
|
-
mat4.multiply(this.motionRaster, MOTION_CLIP_FLIP, this.viewProj ?? this.correctedViewProj);
|
|
10302
|
-
this.motionFrameStaging.set(this.motionRaster as Float32Array, 0);
|
|
10303
|
-
this.motionFrameStaging.set(this.frameRawViewProj as Float32Array, 16);
|
|
10304
|
-
this.motionFrameStaging.set(this.reconPreviousRawViewProj as Float32Array, 32);
|
|
10305
|
-
device.queue.writeBuffer(this.motionFrameBuffer, 0, this.motionFrameStaging);
|
|
10306
|
-
|
|
10307
|
-
const draws = new Float32Array(this.motionDrawStaging.buffer);
|
|
10308
|
-
for (let i = 0; i < count; i += 1) {
|
|
10309
|
-
draws.set(
|
|
10310
|
-
this.motionMatrices.subarray(i * MOTION_DRAW_FLOATS, (i + 1) * MOTION_DRAW_FLOATS),
|
|
10311
|
-
(i * MOTION_DRAW_STRIDE) / 4,
|
|
10312
|
-
);
|
|
10313
|
-
}
|
|
10314
|
-
device.queue.writeBuffer(
|
|
10315
|
-
this.motionDrawBuffer as GPUBuffer,
|
|
10316
|
-
0,
|
|
10317
|
-
this.motionDrawStaging,
|
|
10318
|
-
0,
|
|
10319
|
-
bytes,
|
|
10320
|
-
);
|
|
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
|
+
}
|
|
10321
10874
|
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
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;
|
|
10337
10894
|
}
|
|
10338
|
-
|
|
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
|
+
);
|
|
10339
10968
|
}
|
|
10340
10969
|
|
|
10341
10970
|
/**
|
|
@@ -10781,9 +11410,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10781
11410
|
if (!this.oitActive || this.translucentQueue.length === 0) return;
|
|
10782
11411
|
const accum = this.oitAccumView;
|
|
10783
11412
|
const reveal = this.oitRevealView;
|
|
10784
|
-
|
|
11413
|
+
/* After the upscale while reconstructing: over the reconstructed picture, against its depth. */
|
|
11414
|
+
const scene = this.reconstructing ? this.reconShownView : this.sceneColorView;
|
|
10785
11415
|
const group = this.oitResolveGroup;
|
|
10786
|
-
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;
|
|
10787
11422
|
if (accum === null || reveal === null || scene === null || group === null || depth === null) {
|
|
10788
11423
|
return;
|
|
10789
11424
|
}
|
|
@@ -10797,12 +11432,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10797
11432
|
*/
|
|
10798
11433
|
if (this.translucentQueue.refracts && !this.refractSnapshotTaken) {
|
|
10799
11434
|
const snapshot = this.refractSnapshot;
|
|
10800
|
-
const source = this.sceneColor;
|
|
11435
|
+
const source = this.reconstructing ? this.reconShown : this.sceneColor;
|
|
10801
11436
|
if (snapshot !== null && source !== null) {
|
|
10802
11437
|
encoder.copyTextureToTexture({ texture: source }, { texture: snapshot }, [
|
|
10803
11438
|
snapshot.width,
|
|
10804
11439
|
snapshot.height,
|
|
10805
11440
|
]);
|
|
11441
|
+
this.refractMips?.record(encoder);
|
|
10806
11442
|
this.refractSnapshotTaken = true;
|
|
10807
11443
|
}
|
|
10808
11444
|
}
|
|
@@ -10843,6 +11479,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10843
11479
|
options.refraction = draw.refraction;
|
|
10844
11480
|
options.refractTint = draw.refractTint as unknown as Vec3 | undefined;
|
|
10845
11481
|
options.thicknessM = draw.thicknessM;
|
|
11482
|
+
options.glass = draw.glass ?? undefined;
|
|
10846
11483
|
this.drawTranslucentMesh(draw.mesh as GpuMesh, draw.model, draw.opacity, options);
|
|
10847
11484
|
});
|
|
10848
11485
|
this.oitReplaying = false;
|
|
@@ -10873,9 +11510,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10873
11510
|
this.pipelines,
|
|
10874
11511
|
device,
|
|
10875
11512
|
this.oitResolveLayout,
|
|
10876
|
-
'post.oitResolve',
|
|
11513
|
+
this.reconstructing ? 'post.oitResolve.recon' : 'post.oitResolve',
|
|
10877
11514
|
OIT_RESOLVE_FRAG_WGSL,
|
|
10878
|
-
sceneColorFormat(this.quality.hdrScene),
|
|
11515
|
+
this.reconstructing ? RECON_HISTORY_FORMAT : sceneColorFormat(this.quality.hdrScene),
|
|
10879
11516
|
OIT_COMPOSITE_BLEND,
|
|
10880
11517
|
),
|
|
10881
11518
|
);
|
|
@@ -11265,18 +11902,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11265
11902
|
this.lightVolumeVertices.buffer,
|
|
11266
11903
|
this.lightVolumeFragments.buffer,
|
|
11267
11904
|
(name) =>
|
|
11268
|
-
name === '
|
|
11269
|
-
? { view: this.
|
|
11270
|
-
:
|
|
11271
|
-
? { view: this.peelView, sampler: this.volumeShadowSampler }
|
|
11272
|
-
: name === 'uDynamicShadowMap' && this.dynamicView !== null
|
|
11273
|
-
? { view: this.dynamicView, sampler: this.volumeShadowSampler }
|
|
11274
|
-
: /* The frame's own depth, snapshotted before the first volume of the frame draws.
|
|
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.
|
|
11275
11908
|
Null under a profile with no composite, and then `uSceneDepthEnabled` is 0 and
|
|
11276
11909
|
the stand-in is never sampled. */
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11910
|
+
name === 'uSceneDepth' && this.resolvedDepthView !== null
|
|
11911
|
+
? { view: this.resolvedDepthView, sampler: this.postDepthSampler }
|
|
11912
|
+
: this.volumeBlank,
|
|
11280
11913
|
);
|
|
11281
11914
|
}
|
|
11282
11915
|
|
|
@@ -11302,6 +11935,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11302
11935
|
private takeRefractSnapshot(): boolean {
|
|
11303
11936
|
if (this.reflectionPassActive || this.probePassActive) return false;
|
|
11304
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
|
+
}
|
|
11305
11944
|
/* The replay of an order-independent set runs after the scene's pass has ended, and `runOit`
|
|
11306
11945
|
took the copy for it before it began; there is no pass here to end and reopen. */
|
|
11307
11946
|
if (this.oitReplaying) return false;
|
|
@@ -11340,6 +11979,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11340
11979
|
snapshot.width,
|
|
11341
11980
|
snapshot.height,
|
|
11342
11981
|
]);
|
|
11982
|
+
/* The chain, between the passes, where nothing is attached: what frosted glass reads. */
|
|
11983
|
+
this.refractMips?.record(encoder);
|
|
11343
11984
|
const multisampled = this.compositeMsaa() !== null;
|
|
11344
11985
|
this.pass = encoder.beginRenderPass({
|
|
11345
11986
|
label: 'refract.snapshotTaken',
|
|
@@ -11701,8 +12342,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11701
12342
|
}
|
|
11702
12343
|
if (verb !== VERB_DRAW) return;
|
|
11703
12344
|
const command = this.commands.commands[nodeState(this.arena, at)];
|
|
11704
|
-
if (command === undefined
|
|
12345
|
+
if (command === undefined) return;
|
|
12346
|
+
this.issueCommand(pass, command);
|
|
12347
|
+
}
|
|
11705
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;
|
|
11706
12352
|
pass.setPipeline(command.pipeline);
|
|
11707
12353
|
if (command.offsetCount === 2) {
|
|
11708
12354
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB]);
|
|
@@ -11946,6 +12592,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11946
12592
|
}
|
|
11947
12593
|
|
|
11948
12594
|
endFrame(): void {
|
|
12595
|
+
this.inFrame = false;
|
|
11949
12596
|
if (this.surface.lost || this.encoder === null) return;
|
|
11950
12597
|
/*
|
|
11951
12598
|
* Anything still recorded goes now.
|
|
@@ -13212,6 +13859,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13212
13859
|
*
|
|
13213
13860
|
* What it gives up is one frame of history: the frame after a cut has no blur and no temporal
|
|
13214
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.
|
|
13215
13866
|
*/
|
|
13216
13867
|
cameraCut(): void {
|
|
13217
13868
|
this.hasPreviousView = false;
|
|
@@ -13421,9 +14072,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13421
14072
|
this.causticsVerts.writeBlock(vertexSlot, this.causticsVertBlock);
|
|
13422
14073
|
this.causticsFrags.writeBlock(fragmentSlot, this.causticsFragInts);
|
|
13423
14074
|
|
|
13424
|
-
const causticsPipe = causticsPipeline(
|
|
14075
|
+
const causticsPipe = causticsPipeline(
|
|
14076
|
+
this.blendedPipelines(),
|
|
14077
|
+
this.surface.device,
|
|
14078
|
+
this.causticsLayout,
|
|
14079
|
+
);
|
|
13425
14080
|
/* The pattern is generated in the shader from the two uniform blocks; nothing is sampled. */
|
|
13426
|
-
const causticsCommand = this.
|
|
14081
|
+
const causticsCommand = this.recordBlended(0);
|
|
13427
14082
|
if (causticsCommand !== null) {
|
|
13428
14083
|
causticsCommand.pipeline = causticsPipe;
|
|
13429
14084
|
causticsCommand.bindGroup = this.causticsBindGroup;
|
|
@@ -14418,6 +15073,33 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14418
15073
|
* The occlusion and bloom targets are half size. Both are blurs read back at full size, so a
|
|
14419
15074
|
* full-resolution intermediate buys nothing a bilinear fetch does not already give.
|
|
14420
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
|
+
|
|
14421
15103
|
private ensureComposite(width: number, height: number): void {
|
|
14422
15104
|
if (!this.hasComposite) return;
|
|
14423
15105
|
if (this.sceneColor !== null && this.frameWidth === width && this.frameHeight === height)
|
|
@@ -14471,13 +15153,39 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14471
15153
|
* the allocation the house rules are about. A frame that never refracts pays the memory and
|
|
14472
15154
|
* takes no copy.
|
|
14473
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;
|
|
14474
15162
|
this.refractSnapshot = device.createTexture({
|
|
14475
15163
|
label: 'refract.snapshot',
|
|
14476
|
-
size:
|
|
14477
|
-
|
|
14478
|
-
|
|
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
|
|
14479
15181
|
});
|
|
14480
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
|
+
);
|
|
14481
15189
|
/*
|
|
14482
15190
|
* **Every cached flat bind group is stale the moment this view changes**, and the failure is
|
|
14483
15191
|
* silent: `flatTextures` resolves `uRefractScene` to the one-texel white stand-in when there is
|
|
@@ -14619,10 +15327,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14619
15327
|
* that range — a step of 1/255 in how much scene shows through, below what a viewer can see
|
|
14620
15328
|
* against a blended surface.
|
|
14621
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];
|
|
14622
15334
|
this.oitAccum?.destroy();
|
|
14623
15335
|
this.oitAccum = device.createTexture({
|
|
14624
15336
|
label: 'post.oitAccum',
|
|
14625
|
-
size:
|
|
15337
|
+
size: oitSize,
|
|
14626
15338
|
format: 'rgba16float',
|
|
14627
15339
|
usage: USAGE,
|
|
14628
15340
|
});
|
|
@@ -14630,7 +15342,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14630
15342
|
this.oitReveal?.destroy();
|
|
14631
15343
|
this.oitReveal = device.createTexture({
|
|
14632
15344
|
label: 'post.oitReveal',
|
|
14633
|
-
size:
|
|
15345
|
+
size: oitSize,
|
|
14634
15346
|
format: 'r8unorm',
|
|
14635
15347
|
usage: USAGE,
|
|
14636
15348
|
});
|
|
@@ -14735,42 +15447,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14735
15447
|
...entry(AO_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
14736
15448
|
],
|
|
14737
15449
|
});
|
|
14738
|
-
|
|
14739
|
-
* The medium's two groups, holding the shadow views the march reads.
|
|
14740
|
-
*
|
|
14741
|
-
* **The stand-in has to read as unoccluded and not as absent**, which is `emptyTexture.ts`'s
|
|
14742
|
-
* point and `createLightVolumeBindGroup`'s: the dynamic layer is sampled unconditionally, and
|
|
14743
|
-
* `occlusion()` calls any stored depth below the receiver's a blocker — so a black stand-in
|
|
14744
|
-
* would put the whole medium in shadow, which is a picture rather than an error. `volumeBlank`
|
|
14745
|
-
* is the white one-texel texture already built for exactly this.
|
|
14746
|
-
*/
|
|
14747
|
-
const shadowPair = (
|
|
14748
|
-
b: { texture: number; sampler: number },
|
|
14749
|
-
view: GPUTextureView | null,
|
|
14750
|
-
): GPUBindGroupEntry[] => [
|
|
14751
|
-
{ binding: b.texture, resource: view ?? this.volumeBlank.view },
|
|
14752
|
-
{
|
|
14753
|
-
binding: b.sampler,
|
|
14754
|
-
resource: view === null ? this.volumeBlank.sampler : this.volumeShadowSampler,
|
|
14755
|
-
},
|
|
14756
|
-
];
|
|
14757
|
-
this.mediumBindGroup =
|
|
14758
|
-
this.resolvedDepthView === null
|
|
14759
|
-
? null
|
|
14760
|
-
: device.createBindGroup({
|
|
14761
|
-
label: 'post.mediumBindGroup',
|
|
14762
|
-
layout: this.mediumLayout,
|
|
14763
|
-
entries: [
|
|
14764
|
-
{
|
|
14765
|
-
binding: MEDIUM_UNIFORMS,
|
|
14766
|
-
resource: { buffer: this.mediumUniforms, size: MEDIUM_FRAG_SIZE },
|
|
14767
|
-
},
|
|
14768
|
-
...entry(MEDIUM_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
14769
|
-
...shadowPair(MEDIUM_TEXTURES.uStaticShadowMap, this.shadowView),
|
|
14770
|
-
...shadowPair(MEDIUM_TEXTURES.uPeeledShadowMap, this.peelView),
|
|
14771
|
-
...shadowPair(MEDIUM_TEXTURES.uDynamicShadowMap, this.dynamicView),
|
|
14772
|
-
],
|
|
14773
|
-
});
|
|
15450
|
+
this.mediumBindGroup = this.buildMediumBindGroup();
|
|
14774
15451
|
this.mediumUpsampleGroup =
|
|
14775
15452
|
this.resolvedDepthView === null || this.mediumTargetView === null
|
|
14776
15453
|
? null
|
|
@@ -15002,6 +15679,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15002
15679
|
* the 2026-08-13 rule asks for — one decision, bound twice.
|
|
15003
15680
|
*/
|
|
15004
15681
|
dispose(): void {
|
|
15682
|
+
this.motionPass?.dispose();
|
|
15683
|
+
this.motionPass = null;
|
|
15684
|
+
this.latePass?.dispose();
|
|
15685
|
+
this.latePass = null;
|
|
15005
15686
|
for (const texture of this.retiredTextures) texture.destroy();
|
|
15006
15687
|
this.retiredTextures.length = 0;
|
|
15007
15688
|
const passDevice = this.passDevice();
|
|
@@ -15018,6 +15699,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15018
15699
|
this.fieldComposer = null;
|
|
15019
15700
|
this.driftIndex?.destroy();
|
|
15020
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;
|
|
15021
15708
|
|
|
15022
15709
|
this.pass = null;
|
|
15023
15710
|
this.encoder = null;
|