@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,902 @@
1
+ import { type GlState, linkProgram } from "@swf2es/context3d/device";
2
+ import { type Commands, RUN, STENCIL_POP, STENCIL_PUSH } from "../commands.js";
3
+ import { FILL_SHADER, type Filters } from "../filters.js";
4
+ import { fillKernel, kernelImagesFit } from "../kernel-shaders.js";
5
+ import {
6
+ CLAMP,
7
+ DRAW,
8
+ MESH,
9
+ MIRROR,
10
+ NORMAL,
11
+ PLANE,
12
+ REPEAT,
13
+ type SceneList,
14
+ SHADED,
15
+ SMOOTH,
16
+ SNAP,
17
+ } from "../scene.js";
18
+ import type { GeometryAtlas, Place } from "./atlas.js";
19
+ import { setModeBlend } from "./composite.js";
20
+ import type { GpuTextures } from "./gpu-textures.js";
21
+ import type { KernelHost, KernelPrograms } from "./kernels.js";
22
+ import {
23
+ BACK_UNIT,
24
+ blendFillFragment,
25
+ FILL_SLOT,
26
+ FILL_TEXTURES,
27
+ FILL_UNIT,
28
+ fillFragment,
29
+ fillVertex,
30
+ glyphsFragment,
31
+ glyphsVertex,
32
+ meshFragment,
33
+ meshVertex,
34
+ OUTLINE_UNIT,
35
+ RUN_MOST,
36
+ RUN_UNIT,
37
+ SNAP_BIT,
38
+ TEXTURE_UNIT,
39
+ } from "./programs.js";
40
+ import { framebufferOf, type Open, type Target, type TargetPool } from "./targets.js";
41
+
42
+ /** What the backend calls of WEBGL_multi_draw. */
43
+ interface MultiDraw {
44
+ multiDrawElementsWEBGL(
45
+ mode: number,
46
+ counts: Int32Array,
47
+ countsOffset: number,
48
+ type: number,
49
+ offsets: Int32Array,
50
+ offsetsOffset: number,
51
+ drawCount: number,
52
+ ): void;
53
+ }
54
+
55
+ /** Last scene entry, table start, draw count, used units, then each unit's texture and sampling. */
56
+ const RUN_STRIDE = 4 + FILL_TEXTURES * 2;
57
+
58
+ /** The most masks the stencil's eight bits count. */
59
+ const STENCIL_MOST = 255;
60
+
61
+ /**
62
+ * The masks pushed on a target drawn into: what is drawn passes where its
63
+ * stencil holds `depth` of them. Its eight bits hold STENCIL_MOST: those
64
+ * pushed past that are left out, `past` of them, and their pops with them.
65
+ */
66
+ export interface Masks {
67
+ depth: number;
68
+ past: number;
69
+ }
70
+
71
+ /**
72
+ * The draws of a scene list's entries into the target bound: fills in runs,
73
+ * one multi-draw of the atlas's index ranges a run where WEBGL_multi_draw
74
+ * is, meshes and advanced text's glyphs each by a program of its own, and
75
+ * the masks' coverage into the stencil; and the view of the target drawn
76
+ * into, which each program is told.
77
+ */
78
+ export class Draws {
79
+ private readonly gl: WebGL2RenderingContext;
80
+ private readonly state: GlState;
81
+ private readonly atlas: GeometryAtlas;
82
+ private readonly gpu: GpuTextures;
83
+ private readonly pool: TargetPool;
84
+ /** The fill program, and where it takes its row, view and Flash's pixels a stage pixel. */
85
+ program!: WebGLProgram;
86
+ private rowLocation: WebGLUniformLocation | null = null;
87
+ private viewLocation: WebGLUniformLocation | null = null;
88
+ private fillUnitLocation: WebGLUniformLocation | null = null;
89
+ /**
90
+ * The fill program that blends a fill straight onto what is below it
91
+ * (drawBlended), a draw a call, and where it takes what the fill program
92
+ * does and the blend's mode, its layer's row and where the copy of what
93
+ * is below lies.
94
+ */
95
+ private blendProgram!: WebGLProgram;
96
+ private blendRowLocation: WebGLUniformLocation | null = null;
97
+ private blendViewLocation: WebGLUniformLocation | null = null;
98
+ private blendUnitLocation: WebGLUniformLocation | null = null;
99
+ private blendModeLocation: WebGLUniformLocation | null = null;
100
+ private blendLayerRowLocation: WebGLUniformLocation | null = null;
101
+ private blendBackLocation: WebGLUniformLocation | null = null;
102
+ /** WEBGL_multi_draw, which draws a run of fills alike a call (drawEntries), where there is. */
103
+ private multi: MultiDraw | null = null;
104
+ /** Avoid the dynamically indexed uniform table used in the Intel GPU hang reproducer. */
105
+ private runTable: WebGLTexture | null = null;
106
+ private runBaseLocation: WebGLUniformLocation | null = null;
107
+ private runTableHeight = 0;
108
+ private runTableMost = 0;
109
+ private tableRows = new Int32Array(RUN_MOST);
110
+ private tableCounts = new Int32Array(RUN_MOST);
111
+ private tableOffsets = new Int32Array(RUN_MOST);
112
+ private runs = new Int32Array(RUN_STRIDE * 256);
113
+ private commandStarts = new Uint32Array(256);
114
+ private prepared = false;
115
+ /** A run's draws: their index counts, their offsets in bytes, and their rows (FILL_SLOT). */
116
+ private readonly runCounts = new Int32Array(RUN_MOST);
117
+ private readonly runOffsets = new Int32Array(RUN_MOST);
118
+ private readonly runRows = new Int32Array(RUN_MOST);
119
+ /** Draws gathered by gatherRun, and the fill units they sample, a bit each. */
120
+ private runCount = 0;
121
+ private runUnits = 0;
122
+ /**
123
+ * The texture and sampling each fill unit was last given (runSlot), -1 for
124
+ * none, and the unit to take next when every one holds a texture.
125
+ */
126
+ private readonly unitIds = new Int32Array(FILL_TEXTURES).fill(-1);
127
+ private readonly unitSampling = new Int32Array(FILL_TEXTURES);
128
+ private unitNext = 0;
129
+ /** The meshes' program (MESH), and where it takes its row, view, fragments' place and mode. */
130
+ private mesh!: WebGLProgram;
131
+ private meshRowLocation: WebGLUniformLocation | null = null;
132
+ private meshViewLocation: WebGLUniformLocation | null = null;
133
+ private meshFragLocation: WebGLUniformLocation | null = null;
134
+ private meshModeLocation: WebGLUniformLocation | null = null;
135
+ /**
136
+ * The view the target drawn into gives the fill program, and where its
137
+ * fragments lie from the stage's origin (meshFragment's uFrag): told the
138
+ * mesh program as it next draws a mesh, once they change (meshStale).
139
+ */
140
+ private readonly view = new Float32Array(4);
141
+ private readonly frag = new Float32Array(4);
142
+ private meshStale = true;
143
+ /** The row and mode (REPEAT, SMOOTH) the mesh program was told last, -1 for none. */
144
+ private meshRow = -1;
145
+ private meshMode = -1;
146
+ /**
147
+ * Advanced text's program (GeometryData.glyphs), where it takes its row, view, Flash's
148
+ * pixels a stage pixel, mode and table; its vertices are in an atlas of their own.
149
+ */
150
+ private glyphProgram!: WebGLProgram;
151
+ private glyphRowLocation: WebGLUniformLocation | null = null;
152
+ private glyphViewLocation: WebGLUniformLocation | null = null;
153
+ private glyphUnitLocation: WebGLUniformLocation | null = null;
154
+ private glyphModeLocation: WebGLUniformLocation | null = null;
155
+ private glyphTableLocation: WebGLUniformLocation | null = null;
156
+ private glyphStale = true;
157
+ /** Flash's pixels a stage pixel: the target's pixels a stage pixel over those a pixel of Flash's. */
158
+ private unit = 1;
159
+ /**
160
+ * The row a fill drawn alone (without WEBGL_multi_draw) last told the fill
161
+ * program, so that it is not told again; -1 for none (forgetRow).
162
+ */
163
+ private lastRow = -1;
164
+
165
+ /** Each shader fill's fragment shader by its entry in the frame's filters, for the generation it is of. */
166
+ private readonly fillShaders = new Map<number, string | null>();
167
+ /** A shader fill's program as it is linked: its rows' unit. */
168
+ private readonly prepareFill = (program: WebGLProgram) => {
169
+ this.gl.uniform1i(this.gl.getUniformLocation(program, "uRows"), 0);
170
+ };
171
+ private fillFilters: Filters | null = null;
172
+ private fillGeneration = -1;
173
+
174
+ constructor(
175
+ gl: WebGL2RenderingContext,
176
+ state: GlState,
177
+ atlas: GeometryAtlas,
178
+ gpu: GpuTextures,
179
+ pool: TargetPool,
180
+ /** Where shader fills find the frame's filters, whose chains their draws name (SHADED). */
181
+ private readonly host: KernelHost & { filters(): Filters | null },
182
+ /** Shader fills' programs, the backend's one cache of kernels'. */
183
+ private readonly kernels: KernelPrograms,
184
+ ) {
185
+ this.gl = gl;
186
+ this.state = state;
187
+ this.atlas = atlas;
188
+ this.gpu = gpu;
189
+ this.pool = pool;
190
+ }
191
+
192
+ /** The fill, mesh and glyph programs made, as the context comes or comes back. */
193
+ setUp(textureMost: number): void {
194
+ const gl = this.gl;
195
+ this.multi = gl.getExtension("WEBGL_multi_draw") as MultiDraw | null;
196
+ try {
197
+ this.program = linkProgram(gl, fillVertex(this.multi !== null), fillFragment);
198
+ } catch (e) {
199
+ if (!this.multi) {
200
+ throw e;
201
+ }
202
+
203
+ // An extension offered whose shader directive the compiler refuses: a call a draw.
204
+ this.multi = null;
205
+ this.program = linkProgram(gl, fillVertex(false), fillFragment);
206
+ }
207
+
208
+ this.unitIds.fill(-1);
209
+ this.rowLocation = gl.getUniformLocation(this.program, "uRow");
210
+ this.viewLocation = gl.getUniformLocation(this.program, "uView");
211
+ this.state.useProgram(this.program);
212
+ gl.uniform1i(gl.getUniformLocation(this.program, "uRows"), 0);
213
+ gl.uniform1iv(
214
+ gl.getUniformLocation(this.program, "uTextures"),
215
+ Array.from({ length: FILL_TEXTURES }, (_, k) => FILL_UNIT + k),
216
+ );
217
+ this.runTable = null;
218
+ this.runTableHeight = 0;
219
+ this.runTableMost = textureMost;
220
+ this.prepared = false;
221
+ this.runBaseLocation = gl.getUniformLocation(this.program, "uRunBase");
222
+ if (this.multi) {
223
+ this.sizeRunTable(1);
224
+ gl.uniform1i(gl.getUniformLocation(this.program, "uRunTable"), RUN_UNIT);
225
+ }
226
+
227
+ this.fillUnitLocation = gl.getUniformLocation(this.program, "uUnit");
228
+ this.blendProgram = linkProgram(gl, fillVertex(false), blendFillFragment);
229
+ this.blendRowLocation = gl.getUniformLocation(this.blendProgram, "uRow");
230
+ this.blendViewLocation = gl.getUniformLocation(this.blendProgram, "uView");
231
+ this.blendUnitLocation = gl.getUniformLocation(this.blendProgram, "uUnit");
232
+ this.blendModeLocation = gl.getUniformLocation(this.blendProgram, "uMode");
233
+ this.blendLayerRowLocation = gl.getUniformLocation(this.blendProgram, "uLayerRow");
234
+ this.blendBackLocation = gl.getUniformLocation(this.blendProgram, "uBackOffset");
235
+ this.state.useProgram(this.blendProgram);
236
+ gl.uniform1i(gl.getUniformLocation(this.blendProgram, "uRows"), 0);
237
+ gl.uniform1i(gl.getUniformLocation(this.blendProgram, "uBack"), BACK_UNIT);
238
+ gl.uniform1iv(
239
+ gl.getUniformLocation(this.blendProgram, "uTextures"),
240
+ Array.from({ length: FILL_TEXTURES }, (_, k) => FILL_UNIT + k),
241
+ );
242
+ this.mesh = linkProgram(gl, meshVertex, meshFragment);
243
+ this.meshRowLocation = gl.getUniformLocation(this.mesh, "uRow");
244
+ this.meshViewLocation = gl.getUniformLocation(this.mesh, "uView");
245
+ this.meshFragLocation = gl.getUniformLocation(this.mesh, "uFrag");
246
+ this.meshModeLocation = gl.getUniformLocation(this.mesh, "uMode");
247
+ this.state.useProgram(this.mesh);
248
+ gl.uniform1i(gl.getUniformLocation(this.mesh, "uRows"), 0);
249
+ gl.uniform1i(gl.getUniformLocation(this.mesh, "uTexture"), TEXTURE_UNIT);
250
+ this.meshStale = true;
251
+ this.meshRow = -1;
252
+ this.meshMode = -1;
253
+ this.glyphProgram = linkProgram(gl, glyphsVertex, glyphsFragment);
254
+ this.glyphRowLocation = gl.getUniformLocation(this.glyphProgram, "uRow");
255
+ this.glyphViewLocation = gl.getUniformLocation(this.glyphProgram, "uView");
256
+ this.glyphUnitLocation = gl.getUniformLocation(this.glyphProgram, "uUnit");
257
+ this.glyphModeLocation = gl.getUniformLocation(this.glyphProgram, "uMode");
258
+ this.glyphTableLocation = gl.getUniformLocation(this.glyphProgram, "uTable");
259
+ this.state.useProgram(this.glyphProgram);
260
+ gl.uniform1i(gl.getUniformLocation(this.glyphProgram, "uRows"), 0);
261
+ gl.uniform1i(gl.getUniformLocation(this.glyphProgram, "uSegments"), OUTLINE_UNIT);
262
+ this.glyphStale = true;
263
+ this.lastRow = -1;
264
+ }
265
+
266
+ /** Upload before any draws, so that updating a run cannot stall earlier draws reading it. */
267
+ prepare(commands: Commands, scene: SceneList): void {
268
+ this.prepared = false;
269
+ if (!this.multi) {
270
+ return;
271
+ }
272
+
273
+ if (commands.length > this.commandStarts.length) {
274
+ this.commandStarts = new Uint32Array(commands.ops.length / 4);
275
+ }
276
+
277
+ let count = 0;
278
+ let run = 0;
279
+ const ops = commands.ops;
280
+ for (let k = 0; k < commands.length; k++) {
281
+ const at = k * 4;
282
+ const op = ops[at];
283
+ if (op !== RUN && op !== STENCIL_PUSH && op !== STENCIL_POP) {
284
+ continue;
285
+ }
286
+
287
+ const stencil = op !== RUN;
288
+ const end = ops[at + 1] + ops[at + 2];
289
+ this.commandStarts[k] = run;
290
+ // Held layers execute in a different order. Save each run's bindings independently
291
+ // of the commands executed before it.
292
+ this.unitIds.fill(-1);
293
+ this.unitNext = 0;
294
+ for (let i = ops[at + 1]; i < end; i++) {
295
+ if (scene.kind[i] !== DRAW) {
296
+ continue;
297
+ }
298
+
299
+ const place = this.atlas.places[scene.geometry[i]];
300
+ if (!place || place.glyphs || (!stencil && scene.sampler[i] & (MESH | PLANE | SHADED))) {
301
+ continue;
302
+ }
303
+
304
+ i = this.gatherRun(scene, i, end, stencil);
305
+ const needed = count + this.runCount;
306
+ if (needed > RUN_MOST * this.runTableMost) {
307
+ // An unusually large frame still draws by updating the first row a run at a time.
308
+ return;
309
+ }
310
+
311
+ if (needed > this.tableRows.length) {
312
+ const size = Math.min(
313
+ RUN_MOST * this.runTableMost,
314
+ Math.max(needed, this.tableRows.length * 2),
315
+ );
316
+ const rows = new Int32Array(size);
317
+ rows.set(this.tableRows);
318
+ this.tableRows = rows;
319
+ const counts = new Int32Array(size);
320
+ counts.set(this.tableCounts);
321
+ this.tableCounts = counts;
322
+ const offsets = new Int32Array(size);
323
+ offsets.set(this.tableOffsets);
324
+ this.tableOffsets = offsets;
325
+ }
326
+
327
+ if (run + RUN_STRIDE > this.runs.length) {
328
+ const runs = new Int32Array(this.runs.length * 2);
329
+ runs.set(this.runs);
330
+ this.runs = runs;
331
+ }
332
+
333
+ this.runs[run] = i;
334
+ this.runs[run + 1] = count;
335
+ this.runs[run + 2] = this.runCount;
336
+ this.runs[run + 3] = this.runUnits;
337
+ for (let j = 0; j < FILL_TEXTURES; j++) {
338
+ if (this.runUnits & (1 << j)) {
339
+ this.runs[run + 4 + j * 2] = this.unitIds[j];
340
+ this.runs[run + 5 + j * 2] = this.unitSampling[j];
341
+ }
342
+ }
343
+
344
+ run += RUN_STRIDE;
345
+ for (let j = 0; j < this.runCount; j++) {
346
+ this.tableRows[count] = this.runRows[j];
347
+ this.tableCounts[count] = this.runCounts[j];
348
+ this.tableOffsets[count++] = this.runOffsets[j];
349
+ }
350
+ }
351
+ }
352
+
353
+ const height = Math.ceil(count / RUN_MOST);
354
+ if (height > 0) {
355
+ this.sizeRunTable(height);
356
+ const gl = this.gl;
357
+ this.state.bindTexture(RUN_UNIT, this.runTable);
358
+ this.state.activeTexture(RUN_UNIT);
359
+ gl.texSubImage2D(
360
+ gl.TEXTURE_2D,
361
+ 0,
362
+ 0,
363
+ 0,
364
+ RUN_MOST,
365
+ height,
366
+ gl.RED_INTEGER,
367
+ gl.INT,
368
+ this.tableRows,
369
+ );
370
+ }
371
+
372
+ this.prepared = true;
373
+ }
374
+
375
+ private sizeRunTable(height: number): void {
376
+ if (height <= this.runTableHeight) {
377
+ return;
378
+ }
379
+
380
+ const gl = this.gl;
381
+ if (this.runTable) {
382
+ this.state.dropTexture(this.runTable);
383
+ gl.deleteTexture(this.runTable);
384
+ }
385
+
386
+ this.runTableHeight = Math.min(this.runTableMost, Math.max(height, this.runTableHeight * 2));
387
+ this.runTable = gl.createTexture();
388
+ this.state.bindTexture(RUN_UNIT, this.runTable);
389
+ this.state.activeTexture(RUN_UNIT);
390
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32I, RUN_MOST, this.runTableHeight);
391
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
392
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
393
+ }
394
+
395
+ /**
396
+ * Draw into `open` from here on: its framebuffer bound, its viewport its
397
+ * rectangle's size, and the stage placed on it as on the canvas, its
398
+ * rectangle's corner the origin, its rows running up on the canvas and
399
+ * down in a layer. The fill program takes the view.
400
+ */
401
+ drawInto(open: Open, target: Target): void {
402
+ const gl = this.gl;
403
+ const state = this.state;
404
+ const ox = open.ox ?? 0;
405
+ const oy = open.oy ?? 0;
406
+ state.bindFramebuffer(framebufferOf(open));
407
+ state.viewport(ox, oy, open.width, open.height);
408
+ state.useProgram(this.program);
409
+ const down = open.layer ? 1 : -1;
410
+ const view = this.view;
411
+ view[0] = (2 * target.scale) / open.width;
412
+ view[1] = (down * 2 * target.scale) / open.height;
413
+ view[2] = -1 + (2 * (target.x - open.x)) / open.width;
414
+ view[3] = -down * (1 - (2 * (target.y - open.y)) / open.height);
415
+ gl.uniform4fv(this.viewLocation, view);
416
+ // A layer's rows run down from its corner; the canvas' up from its bottom.
417
+ const frag = this.frag;
418
+ frag[0] = open.x - target.x - ox;
419
+ frag[1] = (open.layer ? open.y : open.y + open.height) - target.y - oy;
420
+ frag[2] = down;
421
+ frag[3] = target.pixel;
422
+ this.unit = target.scale / target.pixel;
423
+ gl.uniform1f(this.fillUnitLocation, this.unit);
424
+ this.meshStale = true;
425
+ this.glyphStale = true;
426
+ }
427
+
428
+ /** The row the fill program was told forgotten, so that the next fill tells it again. */
429
+ forgetRow(): void {
430
+ this.lastRow = -1;
431
+ }
432
+
433
+ /** The fill program, its vertex array and rows bound, as runs and masks draw with them. */
434
+ useFill(): void {
435
+ this.state.useProgram(this.program);
436
+ this.state.bindVertexArray(this.atlas.vertexArray);
437
+ this.state.bindTexture(0, this.gpu.rowsTexture);
438
+ }
439
+
440
+ /** Entries `first` up to `end` drawn as fills, where the masks pushed pass. */
441
+ drawRun(scene: SceneList, first: number, end: number, command: number): void {
442
+ const gl = this.gl;
443
+ // Premultiplied, as every blend mode will be.
444
+ this.useFill();
445
+ this.state.setBlend(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
446
+ this.drawEntries(scene, first, end, false, command);
447
+ }
448
+
449
+ /**
450
+ * Whether entry `i` is a flat fill (GeometryData.flat), a bitmap's quad or
451
+ * one fill of a shape, of no mesh, shader fill or text, whose texture, if
452
+ * it samples one, is there. Drawn straight onto what is below in a blend
453
+ * mode, it is as its layer would be composited, as no part of it is over
454
+ * another.
455
+ */
456
+ blendable(scene: SceneList, i: number): boolean {
457
+ if (scene.kind[i] !== DRAW || scene.sampler[i] & (MESH | PLANE | SHADED)) {
458
+ return false;
459
+ }
460
+
461
+ const place = this.atlas.places[scene.geometry[i]];
462
+ const id = scene.texture[i];
463
+ return !!place && place.flat && !place.glyphs && (id < 0 || !!this.gpu.textures[id]);
464
+ }
465
+
466
+ /**
467
+ * Entry `i`, blendable, drawn onto the target bound in blend mode `mode`,
468
+ * coloured by its layer's row `layerRow` (or -1), where the masks pushed
469
+ * pass: GL's blending computing add, screen and, where it does not
470
+ * `reads` below, multiply, the program the rest from the copy of what is
471
+ * below (Compositor.copyBelow), whose texel at a fragment is (`backX`,
472
+ * `backY`) short of it.
473
+ */
474
+ drawBlended(
475
+ scene: SceneList,
476
+ i: number,
477
+ mode: number,
478
+ reads: boolean,
479
+ layerRow: number,
480
+ backX: number,
481
+ backY: number,
482
+ ): void {
483
+ const gl = this.gl;
484
+ const state = this.state;
485
+ state.useProgram(this.blendProgram);
486
+ state.bindVertexArray(this.atlas.vertexArray);
487
+ state.bindTexture(0, this.gpu.rowsTexture);
488
+ gl.uniform4fv(this.blendViewLocation, this.view);
489
+ gl.uniform1f(this.blendUnitLocation, this.unit);
490
+ // Its pixel alone where GL's blending computes the mode (readsBelow).
491
+ gl.uniform1i(this.blendModeLocation, reads ? mode : NORMAL);
492
+ gl.uniform1i(this.blendLayerRowLocation, layerRow);
493
+ gl.uniform2i(this.blendBackLocation, backX, backY);
494
+ this.gatherRun(scene, i, i + 1, false);
495
+ if (this.runCount === 0) {
496
+ return;
497
+ }
498
+
499
+ for (let k = 0; k < FILL_TEXTURES; k++) {
500
+ if (this.runUnits & (1 << k)) {
501
+ state.bindTexture(FILL_UNIT + k, this.gpu.textures[this.unitIds[k]] as WebGLTexture);
502
+ state.bindSampler(FILL_UNIT + k, this.gpu.sampler(this.unitSampling[k]));
503
+ }
504
+ }
505
+
506
+ gl.uniform1i(this.blendRowLocation, this.runRows[0]);
507
+ setModeBlend(gl, state, reads ? -1 : mode);
508
+ gl.drawElements(gl.TRIANGLES, this.runCounts[0], gl.UNSIGNED_INT, this.runOffsets[0]);
509
+ }
510
+
511
+ /**
512
+ * Entries `first` up to `end`, a mask, pushed onto `masks` (STENCIL_PUSH)
513
+ * or popped off them (STENCIL_POP), left out past STENCIL_MOST: its
514
+ * coverage, once a pixel however its fills overlap, counted up into the
515
+ * stencil where the masks pushed before it pass, and back down where it
516
+ * and they did.
517
+ */
518
+ drawMask(
519
+ scene: SceneList,
520
+ first: number,
521
+ end: number,
522
+ push: boolean,
523
+ masks: Masks,
524
+ command: number,
525
+ ): void {
526
+ if (push ? masks.depth >= STENCIL_MOST || masks.past > 0 : masks.past > 0) {
527
+ masks.past += push ? 1 : -1;
528
+ return;
529
+ }
530
+
531
+ const gl = this.gl;
532
+ const state = this.state;
533
+ this.useFill();
534
+ state.colorMask(0);
535
+ state.stencil(gl.EQUAL, masks.depth, push ? gl.INCR : gl.DECR);
536
+ this.drawEntries(scene, first, end, true, command);
537
+ masks.depth += push ? 1 : -1;
538
+ state.colorMask(15);
539
+ this.maskBy(masks);
540
+ }
541
+
542
+ /** What is drawn from here on kept to where `masks` pass, by the stencil. */
543
+ maskBy(masks: Masks): void {
544
+ const gl = this.gl;
545
+ this.state.stencil(masks.depth > 0 ? gl.EQUAL : null, masks.depth, gl.KEEP);
546
+ }
547
+
548
+ /** The draws at `scene`'s entries from `first` up to `end`; for the stencil alone, untextured. */
549
+ drawEntries(
550
+ scene: SceneList,
551
+ first: number,
552
+ end: number,
553
+ stencil: boolean,
554
+ command: number,
555
+ ): void {
556
+ const gl = this.gl;
557
+ const state = this.state;
558
+ const places = this.atlas.places;
559
+ let run = this.prepared ? this.commandStarts[command] : 0;
560
+
561
+ // Whether the mesh or the glyph program is in use, till the next entry of neither.
562
+ let other = false;
563
+ for (let i = first; i < end; i++) {
564
+ if (scene.kind[i] !== DRAW) {
565
+ continue;
566
+ }
567
+
568
+ const place = places[scene.geometry[i]];
569
+ if (!place) {
570
+ continue;
571
+ }
572
+
573
+ // A mesh by the mesh program; in a mask, its triangles' coverage as a fill's. Glyphs by the
574
+ // glyph program, in a mask too, which covers their shapes alone.
575
+ if (place.glyphs) {
576
+ this.drawGlyphs(scene, i, place);
577
+ other = true;
578
+ continue;
579
+ }
580
+
581
+ if (!stencil && scene.sampler[i] & (MESH | PLANE)) {
582
+ other = this.drawMesh(scene, i, place) || other;
583
+ continue;
584
+ }
585
+
586
+ if (!stencil && scene.sampler[i] & SHADED) {
587
+ other = this.drawShaded(scene, i, place) || other;
588
+ continue;
589
+ }
590
+
591
+ if (other) {
592
+ this.useFill();
593
+ other = false;
594
+ }
595
+
596
+ let base = 0;
597
+ let draws: number;
598
+ let units: number;
599
+ const bindings = run + 4;
600
+ if (this.prepared) {
601
+ i = this.runs[run];
602
+ base = this.runs[run + 1];
603
+ draws = this.runs[run + 2];
604
+ units = this.runs[run + 3];
605
+ run += RUN_STRIDE;
606
+ } else {
607
+ i = this.gatherRun(scene, i, end, stencil);
608
+ draws = this.runCount;
609
+ units = this.runUnits;
610
+ }
611
+
612
+ if (draws === 0) {
613
+ continue;
614
+ }
615
+
616
+ for (let k = 0; k < FILL_TEXTURES; k++) {
617
+ if (units & (1 << k)) {
618
+ const id = this.prepared ? this.runs[bindings + k * 2] : this.unitIds[k];
619
+ const sampling = this.prepared ? this.runs[bindings + k * 2 + 1] : this.unitSampling[k];
620
+ state.bindTexture(FILL_UNIT + k, this.gpu.textures[id] as WebGLTexture);
621
+ state.bindSampler(FILL_UNIT + k, this.gpu.sampler(sampling));
622
+ }
623
+ }
624
+
625
+ if (!this.multi) {
626
+ if (this.runRows[0] !== this.lastRow) {
627
+ this.lastRow = this.runRows[0];
628
+ gl.uniform1i(this.rowLocation, this.lastRow);
629
+ }
630
+
631
+ gl.drawElements(gl.TRIANGLES, this.runCounts[0], gl.UNSIGNED_INT, this.runOffsets[0]);
632
+ continue;
633
+ }
634
+
635
+ state.bindTexture(RUN_UNIT, this.runTable);
636
+ state.bindSampler(RUN_UNIT, null);
637
+ if (!this.prepared) {
638
+ state.activeTexture(RUN_UNIT);
639
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, draws, 1, gl.RED_INTEGER, gl.INT, this.runRows);
640
+ }
641
+
642
+ gl.uniform1i(this.runBaseLocation, base);
643
+ this.multi.multiDrawElementsWEBGL(
644
+ gl.TRIANGLES,
645
+ this.prepared ? this.tableCounts : this.runCounts,
646
+ base,
647
+ gl.UNSIGNED_INT,
648
+ this.prepared ? this.tableOffsets : this.runOffsets,
649
+ base,
650
+ draws,
651
+ );
652
+ }
653
+
654
+ if (other) {
655
+ this.useFill();
656
+ }
657
+ }
658
+
659
+ /**
660
+ * The fills from entry `i` on that one call draws, as many as fit its
661
+ * textures (FILL_TEXTURES, each with its sampling) and, without
662
+ * WEBGL_multi_draw, one: their runCounts, runOffsets and runRows (FILL_SLOT),
663
+ * and the units their textures take in runUnits. The last entry gathered,
664
+ * past those whose texture is gone, which draw nothing.
665
+ */
666
+ private gatherRun(scene: SceneList, i: number, end: number, stencil: boolean): number {
667
+ const places = this.atlas.places;
668
+ const textures = this.gpu.textures;
669
+ const most = this.multi ? RUN_MOST : 1;
670
+ let draws = 0;
671
+ let last = i;
672
+ this.runUnits = 0;
673
+
674
+ for (let j = i; j < end && draws < most; j++) {
675
+ if (scene.kind[j] !== DRAW) {
676
+ continue;
677
+ }
678
+
679
+ const place = places[scene.geometry[j]];
680
+ if (!place) {
681
+ continue;
682
+ }
683
+
684
+ if (place.glyphs || (!stencil && scene.sampler[j] & (MESH | PLANE | SHADED))) {
685
+ break;
686
+ }
687
+
688
+ const id = stencil ? -1 : scene.texture[j];
689
+ let slot = 0;
690
+ if (id >= 0) {
691
+ if (!textures[id]) {
692
+ last = j;
693
+ continue;
694
+ }
695
+
696
+ const sampling = scene.sampler[j] & (SMOOTH | REPEAT | MIRROR);
697
+ slot = this.runSlot(id, sampling);
698
+ if (slot === 0) {
699
+ break;
700
+ }
701
+ }
702
+
703
+ this.runCounts[draws] = place.indices;
704
+ this.runOffsets[draws] = place.indexStart * 4;
705
+ this.runRows[draws] =
706
+ scene.row[j] | (slot << FILL_SLOT) | (scene.sampler[j] & SNAP ? SNAP_BIT : 0);
707
+ draws++;
708
+ last = j;
709
+ }
710
+
711
+ this.runCount = draws;
712
+
713
+ return last;
714
+ }
715
+
716
+ /**
717
+ * The slot (FILL_SLOT) of the unit texture `id` is sampled from so in the
718
+ * run: the one it was left on, else one the run has not taken, an empty
719
+ * one first, so that a texture stays where it is from run to run; 0 when
720
+ * the run has taken all.
721
+ */
722
+ private runSlot(id: number, sampling: number): number {
723
+ for (let k = 0; k < FILL_TEXTURES; k++) {
724
+ if (this.unitIds[k] === id && this.unitSampling[k] === sampling) {
725
+ this.runUnits |= 1 << k;
726
+ return k + 1;
727
+ }
728
+ }
729
+
730
+ let free = -1;
731
+ for (let n = 0; n < FILL_TEXTURES; n++) {
732
+ const k = (this.unitNext + n) % FILL_TEXTURES;
733
+ if (!(this.runUnits & (1 << k)) && (free < 0 || this.unitIds[k] < 0)) {
734
+ free = k;
735
+ if (this.unitIds[k] < 0) {
736
+ break;
737
+ }
738
+ }
739
+ }
740
+
741
+ if (free < 0) {
742
+ return 0;
743
+ }
744
+
745
+ this.unitIds[free] = id;
746
+ this.unitSampling[free] = sampling;
747
+ this.unitNext = (free + 1) % FILL_TEXTURES;
748
+ this.runUnits |= 1 << free;
749
+ return free + 1;
750
+ }
751
+
752
+ /**
753
+ * Entry `i`, advanced text's glyph quads (GeometryData.glyphs), drawn by the glyph
754
+ * program from its atlas, which is told the view as it changed, and its
755
+ * text's mode and table; the glyph program left in use.
756
+ */
757
+ private drawGlyphs(scene: SceneList, i: number, place: Place): void {
758
+ const gl = this.gl;
759
+ const state = this.state;
760
+ const glyphs = place.glyphs as NonNullable<Place["glyphs"]>;
761
+ state.useProgram(this.glyphProgram);
762
+ state.bindVertexArray(this.atlas.glyphArray);
763
+ if (this.glyphStale) {
764
+ gl.uniform4fv(this.glyphViewLocation, this.view);
765
+ gl.uniform1f(this.glyphUnitLocation, this.unit);
766
+ this.glyphStale = false;
767
+ }
768
+
769
+ gl.uniform1i(this.glyphRowLocation, scene.row[i]);
770
+ gl.uniform4fv(this.glyphModeLocation, glyphs.mode);
771
+ gl.uniform4fv(this.glyphTableLocation, glyphs.table);
772
+ state.bindTexture(OUTLINE_UNIT, this.gpu.outlines);
773
+ state.bindSampler(OUTLINE_UNIT, null);
774
+ gl.drawElements(gl.TRIANGLES, place.indices, gl.UNSIGNED_INT, place.indexStart * 4);
775
+ }
776
+
777
+ /**
778
+ * Entry `i`, a mesh (MESH), drawn by the mesh program, which is told the
779
+ * view as it changed, and its row and mode as they change; whether it
780
+ * was drawn, the mesh program left in use.
781
+ */
782
+ private drawMesh(scene: SceneList, i: number, place: Place): boolean {
783
+ const id = scene.texture[i];
784
+ const plane = (scene.sampler[i] & PLANE) !== 0;
785
+ const texture =
786
+ id < 0
787
+ ? null
788
+ : plane
789
+ ? (this.pool.keptLayer(id)?.texture ?? null)
790
+ : (this.gpu.textures[id] ?? null);
791
+ if (!texture) {
792
+ return false;
793
+ }
794
+
795
+ const gl = this.gl;
796
+ const state = this.state;
797
+ state.useProgram(this.mesh);
798
+ // The atlas' vertices, which a glyph draw before may have left bound for its own.
799
+ state.bindVertexArray(this.atlas.vertexArray);
800
+ if (this.meshStale) {
801
+ gl.uniform4fv(this.meshViewLocation, this.view);
802
+ gl.uniform4fv(this.meshFragLocation, this.frag);
803
+ this.meshStale = false;
804
+ }
805
+
806
+ const row = scene.row[i];
807
+ if (row !== this.meshRow) {
808
+ this.meshRow = row;
809
+ gl.uniform1i(this.meshRowLocation, row);
810
+ }
811
+
812
+ const mode = scene.sampler[i] & (REPEAT | SMOOTH | PLANE);
813
+ if (mode !== this.meshMode) {
814
+ this.meshMode = mode;
815
+ gl.uniform3i(
816
+ this.meshModeLocation,
817
+ mode & REPEAT ? 1 : 0,
818
+ mode & SMOOTH ? 1 : 0,
819
+ mode & PLANE ? 1 : 0,
820
+ );
821
+ }
822
+
823
+ state.bindTexture(TEXTURE_UNIT, texture);
824
+ // A plane's bitmap smoothed; a mesh's read texel by texel, but through a sampler without
825
+ // mipmaps, as a texture with none needs.
826
+ state.bindSampler(TEXTURE_UNIT, this.gpu.sampler(plane ? SMOOTH | CLAMP : CLAMP));
827
+ gl.drawElements(gl.TRIANGLES, place.indices, gl.UNSIGNED_INT, place.indexStart * 4);
828
+ return true;
829
+ }
830
+
831
+ /**
832
+ * Entry `i`, a shader fill (SHADED), drawn by its kernel's program, after
833
+ * the mesh's vertex shader, told the view, its row and its kernel's
834
+ * uniforms; whether it was drawn, its program left in use.
835
+ */
836
+ private drawShaded(scene: SceneList, i: number, place: Place): boolean {
837
+ const filters = this.host.filters();
838
+ const chain = scene.texture[i];
839
+ if (!filters || chain < 0 || chain >= filters.chainCount) {
840
+ return false;
841
+ }
842
+
843
+ // A kernel the GPU will not link, of more images than a program binds, or with an image
844
+ // gone, fills nothing. Its shader is found once a frame for its entry; its program is the
845
+ // cache's, linked again where another took its place.
846
+ const entry = filters.chains[chain];
847
+ const fragment = this.fillShader(filters, entry);
848
+ const kernel = fragment ? this.kernels.use(meshVertex, fragment, this.prepareFill) : null;
849
+ if (!kernel) {
850
+ return false;
851
+ }
852
+
853
+ const gl = this.gl;
854
+ this.state.bindVertexArray(this.atlas.vertexArray);
855
+ gl.uniform4fv(kernel.at("uView"), this.view);
856
+ gl.uniform4fv(kernel.at("uFrag"), this.frag);
857
+ gl.uniform1i(kernel.at("uRow"), scene.row[i]);
858
+ this.kernels.setUniforms(kernel, filters, entry, this.host);
859
+ // The mesh program is told its uniforms again after another program drew.
860
+ this.meshStale = true;
861
+ this.meshRow = -1;
862
+ this.meshMode = -1;
863
+ gl.drawElements(gl.TRIANGLES, place.indices, gl.UNSIGNED_INT, place.indexStart * 4);
864
+ return true;
865
+ }
866
+
867
+ /** The fragment shader of the shader fill at entry `i` of `filters`, made once a frame; null for none. */
868
+ private fillShader(filters: Filters, i: number): string | null {
869
+ if (this.fillFilters !== filters || this.fillGeneration !== filters.generation) {
870
+ this.fillShaders.clear();
871
+ this.fillFilters = filters;
872
+ this.fillGeneration = filters.generation;
873
+ }
874
+
875
+ let fragment = this.fillShaders.get(i);
876
+ if (fragment === undefined) {
877
+ const source = filters.kernels[i];
878
+ const images =
879
+ source && filters.kind[i] === FILL_SHADER
880
+ ? this.kernels.readImages(filters, i, this.host)
881
+ : null;
882
+ fragment = source && images && kernelImagesFit(images) ? fillKernel(source, images) : null;
883
+ this.fillShaders.set(i, fragment);
884
+ }
885
+
886
+ return fragment;
887
+ }
888
+
889
+ /** The programs and draw table made on the context, deleted. */
890
+ destroy(): void {
891
+ if (this.runTable) {
892
+ this.state.dropTexture(this.runTable);
893
+ this.gl.deleteTexture(this.runTable);
894
+ this.runTable = null;
895
+ }
896
+
897
+ const gl = this.gl;
898
+ gl.deleteProgram(this.program);
899
+ gl.deleteProgram(this.mesh);
900
+ gl.deleteProgram(this.glyphProgram);
901
+ }
902
+ }