@swf2es/render 0.0.0-stage → 0.1.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/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
package/src/scene.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blend modes as a scene list names them, in Flash's order: the separable
|
|
3
|
+
* ones blend straight colours, the rest have formulas of their own.
|
|
4
|
+
*/
|
|
5
|
+
export const NORMAL = 0;
|
|
6
|
+
export const LAYER = 1;
|
|
7
|
+
export const MULTIPLY = 2;
|
|
8
|
+
export const SCREEN = 3;
|
|
9
|
+
export const LIGHTEN = 4;
|
|
10
|
+
export const DARKEN = 5;
|
|
11
|
+
export const DIFFERENCE = 6;
|
|
12
|
+
export const ADD = 7;
|
|
13
|
+
export const SUBTRACT = 8;
|
|
14
|
+
export const INVERT = 9;
|
|
15
|
+
export const ALPHA = 10;
|
|
16
|
+
export const ERASE = 11;
|
|
17
|
+
export const OVERLAY = 12;
|
|
18
|
+
export const HARDLIGHT = 13;
|
|
19
|
+
/**
|
|
20
|
+
* A Pixel Bender blend shader's, from KERNEL on: KERNEL + k blends by the
|
|
21
|
+
* kernel of chain k of the frame's filters (filters.ts, BLEND). A layer
|
|
22
|
+
* alone takes it, never a draw or a kept layer, whose modes are bytes.
|
|
23
|
+
*/
|
|
24
|
+
export const KERNEL = 14;
|
|
25
|
+
/**
|
|
26
|
+
* Not Flash's: an alpha mask's layer composited onto the unfiltered layer
|
|
27
|
+
* that ends after it, the layer of what it masks, keeping only what the
|
|
28
|
+
* mask covers, scaled by the mask's alpha. measureLayers cuts that layer to
|
|
29
|
+
* the mask's rectangle, past which nothing of it shows. A LAYER_END alone
|
|
30
|
+
* takes it; negative, as no blend mode's number is.
|
|
31
|
+
*/
|
|
32
|
+
export const ALPHA_MASK = -2;
|
|
33
|
+
|
|
34
|
+
/** Flash's blend mode names, by their number here. */
|
|
35
|
+
export const BLEND_NAMES = [
|
|
36
|
+
"normal",
|
|
37
|
+
"layer",
|
|
38
|
+
"multiply",
|
|
39
|
+
"screen",
|
|
40
|
+
"lighten",
|
|
41
|
+
"darken",
|
|
42
|
+
"difference",
|
|
43
|
+
"add",
|
|
44
|
+
"subtract",
|
|
45
|
+
"invert",
|
|
46
|
+
"alpha",
|
|
47
|
+
"erase",
|
|
48
|
+
"overlay",
|
|
49
|
+
"hardlight",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* What an entry is: a draw, or a mask's marker. A mask's own draws lie
|
|
54
|
+
* between its MASK_BEGIN and its MASK_USE, which names them (its geometry
|
|
55
|
+
* the first, its row how many) for the planner to push them into the
|
|
56
|
+
* stencil; what it masks follows, up to the MASK_POP that names them again
|
|
57
|
+
* to take them out.
|
|
58
|
+
*/
|
|
59
|
+
export const DRAW = 0;
|
|
60
|
+
export const MASK_BEGIN = 1;
|
|
61
|
+
export const MASK_USE = 2;
|
|
62
|
+
export const MASK_POP = 3;
|
|
63
|
+
/**
|
|
64
|
+
* A layer: what lies between LAYER_BEGIN and LAYER_END is drawn into a
|
|
65
|
+
* target of its own over a rectangle of the target, then composited onto
|
|
66
|
+
* what is below in the END's blend mode (its geometry), its colour
|
|
67
|
+
* transform the END's row, or none (-1), after the END's chain of filters
|
|
68
|
+
* (its texture, or none, -1) runs over it. The BEGIN's geometry is the
|
|
69
|
+
* layer's number, whose rectangle measureLayers finds.
|
|
70
|
+
*/
|
|
71
|
+
export const LAYER_BEGIN = 4;
|
|
72
|
+
export const LAYER_END = 5;
|
|
73
|
+
/**
|
|
74
|
+
* A layer kept from a frame before (Keeps) composited again, over the
|
|
75
|
+
* rectangle of its layer number (its geometry), in its blend mode, its
|
|
76
|
+
* colour transform its row; its texture the kept one's id.
|
|
77
|
+
*/
|
|
78
|
+
export const KEPT = 6;
|
|
79
|
+
|
|
80
|
+
/** How an entry samples its texture: smoothed, and clamped, repeated or mirrored past its edges. */
|
|
81
|
+
export const SMOOTH = 1;
|
|
82
|
+
export const CLAMP = 0;
|
|
83
|
+
export const REPEAT = 2;
|
|
84
|
+
export const MIRROR = 4;
|
|
85
|
+
/**
|
|
86
|
+
* Drawn as drawTriangles' textured triangles are: its texels each
|
|
87
|
+
* vertex's u and v from 0 to 1, interpolated in perspective by the depth
|
|
88
|
+
* (t) it carries (GeometryData.depths), and the texture read as Flash
|
|
89
|
+
* reads it, once a pixel of Flash's (webgl/programs.ts, meshFragment).
|
|
90
|
+
*/
|
|
91
|
+
export const MESH = 8;
|
|
92
|
+
/**
|
|
93
|
+
* A 3D object's quad: drawn by the mesh program, its texture the kept
|
|
94
|
+
* output (Keeps) its bitmap was drawn into (WebGLBackend.drawPlane), its
|
|
95
|
+
* texels in that bitmap's texels, read smoothed at the centre of each pixel
|
|
96
|
+
* of Flash's, as Flash reads it.
|
|
97
|
+
*/
|
|
98
|
+
export const PLANE = 16;
|
|
99
|
+
/**
|
|
100
|
+
* Its vertices placed on quarters of a pixel of Flash's, ties to even, as
|
|
101
|
+
* Flash places a glyph's outline (the `legacy-static-text` case, drawn in
|
|
102
|
+
* adl): a pixel of the screen, a BitmapData's in a draw, a texel of a 3D
|
|
103
|
+
* object's bitmap.
|
|
104
|
+
*/
|
|
105
|
+
export const SNAP = 32;
|
|
106
|
+
/**
|
|
107
|
+
* A shader fill's part: its texels the kernel's coordinates, carried as a
|
|
108
|
+
* mesh's are with a depth of 1, and its texture the chain of the frame's
|
|
109
|
+
* filters that holds the kernel (filters.ts, FILL_SHADER); drawn by a
|
|
110
|
+
* program of the kernel's, once a pixel of Flash's, at its centre, as adl
|
|
111
|
+
* draws one.
|
|
112
|
+
*/
|
|
113
|
+
export const SHADED = 64;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Which pixels a filtered layer's object covers (SceneList.wholes): those
|
|
117
|
+
* of the target its drawing touches; or whole units, as Flash's bitmap of
|
|
118
|
+
* an object is, antialiased those its edges touch and one more right and
|
|
119
|
+
* down where an edge lies on one, aliased those whose centres it covers.
|
|
120
|
+
*/
|
|
121
|
+
export const NO_WHOLE = 0;
|
|
122
|
+
export const WHOLE_SMOOTH = 1;
|
|
123
|
+
export const WHOLE_ALIASED = 2;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A frame's drawing in order: each entry a geometry drawn under a row with
|
|
127
|
+
* a blend mode, and the texture it samples (-1 for none) and how. Boundaries
|
|
128
|
+
* (filters, masks, blend layers) come with the features that need them.
|
|
129
|
+
*/
|
|
130
|
+
export class SceneList {
|
|
131
|
+
geometry = new Int32Array(1024);
|
|
132
|
+
row = new Int32Array(1024);
|
|
133
|
+
blend = new Uint8Array(1024);
|
|
134
|
+
texture = new Int32Array(1024);
|
|
135
|
+
sampler = new Uint8Array(1024);
|
|
136
|
+
kind = new Uint8Array(1024);
|
|
137
|
+
length = 0;
|
|
138
|
+
/** Each layer's rectangle of the target, in its pixels from the top left: x, y, width, height. */
|
|
139
|
+
layers = new Int32Array(64);
|
|
140
|
+
/**
|
|
141
|
+
* Each layer's padding, as its filters reach past what is drawn: in the
|
|
142
|
+
* target's units (its pixels over the resolution), or -1 for an
|
|
143
|
+
* unfiltered layer, which covers what is drawn alone.
|
|
144
|
+
*/
|
|
145
|
+
pads = new Int32Array(16);
|
|
146
|
+
/**
|
|
147
|
+
* Whether each filtered layer covers whole units of the target, as Flash
|
|
148
|
+
* places the bitmap a ShaderFilter's kernel runs over, its coordinates
|
|
149
|
+
* from its corner: NO_WHOLE, WHOLE_SMOOTH or WHOLE_ALIASED.
|
|
150
|
+
*/
|
|
151
|
+
wholes = new Uint8Array(16);
|
|
152
|
+
/**
|
|
153
|
+
* Each whole layer's object, Flash's bitmap of it, uncut by the target:
|
|
154
|
+
* x, y, width and height in target pixels, as measureLayers finds it.
|
|
155
|
+
*/
|
|
156
|
+
objects = new Float64Array(64);
|
|
157
|
+
/** Each layer's kept output, which its output becomes (Keeps), or -1. */
|
|
158
|
+
keeps = new Int32Array(16);
|
|
159
|
+
/** The layer each layer is in, or -1. */
|
|
160
|
+
parents = new Int32Array(16);
|
|
161
|
+
/** The layers begun and not yet ended, innermost last. */
|
|
162
|
+
private readonly open: number[] = [];
|
|
163
|
+
/**
|
|
164
|
+
* Each filtered layer's area as measured, before it was rounded and
|
|
165
|
+
* padded: least x and y, greatest x and y, in target pixels.
|
|
166
|
+
*/
|
|
167
|
+
areas = new Float64Array(64);
|
|
168
|
+
layerCount = 0;
|
|
169
|
+
|
|
170
|
+
reset(): void {
|
|
171
|
+
this.length = 0;
|
|
172
|
+
this.layerCount = 0;
|
|
173
|
+
this.open.length = 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
draw(geometry: number, row: number, blend: number, texture = -1, sampler = 0): void {
|
|
177
|
+
this.push(DRAW, geometry, row, blend, texture, sampler);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A layer begun, padded for its filters (or -1), over whole units as
|
|
182
|
+
* `whole` says (wholes), its rectangle none till measured (layerRect):
|
|
183
|
+
* its number.
|
|
184
|
+
*/
|
|
185
|
+
layerBegin(padding = -1, keep = -1, whole = NO_WHOLE): number {
|
|
186
|
+
const layer = this.newLayer(padding, keep);
|
|
187
|
+
this.wholes[layer] = whole;
|
|
188
|
+
this.push(LAYER_BEGIN, layer, 0, 0, -1, 0);
|
|
189
|
+
this.open.push(layer);
|
|
190
|
+
return layer;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Kept output `keep` composited again over the rectangle x y width height
|
|
195
|
+
* of the target, in blend mode `mode`, coloured by `row`.
|
|
196
|
+
*/
|
|
197
|
+
keptLayer(
|
|
198
|
+
keep: number,
|
|
199
|
+
mode: number,
|
|
200
|
+
row: number,
|
|
201
|
+
x: number,
|
|
202
|
+
y: number,
|
|
203
|
+
width: number,
|
|
204
|
+
height: number,
|
|
205
|
+
): void {
|
|
206
|
+
const layer = this.newLayer(-1, keep);
|
|
207
|
+
this.layerRect(layer, x, y, width, height);
|
|
208
|
+
this.push(KEPT, layer, row, mode, keep, 0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private newLayer(padding: number, keep: number): number {
|
|
212
|
+
const layer = this.layerCount++;
|
|
213
|
+
if (layer >= this.pads.length) {
|
|
214
|
+
this.layers = grow(this.layers);
|
|
215
|
+
this.pads = grow(this.pads);
|
|
216
|
+
this.wholes = grow(this.wholes);
|
|
217
|
+
const objects = new Float64Array(this.objects.length * 2);
|
|
218
|
+
objects.set(this.objects);
|
|
219
|
+
this.objects = objects;
|
|
220
|
+
this.keeps = grow(this.keeps);
|
|
221
|
+
this.parents = grow(this.parents);
|
|
222
|
+
const areas = new Float64Array(this.areas.length * 2);
|
|
223
|
+
areas.set(this.areas);
|
|
224
|
+
this.areas = areas;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.pads[layer] = padding;
|
|
228
|
+
this.wholes[layer] = NO_WHOLE;
|
|
229
|
+
this.keeps[layer] = keep;
|
|
230
|
+
this.parents[layer] = this.open.at(-1) ?? -1;
|
|
231
|
+
this.layerRect(layer, 0, 0, 0, 0);
|
|
232
|
+
return layer;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Filtered layer `layer` padded by `padding` after all, as its filters were fitted once it was drawn. */
|
|
236
|
+
padLayer(layer: number, padding: number): void {
|
|
237
|
+
this.pads[layer] = padding;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** The layer begun last ended: filtered by `chain` (or -1), composited in `mode`, coloured by `row`. */
|
|
241
|
+
layerEnd(mode: number, row: number, chain = -1): void {
|
|
242
|
+
this.open.pop();
|
|
243
|
+
this.push(LAYER_END, mode, row, 0, chain, 0);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Layer `layer` covers the rectangle x y width height of the target, in
|
|
248
|
+
* its pixels from the top left; one of no size shows nothing.
|
|
249
|
+
*/
|
|
250
|
+
layerRect(layer: number, x: number, y: number, width: number, height: number): void {
|
|
251
|
+
const r = this.layers;
|
|
252
|
+
r[layer * 4] = x;
|
|
253
|
+
r[layer * 4 + 1] = y;
|
|
254
|
+
r[layer * 4 + 2] = width;
|
|
255
|
+
r[layer * 4 + 3] = height;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* A mask's marker, with two numbers in its geometry and row: MASK_USE
|
|
260
|
+
* and MASK_POP name the mask's draws, from the first, so many of them.
|
|
261
|
+
*/
|
|
262
|
+
marker(kind: number, a = 0, b = 0): void {
|
|
263
|
+
this.push(kind, a, b, 0, -1, 0);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private push(
|
|
267
|
+
kind: number,
|
|
268
|
+
geometry: number,
|
|
269
|
+
row: number,
|
|
270
|
+
blend: number,
|
|
271
|
+
texture: number,
|
|
272
|
+
sampler: number,
|
|
273
|
+
): void {
|
|
274
|
+
if (this.length === this.geometry.length) {
|
|
275
|
+
this.geometry = grow(this.geometry);
|
|
276
|
+
this.row = grow(this.row);
|
|
277
|
+
this.blend = grow(this.blend);
|
|
278
|
+
this.texture = grow(this.texture);
|
|
279
|
+
this.sampler = grow(this.sampler);
|
|
280
|
+
this.kind = grow(this.kind);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const at = this.length++;
|
|
284
|
+
this.kind[at] = kind;
|
|
285
|
+
this.geometry[at] = geometry;
|
|
286
|
+
this.row[at] = row;
|
|
287
|
+
this.blend[at] = blend;
|
|
288
|
+
this.texture[at] = texture;
|
|
289
|
+
this.sampler[at] = sampler;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function grow<T extends Int32Array | Uint8Array>(array: T): T {
|
|
294
|
+
const grown = new (array.constructor as new (length: number) => T)(array.length * 2);
|
|
295
|
+
grown.set(array);
|
|
296
|
+
return grown;
|
|
297
|
+
}
|
package/src/shelves.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Rectangles placed side by side on pages, in shelves, in the order they
|
|
2
|
+
// come: a frame's held layers and their filters' targets (plan, HOLD_PUSH),
|
|
3
|
+
// each of a page a pass draws into with the others of its kind.
|
|
4
|
+
|
|
5
|
+
/** Rectangles on pages `width` wide and at most `most` high, each page as high as they need. */
|
|
6
|
+
export class Shelves {
|
|
7
|
+
/** Each rectangle's page, and its corner there. */
|
|
8
|
+
readonly page: number[] = [];
|
|
9
|
+
readonly x: number[] = [];
|
|
10
|
+
readonly y: number[] = [];
|
|
11
|
+
count = 0;
|
|
12
|
+
/** Each page's height, as far down as its rectangles reach. */
|
|
13
|
+
readonly heights: number[] = [];
|
|
14
|
+
pages = 0;
|
|
15
|
+
private width = 0;
|
|
16
|
+
private most = 0;
|
|
17
|
+
/** Where the shelf open starts, how high it is, and how far along it is filled. */
|
|
18
|
+
private shelf = 0;
|
|
19
|
+
private shelfHeight = 0;
|
|
20
|
+
private along = 0;
|
|
21
|
+
|
|
22
|
+
/** Empty, for pages `width` wide and at most `most` high. */
|
|
23
|
+
reset(width: number, most: number): void {
|
|
24
|
+
this.width = width;
|
|
25
|
+
this.most = most;
|
|
26
|
+
this.count = 0;
|
|
27
|
+
this.pages = 0;
|
|
28
|
+
this.shelf = 0;
|
|
29
|
+
this.shelfHeight = 0;
|
|
30
|
+
this.along = 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A rectangle `w` by `h` placed: its number; -1 for one larger than a page. */
|
|
34
|
+
add(w: number, h: number): number {
|
|
35
|
+
if (w > this.width || h > this.most) {
|
|
36
|
+
return -1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (this.pages === 0) {
|
|
40
|
+
this.newPage();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (this.along + w > this.width) {
|
|
44
|
+
this.shelf += this.shelfHeight;
|
|
45
|
+
this.shelfHeight = 0;
|
|
46
|
+
this.along = 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (this.shelf + h > this.most) {
|
|
50
|
+
this.newPage();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const i = this.count++;
|
|
54
|
+
const page = this.pages - 1;
|
|
55
|
+
this.page[i] = page;
|
|
56
|
+
this.x[i] = this.along;
|
|
57
|
+
this.y[i] = this.shelf;
|
|
58
|
+
this.along += w;
|
|
59
|
+
this.shelfHeight = Math.max(this.shelfHeight, h);
|
|
60
|
+
this.heights[page] = Math.max(this.heights[page], this.shelf + this.shelfHeight);
|
|
61
|
+
return i;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private newPage(): void {
|
|
65
|
+
this.heights[this.pages++] = 0;
|
|
66
|
+
this.shelf = 0;
|
|
67
|
+
this.shelfHeight = 0;
|
|
68
|
+
this.along = 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/textures.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { IDLE_FRAMES } from "./geometry.js";
|
|
2
|
+
import { type Handle, Registry } from "./registry.js";
|
|
3
|
+
|
|
4
|
+
/** A texture as its owner holds it: its id, and the generation the id had. */
|
|
5
|
+
export type TextureHandle = Handle;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A texture's size, and its pixels while they wait to be uploaded:
|
|
9
|
+
* premultiplied RGBA bytes, or RGBA floats, which only a kernel reads, a
|
|
10
|
+
* texel at a time.
|
|
11
|
+
*/
|
|
12
|
+
export interface TextureData {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
pixels: Uint8Array | Float32Array | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How many frames a texture keeps the bytes it was last set from without
|
|
20
|
+
* being set again, and spare bytes wait for another texture: a texture set
|
|
21
|
+
* every frame, as a BitmapData drawn into every frame is, is converted into
|
|
22
|
+
* the same bytes each time; one that stops changing lets them go a couple
|
|
23
|
+
* of seconds later.
|
|
24
|
+
*/
|
|
25
|
+
export const KEEP_FRAMES = 60;
|
|
26
|
+
|
|
27
|
+
/** The spare bytes kept at most, the least lately spared let go of first. */
|
|
28
|
+
const SPARE_BYTES = 32 << 20;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Every texture a scene list may name, by id. An owner adds a texture's
|
|
32
|
+
* pixels, and sets them again as they change; the backend uploads what is
|
|
33
|
+
* pending in its next render and lets the pixels go, keeping no hold on
|
|
34
|
+
* them. What goes undrawn for its idle frames, IDLE_FRAMES unless it was
|
|
35
|
+
* given fewer, is let go of, as geometries are.
|
|
36
|
+
*
|
|
37
|
+
* The bytes an owner fills to add or set a texture come from `bytes`, and
|
|
38
|
+
* stay this registry's: filled, they are passed to add or set at once and
|
|
39
|
+
* left be after, as they are lent again, to the same texture to be set again
|
|
40
|
+
* or, once it let them go, to another. A texture set again keeps the bytes it
|
|
41
|
+
* was last set from for KEEP_FRAMES; then, or once uploaded for one only
|
|
42
|
+
* added, or when it is removed, idle or with a lost context, they are spare
|
|
43
|
+
* for as long, for the next texture of their length.
|
|
44
|
+
*/
|
|
45
|
+
export class Textures extends Registry {
|
|
46
|
+
private readonly data: (TextureData | null)[] = [];
|
|
47
|
+
/** The bytes each texture was last added or set from, where `bytes` lent them, and the frame. */
|
|
48
|
+
private readonly kept: (Uint8Array | null)[] = [];
|
|
49
|
+
private readonly setAt: number[] = [];
|
|
50
|
+
/** Whether each texture was set again since it was added, which keeps its bytes for a while. */
|
|
51
|
+
private readonly changing: boolean[] = [];
|
|
52
|
+
/** Bytes no texture keeps, by length, the least lately spared first. */
|
|
53
|
+
private readonly spare = new Map<number, { bytes: Uint8Array; at: number }[]>();
|
|
54
|
+
private spareBytes = 0;
|
|
55
|
+
private readonly lent = new WeakSet<Uint8Array>();
|
|
56
|
+
/** Added or set again since the backend last took them. */
|
|
57
|
+
pending: number[] = [];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `length` bytes to fill and add or set a texture from: those texture `id`
|
|
61
|
+
* keeps where they are as long, else spare ones, else new.
|
|
62
|
+
*/
|
|
63
|
+
bytes(length: number, id = -1): Uint8Array {
|
|
64
|
+
const own = id >= 0 && this.holds(id) ? this.kept[id] : null;
|
|
65
|
+
if (own?.length === length) {
|
|
66
|
+
return own;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const spare = this.spare.get(length)?.pop();
|
|
70
|
+
if (spare) {
|
|
71
|
+
this.spareBytes -= length;
|
|
72
|
+
return spare.bytes;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const bytes = new Uint8Array(length);
|
|
76
|
+
this.lent.add(bytes);
|
|
77
|
+
return bytes;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A texture of `pixels`, let go of once undrawn for `idle` frames. */
|
|
81
|
+
add(
|
|
82
|
+
width: number,
|
|
83
|
+
height: number,
|
|
84
|
+
pixels: Uint8Array | Float32Array,
|
|
85
|
+
idle = IDLE_FRAMES,
|
|
86
|
+
): TextureHandle {
|
|
87
|
+
const handle = this.hold(idle);
|
|
88
|
+
this.data[handle.id] = { width, height, pixels };
|
|
89
|
+
this.keep(handle.id, pixels);
|
|
90
|
+
this.changing[handle.id] = false;
|
|
91
|
+
this.pending.push(handle.id);
|
|
92
|
+
return handle;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* `id`'s pixels changed, its size with them perhaps; with `idle`, it is
|
|
97
|
+
* let go of after as many undrawn frames where that is fewer.
|
|
98
|
+
*/
|
|
99
|
+
set(id: number, width: number, height: number, pixels: Uint8Array, idle?: number): void {
|
|
100
|
+
const data = this.data[id];
|
|
101
|
+
if (data) {
|
|
102
|
+
data.width = width;
|
|
103
|
+
data.height = height;
|
|
104
|
+
data.pixels = pixels;
|
|
105
|
+
this.keep(id, pixels);
|
|
106
|
+
this.changing[id] = true;
|
|
107
|
+
this.pending.push(id);
|
|
108
|
+
if (idle !== undefined) {
|
|
109
|
+
this.idleWithin(id, idle);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get(id: number): TextureData | null {
|
|
115
|
+
return this.data[id] ?? null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The bytes held on the CPU: those textures keep, and spare ones. */
|
|
119
|
+
get heldBytes(): number {
|
|
120
|
+
let held = this.spareBytes;
|
|
121
|
+
for (const bytes of this.kept) {
|
|
122
|
+
held += bytes?.length ?? 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return held;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A frame ends: what went unused too long is let go of, and bytes kept no longer needed spared. */
|
|
129
|
+
override endFrame(): void {
|
|
130
|
+
super.endFrame();
|
|
131
|
+
for (let id = 0; id < this.kept.length; id++) {
|
|
132
|
+
const bytes = this.kept[id];
|
|
133
|
+
const done = !this.changing[id] || this.frame - this.setAt[id] > KEEP_FRAMES;
|
|
134
|
+
// Never while they wait to be uploaded, as they may where the backend drew no frame.
|
|
135
|
+
if (bytes && done && this.data[id]?.pixels !== bytes) {
|
|
136
|
+
this.kept[id] = null;
|
|
137
|
+
this.spareOf(bytes);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const [length, list] of this.spare) {
|
|
142
|
+
while (list.length && this.frame - list[0].at > KEEP_FRAMES) {
|
|
143
|
+
list.shift();
|
|
144
|
+
this.spareBytes -= length;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
protected override dropped(id: number): void {
|
|
150
|
+
this.data[id] = null;
|
|
151
|
+
const bytes = this.kept[id];
|
|
152
|
+
this.kept[id] = null;
|
|
153
|
+
if (bytes) {
|
|
154
|
+
this.spareOf(bytes);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** `pixels` kept for `id` where `bytes` lent them; those it kept before spared. */
|
|
159
|
+
private keep(id: number, pixels: Uint8Array | Float32Array): void {
|
|
160
|
+
const before = this.kept[id];
|
|
161
|
+
const own = pixels instanceof Uint8Array && this.lent.has(pixels) ? pixels : null;
|
|
162
|
+
if (before && before !== own) {
|
|
163
|
+
this.spareOf(before);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.kept[id] = own;
|
|
167
|
+
this.setAt[id] = this.frame;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private spareOf(bytes: Uint8Array): void {
|
|
171
|
+
if (bytes.length > SPARE_BYTES) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let list = this.spare.get(bytes.length);
|
|
176
|
+
if (!list) {
|
|
177
|
+
list = [];
|
|
178
|
+
this.spare.set(bytes.length, list);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
list.push({ bytes, at: this.frame });
|
|
182
|
+
this.spareBytes += bytes.length;
|
|
183
|
+
// Past the cap, the least lately spared go, whatever their length.
|
|
184
|
+
while (this.spareBytes > SPARE_BYTES) {
|
|
185
|
+
let oldest = -1;
|
|
186
|
+
let oldestAt = Number.POSITIVE_INFINITY;
|
|
187
|
+
for (const [length, entries] of this.spare) {
|
|
188
|
+
if (entries.length && entries[0].at < oldestAt) {
|
|
189
|
+
oldestAt = entries[0].at;
|
|
190
|
+
oldest = length;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.spare.get(oldest)?.shift();
|
|
195
|
+
this.spareBytes -= oldest;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|