@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/atlas.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GlState } from "@swf2es/context3d/device";
|
|
2
2
|
import { type Geometries, type GeometryData, GLYPH_FLOATS } from "../geometry.js";
|
|
3
|
+
import { GlyphTables } from "./glyph-tables.js";
|
|
3
4
|
import {
|
|
4
5
|
COLOR_AT,
|
|
5
6
|
DEPTH_AT,
|
|
@@ -67,13 +68,16 @@ function interleave(
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Where a geometry lies in the atlas, or the glyph atlas with its text's
|
|
73
|
+
* mode and table, and their slot in the glyph tables (-1 past their room).
|
|
74
|
+
*/
|
|
71
75
|
export interface Place {
|
|
72
76
|
vertexStart: number;
|
|
73
77
|
vertices: number;
|
|
74
78
|
indexStart: number;
|
|
75
79
|
indices: number;
|
|
76
|
-
glyphs: { mode: Float32Array; table: Float32Array } | null;
|
|
80
|
+
glyphs: { mode: Float32Array; table: Float32Array; slot: number } | null;
|
|
77
81
|
/** Its geometry's flat (GeometryData.flat). */
|
|
78
82
|
flat: boolean;
|
|
79
83
|
/** Small, unchanged geometries may be copied into a run's streaming buffer. */
|
|
@@ -122,10 +126,13 @@ export class GeometryAtlas {
|
|
|
122
126
|
live: 0,
|
|
123
127
|
holes: 0,
|
|
124
128
|
};
|
|
129
|
+
/** The texts' modes and tables, which the glyph program reads. */
|
|
130
|
+
readonly tables: GlyphTables;
|
|
125
131
|
|
|
126
132
|
constructor(gl: WebGL2RenderingContext, state: GlState) {
|
|
127
133
|
this.gl = gl;
|
|
128
134
|
this.state = state;
|
|
135
|
+
this.tables = new GlyphTables(gl, state);
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
/** The buffers and vertex arrays made, both atlases empty, as the context comes or comes back. */
|
|
@@ -177,6 +184,7 @@ export class GeometryAtlas {
|
|
|
177
184
|
this.stagedVertices = 0;
|
|
178
185
|
this.stagedIndexCount = 0;
|
|
179
186
|
this.places = [];
|
|
187
|
+
this.tables.setUp();
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
/**
|
|
@@ -254,7 +262,8 @@ export class GeometryAtlas {
|
|
|
254
262
|
const texts =
|
|
255
263
|
g.top + glyphVertices > g.capacity ||
|
|
256
264
|
g.indexTop + glyphIndices > g.indexCapacity ||
|
|
257
|
-
(g.holes > GLYPHS_LEAST && g.holes > g.live)
|
|
265
|
+
(g.holes > GLYPHS_LEAST && g.holes > g.live) ||
|
|
266
|
+
this.tables.full;
|
|
258
267
|
if (shapes) {
|
|
259
268
|
this.repack(geometries);
|
|
260
269
|
}
|
|
@@ -272,6 +281,7 @@ export class GeometryAtlas {
|
|
|
272
281
|
|
|
273
282
|
geometries.added.length = 0;
|
|
274
283
|
this.flush();
|
|
284
|
+
this.tables.upload();
|
|
275
285
|
}
|
|
276
286
|
|
|
277
287
|
/** Every live shape's geometry copied into new buffers, with room for twice what is live. */
|
|
@@ -336,12 +346,16 @@ export class GeometryAtlas {
|
|
|
336
346
|
g.indexTop = 0;
|
|
337
347
|
g.live = 0;
|
|
338
348
|
g.holes = 0;
|
|
349
|
+
// Only the live texts' tables kept.
|
|
350
|
+
this.tables.reset();
|
|
339
351
|
for (const id of ids) {
|
|
340
352
|
const data = geometries.get(id) as GeometryData;
|
|
341
353
|
if (data.glyphs) {
|
|
342
354
|
this.copyGlyphs(id, data);
|
|
343
355
|
}
|
|
344
356
|
}
|
|
357
|
+
|
|
358
|
+
this.tables.upload();
|
|
345
359
|
}
|
|
346
360
|
|
|
347
361
|
/** `data` copied in at the top of the atlas, its indices made absolute. */
|
|
@@ -483,7 +497,11 @@ export class GeometryAtlas {
|
|
|
483
497
|
vertices: count,
|
|
484
498
|
indexStart: g.indexTop,
|
|
485
499
|
indices: indices.length,
|
|
486
|
-
glyphs: {
|
|
500
|
+
glyphs: {
|
|
501
|
+
mode: glyphs.mode,
|
|
502
|
+
table: glyphs.table,
|
|
503
|
+
slot: this.tables.slot(glyphs.mode, glyphs.table),
|
|
504
|
+
},
|
|
487
505
|
flat: false,
|
|
488
506
|
batch: null,
|
|
489
507
|
};
|
|
@@ -501,5 +519,6 @@ export class GeometryAtlas {
|
|
|
501
519
|
gl.deleteVertexArray(this.glyphArray);
|
|
502
520
|
gl.deleteBuffer(this.glyphBuffer);
|
|
503
521
|
gl.deleteBuffer(this.glyphIndexBuffer);
|
|
522
|
+
this.tables.destroy();
|
|
504
523
|
}
|
|
505
524
|
}
|
package/src/webgl/batches.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { GlState } from "@swf2es/context3d/device";
|
|
2
2
|
import type { Place } from "./atlas.js";
|
|
3
|
-
import { COLOR_AT, DRAW_AT, POSITION_AT, TEXEL_AT } from "./programs.js";
|
|
3
|
+
import { COLOR_AT, DRAW_AT, MERGED_LINE, MERGED_UNIT, POSITION_AT, TEXEL_AT } from "./programs.js";
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Atlas attributes followed by where the draw's packed row is in the rows'
|
|
7
|
+
* texture: transforms and snapping stay in the shader, and a row that
|
|
8
|
+
* changes changes no vertex.
|
|
9
|
+
*/
|
|
6
10
|
const WORDS = 6;
|
|
7
11
|
/** Bound the extra CPU and GPU storage, falling back to the atlas after it fills. */
|
|
8
12
|
const VERTICES_MOST = 2 << 20;
|
|
@@ -13,12 +17,30 @@ const INDICES_LEAST = 3 << 14;
|
|
|
13
17
|
/** Frames over which the most used is watched; room four times that is let go of after them. */
|
|
14
18
|
const SHRINK_FRAMES = 240;
|
|
15
19
|
|
|
20
|
+
type Batch = NonNullable<Place["batch"]>;
|
|
21
|
+
|
|
22
|
+
/** A run's geometries copied in a row into the stream, where every run of the same geometries finds them. */
|
|
23
|
+
interface Segment {
|
|
24
|
+
batches: Batch[];
|
|
25
|
+
vertexStart: number;
|
|
26
|
+
vertices: number;
|
|
27
|
+
indexStart: number;
|
|
28
|
+
indices: number;
|
|
29
|
+
/** Where its draws' rows are in the rows' texture, one after another. */
|
|
30
|
+
slot: number;
|
|
31
|
+
/** The last frame a run used it (FillBatches.frame). */
|
|
32
|
+
frame: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
16
35
|
/**
|
|
17
|
-
* Small
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
36
|
+
* Small geometries merged run by run in painter's order, each run's copy a
|
|
37
|
+
* segment of one stream that any later run of the same geometries draws
|
|
38
|
+
* again, wherever it comes in the frame: its rows are in a texture of its
|
|
39
|
+
* own, written each frame, not in its vertices. Only the segments a frame adds are staged on the
|
|
40
|
+
* CPU, after the stream's top, and uploaded there at once; what is below
|
|
41
|
+
* stays on the GPU alone, and a buffer grown keeps it by a copy there.
|
|
42
|
+
* Segments no run used in a frame are let go of, and their room once the
|
|
43
|
+
* stream is twice what the last frame used, the stream then made anew.
|
|
22
44
|
*/
|
|
23
45
|
export class FillBatches {
|
|
24
46
|
vertexArray!: WebGLVertexArrayObject;
|
|
@@ -32,14 +54,36 @@ export class FillBatches {
|
|
|
32
54
|
private indexCapacity = 0;
|
|
33
55
|
private vertexTop = 0;
|
|
34
56
|
indexTop = 0;
|
|
35
|
-
/** Where this frame's staging starts in the stream:
|
|
57
|
+
/** Where this frame's staging starts in the stream: its top as the frame began. */
|
|
36
58
|
private fromVertex = 0;
|
|
37
59
|
private fromIndex = 0;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
/**
|
|
61
|
+
* The segments' rows, slot by slot (Segment.slot), as their texture holds
|
|
62
|
+
* them, the slots taken, and the range of them this frame changed.
|
|
63
|
+
*/
|
|
64
|
+
rowsTexture: WebGLTexture | null = null;
|
|
65
|
+
private rows = new Int32Array(0);
|
|
66
|
+
private rowsLines = 0;
|
|
67
|
+
private slotTop = 0;
|
|
68
|
+
private slotsMost = 0;
|
|
69
|
+
private changedFrom = 0;
|
|
70
|
+
private changedTo = 0;
|
|
71
|
+
/** The segments by their first geometry. */
|
|
72
|
+
private readonly segments = new Map<Batch, Segment[]>();
|
|
73
|
+
private frame = 0;
|
|
74
|
+
/** What the frame's runs used of the stream, each segment once. */
|
|
75
|
+
private liveVertices = 0;
|
|
76
|
+
private liveIndices = 0;
|
|
77
|
+
/**
|
|
78
|
+
* The keys of the runs this frame and the last asked for (add's
|
|
79
|
+
* `settled`), so that a run is copied only once it is drawn again.
|
|
80
|
+
*/
|
|
81
|
+
private seen = new Set<number>();
|
|
82
|
+
private seenBefore = new Set<number>();
|
|
83
|
+
/** A frame prepared but not uploaded, as an overflowing run table leaves one: its staging lost. */
|
|
84
|
+
private pending = false;
|
|
85
|
+
/** Whether a run found no room this frame, so that the next makes the stream anew. */
|
|
86
|
+
private full = false;
|
|
43
87
|
/** The frames of the current watch, and the most of the stream and of the staging they used. */
|
|
44
88
|
private frames = 0;
|
|
45
89
|
private mostVertices = 0;
|
|
@@ -52,8 +96,13 @@ export class FillBatches {
|
|
|
52
96
|
private readonly state: GlState,
|
|
53
97
|
) {}
|
|
54
98
|
|
|
55
|
-
|
|
99
|
+
/** The buffers and the rows' texture made, `textureMost` lines at most, as the context comes. */
|
|
100
|
+
setUp(textureMost: number): void {
|
|
56
101
|
const gl = this.gl;
|
|
102
|
+
this.rowsTexture = null;
|
|
103
|
+
this.rowsLines = 0;
|
|
104
|
+
this.rows = new Int32Array(0);
|
|
105
|
+
this.slotsMost = MERGED_LINE * textureMost;
|
|
57
106
|
this.vertexArray = gl.createVertexArray() as WebGLVertexArrayObject;
|
|
58
107
|
this.vertexBuffer = gl.createBuffer() as WebGLBuffer;
|
|
59
108
|
this.indexBuffer = gl.createBuffer() as WebGLBuffer;
|
|
@@ -72,69 +121,142 @@ export class FillBatches {
|
|
|
72
121
|
gl.vertexAttribI4i(DRAW_AT, -1, 0, 0, 0);
|
|
73
122
|
this.vertexCapacity = 0;
|
|
74
123
|
this.indexCapacity = 0;
|
|
75
|
-
this.
|
|
76
|
-
this.previousRows.length = 0;
|
|
77
|
-
this.changed = false;
|
|
78
|
-
this.reset();
|
|
124
|
+
this.clear();
|
|
79
125
|
}
|
|
80
126
|
|
|
81
|
-
|
|
127
|
+
/** No segment kept: the stream made anew from its start. */
|
|
128
|
+
private clear(): void {
|
|
129
|
+
this.segments.clear();
|
|
82
130
|
this.vertexTop = 0;
|
|
83
131
|
this.indexTop = 0;
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
this.fromIndex = 0;
|
|
132
|
+
this.slotTop = 0;
|
|
133
|
+
this.pending = false;
|
|
134
|
+
this.full = false;
|
|
88
135
|
}
|
|
89
136
|
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
137
|
+
/** A frame's preparation begun: staging from the stream's top, or from its start once it is mostly dead. */
|
|
138
|
+
reset(): void {
|
|
139
|
+
if (this.pending) {
|
|
140
|
+
// Rows written and not uploaded: the texture made again, so that the CPU's copy is its own.
|
|
141
|
+
this.rowsLines = 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
this.pending ||
|
|
146
|
+
// Room let go of where a run found none, unless the last frame used all of the stream.
|
|
147
|
+
(this.full && (this.vertexTop > this.liveVertices || this.indexTop > this.liveIndices)) ||
|
|
148
|
+
(this.vertexTop > VERTICES_LEAST && this.vertexTop > this.liveVertices * 2) ||
|
|
149
|
+
(this.indexTop > INDICES_LEAST && this.indexTop > this.liveIndices * 2)
|
|
150
|
+
) {
|
|
151
|
+
this.clear();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.full = false;
|
|
155
|
+
const seen = this.seenBefore;
|
|
156
|
+
this.seenBefore = this.seen;
|
|
157
|
+
this.seen = seen;
|
|
158
|
+
seen.clear();
|
|
159
|
+
this.frame++;
|
|
160
|
+
this.fromVertex = this.vertexTop;
|
|
161
|
+
this.fromIndex = this.indexTop;
|
|
162
|
+
this.liveVertices = 0;
|
|
163
|
+
this.liveIndices = 0;
|
|
164
|
+
this.changedFrom = this.slotTop;
|
|
165
|
+
this.changedTo = 0;
|
|
166
|
+
this.pending = true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The segment that draws the run, of at least `least` draws: one kept, or
|
|
171
|
+
* one staged now; null leaves it to the atlas, where copying would cost
|
|
172
|
+
* too much: a run short or of geometry added this `frame` is cheaper
|
|
173
|
+
* drawn from it, and with `settled` a run not drawn the frame before
|
|
174
|
+
* too, as a run that changes from frame to frame, as an animation's do,
|
|
175
|
+
* would be copied again each frame. Its vertices carry each draw's slot
|
|
176
|
+
* in the rows' texture, where `rows` are written.
|
|
177
|
+
*/
|
|
178
|
+
add(
|
|
179
|
+
places: Place[],
|
|
180
|
+
rows: Int32Array,
|
|
181
|
+
count: number,
|
|
182
|
+
frame: number,
|
|
183
|
+
least: number,
|
|
184
|
+
settled: boolean,
|
|
185
|
+
): { indexStart: number; indices: number } | null {
|
|
186
|
+
if (count < least) {
|
|
187
|
+
return null;
|
|
94
188
|
}
|
|
95
189
|
|
|
96
|
-
let vertices =
|
|
97
|
-
let indices =
|
|
190
|
+
let vertices = 0;
|
|
191
|
+
let indices = 0;
|
|
192
|
+
// The run's geometries by where they lie in the atlas, which no two live geometries share.
|
|
193
|
+
let key = count;
|
|
98
194
|
for (let i = 0; i < count; i++) {
|
|
99
195
|
const place = places[i];
|
|
100
196
|
if (!place.batch || place.batch.frame === frame) {
|
|
101
|
-
return;
|
|
197
|
+
return null;
|
|
102
198
|
}
|
|
103
199
|
|
|
104
200
|
vertices += place.vertices;
|
|
105
201
|
indices += place.indices;
|
|
202
|
+
key = Math.imul(key ^ place.indexStart, 0x9e3779b1) ^ place.indices;
|
|
106
203
|
}
|
|
107
204
|
|
|
108
|
-
|
|
109
|
-
|
|
205
|
+
const first = places[0].batch as Batch;
|
|
206
|
+
let list = this.segments.get(first);
|
|
207
|
+
if (list) {
|
|
208
|
+
for (const segment of list) {
|
|
209
|
+
// A copy a run drew this frame already serves another only with the same rows.
|
|
210
|
+
if (
|
|
211
|
+
segment.batches.length === count &&
|
|
212
|
+
this.holds(segment, places) &&
|
|
213
|
+
(segment.frame !== this.frame || this.rowsAre(segment.slot, rows, count))
|
|
214
|
+
) {
|
|
215
|
+
this.use(segment);
|
|
216
|
+
this.writeRows(segment.slot, rows, count);
|
|
217
|
+
return segment;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
110
220
|
}
|
|
111
221
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
!this.changed &&
|
|
118
|
-
(this.previous[this.cursor] !== batch || this.previousRows[this.cursor] !== rows[i])
|
|
119
|
-
) {
|
|
120
|
-
this.changed = true;
|
|
121
|
-
this.fromVertex = this.vertexTop;
|
|
122
|
-
this.fromIndex = this.indexTop;
|
|
123
|
-
}
|
|
222
|
+
// A key alike by chance merges a run a frame early, which costs a copy and draws the same.
|
|
223
|
+
this.seen.add(key);
|
|
224
|
+
if (settled && !this.seenBefore.has(key)) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
124
227
|
|
|
125
|
-
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
228
|
+
if (
|
|
229
|
+
this.vertexTop + vertices > VERTICES_MOST ||
|
|
230
|
+
this.indexTop + indices > INDICES_MOST ||
|
|
231
|
+
this.slotTop + count > this.slotsMost
|
|
232
|
+
) {
|
|
233
|
+
this.full = true;
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
132
236
|
|
|
133
|
-
|
|
237
|
+
const segment: Segment = {
|
|
238
|
+
batches: new Array<Batch>(count),
|
|
239
|
+
vertexStart: this.vertexTop,
|
|
240
|
+
vertices,
|
|
241
|
+
indexStart: this.indexTop,
|
|
242
|
+
indices,
|
|
243
|
+
slot: this.slotTop,
|
|
244
|
+
frame: -1,
|
|
245
|
+
};
|
|
246
|
+
this.slotTop += count;
|
|
247
|
+
this.stageRoom(
|
|
248
|
+
this.vertexTop + vertices - this.fromVertex,
|
|
249
|
+
this.indexTop + indices - this.fromIndex,
|
|
250
|
+
);
|
|
251
|
+
const floats = this.floats;
|
|
252
|
+
const words = this.words;
|
|
253
|
+
const staged = this.indices;
|
|
254
|
+
for (let i = 0; i < count; i++) {
|
|
255
|
+
const place = places[i];
|
|
256
|
+
const batch = place.batch as Batch;
|
|
257
|
+
segment.batches[i] = batch;
|
|
134
258
|
const data = batch.data;
|
|
135
259
|
const base = this.vertexTop;
|
|
136
|
-
const floats = this.floats;
|
|
137
|
-
const words = this.words;
|
|
138
260
|
for (let j = 0; j < place.vertices; j++) {
|
|
139
261
|
const at = (base - this.fromVertex + j) * WORDS;
|
|
140
262
|
floats[at] = data.positions[j * 2];
|
|
@@ -142,10 +264,9 @@ export class FillBatches {
|
|
|
142
264
|
floats[at + 2] = data.texels ? data.texels[j * 2] : 0;
|
|
143
265
|
floats[at + 3] = data.texels ? data.texels[j * 2 + 1] : 0;
|
|
144
266
|
words[at + 4] = data.colors[j];
|
|
145
|
-
words[at + 5] =
|
|
267
|
+
words[at + 5] = segment.slot + i;
|
|
146
268
|
}
|
|
147
269
|
|
|
148
|
-
const staged = this.indices;
|
|
149
270
|
let at = this.indexTop - this.fromIndex;
|
|
150
271
|
for (let j = 0; j < place.indices; j++) {
|
|
151
272
|
staged[at++] = base + data.indices[j];
|
|
@@ -154,22 +275,146 @@ export class FillBatches {
|
|
|
154
275
|
this.vertexTop += place.vertices;
|
|
155
276
|
this.indexTop += place.indices;
|
|
156
277
|
}
|
|
278
|
+
|
|
279
|
+
if (!list) {
|
|
280
|
+
list = [];
|
|
281
|
+
this.segments.set(first, list);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
list.push(segment);
|
|
285
|
+
this.use(segment);
|
|
286
|
+
this.writeRows(segment.slot, rows, count);
|
|
287
|
+
return segment;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Whether the `count` rows from `slot` are `rows`. */
|
|
291
|
+
private rowsAre(slot: number, rows: Int32Array, count: number): boolean {
|
|
292
|
+
for (let i = 0; i < count; i++) {
|
|
293
|
+
if (this.rows[slot + i] !== rows[i]) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** `rows` written from `slot` on, what changes counted for the upload. */
|
|
302
|
+
private writeRows(slot: number, rows: Int32Array, count: number): void {
|
|
303
|
+
if (slot + count > this.rows.length) {
|
|
304
|
+
const lines = Math.ceil(Math.max(slot + count, this.rows.length * 2) / MERGED_LINE);
|
|
305
|
+
const grown = new Int32Array(lines * MERGED_LINE);
|
|
306
|
+
grown.set(this.rows);
|
|
307
|
+
this.rows = grown;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const held = this.rows;
|
|
311
|
+
for (let i = 0; i < count; i++) {
|
|
312
|
+
if (held[slot + i] !== rows[i]) {
|
|
313
|
+
held[slot + i] = rows[i];
|
|
314
|
+
this.changedFrom = Math.min(this.changedFrom, slot + i);
|
|
315
|
+
this.changedTo = Math.max(this.changedTo, slot + i + 1);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** The rows' texture made tall enough for the slots taken, and the lines this frame changed uploaded. */
|
|
321
|
+
private uploadRows(): void {
|
|
322
|
+
const gl = this.gl;
|
|
323
|
+
const lines = Math.ceil(this.slotTop / MERGED_LINE);
|
|
324
|
+
if (lines > this.rowsLines) {
|
|
325
|
+
if (this.rowsTexture) {
|
|
326
|
+
this.state.dropTexture(this.rowsTexture);
|
|
327
|
+
gl.deleteTexture(this.rowsTexture);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// A new texture holds zeros: the rows taken uploaded again, the CPU's past them zeros too.
|
|
331
|
+
this.rows.fill(0, this.slotTop);
|
|
332
|
+
this.rowsLines = Math.min(this.slotsMost / MERGED_LINE, Math.max(lines, this.rowsLines * 2));
|
|
333
|
+
this.rowsTexture = gl.createTexture();
|
|
334
|
+
this.state.bindTexture(MERGED_UNIT, this.rowsTexture);
|
|
335
|
+
this.state.activeTexture(MERGED_UNIT);
|
|
336
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32I, MERGED_LINE, this.rowsLines);
|
|
337
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
338
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
339
|
+
this.changedFrom = 0;
|
|
340
|
+
this.changedTo = this.slotTop;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (this.changedTo <= this.changedFrom) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const from = Math.floor(this.changedFrom / MERGED_LINE);
|
|
348
|
+
const to = Math.ceil(this.changedTo / MERGED_LINE);
|
|
349
|
+
this.state.bindTexture(MERGED_UNIT, this.rowsTexture);
|
|
350
|
+
this.state.activeTexture(MERGED_UNIT);
|
|
351
|
+
gl.texSubImage2D(
|
|
352
|
+
gl.TEXTURE_2D,
|
|
353
|
+
0,
|
|
354
|
+
0,
|
|
355
|
+
from,
|
|
356
|
+
MERGED_LINE,
|
|
357
|
+
to - from,
|
|
358
|
+
gl.RED_INTEGER,
|
|
359
|
+
gl.INT,
|
|
360
|
+
this.rows,
|
|
361
|
+
from * MERGED_LINE,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Whether `segment` is of the geometries of `places`, in their order. */
|
|
366
|
+
private holds(segment: Segment, places: Place[]): boolean {
|
|
367
|
+
const batches = segment.batches;
|
|
368
|
+
for (let i = 0; i < batches.length; i++) {
|
|
369
|
+
if (batches[i] !== places[i].batch) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** `segment` used this frame, counted once among what the frame used. */
|
|
378
|
+
private use(segment: Segment): void {
|
|
379
|
+
if (segment.frame !== this.frame) {
|
|
380
|
+
segment.frame = this.frame;
|
|
381
|
+
this.liveVertices += segment.vertices;
|
|
382
|
+
this.liveIndices += segment.indices;
|
|
383
|
+
}
|
|
157
384
|
}
|
|
158
385
|
|
|
159
386
|
/**
|
|
160
|
-
* The staged
|
|
161
|
-
*
|
|
162
|
-
* SHRINK_FRAMES has used a quarter of the room or
|
|
163
|
-
* the staging made smaller.
|
|
387
|
+
* The staged segments uploaded where they lie, the buffers grown to fit
|
|
388
|
+
* them first, keeping what is below; the segments no run used let go of;
|
|
389
|
+
* and, once a watch of SHRINK_FRAMES has used a quarter of the room or
|
|
390
|
+
* less, the buffers and the staging made smaller.
|
|
164
391
|
*/
|
|
165
392
|
upload(): void {
|
|
166
|
-
this.
|
|
167
|
-
this.previousRows.length = this.cursor;
|
|
168
|
-
const staging = this.changed && this.indexTop > 0;
|
|
393
|
+
const staging = this.vertexTop > this.fromVertex;
|
|
169
394
|
if (staging) {
|
|
170
395
|
this.uploadStaged();
|
|
171
396
|
}
|
|
172
397
|
|
|
398
|
+
if (this.slotTop > 0) {
|
|
399
|
+
this.uploadRows();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
for (const [first, list] of this.segments) {
|
|
403
|
+
let kept = 0;
|
|
404
|
+
for (const segment of list) {
|
|
405
|
+
if (segment.frame === this.frame) {
|
|
406
|
+
list[kept++] = segment;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (kept === 0) {
|
|
411
|
+
this.segments.delete(first);
|
|
412
|
+
} else {
|
|
413
|
+
list.length = kept;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
this.pending = false;
|
|
173
418
|
this.watch(staging);
|
|
174
419
|
}
|
|
175
420
|
|
|
@@ -215,10 +460,15 @@ export class FillBatches {
|
|
|
215
460
|
0,
|
|
216
461
|
this.indexTop - this.fromIndex,
|
|
217
462
|
);
|
|
218
|
-
this.changed = false;
|
|
219
463
|
}
|
|
220
464
|
|
|
221
465
|
destroy(): void {
|
|
466
|
+
if (this.rowsTexture) {
|
|
467
|
+
this.state.dropTexture(this.rowsTexture);
|
|
468
|
+
this.gl.deleteTexture(this.rowsTexture);
|
|
469
|
+
this.rowsTexture = null;
|
|
470
|
+
}
|
|
471
|
+
|
|
222
472
|
this.gl.deleteVertexArray(this.vertexArray);
|
|
223
473
|
this.gl.deleteBuffer(this.vertexBuffer);
|
|
224
474
|
this.gl.deleteBuffer(this.indexBuffer);
|
|
@@ -266,10 +516,8 @@ export class FillBatches {
|
|
|
266
516
|
}
|
|
267
517
|
|
|
268
518
|
const gl = this.gl;
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
const keepVertices = kept ? this.vertexTop : 0;
|
|
272
|
-
const keepIndices = kept ? this.indexTop : 0;
|
|
519
|
+
const keepVertices = this.vertexTop;
|
|
520
|
+
const keepIndices = this.indexTop;
|
|
273
521
|
const vertices = Math.max(VERTICES_LEAST, this.mostVertices * 2);
|
|
274
522
|
if (this.mostVertices * 4 <= this.vertexCapacity && vertices < this.vertexCapacity) {
|
|
275
523
|
this.state.bindVertexArray(this.vertexArray);
|