@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.
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,372 @@
1
+ import { type GlState, linkProgram } from "@swf2es/context3d/device";
2
+ import { BLEND, type Filters } from "../filters.js";
3
+ import { blendKernel, kernelImagesFit } from "../kernel-shaders.js";
4
+ import { ADD, ALPHA_MASK, KERNEL, LAYER, MULTIPLY, NORMAL, SCREEN } from "../scene.js";
5
+ import type { KernelHost, KernelProgram, KernelPrograms } from "./kernels.js";
6
+ import {
7
+ BACK_UNIT,
8
+ compositeFragment,
9
+ compositeVertex,
10
+ LAYER_COLOR,
11
+ LAYER_UNIT,
12
+ } from "./programs.js";
13
+ import { framebufferOf, LAYER_STEP, type Layer, type Open } from "./targets.js";
14
+
15
+ /**
16
+ * Whether blend mode `mode` reads what is below, which GL's blending does
17
+ * not compute, onto a target `opaque` throughout or not. Over an opaque
18
+ * one, multiply's src × (1 − da) term is none, and premultiplied it is
19
+ * s × d + d × (1 − sa), alpha as normal's: GL's blending (setModeBlend).
20
+ * An alpha mask's is d × sa (ALPHA_MASK).
21
+ */
22
+ export function readsBelow(mode: number, opaque = false): boolean {
23
+ return !(
24
+ mode === NORMAL ||
25
+ mode === ALPHA_MASK ||
26
+ mode === LAYER ||
27
+ mode === ADD ||
28
+ mode === SCREEN ||
29
+ (mode === MULTIPLY && opaque)
30
+ );
31
+ }
32
+
33
+ /**
34
+ * GL's blending set for `mode` where it computes it (readsBelow false),
35
+ * the fragment then its pixel alone (BLEND_FRONT's normal); else off, the
36
+ * fragment the blend itself.
37
+ */
38
+ export function setModeBlend(gl: WebGL2RenderingContext, state: GlState, mode: number): void {
39
+ switch (mode) {
40
+ case ADD:
41
+ state.setBlend(gl.ONE, gl.ONE);
42
+ break;
43
+ case SCREEN:
44
+ state.setBlend(gl.ONE, gl.ONE_MINUS_SRC_COLOR);
45
+ break;
46
+ case NORMAL:
47
+ case LAYER:
48
+ state.setBlend(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
49
+ break;
50
+ case MULTIPLY:
51
+ state.setBlend(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
52
+ break;
53
+ case ALPHA_MASK:
54
+ state.setBlend(gl.ZERO, gl.SRC_ALPHA);
55
+ break;
56
+ default:
57
+ state.setBlend(null);
58
+ }
59
+ }
60
+
61
+ /** Where a compositor finds the frame's blend shaders and the textures they read. */
62
+ export interface CompositeHost extends KernelHost {
63
+ filters(): Filters | null;
64
+ }
65
+
66
+ /**
67
+ * Layers composited onto what they are drawn over, in Flash's blend modes:
68
+ * through GL's blending where it computes the blend, else by the composite
69
+ * program from a copy of what is below, or by a blend shader's program
70
+ * (KERNEL).
71
+ */
72
+ export class Compositor {
73
+ private readonly gl: WebGL2RenderingContext;
74
+ private readonly state: GlState;
75
+ /** The layers' composite program and where it takes its mode and row. */
76
+ private composite!: WebGLProgram;
77
+ private modeLocation: WebGLUniformLocation | null = null;
78
+ private compositeRowLocation: WebGLUniformLocation | null = null;
79
+ private offsetLocation: WebGLUniformLocation | null = null;
80
+ private flipLocation: WebGLUniformLocation | null = null;
81
+ private backOffsetLocation: WebGLUniformLocation | null = null;
82
+ private rectLocation: WebGLUniformLocation | null = null;
83
+ /** A copy of what is below a layer whose mode reads it, the target's size. */
84
+ private back: WebGLTexture | null = null;
85
+ private backFramebuffer: WebGLFramebuffer | null = null;
86
+ private backWidth = 0;
87
+ private backHeight = 0;
88
+ constructor(
89
+ gl: WebGL2RenderingContext,
90
+ state: GlState,
91
+ private readonly host: CompositeHost,
92
+ /** Blend shaders' programs, the backend's one cache of kernels'. */
93
+ private readonly kernels: KernelPrograms,
94
+ ) {
95
+ this.gl = gl;
96
+ this.state = state;
97
+ }
98
+
99
+ /** The composite program made, and no copy yet, as the context comes or comes back. */
100
+ setUp(): void {
101
+ const gl = this.gl;
102
+ this.composite = linkProgram(gl, compositeVertex, compositeFragment);
103
+ this.state.useProgram(this.composite);
104
+ gl.uniform1i(gl.getUniformLocation(this.composite, "uLayer"), LAYER_UNIT);
105
+ gl.uniform1i(gl.getUniformLocation(this.composite, "uBack"), BACK_UNIT);
106
+ gl.uniform1i(gl.getUniformLocation(this.composite, "uRows"), 0);
107
+ this.modeLocation = gl.getUniformLocation(this.composite, "uMode");
108
+ this.compositeRowLocation = gl.getUniformLocation(this.composite, "uRow");
109
+ this.offsetLocation = gl.getUniformLocation(this.composite, "uOffset");
110
+ this.flipLocation = gl.getUniformLocation(this.composite, "uFlip");
111
+ this.backOffsetLocation = gl.getUniformLocation(this.composite, "uBackOffset");
112
+ this.rectLocation = gl.getUniformLocation(this.composite, "uRect");
113
+ this.back = null;
114
+ this.backFramebuffer = null;
115
+ this.backWidth = 0;
116
+ this.backHeight = 0;
117
+ }
118
+
119
+ /**
120
+ * `open` composited onto `under`, bound, in blend mode `mode`, coloured by
121
+ * row `row` (or -1), over its rectangle alone: through GL's blending where
122
+ * it computes Flash's blend, else from a copy of what is below there, the
123
+ * result replacing it. Its rectangle is cut to `under`'s, as a filtered
124
+ * one may reach past the canvas; in `under`'s framebuffer its rows run
125
+ * up on the canvas and down in a layer.
126
+ */
127
+ compositeLayer(open: Open, under: Open, given: number, row: number): void {
128
+ const gl = this.gl;
129
+ // A blend shader that will not link, of too many images, or with an image gone, composites as
130
+ // normal.
131
+ const kernel = given >= KERNEL ? this.useKernel(given - KERNEL) : null;
132
+ const mode = given >= KERNEL && !kernel ? NORMAL : given;
133
+ const state = this.state;
134
+ const reads = readsBelow(mode, under.opaque);
135
+ if (!this.cover(open, under) || (reads && !this.copyBelow(under))) {
136
+ return;
137
+ }
138
+
139
+ const { dx, dy, x0, y0, width, height, fx, fy, backX, backY } = this.place;
140
+ const flip = !under.layer;
141
+ state.bindTexture(LAYER_UNIT, (open.layer as Layer).texture);
142
+ state.bindSampler(LAYER_UNIT, null);
143
+ state.bindSampler(BACK_UNIT, null);
144
+ state.bindVertexArray(null);
145
+ // Less its corner on its page, where it is a held layer's rectangle of one (HELD), and past
146
+ // the target's, where that is one.
147
+ const ox = (under.ox ?? 0) - (open.ox ?? 0);
148
+ const oy = (under.oy ?? 0) - (open.oy ?? 0);
149
+ const offsetY = flip ? under.height - 1 - dy - oy : dy + oy;
150
+ if (kernel) {
151
+ const filters = this.host.filters() as Filters;
152
+ const entry = filters.chains[mode - KERNEL];
153
+ state.useProgram(kernel.program);
154
+ gl.uniform1i(kernel.at("uRow"), row);
155
+ gl.uniform2i(kernel.at("uOffset"), dx + ox, offsetY);
156
+ gl.uniform1i(kernel.at("uFlip"), flip ? 1 : 0);
157
+ gl.uniform2i(kernel.at("uBackOffset"), backX, backY);
158
+ gl.uniform1i(kernel.at("uWhole"), filters.values[filters.start[entry]]);
159
+ this.kernels.setUniforms(kernel, filters, entry, this.host);
160
+ } else {
161
+ state.useProgram(this.composite);
162
+ // Its pixel alone where GL's blending computes the mode (MULTIPLY over an opaque target).
163
+ gl.uniform1i(this.modeLocation, reads ? mode : NORMAL);
164
+ gl.uniform1i(this.compositeRowLocation, row);
165
+ gl.uniform2i(this.offsetLocation, dx + ox, offsetY);
166
+ gl.uniform1i(this.flipLocation, flip ? 1 : 0);
167
+ gl.uniform2i(this.backOffsetLocation, backX, backY);
168
+ }
169
+
170
+ setModeBlend(gl, state, reads ? -1 : mode);
171
+ // Over the rectangle alone, in the target's clip space; the scissor keeps it to whole pixels.
172
+ const tw = under.width;
173
+ const th = under.height;
174
+ gl.uniform4f(
175
+ kernel ? kernel.at("uRect") : this.rectLocation,
176
+ (2 * x0) / tw - 1,
177
+ (2 * y0) / th - 1,
178
+ (2 * (x0 + width)) / tw - 1,
179
+ (2 * (y0 + height)) / th - 1,
180
+ );
181
+ state.scissor([fx, fy, width, height]);
182
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
183
+ state.scissor(null);
184
+ }
185
+
186
+ /** Where cover last found a rectangle on its target, and where copyBelow copied what is below it. */
187
+ readonly place = {
188
+ dx: 0,
189
+ dy: 0,
190
+ x0: 0,
191
+ y0: 0,
192
+ width: 0,
193
+ height: 0,
194
+ fx: 0,
195
+ fy: 0,
196
+ backX: 0,
197
+ backY: 0,
198
+ };
199
+
200
+ /**
201
+ * Where `rect` lies on `under`, into place: cut to `under`'s, as a
202
+ * filtered layer may reach past the canvas; in `under`'s framebuffer its
203
+ * rows run up on the canvas and down in a layer. Whether any of it does.
204
+ */
205
+ cover(rect: { x: number; y: number; width: number; height: number }, under: Open): boolean {
206
+ const place = this.place;
207
+ const dx = rect.x - under.x;
208
+ const dy = rect.y - under.y;
209
+ const y = under.layer ? dy : under.height - dy - rect.height;
210
+ place.dx = dx;
211
+ place.dy = dy;
212
+ place.x0 = Math.max(0, dx);
213
+ place.y0 = Math.max(0, y);
214
+ place.width = Math.min(under.width, dx + rect.width) - place.x0;
215
+ place.height = Math.min(under.height, y + rect.height) - place.y0;
216
+ // The rectangle's corner in the target's framebuffer, where the target is a held layer's
217
+ // rectangle of a page (HOLD_PUSH).
218
+ place.fx = place.x0 + (under.ox ?? 0);
219
+ place.fy = place.y0 + (under.oy ?? 0);
220
+ // Where the copy of what is below has the rectangle's corner, less where the target has it.
221
+ place.backX = 0;
222
+ place.backY = 0;
223
+ return place.width > 0 && place.height > 0;
224
+ }
225
+
226
+ /**
227
+ * What is below the rectangle cover found copied, for a blend that reads
228
+ * it: bound to BACK_UNIT, where the copy has the rectangle's corner, less
229
+ * where the target has it, into place, and `under`'s framebuffer bound again.
230
+ */
231
+ copyBelow(under: Open): boolean {
232
+ const gl = this.gl;
233
+ const state = this.state;
234
+ const { fx, fy, width, height } = this.place;
235
+ // Out of a multisampled page, its rectangle resolved into the page's own texture first, which
236
+ // a blit may then copy anywhere: copied where it lies, the copy would stay as large as the
237
+ // page's far corner.
238
+ let from = framebufferOf(under);
239
+ const paged = under.msaa !== null && under.layer !== null && under.ox !== undefined;
240
+ if (paged) {
241
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, from);
242
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, (under.layer as Layer).framebuffer);
243
+ gl.blitFramebuffer(
244
+ fx,
245
+ fy,
246
+ fx + width,
247
+ fy + height,
248
+ fx,
249
+ fy,
250
+ fx + width,
251
+ fy + height,
252
+ gl.COLOR_BUFFER_BIT,
253
+ gl.NEAREST,
254
+ );
255
+ from = (under.layer as Layer).framebuffer;
256
+ }
257
+
258
+ // Copied from its corner, or out of a multisampled target to where it lies, as a blit
259
+ // resolving must keep its rectangle: then as large as the rectangle's far corner.
260
+ const bx = under.msaa && !paged ? fx : 0;
261
+ const by = under.msaa && !paged ? fy : 0;
262
+ this.place.backX = fx - bx;
263
+ this.place.backY = fy - by;
264
+ const right = bx + width;
265
+ const top = by + height;
266
+ if (!this.back || this.backWidth < right || this.backHeight < top) {
267
+ if (this.back) {
268
+ state.dropTexture(this.back);
269
+ gl.deleteTexture(this.back);
270
+ gl.deleteFramebuffer(this.backFramebuffer);
271
+ this.backFramebuffer = null;
272
+ }
273
+
274
+ this.back = gl.createTexture() as WebGLTexture;
275
+ this.backWidth = Math.max(this.backWidth, Math.ceil(right / LAYER_STEP) * LAYER_STEP);
276
+ this.backHeight = Math.max(this.backHeight, Math.ceil(top / LAYER_STEP) * LAYER_STEP);
277
+ state.bindTexture(BACK_UNIT, this.back);
278
+ state.activeTexture(BACK_UNIT);
279
+ gl.texImage2D(
280
+ gl.TEXTURE_2D,
281
+ 0,
282
+ gl.RGBA8,
283
+ this.backWidth,
284
+ this.backHeight,
285
+ 0,
286
+ gl.RGBA,
287
+ gl.UNSIGNED_BYTE,
288
+ null,
289
+ );
290
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
291
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
292
+ }
293
+
294
+ // Blitted, not copied: the canvas' colour may have no alpha, which copyTexSubImage2D into an
295
+ // RGBA texture refuses, where a blit fills it in.
296
+ // Out of a multisampled target the blit resolves the rectangle alone.
297
+ if (!this.backFramebuffer) {
298
+ this.backFramebuffer = gl.createFramebuffer() as WebGLFramebuffer;
299
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.backFramebuffer);
300
+ gl.framebufferTexture2D(
301
+ gl.DRAW_FRAMEBUFFER,
302
+ gl.COLOR_ATTACHMENT0,
303
+ gl.TEXTURE_2D,
304
+ this.back,
305
+ 0,
306
+ );
307
+ } else {
308
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.backFramebuffer);
309
+ }
310
+
311
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, from);
312
+ gl.blitFramebuffer(
313
+ fx,
314
+ fy,
315
+ fx + width,
316
+ fy + height,
317
+ bx,
318
+ by,
319
+ right,
320
+ top,
321
+ gl.COLOR_BUFFER_BIT,
322
+ gl.NEAREST,
323
+ );
324
+ // Both bindings back as the state cache has them.
325
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebufferOf(under));
326
+ state.bindTexture(BACK_UNIT, this.back);
327
+ state.bindSampler(BACK_UNIT, null);
328
+ return true;
329
+ }
330
+
331
+ /**
332
+ * The program of the blend shader of chain `chain` of the frame's
333
+ * filters (KernelPrograms), in use; null for none, one that would not
334
+ * link, one of more images than a program binds, or one whose image is gone.
335
+ */
336
+ private useKernel(chain: number): KernelProgram | null {
337
+ const filters = this.host.filters();
338
+ if (!filters || chain >= filters.chainCount) {
339
+ return null;
340
+ }
341
+
342
+ const i = filters.chains[chain];
343
+ const source = filters.kernels[i];
344
+ const images =
345
+ source && filters.kind[i] === BLEND ? this.kernels.readImages(filters, i, this.host) : null;
346
+ if (!source || !images || !kernelImagesFit(images)) {
347
+ return null;
348
+ }
349
+
350
+ return this.kernels.use(
351
+ compositeVertex,
352
+ blendKernel(source, images, LAYER_COLOR),
353
+ (program) => {
354
+ const gl = this.gl;
355
+ gl.uniform1i(gl.getUniformLocation(program, "uLayer"), LAYER_UNIT);
356
+ gl.uniform1i(gl.getUniformLocation(program, "uBack"), BACK_UNIT);
357
+ gl.uniform1i(gl.getUniformLocation(program, "uRows"), 0);
358
+ },
359
+ );
360
+ }
361
+
362
+ /** The program and the copy made on the context, deleted. */
363
+ destroy(): void {
364
+ const gl = this.gl;
365
+ gl.deleteProgram(this.composite);
366
+
367
+ if (this.back) {
368
+ gl.deleteTexture(this.back);
369
+ gl.deleteFramebuffer(this.backFramebuffer);
370
+ }
371
+ }
372
+ }