@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.
Files changed (143) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +17 -2
  3. package/dist/commands.d.ts +67 -0
  4. package/dist/commands.d.ts.map +1 -0
  5. package/dist/commands.js +93 -0
  6. package/dist/commands.js.map +1 -0
  7. package/dist/filter-plan.d.ts +110 -0
  8. package/dist/filter-plan.d.ts.map +1 -0
  9. package/dist/filter-plan.js +480 -0
  10. package/dist/filter-plan.js.map +1 -0
  11. package/dist/filter-shaders.d.ts +63 -0
  12. package/dist/filter-shaders.d.ts.map +1 -0
  13. package/dist/filter-shaders.js +350 -0
  14. package/dist/filter-shaders.js.map +1 -0
  15. package/dist/filters.d.ts +107 -0
  16. package/dist/filters.d.ts.map +1 -0
  17. package/dist/filters.js +187 -0
  18. package/dist/filters.js.map +1 -0
  19. package/dist/geometry.d.ts +83 -0
  20. package/dist/geometry.d.ts.map +1 -0
  21. package/dist/geometry.js +93 -0
  22. package/dist/geometry.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +13 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/keeps.d.ts +17 -0
  28. package/dist/keeps.d.ts.map +1 -0
  29. package/dist/keeps.js +17 -0
  30. package/dist/keeps.js.map +1 -0
  31. package/dist/kernel-shaders.d.ts +74 -0
  32. package/dist/kernel-shaders.d.ts.map +1 -0
  33. package/dist/kernel-shaders.js +323 -0
  34. package/dist/kernel-shaders.js.map +1 -0
  35. package/dist/measure.d.ts +56 -0
  36. package/dist/measure.d.ts.map +1 -0
  37. package/dist/measure.js +287 -0
  38. package/dist/measure.js.map +1 -0
  39. package/dist/plan.d.ts +14 -0
  40. package/dist/plan.d.ts.map +1 -0
  41. package/dist/plan.js +247 -0
  42. package/dist/plan.js.map +1 -0
  43. package/dist/registry.d.ts +49 -0
  44. package/dist/registry.d.ts.map +1 -0
  45. package/dist/registry.js +87 -0
  46. package/dist/registry.js.map +1 -0
  47. package/dist/rows.d.ts +39 -0
  48. package/dist/rows.d.ts.map +1 -0
  49. package/dist/rows.js +60 -0
  50. package/dist/rows.js.map +1 -0
  51. package/dist/scene.d.ts +179 -0
  52. package/dist/scene.d.ts.map +1 -0
  53. package/dist/scene.js +265 -0
  54. package/dist/scene.js.map +1 -0
  55. package/dist/shelves.d.ts +23 -0
  56. package/dist/shelves.d.ts.map +1 -0
  57. package/dist/shelves.js +63 -0
  58. package/dist/shelves.js.map +1 -0
  59. package/dist/textures.d.ts +72 -0
  60. package/dist/textures.d.ts.map +1 -0
  61. package/dist/textures.js +160 -0
  62. package/dist/textures.js.map +1 -0
  63. package/dist/webgl/atlas.d.ts +63 -0
  64. package/dist/webgl/atlas.d.ts.map +1 -0
  65. package/dist/webgl/atlas.js +328 -0
  66. package/dist/webgl/atlas.js.map +1 -0
  67. package/dist/webgl/backend.d.ts +141 -0
  68. package/dist/webgl/backend.d.ts.map +1 -0
  69. package/dist/webgl/backend.js +659 -0
  70. package/dist/webgl/backend.js.map +1 -0
  71. package/dist/webgl/composite.d.ts +101 -0
  72. package/dist/webgl/composite.d.ts.map +1 -0
  73. package/dist/webgl/composite.js +293 -0
  74. package/dist/webgl/composite.js.map +1 -0
  75. package/dist/webgl/draws.d.ts +213 -0
  76. package/dist/webgl/draws.d.ts.map +1 -0
  77. package/dist/webgl/draws.js +714 -0
  78. package/dist/webgl/draws.js.map +1 -0
  79. package/dist/webgl/filters.d.ts +107 -0
  80. package/dist/webgl/filters.d.ts.map +1 -0
  81. package/dist/webgl/filters.js +351 -0
  82. package/dist/webgl/filters.js.map +1 -0
  83. package/dist/webgl/gpu-textures.d.ts +44 -0
  84. package/dist/webgl/gpu-textures.d.ts.map +1 -0
  85. package/dist/webgl/gpu-textures.js +178 -0
  86. package/dist/webgl/gpu-textures.js.map +1 -0
  87. package/dist/webgl/holds.d.ts +97 -0
  88. package/dist/webgl/holds.d.ts.map +1 -0
  89. package/dist/webgl/holds.js +453 -0
  90. package/dist/webgl/holds.js.map +1 -0
  91. package/dist/webgl/index.d.ts +2 -0
  92. package/dist/webgl/index.d.ts.map +1 -0
  93. package/dist/webgl/index.js +4 -0
  94. package/dist/webgl/index.js.map +1 -0
  95. package/dist/webgl/kernels.d.ts +64 -0
  96. package/dist/webgl/kernels.d.ts.map +1 -0
  97. package/dist/webgl/kernels.js +160 -0
  98. package/dist/webgl/kernels.js.map +1 -0
  99. package/dist/webgl/pool.d.ts +71 -0
  100. package/dist/webgl/pool.d.ts.map +1 -0
  101. package/dist/webgl/pool.js +192 -0
  102. package/dist/webgl/pool.js.map +1 -0
  103. package/dist/webgl/programs.d.ts +130 -0
  104. package/dist/webgl/programs.d.ts.map +1 -0
  105. package/dist/webgl/programs.js +582 -0
  106. package/dist/webgl/programs.js.map +1 -0
  107. package/dist/webgl/stage.d.ts +67 -0
  108. package/dist/webgl/stage.d.ts.map +1 -0
  109. package/dist/webgl/stage.js +173 -0
  110. package/dist/webgl/stage.js.map +1 -0
  111. package/dist/webgl/targets.d.ts +172 -0
  112. package/dist/webgl/targets.d.ts.map +1 -0
  113. package/dist/webgl/targets.js +323 -0
  114. package/dist/webgl/targets.js.map +1 -0
  115. package/package.json +35 -3
  116. package/src/commands.ts +98 -0
  117. package/src/filter-plan.ts +755 -0
  118. package/src/filter-shaders.ts +360 -0
  119. package/src/filters.ts +220 -0
  120. package/src/geometry.ts +143 -0
  121. package/src/index.ts +87 -0
  122. package/src/keeps.ts +21 -0
  123. package/src/kernel-shaders.ts +375 -0
  124. package/src/measure.ts +400 -0
  125. package/src/plan.ts +331 -0
  126. package/src/registry.ts +105 -0
  127. package/src/rows.ts +93 -0
  128. package/src/scene.ts +297 -0
  129. package/src/shelves.ts +70 -0
  130. package/src/textures.ts +198 -0
  131. package/src/webgl/atlas.ts +370 -0
  132. package/src/webgl/backend.ts +973 -0
  133. package/src/webgl/composite.ts +372 -0
  134. package/src/webgl/draws.ts +902 -0
  135. package/src/webgl/filters.ts +502 -0
  136. package/src/webgl/gpu-textures.ts +253 -0
  137. package/src/webgl/holds.ts +624 -0
  138. package/src/webgl/index.ts +3 -0
  139. package/src/webgl/kernels.ts +205 -0
  140. package/src/webgl/pool.ts +245 -0
  141. package/src/webgl/programs.ts +620 -0
  142. package/src/webgl/stage.ts +214 -0
  143. package/src/webgl/targets.ts +464 -0
@@ -0,0 +1,973 @@
1
+ import { GlState, linkProgram } from "@swf2es/context3d/device";
2
+ import {
3
+ CLEAR,
4
+ COMPOSITE,
5
+ type Commands,
6
+ FILTER,
7
+ HELD,
8
+ HOLD_POP,
9
+ HOLD_PUSH,
10
+ LAYER_POP,
11
+ LAYER_PUSH,
12
+ RUN,
13
+ STENCIL_POP,
14
+ STENCIL_PUSH,
15
+ } from "../commands.js";
16
+ import type { Filters } from "../filters.js";
17
+ import type { Geometries } from "../geometry.js";
18
+ import type { Keeps } from "../keeps.js";
19
+ import type { Rows } from "../rows.js";
20
+ import {
21
+ ADD,
22
+ DARKEN,
23
+ DIFFERENCE,
24
+ HARDLIGHT,
25
+ INVERT,
26
+ LIGHTEN,
27
+ MULTIPLY,
28
+ NORMAL,
29
+ OVERLAY,
30
+ SCREEN,
31
+ type SceneList,
32
+ } from "../scene.js";
33
+ import type { Textures } from "../textures.js";
34
+ import { GeometryAtlas } from "./atlas.js";
35
+ import { Compositor, readsBelow } from "./composite.js";
36
+ import { Draws, type Masks } from "./draws.js";
37
+ import { FilterRunner } from "./filters.js";
38
+ import { GpuTextures } from "./gpu-textures.js";
39
+ import { HeldLayers } from "./holds.js";
40
+ import { KernelPrograms } from "./kernels.js";
41
+ import { LAYER_UNIT, TEXTURE_UNIT } from "./programs.js";
42
+ import { Stage } from "./stage.js";
43
+ import { type Layer, type Open, objectOf, type Target, TargetPool } from "./targets.js";
44
+
45
+ export type { Target } from "./targets.js";
46
+
47
+ /** The blend modes a flat fill may be drawn in straight onto what is below (blendDirect). */
48
+ function direct(mode: number): boolean {
49
+ switch (mode) {
50
+ case ADD:
51
+ case SCREEN:
52
+ case MULTIPLY:
53
+ case LIGHTEN:
54
+ case DARKEN:
55
+ case DIFFERENCE:
56
+ case OVERLAY:
57
+ case HARDLIGHT:
58
+ case INVERT:
59
+ return true;
60
+ default:
61
+ return false;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Executes a frame's commands on a WebGL 2 context it owns, through one
67
+ * cache of its state. Every geometry is copied once into an atlas of a
68
+ * vertex and an index buffer, and drawn from it a call an entry, the
69
+ * buffers bound once and the entry told its row; rows go up once a frame
70
+ * into a float texture. A geometry let go of leaves a hole, and the atlas
71
+ * is packed again from the geometries' own arrays once the holes outweigh
72
+ * what is live. Its parts, which it owns and makes again with the context:
73
+ * the geometry atlas (atlas.ts), the textures and rows (gpu-textures.ts),
74
+ * the entries' draws (draws.ts), the render targets (targets.ts), the held
75
+ * layers (holds.ts), the composites (composite.ts) and the stage's way onto
76
+ * the canvas (stage.ts).
77
+ */
78
+ /** compositeOver's program and its uniforms. */
79
+ interface OverProgram {
80
+ program: WebGLProgram;
81
+ at: WebGLUniformLocation | null;
82
+ opaque: WebGLUniformLocation | null;
83
+ }
84
+
85
+ /** A triangle over the viewport, of no attributes. */
86
+ const OVER_VERTEX = `#version 300 es
87
+ void main() {
88
+ vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
89
+ gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
90
+ }`;
91
+
92
+ /** Flash's source over on whole bytes (WebGLBackend.compositeOver), both textures read at the pixel's own texel. */
93
+ const OVER_FRAGMENT = `#version 300 es
94
+ precision highp float;
95
+ precision highp int;
96
+ uniform sampler2D uSource;
97
+ uniform sampler2D uBelow;
98
+ uniform ivec2 uAt;
99
+ uniform bool uOpaque;
100
+ out vec4 finalColor;
101
+ void main() {
102
+ ivec2 p = ivec2(gl_FragCoord.xy) - uAt;
103
+ ivec4 s = ivec4(round(texelFetch(uSource, p, 0) * 255.0));
104
+ ivec4 d = ivec4(round(texelFetch(uBelow, p, 0) * 255.0));
105
+ ivec4 o = s.a == 255 ? s : s.a == 0 ? d : min(ivec4(255), s + ((d * (256 - s.a)) >> 8));
106
+ if (uOpaque) {
107
+ o.a = 255;
108
+ }
109
+
110
+ finalColor = vec4(o) / 255.0;
111
+ }`;
112
+
113
+ export class WebGLBackend {
114
+ readonly gl: WebGL2RenderingContext;
115
+ readonly state: GlState;
116
+ private readonly pool: TargetPool;
117
+ private readonly stage: Stage;
118
+ private readonly gpu: GpuTextures;
119
+ private readonly atlas: GeometryAtlas;
120
+ private readonly compositor: Compositor;
121
+ private readonly draws: Draws;
122
+ private readonly holds: HeldLayers;
123
+ /** Pixel Bender kernels' programs, which the filters, the compositor and the draws share. */
124
+ private readonly kernels: KernelPrograms;
125
+ /** The filters' passes, their programs made as first run. */
126
+ private filters!: FilterRunner<Layer>;
127
+ /** The layers drawn into now, innermost last. */
128
+ private layers: Open[] = [];
129
+ /** The frame's filters being drawn, whose chains blend shaders' modes name (KERNEL). */
130
+ private frameFilters: Filters | null = null;
131
+ /**
132
+ * Samples a pixel the stage and its layers are drawn with, 1 for none:
133
+ * Flash's anti-aliasing by the quality, as the host sets it.
134
+ */
135
+ samples = 1;
136
+ /** The masks pushed on the target drawn into now (execute). */
137
+ private readonly masks: Masks = { depth: 0, past: 0 };
138
+ /** A layer's rectangle, as blendDirect reads it. */
139
+ private readonly rect = { x: 0, y: 0, width: 0, height: 0 };
140
+ /** The mask counts of the targets below the layers drawn into now, two numbers each. */
141
+ private readonly below: number[] = [];
142
+ /** The program `compositeOver` draws with, linked as first used; null till then and after a lost context. */
143
+ private overProgram: OverProgram | null = null;
144
+ /** Framebuffers onto registry textures, drawn into or read, by texture id, with the texture they were made for. */
145
+ private textureFramebuffers: ({ texture: WebGLTexture; framebuffer: WebGLFramebuffer } | null)[] =
146
+ [];
147
+
148
+ constructor(gl: WebGL2RenderingContext) {
149
+ this.gl = gl;
150
+ this.state = new GlState(gl);
151
+ this.kernels = new KernelPrograms(gl, this.state);
152
+ this.pool = new TargetPool(gl, this.state);
153
+ this.stage = new Stage(gl, this.state, this.pool);
154
+ this.gpu = new GpuTextures(gl, this.state);
155
+ this.atlas = new GeometryAtlas(gl, this.state);
156
+ this.compositor = new Compositor(
157
+ gl,
158
+ this.state,
159
+ {
160
+ filters: () => this.frameFilters,
161
+ texture: (id) => this.gpu.textures[id] ?? null,
162
+ sampler: (bits) => this.gpu.sampler(bits),
163
+ },
164
+ this.kernels,
165
+ );
166
+ this.draws = new Draws(
167
+ gl,
168
+ this.state,
169
+ this.atlas,
170
+ this.gpu,
171
+ this.pool,
172
+ {
173
+ filters: () => this.frameFilters,
174
+ texture: (id) => this.gpu.textures[id] ?? null,
175
+ sampler: (bits) => this.gpu.sampler(bits),
176
+ },
177
+ this.kernels,
178
+ );
179
+ this.holds = new HeldLayers(gl, this.state, this.pool, this.draws, this.compositor, this.gpu);
180
+ this.setUp();
181
+ }
182
+
183
+ /**
184
+ * The context came back after it was lost: everything made again, every
185
+ * geometry copied in again from the registry's arrays. The textures were
186
+ * lost with it, which their owners add again (Textures.lost).
187
+ */
188
+ restore(geometries: Geometries): void {
189
+ this.setUp();
190
+ geometries.added.length = 0;
191
+ geometries.removed.length = 0;
192
+ geometries.updated.length = 0;
193
+ this.atlas.repack(geometries);
194
+ this.atlas.repackGlyphs(geometries);
195
+ }
196
+
197
+ /** Programs, buffers and textures made, as the context comes or comes back. */
198
+ private setUp(): void {
199
+ const gl = this.gl;
200
+ this.state.forget();
201
+ // Kernels' programs made again as first used, the context's own gone with it.
202
+ this.kernels.clear();
203
+ const textureMost = gl.getParameter(gl.MAX_TEXTURE_SIZE) as number;
204
+ this.draws.setUp(textureMost);
205
+ this.compositor.setUp();
206
+ this.filters = new FilterRunner(
207
+ gl,
208
+ this.state,
209
+ {
210
+ take: (width, height) => this.pool.takeLayer(width, height, true),
211
+ give: (layer) => this.pool.giveLayer(layer),
212
+ texture: (id) => this.gpu.textures[id] ?? null,
213
+ sampler: (bits) => this.gpu.sampler(bits),
214
+ },
215
+ this.kernels,
216
+ );
217
+ this.holds.setUp(this.filters, textureMost);
218
+ this.pool.setUp();
219
+ this.layers = [];
220
+ this.stage.setUp();
221
+ this.atlas.setUp();
222
+ this.gpu.setUp();
223
+ // The context's own, gone with it.
224
+ this.textureFramebuffers = [];
225
+ this.overProgram = null;
226
+ }
227
+
228
+ /**
229
+ * Draw a frame: what the geometries added and removed first, then the
230
+ * rows, then the commands; onto the canvas, or into `root`, a layer the
231
+ * target's size, as BitmapData.draw's frames go.
232
+ */
233
+ execute(
234
+ commands: Commands,
235
+ scene: SceneList,
236
+ rows: Rows,
237
+ geometries: Geometries,
238
+ textures: Textures,
239
+ filters: Filters,
240
+ keeps: Keeps,
241
+ target: Target,
242
+ root: Layer | null = null,
243
+ ): void {
244
+ // A frame drawn into a layer, for BitmapData, is no frame of the canvas': idle layers are
245
+ // counted in the canvas' frames.
246
+ const counted = root === null;
247
+ const gl = this.gl;
248
+ if (gl.isContextLost()) {
249
+ return;
250
+ }
251
+
252
+ this.frameFilters = filters;
253
+ this.atlas.place(geometries);
254
+ this.gpu.uploadTextures(textures);
255
+ for (const id of keeps.removed) {
256
+ this.pool.release(id);
257
+ }
258
+
259
+ keeps.removed.length = 0;
260
+ this.gpu.uploadRows(rows);
261
+ this.draws.prepare(commands, scene);
262
+ const state = this.state;
263
+ state.scissor(null);
264
+ state.colorMask(15);
265
+ state.useProgram(this.draws.program);
266
+ state.bindVertexArray(this.atlas.vertexArray);
267
+ state.bindTexture(0, this.gpu.rowsTexture);
268
+ // As many samples as the GPU takes; a BitmapData's frame supersamples instead, as Flash's draw
269
+ // does. Its layers take the stage's, so that their edges are the stage's.
270
+ const stage = root ? null : this.stage.stageBuffers(target.width, target.height, this.samples);
271
+ // An erase or alpha blend outside any layer clears what it covers on the stage, as adl does,
272
+ // which the blends after it read: an opaque canvas keeps no alpha, so such a frame is drawn
273
+ // into a layer the canvas' size, then copied onto it. Multisampled buffers keep it already.
274
+ const cleared =
275
+ !root && !stage && !this.stage.canvasAlpha && commands.clearsTarget
276
+ ? this.stage.stageAlpha(target.width, target.height)
277
+ : null;
278
+ const into = root ?? cleared;
279
+ if (into) {
280
+ // Its own stencil, for the masks drawn into it.
281
+ this.pool.layerMsaa(into, 1);
282
+ }
283
+
284
+ this.pool.beginFrame(stage?.samples ?? 1);
285
+ this.holds.drawHolds(commands, scene, filters, target);
286
+ const canvas: Open = {
287
+ layer: into,
288
+ msaa: stage,
289
+ x: 0,
290
+ y: 0,
291
+ width: target.width,
292
+ height: target.height,
293
+ opaque: commands.opaqueStage,
294
+ };
295
+ this.layers.length = 0;
296
+ this.draws.drawInto(canvas, target);
297
+
298
+ this.draws.forgetRow();
299
+ // The masks pushed on the target drawn into. A layer has a stencil of its own, and the
300
+ // target below keeps its count meanwhile.
301
+ const masks = this.masks;
302
+ masks.depth = 0;
303
+ masks.past = 0;
304
+ const below = this.below;
305
+ below.length = 0;
306
+ state.stencil(null);
307
+ const ops = commands.ops;
308
+ for (let k = 0; k < commands.length; k++) {
309
+ const at = k * 4;
310
+ switch (ops[at]) {
311
+ case CLEAR: {
312
+ const p = commands.params;
313
+ const from = ops[at + 1];
314
+ gl.clearColor(p[from], p[from + 1], p[from + 2], p[from + 3]);
315
+ gl.clearStencil(0);
316
+ gl.stencilMask(0xff);
317
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
318
+ break;
319
+ }
320
+
321
+ case RUN:
322
+ this.draws.drawRun(scene, ops[at + 1], ops[at + 1] + ops[at + 2], k);
323
+ break;
324
+ case STENCIL_PUSH:
325
+ case STENCIL_POP:
326
+ this.draws.drawMask(
327
+ scene,
328
+ ops[at + 1],
329
+ ops[at + 1] + ops[at + 2],
330
+ ops[at] === STENCIL_PUSH,
331
+ masks,
332
+ k,
333
+ );
334
+ break;
335
+
336
+ case HOLD_PUSH:
337
+ // Drawn ahead of the stage (drawHolds).
338
+ while (ops[k * 4] !== HOLD_POP) {
339
+ k++;
340
+ }
341
+
342
+ break;
343
+ case HELD:
344
+ this.draws.maskBy(masks);
345
+ this.holds.compositeHeld(
346
+ ops[at + 1],
347
+ this.layers.at(-1) ?? canvas,
348
+ ops[at + 2],
349
+ ops[at + 3],
350
+ );
351
+ break;
352
+
353
+ case LAYER_PUSH: {
354
+ if (this.blendDirect(commands, k, scene, canvas)) {
355
+ // Its run and its pop with it.
356
+ k += 2;
357
+ break;
358
+ }
359
+
360
+ below.push(masks.depth, masks.past);
361
+ masks.depth = 0;
362
+ masks.past = 0;
363
+ const p = commands.params;
364
+ const from = ops[at + 1];
365
+ const width = p[from + 2];
366
+ const height = p[from + 3];
367
+ const filtered = ops[at + 2] === 1;
368
+ // A filtered one of the size its filters' targets take for it, which they read alike.
369
+ const layer = this.pool.takeLayer(width, height, filtered);
370
+ const open: Open = {
371
+ layer,
372
+ msaa: this.pool.layerMsaa(layer, this.pool.frameSamples),
373
+ x: p[from],
374
+ y: p[from + 1],
375
+ width,
376
+ height,
377
+ object: objectOf(p, from + 4),
378
+ };
379
+ this.layers.push(open);
380
+ this.draws.drawInto(open, target);
381
+ state.stencil(null);
382
+ state.colorMask(15);
383
+ gl.clearColor(0, 0, 0, 0);
384
+ gl.clearStencil(0);
385
+ gl.stencilMask(0xff);
386
+ // Its rectangle alone, as a layer taken may be larger, unless filters read past it, as far
387
+ // as they read.
388
+ state.scissor(filtered ? [0, 0, layer.width, layer.height] : [0, 0, width, height]);
389
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
390
+ state.scissor(null);
391
+ break;
392
+ }
393
+
394
+ case FILTER: {
395
+ const open = this.layers.at(-1);
396
+ if (open?.layer) {
397
+ state.stencil(null);
398
+ // Whole, as the filters read past its rectangle.
399
+ this.pool.resolve(open, open.layer.width, open.layer.height);
400
+ open.layer = this.filters.run(
401
+ filters,
402
+ ops[at + 1],
403
+ open.layer,
404
+ open.width,
405
+ open.height,
406
+ target.resolution,
407
+ open.object,
408
+ );
409
+ }
410
+
411
+ break;
412
+ }
413
+
414
+ case LAYER_POP: {
415
+ const open = this.layers.pop() as Open;
416
+ masks.past = below.pop() as number;
417
+ masks.depth = below.pop() as number;
418
+ this.pool.resolve(open, open.width, open.height);
419
+ const under = this.layers.at(-1) ?? canvas;
420
+ this.draws.drawInto(under, target);
421
+ // Composited where the masks on the target below let it be.
422
+ this.draws.maskBy(masks);
423
+ this.compositor.compositeLayer(open, under, ops[at + 1], ops[at + 2]);
424
+ const keep = ops[at + 3];
425
+ if (keep >= 0) {
426
+ // Kept for the frames after, the output kept before given back.
427
+ this.pool.keep(keep, open.layer as Layer);
428
+ } else {
429
+ this.pool.giveLayer(open.layer as Layer);
430
+ }
431
+
432
+ break;
433
+ }
434
+
435
+ case COMPOSITE: {
436
+ const p = commands.params;
437
+ const from = ops[at + 1];
438
+ const layer = this.pool.keptLayer(p[from + 4]);
439
+ if (layer) {
440
+ const kept: Open = {
441
+ layer,
442
+ msaa: null,
443
+ x: p[from],
444
+ y: p[from + 1],
445
+ width: p[from + 2],
446
+ height: p[from + 3],
447
+ };
448
+ this.draws.maskBy(masks);
449
+ this.compositor.compositeLayer(
450
+ kept,
451
+ this.layers.at(-1) ?? canvas,
452
+ ops[at + 2],
453
+ ops[at + 3],
454
+ );
455
+ }
456
+
457
+ break;
458
+ }
459
+ }
460
+ }
461
+
462
+ state.stencil(null);
463
+ if (canvas.msaa) {
464
+ this.stage.present(canvas.msaa);
465
+ }
466
+
467
+ if (cleared) {
468
+ // Its rows run down, the canvas' up.
469
+ this.stage.toCanvas(cleared.framebuffer, target.width, target.height, true);
470
+ }
471
+
472
+ this.holds.givePages();
473
+ if (counted) {
474
+ // The free layers, and the stage's (stageAlpha), undrawn into for LAYER_IDLE_FRAMES let go of.
475
+ this.stage.dropIdleLayer();
476
+ this.pool.endFrame();
477
+ }
478
+ }
479
+
480
+ /**
481
+ * A frame drawn into a layer `samples` times the size of a `width` by
482
+ * `height` bitmap a side, as `execute` draws, then halved till a sample a
483
+ * pixel, and read back: premultiplied RGBA bytes, a row at a time from
484
+ * the top, as BitmapData.draw takes them.
485
+ */
486
+ snapshot(
487
+ commands: Commands,
488
+ scene: SceneList,
489
+ rows: Rows,
490
+ geometries: Geometries,
491
+ textures: Textures,
492
+ filters: Filters,
493
+ keeps: Keeps,
494
+ target: Target,
495
+ width: number,
496
+ height: number,
497
+ samples: number,
498
+ ): Uint8Array {
499
+ const gl = this.gl;
500
+ const bytes = new Uint8Array(width * height * 4);
501
+ if (gl.isContextLost()) {
502
+ return bytes;
503
+ }
504
+
505
+ const layer = this.drawSampled(
506
+ commands,
507
+ scene,
508
+ rows,
509
+ geometries,
510
+ textures,
511
+ filters,
512
+ keeps,
513
+ target,
514
+ width,
515
+ height,
516
+ samples,
517
+ );
518
+ this.state.bindFramebuffer(layer.framebuffer);
519
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, bytes);
520
+ this.pool.giveLayer(layer);
521
+ return bytes;
522
+ }
523
+
524
+ /**
525
+ * BitmapData.draw on the GPU: the commands drawn as `snapshot` draws
526
+ * them, into a layer of their own (the object may show the texture
527
+ * itself), then over texture `id` of the registry at (x, y), as Flash's
528
+ * store composites source over (compositeOver), where it stays: nothing
529
+ * is read back; `opaque` for an opaque store's texture. Pending
530
+ * textures, the one drawn into among them, are uploaded first. Whether
531
+ * it was drawn; not on a lost context or without the texture.
532
+ */
533
+ drawIntoTexture(
534
+ commands: Commands,
535
+ scene: SceneList,
536
+ rows: Rows,
537
+ geometries: Geometries,
538
+ textures: Textures,
539
+ filters: Filters,
540
+ keeps: Keeps,
541
+ target: Target,
542
+ width: number,
543
+ height: number,
544
+ samples: number,
545
+ id: number,
546
+ x: number,
547
+ y: number,
548
+ opaque: boolean,
549
+ ): boolean {
550
+ const gl = this.gl;
551
+ if (gl.isContextLost()) {
552
+ return false;
553
+ }
554
+
555
+ const layer = this.drawSampled(
556
+ commands,
557
+ scene,
558
+ rows,
559
+ geometries,
560
+ textures,
561
+ filters,
562
+ keeps,
563
+ target,
564
+ width,
565
+ height,
566
+ samples,
567
+ );
568
+ const into = this.textureFramebuffer(id);
569
+ if (into) {
570
+ this.compositeOver(layer, into, x, y, width, height, opaque);
571
+ }
572
+
573
+ this.pool.giveLayer(layer);
574
+ return into !== null;
575
+ }
576
+
577
+ /**
578
+ * `layer`'s first `width` by `height` texels over those of `into` at
579
+ * (x, y), as Flash's store composites source over, a channel at a time
580
+ * on whole bytes: s + ((d * (256 - sa)) >> 8), at most 255, a source of
581
+ * alpha 255 put as it is and one of 0 leaving what was there; an opaque
582
+ * store's alpha 255 after. GL's own blending rounds where Flash floors,
583
+ * a level apart: the destination's rectangle is copied into a layer of
584
+ * its own first, and both read exactly.
585
+ */
586
+ private compositeOver(
587
+ layer: Layer,
588
+ into: WebGLFramebuffer,
589
+ x: number,
590
+ y: number,
591
+ width: number,
592
+ height: number,
593
+ opaque: boolean,
594
+ ): void {
595
+ const gl = this.gl;
596
+ const state = this.state;
597
+ state.scissor(null);
598
+ const below = this.pool.takeLayer(width, height);
599
+ this.blit(into, x, y, below.framebuffer, 0, 0, width, height);
600
+ const over = this.overProgramMade();
601
+ state.bindFramebuffer(into);
602
+ state.viewport(x, y, width, height);
603
+ state.colorMask(15);
604
+ state.stencil(null);
605
+ state.setBlend(null);
606
+ state.useProgram(over.program);
607
+ state.bindVertexArray(null);
608
+ const nearest = this.gpu.sampler(0);
609
+ state.bindTexture(TEXTURE_UNIT, layer.texture);
610
+ state.bindSampler(TEXTURE_UNIT, nearest);
611
+ state.bindTexture(LAYER_UNIT, below.texture);
612
+ state.bindSampler(LAYER_UNIT, nearest);
613
+ gl.uniform2i(over.at, x, y);
614
+ gl.uniform1i(over.opaque, opaque ? 1 : 0);
615
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
616
+ this.pool.giveLayer(below);
617
+ }
618
+
619
+ /** compositeOver's program, linked as first asked for. */
620
+ private overProgramMade(): OverProgram {
621
+ if (this.overProgram) {
622
+ return this.overProgram;
623
+ }
624
+
625
+ const gl = this.gl;
626
+ const program = linkProgram(gl, OVER_VERTEX, OVER_FRAGMENT);
627
+ this.state.useProgram(program);
628
+ gl.uniform1i(gl.getUniformLocation(program, "uSource"), TEXTURE_UNIT);
629
+ gl.uniform1i(gl.getUniformLocation(program, "uBelow"), LAYER_UNIT);
630
+ this.overProgram = {
631
+ program,
632
+ at: gl.getUniformLocation(program, "uAt"),
633
+ opaque: gl.getUniformLocation(program, "uOpaque"),
634
+ };
635
+ return this.overProgram;
636
+ }
637
+
638
+ /**
639
+ * Texture `id`'s `width` by `height` texels read back, premultiplied RGBA
640
+ * row by row; null on a lost context or without the texture.
641
+ */
642
+ readTexture(id: number, width: number, height: number): Uint8Array | null {
643
+ const framebuffer = this.gl.isContextLost() ? null : this.textureFramebuffer(id);
644
+ if (!framebuffer) {
645
+ return null;
646
+ }
647
+
648
+ const bytes = new Uint8Array(width * height * 4);
649
+ this.state.bindFramebuffer(framebuffer);
650
+ this.gl.readPixels(0, 0, width, height, this.gl.RGBA, this.gl.UNSIGNED_BYTE, bytes);
651
+ return bytes;
652
+ }
653
+
654
+ /**
655
+ * Texture `id`'s `width` by `height` texels at (x, y) cleared to `pixel`,
656
+ * premultiplied ARGB, which comes back exactly; false on a lost context
657
+ * or without the texture.
658
+ */
659
+ fillTexture(
660
+ textures: Textures,
661
+ id: number,
662
+ x: number,
663
+ y: number,
664
+ width: number,
665
+ height: number,
666
+ pixel: number,
667
+ ): boolean {
668
+ const gl = this.gl;
669
+ this.gpu.uploadTextures(textures);
670
+ const framebuffer = gl.isContextLost() ? null : this.textureFramebuffer(id);
671
+ if (!framebuffer) {
672
+ return false;
673
+ }
674
+
675
+ this.state.bindFramebuffer(framebuffer);
676
+ this.state.colorMask(15);
677
+ this.state.scissor([x, y, width, height]);
678
+ gl.clearColor(
679
+ ((pixel >>> 16) & 0xff) / 255,
680
+ ((pixel >>> 8) & 0xff) / 255,
681
+ (pixel & 0xff) / 255,
682
+ (pixel >>> 24) / 255,
683
+ );
684
+ gl.clear(gl.COLOR_BUFFER_BIT);
685
+ this.state.scissor(null);
686
+ return true;
687
+ }
688
+
689
+ /**
690
+ * Texture `from`'s `width` by `height` texels at (x, y) copied to (dx, dy)
691
+ * of texture `to`, replacing what was there: as a memmove where they are
692
+ * one texture, through a layer of its own, as a texture copied into
693
+ * itself is GL's feedback loop. Pending textures are uploaded first.
694
+ * False on a lost context or without either texture.
695
+ */
696
+ copyTexture(
697
+ textures: Textures,
698
+ from: number,
699
+ x: number,
700
+ y: number,
701
+ width: number,
702
+ height: number,
703
+ to: number,
704
+ dx: number,
705
+ dy: number,
706
+ ): boolean {
707
+ const gl = this.gl;
708
+ this.gpu.uploadTextures(textures);
709
+ const source = gl.isContextLost() ? null : this.textureFramebuffer(from);
710
+ const target = source ? this.textureFramebuffer(to) : null;
711
+ if (!source || !target) {
712
+ return false;
713
+ }
714
+
715
+ this.state.scissor(null);
716
+ if (from !== to) {
717
+ this.blit(source, x, y, target, dx, dy, width, height);
718
+ return true;
719
+ }
720
+
721
+ const between = this.pool.takeLayer(width, height);
722
+ this.state.bindFramebuffer(between.framebuffer);
723
+ gl.invalidateSubFramebuffer(gl.FRAMEBUFFER, [gl.COLOR_ATTACHMENT0], 0, 0, width, height);
724
+ this.blit(source, x, y, between.framebuffer, 0, 0, width, height);
725
+ this.blit(between.framebuffer, 0, 0, target, dx, dy, width, height);
726
+ this.pool.giveLayer(between);
727
+ return true;
728
+ }
729
+
730
+ /** `width` by `height` texels at (x, y) of `from` blitted to (dx, dy) of `to`, the state cache's binding `to` after. */
731
+ private blit(
732
+ from: WebGLFramebuffer,
733
+ x: number,
734
+ y: number,
735
+ to: WebGLFramebuffer,
736
+ dx: number,
737
+ dy: number,
738
+ width: number,
739
+ height: number,
740
+ ): void {
741
+ const gl = this.gl;
742
+ this.state.bindFramebuffer(to);
743
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, from);
744
+ gl.blitFramebuffer(
745
+ x,
746
+ y,
747
+ x + width,
748
+ y + height,
749
+ dx,
750
+ dy,
751
+ dx + width,
752
+ dy + height,
753
+ gl.COLOR_BUFFER_BIT,
754
+ gl.NEAREST,
755
+ );
756
+ // Bound as the state cache has it.
757
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, to);
758
+ }
759
+
760
+ /** A framebuffer onto texture `id`, made as first asked for; null for an id of no texture. */
761
+ private textureFramebuffer(id: number): WebGLFramebuffer | null {
762
+ const gl = this.gl;
763
+ const texture = this.gpu.textures[id] ?? null;
764
+ const known = this.textureFramebuffers[id] ?? null;
765
+ if (known?.texture === texture) {
766
+ return known.framebuffer;
767
+ }
768
+
769
+ // The id names another texture now: its framebuffer goes with the one it was made for.
770
+ if (known) {
771
+ gl.deleteFramebuffer(known.framebuffer);
772
+ this.textureFramebuffers[id] = null;
773
+ }
774
+
775
+ if (!texture) {
776
+ return null;
777
+ }
778
+
779
+ const framebuffer = gl.createFramebuffer() as WebGLFramebuffer;
780
+ this.state.bindFramebuffer(framebuffer);
781
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
782
+ this.textureFramebuffers[id] = { texture, framebuffer };
783
+ return framebuffer;
784
+ }
785
+
786
+ /**
787
+ * A 3D object's bitmap drawn (PLANE): the commands, as `execute` draws
788
+ * them, into a layer `samples` times the size of a `width` by `height`
789
+ * bitmap a side, halved till a sample a texel, and kept as kept output
790
+ * `keep` (Keeps), the one kept before given back. Called as the frame is
791
+ * walked, before its own commands are drawn, which composite it.
792
+ */
793
+ drawPlane(
794
+ keep: number,
795
+ commands: Commands,
796
+ scene: SceneList,
797
+ rows: Rows,
798
+ geometries: Geometries,
799
+ textures: Textures,
800
+ filters: Filters,
801
+ keeps: Keeps,
802
+ target: Target,
803
+ width: number,
804
+ height: number,
805
+ samples: number,
806
+ ): void {
807
+ const gl = this.gl;
808
+ if (gl.isContextLost()) {
809
+ return;
810
+ }
811
+
812
+ const layer = this.drawSampled(
813
+ commands,
814
+ scene,
815
+ rows,
816
+ geometries,
817
+ textures,
818
+ filters,
819
+ keeps,
820
+ target,
821
+ width,
822
+ height,
823
+ samples,
824
+ );
825
+ // Clear past the bitmap, which a layer taken may be larger than: its quad's smoothing reads
826
+ // half a texel past its edges.
827
+ this.state.bindFramebuffer(layer.framebuffer);
828
+ this.state.stencil(null);
829
+ this.state.colorMask(15);
830
+ gl.clearColor(0, 0, 0, 0);
831
+ for (const [x, y, w, h] of [
832
+ [width, 0, layer.width - width, layer.height],
833
+ [0, height, width, layer.height - height],
834
+ ]) {
835
+ if (w > 0 && h > 0) {
836
+ this.state.scissor([x, y, w, h]);
837
+ gl.clear(gl.COLOR_BUFFER_BIT);
838
+ }
839
+ }
840
+
841
+ this.state.scissor(null);
842
+ this.pool.keep(keep, layer);
843
+ }
844
+
845
+ /**
846
+ * The commands drawn, as `execute` draws them, into a layer `samples`
847
+ * times the size of a `width` by `height` bitmap a side, halved till a
848
+ * sample a pixel: the layer, its first `width` by `height` the bitmap.
849
+ */
850
+ private drawSampled(
851
+ commands: Commands,
852
+ scene: SceneList,
853
+ rows: Rows,
854
+ geometries: Geometries,
855
+ textures: Textures,
856
+ filters: Filters,
857
+ keeps: Keeps,
858
+ target: Target,
859
+ width: number,
860
+ height: number,
861
+ samples: number,
862
+ ): Layer {
863
+ let layer = this.pool.takeLayer(width * samples, height * samples);
864
+ this.execute(commands, scene, rows, geometries, textures, filters, keeps, target, layer);
865
+ for (let k = samples; k > 1; k /= 2) {
866
+ const half = this.filters.halve(layer, (width * k) / 2, (height * k) / 2);
867
+ this.pool.giveLayer(layer);
868
+ layer = half;
869
+ }
870
+
871
+ return layer;
872
+ }
873
+
874
+ /** Advanced text's outlines uploaded (GpuTextures.uploadOutlines). */
875
+ uploadOutlines(data: Float32Array, count: number, epoch: number): void {
876
+ this.gpu.uploadOutlines(data, count, epoch);
877
+ }
878
+
879
+ /** What the backend made on the context, deleted. */
880
+ destroy(): void {
881
+ this.draws.destroy();
882
+ this.atlas.destroy();
883
+ this.compositor.destroy();
884
+ this.filters.destroy();
885
+ this.kernels.clear();
886
+ for (const open of this.layers) {
887
+ if (open.layer) {
888
+ this.pool.deleteLayer(open.layer);
889
+ }
890
+ }
891
+
892
+ this.pool.destroy();
893
+ this.stage.destroy();
894
+ for (const known of this.textureFramebuffers) {
895
+ if (known) {
896
+ this.gl.deleteFramebuffer(known.framebuffer);
897
+ }
898
+ }
899
+
900
+ this.textureFramebuffers = [];
901
+ if (this.overProgram) {
902
+ this.gl.deleteProgram(this.overProgram.program);
903
+ this.overProgram = null;
904
+ }
905
+
906
+ this.gpu.destroy();
907
+ this.state.forget();
908
+ }
909
+
910
+ /**
911
+ * The layer pushed at command `k` drawn straight onto the target below,
912
+ * if it may be: unfiltered and not kept, one run of a flat fill alone
913
+ * (Draws.blendable) drawn normally in it, and a mode whose blend of the
914
+ * fill pixel by pixel, its edges covering the pixels in part, is that
915
+ * of the layer's pixels covered alike, as each is linear in what covers it
916
+ * (add short of its sums' clamp); its colour on the quad's. A layer and its
917
+ * resolve and composite left out, what reads what is below copies it as
918
+ * the composite would. Whether it was.
919
+ */
920
+ private blendDirect(commands: Commands, k: number, scene: SceneList, canvas: Open): boolean {
921
+ const ops = commands.ops;
922
+ const at = k * 4;
923
+ const run = at + 4;
924
+ const pop = at + 8;
925
+ if (
926
+ k + 2 >= commands.length ||
927
+ ops[at + 2] !== 0 ||
928
+ ops[run] !== RUN ||
929
+ ops[run + 2] !== 1 ||
930
+ ops[run + 3] !== NORMAL ||
931
+ ops[pop] !== LAYER_POP ||
932
+ ops[pop + 3] >= 0 ||
933
+ !direct(ops[pop + 1]) ||
934
+ !this.draws.blendable(scene, ops[run + 1])
935
+ ) {
936
+ return false;
937
+ }
938
+
939
+ const p = commands.params;
940
+ const from = ops[at + 1];
941
+ const under = this.layers.at(-1) ?? canvas;
942
+ const rect = this.rect;
943
+ rect.x = p[from];
944
+ rect.y = p[from + 1];
945
+ rect.width = p[from + 2];
946
+ rect.height = p[from + 3];
947
+ const mode = ops[pop + 1];
948
+ const compositor = this.compositor;
949
+ if (!compositor.cover(rect, under)) {
950
+ return true;
951
+ }
952
+
953
+ // Drawn into already, as a kept output is composited (COMPOSITE); a copy of what is below
954
+ // binds its framebuffer back.
955
+ const reads = readsBelow(mode, under.opaque);
956
+ const back = reads && compositor.copyBelow(under);
957
+ this.draws.maskBy(this.masks);
958
+ const place = compositor.place;
959
+ // Kept to the layer's rectangle, as the composite is, past which nothing below is copied.
960
+ this.state.scissor([place.fx, place.fy, place.width, place.height]);
961
+ this.draws.drawBlended(
962
+ scene,
963
+ ops[run + 1],
964
+ mode,
965
+ reads,
966
+ ops[pop + 2],
967
+ back ? place.backX : 0,
968
+ back ? place.backY : 0,
969
+ );
970
+ this.state.scissor(null);
971
+ return true;
972
+ }
973
+ }