@driftengine/core 4.3.0 → 4.4.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 +261 -0
- package/README.md +1 -1
- package/dist/cinematic/cameraPath.d.ts +53 -0
- package/dist/cinematic/cameraPath.js +118 -0
- package/dist/environment/daylight.d.ts +77 -0
- package/dist/environment/daylight.js +77 -0
- package/dist/geometry/meshConcat.d.ts +9 -0
- package/dist/geometry/meshConcat.js +101 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +14 -1
- package/dist/render/backend/createRenderer.d.ts +13 -1
- package/dist/render/backend/createRenderer.js +6 -5
- package/dist/render/backend/webgl2/renderer.d.ts +147 -1
- package/dist/render/backend/webgl2/renderer.js +402 -23
- package/dist/render/backend/webgpu/buffers.js +46 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +81 -3
- package/dist/render/backend/webgpu/depthPass.js +75 -14
- package/dist/render/backend/webgpu/exposurePass.d.ts +39 -0
- package/dist/render/backend/webgpu/exposurePass.js +190 -0
- package/dist/render/backend/webgpu/fieldCompose.d.ts +11 -0
- package/dist/render/backend/webgpu/fieldCompose.js +13 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +3 -1
- package/dist/render/backend/webgpu/flatPass.js +12 -5
- package/dist/render/backend/webgpu/particlePass.d.ts +8 -0
- package/dist/render/backend/webgpu/particlePass.js +1 -0
- package/dist/render/backend/webgpu/postPass.d.ts +12 -2
- package/dist/render/backend/webgpu/probeBake.d.ts +59 -3
- package/dist/render/backend/webgpu/probeBake.js +97 -7
- package/dist/render/backend/webgpu/probeTargets.d.ts +65 -0
- package/dist/render/backend/webgpu/probeTargets.js +180 -0
- package/dist/render/backend/webgpu/renderer.d.ts +174 -11
- package/dist/render/backend/webgpu/renderer.js +675 -249
- package/dist/render/backend/webgpu/scatterPass.js +7 -7
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +9 -0
- package/dist/render/backend/webgpu/skyPass.js +5 -1
- package/dist/render/backend/webgpu/uniformRing.d.ts +31 -1
- package/dist/render/backend/webgpu/uniformRing.js +53 -3
- package/dist/render/bloomChain.d.ts +6 -0
- package/dist/render/bloomChain.js +8 -0
- package/dist/render/clusteredLights.d.ts +19 -11
- package/dist/render/clusteredLights.js +96 -45
- package/dist/render/cutoutCaster.d.ts +23 -0
- package/dist/render/cutoutCaster.js +20 -0
- package/dist/render/driftLight/bake.d.ts +34 -0
- package/dist/render/driftLight/bake.js +95 -0
- package/dist/render/driftLight/falloff.d.ts +16 -0
- package/dist/render/driftLight/falloff.js +23 -0
- package/dist/render/driftLight/layout.d.ts +59 -0
- package/dist/render/driftLight/layout.js +138 -0
- package/dist/render/driftLight/lightField.d.ts +108 -0
- package/dist/render/driftLight/lightField.js +179 -0
- package/dist/render/driftLight/uniforms.d.ts +28 -0
- package/dist/render/driftLight/uniforms.js +41 -0
- package/dist/render/driftLight/visibility.d.ts +18 -0
- package/dist/render/driftLight/visibility.js +47 -0
- package/dist/render/emptyTexture.d.ts +6 -0
- package/dist/render/emptyTexture.js +19 -0
- package/dist/render/exposurePass.d.ts +48 -0
- package/dist/render/exposurePass.js +172 -0
- package/dist/render/flameLight.d.ts +71 -0
- package/dist/render/flameLight.js +63 -0
- package/dist/render/gi/bounceLights.d.ts +34 -0
- package/dist/render/gi/bounceLights.js +66 -0
- package/dist/render/gi/fieldReach.d.ts +12 -0
- package/dist/render/gi/fieldReach.js +62 -0
- package/dist/render/gi/globalField.d.ts +8 -0
- package/dist/render/gi/globalField.js +31 -0
- package/dist/render/gi/probeTrace.d.ts +3 -1
- package/dist/render/gi/probeTrace.js +3 -1
- package/dist/render/gpuCapability.js +19 -4
- package/dist/render/lightBudget.d.ts +11 -0
- package/dist/render/lightBudget.js +11 -0
- package/dist/render/pointLightSelection.d.ts +16 -0
- package/dist/render/pointLightSelection.js +178 -83
- package/dist/render/probeGrid.d.ts +11 -1
- package/dist/render/probeGrid.js +5 -2
- package/dist/render/probeSweeps.d.ts +45 -0
- package/dist/render/probeSweeps.js +77 -0
- package/dist/render/reflectionProbe.d.ts +43 -1
- package/dist/render/reflectionProbe.js +17 -3
- package/dist/render/renderQuality.d.ts +3 -1
- package/dist/render/rendererHarness.d.ts +9 -0
- package/dist/render/rendererHarness.js +18 -3
- package/dist/render/sceneTarget.d.ts +20 -0
- package/dist/render/sceneTarget.js +62 -1
- package/dist/render/shaders/agx.d.ts +31 -0
- package/dist/render/shaders/agx.js +81 -0
- package/dist/render/shaders/ambientOcclusion.d.ts +24 -5
- package/dist/render/shaders/ambientOcclusion.js +120 -63
- package/dist/render/shaders/cutoutCoverage.d.ts +31 -0
- package/dist/render/shaders/cutoutCoverage.js +40 -0
- package/dist/render/shaders/depth.d.ts +25 -0
- package/dist/render/shaders/depth.js +69 -3
- package/dist/render/shaders/exposure.d.ts +52 -0
- package/dist/render/shaders/exposure.js +124 -0
- package/dist/render/shaders/filmLook.d.ts +47 -0
- package/dist/render/shaders/filmLook.js +98 -0
- package/dist/render/shaders/flat/directionalShadow.js +35 -4
- package/dist/render/shaders/flat/driftLight.d.ts +29 -0
- package/dist/render/shaders/flat/driftLight.js +75 -0
- package/dist/render/shaders/flat/index.js +5 -0
- package/dist/render/shaders/flat/lobes.js +13 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +68 -9
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/probeGrid.js +46 -3
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +100 -0
- package/dist/render/shaders/generated/depth.wgsl.js +100 -0
- package/dist/render/shaders/generated/exposure.wgsl.d.ts +42 -0
- package/dist/render/shaders/generated/exposure.wgsl.js +48 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1800 -1344
- package/dist/render/shaders/generated/flat.wgsl.js +1816 -1360
- package/dist/render/shaders/generated/localExposure.wgsl.d.ts +18 -0
- package/dist/render/shaders/generated/localExposure.wgsl.js +24 -0
- package/dist/render/shaders/generated/rush.wgsl.d.ts +40 -10
- package/dist/render/shaders/generated/rush.wgsl.js +40 -10
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +16 -0
- package/dist/render/shaders/gi/driftLight.wgsl.js +49 -0
- package/dist/render/shaders/gi/probeBake.wgsl.d.ts +29 -4
- package/dist/render/shaders/gi/probeBake.wgsl.js +190 -20
- package/dist/render/shaders/globalMedium.js +4 -0
- package/dist/render/shaders/localExposure.d.ts +35 -0
- package/dist/render/shaders/localExposure.js +138 -0
- package/dist/render/shaders/prefilter.d.ts +1 -1
- package/dist/render/shaders/prefilter.js +6 -0
- package/dist/render/shaders/rush.js +33 -5
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +15 -3
- package/dist/render/shaders/temporalResolve.js +39 -1
- package/dist/render/shadowCasters.d.ts +5 -3
- package/dist/render/shadowMap.js +13 -0
- package/dist/render/surfaceTexture.d.ts +5 -0
- package/package.json +6 -6
- package/scripts/gpuCompute.mjs +24 -3
- package/scripts/png.mjs +23 -12
- package/src/cinematic/cameraPath.ts +178 -0
- package/src/environment/daylight.ts +152 -0
- package/src/geometry/meshConcat.ts +100 -0
- package/src/index.ts +32 -0
- package/src/render/backend/createRenderer.ts +22 -6
- package/src/render/backend/webgl2/renderer.ts +439 -20
- package/src/render/backend/webgpu/buffers.ts +51 -8
- package/src/render/backend/webgpu/depthPass.ts +78 -10
- package/src/render/backend/webgpu/exposurePass.ts +234 -0
- package/src/render/backend/webgpu/fieldCompose.ts +15 -0
- package/src/render/backend/webgpu/flatPass.ts +10 -2
- package/src/render/backend/webgpu/particlePass.ts +9 -0
- package/src/render/backend/webgpu/probeBake.ts +143 -9
- package/src/render/backend/webgpu/probeTargets.ts +211 -0
- package/src/render/backend/webgpu/renderer.ts +801 -270
- package/src/render/backend/webgpu/scatterPass.ts +7 -7
- package/src/render/backend/webgpu/skinPaletteRing.ts +10 -0
- package/src/render/backend/webgpu/skyPass.ts +5 -1
- package/src/render/backend/webgpu/uniformRing.ts +49 -3
- package/src/render/bloomChain.ts +9 -0
- package/src/render/clusteredLights.ts +108 -63
- package/src/render/cutoutCaster.ts +44 -0
- package/src/render/driftLight/bake.ts +125 -0
- package/src/render/driftLight/falloff.ts +26 -0
- package/src/render/driftLight/layout.ts +177 -0
- package/src/render/driftLight/lightField.ts +227 -0
- package/src/render/driftLight/uniforms.ts +61 -0
- package/src/render/driftLight/visibility.ts +58 -0
- package/src/render/emptyTexture.ts +41 -0
- package/src/render/exposurePass.ts +186 -0
- package/src/render/flameLight.ts +135 -0
- package/src/render/gi/bounceLights.ts +68 -0
- package/src/render/gi/fieldReach.ts +74 -0
- package/src/render/gi/globalField.ts +39 -0
- package/src/render/gi/probeTrace.ts +3 -1
- package/src/render/gpuCapability.ts +18 -5
- package/src/render/lightBudget.ts +12 -0
- package/src/render/pointLightSelection.ts +191 -79
- package/src/render/probeGrid.ts +13 -2
- package/src/render/probeSweeps.ts +80 -0
- package/src/render/reflectionProbe.ts +54 -2
- package/src/render/renderQuality.ts +3 -1
- package/src/render/rendererHarness.ts +25 -3
- package/src/render/sceneTarget.ts +75 -1
- package/src/render/shaders/ambientOcclusion.ts +125 -63
- package/src/render/shaders/cutoutCoverage.ts +43 -0
- package/src/render/shaders/depth.ts +80 -3
- package/src/render/shaders/exposure.ts +128 -0
- package/src/render/shaders/filmLook.ts +111 -0
- package/src/render/shaders/flat/directionalShadow.ts +35 -4
- package/src/render/shaders/flat/driftLight.ts +75 -0
- package/src/render/shaders/flat/index.ts +5 -0
- package/src/render/shaders/flat/lobes.ts +13 -0
- package/src/render/shaders/flat/main.ts +68 -9
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/probeGrid.ts +46 -3
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +103 -0
- package/src/render/shaders/generated/exposure.wgsl.ts +51 -0
- package/src/render/shaders/generated/flat.wgsl.ts +1816 -1360
- package/src/render/shaders/generated/localExposure.wgsl.ts +26 -0
- package/src/render/shaders/generated/rush.wgsl.ts +40 -10
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +49 -0
- package/src/render/shaders/gi/probeBake.wgsl.ts +191 -20
- package/src/render/shaders/globalMedium.ts +4 -0
- package/src/render/shaders/localExposure.ts +154 -0
- package/src/render/shaders/prefilter.ts +6 -0
- package/src/render/shaders/rush.ts +33 -5
- package/src/render/shaders/tangentFrame.ts +15 -3
- package/src/render/shaders/temporalResolve.ts +39 -1
- package/src/render/shadowCasters.ts +5 -3
- package/src/render/shadowMap.ts +14 -0
- package/src/render/surfaceTexture.ts +5 -0
|
@@ -179,7 +179,9 @@ export function createFlatBindGroupLayout(device, variant, skinned = false, morp
|
|
|
179
179
|
* other kind against a `uint` texture, and the shader never samples it — every read is a
|
|
180
180
|
* `texelFetch`, which is why the declaration shares a sampler rather than bringing its own.
|
|
181
181
|
*/
|
|
182
|
-
const integer = texture.type === 'usampler2D';
|
|
182
|
+
const integer = texture.type === 'usampler2D' || texture.type === 'usampler3D';
|
|
183
|
+
/* DriftLight's brick index and atlas, the lit pass's only volumes. See `driftLight.ts`. */
|
|
184
|
+
const volume = texture.type === 'sampler3D' || texture.type === 'usampler3D';
|
|
183
185
|
const isShadow = integer || (name !== 'uEnvironment' && (name.endsWith('ShadowMap') || cube || array));
|
|
184
186
|
entries.push({
|
|
185
187
|
binding: texture.texture,
|
|
@@ -188,6 +190,7 @@ export function createFlatBindGroupLayout(device, variant, skinned = false, morp
|
|
|
188
190
|
sampleType: integer ? 'uint' : isShadow ? 'unfilterable-float' : 'float',
|
|
189
191
|
...(cube ? { viewDimension: 'cube' } : {}),
|
|
190
192
|
...(array ? { viewDimension: '2d-array' } : {}),
|
|
193
|
+
...(volume ? { viewDimension: '3d' } : {}),
|
|
191
194
|
},
|
|
192
195
|
});
|
|
193
196
|
/*
|
|
@@ -347,8 +350,10 @@ depthWrite = true, depthLayer = 0,
|
|
|
347
350
|
/** Whether placement and tint arrive per instance. See `FlatVertexOptions`. */
|
|
348
351
|
instanced = false,
|
|
349
352
|
/** Which order-independent buffer this pipeline writes, if any. The key must carry it. */
|
|
350
|
-
oit = 'none'
|
|
351
|
-
|
|
353
|
+
oit = 'none',
|
|
354
|
+
/** Culls nothing, for a surface seen from both faces. The key must carry it: `|2s`. */
|
|
355
|
+
doubleSided = false) {
|
|
356
|
+
return cache.get(key, () => flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed, depthWrite, depthLayer, instanced, oit, doubleSided));
|
|
352
357
|
}
|
|
353
358
|
/**
|
|
354
359
|
* The same pipeline, compiled off the main thread.
|
|
@@ -418,7 +423,9 @@ instanced = false,
|
|
|
418
423
|
* ordinary translucent path uses, weighted by a uniform. That is what keeps this off
|
|
419
424
|
* `flatFrag`'s permutation axis, which `scripts/wgsl.ts` measures at about 247 KB gzipped.
|
|
420
425
|
*/
|
|
421
|
-
oit = 'none'
|
|
426
|
+
oit = 'none',
|
|
427
|
+
/** Culls nothing: a surface seen from both faces. */
|
|
428
|
+
doubleSided = false) {
|
|
422
429
|
const offset = depthOffsetForLayer(depthLayer);
|
|
423
430
|
return {
|
|
424
431
|
label: key,
|
|
@@ -459,7 +466,7 @@ oit = 'none') {
|
|
|
459
466
|
: { format: cache.format }),
|
|
460
467
|
],
|
|
461
468
|
},
|
|
462
|
-
primitive: { topology: 'triangle-list', cullMode: 'back' },
|
|
469
|
+
primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
|
|
463
470
|
multisample: { count: cache.sampleCount },
|
|
464
471
|
/*
|
|
465
472
|
* `less`, matching the WebGL2 path's `LESS` rather than `LEQUAL`, and for the reason
|
|
@@ -50,6 +50,14 @@ export interface GpuParticles {
|
|
|
50
50
|
readonly fragInts: Int32Array;
|
|
51
51
|
readonly fields: UniformFields;
|
|
52
52
|
readonly bindGroup: GPUBindGroup;
|
|
53
|
+
/**
|
|
54
|
+
* The encoder this batch was last drawn into, by the renderer's count, or -1.
|
|
55
|
+
*
|
|
56
|
+
* **A second draw into the same encoder is refused**, because this batch's buffers are written
|
|
57
|
+
* at each draw and every write lands before the encoder runs: both draws would show the second.
|
|
58
|
+
* WebGL2 has no such limit, and a limit one backend has is said, not left to a wrong picture.
|
|
59
|
+
*/
|
|
60
|
+
drawnInEncoder: number;
|
|
53
61
|
dispose(): void;
|
|
54
62
|
}
|
|
55
63
|
/**
|
|
@@ -145,6 +145,7 @@ export function createGpuParticles(device, capacity, material, blend, stretchSec
|
|
|
145
145
|
const vertStaging = new ArrayBuffer(PARTICLE_VERT_SIZE);
|
|
146
146
|
const fragStaging = new ArrayBuffer(fragSize);
|
|
147
147
|
return {
|
|
148
|
+
drawnInEncoder: -1,
|
|
148
149
|
corners: cornerBuffer,
|
|
149
150
|
instances,
|
|
150
151
|
indexBuffer,
|
|
@@ -26,7 +26,7 @@ import type { PipelineCache } from './pipelineCache.ts';
|
|
|
26
26
|
* float target the generated shaders can sample as an ordinary texture.
|
|
27
27
|
*/
|
|
28
28
|
export declare const RUSH_FRAG_FIELDS: UniformFields;
|
|
29
|
-
export declare const RUSH_FRAG_SIZE:
|
|
29
|
+
export declare const RUSH_FRAG_SIZE: 192;
|
|
30
30
|
declare const RUSH_UNIFORMS: 1;
|
|
31
31
|
declare const RUSH_TEXTURES: {
|
|
32
32
|
readonly uScene: {
|
|
@@ -49,9 +49,19 @@ declare const RUSH_TEXTURES: {
|
|
|
49
49
|
readonly sampler: 39;
|
|
50
50
|
readonly type: "sampler2D";
|
|
51
51
|
};
|
|
52
|
-
readonly
|
|
52
|
+
readonly uExposureHeld: {
|
|
53
53
|
readonly texture: 40;
|
|
54
54
|
readonly sampler: 41;
|
|
55
|
+
readonly type: "sampler2D";
|
|
56
|
+
};
|
|
57
|
+
readonly uExposureLocal: {
|
|
58
|
+
readonly texture: 42;
|
|
59
|
+
readonly sampler: 43;
|
|
60
|
+
readonly type: "sampler2D";
|
|
61
|
+
};
|
|
62
|
+
readonly uGradeLut: {
|
|
63
|
+
readonly texture: 44;
|
|
64
|
+
readonly sampler: 45;
|
|
55
65
|
readonly type: "sampler3D";
|
|
56
66
|
};
|
|
57
67
|
};
|
|
@@ -51,6 +51,19 @@ import type { PassDistanceField } from '../../pass.ts';
|
|
|
51
51
|
* thirteen frames, which at sixty a second is a fifth of a second for a light that moved.
|
|
52
52
|
*/
|
|
53
53
|
export declare const PROBES_PER_FRAME = 5;
|
|
54
|
+
/**
|
|
55
|
+
* How much of what a probe held each refresh keeps, once the grid has been traced whole.
|
|
56
|
+
*
|
|
57
|
+
* Four fifths, so a probe is about the mean of its last five refreshes. The directions no longer
|
|
58
|
+
* turn, so a still scene gives the same estimate every time and this has nothing to average there;
|
|
59
|
+
* what it smooths is a light that moves, whose sun crosses from one direction of the set to the next
|
|
60
|
+
* in steps. What it gives up is how fast the bounce follows: a fifth of the way each time a probe
|
|
61
|
+
* comes round, which with 64
|
|
62
|
+
* probes at five a frame is a time constant of about half a second at 120 frames and a second at
|
|
63
|
+
* 60. It is counted in refreshes rather than seconds, because the renderer is given no clock; what
|
|
64
|
+
* would make it wrong is a scene whose light jumps and has to be followed at once.
|
|
65
|
+
*/
|
|
66
|
+
export declare const PROBE_HISTORY = 0.8;
|
|
54
67
|
export interface ProbeBakeTargets {
|
|
55
68
|
/** The array the shading samples, whose irradiance level this writes. */
|
|
56
69
|
readonly array: GPUTexture;
|
|
@@ -61,6 +74,25 @@ export interface ProbeBakeTargets {
|
|
|
61
74
|
readonly edge: number;
|
|
62
75
|
readonly format: GPUTextureFormat;
|
|
63
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* A DriftLight field as the bake reads it: its two volumes and its two uniform rows, resolved for a
|
|
79
|
+
* probe by `resolveDriftLight` (radius zero, every fixed light through the volume).
|
|
80
|
+
*/
|
|
81
|
+
/**
|
|
82
|
+
* The frame's exact lights as the bake lights a hit with them: `resolveBounceLights`'s packing,
|
|
83
|
+
* how many it packed, and the frame's falloff code.
|
|
84
|
+
*/
|
|
85
|
+
export interface ProbeBakeLamps {
|
|
86
|
+
readonly data: Float32Array;
|
|
87
|
+
readonly count: number;
|
|
88
|
+
readonly falloff: number;
|
|
89
|
+
}
|
|
90
|
+
export interface ProbeBakeLightField {
|
|
91
|
+
readonly index: GPUTextureView;
|
|
92
|
+
readonly atlas: GPUTextureView;
|
|
93
|
+
readonly light: ArrayLike<number>;
|
|
94
|
+
readonly origin: ArrayLike<number>;
|
|
95
|
+
}
|
|
64
96
|
export declare class ProbeBaker {
|
|
65
97
|
private readonly device;
|
|
66
98
|
private readonly targets;
|
|
@@ -75,14 +107,21 @@ export declare class ProbeBaker {
|
|
|
75
107
|
private readonly marchBuffer;
|
|
76
108
|
private readonly bakeBuffer;
|
|
77
109
|
private readonly volumeBuffer;
|
|
110
|
+
private readonly lampBuffer;
|
|
78
111
|
private readonly scheduleBuffer;
|
|
79
112
|
private readonly rayBuffer;
|
|
80
113
|
private readonly irradianceBuffer;
|
|
81
114
|
private readonly blitParams;
|
|
82
115
|
private readonly blitGroup;
|
|
83
|
-
/** Rebuilt only when the field it was built against
|
|
116
|
+
/** Rebuilt only when the field or the light field's volumes it was built against change. */
|
|
84
117
|
private computeGroup;
|
|
85
118
|
private boundTo;
|
|
119
|
+
private boundAtlas;
|
|
120
|
+
/** DriftLight's volumes while a scene has none whole: one texel each, never read while off. */
|
|
121
|
+
private readonly emptyIndex;
|
|
122
|
+
private readonly emptyAtlas;
|
|
123
|
+
private readonly emptyIndexView;
|
|
124
|
+
private readonly emptyAtlasView;
|
|
86
125
|
private readonly marchScratch;
|
|
87
126
|
private readonly marchInts;
|
|
88
127
|
private readonly bakeScratch;
|
|
@@ -134,15 +173,32 @@ export declare class ProbeBaker {
|
|
|
134
173
|
setGrid(layers: number): void;
|
|
135
174
|
/** Whether every layer has had its chain written, which is what makes the array safe to sample. */
|
|
136
175
|
get complete(): boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Whether the trace has written `layer`'s irradiance, after which that level is the trace's alone.
|
|
178
|
+
*
|
|
179
|
+
* A rasterised bake of a traced layer refreshes only its roughness chain. Two writers of one
|
|
180
|
+
* level is a layer that jumps to the raster's answer and is pulled back over the next refreshes,
|
|
181
|
+
* which on screen is light sweeping across a scene a probe at a time. What it gives up is the
|
|
182
|
+
* raster's own diffuse answer for that layer, which the trace replaces within a few frames anyway.
|
|
183
|
+
*/
|
|
184
|
+
traced(layer: number): boolean;
|
|
137
185
|
/**
|
|
138
186
|
* Refresh this frame's share of the grid.
|
|
139
187
|
*
|
|
140
188
|
* Returns how many probes were baked, which is zero when there is nothing to trace against.
|
|
141
189
|
*/
|
|
142
|
-
bake(encoder: GPUCommandEncoder, field: PassDistanceField, grid: ProbeGrid, seeded: boolean, sun: {
|
|
190
|
+
bake(encoder: GPUCommandEncoder, field: PassDistanceField, grid: ProbeGrid, seeded: boolean, lightField: ProbeBakeLightField | null, lamps: ProbeBakeLamps, sun: {
|
|
143
191
|
readonly direction: ArrayLike<number>;
|
|
144
192
|
readonly colour: ArrayLike<number>;
|
|
145
|
-
|
|
193
|
+
/** Outside, flat, for a frame that drew no sky: the environment's ambient. */
|
|
194
|
+
readonly ambient: ArrayLike<number>;
|
|
195
|
+
/** The sky the frame drew, which an escaping ray reads when there is one. */
|
|
196
|
+
readonly sky: {
|
|
197
|
+
readonly top: ArrayLike<number>;
|
|
198
|
+
readonly horizon: ArrayLike<number>;
|
|
199
|
+
readonly deep: ArrayLike<number>;
|
|
200
|
+
readonly sunDir: ArrayLike<number>;
|
|
201
|
+
} | null;
|
|
146
202
|
}): number;
|
|
147
203
|
dispose(): void;
|
|
148
204
|
}
|
|
@@ -41,9 +41,11 @@
|
|
|
41
41
|
* irradiance level is rewritten, which is the level the diffuse term reads.
|
|
42
42
|
*/
|
|
43
43
|
import { PROBE_DIRECTIONS } from '../../gi/probeTrace.js';
|
|
44
|
+
import { BOUNCE_LIGHT_FLOATS } from '../../gi/bounceLights.js';
|
|
44
45
|
import { probeUpdateSchedule } from '../../gi/probeVolume.js';
|
|
46
|
+
import { probesTheFieldReaches } from '../../gi/fieldReach.js';
|
|
45
47
|
import { irradianceLevelFor } from '../../prefilterEnvMap.js';
|
|
46
|
-
import { BAKE_COUNTS, BAKE_EDGE, BAKE_FRAME, BAKE_ORIGIN, BAKE_SCHEDULED, BAKE_SPACING, BAKE_SKY_COLOUR, BAKE_SUN_COLOUR, BAKE_SUN_DIR, PROBE_BAKE_FLOATS, PROBE_BAKE_WORKGROUP, PROBE_BLIT_FLOATS, PROBE_BLIT_STRIDE, PROBE_BLIT_WGSL, PROBE_RAY_FLOATS, PROBE_TEXEL_FLOATS, probeBakeWgsl, } from '../../shaders/gi/probeBake.wgsl.js';
|
|
48
|
+
import { BAKE_COUNTS, BAKE_DRIFT_LIGHT, BAKE_DRIFT_ORIGIN, BAKE_EDGE, BAKE_FALLOFF, BAKE_LIGHT_COUNT, BOUNCE_LIGHT_ROWS, BAKE_FRAME, BAKE_ORIGIN, BAKE_SCHEDULED, BAKE_SPACING, BAKE_HISTORY, BAKE_SKY_COLOUR, BAKE_SKY_DEEP, BAKE_SKY_DRAWN, BAKE_SKY_SUN, BAKE_SKY_TOP, BAKE_SUN_COLOUR, BAKE_SUN_DIR, PROBE_BAKE_FLOATS, PROBE_BAKE_WORKGROUP, PROBE_BLIT_FLOATS, PROBE_BLIT_STRIDE, PROBE_BLIT_WGSL, PROBE_RAY_FLOATS, PROBE_TEXEL_FLOATS, probeBakeWgsl, } from '../../shaders/gi/probeBake.wgsl.js';
|
|
47
49
|
import { PROBE_MARCH_DEFAULTS, PROBE_MARCH_FLOATS } from '../../shaders/gi/probeTrace.wgsl.js';
|
|
48
50
|
import { PROBE_VOLUME_FLOATS, VOLUME_COUNTS, VOLUME_EDGE, VOLUME_INV_SPACING, VOLUME_LEVEL, VOLUME_ORIGIN, VOLUME_RADIANCE_EDGE, VOLUME_RADIANCE_LEVEL, } from '../../shaders/gi/probeVolume.wgsl.js';
|
|
49
51
|
const COPY_DST = 0x0008;
|
|
@@ -63,6 +65,19 @@ const VISIBILITY_COMPUTE = 0x4;
|
|
|
63
65
|
* thirteen frames, which at sixty a second is a fifth of a second for a light that moved.
|
|
64
66
|
*/
|
|
65
67
|
export const PROBES_PER_FRAME = 5;
|
|
68
|
+
/**
|
|
69
|
+
* How much of what a probe held each refresh keeps, once the grid has been traced whole.
|
|
70
|
+
*
|
|
71
|
+
* Four fifths, so a probe is about the mean of its last five refreshes. The directions no longer
|
|
72
|
+
* turn, so a still scene gives the same estimate every time and this has nothing to average there;
|
|
73
|
+
* what it smooths is a light that moves, whose sun crosses from one direction of the set to the next
|
|
74
|
+
* in steps. What it gives up is how fast the bounce follows: a fifth of the way each time a probe
|
|
75
|
+
* comes round, which with 64
|
|
76
|
+
* probes at five a frame is a time constant of about half a second at 120 frames and a second at
|
|
77
|
+
* 60. It is counted in refreshes rather than seconds, because the renderer is given no clock; what
|
|
78
|
+
* would make it wrong is a scene whose light jumps and has to be followed at once.
|
|
79
|
+
*/
|
|
80
|
+
export const PROBE_HISTORY = 0.8;
|
|
66
81
|
export class ProbeBaker {
|
|
67
82
|
device;
|
|
68
83
|
targets;
|
|
@@ -77,14 +92,21 @@ export class ProbeBaker {
|
|
|
77
92
|
marchBuffer;
|
|
78
93
|
bakeBuffer;
|
|
79
94
|
volumeBuffer;
|
|
95
|
+
lampBuffer;
|
|
80
96
|
scheduleBuffer;
|
|
81
97
|
rayBuffer;
|
|
82
98
|
irradianceBuffer;
|
|
83
99
|
blitParams;
|
|
84
100
|
blitGroup;
|
|
85
|
-
/** Rebuilt only when the field it was built against
|
|
101
|
+
/** Rebuilt only when the field or the light field's volumes it was built against change. */
|
|
86
102
|
computeGroup = null;
|
|
87
103
|
boundTo = null;
|
|
104
|
+
boundAtlas = null;
|
|
105
|
+
/** DriftLight's volumes while a scene has none whole: one texel each, never read while off. */
|
|
106
|
+
emptyIndex;
|
|
107
|
+
emptyAtlas;
|
|
108
|
+
emptyIndexView;
|
|
109
|
+
emptyAtlasView;
|
|
88
110
|
marchScratch = new Float32Array(PROBE_MARCH_FLOATS);
|
|
89
111
|
marchInts = new Uint32Array(this.marchScratch.buffer);
|
|
90
112
|
bakeScratch = new Float32Array(PROBE_BAKE_FLOATS);
|
|
@@ -146,8 +168,25 @@ export class ProbeBaker {
|
|
|
146
168
|
},
|
|
147
169
|
{ binding: 9, visibility: VISIBILITY_COMPUTE, sampler: { type: 'filtering' } },
|
|
148
170
|
{ binding: 10, visibility: VISIBILITY_COMPUTE, buffer: storageRead },
|
|
171
|
+
{
|
|
172
|
+
binding: 11,
|
|
173
|
+
visibility: VISIBILITY_COMPUTE,
|
|
174
|
+
texture: { sampleType: 'uint', viewDimension: '3d' },
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
binding: 12,
|
|
178
|
+
visibility: VISIBILITY_COMPUTE,
|
|
179
|
+
texture: { sampleType: 'float', viewDimension: '3d' },
|
|
180
|
+
},
|
|
181
|
+
{ binding: 13, visibility: VISIBILITY_COMPUTE, sampler: { type: 'filtering' } },
|
|
182
|
+
{ binding: 14, visibility: VISIBILITY_COMPUTE, buffer: { type: 'uniform' } },
|
|
149
183
|
],
|
|
150
184
|
});
|
|
185
|
+
const emptyVolume = (label, format) => device.createTexture({ label, size: [1, 1, 1], dimension: '3d', format, usage: 0x4 });
|
|
186
|
+
this.emptyIndex = emptyVolume('probe bake.emptyDriftLightIndex', 'r32uint');
|
|
187
|
+
this.emptyAtlas = emptyVolume('probe bake.emptyDriftLightAtlas', 'rgba16float');
|
|
188
|
+
this.emptyIndexView = this.emptyIndex.createView({ dimension: '3d' });
|
|
189
|
+
this.emptyAtlasView = this.emptyAtlas.createView({ dimension: '3d' });
|
|
151
190
|
const computeLayout = device.createPipelineLayout({
|
|
152
191
|
bindGroupLayouts: [this.computeLayout],
|
|
153
192
|
});
|
|
@@ -202,6 +241,7 @@ export class ProbeBaker {
|
|
|
202
241
|
this.marchBuffer = this.uniform('march', PROBE_MARCH_FLOATS * 4);
|
|
203
242
|
this.bakeBuffer = this.uniform('bake', PROBE_BAKE_FLOATS * 4);
|
|
204
243
|
this.volumeBuffer = this.uniform('volume', PROBE_VOLUME_FLOATS * 4);
|
|
244
|
+
this.lampBuffer = this.uniform('lamps', BOUNCE_LIGHT_ROWS * 16);
|
|
205
245
|
this.scheduleBuffer = this.storage('schedule', PROBES_PER_FRAME * 4);
|
|
206
246
|
this.rayBuffer = this.storage('rays', PROBES_PER_FRAME * PROBE_DIRECTIONS * PROBE_RAY_FLOATS * 4);
|
|
207
247
|
this.irradianceBuffer = this.storage('irradiance', PROBES_PER_FRAME * this.texels * PROBE_TEXEL_FLOATS * 4);
|
|
@@ -348,15 +388,27 @@ export class ProbeBaker {
|
|
|
348
388
|
}
|
|
349
389
|
return true;
|
|
350
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Whether the trace has written `layer`'s irradiance, after which that level is the trace's alone.
|
|
393
|
+
*
|
|
394
|
+
* A rasterised bake of a traced layer refreshes only its roughness chain. Two writers of one
|
|
395
|
+
* level is a layer that jumps to the raster's answer and is pulled back over the next refreshes,
|
|
396
|
+
* which on screen is light sweeping across a scene a probe at a time. What it gives up is the
|
|
397
|
+
* raster's own diffuse answer for that layer, which the trace replaces within a few frames anyway.
|
|
398
|
+
*/
|
|
399
|
+
traced(layer) {
|
|
400
|
+
return this.filled[layer] === 1;
|
|
401
|
+
}
|
|
351
402
|
/**
|
|
352
403
|
* Refresh this frame's share of the grid.
|
|
353
404
|
*
|
|
354
405
|
* Returns how many probes were baked, which is zero when there is nothing to trace against.
|
|
355
406
|
*/
|
|
356
|
-
bake(encoder, field, grid, seeded, sun) {
|
|
407
|
+
bake(encoder, field, grid, seeded, lightField, lamps, sun) {
|
|
357
408
|
if (this.layers !== grid.layers)
|
|
358
409
|
this.setGrid(grid.layers);
|
|
359
|
-
|
|
410
|
+
/* A probe the field does not reach keeps what it has. See `gi/fieldReach.ts`. */
|
|
411
|
+
const scheduled = probesTheFieldReaches(grid, this.scheduleScratch, probeUpdateSchedule(grid.layers, PROBES_PER_FRAME, this.frameIndex, this.scheduleScratch), field.outerBounds);
|
|
360
412
|
if (scheduled === 0)
|
|
361
413
|
return 0;
|
|
362
414
|
const queue = this.device.queue;
|
|
@@ -389,8 +441,19 @@ export class ProbeBaker {
|
|
|
389
441
|
this.volumeScratch[VOLUME_COUNTS + axis] = grid.counts[axis];
|
|
390
442
|
}
|
|
391
443
|
this.bakeScratch[BAKE_EDGE] = this.levelEdge;
|
|
392
|
-
|
|
444
|
+
/*
|
|
445
|
+
* **The same directions at every refresh.** The set used to turn with the frame so a probe
|
|
446
|
+
* would converge, but only a running mean converges and a blended history never stops moving:
|
|
447
|
+
* every refresh was a new estimate a few per cent from the last, and a courtyard held still
|
|
448
|
+
* flickered, 77% of its pixels moving by more than 3 levels over a dozen frames against 2.5%
|
|
449
|
+
* rasterised. Held, it is 4.8%. What it gives up is the quadrature error of one set of 256
|
|
450
|
+
* directions, which stays where it is rather than averaging away: the held frame read 2.5 levels
|
|
451
|
+
* brighter than the turning one's mean. What would make it wrong is a small bright source that
|
|
452
|
+
* falls between the directions of every probe that sees it.
|
|
453
|
+
*/
|
|
454
|
+
this.bakeScratch[BAKE_FRAME] = 0;
|
|
393
455
|
this.bakeScratch[BAKE_SCHEDULED] = scheduled;
|
|
456
|
+
this.bakeScratch[BAKE_HISTORY] = this.complete ? PROBE_HISTORY : 0;
|
|
394
457
|
/*
|
|
395
458
|
* **The sun, because the trace shades what it hits rather than reading a capture.** That is
|
|
396
459
|
* the whole of what a traced grid buys over a rasterised one: a light that moves changes the
|
|
@@ -399,7 +462,24 @@ export class ProbeBaker {
|
|
|
399
462
|
for (let axis = 0; axis < 3; axis += 1) {
|
|
400
463
|
this.bakeScratch[BAKE_SUN_DIR + axis] = sun.direction[axis] ?? 0;
|
|
401
464
|
this.bakeScratch[BAKE_SUN_COLOUR + axis] = sun.colour[axis] ?? 0;
|
|
402
|
-
|
|
465
|
+
const sky = sun.sky;
|
|
466
|
+
this.bakeScratch[BAKE_SKY_COLOUR + axis] =
|
|
467
|
+
sky === null ? (sun.ambient[axis] ?? 0) : (sky.horizon[axis] ?? 0);
|
|
468
|
+
this.bakeScratch[BAKE_SKY_TOP + axis] = sky === null ? 0 : (sky.top[axis] ?? 0);
|
|
469
|
+
this.bakeScratch[BAKE_SKY_DEEP + axis] = sky === null ? 0 : (sky.deep[axis] ?? 0);
|
|
470
|
+
this.bakeScratch[BAKE_SKY_SUN + axis] = sky === null ? 0 : (sky.sunDir[axis] ?? 0);
|
|
471
|
+
}
|
|
472
|
+
this.bakeScratch[BAKE_SKY_DRAWN] = sun.sky === null ? 0 : 1;
|
|
473
|
+
/* The summed light of the scene's fixed lights, or zeros that switch it off. */
|
|
474
|
+
for (let lane = 0; lane < 4; lane += 1) {
|
|
475
|
+
this.bakeScratch[BAKE_DRIFT_LIGHT + lane] = lightField?.light[lane] ?? 0;
|
|
476
|
+
this.bakeScratch[BAKE_DRIFT_ORIGIN + lane] = lightField?.origin[lane] ?? 0;
|
|
477
|
+
}
|
|
478
|
+
/* And the frame's exact lights, which light what a ray strikes as they light the frame. */
|
|
479
|
+
this.bakeScratch[BAKE_LIGHT_COUNT] = lamps.count;
|
|
480
|
+
this.bakeScratch[BAKE_FALLOFF] = lamps.falloff;
|
|
481
|
+
if (lamps.count > 0) {
|
|
482
|
+
queue.writeBuffer(this.lampBuffer, 0, lamps.data, 0, lamps.count * BOUNCE_LIGHT_FLOATS);
|
|
403
483
|
}
|
|
404
484
|
queue.writeBuffer(this.bakeBuffer, 0, this.bakeScratch);
|
|
405
485
|
this.volumeScratch[VOLUME_EDGE] = this.levelEdge;
|
|
@@ -412,7 +492,8 @@ export class ProbeBaker {
|
|
|
412
492
|
this.volumeScratch[VOLUME_RADIANCE_EDGE] = this.targets.edge;
|
|
413
493
|
this.volumeScratch[VOLUME_RADIANCE_LEVEL] = 0;
|
|
414
494
|
queue.writeBuffer(this.volumeBuffer, 0, this.volumeScratch);
|
|
415
|
-
|
|
495
|
+
const atlas = lightField?.atlas ?? this.emptyAtlasView;
|
|
496
|
+
if (this.boundTo !== field || this.boundAtlas !== atlas || this.computeGroup === null) {
|
|
416
497
|
this.computeGroup = this.device.createBindGroup({
|
|
417
498
|
label: 'probe bake',
|
|
418
499
|
layout: this.computeLayout,
|
|
@@ -428,9 +509,14 @@ export class ProbeBaker {
|
|
|
428
509
|
{ binding: 8, resource: this.targets.view },
|
|
429
510
|
{ binding: 9, resource: this.targets.sampler },
|
|
430
511
|
{ binding: 10, resource: { buffer: field.albedo } },
|
|
512
|
+
{ binding: 11, resource: lightField?.index ?? this.emptyIndexView },
|
|
513
|
+
{ binding: 12, resource: atlas },
|
|
514
|
+
{ binding: 13, resource: this.targets.sampler },
|
|
515
|
+
{ binding: 14, resource: { buffer: this.lampBuffer } },
|
|
431
516
|
],
|
|
432
517
|
});
|
|
433
518
|
this.boundTo = field;
|
|
519
|
+
this.boundAtlas = atlas;
|
|
434
520
|
}
|
|
435
521
|
const pass = encoder.beginComputePass({
|
|
436
522
|
label: 'probe bake',
|
|
@@ -485,9 +571,13 @@ export class ProbeBaker {
|
|
|
485
571
|
this.rayBuffer.destroy();
|
|
486
572
|
this.irradianceBuffer.destroy();
|
|
487
573
|
this.blitParams.destroy();
|
|
574
|
+
this.lampBuffer.destroy();
|
|
575
|
+
this.emptyIndex.destroy();
|
|
576
|
+
this.emptyAtlas.destroy();
|
|
488
577
|
this.views = [];
|
|
489
578
|
this.computeGroup = null;
|
|
490
579
|
this.boundTo = null;
|
|
580
|
+
this.boundAtlas = null;
|
|
491
581
|
}
|
|
492
582
|
}
|
|
493
583
|
const BLACK = { r: 0, g: 0, b: 0, a: 1 };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a probe bake draws into and reads from, made once and kept rather than made per bake.
|
|
3
|
+
*
|
|
4
|
+
* **A bake is a per-frame path now, and it was written as a one-off.** It created a depth and a
|
|
5
|
+
* multisampled colour texture per call and destroyed them after, a view per face, and for each
|
|
6
|
+
* probe it completed a view and a bind group per level and face of the blur and the convolution.
|
|
7
|
+
* That was a load-time cost while a scene baked its probes once. Since a bake can be spread a few
|
|
8
|
+
* faces a frame (`ProbeBakeOptions.faces`) and a crossfading grid re-bakes as the light moves, it
|
|
9
|
+
* is every frame, and on the native host, where each object costs most, a courtyard with the sun
|
|
10
|
+
* moving spent a third of its CPU in bakes. The capture cube lives as long as the renderer, so
|
|
11
|
+
* every view of it can too; the array is replaced only when the grid is, and `forgetArray` drops
|
|
12
|
+
* what was made against the old one.
|
|
13
|
+
*
|
|
14
|
+
* What it gives up is holding the attachments between bakes: a depth and a colour texture the size
|
|
15
|
+
* of one face, at the scene's sample count, kept for as long as the renderer is.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ProbeTargets {
|
|
18
|
+
private readonly device;
|
|
19
|
+
private readonly probe;
|
|
20
|
+
private readonly size;
|
|
21
|
+
/** Level 0 of each face, which the bake's passes resolve into. */
|
|
22
|
+
readonly faceViews: readonly GPUTextureView[];
|
|
23
|
+
/** The whole chain as a cube, which the convolution samples. */
|
|
24
|
+
readonly wholeCube: GPUTextureView;
|
|
25
|
+
/** Level `l - 1` as a cube, which the blur into level `l` samples; index 0 unused. */
|
|
26
|
+
private readonly chainSources;
|
|
27
|
+
/** Level `l` of face `f`, at `l * faces + f`, which the blur writes. */
|
|
28
|
+
private readonly chainTargets;
|
|
29
|
+
private depth;
|
|
30
|
+
private depthView;
|
|
31
|
+
private colorMsaa;
|
|
32
|
+
private colorMsaaView;
|
|
33
|
+
private attachmentKey;
|
|
34
|
+
private readonly arrayViews;
|
|
35
|
+
private chainGroups;
|
|
36
|
+
private prefilterGroup;
|
|
37
|
+
private groupsBuffer;
|
|
38
|
+
constructor(device: GPUDevice, probe: GPUTexture, size: number);
|
|
39
|
+
/**
|
|
40
|
+
* The depth and, above one sample, the multisampled colour a face is drawn into: made on the first
|
|
41
|
+
* bake and again only if the sample count or the format has changed since.
|
|
42
|
+
*/
|
|
43
|
+
attachments(samples: number, format: GPUTextureFormat): {
|
|
44
|
+
depth: GPUTextureView;
|
|
45
|
+
colorMsaa: GPUTextureView | null;
|
|
46
|
+
};
|
|
47
|
+
/** The blur's target for `level` of `face`. */
|
|
48
|
+
chainTarget(level: number, face: number): GPUTextureView;
|
|
49
|
+
/**
|
|
50
|
+
* The blur's group for `level`, over `buffer`: the uniforms, the level above as a cube, the
|
|
51
|
+
* sampler. Remade only if the ring's buffer has been replaced.
|
|
52
|
+
*/
|
|
53
|
+
chainGroup(layout: GPUBindGroupLayout, buffer: GPUBuffer, sampler: GPUSampler, level: number): GPUBindGroup;
|
|
54
|
+
/** The convolution's group over `buffer`: the uniforms, the whole chain, the sampler. */
|
|
55
|
+
prefilterGroupFor(layout: GPUBindGroupLayout, buffer: GPUBuffer, sampler: GPUSampler): GPUBindGroup;
|
|
56
|
+
/** `level` of `layer` of the grid's array, which the convolution writes. */
|
|
57
|
+
arrayView(array: GPUTexture, layer: number, level: number): GPUTextureView;
|
|
58
|
+
/** The grid's array has been replaced: nothing made against the old one may be handed out. */
|
|
59
|
+
forgetArray(): void;
|
|
60
|
+
dispose(): void;
|
|
61
|
+
private faceLevel;
|
|
62
|
+
/** Groups hold the ring's buffer, so a replaced buffer makes every one of them stale. */
|
|
63
|
+
private forBuffer;
|
|
64
|
+
private destroyAttachments;
|
|
65
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a probe bake draws into and reads from, made once and kept rather than made per bake.
|
|
3
|
+
*
|
|
4
|
+
* **A bake is a per-frame path now, and it was written as a one-off.** It created a depth and a
|
|
5
|
+
* multisampled colour texture per call and destroyed them after, a view per face, and for each
|
|
6
|
+
* probe it completed a view and a bind group per level and face of the blur and the convolution.
|
|
7
|
+
* That was a load-time cost while a scene baked its probes once. Since a bake can be spread a few
|
|
8
|
+
* faces a frame (`ProbeBakeOptions.faces`) and a crossfading grid re-bakes as the light moves, it
|
|
9
|
+
* is every frame, and on the native host, where each object costs most, a courtyard with the sun
|
|
10
|
+
* moving spent a third of its CPU in bakes. The capture cube lives as long as the renderer, so
|
|
11
|
+
* every view of it can too; the array is replaced only when the grid is, and `forgetArray` drops
|
|
12
|
+
* what was made against the old one.
|
|
13
|
+
*
|
|
14
|
+
* What it gives up is holding the attachments between bakes: a depth and a colour texture the size
|
|
15
|
+
* of one face, at the scene's sample count, kept for as long as the renderer is.
|
|
16
|
+
*/
|
|
17
|
+
import { DEPTH_FORMAT } from '../../depthConvention.js';
|
|
18
|
+
import { PREFILTER_SAMPLER_BINDING, PREFILTER_TEXTURE_BINDING, PREFILTER_UNIFORM_BINDING, PROBE_FACE_BASIS, PROBE_MIP_UNIFORM_SIZE, probeLevels, } from './probePass.js';
|
|
19
|
+
export class ProbeTargets {
|
|
20
|
+
device;
|
|
21
|
+
probe;
|
|
22
|
+
size;
|
|
23
|
+
/** Level 0 of each face, which the bake's passes resolve into. */
|
|
24
|
+
faceViews;
|
|
25
|
+
/** The whole chain as a cube, which the convolution samples. */
|
|
26
|
+
wholeCube;
|
|
27
|
+
/** Level `l - 1` as a cube, which the blur into level `l` samples; index 0 unused. */
|
|
28
|
+
chainSources = [];
|
|
29
|
+
/** Level `l` of face `f`, at `l * faces + f`, which the blur writes. */
|
|
30
|
+
chainTargets = [];
|
|
31
|
+
depth = null;
|
|
32
|
+
depthView = null;
|
|
33
|
+
colorMsaa = null;
|
|
34
|
+
colorMsaaView = null;
|
|
35
|
+
attachmentKey = '';
|
|
36
|
+
/* The array's level-and-layer views and the groups over this ring's buffer, each made once. */
|
|
37
|
+
arrayViews = new Map();
|
|
38
|
+
chainGroups = [];
|
|
39
|
+
prefilterGroup = null;
|
|
40
|
+
groupsBuffer = null;
|
|
41
|
+
constructor(device, probe, size) {
|
|
42
|
+
this.device = device;
|
|
43
|
+
this.probe = probe;
|
|
44
|
+
this.size = size;
|
|
45
|
+
const faces = [];
|
|
46
|
+
for (let face = 0; face < PROBE_FACE_BASIS.length; face++) {
|
|
47
|
+
faces.push(this.faceLevel(face, 0));
|
|
48
|
+
}
|
|
49
|
+
this.faceViews = faces;
|
|
50
|
+
this.wholeCube = probe.createView({ dimension: 'cube' });
|
|
51
|
+
const levels = probeLevels(size);
|
|
52
|
+
for (let level = 1; level < levels; level++) {
|
|
53
|
+
this.chainSources[level] = probe.createView({
|
|
54
|
+
dimension: 'cube',
|
|
55
|
+
baseMipLevel: level - 1,
|
|
56
|
+
mipLevelCount: 1,
|
|
57
|
+
});
|
|
58
|
+
for (let face = 0; face < PROBE_FACE_BASIS.length; face++) {
|
|
59
|
+
this.chainTargets[level * PROBE_FACE_BASIS.length + face] = this.faceLevel(face, level);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The depth and, above one sample, the multisampled colour a face is drawn into: made on the first
|
|
65
|
+
* bake and again only if the sample count or the format has changed since.
|
|
66
|
+
*/
|
|
67
|
+
attachments(samples, format) {
|
|
68
|
+
const key = `${samples}:${format}`;
|
|
69
|
+
if (this.depthView === null || key !== this.attachmentKey) {
|
|
70
|
+
this.destroyAttachments();
|
|
71
|
+
this.attachmentKey = key;
|
|
72
|
+
this.depth = this.device.createTexture({
|
|
73
|
+
label: 'probe.depth',
|
|
74
|
+
size: [this.size, this.size],
|
|
75
|
+
format: DEPTH_FORMAT,
|
|
76
|
+
/* The world's sample count, because a bake draws the world with the world's pipelines. */
|
|
77
|
+
sampleCount: samples,
|
|
78
|
+
usage: 0x10, // RENDER_ATTACHMENT
|
|
79
|
+
});
|
|
80
|
+
this.depthView = this.depth.createView();
|
|
81
|
+
this.colorMsaa =
|
|
82
|
+
samples > 1
|
|
83
|
+
? this.device.createTexture({
|
|
84
|
+
label: 'probe.colorMsaa',
|
|
85
|
+
size: [this.size, this.size],
|
|
86
|
+
format,
|
|
87
|
+
sampleCount: samples,
|
|
88
|
+
usage: 0x10, // RENDER_ATTACHMENT
|
|
89
|
+
})
|
|
90
|
+
: null;
|
|
91
|
+
this.colorMsaaView = this.colorMsaa?.createView() ?? null;
|
|
92
|
+
}
|
|
93
|
+
return { depth: this.depthView, colorMsaa: this.colorMsaaView };
|
|
94
|
+
}
|
|
95
|
+
/** The blur's target for `level` of `face`. */
|
|
96
|
+
chainTarget(level, face) {
|
|
97
|
+
return this.chainTargets[level * PROBE_FACE_BASIS.length + face];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The blur's group for `level`, over `buffer`: the uniforms, the level above as a cube, the
|
|
101
|
+
* sampler. Remade only if the ring's buffer has been replaced.
|
|
102
|
+
*/
|
|
103
|
+
chainGroup(layout, buffer, sampler, level) {
|
|
104
|
+
this.forBuffer(buffer);
|
|
105
|
+
let group = this.chainGroups[level];
|
|
106
|
+
if (group === undefined) {
|
|
107
|
+
group = this.device.createBindGroup({
|
|
108
|
+
layout,
|
|
109
|
+
entries: [
|
|
110
|
+
{ binding: 0, resource: { buffer, size: PROBE_MIP_UNIFORM_SIZE } },
|
|
111
|
+
{ binding: 1, resource: this.chainSources[level] },
|
|
112
|
+
{ binding: 2, resource: sampler },
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
this.chainGroups[level] = group;
|
|
116
|
+
}
|
|
117
|
+
return group;
|
|
118
|
+
}
|
|
119
|
+
/** The convolution's group over `buffer`: the uniforms, the whole chain, the sampler. */
|
|
120
|
+
prefilterGroupFor(layout, buffer, sampler) {
|
|
121
|
+
this.forBuffer(buffer);
|
|
122
|
+
this.prefilterGroup ??= this.device.createBindGroup({
|
|
123
|
+
layout,
|
|
124
|
+
entries: [
|
|
125
|
+
{ binding: PREFILTER_UNIFORM_BINDING, resource: { buffer, size: PROBE_MIP_UNIFORM_SIZE } },
|
|
126
|
+
{ binding: PREFILTER_TEXTURE_BINDING, resource: this.wholeCube },
|
|
127
|
+
{ binding: PREFILTER_SAMPLER_BINDING, resource: sampler },
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
return this.prefilterGroup;
|
|
131
|
+
}
|
|
132
|
+
/** `level` of `layer` of the grid's array, which the convolution writes. */
|
|
133
|
+
arrayView(array, layer, level) {
|
|
134
|
+
const key = layer * 32 + level;
|
|
135
|
+
let view = this.arrayViews.get(key);
|
|
136
|
+
if (view === undefined) {
|
|
137
|
+
view = array.createView({
|
|
138
|
+
dimension: '2d',
|
|
139
|
+
baseArrayLayer: layer,
|
|
140
|
+
arrayLayerCount: 1,
|
|
141
|
+
baseMipLevel: level,
|
|
142
|
+
mipLevelCount: 1,
|
|
143
|
+
});
|
|
144
|
+
this.arrayViews.set(key, view);
|
|
145
|
+
}
|
|
146
|
+
return view;
|
|
147
|
+
}
|
|
148
|
+
/** The grid's array has been replaced: nothing made against the old one may be handed out. */
|
|
149
|
+
forgetArray() {
|
|
150
|
+
this.arrayViews.clear();
|
|
151
|
+
}
|
|
152
|
+
dispose() {
|
|
153
|
+
this.destroyAttachments();
|
|
154
|
+
}
|
|
155
|
+
faceLevel(face, level) {
|
|
156
|
+
return this.probe.createView({
|
|
157
|
+
dimension: '2d',
|
|
158
|
+
baseArrayLayer: face,
|
|
159
|
+
arrayLayerCount: 1,
|
|
160
|
+
baseMipLevel: level,
|
|
161
|
+
mipLevelCount: 1,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/** Groups hold the ring's buffer, so a replaced buffer makes every one of them stale. */
|
|
165
|
+
forBuffer(buffer) {
|
|
166
|
+
if (buffer === this.groupsBuffer)
|
|
167
|
+
return;
|
|
168
|
+
this.groupsBuffer = buffer;
|
|
169
|
+
this.chainGroups = [];
|
|
170
|
+
this.prefilterGroup = null;
|
|
171
|
+
}
|
|
172
|
+
destroyAttachments() {
|
|
173
|
+
this.depth?.destroy();
|
|
174
|
+
this.colorMsaa?.destroy();
|
|
175
|
+
this.depth = null;
|
|
176
|
+
this.depthView = null;
|
|
177
|
+
this.colorMsaa = null;
|
|
178
|
+
this.colorMsaaView = null;
|
|
179
|
+
}
|
|
180
|
+
}
|