@weasel-js/core 1.7.3 → 1.8.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/animation/colorHelpers.js +58 -58
  3. package/dist/animation/colorHelpers.js.map +1 -1
  4. package/dist/animation/engine/axes.js +36 -0
  5. package/dist/animation/engine/axes.js.map +1 -0
  6. package/dist/animation/engine/codec.js +170 -0
  7. package/dist/animation/engine/codec.js.map +1 -0
  8. package/dist/animation/engine/integrator.js +12 -0
  9. package/dist/animation/engine/integrator.js.map +1 -0
  10. package/dist/animation/useAnimator.js +209 -160
  11. package/dist/animation/useAnimator.js.map +1 -1
  12. package/dist/{builtins-C-gRyVRx.d.ts → builtins-CMEh6-Oc.d.ts} +81 -8
  13. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +12 -12
  14. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  15. package/dist/canvas/pickWalk.js +2 -2
  16. package/dist/canvas/pickWalk.js.map +1 -1
  17. package/dist/canvas/sceneSlotWiring.js +1 -1
  18. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  19. package/dist/canvas/sceneViewRender.js +1 -1
  20. package/dist/canvas/sceneViewRender.js.map +1 -1
  21. package/dist/core/scene/effectivePose.js +1 -1
  22. package/dist/core/scene/effectivePose.js.map +1 -1
  23. package/dist/core/scene/poseOverrides.js +42 -22
  24. package/dist/core/scene/poseOverrides.js.map +1 -1
  25. package/dist/core/scene/scene.js +9 -0
  26. package/dist/core/scene/scene.js.map +1 -1
  27. package/dist/core/scene/types.js.map +1 -1
  28. package/dist/core/selection/useSelection.js +27 -33
  29. package/dist/core/selection/useSelection.js.map +1 -1
  30. package/dist/core/stableStringify.js +8 -0
  31. package/dist/core/stableStringify.js.map +1 -0
  32. package/dist/index.d.ts +35 -25
  33. package/dist/index.js +269 -268
  34. package/dist/renderer/WeaselRenderer.js +4 -1
  35. package/dist/renderer/WeaselRenderer.js.map +1 -1
  36. package/dist/renderer/batchClip.js +46 -0
  37. package/dist/renderer/batchClip.js.map +1 -0
  38. package/dist/renderer/draw.js +367 -294
  39. package/dist/renderer/draw.js.map +1 -1
  40. package/dist/renderer/drawBatch.js +75 -45
  41. package/dist/renderer/drawBatch.js.map +1 -1
  42. package/dist/renderer/shaders/batchFill.js +3 -2
  43. package/dist/renderer/shaders/batchFill.js.map +1 -1
  44. package/dist/renderer.d.ts +1 -1
  45. package/dist/tools/builtin/text/useTextTool.js +8 -8
  46. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  47. package/dist/tools/prefs.js.map +1 -1
  48. package/dist/version.js +1 -1
  49. package/package.json +17 -15
@@ -1,18 +1,19 @@
1
1
  import { PAINT_MODE_PLAIN as e, WHITE_SLOT as t, packSlot as n } from "./shaders/batchFill.js";
2
+ import { clipConvexPolygon as r } from "./batchClip.js";
2
3
  //#region src/renderer/drawBatch.ts
3
- var r = 32768, i = 10, a = 256, o = 384, s = .5, c = .5, l = n(t, e), u = 64, d = [{
4
+ var i = 32768, a = 10, o = 256, s = 384, c = .5, l = .5, u = n(t, e), d = 64, f = [{
4
5
  vertices: 16,
5
6
  indices: 24
6
7
  }, {
7
8
  vertices: 1024,
8
9
  indices: 3072
9
- }], f = d[d.length - 1].vertices, p = 4;
10
- function m(e, t) {
10
+ }], p = f[f.length - 1].vertices, m = 4;
11
+ function h(e, t) {
11
12
  let n = e;
12
13
  for (; n < t;) n *= 2;
13
14
  return n;
14
15
  }
15
- function h(e) {
16
+ function g(e) {
16
17
  let t = new Uint32Array(e * 6);
17
18
  for (let n = 0; n < e; n++) {
18
19
  let e = n * 4, r = n * 6;
@@ -20,22 +21,26 @@ function h(e) {
20
21
  }
21
22
  return t;
22
23
  }
23
- var g = class {
24
+ var _ = class {
24
25
  gl;
25
26
  aPos;
26
27
  aColor;
27
28
  aUv;
28
29
  aPost;
29
30
  aSlot;
30
- rings = d.map(() => Array(u).fill(void 0));
31
- nextInRing = d.map(() => 0);
32
- largeRing = Array(p).fill(void 0);
31
+ rings = f.map(() => Array(d).fill(void 0));
32
+ nextInRing = f.map(() => 0);
33
+ largeRing = Array(m).fill(void 0);
33
34
  nextLarge = 0;
34
- verts = new Float32Array(a * i);
35
- idx = new Uint32Array(o);
35
+ verts = new Float32Array(o * a);
36
+ idx = new Uint32Array(s);
36
37
  nVerts = 0;
37
38
  nIdx = 0;
38
39
  pureRects = !0;
40
+ clip = null;
41
+ polyIn = /* @__PURE__ */ new Float32Array(80);
42
+ polyOut = /* @__PURE__ */ new Float32Array(80);
43
+ polyTmp = /* @__PURE__ */ new Float32Array(80);
39
44
  constructor(e, t) {
40
45
  let n = t.attribute("a_position"), r = t.attribute("a_vertexColor"), i = t.attribute("a_uv"), a = t.attribute("a_post"), o = t.attribute("a_texSlot");
41
46
  if (n === void 0 || r === void 0 || i === void 0 || a === void 0 || o === void 0) throw Error("DrawBatch: batch program is missing a_position / a_vertexColor / a_uv / a_post / a_texSlot");
@@ -45,61 +50,61 @@ var g = class {
45
50
  return this.nIdx;
46
51
  }
47
52
  wouldOverflow(e) {
48
- return this.nVerts + e > r;
53
+ return this.nVerts + e > i;
49
54
  }
50
- pushRect(e, t, n, r, a, o, u, d, f) {
55
+ pushRect(e, t, n, r, i, o, s, d, f) {
51
56
  this.reserve(4, 6);
52
- let p = this.nVerts * i, m = a[0], h = a[1], g = a[3], _ = a[4], v = a[6], y = a[7], b = e + n, x = t + r, S = m * e + g * t + v, C = h * e + _ * t + y, w = m * b + g * t + v, T = h * b + _ * t + y, E = m * b + g * x + v, D = h * b + _ * x + y, O = m * e + g * x + v, k = h * e + _ * x + y, A = l;
53
- p = this.writeVertex(p, S, C, o, u, d, f, s, c, 1, A), p = this.writeVertex(p, w, T, o, u, d, f, s, c, 1, A), p = this.writeVertex(p, E, D, o, u, d, f, s, c, 1, A), this.writeVertex(p, O, k, o, u, d, f, s, c, 1, A), this.pushQuadIndices();
57
+ let p = this.nVerts * a, m = i[0], h = i[1], g = i[3], _ = i[4], v = i[6], y = i[7], b = e + n, x = t + r, S = m * e + g * t + v, C = h * e + _ * t + y, w = m * b + g * t + v, T = h * b + _ * t + y, E = m * b + g * x + v, D = h * b + _ * x + y, O = m * e + g * x + v, k = h * e + _ * x + y, A = u;
58
+ return p = this.writeVertex(p, S, C, o, s, d, f, c, l, 1, A), p = this.writeVertex(p, w, T, o, s, d, f, c, l, 1, A), p = this.writeVertex(p, E, D, o, s, d, f, c, l, 1, A), this.writeVertex(p, O, k, o, s, d, f, c, l, 1, A), this.pushQuadIndices(), this.clip === null || this.clipQuad(this.nVerts - 4, this.nIdx - 6, this.clip);
54
59
  }
55
- pushQuad(t, r, a, o, s, c, l, u, d, f, p) {
60
+ pushQuad(t, r, i, o, s, c, l, u, d, f, p) {
56
61
  this.reserve(4, 6);
57
- let m = this.nVerts * i, h = s[0], g = s[1], _ = s[3], v = s[4], y = s[6], b = s[7], x = t + a, S = r + o, C = h * t + _ * r + y, w = g * t + v * r + b, T = h * x + _ * r + y, E = g * x + v * r + b, D = h * x + _ * S + y, O = g * x + v * S + b, k = h * t + _ * S + y, A = g * t + v * S + b, j = n(p, e);
58
- m = this.writeVertex(m, C, w, 1, 1, 1, 1, c, l, f, j), m = this.writeVertex(m, T, E, 1, 1, 1, 1, u, l, f, j), m = this.writeVertex(m, D, O, 1, 1, 1, 1, u, d, f, j), this.writeVertex(m, k, A, 1, 1, 1, 1, c, d, f, j), this.pushQuadIndices();
62
+ let m = this.nVerts * a, h = s[0], g = s[1], _ = s[3], v = s[4], y = s[6], b = s[7], x = t + i, S = r + o, C = h * t + _ * r + y, w = g * t + v * r + b, T = h * x + _ * r + y, E = g * x + v * r + b, D = h * x + _ * S + y, O = g * x + v * S + b, k = h * t + _ * S + y, A = g * t + v * S + b, j = n(p, e);
63
+ return m = this.writeVertex(m, C, w, 1, 1, 1, 1, c, l, f, j), m = this.writeVertex(m, T, E, 1, 1, 1, 1, u, l, f, j), m = this.writeVertex(m, D, O, 1, 1, 1, 1, u, d, f, j), this.writeVertex(m, k, A, 1, 1, 1, 1, c, d, f, j), this.pushQuadIndices(), this.clip === null || this.clipQuad(this.nVerts - 4, this.nIdx - 6, this.clip);
59
64
  }
60
- pushGlyph(e, t, r, a, o, s, c, l, u, d, f, p, m, h, g, _, v) {
65
+ pushGlyph(e, t, r, i, o, s, c, l, u, d, f, p, m, h, g, _, v) {
61
66
  this.reserve(4, 6);
62
- let y = this.nVerts * i, b = c[0], x = c[1], S = c[3], C = c[4], w = c[6], T = c[7], E = (o - t) * s, D = (o - a) * s, O = e + E, k = r + E, A = e + D, j = r + D, M = b * O + S * t + w, N = x * O + C * t + T, P = b * k + S * t + w, F = x * k + C * t + T, I = b * j + S * a + w, L = x * j + C * a + T, R = b * A + S * a + w, z = x * A + C * a + T, B = n(_, v);
63
- y = this.writeVertex(y, M, N, p, m, h, g, l, u, 1, B), y = this.writeVertex(y, P, F, p, m, h, g, d, u, 1, B), y = this.writeVertex(y, I, L, p, m, h, g, d, f, 1, B), this.writeVertex(y, R, z, p, m, h, g, l, f, 1, B), this.pushQuadIndices();
67
+ let y = this.nVerts * a, b = c[0], x = c[1], S = c[3], C = c[4], w = c[6], T = c[7], E = (o - t) * s, D = (o - i) * s, O = e + E, k = r + E, A = e + D, j = r + D, M = b * O + S * t + w, N = x * O + C * t + T, P = b * k + S * t + w, F = x * k + C * t + T, I = b * j + S * i + w, L = x * j + C * i + T, R = b * A + S * i + w, z = x * A + C * i + T, B = n(_, v);
68
+ return y = this.writeVertex(y, M, N, p, m, h, g, l, u, 1, B), y = this.writeVertex(y, P, F, p, m, h, g, d, u, 1, B), y = this.writeVertex(y, I, L, p, m, h, g, d, f, 1, B), this.writeVertex(y, R, z, p, m, h, g, l, f, 1, B), this.pushQuadIndices(), this.clip === null || this.clipQuad(this.nVerts - 4, this.nIdx - 6, this.clip);
64
69
  }
65
- pushGradientRect(e, t, r, a, o, s, c, l, u, d, f, p, m) {
70
+ pushGradientRect(e, t, r, i, o, s, c, l, u, d, f, p, m) {
66
71
  this.reserve(4, 6);
67
- let h = this.nVerts * i, g = o[0], _ = o[1], v = o[3], y = o[4], b = o[6], x = o[7], S = e + r, C = t + a, w = n(l, u), T = (e, t) => {
72
+ let h = this.nVerts * a, g = o[0], _ = o[1], v = o[3], y = o[4], b = o[6], x = o[7], S = e + r, C = t + i, w = n(l, u), T = (e, t) => {
68
73
  let n = h;
69
74
  return h = this.writeVertex(n, g * e + v * t + b, _ * e + y * t + x, d, f, p, m, s.ux * e + s.uy * t + s.u0, s.vx * e + s.vy * t + s.v0, c, w), h;
70
75
  };
71
- T(e, t), T(S, t), T(S, C), T(e, C), this.pushQuadIndices();
76
+ return T(e, t), T(S, t), T(S, C), T(e, C), this.pushQuadIndices(), this.clip === null || this.clipQuad(this.nVerts - 4, this.nIdx - 6, this.clip);
72
77
  }
73
- pushGradientMesh(e, t, r, a, o, s, c, l, u, d) {
78
+ pushGradientMesh(e, t, r, i, o, s, c, l, u, d) {
74
79
  let f = e.vertices, p = e.indices, m = f.length >> 1;
75
80
  this.reserve(m, p.length), this.pureRects = !1;
76
- let h = this.verts, g = this.nVerts * i, _ = t[0], v = t[1], y = t[3], b = t[4], x = t[6], S = t[7], C = n(o, s);
81
+ let h = this.verts, g = this.nVerts * a, _ = t[0], v = t[1], y = t[3], b = t[4], x = t[6], S = t[7], C = n(o, s);
77
82
  for (let e = 0; e < m; e++) {
78
83
  let t = f[e * 2], n = f[e * 2 + 1];
79
- h[g++] = _ * t + y * n + x, h[g++] = v * t + b * n + S, h[g++] = c, h[g++] = l, h[g++] = u, h[g++] = d, h[g++] = r.ux * t + r.uy * n + r.u0, h[g++] = r.vx * t + r.vy * n + r.v0, h[g++] = a, h[g++] = C;
84
+ h[g++] = _ * t + y * n + x, h[g++] = v * t + b * n + S, h[g++] = c, h[g++] = l, h[g++] = u, h[g++] = d, h[g++] = r.ux * t + r.uy * n + r.u0, h[g++] = r.vx * t + r.vy * n + r.v0, h[g++] = i, h[g++] = C;
80
85
  }
81
86
  let w = this.nVerts, T = this.idx, E = this.nIdx;
82
87
  for (let e = 0; e < p.length; e++) T[E++] = w + p[e];
83
- this.nVerts += m, this.nIdx += p.length;
88
+ return this.nVerts += m, this.nIdx += p.length, this.clip === null || this.clipMesh(w, this.nIdx - p.length, this.clip);
84
89
  }
85
- pushMesh(e, t, n, r, a, o) {
86
- let u = e.vertices, d = e.indices, f = u.length >> 1;
90
+ pushMesh(e, t, n, r, i, o) {
91
+ let s = e.vertices, d = e.indices, f = s.length >> 1;
87
92
  this.reserve(f, d.length), this.pureRects = !1;
88
- let p = this.verts, m = this.nVerts * i, h = t[0], g = t[1], _ = t[3], v = t[4], y = t[6], b = t[7];
93
+ let p = this.verts, m = this.nVerts * a, h = t[0], g = t[1], _ = t[3], v = t[4], y = t[6], b = t[7];
89
94
  for (let e = 0; e < f; e++) {
90
- let t = u[e * 2], i = u[e * 2 + 1];
91
- p[m++] = h * t + _ * i + y, p[m++] = g * t + v * i + b, p[m++] = n, p[m++] = r, p[m++] = a, p[m++] = o, p[m++] = s, p[m++] = c, p[m++] = 1, p[m++] = l;
95
+ let t = s[e * 2], a = s[e * 2 + 1];
96
+ p[m++] = h * t + _ * a + y, p[m++] = g * t + v * a + b, p[m++] = n, p[m++] = r, p[m++] = i, p[m++] = o, p[m++] = c, p[m++] = l, p[m++] = 1, p[m++] = u;
92
97
  }
93
98
  let x = this.nVerts, S = this.idx, C = this.nIdx;
94
99
  for (let e = 0; e < d.length; e++) S[C++] = x + d[e];
95
- this.nVerts += f, this.nIdx += d.length;
100
+ return this.nVerts += f, this.nIdx += d.length, this.clip === null || this.clipMesh(x, this.nIdx - d.length, this.clip);
96
101
  }
97
102
  uploadAndBind() {
98
- let e = this.gl, t = d.findIndex((e) => this.nVerts <= e.vertices && this.nIdx <= e.indices), n = t < 0 ? this.nextLargeSlot() : this.nextRingSlot(t);
99
- if (e.bindVertexArray(n.vao), e.bindBuffer(e.ARRAY_BUFFER, n.vbo), e.bufferSubData(e.ARRAY_BUFFER, 0, this.verts, 0, this.nVerts * i), !this.pureRects) e.bufferSubData(e.ELEMENT_ARRAY_BUFFER, 0, this.idx, 0, this.nIdx), n.canonicalQuads = 0;
103
+ let e = this.gl, t = f.findIndex((e) => this.nVerts <= e.vertices && this.nIdx <= e.indices), n = t < 0 ? this.nextLargeSlot() : this.nextRingSlot(t);
104
+ if (e.bindVertexArray(n.vao), e.bindBuffer(e.ARRAY_BUFFER, n.vbo), e.bufferSubData(e.ARRAY_BUFFER, 0, this.verts, 0, this.nVerts * a), !this.pureRects) e.bufferSubData(e.ELEMENT_ARRAY_BUFFER, 0, this.idx, 0, this.nIdx), n.canonicalQuads = 0;
100
105
  else if (n.canonicalQuads < this.nVerts >> 2) {
101
106
  let t = Math.min(n.vertexCapacity >> 2, Math.floor(n.indexCapacity / 6));
102
- e.bufferSubData(e.ELEMENT_ARRAY_BUFFER, 0, h(t)), n.canonicalQuads = t;
107
+ e.bufferSubData(e.ELEMENT_ARRAY_BUFFER, 0, g(t)), n.canonicalQuads = t;
103
108
  }
104
109
  return this.nIdx;
105
110
  }
@@ -111,23 +116,48 @@ var g = class {
111
116
  for (let e of this.rings) e.fill(void 0);
112
117
  this.largeRing.fill(void 0);
113
118
  }
114
- writeVertex(e, t, n, r, a, o, s, c, l, u, d) {
119
+ writeVertex(e, t, n, r, i, o, s, c, l, u, d) {
115
120
  let f = this.verts;
116
- return f[e] = t, f[e + 1] = n, f[e + 2] = r, f[e + 3] = a, f[e + 4] = o, f[e + 5] = s, f[e + 6] = c, f[e + 7] = l, f[e + 8] = u, f[e + 9] = d, e + i;
121
+ return f[e] = t, f[e + 1] = n, f[e + 2] = r, f[e + 3] = i, f[e + 4] = o, f[e + 5] = s, f[e + 6] = c, f[e + 7] = l, f[e + 8] = u, f[e + 9] = d, e + a;
122
+ }
123
+ clipQuad(e, t, n) {
124
+ let i = this.verts, o = e * a, s = a, c = i[o], l = i[o + 1], u = i[o + s], d = i[o + s + 1], f = i[o + 20], p = i[o + 20 + 1], m = i[o + 30], h = i[o + 30 + 1], g = Math.min(c, u, f, m), _ = Math.max(c, u, f, m), v = Math.min(l, d, p, h), y = Math.max(l, d, p, h);
125
+ if (g >= n.x0 && _ <= n.x1 && v >= n.y0 && y <= n.y1 || (this.nVerts = e, this.nIdx = t, _ <= n.x0 || g >= n.x1 || y <= n.y0 || v >= n.y1)) return !0;
126
+ if (!(l === d && u === f && p === h && m === c || c === u && d === p && f === m && h === l)) return !1;
127
+ this.polyIn.set(i.subarray(o, o + 40));
128
+ let b = r(this.polyIn, 4, s, n, this.polyOut, this.polyTmp);
129
+ return this.appendFan(this.polyOut, b), !0;
130
+ }
131
+ clipMesh(e, t, n) {
132
+ let r = this.verts, i = Infinity, o = Infinity, s = -Infinity, c = -Infinity;
133
+ for (let t = e * a; t < this.nVerts * a; t += a) {
134
+ let e = r[t], n = r[t + 1];
135
+ e < i && (i = e), e > s && (s = e), n < o && (o = n), n > c && (c = n);
136
+ }
137
+ return i >= n.x0 && s <= n.x1 && o >= n.y0 && c <= n.y1 || (this.nVerts = e, this.nIdx = t, s <= n.x0 || i >= n.x1 || c <= n.y0 || o >= n.y1);
138
+ }
139
+ appendFan(e, t) {
140
+ if (t < 3) return;
141
+ this.reserve(t, (t - 2) * 3), t !== 4 && (this.pureRects = !1);
142
+ let n = this.nVerts;
143
+ this.verts.set(e.subarray(0, t * a), n * a);
144
+ let r = this.nIdx;
145
+ for (let e = 1; e < t - 1; e++) this.idx[r++] = n, this.idx[r++] = n + e, this.idx[r++] = n + e + 1;
146
+ this.nVerts += t, this.nIdx = r;
117
147
  }
118
148
  pushQuadIndices() {
119
149
  let e = this.nVerts, t = this.nIdx;
120
150
  this.idx[t++] = e, this.idx[t++] = e + 1, this.idx[t++] = e + 2, this.idx[t++] = e, this.idx[t++] = e + 2, this.idx[t++] = e + 3, this.nVerts += 4, this.nIdx += 6;
121
151
  }
122
152
  reserve(e, t) {
123
- let n = (this.nVerts + e) * i;
153
+ let n = (this.nVerts + e) * a;
124
154
  if (n > this.verts.length) {
125
- let e = new Float32Array(m(this.verts.length, n));
155
+ let e = new Float32Array(h(this.verts.length, n));
126
156
  e.set(this.verts), this.verts = e;
127
157
  }
128
158
  let r = this.nIdx + t;
129
159
  if (r > this.idx.length) {
130
- let e = new Uint32Array(m(this.idx.length, r));
160
+ let e = new Uint32Array(h(this.idx.length, r));
131
161
  e.set(this.idx), this.idx = e;
132
162
  }
133
163
  }
@@ -136,14 +166,14 @@ var g = class {
136
166
  this.nextInRing[e] = (n + 1) % t.length;
137
167
  let r = t[n];
138
168
  if (r) return r;
139
- let i = this.createSet(d[e].vertices, d[e].indices);
169
+ let i = this.createSet(f[e].vertices, f[e].indices);
140
170
  return t[n] = i, i;
141
171
  }
142
172
  nextLargeSlot() {
143
173
  let e = this.gl, t = this.nextLarge;
144
- this.nextLarge = (t + 1) % p;
174
+ this.nextLarge = (t + 1) % m;
145
175
  let n = this.largeRing[t];
146
- return n ? (this.nVerts > n.vertexCapacity && (n.vertexCapacity = m(n.vertexCapacity, this.nVerts), e.bindBuffer(e.ARRAY_BUFFER, n.vbo), e.bufferData(e.ARRAY_BUFFER, n.vertexCapacity * i * 4, e.DYNAMIC_DRAW)), this.nIdx > n.indexCapacity && (n.indexCapacity = m(n.indexCapacity, this.nIdx), e.bindVertexArray(n.vao), e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, n.ibo), e.bufferData(e.ELEMENT_ARRAY_BUFFER, n.indexCapacity * 4, e.DYNAMIC_DRAW), e.bindVertexArray(null), n.canonicalQuads = 0), n) : (n = this.createSet(m(f, this.nVerts), m(d[d.length - 1].indices, this.nIdx)), this.largeRing[t] = n, n);
176
+ return n ? (this.nVerts > n.vertexCapacity && (n.vertexCapacity = h(n.vertexCapacity, this.nVerts), e.bindBuffer(e.ARRAY_BUFFER, n.vbo), e.bufferData(e.ARRAY_BUFFER, n.vertexCapacity * a * 4, e.DYNAMIC_DRAW)), this.nIdx > n.indexCapacity && (n.indexCapacity = h(n.indexCapacity, this.nIdx), e.bindVertexArray(n.vao), e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, n.ibo), e.bufferData(e.ELEMENT_ARRAY_BUFFER, n.indexCapacity * 4, e.DYNAMIC_DRAW), e.bindVertexArray(null), n.canonicalQuads = 0), n) : (n = this.createSet(h(p, this.nVerts), h(f[f.length - 1].indices, this.nIdx)), this.largeRing[t] = n, n);
147
177
  }
148
178
  createSet(e, t) {
149
179
  let n = this.gl, r = n.createVertexArray(), i = n.createBuffer(), a = n.createBuffer();
@@ -163,6 +193,6 @@ var g = class {
163
193
  }
164
194
  };
165
195
  //#endregion
166
- export { d as BATCH_TIERS, g as DrawBatch, i as FLOATS_PER_VERTEX, r as MAX_VERTICES_PER_BATCH, p as SOLID_LARGE_RING_SIZE, u as SOLID_RING_SIZE, f as SOLID_RING_SLOT_VERTICES, s as WHITE_U, c as WHITE_V };
196
+ export { f as BATCH_TIERS, _ as DrawBatch, a as FLOATS_PER_VERTEX, i as MAX_VERTICES_PER_BATCH, m as SOLID_LARGE_RING_SIZE, d as SOLID_RING_SIZE, p as SOLID_RING_SLOT_VERTICES, c as WHITE_U, l as WHITE_V };
167
197
 
168
198
  //# sourceMappingURL=drawBatch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"drawBatch.js","names":["MAX_VERTICES_PER_BATCH","FLOATS_PER_VERTEX","INITIAL_VERTICES","INITIAL_INDICES","WHITE_U","WHITE_V","PLAIN_WHITE","packSlot","WHITE_SLOT","PAINT_MODE_PLAIN","SOLID_RING_SIZE","BATCH_TIERS","SOLID_RING_SLOT_VERTICES","SOLID_LARGE_RING_SIZE","doubledTo","capacity","from","need","quadIndices","out","quads","q","b","j","DrawBatch","aPos","prog","aColor","aUv","aPost","aSlot","gl","vertices","i","ma","m","mb","mc","md","mtx","mty","x1","x","w","y1","y","h","ax","ay","bx","by","cx","cy","dx","dy","P","r","g","a","slot","u0","v0","post","u1","v1","topShift","baselineY","y0","tanItalic","botShift","tx0","x0","tx1","bx0","bx1","mode","corner","at","uv","src","mesh","srcIdx","n","v","k","base","tier","t","set","ring","u","packed","needVerts","grown","needIdx","indices","existing","vao","vbo","ibo"],"sources":["../../src/renderer/drawBatch.ts"],"sourcesContent":["/**\n * Growable vertex staging for a run of solid-fill geometry, image quads and\n * glyphs.\n *\n * Geometry only: `draw.ts` owns when a run starts, what breaks it, and the\n * uniforms the flush draws under.\n *\n * **One batch for all three, because a page interleaves them.** A grid of\n * thumbnails is a ground rect under an atlas quad under a caption, per cell,\n * and while each kind staged separately every one had to drain the others\n * before it could stage — so a shape that batches perfectly in any one half\n * alone paid a flush per command. Everything a vertex needs to say which it is\n * fits in the same vertex: solids carry the UV of a 1x1 white texel and are\n * their own color, quads carry their atlas UV and a white color, glyphs carry\n * a font atlas UV, their text color, and a paint mode saying the texel is a\n * distance field rather than a color. Gradients join as a fourth off the ramp\n * atlas — a linear one without a mode of its own, since (ramp position, row) is\n * what the plain mode already samples, and a radial or conic one with a mode\n * that says its UV is a gradient-space coordinate to take a `length` or an\n * `atan` of. See `shaders/batchFill.ts`.\n *\n * Colors ride the vertices because shapes in a run differ in color and a merged\n * draw has one set of uniforms — and so does the model transform, applied here\n * rather than uploaded, so shapes under different transforms still share a\n * draw. The texture rides them too, as a slot index into the units the flush\n * binds: slot 0 is the white texel every solid samples, and `draw.ts` hands out\n * the rest per bitmap. A run holds as many bitmaps as there are slots, so an\n * atlas is what makes a wall of one sheet coalesce and a handful of loose\n * bitmaps no longer breaks a run per command.\n */\n\nimport type { Mesh } from '@weasel-js/geom/tessellate';\nimport type { GlMat3 } from './math/mat3';\nimport type { ShaderProgram } from './shaders/ShaderProgram';\nimport { PAINT_MODE_PLAIN, WHITE_SLOT, packSlot } from './shaders/batchFill';\n\n/** Vertices per flush. Caps staging memory; a longer run flushes in chunks,\n * which stays correct because painter's order survives a flush. */\nexport const MAX_VERTICES_PER_BATCH = 32768;\n\n/** vec2 a_position + vec4 a_vertexColor + vec2 a_uv + float a_post + float\n * a_texSlot. Exported so the buffer-replay tests read fields by name rather\n * than by a stride they repeat — a hard-coded one silently reads a different\n * field when the vertex grows, instead of failing. */\nexport const FLOATS_PER_VERTEX = 10;\n\n/** Float offset of `a_texSlot` within a vertex, which carries the paint mode\n * packed alongside the slot — `paintModeOf` unpacks it. */\nexport const TEX_SLOT_OFFSET = 9;\nconst INITIAL_VERTICES = 256;\nconst INITIAL_INDICES = 384;\n\n/** UV of the white texel bound at slot 0. Its texture is 1x1, so any\n * coordinate lands on it; the center is the one that stays on it under either\n * filter. */\nexport const WHITE_U = 0.5;\nexport const WHITE_V = 0.5;\n\n/**\n * A gradient vertex's `a_uv`, each channel affine in the coordinates the\n * geometry arrives in: `u = ux*x + uy*y + u0`, and the same for `v`.\n *\n * One shape for all three gradients. A linear one's `v` row is the constant\n * atlas row and its `u` row is the ramp position; a radial or conic one's two\n * rows are the gradient-space coordinate, and the row rides `a_post` instead.\n */\nexport interface GradientUV {\n ux: number; uy: number; u0: number;\n vx: number; vy: number; v0: number;\n}\n\n/** What `a_texSlot` carries on a vertex that is its own color: the white texel\n * at the plain paint mode. */\nconst PLAIN_WHITE = packSlot(WHITE_SLOT, PAINT_MODE_PLAIN);\n\n/**\n * Flushes between one ring slot's write and its next.\n *\n * The driver tracks a write hazard per buffer object, so rewriting one buffer\n * before every flush makes each write wait on the draw still reading it — that\n * wait, not the draw, is what a flush costs. Measured per draw on an M2 Max via\n * ANGLE (`tests/perf/image-quad.spec.ts`): 40–80 us rewriting one buffer, 0.34\n * us for a ring of 64, 0.03 us for a buffer nothing writes. Writing disjoint\n * *ranges* of one buffer does not help — the hazard is per object.\n */\nexport const SOLID_RING_SIZE = 64;\n\n/**\n * Slot sizes, smallest first. A flush takes the first it fits in.\n *\n * **Tiered because the hazard costs what the buffer is worth, not what the\n * write is.** A flush writing one quad into a slot the GPU is still reading\n * waits on the whole buffer object, and resolving that on a 36 KB buffer is far\n * dearer than on a 576-byte one. Measured on a frame of 20,000 quads that\n * nothing merged (`tests/perf/image-quad.spec.ts`): 92 ms with a 256-quad\n * smallest slot against 53 ms with a one-quad one, and a merged run unmoved\n * either way. The run that most wants a small buffer — one command, because the\n * next broke it — is exactly the run a batch is otherwise pure overhead for.\n */\nexport const BATCH_TIERS: readonly { vertices: number; indices: number }[] = [\n { vertices: 16, indices: 24 },\n { vertices: 1024, indices: 3072 },\n];\n\n/** What a standard slot holds, named for the tests and the flush-cost specs. */\nexport const SOLID_RING_SLOT_VERTICES = BATCH_TIERS[BATCH_TIERS.length - 1].vertices;\n\n/** Slots for flushes past the largest tier. These grow to fit, so they are few:\n * a run that big is a run few things broke, and there are not many of them in\n * a frame to cycle between. */\nexport const SOLID_LARGE_RING_SIZE = 4;\n\n/** One VAO and the two buffers its attribute and element bindings name. */\ninterface BufferSet {\n vao: WebGLVertexArrayObject;\n vbo: WebGLBuffer;\n ibo: WebGLBuffer;\n /** What the GPU buffers are currently sized for. */\n vertexCapacity: number;\n indexCapacity: number;\n /**\n * Quads for which this slot's element buffer holds the canonical pattern,\n * or 0 for contents that cannot be named that way (a mesh's rebased indices,\n * or a buffer just respecified).\n *\n * The pattern for N quads is a prefix of the pattern for any larger N, so a\n * slot written to its capacity serves every quad flush it ever takes and the\n * upload happens once per slot rather than once per flush — 0.89 us of the\n * 3.87 a flush costs (`tests/perf/flush-anatomy.spec.ts`). Rects and image\n * quads share it: both wind 0-1-2 / 0-2-3.\n */\n canonicalQuads: number;\n}\n\nfunction doubledTo(from: number, need: number): number {\n let capacity = from;\n while (capacity < need) capacity *= 2;\n return capacity;\n}\n\n/** The canonical quad index pattern for `quads` quads, as the push helpers\n * wind them: two triangles over corners 0-1-2 and 0-2-3. */\nfunction quadIndices(quads: number): Uint32Array {\n const out = new Uint32Array(quads * 6);\n for (let q = 0; q < quads; q++) {\n const b = q * 4;\n const j = q * 6;\n out[j] = b; out[j + 1] = b + 1; out[j + 2] = b + 2;\n out[j + 3] = b; out[j + 4] = b + 2; out[j + 5] = b + 3;\n }\n return out;\n}\n\nexport class DrawBatch {\n private readonly gl: WebGL2RenderingContext;\n private readonly aPos: number;\n private readonly aColor: number;\n private readonly aUv: number;\n private readonly aPost: number;\n private readonly aSlot: number;\n\n /** One ring per tier, cycled per flush. Slots are created on first use, so a\n * renderer that flushes rarely allocates as few as it flushes. */\n private readonly rings: (BufferSet | undefined)[][] =\n BATCH_TIERS.map(() => new Array(SOLID_RING_SIZE).fill(undefined));\n private readonly nextInRing: number[] = BATCH_TIERS.map(() => 0);\n /** The same, for flushes past the largest tier; these sets grow to fit. */\n private readonly largeRing: (BufferSet | undefined)[] =\n new Array(SOLID_LARGE_RING_SIZE).fill(undefined);\n private nextLarge = 0;\n\n private verts = new Float32Array(INITIAL_VERTICES * FLOATS_PER_VERTEX);\n private idx = new Uint32Array(INITIAL_INDICES);\n private nVerts = 0;\n private nIdx = 0;\n /** Whether the staged run is quads alone, so its indices are the canonical\n * pattern and a slot already holding that pattern needs no upload. */\n private pureRects = true;\n\n constructor(gl: WebGL2RenderingContext, prog: ShaderProgram) {\n const aPos = prog.attribute('a_position');\n const aColor = prog.attribute('a_vertexColor');\n const aUv = prog.attribute('a_uv');\n const aPost = prog.attribute('a_post');\n const aSlot = prog.attribute('a_texSlot');\n if (aPos === undefined || aColor === undefined || aUv === undefined\n || aPost === undefined || aSlot === undefined) {\n throw new Error(\n 'DrawBatch: batch program is missing a_position / a_vertexColor / a_uv / '\n + 'a_post / a_texSlot',\n );\n }\n this.gl = gl;\n this.aPos = aPos;\n this.aColor = aColor;\n this.aUv = aUv;\n this.aPost = aPost;\n this.aSlot = aSlot;\n }\n\n get length(): number {\n return this.nIdx;\n }\n\n /** Whether staging `vertices` more would put the run past the per-flush cap. */\n wouldOverflow(vertices: number): boolean {\n return this.nVerts + vertices > MAX_VERTICES_PER_BATCH;\n }\n\n /**\n * Append one rect's four corners through `m`, all carrying `rgba` (straight\n * alpha). An affine maps a rect to a parallelogram, so two triangles still\n * cover it and the batch draws at `u_model` identity.\n */\n pushRect(\n x: number, y: number, w: number, h: number, m: GlMat3,\n r: number, g: number, b: number, a: number,\n ): void {\n this.reserve(4, 6);\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n const x1 = x + w;\n const y1 = y + h;\n const ax = ma * x + mc * y + mtx, ay = mb * x + md * y + mty;\n const bx = ma * x1 + mc * y + mtx, by = mb * x1 + md * y + mty;\n const cx = ma * x1 + mc * y1 + mtx, cy = mb * x1 + md * y1 + mty;\n const dx = ma * x + mc * y1 + mtx, dy = mb * x + md * y1 + mty;\n const P = PLAIN_WHITE;\n i = this.writeVertex(i, ax, ay, r, g, b, a, WHITE_U, WHITE_V, 1, P);\n i = this.writeVertex(i, bx, by, r, g, b, a, WHITE_U, WHITE_V, 1, P);\n i = this.writeVertex(i, cx, cy, r, g, b, a, WHITE_U, WHITE_V, 1, P);\n this.writeVertex(i, dx, dy, r, g, b, a, WHITE_U, WHITE_V, 1, P);\n this.pushQuadIndices();\n }\n\n /**\n * Append one image quad: the destination rect `(x, y, w, h)` mapped through\n * `m`, sampling `(u0, v0)`-`(u1, v1)`, every corner carrying `post` as the\n * after-the-color-matrix alpha factor.\n *\n * Corners wind top-left, top-right, bottom-right, bottom-left with UVs\n * following — the same winding `pushRect` uses, which is what lets a run of\n * mixed rects and quads keep the canonical index pattern. The flips a command\n * asks for are already in the `u` / `v` the caller passes.\n *\n * `slot` is the texture unit the corners sample, which `draw.ts` assigns per\n * bitmap within the run.\n */\n pushQuad(\n x: number, y: number, w: number, h: number, m: GlMat3,\n u0: number, v0: number, u1: number, v1: number,\n post: number, slot: number,\n ): void {\n this.reserve(4, 6);\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n const x1 = x + w;\n const y1 = y + h;\n const ax = ma * x + mc * y + mtx, ay = mb * x + md * y + mty;\n const bx = ma * x1 + mc * y + mtx, by = mb * x1 + md * y + mty;\n const cx = ma * x1 + mc * y1 + mtx, cy = mb * x1 + md * y1 + mty;\n const dx = ma * x + mc * y1 + mtx, dy = mb * x + md * y1 + mty;\n const P = packSlot(slot, PAINT_MODE_PLAIN);\n i = this.writeVertex(i, ax, ay, 1, 1, 1, 1, u0, v0, post, P);\n i = this.writeVertex(i, bx, by, 1, 1, 1, 1, u1, v0, post, P);\n i = this.writeVertex(i, cx, cy, 1, 1, 1, 1, u1, v1, post, P);\n this.writeVertex(i, dx, dy, 1, 1, 1, 1, u0, v1, post, P);\n this.pushQuadIndices();\n }\n\n /**\n * Append one glyph quad: the box `(x0, y0)`-`(x1, y1)` mapped through `m`,\n * sampling `(u0, v0)`-`(u1, v1)` of the font atlas at `slot`, painted in\n * `rgba`.\n *\n * `mode` says which channels of that atlas carry the distance field, and\n * rides the vertices packed into the slot — so a run off a baked MSDF atlas\n * and one off the runtime canvas bake still share a draw. The synthetic-bold\n * threshold does not: it is `u_synthBold`, and `draw.ts` breaks the run when\n * it changes.\n *\n * **A synthetic oblique is sheared here rather than in the shader.** The old\n * text program carried the baseline per vertex and skewed against\n * `u_synthItalic`; the batch places its own corners, so the shear is one\n * multiply while they are being placed, and `tanItalic` is 0 for an upright\n * face. It has to happen before `m`, which is where the shader had it too.\n *\n * Corners wind top-left, top-right, bottom-right, bottom-left — `pushQuad`'s\n * winding, which is what lets a run of mixed rects, image quads and glyphs\n * keep the canonical index pattern.\n */\n pushGlyph(\n x0: number, y0: number, x1: number, y1: number,\n baselineY: number, tanItalic: number, m: GlMat3,\n u0: number, v0: number, u1: number, v1: number,\n r: number, g: number, b: number, a: number,\n slot: number, mode: number,\n ): void {\n this.reserve(4, 6);\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n // Above-baseline corners (lower y in screen coords) lean further right.\n const topShift = (baselineY - y0) * tanItalic;\n const botShift = (baselineY - y1) * tanItalic;\n const tx0 = x0 + topShift, tx1 = x1 + topShift;\n const bx0 = x0 + botShift, bx1 = x1 + botShift;\n const ax = ma * tx0 + mc * y0 + mtx, ay = mb * tx0 + md * y0 + mty;\n const bx = ma * tx1 + mc * y0 + mtx, by = mb * tx1 + md * y0 + mty;\n const cx = ma * bx1 + mc * y1 + mtx, cy = mb * bx1 + md * y1 + mty;\n const dx = ma * bx0 + mc * y1 + mtx, dy = mb * bx0 + md * y1 + mty;\n const P = packSlot(slot, mode);\n i = this.writeVertex(i, ax, ay, r, g, b, a, u0, v0, 1, P);\n i = this.writeVertex(i, bx, by, r, g, b, a, u1, v0, 1, P);\n i = this.writeVertex(i, cx, cy, r, g, b, a, u1, v1, 1, P);\n this.writeVertex(i, dx, dy, r, g, b, a, u0, v1, 1, P);\n this.pushQuadIndices();\n }\n\n /**\n * Append one rect filled by a gradient: the corners of `(x, y, w, h)` mapped\n * through `m`, sampling the ramp atlas at `slot`.\n *\n * `uv` gives each channel of `a_uv` as an affine function of the coordinates\n * the corners arrive in, which is what lets a gradient ride the vertices at\n * all: a linear one's ramp position is affine in position outright, and a\n * radial or conic one's gradient-space *coordinate* is, even though the ramp\n * position it yields is not. Either way the rasterizer's interpolation across\n * a triangle is exact. `mode` says which of the two the fragment shader is\n * looking at, and `post` carries the atlas row for the modes that read it —\n * see `shaders/batchFill.ts`.\n *\n * Values outside 0..1 are the sampler's business; the atlas clamps to the\n * edge texel, which is what the gradient shader's own `clamp` did.\n */\n pushGradientRect(\n x: number, y: number, w: number, h: number, m: GlMat3,\n uv: GradientUV, post: number, slot: number, mode: number,\n r: number, g: number, b: number, a: number,\n ): void {\n this.reserve(4, 6);\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n const x1 = x + w;\n const y1 = y + h;\n const P = packSlot(slot, mode);\n const corner = (cx: number, cy: number): number => {\n const at = i;\n i = this.writeVertex(\n at, ma * cx + mc * cy + mtx, mb * cx + md * cy + mty,\n r, g, b, a,\n uv.ux * cx + uv.uy * cy + uv.u0,\n uv.vx * cx + uv.vy * cy + uv.v0,\n post, P,\n );\n return i;\n };\n corner(x, y);\n corner(x1, y);\n corner(x1, y1);\n corner(x, y1);\n this.pushQuadIndices();\n }\n\n /** `pushMesh` for a mesh filled by a gradient — see `pushGradientRect` for\n * what `uv`, `post` and `mode` carry. */\n pushGradientMesh(\n mesh: Mesh, m: GlMat3,\n uv: GradientUV, post: number, slot: number, mode: number,\n r: number, g: number, b: number, a: number,\n ): void {\n const src = mesh.vertices;\n const srcIdx = mesh.indices;\n const n = src.length >> 1;\n this.reserve(n, srcIdx.length);\n this.pureRects = false;\n const v = this.verts;\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n const P = packSlot(slot, mode);\n for (let k = 0; k < n; k++) {\n const x = src[k * 2];\n const y = src[k * 2 + 1];\n v[i++] = ma * x + mc * y + mtx;\n v[i++] = mb * x + md * y + mty;\n v[i++] = r; v[i++] = g; v[i++] = b; v[i++] = a;\n v[i++] = uv.ux * x + uv.uy * y + uv.u0;\n v[i++] = uv.vx * x + uv.vy * y + uv.v0;\n v[i++] = post; v[i++] = P;\n }\n const base = this.nVerts;\n const out = this.idx;\n let j = this.nIdx;\n for (let k = 0; k < srcIdx.length; k++) out[j++] = base + srcIdx[k];\n this.nVerts += n;\n this.nIdx += srcIdx.length;\n }\n\n /**\n * Append a tessellated mesh through `m`, all vertices carrying `rgba`. The\n * mesh's own indices are rebased onto the staged vertices, which is why the\n * index buffer is uploaded per flush rather than written once.\n */\n pushMesh(\n mesh: Mesh, m: GlMat3,\n r: number, g: number, b: number, a: number,\n ): void {\n const src = mesh.vertices;\n const srcIdx = mesh.indices;\n const n = src.length >> 1;\n this.reserve(n, srcIdx.length);\n this.pureRects = false;\n const v = this.verts;\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n for (let k = 0; k < n; k++) {\n const x = src[k * 2];\n const y = src[k * 2 + 1];\n v[i++] = ma * x + mc * y + mtx;\n v[i++] = mb * x + md * y + mty;\n v[i++] = r; v[i++] = g; v[i++] = b; v[i++] = a;\n v[i++] = WHITE_U; v[i++] = WHITE_V; v[i++] = 1; v[i++] = PLAIN_WHITE;\n }\n const base = this.nVerts;\n const out = this.idx;\n let j = this.nIdx;\n for (let k = 0; k < srcIdx.length; k++) out[j++] = base + srcIdx[k];\n this.nVerts += n;\n this.nIdx += srcIdx.length;\n }\n\n /** Upload the staged geometry into the next set of buffers and bind its VAO.\n * Returns the index count for the caller's `drawElements`. */\n uploadAndBind(): number {\n const gl = this.gl;\n const tier = BATCH_TIERS.findIndex(\n (t) => this.nVerts <= t.vertices && this.nIdx <= t.indices,\n );\n const set = tier < 0 ? this.nextLargeSlot() : this.nextRingSlot(tier);\n gl.bindVertexArray(set.vao);\n gl.bindBuffer(gl.ARRAY_BUFFER, set.vbo);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verts, 0, this.nVerts * FLOATS_PER_VERTEX);\n if (!this.pureRects) {\n gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, this.idx, 0, this.nIdx);\n set.canonicalQuads = 0;\n } else if (set.canonicalQuads < this.nVerts >> 2) {\n // Written to the slot's capacity, not this flush's length: the extra\n // costs one upload the slot never repeats.\n const quads = Math.min(set.vertexCapacity >> 2, Math.floor(set.indexCapacity / 6));\n gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, quadIndices(quads));\n set.canonicalQuads = quads;\n }\n return this.nIdx;\n }\n\n reset(): void {\n this.nVerts = 0;\n this.nIdx = 0;\n this.pureRects = true;\n }\n\n dispose(): void {\n for (const set of [...this.rings.flat(), ...this.largeRing]) {\n if (set) this.deleteSet(set);\n }\n for (const ring of this.rings) ring.fill(undefined);\n this.largeRing.fill(undefined);\n }\n\n private writeVertex(\n i: number,\n x: number, y: number,\n r: number, g: number, b: number, a: number,\n u: number, v: number, post: number, packed: number,\n ): number {\n const out = this.verts;\n out[i] = x; out[i + 1] = y;\n out[i + 2] = r; out[i + 3] = g; out[i + 4] = b; out[i + 5] = a;\n out[i + 6] = u; out[i + 7] = v; out[i + 8] = post; out[i + 9] = packed;\n return i + FLOATS_PER_VERTEX;\n }\n\n /** Two triangles over the four corners just written. */\n private pushQuadIndices(): void {\n const base = this.nVerts;\n let j = this.nIdx;\n this.idx[j++] = base; this.idx[j++] = base + 1; this.idx[j++] = base + 2;\n this.idx[j++] = base; this.idx[j++] = base + 2; this.idx[j++] = base + 3;\n this.nVerts += 4;\n this.nIdx += 6;\n }\n\n /** Grow the CPU arrays so `vertices` / `indices` more fit. */\n private reserve(vertices: number, indices: number): void {\n const needVerts = (this.nVerts + vertices) * FLOATS_PER_VERTEX;\n if (needVerts > this.verts.length) {\n const grown = new Float32Array(doubledTo(this.verts.length, needVerts));\n grown.set(this.verts);\n this.verts = grown;\n }\n const needIdx = this.nIdx + indices;\n if (needIdx > this.idx.length) {\n const grown = new Uint32Array(doubledTo(this.idx.length, needIdx));\n grown.set(this.idx);\n this.idx = grown;\n }\n }\n\n private nextRingSlot(tier: number): BufferSet {\n const ring = this.rings[tier];\n const slot = this.nextInRing[tier];\n this.nextInRing[tier] = (slot + 1) % ring.length;\n const existing = ring[slot];\n if (existing) return existing;\n const set = this.createSet(BATCH_TIERS[tier].vertices, BATCH_TIERS[tier].indices);\n ring[slot] = set;\n return set;\n }\n\n private nextLargeSlot(): BufferSet {\n const gl = this.gl;\n const slot = this.nextLarge;\n this.nextLarge = (slot + 1) % SOLID_LARGE_RING_SIZE;\n let set = this.largeRing[slot];\n if (!set) {\n set = this.createSet(\n doubledTo(SOLID_RING_SLOT_VERTICES, this.nVerts),\n doubledTo(BATCH_TIERS[BATCH_TIERS.length - 1].indices, this.nIdx),\n );\n this.largeRing[slot] = set;\n return set;\n }\n if (this.nVerts > set.vertexCapacity) {\n set.vertexCapacity = doubledTo(set.vertexCapacity, this.nVerts);\n gl.bindBuffer(gl.ARRAY_BUFFER, set.vbo);\n gl.bufferData(gl.ARRAY_BUFFER, set.vertexCapacity * FLOATS_PER_VERTEX * 4, gl.DYNAMIC_DRAW);\n }\n if (this.nIdx > set.indexCapacity) {\n set.indexCapacity = doubledTo(set.indexCapacity, this.nIdx);\n gl.bindVertexArray(set.vao);\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, set.ibo);\n gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, set.indexCapacity * 4, gl.DYNAMIC_DRAW);\n gl.bindVertexArray(null);\n // Respecified, so whatever pattern it held is gone.\n set.canonicalQuads = 0;\n }\n return set;\n }\n\n private createSet(vertices: number, indices: number): BufferSet {\n const gl = this.gl;\n const vao = gl.createVertexArray();\n const vbo = gl.createBuffer();\n const ibo = gl.createBuffer();\n if (!vao || !vbo || !ibo) throw new Error('DrawBatch: failed to create GL objects');\n const stride = FLOATS_PER_VERTEX * 4;\n gl.bindVertexArray(vao);\n gl.bindBuffer(gl.ARRAY_BUFFER, vbo);\n gl.bufferData(gl.ARRAY_BUFFER, vertices * stride, gl.DYNAMIC_DRAW);\n gl.enableVertexAttribArray(this.aPos);\n gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, stride, 0);\n gl.enableVertexAttribArray(this.aColor);\n gl.vertexAttribPointer(this.aColor, 4, gl.FLOAT, false, stride, 8);\n gl.enableVertexAttribArray(this.aUv);\n gl.vertexAttribPointer(this.aUv, 2, gl.FLOAT, false, stride, 24);\n gl.enableVertexAttribArray(this.aPost);\n gl.vertexAttribPointer(this.aPost, 1, gl.FLOAT, false, stride, 32);\n gl.enableVertexAttribArray(this.aSlot);\n gl.vertexAttribPointer(this.aSlot, 1, gl.FLOAT, false, stride, 36);\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);\n gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices * 4, gl.DYNAMIC_DRAW);\n gl.bindVertexArray(null);\n return { vao, vbo, ibo, vertexCapacity: vertices, indexCapacity: indices, canonicalQuads: 0 };\n }\n\n private deleteSet(set: BufferSet): void {\n const gl = this.gl;\n gl.deleteBuffer(set.vbo);\n gl.deleteBuffer(set.ibo);\n gl.deleteVertexArray(set.vao);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"drawBatch.js","names":["MAX_VERTICES_PER_BATCH","FLOATS_PER_VERTEX","INITIAL_VERTICES","INITIAL_INDICES","WHITE_U","WHITE_V","PLAIN_WHITE","packSlot","WHITE_SLOT","PAINT_MODE_PLAIN","SOLID_RING_SIZE","BATCH_TIERS","SOLID_RING_SLOT_VERTICES","SOLID_LARGE_RING_SIZE","doubledTo","capacity","from","need","quadIndices","out","quads","q","b","j","DrawBatch","aPos","prog","aColor","aUv","aPost","aSlot","gl","vertices","i","ma","m","mb","mc","md","mtx","mty","x1","x","w","y1","y","h","ax","ay","bx","by","cx","cy","dx","dy","P","r","g","a","slot","u0","v0","post","u1","v1","topShift","baselineY","y0","tanItalic","botShift","tx0","x0","tx1","bx0","bx1","mode","corner","at","uv","src","mesh","srcIdx","n","v","k","base","tier","t","set","ring","u","packed","F","minX","maxX","minY","maxY","rect","i0","clipConvexPolygon","poly","needVerts","grown","needIdx","indices","existing","vao","vbo","ibo"],"sources":["../../src/renderer/drawBatch.ts"],"sourcesContent":["/**\n * Growable vertex staging for a run of solid-fill geometry, image quads and\n * glyphs.\n *\n * Geometry only: `draw.ts` owns when a run starts, what breaks it, and the\n * uniforms the flush draws under.\n *\n * **One batch for all three, because a page interleaves them.** A grid of\n * thumbnails is a ground rect under an atlas quad under a caption, per cell,\n * and while each kind staged separately every one had to drain the others\n * before it could stage — so a shape that batches perfectly in any one half\n * alone paid a flush per command. Everything a vertex needs to say which it is\n * fits in the same vertex: solids carry the UV of a 1x1 white texel and are\n * their own color, quads carry their atlas UV and a white color, glyphs carry\n * a font atlas UV, their text color, and a paint mode saying the texel is a\n * distance field rather than a color. Gradients join as a fourth off the ramp\n * atlas — a linear one without a mode of its own, since (ramp position, row) is\n * what the plain mode already samples, and a radial or conic one with a mode\n * that says its UV is a gradient-space coordinate to take a `length` or an\n * `atan` of. See `shaders/batchFill.ts`.\n *\n * Colors ride the vertices because shapes in a run differ in color and a merged\n * draw has one set of uniforms — and so does the model transform, applied here\n * rather than uploaded, so shapes under different transforms still share a\n * draw. The texture rides them too, as a slot index into the units the flush\n * binds: slot 0 is the white texel every solid samples, and `draw.ts` hands out\n * the rest per bitmap. A run holds as many bitmaps as there are slots, so an\n * atlas is what makes a wall of one sheet coalesce and a handful of loose\n * bitmaps no longer breaks a run per command.\n */\n\nimport type { Mesh } from '@weasel-js/geom/tessellate';\nimport type { GlMat3 } from './math/mat3';\nimport type { ShaderProgram } from './shaders/ShaderProgram';\nimport { PAINT_MODE_PLAIN, WHITE_SLOT, packSlot } from './shaders/batchFill';\nimport { clipConvexPolygon, type ClipRect } from './batchClip';\n\n/** Vertices per flush. Caps staging memory; a longer run flushes in chunks,\n * which stays correct because painter's order survives a flush. */\nexport const MAX_VERTICES_PER_BATCH = 32768;\n\n/** vec2 a_position + vec4 a_vertexColor + vec2 a_uv + float a_post + float\n * a_texSlot. Exported so the buffer-replay tests read fields by name rather\n * than by a stride they repeat — a hard-coded one silently reads a different\n * field when the vertex grows, instead of failing. */\nexport const FLOATS_PER_VERTEX = 10;\n\n/** Float offset of `a_texSlot` within a vertex, which carries the paint mode\n * packed alongside the slot — `paintModeOf` unpacks it. */\nexport const TEX_SLOT_OFFSET = 9;\nconst INITIAL_VERTICES = 256;\nconst INITIAL_INDICES = 384;\n\n/** UV of the white texel bound at slot 0. Its texture is 1x1, so any\n * coordinate lands on it; the center is the one that stays on it under either\n * filter. */\nexport const WHITE_U = 0.5;\nexport const WHITE_V = 0.5;\n\n/**\n * A gradient vertex's `a_uv`, each channel affine in the coordinates the\n * geometry arrives in: `u = ux*x + uy*y + u0`, and the same for `v`.\n *\n * One shape for all three gradients. A linear one's `v` row is the constant\n * atlas row and its `u` row is the ramp position; a radial or conic one's two\n * rows are the gradient-space coordinate, and the row rides `a_post` instead.\n */\nexport interface GradientUV {\n ux: number; uy: number; u0: number;\n vx: number; vy: number; v0: number;\n}\n\n/** What `a_texSlot` carries on a vertex that is its own color: the white texel\n * at the plain paint mode. */\nconst PLAIN_WHITE = packSlot(WHITE_SLOT, PAINT_MODE_PLAIN);\n\n/**\n * Flushes between one ring slot's write and its next.\n *\n * Sized for a driver that tracks a write hazard per buffer object, where\n * rewriting one buffer before every flush makes each write wait on the draw\n * still reading it. That was measured on an M2 Max by timing blocks of frames,\n * a method since retired (`tests/perf/README.md`, \"Timing a frame\"), and on\n * teitou (M5 Max, ANGLE Metal, 2026-10-05) one frame a task does not show it:\n * `tests/perf/image-quad.spec.ts` puts a quad through a ring of 64 at 3.3–5.2 us\n * over two sittings, through one buffer rewritten each time at 2.5–3.0, and\n * through a buffer nothing writes at 0.16–0.22.\n */\nexport const SOLID_RING_SIZE = 64;\n\n/**\n * Slot sizes, smallest first. A flush takes the first it fits in.\n *\n * **Tiered because a write into a big slot costs more than the same write into\n * a small one.** On teitou (M5 Max, ANGLE Metal, 2026-10-05), a frame of 20,000\n * quads each drawn alone (`tests/perf/image-quad.spec.ts`, `renderer/unmerged`)\n * costs 19.2–19.9 us a quad with these tiers against 21.0 with 1,024 vertices\n * as the smallest slot, and a merged run is unmoved either way. The 92 ms\n * against 53 recorded before came from timing blocks of frames, a method since\n * retired. The run that most wants a small buffer — one command, because the\n * next broke it — is exactly the run a batch is otherwise pure overhead for.\n */\nexport const BATCH_TIERS: readonly { vertices: number; indices: number }[] = [\n { vertices: 16, indices: 24 },\n { vertices: 1024, indices: 3072 },\n];\n\n/** What a standard slot holds, named for the tests and the flush-cost specs. */\nexport const SOLID_RING_SLOT_VERTICES = BATCH_TIERS[BATCH_TIERS.length - 1].vertices;\n\n/** Slots for flushes past the largest tier. These grow to fit, so they are few:\n * a run that big is a run few things broke, and there are not many of them in\n * a frame to cycle between. */\nexport const SOLID_LARGE_RING_SIZE = 4;\n\n/** One VAO and the two buffers its attribute and element bindings name. */\ninterface BufferSet {\n vao: WebGLVertexArrayObject;\n vbo: WebGLBuffer;\n ibo: WebGLBuffer;\n /** What the GPU buffers are currently sized for. */\n vertexCapacity: number;\n indexCapacity: number;\n /**\n * Quads for which this slot's element buffer holds the canonical pattern,\n * or 0 for contents that cannot be named that way (a mesh's rebased indices,\n * or a buffer just respecified).\n *\n * The pattern for N quads is a prefix of the pattern for any larger N, so a\n * slot written to its capacity serves every quad flush it ever takes and the\n * upload happens once per slot rather than once per flush. Rects and image\n * quads share it: both wind 0-1-2 / 0-2-3.\n */\n canonicalQuads: number;\n}\n\nfunction doubledTo(from: number, need: number): number {\n let capacity = from;\n while (capacity < need) capacity *= 2;\n return capacity;\n}\n\n/** The canonical quad index pattern for `quads` quads, as the push helpers\n * wind them: two triangles over corners 0-1-2 and 0-2-3. */\nfunction quadIndices(quads: number): Uint32Array {\n const out = new Uint32Array(quads * 6);\n for (let q = 0; q < quads; q++) {\n const b = q * 4;\n const j = q * 6;\n out[j] = b; out[j + 1] = b + 1; out[j + 2] = b + 2;\n out[j + 3] = b; out[j + 4] = b + 2; out[j + 5] = b + 3;\n }\n return out;\n}\n\nexport class DrawBatch {\n private readonly gl: WebGL2RenderingContext;\n private readonly aPos: number;\n private readonly aColor: number;\n private readonly aUv: number;\n private readonly aPost: number;\n private readonly aSlot: number;\n\n /** One ring per tier, cycled per flush. Slots are created on first use, so a\n * renderer that flushes rarely allocates as few as it flushes. */\n private readonly rings: (BufferSet | undefined)[][] =\n BATCH_TIERS.map(() => new Array(SOLID_RING_SIZE).fill(undefined));\n private readonly nextInRing: number[] = BATCH_TIERS.map(() => 0);\n /** The same, for flushes past the largest tier; these sets grow to fit. */\n private readonly largeRing: (BufferSet | undefined)[] =\n new Array(SOLID_LARGE_RING_SIZE).fill(undefined);\n private nextLarge = 0;\n\n private verts = new Float32Array(INITIAL_VERTICES * FLOATS_PER_VERTEX);\n private idx = new Uint32Array(INITIAL_INDICES);\n private nVerts = 0;\n private nIdx = 0;\n /** Whether the staged run is quads alone, so its indices are the canonical\n * pattern and a slot already holding that pattern needs no upload. */\n private pureRects = true;\n\n /** The rect every push is cut to, or `null`. `draw.ts` sets it as rect\n * clips enter and leave — see `batchClip.ts`. A push returns `false`, having\n * staged nothing, for geometry it cannot cut exactly; see `clipQuad`. */\n clip: ClipRect | null = null;\n /** Polygon scratch for `clip`: a quad cut by four edges has at most eight\n * corners. */\n private readonly polyIn = new Float32Array(8 * FLOATS_PER_VERTEX);\n private readonly polyOut = new Float32Array(8 * FLOATS_PER_VERTEX);\n private readonly polyTmp = new Float32Array(8 * FLOATS_PER_VERTEX);\n\n constructor(gl: WebGL2RenderingContext, prog: ShaderProgram) {\n const aPos = prog.attribute('a_position');\n const aColor = prog.attribute('a_vertexColor');\n const aUv = prog.attribute('a_uv');\n const aPost = prog.attribute('a_post');\n const aSlot = prog.attribute('a_texSlot');\n if (aPos === undefined || aColor === undefined || aUv === undefined\n || aPost === undefined || aSlot === undefined) {\n throw new Error(\n 'DrawBatch: batch program is missing a_position / a_vertexColor / a_uv / '\n + 'a_post / a_texSlot',\n );\n }\n this.gl = gl;\n this.aPos = aPos;\n this.aColor = aColor;\n this.aUv = aUv;\n this.aPost = aPost;\n this.aSlot = aSlot;\n }\n\n get length(): number {\n return this.nIdx;\n }\n\n /** Whether staging `vertices` more would put the run past the per-flush cap. */\n wouldOverflow(vertices: number): boolean {\n return this.nVerts + vertices > MAX_VERTICES_PER_BATCH;\n }\n\n /**\n * Append one rect's four corners through `m`, all carrying `rgba` (straight\n * alpha). An affine maps a rect to a parallelogram, so two triangles still\n * cover it and the batch draws at `u_model` identity.\n */\n pushRect(\n x: number, y: number, w: number, h: number, m: GlMat3,\n r: number, g: number, b: number, a: number,\n ): boolean {\n this.reserve(4, 6);\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n const x1 = x + w;\n const y1 = y + h;\n const ax = ma * x + mc * y + mtx, ay = mb * x + md * y + mty;\n const bx = ma * x1 + mc * y + mtx, by = mb * x1 + md * y + mty;\n const cx = ma * x1 + mc * y1 + mtx, cy = mb * x1 + md * y1 + mty;\n const dx = ma * x + mc * y1 + mtx, dy = mb * x + md * y1 + mty;\n const P = PLAIN_WHITE;\n i = this.writeVertex(i, ax, ay, r, g, b, a, WHITE_U, WHITE_V, 1, P);\n i = this.writeVertex(i, bx, by, r, g, b, a, WHITE_U, WHITE_V, 1, P);\n i = this.writeVertex(i, cx, cy, r, g, b, a, WHITE_U, WHITE_V, 1, P);\n this.writeVertex(i, dx, dy, r, g, b, a, WHITE_U, WHITE_V, 1, P);\n this.pushQuadIndices();\n return this.clip === null || this.clipQuad(this.nVerts - 4, this.nIdx - 6, this.clip);\n }\n\n /**\n * Append one image quad: the destination rect `(x, y, w, h)` mapped through\n * `m`, sampling `(u0, v0)`-`(u1, v1)`, every corner carrying `post` as the\n * after-the-color-matrix alpha factor.\n *\n * Corners wind top-left, top-right, bottom-right, bottom-left with UVs\n * following — the same winding `pushRect` uses, which is what lets a run of\n * mixed rects and quads keep the canonical index pattern. The flips a command\n * asks for are already in the `u` / `v` the caller passes.\n *\n * `slot` is the texture unit the corners sample, which `draw.ts` assigns per\n * bitmap within the run.\n */\n pushQuad(\n x: number, y: number, w: number, h: number, m: GlMat3,\n u0: number, v0: number, u1: number, v1: number,\n post: number, slot: number,\n ): boolean {\n this.reserve(4, 6);\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n const x1 = x + w;\n const y1 = y + h;\n const ax = ma * x + mc * y + mtx, ay = mb * x + md * y + mty;\n const bx = ma * x1 + mc * y + mtx, by = mb * x1 + md * y + mty;\n const cx = ma * x1 + mc * y1 + mtx, cy = mb * x1 + md * y1 + mty;\n const dx = ma * x + mc * y1 + mtx, dy = mb * x + md * y1 + mty;\n const P = packSlot(slot, PAINT_MODE_PLAIN);\n i = this.writeVertex(i, ax, ay, 1, 1, 1, 1, u0, v0, post, P);\n i = this.writeVertex(i, bx, by, 1, 1, 1, 1, u1, v0, post, P);\n i = this.writeVertex(i, cx, cy, 1, 1, 1, 1, u1, v1, post, P);\n this.writeVertex(i, dx, dy, 1, 1, 1, 1, u0, v1, post, P);\n this.pushQuadIndices();\n return this.clip === null || this.clipQuad(this.nVerts - 4, this.nIdx - 6, this.clip);\n }\n\n /**\n * Append one glyph quad: the box `(x0, y0)`-`(x1, y1)` mapped through `m`,\n * sampling `(u0, v0)`-`(u1, v1)` of the font atlas at `slot`, painted in\n * `rgba`.\n *\n * `mode` says which channels of that atlas carry the distance field, and\n * rides the vertices packed into the slot — so a run off a baked MSDF atlas\n * and one off the runtime canvas bake still share a draw. The synthetic-bold\n * threshold does not: it is `u_synthBold`, and `draw.ts` breaks the run when\n * it changes.\n *\n * **A synthetic oblique is sheared here rather than in the shader.** The old\n * text program carried the baseline per vertex and skewed against\n * `u_synthItalic`; the batch places its own corners, so the shear is one\n * multiply while they are being placed, and `tanItalic` is 0 for an upright\n * face. It has to happen before `m`, which is where the shader had it too.\n *\n * Corners wind top-left, top-right, bottom-right, bottom-left — `pushQuad`'s\n * winding, which is what lets a run of mixed rects, image quads and glyphs\n * keep the canonical index pattern.\n */\n pushGlyph(\n x0: number, y0: number, x1: number, y1: number,\n baselineY: number, tanItalic: number, m: GlMat3,\n u0: number, v0: number, u1: number, v1: number,\n r: number, g: number, b: number, a: number,\n slot: number, mode: number,\n ): boolean {\n this.reserve(4, 6);\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n // Above-baseline corners (lower y in screen coords) lean further right.\n const topShift = (baselineY - y0) * tanItalic;\n const botShift = (baselineY - y1) * tanItalic;\n const tx0 = x0 + topShift, tx1 = x1 + topShift;\n const bx0 = x0 + botShift, bx1 = x1 + botShift;\n const ax = ma * tx0 + mc * y0 + mtx, ay = mb * tx0 + md * y0 + mty;\n const bx = ma * tx1 + mc * y0 + mtx, by = mb * tx1 + md * y0 + mty;\n const cx = ma * bx1 + mc * y1 + mtx, cy = mb * bx1 + md * y1 + mty;\n const dx = ma * bx0 + mc * y1 + mtx, dy = mb * bx0 + md * y1 + mty;\n const P = packSlot(slot, mode);\n i = this.writeVertex(i, ax, ay, r, g, b, a, u0, v0, 1, P);\n i = this.writeVertex(i, bx, by, r, g, b, a, u1, v0, 1, P);\n i = this.writeVertex(i, cx, cy, r, g, b, a, u1, v1, 1, P);\n this.writeVertex(i, dx, dy, r, g, b, a, u0, v1, 1, P);\n this.pushQuadIndices();\n return this.clip === null || this.clipQuad(this.nVerts - 4, this.nIdx - 6, this.clip);\n }\n\n /**\n * Append one rect filled by a gradient: the corners of `(x, y, w, h)` mapped\n * through `m`, sampling the ramp atlas at `slot`.\n *\n * `uv` gives each channel of `a_uv` as an affine function of the coordinates\n * the corners arrive in, which is what lets a gradient ride the vertices at\n * all: a linear one's ramp position is affine in position outright, and a\n * radial or conic one's gradient-space *coordinate* is, even though the ramp\n * position it yields is not. Either way the rasterizer's interpolation across\n * a triangle is exact. `mode` says which of the two the fragment shader is\n * looking at, and `post` carries the atlas row for the modes that read it —\n * see `shaders/batchFill.ts`.\n *\n * Values outside 0..1 are the sampler's business; the atlas clamps to the\n * edge texel, which is what the gradient shader's own `clamp` did.\n */\n pushGradientRect(\n x: number, y: number, w: number, h: number, m: GlMat3,\n uv: GradientUV, post: number, slot: number, mode: number,\n r: number, g: number, b: number, a: number,\n ): boolean {\n this.reserve(4, 6);\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n const x1 = x + w;\n const y1 = y + h;\n const P = packSlot(slot, mode);\n const corner = (cx: number, cy: number): number => {\n const at = i;\n i = this.writeVertex(\n at, ma * cx + mc * cy + mtx, mb * cx + md * cy + mty,\n r, g, b, a,\n uv.ux * cx + uv.uy * cy + uv.u0,\n uv.vx * cx + uv.vy * cy + uv.v0,\n post, P,\n );\n return i;\n };\n corner(x, y);\n corner(x1, y);\n corner(x1, y1);\n corner(x, y1);\n this.pushQuadIndices();\n return this.clip === null || this.clipQuad(this.nVerts - 4, this.nIdx - 6, this.clip);\n }\n\n /** `pushMesh` for a mesh filled by a gradient — see `pushGradientRect` for\n * what `uv`, `post` and `mode` carry. */\n pushGradientMesh(\n mesh: Mesh, m: GlMat3,\n uv: GradientUV, post: number, slot: number, mode: number,\n r: number, g: number, b: number, a: number,\n ): boolean {\n const src = mesh.vertices;\n const srcIdx = mesh.indices;\n const n = src.length >> 1;\n this.reserve(n, srcIdx.length);\n this.pureRects = false;\n const v = this.verts;\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n const P = packSlot(slot, mode);\n for (let k = 0; k < n; k++) {\n const x = src[k * 2];\n const y = src[k * 2 + 1];\n v[i++] = ma * x + mc * y + mtx;\n v[i++] = mb * x + md * y + mty;\n v[i++] = r; v[i++] = g; v[i++] = b; v[i++] = a;\n v[i++] = uv.ux * x + uv.uy * y + uv.u0;\n v[i++] = uv.vx * x + uv.vy * y + uv.v0;\n v[i++] = post; v[i++] = P;\n }\n const base = this.nVerts;\n const out = this.idx;\n let j = this.nIdx;\n for (let k = 0; k < srcIdx.length; k++) out[j++] = base + srcIdx[k];\n this.nVerts += n;\n this.nIdx += srcIdx.length;\n return this.clip === null || this.clipMesh(base, this.nIdx - srcIdx.length, this.clip);\n }\n\n /**\n * Append a tessellated mesh through `m`, all vertices carrying `rgba`. The\n * mesh's own indices are rebased onto the staged vertices, which is why the\n * index buffer is uploaded per flush rather than written once.\n */\n pushMesh(\n mesh: Mesh, m: GlMat3,\n r: number, g: number, b: number, a: number,\n ): boolean {\n const src = mesh.vertices;\n const srcIdx = mesh.indices;\n const n = src.length >> 1;\n this.reserve(n, srcIdx.length);\n this.pureRects = false;\n const v = this.verts;\n let i = this.nVerts * FLOATS_PER_VERTEX;\n const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];\n for (let k = 0; k < n; k++) {\n const x = src[k * 2];\n const y = src[k * 2 + 1];\n v[i++] = ma * x + mc * y + mtx;\n v[i++] = mb * x + md * y + mty;\n v[i++] = r; v[i++] = g; v[i++] = b; v[i++] = a;\n v[i++] = WHITE_U; v[i++] = WHITE_V; v[i++] = 1; v[i++] = PLAIN_WHITE;\n }\n const base = this.nVerts;\n const out = this.idx;\n let j = this.nIdx;\n for (let k = 0; k < srcIdx.length; k++) out[j++] = base + srcIdx[k];\n this.nVerts += n;\n this.nIdx += srcIdx.length;\n return this.clip === null || this.clipMesh(base, this.nIdx - srcIdx.length, this.clip);\n }\n\n /** Upload the staged geometry into the next set of buffers and bind its VAO.\n * Returns the index count for the caller's `drawElements`. */\n uploadAndBind(): number {\n const gl = this.gl;\n const tier = BATCH_TIERS.findIndex(\n (t) => this.nVerts <= t.vertices && this.nIdx <= t.indices,\n );\n const set = tier < 0 ? this.nextLargeSlot() : this.nextRingSlot(tier);\n gl.bindVertexArray(set.vao);\n gl.bindBuffer(gl.ARRAY_BUFFER, set.vbo);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verts, 0, this.nVerts * FLOATS_PER_VERTEX);\n if (!this.pureRects) {\n gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, this.idx, 0, this.nIdx);\n set.canonicalQuads = 0;\n } else if (set.canonicalQuads < this.nVerts >> 2) {\n // Written to the slot's capacity, not this flush's length: the extra\n // costs one upload the slot never repeats.\n const quads = Math.min(set.vertexCapacity >> 2, Math.floor(set.indexCapacity / 6));\n gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, quadIndices(quads));\n set.canonicalQuads = quads;\n }\n return this.nIdx;\n }\n\n reset(): void {\n this.nVerts = 0;\n this.nIdx = 0;\n this.pureRects = true;\n }\n\n dispose(): void {\n for (const set of [...this.rings.flat(), ...this.largeRing]) {\n if (set) this.deleteSet(set);\n }\n for (const ring of this.rings) ring.fill(undefined);\n this.largeRing.fill(undefined);\n }\n\n private writeVertex(\n i: number,\n x: number, y: number,\n r: number, g: number, b: number, a: number,\n u: number, v: number, post: number, packed: number,\n ): number {\n const out = this.verts;\n out[i] = x; out[i + 1] = y;\n out[i + 2] = r; out[i + 3] = g; out[i + 4] = b; out[i + 5] = a;\n out[i + 6] = u; out[i + 7] = v; out[i + 8] = post; out[i + 9] = packed;\n return i + FLOATS_PER_VERTEX;\n }\n\n /**\n * Fit the quad just written at `v0` to `rect`: keep it if it lies inside,\n * drop it if it misses, and cut it if it crosses — but only if its edges\n * run along the axes. Cutting a slanted edge moves its endpoint off the\n * subpixel grid the rasterizer snaps to, and the shortened edge then covers\n * different samples than the whole one did; along an axis the cut edge is\n * the same line exactly. So a slanted quad that crosses is rolled back and\n * refused, for `draw.ts` to clip through the stencil instead.\n *\n * A cut axis-aligned quad is still four corners, so the run stays pure.\n */\n private clipQuad(v0: number, i0: number, rect: ClipRect): boolean {\n const v = this.verts;\n const at = v0 * FLOATS_PER_VERTEX;\n const F = FLOATS_PER_VERTEX;\n const ax = v[at], ay = v[at + 1], bx = v[at + F], by = v[at + F + 1];\n const cx = v[at + 2 * F], cy = v[at + 2 * F + 1], dx = v[at + 3 * F], dy = v[at + 3 * F + 1];\n const minX = Math.min(ax, bx, cx, dx), maxX = Math.max(ax, bx, cx, dx);\n const minY = Math.min(ay, by, cy, dy), maxY = Math.max(ay, by, cy, dy);\n if (minX >= rect.x0 && maxX <= rect.x1 && minY >= rect.y0 && maxY <= rect.y1) return true;\n this.nVerts = v0;\n this.nIdx = i0;\n if (maxX <= rect.x0 || minX >= rect.x1 || maxY <= rect.y0 || minY >= rect.y1) return true;\n const alongAxes = (ay === by && bx === cx && cy === dy && dx === ax)\n || (ax === bx && by === cy && cx === dx && dy === ay);\n if (!alongAxes) return false;\n this.polyIn.set(v.subarray(at, at + 4 * F));\n const n = clipConvexPolygon(this.polyIn, 4, F, rect, this.polyOut, this.polyTmp);\n this.appendFan(this.polyOut, n);\n return true;\n }\n\n /** `clipQuad` for a mesh staged from `v0`. A mesh's edges are slanted in\n * general, so one that crosses is refused rather than cut. */\n private clipMesh(v0: number, i0: number, rect: ClipRect): boolean {\n const v = this.verts;\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (let i = v0 * FLOATS_PER_VERTEX; i < this.nVerts * FLOATS_PER_VERTEX; i += FLOATS_PER_VERTEX) {\n const x = v[i];\n const y = v[i + 1];\n if (x < minX) minX = x; if (x > maxX) maxX = x;\n if (y < minY) minY = y; if (y > maxY) maxY = y;\n }\n if (minX >= rect.x0 && maxX <= rect.x1 && minY >= rect.y0 && maxY <= rect.y1) return true;\n this.nVerts = v0;\n this.nIdx = i0;\n return maxX <= rect.x0 || minX >= rect.x1 || maxY <= rect.y0 || minY >= rect.y1;\n }\n\n /** Append the `n`-corner convex polygon in `poly` as a triangle fan. */\n private appendFan(poly: Float32Array, n: number): void {\n if (n < 3) return;\n this.reserve(n, (n - 2) * 3);\n if (n !== 4) this.pureRects = false;\n const base = this.nVerts;\n this.verts.set(poly.subarray(0, n * FLOATS_PER_VERTEX), base * FLOATS_PER_VERTEX);\n let j = this.nIdx;\n for (let k = 1; k < n - 1; k++) {\n this.idx[j++] = base; this.idx[j++] = base + k; this.idx[j++] = base + k + 1;\n }\n this.nVerts += n;\n this.nIdx = j;\n }\n\n /** Two triangles over the four corners just written. */\n private pushQuadIndices(): void {\n const base = this.nVerts;\n let j = this.nIdx;\n this.idx[j++] = base; this.idx[j++] = base + 1; this.idx[j++] = base + 2;\n this.idx[j++] = base; this.idx[j++] = base + 2; this.idx[j++] = base + 3;\n this.nVerts += 4;\n this.nIdx += 6;\n }\n\n /** Grow the CPU arrays so `vertices` / `indices` more fit. */\n private reserve(vertices: number, indices: number): void {\n const needVerts = (this.nVerts + vertices) * FLOATS_PER_VERTEX;\n if (needVerts > this.verts.length) {\n const grown = new Float32Array(doubledTo(this.verts.length, needVerts));\n grown.set(this.verts);\n this.verts = grown;\n }\n const needIdx = this.nIdx + indices;\n if (needIdx > this.idx.length) {\n const grown = new Uint32Array(doubledTo(this.idx.length, needIdx));\n grown.set(this.idx);\n this.idx = grown;\n }\n }\n\n private nextRingSlot(tier: number): BufferSet {\n const ring = this.rings[tier];\n const slot = this.nextInRing[tier];\n this.nextInRing[tier] = (slot + 1) % ring.length;\n const existing = ring[slot];\n if (existing) return existing;\n const set = this.createSet(BATCH_TIERS[tier].vertices, BATCH_TIERS[tier].indices);\n ring[slot] = set;\n return set;\n }\n\n private nextLargeSlot(): BufferSet {\n const gl = this.gl;\n const slot = this.nextLarge;\n this.nextLarge = (slot + 1) % SOLID_LARGE_RING_SIZE;\n let set = this.largeRing[slot];\n if (!set) {\n set = this.createSet(\n doubledTo(SOLID_RING_SLOT_VERTICES, this.nVerts),\n doubledTo(BATCH_TIERS[BATCH_TIERS.length - 1].indices, this.nIdx),\n );\n this.largeRing[slot] = set;\n return set;\n }\n if (this.nVerts > set.vertexCapacity) {\n set.vertexCapacity = doubledTo(set.vertexCapacity, this.nVerts);\n gl.bindBuffer(gl.ARRAY_BUFFER, set.vbo);\n gl.bufferData(gl.ARRAY_BUFFER, set.vertexCapacity * FLOATS_PER_VERTEX * 4, gl.DYNAMIC_DRAW);\n }\n if (this.nIdx > set.indexCapacity) {\n set.indexCapacity = doubledTo(set.indexCapacity, this.nIdx);\n gl.bindVertexArray(set.vao);\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, set.ibo);\n gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, set.indexCapacity * 4, gl.DYNAMIC_DRAW);\n gl.bindVertexArray(null);\n // Respecified, so whatever pattern it held is gone.\n set.canonicalQuads = 0;\n }\n return set;\n }\n\n private createSet(vertices: number, indices: number): BufferSet {\n const gl = this.gl;\n const vao = gl.createVertexArray();\n const vbo = gl.createBuffer();\n const ibo = gl.createBuffer();\n if (!vao || !vbo || !ibo) throw new Error('DrawBatch: failed to create GL objects');\n const stride = FLOATS_PER_VERTEX * 4;\n gl.bindVertexArray(vao);\n gl.bindBuffer(gl.ARRAY_BUFFER, vbo);\n gl.bufferData(gl.ARRAY_BUFFER, vertices * stride, gl.DYNAMIC_DRAW);\n gl.enableVertexAttribArray(this.aPos);\n gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, stride, 0);\n gl.enableVertexAttribArray(this.aColor);\n gl.vertexAttribPointer(this.aColor, 4, gl.FLOAT, false, stride, 8);\n gl.enableVertexAttribArray(this.aUv);\n gl.vertexAttribPointer(this.aUv, 2, gl.FLOAT, false, stride, 24);\n gl.enableVertexAttribArray(this.aPost);\n gl.vertexAttribPointer(this.aPost, 1, gl.FLOAT, false, stride, 32);\n gl.enableVertexAttribArray(this.aSlot);\n gl.vertexAttribPointer(this.aSlot, 1, gl.FLOAT, false, stride, 36);\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);\n gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices * 4, gl.DYNAMIC_DRAW);\n gl.bindVertexArray(null);\n return { vao, vbo, ibo, vertexCapacity: vertices, indexCapacity: indices, canonicalQuads: 0 };\n }\n\n private deleteSet(set: BufferSet): void {\n const gl = this.gl;\n gl.deleteBuffer(set.vbo);\n gl.deleteBuffer(set.ibo);\n gl.deleteVertexArray(set.vao);\n }\n}\n"],"mappings":"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"}
@@ -69,8 +69,9 @@ void main() {
69
69
  // split across the branch rather than the coordinate selected before it: an
70
70
  // arm that reads v_uv straight is a fetch the hardware can schedule against a
71
71
  // varying, and one reading a coordinate the shader computed is not. Selecting
72
- // the coordinate first and sampling once costs 65% of a fragment that is not
73
- // a gradient; splitting it costs 5% (tests/perf/fill-rate.spec.ts).
72
+ // the coordinate first and sampling once costs 29-54% of a fragment that is
73
+ // not a gradient; splitting it is within the measurement's own spread of free
74
+ // (tests/perf/fill-rate.spec.ts, teitou, 2026-10-05).
74
75
  //
75
76
  // Branching at all is safe here where it would not be around the glyph math:
76
77
  // v_paintMode is flat, so every fragment of a quad takes the same arm, and
@@ -1 +1 @@
1
- {"version":3,"file":"batchFill.js","names":["BATCH_TEXTURE_SLOTS","WHITE_SLOT","PAINT_MODE_PLAIN","PAINT_MODE_RADIAL","PAINT_MODE_CONIC","INV_TAU","packSlot","slot","mode","BATCH_ATTRIBUTE_LOCATIONS","L","sampleChain","arms","i","slots","BATCH_VERT_SRC","BATCH_FRAG_SRC","GLYPH_COVERAGE_GLSL","BATCH_FILL_UNIFORMS","BATCH_FILL_ATTRIBUTES"],"sources":["../../../src/renderer/shaders/batchFill.ts"],"sourcesContent":["/**\n * GLSL ES 3.0 source for the batch program — the one shader a run of solid\n * geometry, image quads and glyphs shares.\n *\n * **Every vertex names the texture it samples.** `a_texSlot` is an index into\n * `u_samplers`, and slot 0 is always the 1x1 white texel, so a solid's\n * `texture() * a_vertexColor` is the vertex color exactly. The multiply is\n * exact: an 8-bit white texel samples to 1.0, and 1.0 * x is x.\n *\n * The slot is per vertex rather than per flush because the run holds several\n * textures at once. Reserving slot 0 for white is what makes a solid's promise\n * true no matter what else joins its run: before this, a solid carried the\n * white texel's UV but the flush bound the run's *image*, so every ground rect\n * beside an atlas quad came out multiplied by whatever texel sat at the middle\n * of that atlas. A wall of white grounds drew olive.\n *\n * **The chain is unrolled because GLSL ES 3.0 will not index a sampler array\n * with a variable** — the index must be a constant expression, so `if (i == 0)\n * … if (i == 1) …` over constants is the only legal form. `v_texSlot` is\n * constant across a primitive, so the branch is uniform within any one\n * triangle; the coordinate it samples is a varying computed before the branch,\n * which is what keeps the implicit derivatives well defined.\n *\n * **The paint mode rides in `a_texSlot`, and the glyph math runs either way.**\n * A glyph vertex names a font atlas whose texel is a distance field rather than\n * a color, so its coverage comes from `glyphCoverage` and its color comes from\n * the vertex alone. That coverage is computed on every fragment, glyph or not,\n * because a derivative in non-uniform control flow is undefined and it\n * has to be taken before anything selects on the mode — see\n * `GLYPH_COVERAGE_GLSL`. Priced head to head at 432M fragments a frame\n * (`tests/perf/fill-rate.spec.ts`), that roughly doubles a fragment that is not\n * a glyph — the figure recorded when text landed was 1.4%, and it was wrong:\n * the control gated its glyph math on a factor the compiler folds to zero, so\n * it timed a shader with that math deleted. Fill is not what a wall is bound\n * by, which is why the conclusion stands even though the number did not.\n *\n * **A gradient is a textured quad off the ramp atlas.** A linear one needs no\n * mode: its ramp position is affine in position, so `a_uv` is (ramp position,\n * atlas row) and the plain path samples it. A radial or conic one carries the\n * gradient-space coordinate instead — affine even where the ramp position is\n * not — with the row in `a_post`, and the fragment takes a `length` or an\n * `atan` of it behind a branch on the flat mode. That branch carries the sample\n * itself, not just the coordinate: a fetch from a varying is one the hardware\n * schedules ahead, and one from a computed coordinate is not, which is 65% of\n * a fragment against 5%.\n *\n * **Mode and slot share one attribute because the vertex is the thing that\n * costs.** Both are small enumerations, so `slot + 8 * mode` packs them with\n * room to spare. Carrying the mode as a float of its own instead measures 9%\n * slower at the densest rung of `tests/perf/atlas-wall.spec.ts` — 1.78 ms\n * against 1.63 for 7,500 commands, taken ABBA in one sitting — because the\n * whole batch exists to make one buffer write a frame cheap, and every extra\n * float widens that write for every rect and quad in the run, not just for the\n * glyphs that read it.\n *\n * `a_post` is the alpha factor applied *after* the color matrix. An image quad\n * puts its opacity there, where `u_opacity` used to be — the fold is exact for\n * the same reason the old image shader's was, the matrix cannot see it. Solid\n * geometry and glyphs leave it at 1 and fold their own alpha into\n * `a_vertexColor`, which is only sound under an identity matrix; `draw.ts` owns\n * that condition.\n *\n * Inputs:\n * a_position vec2 screen-space x,y of the corner\n * a_vertexColor vec4 straight-alpha color; (1,1,1,1) on an image vertex\n * a_uv vec2 texture coordinate 0..1; the white texel for solids\n * a_post float alpha factor applied after the color matrix\n * a_texSlot float slot + 8 * paint mode; slot 0 is the white texel\n *\n * Output: PREMULTIPLIED alpha. Blend: `gl.blendFunc(ONE, ONE_MINUS_SRC_ALPHA)`.\n */\n\nimport { GLYPH_COVERAGE_GLSL } from '@weasel-js/font';\n\n/**\n * Texture units the batch program samples, slot 0 being the white texel.\n *\n * Fixed rather than queried: WebGL2 guarantees at least 16 fragment texture\n * units, so 8 is available everywhere without asking, and asking would mean a\n * `getParameter` the GL recorder answers with a recording function rather than\n * a number. Seven textures in one run is what \"a document with a handful of\n * distinct images, and a font or two\" needs; past that the run breaks as it\n * always did, and every extra slot is another compare in the fragment chain.\n */\nexport const BATCH_TEXTURE_SLOTS = 8;\n\n/** Slot every solid vertex carries — the white texel, bound by `flushBatch`. */\nexport const WHITE_SLOT = 0;\n\n/** Paint mode for everything whose texel is a color rather than a field. */\nexport const PAINT_MODE_PLAIN = 0;\n\n/**\n * Paint modes for the two gradients whose ramp position is not affine in\n * position — the ones a vertex cannot simply carry.\n *\n * They sit above the glyph modes (1 and 2), so `isGlyph` is a range rather than\n * a threshold. What a vertex carries in these modes is the *gradient-space\n * coordinate* in `a_uv` and its atlas row in `a_post`: that coordinate is\n * affine even where the ramp position is not, so the CPU folds it to two affine\n * rows and the shader takes a `length` or an `atan` of the result.\n *\n * A linear gradient has neither mode: its ramp position is affine outright, so\n * it is a textured quad off the atlas at `PAINT_MODE_PLAIN` and pays nothing\n * here.\n */\nexport const PAINT_MODE_RADIAL = 3;\nexport const PAINT_MODE_CONIC = 4;\n\n/** 1 / 2π, which turns a conic gradient's angle into a ramp position. */\nconst INV_TAU = (1 / (2 * Math.PI)).toFixed(10);\n\n/** Pack a texture slot and a paint mode into the one `a_texSlot` float. */\nexport function packSlot(slot: number, mode: number): number {\n return slot + BATCH_TEXTURE_SLOTS * mode;\n}\n\n/** The paint mode packed into an `a_texSlot` value — for the tests, which read\n * a vertex back out of the buffer it was staged into. */\nexport function paintModeOf(packed: number): number {\n return Math.floor((packed + 0.5) / BATCH_TEXTURE_SLOTS);\n}\n\n/**\n * Where each batch attribute lives, pinned rather than left to the linker so\n * `glyphPaint`'s program can draw from the same VAO.\n */\nexport const BATCH_ATTRIBUTE_LOCATIONS = {\n a_position: 0, a_vertexColor: 1, a_uv: 2, a_post: 3, a_texSlot: 4,\n} as const;\n\nconst L = BATCH_ATTRIBUTE_LOCATIONS;\n\nfunction sampleChain(slots: number): string {\n const arms: string[] = [];\n for (let i = 1; i < slots; i++) {\n arms.push(` if (slot == ${i}) return texture(u_samplers[${i}], uv);`);\n }\n return arms.join('\\n');\n}\n\nexport const BATCH_VERT_SRC = /* glsl */ `#version 300 es\nlayout(location = ${L.a_position}) in vec2 a_position;\nlayout(location = ${L.a_vertexColor}) in vec4 a_vertexColor;\nlayout(location = ${L.a_uv}) in vec2 a_uv;\nlayout(location = ${L.a_post}) in float a_post;\nlayout(location = ${L.a_texSlot}) in float a_texSlot;\nuniform mat3 u_proj;\nuniform mat3 u_model;\nout vec4 v_vertexColor;\nout vec2 v_uv;\nout float v_post;\nflat out int v_texSlot;\nflat out float v_paintMode;\nvoid main() {\n vec3 screen = u_model * vec3(a_position, 1.0);\n vec3 clip = u_proj * vec3(screen.xy, 1.0);\n gl_Position = vec4(clip.xy, 0.0, 1.0);\n v_vertexColor = a_vertexColor;\n v_uv = a_uv;\n v_post = a_post;\n // slot + 8 * mode, unpacked — see the file header for why they share a float.\n int packed = int(a_texSlot + 0.5);\n v_texSlot = packed % ${BATCH_TEXTURE_SLOTS};\n v_paintMode = float(packed / ${BATCH_TEXTURE_SLOTS});\n}\n`;\n\nexport const BATCH_FRAG_SRC = /* glsl */ `#version 300 es\nprecision highp float;\nin vec4 v_vertexColor;\nin vec2 v_uv;\nin float v_post;\nflat in int v_texSlot;\nflat in float v_paintMode;\nuniform sampler2D u_samplers[${BATCH_TEXTURE_SLOTS}];\nuniform vec4 u_color;\nuniform float u_alpha;\nuniform float u_synthBold;\nuniform vec2 u_fieldScale[${BATCH_TEXTURE_SLOTS}];\nuniform mat4 u_colorMatrix;\nuniform vec4 u_colorBias;\nout vec4 outColor;\n\nvec4 sampleSlot(int slot, vec2 uv) {\n${sampleChain(BATCH_TEXTURE_SLOTS)}\n return texture(u_samplers[0], uv);\n}\n${GLYPH_COVERAGE_GLSL}\nvoid main() {\n float isGrad = step(2.5, v_paintMode);\n // A radial or conic vertex carries a gradient-space coordinate rather than a\n // texture coordinate, and its atlas row rather than an alpha. The sample is\n // split across the branch rather than the coordinate selected before it: an\n // arm that reads v_uv straight is a fetch the hardware can schedule against a\n // varying, and one reading a coordinate the shader computed is not. Selecting\n // the coordinate first and sampling once costs 65% of a fragment that is not\n // a gradient; splitting it costs 5% (tests/perf/fill-rate.spec.ts).\n //\n // Branching at all is safe here where it would not be around the glyph math:\n // v_paintMode is flat, so every fragment of a quad takes the same arm, and\n // nothing in either arm is a derivative.\n vec4 texel;\n if (isGrad > 0.5) {\n float t = v_paintMode > ${PAINT_MODE_CONIC - 0.5}\n ? fract(atan(v_uv.y, v_uv.x) * ${INV_TAU})\n : length(v_uv);\n texel = sampleSlot(v_texSlot, vec2(t, v_post));\n } else {\n texel = sampleSlot(v_texSlot, v_uv);\n }\n float isGlyph = step(0.5, v_paintMode) * step(v_paintMode, 2.5);\n // Unconditional, and multiplied out afterwards rather than branched around:\n // see the file header.\n float coverage = glyphCoverage(texel, v_paintMode, u_synthBold, v_uv, u_fieldScale[v_texSlot]);\n // A glyph's texel is a distance field, so it takes no part in the color; a\n // plain vertex multiplies its texel in as it always did.\n vec4 src = mix(texel, vec4(1.0), isGlyph) * u_color * v_vertexColor;\n vec4 mapped = clamp(u_colorMatrix * src + u_colorBias, 0.0, 1.0);\n float a = mapped.a * u_alpha * mix(v_post, 1.0, isGrad) * mix(1.0, coverage, isGlyph);\n outColor = vec4(mapped.rgb * a, a);\n}\n`;\n\nexport const BATCH_FILL_UNIFORMS = [\n 'u_proj', 'u_model', 'u_samplers', 'u_color', 'u_alpha', 'u_synthBold', 'u_fieldScale',\n 'u_colorMatrix', 'u_colorBias',\n] as const;\n\nexport const BATCH_FILL_ATTRIBUTES = [\n 'a_position', 'a_vertexColor', 'a_uv', 'a_post', 'a_texSlot',\n] as const;\n"],"mappings":";;AAoFA,IAAaA,IAAsB,GAGtBC,IAAa,GAGbC,IAAmB,GAgBnBC,IAAoB,GACpBC,IAAmB,GAG1BC,KAAW,KAAK,IAAI,KAAK,IAAA,CAAK,QAAQ,EAAE;AAG9C,SAAgBC,EAAS,GAAc,GAAsB;CAC3D,OAAOC,IAAOP,IAAsBQ;AACtC;AAYA,IAAaC,IAA4B;CACvC,YAAY;CAAG,eAAe;CAAG,MAAM;CAAG,QAAQ;CAAG,WAAW;AAClE,GAEMC,IAAID;AAEV,SAASE,EAAY,GAAuB;CAC1C,IAAMC,IAAiB,CAAC;CACxB,KAAK,IAAIC,IAAI,GAAGA,IAAIC,GAAO,KACzB,EAAK,KAAK,iBAAiBD,EAAE,8BAA8BA,EAAE,QAAQ;CAEvE,OAAOD,EAAK,KAAK,IAAI;AACvB;AAEA,IAAaG,IAA4B;oBACrBL,EAAE,WAAW;oBACbA,EAAE,cAAc;oBAChBA,EAAE,KAAK;oBACPA,EAAE,OAAO;oBACTA,EAAE,UAAU;;;;;;;;;;;;;;;;;yBAiBPV,EAAoB;iCACZA,EAAoB;;GAIxCgB,IAA4B;;;;;;;+BAOVhB,EAAoB;;;;4BAIvBA,EAAoB;;;;;;EAM9CW,EAAYX,CAAmB,EAAE;;;EAGjCiB,EAAoB;;;;;;;;;;;;;;;;;uCAiBiBZ,EAAQ;;;;;;;;;;;;;;;;;GAmBlCa,IAAsB;CACjC;CAAU;CAAW;CAAc;CAAW;CAAW;CAAe;CACxE;CAAiB;AACnB,GAEaC,IAAwB;CACnC;CAAc;CAAiB;CAAQ;CAAU;AACnD"}
1
+ {"version":3,"file":"batchFill.js","names":["BATCH_TEXTURE_SLOTS","WHITE_SLOT","PAINT_MODE_PLAIN","PAINT_MODE_RADIAL","PAINT_MODE_CONIC","INV_TAU","packSlot","slot","mode","BATCH_ATTRIBUTE_LOCATIONS","L","sampleChain","arms","i","slots","BATCH_VERT_SRC","BATCH_FRAG_SRC","GLYPH_COVERAGE_GLSL","BATCH_FILL_UNIFORMS","BATCH_FILL_ATTRIBUTES"],"sources":["../../../src/renderer/shaders/batchFill.ts"],"sourcesContent":["/**\n * GLSL ES 3.0 source for the batch program — the one shader a run of solid\n * geometry, image quads and glyphs shares.\n *\n * **Every vertex names the texture it samples.** `a_texSlot` is an index into\n * `u_samplers`, and slot 0 is always the 1x1 white texel, so a solid's\n * `texture() * a_vertexColor` is the vertex color exactly. The multiply is\n * exact: an 8-bit white texel samples to 1.0, and 1.0 * x is x.\n *\n * The slot is per vertex rather than per flush because the run holds several\n * textures at once. Reserving slot 0 for white is what makes a solid's promise\n * true no matter what else joins its run: before this, a solid carried the\n * white texel's UV but the flush bound the run's *image*, so every ground rect\n * beside an atlas quad came out multiplied by whatever texel sat at the middle\n * of that atlas. A wall of white grounds drew olive.\n *\n * **The chain is unrolled because GLSL ES 3.0 will not index a sampler array\n * with a variable** — the index must be a constant expression, so `if (i == 0)\n * … if (i == 1) …` over constants is the only legal form. `v_texSlot` is\n * constant across a primitive, so the branch is uniform within any one\n * triangle; the coordinate it samples is a varying computed before the branch,\n * which is what keeps the implicit derivatives well defined.\n *\n * **The paint mode rides in `a_texSlot`, and the glyph math runs either way.**\n * A glyph vertex names a font atlas whose texel is a distance field rather than\n * a color, so its coverage comes from `glyphCoverage` and its color comes from\n * the vertex alone. That coverage is computed on every fragment, glyph or not,\n * because a derivative in non-uniform control flow is undefined and it\n * has to be taken before anything selects on the mode — see\n * `GLYPH_COVERAGE_GLSL`. Priced head to head at 432M fragments a frame\n * (`tests/perf/fill-rate.spec.ts`, teitou, 2026-10-05), that adds a quarter to\n * a half to a fragment that is not a glyph — 27–48% over four sittings. The\n * figure recorded when text landed was 1.4%, and it was wrong: the control\n * gated its glyph math on a factor the compiler folds to zero, so it timed a\n * shader with that math deleted. Fill is not what a wall is bound by, which is\n * why the conclusion stands even though the number did not.\n *\n * **A gradient is a textured quad off the ramp atlas.** A linear one needs no\n * mode: its ramp position is affine in position, so `a_uv` is (ramp position,\n * atlas row) and the plain path samples it. A radial or conic one carries the\n * gradient-space coordinate instead — affine even where the ramp position is\n * not — with the row in `a_post`, and the fragment takes a `length` or an\n * `atan` of it behind a branch on the flat mode. That branch carries the sample\n * itself, not just the coordinate: a fetch from a varying is one the hardware\n * schedules ahead, and one from a computed coordinate is not: on teitou the\n * computed-coordinate form costs 29–54% of a fragment, and the split one\n * −5–11%, which is the spread of the measurement itself\n * (`tests/perf/fill-rate.spec.ts`, 2026-10-05).\n *\n * **Mode and slot share one attribute because the vertex is the thing that\n * costs.** Both are small enumerations, so `slot + 8 * mode` packs them with\n * room to spare. Carrying the mode as a float of its own instead measured\n * slower at the densest rung of `tests/perf/atlas-wall.spec.ts`, timed then in\n * blocks of frames, a method since retired (`tests/perf/README.md`, \"Timing a\n * frame\"), so the size of it is unknown. The reason holds without the number: the\n * whole batch exists to make one buffer write a frame cheap, and every extra\n * float widens that write for every rect and quad in the run, not just for the\n * glyphs that read it.\n *\n * `a_post` is the alpha factor applied *after* the color matrix. An image quad\n * puts its opacity there, where `u_opacity` used to be — the fold is exact for\n * the same reason the old image shader's was, the matrix cannot see it. Solid\n * geometry and glyphs leave it at 1 and fold their own alpha into\n * `a_vertexColor`, which is only sound under an identity matrix; `draw.ts` owns\n * that condition.\n *\n * Inputs:\n * a_position vec2 screen-space x,y of the corner\n * a_vertexColor vec4 straight-alpha color; (1,1,1,1) on an image vertex\n * a_uv vec2 texture coordinate 0..1; the white texel for solids\n * a_post float alpha factor applied after the color matrix\n * a_texSlot float slot + 8 * paint mode; slot 0 is the white texel\n *\n * Output: PREMULTIPLIED alpha. Blend: `gl.blendFunc(ONE, ONE_MINUS_SRC_ALPHA)`.\n */\n\nimport { GLYPH_COVERAGE_GLSL } from '@weasel-js/font';\n\n/**\n * Texture units the batch program samples, slot 0 being the white texel.\n *\n * Fixed rather than queried: WebGL2 guarantees at least 16 fragment texture\n * units, so 8 is available everywhere without asking, and asking would mean a\n * `getParameter` the GL recorder answers with a recording function rather than\n * a number. Seven textures in one run is what \"a document with a handful of\n * distinct images, and a font or two\" needs; past that the run breaks as it\n * always did, and every extra slot is another compare in the fragment chain.\n */\nexport const BATCH_TEXTURE_SLOTS = 8;\n\n/** Slot every solid vertex carries — the white texel, bound by `flushBatch`. */\nexport const WHITE_SLOT = 0;\n\n/** Paint mode for everything whose texel is a color rather than a field. */\nexport const PAINT_MODE_PLAIN = 0;\n\n/**\n * Paint modes for the two gradients whose ramp position is not affine in\n * position — the ones a vertex cannot simply carry.\n *\n * They sit above the glyph modes (1 and 2), so `isGlyph` is a range rather than\n * a threshold. What a vertex carries in these modes is the *gradient-space\n * coordinate* in `a_uv` and its atlas row in `a_post`: that coordinate is\n * affine even where the ramp position is not, so the CPU folds it to two affine\n * rows and the shader takes a `length` or an `atan` of the result.\n *\n * A linear gradient has neither mode: its ramp position is affine outright, so\n * it is a textured quad off the atlas at `PAINT_MODE_PLAIN` and pays nothing\n * here.\n */\nexport const PAINT_MODE_RADIAL = 3;\nexport const PAINT_MODE_CONIC = 4;\n\n/** 1 / 2π, which turns a conic gradient's angle into a ramp position. */\nconst INV_TAU = (1 / (2 * Math.PI)).toFixed(10);\n\n/** Pack a texture slot and a paint mode into the one `a_texSlot` float. */\nexport function packSlot(slot: number, mode: number): number {\n return slot + BATCH_TEXTURE_SLOTS * mode;\n}\n\n/** The paint mode packed into an `a_texSlot` value — for the tests, which read\n * a vertex back out of the buffer it was staged into. */\nexport function paintModeOf(packed: number): number {\n return Math.floor((packed + 0.5) / BATCH_TEXTURE_SLOTS);\n}\n\n/**\n * Where each batch attribute lives, pinned rather than left to the linker so\n * `glyphPaint`'s program can draw from the same VAO.\n */\nexport const BATCH_ATTRIBUTE_LOCATIONS = {\n a_position: 0, a_vertexColor: 1, a_uv: 2, a_post: 3, a_texSlot: 4,\n} as const;\n\nconst L = BATCH_ATTRIBUTE_LOCATIONS;\n\nfunction sampleChain(slots: number): string {\n const arms: string[] = [];\n for (let i = 1; i < slots; i++) {\n arms.push(` if (slot == ${i}) return texture(u_samplers[${i}], uv);`);\n }\n return arms.join('\\n');\n}\n\nexport const BATCH_VERT_SRC = /* glsl */ `#version 300 es\nlayout(location = ${L.a_position}) in vec2 a_position;\nlayout(location = ${L.a_vertexColor}) in vec4 a_vertexColor;\nlayout(location = ${L.a_uv}) in vec2 a_uv;\nlayout(location = ${L.a_post}) in float a_post;\nlayout(location = ${L.a_texSlot}) in float a_texSlot;\nuniform mat3 u_proj;\nuniform mat3 u_model;\nout vec4 v_vertexColor;\nout vec2 v_uv;\nout float v_post;\nflat out int v_texSlot;\nflat out float v_paintMode;\nvoid main() {\n vec3 screen = u_model * vec3(a_position, 1.0);\n vec3 clip = u_proj * vec3(screen.xy, 1.0);\n gl_Position = vec4(clip.xy, 0.0, 1.0);\n v_vertexColor = a_vertexColor;\n v_uv = a_uv;\n v_post = a_post;\n // slot + 8 * mode, unpacked — see the file header for why they share a float.\n int packed = int(a_texSlot + 0.5);\n v_texSlot = packed % ${BATCH_TEXTURE_SLOTS};\n v_paintMode = float(packed / ${BATCH_TEXTURE_SLOTS});\n}\n`;\n\nexport const BATCH_FRAG_SRC = /* glsl */ `#version 300 es\nprecision highp float;\nin vec4 v_vertexColor;\nin vec2 v_uv;\nin float v_post;\nflat in int v_texSlot;\nflat in float v_paintMode;\nuniform sampler2D u_samplers[${BATCH_TEXTURE_SLOTS}];\nuniform vec4 u_color;\nuniform float u_alpha;\nuniform float u_synthBold;\nuniform vec2 u_fieldScale[${BATCH_TEXTURE_SLOTS}];\nuniform mat4 u_colorMatrix;\nuniform vec4 u_colorBias;\nout vec4 outColor;\n\nvec4 sampleSlot(int slot, vec2 uv) {\n${sampleChain(BATCH_TEXTURE_SLOTS)}\n return texture(u_samplers[0], uv);\n}\n${GLYPH_COVERAGE_GLSL}\nvoid main() {\n float isGrad = step(2.5, v_paintMode);\n // A radial or conic vertex carries a gradient-space coordinate rather than a\n // texture coordinate, and its atlas row rather than an alpha. The sample is\n // split across the branch rather than the coordinate selected before it: an\n // arm that reads v_uv straight is a fetch the hardware can schedule against a\n // varying, and one reading a coordinate the shader computed is not. Selecting\n // the coordinate first and sampling once costs 29-54% of a fragment that is\n // not a gradient; splitting it is within the measurement's own spread of free\n // (tests/perf/fill-rate.spec.ts, teitou, 2026-10-05).\n //\n // Branching at all is safe here where it would not be around the glyph math:\n // v_paintMode is flat, so every fragment of a quad takes the same arm, and\n // nothing in either arm is a derivative.\n vec4 texel;\n if (isGrad > 0.5) {\n float t = v_paintMode > ${PAINT_MODE_CONIC - 0.5}\n ? fract(atan(v_uv.y, v_uv.x) * ${INV_TAU})\n : length(v_uv);\n texel = sampleSlot(v_texSlot, vec2(t, v_post));\n } else {\n texel = sampleSlot(v_texSlot, v_uv);\n }\n float isGlyph = step(0.5, v_paintMode) * step(v_paintMode, 2.5);\n // Unconditional, and multiplied out afterwards rather than branched around:\n // see the file header.\n float coverage = glyphCoverage(texel, v_paintMode, u_synthBold, v_uv, u_fieldScale[v_texSlot]);\n // A glyph's texel is a distance field, so it takes no part in the color; a\n // plain vertex multiplies its texel in as it always did.\n vec4 src = mix(texel, vec4(1.0), isGlyph) * u_color * v_vertexColor;\n vec4 mapped = clamp(u_colorMatrix * src + u_colorBias, 0.0, 1.0);\n float a = mapped.a * u_alpha * mix(v_post, 1.0, isGrad) * mix(1.0, coverage, isGlyph);\n outColor = vec4(mapped.rgb * a, a);\n}\n`;\n\nexport const BATCH_FILL_UNIFORMS = [\n 'u_proj', 'u_model', 'u_samplers', 'u_color', 'u_alpha', 'u_synthBold', 'u_fieldScale',\n 'u_colorMatrix', 'u_colorBias',\n] as const;\n\nexport const BATCH_FILL_ATTRIBUTES = [\n 'a_position', 'a_vertexColor', 'a_uv', 'a_post', 'a_texSlot',\n] as const;\n"],"mappings":";;AAwFA,IAAaA,IAAsB,GAGtBC,IAAa,GAGbC,IAAmB,GAgBnBC,IAAoB,GACpBC,IAAmB,GAG1BC,KAAW,KAAK,IAAI,KAAK,IAAA,CAAK,QAAQ,EAAE;AAG9C,SAAgBC,EAAS,GAAc,GAAsB;CAC3D,OAAOC,IAAOP,IAAsBQ;AACtC;AAYA,IAAaC,IAA4B;CACvC,YAAY;CAAG,eAAe;CAAG,MAAM;CAAG,QAAQ;CAAG,WAAW;AAClE,GAEMC,IAAID;AAEV,SAASE,EAAY,GAAuB;CAC1C,IAAMC,IAAiB,CAAC;CACxB,KAAK,IAAIC,IAAI,GAAGA,IAAIC,GAAO,KACzB,EAAK,KAAK,iBAAiBD,EAAE,8BAA8BA,EAAE,QAAQ;CAEvE,OAAOD,EAAK,KAAK,IAAI;AACvB;AAEA,IAAaG,IAA4B;oBACrBL,EAAE,WAAW;oBACbA,EAAE,cAAc;oBAChBA,EAAE,KAAK;oBACPA,EAAE,OAAO;oBACTA,EAAE,UAAU;;;;;;;;;;;;;;;;;yBAiBPV,EAAoB;iCACZA,EAAoB;;GAIxCgB,IAA4B;;;;;;;+BAOVhB,EAAoB;;;;4BAIvBA,EAAoB;;;;;;EAM9CW,EAAYX,CAAmB,EAAE;;;EAGjCiB,EAAoB;;;;;;;;;;;;;;;;;;uCAkBiBZ,EAAQ;;;;;;;;;;;;;;;;;GAmBlCa,IAAsB;CACjC;CAAU;CAAW;CAAc;CAAW;CAAW;CAAe;CACxE;CAAiB;AACnB,GAEaC,IAAwB;CACnC;CAAc;CAAiB;CAAQ;CAAU;AACnD"}
@@ -1,4 +1,4 @@
1
- export { C as CullRect, D as DrawCommand, E as Effect, G as GroupDrawCommand, I as IDENTITY_COLOR_MATRIX, a as ImageDrawCommand, b as ImageMinification, P as PathDrawCommand, R as RenderOptions, c as RenderSpan, d as RenderSpanStats, e as RenderStats, f as RenderTarget, S as SPRITE_STRIDE, g as ShaderDrawCommand, h as SolidPaint, i as SpriteSheet, j as SpritesDrawCommand, k as StrokeOptions, T as TextDrawCommand, W as WeaselRenderer, l as WeaselRendererOptions, m as blur, n as buildGradientRamp, o as cullDrawCommands, p as frameRect, r as registerEffect, t as tessellateStroke, v as viewToMat3, q as vignette } from './builtins-C-gRyVRx.js';
1
+ export { C as CullRect, D as DrawCommand, E as Effect, G as GroupDrawCommand, I as IDENTITY_COLOR_MATRIX, a as ImageDrawCommand, b as ImageMinification, P as PathDrawCommand, R as RenderOptions, c as RenderSpan, d as RenderSpanStats, e as RenderStats, f as RenderTarget, S as SPRITE_STRIDE, g as ShaderDrawCommand, h as SolidPaint, i as SpriteSheet, j as SpritesDrawCommand, k as StrokeOptions, T as TextDrawCommand, W as WeaselRenderer, l as WeaselRendererOptions, m as blur, n as buildGradientRamp, o as cullDrawCommands, p as frameRect, r as registerEffect, t as tessellateStroke, v as viewToMat3, q as vignette } from './builtins-CMEh6-Oc.js';
2
2
  export { G as GlMat3, P as ProgramSource, S as ShaderCompileError, a as ShaderProgramHandle, b as ShaderUniform, m as mat3, p as programSourceRegistry, r as registerProgram } from './registerProgram-ateHDGjw.js';
3
3
  export { Mesh, TessellateOptions, tessellate } from '@weasel-js/geom/tessellate';
4
4
  export { RegisterFontOptions, canQueryLocalFonts, cssFontFamily, cssFontFamilyLoading, enableLocalFontOutlines, fontOutlineRegistry, fontRegistry, hasFontOutlines, isCanvasFont, listFontOutlines, loadFontOutlines, outlineStatus, registerCanvasFont, registerFont, registerFontOutlines, subscribeGlyphReady, unregisterCanvasFont, unregisterFontOutlines } from '@weasel-js/font';
@@ -81,22 +81,22 @@ o.options = {
81
81
  {
82
82
  value: "none",
83
83
  label: "None",
84
- short: "–"
84
+ short: ["–"]
85
85
  },
86
86
  {
87
87
  value: "uppercase",
88
88
  label: "Uppercase",
89
- short: "AA"
89
+ short: ["AA"]
90
90
  },
91
91
  {
92
92
  value: "lowercase",
93
93
  label: "Lowercase",
94
- short: "aa"
94
+ short: ["aa"]
95
95
  },
96
96
  {
97
97
  value: "capitalize",
98
98
  label: "Capitalize",
99
- short: "Aa"
99
+ short: ["Aa"]
100
100
  }
101
101
  ]
102
102
  },
@@ -109,11 +109,11 @@ o.options = {
109
109
  options: [{
110
110
  value: "normal",
111
111
  label: "Normal",
112
- short: "–"
112
+ short: ["–"]
113
113
  }, {
114
114
  value: "small-caps",
115
115
  label: "Small caps",
116
- short: "Sc"
116
+ short: ["Sc"]
117
117
  }]
118
118
  },
119
119
  fontFamily: {
@@ -139,7 +139,7 @@ o.options = {
139
139
  letterSpacing: {
140
140
  kind: "number",
141
141
  name: "Tracking",
142
- short: "VA",
142
+ short: ["VA"],
143
143
  description: "Space added between characters.",
144
144
  step: .1,
145
145
  default: 0
@@ -147,7 +147,7 @@ o.options = {
147
147
  baselineShift: {
148
148
  kind: "number",
149
149
  name: "Baseline shift",
150
- short: "Shift",
150
+ short: ["Shift"],
151
151
  description: "Raise or lower the range, in ems of the inherited size.",
152
152
  step: .001,
153
153
  unit: s,