@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,624 @@
1
+ import type { GlState } from "@swf2es/context3d/device";
2
+ import {
3
+ type Commands,
4
+ HELD,
5
+ HOLD_POP,
6
+ HOLD_PUSH,
7
+ RUN,
8
+ STENCIL_POP,
9
+ STENCIL_PUSH,
10
+ } from "../commands.js";
11
+ import { FilterPlan, type KernelObject, planChain } from "../filter-plan.js";
12
+ import type { Filters } from "../filters.js";
13
+ import type { SceneList } from "../scene.js";
14
+ import { Shelves } from "../shelves.js";
15
+ import type { Compositor } from "./composite.js";
16
+ import type { Draws, Masks } from "./draws.js";
17
+ import type { FilterRunner, FilterTarget } from "./filters.js";
18
+ import type { GpuTextures } from "./gpu-textures.js";
19
+ import {
20
+ framebufferOf,
21
+ LAYER_STEP,
22
+ type Layer,
23
+ layerSide,
24
+ type Open,
25
+ objectOf,
26
+ type Target,
27
+ type TargetPool,
28
+ } from "./targets.js";
29
+
30
+ /** The width of the pages held layers and their filters' targets are placed on, side by side. */
31
+ const PAGE_WIDTH = 2048;
32
+ /** Keep content pages below the free pool's budget so they can be reused. */
33
+ const CONTENT_PAGE_BYTES = 64 * 1024 * 1024;
34
+
35
+ /** A rectangle of a page, read and drawn into as a texture of its own (FilterTarget). */
36
+ interface Slot extends FilterTarget {
37
+ x: number;
38
+ y: number;
39
+ page: Layer;
40
+ /** Its page's index in the frame's (HeldLayers.pages). */
41
+ index: number;
42
+ }
43
+
44
+ /**
45
+ * A held layer (HOLD_PUSH): its rectangle on the target, the operations it
46
+ * draws by, its chain of filters, planned, and its targets by the plan's
47
+ * slots; its output once drawn, or null where none is.
48
+ */
49
+ interface Hold {
50
+ x: number;
51
+ y: number;
52
+ width: number;
53
+ height: number;
54
+ first: number;
55
+ end: number;
56
+ chain: number;
57
+ /** Where its object lies in it, which its ShaderFilters read (filter-plan.ts). */
58
+ object: KernelObject | null;
59
+ plan: FilterPlan;
60
+ targets: (Slot | null)[];
61
+ /** Its slots' records, kept from frame to frame, and the last pass that uses each. */
62
+ slots: Slot[];
63
+ lastUse: number[];
64
+ result: Slot | null;
65
+ /** Its content's rectangle on the pages (Shelves), -1 where it is drawn alone; the wave's. */
66
+ shelf: number;
67
+ wave: number;
68
+ /** Whether its pass of the wave drawing is drawn, or it has none. */
69
+ drawn: boolean;
70
+ /** Its level (HOLD_PUSH), one past the deepest held layer in it. */
71
+ level: number;
72
+ }
73
+
74
+ /**
75
+ * The frame's held layers (HOLD_PUSH), drawn ahead of the stage a level at
76
+ * a time: their content side by side on pages, then their filters' passes
77
+ * in waves, each wave's targets on pages of their own; their outputs left
78
+ * on the pages for the frame's HELD composites.
79
+ */
80
+ export class HeldLayers {
81
+ private readonly gl: WebGL2RenderingContext;
82
+ private readonly state: GlState;
83
+ private readonly pool: TargetPool;
84
+ private readonly draws: Draws;
85
+ private readonly compositor: Compositor;
86
+ private readonly gpu: GpuTextures;
87
+ /** The filters' passes, the backend's. */
88
+ private filters!: FilterRunner<Layer>;
89
+ /**
90
+ * The frame's held layers (HOLD_PUSH); the pages they took, null once
91
+ * given back; the last wave each is read in, and whether an output is on
92
+ * it, which keeps it for the frame.
93
+ */
94
+ private readonly holds: Hold[] = [];
95
+ private readonly pages: (Layer | null)[] = [];
96
+ private readonly pageUntil: number[] = [];
97
+ private readonly pageKept: boolean[] = [];
98
+ /** A page drawn into whole, a held layer's rectangle of one, and a held output composited. */
99
+ private readonly pageOpen: Open = { layer: null, msaa: null, x: 0, y: 0, width: 0, height: 0 };
100
+ private readonly holdOpen: Open = { layer: null, msaa: null, x: 0, y: 0, width: 0, height: 0 };
101
+ private readonly heldOpen: Open = { layer: null, msaa: null, x: 0, y: 0, width: 0, height: 0 };
102
+ private readonly shelves = new Shelves();
103
+ /** The masks pushed on the held layer whose content is drawn (drawHoldContent). */
104
+ private readonly masks: Masks = { depth: 0, past: 0 };
105
+ /** The largest side a texture may have. */
106
+ private textureMost = 4096;
107
+
108
+ constructor(
109
+ gl: WebGL2RenderingContext,
110
+ state: GlState,
111
+ pool: TargetPool,
112
+ draws: Draws,
113
+ compositor: Compositor,
114
+ gpu: GpuTextures,
115
+ ) {
116
+ this.gl = gl;
117
+ this.state = state;
118
+ this.pool = pool;
119
+ this.draws = draws;
120
+ this.compositor = compositor;
121
+ this.gpu = gpu;
122
+ }
123
+
124
+ /**
125
+ * The filters' passes and the largest side a texture may have, as the
126
+ * context comes or comes back.
127
+ */
128
+ setUp(filters: FilterRunner<Layer>, textureMost: number): void {
129
+ this.filters = filters;
130
+ this.textureMost = textureMost;
131
+ }
132
+
133
+ /**
134
+ * The frame's held layers (HOLD_PUSH), drawn ahead of the stage side by
135
+ * side: their content on pages of their own, one bind, clear and resolve
136
+ * a page; then their filters' passes in waves, the k-th pass of every
137
+ * chain at once, each wave's targets on pages of its own, which no pass
138
+ * of it reads. Each layer's output is left on its page for HELD. A layer
139
+ * larger than a page is drawn alone, as a layer of its own.
140
+ */
141
+ drawHolds(commands: Commands, scene: SceneList, filters: Filters, target: Target): void {
142
+ const count = this.gatherHolds(commands);
143
+ // A level at a time, which the planner gives in order: a held layer's content composites
144
+ // those in it, of the levels before.
145
+ for (let from = 0; from < count; ) {
146
+ let to = from + 1;
147
+ while (to < count && this.holds[to].level === this.holds[from].level) {
148
+ to++;
149
+ }
150
+
151
+ this.drawHoldLevel(from, to, commands, scene, filters, target);
152
+ from = to;
153
+ }
154
+ }
155
+
156
+ /** Held layers `from` up to `to`, of one level, drawn side by side (drawHolds). */
157
+ private drawHoldLevel(
158
+ from: number,
159
+ to: number,
160
+ commands: Commands,
161
+ scene: SceneList,
162
+ filters: Filters,
163
+ target: Target,
164
+ ): void {
165
+ const gl = this.gl;
166
+ const state = this.state;
167
+ const holds = this.holds;
168
+ // The pages this level takes, from here on, which its waves give back.
169
+ const levelPages = this.pages.length;
170
+ const shelves = this.shelves;
171
+ this.draws.forgetRow();
172
+ // Their content, each at the size its own layer would take, on pages no taller than the
173
+ // frame's samples fit (fittingSamples), so that each takes them as its own layer did.
174
+ let area = 0;
175
+ let widest = 0;
176
+ for (let h = from; h < to; h++) {
177
+ area += layerSide(holds[h].width) * layerSide(holds[h].height);
178
+ widest = Math.max(widest, layerSide(holds[h].width));
179
+ }
180
+
181
+ const samples = this.pool.frameSamples;
182
+ let width = this.pageWidth(area, widest);
183
+ const fitting = Math.max(
184
+ LAYER_STEP,
185
+ Math.floor(CONTENT_PAGE_BYTES / (width * samples * 8) / LAYER_STEP) * LAYER_STEP,
186
+ );
187
+ shelves.reset(width, samples > 1 ? Math.min(this.textureMost, fitting) : this.textureMost);
188
+ for (let h = from; h < to; h++) {
189
+ const hold = holds[h];
190
+ hold.shelf = shelves.add(layerSide(hold.width), layerSide(hold.height));
191
+ if (hold.shelf < 0) {
192
+ this.drawHoldAlone(hold, commands, scene, filters, target);
193
+ }
194
+ }
195
+
196
+ for (let p = 0; p < shelves.pages; p++) {
197
+ const index = this.takePage(width, shelves.heights[p]);
198
+ const page = this.pages[index] as Layer;
199
+ const whole = this.pageOpen;
200
+ whole.layer = page;
201
+ whole.msaa = this.pool.layerMsaa(page, samples);
202
+ whole.width = page.width;
203
+ whole.height = page.height;
204
+ state.bindFramebuffer(framebufferOf(whole));
205
+ state.scissor(null);
206
+ state.stencil(null);
207
+ state.colorMask(15);
208
+ gl.clearColor(0, 0, 0, 0);
209
+ gl.clearStencil(0);
210
+ gl.stencilMask(0xff);
211
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
212
+ for (let h = from; h < to; h++) {
213
+ const hold = holds[h];
214
+ if (hold.shelf < 0 || shelves.page[hold.shelf] !== p) {
215
+ continue;
216
+ }
217
+
218
+ const content = this.holdOpen;
219
+ content.layer = page;
220
+ content.msaa = whole.msaa;
221
+ content.x = hold.x;
222
+ content.y = hold.y;
223
+ content.width = hold.width;
224
+ content.height = hold.height;
225
+ content.ox = shelves.x[hold.shelf];
226
+ content.oy = shelves.y[hold.shelf];
227
+ // Kept to its rectangle by the viewport alone, as the composites in it scissor themselves.
228
+ this.draws.drawInto(content, target);
229
+ this.drawHoldContent(commands, hold, scene, content);
230
+ this.slotOf(hold, 0, index, content.ox, content.oy, hold.width, hold.height);
231
+ }
232
+
233
+ this.pool.resolve(whole, page.width, page.height);
234
+ }
235
+
236
+ // Their filters, planned, then drawn a wave at a time: each wave's new targets on pages of its
237
+ // own, which none of its passes reads, as each reads what earlier waves drew. A page goes back
238
+ // once no pass after reads it, unless an output is on it.
239
+ let waves = 0;
240
+ for (let h = from; h < to; h++) {
241
+ const hold = holds[h];
242
+ if (hold.shelf < 0) {
243
+ continue;
244
+ }
245
+
246
+ const plan = hold.plan;
247
+ planChain(
248
+ plan,
249
+ filters,
250
+ hold.chain,
251
+ hold.width,
252
+ hold.height,
253
+ target.resolution,
254
+ this.gpu.hasTexture,
255
+ this.filters.links,
256
+ hold.object,
257
+ );
258
+ hold.targets.length = plan.slots;
259
+ hold.targets.fill(null, 1);
260
+ hold.lastUse.length = plan.slots;
261
+ hold.lastUse.fill(-1);
262
+ for (let k = 0; k < plan.count; k++) {
263
+ const pass = plan.passes[k];
264
+ hold.lastUse[pass.input] = k;
265
+ hold.lastUse[pass.output] = k;
266
+ if (pass.blurred >= 0) {
267
+ hold.lastUse[pass.blurred] = k;
268
+ }
269
+ }
270
+
271
+ this.notePage(hold, 0);
272
+ waves = Math.max(waves, plan.count);
273
+ }
274
+
275
+ for (let k = 0; k < waves; k++) {
276
+ area = 0;
277
+ widest = 0;
278
+ for (let h = from; h < to; h++) {
279
+ const hold = holds[h];
280
+ const plan = hold.plan;
281
+ if (hold.shelf >= 0 && k < plan.count && !hold.targets[plan.passes[k].output]) {
282
+ const slot = plan.passes[k].output;
283
+ area += layerSide(plan.slotWidth[slot]) * layerSide(plan.slotHeight[slot]);
284
+ widest = Math.max(widest, layerSide(plan.slotWidth[slot]));
285
+ }
286
+ }
287
+
288
+ width = this.pageWidth(area, widest);
289
+ shelves.reset(width, this.textureMost);
290
+ for (let h = from; h < to; h++) {
291
+ const hold = holds[h];
292
+ const plan = hold.plan;
293
+ hold.wave = -1;
294
+ if (hold.shelf >= 0 && k < plan.count && !hold.targets[plan.passes[k].output]) {
295
+ const slot = plan.passes[k].output;
296
+ hold.wave = shelves.add(
297
+ layerSide(plan.slotWidth[slot]),
298
+ layerSide(plan.slotHeight[slot]),
299
+ );
300
+ }
301
+ }
302
+
303
+ const first = this.pages.length;
304
+ for (let p = 0; p < shelves.pages; p++) {
305
+ const page = this.pages[this.takePage(width, shelves.heights[p])] as Layer;
306
+ state.bindFramebuffer(page.framebuffer);
307
+ state.scissor(null);
308
+ state.colorMask(15);
309
+ gl.clearColor(0, 0, 0, 0);
310
+ gl.clear(gl.COLOR_BUFFER_BIT);
311
+ }
312
+
313
+ for (let h = from; h < to; h++) {
314
+ const hold = holds[h];
315
+ if (hold.wave >= 0) {
316
+ const plan = hold.plan;
317
+ const slot = plan.passes[k].output;
318
+ this.slotOf(
319
+ hold,
320
+ slot,
321
+ first + shelves.page[hold.wave],
322
+ shelves.x[hold.wave],
323
+ shelves.y[hold.wave],
324
+ plan.slotWidth[slot],
325
+ plan.slotHeight[slot],
326
+ );
327
+ this.notePage(hold, slot);
328
+ }
329
+ }
330
+
331
+ // The passes drawing into one page together, so that it is bound once: a target drawn into
332
+ // again, as a blur's runs take turns between two, is on an earlier wave's page.
333
+ let left = 0;
334
+ for (let h = from; h < to; h++) {
335
+ const hold = holds[h];
336
+ hold.drawn = !(hold.shelf >= 0 && k < hold.plan.count);
337
+ left += hold.drawn ? 0 : 1;
338
+ }
339
+
340
+ while (left > 0) {
341
+ let page = -1;
342
+ for (let h = from; h < to; h++) {
343
+ const hold = holds[h];
344
+ if (hold.drawn) {
345
+ continue;
346
+ }
347
+
348
+ const output = hold.targets[hold.plan.passes[k].output] as Slot;
349
+ if (page < 0) {
350
+ page = output.index;
351
+ }
352
+
353
+ if (output.index === page) {
354
+ this.filters.drawPass(hold.plan, k, hold.targets, false);
355
+ hold.drawn = true;
356
+ left--;
357
+ }
358
+ }
359
+ }
360
+
361
+ for (let index = levelPages; index < this.pages.length; index++) {
362
+ const page = this.pages[index];
363
+ if (page && !this.pageKept[index] && this.pageUntil[index] <= k) {
364
+ this.pool.giveLayer(page);
365
+ this.pages[index] = null;
366
+ }
367
+ }
368
+ }
369
+
370
+ for (let h = from; h < to; h++) {
371
+ const hold = holds[h];
372
+ if (hold.shelf >= 0) {
373
+ hold.result = hold.targets[hold.plan.result];
374
+ }
375
+ }
376
+ }
377
+
378
+ /**
379
+ * The width of a page for rectangles of `area` in all, the widest `widest`:
380
+ * about square, in LAYER_STEP's steps, at most PAGE_WIDTH.
381
+ */
382
+ private pageWidth(area: number, widest: number): number {
383
+ const square = Math.ceil(Math.sqrt(area) / LAYER_STEP) * LAYER_STEP;
384
+ return Math.min(this.textureMost, PAGE_WIDTH, Math.max(widest, square));
385
+ }
386
+
387
+ /** A page of at least `width` by `height` taken for the frame: its index in `pages`. */
388
+ private takePage(width: number, height: number): number {
389
+ return this.addPage(this.pool.takeLayer(width, height), false);
390
+ }
391
+
392
+ /**
393
+ * `layer` added to the frame's pages, read by no pass yet, kept for the
394
+ * frame where `kept`: its index in `pages`.
395
+ */
396
+ private addPage(layer: Layer, kept: boolean): number {
397
+ const index = this.pages.length;
398
+ this.pages.push(layer);
399
+ this.pageUntil.push(-1);
400
+ this.pageKept.push(kept);
401
+ return index;
402
+ }
403
+
404
+ /** Slot `slot` of `hold` noted on its page: read until its last pass, or kept as its output. */
405
+ private notePage(hold: Hold, slot: number): void {
406
+ const index = (hold.targets[slot] as Slot).index;
407
+ this.pageUntil[index] = Math.max(this.pageUntil[index], hold.lastUse[slot]);
408
+ if (slot === hold.plan.result) {
409
+ this.pageKept[index] = true;
410
+ }
411
+ }
412
+
413
+ /** The held layers' rectangles and operations (HOLD_PUSH), which the commands begin with: how many. */
414
+ private gatherHolds(commands: Commands): number {
415
+ const ops = commands.ops;
416
+ const params = commands.params;
417
+ const holds = this.holds;
418
+ let count = 0;
419
+ for (let k = 0; k < commands.length && ops[k * 4] === HOLD_PUSH; k++) {
420
+ const from = ops[k * 4 + 1];
421
+ let hold = holds[count];
422
+ if (!hold) {
423
+ hold = {
424
+ x: 0,
425
+ y: 0,
426
+ width: 0,
427
+ height: 0,
428
+ first: 0,
429
+ end: 0,
430
+ chain: 0,
431
+ object: null,
432
+ plan: new FilterPlan(),
433
+ targets: [],
434
+ slots: [],
435
+ lastUse: [],
436
+ result: null,
437
+ shelf: -1,
438
+ wave: -1,
439
+ drawn: false,
440
+ level: 0,
441
+ };
442
+ holds[count] = hold;
443
+ }
444
+
445
+ count++;
446
+ hold.level = ops[k * 4 + 3];
447
+ hold.x = params[from];
448
+ hold.y = params[from + 1];
449
+ hold.width = params[from + 2];
450
+ hold.height = params[from + 3];
451
+ hold.object = objectOf(params, from + 4);
452
+ hold.first = k + 1;
453
+
454
+ while (ops[k * 4] !== HOLD_POP) {
455
+ k++;
456
+ }
457
+
458
+ hold.end = k;
459
+ hold.chain = ops[k * 4 + 1];
460
+ hold.result = null;
461
+ hold.targets.length = 0;
462
+ }
463
+
464
+ return count;
465
+ }
466
+
467
+ /**
468
+ * Slot `slot` of `hold`'s targets: the rectangle of page `index` from (x,
469
+ * y) a target of `width` by `height` takes, as its own layer would; its
470
+ * record kept from frame to frame.
471
+ */
472
+ private slotOf(
473
+ hold: Hold,
474
+ slot: number,
475
+ index: number,
476
+ x: number,
477
+ y: number,
478
+ width: number,
479
+ height: number,
480
+ ): Slot {
481
+ const page = this.pages[index] as Layer;
482
+ let record = hold.slots[slot];
483
+ if (!record) {
484
+ record = {
485
+ framebuffer: page.framebuffer,
486
+ texture: page.texture,
487
+ x,
488
+ y,
489
+ width,
490
+ height,
491
+ page,
492
+ index,
493
+ };
494
+ hold.slots[slot] = record;
495
+ }
496
+
497
+ record.framebuffer = page.framebuffer;
498
+ record.texture = page.texture;
499
+ record.x = x;
500
+ record.y = y;
501
+ record.width = layerSide(width);
502
+ record.height = layerSide(height);
503
+ record.page = page;
504
+ record.index = index;
505
+ hold.targets[slot] = record;
506
+ return record;
507
+ }
508
+
509
+ /**
510
+ * Held layer `hold`'s draws, masks and held layers' composites into
511
+ * `open`, bound, its stencil starting clear.
512
+ */
513
+ private drawHoldContent(commands: Commands, hold: Hold, scene: SceneList, open: Open): void {
514
+ const state = this.state;
515
+ const ops = commands.ops;
516
+ const masks = this.masks;
517
+ masks.depth = 0;
518
+ masks.past = 0;
519
+ state.stencil(null);
520
+ state.colorMask(15);
521
+ for (let k = hold.first; k < hold.end; k++) {
522
+ const at = k * 4;
523
+ switch (ops[at]) {
524
+ case RUN:
525
+ this.draws.drawRun(scene, ops[at + 1], ops[at + 1] + ops[at + 2], k);
526
+ break;
527
+ case STENCIL_PUSH:
528
+ case STENCIL_POP:
529
+ this.draws.drawMask(
530
+ scene,
531
+ ops[at + 1],
532
+ ops[at + 1] + ops[at + 2],
533
+ ops[at] === STENCIL_PUSH,
534
+ masks,
535
+ k,
536
+ );
537
+ break;
538
+ case HELD:
539
+ this.draws.maskBy(masks);
540
+ this.compositeHeld(ops[at + 1], open, ops[at + 2], ops[at + 3]);
541
+ break;
542
+ }
543
+ }
544
+
545
+ state.stencil(null);
546
+ }
547
+
548
+ /** Held output `held` composited onto `under`, bound, in blend mode `mode`, coloured by `row`. */
549
+ compositeHeld(held: number, under: Open, mode: number, row: number): void {
550
+ const hold = this.holds[held];
551
+ const result = hold.result;
552
+ if (!result) {
553
+ return;
554
+ }
555
+
556
+ const open = this.heldOpen;
557
+ open.layer = result.page;
558
+ open.x = hold.x;
559
+ open.y = hold.y;
560
+ open.width = hold.width;
561
+ open.height = hold.height;
562
+ open.ox = result.x;
563
+ open.oy = result.y;
564
+ this.compositor.compositeLayer(open, under, mode, row);
565
+ }
566
+
567
+ /**
568
+ * Held layer `hold`, too large for a page, drawn as a layer of its own and
569
+ * filtered there, its output left in a page of its own.
570
+ */
571
+ private drawHoldAlone(
572
+ hold: Hold,
573
+ commands: Commands,
574
+ scene: SceneList,
575
+ filters: Filters,
576
+ target: Target,
577
+ ): void {
578
+ const gl = this.gl;
579
+ const state = this.state;
580
+ const layer = this.pool.takeLayer(hold.width, hold.height, true);
581
+ const open: Open = {
582
+ layer,
583
+ msaa: this.pool.layerMsaa(layer, this.pool.frameSamples),
584
+ x: hold.x,
585
+ y: hold.y,
586
+ width: hold.width,
587
+ height: hold.height,
588
+ };
589
+ this.draws.drawInto(open, target);
590
+ state.scissor(null);
591
+ state.colorMask(15);
592
+ state.stencil(null);
593
+ gl.clearColor(0, 0, 0, 0);
594
+ gl.clearStencil(0);
595
+ gl.stencilMask(0xff);
596
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
597
+ this.drawHoldContent(commands, hold, scene, open);
598
+ this.pool.resolve(open, layer.width, layer.height);
599
+ const output = this.filters.run(
600
+ filters,
601
+ hold.chain,
602
+ layer,
603
+ hold.width,
604
+ hold.height,
605
+ target.resolution,
606
+ hold.object,
607
+ );
608
+ const index = this.addPage(output, true);
609
+ hold.result = this.slotOf(hold, 0, index, 0, 0, output.width, output.height);
610
+ }
611
+
612
+ /** The pages the frame took given back, as it ends. */
613
+ givePages(): void {
614
+ for (const page of this.pages) {
615
+ if (page) {
616
+ this.pool.giveLayer(page);
617
+ }
618
+ }
619
+
620
+ this.pages.length = 0;
621
+ this.pageUntil.length = 0;
622
+ this.pageKept.length = 0;
623
+ }
624
+ }
@@ -0,0 +1,3 @@
1
+ // The renderer's WebGL 2 backend (docs/renderer.md, "The backend"): the
2
+ // only part that calls the GPU.
3
+ export { type Target, WebGLBackend } from "./backend.js";