@swf2es/render 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +17 -2
- package/dist/commands.d.ts +67 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +93 -0
- package/dist/commands.js.map +1 -0
- package/dist/filter-plan.d.ts +110 -0
- package/dist/filter-plan.d.ts.map +1 -0
- package/dist/filter-plan.js +480 -0
- package/dist/filter-plan.js.map +1 -0
- package/dist/filter-shaders.d.ts +63 -0
- package/dist/filter-shaders.d.ts.map +1 -0
- package/dist/filter-shaders.js +350 -0
- package/dist/filter-shaders.js.map +1 -0
- package/dist/filters.d.ts +107 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +187 -0
- package/dist/filters.js.map +1 -0
- package/dist/geometry.d.ts +83 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +93 -0
- package/dist/geometry.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/keeps.d.ts +17 -0
- package/dist/keeps.d.ts.map +1 -0
- package/dist/keeps.js +17 -0
- package/dist/keeps.js.map +1 -0
- package/dist/kernel-shaders.d.ts +74 -0
- package/dist/kernel-shaders.d.ts.map +1 -0
- package/dist/kernel-shaders.js +323 -0
- package/dist/kernel-shaders.js.map +1 -0
- package/dist/measure.d.ts +56 -0
- package/dist/measure.d.ts.map +1 -0
- package/dist/measure.js +287 -0
- package/dist/measure.js.map +1 -0
- package/dist/plan.d.ts +14 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +247 -0
- package/dist/plan.js.map +1 -0
- package/dist/registry.d.ts +49 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +87 -0
- package/dist/registry.js.map +1 -0
- package/dist/rows.d.ts +39 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +60 -0
- package/dist/rows.js.map +1 -0
- package/dist/scene.d.ts +179 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +265 -0
- package/dist/scene.js.map +1 -0
- package/dist/shelves.d.ts +23 -0
- package/dist/shelves.d.ts.map +1 -0
- package/dist/shelves.js +63 -0
- package/dist/shelves.js.map +1 -0
- package/dist/textures.d.ts +72 -0
- package/dist/textures.d.ts.map +1 -0
- package/dist/textures.js +160 -0
- package/dist/textures.js.map +1 -0
- package/dist/webgl/atlas.d.ts +63 -0
- package/dist/webgl/atlas.d.ts.map +1 -0
- package/dist/webgl/atlas.js +328 -0
- package/dist/webgl/atlas.js.map +1 -0
- package/dist/webgl/backend.d.ts +141 -0
- package/dist/webgl/backend.d.ts.map +1 -0
- package/dist/webgl/backend.js +659 -0
- package/dist/webgl/backend.js.map +1 -0
- package/dist/webgl/composite.d.ts +101 -0
- package/dist/webgl/composite.d.ts.map +1 -0
- package/dist/webgl/composite.js +293 -0
- package/dist/webgl/composite.js.map +1 -0
- package/dist/webgl/draws.d.ts +213 -0
- package/dist/webgl/draws.d.ts.map +1 -0
- package/dist/webgl/draws.js +714 -0
- package/dist/webgl/draws.js.map +1 -0
- package/dist/webgl/filters.d.ts +107 -0
- package/dist/webgl/filters.d.ts.map +1 -0
- package/dist/webgl/filters.js +351 -0
- package/dist/webgl/filters.js.map +1 -0
- package/dist/webgl/gpu-textures.d.ts +44 -0
- package/dist/webgl/gpu-textures.d.ts.map +1 -0
- package/dist/webgl/gpu-textures.js +178 -0
- package/dist/webgl/gpu-textures.js.map +1 -0
- package/dist/webgl/holds.d.ts +97 -0
- package/dist/webgl/holds.d.ts.map +1 -0
- package/dist/webgl/holds.js +453 -0
- package/dist/webgl/holds.js.map +1 -0
- package/dist/webgl/index.d.ts +2 -0
- package/dist/webgl/index.d.ts.map +1 -0
- package/dist/webgl/index.js +4 -0
- package/dist/webgl/index.js.map +1 -0
- package/dist/webgl/kernels.d.ts +64 -0
- package/dist/webgl/kernels.d.ts.map +1 -0
- package/dist/webgl/kernels.js +160 -0
- package/dist/webgl/kernels.js.map +1 -0
- package/dist/webgl/pool.d.ts +71 -0
- package/dist/webgl/pool.d.ts.map +1 -0
- package/dist/webgl/pool.js +192 -0
- package/dist/webgl/pool.js.map +1 -0
- package/dist/webgl/programs.d.ts +130 -0
- package/dist/webgl/programs.d.ts.map +1 -0
- package/dist/webgl/programs.js +582 -0
- package/dist/webgl/programs.js.map +1 -0
- package/dist/webgl/stage.d.ts +67 -0
- package/dist/webgl/stage.d.ts.map +1 -0
- package/dist/webgl/stage.js +173 -0
- package/dist/webgl/stage.js.map +1 -0
- package/dist/webgl/targets.d.ts +172 -0
- package/dist/webgl/targets.d.ts.map +1 -0
- package/dist/webgl/targets.js +323 -0
- package/dist/webgl/targets.js.map +1 -0
- package/package.json +35 -3
- package/src/commands.ts +98 -0
- package/src/filter-plan.ts +755 -0
- package/src/filter-shaders.ts +360 -0
- package/src/filters.ts +220 -0
- package/src/geometry.ts +143 -0
- package/src/index.ts +87 -0
- package/src/keeps.ts +21 -0
- package/src/kernel-shaders.ts +375 -0
- package/src/measure.ts +400 -0
- package/src/plan.ts +331 -0
- package/src/registry.ts +105 -0
- package/src/rows.ts +93 -0
- package/src/scene.ts +297 -0
- package/src/shelves.ts +70 -0
- package/src/textures.ts +198 -0
- package/src/webgl/atlas.ts +370 -0
- package/src/webgl/backend.ts +973 -0
- package/src/webgl/composite.ts +372 -0
- package/src/webgl/draws.ts +902 -0
- package/src/webgl/filters.ts +502 -0
- package/src/webgl/gpu-textures.ts +253 -0
- package/src/webgl/holds.ts +624 -0
- package/src/webgl/index.ts +3 -0
- package/src/webgl/kernels.ts +205 -0
- package/src/webgl/pool.ts +245 -0
- package/src/webgl/programs.ts +620 -0
- package/src/webgl/stage.ts +214 -0
- package/src/webgl/targets.ts +464 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { type GlState, linkProgram, ShaderError } from "@swf2es/context3d/device";
|
|
2
|
+
import type { Filters } from "../filters.js";
|
|
3
|
+
import type { KernelImage } from "../kernel-shaders.js";
|
|
4
|
+
import { CLAMP } from "../scene.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A Pixel Bender kernel's program: where it takes its uniforms, asked
|
|
8
|
+
* once each, and the values of up to four floats it was last told for
|
|
9
|
+
* each, by name (the filter runner's `set`).
|
|
10
|
+
*/
|
|
11
|
+
export interface KernelProgram {
|
|
12
|
+
program: WebGLProgram;
|
|
13
|
+
uniforms: Map<string, WebGLUniformLocation | null>;
|
|
14
|
+
told: Map<string, Float32Array>;
|
|
15
|
+
at(name: string): WebGLUniformLocation | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Where kernels' programs find the textures their images are and the samplers they read them by. */
|
|
19
|
+
export interface KernelHost {
|
|
20
|
+
texture(id: number): WebGLTexture | null;
|
|
21
|
+
sampler(bits: number): WebGLSampler;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The first unit a kernel's own images are bound to, as many as
|
|
26
|
+
* kernel-shaders.ts's KERNEL_IMAGES: past every unit the other programs
|
|
27
|
+
* use (programs.ts's, and the filter passes' 4 to 6), as WebGL 2 has 32
|
|
28
|
+
* for a draw's programs together.
|
|
29
|
+
*/
|
|
30
|
+
export const KERNEL_UNIT = 16;
|
|
31
|
+
|
|
32
|
+
/** The most kernels' programs kept, the one used longest ago deleted for another. */
|
|
33
|
+
const KERNEL_PROGRAMS = 32;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The programs of Pixel Bender kernels, a ShaderFilter's, a blend
|
|
37
|
+
* shader's or a shader fill's, by their fragment shaders, each a use's
|
|
38
|
+
* own and so with one vertex shader:
|
|
39
|
+
* linked as first used, the one used longest ago deleted past
|
|
40
|
+
* KERNEL_PROGRAMS; null for one the GPU would not link, which its user
|
|
41
|
+
* draws otherwise. One for the backend, which its users share.
|
|
42
|
+
*/
|
|
43
|
+
export class KernelPrograms {
|
|
44
|
+
private readonly programs = new Map<string, KernelProgram | null>();
|
|
45
|
+
private readonly ints = new Int32Array(4);
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
private readonly gl: WebGL2RenderingContext,
|
|
49
|
+
private readonly state: GlState,
|
|
50
|
+
) {}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The program of `vertex` and `fragment`, in use; null where it would
|
|
54
|
+
* not link. `prepare` is told it as it is linked, to set its samplers.
|
|
55
|
+
*/
|
|
56
|
+
use(
|
|
57
|
+
vertex: string,
|
|
58
|
+
fragment: string,
|
|
59
|
+
prepare: (program: WebGLProgram) => void,
|
|
60
|
+
): KernelProgram | null {
|
|
61
|
+
const gl = this.gl;
|
|
62
|
+
// By the fragment shader alone, a use's own, which a frame gives again as the same string.
|
|
63
|
+
const key = fragment;
|
|
64
|
+
let kernel = this.programs.get(key);
|
|
65
|
+
if (kernel !== undefined) {
|
|
66
|
+
// Used last now.
|
|
67
|
+
this.programs.delete(key);
|
|
68
|
+
} else {
|
|
69
|
+
if (this.programs.size >= KERNEL_PROGRAMS) {
|
|
70
|
+
const [oldest, old] = this.programs.entries().next().value as [
|
|
71
|
+
string,
|
|
72
|
+
KernelProgram | null,
|
|
73
|
+
];
|
|
74
|
+
if (old) {
|
|
75
|
+
gl.deleteProgram(old.program);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.programs.delete(oldest);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
kernel = null;
|
|
82
|
+
try {
|
|
83
|
+
const program = linkProgram(gl, vertex, fragment);
|
|
84
|
+
const uniforms = new Map<string, WebGLUniformLocation | null>();
|
|
85
|
+
kernel = {
|
|
86
|
+
program,
|
|
87
|
+
uniforms,
|
|
88
|
+
told: new Map(),
|
|
89
|
+
at(name) {
|
|
90
|
+
let location = uniforms.get(name);
|
|
91
|
+
if (location === undefined) {
|
|
92
|
+
location = gl.getUniformLocation(program, name);
|
|
93
|
+
uniforms.set(name, location);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return location;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
this.state.useProgram(program);
|
|
100
|
+
prepare(program);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
if (!(e instanceof ShaderError)) {
|
|
103
|
+
throw e;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.programs.set(key, kernel);
|
|
109
|
+
if (kernel) {
|
|
110
|
+
this.state.useProgram(kernel.program);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return kernel;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Texture `id` bound to a kernel's own image unit `k`, which its sampler at `location` names. */
|
|
117
|
+
bindImage(location: WebGLUniformLocation | null, k: number, id: number, host: KernelHost): void {
|
|
118
|
+
this.state.bindTexture(KERNEL_UNIT + k, host.texture(id));
|
|
119
|
+
// Nearest: a float texture with a linear sampler is incomplete, and texelFetch reads either.
|
|
120
|
+
this.state.bindSampler(KERNEL_UNIT + k, host.sampler(CLAMP));
|
|
121
|
+
this.gl.uniform1i(location, KERNEL_UNIT + k);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** `count` ints of `source` from `at` set as the ivec4 array at `location`, its program in use. */
|
|
125
|
+
setInts(location: WebGLUniformLocation | null, source: Int32Array, at: number, count: number) {
|
|
126
|
+
let values = this.ints;
|
|
127
|
+
if (count > values.length) {
|
|
128
|
+
values = new Int32Array(count);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
values.set(source.subarray(at, at + count));
|
|
132
|
+
this.gl.uniform4iv(location, values, 0, count);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** What readImages reads into, its records kept from call to call. */
|
|
136
|
+
private readonly images: KernelImage[] = [];
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The images of the kernel at entry `i` of `filters`, a blend shader's
|
|
140
|
+
* or a shader fill's, its values laid out as a ShaderFilter's
|
|
141
|
+
* (filters.ts, SHADER): -1 for what is below, -2 for the layer blended.
|
|
142
|
+
* Null where a texture of its own is gone, as such a kernel is left out.
|
|
143
|
+
*/
|
|
144
|
+
readImages(filters: Filters, i: number, host: KernelHost): KernelImage[] | null {
|
|
145
|
+
const v = filters.values;
|
|
146
|
+
const at = filters.start[i];
|
|
147
|
+
const count = v[at + 7];
|
|
148
|
+
const images = this.images;
|
|
149
|
+
for (let k = 0; k < count; k++) {
|
|
150
|
+
const id = v[at + 9 + k * 3];
|
|
151
|
+
if (id >= 0 && !host.texture(id)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!images[k]) {
|
|
156
|
+
images[k] = { texture: 0, layer: false, float: false, back: false };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const image = images[k];
|
|
160
|
+
image.texture = v[at + 8 + k * 3];
|
|
161
|
+
image.layer = id < 0;
|
|
162
|
+
image.back = id === -1;
|
|
163
|
+
image.float = v[at + 10 + k * 3] > 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
images.length = count;
|
|
167
|
+
return images;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The parameters and own images of the kernel at entry `i` of `filters` set on `kernel`, in use. */
|
|
171
|
+
setUniforms(kernel: KernelProgram, filters: Filters, i: number, host: KernelHost): void {
|
|
172
|
+
const v = filters.values;
|
|
173
|
+
const at = filters.start[i];
|
|
174
|
+
const count = v[at + 7];
|
|
175
|
+
let unit = 0;
|
|
176
|
+
for (let k = 0; k < count; k++) {
|
|
177
|
+
const id = v[at + 9 + k * 3];
|
|
178
|
+
if (id >= 0) {
|
|
179
|
+
this.bindImage(kernel.at(`pbImage${v[at + 8 + k * 3]}`), unit++, id, host);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const floats = at + 8 + count * 3;
|
|
184
|
+
if (v[floats] > 0) {
|
|
185
|
+
this.gl.uniform4fv(kernel.at("pbFloats"), v, floats + 1, v[floats]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const ints = filters.intStart[i];
|
|
189
|
+
const n = filters.intStart[i + 1] - ints;
|
|
190
|
+
if (n > 0) {
|
|
191
|
+
this.setInts(kernel.at("pbInts"), filters.ints, ints, n);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Every program deleted, as the backend goes; none kept, as a lost context comes back. */
|
|
196
|
+
clear(): void {
|
|
197
|
+
for (const kernel of this.programs.values()) {
|
|
198
|
+
if (kernel) {
|
|
199
|
+
this.gl.deleteProgram(kernel.program);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.programs.clear();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a pool holds: its size, and the frame it was last freed in; where
|
|
3
|
+
* it may be used as smaller than it is, its own size, which it is pooled by.
|
|
4
|
+
*/
|
|
5
|
+
export interface Pooled {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
textureWidth?: number;
|
|
9
|
+
textureHeight?: number;
|
|
10
|
+
usedAt: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** What a size, and samples where they count, are keyed by. */
|
|
14
|
+
const SIZE_KEY = 65536;
|
|
15
|
+
const SAMPLES_KEY = 32;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Free targets kept by their size (and samples) for the next to take them:
|
|
19
|
+
* an exact one found at once, a larger one among the sizes there are, each
|
|
20
|
+
* size's list in the order they were freed. A frame's end lets go of those
|
|
21
|
+
* idle too long, then of the least lately freed past a budget, oldest first
|
|
22
|
+
* from the lists' heads, as the frame's own working set may pass it for a
|
|
23
|
+
* while without what it gives back being made again.
|
|
24
|
+
*/
|
|
25
|
+
export class SizedPool<T extends Pooled> {
|
|
26
|
+
private readonly lists = new Map<number, T[]>();
|
|
27
|
+
private readonly bytesOf: (item: T) => number;
|
|
28
|
+
/** The bytes all held take together. */
|
|
29
|
+
bytes = 0;
|
|
30
|
+
|
|
31
|
+
constructor(bytesOf: (item: T) => number) {
|
|
32
|
+
this.bytesOf = bytesOf;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** One of just `width` by `height` (and `samples`), the latest freed; none for none. */
|
|
36
|
+
take(width: number, height: number, samples = 0): T | null {
|
|
37
|
+
return this.takeAt((width * SIZE_KEY + height) * SAMPLES_KEY + samples);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The smallest target that fits, at these samples and within `maxArea`; none for none. */
|
|
41
|
+
takeFitting(
|
|
42
|
+
width: number,
|
|
43
|
+
height: number,
|
|
44
|
+
samples = 0,
|
|
45
|
+
maxArea = Number.POSITIVE_INFINITY,
|
|
46
|
+
): T | null {
|
|
47
|
+
let best = -1;
|
|
48
|
+
let least = Number.POSITIVE_INFINITY;
|
|
49
|
+
for (const key of this.lists.keys()) {
|
|
50
|
+
const size = Math.floor(key / SAMPLES_KEY);
|
|
51
|
+
const w = Math.floor(size / SIZE_KEY);
|
|
52
|
+
const h = size % SIZE_KEY;
|
|
53
|
+
if (
|
|
54
|
+
key % SAMPLES_KEY === samples &&
|
|
55
|
+
w >= width &&
|
|
56
|
+
h >= height &&
|
|
57
|
+
w * h <= maxArea &&
|
|
58
|
+
w * h < least
|
|
59
|
+
) {
|
|
60
|
+
least = w * h;
|
|
61
|
+
best = key;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return best < 0 ? null : this.takeAt(best);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private takeAt(key: number): T | null {
|
|
69
|
+
const list = this.lists.get(key);
|
|
70
|
+
const item = list?.pop() ?? null;
|
|
71
|
+
if (!item) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// An emptied size goes, so that finding a larger one looks at those held alone.
|
|
76
|
+
if ((list as T[]).length === 0) {
|
|
77
|
+
this.lists.delete(key);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.bytes -= this.bytesOf(item);
|
|
81
|
+
return item;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** `item` free again, freed in frame `frame`, of `samples` where they count. */
|
|
85
|
+
give(item: T, frame: number, samples = 0): void {
|
|
86
|
+
item.usedAt = frame;
|
|
87
|
+
const key =
|
|
88
|
+
((item.textureWidth ?? item.width) * SIZE_KEY + (item.textureHeight ?? item.height)) *
|
|
89
|
+
SAMPLES_KEY +
|
|
90
|
+
samples;
|
|
91
|
+
let list = this.lists.get(key);
|
|
92
|
+
if (!list) {
|
|
93
|
+
list = [];
|
|
94
|
+
this.lists.set(key, list);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
list.push(item);
|
|
98
|
+
this.bytes += this.bytesOf(item);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Those freed more than `idle` frames before `frame` let go of, then the
|
|
103
|
+
* least lately freed till they take `budget` bytes at most, each `drop`ped.
|
|
104
|
+
*/
|
|
105
|
+
trim(frame: number, idle: number, budget: number, drop: (item: T) => void): void {
|
|
106
|
+
this.trimIdle(frame, idle, drop);
|
|
107
|
+
trimTogether([{ pool: this, drop }], budget);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Those freed more than `idle` frames before `frame` let go of, each `drop`ped. */
|
|
111
|
+
trimIdle(frame: number, idle: number, drop: (item: T) => void): void {
|
|
112
|
+
for (const [key, list] of this.lists) {
|
|
113
|
+
let gone = 0;
|
|
114
|
+
while (gone < list.length && frame - list[gone].usedAt > idle) {
|
|
115
|
+
this.bytes -= this.bytesOf(list[gone]);
|
|
116
|
+
drop(list[gone]);
|
|
117
|
+
gone++;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (gone === list.length) {
|
|
121
|
+
this.lists.delete(key);
|
|
122
|
+
} else if (gone > 0) {
|
|
123
|
+
list.splice(0, gone);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The key of the list whose head goes first past a budget: the least
|
|
130
|
+
* lately freed, and of those freed in one frame the largest, so that as
|
|
131
|
+
* few go as may; -1 for none.
|
|
132
|
+
*/
|
|
133
|
+
firstOut(): number {
|
|
134
|
+
let best = -1;
|
|
135
|
+
let bestAt = 0;
|
|
136
|
+
let bestBytes = 0;
|
|
137
|
+
for (const [key, list] of this.lists) {
|
|
138
|
+
const head = list[0];
|
|
139
|
+
const bytes = this.bytesOf(head);
|
|
140
|
+
if (best < 0 || head.usedAt < bestAt || (head.usedAt === bestAt && bytes > bestBytes)) {
|
|
141
|
+
best = key;
|
|
142
|
+
bestAt = head.usedAt;
|
|
143
|
+
bestBytes = bytes;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return best;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The head of list `key` (firstOut), as `firstOut` found it. */
|
|
151
|
+
headOf(key: number): T {
|
|
152
|
+
return (this.lists.get(key) as T[])[0];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The head of list `key` (firstOut) let go of, `drop`ped. */
|
|
156
|
+
dropHead(key: number, drop: (item: T) => void): void {
|
|
157
|
+
const list = this.lists.get(key) as T[];
|
|
158
|
+
const item = list.shift() as T;
|
|
159
|
+
this.bytes -= this.bytesOf(item);
|
|
160
|
+
drop(item);
|
|
161
|
+
if (list.length === 0) {
|
|
162
|
+
this.lists.delete(key);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The bytes those freed in frame `frame` take, which that frame drew with. */
|
|
167
|
+
bytesFreedIn(frame: number): number {
|
|
168
|
+
let bytes = 0;
|
|
169
|
+
for (const list of this.lists.values()) {
|
|
170
|
+
for (let i = list.length - 1; i >= 0 && list[i].usedAt === frame; i--) {
|
|
171
|
+
bytes += this.bytesOf(list[i]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return bytes;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The bytes `item` takes, as this pool counts them. */
|
|
179
|
+
bytesFor(item: T): number {
|
|
180
|
+
return this.bytesOf(item);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Every one held let go of, as the context goes. */
|
|
184
|
+
clear(drop: (item: T) => void): void {
|
|
185
|
+
for (const list of this.lists.values()) {
|
|
186
|
+
for (const item of list) {
|
|
187
|
+
drop(item);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.lists.clear();
|
|
192
|
+
this.bytes = 0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** A pool and how what it lets go of is deleted. */
|
|
197
|
+
interface Trimmed {
|
|
198
|
+
// biome-ignore lint/suspicious/noExplicitAny: pools of any kind share one budget.
|
|
199
|
+
pool: SizedPool<any>;
|
|
200
|
+
// biome-ignore lint/suspicious/noExplicitAny: as the pool's.
|
|
201
|
+
drop: (item: any) => void;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* `pools` let go of till together they take `budget` bytes at most: the
|
|
206
|
+
* least lately freed of all first, of those freed in one frame the
|
|
207
|
+
* largest. One budget for all, as trimming one pool against what another
|
|
208
|
+
* holds dropped a frame's own small layers while large multisampled
|
|
209
|
+
* buffers idle for many frames stayed, each frame making them again.
|
|
210
|
+
*/
|
|
211
|
+
export function trimTogether(pools: readonly Trimmed[], budget: number): void {
|
|
212
|
+
let bytes = 0;
|
|
213
|
+
for (const { pool } of pools) {
|
|
214
|
+
bytes += pool.bytes;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
while (bytes > budget) {
|
|
218
|
+
let from: Trimmed | null = null;
|
|
219
|
+
let key = -1;
|
|
220
|
+
let at = 0;
|
|
221
|
+
let size = 0;
|
|
222
|
+
for (const trimmed of pools) {
|
|
223
|
+
const k = trimmed.pool.firstOut();
|
|
224
|
+
if (k < 0) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const head = trimmed.pool.headOf(k);
|
|
229
|
+
const headBytes = trimmed.pool.bytesFor(head);
|
|
230
|
+
if (!from || head.usedAt < at || (head.usedAt === at && headBytes > size)) {
|
|
231
|
+
from = trimmed;
|
|
232
|
+
key = k;
|
|
233
|
+
at = head.usedAt;
|
|
234
|
+
size = headBytes;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!from) {
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
bytes -= size;
|
|
243
|
+
from.pool.dropHead(key, from.drop);
|
|
244
|
+
}
|
|
245
|
+
}
|