@driftengine/core 4.8.7 → 4.10.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 +123 -11
- package/README.md +43 -38
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -3
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/atmosphere.d.ts +13 -0
- package/dist/render/atmosphere.js +9 -1
- package/dist/render/backend/api.d.ts +19 -4
- package/dist/render/backend/createRenderer.d.ts +6 -0
- package/dist/render/backend/createRenderer.js +4 -1
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +180 -4
- package/dist/render/backend/webgl2/renderer.js +550 -40
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +19 -4
- package/dist/render/backend/webgpu/buffers.js +18 -11
- package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/device.d.ts +7 -1
- package/dist/render/backend/webgpu/device.js +31 -3
- package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
- package/dist/render/backend/webgpu/drawCommand.js +10 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +100 -42
- package/dist/render/backend/webgpu/flatPass.js +191 -43
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
- package/dist/render/backend/webgpu/pipelineCache.js +7 -0
- package/dist/render/backend/webgpu/postPass.d.ts +51 -9
- package/dist/render/backend/webgpu/postPass.js +16 -2
- package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
- package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
- package/dist/render/backend/webgpu/renderer.d.ts +283 -37
- package/dist/render/backend/webgpu/renderer.js +1363 -202
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +241 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/displayRange.d.ts +31 -0
- package/dist/render/displayRange.js +47 -0
- package/dist/render/filmicCurve.d.ts +74 -0
- package/dist/render/filmicCurve.js +203 -0
- package/dist/render/fog.d.ts +4 -1
- package/dist/render/fog.js +12 -2
- package/dist/render/frame/resources.d.ts +2 -2
- package/dist/render/frame/resources.js +6 -0
- package/dist/render/frameReflections.d.ts +40 -0
- package/dist/render/frameReflections.js +48 -0
- package/dist/render/glCompressed.js +83 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +5 -0
- package/dist/render/mesh.js +6 -0
- package/dist/render/pass.d.ts +49 -0
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
- package/dist/render/reflectionSurfaceTargets.js +99 -0
- package/dist/render/renderQuality.d.ts +39 -8
- package/dist/render/renderQuality.js +4 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +24 -1
- package/dist/render/screenSpaceReflection.d.ts +7 -1
- package/dist/render/screenSpaceReflection.js +5 -0
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/filmLook.js +2 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +2 -0
- package/dist/render/shaders/flat/index.d.ts +16 -0
- package/dist/render/shaders/flat/index.js +33 -5
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +43 -5
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/overlay.d.ts +16 -0
- package/dist/render/shaders/flat/overlay.js +117 -0
- package/dist/render/shaders/flat/pointShadow.js +3 -3
- package/dist/render/shaders/flat/preamble.js +38 -24
- package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
- package/dist/render/shaders/flat/reflectionSurface.js +61 -0
- package/dist/render/shaders/fog.d.ts +1 -1
- package/dist/render/shaders/fog.js +9 -2
- package/dist/render/shaders/fringe.d.ts +29 -0
- package/dist/render/shaders/fringe.js +55 -0
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/film.wgsl.js +1 -1
- package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/fire.wgsl.js +1 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3254 -2920
- package/dist/render/shaders/generated/flat.wgsl.js +3292 -2957
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/particle.wgsl.js +4 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
- package/dist/render/shaders/generated/rush.wgsl.js +56 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
- package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
- package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/water.wgsl.js +1 -1
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
- package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/rush.js +54 -11
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/ssrResolve.d.ts +17 -0
- package/dist/render/shaders/ssrResolve.js +51 -0
- package/dist/render/shaders/ssrTrace.js +26 -0
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/ssrPass.d.ts +16 -0
- package/dist/render/ssrPass.js +106 -1
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceOverlay.d.ts +132 -0
- package/dist/render/surfaceOverlay.js +85 -0
- package/dist/render/surfaceTexture.d.ts +57 -7
- package/dist/render/surfaceTexture.js +125 -17
- package/dist/render/temporalAa.d.ts +14 -0
- package/dist/render/temporalAa.js +20 -0
- package/dist/render/vertexDefaults.d.ts +17 -0
- package/dist/render/vertexDefaults.js +24 -0
- package/package.json +6 -6
- package/src/index.ts +24 -2
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/atmosphere.ts +22 -1
- package/src/render/backend/api.ts +22 -3
- package/src/render/backend/createRenderer.ts +18 -1
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +672 -37
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +42 -16
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/device.ts +42 -3
- package/src/render/backend/webgpu/drawCommand.ts +22 -4
- package/src/render/backend/webgpu/flatPass.ts +250 -40
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/pipelineCache.ts +7 -0
- package/src/render/backend/webgpu/postPass.ts +18 -0
- package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
- package/src/render/backend/webgpu/renderer.ts +1655 -230
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +363 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/displayRange.ts +61 -0
- package/src/render/filmicCurve.ts +229 -0
- package/src/render/fog.ts +19 -3
- package/src/render/frame/resources.ts +6 -0
- package/src/render/frameReflections.ts +72 -0
- package/src/render/glCompressed.ts +83 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +6 -0
- package/src/render/pass.ts +53 -1
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/reflectionSurfaceTargets.ts +105 -0
- package/src/render/renderQuality.ts +44 -8
- package/src/render/sceneTarget.ts +30 -2
- package/src/render/screenSpaceReflection.ts +7 -1
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/filmLook.ts +2 -1
- package/src/render/shaders/flat/glassTint.ts +5 -1
- package/src/render/shaders/flat/index.ts +49 -5
- package/src/render/shaders/flat/main.ts +43 -5
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/overlay.ts +117 -0
- package/src/render/shaders/flat/pointShadow.ts +3 -3
- package/src/render/shaders/flat/preamble.ts +38 -24
- package/src/render/shaders/flat/reflectionSurface.ts +61 -0
- package/src/render/shaders/fog.ts +9 -2
- package/src/render/shaders/fringe.ts +59 -0
- package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/film.wgsl.ts +1 -1
- package/src/render/shaders/generated/fire.wgsl.ts +1 -1
- package/src/render/shaders/generated/flat.wgsl.ts +3292 -2957
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +4 -4
- package/src/render/shaders/generated/rush.wgsl.ts +56 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
- package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
- package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
- package/src/render/shaders/generated/water.wgsl.ts +1 -1
- package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/rush.ts +54 -11
- package/src/render/shaders/ssrResolve.ts +52 -0
- package/src/render/shaders/ssrTrace.ts +26 -0
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/ssrPass.ts +138 -1
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceOverlay.ts +195 -0
- package/src/render/surfaceTexture.ts +161 -27
- package/src/render/temporalAa.ts +20 -0
- package/src/render/vertexDefaults.ts +28 -0
|
@@ -29,8 +29,8 @@ import { SkinPaletteTexture } from './skinPaletteTexture.ts';
|
|
|
29
29
|
* different rig at every set — the comparison is what tells the two apart.
|
|
30
30
|
*
|
|
31
31
|
* **It was capped at 128 until 4.8.6, and the cap declined draws.** A stage of three dozen idling
|
|
32
|
-
* extras beside two
|
|
33
|
-
* the consumer stood its crowd in the bind pose. The ring now holds as many palettes as a frame
|
|
32
|
+
* extras beside two characters filled it in one frame and every skinned draw past it was refused,
|
|
33
|
+
* so the consumer stood its crowd in the bind pose. The ring now holds as many palettes as a frame
|
|
34
34
|
* holds draws (`RenderQuality.drawsPerFrame`), since no frame can bind more palettes than it draws;
|
|
35
35
|
* a slot still allocates its texture only the first time a frame reaches it, so a scene with two
|
|
36
36
|
* characters holds two. **What it costs** is a texture and a cached bind group a slot, 1.5 KB for a
|
|
@@ -46,8 +46,12 @@ import { SkinPaletteTexture } from './skinPaletteTexture.ts';
|
|
|
46
46
|
*/
|
|
47
47
|
export class SkinPaletteRing {
|
|
48
48
|
private readonly slots: SkinPaletteTexture[] = [];
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* What each slot was last written with, for the comparison that lets a pass reuse it: a copy
|
|
51
|
+
* that grows with the largest rig the slot has held, and how much of it the last rig filled.
|
|
52
|
+
*/
|
|
50
53
|
private readonly held: Float32Array[] = [];
|
|
54
|
+
private readonly heldLength: number[] = [];
|
|
51
55
|
/** The slot each palette array last went into, by identity. Weak, so a caller's array can go. */
|
|
52
56
|
private readonly lastSlot = new WeakMap<Float32Array, number>();
|
|
53
57
|
private used = 0;
|
|
@@ -119,12 +123,13 @@ export class SkinPaletteRing {
|
|
|
119
123
|
slot.update(device, palette);
|
|
120
124
|
this.uploads += 1;
|
|
121
125
|
let copy = this.held[at];
|
|
122
|
-
if (copy === undefined || copy.length
|
|
123
|
-
/* Once per slot and
|
|
126
|
+
if (copy === undefined || copy.length < palette.length) {
|
|
127
|
+
/* Once per slot and larger rig, the way the slot's own texture is: never per frame. */
|
|
124
128
|
copy = new Float32Array(palette.length);
|
|
125
129
|
this.held[at] = copy;
|
|
126
130
|
}
|
|
127
131
|
copy.set(palette);
|
|
132
|
+
this.heldLength[at] = palette.length;
|
|
128
133
|
this.lastSlot.set(palette, at);
|
|
129
134
|
return at;
|
|
130
135
|
}
|
|
@@ -138,14 +143,15 @@ export class SkinPaletteRing {
|
|
|
138
143
|
for (const slot of this.slots) slot.dispose();
|
|
139
144
|
this.slots.length = 0;
|
|
140
145
|
this.held.length = 0;
|
|
146
|
+
this.heldLength.length = 0;
|
|
141
147
|
this.used = 0;
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
/** Whether a slot was last written with exactly these numbers. */
|
|
145
151
|
private holds(slot: number, palette: Float32Array): boolean {
|
|
146
152
|
const copy = this.held[slot];
|
|
147
|
-
if (copy === undefined ||
|
|
148
|
-
for (let i = 0; i <
|
|
153
|
+
if (copy === undefined || this.heldLength[slot] !== palette.length) return false;
|
|
154
|
+
for (let i = 0; i < palette.length; i++) if (copy[i] !== palette[i]) return false;
|
|
149
155
|
return true;
|
|
150
156
|
}
|
|
151
157
|
}
|
|
@@ -22,6 +22,12 @@ const TEXTURE_USAGE = {
|
|
|
22
22
|
|
|
23
23
|
export class SkinPaletteTexture {
|
|
24
24
|
private texture: GPUTexture | null = null;
|
|
25
|
+
/**
|
|
26
|
+
* How many joints the texture has room for, which is the most any rig in this slot has had.
|
|
27
|
+
* **It grows and never shrinks**: the shader reads a joint by index, so a texture wider than the
|
|
28
|
+
* rig is read exactly as one that fits, and a slot that a 10-joint prop and a 296-joint character
|
|
29
|
+
* take in turn keeps one texture rather than making one at every change of rig.
|
|
30
|
+
*/
|
|
25
31
|
private joints = 0;
|
|
26
32
|
/**
|
|
27
33
|
* The view, cached rather than created per call.
|
|
@@ -38,7 +44,7 @@ export class SkinPaletteTexture {
|
|
|
38
44
|
const joints = palette.length / 16;
|
|
39
45
|
const width = paletteTextureWidth(joints);
|
|
40
46
|
|
|
41
|
-
if (this.texture === null ||
|
|
47
|
+
if (this.texture === null || joints > this.joints) {
|
|
42
48
|
this.texture?.destroy();
|
|
43
49
|
this.cachedView = null;
|
|
44
50
|
this.texture = device.createTexture({
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A static list on this backend: its draws recorded once, through the renderer's own draw path,
|
|
3
|
+
* into slots the list owns, and replayed as one render bundle per view.
|
|
4
|
+
*
|
|
5
|
+
* **What a recording holds is the frame's draws as the renderer would have recorded them**, taken
|
|
6
|
+
* from a pool of the list's instead of the frame graph's: a pipeline, a bind group, vertex buffers
|
|
7
|
+
* and four dynamic offsets each. Three of the four never change once recorded — the draw's own
|
|
8
|
+
* slot in `entries`, written once, and its material's slot in the store, pinned. The other two are
|
|
9
|
+
* the view's: the pass block and the view block, which differ between the frame, a mirror and a
|
|
10
|
+
* capture of the same frame. So each view a frame draws the list into takes a *copy* — a slot of
|
|
11
|
+
* `passes` and one of `views`, written for that view — and a bundle is encoded per copy from the
|
|
12
|
+
* same commands with that copy's two offsets. A frame that draws a list twice uploads two blocks
|
|
13
|
+
* and executes two bundles, and records nothing.
|
|
14
|
+
*
|
|
15
|
+
* **Its own buffers, and bind groups over them.** A bundle bakes the buffers its groups bind, and
|
|
16
|
+
* the frame's rings are reset every frame, so a list binds rings of its own: the renderer swaps
|
|
17
|
+
* them in while it records the list's draws, and the groups that recording builds go into the
|
|
18
|
+
* list's cache rather than the frame's.
|
|
19
|
+
*
|
|
20
|
+
* **Recorded again when what it baked has moved**: the renderer's groups (a texture forgotten, a
|
|
21
|
+
* frame texture rebound, a ring grown), the pipeline cache's lit set, the store's slots, or the
|
|
22
|
+
* list's own rings. Each is a number compared at replay, so a stale recording is never replayed.
|
|
23
|
+
*
|
|
24
|
+
* What it gives up: culling, since a bundle draws everything recorded into it; and memory, a pass
|
|
25
|
+
* block a view a list, which is a few kilobytes.
|
|
26
|
+
*/
|
|
27
|
+
import type { StaticDrawsList, StaticEntry } from '../../staticDraws.ts';
|
|
28
|
+
import type { CommandPool, DrawCommand } from './drawCommand.ts';
|
|
29
|
+
import { createCommandPool, resetPool, takeCommand } from './drawCommand.ts';
|
|
30
|
+
import type { PipelineCache } from './pipelineCache.ts';
|
|
31
|
+
import { UniformRing } from './uniformRing.ts';
|
|
32
|
+
|
|
33
|
+
const USAGE_UNIFORM_DST = 0x0040 | 0x0008;
|
|
34
|
+
/** Views a list starts with room for: the frame's and one capture's. Doubles when a frame asks more. */
|
|
35
|
+
const FIRST_COPIES = 2;
|
|
36
|
+
/** Recordings a list keeps before letting the oldest go: a target and a starting state each. */
|
|
37
|
+
const MOST_RECORDINGS = 4;
|
|
38
|
+
|
|
39
|
+
/** What one recording was made against, and what it recorded. */
|
|
40
|
+
export interface StaticRecording {
|
|
41
|
+
readonly cache: PipelineCache;
|
|
42
|
+
/** The material block's words the list started from: the pass's state, which entries patch. */
|
|
43
|
+
readonly start: Int32Array;
|
|
44
|
+
readonly startHash: number;
|
|
45
|
+
readonly cutout: string;
|
|
46
|
+
readonly surfaceKept: boolean;
|
|
47
|
+
/** Stamps compared at replay; any that moved makes the recording stale. */
|
|
48
|
+
cacheVersion: number;
|
|
49
|
+
groupEpoch: number;
|
|
50
|
+
storeEpoch: number;
|
|
51
|
+
ringEpoch: number;
|
|
52
|
+
/** The frame pass's commands, and the reflection pass's surface halves, with the record's offsets. */
|
|
53
|
+
readonly frame: CommandPool;
|
|
54
|
+
readonly surface: CommandPool;
|
|
55
|
+
/** Material slots pinned for these commands, and the material changes recording them asked for. */
|
|
56
|
+
readonly pins: number[];
|
|
57
|
+
materialAsks: number;
|
|
58
|
+
/** Under the screen-space skin blur a skin entry splits into halves no bundle replays. */
|
|
59
|
+
immediate: boolean;
|
|
60
|
+
/** Per copy, the bundles `executeBundles` takes; null until a copy is first replayed. */
|
|
61
|
+
readonly frameBundles: (readonly GPURenderBundle[] | null)[];
|
|
62
|
+
readonly surfaceBundles: (readonly GPURenderBundle[] | null)[];
|
|
63
|
+
/** The frame it was last replayed in, which picks the one to let go. */
|
|
64
|
+
usedIn: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class GpuStaticDraws implements StaticDrawsList {
|
|
68
|
+
disposed = false;
|
|
69
|
+
/** The draws' own slots, one a draw, written by the recording. */
|
|
70
|
+
readonly drawRing: UniformRing;
|
|
71
|
+
/** A pass block and a view block a copy. */
|
|
72
|
+
passes: UniformRing;
|
|
73
|
+
views: UniformRing;
|
|
74
|
+
/** Bumped whenever the three rings are made again, which invalidates every recording. */
|
|
75
|
+
ringEpoch = 0;
|
|
76
|
+
/** The list's own lit groups and blank group, built over its rings by the renderer. */
|
|
77
|
+
groups: Map<unknown, unknown> = new Map();
|
|
78
|
+
blank: GPUBindGroup | null = null;
|
|
79
|
+
/** The group epoch the cache above was built in. */
|
|
80
|
+
groupEpoch = -1;
|
|
81
|
+
/** The store epoch the list's pins were taken in: pins from before a forgetting are let go of. */
|
|
82
|
+
pinEpoch = -1;
|
|
83
|
+
readonly recordings: StaticRecording[] = [];
|
|
84
|
+
private copies = 0;
|
|
85
|
+
private copyFrame = -1;
|
|
86
|
+
|
|
87
|
+
constructor(
|
|
88
|
+
private readonly device: GPUDevice,
|
|
89
|
+
readonly list: StaticDrawsList,
|
|
90
|
+
private readonly passSize: number,
|
|
91
|
+
private readonly viewSize: number,
|
|
92
|
+
drawSize: number,
|
|
93
|
+
) {
|
|
94
|
+
this.drawRing = new UniformRing(
|
|
95
|
+
device,
|
|
96
|
+
drawSize,
|
|
97
|
+
Math.max(1, list.draws),
|
|
98
|
+
USAGE_UNIFORM_DST,
|
|
99
|
+
'static.draws',
|
|
100
|
+
);
|
|
101
|
+
this.passes = new UniformRing(
|
|
102
|
+
device,
|
|
103
|
+
passSize,
|
|
104
|
+
FIRST_COPIES,
|
|
105
|
+
USAGE_UNIFORM_DST,
|
|
106
|
+
'static.passes',
|
|
107
|
+
);
|
|
108
|
+
this.views = new UniformRing(device, viewSize, FIRST_COPIES, USAGE_UNIFORM_DST, 'static.views');
|
|
109
|
+
this.claimCopies();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The captured entries, which the renderer replays to record them. */
|
|
113
|
+
get entries(): readonly StaticEntry[] {
|
|
114
|
+
return this.list.entries;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get draws(): number {
|
|
118
|
+
return this.list.draws;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The next view's copy in frame `frame`, growing the copies when a frame asks for more than there
|
|
123
|
+
* are — which makes every recording stale, since the groups bound the old rings.
|
|
124
|
+
*/
|
|
125
|
+
takeCopy(frame: number): number {
|
|
126
|
+
if (frame !== this.copyFrame) {
|
|
127
|
+
this.copyFrame = frame;
|
|
128
|
+
this.copies = 0;
|
|
129
|
+
}
|
|
130
|
+
const copy = this.copies;
|
|
131
|
+
this.copies += 1;
|
|
132
|
+
if (copy >= this.passes.slots) this.growCopies(this.passes.slots * 2);
|
|
133
|
+
return copy;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** The two offsets copy `copy` binds. */
|
|
137
|
+
passOffset(copy: number): number {
|
|
138
|
+
return copy * this.passes.slotSize;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
viewOffset(copy: number): number {
|
|
142
|
+
return copy * this.views.slotSize;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Copy `copy`'s pass block, written whole. Uploaded by `uploadCopy`. */
|
|
146
|
+
writePass(copy: number, block: Int32Array): void {
|
|
147
|
+
this.passes.writeBlock(this.passOffset(copy), block);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Copy `copy`'s two blocks to the device, and no other copy's: each view writes only its own. */
|
|
151
|
+
uploadCopy(copy: number): void {
|
|
152
|
+
this.passes.uploadSlot(this.passOffset(copy));
|
|
153
|
+
this.views.uploadSlot(this.viewOffset(copy));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The draws' own slots, after a recording wrote them. */
|
|
157
|
+
uploadDraws(): void {
|
|
158
|
+
this.drawRing.flush();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** A recording that matches, if one does: the same target, starting state and side passes. */
|
|
162
|
+
find(
|
|
163
|
+
cache: PipelineCache,
|
|
164
|
+
start: Int32Array,
|
|
165
|
+
startHash: number,
|
|
166
|
+
cutout: string,
|
|
167
|
+
surfaceKept: boolean,
|
|
168
|
+
): StaticRecording | null {
|
|
169
|
+
for (const recording of this.recordings) {
|
|
170
|
+
if (
|
|
171
|
+
recording.cache === cache &&
|
|
172
|
+
recording.startHash === startHash &&
|
|
173
|
+
recording.cutout === cutout &&
|
|
174
|
+
recording.surfaceKept === surfaceKept &&
|
|
175
|
+
sameWords(recording.start, start)
|
|
176
|
+
) {
|
|
177
|
+
return recording;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** A new, empty recording, letting the least recently used go past the most kept. */
|
|
184
|
+
open(
|
|
185
|
+
cache: PipelineCache,
|
|
186
|
+
start: Int32Array,
|
|
187
|
+
startHash: number,
|
|
188
|
+
cutout: string,
|
|
189
|
+
surfaceKept: boolean,
|
|
190
|
+
unpin: (offset: number) => void,
|
|
191
|
+
): StaticRecording {
|
|
192
|
+
if (this.recordings.length >= MOST_RECORDINGS) {
|
|
193
|
+
let oldest = 0;
|
|
194
|
+
for (let i = 1; i < this.recordings.length; i++) {
|
|
195
|
+
if (
|
|
196
|
+
(this.recordings[i] as StaticRecording).usedIn <
|
|
197
|
+
(this.recordings[oldest] as StaticRecording).usedIn
|
|
198
|
+
)
|
|
199
|
+
oldest = i;
|
|
200
|
+
}
|
|
201
|
+
const [gone] = this.recordings.splice(oldest, 1);
|
|
202
|
+
if (gone !== undefined) release(gone, unpin);
|
|
203
|
+
}
|
|
204
|
+
const recording: StaticRecording = {
|
|
205
|
+
cache,
|
|
206
|
+
start: new Int32Array(start),
|
|
207
|
+
startHash,
|
|
208
|
+
cutout,
|
|
209
|
+
surfaceKept,
|
|
210
|
+
cacheVersion: -1,
|
|
211
|
+
groupEpoch: -1,
|
|
212
|
+
storeEpoch: -1,
|
|
213
|
+
ringEpoch: -1,
|
|
214
|
+
frame: createCommandPool(this.list.draws),
|
|
215
|
+
surface: createCommandPool(0),
|
|
216
|
+
pins: [],
|
|
217
|
+
materialAsks: 0,
|
|
218
|
+
immediate: false,
|
|
219
|
+
frameBundles: [],
|
|
220
|
+
surfaceBundles: [],
|
|
221
|
+
usedIn: -1,
|
|
222
|
+
};
|
|
223
|
+
this.recordings.push(recording);
|
|
224
|
+
return recording;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Empty a recording to record it again: its pins let go, its commands and bundles dropped. */
|
|
228
|
+
clear(recording: StaticRecording, unpin: (offset: number) => void): void {
|
|
229
|
+
release(recording, unpin);
|
|
230
|
+
resetPool(recording.frame);
|
|
231
|
+
resetPool(recording.surface);
|
|
232
|
+
recording.frameBundles.length = 0;
|
|
233
|
+
recording.surfaceBundles.length = 0;
|
|
234
|
+
recording.immediate = false;
|
|
235
|
+
recording.materialAsks = 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Every recording, let go; what disposal and a forgetting store both do. */
|
|
239
|
+
forget(unpin: ((offset: number) => void) | null): void {
|
|
240
|
+
for (const recording of this.recordings) release(recording, unpin);
|
|
241
|
+
this.recordings.length = 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
dispose(unpin: ((offset: number) => void) | null): void {
|
|
245
|
+
this.disposed = true;
|
|
246
|
+
this.forget(unpin);
|
|
247
|
+
this.drawRing.dispose();
|
|
248
|
+
this.passes.dispose();
|
|
249
|
+
this.views.dispose();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private growCopies(copies: number): void {
|
|
253
|
+
this.passes.dispose();
|
|
254
|
+
this.views.dispose();
|
|
255
|
+
this.passes = new UniformRing(
|
|
256
|
+
this.device,
|
|
257
|
+
this.passSize,
|
|
258
|
+
copies,
|
|
259
|
+
USAGE_UNIFORM_DST,
|
|
260
|
+
'static.passes',
|
|
261
|
+
);
|
|
262
|
+
this.views = new UniformRing(
|
|
263
|
+
this.device,
|
|
264
|
+
this.viewSize,
|
|
265
|
+
copies,
|
|
266
|
+
USAGE_UNIFORM_DST,
|
|
267
|
+
'static.views',
|
|
268
|
+
);
|
|
269
|
+
this.claimCopies();
|
|
270
|
+
this.ringEpoch += 1;
|
|
271
|
+
this.groups = new Map();
|
|
272
|
+
this.blank = null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Every copy's slot, taken once: a copy is a fixed offset, never handed out again. */
|
|
276
|
+
private claimCopies(): void {
|
|
277
|
+
for (let copy = 0; copy < this.passes.slots; copy++) {
|
|
278
|
+
this.passes.allocate();
|
|
279
|
+
this.views.allocate();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** A command the recording takes in place of the frame graph's. */
|
|
285
|
+
export function takeRecorded(pool: CommandPool): DrawCommand {
|
|
286
|
+
const command = pool.commands[takeCommand(pool)] as DrawCommand;
|
|
287
|
+
command.offsetCount = 0;
|
|
288
|
+
command.vertexCount = 0;
|
|
289
|
+
command.indexBuffer = null;
|
|
290
|
+
command.indexed = false;
|
|
291
|
+
command.instances = 1;
|
|
292
|
+
command.indirect = null;
|
|
293
|
+
return command;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** What a render bundle must agree with: the pass it is executed in. */
|
|
297
|
+
export interface BundleTarget {
|
|
298
|
+
readonly colorFormats: (GPUTextureFormat | null)[];
|
|
299
|
+
readonly depthStencilFormat: GPUTextureFormat;
|
|
300
|
+
readonly sampleCount: number;
|
|
301
|
+
readonly depthReadOnly: boolean;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* One copy's bundle of a pool's commands: each lit command as recorded but for the pass and view
|
|
306
|
+
* offsets, which are the copy's. Allocates, and runs once a copy a recording, never per frame.
|
|
307
|
+
*/
|
|
308
|
+
export function encodeBundle(
|
|
309
|
+
device: GPUDevice,
|
|
310
|
+
target: BundleTarget,
|
|
311
|
+
pool: CommandPool,
|
|
312
|
+
passOffset: number,
|
|
313
|
+
viewOffset: number,
|
|
314
|
+
label: string,
|
|
315
|
+
): readonly GPURenderBundle[] {
|
|
316
|
+
const encoder = device.createRenderBundleEncoder({
|
|
317
|
+
label,
|
|
318
|
+
colorFormats: target.colorFormats,
|
|
319
|
+
depthStencilFormat: target.depthStencilFormat,
|
|
320
|
+
sampleCount: target.sampleCount,
|
|
321
|
+
depthReadOnly: target.depthReadOnly,
|
|
322
|
+
stencilReadOnly: target.depthReadOnly,
|
|
323
|
+
});
|
|
324
|
+
for (let i = 0; i < pool.taken; i++) {
|
|
325
|
+
const command = pool.commands[i] as DrawCommand;
|
|
326
|
+
if (command.pipeline === null || command.bindGroup === null) continue;
|
|
327
|
+
if (command.offsetCount !== 4) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`static draws: a recorded command bound ${command.offsetCount} offsets, not a lit draw's four`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
encoder.setPipeline(command.pipeline);
|
|
333
|
+
encoder.setBindGroup(0, command.bindGroup, [
|
|
334
|
+
command.offsetA,
|
|
335
|
+
passOffset,
|
|
336
|
+
command.offsetC,
|
|
337
|
+
viewOffset,
|
|
338
|
+
]);
|
|
339
|
+
for (let slot = 0; slot < command.vertexCount; slot++) {
|
|
340
|
+
const buffer = command.vertexBuffers[slot];
|
|
341
|
+
if (buffer != null) encoder.setVertexBuffer(slot, buffer);
|
|
342
|
+
}
|
|
343
|
+
if (command.indexed && command.indexBuffer !== null) {
|
|
344
|
+
encoder.setIndexBuffer(command.indexBuffer, 'uint32');
|
|
345
|
+
if (command.indirect !== null) encoder.drawIndexedIndirect(command.indirect, 0);
|
|
346
|
+
else encoder.drawIndexed(command.count, command.instances);
|
|
347
|
+
} else {
|
|
348
|
+
encoder.draw(command.count, command.instances);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return [encoder.finish({ label })];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function release(recording: StaticRecording, unpin: ((offset: number) => void) | null): void {
|
|
355
|
+
if (unpin !== null) for (const offset of recording.pins) unpin(offset);
|
|
356
|
+
recording.pins.length = 0;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function sameWords(a: Int32Array, b: Int32Array): boolean {
|
|
360
|
+
if (a.length !== b.length) return false;
|
|
361
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
@@ -6,7 +6,12 @@ import type { LightmapTexels } from '../../lightmap.ts';
|
|
|
6
6
|
import { isSceneCaptureTexels } from '../../sceneCapture.ts';
|
|
7
7
|
import type { SceneCaptureTexels } from '../../sceneCapture.ts';
|
|
8
8
|
import { layerSize, refuseArrayUpdate, sourceSize } from '../../textureSource.ts';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
compressedLayers,
|
|
11
|
+
isCompressedSource,
|
|
12
|
+
planBlocks,
|
|
13
|
+
refuseBlockUpdate,
|
|
14
|
+
} from '../../compressedSource.ts';
|
|
10
15
|
import type {
|
|
11
16
|
BlockFormat,
|
|
12
17
|
CompressedTextureFormat,
|
|
@@ -192,7 +197,9 @@ export class GpuSurfaceTexture {
|
|
|
192
197
|
/** Its view, which the flat group binds beside this image when it is a material's albedo. */
|
|
193
198
|
effectsView: GPUTextureView | null = null;
|
|
194
199
|
/** The block format a compressed texture holds, or null for one uploaded from images. */
|
|
195
|
-
private
|
|
200
|
+
private blockFormat: BlockFormat | null;
|
|
201
|
+
/** Whether the image is colour, decoded from sRGB as it is sampled: what blocks swapped in keep. */
|
|
202
|
+
private readonly srgb: boolean;
|
|
196
203
|
|
|
197
204
|
constructor(
|
|
198
205
|
private readonly device: GPUDevice,
|
|
@@ -210,6 +217,7 @@ export class GpuSurfaceTexture {
|
|
|
210
217
|
) as readonly SurfaceSource[];
|
|
211
218
|
const blocks = compressedLayers(listed);
|
|
212
219
|
const srgb = (options.colorSpace ?? 'linear') === 'srgb';
|
|
220
|
+
this.srgb = srgb;
|
|
213
221
|
/* `SRGB8_ALPHA8`'s equivalent. Decoded in the sampler, before filtering, which is the only
|
|
214
222
|
place it is correct — `surfaceTexture.ts` makes the argument in full. A capture takes the
|
|
215
223
|
world's own format, which its pass resolves into. */
|
|
@@ -358,11 +366,29 @@ export class GpuSurfaceTexture {
|
|
|
358
366
|
* hit that whenever the preview's decode won the race. The view is new then, and the texture it
|
|
359
367
|
* replaced is handed back rather than destroyed: a draw recorded earlier in this frame may still
|
|
360
368
|
* read it, so the renderer destroys it once nothing can, and drops every binding of the old view.
|
|
369
|
+
*
|
|
370
|
+
* **Blocks replace an image the same way**, at any size: a texture of their format holds them,
|
|
371
|
+
* read in the colour space this one was made for, and the image's is handed back. Refused, as a
|
|
372
|
+
* texture made from them would be, where `compressed` — the formats the device samples — does not
|
|
373
|
+
* take them. The way back is refused: an image cannot replace blocks.
|
|
361
374
|
*/
|
|
362
|
-
update(
|
|
375
|
+
update(
|
|
376
|
+
source: TexImageSource | CompressedTextureSource,
|
|
377
|
+
compressed: readonly CompressedTextureFormat[] = [],
|
|
378
|
+
): GPUTexture | null {
|
|
363
379
|
const replaced = this.texture;
|
|
364
380
|
if (replaced === null) return null;
|
|
365
381
|
refuseArrayUpdate(this.layers);
|
|
382
|
+
if (isCompressedSource(source)) {
|
|
383
|
+
const plan = planBlocks([source], this.srgb, compressed);
|
|
384
|
+
this.blockFormat = source.format;
|
|
385
|
+
this.width = source.width;
|
|
386
|
+
this.height = source.height;
|
|
387
|
+
this.levels = this.mipmapped ? plan.levels : 1;
|
|
388
|
+
this.texture = createCompressedTexture(this.device, plan.name, [source], this.levels);
|
|
389
|
+
this.current = this.arrayView(this.texture);
|
|
390
|
+
return replaced;
|
|
391
|
+
}
|
|
366
392
|
refuseBlockUpdate(this.blockFormat);
|
|
367
393
|
const { width, height } = sourceSize(source);
|
|
368
394
|
if (width === this.width && height === this.height) {
|
|
@@ -23,7 +23,7 @@ export const DYNAMIC_ALIGNMENT = 256;
|
|
|
23
23
|
* is five thousand material changes in one frame. The device buffer matches the staging, so a ring
|
|
24
24
|
* at this ceiling holds 128 MiB in total and only ever reaches it by being asked to.
|
|
25
25
|
*/
|
|
26
|
-
const MAX_RING_BYTES = 64 * 1024 * 1024;
|
|
26
|
+
export const MAX_RING_BYTES = 64 * 1024 * 1024;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Per-draw uniforms, in slots, written once and bound by offset.
|
|
@@ -268,6 +268,15 @@ export class UniformRing {
|
|
|
268
268
|
this.clean = end;
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Upload one slot now, and nothing else: for a ring whose slots are all claimed once and written
|
|
273
|
+
* one at a time — a static list's copies — where `flush` would send every slot above the lowest
|
|
274
|
+
* one written. Leaves the watermark as it was, so a later `flush` still owes what it owed.
|
|
275
|
+
*/
|
|
276
|
+
uploadSlot(slot: number): void {
|
|
277
|
+
this.device.queue.writeBuffer(this.buffer, slot, this.staging, slot, this.slotSize);
|
|
278
|
+
}
|
|
279
|
+
|
|
271
280
|
dispose(): void {
|
|
272
281
|
this.buffer.destroy();
|
|
273
282
|
}
|
package/src/render/bloomChain.ts
CHANGED
|
@@ -86,7 +86,7 @@ export function bloomProfileWarning(quality: {
|
|
|
86
86
|
* already takes. Absent, or a field absent, is the response every frame had before 4.8.6.
|
|
87
87
|
*
|
|
88
88
|
* **Why it exists: one band over the colour less the threshold washes a stage built for another
|
|
89
|
-
* engine.**
|
|
89
|
+
* engine.** A ramped bloom keeps `saturate((L − threshold) / 2)` of a colour rather than
|
|
90
90
|
* the colour less the threshold, and spreads it over six bands, each in a tint of its own. A
|
|
91
91
|
* stage authored against that — a threshold of 0.1 and tints summing below one — came out white
|
|
92
92
|
* here, because subtracting so low a threshold keeps nearly every light in the frame at full
|
|
@@ -96,8 +96,8 @@ export interface BloomResponse {
|
|
|
96
96
|
/**
|
|
97
97
|
* Scene units over which a colour comes in, from none of it at the threshold to all of it this
|
|
98
98
|
* far past. **0, the default, subtracts instead**: a colour keeps what it has past the threshold,
|
|
99
|
-
* the response since bloom shipped.
|
|
100
|
-
* wants it passes `2 / e`, as it passes the threshold over the exposure.
|
|
99
|
+
* the response since bloom shipped. A ramped bloom's is 2 after exposure, so a frame exposed by
|
|
100
|
+
* `e` that wants it passes `2 / e`, as it passes the threshold over the exposure.
|
|
101
101
|
*
|
|
102
102
|
* Brightness is still the largest channel rather than luminance, for the reason
|
|
103
103
|
* `shaders/bloom.ts` gives; on a neutral colour the two are equal.
|