@swf2es/render 0.1.20 → 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.
@@ -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
+ }
@@ -37,6 +37,13 @@ export const DRAW_AT = 8;
37
37
 
38
38
  /** The most cutoff table entries a text's draw takes, as the player's text/saffron.ts builds them. */
39
39
  export const TABLE_ENTRIES = 48;
40
+ /**
41
+ * Texels a text's slot in the glyph tables' texture (GlyphTables): its mode,
42
+ * then its table's entries; and texels a line of it. Powers of two, which
43
+ * the vertex shader splits a slot's texel into column and line with.
44
+ */
45
+ export const TABLE_SLOT = 64;
46
+ export const TABLE_LINE = 1024;
40
47
  /** Segments a row of the outlines' texture, as the player's text/saffron.ts stores them. */
41
48
  export const OUTLINE_ROW = 1024;
42
49
 
@@ -74,6 +81,8 @@ export const RUN_UNIT = FILL_UNIT + FILL_TEXTURES;
74
81
  export const MERGED_UNIT = RUN_UNIT + 1;
75
82
  /** The merged fills' rows a line of their integer texture: a power of two, as for RUN_MOST. */
76
83
  export const MERGED_LINE = 1024;
84
+ /** The glyph tables' unit (GlyphTables), past the merged fills' rows. */
85
+ export const TABLE_UNIT = MERGED_UNIT + 1;
77
86
 
78
87
  /**
79
88
  * Places a vertex by its row's transform, on a quarter of a pixel of
@@ -299,11 +308,14 @@ void main(void) {
299
308
  * pixels to Flash's, uUnit of them a stage pixel, on their grid from the
300
309
  * stage's origin, a BitmapData's in a draw. A glyph's pen stands where its
301
310
  * text's origin, at a whole pixel, puts it, snapped as its grid fits it
302
- * (uMode: gridFitType 0 none, 1 pixel, 2 subpixel, thickness, sharpness),
303
- * and its quad reaches out past its outline by the furthest cutoff. Its
304
- * colour is drawn under the row's colour transform, light or dark as adl
305
- * chooses its table. aOrigin: the pen, and its light and dark tables, each
306
- * its first entry plus 64 times its entries; aGlyph: a corner of its
311
+ * (its mode: gridFitType 0 none, 1 pixel, 2 subpixel, thickness,
312
+ * sharpness), and its quad reaches out past its outline by the furthest
313
+ * cutoff. Its colour is drawn under the row's colour transform, light or
314
+ * dark as adl chooses its table. The text's mode and tables are in its slot
315
+ * of uTables (uTable; GlyphTables), read by texelFetch: not a uniform array
316
+ * indexed by each glyph's table start, the access that hung Intel's GPUs
317
+ * in the fill program's run table. aOrigin: the pen, and its light and dark tables,
318
+ * each its first entry plus 64 times its entries; aGlyph: a corner of its
307
319
  * outline's bounds and the way out from it; aRun: its segments, first and
308
320
  * count, the pixels of a unit of its outline, and its size.
309
321
  */
@@ -318,8 +330,8 @@ uniform highp sampler2D uRows;
318
330
  uniform int uRow;
319
331
  uniform vec4 uView;
320
332
  uniform float uUnit;
321
- uniform vec4 uMode;
322
- uniform vec4 uTable[${TABLE_ENTRIES}];
333
+ uniform highp sampler2D uTables;
334
+ uniform int uTable;
323
335
  out vec2 vPixel;
324
336
  flat out vec2 vPen;
325
337
  flat out vec4 vToGlyph;
@@ -327,10 +339,16 @@ flat out vec4 vToPixel;
327
339
  flat out vec4 vCut;
328
340
  flat out vec4 vShown;
329
341
 
342
+ // Texel i of the text's slot: its mode, then its tables' entries.
343
+ vec4 slot(int i) {
344
+ int at = uTable * ${TABLE_SLOT} + i;
345
+ return texelFetch(uTables, ivec2(at & ${TABLE_LINE - 1}, at >> ${Math.log2(TABLE_LINE)}), 0);
346
+ }
347
+
330
348
  // The cutoffs at ppem by the table of entries start to start + count, as text/csm.ts' cutoffs.
331
- vec2 cutoffs(int start, int count, float ppem) {
332
- vec4 first = uTable[start];
333
- vec4 last = uTable[start + count - 1];
349
+ vec2 cutoffs(vec4 mode, int start, int count, float ppem) {
350
+ vec4 first = slot(1 + start);
351
+ vec4 last = slot(start + count);
334
352
  vec2 c = first.yz;
335
353
  if (ppem >= last.x) {
336
354
  c = last.yz;
@@ -340,9 +358,9 @@ vec2 cutoffs(int start, int count, float ppem) {
340
358
  break;
341
359
  }
342
360
 
343
- vec4 b = uTable[start + i];
361
+ vec4 b = slot(1 + start + i);
344
362
  if (ppem <= b.x) {
345
- vec4 a = uTable[start + i - 1];
363
+ vec4 a = slot(start + i);
346
364
  c = mix(a.yz, b.yz, (ppem - a.x) / (b.x - a.x));
347
365
  break;
348
366
  }
@@ -350,8 +368,8 @@ vec2 cutoffs(int start, int count, float ppem) {
350
368
  }
351
369
 
352
370
  float unit = 0.0001 * ppem;
353
- float t = clamp(uMode.y, -200.0, 200.0);
354
- float s = clamp(uMode.z, -400.0, 400.0);
371
+ float t = clamp(mode.y, -200.0, 200.0);
372
+ float s = clamp(mode.z, -400.0, 400.0);
355
373
  c -= unit * vec2(t + s * 0.5, t - s * 0.5);
356
374
  if (c.x < c.y) {
357
375
  c = vec2((c.x + c.y) * 0.5);
@@ -366,6 +384,7 @@ void main(void) {
366
384
  vec4 t = texelFetch(uRows, at + ivec2(1, 0), 0);
367
385
  vec4 mul = texelFetch(uRows, at + ivec2(2, 0), 0);
368
386
  vec4 add = texelFetch(uRows, at + ivec2(3, 0), 0);
387
+ vec4 mode = slot(0);
369
388
  // The text's pixels to Flash's.
370
389
  mat2 linear = mat2(m.xy, m.zw) * uUnit;
371
390
  vec2 origin = t.xy * uUnit;
@@ -373,9 +392,9 @@ void main(void) {
373
392
  float across = max(abs(linear[0][1]), abs(linear[1][0]));
374
393
  if (across > max(abs(linear[0][0]), abs(linear[1][1])) / 65536.0) {
375
394
  // Turned or skewed: no grid.
376
- } else if (uMode.x == 1.0) {
395
+ } else if (mode.x == 1.0) {
377
396
  pen = vec2(roundEven(pen.x * 8.0) / 8.0 + 0.5, roundEven(pen.y));
378
- } else if (uMode.x == 2.0) {
397
+ } else if (mode.x == 2.0) {
379
398
  pen = vec2(roundEven(pen.x * 4.0) / 4.0 + 0.5, roundEven(pen.y));
380
399
  } else {
381
400
  pen = roundEven(pen * 4.0) / 4.0 + vec2(0.5, 0.0);
@@ -386,7 +405,7 @@ void main(void) {
386
405
  vec4 shown = clamp(aColor * mul + add, 0.0, 1.0);
387
406
  bool light = dot(floor(shown.rgb * 255.0 + 0.5), vec3(30.0, 59.0, 11.0)) > 20000.0;
388
407
  int table = int(light ? aOrigin.z : aOrigin.w);
389
- vec2 cut = cutoffs(table & 63, table >> 6, ppem);
408
+ vec2 cut = cutoffs(mode, table & 63, table >> 6, ppem);
390
409
  mat2 toPixel = linear * aRun.z;
391
410
  float margin = max(abs(cut.x), abs(cut.y)) + 1.0;
392
411
  float det = determinant(toPixel);