@driftengine/core 4.9.0 → 4.11.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 +133 -11
- package/README.md +43 -38
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -4
- 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/backend/api.d.ts +10 -3
- 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 +189 -4
- package/dist/render/backend/webgl2/renderer.js +614 -66
- 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 +24 -4
- package/dist/render/backend/webgpu/buffers.js +116 -59
- package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
- package/dist/render/backend/webgpu/clothKernels.js +76 -0
- package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
- package/dist/render/backend/webgpu/clothOps.js +78 -0
- package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
- package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
- package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
- package/dist/render/backend/webgpu/clothSetPack.js +144 -0
- 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/drawCommand.d.ts +20 -4
- package/dist/render/backend/webgpu/drawCommand.js +11 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
- package/dist/render/backend/webgpu/flatPass.js +137 -20
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
- package/dist/render/backend/webgpu/pipelineCache.js +46 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +200 -35
- package/dist/render/backend/webgpu/renderer.js +993 -168
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
- 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 +242 -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/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- 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/glCompressed.js +83 -0
- package/dist/render/globalMediumPass.d.ts +2 -0
- package/dist/render/globalMediumPass.js +1 -0
- package/dist/render/indexWidth.d.ts +17 -0
- package/dist/render/indexWidth.js +23 -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/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +14 -0
- package/dist/render/mesh.js +44 -14
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/renderQuality.d.ts +30 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.d.ts +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/screenSpaceReflection.d.ts +2 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/flat/directionalShadow.js +84 -10
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +3 -0
- package/dist/render/shaders/flat/index.d.ts +21 -0
- package/dist/render/shaders/flat/index.js +42 -7
- package/dist/render/shaders/flat/layered.d.ts +19 -0
- package/dist/render/shaders/flat/layered.js +115 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +58 -21
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/movingSun.d.ts +21 -0
- package/dist/render/shaders/flat/movingSun.js +46 -0
- package/dist/render/shaders/flat/normalMap.d.ts +6 -2
- package/dist/render/shaders/flat/normalMap.js +14 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +42 -20
- package/dist/render/shaders/flat/pointShadow.js +2 -2
- package/dist/render/shaders/flat/preamble.js +59 -27
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- 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/flat.wgsl.d.ts +3616 -3042
- package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
- package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
- package/dist/render/shaders/globalMedium.js +9 -1
- package/dist/render/shaders/lightVolume.js +9 -1
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceLayers.d.ts +39 -0
- package/dist/render/surfaceLayers.js +26 -0
- package/dist/render/surfaceOverlay.d.ts +16 -3
- package/dist/render/surfaceOverlay.js +11 -6
- package/dist/render/surfaceProjection.d.ts +34 -0
- package/dist/render/surfaceProjection.js +30 -0
- package/dist/render/surfaceTexture.d.ts +68 -7
- package/dist/render/surfaceTexture.js +56 -17
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/package.json +6 -6
- package/src/index.ts +12 -4
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/backend/api.ts +13 -3
- 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 +723 -79
- 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 +195 -70
- package/src/render/backend/webgpu/clothKernels.ts +86 -0
- package/src/render/backend/webgpu/clothOps.ts +86 -0
- package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
- package/src/render/backend/webgpu/clothSetPack.ts +197 -0
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/drawCommand.ts +25 -4
- package/src/render/backend/webgpu/flatPass.ts +212 -41
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +53 -7
- package/src/render/backend/webgpu/renderer.ts +1201 -202
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- 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 +364 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/glCompressed.ts +83 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +72 -14
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/screenSpaceReflection.ts +2 -1
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/flat/directionalShadow.ts +84 -10
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +63 -7
- package/src/render/shaders/flat/layered.ts +115 -0
- package/src/render/shaders/flat/main.ts +58 -21
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +14 -3
- package/src/render/shaders/flat/overlay.ts +42 -20
- package/src/render/shaders/flat/pointShadow.ts +2 -2
- package/src/render/shaders/flat/preamble.ts +59 -27
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
- package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
- package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
- package/src/render/shaders/globalMedium.ts +9 -1
- package/src/render/shaders/lightVolume.ts +9 -1
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceLayers.ts +57 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +124 -27
- package/src/render/vertexPacking.ts +109 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
|
@@ -0,0 +1,364 @@
|
|
|
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.indexFormat = 'uint32';
|
|
291
|
+
command.indexed = false;
|
|
292
|
+
command.instances = 1;
|
|
293
|
+
command.indirect = null;
|
|
294
|
+
return command;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** What a render bundle must agree with: the pass it is executed in. */
|
|
298
|
+
export interface BundleTarget {
|
|
299
|
+
readonly colorFormats: (GPUTextureFormat | null)[];
|
|
300
|
+
readonly depthStencilFormat: GPUTextureFormat;
|
|
301
|
+
readonly sampleCount: number;
|
|
302
|
+
readonly depthReadOnly: boolean;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* One copy's bundle of a pool's commands: each lit command as recorded but for the pass and view
|
|
307
|
+
* offsets, which are the copy's. Allocates, and runs once a copy a recording, never per frame.
|
|
308
|
+
*/
|
|
309
|
+
export function encodeBundle(
|
|
310
|
+
device: GPUDevice,
|
|
311
|
+
target: BundleTarget,
|
|
312
|
+
pool: CommandPool,
|
|
313
|
+
passOffset: number,
|
|
314
|
+
viewOffset: number,
|
|
315
|
+
label: string,
|
|
316
|
+
): readonly GPURenderBundle[] {
|
|
317
|
+
const encoder = device.createRenderBundleEncoder({
|
|
318
|
+
label,
|
|
319
|
+
colorFormats: target.colorFormats,
|
|
320
|
+
depthStencilFormat: target.depthStencilFormat,
|
|
321
|
+
sampleCount: target.sampleCount,
|
|
322
|
+
depthReadOnly: target.depthReadOnly,
|
|
323
|
+
stencilReadOnly: target.depthReadOnly,
|
|
324
|
+
});
|
|
325
|
+
for (let i = 0; i < pool.taken; i++) {
|
|
326
|
+
const command = pool.commands[i] as DrawCommand;
|
|
327
|
+
if (command.pipeline === null || command.bindGroup === null) continue;
|
|
328
|
+
if (command.offsetCount !== 4) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
`static draws: a recorded command bound ${command.offsetCount} offsets, not a lit draw's four`,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
encoder.setPipeline(command.pipeline);
|
|
334
|
+
encoder.setBindGroup(0, command.bindGroup, [
|
|
335
|
+
command.offsetA,
|
|
336
|
+
passOffset,
|
|
337
|
+
command.offsetC,
|
|
338
|
+
viewOffset,
|
|
339
|
+
]);
|
|
340
|
+
for (let slot = 0; slot < command.vertexCount; slot++) {
|
|
341
|
+
const buffer = command.vertexBuffers[slot];
|
|
342
|
+
if (buffer != null) encoder.setVertexBuffer(slot, buffer);
|
|
343
|
+
}
|
|
344
|
+
if (command.indexed && command.indexBuffer !== null) {
|
|
345
|
+
encoder.setIndexBuffer(command.indexBuffer, command.indexFormat);
|
|
346
|
+
if (command.indirect !== null) encoder.drawIndexedIndirect(command.indirect, 0);
|
|
347
|
+
else encoder.drawIndexed(command.count, command.instances);
|
|
348
|
+
} else {
|
|
349
|
+
encoder.draw(command.count, command.instances);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return [encoder.finish({ label })];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function release(recording: StaticRecording, unpin: ((offset: number) => void) | null): void {
|
|
356
|
+
if (unpin !== null) for (const offset of recording.pins) unpin(offset);
|
|
357
|
+
recording.pins.length = 0;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function sameWords(a: Int32Array, b: Int32Array): boolean {
|
|
361
|
+
if (a.length !== b.length) return false;
|
|
362
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PACKED_ATTRIBUTES,
|
|
3
|
+
toFixed16,
|
|
4
|
+
type AttributePacking,
|
|
5
|
+
type VertexPacking,
|
|
6
|
+
} from '../../vertexPacking.ts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A mesh's packed attributes as a WebGPU vertex reads them: each one's format and bytes, the words
|
|
10
|
+
* a pipeline key carries for them, and the writers that put sixteen-bit fixed point into the
|
|
11
|
+
* interleaved rows. What is packed how is `vertexPacking.ts`'s decision; this is only its binding.
|
|
12
|
+
*
|
|
13
|
+
* **Sixteen-bit fields are four wide, never three**, because WebGPU has no three-component
|
|
14
|
+
* sixteen-bit format: a normal or a colour takes `x4` and leaves the fourth zero, and the shader's
|
|
15
|
+
* `vec3` input reads the first three. So a normal costs eight bytes here against six on WebGL2,
|
|
16
|
+
* which reads three. Every field stays a whole number of four-byte words, so a vertex does too.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** The ways an attribute can travel other than as floats, as the key words name them. */
|
|
20
|
+
const PACKINGS = ['snorm16', 'unorm16', 'constant'] as const;
|
|
21
|
+
|
|
22
|
+
/** The pipeline key's word for one attribute's packing. Not a prefix of any other word. */
|
|
23
|
+
function word(name: string, packing: AttributePacking): string {
|
|
24
|
+
return `${name}~${packing}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The key's suffix for this packing: one word each attribute not travelling as floats. */
|
|
28
|
+
export function packingKey(packing: VertexPacking): string {
|
|
29
|
+
let key = '';
|
|
30
|
+
for (const name of PACKED_ATTRIBUTES) {
|
|
31
|
+
if (packing[name] !== 'float') key += `:${word(name, packing[name])}`;
|
|
32
|
+
}
|
|
33
|
+
return key;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Mark this packing in a pipeline's `present`, which is what `vertexBufferLayouts` reads. */
|
|
37
|
+
export function markPacking(present: Record<string, boolean>, packing: VertexPacking): void {
|
|
38
|
+
for (const name of PACKED_ATTRIBUTES) {
|
|
39
|
+
if (packing[name] !== 'float') present[word(name, packing[name])] = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Mark the packing a mesh's key names, for a pass that has the mesh and not its data. */
|
|
44
|
+
export function markPackingOfKey(present: Record<string, boolean>, key: string): void {
|
|
45
|
+
for (const name of PACKED_ATTRIBUTES) {
|
|
46
|
+
for (const packing of PACKINGS) {
|
|
47
|
+
if (key.includes(`:${word(name, packing)}`)) present[word(name, packing)] = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** How `present` says this attribute travels: floats unless a word says otherwise. */
|
|
53
|
+
export function packingIn(
|
|
54
|
+
present: Readonly<Record<string, boolean>>,
|
|
55
|
+
name: string,
|
|
56
|
+
): AttributePacking {
|
|
57
|
+
for (const packing of PACKINGS) {
|
|
58
|
+
if (present[word(name, packing)] === true) return packing;
|
|
59
|
+
}
|
|
60
|
+
return 'float';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The format an attribute is read with, packed this way, or `float` as given. */
|
|
64
|
+
export function packedFormat(float: GPUVertexFormat, packing: AttributePacking): GPUVertexFormat {
|
|
65
|
+
if (packing === 'snorm16') return 'snorm16x4';
|
|
66
|
+
if (packing === 'unorm16') return 'unorm16x4';
|
|
67
|
+
return float;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Bytes a vertex spends on one attribute read with `format`. */
|
|
71
|
+
export function formatBytes(format: GPUVertexFormat): number {
|
|
72
|
+
if (format === 'snorm16x4' || format === 'unorm16x4') return 8;
|
|
73
|
+
if (format === 'float32x4') return 16;
|
|
74
|
+
if (format === 'float32x3') return 12;
|
|
75
|
+
if (format === 'float32x2') return 8;
|
|
76
|
+
return 4;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* One attribute of vertices `from` to `to` into its sixteen-bit field of the interleaved rows,
|
|
81
|
+
* `scale` being `SNORM16_ONE` or `UNORM16_ONE`. Rounded by `toFixed16`, as `fixedArray` rounds
|
|
82
|
+
* for the other backend; the range was checked when the packing was chosen.
|
|
83
|
+
*
|
|
84
|
+
* Unrolled by width, as `interleaveField` is and for its reason: the general loop was most of a
|
|
85
|
+
* heavy load's upload. Three and four are the widths a packed attribute has.
|
|
86
|
+
*/
|
|
87
|
+
export function interleaveFixed(
|
|
88
|
+
rows: Int16Array | Uint16Array,
|
|
89
|
+
source: Float32Array,
|
|
90
|
+
width: number,
|
|
91
|
+
step: number,
|
|
92
|
+
fieldOffset: number,
|
|
93
|
+
from: number,
|
|
94
|
+
to: number,
|
|
95
|
+
scale: number,
|
|
96
|
+
): void {
|
|
97
|
+
let write = from * step + fieldOffset;
|
|
98
|
+
const end = to * width;
|
|
99
|
+
if (width === 3) {
|
|
100
|
+
for (let read = from * 3; read < end; read += 3, write += step) {
|
|
101
|
+
rows[write] = toFixed16(source[read] as number, scale);
|
|
102
|
+
rows[write + 1] = toFixed16(source[read + 1] as number, scale);
|
|
103
|
+
rows[write + 2] = toFixed16(source[read + 2] as number, scale);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
for (let read = from * 4; read < end; read += 4, write += step) {
|
|
107
|
+
rows[write] = toFixed16(source[read] as number, scale);
|
|
108
|
+
rows[write + 1] = toFixed16(source[read + 1] as number, scale);
|
|
109
|
+
rows[write + 2] = toFixed16(source[read + 2] as number, scale);
|
|
110
|
+
rows[write + 3] = toFixed16(source[read + 3] as number, scale);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
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.
|