@weasel-js/renderer 1.9.4 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-2VOBNWBN.js → chunk-43YL7HZV.js} +3 -3
- package/dist/chunk-43YL7HZV.js.map +1 -0
- package/dist/{chunk-VHQJ56XQ.js → chunk-LFD47OV2.js} +180 -130
- package/dist/chunk-LFD47OV2.js.map +1 -0
- package/dist/{chunk-ORBXHIRB.js → chunk-Q4SIHV7Q.js} +4 -4
- package/dist/chunk-Q4SIHV7Q.js.map +1 -0
- package/dist/{imageCache-oJVgxhkr.d.ts → imageCache-DYcWftY8.d.ts} +66 -109
- package/dist/index.d.ts +192 -200
- package/dist/index.js +327 -145
- package/dist/index.js.map +1 -1
- package/dist/mesh.d.ts +34 -67
- package/dist/mesh.js +5 -5
- package/dist/mesh.js.map +1 -1
- package/dist/{paintKinds-B2vEy9A4.d.ts → paintKinds-BmPAT51k.d.ts} +22 -38
- package/dist/patterns-builtin.d.ts +8 -16
- package/dist/patterns-builtin.js +7 -7
- package/dist/patterns-builtin.js.map +1 -1
- package/dist/test-seams.d.ts +22 -63
- package/dist/test-seams.js +2 -2
- package/dist/testing/glRecorder.d.ts +2 -5
- package/dist/testing/glRecorder.js +3 -0
- package/dist/testing/glRecorder.js.map +1 -1
- package/dist/testing/vertexUploads.d.ts +4 -6
- package/dist/testing/vertexUploads.js.map +1 -1
- package/package.json +7 -7
- package/dist/chunk-2VOBNWBN.js.map +0 -1
- package/dist/chunk-ORBXHIRB.js.map +0 -1
- package/dist/chunk-VHQJ56XQ.js.map +0 -1
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
import { ownTextureIds, rasterizeTile, drawTileShapes, tileGeometry, tileSize, registerTextureAs, textureRegistry, unregisterTexture } from './chunk-
|
|
1
|
+
import { ownTextureIds, rasterizeTile, drawTileShapes, tileGeometry, tileSize, registerTextureAs, textureRegistry, unregisterTexture } from './chunk-Q4SIHV7Q.js';
|
|
2
|
+
import { defaultOpenFill, pathFromD, boundsOfPath } from '@weasel-js/geom';
|
|
2
3
|
import { tessellate } from '@weasel-js/geom/tessellate';
|
|
3
|
-
import { createReflectable } from '@weasel-js/registry';
|
|
4
|
+
import { createReflectable, createEmitter } from '@weasel-js/registry';
|
|
4
5
|
import { resolveStrokeWidth, strokeInsets, tessellateStroke, markerGeneration, screenSpaceKey, markerHeads, metricKey, getMarker, markerKeyOf, markerReadsKey } from '@weasel-js/stroke';
|
|
5
|
-
import {
|
|
6
|
+
import { isTileKind, isPatternPaint, tileSpecOf, isTilePaint, patternPlacementOf } from '@weasel-js/paint';
|
|
6
7
|
import { GLYPH_COVERAGE_GLSL } from '@weasel-js/font';
|
|
7
8
|
|
|
9
|
+
// src/cache/uploadPremultiplied.ts
|
|
10
|
+
function uploadPremultiplied(gl, source) {
|
|
11
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
12
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
|
|
13
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
// src/cache/GLImageCache.ts
|
|
9
17
|
var DEFAULT_MAX_TEXTURE_SIDE = 4096;
|
|
10
18
|
var live = /* @__PURE__ */ new Set();
|
|
11
19
|
function releaseImageSource(key) {
|
|
12
20
|
for (const cache7 of live) cache7.release(key);
|
|
13
21
|
}
|
|
22
|
+
function imageSourceInLatestFrame(key) {
|
|
23
|
+
for (const cache7 of live) if (cache7.drewInLatestFrame(key)) return true;
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
14
26
|
function maxImageTextureSide() {
|
|
15
27
|
let side = Infinity;
|
|
16
28
|
for (const cache7 of live) side = Math.min(side, cache7.maxTextureSide);
|
|
@@ -18,11 +30,10 @@ function maxImageTextureSide() {
|
|
|
18
30
|
}
|
|
19
31
|
var GLImageCache = class {
|
|
20
32
|
/** `minification` selects the MIN_FILTER strategy for uploaded textures.
|
|
21
|
-
* `'
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* and produces moiré. */
|
|
33
|
+
* `'mipmap'` generates mipmaps and filters LINEAR_MIPMAP_LINEAR: when a
|
|
34
|
+
* large source bitmap is drawn small, bilinear-only minification
|
|
35
|
+
* undersamples and produces moiré. `'auto'` pays for that only on the
|
|
36
|
+
* textures a frame draws small, when it first does. */
|
|
26
37
|
constructor(gl, minification = "linear") {
|
|
27
38
|
this.gl = gl;
|
|
28
39
|
this.minification = minification;
|
|
@@ -43,25 +54,29 @@ var GLImageCache = class {
|
|
|
43
54
|
textures = /* @__PURE__ */ new Set();
|
|
44
55
|
/**
|
|
45
56
|
* MAG_FILTER each texture currently carries, so a redundant write can be
|
|
46
|
-
* skipped.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* at upload. Filtering is state on the *texture object*, though, not on the
|
|
51
|
-
* unit, so re-asserting a value it already has is a write to a live texture
|
|
52
|
-
* for no reason. On a large sheet that is not free: a consumer's wall samples
|
|
53
|
-
* a 5652px-square atlas, 122MB resident, and measured `sampling: 'nearest'`
|
|
54
|
-
* costing up to 8x `'linear'` there — where the linear pass re-asserts the
|
|
55
|
-
* upload default and the nearest pass changes state on every draw.
|
|
56
|
-
*
|
|
57
|
-
* Whether that is the cause is unproven (see `docs/TODO.md`), but the write
|
|
58
|
-
* was redundant either way.
|
|
57
|
+
* skipped. The batch sets this per flush — the same bitmap can be drawn at
|
|
58
|
+
* both filters in one frame — but filtering is state on the *texture
|
|
59
|
+
* object*, not on the unit, so re-asserting a value it already has is a
|
|
60
|
+
* write to a live texture for no reason.
|
|
59
61
|
*/
|
|
60
62
|
magFilters = /* @__PURE__ */ new WeakMap();
|
|
61
63
|
finalizer;
|
|
62
64
|
pendingDeletes = [];
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
+
/** Under `'auto'`, the sources whose mipmaps are built. */
|
|
66
|
+
mipped = /* @__PURE__ */ new WeakSet();
|
|
67
|
+
frame = 0;
|
|
68
|
+
lastDrawn = /* @__PURE__ */ new WeakMap();
|
|
69
|
+
/** Open a frame: what {@link drewInLatestFrame} answers about from here on.
|
|
70
|
+
* The renderer calls this at the top of each `render()`. */
|
|
71
|
+
beginFrame() {
|
|
72
|
+
this.frame++;
|
|
73
|
+
this.drainPendingDeletes();
|
|
74
|
+
}
|
|
75
|
+
/** Whether `key` was uploaded or bound since the last {@link beginFrame}. */
|
|
76
|
+
drewInLatestFrame(key) {
|
|
77
|
+
return this.lastDrawn.get(key) === this.frame;
|
|
78
|
+
}
|
|
79
|
+
/** Delete the textures of sources collected since the last call. */
|
|
65
80
|
drainPendingDeletes() {
|
|
66
81
|
if (this.pendingDeletes.length === 0) return;
|
|
67
82
|
for (const tex of this.pendingDeletes) this.gl.deleteTexture(tex);
|
|
@@ -76,6 +91,8 @@ var GLImageCache = class {
|
|
|
76
91
|
this.finalizer.unregister(key);
|
|
77
92
|
this.map.delete(key);
|
|
78
93
|
this.magFilters.delete(key);
|
|
94
|
+
this.lastDrawn.delete(key);
|
|
95
|
+
this.mipped.delete(key);
|
|
79
96
|
}
|
|
80
97
|
/** Delete every texture this cache uploaded and stop answering
|
|
81
98
|
* {@link releaseImageSource} and {@link maxImageTextureSide}. Idempotent. */
|
|
@@ -92,13 +109,14 @@ var GLImageCache = class {
|
|
|
92
109
|
live.delete(this);
|
|
93
110
|
}
|
|
94
111
|
upload(key, source, repetition) {
|
|
112
|
+
this.lastDrawn.set(key, this.frame);
|
|
95
113
|
const existing = this.map.get(key);
|
|
96
114
|
if (existing) return existing;
|
|
97
115
|
const gl = this.gl;
|
|
98
116
|
const tex = gl.createTexture();
|
|
99
117
|
if (!tex) throw new Error("GLImageCache: createTexture failed");
|
|
100
118
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
101
|
-
|
|
119
|
+
uploadPremultiplied(gl, source);
|
|
102
120
|
gl.texParameteri(
|
|
103
121
|
gl.TEXTURE_2D,
|
|
104
122
|
gl.TEXTURE_MIN_FILTER,
|
|
@@ -116,9 +134,27 @@ var GLImageCache = class {
|
|
|
116
134
|
this.finalizer.register(key, tex, key);
|
|
117
135
|
return tex;
|
|
118
136
|
}
|
|
137
|
+
/** Whether `key` would gain from {@link buildMipmaps}: only under `'auto'`,
|
|
138
|
+
* and only until they are built. */
|
|
139
|
+
lacksMipmaps(key) {
|
|
140
|
+
return this.minification === "auto" && !this.mipped.has(key);
|
|
141
|
+
}
|
|
142
|
+
/** Build `key`'s mipmaps and filter minification through them from here
|
|
143
|
+
* on. Its texture must be uploaded. Leaves no texture bound. */
|
|
144
|
+
buildMipmaps(key) {
|
|
145
|
+
const tex = this.map.get(key);
|
|
146
|
+
if (!tex || this.mipped.has(key)) return;
|
|
147
|
+
const gl = this.gl;
|
|
148
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
149
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
150
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
151
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
152
|
+
this.mipped.add(key);
|
|
153
|
+
}
|
|
119
154
|
bind(key, unit) {
|
|
120
155
|
const tex = this.map.get(key);
|
|
121
156
|
if (!tex) throw new Error("GLImageCache: image not uploaded; call upload() first");
|
|
157
|
+
this.lastDrawn.set(key, this.frame);
|
|
122
158
|
const gl = this.gl;
|
|
123
159
|
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
124
160
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
@@ -146,10 +182,11 @@ function wrapModes(gl, rep) {
|
|
|
146
182
|
}
|
|
147
183
|
var cache = /* @__PURE__ */ new WeakMap();
|
|
148
184
|
function getMesh(path) {
|
|
185
|
+
const openFill = defaultOpenFill();
|
|
149
186
|
const cached = cache.get(path);
|
|
150
|
-
if (cached !== void 0) return cached;
|
|
187
|
+
if (cached !== void 0 && cached.openFill === openFill) return cached.mesh;
|
|
151
188
|
const mesh = tessellate(path);
|
|
152
|
-
cache.set(path, mesh);
|
|
189
|
+
cache.set(path, { mesh, openFill });
|
|
153
190
|
return mesh;
|
|
154
191
|
}
|
|
155
192
|
function _resetCacheForTests() {
|
|
@@ -298,11 +335,9 @@ function insetIn(length, head, ribbon) {
|
|
|
298
335
|
function _resetStrokeMeshCacheForTests() {
|
|
299
336
|
cache2 = /* @__PURE__ */ new WeakMap();
|
|
300
337
|
}
|
|
301
|
-
|
|
302
|
-
// src/fillInPoseFrame.ts
|
|
303
338
|
function fillInPoseFrame(fill, box) {
|
|
304
339
|
if (!isBuiltinKind(fill)) {
|
|
305
|
-
return getPaintDrawKind(fill.
|
|
340
|
+
return getPaintDrawKind(fill.kind)?.inPoseFrame?.(fill, box) ?? fill;
|
|
306
341
|
}
|
|
307
342
|
if (isBoundsPattern(fill)) {
|
|
308
343
|
const o = fill.origin ?? { x: 0, y: 0 };
|
|
@@ -313,10 +348,10 @@ function fillInPoseFrame(fill, box) {
|
|
|
313
348
|
x: box.x + p.x * box.width,
|
|
314
349
|
y: box.y + p.y * box.height
|
|
315
350
|
});
|
|
316
|
-
if (fill.
|
|
351
|
+
if (fill.kind === "linear-gradient") {
|
|
317
352
|
return { ...fill, from: toBox(fill.from), to: toBox(fill.to), units: "local" };
|
|
318
353
|
}
|
|
319
|
-
if (fill.
|
|
354
|
+
if (fill.kind === "radial-gradient") {
|
|
320
355
|
const scale = Math.hypot(box.width, box.height) / Math.SQRT2;
|
|
321
356
|
return { ...fill, center: toBox(fill.center), radius: fill.radius * scale, units: "local" };
|
|
322
357
|
}
|
|
@@ -324,45 +359,43 @@ function fillInPoseFrame(fill, box) {
|
|
|
324
359
|
}
|
|
325
360
|
function fillToBoundsFrame(fill, box) {
|
|
326
361
|
if (!isBuiltinKind(fill)) {
|
|
327
|
-
return getPaintDrawKind(fill.
|
|
362
|
+
return getPaintDrawKind(fill.kind)?.toBoundsFrame?.(fill, box) ?? fill;
|
|
328
363
|
}
|
|
329
|
-
if (fill
|
|
364
|
+
if (isPatternPaint(fill)) {
|
|
330
365
|
const o = fill.origin ?? { x: 0, y: 0 };
|
|
331
366
|
return { ...fill, origin: { x: o.x - box.x, y: o.y - box.y }, units: "bounds" };
|
|
332
367
|
}
|
|
333
|
-
const isGradient = fill.
|
|
368
|
+
const isGradient = fill.kind === "linear-gradient" || fill.kind === "radial-gradient" || fill.kind === "conic-gradient";
|
|
334
369
|
if (!isGradient) return fill;
|
|
335
370
|
if (box.width === 0 || box.height === 0) return fill;
|
|
336
371
|
const fromBox = (p) => ({
|
|
337
372
|
x: (p.x - box.x) / box.width,
|
|
338
373
|
y: (p.y - box.y) / box.height
|
|
339
374
|
});
|
|
340
|
-
if (fill.
|
|
375
|
+
if (fill.kind === "linear-gradient") {
|
|
341
376
|
return { ...fill, from: fromBox(fill.from), to: fromBox(fill.to), units: "bounds" };
|
|
342
377
|
}
|
|
343
|
-
if (fill.
|
|
378
|
+
if (fill.kind === "radial-gradient") {
|
|
344
379
|
const scale = Math.hypot(box.width, box.height) / Math.SQRT2;
|
|
345
380
|
return { ...fill, center: fromBox(fill.center), radius: fill.radius / scale, units: "bounds" };
|
|
346
381
|
}
|
|
347
382
|
return { ...fill, center: fromBox(fill.center), units: "bounds" };
|
|
348
383
|
}
|
|
349
384
|
function isBoundsPattern(fill) {
|
|
350
|
-
return fill
|
|
385
|
+
return isPatternPaint(fill) && fill.units === "bounds";
|
|
351
386
|
}
|
|
352
387
|
function isBoundsGradient(fill) {
|
|
353
388
|
if (fill === void 0) return false;
|
|
354
|
-
const kind = fill.
|
|
389
|
+
const kind = fill.kind;
|
|
355
390
|
if (kind !== "linear-gradient" && kind !== "radial-gradient" && kind !== "conic-gradient") {
|
|
356
391
|
return false;
|
|
357
392
|
}
|
|
358
393
|
return fill.units === "bounds";
|
|
359
394
|
}
|
|
360
395
|
function isBuiltinKind(fill) {
|
|
361
|
-
const kind = fill.
|
|
362
|
-
return kind === "solid" ||
|
|
396
|
+
const kind = fill.kind ?? "solid";
|
|
397
|
+
return kind === "solid" || isPatternPaint(fill) || kind === "linear-gradient" || kind === "radial-gradient" || kind === "conic-gradient";
|
|
363
398
|
}
|
|
364
|
-
|
|
365
|
-
// src/patterns/resolveSpec.ts
|
|
366
399
|
var PATTERN_TILE_CACHE_LIMIT = 256;
|
|
367
400
|
var ID_PREFIX = "pattern:";
|
|
368
401
|
var cache3 = /* @__PURE__ */ new Map();
|
|
@@ -376,13 +409,14 @@ function _resetPatternSpecCacheForTests() {
|
|
|
376
409
|
cache3.clear();
|
|
377
410
|
}
|
|
378
411
|
function resolvePatternSpec(spec) {
|
|
379
|
-
const
|
|
412
|
+
const tile = tileSpecOf(spec);
|
|
413
|
+
const id = specId(tile);
|
|
380
414
|
const hit = cache3.get(id);
|
|
381
415
|
if (hit !== void 0) {
|
|
382
416
|
markUsed(id);
|
|
383
417
|
return hit;
|
|
384
418
|
}
|
|
385
|
-
return build(id,
|
|
419
|
+
return build(id, tile);
|
|
386
420
|
}
|
|
387
421
|
function markUsed(id) {
|
|
388
422
|
const hit = cache3.get(id);
|
|
@@ -391,7 +425,7 @@ function markUsed(id) {
|
|
|
391
425
|
cache3.set(id, hit);
|
|
392
426
|
}
|
|
393
427
|
function build(id, spec) {
|
|
394
|
-
const bitmap =
|
|
428
|
+
const bitmap = isTileKind(spec.kind) ? rasterizeTile({
|
|
395
429
|
size: tileSize(spec),
|
|
396
430
|
draw: (oc) => drawTileShapes(oc, tileGeometry(spec))
|
|
397
431
|
}) : null;
|
|
@@ -401,12 +435,6 @@ function build(id, spec) {
|
|
|
401
435
|
while (cache3.size > PATTERN_TILE_CACHE_LIMIT) evict(cache3.keys().next().value);
|
|
402
436
|
return handle;
|
|
403
437
|
}
|
|
404
|
-
var TILES = /* @__PURE__ */ new Set([
|
|
405
|
-
"hatch",
|
|
406
|
-
"crosshatch",
|
|
407
|
-
"dots",
|
|
408
|
-
"chunks"
|
|
409
|
-
]);
|
|
410
438
|
function evict(id) {
|
|
411
439
|
cache3.delete(id);
|
|
412
440
|
const source = textureRegistry.get(id)?.source;
|
|
@@ -420,27 +448,29 @@ function revive(id) {
|
|
|
420
448
|
} catch {
|
|
421
449
|
return;
|
|
422
450
|
}
|
|
423
|
-
if (typeof spec !== "object" || spec === null || !("
|
|
451
|
+
if (typeof spec !== "object" || spec === null || !("kind" in spec)) return;
|
|
424
452
|
build(id, spec);
|
|
425
453
|
}
|
|
426
454
|
ownTextureIds(ID_PREFIX, { revive, touched: markUsed });
|
|
427
|
-
function isPatternSpec(payload) {
|
|
428
|
-
return "tile" in payload;
|
|
429
|
-
}
|
|
430
455
|
function resolveFillPattern(fill) {
|
|
431
|
-
if (fill
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
return handle ? { ...fill, pattern: handle } : null;
|
|
456
|
+
if (!isTilePaint(fill)) return fill;
|
|
457
|
+
const texture = resolvePatternSpec(fill);
|
|
458
|
+
return texture ? { kind: "texture", texture, ...patternPlacementOf(fill) } : null;
|
|
435
459
|
}
|
|
436
460
|
|
|
437
461
|
// src/patterns/commandPaint.ts
|
|
462
|
+
function commandPaint(paint, box) {
|
|
463
|
+
return resolveFillPattern(fillInPoseFrame(paint, box));
|
|
464
|
+
}
|
|
438
465
|
function commandFill(fill, box) {
|
|
439
|
-
|
|
466
|
+
if (fill.paint == null) return null;
|
|
467
|
+
const paint = commandPaint(fill.paint, box);
|
|
468
|
+
if (paint === null) return null;
|
|
469
|
+
return paint === fill.paint ? fill : { ...fill, paint };
|
|
440
470
|
}
|
|
441
471
|
function commandStroke(stroke, box) {
|
|
442
472
|
if (stroke.paint == null) return null;
|
|
443
|
-
const paint =
|
|
473
|
+
const paint = commandPaint(stroke.paint, box);
|
|
444
474
|
if (paint === null) return null;
|
|
445
475
|
return paint === stroke.paint ? stroke : { ...stroke, paint };
|
|
446
476
|
}
|
|
@@ -448,7 +478,7 @@ function resolvePaint(p, stroke, fallback, head) {
|
|
|
448
478
|
const v = p ?? fallback;
|
|
449
479
|
if (v === "none") return void 0;
|
|
450
480
|
if (v === "line") return stroke.paint;
|
|
451
|
-
return
|
|
481
|
+
return commandPaint(v, boundsOfPath(head)) ?? void 0;
|
|
452
482
|
}
|
|
453
483
|
function headCommands(heads, stroke) {
|
|
454
484
|
const out = [];
|
|
@@ -460,7 +490,7 @@ function headCommands(heads, stroke) {
|
|
|
460
490
|
out.push({
|
|
461
491
|
kind: "path",
|
|
462
492
|
path,
|
|
463
|
-
...fill ? { fill } : {},
|
|
493
|
+
...fill ? { fill: { paint: fill } } : {},
|
|
464
494
|
...outline && outline.paint ? { stroke: { paint: outline.paint, width: outline.width, cap: "round", join: "round" } } : {}
|
|
465
495
|
});
|
|
466
496
|
}
|
|
@@ -477,14 +507,14 @@ function readsKey(stroke) {
|
|
|
477
507
|
key += "/";
|
|
478
508
|
continue;
|
|
479
509
|
}
|
|
480
|
-
const
|
|
481
|
-
if (
|
|
482
|
-
key += `${
|
|
510
|
+
const reads2 = markerReadsKey(entry, stroke);
|
|
511
|
+
if (reads2 === null) return null;
|
|
512
|
+
key += `${reads2}/`;
|
|
483
513
|
}
|
|
484
514
|
return key;
|
|
485
515
|
}
|
|
486
|
-
function mapFor(path, source,
|
|
487
|
-
if (
|
|
516
|
+
function mapFor(path, source, reads2) {
|
|
517
|
+
if (reads2 !== null) {
|
|
488
518
|
let map2 = byValue.get(path);
|
|
489
519
|
if (map2 === void 0) byValue.set(path, map2 = /* @__PURE__ */ new Map());
|
|
490
520
|
return map2;
|
|
@@ -499,8 +529,8 @@ function cachedMarkerCommands(path, source, stroke, strokeWidth, flattenToleranc
|
|
|
499
529
|
if (stroke.markerStart === void 0 && stroke.markerMid === void 0 && stroke.markerEnd === void 0) {
|
|
500
530
|
return [];
|
|
501
531
|
}
|
|
502
|
-
const
|
|
503
|
-
const map = mapFor(path, source,
|
|
532
|
+
const reads2 = readsKey(stroke);
|
|
533
|
+
const map = mapFor(path, source, reads2);
|
|
504
534
|
const key = [
|
|
505
535
|
strokeWidth,
|
|
506
536
|
flattenTolerance ?? "",
|
|
@@ -508,7 +538,7 @@ function cachedMarkerCommands(path, source, stroke, strokeWidth, flattenToleranc
|
|
|
508
538
|
refKey(stroke.markerStart),
|
|
509
539
|
refKey(stroke.markerMid),
|
|
510
540
|
refKey(stroke.markerEnd),
|
|
511
|
-
|
|
541
|
+
reads2 ?? "",
|
|
512
542
|
screenSpaceKey(spaces.start),
|
|
513
543
|
screenSpaceKey(spaces.mid),
|
|
514
544
|
screenSpaceKey(spaces.end)
|
|
@@ -575,11 +605,24 @@ function outlineStrokeMesh(glyphKey, d, emWidth, stroke, metric) {
|
|
|
575
605
|
function _resetOutlineStrokeMeshCacheForTests() {
|
|
576
606
|
cache5 = /* @__PURE__ */ new Map();
|
|
577
607
|
}
|
|
608
|
+
|
|
609
|
+
// src/shaders/straightRgb.ts
|
|
610
|
+
var STRAIGHT_RGB_GLSL = (
|
|
611
|
+
/* glsl */
|
|
612
|
+
`
|
|
613
|
+
vec3 straightRgb(vec4 premultiplied) {
|
|
614
|
+
return premultiplied.rgb / max(premultiplied.a, 0.0001);
|
|
615
|
+
}
|
|
616
|
+
`
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
// src/shaders/batchFill.ts
|
|
578
620
|
var BATCH_TEXTURE_SLOTS = 8;
|
|
579
621
|
var WHITE_SLOT = 0;
|
|
580
622
|
var PAINT_MODE_PLAIN = 0;
|
|
581
623
|
var PAINT_MODE_RADIAL = 3;
|
|
582
624
|
var PAINT_MODE_CONIC = 4;
|
|
625
|
+
var PAINT_MODE_IMAGE = 5;
|
|
583
626
|
var INV_TAU = (1 / (2 * Math.PI)).toFixed(10);
|
|
584
627
|
function packSlot(slot, mode) {
|
|
585
628
|
return slot + BATCH_TEXTURE_SLOTS * mode;
|
|
@@ -651,8 +694,9 @@ ${sampleChain(BATCH_TEXTURE_SLOTS)}
|
|
|
651
694
|
return texture(u_samplers[0], uv);
|
|
652
695
|
}
|
|
653
696
|
${GLYPH_COVERAGE_GLSL}
|
|
697
|
+
${STRAIGHT_RGB_GLSL}
|
|
654
698
|
void main() {
|
|
655
|
-
float isGrad = step(2.5, v_paintMode);
|
|
699
|
+
float isGrad = step(2.5, v_paintMode) * step(v_paintMode, ${PAINT_MODE_CONIC + 0.5});
|
|
656
700
|
// A radial or conic vertex carries a gradient-space coordinate rather than a
|
|
657
701
|
// texture coordinate, and its atlas row rather than an alpha. The sample is
|
|
658
702
|
// split across the branch rather than the coordinate selected before it: an
|
|
@@ -674,6 +718,8 @@ void main() {
|
|
|
674
718
|
} else {
|
|
675
719
|
texel = sampleSlot(v_texSlot, v_uv);
|
|
676
720
|
}
|
|
721
|
+
// At 0 the mix is the texel itself, so a solid still gets its color exactly.
|
|
722
|
+
texel.rgb = mix(texel.rgb, straightRgb(texel), step(${PAINT_MODE_IMAGE - 0.5}, v_paintMode));
|
|
677
723
|
float isGlyph = step(0.5, v_paintMode) * step(v_paintMode, 2.5);
|
|
678
724
|
// Unconditional, and multiplied out afterwards rather than branched around:
|
|
679
725
|
// see the file header.
|
|
@@ -821,8 +867,7 @@ var DrawBatch = class {
|
|
|
821
867
|
/** Whether the staged run is quads alone, so its indices are the canonical
|
|
822
868
|
* pattern and a slot already holding that pattern needs no upload. */
|
|
823
869
|
pureRects = true;
|
|
824
|
-
/** The rect every push is cut to, or `null`.
|
|
825
|
-
* clips enter and leave — see `batchClip.ts`. A push returns `false`, having
|
|
870
|
+
/** The rect every push is cut to, or `null`. A push returns `false`, having
|
|
826
871
|
* staged nothing, for geometry it cannot cut exactly; see `clipQuad`. */
|
|
827
872
|
clip = null;
|
|
828
873
|
/** Polygon scratch for `clip`: a quad cut by four edges has at most eight
|
|
@@ -857,8 +902,7 @@ var DrawBatch = class {
|
|
|
857
902
|
}
|
|
858
903
|
/**
|
|
859
904
|
* Append one rect's four corners through `m`, all carrying `rgba` (straight
|
|
860
|
-
* alpha).
|
|
861
|
-
* cover it and the batch draws at `u_model` identity.
|
|
905
|
+
* alpha).
|
|
862
906
|
*/
|
|
863
907
|
pushRect(x, y, w, h, m, r, g, b, a) {
|
|
864
908
|
this.reserve(4, 6);
|
|
@@ -881,17 +925,14 @@ var DrawBatch = class {
|
|
|
881
925
|
/**
|
|
882
926
|
* Append one image quad: the destination rect `(x, y, w, h)` mapped through
|
|
883
927
|
* `m`, sampling `(u0, v0)`-`(u1, v1)`, every corner carrying `post` as the
|
|
884
|
-
* after-the-color-matrix alpha factor.
|
|
885
|
-
*
|
|
886
|
-
* Corners wind top-left, top-right, bottom-right, bottom-left with UVs
|
|
887
|
-
* following — the same winding `pushRect` uses, which is what lets a run of
|
|
888
|
-
* mixed rects and quads keep the canonical index pattern. The flips a command
|
|
889
|
-
* asks for are already in the `u` / `v` the caller passes.
|
|
928
|
+
* after-the-color-matrix alpha factor and `rgb` as the scale on its texel.
|
|
929
|
+
* `slot` is the texture unit the corners sample.
|
|
890
930
|
*
|
|
891
|
-
*
|
|
892
|
-
*
|
|
931
|
+
* Corners wind top-left, top-right, bottom-right, bottom-left — the same
|
|
932
|
+
* winding `pushRect` uses, which is what lets a run of mixed rects and quads
|
|
933
|
+
* keep the canonical index pattern.
|
|
893
934
|
*/
|
|
894
|
-
pushQuad(x, y, w, h, m, u0, v0, u1, v1, post, slot) {
|
|
935
|
+
pushQuad(x, y, w, h, m, u0, v0, u1, v1, post, slot, r = 1, g = 1, b = 1) {
|
|
895
936
|
this.reserve(4, 6);
|
|
896
937
|
let i = this.nVerts * FLOATS_PER_VERTEX;
|
|
897
938
|
const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];
|
|
@@ -901,11 +942,11 @@ var DrawBatch = class {
|
|
|
901
942
|
const bx = ma * x1 + mc * y + mtx, by = mb * x1 + md * y + mty;
|
|
902
943
|
const cx = ma * x1 + mc * y1 + mtx, cy = mb * x1 + md * y1 + mty;
|
|
903
944
|
const dx = ma * x + mc * y1 + mtx, dy = mb * x + md * y1 + mty;
|
|
904
|
-
const P = packSlot(slot,
|
|
905
|
-
i = this.writeVertex(i, ax, ay,
|
|
906
|
-
i = this.writeVertex(i, bx, by,
|
|
907
|
-
i = this.writeVertex(i, cx, cy,
|
|
908
|
-
this.writeVertex(i, dx, dy,
|
|
945
|
+
const P = packSlot(slot, PAINT_MODE_IMAGE);
|
|
946
|
+
i = this.writeVertex(i, ax, ay, r, g, b, 1, u0, v0, post, P);
|
|
947
|
+
i = this.writeVertex(i, bx, by, r, g, b, 1, u1, v0, post, P);
|
|
948
|
+
i = this.writeVertex(i, cx, cy, r, g, b, 1, u1, v1, post, P);
|
|
949
|
+
this.writeVertex(i, dx, dy, r, g, b, 1, u0, v1, post, P);
|
|
909
950
|
this.pushQuadIndices();
|
|
910
951
|
return this.clip === null || this.clipQuad(this.nVerts - 4, this.nIdx - 6, this.clip);
|
|
911
952
|
}
|
|
@@ -915,20 +956,11 @@ var DrawBatch = class {
|
|
|
915
956
|
* `rgba`.
|
|
916
957
|
*
|
|
917
958
|
* `mode` says which channels of that atlas carry the distance field, and
|
|
918
|
-
* rides the vertices packed into the slot
|
|
919
|
-
*
|
|
920
|
-
* threshold does not: it is `u_synthBold`, and `draw.ts` breaks the run when
|
|
921
|
-
* it changes.
|
|
922
|
-
*
|
|
923
|
-
* **A synthetic oblique is sheared here rather than in the shader.** The old
|
|
924
|
-
* text program carried the baseline per vertex and skewed against
|
|
925
|
-
* `u_synthItalic`; the batch places its own corners, so the shear is one
|
|
926
|
-
* multiply while they are being placed, and `tanItalic` is 0 for an upright
|
|
927
|
-
* face. It has to happen before `m`, which is where the shader had it too.
|
|
959
|
+
* rides the vertices packed into the slot. The synthetic-bold threshold does
|
|
960
|
+
* not: it is `u_synthBold`, and the caller breaks the run when it changes.
|
|
928
961
|
*
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
* keep the canonical index pattern.
|
|
962
|
+
* A synthetic oblique is sheared here, before `m`; `tanItalic` is 0 for an
|
|
963
|
+
* upright face. Corners wind as `pushQuad`'s do.
|
|
932
964
|
*/
|
|
933
965
|
pushGlyph(x0, y0, x1, y1, baselineY, tanItalic, m, u0, v0, u1, v1, r, g, b, a, slot, mode) {
|
|
934
966
|
this.reserve(4, 6);
|
|
@@ -955,16 +987,10 @@ var DrawBatch = class {
|
|
|
955
987
|
* through `m`, sampling the ramp atlas at `slot`.
|
|
956
988
|
*
|
|
957
989
|
* `uv` gives each channel of `a_uv` as an affine function of the coordinates
|
|
958
|
-
* the corners arrive in
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
* a triangle is exact. `mode` says which of the two the fragment shader is
|
|
963
|
-
* looking at, and `post` carries the atlas row for the modes that read it —
|
|
964
|
-
* see `shaders/batchFill.ts`.
|
|
965
|
-
*
|
|
966
|
-
* Values outside 0..1 are the sampler's business; the atlas clamps to the
|
|
967
|
-
* edge texel, which is what the gradient shader's own `clamp` did.
|
|
990
|
+
* the corners arrive in. `mode` says whether that is a ramp position or a
|
|
991
|
+
* gradient-space coordinate, and `post` carries the atlas row for the modes
|
|
992
|
+
* that read it — see `shaders/batchFill.ts`. Values outside 0..1 clamp to
|
|
993
|
+
* the atlas's edge texel.
|
|
968
994
|
*/
|
|
969
995
|
pushGradientRect(x, y, w, h, m, uv, post, slot, mode, r, g, b, a) {
|
|
970
996
|
this.reserve(4, 6);
|
|
@@ -1121,9 +1147,7 @@ var DrawBatch = class {
|
|
|
1121
1147
|
* subpixel grid the rasterizer snaps to, and the shortened edge then covers
|
|
1122
1148
|
* different samples than the whole one did; along an axis the cut edge is
|
|
1123
1149
|
* the same line exactly. So a slanted quad that crosses is rolled back and
|
|
1124
|
-
* refused, for
|
|
1125
|
-
*
|
|
1126
|
-
* A cut axis-aligned quad is still four corners, so the run stays pure.
|
|
1150
|
+
* refused, for the caller to clip through the stencil instead.
|
|
1127
1151
|
*/
|
|
1128
1152
|
clipQuad(v0, i0, rect) {
|
|
1129
1153
|
const v = this.verts;
|
|
@@ -1276,14 +1300,16 @@ var DrawBatch = class {
|
|
|
1276
1300
|
gl.deleteVertexArray(set.vao);
|
|
1277
1301
|
}
|
|
1278
1302
|
};
|
|
1279
|
-
|
|
1280
|
-
// src/images/imageCache.ts
|
|
1281
1303
|
var MAX_VECTOR_RASTER_PIXELS = 4096 * 4096;
|
|
1282
1304
|
var SHRINK_SLACK = 4;
|
|
1283
1305
|
var FALLBACK_WIDTH = 300;
|
|
1284
1306
|
var FALLBACK_HEIGHT = 150;
|
|
1307
|
+
var DEFAULT_BUDGET_BYTES = 256 * 1024 * 1024;
|
|
1285
1308
|
var cache6 = /* @__PURE__ */ new Map();
|
|
1286
|
-
var
|
|
1309
|
+
var budgetBytes = DEFAULT_BUDGET_BYTES;
|
|
1310
|
+
var reads = 0;
|
|
1311
|
+
var readsAtLastPass = 0;
|
|
1312
|
+
var ready = createEmitter();
|
|
1287
1313
|
var decodedVectors = /* @__PURE__ */ new Map();
|
|
1288
1314
|
function isVectorImageSrc(src) {
|
|
1289
1315
|
if (/^data:image\/svg\+xml[;,]/i.test(src)) return true;
|
|
@@ -1328,10 +1354,30 @@ var defaultLoader = async (src, size) => {
|
|
|
1328
1354
|
};
|
|
1329
1355
|
var loader = defaultLoader;
|
|
1330
1356
|
function notify() {
|
|
1331
|
-
|
|
1357
|
+
ready.emit();
|
|
1358
|
+
}
|
|
1359
|
+
var bytesOf = (bitmap) => bitmap ? bitmap.width * bitmap.height * 4 : 0;
|
|
1360
|
+
function enforceBudget(landed) {
|
|
1361
|
+
let total = 0;
|
|
1362
|
+
for (const entry of cache6.values()) total += bytesOf(entry.bitmap);
|
|
1363
|
+
if (total > budgetBytes) {
|
|
1364
|
+
const idle = [...cache6].filter(([src, entry]) => src !== landed && entry.bitmap !== void 0 && entry.readAt <= readsAtLastPass && !imageSourceInLatestFrame(entry.bitmap));
|
|
1365
|
+
idle.sort((a, b) => a[1].readAt - b[1].readAt);
|
|
1366
|
+
for (const [src, entry] of idle) {
|
|
1367
|
+
if (total <= budgetBytes) break;
|
|
1368
|
+
total -= bytesOf(entry.bitmap);
|
|
1369
|
+
releaseImage(src);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
readsAtLastPass = reads;
|
|
1373
|
+
}
|
|
1374
|
+
function setImageCacheBudget(bytes) {
|
|
1375
|
+
const previous = budgetBytes;
|
|
1376
|
+
budgetBytes = bytes;
|
|
1377
|
+
return previous;
|
|
1332
1378
|
}
|
|
1333
1379
|
function startLoad2(src) {
|
|
1334
|
-
const entry = { status: "loading" };
|
|
1380
|
+
const entry = { status: "loading", readAt: ++reads };
|
|
1335
1381
|
cache6.set(src, entry);
|
|
1336
1382
|
loader(src).then(
|
|
1337
1383
|
(bitmap) => {
|
|
@@ -1339,13 +1385,15 @@ function startLoad2(src) {
|
|
|
1339
1385
|
cache6.set(src, {
|
|
1340
1386
|
status: "ready",
|
|
1341
1387
|
bitmap,
|
|
1388
|
+
readAt: entry.readAt,
|
|
1342
1389
|
...isVectorImageSrc(src) ? { vector: { natural: { width: bitmap.width, height: bitmap.height }, scale: 1, failed: /* @__PURE__ */ new Set(), floor: 0 } } : {}
|
|
1343
1390
|
});
|
|
1391
|
+
enforceBudget(src);
|
|
1344
1392
|
notify();
|
|
1345
1393
|
},
|
|
1346
1394
|
() => {
|
|
1347
1395
|
if (cache6.get(src) !== entry) return;
|
|
1348
|
-
cache6.set(src, { status: "error" });
|
|
1396
|
+
cache6.set(src, { status: "error", readAt: entry.readAt });
|
|
1349
1397
|
notify();
|
|
1350
1398
|
}
|
|
1351
1399
|
);
|
|
@@ -1384,6 +1432,7 @@ function maybeReraster(src, entry, v, drawnAt) {
|
|
|
1384
1432
|
v.shrunkTo = grow ? void 0 : target;
|
|
1385
1433
|
v.scale = target;
|
|
1386
1434
|
if (old && old !== bitmap) releaseImageSource(old);
|
|
1435
|
+
enforceBudget(src);
|
|
1387
1436
|
notify();
|
|
1388
1437
|
},
|
|
1389
1438
|
() => {
|
|
@@ -1399,6 +1448,7 @@ function getImageBitmap(src, drawnAt) {
|
|
|
1399
1448
|
startLoad2(src);
|
|
1400
1449
|
return void 0;
|
|
1401
1450
|
}
|
|
1451
|
+
entry.readAt = ++reads;
|
|
1402
1452
|
if (drawnAt && entry.vector) maybeReraster(src, entry, entry.vector, drawnAt);
|
|
1403
1453
|
return entry.bitmap;
|
|
1404
1454
|
}
|
|
@@ -1417,21 +1467,21 @@ function retryImage(src) {
|
|
|
1417
1467
|
notify();
|
|
1418
1468
|
}
|
|
1419
1469
|
function subscribeImageReady(cb) {
|
|
1420
|
-
|
|
1421
|
-
return () => {
|
|
1422
|
-
subscribers.delete(cb);
|
|
1423
|
-
};
|
|
1470
|
+
return ready.subscribe(cb);
|
|
1424
1471
|
}
|
|
1425
1472
|
function __setImageLoaderForTests(fn) {
|
|
1426
1473
|
loader = fn;
|
|
1427
1474
|
}
|
|
1428
1475
|
function _resetImageCacheForTests() {
|
|
1429
1476
|
cache6.clear();
|
|
1430
|
-
|
|
1477
|
+
ready = createEmitter();
|
|
1431
1478
|
decodedVectors.clear();
|
|
1432
1479
|
loader = defaultLoader;
|
|
1480
|
+
budgetBytes = DEFAULT_BUDGET_BYTES;
|
|
1481
|
+
reads = 0;
|
|
1482
|
+
readsAtLastPass = 0;
|
|
1433
1483
|
}
|
|
1434
1484
|
|
|
1435
|
-
export { BATCH_ATTRIBUTE_LOCATIONS, BATCH_FILL_ATTRIBUTES, BATCH_FILL_UNIFORMS, BATCH_FRAG_SRC, BATCH_TEXTURE_SLOTS, BATCH_VERT_SRC, DrawBatch, FLOATS_PER_VERTEX, GLImageCache, PAINT_MODE_CONIC, PAINT_MODE_PLAIN, PAINT_MODE_RADIAL, __setImageLoaderForTests, _resetCacheForTests, _resetImageCacheForTests, _resetMarkerCommandCacheForTests, _resetOutlineMeshCacheForTests, _resetOutlineStrokeMeshCacheForTests, _resetPaintDrawKindsForTests, _resetPatternSpecCacheForTests, _resetProgramRegistryForTests, _resetStrokeMeshCacheForTests, axisAlignedClipRect, cachedMarkerCommands, commandFill, commandStroke, fillInPoseFrame, fillToBoundsFrame, getImageBitmap, getMesh, getPaintDrawKind, getProgramSource, imageStatus, intersectClipRects, isPaintKindKnown,
|
|
1436
|
-
//# sourceMappingURL=chunk-
|
|
1437
|
-
//# sourceMappingURL=chunk-
|
|
1485
|
+
export { BATCH_ATTRIBUTE_LOCATIONS, BATCH_FILL_ATTRIBUTES, BATCH_FILL_UNIFORMS, BATCH_FRAG_SRC, BATCH_TEXTURE_SLOTS, BATCH_VERT_SRC, DrawBatch, FLOATS_PER_VERTEX, GLImageCache, PAINT_MODE_CONIC, PAINT_MODE_PLAIN, PAINT_MODE_RADIAL, STRAIGHT_RGB_GLSL, __setImageLoaderForTests, _resetCacheForTests, _resetImageCacheForTests, _resetMarkerCommandCacheForTests, _resetOutlineMeshCacheForTests, _resetOutlineStrokeMeshCacheForTests, _resetPaintDrawKindsForTests, _resetPatternSpecCacheForTests, _resetProgramRegistryForTests, _resetStrokeMeshCacheForTests, axisAlignedClipRect, cachedMarkerCommands, commandFill, commandPaint, commandStroke, fillInPoseFrame, fillToBoundsFrame, getImageBitmap, getMesh, getPaintDrawKind, getProgramSource, imageStatus, intersectClipRects, isPaintKindKnown, isVectorImageSrc, outlineMesh, outlineStrokeMesh, paintDrawKindRegistry, programSourceRegistry, quantizeEmWidth, quantizeStrokeScale, registerPaintDrawKind, registerPaintDrawKindLoader, registerProgram, releaseImage, releaseImageSource, resolveFillPattern, resolvePatternSpec, retryImage, setImageCacheBudget, strokeMesh, subscribeImageReady, uploadPremultiplied, warmPaintKinds };
|
|
1486
|
+
//# sourceMappingURL=chunk-LFD47OV2.js.map
|
|
1487
|
+
//# sourceMappingURL=chunk-LFD47OV2.js.map
|