@weasel-js/renderer 1.10.0 → 1.10.1

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { createTilePattern } from './chunk-43YL7HZV.js';
2
- import { BATCH_ATTRIBUTE_LOCATIONS, BATCH_TEXTURE_SLOTS, registerProgram, BATCH_VERT_SRC, BATCH_FRAG_SRC, BATCH_FILL_UNIFORMS, BATCH_FILL_ATTRIBUTES, STRAIGHT_RGB_GLSL, GLImageCache, DrawBatch, getProgramSource, commandFill, commandPaint, uploadPremultiplied, cachedMarkerCommands, PAINT_MODE_PLAIN, PAINT_MODE_RADIAL, PAINT_MODE_CONIC, getMesh, quantizeStrokeScale, strokeMesh, axisAlignedClipRect, intersectClipRects, getPaintDrawKind, outlineMesh, outlineStrokeMesh, quantizeEmWidth } from './chunk-LFD47OV2.js';
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-LFD47OV2.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, quantizeStrokeScale, strokeMesh, axisAlignedClipRect, intersectClipRects, getPaintDrawKind, outlineMesh, outlineStrokeMesh, quantizeEmWidth } from './chunk-K7XCWVQH.js';
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-K7XCWVQH.js';
4
4
  import { textureRegistry, getTexture } from './chunk-Q4SIHV7Q.js';
5
5
  export { registerTexture, textureRegistry, unregisterTexture } from './chunk-Q4SIHV7Q.js';
6
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';
@@ -229,7 +229,6 @@ uniform mat4 u_colorMatrix;
229
229
  uniform vec4 u_colorBias;
230
230
  uniform float u_opacity;
231
231
  uniform int u_gradKind;
232
- uniform vec2 u_gradP0;
233
232
  uniform vec2 u_gradDir;
234
233
  uniform float u_gradLen;
235
234
  uniform float u_gradRadius;
@@ -240,12 +239,11 @@ const float PI = 3.14159265358979323846;
240
239
  vec4 shadePaint(vec2 world) {
241
240
  float t;
242
241
  if (u_gradKind == 0) {
243
- vec2 d = world - u_gradP0;
244
- t = dot(d, u_gradDir) / max(u_gradLen, 0.0001);
242
+ t = dot(world, u_gradDir) / max(u_gradLen, 0.0001);
245
243
  } else if (u_gradKind == 1) {
246
- t = length(world - u_gradP0) / max(u_gradRadius, 0.0001);
244
+ t = length(world) / max(u_gradRadius, 0.0001);
247
245
  } else {
248
- float a = atan(world.y - u_gradP0.y, world.x - u_gradP0.x) - u_gradAngle;
246
+ float a = atan(world.y, world.x) - u_gradAngle;
249
247
  t = fract(a / (2.0 * PI));
250
248
  }
251
249
  t = clamp(t, 0.0, 1.0);
@@ -284,7 +282,6 @@ var GRAD_FILL_UNIFORMS = [
284
282
  "u_colorMatrix",
285
283
  "u_colorBias",
286
284
  "u_gradKind",
287
- "u_gradP0",
288
285
  "u_gradDir",
289
286
  "u_gradLen",
290
287
  "u_gradRadius",
@@ -355,6 +352,72 @@ var PATTERN_FILL_UNIFORMS = [
355
352
  "u_colorBias"
356
353
  ];
357
354
  var PATTERN_FILL_ATTRIBUTES = ["a_position"];
355
+
356
+ // src/shaders/builtinPrograms.ts
357
+ var LazyProgram = class {
358
+ constructor(gl, spec) {
359
+ this.gl = gl;
360
+ this.spec = spec;
361
+ }
362
+ gl;
363
+ spec;
364
+ program = null;
365
+ get() {
366
+ if (!this.program) {
367
+ const { vert, frag, uniforms, attributes } = this.spec;
368
+ const program = new ShaderProgram(this.gl, vert, frag);
369
+ program.lookupUniforms(uniforms);
370
+ program.lookupAttributes(attributes);
371
+ this.program = program;
372
+ }
373
+ return this.program;
374
+ }
375
+ /** Delete the program if it was built. It builds again on the next `get`. */
376
+ release() {
377
+ if (this.program) this.gl.deleteProgram(this.program.handle);
378
+ this.program = null;
379
+ }
380
+ /** Forget a program its lost context already took with it. */
381
+ abandon() {
382
+ this.program = null;
383
+ }
384
+ };
385
+ var PATH_FILL_SPEC = {
386
+ vert: VERT_SRC,
387
+ frag: FRAG_SRC,
388
+ uniforms: PATH_FILL_UNIFORMS,
389
+ attributes: PATH_FILL_ATTRIBUTES
390
+ };
391
+ var PATH_FILL_VCOLOR_SPEC = {
392
+ vert: VCOLOR_VERT_SRC,
393
+ frag: VCOLOR_FRAG_SRC,
394
+ uniforms: PATH_FILL_UNIFORMS,
395
+ attributes: PATH_FILL_VCOLOR_ATTRIBUTES
396
+ };
397
+ var GRAD_FILL_SPEC = {
398
+ vert: GRAD_VERT_SRC,
399
+ frag: GRAD_FRAG_SRC,
400
+ uniforms: GRAD_FILL_UNIFORMS,
401
+ attributes: GRAD_FILL_ATTRIBUTES
402
+ };
403
+ var BATCH_FILL_MATRICES_SPEC = {
404
+ vert: BATCH_VERT_SRC,
405
+ frag: BATCH_MATRICES_FRAG_SRC,
406
+ uniforms: BATCH_FILL_UNIFORMS,
407
+ attributes: BATCH_FILL_ATTRIBUTES
408
+ };
409
+ var PATTERN_FILL_SPEC = {
410
+ vert: PATTERN_VERT_SRC,
411
+ frag: PATTERN_FRAG_SRC,
412
+ uniforms: PATTERN_FILL_UNIFORMS,
413
+ attributes: PATTERN_FILL_ATTRIBUTES
414
+ };
415
+ function createBatchFill(gl) {
416
+ const program = new ShaderProgram(gl, BATCH_VERT_SRC, BATCH_FRAG_SRC);
417
+ program.lookupUniforms(BATCH_FILL_UNIFORMS);
418
+ program.lookupAttributes(BATCH_FILL_ATTRIBUTES);
419
+ return program;
420
+ }
358
421
  var L = BATCH_ATTRIBUTE_LOCATIONS;
359
422
  var GLYPH_PAINT_VERT_SRC = (
360
423
  /* glsl */
@@ -1553,6 +1616,83 @@ function meshHandle(ctx, mesh) {
1553
1616
  function fillMeshHandle(ctx, path) {
1554
1617
  return meshHandle(ctx, fillMesh(ctx, path));
1555
1618
  }
1619
+
1620
+ // src/draw/foldColor.ts
1621
+ function isIdentityColorMatrix(cm) {
1622
+ return cm === IDENTITY_COLOR_MATRIX || sameValues(IDENTITY_COLOR_MATRIX, cm);
1623
+ }
1624
+ var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
1625
+ function foldColor(ctx, r, g, b, a) {
1626
+ const cm = ctx.state.colorMatrix;
1627
+ const alpha = ctx.state.alpha;
1628
+ if (isIdentityColorMatrix(cm)) return [r, g, b, a * alpha];
1629
+ return [
1630
+ clamp01(cm[0] * r + cm[1] * g + cm[2] * b + cm[3] * a + cm[4]),
1631
+ clamp01(cm[5] * r + cm[6] * g + cm[7] * b + cm[8] * a + cm[9]),
1632
+ clamp01(cm[10] * r + cm[11] * g + cm[12] * b + cm[13] * a + cm[14]),
1633
+ clamp01(cm[15] * r + cm[16] * g + cm[17] * b + cm[18] * a + cm[19]) * alpha
1634
+ ];
1635
+ }
1636
+ var NO_TINT = [1, 1, 1, 1];
1637
+ var DIAGONAL = [0, 6, 12, 18];
1638
+ function textureTint(ctx) {
1639
+ const cm = ctx.state.colorMatrix;
1640
+ const alpha = ctx.state.alpha;
1641
+ if (isIdentityColorMatrix(cm)) return alpha === 1 ? NO_TINT : [1, 1, 1, alpha];
1642
+ for (let i = 0; i < 20; i++) {
1643
+ const v = cm[i];
1644
+ if (DIAGONAL.includes(i) ? v < 0 || v > 1 : v !== 0) return null;
1645
+ }
1646
+ return [cm[0], cm[6], cm[12], cm[18] * alpha];
1647
+ }
1648
+
1649
+ // src/draw/batchMatrices.ts
1650
+ function matrixIndex(matrices, colorMatrix, alpha) {
1651
+ for (let i = 0; i < matrices.length; i++) {
1652
+ const m = matrices[i];
1653
+ if (m.alpha === alpha && (m.colorMatrix === colorMatrix || sameValues(m.colorMatrix, colorMatrix))) return i;
1654
+ }
1655
+ return matrices.length < (BATCH_MATRIX_SLOTS ) ? matrices.length : -1;
1656
+ }
1657
+ var MATRICES = new Float32Array(16 * BATCH_MATRIX_SLOTS);
1658
+ var BIASES = new Float32Array(4 * BATCH_MATRIX_SLOTS);
1659
+ var ALPHAS = new Float32Array(BATCH_MATRIX_SLOTS);
1660
+ var UPLOADED = /* @__PURE__ */ new WeakMap();
1661
+ function holds(held, matrices) {
1662
+ if (held.length < matrices.length) return false;
1663
+ for (let i = 0; i < matrices.length; i++) {
1664
+ const a = held[i];
1665
+ const b = matrices[i];
1666
+ if (a.alpha !== b.alpha) return false;
1667
+ if (a.colorMatrix !== b.colorMatrix && !sameValues(a.colorMatrix, b.colorMatrix)) return false;
1668
+ }
1669
+ return true;
1670
+ }
1671
+ function uploadBatchMatrices(ctx, prog, matrices) {
1672
+ let byProgram = UPLOADED.get(ctx);
1673
+ if (!byProgram) {
1674
+ byProgram = /* @__PURE__ */ new WeakMap();
1675
+ UPLOADED.set(ctx, byProgram);
1676
+ }
1677
+ const held = byProgram.get(prog);
1678
+ if (held !== void 0 && holds(held, matrices)) return;
1679
+ const n = matrices.length;
1680
+ for (let i = 0; i < n; i++) {
1681
+ const cm = matrices[i].colorMatrix;
1682
+ for (let row = 0; row < 4; row++) {
1683
+ for (let col = 0; col < 4; col++) MATRICES[16 * i + col * 4 + row] = cm[row * 5 + col];
1684
+ BIASES[4 * i + row] = cm[row * 5 + 4];
1685
+ }
1686
+ ALPHAS[i] = matrices[i].alpha;
1687
+ }
1688
+ const gl = ctx.gl;
1689
+ gl.uniformMatrix4fv(prog.uniform("u_colorMatrix"), false, MATRICES.subarray(0, 16 * n));
1690
+ gl.uniform4fv(prog.uniform("u_colorBias"), BIASES.subarray(0, 4 * n));
1691
+ gl.uniform1fv(prog.uniform("u_alpha"), ALPHAS.subarray(0, n));
1692
+ byProgram.set(prog, matrices.slice());
1693
+ }
1694
+
1695
+ // src/draw/flushBatch.ts
1556
1696
  var BATCH_MODEL = mat3.identity();
1557
1697
  var SAMPLER_UNITS = new Int32Array(
1558
1698
  Array.from({ length: BATCH_TEXTURE_SLOTS }, (_, i) => i)
@@ -1566,14 +1706,12 @@ function flushBatch(ctx) {
1566
1706
  return;
1567
1707
  }
1568
1708
  const gl = ctx.gl;
1569
- const prog = ctx.batchFill;
1709
+ const prog = staged.matrices.length > 1 ? ctx.batchFillMatrices ?? ctx.batchFill : ctx.batchFill;
1570
1710
  gl.useProgram(prog.handle);
1571
1711
  const indexCount = batch.uploadAndBind();
1572
1712
  setProjAndModel(ctx, prog, BATCH_MODEL);
1573
1713
  setColorUniform(ctx, prog, 1, 1, 1, 1);
1574
- setAlphaUniform(ctx, prog, staged.alpha);
1575
- gl.uniform1f(prog.uniform("u_synthBold"), staged.synthBold);
1576
- setColorMatrixUniforms(ctx, prog, staged.colorMatrix);
1714
+ uploadBatchMatrices(ctx, prog, staged.matrices);
1577
1715
  gl.activeTexture(gl.TEXTURE0);
1578
1716
  gl.bindTexture(gl.TEXTURE_2D, ctx.whiteTexture);
1579
1717
  for (let i = 0; i < staged.textures.length; i++) {
@@ -1584,16 +1722,13 @@ function flushBatch(ctx) {
1584
1722
  FIELD_SCALES[2 * (i + 1) + 1] = entry.fieldScale[1];
1585
1723
  continue;
1586
1724
  }
1587
- if (entry.kind === "ramps") {
1588
- ctx.gradRamps.bind(i + 1);
1589
- continue;
1590
- }
1591
1725
  ctx.imageCache.bind(entry.image, i + 1);
1592
1726
  ctx.imageCache.setMagFilter(
1593
1727
  entry.image,
1594
1728
  entry.sampling === "nearest" ? gl.NEAREST : gl.LINEAR
1595
1729
  );
1596
1730
  }
1731
+ if (staged.ramps) ctx.gradRamps.bind(RAMP_SLOT);
1597
1732
  gl.uniform1iv(prog.uniform("u_samplers"), SAMPLER_UNITS);
1598
1733
  gl.uniform2fv(prog.uniform("u_fieldScale"), FIELD_SCALES);
1599
1734
  applyClipTest(ctx, staged.clipDepth);
@@ -1769,36 +1904,41 @@ function resolvedMarker(ref, scale) {
1769
1904
  return { ...ref, size: resolveMarkerSize(ref, 0, quantizeStrokeScale(ref.size.px, scale)) };
1770
1905
  }
1771
1906
 
1772
- // src/draw/foldColor.ts
1773
- function isIdentityColorMatrix(cm) {
1774
- return cm === IDENTITY_COLOR_MATRIX || sameValues(IDENTITY_COLOR_MATRIX, cm);
1907
+ // src/draw/batchRun.ts
1908
+ function slotForBitmap(staged, image, sampling) {
1909
+ for (let i = 0; i < staged.textures.length; i++) {
1910
+ const entry = staged.textures[i];
1911
+ if (entry.kind !== "bitmap" || entry.image !== image) continue;
1912
+ return entry.sampling === sampling ? i + 1 : -1;
1913
+ }
1914
+ return nextFreeSlot(staged);
1775
1915
  }
1776
- var colorFolding = { enabled: true };
1777
- var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
1778
- function foldColor(ctx, staged, r, g, b, a) {
1779
- if (!staged.foldsAlpha) return [r, g, b, a];
1780
- const cm = ctx.state.colorMatrix;
1781
- const alpha = ctx.state.alpha;
1782
- if (isIdentityColorMatrix(cm)) return [r, g, b, a * alpha];
1783
- return [
1784
- clamp01(cm[0] * r + cm[1] * g + cm[2] * b + cm[3] * a + cm[4]),
1785
- clamp01(cm[5] * r + cm[6] * g + cm[7] * b + cm[8] * a + cm[9]),
1786
- clamp01(cm[10] * r + cm[11] * g + cm[12] * b + cm[13] * a + cm[14]),
1787
- clamp01(cm[15] * r + cm[16] * g + cm[17] * b + cm[18] * a + cm[19]) * alpha
1788
- ];
1916
+ function slotForRamps(staged) {
1917
+ if (staged.ramps) return RAMP_SLOT;
1918
+ return nextFreeSlot(staged) < 0 ? -1 : RAMP_SLOT;
1789
1919
  }
1790
- var NO_TINT = [1, 1, 1, 1];
1791
- var DIAGONAL = [0, 6, 12, 18];
1792
- function textureTint(cm) {
1793
- if (isIdentityColorMatrix(cm)) return NO_TINT;
1794
- for (let i = 0; i < 20; i++) {
1795
- const v = cm[i];
1796
- if (DIAGONAL.includes(i) ? v < 0 || v > 1 : v !== 0) return null;
1920
+ function slotForAtlas(staged, id) {
1921
+ for (let i = 0; i < staged.textures.length; i++) {
1922
+ const entry = staged.textures[i];
1923
+ if (entry.kind === "atlas" && entry.id === id) return i + 1;
1797
1924
  }
1798
- return [cm[0], cm[6], cm[12], cm[18]];
1925
+ return nextFreeSlot(staged);
1799
1926
  }
1800
- function stagedTint(staged, tint) {
1801
- return staged.foldsAlpha && tint !== null ? tint : NO_TINT;
1927
+ function nextFreeSlot(staged) {
1928
+ const taken = staged.textures.length + (staged.ramps ? 1 : 0);
1929
+ return taken + 1 < BATCH_TEXTURE_SLOTS ? staged.textures.length + 1 : -1;
1930
+ }
1931
+ function runTakes(ctx, staged, colorMatrix, alpha) {
1932
+ return staged.clipDepth === runClipDepth(ctx) && matrixIndex(staged.matrices, colorMatrix, alpha) >= 0;
1933
+ }
1934
+ function openRun(ctx) {
1935
+ ctx.batchState ??= { clipDepth: runClipDepth(ctx), matrices: [], textures: [], ramps: false };
1936
+ return ctx.batchState;
1937
+ }
1938
+ function takeMatrix(staged, colorMatrix, alpha) {
1939
+ const index = matrixIndex(staged.matrices, colorMatrix, alpha);
1940
+ if (index === staged.matrices.length) staged.matrices.push({ colorMatrix, alpha });
1941
+ return index;
1802
1942
  }
1803
1943
  function batchPaint(ctx, fill, hasVColors) {
1804
1944
  if (hasVColors) return void 0;
@@ -1887,39 +2027,6 @@ function spaceToDevice(ctx, units) {
1887
2027
  }
1888
2028
 
1889
2029
  // src/draw/batch.ts
1890
- function stagedStateIsLive(ctx, staged, image, sampling, synthBold = 0, folds = false) {
1891
- if (staged.clipDepth !== (ctx.clipStack.length === 0 ? ctx.clipDepth : runClipDepth(ctx))) return false;
1892
- if (staged.synthBold !== synthBold) return false;
1893
- if (!staged.foldsAlpha && staged.alpha !== ctx.state.alpha) return false;
1894
- if (image !== void 0 && slotForBitmap(staged, image, sampling) < 0) return false;
1895
- const colorMatrix = ctx.state.colorMatrix;
1896
- if (staged.colorMatrix === colorMatrix || sameValues(staged.colorMatrix, colorMatrix)) return true;
1897
- return folds && staged.foldsAlpha;
1898
- }
1899
- function slotForBitmap(staged, image, sampling) {
1900
- for (let i = 0; i < staged.textures.length; i++) {
1901
- const entry = staged.textures[i];
1902
- if (entry.kind !== "bitmap" || entry.image !== image) continue;
1903
- return entry.sampling === sampling ? i + 1 : -1;
1904
- }
1905
- return nextFreeSlot(staged);
1906
- }
1907
- function slotForRamps(staged) {
1908
- for (let i = 0; i < staged.textures.length; i++) {
1909
- if (staged.textures[i].kind === "ramps") return i + 1;
1910
- }
1911
- return nextFreeSlot(staged);
1912
- }
1913
- function slotForAtlas(staged, id) {
1914
- for (let i = 0; i < staged.textures.length; i++) {
1915
- const entry = staged.textures[i];
1916
- if (entry.kind === "atlas" && entry.id === id) return i + 1;
1917
- }
1918
- return nextFreeSlot(staged);
1919
- }
1920
- function nextFreeSlot(staged) {
1921
- return staged.textures.length + 1 < BATCH_TEXTURE_SLOTS ? staged.textures.length + 1 : -1;
1922
- }
1923
2030
  var MAX_BATCHED_MESH_VERTICES = 96;
1924
2031
  var MAX_BATCHED_MESH_VERTICES_IN_RUN = 1024;
1925
2032
  var meshBatching = {
@@ -1932,79 +2039,78 @@ function canBatchMesh(ctx, mesh) {
1932
2039
  return mesh.vertices.length >> 1 <= cap;
1933
2040
  }
1934
2041
  function stageSolid(ctx, vertices) {
1935
- if (ctx.batchState !== void 0 && !stagedStateIsLive(ctx, ctx.batchState, void 0, void 0, 0, colorFolding.enabled)) {
1936
- flushBatch(ctx);
1937
- }
2042
+ const colorMatrix = IDENTITY_COLOR_MATRIX ;
2043
+ const alpha = 1 ;
2044
+ const open = ctx.batchState;
2045
+ if (open !== void 0 && !runTakes(ctx, open, colorMatrix, alpha)) flushBatch(ctx);
1938
2046
  if (ctx.drawBatch.wouldOverflow(vertices)) flushBatch(ctx);
1939
- return openRun(ctx, colorFolding.enabled);
1940
- }
1941
- function openRun(ctx, folds = false) {
1942
- if (ctx.batchState === void 0) {
1943
- const colorMatrix = folds ? IDENTITY_COLOR_MATRIX : ctx.state.colorMatrix;
1944
- const foldsAlpha = isIdentityColorMatrix(colorMatrix);
1945
- ctx.batchState = {
1946
- alpha: foldsAlpha ? 1 : ctx.state.alpha,
1947
- colorMatrix,
1948
- clipDepth: runClipDepth(ctx),
1949
- synthBold: 0,
1950
- foldsAlpha,
1951
- textures: []
1952
- };
1953
- }
1954
- return ctx.batchState;
2047
+ const matrix = takeMatrix(openRun(ctx), colorMatrix, alpha);
2048
+ return packSlot(withMatrix(WHITE_SLOT, matrix), PAINT_MODE_PLAIN);
2049
+ }
2050
+ var UNTINTED = [1, 1, 1, 1];
2051
+ function texturedUnder(ctx, alphaInEntry) {
2052
+ const tint = textureTint(ctx);
2053
+ if (tint !== null) return { colorMatrix: IDENTITY_COLOR_MATRIX, alpha: 1, tint, slot: 0 };
2054
+ const colorMatrix = ctx.state.colorMatrix;
2055
+ const alpha = ctx.state.alpha;
2056
+ return alphaInEntry ? { colorMatrix, alpha, tint: UNTINTED, slot: 0 } : { colorMatrix, alpha: 1, tint: [1, 1, 1, alpha], slot: 0 };
1955
2057
  }
1956
2058
  function stageImage(ctx, image, sampling) {
1957
- const tint = textureTint(ctx.state.colorMatrix) ;
1958
- if (ctx.batchState !== void 0 && !stagedStateIsLive(ctx, ctx.batchState, image, sampling, 0, tint !== null)) {
2059
+ const under = texturedUnder(ctx, false);
2060
+ const open = ctx.batchState;
2061
+ if (open !== void 0 && (!runTakes(ctx, open, under.colorMatrix, under.alpha) || slotForBitmap(open, image, sampling) < 0)) {
1959
2062
  flushBatch(ctx);
1960
2063
  }
1961
2064
  if (ctx.drawBatch.wouldOverflow(4)) flushBatch(ctx);
1962
- const staged = openRun(ctx, tint !== null);
2065
+ const staged = openRun(ctx);
1963
2066
  let slot = slotForBitmap(staged, image, sampling);
1964
2067
  if (slot > staged.textures.length) {
1965
2068
  staged.textures.push({ kind: "bitmap", image, sampling });
1966
2069
  slot = staged.textures.length;
1967
2070
  }
1968
- return { staged, slot, tint: stagedTint(staged, tint) };
2071
+ under.slot = withMatrix(slot, takeMatrix(staged, under.colorMatrix, under.alpha));
2072
+ return under;
1969
2073
  }
1970
- function stageGlyphs(ctx, atlasId, fieldScale, synthBold) {
1971
- if (ctx.batchState !== void 0 && (!stagedStateIsLive(ctx, ctx.batchState, void 0, void 0, synthBold, colorFolding.enabled) || slotForAtlas(ctx.batchState, atlasId) < 0)) {
2074
+ function stageGlyphs(ctx, atlasId, fieldScale) {
2075
+ const colorMatrix = IDENTITY_COLOR_MATRIX ;
2076
+ const alpha = 1 ;
2077
+ const open = ctx.batchState;
2078
+ if (open !== void 0 && (!runTakes(ctx, open, colorMatrix, alpha) || slotForAtlas(open, atlasId) < 0)) {
1972
2079
  flushBatch(ctx);
1973
2080
  }
1974
2081
  if (ctx.drawBatch.wouldOverflow(4)) flushBatch(ctx);
1975
- const staged = openRun(ctx, colorFolding.enabled);
1976
- staged.synthBold = synthBold;
2082
+ const staged = openRun(ctx);
1977
2083
  let slot = slotForAtlas(staged, atlasId);
1978
2084
  if (slot > staged.textures.length) {
1979
2085
  staged.textures.push({ kind: "atlas", id: atlasId, fieldScale });
1980
2086
  slot = staged.textures.length;
1981
2087
  }
1982
- return { staged, slot };
2088
+ return withMatrix(slot, takeMatrix(staged, colorMatrix, alpha));
1983
2089
  }
1984
2090
  function stageRamps(ctx, stops, space, vertices) {
1985
2091
  if (ctx.gradRamps.wouldReshape(stops, space)) flushBatch(ctx);
1986
- if (ctx.batchState !== void 0 && (!stagedStateIsLive(ctx, ctx.batchState) || slotForRamps(ctx.batchState) < 0)) {
2092
+ const under = texturedUnder(ctx, true);
2093
+ const open = ctx.batchState;
2094
+ if (open !== void 0 && (!runTakes(ctx, open, under.colorMatrix, under.alpha) || slotForRamps(open) < 0)) {
1987
2095
  flushBatch(ctx);
1988
2096
  }
1989
2097
  if (ctx.drawBatch.wouldOverflow(vertices)) flushBatch(ctx);
1990
2098
  const staged = openRun(ctx);
1991
- let slot = slotForRamps(staged);
1992
- if (slot > staged.textures.length) {
1993
- staged.textures.push({ kind: "ramps" });
1994
- slot = staged.textures.length;
1995
- }
1996
- return { staged, slot, rowV: ctx.gradRamps.rowV(ctx.gradRamps.upload(stops, space)) };
1997
- }
1998
- function stagedAlpha(ctx, staged, opacity) {
1999
- return (opacity ?? 1) * (staged.foldsAlpha ? ctx.state.alpha : 1);
2099
+ const slot = slotForRamps(staged);
2100
+ staged.ramps = true;
2101
+ return {
2102
+ slot: withMatrix(slot, takeMatrix(staged, under.colorMatrix, under.alpha)),
2103
+ tint: under.tint,
2104
+ rowV: ctx.gradRamps.rowV(ctx.gradRamps.upload(stops, space))
2105
+ };
2000
2106
  }
2001
- function stagedColor(ctx, staged, paint) {
2107
+ function stagedColor(ctx, paint) {
2002
2108
  const [r, g, b, a] = resolveColor(paint.color);
2003
- return foldColor(ctx, staged, r, g, b, a * (paint.opacity ?? 1));
2109
+ return foldColor(ctx, r, g, b, a * (paint.opacity ?? 1));
2004
2110
  }
2005
2111
  function pushRect(ctx, rect, fill) {
2006
- const staged = stageSolid(ctx, 4);
2007
- const [r, g, b, a] = stagedColor(ctx, staged, fill);
2112
+ const slot = stageSolid(ctx, 4);
2113
+ const [r, g, b, a] = stagedColor(ctx, fill);
2008
2114
  if (!ctx.drawBatch.pushRect(
2009
2115
  rect.x,
2010
2116
  rect.y,
@@ -2014,18 +2120,19 @@ function pushRect(ctx, rect, fill) {
2014
2120
  r,
2015
2121
  g,
2016
2122
  b,
2017
- a
2123
+ a,
2124
+ slot
2018
2125
  )) retryUncut(ctx, rect, fill, pushRect);
2019
2126
  }
2020
2127
  function pushMesh(ctx, mesh, paint) {
2021
- const staged = stageSolid(ctx, mesh.vertices.length >> 1);
2022
- const [r, g, b, a] = stagedColor(ctx, staged, paint);
2023
- if (!ctx.drawBatch.pushMesh(mesh, ctx.state.transform, r, g, b, a)) {
2128
+ const slot = stageSolid(ctx, mesh.vertices.length >> 1);
2129
+ const [r, g, b, a] = stagedColor(ctx, paint);
2130
+ if (!ctx.drawBatch.pushMesh(mesh, ctx.state.transform, r, g, b, a, slot)) {
2024
2131
  retryUncut(ctx, mesh, paint, pushMesh);
2025
2132
  }
2026
2133
  }
2027
2134
  function pushRampRect(ctx, rect, paint) {
2028
- const { staged, slot, rowV } = stageRamps(ctx, paint.stops, paint.space, 4);
2135
+ const { slot, tint, rowV } = stageRamps(ctx, paint.stops, paint.space, 4);
2029
2136
  const { uv, post } = rampVertices(paint, rowV);
2030
2137
  if (!ctx.drawBatch.pushGradientRect(
2031
2138
  rect.x,
@@ -2037,14 +2144,14 @@ function pushRampRect(ctx, rect, paint) {
2037
2144
  post,
2038
2145
  slot,
2039
2146
  paint.mode,
2040
- 1,
2041
- 1,
2042
- 1,
2043
- stagedAlpha(ctx, staged, paint.opacity)
2147
+ tint[0],
2148
+ tint[1],
2149
+ tint[2],
2150
+ (paint.opacity ?? 1) * tint[3]
2044
2151
  )) retryUncut(ctx, rect, paint, pushRampRect);
2045
2152
  }
2046
2153
  function pushRampMesh(ctx, mesh, paint) {
2047
- const { staged, slot, rowV } = stageRamps(ctx, paint.stops, paint.space, mesh.vertices.length >> 1);
2154
+ const { slot, tint, rowV } = stageRamps(ctx, paint.stops, paint.space, mesh.vertices.length >> 1);
2048
2155
  const { uv, post } = rampVertices(paint, rowV);
2049
2156
  if (!ctx.drawBatch.pushGradientMesh(
2050
2157
  mesh,
@@ -2053,10 +2160,10 @@ function pushRampMesh(ctx, mesh, paint) {
2053
2160
  post,
2054
2161
  slot,
2055
2162
  paint.mode,
2056
- 1,
2057
- 1,
2058
- 1,
2059
- stagedAlpha(ctx, staged, paint.opacity)
2163
+ tint[0],
2164
+ tint[1],
2165
+ tint[2],
2166
+ (paint.opacity ?? 1) * tint[3]
2060
2167
  )) retryUncut(ctx, mesh, paint, pushRampMesh);
2061
2168
  }
2062
2169
  function tryStageFill(ctx, mesh, paint) {
@@ -2070,7 +2177,9 @@ function tryStageFill(ctx, mesh, paint) {
2070
2177
  }
2071
2178
  var NO_FIELD = [0, 0];
2072
2179
  function stageColorGlyphs(ctx, atlasId) {
2073
- if (ctx.batchState !== void 0 && (!stagedStateIsLive(ctx, ctx.batchState) || slotForAtlas(ctx.batchState, atlasId) < 0)) {
2180
+ const under = texturedUnder(ctx, false);
2181
+ const open = ctx.batchState;
2182
+ if (open !== void 0 && (!runTakes(ctx, open, under.colorMatrix, under.alpha) || slotForAtlas(open, atlasId) < 0)) {
2074
2183
  flushBatch(ctx);
2075
2184
  }
2076
2185
  if (ctx.drawBatch.wouldOverflow(4)) flushBatch(ctx);
@@ -2080,7 +2189,8 @@ function stageColorGlyphs(ctx, atlasId) {
2080
2189
  staged.textures.push({ kind: "atlas", id: atlasId, fieldScale: NO_FIELD });
2081
2190
  slot = staged.textures.length;
2082
2191
  }
2083
- return { staged, slot };
2192
+ under.slot = withMatrix(slot, takeMatrix(staged, under.colorMatrix, under.alpha));
2193
+ return under;
2084
2194
  }
2085
2195
  function patternTileSpace(spaceInverse, origin, transform) {
2086
2196
  const [a, b, c, d] = transform ?? [1, 0, 0, 1];
@@ -2142,8 +2252,10 @@ function bindPattern(ctx, prog, fill, model) {
2142
2252
  gl.useProgram(prog.handle);
2143
2253
  setProjAndModel(ctx, prog, model);
2144
2254
  setColorMatrixUniforms(ctx, prog);
2145
- gl.uniformMatrix3fv(prog.uniform("u_worldInv"), false, mat3.toUniform(tileSpace));
2146
2255
  const [tw, th] = textureSize(entry.source);
2256
+ tileSpace[6] -= Math.floor(tileSpace[6] / tw) * tw;
2257
+ tileSpace[7] -= Math.floor(tileSpace[7] / th) * th;
2258
+ gl.uniformMatrix3fv(prog.uniform("u_worldInv"), false, mat3.toUniform(tileSpace));
2147
2259
  gl.uniform2f(prog.uniform("u_tileSize"), tw, th);
2148
2260
  ctx.textureCache.bind(tex.id, 0);
2149
2261
  gl.uniform1i(prog.uniform("u_sampler"), 0);
@@ -2159,11 +2271,13 @@ function textureSize(source) {
2159
2271
  function bindGradient(ctx, prog, fill, model) {
2160
2272
  const inverse = gradientSpaceInverse(ctx, fill.units);
2161
2273
  if (!inverse) return null;
2274
+ const about = fill.kind === "linear-gradient" ? fill.from : fill.center;
2275
+ const aboutStart = mat3.multiply(mat3.translated(mat3.identity(), -about.x, -about.y), inverse);
2162
2276
  const gl = ctx.gl;
2163
2277
  const row = ctx.gradRamps.upload(fill.stops, fill.interpolate ?? "rgb");
2164
2278
  gl.useProgram(prog.handle);
2165
2279
  setProjAndModel(ctx, prog, model);
2166
- gl.uniformMatrix3fv(prog.uniform("u_worldInv"), false, mat3.toUniform(inverse));
2280
+ gl.uniformMatrix3fv(prog.uniform("u_worldInv"), false, mat3.toUniform(aboutStart));
2167
2281
  setColorMatrixUniforms(ctx, prog);
2168
2282
  ctx.gradRamps.bind(0);
2169
2283
  gl.uniform1i(prog.uniform("u_ramp"), 0);
@@ -2175,21 +2289,18 @@ function bindGradient(ctx, prog, fill, model) {
2175
2289
  const dx = fill.to.x - fill.from.x;
2176
2290
  const dy = fill.to.y - fill.from.y;
2177
2291
  const len = Math.hypot(dx, dy) || 1;
2178
- gl.uniform2f(prog.uniform("u_gradP0"), fill.from.x, fill.from.y);
2179
2292
  gl.uniform2f(prog.uniform("u_gradDir"), dx / len, dy / len);
2180
2293
  gl.uniform1f(prog.uniform("u_gradLen"), len);
2181
2294
  gl.uniform1f(prog.uniform("u_gradRadius"), 0);
2182
2295
  gl.uniform1f(prog.uniform("u_gradAngle"), 0);
2183
2296
  } else if (fill.kind === "radial-gradient") {
2184
2297
  gl.uniform1i(prog.uniform("u_gradKind"), 1);
2185
- gl.uniform2f(prog.uniform("u_gradP0"), fill.center.x, fill.center.y);
2186
2298
  gl.uniform2f(prog.uniform("u_gradDir"), 0, 0);
2187
2299
  gl.uniform1f(prog.uniform("u_gradLen"), 0);
2188
2300
  gl.uniform1f(prog.uniform("u_gradRadius"), fill.radius);
2189
2301
  gl.uniform1f(prog.uniform("u_gradAngle"), 0);
2190
2302
  } else {
2191
2303
  gl.uniform1i(prog.uniform("u_gradKind"), 2);
2192
- gl.uniform2f(prog.uniform("u_gradP0"), fill.center.x, fill.center.y);
2193
2304
  gl.uniform2f(prog.uniform("u_gradDir"), 0, 0);
2194
2305
  gl.uniform1f(prog.uniform("u_gradLen"), 0);
2195
2306
  gl.uniform1f(prog.uniform("u_gradRadius"), 0);
@@ -2522,11 +2633,9 @@ function drawColorGlyphs(ctx, group, dx, dy) {
2522
2633
  const atlasId = colorPageTextureId(group.page);
2523
2634
  const batch = ctx.drawBatch;
2524
2635
  const m = ctx.state.transform;
2525
- let { staged, slot } = stageColorGlyphs(ctx, atlasId);
2526
- let alpha = opacity * (staged.foldsAlpha ? ctx.state.alpha : 1);
2636
+ let { slot, tint } = stageColorGlyphs(ctx, atlasId);
2527
2637
  const restage = () => {
2528
- ({ staged, slot } = stageColorGlyphs(ctx, atlasId));
2529
- alpha = opacity * (staged.foldsAlpha ? ctx.state.alpha : 1);
2638
+ ({ slot, tint } = stageColorGlyphs(ctx, atlasId));
2530
2639
  };
2531
2640
  for (const q of group.quads) {
2532
2641
  if (batch.wouldOverflow(4)) {
@@ -2544,8 +2653,11 @@ function drawColorGlyphs(ctx, group, dx, dy) {
2544
2653
  q.v0,
2545
2654
  q.u1,
2546
2655
  q.v1,
2547
- alpha,
2548
- slot
2656
+ opacity * tint[3],
2657
+ slot,
2658
+ tint[0],
2659
+ tint[1],
2660
+ tint[2]
2549
2661
  )) {
2550
2662
  cut = enterUncut(ctx);
2551
2663
  restage();
@@ -2694,14 +2806,19 @@ function drawTextDecorations(ctx, decorations, dx, dy) {
2694
2806
  const [r, g, b, a] = isSolidPaint(paint) ? resolveColor(paint.color) : [0, 0, 0, 1];
2695
2807
  let cut;
2696
2808
  for (; ; ) {
2697
- const staged = stageSolid(ctx, 4);
2809
+ const slot = stageSolid(ctx, 4);
2810
+ const rgba = foldColor(ctx, r, g, b, a);
2698
2811
  if (ctx.drawBatch.pushRect(
2699
2812
  d.x0 + dx,
2700
2813
  d.y0 + dy,
2701
2814
  d.x1 - d.x0,
2702
2815
  d.y1 - d.y0,
2703
2816
  ctx.state.transform,
2704
- ...foldColor(ctx, staged, r, g, b, a)
2817
+ rgba[0],
2818
+ rgba[1],
2819
+ rgba[2],
2820
+ rgba[3],
2821
+ slot
2705
2822
  )) break;
2706
2823
  cut = enterUncut(ctx);
2707
2824
  }
@@ -2743,13 +2860,12 @@ function drawTextGroup(ctx, group, dx, dy) {
2743
2860
  const color = isSolidPaint(fill) ? resolveColor(fill.color) : [0, 0, 0, 1];
2744
2861
  const batch = ctx.drawBatch;
2745
2862
  const m = ctx.state.transform;
2746
- let { staged, slot } = stageGlyphs(ctx, atlasId, fieldScale, bold);
2747
- let rgba = foldColor(ctx, staged, color[0], color[1], color[2], color[3]);
2863
+ let slot = stageGlyphs(ctx, atlasId, fieldScale);
2864
+ const rgba = foldColor(ctx, color[0], color[1], color[2], color[3]);
2748
2865
  for (const q of group.quads) {
2749
2866
  if (batch.wouldOverflow(4)) {
2750
2867
  flushBatch(ctx);
2751
- ({ staged, slot } = stageGlyphs(ctx, atlasId, fieldScale, bold));
2752
- rgba = foldColor(ctx, staged, color[0], color[1], color[2], color[3]);
2868
+ slot = stageGlyphs(ctx, atlasId, fieldScale);
2753
2869
  }
2754
2870
  let cut;
2755
2871
  while (!batch.pushGlyph(
@@ -2769,16 +2885,15 @@ function drawTextGroup(ctx, group, dx, dy) {
2769
2885
  rgba[2],
2770
2886
  rgba[3],
2771
2887
  slot,
2772
- mode
2888
+ mode,
2889
+ bold
2773
2890
  )) {
2774
2891
  cut = enterUncut(ctx);
2775
- ({ staged, slot } = stageGlyphs(ctx, atlasId, fieldScale, bold));
2776
- rgba = foldColor(ctx, staged, color[0], color[1], color[2], color[3]);
2892
+ slot = stageGlyphs(ctx, atlasId, fieldScale);
2777
2893
  }
2778
2894
  if (cut !== void 0) {
2779
2895
  leaveUncut(ctx, cut);
2780
- ({ staged, slot } = stageGlyphs(ctx, atlasId, fieldScale, bold));
2781
- rgba = foldColor(ctx, staged, color[0], color[1], color[2], color[3]);
2896
+ slot = stageGlyphs(ctx, atlasId, fieldScale);
2782
2897
  }
2783
2898
  }
2784
2899
  }
@@ -2835,7 +2950,8 @@ function drawPaintedGlyphs(ctx, group, dx, dy, kind, atlasId, mode, bold, tanIta
2835
2950
  1,
2836
2951
  1,
2837
2952
  GLYPH_PAINT_ATLAS_UNIT,
2838
- mode
2953
+ mode,
2954
+ 0
2839
2955
  );
2840
2956
  }
2841
2957
  drawStagedGlyphs(ctx);
@@ -2891,7 +3007,7 @@ function drawImage(ctx, cmd) {
2891
3007
  const sh = cmd.source?.h ?? cmd.image.height;
2892
3008
  if (isMinified(minifiedLimits(ctx), sw, sh, cmd.w, cmd.h)) ctx.imageCache.buildMipmaps(cmd.image);
2893
3009
  }
2894
- const { staged, slot, tint } = stageImage(ctx, cmd.image, cmd.sampling ?? "linear");
3010
+ const { slot, tint } = stageImage(ctx, cmd.image, cmd.sampling ?? "linear");
2895
3011
  const src = cmd.source;
2896
3012
  let u0 = 0, v0 = 0, u1 = 1, v1 = 1;
2897
3013
  if (src) {
@@ -2921,10 +3037,8 @@ function drawImage(ctx, cmd) {
2921
3037
  v0,
2922
3038
  u1,
2923
3039
  v1,
2924
- // Group alpha rides the vertices only where it rode the solid colors too:
2925
- // with a real color matrix in the run it is `u_alpha`, and folding it here
2926
- // as well would apply it twice.
2927
- (cmd.opacity ?? 1) * (staged.foldsAlpha ? ctx.state.alpha : 1) * tint[3],
3040
+ // Group alpha rides here with the opacity, in the tint's fourth channel.
3041
+ (cmd.opacity ?? 1) * tint[3],
2928
3042
  slot,
2929
3043
  tint[0],
2930
3044
  tint[1],
@@ -2945,17 +3059,15 @@ function drawSprites(ctx, cmd) {
2945
3059
  }
2946
3060
  }
2947
3061
  const sampling = cmd.sampling ?? "linear";
2948
- let { staged, slot, tint } = stageImage(ctx, cmd.image, sampling);
3062
+ let { slot, tint } = stageImage(ctx, cmd.image, sampling);
2949
3063
  const batch = ctx.drawBatch;
2950
3064
  const m = ctx.state.transform;
2951
- let groupAlpha = (staged.foldsAlpha ? ctx.state.alpha : 1) * tint[3];
2952
3065
  const tw = cmd.image.width;
2953
3066
  const th = cmd.image.height;
2954
3067
  for (let s = 0, i = 0; s < count; s++, i += SPRITE_STRIDE) {
2955
3068
  if (batch.wouldOverflow(4)) {
2956
3069
  flushBatch(ctx);
2957
- ({ staged, slot, tint } = stageImage(ctx, cmd.image, sampling));
2958
- groupAlpha = (staged.foldsAlpha ? ctx.state.alpha : 1) * tint[3];
3070
+ ({ slot, tint } = stageImage(ctx, cmd.image, sampling));
2959
3071
  }
2960
3072
  const sx = data[i + 4], sy = data[i + 5], sw = data[i + 6], sh = data[i + 7];
2961
3073
  let cut;
@@ -2969,20 +3081,18 @@ function drawSprites(ctx, cmd) {
2969
3081
  sy / th,
2970
3082
  (sx + sw) / tw,
2971
3083
  (sy + sh) / th,
2972
- data[i + 8] * groupAlpha,
3084
+ data[i + 8] * tint[3],
2973
3085
  slot,
2974
3086
  tint[0],
2975
3087
  tint[1],
2976
3088
  tint[2]
2977
3089
  )) {
2978
3090
  cut = enterUncut(ctx);
2979
- ({ staged, slot, tint } = stageImage(ctx, cmd.image, sampling));
2980
- groupAlpha = (staged.foldsAlpha ? ctx.state.alpha : 1) * tint[3];
3091
+ ({ slot, tint } = stageImage(ctx, cmd.image, sampling));
2981
3092
  }
2982
3093
  if (cut !== void 0) {
2983
3094
  leaveUncut(ctx, cut);
2984
- ({ staged, slot, tint } = stageImage(ctx, cmd.image, sampling));
2985
- groupAlpha = (staged.foldsAlpha ? ctx.state.alpha : 1) * tint[3];
3095
+ ({ slot, tint } = stageImage(ctx, cmd.image, sampling));
2986
3096
  }
2987
3097
  }
2988
3098
  }
@@ -3484,9 +3594,12 @@ var GLYPH_PAINT_SOURCES = {
3484
3594
  var WeaselRenderer = class {
3485
3595
  gl;
3486
3596
  stats = { drawCalls: 0, ms: 0, spans: [] };
3597
+ /** Built on the first frame that draws with each, so a one-shot render of
3598
+ * plain shapes builds the batch program alone. */
3487
3599
  pathFill;
3488
3600
  pathFillVColor;
3489
3601
  batchFill;
3602
+ batchFillMatrices;
3490
3603
  gradFill;
3491
3604
  patternFill;
3492
3605
  /** Compiled on first use: only atlas text under a texture paint needs one. */
@@ -3566,21 +3679,12 @@ var WeaselRenderer = class {
3566
3679
  }
3567
3680
  this.applyGlState();
3568
3681
  this.applyTarget();
3569
- this.pathFill = new ShaderProgram(this.gl, VERT_SRC, FRAG_SRC);
3570
- this.pathFill.lookupUniforms(PATH_FILL_UNIFORMS);
3571
- this.pathFill.lookupAttributes(PATH_FILL_ATTRIBUTES);
3572
- this.pathFillVColor = new ShaderProgram(this.gl, VCOLOR_VERT_SRC, VCOLOR_FRAG_SRC);
3573
- this.pathFillVColor.lookupUniforms(PATH_FILL_UNIFORMS);
3574
- this.pathFillVColor.lookupAttributes(PATH_FILL_VCOLOR_ATTRIBUTES);
3575
- this.batchFill = new ShaderProgram(this.gl, BATCH_VERT_SRC, BATCH_FRAG_SRC);
3576
- this.batchFill.lookupUniforms(BATCH_FILL_UNIFORMS);
3577
- this.batchFill.lookupAttributes(BATCH_FILL_ATTRIBUTES);
3578
- this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
3579
- this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
3580
- this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
3581
- this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
3582
- this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
3583
- this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
3682
+ this.pathFill = new LazyProgram(this.gl, PATH_FILL_SPEC);
3683
+ this.pathFillVColor = new LazyProgram(this.gl, PATH_FILL_VCOLOR_SPEC);
3684
+ this.gradFill = new LazyProgram(this.gl, GRAD_FILL_SPEC);
3685
+ this.patternFill = new LazyProgram(this.gl, PATTERN_FILL_SPEC);
3686
+ this.batchFillMatrices = new LazyProgram(this.gl, BATCH_FILL_MATRICES_SPEC);
3687
+ this.batchFill = createBatchFill(this.gl);
3584
3688
  this.meshCache = new GLMeshCache(this.gl);
3585
3689
  this.textureCache = new GLTextureCache(this.gl);
3586
3690
  this.imageCache = new GLImageCache(this.gl, this.imageMinification);
@@ -3589,6 +3693,9 @@ var WeaselRenderer = class {
3589
3693
  this.drawBatch = new DrawBatch(this.gl, this.batchFill);
3590
3694
  this.whiteTexture = createWhiteTexture(this.gl);
3591
3695
  }
3696
+ lazyPrograms() {
3697
+ return [this.pathFill, this.pathFillVColor, this.gradFill, this.patternFill, this.batchFillMatrices];
3698
+ }
3592
3699
  uploadQuadGeometry() {
3593
3700
  const gl = this.gl;
3594
3701
  this.quadVbo = gl.createBuffer();
@@ -3742,21 +3849,8 @@ var WeaselRenderer = class {
3742
3849
  this.paintScratch.release();
3743
3850
  this.paintResources.release();
3744
3851
  this.paintResources = createPaintResources();
3745
- this.pathFill = new ShaderProgram(this.gl, VERT_SRC, FRAG_SRC);
3746
- this.pathFill.lookupUniforms(PATH_FILL_UNIFORMS);
3747
- this.pathFill.lookupAttributes(PATH_FILL_ATTRIBUTES);
3748
- this.pathFillVColor = new ShaderProgram(this.gl, VCOLOR_VERT_SRC, VCOLOR_FRAG_SRC);
3749
- this.pathFillVColor.lookupUniforms(PATH_FILL_UNIFORMS);
3750
- this.pathFillVColor.lookupAttributes(PATH_FILL_VCOLOR_ATTRIBUTES);
3751
- this.batchFill = new ShaderProgram(this.gl, BATCH_VERT_SRC, BATCH_FRAG_SRC);
3752
- this.batchFill.lookupUniforms(BATCH_FILL_UNIFORMS);
3753
- this.batchFill.lookupAttributes(BATCH_FILL_ATTRIBUTES);
3754
- this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
3755
- this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
3756
- this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
3757
- this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
3758
- this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
3759
- this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
3852
+ for (const lazy of this.lazyPrograms()) lazy.abandon();
3853
+ this.batchFill = createBatchFill(this.gl);
3760
3854
  this.glyphPaintPrograms.clear();
3761
3855
  this.meshCache = new GLMeshCache(this.gl);
3762
3856
  this.textureCache = new GLTextureCache(this.gl);
@@ -3795,14 +3889,8 @@ var WeaselRenderer = class {
3795
3889
  }
3796
3890
  this.meshCache.dispose();
3797
3891
  this.paintResources.release();
3798
- for (const prog of [
3799
- this.pathFill,
3800
- this.pathFillVColor,
3801
- this.batchFill,
3802
- this.gradFill,
3803
- this.patternFill,
3804
- ...this.glyphPaintPrograms.values()
3805
- ]) {
3892
+ for (const lazy of this.lazyPrograms()) lazy.release();
3893
+ for (const prog of [this.batchFill, ...this.glyphPaintPrograms.values()]) {
3806
3894
  gl.deleteProgram(prog.handle);
3807
3895
  }
3808
3896
  this.glyphPaintPrograms.clear();
@@ -3853,13 +3941,25 @@ var WeaselRenderer = class {
3853
3941
  gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
3854
3942
  }
3855
3943
  const device = this.deviceRect();
3944
+ const { pathFill, pathFillVColor, gradFill, patternFill, batchFillMatrices } = this;
3856
3945
  const ctx = {
3857
3946
  gl,
3858
- pathFill: this.pathFill,
3859
- pathFillVColor: this.pathFillVColor,
3947
+ get pathFill() {
3948
+ return pathFill.get();
3949
+ },
3950
+ get pathFillVColor() {
3951
+ return pathFillVColor.get();
3952
+ },
3860
3953
  batchFill: this.batchFill,
3861
- gradFill: this.gradFill,
3862
- patternFill: this.patternFill,
3954
+ get batchFillMatrices() {
3955
+ return batchFillMatrices.get();
3956
+ },
3957
+ get gradFill() {
3958
+ return gradFill.get();
3959
+ },
3960
+ get patternFill() {
3961
+ return patternFill.get();
3962
+ },
3863
3963
  meshCache: this.meshCache,
3864
3964
  textureCache: this.textureCache,
3865
3965
  imageCache: this.imageCache,
@@ -3933,11 +4033,11 @@ var WeaselRenderer = class {
3933
4033
  }
3934
4034
  /** @internal */
3935
4035
  _pathFill() {
3936
- return this.pathFill;
4036
+ return this.pathFill.get();
3937
4037
  }
3938
4038
  /** @internal */
3939
4039
  _pathFillVColor() {
3940
- return this.pathFillVColor;
4040
+ return this.pathFillVColor.get();
3941
4041
  }
3942
4042
  /** @internal */
3943
4043
  _drawBatch() {
@@ -3948,12 +4048,16 @@ var WeaselRenderer = class {
3948
4048
  return this.batchFill;
3949
4049
  }
3950
4050
  /** @internal */
4051
+ _batchFillMatrices() {
4052
+ return this.batchFillMatrices.get();
4053
+ }
4054
+ /** @internal */
3951
4055
  _gradFill() {
3952
- return this.gradFill;
4056
+ return this.gradFill.get();
3953
4057
  }
3954
4058
  /** @internal */
3955
4059
  _patternFill() {
3956
- return this.patternFill;
4060
+ return this.patternFill.get();
3957
4061
  }
3958
4062
  /** @internal */
3959
4063
  _meshCache() {