@driftengine/core 4.2.1 → 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 +272 -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 +166 -1
- package/dist/render/backend/webgl2/renderer.js +424 -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 +176 -11
- package/dist/render/backend/webgpu/renderer.js +680 -250
- 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 +462 -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 +807 -271
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { boundsOfPositions, createBounds } from '../../../math/bounds.js';
|
|
2
2
|
import { MorphTexture } from './morphTexture.js';
|
|
3
|
+
import { validateMeshData } from '../../mesh.js';
|
|
3
4
|
import { ABSENT_ATTRIBUTE } from '../../vertexDefaults.js';
|
|
4
5
|
import { UPLOAD_BYTES_PER_STEP } from '../../uploadStep.js';
|
|
5
6
|
import { INSTANCE_STRIDE } from '../../instances.js';
|
|
@@ -122,6 +123,11 @@ export function createGpuMesh(device, data, dynamic = false) {
|
|
|
122
123
|
* bounds were not yet known would not be usable at once, which is the whole promise here.
|
|
123
124
|
*/
|
|
124
125
|
export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
126
|
+
/*
|
|
127
|
+
* The other backend's refusal, and the premise of the interleave below: every attribute holds
|
|
128
|
+
* exactly a vertex's worth for every vertex, so nothing reads past the end of its array.
|
|
129
|
+
*/
|
|
130
|
+
validateMeshData(data);
|
|
125
131
|
const bounds = boundsOfPositions(data.positions, createBounds());
|
|
126
132
|
const vertexCount = data.positions.length / 3;
|
|
127
133
|
const supplied = VERTEX_LAYOUT.filter((attribute) => !attribute.optional || data[attribute.name] !== undefined);
|
|
@@ -264,14 +270,8 @@ function* uploadSteps(job) {
|
|
|
264
270
|
let fieldOffset = 0;
|
|
265
271
|
for (const attribute of supplied) {
|
|
266
272
|
const source = data[attribute.name];
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
for (let component = 0; component < width; component++) {
|
|
270
|
-
interleaved[vertex * step + fieldOffset + component] =
|
|
271
|
-
source[vertex * width + component] ?? 0;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
fieldOffset += width;
|
|
273
|
+
interleaveField(interleaved, source, attribute.components, step, fieldOffset, from, to);
|
|
274
|
+
fieldOffset += attribute.components;
|
|
275
275
|
}
|
|
276
276
|
device.queue.writeBuffer(vertices, from * stride, interleaved, from * step, (to - from) * step);
|
|
277
277
|
taken += 1;
|
|
@@ -291,6 +291,44 @@ function* uploadSteps(job) {
|
|
|
291
291
|
job.interleaved = null;
|
|
292
292
|
job.progress.uploaded = true;
|
|
293
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* One attribute of vertices `from` to `to` into its field of the interleaved rows.
|
|
296
|
+
*
|
|
297
|
+
* **Unrolled by width, because the general loop was most of a heavy load's upload.** A loop over
|
|
298
|
+
* components inside a loop over vertices, with the index recomputed from both, cost 1.5 s of main
|
|
299
|
+
* thread on a courtyard of five packs; a loop per width walking two cursors does the same writes in
|
|
300
|
+
* about a third of the time, measured on four million vertices in Node. Every width the layout
|
|
301
|
+
* carries is one, two, three or four, so the last arm is the one-float case and nothing else.
|
|
302
|
+
*/
|
|
303
|
+
function interleaveField(rows, source, width, step, fieldOffset, from, to) {
|
|
304
|
+
let write = from * step + fieldOffset;
|
|
305
|
+
const end = to * width;
|
|
306
|
+
if (width === 3) {
|
|
307
|
+
for (let read = from * 3; read < end; read += 3, write += step) {
|
|
308
|
+
rows[write] = source[read];
|
|
309
|
+
rows[write + 1] = source[read + 1];
|
|
310
|
+
rows[write + 2] = source[read + 2];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else if (width === 4) {
|
|
314
|
+
for (let read = from * 4; read < end; read += 4, write += step) {
|
|
315
|
+
rows[write] = source[read];
|
|
316
|
+
rows[write + 1] = source[read + 1];
|
|
317
|
+
rows[write + 2] = source[read + 2];
|
|
318
|
+
rows[write + 3] = source[read + 3];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else if (width === 2) {
|
|
322
|
+
for (let read = from * 2; read < end; read += 2, write += step) {
|
|
323
|
+
rows[write] = source[read];
|
|
324
|
+
rows[write + 1] = source[read + 1];
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
for (let read = from; read < end; read++, write += step)
|
|
329
|
+
rows[write] = source[read];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
294
332
|
/** The handle, made where it can see only what it hands out. */
|
|
295
333
|
function gpuMeshOf(parts) {
|
|
296
334
|
const { vertices, constants, indexBuffer, progress } = parts;
|
|
@@ -46,6 +46,71 @@ export declare const DEPTH_VERT_FIELDS: {
|
|
|
46
46
|
};
|
|
47
47
|
export declare const DEPTH_VERT_SIZE: 160;
|
|
48
48
|
export declare const DEPTH_FRAG_SIZE: 16;
|
|
49
|
+
export declare const DEPTH_CUTOUT_VERT_FIELDS: {
|
|
50
|
+
readonly uLightViewProj: {
|
|
51
|
+
readonly offset: 0;
|
|
52
|
+
readonly size: 64;
|
|
53
|
+
readonly type: "mat4";
|
|
54
|
+
};
|
|
55
|
+
readonly uModel: {
|
|
56
|
+
readonly offset: 64;
|
|
57
|
+
readonly size: 64;
|
|
58
|
+
readonly type: "mat4";
|
|
59
|
+
};
|
|
60
|
+
readonly uWindDirection: {
|
|
61
|
+
readonly offset: 128;
|
|
62
|
+
readonly size: 8;
|
|
63
|
+
readonly type: "vec2";
|
|
64
|
+
};
|
|
65
|
+
readonly uWindSpeed: {
|
|
66
|
+
readonly offset: 136;
|
|
67
|
+
readonly size: 4;
|
|
68
|
+
readonly type: "float";
|
|
69
|
+
};
|
|
70
|
+
readonly uWindGust: {
|
|
71
|
+
readonly offset: 140;
|
|
72
|
+
readonly size: 4;
|
|
73
|
+
readonly type: "float";
|
|
74
|
+
};
|
|
75
|
+
readonly uWindTime: {
|
|
76
|
+
readonly offset: 144;
|
|
77
|
+
readonly size: 4;
|
|
78
|
+
readonly type: "float";
|
|
79
|
+
};
|
|
80
|
+
readonly uWindSpatialPhase: {
|
|
81
|
+
readonly offset: 152;
|
|
82
|
+
readonly size: 8;
|
|
83
|
+
readonly type: "vec2";
|
|
84
|
+
};
|
|
85
|
+
readonly uUvScale: {
|
|
86
|
+
readonly offset: 160;
|
|
87
|
+
readonly size: 8;
|
|
88
|
+
readonly type: "vec2";
|
|
89
|
+
};
|
|
90
|
+
readonly uAlphaCutout: {
|
|
91
|
+
readonly offset: 168;
|
|
92
|
+
readonly size: 4;
|
|
93
|
+
readonly type: "float";
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
export declare const DEPTH_CUTOUT_VERT_SIZE: 176;
|
|
97
|
+
export declare const DEPTH_INSTANCED_CUTOUT_VERT_FIELDS: {
|
|
98
|
+
readonly uLightViewProj: {
|
|
99
|
+
readonly offset: 0;
|
|
100
|
+
readonly size: 64;
|
|
101
|
+
readonly type: "mat4";
|
|
102
|
+
};
|
|
103
|
+
readonly uUvScale: {
|
|
104
|
+
readonly offset: 64;
|
|
105
|
+
readonly size: 8;
|
|
106
|
+
readonly type: "vec2";
|
|
107
|
+
};
|
|
108
|
+
readonly uAlphaCutout: {
|
|
109
|
+
readonly offset: 72;
|
|
110
|
+
readonly size: 4;
|
|
111
|
+
readonly type: "float";
|
|
112
|
+
};
|
|
113
|
+
};
|
|
49
114
|
/** Re-exported so the scatter depth pass can pair with the same fragment stage. */
|
|
50
115
|
export { DEPTH_FRAG_WGSL };
|
|
51
116
|
export declare const DEPTH_FRAG_BINDING: 1;
|
|
@@ -78,10 +143,21 @@ export declare function createDepthBindGroupLayout(device: GPUDevice,
|
|
|
78
143
|
* the rigid path is untouched: no extra binding, no one-pixel texture to keep alive, and a
|
|
79
144
|
* frame that skins nothing pays nothing at all.
|
|
80
145
|
*/
|
|
81
|
-
skinned?: boolean
|
|
146
|
+
skinned?: boolean,
|
|
147
|
+
/**
|
|
148
|
+
* Whether this layout carries a cutout map, for a caster whose shape is in its texture's alpha.
|
|
149
|
+
* Separate for the palette's reason: the plain layout stays exactly what it was, and a frame with
|
|
150
|
+
* no cutout caster pays nothing.
|
|
151
|
+
*/
|
|
152
|
+
cutout?: boolean): GPUBindGroupLayout;
|
|
82
153
|
export declare function createDepthBindGroup(device: GPUDevice, layout: GPUBindGroupLayout, perDraw: GPUBuffer, perPass: GPUBuffer, previous: GPUTextureView, sampler: GPUSampler,
|
|
83
154
|
/** The joint palette, for a layout built with `skinned`. Omit for the rigid layout. */
|
|
84
|
-
palette?: GPUTextureView | null
|
|
155
|
+
palette?: GPUTextureView | null,
|
|
156
|
+
/** The cutout map and its sampler, for a layout built with `cutout`. */
|
|
157
|
+
cutout?: {
|
|
158
|
+
readonly view: GPUTextureView;
|
|
159
|
+
readonly sampler: GPUSampler;
|
|
160
|
+
} | null): GPUBindGroup;
|
|
85
161
|
/**
|
|
86
162
|
* The depth pipeline.
|
|
87
163
|
*
|
|
@@ -111,4 +187,6 @@ skinned?: boolean,
|
|
|
111
187
|
* Excludes `skinned`, and the layout is why rather than the shader: an instanced pipeline
|
|
112
188
|
* reclaims the two attribute locations the joint indices and weights occupy.
|
|
113
189
|
*/
|
|
114
|
-
instanced?: boolean
|
|
190
|
+
instanced?: boolean,
|
|
191
|
+
/** Whether this pipeline casts a cutout: the variant that reads a map and discards by it. */
|
|
192
|
+
cutout?: boolean): GPURenderPipeline;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEPTH_BINDINGS, DEPTH_FRAG_WGSL, DEPTH_INSTANCED_VERT_WGSL, DEPTH_SKINNED_VERT_WGSL, DEPTH_VERT_WGSL, } from '../../shaders/generated/depth.wgsl.js';
|
|
1
|
+
import { DEPTH_BINDINGS, DEPTH_CUTOUT_FRAG_WGSL, DEPTH_CUTOUT_VERT_WGSL, DEPTH_FRAG_WGSL, DEPTH_INSTANCED_CUTOUT_VERT_WGSL, DEPTH_INSTANCED_VERT_WGSL, DEPTH_SKINNED_VERT_WGSL, DEPTH_VERT_WGSL, } from '../../shaders/generated/depth.wgsl.js';
|
|
2
2
|
import { vertexBufferLayouts } from './buffers.js';
|
|
3
3
|
import { shaderModule } from './shaderModules.js';
|
|
4
4
|
/**
|
|
@@ -11,6 +11,15 @@ import { shaderModule } from './shaderModules.js';
|
|
|
11
11
|
export const DEPTH_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_VERT.fields;
|
|
12
12
|
export const DEPTH_VERT_SIZE = DEPTH_BINDINGS.DEPTH_VERT.uniformSize;
|
|
13
13
|
export const DEPTH_FRAG_SIZE = DEPTH_BINDINGS.DEPTH_FRAG.uniformSize;
|
|
14
|
+
/*
|
|
15
|
+
* The cutout variants' vertex blocks. The rigid one is the plain block with `uUvScale` and
|
|
16
|
+
* `uAlphaCutout` after every other field, so a slot written for it is written with the plain
|
|
17
|
+
* offsets plus these two; the instanced one has no model and no wind, so its two sit earlier.
|
|
18
|
+
*/
|
|
19
|
+
export const DEPTH_CUTOUT_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_CUTOUT_VERT.fields;
|
|
20
|
+
export const DEPTH_CUTOUT_VERT_SIZE = DEPTH_BINDINGS.DEPTH_CUTOUT_VERT.uniformSize;
|
|
21
|
+
export const DEPTH_INSTANCED_CUTOUT_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_INSTANCED_CUTOUT_VERT.fields;
|
|
22
|
+
const CUTOUT_MAP = DEPTH_BINDINGS.DEPTH_CUTOUT_FRAG.textures.uCutoutMap;
|
|
14
23
|
/** Re-exported so the scatter depth pass can pair with the same fragment stage. */
|
|
15
24
|
export { DEPTH_FRAG_WGSL };
|
|
16
25
|
export const DEPTH_FRAG_BINDING = DEPTH_BINDINGS.DEPTH_FRAG.uniforms;
|
|
@@ -40,9 +49,15 @@ export function createDepthBindGroupLayout(device,
|
|
|
40
49
|
* the rigid path is untouched: no extra binding, no one-pixel texture to keep alive, and a
|
|
41
50
|
* frame that skins nothing pays nothing at all.
|
|
42
51
|
*/
|
|
43
|
-
skinned = false
|
|
52
|
+
skinned = false,
|
|
53
|
+
/**
|
|
54
|
+
* Whether this layout carries a cutout map, for a caster whose shape is in its texture's alpha.
|
|
55
|
+
* Separate for the palette's reason: the plain layout stays exactly what it was, and a frame with
|
|
56
|
+
* no cutout caster pays nothing.
|
|
57
|
+
*/
|
|
58
|
+
cutout = false) {
|
|
44
59
|
return device.createBindGroupLayout({
|
|
45
|
-
label: skinned ? 'depth.layout.skinned' : 'depth.layout',
|
|
60
|
+
label: skinned ? 'depth.layout.skinned' : cutout ? 'depth.layout.cutout' : 'depth.layout',
|
|
46
61
|
entries: [
|
|
47
62
|
/* The texture and no sampler: the shader reads the palette with `textureLoad`, an integer
|
|
48
63
|
coordinate with nothing to filter, which is what `flatPass.ts` binds for the same
|
|
@@ -59,7 +74,11 @@ skinned = false) {
|
|
|
59
74
|
{
|
|
60
75
|
binding: VERT_BINDING,
|
|
61
76
|
visibility: VISIBILITY_VERTEX,
|
|
62
|
-
buffer: {
|
|
77
|
+
buffer: {
|
|
78
|
+
type: 'uniform',
|
|
79
|
+
hasDynamicOffset: true,
|
|
80
|
+
minBindingSize: cutout ? DEPTH_CUTOUT_VERT_SIZE : DEPTH_VERT_SIZE,
|
|
81
|
+
},
|
|
63
82
|
},
|
|
64
83
|
{
|
|
65
84
|
binding: FRAG_BINDING,
|
|
@@ -92,21 +111,55 @@ skinned = false) {
|
|
|
92
111
|
visibility: VISIBILITY_FRAGMENT,
|
|
93
112
|
sampler: { type: 'non-filtering' },
|
|
94
113
|
},
|
|
114
|
+
/* A colour map sampled with its own filter and mips, so a leaf's edge in the map is the edge
|
|
115
|
+
a colour pass at that distance would draw. */
|
|
116
|
+
...(cutout
|
|
117
|
+
? [
|
|
118
|
+
{
|
|
119
|
+
binding: CUTOUT_MAP.texture,
|
|
120
|
+
visibility: VISIBILITY_FRAGMENT,
|
|
121
|
+
texture: { sampleType: 'float' },
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
binding: CUTOUT_MAP.sampler,
|
|
125
|
+
visibility: VISIBILITY_FRAGMENT,
|
|
126
|
+
sampler: { type: 'filtering' },
|
|
127
|
+
},
|
|
128
|
+
]
|
|
129
|
+
: []),
|
|
95
130
|
],
|
|
96
131
|
});
|
|
97
132
|
}
|
|
98
133
|
export function createDepthBindGroup(device, layout, perDraw, perPass, previous, sampler,
|
|
99
134
|
/** The joint palette, for a layout built with `skinned`. Omit for the rigid layout. */
|
|
100
|
-
palette = null
|
|
135
|
+
palette = null,
|
|
136
|
+
/** The cutout map and its sampler, for a layout built with `cutout`. */
|
|
137
|
+
cutout = null) {
|
|
101
138
|
return device.createBindGroup({
|
|
102
|
-
label: palette
|
|
139
|
+
label: palette !== null
|
|
140
|
+
? 'depth.bindGroup.skinned'
|
|
141
|
+
: cutout !== null
|
|
142
|
+
? 'depth.bindGroup.cutout'
|
|
143
|
+
: 'depth.bindGroup',
|
|
103
144
|
layout,
|
|
104
145
|
entries: [
|
|
105
|
-
{
|
|
146
|
+
{
|
|
147
|
+
binding: VERT_BINDING,
|
|
148
|
+
resource: {
|
|
149
|
+
buffer: perDraw,
|
|
150
|
+
size: cutout !== null ? DEPTH_CUTOUT_VERT_SIZE : DEPTH_VERT_SIZE,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
106
153
|
{ binding: FRAG_BINDING, resource: { buffer: perPass, size: DEPTH_FRAG_SIZE } },
|
|
107
154
|
{ binding: PREVIOUS.texture, resource: previous },
|
|
108
155
|
{ binding: PREVIOUS.sampler, resource: sampler },
|
|
109
156
|
...(palette !== null ? [{ binding: DEPTH_PALETTE_BINDING.texture, resource: palette }] : []),
|
|
157
|
+
...(cutout !== null
|
|
158
|
+
? [
|
|
159
|
+
{ binding: CUTOUT_MAP.texture, resource: cutout.view },
|
|
160
|
+
{ binding: CUTOUT_MAP.sampler, resource: cutout.sampler },
|
|
161
|
+
]
|
|
162
|
+
: []),
|
|
110
163
|
],
|
|
111
164
|
});
|
|
112
165
|
}
|
|
@@ -139,21 +192,29 @@ skinned = false,
|
|
|
139
192
|
* Excludes `skinned`, and the layout is why rather than the shader: an instanced pipeline
|
|
140
193
|
* reclaims the two attribute locations the joint indices and weights occupy.
|
|
141
194
|
*/
|
|
142
|
-
instanced = false
|
|
195
|
+
instanced = false,
|
|
196
|
+
/** Whether this pipeline casts a cutout: the variant that reads a map and discards by it. */
|
|
197
|
+
cutout = false) {
|
|
143
198
|
return cache.get(key, () => ({
|
|
144
199
|
label: key,
|
|
145
200
|
layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
|
|
146
201
|
vertex: {
|
|
147
|
-
module: shaderModule(device,
|
|
148
|
-
?
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
202
|
+
module: shaderModule(device, cutout
|
|
203
|
+
? instanced
|
|
204
|
+
? { label: 'depth.instanced.cutout.vert', code: DEPTH_INSTANCED_CUTOUT_VERT_WGSL }
|
|
205
|
+
: { label: 'depth.cutout.vert', code: DEPTH_CUTOUT_VERT_WGSL }
|
|
206
|
+
: instanced
|
|
207
|
+
? { label: 'depth.instanced.vert', code: DEPTH_INSTANCED_VERT_WGSL }
|
|
208
|
+
: skinned
|
|
209
|
+
? { label: 'depth.skinned.vert', code: DEPTH_SKINNED_VERT_WGSL }
|
|
210
|
+
: { label: 'depth.vert', code: DEPTH_VERT_WGSL }),
|
|
152
211
|
entryPoint: 'main',
|
|
153
212
|
buffers: vertexBufferLayouts(present, instanced),
|
|
154
213
|
},
|
|
155
214
|
fragment: {
|
|
156
|
-
module: shaderModule(device,
|
|
215
|
+
module: shaderModule(device, cutout
|
|
216
|
+
? { label: 'depth.cutout.frag', code: DEPTH_CUTOUT_FRAG_WGSL }
|
|
217
|
+
: { label: 'depth.frag', code: DEPTH_FRAG_WGSL }),
|
|
157
218
|
entryPoint: 'main',
|
|
158
219
|
targets: [],
|
|
159
220
|
},
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { PipelineCache } from './pipelineCache.ts';
|
|
2
|
+
export declare class ExposurePass {
|
|
3
|
+
private readonly device;
|
|
4
|
+
private readonly pipelines;
|
|
5
|
+
private readonly sampler;
|
|
6
|
+
private readonly meter;
|
|
7
|
+
private readonly meterView;
|
|
8
|
+
private readonly held;
|
|
9
|
+
/** The texel the composite binds, for its bind group. */
|
|
10
|
+
readonly heldView: GPUTextureView;
|
|
11
|
+
private readonly scratch;
|
|
12
|
+
private readonly scratchView;
|
|
13
|
+
private readonly localGrid;
|
|
14
|
+
/** Local exposure's grid, for the composite's bind group. */
|
|
15
|
+
readonly localView: GPUTextureView;
|
|
16
|
+
private readonly localLayout;
|
|
17
|
+
private localGroup;
|
|
18
|
+
private localSource;
|
|
19
|
+
private readonly meterLayout;
|
|
20
|
+
private readonly adaptLayout;
|
|
21
|
+
private readonly adaptGroup;
|
|
22
|
+
private readonly uniforms;
|
|
23
|
+
private readonly blend;
|
|
24
|
+
private meterGroup;
|
|
25
|
+
private meterSource;
|
|
26
|
+
/** Whether anything is held yet: the first frame, and the first after a cut, snaps. */
|
|
27
|
+
private holding;
|
|
28
|
+
constructor(device: GPUDevice, pipelines: PipelineCache, sampler: GPUSampler);
|
|
29
|
+
/** Forget what is held, so the next frame meters afresh rather than easing from another shot. */
|
|
30
|
+
cut(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Meter `scene` and move the held brightness toward it by `dtSec` of adaptation, and with `local`
|
|
33
|
+
* draw local exposure's grid too.
|
|
34
|
+
*/
|
|
35
|
+
run(encoder: GPUCommandEncoder, scene: GPUTextureView, dtSec: number, local?: boolean): void;
|
|
36
|
+
/** Local exposure's grid, from the same scene the meter read. */
|
|
37
|
+
private drawLocal;
|
|
38
|
+
dispose(): void;
|
|
39
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { EXPOSURE_ADAPT_FRAG_WGSL, EXPOSURE_BINDINGS, EXPOSURE_METER_FRAG_WGSL, } from '../../shaders/generated/exposure.wgsl.js';
|
|
2
|
+
import { EXPOSURE_LOCAL_FRAG_WGSL, LOCALEXPOSURE_BINDINGS, } from '../../shaders/generated/localExposure.wgsl.js';
|
|
3
|
+
import { METER_GRID, adaptBlend } from '../../shaders/exposure.js';
|
|
4
|
+
import { LOCAL_GRID_WIDTH } from '../../shaders/localExposure.js';
|
|
5
|
+
import { createPostStageLayout, postPipeline } from './postPass.js';
|
|
6
|
+
/**
|
|
7
|
+
* Eye adaptation on WebGPU: meter the finished scene into the frame's own encoder, and follow it
|
|
8
|
+
* with a held brightness the composite reads. See `shaders/exposure.ts` for the design.
|
|
9
|
+
*
|
|
10
|
+
* **One held texel the composite always binds, and a scratch the adaptation writes.** Reading and
|
|
11
|
+
* writing one texture in a pass is refused, and a pair that swapped roles would mean a composite
|
|
12
|
+
* bind group per role; so the adaptation writes the scratch from the held value and a copy puts it
|
|
13
|
+
* back, on the same encoder, before the composite reads it. Recorded into the frame's encoder
|
|
14
|
+
* rather than submitted apart, because a separate submission would run before the frame that
|
|
15
|
+
* draws the scene it is meant to measure.
|
|
16
|
+
*
|
|
17
|
+
* **And local exposure's grid, when that is asked for**, in the same encoder after the meter:
|
|
18
|
+
* `rg16float`, a sum and a count per tile per band, sampled filtered by the composite. See
|
|
19
|
+
* `localExposure.ts`.
|
|
20
|
+
*/
|
|
21
|
+
const METER = EXPOSURE_BINDINGS.EXPOSURE_METER_FRAG.textures;
|
|
22
|
+
const ADAPT = EXPOSURE_BINDINGS.EXPOSURE_ADAPT_FRAG;
|
|
23
|
+
const LOCAL = LOCALEXPOSURE_BINDINGS.EXPOSURE_LOCAL_FRAG.textures;
|
|
24
|
+
const USAGE_RENDER = 0x10;
|
|
25
|
+
const USAGE_TEXTURE = 0x04;
|
|
26
|
+
const USAGE_COPY_SRC = 0x01;
|
|
27
|
+
const USAGE_COPY_DST = 0x02;
|
|
28
|
+
const USAGE_UNIFORM_DST = 0x40 | 0x08;
|
|
29
|
+
const VISIBILITY_FRAGMENT = 0x2;
|
|
30
|
+
export class ExposurePass {
|
|
31
|
+
device;
|
|
32
|
+
pipelines;
|
|
33
|
+
sampler;
|
|
34
|
+
meter;
|
|
35
|
+
meterView;
|
|
36
|
+
held;
|
|
37
|
+
/** The texel the composite binds, for its bind group. */
|
|
38
|
+
heldView;
|
|
39
|
+
scratch;
|
|
40
|
+
scratchView;
|
|
41
|
+
localGrid;
|
|
42
|
+
/** Local exposure's grid, for the composite's bind group. */
|
|
43
|
+
localView;
|
|
44
|
+
localLayout;
|
|
45
|
+
localGroup = null;
|
|
46
|
+
localSource = null;
|
|
47
|
+
meterLayout;
|
|
48
|
+
adaptLayout;
|
|
49
|
+
adaptGroup;
|
|
50
|
+
uniforms;
|
|
51
|
+
blend = new Float32Array(4);
|
|
52
|
+
meterGroup = null;
|
|
53
|
+
meterSource = null;
|
|
54
|
+
/** Whether anything is held yet: the first frame, and the first after a cut, snaps. */
|
|
55
|
+
holding = false;
|
|
56
|
+
constructor(device, pipelines, sampler) {
|
|
57
|
+
this.device = device;
|
|
58
|
+
this.pipelines = pipelines;
|
|
59
|
+
this.sampler = sampler;
|
|
60
|
+
const target = (label, size, usage) => device.createTexture({ label, size: [size, size], format: 'r16float', usage });
|
|
61
|
+
this.meter = target('exposure.meter', METER_GRID, USAGE_RENDER | USAGE_TEXTURE);
|
|
62
|
+
this.held = target('exposure.held', 1, USAGE_RENDER | USAGE_TEXTURE | USAGE_COPY_DST);
|
|
63
|
+
this.scratch = target('exposure.scratch', 1, USAGE_RENDER | USAGE_COPY_SRC);
|
|
64
|
+
this.localGrid = device.createTexture({
|
|
65
|
+
label: 'exposure.localGrid',
|
|
66
|
+
size: [LOCAL_GRID_WIDTH, METER_GRID],
|
|
67
|
+
format: 'rg16float',
|
|
68
|
+
usage: USAGE_RENDER | USAGE_TEXTURE,
|
|
69
|
+
});
|
|
70
|
+
this.localView = this.localGrid.createView();
|
|
71
|
+
this.meterView = this.meter.createView();
|
|
72
|
+
this.heldView = this.held.createView();
|
|
73
|
+
this.scratchView = this.scratch.createView();
|
|
74
|
+
/* The meter has no uniforms, so its layout is its one texture and sampler and nothing else. */
|
|
75
|
+
this.meterLayout = device.createBindGroupLayout({
|
|
76
|
+
label: 'exposure.meterLayout',
|
|
77
|
+
entries: [
|
|
78
|
+
{ binding: METER.uScene.texture, visibility: VISIBILITY_FRAGMENT, texture: {} },
|
|
79
|
+
{ binding: METER.uScene.sampler, visibility: VISIBILITY_FRAGMENT, sampler: {} },
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
this.localLayout = device.createBindGroupLayout({
|
|
83
|
+
label: 'exposure.localLayout',
|
|
84
|
+
entries: [
|
|
85
|
+
{ binding: LOCAL.uScene.texture, visibility: VISIBILITY_FRAGMENT, texture: {} },
|
|
86
|
+
{ binding: LOCAL.uScene.sampler, visibility: VISIBILITY_FRAGMENT, sampler: {} },
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
this.adaptLayout = createPostStageLayout(device, ADAPT.uniforms, ADAPT.uniformSize, [
|
|
90
|
+
{ binding: ADAPT.textures.uMeter },
|
|
91
|
+
{ binding: ADAPT.textures.uHeld },
|
|
92
|
+
]);
|
|
93
|
+
this.uniforms = device.createBuffer({
|
|
94
|
+
label: 'exposure.uniforms',
|
|
95
|
+
size: ADAPT.uniformSize,
|
|
96
|
+
usage: USAGE_UNIFORM_DST,
|
|
97
|
+
});
|
|
98
|
+
this.adaptGroup = device.createBindGroup({
|
|
99
|
+
label: 'exposure.adaptGroup',
|
|
100
|
+
layout: this.adaptLayout,
|
|
101
|
+
entries: [
|
|
102
|
+
{ binding: ADAPT.uniforms, resource: { buffer: this.uniforms, size: ADAPT.uniformSize } },
|
|
103
|
+
{ binding: ADAPT.textures.uMeter.texture, resource: this.meterView },
|
|
104
|
+
{ binding: ADAPT.textures.uMeter.sampler, resource: sampler },
|
|
105
|
+
{ binding: ADAPT.textures.uHeld.texture, resource: this.heldView },
|
|
106
|
+
{ binding: ADAPT.textures.uHeld.sampler, resource: sampler },
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** Forget what is held, so the next frame meters afresh rather than easing from another shot. */
|
|
111
|
+
cut() {
|
|
112
|
+
this.holding = false;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Meter `scene` and move the held brightness toward it by `dtSec` of adaptation, and with `local`
|
|
116
|
+
* draw local exposure's grid too.
|
|
117
|
+
*/
|
|
118
|
+
run(encoder, scene, dtSec, local = false) {
|
|
119
|
+
const { device } = this;
|
|
120
|
+
if (this.meterSource !== scene || this.meterGroup === null) {
|
|
121
|
+
this.meterGroup = device.createBindGroup({
|
|
122
|
+
label: 'exposure.meterGroup',
|
|
123
|
+
layout: this.meterLayout,
|
|
124
|
+
entries: [
|
|
125
|
+
{ binding: METER.uScene.texture, resource: scene },
|
|
126
|
+
{ binding: METER.uScene.sampler, resource: this.sampler },
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
this.meterSource = scene;
|
|
130
|
+
}
|
|
131
|
+
this.blend[0] = adaptBlend(dtSec, !this.holding);
|
|
132
|
+
device.queue.writeBuffer(this.uniforms, 0, this.blend);
|
|
133
|
+
const meter = encoder.beginRenderPass({
|
|
134
|
+
label: 'exposure.meter',
|
|
135
|
+
colorAttachments: [
|
|
136
|
+
{ view: this.meterView, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
meter.setPipeline(postPipeline(this.pipelines, device, this.meterLayout, 'exposure.meter', EXPOSURE_METER_FRAG_WGSL, 'r16float'));
|
|
140
|
+
meter.setBindGroup(0, this.meterGroup);
|
|
141
|
+
meter.draw(3);
|
|
142
|
+
meter.end();
|
|
143
|
+
if (local)
|
|
144
|
+
this.drawLocal(encoder, scene);
|
|
145
|
+
const adapt = encoder.beginRenderPass({
|
|
146
|
+
label: 'exposure.adapt',
|
|
147
|
+
colorAttachments: [
|
|
148
|
+
{ view: this.scratchView, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
adapt.setPipeline(postPipeline(this.pipelines, device, this.adaptLayout, 'exposure.adapt', EXPOSURE_ADAPT_FRAG_WGSL, 'r16float'));
|
|
152
|
+
adapt.setBindGroup(0, this.adaptGroup, [0]);
|
|
153
|
+
adapt.draw(3);
|
|
154
|
+
adapt.end();
|
|
155
|
+
encoder.copyTextureToTexture({ texture: this.scratch }, { texture: this.held }, [1, 1, 1]);
|
|
156
|
+
this.holding = true;
|
|
157
|
+
}
|
|
158
|
+
/** Local exposure's grid, from the same scene the meter read. */
|
|
159
|
+
drawLocal(encoder, scene) {
|
|
160
|
+
const { device } = this;
|
|
161
|
+
if (this.localSource !== scene || this.localGroup === null) {
|
|
162
|
+
this.localGroup = device.createBindGroup({
|
|
163
|
+
label: 'exposure.localGroup',
|
|
164
|
+
layout: this.localLayout,
|
|
165
|
+
entries: [
|
|
166
|
+
{ binding: LOCAL.uScene.texture, resource: scene },
|
|
167
|
+
{ binding: LOCAL.uScene.sampler, resource: this.sampler },
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
this.localSource = scene;
|
|
171
|
+
}
|
|
172
|
+
const pass = encoder.beginRenderPass({
|
|
173
|
+
label: 'exposure.local',
|
|
174
|
+
colorAttachments: [
|
|
175
|
+
{ view: this.localView, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
pass.setPipeline(postPipeline(this.pipelines, device, this.localLayout, 'exposure.local', EXPOSURE_LOCAL_FRAG_WGSL, 'rg16float'));
|
|
179
|
+
pass.setBindGroup(0, this.localGroup);
|
|
180
|
+
pass.draw(3);
|
|
181
|
+
pass.end();
|
|
182
|
+
}
|
|
183
|
+
dispose() {
|
|
184
|
+
this.localGrid.destroy();
|
|
185
|
+
this.meter.destroy();
|
|
186
|
+
this.held.destroy();
|
|
187
|
+
this.scratch.destroy();
|
|
188
|
+
this.uniforms.destroy();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -39,6 +39,15 @@ export type ComposedField = PassDistanceField;
|
|
|
39
39
|
* which the chain's probe level answers and never declines.
|
|
40
40
|
*/
|
|
41
41
|
export declare const DEFAULT_FIELD_COMPOSE: FieldComposeOptions;
|
|
42
|
+
/**
|
|
43
|
+
* The most cascades a field is given to reach a probe grid, which is `cascadesForGrid`'s ceiling.
|
|
44
|
+
*
|
|
45
|
+
* Six from the 4 m default reach 128 m, and each is the same 49 cubed samples, about half a
|
|
46
|
+
* megabyte, so the bound is on memory rather than on distance. What would make it wrong is a scene
|
|
47
|
+
* whose probe grid is longer than about a hundred metres, whose far probes are then left untraced
|
|
48
|
+
* rather than traced against a field that is not there.
|
|
49
|
+
*/
|
|
50
|
+
export declare const MAX_FIELD_CASCADES = 6;
|
|
42
51
|
export interface FieldComposeOptions {
|
|
43
52
|
/** Samples a side, every cascade. */
|
|
44
53
|
readonly resolution: number;
|
|
@@ -106,6 +115,8 @@ export declare class FieldComposer {
|
|
|
106
115
|
private queryMapping;
|
|
107
116
|
private timed;
|
|
108
117
|
private composeMsValue;
|
|
118
|
+
/** How many cascades this composer was built with. */
|
|
119
|
+
get cascadeCount(): number;
|
|
109
120
|
constructor(device: GPUDevice, options: FieldComposeOptions);
|
|
110
121
|
/** One bind group a cascade. Rebuilt whenever the source buffer is replaced by a larger one. */
|
|
111
122
|
private rebuildGroups;
|
|
@@ -41,6 +41,15 @@ export const DEFAULT_FIELD_COMPOSE = {
|
|
|
41
41
|
cascades: 3,
|
|
42
42
|
radius: 4,
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* The most cascades a field is given to reach a probe grid, which is `cascadesForGrid`'s ceiling.
|
|
46
|
+
*
|
|
47
|
+
* Six from the 4 m default reach 128 m, and each is the same 49 cubed samples, about half a
|
|
48
|
+
* megabyte, so the bound is on memory rather than on distance. What would make it wrong is a scene
|
|
49
|
+
* whose probe grid is longer than about a hundred metres, whose far probes are then left untraced
|
|
50
|
+
* rather than traced against a field that is not there.
|
|
51
|
+
*/
|
|
52
|
+
export const MAX_FIELD_CASCADES = 6;
|
|
44
53
|
export class FieldComposer {
|
|
45
54
|
options;
|
|
46
55
|
samples;
|
|
@@ -101,6 +110,10 @@ export class FieldComposer {
|
|
|
101
110
|
queryMapping = false;
|
|
102
111
|
timed = false;
|
|
103
112
|
composeMsValue = 0;
|
|
113
|
+
/** How many cascades this composer was built with. */
|
|
114
|
+
get cascadeCount() {
|
|
115
|
+
return this.options.cascades;
|
|
116
|
+
}
|
|
104
117
|
constructor(device, options) {
|
|
105
118
|
if (!(Math.trunc(options.resolution) >= 2)) {
|
|
106
119
|
throw new Error(`FieldComposer: resolution is ${String(options.resolution)}; the minimum is 2.`);
|
|
@@ -194,7 +194,9 @@ depthWrite?: boolean, depthLayer?: number,
|
|
|
194
194
|
/** Whether placement and tint arrive per instance. See `FlatVertexOptions`. */
|
|
195
195
|
instanced?: boolean,
|
|
196
196
|
/** Which order-independent buffer this pipeline writes, if any. The key must carry it. */
|
|
197
|
-
oit?: OitTarget
|
|
197
|
+
oit?: OitTarget,
|
|
198
|
+
/** Culls nothing, for a surface seen from both faces. The key must carry it: `|2s`. */
|
|
199
|
+
doubleSided?: boolean): GPURenderPipeline;
|
|
198
200
|
/**
|
|
199
201
|
* The same pipeline, compiled off the main thread.
|
|
200
202
|
*
|