@driftengine/core 4.5.0 → 4.6.1
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 +62 -0
- package/README.md +1 -1
- package/dist/geometry/boxCsg.d.ts +28 -0
- package/dist/geometry/boxCsg.js +139 -0
- package/dist/geometry/csg.d.ts +36 -0
- package/dist/geometry/csg.js +329 -0
- package/dist/geometry/meshBuilder.js +13 -0
- package/dist/geometry/meshConcat.js +1 -0
- package/dist/geometry/pixelFont.js +2 -0
- package/dist/geometry/solid.d.ts +61 -0
- package/dist/geometry/solid.js +152 -0
- package/dist/geometry/solidFlat.d.ts +35 -0
- package/dist/geometry/solidFlat.js +428 -0
- package/dist/geometry/solidHarness.d.ts +19 -0
- package/dist/geometry/solidHarness.js +66 -0
- package/dist/geometry/solidNormals.d.ts +17 -0
- package/dist/geometry/solidNormals.js +86 -0
- package/dist/geometry/solidRound.d.ts +41 -0
- package/dist/geometry/solidRound.js +323 -0
- package/dist/geometry/solidWriter.d.ts +16 -0
- package/dist/geometry/solidWriter.js +42 -0
- package/dist/index.d.ts +23 -2
- package/dist/index.js +13 -1
- package/dist/render/backend/api.d.ts +17 -3
- package/dist/render/backend/drawFog.d.ts +26 -0
- package/dist/render/backend/drawFog.js +30 -0
- package/dist/render/backend/materialChanges.d.ts +2 -1
- package/dist/render/backend/materialChanges.js +6 -1
- package/dist/render/backend/webgl2/instanced.d.ts +24 -5
- package/dist/render/backend/webgl2/instanced.js +72 -15
- package/dist/render/backend/webgl2/renderer.d.ts +122 -8
- package/dist/render/backend/webgl2/renderer.js +249 -128
- package/dist/render/backend/webgpu/buffers.d.ts +17 -0
- package/dist/render/backend/webgpu/buffers.js +34 -3
- package/dist/render/backend/webgpu/depthPass.js +5 -1
- package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +4 -1
- package/dist/render/backend/webgpu/flatPass.js +38 -9
- package/dist/render/backend/webgpu/glassTintPass.js +2 -1
- package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
- package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
- package/dist/render/backend/webgpu/instanced.d.ts +11 -1
- package/dist/render/backend/webgpu/instanced.js +21 -3
- package/dist/render/backend/webgpu/latePass.d.ts +6 -2
- package/dist/render/backend/webgpu/latePass.js +11 -4
- package/dist/render/backend/webgpu/motionPass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +100 -10
- package/dist/render/backend/webgpu/renderer.js +478 -214
- package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +17 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +77 -38
- package/dist/render/driftLight/bake.d.ts +7 -0
- package/dist/render/driftLight/bake.js +61 -56
- package/dist/render/driftLight/denseField.d.ts +46 -0
- package/dist/render/driftLight/denseField.js +109 -0
- package/dist/render/driftLight/lightField.d.ts +10 -5
- package/dist/render/driftLight/lightField.js +25 -19
- package/dist/render/driftLight/presence.d.ts +50 -0
- package/dist/render/driftLight/presence.js +43 -0
- package/dist/render/driftLight/uniforms.d.ts +3 -3
- package/dist/render/driftLight/uniforms.js +5 -6
- package/dist/render/driftLight/worldLightField.d.ts +41 -0
- package/dist/render/driftLight/worldLightField.js +66 -0
- package/dist/render/fixtureAtlas.d.ts +20 -0
- package/dist/render/fixtureAtlas.js +49 -0
- package/dist/render/instanceCull.d.ts +63 -0
- package/dist/render/instanceCull.js +160 -0
- package/dist/render/instances.d.ts +15 -0
- package/dist/render/lightBudget.d.ts +22 -33
- package/dist/render/lightBudget.js +24 -35
- package/dist/render/lightGrid.d.ts +42 -0
- package/dist/render/lightGrid.js +87 -0
- package/dist/render/mesh.d.ts +23 -24
- package/dist/render/mesh.js +56 -68
- package/dist/render/occlusion.d.ts +10 -0
- package/dist/render/occlusion.js +36 -7
- package/dist/render/pointLightSelection.d.ts +7 -0
- package/dist/render/pointLightSelection.js +29 -4
- package/dist/render/rainField.d.ts +13 -0
- package/dist/render/rainField.js +27 -5
- package/dist/render/recon/lateDepth.d.ts +11 -0
- package/dist/render/recon/lateDepth.js +70 -0
- package/dist/render/recon/resolve.d.ts +34 -4
- package/dist/render/recon/resolve.js +161 -20
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +2 -0
- package/dist/render/shaders/depth.js +12 -9
- package/dist/render/shaders/flat/driftLight.d.ts +1 -1
- package/dist/render/shaders/flat/driftLight.js +32 -7
- package/dist/render/shaders/flat/index.js +5 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +75 -23
- package/dist/render/shaders/flat/preamble.js +75 -20
- package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
- package/dist/render/shaders/flat/surfaceEffects.js +152 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +8 -8
- package/dist/render/shaders/generated/depth.wgsl.js +8 -8
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1320 -1320
- package/dist/render/shaders/generated/flat.wgsl.js +1359 -1345
- package/dist/render/shaders/generated/lightVolume.wgsl.js +18 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
- package/dist/render/shaders/generated/rush.wgsl.js +21 -16
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
- package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
- package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/text.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
- package/dist/render/shaders/recon/lateDepth.wgsl.js +38 -13
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
- package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
- package/dist/render/shaders/rush.js +11 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/sdfText.js +3 -2
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +18 -9
- package/dist/render/surfaceEffects.d.ts +83 -0
- package/dist/render/surfaceEffects.js +75 -0
- package/dist/render/surfaceScene.d.ts +8 -0
- package/dist/render/surfaceScene.js +8 -0
- package/dist/render/surfaceTexture.d.ts +18 -2
- package/dist/render/surfaceTexture.js +68 -15
- package/dist/render/temporalAa.d.ts +25 -6
- package/dist/render/temporalAa.js +45 -15
- package/dist/render/textureSource.d.ts +30 -0
- package/dist/render/textureSource.js +53 -0
- package/dist/render/vertexDefaults.js +2 -0
- package/dist/world/hlod.d.ts +104 -0
- package/dist/world/hlod.js +214 -0
- package/package.json +6 -6
- package/src/geometry/boxCsg.ts +186 -0
- package/src/geometry/csg.ts +384 -0
- package/src/geometry/meshBuilder.ts +15 -0
- package/src/geometry/meshConcat.ts +1 -0
- package/src/geometry/pixelFont.ts +2 -0
- package/src/geometry/solid.ts +189 -0
- package/src/geometry/solidFlat.ts +522 -0
- package/src/geometry/solidHarness.ts +83 -0
- package/src/geometry/solidNormals.ts +103 -0
- package/src/geometry/solidRound.ts +443 -0
- package/src/geometry/solidWriter.ts +58 -0
- package/src/index.ts +51 -1
- package/src/render/backend/api.ts +21 -2
- package/src/render/backend/drawFog.ts +31 -0
- package/src/render/backend/materialChanges.ts +9 -2
- package/src/render/backend/webgl2/instanced.ts +80 -14
- package/src/render/backend/webgl2/renderer.ts +311 -150
- package/src/render/backend/webgpu/buffers.ts +49 -3
- package/src/render/backend/webgpu/depthPass.ts +5 -1
- package/src/render/backend/webgpu/drawCommand.ts +6 -0
- package/src/render/backend/webgpu/flatPass.ts +41 -11
- package/src/render/backend/webgpu/glassTintPass.ts +2 -1
- package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
- package/src/render/backend/webgpu/instanced.ts +21 -3
- package/src/render/backend/webgpu/latePass.ts +12 -3
- package/src/render/backend/webgpu/motionPass.ts +2 -1
- package/src/render/backend/webgpu/renderer.ts +556 -239
- package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
- package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +90 -50
- package/src/render/driftLight/bake.ts +85 -62
- package/src/render/driftLight/denseField.ts +149 -0
- package/src/render/driftLight/lightField.ts +33 -18
- package/src/render/driftLight/presence.ts +69 -0
- package/src/render/driftLight/uniforms.ts +8 -9
- package/src/render/driftLight/worldLightField.ts +83 -0
- package/src/render/fixtureAtlas.ts +63 -0
- package/src/render/instanceCull.ts +205 -0
- package/src/render/instances.ts +16 -0
- package/src/render/lightBudget.ts +25 -36
- package/src/render/lightGrid.ts +130 -0
- package/src/render/mesh.ts +64 -72
- package/src/render/occlusion.ts +51 -7
- package/src/render/pointLightSelection.ts +72 -4
- package/src/render/rainField.ts +39 -5
- package/src/render/recon/lateDepth.ts +83 -0
- package/src/render/recon/resolve.ts +206 -20
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +4 -0
- package/src/render/shaders/depth.ts +12 -9
- package/src/render/shaders/flat/driftLight.ts +32 -7
- package/src/render/shaders/flat/index.ts +5 -3
- package/src/render/shaders/flat/main.ts +75 -23
- package/src/render/shaders/flat/preamble.ts +75 -20
- package/src/render/shaders/flat/surfaceEffects.ts +152 -0
- package/src/render/shaders/generated/depth.wgsl.ts +8 -8
- package/src/render/shaders/generated/flat.wgsl.ts +1361 -1345
- package/src/render/shaders/generated/lightVolume.wgsl.ts +20 -4
- package/src/render/shaders/generated/rush.wgsl.ts +21 -16
- package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
- package/src/render/shaders/generated/text.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
- package/src/render/shaders/recon/lateDepth.wgsl.ts +38 -13
- package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
- package/src/render/shaders/rush.ts +11 -1
- package/src/render/shaders/sdfText.ts +3 -2
- package/src/render/shaders/text.ts +18 -9
- package/src/render/surfaceEffects.ts +132 -0
- package/src/render/surfaceScene.ts +16 -0
- package/src/render/surfaceTexture.ts +110 -19
- package/src/render/temporalAa.ts +78 -14
- package/src/render/textureSource.ts +72 -0
- package/src/render/vertexDefaults.ts +2 -0
- package/src/world/hlod.ts +290 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mat4 } from 'gl-matrix';
|
|
2
2
|
import { probeFaceRange } from '../../reflectionProbe.js';
|
|
3
|
-
import { TEMPORAL_HISTORY_BLEND, TemporalHistory, jitterOffset, jitterProjection, } from '../../temporalAa.js';
|
|
3
|
+
import { TEMPORAL_FLICKER, TEMPORAL_HISTORY_BLEND, TemporalHistory, flickerRule, jitterOffset, jitterProjection, } from '../../temporalAa.js';
|
|
4
4
|
import { OIT_MULTISAMPLE_REFUSAL } from '../../orderIndependent.js';
|
|
5
5
|
import { TranslucentQueue } from '../../translucentQueue.js';
|
|
6
6
|
import { DEPTH_CLEAR, MAX_DEPTH_LAYER, REVERSED_DEPTH } from '../../depthConvention.js';
|
|
@@ -36,6 +36,8 @@ import { ProbeSweeps } from '../../probeSweeps.js';
|
|
|
36
36
|
import { ggxMaxLevelFor, irradianceLevelFor, octahedralEdgeFor } from '../../prefilterEnvMap.js';
|
|
37
37
|
import { environmentTexels } from './environmentTexels.js';
|
|
38
38
|
import { packIesAtlas } from '../../iesProfile.js';
|
|
39
|
+
import { fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.js';
|
|
40
|
+
import { resolveSurfaceScene } from '../../surfaceScene.js';
|
|
39
41
|
import { COOKIE_TILE } from '../../lightBudget.js';
|
|
40
42
|
import { FULL_LIGHT_BUDGET } from '../../uniformVectorBudget.js';
|
|
41
43
|
import { toHalfFloats } from '../../halfFloat.js';
|
|
@@ -96,6 +98,7 @@ import { LIGHT_VOLUME_VERT_FIELDS, LIGHT_VOLUME_VERT_SIZE, createLightVolumeBind
|
|
|
96
98
|
import { aimReflection, createGpuReflection } from './reflectionPass.js';
|
|
97
99
|
import { FrameBudget } from '../budget.js';
|
|
98
100
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.js';
|
|
101
|
+
import { FOG_RECEDE, fogModeOf } from '../drawFog.js';
|
|
99
102
|
import { DYNAMIC_ALIGNMENT as DYNAMIC_UNIFORM_ALIGNMENT, UniformRing } from './uniformRing.js';
|
|
100
103
|
import { DecalQueue, MAX_DRAWN_DECALS } from '../../decalQueue.js';
|
|
101
104
|
import { DistanceFieldScene } from '../../gi/fieldScene.js';
|
|
@@ -122,6 +125,8 @@ import { decalScissor } from '../../decalProjector.js';
|
|
|
122
125
|
import { createCommandPool, resetPool, takeCommand } from './drawCommand.js';
|
|
123
126
|
import { RESOURCE_COUNT, createArena, createFlushSchedule, keptNodes, maskOf, nodeCount, nodeState, nodeVerb, recordNode, resetArena, schedule, scheduleFlush, scratchFor, } from '../../frame/index.js';
|
|
124
127
|
import { createFrustum, frustumFromViewProjection } from '../../../math/frustum.js';
|
|
128
|
+
import { batchBoxVisible, cullsInstances } from '../../instanceCull.js';
|
|
129
|
+
import { InstanceCullPass, MAX_CULL_SLOTS } from './instanceCullPass.js';
|
|
125
130
|
import { OcclusionBuffer } from '../../occlusion.js';
|
|
126
131
|
import { boundsVisible } from '../../visibility.js';
|
|
127
132
|
import { createPassRegistry, drainRegistry, passAt, registerIn, unregisterIn } from '../../pass.js';
|
|
@@ -131,6 +136,7 @@ import { computeAt, createComputeRegistry, } from '../../compute.js';
|
|
|
131
136
|
import { GpuInstancedBatch } from './instanced.js';
|
|
132
137
|
import { cutoutOf } from '../../cutoutCaster.js';
|
|
133
138
|
import { LightField } from '../../driftLight/lightField.js';
|
|
139
|
+
import { WorldLightField } from '../../driftLight/worldLightField.js';
|
|
134
140
|
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.js';
|
|
135
141
|
/**
|
|
136
142
|
* How many draws one frame may make before the ring is full.
|
|
@@ -1295,6 +1301,12 @@ export class WebGPURenderer {
|
|
|
1295
1301
|
* what the layout already declares.
|
|
1296
1302
|
*/
|
|
1297
1303
|
bloomGroups = [];
|
|
1304
|
+
/**
|
|
1305
|
+
* The picture the bloom reads, and its size: the scene target, or while reconstructing the
|
|
1306
|
+
* resolved picture at the drawing buffer's size. See `rebuildBloomGroups`.
|
|
1307
|
+
*/
|
|
1308
|
+
bloomSourceWidth = 1;
|
|
1309
|
+
bloomSourceHeight = 1;
|
|
1298
1310
|
frameWidth = 1;
|
|
1299
1311
|
frameHeight = 1;
|
|
1300
1312
|
/**
|
|
@@ -1453,6 +1465,18 @@ export class WebGPURenderer {
|
|
|
1453
1465
|
*/
|
|
1454
1466
|
reconHistories = [null, null];
|
|
1455
1467
|
reconHistoryViews = [null, null];
|
|
1468
|
+
/**
|
|
1469
|
+
* Each output pixel's flicker record, a pair swapped with the histories: the temporal resolve's
|
|
1470
|
+
* anti-flicker, over the reconstruction's own period (`recon/resolve.ts`).
|
|
1471
|
+
*/
|
|
1472
|
+
reconFlickers = [null, null];
|
|
1473
|
+
reconFlickerViews = [null, null];
|
|
1474
|
+
/** Each output pixel's held depth swing, a pair of its own: see `recon/resolve.ts`. */
|
|
1475
|
+
reconSwings = [null, null];
|
|
1476
|
+
reconSwingViews = [null, null];
|
|
1477
|
+
/** The anti-flicker's rule for the period last asked for, kept so a frame makes none. */
|
|
1478
|
+
reconFlickerPhases = 0;
|
|
1479
|
+
reconFlickerRule = TEMPORAL_FLICKER;
|
|
1456
1480
|
reconWrite = 0;
|
|
1457
1481
|
reconHasHistory = false;
|
|
1458
1482
|
reconShown = null;
|
|
@@ -1486,6 +1510,9 @@ export class WebGPURenderer {
|
|
|
1486
1510
|
/** Last frame's raw view-projection and its inverse, and the two eyes. */
|
|
1487
1511
|
reconPreviousRawViewProj = mat4.create();
|
|
1488
1512
|
reconInverseViewProj = mat4.create();
|
|
1513
|
+
/** The inverse and the reprojection, multiplied in double precision. See `runReconstruction`. */
|
|
1514
|
+
reconInverse64 = new Float64Array(16);
|
|
1515
|
+
reconReprojection64 = new Float64Array(16);
|
|
1489
1516
|
reconPreviousInverseViewProj = mat4.create();
|
|
1490
1517
|
reconEye = new Float32Array(3);
|
|
1491
1518
|
reconPreviousEye = new Float32Array(3);
|
|
@@ -1631,6 +1658,8 @@ export class WebGPURenderer {
|
|
|
1631
1658
|
/** Said once for the whole renderer. See `refuseOitMultisampled`. */
|
|
1632
1659
|
oitMultisampleRefused = false;
|
|
1633
1660
|
oitReplaying = false;
|
|
1661
|
+
/** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
|
|
1662
|
+
surfaceScene = new Float32Array(4);
|
|
1634
1663
|
/** Which buffer the replay is filling, which the pipeline key must carry. */
|
|
1635
1664
|
oitMode = 'none';
|
|
1636
1665
|
/**
|
|
@@ -1906,6 +1935,8 @@ export class WebGPURenderer {
|
|
|
1906
1935
|
return this.ringList;
|
|
1907
1936
|
}
|
|
1908
1937
|
flushRings() {
|
|
1938
|
+
/* Culls first: a submit that follows may carry a draw reading what they write. */
|
|
1939
|
+
this.instanceCulls?.flush();
|
|
1909
1940
|
for (const ring of this.rings())
|
|
1910
1941
|
ring.flush();
|
|
1911
1942
|
}
|
|
@@ -1951,7 +1982,7 @@ export class WebGPURenderer {
|
|
|
1951
1982
|
if (!this.canDraw())
|
|
1952
1983
|
return;
|
|
1953
1984
|
const geometry = mesh;
|
|
1954
|
-
if (geometry.vertexBuffers === undefined)
|
|
1985
|
+
if (geometry.vertexBuffers === undefined || geometry.indexCount === 0)
|
|
1955
1986
|
return;
|
|
1956
1987
|
const vertexSlot = this.filmVerts.allocate();
|
|
1957
1988
|
const fragmentSlot = this.filmFrags.allocate();
|
|
@@ -2102,6 +2133,10 @@ export class WebGPURenderer {
|
|
|
2102
2133
|
createSurfaceTexture(source, options = {}) {
|
|
2103
2134
|
return new GpuSurfaceTexture(this.surface.device, this.pipelines, source, options);
|
|
2104
2135
|
}
|
|
2136
|
+
/** Several images of one size as an array's layers. See `RendererApi.createSurfaceTextureArray`. */
|
|
2137
|
+
createSurfaceTextureArray(sources, options = {}) {
|
|
2138
|
+
return new GpuSurfaceTexture(this.surface.device, this.pipelines, sources, options);
|
|
2139
|
+
}
|
|
2105
2140
|
/**
|
|
2106
2141
|
* GPU textures an `updateSurfaceTexture` replaced, destroyed when the next frame begins.
|
|
2107
2142
|
*
|
|
@@ -2367,6 +2402,19 @@ export class WebGPURenderer {
|
|
|
2367
2402
|
f[this.materialField('uGrain')] = Math.min(1, Math.max(0, amount));
|
|
2368
2403
|
});
|
|
2369
2404
|
}
|
|
2405
|
+
/**
|
|
2406
|
+
* How much of the surfaces drawn next survives a screen-door dither. See `renderer.ts`.
|
|
2407
|
+
*
|
|
2408
|
+
* Written straight into the scratch block rather than through `material()`'s callback, because
|
|
2409
|
+
* this one is called per crossfading region per frame and a closure per call is an allocation.
|
|
2410
|
+
*/
|
|
2411
|
+
setDitherFade(amount) {
|
|
2412
|
+
if (this.surface.lost)
|
|
2413
|
+
return;
|
|
2414
|
+
/* The second half of `uWriteMode`, which the order-independent weight shares. */
|
|
2415
|
+
this.perFrameFloats[this.materialField('uWriteMode') + 1] = Math.min(1, Math.max(-1, amount));
|
|
2416
|
+
this.materials.dirty();
|
|
2417
|
+
}
|
|
2370
2418
|
/**
|
|
2371
2419
|
* How strong the microscopic relief on the surfaces drawn next is, and how coarse.
|
|
2372
2420
|
*
|
|
@@ -3417,8 +3465,9 @@ export class WebGPURenderer {
|
|
|
3417
3465
|
return;
|
|
3418
3466
|
/* Geometry that has not all arrived is not drawn, which is the surface's contract and what
|
|
3419
3467
|
every other mesh verb here and the other backend's volume already kept. This one drew a
|
|
3420
|
-
beam from a buffer still filling, and counted it, until 2026-09-19. See `Mesh.complete`.
|
|
3421
|
-
|
|
3468
|
+
beam from a buffer still filling, and counted it, until 2026-09-19. See `Mesh.complete`. A
|
|
3469
|
+
hull with no triangles draws nothing either: see `GpuMesh.indexCount`. */
|
|
3470
|
+
if (!mesh.complete || mesh.indexCount === 0)
|
|
3422
3471
|
return;
|
|
3423
3472
|
/* At zero it draws nothing rather than adding black, so a caller may keep the call in. That
|
|
3424
3473
|
now covers a clear night too: a beam with no medium to light is not drawn at all. */
|
|
@@ -3978,6 +4027,8 @@ export class WebGPURenderer {
|
|
|
3978
4027
|
waterLayout;
|
|
3979
4028
|
/** The one-pixel stand-in, kept so the water bind group can be rebuilt without one. */
|
|
3980
4029
|
blankView;
|
|
4030
|
+
/** The blank texel as a one-layer array: the stand-in every surface-texture binding takes. */
|
|
4031
|
+
blankSurfaceView;
|
|
3981
4032
|
blankSampler;
|
|
3982
4033
|
/**
|
|
3983
4034
|
* A slot per body, not one buffer rewritten before each draw.
|
|
@@ -4376,7 +4427,7 @@ export class WebGPURenderer {
|
|
|
4376
4427
|
if (cutout !== null && cutoutGroup !== null)
|
|
4377
4428
|
this.writeCutout(slot, cutout, DEPTH_CUTOUT_VERT_FIELDS);
|
|
4378
4429
|
const geometry = mesh;
|
|
4379
|
-
if (geometry.vertexBuffers === undefined)
|
|
4430
|
+
if (geometry.vertexBuffers === undefined || geometry.indexCount === 0)
|
|
4380
4431
|
return;
|
|
4381
4432
|
const cuts = cutoutGroup !== null;
|
|
4382
4433
|
/* A two-sided caster casts from whichever face the light sees: nothing culled. */
|
|
@@ -4405,7 +4456,7 @@ export class WebGPURenderer {
|
|
|
4405
4456
|
return;
|
|
4406
4457
|
const gpuBatch = batch;
|
|
4407
4458
|
const geometry = gpuBatch.mesh;
|
|
4408
|
-
if (geometry.vertexBuffers === undefined)
|
|
4459
|
+
if (geometry.vertexBuffers === undefined || geometry.indexCount === 0)
|
|
4409
4460
|
return;
|
|
4410
4461
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
4411
4462
|
if (count === 0)
|
|
@@ -4460,7 +4511,7 @@ export class WebGPURenderer {
|
|
|
4460
4511
|
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uModel.offset, model);
|
|
4461
4512
|
this.writeShadowWind(slot);
|
|
4462
4513
|
const geometry = mesh;
|
|
4463
|
-
if (geometry.vertexBuffers === undefined)
|
|
4514
|
+
if (geometry.vertexBuffers === undefined || geometry.indexCount === 0)
|
|
4464
4515
|
return;
|
|
4465
4516
|
pass.setPipeline(depthPipeline(this.pipelines, this.surface.device, this.shadowSkinnedLayout, `depth-skinned|${this.depthCullMode}|${geometry.key ?? ''}`, this.presentOf(geometry), this.depthCullMode, true));
|
|
4466
4517
|
pass.setBindGroup(0, this.shadowSkinnedGroup(view, paletteSlot), [slot]);
|
|
@@ -4482,7 +4533,7 @@ export class WebGPURenderer {
|
|
|
4482
4533
|
if (pass === null || data.count === 0)
|
|
4483
4534
|
return;
|
|
4484
4535
|
const geometry = scatter;
|
|
4485
|
-
if (geometry.vertexBuffers === undefined)
|
|
4536
|
+
if (geometry.vertexBuffers === undefined || geometry.indexCount === 0)
|
|
4486
4537
|
return;
|
|
4487
4538
|
const v = this.scatterDepthFloats;
|
|
4488
4539
|
const at = (name) => (SCATTER_DEPTH_FIELDS[name]?.offset ?? -4) / 4;
|
|
@@ -4865,6 +4916,9 @@ export class WebGPURenderer {
|
|
|
4865
4916
|
* `uStaticShadowMap` rather than by anything here changing shape.
|
|
4866
4917
|
*/
|
|
4867
4918
|
const blankView = blank.createView();
|
|
4919
|
+
/* The same texel as a one-layer array, for the four material maps, which are arrays now. */
|
|
4920
|
+
const blankSurfaceView = blank.createView({ dimension: '2d-array' });
|
|
4921
|
+
this.blankSurfaceView = blankSurfaceView;
|
|
4868
4922
|
const blankSampler = device.createSampler({ label: 'flat.sampler' });
|
|
4869
4923
|
/*
|
|
4870
4924
|
* A one-texel integer stand-in for the froxel table.
|
|
@@ -5037,7 +5091,7 @@ export class WebGPURenderer {
|
|
|
5037
5091
|
if (name === 'uNormalMap') {
|
|
5038
5092
|
const map = this.normalMap;
|
|
5039
5093
|
return map === null
|
|
5040
|
-
? { view:
|
|
5094
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
5041
5095
|
: { view: map.view, sampler: map.sampler };
|
|
5042
5096
|
}
|
|
5043
5097
|
/* The caller's ORM map, or the stand-in, for the reason `uNormalMap` gives just above. */
|
|
@@ -5045,21 +5099,26 @@ export class WebGPURenderer {
|
|
|
5045
5099
|
if (name === 'uEmissiveMap') {
|
|
5046
5100
|
const map = this.emissiveMap;
|
|
5047
5101
|
return map === null
|
|
5048
|
-
? { view:
|
|
5102
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
5049
5103
|
: { view: map.view, sampler: map.sampler };
|
|
5050
5104
|
}
|
|
5051
5105
|
if (name === 'uOrmMap') {
|
|
5052
5106
|
const map = this.ormMap;
|
|
5053
5107
|
return map === null
|
|
5054
|
-
? { view:
|
|
5108
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
5055
5109
|
: { view: map.view, sampler: map.sampler };
|
|
5056
5110
|
}
|
|
5057
5111
|
if (name === 'uAlbedo') {
|
|
5058
5112
|
const albedo = this.albedo;
|
|
5059
5113
|
return albedo === null
|
|
5060
|
-
? { view:
|
|
5114
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
5061
5115
|
: { view: albedo.view, sampler: albedo.sampler };
|
|
5062
5116
|
}
|
|
5117
|
+
/* The albedo's effects table, or a one-texel stand-in the shader's size test reads as none.
|
|
5118
|
+
The albedo is the group's key, so its table needs no key of its own. */
|
|
5119
|
+
if (name === 'uSurfaceEffects') {
|
|
5120
|
+
return { view: this.albedo?.effectsView ?? blankView, sampler: blankSampler };
|
|
5121
|
+
}
|
|
5063
5122
|
/*
|
|
5064
5123
|
* The room, once a bake has filled it. Unbaked reads as the blank cube: nothing to mirror.
|
|
5065
5124
|
*
|
|
@@ -5091,18 +5150,8 @@ export class WebGPURenderer {
|
|
|
5091
5150
|
* this one is a curve sampled between two angles rather than data addressed by index, so
|
|
5092
5151
|
* interpolation across a row is the point. `postSampler` is linear and clamped.
|
|
5093
5152
|
*/
|
|
5094
|
-
if (name === '
|
|
5095
|
-
return { view: this.
|
|
5096
|
-
}
|
|
5097
|
-
/*
|
|
5098
|
-
* The cookie atlas, or the single white texel a scene with no cookie falls back to.
|
|
5099
|
-
*
|
|
5100
|
-
* A filtering sampler, like the photometric atlas and unlike the froxel table: a cookie is
|
|
5101
|
-
* an image and its whole point is that it is soft at the edges. The shader's own half-texel
|
|
5102
|
-
* inset is what keeps one tile out of the next; a clamp cannot, the seam being interior.
|
|
5103
|
-
*/
|
|
5104
|
-
if (name === 'uCookieAtlas') {
|
|
5105
|
-
return { view: this.cookieView ?? blankView, sampler: iesSampler };
|
|
5153
|
+
if (name === 'uFixtureAtlas') {
|
|
5154
|
+
return { view: this.fixtureAtlasView(), sampler: iesSampler };
|
|
5106
5155
|
}
|
|
5107
5156
|
/*
|
|
5108
5157
|
* DriftLight's brick index, read by `texelFetch` alone, so it shares the shadow group's
|
|
@@ -5622,6 +5671,8 @@ export class WebGPURenderer {
|
|
|
5622
5671
|
continue;
|
|
5623
5672
|
present[String(attribute.name)] = key.includes(`:${String(attribute.name)}`);
|
|
5624
5673
|
}
|
|
5674
|
+
/* Not a layout entry of its own — it widens `uvs` — so read from the key beside them. */
|
|
5675
|
+
present['layers'] = key.includes(':layers');
|
|
5625
5676
|
return present;
|
|
5626
5677
|
}
|
|
5627
5678
|
/**
|
|
@@ -5633,6 +5684,7 @@ export class WebGPURenderer {
|
|
|
5633
5684
|
* uncaught exceptions a second in somebody's error reporting.
|
|
5634
5685
|
*/
|
|
5635
5686
|
beginFrame(clearColor) {
|
|
5687
|
+
this.cullFrame += 1;
|
|
5636
5688
|
/* Whatever an update replaced during the last frame; nothing recorded can read it now. */
|
|
5637
5689
|
for (const texture of this.retiredTextures)
|
|
5638
5690
|
texture.destroy();
|
|
@@ -5890,6 +5942,15 @@ export class WebGPURenderer {
|
|
|
5890
5942
|
present[String(attribute.name)] = supplied;
|
|
5891
5943
|
key += supplied ? `:${String(attribute.name)}` : '';
|
|
5892
5944
|
}
|
|
5945
|
+
/*
|
|
5946
|
+
* Texture-array layers widen the UV field to three floats (`LAYERED_UVS`), which is a different
|
|
5947
|
+
* vertex layout, so the key has to say so or a layered and a plain mesh would share a pipeline
|
|
5948
|
+
* and one of them would read every field after the UVs from the wrong offset.
|
|
5949
|
+
*/
|
|
5950
|
+
if (data.layers !== undefined) {
|
|
5951
|
+
present['layers'] = true;
|
|
5952
|
+
key += ':layers';
|
|
5953
|
+
}
|
|
5893
5954
|
const fullKey = `${this.variant}|${key}`;
|
|
5894
5955
|
/*
|
|
5895
5956
|
* **For every target this mesh can land on, which is two when the overlay is its own.**
|
|
@@ -6081,46 +6142,17 @@ export class WebGPURenderer {
|
|
|
6081
6142
|
* Its sequence is its own: `reconJitterPhases` takes eight positions for every output pixel a
|
|
6082
6143
|
* render pixel covers, where the temporal resolve's eight are enough at one to one.
|
|
6083
6144
|
*/
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
* advanced here: a frame binding its mesh pass twice is one frame of the sequence.
|
|
6093
|
-
*
|
|
6094
|
-
* **The y offset is negated on the way in, and that is not a preference.** This shifts the
|
|
6095
|
-
* *corrected* matrix, whose y `CLIP_CORRECTION` has already negated so that the negation
|
|
6096
|
-
* every generated vertex stage ends with cancels out — §3 row 56. So an offset added here
|
|
6097
|
-
* arrives on screen with its sign reversed, while x, which nothing negates, arrives as
|
|
6098
|
-
* given. The temporal resolve cannot see this because its shader never reads the offset; the
|
|
6099
|
-
* reconstruction reads it at every texel it unprojects, and a sign error there moves every
|
|
6100
|
-
* sample two jitters away from where the depth says it is.
|
|
6101
|
-
*/
|
|
6102
|
-
this.viewProj = jitterProjection(this.jitteredViewProj, this.correctedViewProj, this.reconJitter[0], -this.reconJitter[1], this.renderWidth, this.renderHeight);
|
|
6103
|
-
this.temporalJittering = false;
|
|
6104
|
-
this.temporalHistoryUsable = false;
|
|
6105
|
-
}
|
|
6106
|
-
this.temporalJittering =
|
|
6107
|
-
!this.reconstructing &&
|
|
6108
|
-
this.quality.temporalAa &&
|
|
6109
|
-
this.quality.screenEffects &&
|
|
6110
|
-
!this.reflectionPassActive;
|
|
6111
|
-
if (this.temporalJittering) {
|
|
6112
|
-
/* The scene's size, not the drawing buffer's: the history is a picture of the render. */
|
|
6113
|
-
this.temporalHistoryUsable = this.temporalHistory.openFrame(this.renderWidth, this.renderHeight);
|
|
6114
|
-
const [jx, jy] = jitterOffset(this.temporalHistory.frameIndex);
|
|
6115
|
-
this.temporalJitterX = jx;
|
|
6116
|
-
this.temporalJitterY = jy;
|
|
6117
|
-
/* A jitter is half a *render* texel, which is what the sequence is spread over. */
|
|
6118
|
-
this.viewProj = jitterProjection(this.jitteredViewProj, this.correctedViewProj, jx, jy, this.renderWidth, this.renderHeight);
|
|
6119
|
-
}
|
|
6120
|
-
else if (!this.reconstructing) {
|
|
6121
|
-
this.temporalHistoryUsable = false;
|
|
6145
|
+
/*
|
|
6146
|
+
* **After the present there is no jitter to add and no temporal frame to open.** A mesh pass
|
|
6147
|
+
* bound then is the interface's, drawn over a picture already resolved: jittered, a map and every
|
|
6148
|
+
* edge of it moved by a fraction of a pixel each frame with nothing to resolve it; and opening a
|
|
6149
|
+
* second temporal frame stepped the sequence twice a frame, so the world saw every other
|
|
6150
|
+
* position and the anti-flicker's period never started.
|
|
6151
|
+
*/
|
|
6152
|
+
if (this.framePresented)
|
|
6122
6153
|
this.viewProj = this.correctedViewProj;
|
|
6123
|
-
|
|
6154
|
+
else
|
|
6155
|
+
this.jitterFrame();
|
|
6124
6156
|
/*
|
|
6125
6157
|
* The frustum, once a frame, and from the **uncorrected** matrix.
|
|
6126
6158
|
*
|
|
@@ -6213,8 +6245,11 @@ export class WebGPURenderer {
|
|
|
6213
6245
|
* frame either way, because an unwritten one is four zeroes and `dot(p, 0) < 0` is false,
|
|
6214
6246
|
* so the term would silently pass everything the first time somebody enabled it.
|
|
6215
6247
|
*/
|
|
6216
|
-
|
|
6217
|
-
|
|
6248
|
+
/* The mirror's plane in a reflection pass and zero, which clips nothing, in every other: the lit
|
|
6249
|
+
stage has no flag beside the plane. See `uClipPlane` in `flat/preamble.ts`. */
|
|
6250
|
+
f.set(this.reflectionPassActive ? (this.reflection?.clipPlane ?? NO_CLIP_PLANE) : NO_CLIP_PLANE, at('uClipPlane'));
|
|
6251
|
+
resolveSurfaceScene(env, this.surfaceScene);
|
|
6252
|
+
f.set(this.surfaceScene, at('uSurfaceScene'));
|
|
6218
6253
|
/*
|
|
6219
6254
|
* The material and grading terms, matching `renderer.ts` value for value.
|
|
6220
6255
|
*
|
|
@@ -6234,6 +6269,9 @@ export class WebGPURenderer {
|
|
|
6234
6269
|
*/
|
|
6235
6270
|
f[at('uGrain')] = 1;
|
|
6236
6271
|
f[at('uReflectivity')] = 0;
|
|
6272
|
+
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
6273
|
+
f[at('uWriteMode')] = 0;
|
|
6274
|
+
f[at('uWriteMode') + 1] = 0;
|
|
6237
6275
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
6238
6276
|
f[at('uEnvironmentGain')] = 1;
|
|
6239
6277
|
/*
|
|
@@ -6753,7 +6791,7 @@ export class WebGPURenderer {
|
|
|
6753
6791
|
* **Recorded rather than drawn**, when the effect is on and this is not already the replay.
|
|
6754
6792
|
* Everything is copied — see `TranslucentQueue`, and the scratch matrix that made it necessary.
|
|
6755
6793
|
*/
|
|
6756
|
-
if (this.oitActive && !this.oitReplaying) {
|
|
6794
|
+
if (this.oitActive && !this.oitReplaying && options.additive !== true) {
|
|
6757
6795
|
this.translucentQueue.record(mesh, model, opacity, options);
|
|
6758
6796
|
return;
|
|
6759
6797
|
}
|
|
@@ -6786,8 +6824,9 @@ export class WebGPURenderer {
|
|
|
6786
6824
|
submitMesh(mesh, model, tint, opacity, blend, options = {}) {
|
|
6787
6825
|
if (!this.canDraw() || this.viewProj === null)
|
|
6788
6826
|
return;
|
|
6789
|
-
/* Geometry that has not all arrived is not drawn. See `GpuMesh.complete
|
|
6790
|
-
|
|
6827
|
+
/* Geometry that has not all arrived is not drawn, nor a mesh with none. See `GpuMesh.complete`
|
|
6828
|
+
and `GpuMesh.indexCount`. */
|
|
6829
|
+
if (!mesh.complete || mesh.indexCount === 0)
|
|
6791
6830
|
return;
|
|
6792
6831
|
const slot = this.perDraw.allocate();
|
|
6793
6832
|
if (slot === null) {
|
|
@@ -6798,7 +6837,10 @@ export class WebGPURenderer {
|
|
|
6798
6837
|
return;
|
|
6799
6838
|
}
|
|
6800
6839
|
/* A blended draw landing after the upscale is drawn into an unjittered picture. */
|
|
6801
|
-
|
|
6840
|
+
/* Unless it moves with what it lies on, which the resolve reconstructs it with. See
|
|
6841
|
+
`TranslucentMeshOptions.reconstructed`. */
|
|
6842
|
+
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
6843
|
+
const late = lands || (blend && this.oitReplaying && this.reconstructing);
|
|
6802
6844
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uViewProj.offset, late ? this.correctedViewProj : this.viewProj);
|
|
6803
6845
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
6804
6846
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
@@ -6832,19 +6874,19 @@ export class WebGPURenderer {
|
|
|
6832
6874
|
this.perFrameFloats[this.materialField('uOpacity')] = opacity;
|
|
6833
6875
|
/* Material state exactly like `uOpacity`, dirtied for the accumulation pass and restored
|
|
6834
6876
|
below — the revealage pass draws with it at zero and its colour multiplied away. */
|
|
6835
|
-
this.perFrameFloats[this.materialField('
|
|
6877
|
+
this.perFrameFloats[this.materialField('uWriteMode')] = this.oitMode === 'accum' ? 1 : 0;
|
|
6836
6878
|
}
|
|
6837
6879
|
/* Same shape as `dimmed`: a slot is only worth taking when either differs from the default
|
|
6838
6880
|
`bindMeshPass` already wrote, and both are put back together once the draw is submitted. */
|
|
6839
6881
|
const lit = options.lit ?? true;
|
|
6840
|
-
|
|
6882
|
+
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
6883
|
+
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
6841
6884
|
const toneMapped = options.toneMapped ?? true;
|
|
6842
|
-
const unlitOrUnfogged = !lit ||
|
|
6885
|
+
const unlitOrUnfogged = !lit || fog !== FOG_RECEDE;
|
|
6843
6886
|
if (unlitOrUnfogged) {
|
|
6844
6887
|
if (!lit)
|
|
6845
6888
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
|
|
6846
|
-
|
|
6847
|
-
this.perFrameInts[this.materialField('uFogEnabled')] = 0;
|
|
6889
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = fog;
|
|
6848
6890
|
}
|
|
6849
6891
|
/* `1` is sRGB alone: the conversion without the curve. `Math.min` rather than a literal, so
|
|
6850
6892
|
a renderer asked for `none` stays at none. See `TranslucentMeshOptions.toneMapped`. */
|
|
@@ -6890,7 +6932,9 @@ export class WebGPURenderer {
|
|
|
6890
6932
|
*/
|
|
6891
6933
|
const depthWrite = options.depthWrite ?? true;
|
|
6892
6934
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
6893
|
-
|
|
6935
|
+
/* Added rather than blended over: its own blend state, so its own pipeline. */
|
|
6936
|
+
const adds = blend && options.additive === true;
|
|
6937
|
+
const key = `${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
6894
6938
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
6895
6939
|
/* The two order-independent buffers are two formats and two blend states, so a draw into
|
|
6896
6940
|
one must never share a cached pipeline with the same mesh drawn into the other. */
|
|
@@ -6919,14 +6963,14 @@ export class WebGPURenderer {
|
|
|
6919
6963
|
* pays the compile it was trying to avoid. That is a slow frame; the alternative is a
|
|
6920
6964
|
* thrown error in the middle of one.
|
|
6921
6965
|
*/
|
|
6922
|
-
const pipelines = blend ? this.blendedPipelines() : this.targetPipelines();
|
|
6966
|
+
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
6923
6967
|
let pipeline = pipelines.peek(key);
|
|
6924
6968
|
if (pipeline === undefined) {
|
|
6925
6969
|
const present = this.meshPresent.get(base);
|
|
6926
6970
|
if (present === undefined) {
|
|
6927
6971
|
throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
|
|
6928
6972
|
}
|
|
6929
|
-
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided);
|
|
6973
|
+
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, adds ? 'additive' : blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided);
|
|
6930
6974
|
}
|
|
6931
6975
|
/*
|
|
6932
6976
|
* Guards the draw call only, not the restore below.
|
|
@@ -6951,7 +6995,9 @@ export class WebGPURenderer {
|
|
|
6951
6995
|
* the uniform rings are the state arena this design would otherwise have had to build —
|
|
6952
6996
|
* so the command carries them as its two dynamic offsets.
|
|
6953
6997
|
*/
|
|
6954
|
-
const command = blend
|
|
6998
|
+
const command = blend
|
|
6999
|
+
? this.recordBlended(0, lands)
|
|
7000
|
+
: this.recordDraw(0, this.currentTarget());
|
|
6955
7001
|
if (command !== null) {
|
|
6956
7002
|
command.pipeline = pipeline;
|
|
6957
7003
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
@@ -6984,13 +7030,12 @@ export class WebGPURenderer {
|
|
|
6984
7030
|
whether or not this one found a slot to draw with. */
|
|
6985
7031
|
if (dimmed) {
|
|
6986
7032
|
this.perFrameFloats[this.materialField('uOpacity')] = 1;
|
|
6987
|
-
this.perFrameFloats[this.materialField('
|
|
7033
|
+
this.perFrameFloats[this.materialField('uWriteMode')] = 0;
|
|
6988
7034
|
}
|
|
6989
7035
|
if (unlitOrUnfogged) {
|
|
6990
7036
|
if (!lit)
|
|
6991
7037
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
6992
|
-
|
|
6993
|
-
this.perFrameInts[this.materialField('uFogEnabled')] = 1;
|
|
7038
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = FOG_RECEDE;
|
|
6994
7039
|
}
|
|
6995
7040
|
/* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
|
|
6996
7041
|
shared, so a strength left set is worn by everything drawn after it. */
|
|
@@ -7015,7 +7060,7 @@ export class WebGPURenderer {
|
|
|
7015
7060
|
* anyway, because WebGL2 *cannot* and a capability one backend has and the other does not is
|
|
7016
7061
|
* the shape this repository spent a week removing.
|
|
7017
7062
|
*/
|
|
7018
|
-
createInstanced(mesh, capacity) {
|
|
7063
|
+
createInstanced(mesh, capacity, options = {}) {
|
|
7019
7064
|
/*
|
|
7020
7065
|
* **A skinned mesh cannot be instanced, and the reason is the vertex layout rather than the
|
|
7021
7066
|
* shader.** An instanced pipeline reclaims locations 11 and 12 from the joint indices and
|
|
@@ -7041,16 +7086,13 @@ export class WebGPURenderer {
|
|
|
7041
7086
|
'an ordinary mesh, or drop the channel — the sway, sky and alpha lanes cannot reach an ' +
|
|
7042
7087
|
'instanced pipeline.');
|
|
7043
7088
|
}
|
|
7044
|
-
|
|
7045
|
-
throw new Error('WebGPU: this mesh already has an instanced batch — a mesh may have one, because the ' +
|
|
7046
|
-
"other backend binds the attributes to the mesh's own vertex array.");
|
|
7047
|
-
}
|
|
7048
|
-
const batch = new GpuInstancedBatch(this.surface.device, mesh, capacity, `flat.instances:${mesh.key ?? 'mesh'}`);
|
|
7049
|
-
this.instanced.set(mesh, batch);
|
|
7089
|
+
const batch = new GpuInstancedBatch(this.surface.device, mesh, capacity, `flat.instances:${mesh.key ?? 'mesh'}`, options.cull ?? false);
|
|
7050
7090
|
return batch;
|
|
7051
7091
|
}
|
|
7052
|
-
/**
|
|
7053
|
-
|
|
7092
|
+
/** The device instance culls, built on the first culling draw. See `InstanceCullPass`. */
|
|
7093
|
+
instanceCulls = null;
|
|
7094
|
+
/** Counts frames, so a batch knows when its cull slots were last taken. */
|
|
7095
|
+
cullFrame = 0;
|
|
7054
7096
|
/** Push placement and colour. Only the live prefix; see `GpuInstancedBatch.upload`. */
|
|
7055
7097
|
uploadInstanced(batch, data) {
|
|
7056
7098
|
if (this.surface.lost)
|
|
@@ -7091,7 +7133,6 @@ export class WebGPURenderer {
|
|
|
7091
7133
|
}
|
|
7092
7134
|
/** Release the placement. The mesh is the caller's and is not released. */
|
|
7093
7135
|
disposeInstanced(batch) {
|
|
7094
|
-
this.instanced.delete(batch.mesh);
|
|
7095
7136
|
batch.dispose();
|
|
7096
7137
|
}
|
|
7097
7138
|
/**
|
|
@@ -7144,15 +7185,49 @@ export class WebGPURenderer {
|
|
|
7144
7185
|
* **What it gives up** against `drawMesh` is skinning and morphing, refused where the variant
|
|
7145
7186
|
* is built, and a per-instance opacity: `opacity` here dims the whole batch.
|
|
7146
7187
|
*/
|
|
7188
|
+
/**
|
|
7189
|
+
* The next of a batch's cull slots this frame, with its cull queued against the current view; null
|
|
7190
|
+
* when the batch has drawn from `MAX_CULL_SLOTS` views already or the frame's job ring is full,
|
|
7191
|
+
* and the draw then takes every instance.
|
|
7192
|
+
*/
|
|
7193
|
+
takeCullSlot(batch, count) {
|
|
7194
|
+
if (batch.slotFrame !== this.cullFrame) {
|
|
7195
|
+
batch.slotFrame = this.cullFrame;
|
|
7196
|
+
batch.slotsTaken = 0;
|
|
7197
|
+
}
|
|
7198
|
+
if (batch.slotsTaken >= MAX_CULL_SLOTS)
|
|
7199
|
+
return null;
|
|
7200
|
+
this.instanceCulls ??= new InstanceCullPass(this.surface.device);
|
|
7201
|
+
let slot = batch.slots[batch.slotsTaken];
|
|
7202
|
+
if (slot === undefined) {
|
|
7203
|
+
slot = this.instanceCulls.createSlot(batch.buffer, batch.capacity, 'flat.instances');
|
|
7204
|
+
batch.slots.push(slot);
|
|
7205
|
+
}
|
|
7206
|
+
const bounds = batch.mesh.bounds;
|
|
7207
|
+
const queued = this.instanceCulls.queue(slot, this.frustum, bounds.centre, bounds.radius, count, batch.mesh.indexCount);
|
|
7208
|
+
if (!queued)
|
|
7209
|
+
return null;
|
|
7210
|
+
batch.slotsTaken += 1;
|
|
7211
|
+
return slot;
|
|
7212
|
+
}
|
|
7147
7213
|
submitInstanced(batch, data, opacity, blend, options) {
|
|
7148
7214
|
if (!this.canDraw() || this.viewProj === null)
|
|
7149
7215
|
return;
|
|
7150
7216
|
const mesh = batch.mesh;
|
|
7151
|
-
|
|
7217
|
+
/* Nor a batch of a mesh with nothing in it: see `GpuMesh.indexCount`. */
|
|
7218
|
+
if (!mesh.complete || mesh.indexCount === 0)
|
|
7152
7219
|
return;
|
|
7153
7220
|
const count = Math.min(data.count, batch.capacity);
|
|
7154
7221
|
if (count === 0)
|
|
7155
7222
|
return;
|
|
7223
|
+
/* A culling batch's camera draw: the whole batch against the view and the occluders on the CPU,
|
|
7224
|
+
then instance by instance on the device into an indirect draw. Blended draws keep every
|
|
7225
|
+
instance and their order. See `InstancedOptions.cull`. */
|
|
7226
|
+
const culling = !blend && batch.cull;
|
|
7227
|
+
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion))
|
|
7228
|
+
return;
|
|
7229
|
+
const cut = culling && cullsInstances(count, mesh.indexCount) ? this.takeCullSlot(batch, count) : null;
|
|
7230
|
+
const instanceBuffer = cut === null ? batch.buffer : cut.instances;
|
|
7156
7231
|
const slot = this.perDraw.allocate();
|
|
7157
7232
|
if (slot === null) {
|
|
7158
7233
|
if (!this.warnedFull) {
|
|
@@ -7161,7 +7236,9 @@ export class WebGPURenderer {
|
|
|
7161
7236
|
}
|
|
7162
7237
|
return;
|
|
7163
7238
|
}
|
|
7164
|
-
|
|
7239
|
+
/* Late unless it moves with what it lies on. See `TranslucentMeshOptions.reconstructed`. */
|
|
7240
|
+
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
7241
|
+
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uViewProj.offset, lands ? this.correctedViewProj : this.viewProj);
|
|
7165
7242
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
7166
7243
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
7167
7244
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
@@ -7170,14 +7247,13 @@ export class WebGPURenderer {
|
|
|
7170
7247
|
this.perFrameFloats[this.materialField('uOpacity')] = opacity;
|
|
7171
7248
|
}
|
|
7172
7249
|
const lit = options.lit ?? true;
|
|
7173
|
-
const fog = options.fog ?? true;
|
|
7250
|
+
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
7174
7251
|
const toneMapped = options.toneMapped ?? true;
|
|
7175
|
-
const unlitOrUnfogged = !lit ||
|
|
7252
|
+
const unlitOrUnfogged = !lit || fog !== FOG_RECEDE;
|
|
7176
7253
|
if (unlitOrUnfogged) {
|
|
7177
7254
|
if (!lit)
|
|
7178
7255
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
|
|
7179
|
-
|
|
7180
|
-
this.perFrameInts[this.materialField('uFogEnabled')] = 0;
|
|
7256
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = fog;
|
|
7181
7257
|
}
|
|
7182
7258
|
if (!toneMapped) {
|
|
7183
7259
|
this.perFrameInts[this.materialField('uOutputTransform')] = Math.min(this.gradeCode(), 1);
|
|
@@ -7193,21 +7269,24 @@ export class WebGPURenderer {
|
|
|
7193
7269
|
/* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
|
|
7194
7270
|
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
7195
7271
|
plain one would hand a draw the wrong layout. */
|
|
7196
|
-
const
|
|
7272
|
+
const adds = blend && options.additive === true;
|
|
7273
|
+
const key = `${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
7197
7274
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
7198
7275
|
`${this.materialDoubleSided ? '|2s' : ''}`;
|
|
7199
|
-
const pipelines = blend ? this.blendedPipelines() : this.targetPipelines();
|
|
7276
|
+
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
7200
7277
|
let pipeline = pipelines.peek(key);
|
|
7201
7278
|
if (pipeline === undefined) {
|
|
7202
7279
|
const present = this.meshPresent.get(base);
|
|
7203
7280
|
if (present === undefined) {
|
|
7204
7281
|
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
7205
7282
|
}
|
|
7206
|
-
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(false, false), this.variant, key, present, blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided);
|
|
7283
|
+
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(false, false), this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided);
|
|
7207
7284
|
}
|
|
7208
7285
|
const material = this.materialSlotForDraw();
|
|
7209
7286
|
if (material !== null) {
|
|
7210
|
-
const command = blend
|
|
7287
|
+
const command = blend
|
|
7288
|
+
? this.recordBlended(0, lands)
|
|
7289
|
+
: this.recordDraw(0, this.currentTarget());
|
|
7211
7290
|
if (command !== null) {
|
|
7212
7291
|
command.pipeline = pipeline;
|
|
7213
7292
|
command.bindGroup = this.flatBindGroupFor(false, false, null);
|
|
@@ -7218,12 +7297,13 @@ export class WebGPURenderer {
|
|
|
7218
7297
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
7219
7298
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
7220
7299
|
}
|
|
7221
|
-
command.vertexBuffers[index] =
|
|
7300
|
+
command.vertexBuffers[index] = instanceBuffer;
|
|
7222
7301
|
command.vertexCount = mesh.vertexBuffers.length + 1;
|
|
7223
7302
|
command.indexBuffer = mesh.indexBuffer;
|
|
7224
7303
|
command.indexed = true;
|
|
7225
7304
|
command.count = mesh.indexCount;
|
|
7226
7305
|
command.instances = count;
|
|
7306
|
+
command.indirect = cut === null ? null : cut.args;
|
|
7227
7307
|
}
|
|
7228
7308
|
else {
|
|
7229
7309
|
const pass = this.openPass();
|
|
@@ -7235,9 +7315,12 @@ export class WebGPURenderer {
|
|
|
7235
7315
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
7236
7316
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
|
|
7237
7317
|
}
|
|
7238
|
-
pass.setVertexBuffer(index,
|
|
7318
|
+
pass.setVertexBuffer(index, instanceBuffer);
|
|
7239
7319
|
pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
|
|
7240
|
-
|
|
7320
|
+
if (cut === null)
|
|
7321
|
+
pass.drawIndexed(mesh.indexCount, count);
|
|
7322
|
+
else
|
|
7323
|
+
pass.drawIndexedIndirect(cut.args, 0);
|
|
7241
7324
|
}
|
|
7242
7325
|
}
|
|
7243
7326
|
/* Back to the defaults, unconditionally and for the reason `drawMesh` gives at length: this
|
|
@@ -7248,7 +7331,7 @@ export class WebGPURenderer {
|
|
|
7248
7331
|
}
|
|
7249
7332
|
if (unlitOrUnfogged) {
|
|
7250
7333
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
7251
|
-
this.perFrameInts[this.materialField('uFogEnabled')] =
|
|
7334
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = FOG_RECEDE;
|
|
7252
7335
|
}
|
|
7253
7336
|
if (!toneMapped) {
|
|
7254
7337
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
@@ -7723,7 +7806,7 @@ export class WebGPURenderer {
|
|
|
7723
7806
|
if (pass === null || tint === null)
|
|
7724
7807
|
return;
|
|
7725
7808
|
const geometry = mesh;
|
|
7726
|
-
if (geometry.vertexBuffers === undefined)
|
|
7809
|
+
if (geometry.vertexBuffers === undefined || geometry.indexCount === 0)
|
|
7727
7810
|
return;
|
|
7728
7811
|
const cutout = cutoutOf(material);
|
|
7729
7812
|
const albedo = cutout === null ? null : cutout.albedo;
|
|
@@ -7754,7 +7837,7 @@ export class WebGPURenderer {
|
|
|
7754
7837
|
return;
|
|
7755
7838
|
const gpuBatch = batch;
|
|
7756
7839
|
const geometry = gpuBatch.mesh;
|
|
7757
|
-
if (geometry.vertexBuffers === undefined)
|
|
7840
|
+
if (geometry.vertexBuffers === undefined || geometry.indexCount === 0)
|
|
7758
7841
|
return;
|
|
7759
7842
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
7760
7843
|
if (count === 0)
|
|
@@ -7787,7 +7870,7 @@ export class WebGPURenderer {
|
|
|
7787
7870
|
if (pass === null || tint === null)
|
|
7788
7871
|
return;
|
|
7789
7872
|
const geometry = mesh;
|
|
7790
|
-
if (geometry.vertexBuffers === undefined)
|
|
7873
|
+
if (geometry.vertexBuffers === undefined || geometry.indexCount === 0)
|
|
7791
7874
|
return;
|
|
7792
7875
|
const paletteSlot = this.skinPalettes.take(this.surface.device, palette);
|
|
7793
7876
|
const view = paletteSlot === null ? null : this.skinPalettes.view(paletteSlot);
|
|
@@ -8574,6 +8657,23 @@ export class WebGPURenderer {
|
|
|
8574
8657
|
f[at('uMotionStrength')] = motionStrength;
|
|
8575
8658
|
f[at('uMotionMax')] = MOTION_BLUR_MAX_UV;
|
|
8576
8659
|
f[at('uAoStrength')] = this.frameProjection === null ? 0 : aoStrength;
|
|
8660
|
+
/*
|
|
8661
|
+
* The occlusion read where this frame's jitter put it (`uAoOffset`): measured from the jittered
|
|
8662
|
+
* depth and applied to a picture the jitter has been taken out of. A texel stands at its index
|
|
8663
|
+
* plus a half less the jitter, so the occlusion of a point is a jitter further on. The
|
|
8664
|
+
* reconstruction's y is given upward and negated into the corrected matrix, which this pass's
|
|
8665
|
+
* rows run down from; the temporal resolve's goes in as it is, and arrives as it is.
|
|
8666
|
+
*/
|
|
8667
|
+
f[at('uAoOffset')] = this.reconstructing
|
|
8668
|
+
? this.reconJitter[0] / this.renderWidth
|
|
8669
|
+
: this.temporalJittering
|
|
8670
|
+
? this.temporalJitterX / this.renderWidth
|
|
8671
|
+
: 0;
|
|
8672
|
+
f[at('uAoOffset') + 1] = this.reconstructing
|
|
8673
|
+
? -this.reconJitter[1] / this.renderHeight
|
|
8674
|
+
: this.temporalJittering
|
|
8675
|
+
? this.temporalJitterY / this.renderHeight
|
|
8676
|
+
: 0;
|
|
8577
8677
|
f[at('uDofStrength')] = dofStrength;
|
|
8578
8678
|
if (dofStrength > 0 && this.frameProjection !== null) {
|
|
8579
8679
|
mat4.invert(this.dofInvProjection, this.frameProjection);
|
|
@@ -8715,6 +8815,8 @@ export class WebGPURenderer {
|
|
|
8715
8815
|
this.reconSizeKey = key;
|
|
8716
8816
|
for (const dead of [
|
|
8717
8817
|
...this.reconHistories,
|
|
8818
|
+
...this.reconFlickers,
|
|
8819
|
+
...this.reconSwings,
|
|
8718
8820
|
this.reconShown,
|
|
8719
8821
|
this.reconMotion,
|
|
8720
8822
|
this.reconPreviousDepth,
|
|
@@ -8723,6 +8825,10 @@ export class WebGPURenderer {
|
|
|
8723
8825
|
}
|
|
8724
8826
|
this.reconHistories = [null, null];
|
|
8725
8827
|
this.reconHistoryViews = [null, null];
|
|
8828
|
+
this.reconFlickers = [null, null];
|
|
8829
|
+
this.reconFlickerViews = [null, null];
|
|
8830
|
+
this.reconSwings = [null, null];
|
|
8831
|
+
this.reconSwingViews = [null, null];
|
|
8726
8832
|
this.reconShown = null;
|
|
8727
8833
|
this.reconShownView = null;
|
|
8728
8834
|
this.reconMotion = null;
|
|
@@ -8747,6 +8853,22 @@ export class WebGPURenderer {
|
|
|
8747
8853
|
});
|
|
8748
8854
|
this.reconHistories[i] = texture;
|
|
8749
8855
|
this.reconHistoryViews[i] = texture.createView();
|
|
8856
|
+
const record = device.createTexture({
|
|
8857
|
+
label: `recon.flicker${String(i)}`,
|
|
8858
|
+
size: [outputWidth, outputHeight],
|
|
8859
|
+
format: RECON_HISTORY_FORMAT,
|
|
8860
|
+
usage: HISTORY_USAGE,
|
|
8861
|
+
});
|
|
8862
|
+
this.reconFlickers[i] = record;
|
|
8863
|
+
this.reconFlickerViews[i] = record.createView();
|
|
8864
|
+
const swing = device.createTexture({
|
|
8865
|
+
label: `recon.swing${String(i)}`,
|
|
8866
|
+
size: [outputWidth, outputHeight],
|
|
8867
|
+
format: 'r32float',
|
|
8868
|
+
usage: HISTORY_USAGE,
|
|
8869
|
+
});
|
|
8870
|
+
this.reconSwings[i] = swing;
|
|
8871
|
+
this.reconSwingViews[i] = swing.createView();
|
|
8750
8872
|
}
|
|
8751
8873
|
this.reconShown = device.createTexture({
|
|
8752
8874
|
label: 'recon.shown',
|
|
@@ -8822,6 +8944,26 @@ export class WebGPURenderer {
|
|
|
8822
8944
|
viewDimension: '2d',
|
|
8823
8945
|
},
|
|
8824
8946
|
},
|
|
8947
|
+
sampled(8, 'float'),
|
|
8948
|
+
{
|
|
8949
|
+
binding: 9,
|
|
8950
|
+
visibility: COMPUTE,
|
|
8951
|
+
storageTexture: {
|
|
8952
|
+
access: 'write-only',
|
|
8953
|
+
format: RECON_HISTORY_FORMAT,
|
|
8954
|
+
viewDimension: '2d',
|
|
8955
|
+
},
|
|
8956
|
+
},
|
|
8957
|
+
sampled(10, 'unfilterable-float'),
|
|
8958
|
+
{
|
|
8959
|
+
binding: 11,
|
|
8960
|
+
visibility: COMPUTE,
|
|
8961
|
+
storageTexture: {
|
|
8962
|
+
access: 'write-only',
|
|
8963
|
+
format: 'r32float',
|
|
8964
|
+
viewDimension: '2d',
|
|
8965
|
+
},
|
|
8966
|
+
},
|
|
8825
8967
|
],
|
|
8826
8968
|
});
|
|
8827
8969
|
const module = device.createShaderModule({ label: 'recon.resolve', code: reconResolveWgsl() });
|
|
@@ -8856,7 +8998,7 @@ export class WebGPURenderer {
|
|
|
8856
8998
|
if (this.reconShownView === null)
|
|
8857
8999
|
return;
|
|
8858
9000
|
const { device } = this.surface;
|
|
8859
|
-
const build = (label, history, target) => device.createBindGroup({
|
|
9001
|
+
const build = (label, history, target, records, nextRecords, swings, nextSwings) => device.createBindGroup({
|
|
8860
9002
|
label,
|
|
8861
9003
|
layout,
|
|
8862
9004
|
entries: [
|
|
@@ -8868,20 +9010,77 @@ export class WebGPURenderer {
|
|
|
8868
9010
|
{ binding: 5, resource: this.postSampler },
|
|
8869
9011
|
{ binding: 6, resource: { buffer: params } },
|
|
8870
9012
|
{ binding: 7, resource: target },
|
|
9013
|
+
{ binding: 8, resource: records },
|
|
9014
|
+
{ binding: 9, resource: nextRecords },
|
|
9015
|
+
{ binding: 10, resource: swings },
|
|
9016
|
+
{ binding: 11, resource: nextSwings },
|
|
8871
9017
|
],
|
|
8872
9018
|
});
|
|
8873
9019
|
for (let write = 0; write < 2; write += 1) {
|
|
8874
9020
|
const read = this.reconHistoryViews[1 - write];
|
|
8875
9021
|
const target = this.reconHistoryViews[write];
|
|
8876
|
-
|
|
9022
|
+
const records = this.reconFlickerViews[1 - write];
|
|
9023
|
+
const nextRecords = this.reconFlickerViews[write];
|
|
9024
|
+
const swings = this.reconSwingViews[1 - write];
|
|
9025
|
+
const nextSwings = this.reconSwingViews[write];
|
|
9026
|
+
if (read === null || target === null || records === null || nextRecords === null)
|
|
9027
|
+
continue;
|
|
9028
|
+
if (swings === null || nextSwings === null)
|
|
8877
9029
|
continue;
|
|
8878
|
-
this.reconGroups[write] = build(`recon.resolve${String(write)}`, read, target);
|
|
8879
|
-
|
|
9030
|
+
this.reconGroups[write] = build(`recon.resolve${String(write)}`, read, target, records, nextRecords, swings, nextSwings);
|
|
9031
|
+
/* The sharpen reads no record, but one layout serves both dispatches: the same pair, so no
|
|
9032
|
+
texture is both read and written. */
|
|
9033
|
+
this.reconSharpenGroups[write] = build(`recon.sharpen${String(write)}`, target, this.reconShownView, records, nextRecords, swings, nextSwings);
|
|
8880
9034
|
}
|
|
8881
9035
|
/* The composite's own group, over the resolved picture instead of the scene target. */
|
|
8882
9036
|
this.reconRushGroup = this.buildRushBindGroup('recon.rushBindGroup', this.reconShownView);
|
|
8883
9037
|
/* And the scene's, because `ensureComposite` built it before this one existed. */
|
|
8884
9038
|
this.rebuildRushBindGroup();
|
|
9039
|
+
/* And the bloom's, which blooms the resolved picture: see `rebuildBloomGroups`. */
|
|
9040
|
+
this.rebuildBloomGroups();
|
|
9041
|
+
}
|
|
9042
|
+
/** This frame's jitter and temporal frame, decided where its scene camera is bound. See `bindMeshPass`. */
|
|
9043
|
+
jitterFrame() {
|
|
9044
|
+
this.reconstructing = this.reconstructionWanted && !this.reflectionPassActive;
|
|
9045
|
+
if (this.quality.reconstruction > 0 && this.samples > 1 && !this.reconMultisampleSaid) {
|
|
9046
|
+
this.reconMultisampleSaid = true;
|
|
9047
|
+
console.warn(RECONSTRUCTION_MULTISAMPLE_REFUSAL);
|
|
9048
|
+
}
|
|
9049
|
+
if (this.reconstructing) {
|
|
9050
|
+
/*
|
|
9051
|
+
* The offset `settleReconJitter` chose for the frame at `beginFrame`, applied here and not
|
|
9052
|
+
* advanced here: a frame binding its mesh pass twice is one frame of the sequence.
|
|
9053
|
+
*
|
|
9054
|
+
* **The y offset is negated on the way in, and that is not a preference.** This shifts the
|
|
9055
|
+
* *corrected* matrix, whose y `CLIP_CORRECTION` has already negated so that the negation
|
|
9056
|
+
* every generated vertex stage ends with cancels out — §3 row 56. So an offset added here
|
|
9057
|
+
* arrives on screen with its sign reversed, while x, which nothing negates, arrives as
|
|
9058
|
+
* given. The temporal resolve cannot see this because its shader never reads the offset; the
|
|
9059
|
+
* reconstruction reads it at every texel it unprojects, and a sign error there moves every
|
|
9060
|
+
* sample two jitters away from where the depth says it is.
|
|
9061
|
+
*/
|
|
9062
|
+
this.viewProj = jitterProjection(this.jitteredViewProj, this.correctedViewProj, this.reconJitter[0], -this.reconJitter[1], this.renderWidth, this.renderHeight);
|
|
9063
|
+
this.temporalJittering = false;
|
|
9064
|
+
this.temporalHistoryUsable = false;
|
|
9065
|
+
}
|
|
9066
|
+
this.temporalJittering =
|
|
9067
|
+
!this.reconstructing &&
|
|
9068
|
+
this.quality.temporalAa &&
|
|
9069
|
+
this.quality.screenEffects &&
|
|
9070
|
+
!this.reflectionPassActive;
|
|
9071
|
+
if (this.temporalJittering) {
|
|
9072
|
+
/* The scene's size, not the drawing buffer's: the history is a picture of the render. */
|
|
9073
|
+
this.temporalHistoryUsable = this.temporalHistory.openFrame(this.renderWidth, this.renderHeight);
|
|
9074
|
+
const [jx, jy] = jitterOffset(this.temporalHistory.frameIndex);
|
|
9075
|
+
this.temporalJitterX = jx;
|
|
9076
|
+
this.temporalJitterY = jy;
|
|
9077
|
+
/* A jitter is half a *render* texel, which is what the sequence is spread over. */
|
|
9078
|
+
this.viewProj = jitterProjection(this.jitteredViewProj, this.correctedViewProj, jx, jy, this.renderWidth, this.renderHeight);
|
|
9079
|
+
}
|
|
9080
|
+
else if (!this.reconstructing) {
|
|
9081
|
+
this.temporalHistoryUsable = false;
|
|
9082
|
+
this.viewProj = this.correctedViewProj;
|
|
9083
|
+
}
|
|
8885
9084
|
}
|
|
8886
9085
|
/**
|
|
8887
9086
|
* Whether a blended draw recorded now lands after the upscale. See `lateRouting.ts`.
|
|
@@ -8898,15 +9097,15 @@ export class WebGPURenderer {
|
|
|
8898
9097
|
* otherwise. **Late draws record whatever `quality.frameGraph` says**, since the late replay is
|
|
8899
9098
|
* the only moment they can land; everything else keeps the path it has.
|
|
8900
9099
|
*/
|
|
8901
|
-
recordBlended(reads) {
|
|
9100
|
+
recordBlended(reads, lands = this.drawsLate()) {
|
|
8902
9101
|
const late = this.latePass;
|
|
8903
|
-
if (late !== null &&
|
|
9102
|
+
if (late !== null && lands)
|
|
8904
9103
|
return late.take();
|
|
8905
9104
|
return this.recordDraw(reads, this.currentTarget());
|
|
8906
9105
|
}
|
|
8907
9106
|
/** The pipelines a blended draw is built for: the reconstructed picture's while late. */
|
|
8908
|
-
blendedPipelines() {
|
|
8909
|
-
return
|
|
9107
|
+
blendedPipelines(lands = this.drawsLate()) {
|
|
9108
|
+
return lands ? this.latePipelines : this.targetPipelines();
|
|
8910
9109
|
}
|
|
8911
9110
|
/**
|
|
8912
9111
|
* The blended draws of a reconstructing frame, over the reconstructed picture at the output size
|
|
@@ -8925,7 +9124,7 @@ export class WebGPURenderer {
|
|
|
8925
9124
|
const width = Math.max(1, this.surface.canvas.width);
|
|
8926
9125
|
const height = Math.max(1, this.surface.canvas.height);
|
|
8927
9126
|
/* The order-independent set tests against this depth too, so it is made for either. */
|
|
8928
|
-
late.upscale(encoder, depth, this.renderWidth, this.renderHeight, width, height);
|
|
9127
|
+
late.upscale(encoder, depth, this.renderWidth, this.renderHeight, width, height, this.reconJitter[0], this.reconJitter[1]);
|
|
8929
9128
|
if (this.lateRefractWanted && this.refractSnapshot !== null && this.reconShown !== null) {
|
|
8930
9129
|
encoder.copyTextureToTexture({ texture: this.reconShown }, { texture: this.refractSnapshot }, [this.refractSnapshot.width, this.refractSnapshot.height]);
|
|
8931
9130
|
this.refractMips?.record(encoder);
|
|
@@ -9060,7 +9259,16 @@ export class WebGPURenderer {
|
|
|
9060
9259
|
* OpenGL's depth convention — which is the convention the shader's accessor converts the
|
|
9061
9260
|
* renderer's reversed buffer into.
|
|
9062
9261
|
*/
|
|
9063
|
-
|
|
9262
|
+
/*
|
|
9263
|
+
* The inverse and the reprojection in double precision, rounded once on the way into the
|
|
9264
|
+
* uniform: the product of two single-precision matrices this far apart in depth is not the
|
|
9265
|
+
* identity for a still camera, and the resolve read the difference as motion.
|
|
9266
|
+
*/
|
|
9267
|
+
mat4.invert(this.reconInverse64, this.frameRawViewProj);
|
|
9268
|
+
mat4.multiply(this.reconReprojection64, this.reconPreviousRawViewProj, this.reconInverse64);
|
|
9269
|
+
for (let i = 0; i < 16; i += 1) {
|
|
9270
|
+
this.reconInverseViewProj[i] = this.reconInverse64[i];
|
|
9271
|
+
}
|
|
9064
9272
|
const f = this.reconFloats;
|
|
9065
9273
|
const u = this.reconInts;
|
|
9066
9274
|
u[0] = this.renderWidth;
|
|
@@ -9084,6 +9292,17 @@ export class WebGPURenderer {
|
|
|
9084
9292
|
f[69] = DEFAULT_DISOCCLUSION.motionScale;
|
|
9085
9293
|
f[70] = DEFAULT_DISOCCLUSION.normalFloor;
|
|
9086
9294
|
f[71] = DEFAULT_DISOCCLUSION.normalCeiling;
|
|
9295
|
+
/* The anti-flicker over the reconstruction's own period, its rule made once a period. */
|
|
9296
|
+
const phases = reconJitterPhases(this.renderWidth, outputWidth);
|
|
9297
|
+
if (phases !== this.reconFlickerPhases) {
|
|
9298
|
+
this.reconFlickerPhases = phases;
|
|
9299
|
+
this.reconFlickerRule = flickerRule(phases);
|
|
9300
|
+
}
|
|
9301
|
+
u[72] = this.reconFrameIndex % phases === 0 ? 1 : 0;
|
|
9302
|
+
u[73] = 1;
|
|
9303
|
+
f[74] = this.reconFlickerRule.memory;
|
|
9304
|
+
f[75] = this.reconFlickerRule.width;
|
|
9305
|
+
f.set(this.reconReprojection64, 76);
|
|
9087
9306
|
device.queue.writeBuffer(params, 0, this.reconStaging);
|
|
9088
9307
|
const groupsX = Math.ceil(outputWidth / RECON_WORKGROUP);
|
|
9089
9308
|
const groupsY = Math.ceil(outputHeight / RECON_WORKGROUP);
|
|
@@ -10215,6 +10434,7 @@ export class WebGPURenderer {
|
|
|
10215
10434
|
command.indexBuffer = null;
|
|
10216
10435
|
command.indexed = false;
|
|
10217
10436
|
command.instances = 1;
|
|
10437
|
+
command.indirect = null;
|
|
10218
10438
|
recordNode(this.arena, VERB_DRAW, reads, writes, at, 0);
|
|
10219
10439
|
return command;
|
|
10220
10440
|
}
|
|
@@ -10266,7 +10486,10 @@ export class WebGPURenderer {
|
|
|
10266
10486
|
}
|
|
10267
10487
|
if (command.indexed && command.indexBuffer !== null) {
|
|
10268
10488
|
pass.setIndexBuffer(command.indexBuffer, 'uint32');
|
|
10269
|
-
|
|
10489
|
+
if (command.indirect !== null)
|
|
10490
|
+
pass.drawIndexedIndirect(command.indirect, 0);
|
|
10491
|
+
else
|
|
10492
|
+
pass.drawIndexed(command.count, command.instances);
|
|
10270
10493
|
}
|
|
10271
10494
|
else {
|
|
10272
10495
|
pass.draw(command.count, command.instances);
|
|
@@ -10882,6 +11105,10 @@ export class WebGPURenderer {
|
|
|
10882
11105
|
* declares nothing behaves exactly as it did before. Read it beside `visible`, which answers the
|
|
10883
11106
|
* other half of the same question.
|
|
10884
11107
|
*/
|
|
11108
|
+
/** Whether a world-space box is wholly behind the declared occluders. See `Renderer.occludedBox`. */
|
|
11109
|
+
occludedBox(min, max) {
|
|
11110
|
+
return this.occlusion?.occludedBox(min, max) ?? false;
|
|
11111
|
+
}
|
|
10885
11112
|
occluded(bounds, model) {
|
|
10886
11113
|
return this.occlusion?.occluded(bounds, model) ?? false;
|
|
10887
11114
|
}
|
|
@@ -11280,12 +11507,14 @@ export class WebGPURenderer {
|
|
|
11280
11507
|
const cssHeight = Math.max(canvas.clientHeight, 1);
|
|
11281
11508
|
const origin = canvas.getBoundingClientRect();
|
|
11282
11509
|
/*
|
|
11283
|
-
* **CSS pixels to the
|
|
11284
|
-
*
|
|
11285
|
-
*
|
|
11510
|
+
* **CSS pixels to the pixels of the attachment this pass draws into.** Mid-frame that is the
|
|
11511
|
+
* scene's render size, which a reconstruction makes smaller than the drawing buffer; after the
|
|
11512
|
+
* present it is the swap image, the drawing buffer itself. Measured against the scene's size
|
|
11513
|
+
* there, an interface's inset landed two thirds of the way to where it was asked for.
|
|
11286
11514
|
*/
|
|
11287
|
-
const
|
|
11288
|
-
const
|
|
11515
|
+
const target = this.passSize();
|
|
11516
|
+
const scaleX = target[0] / cssWidth;
|
|
11517
|
+
const scaleY = target[1] / cssHeight;
|
|
11289
11518
|
const left = rect.left - origin.left;
|
|
11290
11519
|
const top = rect.top - origin.top;
|
|
11291
11520
|
const w = Math.max(1, Math.round(rect.width * scaleX));
|
|
@@ -11312,9 +11541,8 @@ export class WebGPURenderer {
|
|
|
11312
11541
|
* edge for the frames it is half-off. That asymmetry is the cost of the clamp, it is
|
|
11313
11542
|
* recorded in the parity ledger, and the alternative was an exception.
|
|
11314
11543
|
*/
|
|
11315
|
-
|
|
11316
|
-
const
|
|
11317
|
-
const targetHeight = this.renderHeight;
|
|
11544
|
+
const targetWidth = target[0];
|
|
11545
|
+
const targetHeight = target[1];
|
|
11318
11546
|
const clampedX = Math.max(0, Math.min(x, targetWidth));
|
|
11319
11547
|
const clampedY = Math.max(0, Math.min(y, targetHeight));
|
|
11320
11548
|
const clampedW = Math.max(0, Math.min(x + w, targetWidth) - clampedX);
|
|
@@ -11386,10 +11614,29 @@ export class WebGPURenderer {
|
|
|
11386
11614
|
const pass = this.pass;
|
|
11387
11615
|
if (this.surface.lost || pass === null)
|
|
11388
11616
|
return;
|
|
11389
|
-
/* The whole of what this pass draws into
|
|
11390
|
-
|
|
11391
|
-
pass.
|
|
11617
|
+
/* The whole of what this pass draws into: the scene's size mid-frame, the canvas after. */
|
|
11618
|
+
const [width, height] = this.passSize();
|
|
11619
|
+
pass.setViewport(0, 0, width, height, 0, 1);
|
|
11620
|
+
pass.setScissorRect(0, 0, width, height);
|
|
11392
11621
|
}
|
|
11622
|
+
/**
|
|
11623
|
+
* The size of the attachment the open pass draws into: the swap image — the drawing buffer —
|
|
11624
|
+
* in the overlay after the present, the scene's render size in the frame's own pass. A tuple
|
|
11625
|
+
* held once rather than a new array a call.
|
|
11626
|
+
*/
|
|
11627
|
+
passSize() {
|
|
11628
|
+
const size = this.passSizeOut;
|
|
11629
|
+
if (this.overlayActive) {
|
|
11630
|
+
size[0] = Math.max(1, this.surface.canvas.width);
|
|
11631
|
+
size[1] = Math.max(1, this.surface.canvas.height);
|
|
11632
|
+
}
|
|
11633
|
+
else {
|
|
11634
|
+
size[0] = this.renderWidth;
|
|
11635
|
+
size[1] = this.renderHeight;
|
|
11636
|
+
}
|
|
11637
|
+
return size;
|
|
11638
|
+
}
|
|
11639
|
+
passSizeOut = [0, 0];
|
|
11393
11640
|
/**
|
|
11394
11641
|
* Copy a rectangle of the frame into a 2D canvas, pixel for pixel.
|
|
11395
11642
|
*
|
|
@@ -11911,8 +12158,12 @@ export class WebGPURenderer {
|
|
|
11911
12158
|
probeView = null;
|
|
11912
12159
|
probeSize;
|
|
11913
12160
|
probeMipLayout;
|
|
11914
|
-
/** The
|
|
11915
|
-
|
|
12161
|
+
/** The fixture atlas — cookies over profiles — built on first use. See `fixtureAtlas.ts`. */
|
|
12162
|
+
fixtureTexture = null;
|
|
12163
|
+
fixtureView = null;
|
|
12164
|
+
/** The profiles as packed and the cookie images, kept so either can rebuild the atlas. */
|
|
12165
|
+
iesAtlas = packIesAtlas([]);
|
|
12166
|
+
cookieImages = [];
|
|
11916
12167
|
/** The scene's DriftLight field, and its two volumes once it is whole. See `createLightField`. */
|
|
11917
12168
|
lightField = null;
|
|
11918
12169
|
driftIndex = null;
|
|
@@ -11926,10 +12177,7 @@ export class WebGPURenderer {
|
|
|
11926
12177
|
/** The frame's exact lights packed for the traced bake. See `gi/bounceLights.ts`. */
|
|
11927
12178
|
bounceLights = createBounceLights();
|
|
11928
12179
|
bakeLamps = { data: this.bounceLights, count: 0, falloff: 0 };
|
|
11929
|
-
|
|
11930
|
-
cookieTexture = null;
|
|
11931
|
-
cookieView = null;
|
|
11932
|
-
/** Tiles in the cookie atlas. 0 is the white placeholder and the whole off path. */
|
|
12180
|
+
/** Cookie tiles in the atlas. 0 is none, and the whole off path. */
|
|
11933
12181
|
cookieTiles = 0;
|
|
11934
12182
|
/** Rows in that atlas. One until a consumer loads a profile, which is the row of ones. */
|
|
11935
12183
|
iesRows = 1;
|
|
@@ -12376,35 +12624,50 @@ export class WebGPURenderer {
|
|
|
12376
12624
|
setSpotCookies(images) {
|
|
12377
12625
|
if (this.surface.lost)
|
|
12378
12626
|
return;
|
|
12627
|
+
this.cookieImages = images.slice();
|
|
12628
|
+
this.forgetFixtureAtlas();
|
|
12629
|
+
this.rebuildFlatBindGroup();
|
|
12630
|
+
}
|
|
12631
|
+
/** Let the atlas go, to be built again from what is in hand the next time it is bound. */
|
|
12632
|
+
forgetFixtureAtlas() {
|
|
12633
|
+
this.fixtureTexture?.destroy();
|
|
12634
|
+
this.fixtureTexture = null;
|
|
12635
|
+
this.fixtureView = null;
|
|
12636
|
+
}
|
|
12637
|
+
/**
|
|
12638
|
+
* The fixture atlas's view, built from the profiles and cookies in hand if it is not already: the
|
|
12639
|
+
* profiles' rows at the bottom, the cookies' tiles on top. See `renderer.ts` and `fixtureAtlas.ts`.
|
|
12640
|
+
*
|
|
12641
|
+
* **`rgba16float`, not `rgba32float`**: a 32-bit float texture is not filterable in core WebGPU,
|
|
12642
|
+
* and binding one to a filtering sampler takes the whole bind group with it. Both tables want
|
|
12643
|
+
* linear filtering. `RENDER_ATTACHMENT` because `copyExternalImageToTexture` writes through one.
|
|
12644
|
+
*/
|
|
12645
|
+
fixtureAtlasView() {
|
|
12646
|
+
if (this.fixtureView !== null)
|
|
12647
|
+
return this.fixtureView;
|
|
12379
12648
|
const { device } = this.surface;
|
|
12380
|
-
const
|
|
12381
|
-
const
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
/* White, not black: this multiplies a light's colour. */
|
|
12396
|
-
device.queue.writeTexture({ texture: this.cookieTexture }, new Uint8Array([255, 255, 255, 255]), { bytesPerRow: 4, rowsPerImage: 1 }, { width: 1, height: 1, depthOrArrayLayers: 1 });
|
|
12397
|
-
}
|
|
12398
|
-
else {
|
|
12399
|
-
for (let tile = 0; tile < tiles; tile++) {
|
|
12400
|
-
const source = images[tile];
|
|
12401
|
-
if (source === undefined)
|
|
12402
|
-
continue;
|
|
12403
|
-
device.queue.copyExternalImageToTexture({ source: source }, { texture: this.cookieTexture, origin: { x: tile * COOKIE_TILE, y: 0 } }, { width: COOKIE_TILE, height: COOKIE_TILE });
|
|
12404
|
-
}
|
|
12649
|
+
const layout = fixtureAtlasLayout(this.iesAtlas.height, this.cookieImages.length);
|
|
12650
|
+
const texture = device.createTexture({
|
|
12651
|
+
label: 'fixture.atlas',
|
|
12652
|
+
size: [layout.width, layout.height],
|
|
12653
|
+
format: 'rgba16float',
|
|
12654
|
+
usage: 0x4 | 0x2 | 0x10,
|
|
12655
|
+
});
|
|
12656
|
+
device.queue.writeTexture({ texture },
|
|
12657
|
+
/* Converted, because `writeTexture` does not — see `environmentTexels`. */
|
|
12658
|
+
toHalfFloats(fixtureAtlasTexels(layout, this.iesAtlas)), { bytesPerRow: layout.width * 8, rowsPerImage: layout.height }, { width: layout.width, height: layout.height, depthOrArrayLayers: 1 });
|
|
12659
|
+
for (let tile = 0; tile < this.cookieImages.length; tile++) {
|
|
12660
|
+
const source = this.cookieImages[tile];
|
|
12661
|
+
if (source === undefined)
|
|
12662
|
+
continue;
|
|
12663
|
+
device.queue.copyExternalImageToTexture({ source: source }, { texture, origin: { x: tile * COOKIE_TILE, y: 0 } }, { width: COOKIE_TILE, height: COOKIE_TILE });
|
|
12405
12664
|
}
|
|
12406
|
-
this.
|
|
12407
|
-
this.
|
|
12665
|
+
this.fixtureTexture = texture;
|
|
12666
|
+
this.fixtureView = texture.createView();
|
|
12667
|
+
this.iesRows = this.iesAtlas.height;
|
|
12668
|
+
this.iesPlanes = this.iesAtlas.planes;
|
|
12669
|
+
this.cookieTiles = this.cookieImages.length;
|
|
12670
|
+
return this.fixtureView;
|
|
12408
12671
|
}
|
|
12409
12672
|
/**
|
|
12410
12673
|
* Sum a scene's many fixed lights into a DriftLight field. The same contract as
|
|
@@ -12416,6 +12679,19 @@ export class WebGPURenderer {
|
|
|
12416
12679
|
this.lightField = field;
|
|
12417
12680
|
return field;
|
|
12418
12681
|
}
|
|
12682
|
+
/**
|
|
12683
|
+
* Hand a world's DriftLight volume to the renderer: every fixed light of a city, summed offline by
|
|
12684
|
+
* `bakeDenseField`, standing in past the frame's exact choice. Replaces any field made before, of
|
|
12685
|
+
* either kind; the scene calls `field.follow(buffer.complete, ...)` each frame as it would for
|
|
12686
|
+
* `createLightField`, and marks the lights the volume summed `inLightField` so none is counted
|
|
12687
|
+
* twice. See `driftLight/worldLightField.ts`.
|
|
12688
|
+
*/
|
|
12689
|
+
createWorldLightField(volume, options = {}) {
|
|
12690
|
+
this.forgetLightFieldVolumes();
|
|
12691
|
+
const field = new WorldLightField(volume, options);
|
|
12692
|
+
this.lightField = field;
|
|
12693
|
+
return field;
|
|
12694
|
+
}
|
|
12419
12695
|
/** Let go of the field and its volumes; the scene's lights are all exact again. */
|
|
12420
12696
|
disposeLightField() {
|
|
12421
12697
|
this.forgetLightFieldVolumes();
|
|
@@ -12441,13 +12717,13 @@ export class WebGPURenderer {
|
|
|
12441
12717
|
*/
|
|
12442
12718
|
uploadLightField() {
|
|
12443
12719
|
const field = this.lightField;
|
|
12444
|
-
if (field === null || this.driftAtlas !== null || !field.ready || field.
|
|
12720
|
+
if (field === null || this.driftAtlas !== null || !field.ready || field.empty) {
|
|
12445
12721
|
return;
|
|
12446
12722
|
}
|
|
12447
12723
|
if (this.surface.lost)
|
|
12448
12724
|
return;
|
|
12449
12725
|
const { device } = this.surface;
|
|
12450
|
-
const [ix, iy, iz] = field.
|
|
12726
|
+
const [ix, iy, iz] = field.indexDims;
|
|
12451
12727
|
const index = device.createTexture({
|
|
12452
12728
|
label: 'driftLight.index',
|
|
12453
12729
|
size: [ix, iy, iz],
|
|
@@ -12455,7 +12731,7 @@ export class WebGPURenderer {
|
|
|
12455
12731
|
format: 'r32uint',
|
|
12456
12732
|
usage: 0x2 | 0x4, // COPY_DST | TEXTURE_BINDING
|
|
12457
12733
|
});
|
|
12458
|
-
device.queue.writeTexture({ texture: index }, field.
|
|
12734
|
+
device.queue.writeTexture({ texture: index }, field.index, { bytesPerRow: ix * 4, rowsPerImage: iy }, [ix, iy, iz]);
|
|
12459
12735
|
const [aw, ah, ad] = field.atlasSize;
|
|
12460
12736
|
const atlas = device.createTexture({
|
|
12461
12737
|
label: 'driftLight.atlas',
|
|
@@ -12480,41 +12756,8 @@ export class WebGPURenderer {
|
|
|
12480
12756
|
setIesProfiles(profiles) {
|
|
12481
12757
|
if (this.surface.lost)
|
|
12482
12758
|
return;
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
/*
|
|
12486
|
-
* Reallocated when the row count changes, because a texture's size is immutable. A set of the
|
|
12487
|
-
* same size overwrites in place, which is the common case of a consumer swapping one fixture.
|
|
12488
|
-
*/
|
|
12489
|
-
if (this.iesTexture === null || this.iesTexture.height !== atlas.height) {
|
|
12490
|
-
this.iesTexture?.destroy();
|
|
12491
|
-
this.iesTexture = device.createTexture({
|
|
12492
|
-
label: 'ies.atlas',
|
|
12493
|
-
size: [atlas.width, atlas.height],
|
|
12494
|
-
/*
|
|
12495
|
-
* **`r16float`, not `r32float`, and that is a filtering constraint rather than a size
|
|
12496
|
-
* choice.** A 32-bit float texture is not filterable in core WebGPU — it needs the
|
|
12497
|
-
* optional `float32-filterable` feature — and binding one to a filtering sampler is a
|
|
12498
|
-
* validation failure that takes the whole bind group with it. This row *wants* linear
|
|
12499
|
-
* filtering: it is a curve sampled between two angles, which is the entire reason it is
|
|
12500
|
-
* not `NEAREST` like the froxel table beside it.
|
|
12501
|
-
*
|
|
12502
|
-
* Half precision holds a normalised intensity to about three decimal digits, which is far
|
|
12503
|
-
* finer than the eight-bit frame it ends up in. **What would make it wrong** is a profile
|
|
12504
|
-
* carrying absolute candela rather than a normalised curve, which would run out of range —
|
|
12505
|
-
* and `packIesAtlas` normalises for its own reasons.
|
|
12506
|
-
*/
|
|
12507
|
-
format: 'r16float',
|
|
12508
|
-
usage: 0x4 | 0x2, // TEXTURE_BINDING | COPY_DST
|
|
12509
|
-
});
|
|
12510
|
-
this.iesView = this.iesTexture.createView();
|
|
12511
|
-
}
|
|
12512
|
-
device.queue.writeTexture({ texture: this.iesTexture },
|
|
12513
|
-
/* Converted, because `writeTexture` does not — see `environmentTexels` for what handing it
|
|
12514
|
-
the wrong width silently produces. */
|
|
12515
|
-
toHalfFloats(atlas.data), { bytesPerRow: atlas.width * 2, rowsPerImage: atlas.height }, { width: atlas.width, height: atlas.height, depthOrArrayLayers: 1 });
|
|
12516
|
-
this.iesRows = atlas.height;
|
|
12517
|
-
this.iesPlanes = atlas.planes;
|
|
12759
|
+
this.iesAtlas = packIesAtlas(profiles);
|
|
12760
|
+
this.forgetFixtureAtlas();
|
|
12518
12761
|
this.rebuildFlatBindGroup();
|
|
12519
12762
|
}
|
|
12520
12763
|
/** Each level from the one above it, six faces at a time. See `probePass.ts`. */
|
|
@@ -12959,7 +13202,10 @@ export class WebGPURenderer {
|
|
|
12959
13202
|
* An empty list is a real answer for a frame too small to hold one, and `runBloom` reads it
|
|
12960
13203
|
* as no bloom rather than as a target it should have had.
|
|
12961
13204
|
*/
|
|
12962
|
-
|
|
13205
|
+
/* The resolved picture's size while reconstructing, as the order-independent pair above is. */
|
|
13206
|
+
this.bloomSourceWidth = oitSize[0];
|
|
13207
|
+
this.bloomSourceHeight = oitSize[1];
|
|
13208
|
+
this.bloomLevels = bloomLevelSizes(oitSize[0], oitSize[1]).map((size, index) => {
|
|
12963
13209
|
const texture = device.createTexture({
|
|
12964
13210
|
label: `post.bloom${index}`,
|
|
12965
13211
|
size: [size.width, size.height],
|
|
@@ -13124,13 +13370,30 @@ export class WebGPURenderer {
|
|
|
13124
13370
|
/* The volume's group holds the depth snapshot, which is one of the targets just replaced.
|
|
13125
13371
|
Keeping the old group would clamp every beam against a destroyed texture. */
|
|
13126
13372
|
this.lightVolumeBindGroup = this.buildLightVolumeBindGroup();
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13373
|
+
this.rebuildBloomGroups();
|
|
13374
|
+
this.writeBloomBlocks();
|
|
13375
|
+
}
|
|
13376
|
+
/**
|
|
13377
|
+
* One group per source the bloom chain reads, in the order `runBloom` indexes them: the picture
|
|
13378
|
+
* first, then every level. The prefilter reads the picture; the downsample into level `i` reads
|
|
13379
|
+
* level `i - 1`, which is group `i`; the upsample into level `i - 1` reads level `i`, which is
|
|
13380
|
+
* group `i + 1`.
|
|
13381
|
+
*
|
|
13382
|
+
* **While reconstructing the picture is the resolved one**, which `ensureReconstruction` makes
|
|
13383
|
+
* after this runs and rebuilds these over. The scene target is the jittered render then: a bright
|
|
13384
|
+
* thing smaller than a render texel is caught in some phases and missed in others, and its halo,
|
|
13385
|
+
* wide and over everything round it, flashed with it — a night street's panels breathing a few
|
|
13386
|
+
* levels of luma frame to frame. Built at the drawing buffer's size, which `ensureComposite`
|
|
13387
|
+
* already sized the pyramid for.
|
|
13388
|
+
*/
|
|
13389
|
+
rebuildBloomGroups() {
|
|
13390
|
+
const picture = this.reconstructionWanted && this.reconShownView !== null
|
|
13391
|
+
? this.reconShownView
|
|
13392
|
+
: this.sceneColorView;
|
|
13393
|
+
if (picture === null)
|
|
13394
|
+
return;
|
|
13395
|
+
const { device } = this.surface;
|
|
13396
|
+
this.bloomGroups = [picture, ...this.bloomLevels.map((level) => level.view)].map((view, index) => device.createBindGroup({
|
|
13134
13397
|
label: `post.bloomGroup${index}`,
|
|
13135
13398
|
layout: this.bloomLayout,
|
|
13136
13399
|
entries: [
|
|
@@ -13138,10 +13401,10 @@ export class WebGPURenderer {
|
|
|
13138
13401
|
binding: BLOOM_UNIFORMS,
|
|
13139
13402
|
resource: { buffer: this.bloomUniforms, size: BLOOM_STAGE_SIZE },
|
|
13140
13403
|
},
|
|
13141
|
-
|
|
13404
|
+
{ binding: BLOOM_TEXTURES.uSource.texture, resource: view },
|
|
13405
|
+
{ binding: BLOOM_TEXTURES.uSource.sampler, resource: this.postSampler },
|
|
13142
13406
|
],
|
|
13143
13407
|
}));
|
|
13144
|
-
this.writeBloomBlocks();
|
|
13145
13408
|
}
|
|
13146
13409
|
/**
|
|
13147
13410
|
* Every bloom stage's uniform block, written once per size rather than once per frame.
|
|
@@ -13158,12 +13421,12 @@ export class WebGPURenderer {
|
|
|
13158
13421
|
if (levels.length === 0)
|
|
13159
13422
|
return;
|
|
13160
13423
|
const slot = (index) => (index * BLOOM_SLOT) / 4;
|
|
13161
|
-
/* The prefilter reads the *
|
|
13424
|
+
/* The prefilter reads the *picture*, so its texel is the picture's rather than a level's.
|
|
13162
13425
|
`uTexel`, not `uTexelSize`, and the block carries no strength at all: the composite
|
|
13163
13426
|
applies that when it adds the result back. Both were written wrong here first. */
|
|
13164
13427
|
const prefilter = (name) => this.postField(BLOOM_PREFILTER_FIELDS, name);
|
|
13165
|
-
f[slot(0) + prefilter('uTexel')] = 1 / Math.max(1, this.
|
|
13166
|
-
f[slot(0) + prefilter('uTexel') + 1] = 1 / Math.max(1, this.
|
|
13428
|
+
f[slot(0) + prefilter('uTexel')] = 1 / Math.max(1, this.bloomSourceWidth);
|
|
13429
|
+
f[slot(0) + prefilter('uTexel') + 1] = 1 / Math.max(1, this.bloomSourceHeight);
|
|
13167
13430
|
/* In scene units, which is why an HDR target is what makes a threshold above 1 mean
|
|
13168
13431
|
anything: against a clamped buffer nothing is ever brighter than white. */
|
|
13169
13432
|
f[slot(0) + prefilter('uThreshold')] = this.bloomThresholdSet ?? this.quality.bloomThreshold;
|
|
@@ -13248,6 +13511,7 @@ export class WebGPURenderer {
|
|
|
13248
13511
|
this.fieldComposer = null;
|
|
13249
13512
|
this.driftIndex?.destroy();
|
|
13250
13513
|
this.driftAtlas?.destroy();
|
|
13514
|
+
this.forgetFixtureAtlas();
|
|
13251
13515
|
this.sunTint?.texture.destroy();
|
|
13252
13516
|
this.sunTint = null;
|
|
13253
13517
|
this.glassTint?.dispose();
|