@swf2es/render 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/filter-plan.d.ts +5 -2
- package/dist/filter-plan.d.ts.map +1 -1
- package/dist/filter-plan.js +9 -6
- package/dist/filter-plan.js.map +1 -1
- package/dist/filter-shaders.d.ts +1 -1
- package/dist/filter-shaders.d.ts.map +1 -1
- package/dist/filter-shaders.js +3 -3
- package/dist/webgl/atlas.d.ts +8 -1
- package/dist/webgl/atlas.d.ts.map +1 -1
- package/dist/webgl/atlas.js +17 -2
- package/dist/webgl/atlas.js.map +1 -1
- package/dist/webgl/batches.d.ts +68 -18
- package/dist/webgl/batches.d.ts.map +1 -1
- package/dist/webgl/batches.js +252 -63
- package/dist/webgl/batches.js.map +1 -1
- package/dist/webgl/draws.d.ts +32 -11
- package/dist/webgl/draws.d.ts.map +1 -1
- package/dist/webgl/draws.js +114 -43
- package/dist/webgl/draws.js.map +1 -1
- package/dist/webgl/filters.d.ts +10 -0
- package/dist/webgl/filters.d.ts.map +1 -1
- package/dist/webgl/filters.js +50 -1
- package/dist/webgl/filters.js.map +1 -1
- package/dist/webgl/glyph-tables.d.ts +44 -0
- package/dist/webgl/glyph-tables.d.ts.map +1 -0
- package/dist/webgl/glyph-tables.js +148 -0
- package/dist/webgl/glyph-tables.js.map +1 -0
- package/dist/webgl/programs.d.ts +27 -8
- package/dist/webgl/programs.d.ts.map +1 -1
- package/dist/webgl/programs.js +49 -22
- package/dist/webgl/programs.js.map +1 -1
- package/package.json +2 -2
- package/src/filter-plan.ts +9 -6
- package/src/filter-shaders.ts +3 -3
- package/src/webgl/atlas.ts +23 -4
- package/src/webgl/batches.ts +316 -68
- package/src/webgl/draws.ts +129 -47
- package/src/webgl/filters.ts +55 -0
- package/src/webgl/glyph-tables.ts +179 -0
- package/src/webgl/programs.ts +49 -22
package/src/webgl/draws.ts
CHANGED
|
@@ -30,12 +30,14 @@ import {
|
|
|
30
30
|
fillVertex,
|
|
31
31
|
glyphsFragment,
|
|
32
32
|
glyphsVertex,
|
|
33
|
+
MERGED_UNIT,
|
|
33
34
|
meshFragment,
|
|
34
35
|
meshVertex,
|
|
35
36
|
OUTLINE_UNIT,
|
|
36
37
|
RUN_MOST,
|
|
37
38
|
RUN_UNIT,
|
|
38
39
|
SNAP_BIT,
|
|
40
|
+
TABLE_UNIT,
|
|
39
41
|
TEXTURE_UNIT,
|
|
40
42
|
} from "./programs.js";
|
|
41
43
|
import { framebufferOf, type Open, type Target, type TargetPool } from "./targets.js";
|
|
@@ -56,6 +58,15 @@ interface MultiDraw {
|
|
|
56
58
|
/** Last entry, table start, draws, units, batch start/count, then each unit's texture and sampling. */
|
|
57
59
|
const RUN_STRIDE = 6 + FILL_TEXTURES * 2;
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* The fewest fills a run copies into the merged stream (FillBatches): with
|
|
63
|
+
* WEBGL_multi_draw, whose one call already draws a run, enough for the GPU's
|
|
64
|
+
* sub-draws to cost more than the copy; without it, two, each fill
|
|
65
|
+
* otherwise a call of its own.
|
|
66
|
+
*/
|
|
67
|
+
const MERGED_LEAST_MULTI = 32;
|
|
68
|
+
const MERGED_LEAST = 2;
|
|
69
|
+
|
|
59
70
|
/** The most masks the stencil's eight bits count. */
|
|
60
71
|
const STENCIL_MOST = 255;
|
|
61
72
|
|
|
@@ -72,9 +83,10 @@ export interface Masks {
|
|
|
72
83
|
/**
|
|
73
84
|
* The draws of a scene list's entries into the target bound: fills in runs,
|
|
74
85
|
* one multi-draw of the atlas's index ranges a run where WEBGL_multi_draw
|
|
75
|
-
* is,
|
|
76
|
-
*
|
|
77
|
-
*
|
|
86
|
+
* is, else one draw of the run copied into the merged stream where its
|
|
87
|
+
* geometries are small; meshes and advanced text's glyphs each by a program
|
|
88
|
+
* of its own, and the masks' coverage into the stencil; and the view of the
|
|
89
|
+
* target drawn into, which each program is told.
|
|
78
90
|
*/
|
|
79
91
|
export class Draws {
|
|
80
92
|
private readonly gl: WebGL2RenderingContext;
|
|
@@ -103,9 +115,16 @@ export class Draws {
|
|
|
103
115
|
private blendBackLocation: WebGLUniformLocation | null = null;
|
|
104
116
|
/** WEBGL_multi_draw, which draws a run of fills alike a call (drawEntries), where there is. */
|
|
105
117
|
private multi: MultiDraw | null = null;
|
|
118
|
+
/**
|
|
119
|
+
* Whether, without WEBGL_multi_draw, runs are merged; false draws a call a
|
|
120
|
+
* fill, as a reference the merged draws are tested against (multi-draw.ts).
|
|
121
|
+
*/
|
|
122
|
+
merging = true;
|
|
106
123
|
/** Avoid the dynamically indexed uniform table used in the Intel GPU hang reproducer. */
|
|
107
124
|
private runTable: WebGLTexture | null = null;
|
|
108
125
|
private runBaseLocation: WebGLUniformLocation | null = null;
|
|
126
|
+
/** The table start the fill program was told last (uRunBase), -1 for none. */
|
|
127
|
+
private runBase = -1;
|
|
109
128
|
private runTableHeight = 0;
|
|
110
129
|
private runTableMost = 0;
|
|
111
130
|
private tableRows = new Int32Array(RUN_MOST);
|
|
@@ -148,15 +167,17 @@ export class Draws {
|
|
|
148
167
|
private meshMode = -1;
|
|
149
168
|
/**
|
|
150
169
|
* Advanced text's program (GeometryData.glyphs), where it takes its row, view, Flash's
|
|
151
|
-
* pixels a stage pixel
|
|
170
|
+
* pixels a stage pixel and its text's slot in the glyph tables (GlyphTables), and the row
|
|
171
|
+
* and slot told it last, -1 for none; its vertices are in an atlas of their own.
|
|
152
172
|
*/
|
|
153
173
|
private glyphProgram!: WebGLProgram;
|
|
154
174
|
private glyphRowLocation: WebGLUniformLocation | null = null;
|
|
155
175
|
private glyphViewLocation: WebGLUniformLocation | null = null;
|
|
156
176
|
private glyphUnitLocation: WebGLUniformLocation | null = null;
|
|
157
|
-
private glyphModeLocation: WebGLUniformLocation | null = null;
|
|
158
177
|
private glyphTableLocation: WebGLUniformLocation | null = null;
|
|
159
178
|
private glyphStale = true;
|
|
179
|
+
private glyphRow = -1;
|
|
180
|
+
private glyphTable = -1;
|
|
160
181
|
/** Flash's pixels a stage pixel: the target's pixels a stage pixel over those a pixel of Flash's. */
|
|
161
182
|
private unit = 1;
|
|
162
183
|
/**
|
|
@@ -198,15 +219,15 @@ export class Draws {
|
|
|
198
219
|
const gl = this.gl;
|
|
199
220
|
this.multi = gl.getExtension("WEBGL_multi_draw") as MultiDraw | null;
|
|
200
221
|
try {
|
|
201
|
-
this.program = linkProgram(gl, fillVertex(this.multi !== null), fillFragment);
|
|
222
|
+
this.program = linkProgram(gl, fillVertex(this.multi !== null, true), fillFragment);
|
|
202
223
|
} catch (e) {
|
|
203
224
|
if (!this.multi) {
|
|
204
225
|
throw e;
|
|
205
226
|
}
|
|
206
227
|
|
|
207
|
-
// An extension offered whose shader directive the compiler refuses:
|
|
228
|
+
// An extension offered whose shader directive the compiler refuses: runs merged alone.
|
|
208
229
|
this.multi = null;
|
|
209
|
-
this.program = linkProgram(gl, fillVertex(false), fillFragment);
|
|
230
|
+
this.program = linkProgram(gl, fillVertex(false, true), fillFragment);
|
|
210
231
|
}
|
|
211
232
|
|
|
212
233
|
this.unitIds.fill(-1);
|
|
@@ -223,8 +244,10 @@ export class Draws {
|
|
|
223
244
|
this.runTableMost = textureMost;
|
|
224
245
|
this.prepared = false;
|
|
225
246
|
this.runBaseLocation = gl.getUniformLocation(this.program, "uRunBase");
|
|
247
|
+
this.runBase = -1;
|
|
248
|
+
this.batches.setUp(textureMost);
|
|
249
|
+
gl.uniform1i(gl.getUniformLocation(this.program, "uMergedRows"), MERGED_UNIT);
|
|
226
250
|
if (this.multi) {
|
|
227
|
-
this.batches.setUp();
|
|
228
251
|
this.sizeRunTable(1);
|
|
229
252
|
gl.uniform1i(gl.getUniformLocation(this.program, "uRunTable"), RUN_UNIT);
|
|
230
253
|
}
|
|
@@ -259,22 +282,32 @@ export class Draws {
|
|
|
259
282
|
this.glyphRowLocation = gl.getUniformLocation(this.glyphProgram, "uRow");
|
|
260
283
|
this.glyphViewLocation = gl.getUniformLocation(this.glyphProgram, "uView");
|
|
261
284
|
this.glyphUnitLocation = gl.getUniformLocation(this.glyphProgram, "uUnit");
|
|
262
|
-
this.glyphModeLocation = gl.getUniformLocation(this.glyphProgram, "uMode");
|
|
263
285
|
this.glyphTableLocation = gl.getUniformLocation(this.glyphProgram, "uTable");
|
|
264
286
|
this.state.useProgram(this.glyphProgram);
|
|
265
287
|
gl.uniform1i(gl.getUniformLocation(this.glyphProgram, "uRows"), 0);
|
|
266
288
|
gl.uniform1i(gl.getUniformLocation(this.glyphProgram, "uSegments"), OUTLINE_UNIT);
|
|
289
|
+
gl.uniform1i(gl.getUniformLocation(this.glyphProgram, "uTables"), TABLE_UNIT);
|
|
267
290
|
this.glyphStale = true;
|
|
291
|
+
this.glyphRow = -1;
|
|
292
|
+
this.glyphTable = -1;
|
|
268
293
|
this.lastRow = -1;
|
|
269
294
|
}
|
|
270
295
|
|
|
271
|
-
/**
|
|
296
|
+
/**
|
|
297
|
+
* Upload before any draws, so that updating a run cannot stall earlier
|
|
298
|
+
* draws reading it: the run table, with WEBGL_multi_draw, and the merged
|
|
299
|
+
* stream and its rows.
|
|
300
|
+
*/
|
|
272
301
|
prepare(commands: Commands, scene: SceneList): void {
|
|
273
302
|
this.prepared = false;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
303
|
+
const multi = this.multi !== null;
|
|
304
|
+
// Without a multi-draw, rows are told by uniform or merged; the table is not read.
|
|
305
|
+
const tableMost = multi ? RUN_MOST * this.runTableMost : Number.POSITIVE_INFINITY;
|
|
306
|
+
const least = multi
|
|
307
|
+
? MERGED_LEAST_MULTI
|
|
308
|
+
: this.merging
|
|
309
|
+
? MERGED_LEAST
|
|
310
|
+
: Number.POSITIVE_INFINITY;
|
|
278
311
|
this.batches.reset();
|
|
279
312
|
if (commands.length > this.commandStarts.length) {
|
|
280
313
|
this.commandStarts = new Uint32Array(commands.ops.length / 4);
|
|
@@ -309,16 +342,13 @@ export class Draws {
|
|
|
309
342
|
|
|
310
343
|
i = this.gatherRun(scene, i, end, stencil);
|
|
311
344
|
const needed = count + this.runCount;
|
|
312
|
-
if (needed >
|
|
345
|
+
if (needed > tableMost) {
|
|
313
346
|
// An unusually large frame still draws by updating the first row a run at a time.
|
|
314
347
|
return;
|
|
315
348
|
}
|
|
316
349
|
|
|
317
350
|
if (needed > this.tableRows.length) {
|
|
318
|
-
const size = Math.min(
|
|
319
|
-
RUN_MOST * this.runTableMost,
|
|
320
|
-
Math.max(needed, this.tableRows.length * 2),
|
|
321
|
-
);
|
|
351
|
+
const size = Math.min(tableMost, Math.max(needed, this.tableRows.length * 2));
|
|
322
352
|
const rows = new Int32Array(size);
|
|
323
353
|
rows.set(this.tableRows);
|
|
324
354
|
this.tableRows = rows;
|
|
@@ -340,13 +370,21 @@ export class Draws {
|
|
|
340
370
|
this.runs[run + 1] = count;
|
|
341
371
|
this.runs[run + 2] = this.runCount;
|
|
342
372
|
this.runs[run + 3] = this.runUnits;
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
373
|
+
// A mask's fills merged too where each would be a call.
|
|
374
|
+
const segment =
|
|
375
|
+
!stencil || !multi
|
|
376
|
+
? this.batches.add(
|
|
377
|
+
this.runPlaces,
|
|
378
|
+
this.runRows,
|
|
379
|
+
this.runCount,
|
|
380
|
+
this.atlas.frame,
|
|
381
|
+
least,
|
|
382
|
+
!multi,
|
|
383
|
+
)
|
|
384
|
+
: null;
|
|
385
|
+
|
|
386
|
+
this.runs[run + 4] = segment ? segment.indexStart : 0;
|
|
387
|
+
this.runs[run + 5] = segment ? segment.indices : 0;
|
|
350
388
|
for (let j = 0; j < FILL_TEXTURES; j++) {
|
|
351
389
|
if (this.runUnits & (1 << j)) {
|
|
352
390
|
this.runs[run + 6 + j * 2] = this.unitIds[j];
|
|
@@ -364,7 +402,7 @@ export class Draws {
|
|
|
364
402
|
}
|
|
365
403
|
|
|
366
404
|
const height = Math.ceil(count / RUN_MOST);
|
|
367
|
-
if (height > 0) {
|
|
405
|
+
if (multi && height > 0) {
|
|
368
406
|
this.sizeRunTable(height);
|
|
369
407
|
const gl = this.gl;
|
|
370
408
|
this.state.bindTexture(RUN_UNIT, this.runTable);
|
|
@@ -641,20 +679,27 @@ export class Draws {
|
|
|
641
679
|
|
|
642
680
|
if (batchCount > 0) {
|
|
643
681
|
state.bindVertexArray(this.batches.vertexArray);
|
|
644
|
-
state.bindTexture(
|
|
645
|
-
state.bindSampler(
|
|
682
|
+
state.bindTexture(MERGED_UNIT, this.batches.rowsTexture);
|
|
683
|
+
state.bindSampler(MERGED_UNIT, null);
|
|
646
684
|
gl.drawElements(gl.TRIANGLES, batchCount, gl.UNSIGNED_INT, batchStart * 4);
|
|
647
685
|
continue;
|
|
648
686
|
}
|
|
649
687
|
|
|
650
688
|
state.bindVertexArray(this.atlas.vertexArray);
|
|
651
689
|
if (!this.multi) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
690
|
+
// A run not merged: a call a fill, its row told as it changes.
|
|
691
|
+
const rows = this.prepared ? this.tableRows : this.runRows;
|
|
692
|
+
const counts = this.prepared ? this.tableCounts : this.runCounts;
|
|
693
|
+
const offsets = this.prepared ? this.tableOffsets : this.runOffsets;
|
|
694
|
+
for (let k = base; k < base + draws; k++) {
|
|
695
|
+
if (rows[k] !== this.lastRow) {
|
|
696
|
+
this.lastRow = rows[k];
|
|
697
|
+
gl.uniform1i(this.rowLocation, this.lastRow);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
gl.drawElements(gl.TRIANGLES, counts[k], gl.UNSIGNED_INT, offsets[k]);
|
|
655
701
|
}
|
|
656
702
|
|
|
657
|
-
gl.drawElements(gl.TRIANGLES, this.runCounts[0], gl.UNSIGNED_INT, this.runOffsets[0]);
|
|
658
703
|
continue;
|
|
659
704
|
}
|
|
660
705
|
|
|
@@ -665,7 +710,7 @@ export class Draws {
|
|
|
665
710
|
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, draws, 1, gl.RED_INTEGER, gl.INT, this.runRows);
|
|
666
711
|
}
|
|
667
712
|
|
|
668
|
-
|
|
713
|
+
this.tellRunBase(base);
|
|
669
714
|
this.multi.multiDrawElementsWEBGL(
|
|
670
715
|
gl.TRIANGLES,
|
|
671
716
|
this.prepared ? this.tableCounts : this.runCounts,
|
|
@@ -682,17 +727,34 @@ export class Draws {
|
|
|
682
727
|
}
|
|
683
728
|
}
|
|
684
729
|
|
|
730
|
+
/** The fill program told where its run starts in the run table, if it was told another. */
|
|
731
|
+
private tellRunBase(base: number): void {
|
|
732
|
+
if (base !== this.runBase) {
|
|
733
|
+
this.runBase = base;
|
|
734
|
+
this.gl.uniform1i(this.runBaseLocation, base);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
685
738
|
/**
|
|
686
739
|
* The fills from entry `i` on that one call draws, as many as fit its
|
|
687
|
-
* textures (FILL_TEXTURES, each with its sampling)
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
*
|
|
740
|
+
* textures (FILL_TEXTURES, each with its sampling), RUN_MOST at most:
|
|
741
|
+
* their runCounts, runOffsets and runRows (FILL_SLOT), and the units their
|
|
742
|
+
* textures take in runUnits. Without WEBGL_multi_draw, a run is of
|
|
743
|
+
* geometries too large to merge (Place.batch), of geometries added this
|
|
744
|
+
* frame, or of stable ones alone, so that a stable run's merged copy is
|
|
745
|
+
* drawn again while a part of it changes from frame to frame; and without
|
|
746
|
+
* merging each fill is a run. The last entry gathered, past those whose
|
|
747
|
+
* texture is gone, which draw nothing.
|
|
691
748
|
*/
|
|
692
749
|
private gatherRun(scene: SceneList, i: number, end: number, stencil: boolean): number {
|
|
693
750
|
const places = this.atlas.places;
|
|
694
751
|
const textures = this.gpu.textures;
|
|
695
|
-
const
|
|
752
|
+
const single = !this.multi;
|
|
753
|
+
const most = this.multi || this.merging ? RUN_MOST : 1;
|
|
754
|
+
const frame = this.atlas.frame;
|
|
755
|
+
// Without a multi-draw, what the run is of: geometries too large to merge, geometries added
|
|
756
|
+
// this frame, or stable ones, whose merged copy later frames draw again.
|
|
757
|
+
let kind = -1;
|
|
696
758
|
let draws = 0;
|
|
697
759
|
let last = i;
|
|
698
760
|
this.runUnits = 0;
|
|
@@ -711,6 +773,15 @@ export class Draws {
|
|
|
711
773
|
break;
|
|
712
774
|
}
|
|
713
775
|
|
|
776
|
+
if (single) {
|
|
777
|
+
const of = !place.batch ? 0 : place.batch.frame === frame ? 1 : 2;
|
|
778
|
+
if (draws === 0) {
|
|
779
|
+
kind = of;
|
|
780
|
+
} else if (of !== kind) {
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
714
785
|
const id = stencil ? -1 : scene.texture[j];
|
|
715
786
|
let slot = 0;
|
|
716
787
|
if (id >= 0) {
|
|
@@ -780,7 +851,8 @@ export class Draws {
|
|
|
780
851
|
/**
|
|
781
852
|
* Entry `i`, advanced text's glyph quads (GeometryData.glyphs), drawn by the glyph
|
|
782
853
|
* program from its atlas, which is told the view as it changed, and its
|
|
783
|
-
* text's
|
|
854
|
+
* row and its text's slot in the glyph tables as they change; the glyph
|
|
855
|
+
* program left in use.
|
|
784
856
|
*/
|
|
785
857
|
private drawGlyphs(scene: SceneList, i: number, place: Place): void {
|
|
786
858
|
const gl = this.gl;
|
|
@@ -794,11 +866,23 @@ export class Draws {
|
|
|
794
866
|
this.glyphStale = false;
|
|
795
867
|
}
|
|
796
868
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
869
|
+
const row = scene.row[i];
|
|
870
|
+
if (row !== this.glyphRow) {
|
|
871
|
+
gl.uniform1i(this.glyphRowLocation, row);
|
|
872
|
+
this.glyphRow = row;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const tables = this.atlas.tables;
|
|
876
|
+
const slot = glyphs.slot >= 0 ? glyphs.slot : tables.scratch(glyphs.mode, glyphs.table);
|
|
877
|
+
if (slot !== this.glyphTable) {
|
|
878
|
+
gl.uniform1i(this.glyphTableLocation, slot);
|
|
879
|
+
this.glyphTable = slot;
|
|
880
|
+
}
|
|
881
|
+
|
|
800
882
|
state.bindTexture(OUTLINE_UNIT, this.gpu.outlines);
|
|
801
883
|
state.bindSampler(OUTLINE_UNIT, null);
|
|
884
|
+
state.bindTexture(TABLE_UNIT, tables.texture);
|
|
885
|
+
state.bindSampler(TABLE_UNIT, null);
|
|
802
886
|
gl.drawElements(gl.TRIANGLES, place.indices, gl.UNSIGNED_INT, place.indexStart * 4);
|
|
803
887
|
}
|
|
804
888
|
|
|
@@ -916,9 +1000,7 @@ export class Draws {
|
|
|
916
1000
|
|
|
917
1001
|
/** The programs and draw table made on the context, deleted. */
|
|
918
1002
|
destroy(): void {
|
|
919
|
-
|
|
920
|
-
this.batches.destroy();
|
|
921
|
-
}
|
|
1003
|
+
this.batches.destroy();
|
|
922
1004
|
|
|
923
1005
|
if (this.runTable) {
|
|
924
1006
|
this.state.dropTexture(this.runTable);
|
package/src/webgl/filters.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
CONVOLUTION_PASS,
|
|
6
6
|
DISPLACE_PASS,
|
|
7
7
|
FilterPlan,
|
|
8
|
+
FLOAT_LINE,
|
|
8
9
|
FLOATS,
|
|
9
10
|
GLOW_PASS,
|
|
10
11
|
GRADIENT_PASS,
|
|
@@ -59,6 +60,8 @@ export interface FilterHost<T extends FilterTarget> {
|
|
|
59
60
|
const INPUT_UNIT = 4;
|
|
60
61
|
const BLURRED_UNIT = 5;
|
|
61
62
|
const EXTRA_UNIT = 6;
|
|
63
|
+
/** A pass's line of floats (FLOAT_LINE): a convolution's weights, which reads no gradient nor map. */
|
|
64
|
+
const LINE_UNIT = EXTRA_UNIT;
|
|
62
65
|
|
|
63
66
|
/**
|
|
64
67
|
* A triangle over the output's frame, the viewport, reading its input
|
|
@@ -146,6 +149,10 @@ export class FilterRunner<T extends FilterTarget> {
|
|
|
146
149
|
private readonly hasTexture = (id: number) => id >= 0 && this.host.texture(id) !== null;
|
|
147
150
|
/** The target's pixels a unit of its own, for the chain running. */
|
|
148
151
|
private resolution = 1;
|
|
152
|
+
/** The texture of a pass's line of floats (FLOAT_LINE), its width, and the floats' bits it holds. */
|
|
153
|
+
private line: WebGLTexture | null = null;
|
|
154
|
+
private lineWidth = 0;
|
|
155
|
+
private lineBits = new Uint32Array(0);
|
|
149
156
|
|
|
150
157
|
constructor(
|
|
151
158
|
gl: WebGL2RenderingContext,
|
|
@@ -356,6 +363,10 @@ export class FilterRunner<T extends FilterTarget> {
|
|
|
356
363
|
// As many as KERNEL_IMAGES, which the plan holds a kernel to.
|
|
357
364
|
this.kernels.bindImage(location, this.samplers++, values[at], this.host);
|
|
358
365
|
break;
|
|
366
|
+
case FLOAT_LINE:
|
|
367
|
+
gl.uniform1i(location, LINE_UNIT);
|
|
368
|
+
this.bindLine(values, at, count);
|
|
369
|
+
break;
|
|
359
370
|
case FLOATS:
|
|
360
371
|
this.set(
|
|
361
372
|
pass,
|
|
@@ -369,6 +380,45 @@ export class FilterRunner<T extends FilterTarget> {
|
|
|
369
380
|
}
|
|
370
381
|
}
|
|
371
382
|
|
|
383
|
+
/**
|
|
384
|
+
* `count` of `values` from `at` bound on LINE_UNIT as a line of floats,
|
|
385
|
+
* uploaded where they are not the ones it holds: a frame's filters alike
|
|
386
|
+
* take the same.
|
|
387
|
+
*/
|
|
388
|
+
private bindLine(values: Float32Array, at: number, count: number): void {
|
|
389
|
+
const gl = this.gl;
|
|
390
|
+
const bits = new Uint32Array(values.buffer, values.byteOffset + at * 4, count);
|
|
391
|
+
let same = this.line !== null && count === this.lineBits.length;
|
|
392
|
+
for (let k = 0; same && k < count; k++) {
|
|
393
|
+
same = bits[k] === this.lineBits[k];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (!same) {
|
|
397
|
+
if (!this.line || count > this.lineWidth) {
|
|
398
|
+
if (this.line) {
|
|
399
|
+
this.state.dropTexture(this.line);
|
|
400
|
+
gl.deleteTexture(this.line);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
this.line = gl.createTexture() as WebGLTexture;
|
|
404
|
+
this.lineWidth = count;
|
|
405
|
+
this.state.bindTexture(LINE_UNIT, this.line);
|
|
406
|
+
this.state.activeTexture(LINE_UNIT);
|
|
407
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32F, count, 1);
|
|
408
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
409
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
this.state.bindTexture(LINE_UNIT, this.line);
|
|
413
|
+
this.state.activeTexture(LINE_UNIT);
|
|
414
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, count, 1, gl.RED, gl.FLOAT, values, at);
|
|
415
|
+
this.lineBits = bits.slice();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
this.state.bindTexture(LINE_UNIT, this.line);
|
|
419
|
+
this.state.bindSampler(LINE_UNIT, null);
|
|
420
|
+
}
|
|
421
|
+
|
|
372
422
|
/** The pass of program `id` (filter-plan.ts), in use. */
|
|
373
423
|
private program(id: number): Pass {
|
|
374
424
|
switch (id) {
|
|
@@ -498,5 +548,10 @@ export class FilterRunner<T extends FilterTarget> {
|
|
|
498
548
|
}
|
|
499
549
|
|
|
500
550
|
this.passes.clear();
|
|
551
|
+
if (this.line) {
|
|
552
|
+
this.state.dropTexture(this.line);
|
|
553
|
+
this.gl.deleteTexture(this.line);
|
|
554
|
+
this.line = null;
|
|
555
|
+
}
|
|
501
556
|
}
|
|
502
557
|
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { GlState } from "@swf2es/context3d/device";
|
|
2
|
+
import { TABLE_ENTRIES, TABLE_LINE, TABLE_SLOT, TABLE_UNIT } from "./programs.js";
|
|
3
|
+
|
|
4
|
+
/** Floats a slot: its texels' four each. */
|
|
5
|
+
const SLOT_FLOATS = TABLE_SLOT * 4;
|
|
6
|
+
/** Slots a line of the texture. */
|
|
7
|
+
const SLOTS_A_LINE = TABLE_LINE / TABLE_SLOT;
|
|
8
|
+
/** Floats a text's mode and table take of a slot, compared and hashed. */
|
|
9
|
+
const USED_FLOATS = 4 + TABLE_ENTRIES * 4;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Advanced text's modes and CSM cutoff tables (GeometryData.glyphs), each
|
|
13
|
+
* distinct one in a slot of a float texture the glyph program reads by
|
|
14
|
+
* texelFetch: the mode in a slot's first texel, its table's entries after
|
|
15
|
+
* it. They were a uniform array the shader indexed by each glyph's table
|
|
16
|
+
* start, the access that hung Intel's GPUs in the fill program's run table
|
|
17
|
+
* (#717), and uploaded whole for every text drawn. Texts alike share a slot; slots are only
|
|
18
|
+
* added, between the glyph atlas' repacks, which take them all afresh
|
|
19
|
+
* (reset), or once the texture is as large as it may be (full). Slot 0 is
|
|
20
|
+
* left for a text past the room (scratch), uploaded as it is drawn.
|
|
21
|
+
*/
|
|
22
|
+
export class GlyphTables {
|
|
23
|
+
private readonly gl: WebGL2RenderingContext;
|
|
24
|
+
private readonly state: GlState;
|
|
25
|
+
texture: WebGLTexture | null = null;
|
|
26
|
+
/** The slots' floats as uploaded and to upload, and the texture's lines. */
|
|
27
|
+
private data = new Float32Array(SLOT_FLOATS * SLOTS_A_LINE);
|
|
28
|
+
private words = new Uint32Array(this.data.buffer);
|
|
29
|
+
private lines = 0;
|
|
30
|
+
private lineMost = 1;
|
|
31
|
+
/** Slots taken, the first not yet uploaded, and whether the texture's lines are all taken. */
|
|
32
|
+
private count = 1;
|
|
33
|
+
private uploaded = 1;
|
|
34
|
+
full = false;
|
|
35
|
+
/** The first slot by its contents' hash, then the next of that hash, -1 for none. */
|
|
36
|
+
private readonly byHash = new Map<number, number>();
|
|
37
|
+
private next = new Int32Array(SLOTS_A_LINE);
|
|
38
|
+
|
|
39
|
+
constructor(gl: WebGL2RenderingContext, state: GlState) {
|
|
40
|
+
this.gl = gl;
|
|
41
|
+
this.state = state;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** No texture yet, nor slots, as the context comes or comes back. */
|
|
45
|
+
setUp(): void {
|
|
46
|
+
this.texture = null;
|
|
47
|
+
this.lines = 0;
|
|
48
|
+
this.lineMost = this.gl.getParameter(this.gl.MAX_TEXTURE_SIZE) as number;
|
|
49
|
+
this.reset();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Every slot but the scratch let go of, for the texts to take theirs again. */
|
|
53
|
+
reset(): void {
|
|
54
|
+
this.count = 1;
|
|
55
|
+
this.uploaded = 1;
|
|
56
|
+
this.full = false;
|
|
57
|
+
this.byHash.clear();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The slot holding `mode` and `table`, taken if none does; -1 where the room is full. */
|
|
61
|
+
slot(mode: Float32Array, table: Float32Array): number {
|
|
62
|
+
let hash = 0x811c9dc5;
|
|
63
|
+
const modeWords = new Uint32Array(mode.buffer, mode.byteOffset, 4);
|
|
64
|
+
const tableWords = new Uint32Array(table.buffer, table.byteOffset, TABLE_ENTRIES * 4);
|
|
65
|
+
for (let i = 0; i < 4; i++) {
|
|
66
|
+
hash = Math.imul(hash ^ modeWords[i], 0x01000193);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < tableWords.length; i++) {
|
|
70
|
+
hash = Math.imul(hash ^ tableWords[i], 0x01000193);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const words = this.words;
|
|
74
|
+
let s = this.byHash.get(hash) ?? -1;
|
|
75
|
+
for (; s >= 0; s = this.next[s]) {
|
|
76
|
+
const at = s * SLOT_FLOATS;
|
|
77
|
+
let k = 0;
|
|
78
|
+
while (k < 4 && words[at + k] === modeWords[k]) {
|
|
79
|
+
k++;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
while (k < USED_FLOATS && words[at + k] === tableWords[k - 4]) {
|
|
83
|
+
k++;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (k === USED_FLOATS) {
|
|
87
|
+
return s;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.count >= this.lineMost * SLOTS_A_LINE) {
|
|
92
|
+
this.full = true;
|
|
93
|
+
return -1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
s = this.count++;
|
|
97
|
+
if (this.count * SLOT_FLOATS > this.data.length) {
|
|
98
|
+
const grown = new Float32Array(this.data.length * 2);
|
|
99
|
+
grown.set(this.data);
|
|
100
|
+
this.data = grown;
|
|
101
|
+
this.words = new Uint32Array(grown.buffer);
|
|
102
|
+
const next = new Int32Array(this.next.length * 2);
|
|
103
|
+
next.set(this.next);
|
|
104
|
+
this.next = next;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.write(s, mode, table);
|
|
108
|
+
this.next[s] = this.byHash.get(hash) ?? -1;
|
|
109
|
+
this.byHash.set(hash, s);
|
|
110
|
+
return s;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The slots taken since uploaded: into a texture grown to hold them, all of them if grown. */
|
|
114
|
+
upload(): void {
|
|
115
|
+
const gl = this.gl;
|
|
116
|
+
if (this.uploaded === this.count || gl.isContextLost()) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const lines = Math.ceil(this.count / SLOTS_A_LINE);
|
|
121
|
+
let from = Math.floor(this.uploaded / SLOTS_A_LINE);
|
|
122
|
+
if (!this.texture || lines > this.lines) {
|
|
123
|
+
if (this.texture) {
|
|
124
|
+
this.state.dropTexture(this.texture);
|
|
125
|
+
gl.deleteTexture(this.texture);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.lines = Math.min(this.lineMost, Math.max(lines, this.lines * 2));
|
|
129
|
+
this.texture = gl.createTexture() as WebGLTexture;
|
|
130
|
+
this.state.bindTexture(TABLE_UNIT, this.texture);
|
|
131
|
+
this.state.activeTexture(TABLE_UNIT);
|
|
132
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, TABLE_LINE, this.lines);
|
|
133
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
134
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
135
|
+
from = 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.state.bindTexture(TABLE_UNIT, this.texture);
|
|
139
|
+
this.state.activeTexture(TABLE_UNIT);
|
|
140
|
+
gl.texSubImage2D(
|
|
141
|
+
gl.TEXTURE_2D,
|
|
142
|
+
0,
|
|
143
|
+
0,
|
|
144
|
+
from,
|
|
145
|
+
TABLE_LINE,
|
|
146
|
+
lines - from,
|
|
147
|
+
gl.RGBA,
|
|
148
|
+
gl.FLOAT,
|
|
149
|
+
this.data,
|
|
150
|
+
from * TABLE_LINE * 4,
|
|
151
|
+
);
|
|
152
|
+
this.uploaded = this.count;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The scratch slot made to hold `mode` and `table` now, for a text drawn next past the room. */
|
|
156
|
+
scratch(mode: Float32Array, table: Float32Array): number {
|
|
157
|
+
this.write(0, mode, table);
|
|
158
|
+
const gl = this.gl;
|
|
159
|
+
this.state.bindTexture(TABLE_UNIT, this.texture);
|
|
160
|
+
this.state.activeTexture(TABLE_UNIT);
|
|
161
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, TABLE_SLOT, 1, gl.RGBA, gl.FLOAT, this.data, 0);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private write(s: number, mode: Float32Array, table: Float32Array): void {
|
|
166
|
+
const at = s * SLOT_FLOATS;
|
|
167
|
+
this.data.set(mode.subarray(0, 4), at);
|
|
168
|
+
this.data.set(table.subarray(0, TABLE_ENTRIES * 4), at + 4);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** The texture made on the context, deleted. */
|
|
172
|
+
destroy(): void {
|
|
173
|
+
if (this.texture) {
|
|
174
|
+
this.state.dropTexture(this.texture);
|
|
175
|
+
this.gl.deleteTexture(this.texture);
|
|
176
|
+
this.texture = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|