@weasel-js/renderer 1.10.1 → 1.11.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/dist/{chunk-K7XCWVQH.js → chunk-CUJEWF5G.js} +42 -10
- package/dist/chunk-CUJEWF5G.js.map +1 -0
- package/dist/{imageCache-DucOxC7l.d.ts → imageCache-CDm3qgsg.d.ts} +13 -1
- package/dist/index.d.ts +113 -17
- package/dist/index.js +721 -197
- package/dist/index.js.map +1 -1
- package/dist/test-seams.d.ts +6 -6
- package/dist/test-seams.js +1 -1
- package/package.json +10 -7
- package/dist/chunk-K7XCWVQH.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
export { createTilePattern } from './chunk-43YL7HZV.js';
|
|
2
|
-
import { BATCH_ATTRIBUTE_LOCATIONS, BATCH_TEXTURE_SLOTS, registerProgram, STRAIGHT_RGB_GLSL, BATCH_FILL_ATTRIBUTES, BATCH_FILL_UNIFORMS, BATCH_MATRICES_FRAG_SRC, BATCH_VERT_SRC, GLImageCache, DrawBatch, getProgramSource, commandFill, commandPaint, BATCH_FRAG_SRC, uploadPremultiplied, RAMP_SLOT, withMatrix, cachedMarkerCommands, BATCH_MATRIX_SLOTS, packSlot, WHITE_SLOT, PAINT_MODE_PLAIN, PAINT_MODE_RADIAL, PAINT_MODE_CONIC, getMesh,
|
|
3
|
-
export { commandFill, commandPaint, commandStroke, fillInPoseFrame, fillToBoundsFrame, getImageBitmap, getPaintDrawKind, getProgramSource, imageStatus, isPaintKindKnown, isVectorImageSrc, paintDrawKindRegistry, programSourceRegistry, registerPaintDrawKind, registerPaintDrawKindLoader, registerProgram, releaseImage, releaseImageSource, resolveFillPattern, resolvePatternSpec, retryImage, setImageCacheBudget, subscribeImageReady, warmPaintKinds } from './chunk-
|
|
2
|
+
import { BATCH_ATTRIBUTE_LOCATIONS, BATCH_TEXTURE_SLOTS, registerProgram, quantizeStrokeScale, STRAIGHT_RGB_GLSL, BATCH_FILL_ATTRIBUTES, BATCH_FILL_UNIFORMS, BATCH_MATRICES_FRAG_SRC, BATCH_VERT_SRC, GLImageCache, DrawBatch, getProgramSource, paintDrawKindRegistry, commandFill, commandPaint, BATCH_FRAG_SRC, uploadPremultiplied, RAMP_SLOT, withMatrix, cachedMarkerCommands, BATCH_MATRIX_SLOTS, packSlot, WHITE_SLOT, PAINT_MODE_PLAIN, PAINT_MODE_RADIAL, PAINT_MODE_CONIC, getMesh, strokeMesh, axisAlignedClipRect, intersectClipRects, getPaintDrawKind, outlineMesh, outlineStrokeMesh, quantizeEmWidth } from './chunk-CUJEWF5G.js';
|
|
3
|
+
export { bitmapSrc, commandFill, commandPaint, commandStroke, fillInPoseFrame, fillToBoundsFrame, getImageBitmap, getPaintDrawKind, getProgramSource, imageStatus, isPaintKindKnown, isVectorImageSrc, paintDrawKindRegistry, programSourceRegistry, registerPaintDrawKind, registerPaintDrawKindLoader, registerProgram, releaseImage, releaseImageSource, resolveFillPattern, resolvePatternSpec, retryImage, setImageCacheBudget, subscribeImageReady, warmPaintKinds } from './chunk-CUJEWF5G.js';
|
|
4
4
|
import { textureRegistry, getTexture } from './chunk-Q4SIHV7Q.js';
|
|
5
5
|
export { registerTexture, textureRegistry, unregisterTexture } from './chunk-Q4SIHV7Q.js';
|
|
6
|
-
import { DEFAULT_BAKE_BUDGET, markAllFontsNotUploaded, resetBakeBudget, GLYPH_COVERAGE_GLSL, syncColorPageTexture, colorPageTextureId, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey, GLYPH_MODE_R8, GLYPH_MODE_MSDF, glyphFieldScale } from '@weasel-js/font';
|
|
6
|
+
import { DEFAULT_BAKE_BUDGET, markAllFontsNotUploaded, glyphGeneration, resetBakeBudget, GLYPH_COVERAGE_GLSL, syncColorPageTexture, colorPageTextureId, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey, GLYPH_MODE_R8, GLYPH_MODE_MSDF, glyphFieldScale, SYNTH_BOLD_AMOUNT, sharperColorGlyph, syncSharpColorPageTexture, sharpColorPageTextureId, DYNAMIC_PAGE_SIZE } from '@weasel-js/font';
|
|
7
7
|
export { canQueryLocalFonts, cssFontFamily, cssFontFamilyLoading, enableLocalFontOutlines, fontOutlineRegistry, fontRegistry, hasFontOutlines, isCanvasFont, listFontOutlines, loadFontOutlines, outlineStatus, registerCanvasFont, registerFont, registerFontOutlines, subscribeGlyphReady, unregisterCanvasFont, unregisterFontOutlines } from '@weasel-js/font';
|
|
8
|
-
import { resolveGradientStops, sampleResolvedStops, resolveScreenLength, isSolidPaint, resolveColor,
|
|
9
|
-
import { mat3 } from '@weasel-js/geom';
|
|
8
|
+
import { resolveGradientStops, sampleResolvedStops, resolveScreenLength, isSolidPaint, strokeHasInk, resolveColor, scaleVertexWidth } from '@weasel-js/paint';
|
|
9
|
+
import { mat3, DEFAULT_FLATTEN_TOLERANCE, resolveCornersWithSources, remapPerAnchor } from '@weasel-js/geom';
|
|
10
10
|
export { mat3, viewToMat3 } from '@weasel-js/geom';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
import { resolveStrokeWidth, markerReach, metricKeepsRects, strokeSpaceOf, resolveMarkerSize } from '@weasel-js/stroke';
|
|
11
|
+
import { isDev } from '@weasel-js/registry';
|
|
12
|
+
import { resolveStrokeWidth, widestStrokeWidth, markerReach, strokeSpaceOf, resolveMarkerSize, metricKeepsRects } from '@weasel-js/stroke';
|
|
14
13
|
export { tessellateStroke } from '@weasel-js/stroke';
|
|
15
14
|
import { resolveTextStyle, resolveRuns, textPoseLayoutInput, cachedLayoutRuns, cachedPaintRuns, verticalAlignOffset, SYNTHETIC_ITALIC_RADIANS } from '@weasel-js/text';
|
|
15
|
+
export { tessellate } from '@weasel-js/geom/tessellate';
|
|
16
16
|
|
|
17
17
|
// src/paintResources.ts
|
|
18
18
|
function createPaintResources() {
|
|
@@ -1127,6 +1127,22 @@ var GroupState = class {
|
|
|
1127
1127
|
this.colorMatrixStack.pop();
|
|
1128
1128
|
}
|
|
1129
1129
|
};
|
|
1130
|
+
var DEFAULT_FLATTEN_TOLERANCE_PX = DEFAULT_FLATTEN_TOLERANCE;
|
|
1131
|
+
function worldFlattenTolerance(tolerance, transform) {
|
|
1132
|
+
if (typeof tolerance === "number") return tolerance;
|
|
1133
|
+
const px = tolerance?.px ?? DEFAULT_FLATTEN_TOLERANCE_PX;
|
|
1134
|
+
const [a, b, , c, d] = transform;
|
|
1135
|
+
const sum = a * a + b * b + c * c + d * d;
|
|
1136
|
+
const det = a * d - b * c;
|
|
1137
|
+
const stretch = Math.sqrt((sum + Math.sqrt(Math.max(0, sum * sum - 4 * det * det))) / 2);
|
|
1138
|
+
const world = px / stretch;
|
|
1139
|
+
if (!(world > 0) || !Number.isFinite(world)) return DEFAULT_FLATTEN_TOLERANCE;
|
|
1140
|
+
return 2 ** Math.floor(Math.log2(world));
|
|
1141
|
+
}
|
|
1142
|
+
function inCssPixels(tolerance, dpr) {
|
|
1143
|
+
if (typeof tolerance === "number") return tolerance;
|
|
1144
|
+
return { px: (tolerance?.px ?? DEFAULT_FLATTEN_TOLERANCE_PX) / dpr };
|
|
1145
|
+
}
|
|
1130
1146
|
|
|
1131
1147
|
// src/effects/effectPrelude.ts
|
|
1132
1148
|
var EFFECT_VERT_SRC = (
|
|
@@ -1259,14 +1275,11 @@ function endParityMask(ctx) {
|
|
|
1259
1275
|
gl.clear(gl.STENCIL_BUFFER_BIT);
|
|
1260
1276
|
gl.disable(gl.STENCIL_TEST);
|
|
1261
1277
|
}
|
|
1262
|
-
|
|
1263
|
-
// src/draw/customShader.ts
|
|
1264
1278
|
var warnedUniforms = /* @__PURE__ */ new Set();
|
|
1265
1279
|
function warnOnce(key, message) {
|
|
1266
1280
|
if (warnedUniforms.has(key)) return;
|
|
1267
1281
|
warnedUniforms.add(key);
|
|
1268
|
-
|
|
1269
|
-
if (isDev) console.warn(message);
|
|
1282
|
+
if (isDev()) console.warn(message);
|
|
1270
1283
|
}
|
|
1271
1284
|
function warnOnceUniform(programId, name) {
|
|
1272
1285
|
warnOnce(
|
|
@@ -1279,8 +1292,7 @@ function setUniform(gl, loc, value, textureCache, nextTexUnit) {
|
|
|
1279
1292
|
const handle = value;
|
|
1280
1293
|
const entry = getTexture(handle.id);
|
|
1281
1294
|
if (!entry) {
|
|
1282
|
-
|
|
1283
|
-
if (isDev) console.warn(`weasel setUniform: TextureHandle "${handle.id}" not registered`);
|
|
1295
|
+
if (isDev()) console.warn(`weasel setUniform: TextureHandle "${handle.id}" not registered`);
|
|
1284
1296
|
return;
|
|
1285
1297
|
}
|
|
1286
1298
|
const unit = nextTexUnit.value++;
|
|
@@ -1297,8 +1309,7 @@ function setUniform(gl, loc, value, textureCache, nextTexUnit) {
|
|
|
1297
1309
|
} else if (value.length === 16) {
|
|
1298
1310
|
gl.uniformMatrix4fv(loc, false, value);
|
|
1299
1311
|
} else {
|
|
1300
|
-
|
|
1301
|
-
if (isDev) throw new TypeError(`weasel setUniform: Float32Array must be length 9 (mat3) or 16 (mat4), got ${value.length}`);
|
|
1312
|
+
if (isDev()) throw new TypeError(`weasel setUniform: Float32Array must be length 9 (mat3) or 16 (mat4), got ${value.length}`);
|
|
1302
1313
|
}
|
|
1303
1314
|
return;
|
|
1304
1315
|
}
|
|
@@ -1315,8 +1326,7 @@ function setUniform(gl, loc, value, textureCache, nextTexUnit) {
|
|
|
1315
1326
|
gl.uniform4fv(loc, arr);
|
|
1316
1327
|
break;
|
|
1317
1328
|
default: {
|
|
1318
|
-
|
|
1319
|
-
if (isDev) throw new TypeError(`weasel setUniform: array length ${arr.length} not supported`);
|
|
1329
|
+
if (isDev()) throw new TypeError(`weasel setUniform: array length ${arr.length} not supported`);
|
|
1320
1330
|
}
|
|
1321
1331
|
}
|
|
1322
1332
|
return;
|
|
@@ -1360,14 +1370,13 @@ function isArrayValue(value) {
|
|
|
1360
1370
|
return Array.isArray(value) || value instanceof Float32Array || value instanceof Int32Array || value instanceof Uint32Array;
|
|
1361
1371
|
}
|
|
1362
1372
|
function setUniformArray(gl, name, arr, value) {
|
|
1363
|
-
const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
1364
1373
|
const upload = ARRAY_UPLOADS[arr.type];
|
|
1365
1374
|
if (!upload) {
|
|
1366
|
-
if (isDev) console.warn(`weasel setUniform: array uniform "${name}" has type ${arr.type}, which cannot be written as a whole`);
|
|
1375
|
+
if (isDev()) console.warn(`weasel setUniform: array uniform "${name}" has type ${arr.type}, which cannot be written as a whole`);
|
|
1367
1376
|
return;
|
|
1368
1377
|
}
|
|
1369
1378
|
if (value.length % upload.n !== 0 || value.length > upload.n * arr.size) {
|
|
1370
|
-
if (isDev) {
|
|
1379
|
+
if (isDev()) {
|
|
1371
1380
|
throw new TypeError(
|
|
1372
1381
|
`weasel setUniform: "${name}" is ${arr.type}[${arr.size}], so its value must hold a multiple of ${upload.n} numbers, at most ${upload.n * arr.size}; got ${value.length}`
|
|
1373
1382
|
);
|
|
@@ -1562,7 +1571,8 @@ function returnToRenderTarget(ctx, width, height, scissorWasOn) {
|
|
|
1562
1571
|
const target = ctx.renderTarget ?? null;
|
|
1563
1572
|
gl.bindFramebuffer(gl.FRAMEBUFFER, target ? target.fbo : null);
|
|
1564
1573
|
if (target) {
|
|
1565
|
-
|
|
1574
|
+
if (target.restore) target.restore();
|
|
1575
|
+
else gl.viewport(0, 0, width, height);
|
|
1566
1576
|
} else {
|
|
1567
1577
|
ctx.restoreTargetRect?.();
|
|
1568
1578
|
if (scissorWasOn) gl.enable(gl.SCISSOR_TEST);
|
|
@@ -1603,14 +1613,15 @@ function drawFullscreenQuad(ctx, program, texture, width, height, uniforms, comp
|
|
|
1603
1613
|
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
1604
1614
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
|
|
1605
1615
|
}
|
|
1616
|
+
|
|
1617
|
+
// src/draw/mesh.ts
|
|
1618
|
+
function flattenToleranceOf(ctx) {
|
|
1619
|
+
return worldFlattenTolerance(ctx.flattenTolerance, ctx.state.transform);
|
|
1620
|
+
}
|
|
1606
1621
|
function fillMesh(ctx, path) {
|
|
1607
|
-
|
|
1608
|
-
return tessellate(path, { flattenTolerance: ctx.flattenTolerance });
|
|
1609
|
-
}
|
|
1610
|
-
return getMesh(path);
|
|
1622
|
+
return getMesh(path, flattenToleranceOf(ctx));
|
|
1611
1623
|
}
|
|
1612
1624
|
function meshHandle(ctx, mesh) {
|
|
1613
|
-
if (ctx.flattenTolerance !== void 0) return ctx.meshCache.uploadTransient(mesh);
|
|
1614
1625
|
return ctx.meshCache.handleFor(mesh);
|
|
1615
1626
|
}
|
|
1616
1627
|
function fillMeshHandle(ctx, path) {
|
|
@@ -1740,22 +1751,21 @@ function flushBatch(ctx) {
|
|
|
1740
1751
|
}
|
|
1741
1752
|
|
|
1742
1753
|
// src/draw/clip.ts
|
|
1743
|
-
function enterGroupClip(ctx, clip) {
|
|
1754
|
+
function enterGroupClip(ctx, clip, transform = ctx.state.transform) {
|
|
1744
1755
|
const pending = ctx.clipStack.reduce((n, e) => n + (e.depth === 0 ? 1 : 0), 0);
|
|
1745
1756
|
if (ctx.clipDepth + pending + 1 > 7) {
|
|
1746
1757
|
throw new Error(
|
|
1747
1758
|
"weasel: clip nesting depth exceeded (max 7). You can't nest more than 7 levels of clipped containers in a single draw tree. Flatten the hierarchy or compose poses outside the scene graph."
|
|
1748
1759
|
);
|
|
1749
1760
|
}
|
|
1750
|
-
enterClip(ctx, clip);
|
|
1761
|
+
enterClip(ctx, clip, transform);
|
|
1751
1762
|
}
|
|
1752
|
-
function enterClip(ctx, path) {
|
|
1753
|
-
const transform = ctx.state.transform;
|
|
1763
|
+
function enterClip(ctx, path, transform) {
|
|
1754
1764
|
const rect = axisAlignedClipRect(path, transform);
|
|
1755
1765
|
if (rect === null) {
|
|
1756
1766
|
materializeClips(ctx);
|
|
1757
1767
|
const depth = ctx.clipDepth + 1;
|
|
1758
|
-
pushClip(ctx, path, depth);
|
|
1768
|
+
pushClip(ctx, path, depth, transform);
|
|
1759
1769
|
ctx.clipDepth = depth;
|
|
1760
1770
|
ctx.clipStack.push({ path, transform, rect: null, depth });
|
|
1761
1771
|
} else {
|
|
@@ -1869,6 +1879,287 @@ function popClip(ctx, path, oldDepth, model = ctx.state.transform) {
|
|
|
1869
1879
|
gl.colorMask(true, true, true, true);
|
|
1870
1880
|
gl.stencilMask(255);
|
|
1871
1881
|
}
|
|
1882
|
+
|
|
1883
|
+
// src/DrawCommand.ts
|
|
1884
|
+
var SPRITE_STRIDE = 9;
|
|
1885
|
+
var CULL_SLACK_PX = 1;
|
|
1886
|
+
var DEFAULT_MITER_LIMIT = 4;
|
|
1887
|
+
function cullDrawCommands(cmds, transform, rect) {
|
|
1888
|
+
return cullList(cmds, transform, slackBox(rect));
|
|
1889
|
+
}
|
|
1890
|
+
function cullList(cmds, m, box) {
|
|
1891
|
+
let out = null;
|
|
1892
|
+
for (let i = 0; i < cmds.length; i++) {
|
|
1893
|
+
const cmd = cmds[i];
|
|
1894
|
+
const kept = cullOne(cmd, m, box);
|
|
1895
|
+
if (kept === cmd) {
|
|
1896
|
+
out?.push(cmd);
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
out ??= cmds.slice(0, i);
|
|
1900
|
+
if (kept) out.push(kept);
|
|
1901
|
+
}
|
|
1902
|
+
return out ?? cmds;
|
|
1903
|
+
}
|
|
1904
|
+
function cullOne(cmd, m, box) {
|
|
1905
|
+
switch (cmd.kind) {
|
|
1906
|
+
case "group": {
|
|
1907
|
+
if (cmd.effects && cmd.effects.length > 0) return cmd;
|
|
1908
|
+
if (isOwnComposite(cmd.composite)) return cmd;
|
|
1909
|
+
const inner = cmd.transform ? mat3.multiply(m, cmd.transform) : m;
|
|
1910
|
+
const children = cullList(cmd.children, inner, box);
|
|
1911
|
+
if (children === cmd.children) return cmd;
|
|
1912
|
+
return children.length === 0 ? null : { ...cmd, children };
|
|
1913
|
+
}
|
|
1914
|
+
case "path":
|
|
1915
|
+
case "image":
|
|
1916
|
+
case "sprites":
|
|
1917
|
+
return boxMisses(paintedBox(cmd, m), box) ? null : cmd;
|
|
1918
|
+
default:
|
|
1919
|
+
return cmd;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
function paintedBox(cmd, m) {
|
|
1923
|
+
switch (cmd.kind) {
|
|
1924
|
+
case "path":
|
|
1925
|
+
return pathBox(cmd.path, cmd.stroke, m);
|
|
1926
|
+
case "image":
|
|
1927
|
+
return screenBox(cmd.x, cmd.y, cmd.x + cmd.w, cmd.y + cmd.h, m);
|
|
1928
|
+
case "sprites":
|
|
1929
|
+
return spritesBox(cmd.sprites, m);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
function pathBox(path, stroke, m) {
|
|
1933
|
+
let minX, minY, maxX, maxY;
|
|
1934
|
+
if (path.kind === "rect") {
|
|
1935
|
+
minX = Math.min(path.x, path.x + path.width);
|
|
1936
|
+
maxX = Math.max(path.x, path.x + path.width);
|
|
1937
|
+
minY = Math.min(path.y, path.y + path.height);
|
|
1938
|
+
maxY = Math.max(path.y, path.y + path.height);
|
|
1939
|
+
} else if (path.kind === "polygon") {
|
|
1940
|
+
const c = path.coords;
|
|
1941
|
+
if (c.length < 2) return null;
|
|
1942
|
+
minX = maxX = c[0];
|
|
1943
|
+
minY = maxY = c[1];
|
|
1944
|
+
for (let i = 2; i + 1 < c.length; i += 2) {
|
|
1945
|
+
const x = c[i], y = c[i + 1];
|
|
1946
|
+
if (x < minX) minX = x;
|
|
1947
|
+
else if (x > maxX) maxX = x;
|
|
1948
|
+
if (y < minY) minY = y;
|
|
1949
|
+
else if (y > maxY) maxY = y;
|
|
1950
|
+
}
|
|
1951
|
+
} else {
|
|
1952
|
+
return null;
|
|
1953
|
+
}
|
|
1954
|
+
const reach = stroke ? strokeReach(stroke, m) : 0;
|
|
1955
|
+
return screenBox(minX - reach, minY - reach, maxX + reach, maxY + reach, m);
|
|
1956
|
+
}
|
|
1957
|
+
function strokeReach(stroke, m) {
|
|
1958
|
+
return strokeReachAt(stroke, mat3.meanScaleOf(m), mat3.minScaleOf(m));
|
|
1959
|
+
}
|
|
1960
|
+
function strokeReachAt(stroke, scale, least = scale) {
|
|
1961
|
+
const at = least > 0 ? least : scale;
|
|
1962
|
+
const base = resolveStrokeWidth(stroke.width ?? 1, typeof stroke.width === "object" ? at : scale);
|
|
1963
|
+
const width = widestStrokeWidth(base, stroke.vertexWidths);
|
|
1964
|
+
const spike = Math.max(stroke.miterLimit ?? DEFAULT_MITER_LIMIT, Math.SQRT2);
|
|
1965
|
+
const heads = markerReach(stroke);
|
|
1966
|
+
return Math.max(Math.abs(width) * spike, heads.world, resolveScreenLength({ px: heads.px }, at));
|
|
1967
|
+
}
|
|
1968
|
+
function spritesBox(sprites, m) {
|
|
1969
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1970
|
+
for (let i = 0; i + SPRITE_STRIDE <= sprites.length; i += SPRITE_STRIDE) {
|
|
1971
|
+
const x0 = sprites[i], y0 = sprites[i + 1];
|
|
1972
|
+
const x1 = x0 + sprites[i + 2], y1 = y0 + sprites[i + 3];
|
|
1973
|
+
minX = Math.min(minX, x0, x1);
|
|
1974
|
+
maxX = Math.max(maxX, x0, x1);
|
|
1975
|
+
minY = Math.min(minY, y0, y1);
|
|
1976
|
+
maxY = Math.max(maxY, y0, y1);
|
|
1977
|
+
}
|
|
1978
|
+
if (minX === Infinity) return null;
|
|
1979
|
+
return screenBox(minX, minY, maxX, maxY, m);
|
|
1980
|
+
}
|
|
1981
|
+
function boundsMissRect(bounds, transform, rect) {
|
|
1982
|
+
const x0 = Math.min(bounds.x, bounds.x + bounds.width);
|
|
1983
|
+
const x1 = Math.max(bounds.x, bounds.x + bounds.width);
|
|
1984
|
+
const y0 = Math.min(bounds.y, bounds.y + bounds.height);
|
|
1985
|
+
const y1 = Math.max(bounds.y, bounds.y + bounds.height);
|
|
1986
|
+
return boxMisses(screenBox(x0, y0, x1, y1, transform), slackBox(rect));
|
|
1987
|
+
}
|
|
1988
|
+
function slackBox(rect) {
|
|
1989
|
+
return {
|
|
1990
|
+
minX: rect.x - CULL_SLACK_PX,
|
|
1991
|
+
minY: rect.y - CULL_SLACK_PX,
|
|
1992
|
+
maxX: rect.x + rect.width + CULL_SLACK_PX,
|
|
1993
|
+
maxY: rect.y + rect.height + CULL_SLACK_PX
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
function screenBox(x0, y0, x1, y1, m) {
|
|
1997
|
+
const [ax, ay] = mat3.apply(m, x0, y0);
|
|
1998
|
+
const [bx, by] = mat3.apply(m, x1, y0);
|
|
1999
|
+
const [cx, cy] = mat3.apply(m, x1, y1);
|
|
2000
|
+
const [dx, dy] = mat3.apply(m, x0, y1);
|
|
2001
|
+
return {
|
|
2002
|
+
minX: Math.min(ax, bx, cx, dx),
|
|
2003
|
+
minY: Math.min(ay, by, cy, dy),
|
|
2004
|
+
maxX: Math.max(ax, bx, cx, dx),
|
|
2005
|
+
maxY: Math.max(ay, by, cy, dy)
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
function boxMisses(b, box) {
|
|
2009
|
+
return b !== null && (b.maxX < box.minX || b.minX > box.maxX || b.maxY < box.minY || b.minY > box.maxY);
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
// src/retain/contentBounds.ts
|
|
2013
|
+
var EMPTY = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
|
|
2014
|
+
var ANYWHERE = { minX: -Infinity, minY: -Infinity, maxX: Infinity, maxY: Infinity };
|
|
2015
|
+
function contentBounds(children, transform, clips, scale, width, height) {
|
|
2016
|
+
let box = listBox(children, transform);
|
|
2017
|
+
for (const clip of clips) box = clipped(box, pathBox(clip.path, void 0, clip.transform));
|
|
2018
|
+
const x0 = Math.max(0, Math.floor((box.minX - CULL_SLACK_PX) * scale));
|
|
2019
|
+
const y0 = Math.max(0, Math.floor((box.minY - CULL_SLACK_PX) * scale));
|
|
2020
|
+
const x1 = Math.min(width, Math.ceil((box.maxX + CULL_SLACK_PX) * scale));
|
|
2021
|
+
const y1 = Math.min(height, Math.ceil((box.maxY + CULL_SLACK_PX) * scale));
|
|
2022
|
+
if ([x0, y0, x1, y1].some(Number.isNaN)) return { x: 0, y: 0, width, height };
|
|
2023
|
+
if (x1 <= x0 || y1 <= y0) return { x: 0, y: 0, width: 0, height: 0 };
|
|
2024
|
+
return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
|
|
2025
|
+
}
|
|
2026
|
+
function listBox(cmds, m) {
|
|
2027
|
+
let acc = EMPTY;
|
|
2028
|
+
for (const cmd of cmds) {
|
|
2029
|
+
acc = union(acc, commandBox(cmd, m));
|
|
2030
|
+
if (acc === ANYWHERE) break;
|
|
2031
|
+
}
|
|
2032
|
+
return acc;
|
|
2033
|
+
}
|
|
2034
|
+
function commandBox(cmd, m) {
|
|
2035
|
+
switch (cmd.kind) {
|
|
2036
|
+
case "group": {
|
|
2037
|
+
const inner = cmd.transform ? mat3.multiply(m, cmd.transform) : m;
|
|
2038
|
+
const box = cmd.effects && cmd.effects.length > 0 ? ANYWHERE : listBox(cmd.children, inner);
|
|
2039
|
+
return cmd.clip ? clipped(box, pathBox(cmd.clip, void 0, inner)) : box;
|
|
2040
|
+
}
|
|
2041
|
+
case "path":
|
|
2042
|
+
case "image":
|
|
2043
|
+
case "sprites":
|
|
2044
|
+
return paintedBox(cmd, m) ?? ANYWHERE;
|
|
2045
|
+
default:
|
|
2046
|
+
return ANYWHERE;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
function union(a, b) {
|
|
2050
|
+
if (a === EMPTY || b === ANYWHERE) return b;
|
|
2051
|
+
if (b === EMPTY || a === ANYWHERE) return a;
|
|
2052
|
+
return {
|
|
2053
|
+
minX: Math.min(a.minX, b.minX),
|
|
2054
|
+
minY: Math.min(a.minY, b.minY),
|
|
2055
|
+
maxX: Math.max(a.maxX, b.maxX),
|
|
2056
|
+
maxY: Math.max(a.maxY, b.maxY)
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
function clipped(a, clip) {
|
|
2060
|
+
if (!clip || a === EMPTY) return a;
|
|
2061
|
+
const box = {
|
|
2062
|
+
minX: Math.max(a.minX, clip.minX),
|
|
2063
|
+
minY: Math.max(a.minY, clip.minY),
|
|
2064
|
+
maxX: Math.min(a.maxX, clip.maxX),
|
|
2065
|
+
maxY: Math.min(a.maxY, clip.maxY)
|
|
2066
|
+
};
|
|
2067
|
+
return box.minX > box.maxX || box.minY > box.maxY ? EMPTY : box;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
// src/draw/retained.ts
|
|
2071
|
+
var COMPOSITE_AS_DRAWN = { alpha: 1, colorMatrix: IDENTITY_COLOR_MATRIX };
|
|
2072
|
+
var ownPixelsOnly = /* @__PURE__ */ new WeakMap();
|
|
2073
|
+
function readsOnlyOwnPixels(children) {
|
|
2074
|
+
const known = ownPixelsOnly.get(children);
|
|
2075
|
+
if (known !== void 0) return known;
|
|
2076
|
+
let ok = true;
|
|
2077
|
+
for (const child of children) {
|
|
2078
|
+
if (child.kind !== "group") continue;
|
|
2079
|
+
if (isOwnComposite(child.composite) || !readsOnlyOwnPixels(child.children)) {
|
|
2080
|
+
ok = false;
|
|
2081
|
+
break;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
ownPixelsOnly.set(children, ok);
|
|
2085
|
+
return ok;
|
|
2086
|
+
}
|
|
2087
|
+
function drawRetainedGroup(ctx, cmd, ownsSurface2, dispatch2) {
|
|
2088
|
+
const store = ctx.retained;
|
|
2089
|
+
const width = ctx.deviceWidth;
|
|
2090
|
+
const height = ctx.deviceHeight;
|
|
2091
|
+
if (!store || !width || !height) return false;
|
|
2092
|
+
if (ownsSurface2 || ctx.renderTarget || !readsOnlyOwnPixels(cmd.children)) {
|
|
2093
|
+
store.decline();
|
|
2094
|
+
return false;
|
|
2095
|
+
}
|
|
2096
|
+
const id = cmd.retain;
|
|
2097
|
+
ctx.state.push({ transform: cmd.transform, alpha: cmd.alpha, colorMatrix: cmd.colorMatrix });
|
|
2098
|
+
const clips = ctx.clipStack.map(({ path, transform }) => ({ path, transform }));
|
|
2099
|
+
if (cmd.clip) clips.push({ path: cmd.clip, transform: ctx.state.transform });
|
|
2100
|
+
const key = {
|
|
2101
|
+
children: cmd.children,
|
|
2102
|
+
transform: ctx.state.transform,
|
|
2103
|
+
alpha: ctx.state.alpha,
|
|
2104
|
+
colorMatrix: ctx.state.colorMatrix,
|
|
2105
|
+
viewMatrix: ctx.viewMatrix,
|
|
2106
|
+
clips
|
|
2107
|
+
};
|
|
2108
|
+
let pixels = store.lookup(id, width, height, key);
|
|
2109
|
+
if (!pixels) {
|
|
2110
|
+
const rect2 = contentBounds(cmd.children, ctx.state.transform, clips, width / ctx.widthCss, width, height);
|
|
2111
|
+
const draw = store.reserve(id, width, height, rect2);
|
|
2112
|
+
if (!draw) {
|
|
2113
|
+
ctx.state.pop();
|
|
2114
|
+
return false;
|
|
2115
|
+
}
|
|
2116
|
+
flushBatch(ctx);
|
|
2117
|
+
if (draw.fbo) drawInto(ctx, cmd.children, clips, draw.fbo, rect2, dispatch2);
|
|
2118
|
+
pixels = draw.commit(key);
|
|
2119
|
+
}
|
|
2120
|
+
ctx.state.pop();
|
|
2121
|
+
const { texture, rect } = pixels;
|
|
2122
|
+
if (!texture) return true;
|
|
2123
|
+
flushBatch(ctx);
|
|
2124
|
+
const composite = ctx.ensureProgram?.(compositeProgramId()) ?? null;
|
|
2125
|
+
if (composite && ctx.viewportWithin) {
|
|
2126
|
+
applyClipTest(ctx, 0);
|
|
2127
|
+
ctx.viewportWithin(rect);
|
|
2128
|
+
drawFullscreenQuad(ctx, composite, texture, rect.width, rect.height, void 0, COMPOSITE_AS_DRAWN);
|
|
2129
|
+
ctx.restoreTargetRect?.();
|
|
2130
|
+
}
|
|
2131
|
+
return true;
|
|
2132
|
+
}
|
|
2133
|
+
function drawInto(ctx, children, clips, fbo, rect, dispatch2) {
|
|
2134
|
+
const gl = ctx.gl;
|
|
2135
|
+
const width = ctx.deviceWidth;
|
|
2136
|
+
const height = ctx.deviceHeight;
|
|
2137
|
+
const parentClipDepth = ctx.clipDepth;
|
|
2138
|
+
const parentClipStack = ctx.clipStack;
|
|
2139
|
+
const parentBatchClip = ctx.drawBatch.clip;
|
|
2140
|
+
const scissorWasOn = gl.isEnabled(gl.SCISSOR_TEST);
|
|
2141
|
+
ctx.clipDepth = 0;
|
|
2142
|
+
ctx.clipStack = [];
|
|
2143
|
+
ctx.drawBatch.clip = null;
|
|
2144
|
+
const restore = () => {
|
|
2145
|
+
gl.viewport(-rect.x, rect.y + rect.height - height, width, height);
|
|
2146
|
+
gl.scissor(0, 0, rect.width, rect.height);
|
|
2147
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
2148
|
+
};
|
|
2149
|
+
ctx.renderTarget = { fbo, restore };
|
|
2150
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
2151
|
+
restore();
|
|
2152
|
+
gl.stencilMask(255);
|
|
2153
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
|
|
2154
|
+
for (const clip of clips) enterGroupClip(ctx, clip.path, clip.transform);
|
|
2155
|
+
for (const child of children) dispatch2(ctx, child);
|
|
2156
|
+
flushBatch(ctx);
|
|
2157
|
+
ctx.clipDepth = parentClipDepth;
|
|
2158
|
+
ctx.clipStack = parentClipStack;
|
|
2159
|
+
ctx.drawBatch.clip = parentBatchClip;
|
|
2160
|
+
ctx.renderTarget = null;
|
|
2161
|
+
returnToRenderTarget(ctx, width, height, scissorWasOn);
|
|
2162
|
+
}
|
|
1872
2163
|
var WORLD = {};
|
|
1873
2164
|
function resolveStrokeLengths(transform, cmd) {
|
|
1874
2165
|
const stroke = cmd.stroke;
|
|
@@ -1903,6 +2194,36 @@ function isPxMarker(ref) {
|
|
|
1903
2194
|
function resolvedMarker(ref, scale) {
|
|
1904
2195
|
return { ...ref, size: resolveMarkerSize(ref, 0, quantizeStrokeScale(ref.size.px, scale)) };
|
|
1905
2196
|
}
|
|
2197
|
+
var REMAPPED = /* @__PURE__ */ new WeakMap();
|
|
2198
|
+
function remapped(values, sources, stride) {
|
|
2199
|
+
let bySources = REMAPPED.get(values);
|
|
2200
|
+
if (bySources === void 0) {
|
|
2201
|
+
bySources = /* @__PURE__ */ new WeakMap();
|
|
2202
|
+
REMAPPED.set(values, bySources);
|
|
2203
|
+
}
|
|
2204
|
+
let out = bySources.get(sources);
|
|
2205
|
+
if (out === void 0) {
|
|
2206
|
+
out = remapPerAnchor(values, sources, stride);
|
|
2207
|
+
bySources.set(sources, out);
|
|
2208
|
+
}
|
|
2209
|
+
return out;
|
|
2210
|
+
}
|
|
2211
|
+
function withCornersResolved(cmd) {
|
|
2212
|
+
if (cmd.path.kind !== "polygon") return cmd;
|
|
2213
|
+
const { path, anchorSources: src } = resolveCornersWithSources(cmd.path);
|
|
2214
|
+
if (path === cmd.path) return cmd;
|
|
2215
|
+
const out = { ...cmd, path };
|
|
2216
|
+
if (src && cmd.vertexColors) out.vertexColors = remapped(cmd.vertexColors, src, 4);
|
|
2217
|
+
if (src && cmd.stroke && (cmd.stroke.vertexColors || cmd.stroke.vertexWidths)) {
|
|
2218
|
+
const { vertexColors, vertexWidths } = cmd.stroke;
|
|
2219
|
+
out.stroke = {
|
|
2220
|
+
...cmd.stroke,
|
|
2221
|
+
...vertexColors ? { vertexColors: remapped(vertexColors, src, 4) } : {},
|
|
2222
|
+
...vertexWidths ? { vertexWidths: remapped(vertexWidths, src, 1) } : {}
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
return out;
|
|
2226
|
+
}
|
|
1906
2227
|
|
|
1907
2228
|
// src/draw/batchRun.ts
|
|
1908
2229
|
function slotForBitmap(staged, image, sampling) {
|
|
@@ -2104,9 +2425,13 @@ function stageRamps(ctx, stops, space, vertices) {
|
|
|
2104
2425
|
rowV: ctx.gradRamps.rowV(ctx.gradRamps.upload(stops, space))
|
|
2105
2426
|
};
|
|
2106
2427
|
}
|
|
2107
|
-
function
|
|
2428
|
+
function solidColor(paint) {
|
|
2108
2429
|
const [r, g, b, a] = resolveColor(paint.color);
|
|
2109
|
-
return
|
|
2430
|
+
return [r, g, b, a * (paint.opacity ?? 1)];
|
|
2431
|
+
}
|
|
2432
|
+
function stagedColor(ctx, paint) {
|
|
2433
|
+
const [r, g, b, a] = solidColor(paint);
|
|
2434
|
+
return foldColor(ctx, r, g, b, a);
|
|
2110
2435
|
}
|
|
2111
2436
|
function pushRect(ctx, rect, fill) {
|
|
2112
2437
|
const slot = stageSolid(ctx, 4);
|
|
@@ -2198,8 +2523,6 @@ function patternTileSpace(spaceInverse, origin, transform) {
|
|
|
2198
2523
|
const unplace = mat3.invert(placement);
|
|
2199
2524
|
return unplace && mat3.multiply(unplace, spaceInverse);
|
|
2200
2525
|
}
|
|
2201
|
-
|
|
2202
|
-
// src/draw/paintBind.ts
|
|
2203
2526
|
function paintBindContext(ctx) {
|
|
2204
2527
|
const cached = BIND_CONTEXTS.get(ctx);
|
|
2205
2528
|
if (cached) return cached;
|
|
@@ -2239,8 +2562,7 @@ function bindPattern(ctx, prog, fill, model) {
|
|
|
2239
2562
|
const tex = fill.texture;
|
|
2240
2563
|
const entry = getTexture(tex.id);
|
|
2241
2564
|
if (!entry) {
|
|
2242
|
-
|
|
2243
|
-
if (isDev) console.warn(`weasel: pattern TextureHandle "${tex.id}" not registered`);
|
|
2565
|
+
if (isDev()) console.warn(`weasel: pattern TextureHandle "${tex.id}" not registered`);
|
|
2244
2566
|
return null;
|
|
2245
2567
|
}
|
|
2246
2568
|
const inverse = gradientSpaceInverse(ctx, fill.units);
|
|
@@ -2310,7 +2632,8 @@ function bindGradient(ctx, prog, fill, model) {
|
|
|
2310
2632
|
}
|
|
2311
2633
|
|
|
2312
2634
|
// src/draw/pathPaint.ts
|
|
2313
|
-
function drawPath(ctx,
|
|
2635
|
+
function drawPath(ctx, given) {
|
|
2636
|
+
const cmd = withCornersResolved(given);
|
|
2314
2637
|
if (cmd.fill?.paint) drawPathFill(ctx, cmd, cmd.fill.paint);
|
|
2315
2638
|
if (cmd.stroke?.paint) drawPathStroke(ctx, cmd);
|
|
2316
2639
|
}
|
|
@@ -2450,7 +2773,7 @@ function drawPathStroke(ctx, rawCmd) {
|
|
|
2450
2773
|
drawPathStrokeUnclipped(ctx, cmd, spaces);
|
|
2451
2774
|
}
|
|
2452
2775
|
const width = resolveStrokeWidth(stroke.width ?? 1, 1);
|
|
2453
|
-
const heads = cachedMarkerCommands(cmd.path, rawCmd.stroke, stroke, width, ctx
|
|
2776
|
+
const heads = cachedMarkerCommands(cmd.path, rawCmd.stroke, stroke, width, flattenToleranceOf(ctx), spaces);
|
|
2454
2777
|
for (const marker of heads) drawPath(ctx, marker);
|
|
2455
2778
|
}
|
|
2456
2779
|
function drawPathStrokeUnclipped(ctx, cmd, spaces) {
|
|
@@ -2458,7 +2781,7 @@ function drawPathStrokeUnclipped(ctx, cmd, spaces) {
|
|
|
2458
2781
|
const paint = stroke.paint;
|
|
2459
2782
|
const isSolid2 = paint.kind === void 0 || paint.kind === "solid";
|
|
2460
2783
|
const solid = paint;
|
|
2461
|
-
const mesh = strokeMesh(cmd.path, stroke, ctx
|
|
2784
|
+
const mesh = strokeMesh(cmd.path, stroke, flattenToleranceOf(ctx), spaces);
|
|
2462
2785
|
if (mesh.indices.length === 0) return;
|
|
2463
2786
|
const hasVColors = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
|
|
2464
2787
|
const batched = batchPaint(ctx, paint, hasVColors);
|
|
@@ -2497,7 +2820,7 @@ var DOUBLED_VERTEX_WIDTHS = /* @__PURE__ */ new WeakMap();
|
|
|
2497
2820
|
function doubledVertexWidths(widths) {
|
|
2498
2821
|
let doubled = DOUBLED_VERTEX_WIDTHS.get(widths);
|
|
2499
2822
|
if (doubled === void 0) {
|
|
2500
|
-
doubled = widths.map((w) => w
|
|
2823
|
+
doubled = widths.map((w) => scaleVertexWidth(w, 2));
|
|
2501
2824
|
DOUBLED_VERTEX_WIDTHS.set(widths, doubled);
|
|
2502
2825
|
}
|
|
2503
2826
|
return doubled;
|
|
@@ -2515,7 +2838,7 @@ function drawPathStrokeStenciled(ctx, cmd, align, spaces) {
|
|
|
2515
2838
|
};
|
|
2516
2839
|
const useVColor = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
|
|
2517
2840
|
const fillHandle = fillMeshHandle(ctx, cmd.path);
|
|
2518
|
-
const ribbonMesh = strokeMesh(cmd.path, widerStroke, ctx
|
|
2841
|
+
const ribbonMesh = strokeMesh(cmd.path, widerStroke, flattenToleranceOf(ctx), spaces);
|
|
2519
2842
|
if (ribbonMesh.indices.length === 0) return;
|
|
2520
2843
|
const ribbonHandle = useVColor ? ctx.meshCache.uploadTransient(ribbonMesh) : ctx.meshCache.uploadRecurring(ribbonMesh);
|
|
2521
2844
|
const gl = ctx.gl;
|
|
@@ -2625,19 +2948,38 @@ function mergeGlyphMeshes(group, dx, dy, meshFor) {
|
|
|
2625
2948
|
}
|
|
2626
2949
|
return { vertices, indices };
|
|
2627
2950
|
}
|
|
2951
|
+
|
|
2952
|
+
// src/draw/modelScale.ts
|
|
2953
|
+
function modelScale(m) {
|
|
2954
|
+
const sx = Math.hypot(m[0], m[1]);
|
|
2955
|
+
const sy = Math.hypot(m[3], m[4]);
|
|
2956
|
+
return Math.sqrt(sx * sy) || 1;
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
// src/draw/colorGlyphs.ts
|
|
2628
2960
|
function drawColorGlyphs(ctx, group, dx, dy) {
|
|
2629
2961
|
const fill = group.fill?.paint;
|
|
2630
2962
|
if (fill === void 0 || group.quads.length === 0) return;
|
|
2631
2963
|
if (!syncColorPageTexture(ctx.textureCache, group.page)) return;
|
|
2632
|
-
const opacity = isSolidPaint(fill) ?
|
|
2633
|
-
const
|
|
2964
|
+
const opacity = isSolidPaint(fill) ? solidColor(fill)[3] : 1;
|
|
2965
|
+
const baseId = colorPageTextureId(group.page);
|
|
2634
2966
|
const batch = ctx.drawBatch;
|
|
2635
2967
|
const m = ctx.state.transform;
|
|
2968
|
+
const devicePerCss = ctx.deviceWidth && ctx.widthCss ? ctx.deviceWidth / ctx.widthCss : 1;
|
|
2969
|
+
const toDevice = modelScale(m) * devicePerCss;
|
|
2970
|
+
let atlasId = baseId;
|
|
2636
2971
|
let { slot, tint } = stageColorGlyphs(ctx, atlasId);
|
|
2637
2972
|
const restage = () => {
|
|
2638
2973
|
({ slot, tint } = stageColorGlyphs(ctx, atlasId));
|
|
2639
2974
|
};
|
|
2640
|
-
for (const
|
|
2975
|
+
for (const base of group.quads) {
|
|
2976
|
+
const sharp = sharper(ctx, base, toDevice);
|
|
2977
|
+
const q = sharp?.quad ?? base;
|
|
2978
|
+
const id = sharp?.atlasId ?? baseId;
|
|
2979
|
+
if (id !== atlasId) {
|
|
2980
|
+
atlasId = id;
|
|
2981
|
+
restage();
|
|
2982
|
+
}
|
|
2641
2983
|
if (batch.wouldOverflow(4)) {
|
|
2642
2984
|
flushBatch(ctx);
|
|
2643
2985
|
restage();
|
|
@@ -2668,6 +3010,31 @@ function drawColorGlyphs(ctx, group, dx, dy) {
|
|
|
2668
3010
|
}
|
|
2669
3011
|
}
|
|
2670
3012
|
}
|
|
3013
|
+
function sharper(ctx, q, toDevice) {
|
|
3014
|
+
const char = q.colorGlyph;
|
|
3015
|
+
if (!char || char.width === 0) return null;
|
|
3016
|
+
const texel = (q.x1 - q.x0) / char.width;
|
|
3017
|
+
const sharp = sharperColorGlyph(char, texel * toDevice);
|
|
3018
|
+
if (!sharp || !syncSharpColorPageTexture(ctx.textureCache, sharp.glyph.page)) return null;
|
|
3019
|
+
const g = sharp.glyph;
|
|
3020
|
+
const k = 2 ** sharp.level;
|
|
3021
|
+
const x0 = q.x0 + (g.xoffset - char.xoffset) * texel;
|
|
3022
|
+
const y0 = q.y0 + (g.yoffset - char.yoffset) * texel;
|
|
3023
|
+
return {
|
|
3024
|
+
quad: {
|
|
3025
|
+
x0,
|
|
3026
|
+
y0,
|
|
3027
|
+
x1: x0 + g.width / k * texel,
|
|
3028
|
+
y1: y0 + g.height / k * texel,
|
|
3029
|
+
u0: g.x / DYNAMIC_PAGE_SIZE,
|
|
3030
|
+
v0: g.y / DYNAMIC_PAGE_SIZE,
|
|
3031
|
+
u1: (g.x + g.width) / DYNAMIC_PAGE_SIZE,
|
|
3032
|
+
v1: (g.y + g.height) / DYNAMIC_PAGE_SIZE,
|
|
3033
|
+
baselineY: q.baselineY
|
|
3034
|
+
},
|
|
3035
|
+
atlasId: sharpColorPageTextureId(g.page)
|
|
3036
|
+
};
|
|
3037
|
+
}
|
|
2671
3038
|
|
|
2672
3039
|
// src/draw/scratchPass.ts
|
|
2673
3040
|
var UNIT_QUAD = {
|
|
@@ -2745,11 +3112,6 @@ function bindScratchPaint(ctx, prog, painted) {
|
|
|
2745
3112
|
|
|
2746
3113
|
// src/draw/text.ts
|
|
2747
3114
|
var OUTLINE_MIN_SCREEN_PX = 48;
|
|
2748
|
-
function modelScale(m) {
|
|
2749
|
-
const sx = Math.hypot(m[0], m[1]);
|
|
2750
|
-
const sy = Math.hypot(m[3], m[4]);
|
|
2751
|
-
return Math.sqrt(sx * sy) || 1;
|
|
2752
|
-
}
|
|
2753
3115
|
function drawText(ctx, cmd) {
|
|
2754
3116
|
const style = resolveTextStyle(cmd.style);
|
|
2755
3117
|
const lineHeight = style.lineHeight;
|
|
@@ -2803,7 +3165,7 @@ function drawTextDecorations(ctx, decorations, dx, dy) {
|
|
|
2803
3165
|
drawOutlineMesh(ctx, paint, ruleMesh(d.x0 + dx, d.y0 + dy, d.x1 + dx, d.y1 + dy));
|
|
2804
3166
|
continue;
|
|
2805
3167
|
}
|
|
2806
|
-
const [r, g, b, a] = isSolidPaint(paint) ?
|
|
3168
|
+
const [r, g, b, a] = isSolidPaint(paint) ? solidColor(paint) : [0, 0, 0, 1];
|
|
2807
3169
|
let cut;
|
|
2808
3170
|
for (; ; ) {
|
|
2809
3171
|
const slot = stageSolid(ctx, 4);
|
|
@@ -2831,7 +3193,6 @@ function ruleMesh(x0, y0, x1, y1) {
|
|
|
2831
3193
|
indices: new Uint32Array([0, 1, 2, 0, 2, 3])
|
|
2832
3194
|
};
|
|
2833
3195
|
}
|
|
2834
|
-
var SYNTH_BOLD_AMOUNT = 0.08;
|
|
2835
3196
|
function drawTextGroup(ctx, group, dx, dy) {
|
|
2836
3197
|
if (group.source === "canvas") {
|
|
2837
3198
|
if (!syncDynamicPageTexture(ctx.textureCache, group.page)) return;
|
|
@@ -2857,7 +3218,7 @@ function drawTextGroup(ctx, group, dx, dy) {
|
|
|
2857
3218
|
drawPaintedGlyphs(ctx, group, dx, dy, paintKind, atlasId, mode, bold, tanItalic, fieldScale);
|
|
2858
3219
|
return;
|
|
2859
3220
|
}
|
|
2860
|
-
const color = isSolidPaint(fill) ?
|
|
3221
|
+
const color = isSolidPaint(fill) ? solidColor(fill) : [0, 0, 0, 1];
|
|
2861
3222
|
const batch = ctx.drawBatch;
|
|
2862
3223
|
const m = ctx.state.transform;
|
|
2863
3224
|
let slot = stageGlyphs(ctx, atlasId, fieldScale);
|
|
@@ -2988,9 +3349,6 @@ function glyphScreenBounds(group, dx, dy, tanItalic, m) {
|
|
|
2988
3349
|
return b;
|
|
2989
3350
|
}
|
|
2990
3351
|
|
|
2991
|
-
// src/DrawCommand.ts
|
|
2992
|
-
var SPRITE_STRIDE = 9;
|
|
2993
|
-
|
|
2994
3352
|
// src/draw/image.ts
|
|
2995
3353
|
var MINIFIED = 2;
|
|
2996
3354
|
function minifiedLimits(ctx) {
|
|
@@ -3189,6 +3547,7 @@ function ownsSurface(cmd) {
|
|
|
3189
3547
|
return cmd.isolate === true || isOwnComposite(cmd.composite) || cmd.effects !== void 0 && cmd.effects.length > 0;
|
|
3190
3548
|
}
|
|
3191
3549
|
function drawGroup(ctx, cmd) {
|
|
3550
|
+
if (cmd.retain !== void 0 && drawRetainedGroup(ctx, cmd, ownsSurface(cmd), dispatch)) return;
|
|
3192
3551
|
if (ownsSurface(cmd) && ctx.effectTargets && ctx.deviceWidth && ctx.deviceHeight) {
|
|
3193
3552
|
if (cmd.clip) {
|
|
3194
3553
|
ctx.state.push({ transform: cmd.transform });
|
|
@@ -3352,6 +3711,253 @@ var EffectTargets = class {
|
|
|
3352
3711
|
}
|
|
3353
3712
|
};
|
|
3354
3713
|
|
|
3714
|
+
// src/retain/retainedKey.ts
|
|
3715
|
+
function keepKey(key) {
|
|
3716
|
+
return {
|
|
3717
|
+
...key,
|
|
3718
|
+
transform: key.transform.slice(),
|
|
3719
|
+
colorMatrix: key.colorMatrix.slice(),
|
|
3720
|
+
viewMatrix: key.viewMatrix?.slice(),
|
|
3721
|
+
clips: key.clips.map((c) => ({ path: c.path, transform: c.transform.slice() }))
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
function sameKey(a, b) {
|
|
3725
|
+
return a.children === b.children && a.alpha === b.alpha && sameFloats(a.transform, b.transform) && sameFloats(a.colorMatrix, b.colorMatrix) && (a.viewMatrix === void 0 ? b.viewMatrix === void 0 : b.viewMatrix !== void 0 && sameFloats(a.viewMatrix, b.viewMatrix)) && sameClips(a.clips, b.clips);
|
|
3726
|
+
}
|
|
3727
|
+
function sameClips(a, b) {
|
|
3728
|
+
if (a.length !== b.length) return false;
|
|
3729
|
+
for (let i = 0; i < a.length; i++) {
|
|
3730
|
+
if (a[i].path !== b[i].path || !sameFloats(a[i].transform, b[i].transform)) return false;
|
|
3731
|
+
}
|
|
3732
|
+
return true;
|
|
3733
|
+
}
|
|
3734
|
+
function sameFloats(a, b) {
|
|
3735
|
+
if (a.length !== b.length) return false;
|
|
3736
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
3737
|
+
return true;
|
|
3738
|
+
}
|
|
3739
|
+
function sameStamp(a, b) {
|
|
3740
|
+
if (a.length !== b.length) return false;
|
|
3741
|
+
for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
|
|
3742
|
+
return true;
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
// src/retain/surfaceGl.ts
|
|
3746
|
+
function createSurfaceTexture(gl, width, height) {
|
|
3747
|
+
const texture = gl.createTexture();
|
|
3748
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
3749
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
3750
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
3751
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
3752
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
3753
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
3754
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
3755
|
+
const fbo = gl.createFramebuffer();
|
|
3756
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
3757
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
3758
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
3759
|
+
return { texture, fbo };
|
|
3760
|
+
}
|
|
3761
|
+
function deleteSurfaceTexture(gl, surface) {
|
|
3762
|
+
gl.deleteFramebuffer(surface.fbo);
|
|
3763
|
+
gl.deleteTexture(surface.texture);
|
|
3764
|
+
}
|
|
3765
|
+
function createScratch(gl, width, height, samples) {
|
|
3766
|
+
const storage = (format) => {
|
|
3767
|
+
const rb = gl.createRenderbuffer();
|
|
3768
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
|
|
3769
|
+
if (samples > 0) {
|
|
3770
|
+
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, format, width, height);
|
|
3771
|
+
} else {
|
|
3772
|
+
gl.renderbufferStorage(gl.RENDERBUFFER, format, width, height);
|
|
3773
|
+
}
|
|
3774
|
+
return rb;
|
|
3775
|
+
};
|
|
3776
|
+
const color = storage(gl.RGBA8);
|
|
3777
|
+
const stencil = storage(gl.DEPTH24_STENCIL8);
|
|
3778
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
3779
|
+
const fbo = gl.createFramebuffer();
|
|
3780
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
3781
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color);
|
|
3782
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, stencil);
|
|
3783
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
3784
|
+
return { fbo, color, stencil, width, height };
|
|
3785
|
+
}
|
|
3786
|
+
function deleteScratch(gl, scratch) {
|
|
3787
|
+
gl.deleteFramebuffer(scratch.fbo);
|
|
3788
|
+
gl.deleteRenderbuffer(scratch.color);
|
|
3789
|
+
gl.deleteRenderbuffer(scratch.stencil);
|
|
3790
|
+
}
|
|
3791
|
+
function resolveInto(gl, scratch, target, width, height) {
|
|
3792
|
+
const scissor = gl.isEnabled(gl.SCISSOR_TEST);
|
|
3793
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
3794
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, scratch.fbo);
|
|
3795
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, target.fbo);
|
|
3796
|
+
gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
|
|
3797
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
3798
|
+
if (scissor) gl.enable(gl.SCISSOR_TEST);
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
// src/retain/RetainedSurfaces.ts
|
|
3802
|
+
var RetainedSurfaces = class {
|
|
3803
|
+
gl;
|
|
3804
|
+
budget;
|
|
3805
|
+
samples;
|
|
3806
|
+
surfaces = /* @__PURE__ */ new Map();
|
|
3807
|
+
/** Shared: retained draws never nest. */
|
|
3808
|
+
scratch = null;
|
|
3809
|
+
width = 0;
|
|
3810
|
+
height = 0;
|
|
3811
|
+
frame = 0;
|
|
3812
|
+
stamp = [];
|
|
3813
|
+
composited = 0;
|
|
3814
|
+
redrawn = 0;
|
|
3815
|
+
declined = 0;
|
|
3816
|
+
constructor(gl, budget) {
|
|
3817
|
+
this.gl = gl;
|
|
3818
|
+
this.budget = budget;
|
|
3819
|
+
const samples = gl.getParameter(gl.SAMPLES);
|
|
3820
|
+
this.samples = typeof samples === "number" && samples > 1 ? samples : 0;
|
|
3821
|
+
}
|
|
3822
|
+
/** GPU memory held now, scratch buffer included. */
|
|
3823
|
+
get bytes() {
|
|
3824
|
+
return this.keptBytes() + (this.scratch ? this.scratchBytes(this.scratch.width, this.scratch.height) : 0);
|
|
3825
|
+
}
|
|
3826
|
+
/** Change the budget. Lowered below what the surfaces hold, every surface
|
|
3827
|
+
* goes, and groups are drawn again within the new one. */
|
|
3828
|
+
setBudget(budget) {
|
|
3829
|
+
this.budget = budget;
|
|
3830
|
+
if (this.keptBytes() > budget) this.releaseAll();
|
|
3831
|
+
}
|
|
3832
|
+
/** Open a frame. `stamp` holds what every surface depends on beyond its
|
|
3833
|
+
* key — resource generations the renderer watches — compared entry by
|
|
3834
|
+
* entry. */
|
|
3835
|
+
beginFrame(stamp) {
|
|
3836
|
+
this.frame++;
|
|
3837
|
+
this.stamp = stamp;
|
|
3838
|
+
this.composited = 0;
|
|
3839
|
+
this.redrawn = 0;
|
|
3840
|
+
this.declined = 0;
|
|
3841
|
+
}
|
|
3842
|
+
/** `id`'s pixels if they were drawn under `key`, in a drawing buffer
|
|
3843
|
+
* `width × height` device pixels. */
|
|
3844
|
+
lookup(id, width, height, key) {
|
|
3845
|
+
this.fitTo(width, height);
|
|
3846
|
+
const surface = this.surfaces.get(id);
|
|
3847
|
+
if (!surface?.key || !sameKey(surface.key, key) || !sameStamp(surface.stamp, this.stamp)) return null;
|
|
3848
|
+
surface.frame = this.frame;
|
|
3849
|
+
this.composited++;
|
|
3850
|
+
return pixelsOf(surface);
|
|
3851
|
+
}
|
|
3852
|
+
/** Somewhere to draw `id` this frame, covering `rect` of a drawing buffer
|
|
3853
|
+
* `width × height` device pixels, or `null` when that would take the total
|
|
3854
|
+
* past the budget. */
|
|
3855
|
+
reserve(id, width, height, rect) {
|
|
3856
|
+
this.fitTo(width, height);
|
|
3857
|
+
const existing = this.surfaces.get(id);
|
|
3858
|
+
const empty = rect.width === 0 || rect.height === 0;
|
|
3859
|
+
const held = this.scratch;
|
|
3860
|
+
const scratchW = Math.max(held?.width ?? 0, empty ? 0 : rect.width);
|
|
3861
|
+
const scratchH = Math.max(held?.height ?? 0, empty ? 0 : rect.height);
|
|
3862
|
+
const after = this.keptBytes() + rectBytes(rect) - (existing ? rectBytes(existing.rect) : 0);
|
|
3863
|
+
if (after > this.budget) {
|
|
3864
|
+
this.declined++;
|
|
3865
|
+
return null;
|
|
3866
|
+
}
|
|
3867
|
+
const surface = existing ?? { gl: null, rect, key: null, stamp: [], frame: this.frame };
|
|
3868
|
+
this.surfaces.set(id, surface);
|
|
3869
|
+
this.fitSurface(surface, rect);
|
|
3870
|
+
surface.frame = this.frame;
|
|
3871
|
+
this.redrawn++;
|
|
3872
|
+
const scratch = surface.gl ? this.scratchFor(scratchW, scratchH) : null;
|
|
3873
|
+
return {
|
|
3874
|
+
fbo: scratch?.fbo ?? null,
|
|
3875
|
+
commit: (key) => {
|
|
3876
|
+
if (scratch && surface.gl) resolveInto(this.gl, scratch, surface.gl, surface.rect.width, surface.rect.height);
|
|
3877
|
+
surface.key = keepKey(key);
|
|
3878
|
+
surface.stamp = this.stamp;
|
|
3879
|
+
return pixelsOf(surface);
|
|
3880
|
+
}
|
|
3881
|
+
};
|
|
3882
|
+
}
|
|
3883
|
+
/** Count a group drawn straight through for want of a surface. */
|
|
3884
|
+
decline() {
|
|
3885
|
+
this.declined++;
|
|
3886
|
+
}
|
|
3887
|
+
/** Free every surface no group asked for this frame, and the scratch
|
|
3888
|
+
* buffer once this frame redrew nothing or it is larger than every surface
|
|
3889
|
+
* left needs. */
|
|
3890
|
+
endFrame() {
|
|
3891
|
+
let needW = 0;
|
|
3892
|
+
let needH = 0;
|
|
3893
|
+
for (const [id, surface] of this.surfaces) {
|
|
3894
|
+
if (surface.frame === this.frame) {
|
|
3895
|
+
needW = Math.max(needW, surface.rect.width);
|
|
3896
|
+
needH = Math.max(needH, surface.rect.height);
|
|
3897
|
+
continue;
|
|
3898
|
+
}
|
|
3899
|
+
this.deleteSurface(surface);
|
|
3900
|
+
this.surfaces.delete(id);
|
|
3901
|
+
}
|
|
3902
|
+
const oversized = this.scratch && (this.scratch.width > needW || this.scratch.height > needH);
|
|
3903
|
+
if (this.redrawn === 0 || oversized) this.dropScratch();
|
|
3904
|
+
}
|
|
3905
|
+
/** Drop everything: on dispose, and on context loss, where the handles are
|
|
3906
|
+
* already gone and only the bookkeeping has to go. */
|
|
3907
|
+
releaseAll() {
|
|
3908
|
+
for (const surface of this.surfaces.values()) this.deleteSurface(surface);
|
|
3909
|
+
this.surfaces.clear();
|
|
3910
|
+
this.dropScratch();
|
|
3911
|
+
}
|
|
3912
|
+
fitTo(width, height) {
|
|
3913
|
+
if (width === this.width && height === this.height) return;
|
|
3914
|
+
this.releaseAll();
|
|
3915
|
+
this.width = width;
|
|
3916
|
+
this.height = height;
|
|
3917
|
+
}
|
|
3918
|
+
keptBytes() {
|
|
3919
|
+
let n = 0;
|
|
3920
|
+
for (const surface of this.surfaces.values()) n += rectBytes(surface.rect);
|
|
3921
|
+
return n;
|
|
3922
|
+
}
|
|
3923
|
+
scratchBytes(width, height) {
|
|
3924
|
+
return width * height * Math.max(1, this.samples) * 8;
|
|
3925
|
+
}
|
|
3926
|
+
/** The scratch buffer at `width × height`, reallocated if it is not. */
|
|
3927
|
+
scratchFor(width, height) {
|
|
3928
|
+
const held = this.scratch;
|
|
3929
|
+
if (held && held.width === width && held.height === height) return held;
|
|
3930
|
+
this.dropScratch();
|
|
3931
|
+
return this.scratch = createScratch(this.gl, width, height, this.samples);
|
|
3932
|
+
}
|
|
3933
|
+
/** Give `surface` a texture the size of `rect`, or none for an empty one,
|
|
3934
|
+
* keeping one that is already that size. */
|
|
3935
|
+
fitSurface(surface, rect) {
|
|
3936
|
+
const was = surface.rect;
|
|
3937
|
+
surface.rect = { ...rect };
|
|
3938
|
+
if (surface.gl && was.width === rect.width && was.height === rect.height) return;
|
|
3939
|
+
this.deleteSurface(surface);
|
|
3940
|
+
if (rect.width > 0 && rect.height > 0) surface.gl = createSurfaceTexture(this.gl, rect.width, rect.height);
|
|
3941
|
+
}
|
|
3942
|
+
deleteSurface(surface) {
|
|
3943
|
+
if (surface.gl) deleteSurfaceTexture(this.gl, surface.gl);
|
|
3944
|
+
surface.gl = null;
|
|
3945
|
+
}
|
|
3946
|
+
dropScratch() {
|
|
3947
|
+
if (this.scratch) deleteScratch(this.gl, this.scratch);
|
|
3948
|
+
this.scratch = null;
|
|
3949
|
+
}
|
|
3950
|
+
};
|
|
3951
|
+
function pixelsOf(surface) {
|
|
3952
|
+
return { texture: surface.gl?.texture ?? null, rect: surface.rect };
|
|
3953
|
+
}
|
|
3954
|
+
function rectBytes(rect) {
|
|
3955
|
+
return rect.width * rect.height * 4;
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
// src/rendererTypes.ts
|
|
3959
|
+
var DEFAULT_RETAIN_BUDGET = 256 * 1024 * 1024;
|
|
3960
|
+
|
|
3355
3961
|
// src/shaders/extractUniformNames.ts
|
|
3356
3962
|
var QUALIFIERS = /* @__PURE__ */ new Set([
|
|
3357
3963
|
"lowp",
|
|
@@ -3583,6 +4189,7 @@ function compileCustomProgram(gl, src) {
|
|
|
3583
4189
|
program.lookupAttributes(CUSTOM_ATTRIBUTES);
|
|
3584
4190
|
return program;
|
|
3585
4191
|
}
|
|
4192
|
+
var NO_RETAINED = { composited: 0, redrawn: 0, declined: 0, bytes: 0 };
|
|
3586
4193
|
function now() {
|
|
3587
4194
|
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
3588
4195
|
}
|
|
@@ -3593,7 +4200,7 @@ var GLYPH_PAINT_SOURCES = {
|
|
|
3593
4200
|
};
|
|
3594
4201
|
var WeaselRenderer = class {
|
|
3595
4202
|
gl;
|
|
3596
|
-
stats = { drawCalls: 0, ms: 0, spans: [] };
|
|
4203
|
+
stats = { drawCalls: 0, ms: 0, spans: [], retained: NO_RETAINED };
|
|
3597
4204
|
/** Built on the first frame that draws with each, so a one-shot render of
|
|
3598
4205
|
* plain shapes builds the batch program alone. */
|
|
3599
4206
|
pathFill;
|
|
@@ -3630,6 +4237,10 @@ var WeaselRenderer = class {
|
|
|
3630
4237
|
/** Offscreen buffers for group effects. Allocates nothing until a group
|
|
3631
4238
|
* with effects asks, so a canvas without them pays no memory. */
|
|
3632
4239
|
effectTargets;
|
|
4240
|
+
retained;
|
|
4241
|
+
/** Advanced when a program is compiled again over one already drawn with,
|
|
4242
|
+
* which leaves every retained surface drawn by the old one stale. */
|
|
4243
|
+
programEpoch = 0;
|
|
3633
4244
|
paintScratch;
|
|
3634
4245
|
imageMinification;
|
|
3635
4246
|
flattenTolerance;
|
|
@@ -3652,6 +4263,7 @@ var WeaselRenderer = class {
|
|
|
3652
4263
|
if (!gl) throw new Error("WeaselRenderer: WebGL2 not available");
|
|
3653
4264
|
this.gl = gl;
|
|
3654
4265
|
this.effectTargets = new EffectTargets(this.gl);
|
|
4266
|
+
this.retained = new RetainedSurfaces(this.gl, opts.retainBudget ?? DEFAULT_RETAIN_BUDGET);
|
|
3655
4267
|
this.paintScratch = new PaintScratch(this.gl);
|
|
3656
4268
|
const attrs = typeof this.gl.getContextAttributes === "function" ? this.gl.getContextAttributes() : null;
|
|
3657
4269
|
if (attrs && attrs.stencil === false) {
|
|
@@ -3723,15 +4335,16 @@ var WeaselRenderer = class {
|
|
|
3723
4335
|
if (this.disposed) return;
|
|
3724
4336
|
const src = getProgramSource(handle.id);
|
|
3725
4337
|
if (!src) {
|
|
3726
|
-
throw new Error(
|
|
3727
|
-
`WeaselRenderer.registerProgram: program "${handle.id}" not found in source registry. Call the module-level registerProgram() first.`
|
|
3728
|
-
);
|
|
4338
|
+
throw new Error(`WeaselRenderer.registerProgram: program "${handle.id}" not found in source registry. Call the module-level registerProgram() first.`);
|
|
3729
4339
|
}
|
|
3730
4340
|
this.installProgram(handle.id, src, compileCustomProgram(this.gl, src));
|
|
3731
4341
|
}
|
|
3732
4342
|
installProgram(id, src, program) {
|
|
3733
4343
|
const previous = this.programRegistry.get(id);
|
|
3734
|
-
if (previous)
|
|
4344
|
+
if (previous) {
|
|
4345
|
+
this.gl.deleteProgram(previous.handle);
|
|
4346
|
+
this.programEpoch++;
|
|
4347
|
+
}
|
|
3735
4348
|
this.programRegistry.set(id, program);
|
|
3736
4349
|
this.programSources.set(id, src);
|
|
3737
4350
|
}
|
|
@@ -3803,28 +4416,28 @@ var WeaselRenderer = class {
|
|
|
3803
4416
|
const oy = this.target?.origin.y ?? 0;
|
|
3804
4417
|
const x = Math.round(ox * this.dpr);
|
|
3805
4418
|
const y = Math.round(oy * this.dpr);
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
y,
|
|
3809
|
-
width: Math.round((ox + this.widthCss) * this.dpr) - x,
|
|
3810
|
-
height: Math.round((oy + this.heightCss) * this.dpr) - y
|
|
3811
|
-
};
|
|
4419
|
+
const width = Math.round((ox + this.widthCss) * this.dpr) - x;
|
|
4420
|
+
return { x, y, width, height: Math.round((oy + this.heightCss) * this.dpr) - y };
|
|
3812
4421
|
}
|
|
3813
4422
|
/** Viewport and scissor for this frame. Re-applied inside `render()` because
|
|
3814
4423
|
* a co-tenant on the same context moves both between our frames. */
|
|
3815
4424
|
applyTarget() {
|
|
3816
4425
|
const gl = this.gl;
|
|
3817
|
-
const { x, y, width, height } = this.
|
|
4426
|
+
const { x, y, width, height } = this.glRect();
|
|
4427
|
+
gl.viewport(x, y, width, height);
|
|
3818
4428
|
if (!this.target) {
|
|
3819
4429
|
gl.disable(gl.SCISSOR_TEST);
|
|
3820
|
-
gl.viewport(0, 0, width, height);
|
|
3821
4430
|
return;
|
|
3822
4431
|
}
|
|
3823
|
-
|
|
3824
|
-
gl.viewport(x, glY, width, height);
|
|
3825
|
-
gl.scissor(x, glY, width, height);
|
|
4432
|
+
gl.scissor(x, y, width, height);
|
|
3826
4433
|
gl.enable(gl.SCISSOR_TEST);
|
|
3827
4434
|
}
|
|
4435
|
+
/** This renderer's rect as GL addresses it, from the buffer's bottom left. */
|
|
4436
|
+
glRect() {
|
|
4437
|
+
const { x, y, width, height } = this.deviceRect();
|
|
4438
|
+
if (!this.target) return { x: 0, y: 0, width, height };
|
|
4439
|
+
return { x, y: this.gl.drawingBufferHeight - y - height, width, height };
|
|
4440
|
+
}
|
|
3828
4441
|
/** Confine this renderer to a rect of its drawing buffer, or pass null to
|
|
3829
4442
|
* give it the whole buffer back. Takes effect from the next `render()`. */
|
|
3830
4443
|
setTarget(target) {
|
|
@@ -3846,6 +4459,7 @@ var WeaselRenderer = class {
|
|
|
3846
4459
|
this.applyGlState();
|
|
3847
4460
|
this.applyTarget();
|
|
3848
4461
|
this.effectTargets.releaseAll();
|
|
4462
|
+
this.retained.releaseAll();
|
|
3849
4463
|
this.paintScratch.release();
|
|
3850
4464
|
this.paintResources.release();
|
|
3851
4465
|
this.paintResources = createPaintResources();
|
|
@@ -3890,13 +4504,9 @@ var WeaselRenderer = class {
|
|
|
3890
4504
|
this.meshCache.dispose();
|
|
3891
4505
|
this.paintResources.release();
|
|
3892
4506
|
for (const lazy of this.lazyPrograms()) lazy.release();
|
|
3893
|
-
for (const prog of [this.batchFill, ...this.glyphPaintPrograms.values()])
|
|
3894
|
-
gl.deleteProgram(prog.handle);
|
|
3895
|
-
}
|
|
4507
|
+
for (const prog of [this.batchFill, ...this.glyphPaintPrograms.values()]) gl.deleteProgram(prog.handle);
|
|
3896
4508
|
this.glyphPaintPrograms.clear();
|
|
3897
|
-
for (const prog of this.programRegistry.values())
|
|
3898
|
-
gl.deleteProgram(prog.handle);
|
|
3899
|
-
}
|
|
4509
|
+
for (const prog of this.programRegistry.values()) gl.deleteProgram(prog.handle);
|
|
3900
4510
|
this.programRegistry.clear();
|
|
3901
4511
|
this.programSources.clear();
|
|
3902
4512
|
this.textureCache.free();
|
|
@@ -3908,6 +4518,7 @@ var WeaselRenderer = class {
|
|
|
3908
4518
|
if (this.whiteTexture) gl.deleteTexture(this.whiteTexture);
|
|
3909
4519
|
this.whiteTexture = null;
|
|
3910
4520
|
this.effectTargets.releaseAll();
|
|
4521
|
+
this.retained.releaseAll();
|
|
3911
4522
|
this.paintScratch.release();
|
|
3912
4523
|
}
|
|
3913
4524
|
/**
|
|
@@ -3923,12 +4534,22 @@ var WeaselRenderer = class {
|
|
|
3923
4534
|
const t0 = now();
|
|
3924
4535
|
const counters = { drawCalls: 0 };
|
|
3925
4536
|
const spanStats = [];
|
|
3926
|
-
this.stats = { drawCalls: 0, ms: 0, spans: spanStats };
|
|
4537
|
+
this.stats = { drawCalls: 0, ms: 0, spans: spanStats, retained: NO_RETAINED };
|
|
3927
4538
|
const gl = this.gl;
|
|
3928
4539
|
this.meshCache.drainPendingDeletes();
|
|
3929
4540
|
this.imageCache.beginFrame();
|
|
3930
4541
|
this.textureCache.sweep(textureRegistry);
|
|
3931
4542
|
this.refreshPrograms();
|
|
4543
|
+
this.retained.beginFrame([
|
|
4544
|
+
glyphGeneration(),
|
|
4545
|
+
textureRegistry.getVersion(),
|
|
4546
|
+
paintDrawKindRegistry.getVersion(),
|
|
4547
|
+
this.programEpoch,
|
|
4548
|
+
this.flattenTolerance,
|
|
4549
|
+
this.widthCss,
|
|
4550
|
+
this.heightCss,
|
|
4551
|
+
this.dpr
|
|
4552
|
+
]);
|
|
3932
4553
|
resetBakeBudget(this.bakeBudget);
|
|
3933
4554
|
this.groupState.reset();
|
|
3934
4555
|
this.drawBatch.reset();
|
|
@@ -3976,15 +4597,20 @@ var WeaselRenderer = class {
|
|
|
3976
4597
|
heightCss: this.heightCss,
|
|
3977
4598
|
clipDepth: 0,
|
|
3978
4599
|
clipStack: [],
|
|
3979
|
-
flattenTolerance: this.flattenTolerance,
|
|
4600
|
+
flattenTolerance: inCssPixels(this.flattenTolerance, this.dpr),
|
|
3980
4601
|
textOutlineMinScreenSize: this.textOutlineMinScreenSize,
|
|
3981
4602
|
viewMatrix,
|
|
3982
4603
|
effectTargets: this.effectTargets,
|
|
4604
|
+
retained: this.retained,
|
|
3983
4605
|
paintScratch: this.paintScratch,
|
|
3984
4606
|
renderTarget: null,
|
|
3985
4607
|
deviceWidth: device.width,
|
|
3986
4608
|
deviceHeight: device.height,
|
|
3987
4609
|
restoreTargetRect: () => this.applyTarget(),
|
|
4610
|
+
viewportWithin: (rect) => {
|
|
4611
|
+
const own = this.glRect();
|
|
4612
|
+
gl.viewport(own.x + rect.x, own.y + own.height - rect.y - rect.height, rect.width, rect.height);
|
|
4613
|
+
},
|
|
3988
4614
|
paintResources: this.paintResources,
|
|
3989
4615
|
stats: counters
|
|
3990
4616
|
};
|
|
@@ -4003,6 +4629,14 @@ var WeaselRenderer = class {
|
|
|
4003
4629
|
for (; next < commands.length; next++) dispatch(ctx, commands[next]);
|
|
4004
4630
|
flushBatch(ctx);
|
|
4005
4631
|
this.meshCache.freeTransient();
|
|
4632
|
+
const retained = this.retained;
|
|
4633
|
+
retained.endFrame();
|
|
4634
|
+
this.stats.retained = {
|
|
4635
|
+
composited: retained.composited,
|
|
4636
|
+
redrawn: retained.redrawn,
|
|
4637
|
+
declined: retained.declined,
|
|
4638
|
+
bytes: retained.bytes
|
|
4639
|
+
};
|
|
4006
4640
|
this.stats.drawCalls = counters.drawCalls;
|
|
4007
4641
|
this.stats.ms = now() - t0;
|
|
4008
4642
|
}
|
|
@@ -4012,11 +4646,14 @@ var WeaselRenderer = class {
|
|
|
4012
4646
|
lastFrameStats() {
|
|
4013
4647
|
return this.stats;
|
|
4014
4648
|
}
|
|
4015
|
-
/** Change
|
|
4016
|
-
* restores the cached `DEFAULT_FLATTEN_TOLERANCE` route. */
|
|
4649
|
+
/** Change `flattenTolerance` after construction; `undefined` restores the default. */
|
|
4017
4650
|
setFlattenTolerance(tolerance) {
|
|
4018
4651
|
this.flattenTolerance = tolerance;
|
|
4019
4652
|
}
|
|
4653
|
+
/** Change `retainBudget` after construction; `undefined` restores the default. */
|
|
4654
|
+
setRetainBudget(bytes) {
|
|
4655
|
+
this.retained.setBudget(bytes ?? DEFAULT_RETAIN_BUDGET);
|
|
4656
|
+
}
|
|
4020
4657
|
resize(dims) {
|
|
4021
4658
|
this.widthCss = dims.width;
|
|
4022
4659
|
this.heightCss = dims.height;
|
|
@@ -4129,119 +4766,6 @@ function frameRect(sheet, index) {
|
|
|
4129
4766
|
h: frameHeight
|
|
4130
4767
|
};
|
|
4131
4768
|
}
|
|
4132
|
-
var SLACK_PX = 1;
|
|
4133
|
-
var DEFAULT_MITER_LIMIT = 4;
|
|
4134
|
-
function cullDrawCommands(cmds, transform, rect) {
|
|
4135
|
-
return cullList(cmds, transform, slackBox(rect));
|
|
4136
|
-
}
|
|
4137
|
-
function cullList(cmds, m, box) {
|
|
4138
|
-
let out = null;
|
|
4139
|
-
for (let i = 0; i < cmds.length; i++) {
|
|
4140
|
-
const cmd = cmds[i];
|
|
4141
|
-
const kept = cullOne(cmd, m, box);
|
|
4142
|
-
if (kept === cmd) {
|
|
4143
|
-
out?.push(cmd);
|
|
4144
|
-
continue;
|
|
4145
|
-
}
|
|
4146
|
-
out ??= cmds.slice(0, i);
|
|
4147
|
-
if (kept) out.push(kept);
|
|
4148
|
-
}
|
|
4149
|
-
return out ?? cmds;
|
|
4150
|
-
}
|
|
4151
|
-
function cullOne(cmd, m, box) {
|
|
4152
|
-
switch (cmd.kind) {
|
|
4153
|
-
case "group": {
|
|
4154
|
-
if (cmd.effects && cmd.effects.length > 0) return cmd;
|
|
4155
|
-
if (isOwnComposite(cmd.composite)) return cmd;
|
|
4156
|
-
const inner = cmd.transform ? mat3.multiply(m, cmd.transform) : m;
|
|
4157
|
-
const children = cullList(cmd.children, inner, box);
|
|
4158
|
-
if (children === cmd.children) return cmd;
|
|
4159
|
-
return children.length === 0 ? null : { ...cmd, children };
|
|
4160
|
-
}
|
|
4161
|
-
case "path":
|
|
4162
|
-
return pathMisses(cmd.path, cmd.stroke, m, box) ? null : cmd;
|
|
4163
|
-
case "image":
|
|
4164
|
-
return quadMisses(cmd.x, cmd.y, cmd.x + cmd.w, cmd.y + cmd.h, m, box) ? null : cmd;
|
|
4165
|
-
case "sprites":
|
|
4166
|
-
return spritesMiss(cmd.sprites, m, box) ? null : cmd;
|
|
4167
|
-
default:
|
|
4168
|
-
return cmd;
|
|
4169
|
-
}
|
|
4170
|
-
}
|
|
4171
|
-
function pathMisses(path, stroke, m, box) {
|
|
4172
|
-
let minX, minY, maxX, maxY;
|
|
4173
|
-
if (path.kind === "rect") {
|
|
4174
|
-
minX = Math.min(path.x, path.x + path.width);
|
|
4175
|
-
maxX = Math.max(path.x, path.x + path.width);
|
|
4176
|
-
minY = Math.min(path.y, path.y + path.height);
|
|
4177
|
-
maxY = Math.max(path.y, path.y + path.height);
|
|
4178
|
-
} else if (path.kind === "polygon") {
|
|
4179
|
-
const c = path.coords;
|
|
4180
|
-
if (c.length < 2) return false;
|
|
4181
|
-
minX = maxX = c[0];
|
|
4182
|
-
minY = maxY = c[1];
|
|
4183
|
-
for (let i = 2; i + 1 < c.length; i += 2) {
|
|
4184
|
-
const x = c[i], y = c[i + 1];
|
|
4185
|
-
if (x < minX) minX = x;
|
|
4186
|
-
else if (x > maxX) maxX = x;
|
|
4187
|
-
if (y < minY) minY = y;
|
|
4188
|
-
else if (y > maxY) maxY = y;
|
|
4189
|
-
}
|
|
4190
|
-
} else {
|
|
4191
|
-
return false;
|
|
4192
|
-
}
|
|
4193
|
-
const reach = stroke ? strokeReach(stroke, m) : 0;
|
|
4194
|
-
return quadMisses(minX - reach, minY - reach, maxX + reach, maxY + reach, m, box);
|
|
4195
|
-
}
|
|
4196
|
-
function strokeReach(stroke, m) {
|
|
4197
|
-
return strokeReachAt(stroke, mat3.meanScaleOf(m), mat3.minScaleOf(m));
|
|
4198
|
-
}
|
|
4199
|
-
function strokeReachAt(stroke, scale, least = scale) {
|
|
4200
|
-
const at = least > 0 ? least : scale;
|
|
4201
|
-
const base = resolveStrokeWidth(stroke.width ?? 1, typeof stroke.width === "object" ? at : scale);
|
|
4202
|
-
let width = base;
|
|
4203
|
-
if (stroke.vertexWidths) {
|
|
4204
|
-
for (const v of stroke.vertexWidths) if (v > width) width = v;
|
|
4205
|
-
}
|
|
4206
|
-
const spike = Math.max(stroke.miterLimit ?? DEFAULT_MITER_LIMIT, Math.SQRT2);
|
|
4207
|
-
const heads = markerReach(stroke);
|
|
4208
|
-
return Math.max(Math.abs(width) * spike, heads.world, resolveScreenLength({ px: heads.px }, at));
|
|
4209
|
-
}
|
|
4210
|
-
function spritesMiss(sprites, m, box) {
|
|
4211
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
4212
|
-
for (let i = 0; i + SPRITE_STRIDE <= sprites.length; i += SPRITE_STRIDE) {
|
|
4213
|
-
const x0 = sprites[i], y0 = sprites[i + 1];
|
|
4214
|
-
const x1 = x0 + sprites[i + 2], y1 = y0 + sprites[i + 3];
|
|
4215
|
-
minX = Math.min(minX, x0, x1);
|
|
4216
|
-
maxX = Math.max(maxX, x0, x1);
|
|
4217
|
-
minY = Math.min(minY, y0, y1);
|
|
4218
|
-
maxY = Math.max(maxY, y0, y1);
|
|
4219
|
-
}
|
|
4220
|
-
if (minX === Infinity) return false;
|
|
4221
|
-
return quadMisses(minX, minY, maxX, maxY, m, box);
|
|
4222
|
-
}
|
|
4223
|
-
function boundsMissRect(bounds, transform, rect) {
|
|
4224
|
-
const x0 = Math.min(bounds.x, bounds.x + bounds.width);
|
|
4225
|
-
const x1 = Math.max(bounds.x, bounds.x + bounds.width);
|
|
4226
|
-
const y0 = Math.min(bounds.y, bounds.y + bounds.height);
|
|
4227
|
-
const y1 = Math.max(bounds.y, bounds.y + bounds.height);
|
|
4228
|
-
return quadMisses(x0, y0, x1, y1, transform, slackBox(rect));
|
|
4229
|
-
}
|
|
4230
|
-
function slackBox(rect) {
|
|
4231
|
-
return {
|
|
4232
|
-
minX: rect.x - SLACK_PX,
|
|
4233
|
-
minY: rect.y - SLACK_PX,
|
|
4234
|
-
maxX: rect.x + rect.width + SLACK_PX,
|
|
4235
|
-
maxY: rect.y + rect.height + SLACK_PX
|
|
4236
|
-
};
|
|
4237
|
-
}
|
|
4238
|
-
function quadMisses(x0, y0, x1, y1, m, box) {
|
|
4239
|
-
const [ax, ay] = mat3.apply(m, x0, y0);
|
|
4240
|
-
const [bx, by] = mat3.apply(m, x1, y0);
|
|
4241
|
-
const [cx, cy] = mat3.apply(m, x1, y1);
|
|
4242
|
-
const [dx, dy] = mat3.apply(m, x0, y1);
|
|
4243
|
-
return Math.max(ax, bx, cx, dx) < box.minX || Math.min(ax, bx, cx, dx) > box.maxX || Math.max(ay, by, cy, dy) < box.minY || Math.min(ay, by, cy, dy) > box.maxY;
|
|
4244
|
-
}
|
|
4245
4769
|
|
|
4246
4770
|
// src/effects/types.ts
|
|
4247
4771
|
function registerEffect(id, frag) {
|
|
@@ -4367,6 +4891,6 @@ function isSolid(paint) {
|
|
|
4367
4891
|
return paint == null || isSolidPaint(paint);
|
|
4368
4892
|
}
|
|
4369
4893
|
|
|
4370
|
-
export { EFFECT_KIT_UNIFORMS, EFFECT_VERT_SRC, IDENTITY_COLOR_MATRIX, OUTLINE_MIN_SCREEN_PX, SPRITE_STRIDE, ShaderCompileError, WeaselRenderer, blur, boundsMissRect, buildGradientRamp, cullDrawCommands, frameRect, registerEffect, strokeReachAt, textCommand, textCommandFromPose, textCommandFromRuns, vignette };
|
|
4894
|
+
export { DEFAULT_FLATTEN_TOLERANCE_PX, DEFAULT_RETAIN_BUDGET, EFFECT_KIT_UNIFORMS, EFFECT_VERT_SRC, IDENTITY_COLOR_MATRIX, OUTLINE_MIN_SCREEN_PX, SPRITE_STRIDE, ShaderCompileError, WeaselRenderer, blur, boundsMissRect, buildGradientRamp, cullDrawCommands, frameRect, registerEffect, resolveStrokeLengths, strokeReachAt, textCommand, textCommandFromPose, textCommandFromRuns, vignette };
|
|
4371
4895
|
//# sourceMappingURL=index.js.map
|
|
4372
4896
|
//# sourceMappingURL=index.js.map
|