@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.
Files changed (40) hide show
  1. package/dist/filter-plan.d.ts +5 -2
  2. package/dist/filter-plan.d.ts.map +1 -1
  3. package/dist/filter-plan.js +9 -6
  4. package/dist/filter-plan.js.map +1 -1
  5. package/dist/filter-shaders.d.ts +1 -1
  6. package/dist/filter-shaders.d.ts.map +1 -1
  7. package/dist/filter-shaders.js +3 -3
  8. package/dist/webgl/atlas.d.ts +8 -1
  9. package/dist/webgl/atlas.d.ts.map +1 -1
  10. package/dist/webgl/atlas.js +17 -2
  11. package/dist/webgl/atlas.js.map +1 -1
  12. package/dist/webgl/batches.d.ts +68 -18
  13. package/dist/webgl/batches.d.ts.map +1 -1
  14. package/dist/webgl/batches.js +252 -63
  15. package/dist/webgl/batches.js.map +1 -1
  16. package/dist/webgl/draws.d.ts +32 -11
  17. package/dist/webgl/draws.d.ts.map +1 -1
  18. package/dist/webgl/draws.js +114 -43
  19. package/dist/webgl/draws.js.map +1 -1
  20. package/dist/webgl/filters.d.ts +10 -0
  21. package/dist/webgl/filters.d.ts.map +1 -1
  22. package/dist/webgl/filters.js +50 -1
  23. package/dist/webgl/filters.js.map +1 -1
  24. package/dist/webgl/glyph-tables.d.ts +44 -0
  25. package/dist/webgl/glyph-tables.d.ts.map +1 -0
  26. package/dist/webgl/glyph-tables.js +148 -0
  27. package/dist/webgl/glyph-tables.js.map +1 -0
  28. package/dist/webgl/programs.d.ts +27 -8
  29. package/dist/webgl/programs.d.ts.map +1 -1
  30. package/dist/webgl/programs.js +49 -22
  31. package/dist/webgl/programs.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/filter-plan.ts +9 -6
  34. package/src/filter-shaders.ts +3 -3
  35. package/src/webgl/atlas.ts +23 -4
  36. package/src/webgl/batches.ts +316 -68
  37. package/src/webgl/draws.ts +129 -47
  38. package/src/webgl/filters.ts +55 -0
  39. package/src/webgl/glyph-tables.ts +179 -0
  40. package/src/webgl/programs.ts +49 -22
@@ -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, meshes and advanced text's glyphs each by a program of its own, and
76
- * the masks' coverage into the stencil; and the view of the target drawn
77
- * into, which each program is told.
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, mode and table; its vertices are in an atlas of their own.
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: a call a draw.
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
- /** Upload before any draws, so that updating a run cannot stall earlier draws reading it. */
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
- if (!this.multi) {
275
- return;
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 > RUN_MOST * this.runTableMost) {
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
- const batchStart = this.batches.indexTop;
344
- if (!stencil) {
345
- this.batches.add(this.runPlaces, this.runRows, this.runCount, this.atlas.frame);
346
- }
347
-
348
- this.runs[run + 4] = batchStart;
349
- this.runs[run + 5] = this.batches.indexTop - batchStart;
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(RUN_UNIT, this.runTable);
645
- state.bindSampler(RUN_UNIT, null);
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
- if (this.runRows[0] !== this.lastRow) {
653
- this.lastRow = this.runRows[0];
654
- gl.uniform1i(this.rowLocation, this.lastRow);
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
- gl.uniform1i(this.runBaseLocation, base);
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) and, without
688
- * WEBGL_multi_draw, one: their runCounts, runOffsets and runRows (FILL_SLOT),
689
- * and the units their textures take in runUnits. The last entry gathered,
690
- * past those whose texture is gone, which draw nothing.
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 most = this.multi ? RUN_MOST : 1;
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 mode and table; the glyph program left in use.
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
- gl.uniform1i(this.glyphRowLocation, scene.row[i]);
798
- gl.uniform4fv(this.glyphModeLocation, glyphs.mode);
799
- gl.uniform4fv(this.glyphTableLocation, glyphs.table);
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
- if (this.multi) {
920
- this.batches.destroy();
921
- }
1003
+ this.batches.destroy();
922
1004
 
923
1005
  if (this.runTable) {
924
1006
  this.state.dropTexture(this.runTable);
@@ -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
+ }