@vibecook/ghosttea-react 0.11.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -4
- package/dist/TerminalSurface.d.ts +53 -0
- package/dist/TerminalSurface.d.ts.map +1 -1
- package/dist/TerminalSurface.js +106 -12
- package/dist/TerminalSurface.js.map +1 -1
- package/dist/appearance/AppearanceSettings.d.ts +5 -2
- package/dist/appearance/AppearanceSettings.d.ts.map +1 -1
- package/dist/appearance/AppearanceSettings.js +14 -3
- package/dist/appearance/AppearanceSettings.js.map +1 -1
- package/dist/appearance/BackdropBlurControl.d.ts +5 -0
- package/dist/appearance/BackdropBlurControl.d.ts.map +1 -0
- package/dist/appearance/BackdropBlurControl.js +42 -0
- package/dist/appearance/BackdropBlurControl.js.map +1 -0
- package/dist/appearance/types.d.ts +10 -0
- package/dist/appearance/types.d.ts.map +1 -1
- package/dist/catalog-budget.d.ts +2 -2
- package/dist/catalog-budget.d.ts.map +1 -1
- package/dist/catalog-budget.js +2 -2
- package/dist/catalog-budget.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/link-input.d.ts +18 -0
- package/dist/link-input.d.ts.map +1 -0
- package/dist/link-input.js +27 -0
- package/dist/link-input.js.map +1 -0
- package/dist/renderers/background-geometry.d.ts +16 -0
- package/dist/renderers/background-geometry.d.ts.map +1 -0
- package/dist/renderers/background-geometry.js +20 -0
- package/dist/renderers/background-geometry.js.map +1 -0
- package/dist/renderers/glyph-atlas.d.ts +43 -0
- package/dist/renderers/glyph-atlas.d.ts.map +1 -0
- package/dist/renderers/glyph-atlas.js +175 -0
- package/dist/renderers/glyph-atlas.js.map +1 -0
- package/dist/renderers/light-adaptation.d.ts +36 -0
- package/dist/renderers/light-adaptation.d.ts.map +1 -0
- package/dist/renderers/light-adaptation.js +332 -0
- package/dist/renderers/light-adaptation.js.map +1 -0
- package/dist/renderers/types.d.ts +23 -0
- package/dist/renderers/types.d.ts.map +1 -1
- package/dist/renderers/types.js.map +1 -1
- package/dist/renderers/webgpu-renderer.d.ts +4 -0
- package/dist/renderers/webgpu-renderer.d.ts.map +1 -1
- package/dist/renderers/webgpu-renderer.js +331 -239
- package/dist/renderers/webgpu-renderer.js.map +1 -1
- package/dist/routed-control.d.ts +26 -2
- package/dist/routed-control.d.ts.map +1 -1
- package/dist/routed-control.js +145 -27
- package/dist/routed-control.js.map +1 -1
- package/dist/routed-frames.d.ts +1 -1
- package/dist/routed-frames.d.ts.map +1 -1
- package/dist/routed-frames.js +3 -7
- package/dist/routed-frames.js.map +1 -1
- package/dist/row-damage.d.ts +21 -0
- package/dist/row-damage.d.ts.map +1 -0
- package/dist/row-damage.js +76 -0
- package/dist/row-damage.js.map +1 -0
- package/dist/runtime.d.ts +97 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +489 -49
- package/dist/runtime.js.map +1 -1
- package/dist/styles.css +24 -0
- package/dist/terminal-render.worker.js +979 -297
- package/dist/terminal-render.worker.js.map +4 -4
- package/dist/use-terminal-links.d.ts +17 -0
- package/dist/use-terminal-links.d.ts.map +1 -0
- package/dist/use-terminal-links.js +101 -0
- package/dist/use-terminal-links.js.map +1 -0
- package/dist/worker-messages.d.ts +27 -1
- package/dist/worker-messages.d.ts.map +1 -1
- package/dist/workspace/Workspace.d.ts +19 -3
- package/dist/workspace/Workspace.d.ts.map +1 -1
- package/dist/workspace/Workspace.js +53 -20
- package/dist/workspace/Workspace.js.map +1 -1
- package/dist/workspace/index.d.ts +2 -2
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/pane-focus.d.ts +8 -0
- package/dist/workspace/pane-focus.d.ts.map +1 -0
- package/dist/workspace/pane-focus.js +24 -0
- package/dist/workspace/pane-focus.js.map +1 -0
- package/dist/workspace/pane-layout.d.ts +5 -2
- package/dist/workspace/pane-layout.d.ts.map +1 -1
- package/dist/workspace/pane-layout.js +45 -6
- package/dist/workspace/pane-layout.js.map +1 -1
- package/package.json +4 -4
|
@@ -22,6 +22,8 @@ var SectionKind;
|
|
|
22
22
|
SectionKind2[SectionKind2["ViewportMetadata"] = 9] = "ViewportMetadata";
|
|
23
23
|
SectionKind2[SectionKind2["AccessibilityText"] = 10] = "AccessibilityText";
|
|
24
24
|
SectionKind2[SectionKind2["ClipboardWrite"] = 11] = "ClipboardWrite";
|
|
25
|
+
SectionKind2[SectionKind2["LinkTargets"] = 12] = "LinkTargets";
|
|
26
|
+
SectionKind2[SectionKind2["StylePalette"] = 13] = "StylePalette";
|
|
25
27
|
})(SectionKind || (SectionKind = {}));
|
|
26
28
|
var GlyphFormat;
|
|
27
29
|
(function(GlyphFormat2) {
|
|
@@ -40,14 +42,15 @@ function assertRange(condition, message) {
|
|
|
40
42
|
throw new RangeError(`Invalid terminal frame: ${message}`);
|
|
41
43
|
}
|
|
42
44
|
function decodeFrame(buffer) {
|
|
43
|
-
|
|
44
|
-
assertRange(
|
|
45
|
-
|
|
45
|
+
const bytes = buffer instanceof Uint8Array ? new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) : new Uint8Array(buffer);
|
|
46
|
+
assertRange(bytes.byteLength >= FRAME_HEADER_BYTES, "truncated header");
|
|
47
|
+
assertRange(bytes.byteLength <= MAX_FRAME_BYTES, "packet exceeds limit");
|
|
48
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
46
49
|
assertRange(view.getUint32(0, true) === FRAME_MAGIC, "bad magic");
|
|
47
50
|
assertRange(view.getUint16(4, true) === FRAME_PROTOCOL_VERSION, "unsupported protocol version");
|
|
48
51
|
const sectionCount = view.getUint16(60, true);
|
|
49
52
|
const tableEnd = FRAME_HEADER_BYTES + sectionCount * SECTION_HEADER_BYTES;
|
|
50
|
-
assertRange(tableEnd <=
|
|
53
|
+
assertRange(tableEnd <= bytes.byteLength, "truncated section table");
|
|
51
54
|
const sections = [];
|
|
52
55
|
for (let index = 0; index < sectionCount; index += 1) {
|
|
53
56
|
const base = FRAME_HEADER_BYTES + index * SECTION_HEADER_BYTES;
|
|
@@ -57,8 +60,8 @@ function decodeFrame(buffer) {
|
|
|
57
60
|
const length = view.getUint32(base + 8, true);
|
|
58
61
|
const itemCount = view.getUint32(base + 12, true);
|
|
59
62
|
const end = offset + length;
|
|
60
|
-
assertRange(end >= offset && offset >= tableEnd && end <=
|
|
61
|
-
sections.push({ kind, flags, itemCount, bytes:
|
|
63
|
+
assertRange(end >= offset && offset >= tableEnd && end <= bytes.byteLength, "section out of bounds");
|
|
64
|
+
sections.push({ kind, flags, itemCount, bytes: bytes.subarray(offset, end) });
|
|
62
65
|
}
|
|
63
66
|
return {
|
|
64
67
|
protocolVersion: view.getUint16(4, true),
|
|
@@ -151,14 +154,14 @@ function decodeGlyphDefinitions(section) {
|
|
|
151
154
|
bearingX: view.getInt16(offset - 12, true),
|
|
152
155
|
bearingY: view.getInt16(offset - 10, true),
|
|
153
156
|
format,
|
|
154
|
-
pixels: section.bytes.
|
|
157
|
+
pixels: new Uint8Array(section.bytes.subarray(offset, offset + pixelLength))
|
|
155
158
|
});
|
|
156
159
|
offset += pixelLength;
|
|
157
160
|
}
|
|
158
161
|
assertRange(offset === section.bytes.byteLength, "trailing glyph definition bytes");
|
|
159
162
|
return definitions;
|
|
160
163
|
}
|
|
161
|
-
function decodeStyleDefinitions(section) {
|
|
164
|
+
function decodeStyleDefinitions(section, paletteSection) {
|
|
162
165
|
assertRange(section.kind === SectionKind.StyleDefinitions, "wrong style definition section kind");
|
|
163
166
|
assertRange(section.bytes.byteLength >= 4, "truncated style definition header");
|
|
164
167
|
const view = new DataView(section.bytes.buffer, section.bytes.byteOffset, section.bytes.byteLength);
|
|
@@ -187,7 +190,39 @@ function decodeStyleDefinitions(section) {
|
|
|
187
190
|
...backgroundKind === 1 ? { background: [view.getUint8(offset + 11), view.getUint8(offset + 12), view.getUint8(offset + 13)] } : {}
|
|
188
191
|
});
|
|
189
192
|
}
|
|
190
|
-
|
|
193
|
+
if (!paletteSection)
|
|
194
|
+
return styles;
|
|
195
|
+
const palette = new Map(decodeStylePalette(paletteSection).map((entry) => [entry.styleId, entry]));
|
|
196
|
+
return styles.map((style) => {
|
|
197
|
+
const entry = palette.get(style.id);
|
|
198
|
+
if (!entry)
|
|
199
|
+
return style;
|
|
200
|
+
return {
|
|
201
|
+
...style,
|
|
202
|
+
...entry.foreground !== void 0 && style.foreground ? { foregroundPalette: entry.foreground } : {},
|
|
203
|
+
...entry.background !== void 0 && style.background ? { backgroundPalette: entry.background } : {}
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function decodeStylePalette(section) {
|
|
208
|
+
assertRange(section.kind === SectionKind.StylePalette, "wrong style palette section kind");
|
|
209
|
+
assertRange(section.bytes.byteLength >= 4, "truncated style palette header");
|
|
210
|
+
const view = new DataView(section.bytes.buffer, section.bytes.byteOffset, section.bytes.byteLength);
|
|
211
|
+
const count = view.getUint32(0, true);
|
|
212
|
+
assertRange(count === section.itemCount, "style palette count mismatch");
|
|
213
|
+
assertRange(section.bytes.byteLength === 4 + count * 8, "invalid style palette length");
|
|
214
|
+
const entries = [];
|
|
215
|
+
for (let index = 0; index < count; index += 1) {
|
|
216
|
+
const offset = 4 + index * 8;
|
|
217
|
+
const flags = view.getUint8(offset + 4);
|
|
218
|
+
assertRange((flags & ~3) === 0, "invalid style palette flags");
|
|
219
|
+
entries.push({
|
|
220
|
+
styleId: view.getUint32(offset, true),
|
|
221
|
+
...flags & 1 ? { foreground: view.getUint8(offset + 5) } : {},
|
|
222
|
+
...flags & 2 ? { background: view.getUint8(offset + 6) } : {}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return entries;
|
|
191
226
|
}
|
|
192
227
|
function decodeCursorState(section) {
|
|
193
228
|
assertRange(section.kind === SectionKind.CursorState, "wrong cursor section kind");
|
|
@@ -230,6 +265,39 @@ function decodeClipboardWrite(section) {
|
|
|
230
265
|
assertRange(length === section.bytes.byteLength - 4, "clipboard length mismatch");
|
|
231
266
|
return new TextDecoder("utf-8", { fatal: true }).decode(section.bytes.subarray(4));
|
|
232
267
|
}
|
|
268
|
+
function decodeLinkTargets(section, cols, rows) {
|
|
269
|
+
const bytes = section.bytes;
|
|
270
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
271
|
+
assertRange(bytes.length >= 4, "truncated link count");
|
|
272
|
+
const count = view.getUint32(0, true);
|
|
273
|
+
assertRange(count === section.itemCount && count <= cols * rows, "invalid link count");
|
|
274
|
+
const links = [];
|
|
275
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
276
|
+
let offset = 4;
|
|
277
|
+
for (let index = 0; index < count; index++) {
|
|
278
|
+
assertRange(offset + 8 <= bytes.length, "truncated link header");
|
|
279
|
+
const length = view.getUint32(offset, true);
|
|
280
|
+
const spanCount = view.getUint16(offset + 4, true);
|
|
281
|
+
const flags = view.getUint16(offset + 6, true);
|
|
282
|
+
offset += 8;
|
|
283
|
+
assertRange(length > 0 && length <= 8192 && spanCount > 0 && spanCount <= rows && flags <= 1, "invalid link header");
|
|
284
|
+
assertRange(offset + length + spanCount * 6 <= bytes.length, "truncated link target");
|
|
285
|
+
const uri = decoder.decode(bytes.subarray(offset, offset + length));
|
|
286
|
+
offset += length;
|
|
287
|
+
const spans = [];
|
|
288
|
+
for (let spanIndex = 0; spanIndex < spanCount; spanIndex++) {
|
|
289
|
+
const row = view.getUint16(offset, true);
|
|
290
|
+
const startColumn = view.getUint16(offset + 2, true);
|
|
291
|
+
const endColumn = view.getUint16(offset + 4, true);
|
|
292
|
+
assertRange(row < rows && startColumn < endColumn && endColumn <= cols, "link span exceeds viewport");
|
|
293
|
+
spans.push({ row, startColumn, endColumn });
|
|
294
|
+
offset += 6;
|
|
295
|
+
}
|
|
296
|
+
links.push({ uri, explicit: flags === 1, spans });
|
|
297
|
+
}
|
|
298
|
+
assertRange(offset === bytes.length, "trailing link data");
|
|
299
|
+
return links;
|
|
300
|
+
}
|
|
233
301
|
|
|
234
302
|
// src/performance.ts
|
|
235
303
|
function emptyRenderMetrics() {
|
|
@@ -422,6 +490,18 @@ function graphemeCellWidth(grapheme) {
|
|
|
422
490
|
return /\p{Extended_Pictographic}/u.test(grapheme) || codepoint >= 4352 && codepoint <= 4447 || codepoint >= 11904 && codepoint <= 42191 || codepoint >= 44032 && codepoint <= 55203 || codepoint >= 63744 && codepoint <= 64255 || codepoint >= 65040 && codepoint <= 65135 || codepoint >= 65280 && codepoint <= 65376 || codepoint >= 127744 && codepoint <= 129791 || codepoint >= 131072 && codepoint <= 262141 ? 2 : 1;
|
|
423
491
|
}
|
|
424
492
|
|
|
493
|
+
// src/renderers/background-geometry.ts
|
|
494
|
+
function backgroundRunBounds(run, row, cols, rows, scale, viewportWidth, viewportHeight) {
|
|
495
|
+
if (cols <= 0 || row < 0 || row >= rows || run.cellSpan <= 0 || run.cellStart >= cols) return null;
|
|
496
|
+
const end = run.cellStart + run.cellSpan;
|
|
497
|
+
const x = run.cellStart === 0 ? 0 : (ORIGIN_X + run.cellStart * CELL_WIDTH) * scale;
|
|
498
|
+
const y = row === 0 ? 0 : (ORIGIN_Y + row * LINE_HEIGHT) * scale;
|
|
499
|
+
const right = end >= cols ? viewportWidth : Math.min(viewportWidth, (ORIGIN_X + end * CELL_WIDTH) * scale);
|
|
500
|
+
const bottom = row === rows - 1 ? viewportHeight : Math.min(viewportHeight, (ORIGIN_Y + (row + 1) * LINE_HEIGHT) * scale);
|
|
501
|
+
if (right <= x || bottom <= y) return null;
|
|
502
|
+
return { x, y, width: right - x, height: bottom - y };
|
|
503
|
+
}
|
|
504
|
+
|
|
425
505
|
// src/renderers/render-damage.ts
|
|
426
506
|
function rowsForDamage(rowCount, damage, sceneValid) {
|
|
427
507
|
const full = !sceneValid || !damage || damage.full;
|
|
@@ -642,8 +722,418 @@ fn sparks(position: vec2f, uv: vec2f) -> vec4f {
|
|
|
642
722
|
`
|
|
643
723
|
);
|
|
644
724
|
|
|
725
|
+
// src/renderers/light-adaptation.ts
|
|
726
|
+
var clamp = (value, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, value));
|
|
727
|
+
var toLinear = (v) => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
728
|
+
var toGamma = (v) => v <= 31308e-7 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - 0.055;
|
|
729
|
+
function oklab([r8, g8, b8]) {
|
|
730
|
+
const r = toLinear(r8 / 255);
|
|
731
|
+
const g = toLinear(g8 / 255);
|
|
732
|
+
const b = toLinear(b8 / 255);
|
|
733
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
734
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
735
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
736
|
+
return [
|
|
737
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
738
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
739
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
|
|
740
|
+
];
|
|
741
|
+
}
|
|
742
|
+
function oklabToLinear([L, a, b]) {
|
|
743
|
+
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
744
|
+
const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
745
|
+
const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
746
|
+
return [
|
|
747
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
748
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
749
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
750
|
+
];
|
|
751
|
+
}
|
|
752
|
+
var inGamut = (linear) => linear.every((v) => v >= -1e-4 && v <= 1 + 1e-4);
|
|
753
|
+
var fromLch = (L, C, h) => [L, C * Math.cos(h), C * Math.sin(h)];
|
|
754
|
+
var toRgb = (lab) => oklabToLinear(lab).map((v) => Math.round(clamp(toGamma(clamp(v))) * 255));
|
|
755
|
+
function gamutMap(L, C, h) {
|
|
756
|
+
L = clamp(L);
|
|
757
|
+
if (inGamut(oklabToLinear(fromLch(L, C, h)))) return toRgb(fromLch(L, C, h));
|
|
758
|
+
let lo = 0;
|
|
759
|
+
let hi = C;
|
|
760
|
+
for (let i = 0; i < 20; i += 1) {
|
|
761
|
+
const mid = (lo + hi) / 2;
|
|
762
|
+
if (inGamut(oklabToLinear(fromLch(L, mid, h)))) lo = mid;
|
|
763
|
+
else hi = mid;
|
|
764
|
+
}
|
|
765
|
+
return toRgb(fromLch(L, lo, h));
|
|
766
|
+
}
|
|
767
|
+
function maxChroma(L, h) {
|
|
768
|
+
let lo = 0;
|
|
769
|
+
let hi = 0.4;
|
|
770
|
+
for (let i = 0; i < 16; i += 1) {
|
|
771
|
+
const mid = (lo + hi) / 2;
|
|
772
|
+
if (inGamut(oklabToLinear(fromLch(L, mid, h)))) lo = mid;
|
|
773
|
+
else hi = mid;
|
|
774
|
+
}
|
|
775
|
+
return lo;
|
|
776
|
+
}
|
|
777
|
+
var K1 = 0.206;
|
|
778
|
+
var K2 = 0.03;
|
|
779
|
+
var K3 = (1 + K1) / (1 + K2);
|
|
780
|
+
var toe = (L) => 0.5 * (K3 * L - K1 + Math.sqrt((K3 * L - K1) ** 2 + 4 * K2 * K3 * L));
|
|
781
|
+
var toeInv = (Lr) => (Lr * Lr + K1 * Lr) / (K3 * (Lr + K2));
|
|
782
|
+
function apcaY([r, g, b]) {
|
|
783
|
+
const y = 0.2126729 * (r / 255) ** 2.4 + 0.7151522 * (g / 255) ** 2.4 + 0.072175 * (b / 255) ** 2.4;
|
|
784
|
+
return y < 0.022 ? y + (0.022 - y) ** 1.414 : y;
|
|
785
|
+
}
|
|
786
|
+
function apca(text, background) {
|
|
787
|
+
const yt = apcaY(text);
|
|
788
|
+
const yb = apcaY(background);
|
|
789
|
+
if (Math.abs(yb - yt) < 5e-4) return 0;
|
|
790
|
+
if (yb > yt) {
|
|
791
|
+
const sapc2 = (yb ** 0.56 - yt ** 0.57) * 1.14;
|
|
792
|
+
return sapc2 < 0.1 ? 0 : (sapc2 - 0.027) * 100;
|
|
793
|
+
}
|
|
794
|
+
const sapc = (yb ** 0.65 - yt ** 0.62) * 1.14;
|
|
795
|
+
return sapc > -0.1 ? 0 : (sapc + 0.027) * 100;
|
|
796
|
+
}
|
|
797
|
+
var smoothstep = (e0, e1, x) => {
|
|
798
|
+
const t = clamp((x - e0) / (e1 - e0));
|
|
799
|
+
return t * t * (3 - 2 * t);
|
|
800
|
+
};
|
|
801
|
+
var toRgba = ([r, g, b]) => [r / 255, g / 255, b / 255, 1];
|
|
802
|
+
var fromRgba = (color) => [
|
|
803
|
+
Math.round(clamp(color[0]) * 255),
|
|
804
|
+
Math.round(clamp(color[1]) * 255),
|
|
805
|
+
Math.round(clamp(color[2]) * 255)
|
|
806
|
+
];
|
|
807
|
+
var keyOf = ([r, g, b]) => r << 16 | g << 8 | b;
|
|
808
|
+
var luminance = ([r, g, b]) => 0.2126 * toLinear(r / 255) + 0.7152 * toLinear(g / 255) + 0.0722 * toLinear(b / 255);
|
|
809
|
+
var contrastRatio = (a, b) => {
|
|
810
|
+
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
|
|
811
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
812
|
+
};
|
|
813
|
+
function isLightBackground(color) {
|
|
814
|
+
const y = luminance(color);
|
|
815
|
+
return (y + 0.05) / 0.05 > 1.05 / (y + 0.05);
|
|
816
|
+
}
|
|
817
|
+
var contrasted = /* @__PURE__ */ new Map();
|
|
818
|
+
function ensureContrast(text, background, ratio) {
|
|
819
|
+
if (!(ratio > 1)) return text;
|
|
820
|
+
const fg = fromRgba(text);
|
|
821
|
+
const bg = fromRgba(background);
|
|
822
|
+
const cacheKey = `${keyOf(fg)}/${keyOf(bg)}/${ratio}`;
|
|
823
|
+
let result = contrasted.get(cacheKey);
|
|
824
|
+
if (result === void 0) {
|
|
825
|
+
if (contrastRatio(fg, bg) >= ratio) result = null;
|
|
826
|
+
else {
|
|
827
|
+
const towardWhite = contrastRatio([255, 255, 255], bg) > contrastRatio([0, 0, 0], bg);
|
|
828
|
+
const [L, a, b] = oklab(fg);
|
|
829
|
+
const C = Math.hypot(a, b);
|
|
830
|
+
const h = Math.atan2(b, a);
|
|
831
|
+
let near = L;
|
|
832
|
+
let far = towardWhite ? 1 : 0;
|
|
833
|
+
let best = towardWhite ? [255, 255, 255] : [0, 0, 0];
|
|
834
|
+
if (contrastRatio(best, bg) >= ratio) {
|
|
835
|
+
for (let i = 0; i < 20; i += 1) {
|
|
836
|
+
const mid = (near + far) / 2;
|
|
837
|
+
const candidate = gamutMap(mid, C, h);
|
|
838
|
+
if (contrastRatio(candidate, bg) >= ratio) {
|
|
839
|
+
far = mid;
|
|
840
|
+
best = candidate;
|
|
841
|
+
} else near = mid;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
result = best;
|
|
845
|
+
}
|
|
846
|
+
if (contrasted.size > 4096) contrasted.clear();
|
|
847
|
+
contrasted.set(cacheKey, result);
|
|
848
|
+
}
|
|
849
|
+
if (result === null) return text;
|
|
850
|
+
return [result[0] / 255, result[1] / 255, result[2] / 255, text[3]];
|
|
851
|
+
}
|
|
852
|
+
function createLightAdaptation(base, paperColor, inkColor) {
|
|
853
|
+
const paper = fromRgba(paperColor);
|
|
854
|
+
const inkRgb = fromRgba(inkColor);
|
|
855
|
+
const baseLab = oklab(base);
|
|
856
|
+
const paperLab = oklab(paper);
|
|
857
|
+
const inkLab = oklab(inkRgb);
|
|
858
|
+
const baseLr = toe(baseLab[0]);
|
|
859
|
+
const paperLr = toe(paperLab[0]);
|
|
860
|
+
const surfaces2 = /* @__PURE__ */ new Map();
|
|
861
|
+
const surfaceRgb = (color) => {
|
|
862
|
+
const cached = surfaces2.get(keyOf(color));
|
|
863
|
+
if (cached) return cached;
|
|
864
|
+
const [L, a, b] = oklab(color);
|
|
865
|
+
let Lr = clamp(paperLr - (toe(L) - baseLr));
|
|
866
|
+
const ap = a - baseLab[1] + paperLab[1];
|
|
867
|
+
const bp = b - baseLab[2] + paperLab[2];
|
|
868
|
+
const C = Math.hypot(ap, bp);
|
|
869
|
+
const h = Math.atan2(bp, ap);
|
|
870
|
+
if (C > 0.015) {
|
|
871
|
+
const want = Math.min(C, 0.045);
|
|
872
|
+
const stop = Lr - 0.15;
|
|
873
|
+
while (Lr > stop && maxChroma(toeInv(Lr), h) < want) Lr -= 5e-3;
|
|
874
|
+
}
|
|
875
|
+
const result = gamutMap(toeInv(Lr), C, h);
|
|
876
|
+
surfaces2.set(keyOf(color), result);
|
|
877
|
+
return result;
|
|
878
|
+
};
|
|
879
|
+
const solveLc = (want, C, h, background) => {
|
|
880
|
+
const backgroundL = oklab(background)[0];
|
|
881
|
+
let lo = want > 0 ? 0 : backgroundL;
|
|
882
|
+
let hi = want > 0 ? backgroundL : 1;
|
|
883
|
+
let best = gamutMap(want > 0 ? 0 : 1, C, h);
|
|
884
|
+
for (let i = 0; i < 18; i += 1) {
|
|
885
|
+
const mid = (lo + hi) / 2;
|
|
886
|
+
const candidate = gamutMap(mid, C, h);
|
|
887
|
+
const lc = apca(candidate, background);
|
|
888
|
+
const enough = want > 0 ? lc >= want : lc <= want;
|
|
889
|
+
if (enough) best = candidate;
|
|
890
|
+
if (want > 0 === enough) lo = mid;
|
|
891
|
+
else hi = mid;
|
|
892
|
+
}
|
|
893
|
+
return best;
|
|
894
|
+
};
|
|
895
|
+
const inks = /* @__PURE__ */ new Map();
|
|
896
|
+
const ink = (color, sourceBackground, paletteIndex) => {
|
|
897
|
+
const themeColor = paletteIndex !== void 0 && CHROMATIC_ANSI.has(paletteIndex);
|
|
898
|
+
const cacheKey = `${keyOf(color)}/${keyOf(sourceBackground)}${themeColor ? "/p" : ""}`;
|
|
899
|
+
const cached = inks.get(cacheKey);
|
|
900
|
+
if (cached) return cached;
|
|
901
|
+
const mapped = surfaceRgb(sourceBackground);
|
|
902
|
+
if (themeColor) {
|
|
903
|
+
const legibility = Math.min(45, Math.abs(apca(color, paper)));
|
|
904
|
+
if (Math.abs(apca(color, mapped)) >= legibility) {
|
|
905
|
+
const rgba2 = toRgba(color);
|
|
906
|
+
inks.set(cacheKey, rgba2);
|
|
907
|
+
return rgba2;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const [L, a0, b0] = oklab(color);
|
|
911
|
+
const C0 = Math.hypot(a0, b0);
|
|
912
|
+
let a = a0;
|
|
913
|
+
let b = b0;
|
|
914
|
+
if (C0 < 0.03) {
|
|
915
|
+
const w2 = (1 - C0 / 0.03) * 0.7;
|
|
916
|
+
a = a0 * (1 - w2) + inkLab[1] * w2;
|
|
917
|
+
b = b0 * (1 - w2) + inkLab[2] * w2;
|
|
918
|
+
}
|
|
919
|
+
const C = Math.hypot(a, b);
|
|
920
|
+
const h = Math.atan2(b, a);
|
|
921
|
+
const delta = toe(L) - toe(oklab(sourceBackground)[0]);
|
|
922
|
+
const sign = delta >= 0 ? -1 : 1;
|
|
923
|
+
const Lr = clamp(toe(oklab(mapped)[0]) + sign * Math.max(Math.abs(delta), 0.06));
|
|
924
|
+
let result = gamutMap(toeInv(Lr), C, h);
|
|
925
|
+
const w = smoothstep(0.03, 0.08, C0);
|
|
926
|
+
if (w > 0) {
|
|
927
|
+
const source = apca(color, sourceBackground);
|
|
928
|
+
const lightOnDark = source < 0 || source === 0 && delta >= 0;
|
|
929
|
+
const magnitude = clamp(Math.abs(source) * 0.5 + 28, 45, 70);
|
|
930
|
+
const boosted = C0 + w * clamp(0.12 - C0, 0, C0 * 0.8);
|
|
931
|
+
const accent = solveLc(lightOnDark ? magnitude : -magnitude, boosted, Math.atan2(b0, a0), mapped);
|
|
932
|
+
if (w >= 1) result = accent;
|
|
933
|
+
else {
|
|
934
|
+
const p = oklab(result);
|
|
935
|
+
const q = oklab(accent);
|
|
936
|
+
result = toRgb([p[0] * (1 - w) + q[0] * w, p[1] * (1 - w) + q[1] * w, p[2] * (1 - w) + q[2] * w]);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
const rgba = toRgba(result);
|
|
940
|
+
inks.set(cacheKey, rgba);
|
|
941
|
+
return rgba;
|
|
942
|
+
};
|
|
943
|
+
return {
|
|
944
|
+
key: [base, paper, inkRgb].map((color) => keyOf(color).toString(16)).join("/"),
|
|
945
|
+
base,
|
|
946
|
+
surface: (color) => toRgba(surfaceRgb(color)),
|
|
947
|
+
ink
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
var CHROMATIC_ANSI = /* @__PURE__ */ new Set([1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14]);
|
|
951
|
+
var ENGAGE_SHARE = 0.6;
|
|
952
|
+
var RELEASE_SHARE = 0.35;
|
|
953
|
+
function darkPaintedBase(view, engaged) {
|
|
954
|
+
if ((view.theme.lightAdaptation ?? "off") === "off") return null;
|
|
955
|
+
if (!isLightBackground(fromRgba(view.theme.background))) return null;
|
|
956
|
+
const rows = Math.max(view.rows.length, view.nativeStyleRows.length);
|
|
957
|
+
const total = rows * view.cols;
|
|
958
|
+
if (total === 0) return null;
|
|
959
|
+
const areas = /* @__PURE__ */ new Map();
|
|
960
|
+
let dark = 0;
|
|
961
|
+
const darkness = /* @__PURE__ */ new Map();
|
|
962
|
+
for (const runs of view.nativeStyleRows) {
|
|
963
|
+
for (const run of runs) {
|
|
964
|
+
const style = view.styleDefinitions.get(run.styleId);
|
|
965
|
+
const background = style?.inverse ? style.foreground : style?.background;
|
|
966
|
+
if (!background) continue;
|
|
967
|
+
const key = keyOf(background);
|
|
968
|
+
let isDark = darkness.get(key);
|
|
969
|
+
if (isDark === void 0) {
|
|
970
|
+
isDark = !isLightBackground(background);
|
|
971
|
+
darkness.set(key, isDark);
|
|
972
|
+
}
|
|
973
|
+
if (!isDark) continue;
|
|
974
|
+
dark += run.cellSpan;
|
|
975
|
+
areas.set(key, (areas.get(key) ?? 0) + run.cellSpan);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (dark / total < (engaged ? RELEASE_SHARE : ENGAGE_SHARE)) return null;
|
|
979
|
+
let bestKey = 0;
|
|
980
|
+
let bestArea = -1;
|
|
981
|
+
for (const [key, area] of areas) {
|
|
982
|
+
if (area > bestArea) {
|
|
983
|
+
bestKey = key;
|
|
984
|
+
bestArea = area;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return [bestKey >> 16 & 255, bestKey >> 8 & 255, bestKey & 255];
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// src/renderers/glyph-atlas.ts
|
|
991
|
+
var AtlasCapacityError = class extends Error {
|
|
992
|
+
};
|
|
993
|
+
var AtlasBatchFullError = class extends Error {
|
|
994
|
+
};
|
|
995
|
+
var GlyphAtlasPages = class {
|
|
996
|
+
constructor(device, layout, label, format, maxPages, size = 2048) {
|
|
997
|
+
this.device = device;
|
|
998
|
+
this.layout = layout;
|
|
999
|
+
this.label = label;
|
|
1000
|
+
this.format = format;
|
|
1001
|
+
this.maxPages = maxPages;
|
|
1002
|
+
this.size = size;
|
|
1003
|
+
this.#sampler = device.createSampler({ minFilter: "linear", magFilter: "linear" });
|
|
1004
|
+
}
|
|
1005
|
+
#pages = [];
|
|
1006
|
+
#sampler;
|
|
1007
|
+
#current;
|
|
1008
|
+
#generation = 0;
|
|
1009
|
+
#clock = 0;
|
|
1010
|
+
get generation() {
|
|
1011
|
+
return this.#current?.generation ?? 0;
|
|
1012
|
+
}
|
|
1013
|
+
get bindGroup() {
|
|
1014
|
+
if (!this.#current) throw new Error(`${this.label} has not been prepared`);
|
|
1015
|
+
return this.#current.bindGroup;
|
|
1016
|
+
}
|
|
1017
|
+
prepare(input) {
|
|
1018
|
+
const glyphs = /* @__PURE__ */ new Map();
|
|
1019
|
+
for (const glyph of input) glyphs.set(glyph.key, glyph);
|
|
1020
|
+
if (glyphs.size === 0) return;
|
|
1021
|
+
let selected;
|
|
1022
|
+
let selectedPlan;
|
|
1023
|
+
let bestHits = -1;
|
|
1024
|
+
for (const page of this.#pages) {
|
|
1025
|
+
const plan2 = this.#plan(glyphs.values(), page);
|
|
1026
|
+
if (!plan2) continue;
|
|
1027
|
+
let hits = 0;
|
|
1028
|
+
for (const key of glyphs.keys()) if (page.cache.has(key)) hits += 1;
|
|
1029
|
+
if (hits > bestHits || hits === bestHits && page.used > (selected?.used ?? -1)) {
|
|
1030
|
+
selected = page;
|
|
1031
|
+
selectedPlan = plan2;
|
|
1032
|
+
bestHits = hits;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
if (!selected) {
|
|
1036
|
+
selectedPlan = this.#plan(glyphs.values());
|
|
1037
|
+
if (!selectedPlan) {
|
|
1038
|
+
throw new AtlasCapacityError(`${this.label} cannot fit the glyphs visible in one frame`);
|
|
1039
|
+
}
|
|
1040
|
+
if (this.#pages.length === this.maxPages) {
|
|
1041
|
+
const recyclable = this.#pages.filter((page) => !page.pinned).sort((a, b) => a.used - b.used)[0];
|
|
1042
|
+
if (!recyclable) throw new AtlasBatchFullError(this.label);
|
|
1043
|
+
recyclable.texture.destroy();
|
|
1044
|
+
this.#pages.splice(this.#pages.indexOf(recyclable), 1);
|
|
1045
|
+
}
|
|
1046
|
+
const texture = this.device.createTexture({
|
|
1047
|
+
label: this.label,
|
|
1048
|
+
size: [this.size, this.size],
|
|
1049
|
+
format: this.format,
|
|
1050
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
|
|
1051
|
+
});
|
|
1052
|
+
selected = {
|
|
1053
|
+
texture,
|
|
1054
|
+
bindGroup: this.device.createBindGroup({
|
|
1055
|
+
label: `${this.label} bindings`,
|
|
1056
|
+
layout: this.layout,
|
|
1057
|
+
entries: [
|
|
1058
|
+
{ binding: 0, resource: texture.createView() },
|
|
1059
|
+
{ binding: 1, resource: this.#sampler }
|
|
1060
|
+
]
|
|
1061
|
+
}),
|
|
1062
|
+
cache: /* @__PURE__ */ new Map(),
|
|
1063
|
+
slots: /* @__PURE__ */ new Map(),
|
|
1064
|
+
generation: ++this.#generation,
|
|
1065
|
+
used: 0,
|
|
1066
|
+
pinned: false,
|
|
1067
|
+
x: 1,
|
|
1068
|
+
y: 1,
|
|
1069
|
+
rowHeight: 0
|
|
1070
|
+
};
|
|
1071
|
+
this.#pages.push(selected);
|
|
1072
|
+
}
|
|
1073
|
+
const plan = selectedPlan;
|
|
1074
|
+
for (const [key, slot] of plan.slots) selected.slots.set(key, slot);
|
|
1075
|
+
selected.x = plan.x;
|
|
1076
|
+
selected.y = plan.y;
|
|
1077
|
+
selected.rowHeight = plan.rowHeight;
|
|
1078
|
+
selected.used = ++this.#clock;
|
|
1079
|
+
this.#current = selected;
|
|
1080
|
+
}
|
|
1081
|
+
pin() {
|
|
1082
|
+
if (this.#current) this.#current.pinned = true;
|
|
1083
|
+
}
|
|
1084
|
+
#plan(glyphs, page) {
|
|
1085
|
+
const slots = /* @__PURE__ */ new Map();
|
|
1086
|
+
let x = page?.x ?? 1;
|
|
1087
|
+
let y = page?.y ?? 1;
|
|
1088
|
+
let rowHeight = page?.rowHeight ?? 0;
|
|
1089
|
+
for (const { key, width, height } of glyphs) {
|
|
1090
|
+
if (page?.slots.has(key)) continue;
|
|
1091
|
+
if (width + 2 > this.size || height + 2 > this.size) return void 0;
|
|
1092
|
+
if (x + width + 1 > this.size) {
|
|
1093
|
+
x = 1;
|
|
1094
|
+
y += rowHeight + 1;
|
|
1095
|
+
rowHeight = 0;
|
|
1096
|
+
}
|
|
1097
|
+
if (y + height + 1 > this.size) return void 0;
|
|
1098
|
+
slots.set(key, { origin: [x, y], width, height });
|
|
1099
|
+
x += width + 1;
|
|
1100
|
+
rowHeight = Math.max(rowHeight, height);
|
|
1101
|
+
}
|
|
1102
|
+
return { slots, x, y, rowHeight };
|
|
1103
|
+
}
|
|
1104
|
+
get(key) {
|
|
1105
|
+
return this.#current?.cache.get(key);
|
|
1106
|
+
}
|
|
1107
|
+
insert(key, width, height, cells) {
|
|
1108
|
+
const page = this.#current;
|
|
1109
|
+
if (!page) throw new Error(`${this.label} has not been prepared`);
|
|
1110
|
+
const slot = page.slots.get(key);
|
|
1111
|
+
if (!slot || slot.width !== width || slot.height !== height) {
|
|
1112
|
+
throw new Error(`${this.label} glyph does not match its prepared reservation`);
|
|
1113
|
+
}
|
|
1114
|
+
const origin = slot.origin;
|
|
1115
|
+
const [x, y] = origin;
|
|
1116
|
+
const location = {
|
|
1117
|
+
u0: x / this.size,
|
|
1118
|
+
v0: y / this.size,
|
|
1119
|
+
u1: (x + width) / this.size,
|
|
1120
|
+
v1: (y + height) / this.size,
|
|
1121
|
+
cells
|
|
1122
|
+
};
|
|
1123
|
+
page.cache.set(key, location);
|
|
1124
|
+
return { location, texture: page.texture, origin };
|
|
1125
|
+
}
|
|
1126
|
+
releaseBatch() {
|
|
1127
|
+
for (const page of this.#pages) page.pinned = false;
|
|
1128
|
+
}
|
|
1129
|
+
destroy() {
|
|
1130
|
+
for (const page of this.#pages) page.texture.destroy();
|
|
1131
|
+
this.#pages.length = 0;
|
|
1132
|
+
this.#current = void 0;
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
|
|
645
1136
|
// src/renderers/webgpu-renderer.ts
|
|
646
|
-
var ATLAS_SIZE = 2048;
|
|
647
1137
|
var GEOMETRY_CACHE_LIMIT = 8;
|
|
648
1138
|
var FONT_STACK = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
|
|
649
1139
|
var EFFECT_MODES = {
|
|
@@ -824,88 +1314,37 @@ var DynamicVertexBuffer = class {
|
|
|
824
1314
|
var FallbackGlyphAtlas = class {
|
|
825
1315
|
constructor(device, bindGroupLayout) {
|
|
826
1316
|
this.device = device;
|
|
827
|
-
this
|
|
828
|
-
label: "terminal glyph atlas",
|
|
829
|
-
size: [ATLAS_SIZE, ATLAS_SIZE],
|
|
830
|
-
format: "rgba8unorm",
|
|
831
|
-
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
|
|
832
|
-
});
|
|
833
|
-
this.#sampler = device.createSampler({ minFilter: "linear", magFilter: "linear" });
|
|
834
|
-
this.#bindGroup = device.createBindGroup({
|
|
835
|
-
label: "terminal glyph atlas bindings",
|
|
836
|
-
layout: bindGroupLayout,
|
|
837
|
-
entries: [
|
|
838
|
-
{ binding: 0, resource: this.#texture.createView() },
|
|
839
|
-
{ binding: 1, resource: this.#sampler }
|
|
840
|
-
]
|
|
841
|
-
});
|
|
1317
|
+
this.pages = new GlyphAtlasPages(device, bindGroupLayout, "terminal diagnostic glyph atlas", "rgba8unorm", 1);
|
|
842
1318
|
}
|
|
843
|
-
|
|
844
|
-
#sampler;
|
|
845
|
-
#bindGroup;
|
|
846
|
-
#cache = /* @__PURE__ */ new Map();
|
|
1319
|
+
pages;
|
|
847
1320
|
#scratch = new OffscreenCanvas(1, 1);
|
|
848
|
-
#x = 1;
|
|
849
|
-
#y = 1;
|
|
850
|
-
#rowHeight = 0;
|
|
851
1321
|
#uploadBytes = 0;
|
|
852
1322
|
#uploadCalls = 0;
|
|
853
|
-
#generation = 0;
|
|
854
1323
|
get bindGroup() {
|
|
855
|
-
return this
|
|
1324
|
+
return this.pages.bindGroup;
|
|
856
1325
|
}
|
|
857
1326
|
get generation() {
|
|
858
|
-
return this
|
|
1327
|
+
return this.pages.generation;
|
|
859
1328
|
}
|
|
860
1329
|
prepare(values, dpr) {
|
|
861
1330
|
const scale = Math.max(1, Math.min(3, dpr));
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
this.#rowHeight = 0;
|
|
870
|
-
if (!this.#fits(all, scale, this.#x, this.#y, this.#rowHeight)) {
|
|
871
|
-
throw new Error("The diagnostic glyph atlas cannot fit the text visible in one frame");
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
#fits(values, scale, startX, startY, startRowHeight) {
|
|
875
|
-
let x = startX;
|
|
876
|
-
let y = startY;
|
|
877
|
-
let rowHeight = startRowHeight;
|
|
878
|
-
for (const value of values) {
|
|
879
|
-
const width = Math.max(1, Math.ceil(CELL_WIDTH * graphemeCellWidth(value) * scale));
|
|
880
|
-
const height = Math.max(1, Math.ceil(LINE_HEIGHT * scale));
|
|
881
|
-
if (x + width + 1 > ATLAS_SIZE) {
|
|
882
|
-
x = 1;
|
|
883
|
-
y += rowHeight + 1;
|
|
884
|
-
rowHeight = 0;
|
|
885
|
-
}
|
|
886
|
-
if (y + height + 1 > ATLAS_SIZE) return false;
|
|
887
|
-
x += width + 1;
|
|
888
|
-
rowHeight = Math.max(rowHeight, height);
|
|
889
|
-
}
|
|
890
|
-
return true;
|
|
1331
|
+
this.pages.prepare(
|
|
1332
|
+
[...new Set(values)].filter((value) => value.trim().length > 0).map((value) => ({
|
|
1333
|
+
key: `${scale.toFixed(2)}:${value}`,
|
|
1334
|
+
width: Math.max(1, Math.ceil(CELL_WIDTH * graphemeCellWidth(value) * scale)),
|
|
1335
|
+
height: Math.max(1, Math.ceil(LINE_HEIGHT * scale))
|
|
1336
|
+
}))
|
|
1337
|
+
);
|
|
891
1338
|
}
|
|
892
1339
|
glyph(grapheme, dpr) {
|
|
893
1340
|
if (grapheme.trim().length === 0) return void 0;
|
|
894
1341
|
const scale = Math.max(1, Math.min(3, dpr));
|
|
895
1342
|
const cells = graphemeCellWidth(grapheme);
|
|
896
1343
|
const key = `${scale.toFixed(2)}:${grapheme}`;
|
|
897
|
-
const cached = this
|
|
1344
|
+
const cached = this.pages.get(key);
|
|
898
1345
|
if (cached) return cached;
|
|
899
1346
|
const width = Math.max(1, Math.ceil(CELL_WIDTH * cells * scale));
|
|
900
1347
|
const height = Math.max(1, Math.ceil(LINE_HEIGHT * scale));
|
|
901
|
-
if (this.#x + width + 1 > ATLAS_SIZE) {
|
|
902
|
-
this.#x = 1;
|
|
903
|
-
this.#y += this.#rowHeight + 1;
|
|
904
|
-
this.#rowHeight = 0;
|
|
905
|
-
}
|
|
906
|
-
if (this.#y + height + 1 > ATLAS_SIZE) {
|
|
907
|
-
throw new Error("Temporary glyph atlas exhausted");
|
|
908
|
-
}
|
|
909
1348
|
this.#scratch.width = width;
|
|
910
1349
|
this.#scratch.height = height;
|
|
911
1350
|
const context = this.#scratch.getContext("2d", { willReadFrequently: true });
|
|
@@ -916,28 +1355,17 @@ var FallbackGlyphAtlas = class {
|
|
|
916
1355
|
context.textBaseline = "top";
|
|
917
1356
|
context.fillText(grapheme, 0, Math.max(0, Math.floor((height - 15 * scale) / 2)));
|
|
918
1357
|
const image = context.getImageData(0, 0, width, height);
|
|
919
|
-
this.
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
);
|
|
1358
|
+
const { location, texture, origin } = this.pages.insert(key, width, height, cells);
|
|
1359
|
+
this.device.queue.writeTexture({ texture, origin }, image.data, { bytesPerRow: width * 4, rowsPerImage: height }, [
|
|
1360
|
+
width,
|
|
1361
|
+
height
|
|
1362
|
+
]);
|
|
925
1363
|
this.#uploadBytes += image.data.byteLength;
|
|
926
1364
|
this.#uploadCalls += 1;
|
|
927
|
-
const location = {
|
|
928
|
-
u0: this.#x / ATLAS_SIZE,
|
|
929
|
-
v0: this.#y / ATLAS_SIZE,
|
|
930
|
-
u1: (this.#x + width) / ATLAS_SIZE,
|
|
931
|
-
v1: (this.#y + height) / ATLAS_SIZE,
|
|
932
|
-
cells
|
|
933
|
-
};
|
|
934
|
-
this.#cache.set(key, location);
|
|
935
|
-
this.#x += width + 1;
|
|
936
|
-
this.#rowHeight = Math.max(this.#rowHeight, height);
|
|
937
1365
|
return location;
|
|
938
1366
|
}
|
|
939
1367
|
destroy() {
|
|
940
|
-
this
|
|
1368
|
+
this.pages.destroy();
|
|
941
1369
|
}
|
|
942
1370
|
uploadMetrics() {
|
|
943
1371
|
return { bytes: this.#uploadBytes, calls: this.#uploadCalls };
|
|
@@ -948,106 +1376,44 @@ var NativeGlyphAtlas = class {
|
|
|
948
1376
|
this.device = device;
|
|
949
1377
|
this.label = label;
|
|
950
1378
|
this.format = format;
|
|
951
|
-
this
|
|
952
|
-
label,
|
|
953
|
-
size: [ATLAS_SIZE, ATLAS_SIZE],
|
|
954
|
-
format,
|
|
955
|
-
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
|
|
956
|
-
});
|
|
957
|
-
this.#sampler = device.createSampler({ minFilter: "linear", magFilter: "linear" });
|
|
958
|
-
this.#bindGroup = device.createBindGroup({
|
|
959
|
-
label: `${label} bindings`,
|
|
960
|
-
layout: bindGroupLayout,
|
|
961
|
-
entries: [
|
|
962
|
-
{ binding: 0, resource: this.#texture.createView() },
|
|
963
|
-
{ binding: 1, resource: this.#sampler }
|
|
964
|
-
]
|
|
965
|
-
});
|
|
1379
|
+
this.pages = new GlyphAtlasPages(device, bindGroupLayout, label, format, format === "r8unorm" ? 4 : 2);
|
|
966
1380
|
}
|
|
967
|
-
|
|
968
|
-
#sampler;
|
|
969
|
-
#bindGroup;
|
|
970
|
-
#cache = /* @__PURE__ */ new Map();
|
|
971
|
-
#x = 1;
|
|
972
|
-
#y = 1;
|
|
973
|
-
#rowHeight = 0;
|
|
1381
|
+
pages;
|
|
974
1382
|
#uploadBytes = 0;
|
|
975
1383
|
#uploadCalls = 0;
|
|
976
|
-
#generation = 0;
|
|
977
1384
|
get bindGroup() {
|
|
978
|
-
return this
|
|
1385
|
+
return this.pages.bindGroup;
|
|
979
1386
|
}
|
|
980
1387
|
get generation() {
|
|
981
|
-
return this
|
|
1388
|
+
return this.pages.generation;
|
|
982
1389
|
}
|
|
983
1390
|
prepare(definitions) {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
this.#cache.clear();
|
|
992
|
-
this.#generation += 1;
|
|
993
|
-
this.#x = 1;
|
|
994
|
-
this.#y = 1;
|
|
995
|
-
this.#rowHeight = 0;
|
|
996
|
-
if (!this.#fits(all.values(), this.#x, this.#y, this.#rowHeight)) {
|
|
997
|
-
throw new Error(`${this.label} cannot fit the glyphs visible in one frame`);
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
#fits(definitions, startX, startY, startRowHeight) {
|
|
1001
|
-
let x = startX;
|
|
1002
|
-
let y = startY;
|
|
1003
|
-
let rowHeight = startRowHeight;
|
|
1004
|
-
for (const { width, height } of definitions) {
|
|
1005
|
-
if (width + 2 > ATLAS_SIZE || height + 2 > ATLAS_SIZE) return false;
|
|
1006
|
-
if (x + width + 1 > ATLAS_SIZE) {
|
|
1007
|
-
x = 1;
|
|
1008
|
-
y += rowHeight + 1;
|
|
1009
|
-
rowHeight = 0;
|
|
1010
|
-
}
|
|
1011
|
-
if (y + height + 1 > ATLAS_SIZE) return false;
|
|
1012
|
-
x += width + 1;
|
|
1013
|
-
rowHeight = Math.max(rowHeight, height);
|
|
1014
|
-
}
|
|
1015
|
-
return true;
|
|
1391
|
+
this.pages.prepare(
|
|
1392
|
+
Array.from(definitions, (definition) => ({
|
|
1393
|
+
key: definition.id,
|
|
1394
|
+
width: definition.width,
|
|
1395
|
+
height: definition.height
|
|
1396
|
+
}))
|
|
1397
|
+
);
|
|
1016
1398
|
}
|
|
1017
1399
|
glyph(definition) {
|
|
1018
|
-
const cached = this
|
|
1400
|
+
const cached = this.pages.get(definition.id);
|
|
1019
1401
|
if (cached) return cached;
|
|
1020
1402
|
const { width, height } = definition;
|
|
1021
|
-
|
|
1022
|
-
this.#x = 1;
|
|
1023
|
-
this.#y += this.#rowHeight + 1;
|
|
1024
|
-
this.#rowHeight = 0;
|
|
1025
|
-
}
|
|
1026
|
-
if (this.#y + height + 1 > ATLAS_SIZE) throw new Error(`${this.label} exhausted`);
|
|
1027
|
-
const pixels = definition.pixels;
|
|
1403
|
+
const { location, texture, origin } = this.pages.insert(definition.id, width, height, 0);
|
|
1028
1404
|
const bytesPerPixel = this.format === "r8unorm" ? 1 : 4;
|
|
1029
1405
|
this.device.queue.writeTexture(
|
|
1030
|
-
{ texture
|
|
1031
|
-
pixels,
|
|
1406
|
+
{ texture, origin },
|
|
1407
|
+
definition.pixels,
|
|
1032
1408
|
{ bytesPerRow: width * bytesPerPixel, rowsPerImage: height },
|
|
1033
1409
|
[width, height]
|
|
1034
1410
|
);
|
|
1035
|
-
this.#uploadBytes += pixels.byteLength;
|
|
1411
|
+
this.#uploadBytes += definition.pixels.byteLength;
|
|
1036
1412
|
this.#uploadCalls += 1;
|
|
1037
|
-
const location = {
|
|
1038
|
-
u0: this.#x / ATLAS_SIZE,
|
|
1039
|
-
v0: this.#y / ATLAS_SIZE,
|
|
1040
|
-
u1: (this.#x + width) / ATLAS_SIZE,
|
|
1041
|
-
v1: (this.#y + height) / ATLAS_SIZE,
|
|
1042
|
-
cells: 0
|
|
1043
|
-
};
|
|
1044
|
-
this.#cache.set(definition.id, location);
|
|
1045
|
-
this.#x += width + 1;
|
|
1046
|
-
this.#rowHeight = Math.max(this.#rowHeight, height);
|
|
1047
1413
|
return location;
|
|
1048
1414
|
}
|
|
1049
1415
|
destroy() {
|
|
1050
|
-
this
|
|
1416
|
+
this.pages.destroy();
|
|
1051
1417
|
}
|
|
1052
1418
|
uploadMetrics() {
|
|
1053
1419
|
return { bytes: this.#uploadBytes, calls: this.#uploadCalls };
|
|
@@ -1074,11 +1440,12 @@ function createCachedVertexBuffer(device, label, data) {
|
|
|
1074
1440
|
device.queue.writeBuffer(buffer, 0, data);
|
|
1075
1441
|
return buffer;
|
|
1076
1442
|
}
|
|
1077
|
-
function geometryCacheKey(view, rows, hasNativeRows, atlasGenerations) {
|
|
1443
|
+
function geometryCacheKey(view, rows, hasNativeRows, atlasGenerations, adaptationKey) {
|
|
1078
1444
|
const selection = view.selection ? `${view.selection.anchor.row},${view.selection.anchor.column},${view.selection.focus.row},${view.selection.focus.column}` : "-";
|
|
1079
1445
|
return [
|
|
1080
1446
|
view.sessionEpoch.toString(),
|
|
1081
1447
|
view.layoutEpoch.toString(),
|
|
1448
|
+
view.cols,
|
|
1082
1449
|
hasNativeRows ? "native" : "fallback",
|
|
1083
1450
|
rows.map((row) => `${row}:${view.rowRevisions[row]?.toString() ?? "-"}`).join(","),
|
|
1084
1451
|
[
|
|
@@ -1090,6 +1457,8 @@ function geometryCacheKey(view, rows, hasNativeRows, atlasGenerations) {
|
|
|
1090
1457
|
...view.theme.selectionForeground
|
|
1091
1458
|
].join(","),
|
|
1092
1459
|
view.theme.backgroundOpacityCells ? "opacity-cells" : "opaque-cells",
|
|
1460
|
+
view.theme.minimumContrast ?? 1,
|
|
1461
|
+
adaptationKey,
|
|
1093
1462
|
selection,
|
|
1094
1463
|
effectiveCursorStyle(view) === CursorStyle.Block ? `${view.cursor.x},${view.cursor.y}` : "-",
|
|
1095
1464
|
atlasGenerations.join(",")
|
|
@@ -1304,21 +1673,45 @@ function pushBlockCursorBackground(vertices, view, renderRows, scale, viewportWi
|
|
|
1304
1673
|
viewportHeight
|
|
1305
1674
|
);
|
|
1306
1675
|
}
|
|
1307
|
-
function resolveStyle(style, theme) {
|
|
1676
|
+
function resolveStyle(style, theme, adaptation = null) {
|
|
1308
1677
|
let foreground = style?.foreground ? rgb(style.foreground) : theme.foreground;
|
|
1309
1678
|
let background = style?.background ? rgb(style.background) : null;
|
|
1310
|
-
|
|
1679
|
+
let fill = foreground;
|
|
1680
|
+
if (adaptation) {
|
|
1681
|
+
const source = { foreground: style?.foreground, background: style?.background };
|
|
1682
|
+
if (style?.inverse) {
|
|
1683
|
+
background = source.foreground ? adaptation.surface(source.foreground) : theme.foreground;
|
|
1684
|
+
foreground = source.background ? adaptation.ink(source.background, source.foreground ?? adaptation.base, style?.backgroundPalette) : theme.background;
|
|
1685
|
+
fill = foreground;
|
|
1686
|
+
} else {
|
|
1687
|
+
foreground = source.foreground ? adaptation.ink(source.foreground, source.background ?? adaptation.base, style?.foregroundPalette) : theme.foreground;
|
|
1688
|
+
background = source.background ? adaptation.surface(source.background) : null;
|
|
1689
|
+
fill = source.foreground ? adaptation.surface(source.foreground) : theme.foreground;
|
|
1690
|
+
}
|
|
1691
|
+
} else if (style?.inverse) {
|
|
1311
1692
|
const originalForeground = foreground;
|
|
1312
1693
|
foreground = background ?? theme.background;
|
|
1313
1694
|
background = originalForeground;
|
|
1695
|
+
fill = foreground;
|
|
1314
1696
|
}
|
|
1315
1697
|
if (background && theme.backgroundOpacityCells) {
|
|
1316
1698
|
background = [background[0], background[1], background[2], theme.background[3]];
|
|
1317
1699
|
}
|
|
1318
|
-
|
|
1700
|
+
let text = ensureContrast(
|
|
1701
|
+
foreground,
|
|
1702
|
+
over(background ?? theme.background, theme.background),
|
|
1703
|
+
theme.minimumContrast ?? 1
|
|
1704
|
+
);
|
|
1705
|
+
if (style?.faint) {
|
|
1706
|
+
foreground = [foreground[0], foreground[1], foreground[2], foreground[3] * 0.55];
|
|
1707
|
+
fill = [fill[0], fill[1], fill[2], fill[3] * 0.55];
|
|
1708
|
+
text = [text[0], text[1], text[2], text[3] * 0.55];
|
|
1709
|
+
}
|
|
1319
1710
|
return {
|
|
1320
1711
|
foreground,
|
|
1321
1712
|
background,
|
|
1713
|
+
fill,
|
|
1714
|
+
text,
|
|
1322
1715
|
underline: style?.underline ?? false,
|
|
1323
1716
|
strikethrough: style?.strikethrough ?? false,
|
|
1324
1717
|
invisible: style?.invisible ?? false
|
|
@@ -1621,7 +2014,10 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1621
2014
|
#monoAtlas;
|
|
1622
2015
|
#colorAtlas;
|
|
1623
2016
|
#fallbackAtlas;
|
|
2017
|
+
#adaptations = /* @__PURE__ */ new Map();
|
|
1624
2018
|
#performanceMeasurementEnabled = false;
|
|
2019
|
+
#submission = { queueSubmits: 0, surfaceIds: [] };
|
|
2020
|
+
#scrollScratch;
|
|
1625
2021
|
static async create(onLost) {
|
|
1626
2022
|
if (!navigator.gpu) throw new Error("WebGPU is unavailable");
|
|
1627
2023
|
const adapter = await navigator.gpu.requestAdapter();
|
|
@@ -1661,7 +2057,11 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1661
2057
|
cursorBuffer: new DynamicVertexBuffer(this.device, `cursor ${id}`),
|
|
1662
2058
|
geometryCache: /* @__PURE__ */ new Map(),
|
|
1663
2059
|
geometryCandidates: /* @__PURE__ */ new Map(),
|
|
1664
|
-
sceneValid: false
|
|
2060
|
+
sceneValid: false,
|
|
2061
|
+
adaptation: null,
|
|
2062
|
+
sceneHadSelection: false,
|
|
2063
|
+
sceneHadOverhang: false,
|
|
2064
|
+
sceneCursorRow: null
|
|
1665
2065
|
};
|
|
1666
2066
|
this.#surfaces.set(id, surface2);
|
|
1667
2067
|
this.#configure(surface2);
|
|
@@ -1680,6 +2080,10 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1680
2080
|
surface2.cursorBuffer.destroy();
|
|
1681
2081
|
clearGeometryCache(surface2);
|
|
1682
2082
|
this.#surfaces.delete(id);
|
|
2083
|
+
if (this.#surfaces.size === 0) {
|
|
2084
|
+
this.#scrollScratch?.destroy();
|
|
2085
|
+
this.#scrollScratch = void 0;
|
|
2086
|
+
}
|
|
1683
2087
|
}
|
|
1684
2088
|
resize(id, size) {
|
|
1685
2089
|
const surface2 = this.#surfaces.get(id);
|
|
@@ -1708,7 +2112,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1708
2112
|
label: "terminal scene before Ghostty custom shaders",
|
|
1709
2113
|
size: [Math.max(1, width), Math.max(1, height)],
|
|
1710
2114
|
format: this.format,
|
|
1711
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
|
|
2115
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
|
|
1712
2116
|
});
|
|
1713
2117
|
}
|
|
1714
2118
|
#createEffectTexture(width, height, suffix) {
|
|
@@ -1818,32 +2222,186 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1818
2222
|
drawCalls: effectPassCount
|
|
1819
2223
|
};
|
|
1820
2224
|
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Engage, re-key, or release light adaptation for a pane. Any change remaps
|
|
2227
|
+
* every color, so the persistent scene and cached geometry are invalidated.
|
|
2228
|
+
*/
|
|
2229
|
+
#prepareAdaptation(surface2, view) {
|
|
2230
|
+
const base = darkPaintedBase(view, surface2.adaptation !== null);
|
|
2231
|
+
let next = null;
|
|
2232
|
+
if (base) {
|
|
2233
|
+
const key = `${base.join(",")}|${view.theme.background.join(",")}|${view.theme.foreground.join(",")}`;
|
|
2234
|
+
next = this.#adaptations.get(key) ?? null;
|
|
2235
|
+
if (!next) {
|
|
2236
|
+
next = createLightAdaptation(base, view.theme.background, view.theme.foreground);
|
|
2237
|
+
this.#adaptations.set(key, next);
|
|
2238
|
+
if (this.#adaptations.size > 8) this.#adaptations.delete(this.#adaptations.keys().next().value);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
if ((next?.key ?? null) === (surface2.adaptation?.key ?? null)) return;
|
|
2242
|
+
surface2.adaptation = next;
|
|
2243
|
+
surface2.sceneValid = false;
|
|
2244
|
+
clearGeometryCache(surface2);
|
|
2245
|
+
}
|
|
2246
|
+
submission() {
|
|
2247
|
+
return this.#submission;
|
|
2248
|
+
}
|
|
2249
|
+
#releaseAtlasBatch() {
|
|
2250
|
+
this.#monoAtlas.pages.releaseBatch();
|
|
2251
|
+
this.#colorAtlas.pages.releaseBatch();
|
|
2252
|
+
this.#fallbackAtlas.pages.releaseBatch();
|
|
2253
|
+
}
|
|
2254
|
+
#prepareAtlases(view, renderRows, dpr) {
|
|
2255
|
+
const mono = /* @__PURE__ */ new Map();
|
|
2256
|
+
const color = /* @__PURE__ */ new Map();
|
|
2257
|
+
for (const row of renderRows) {
|
|
2258
|
+
for (const glyph of view.nativeRows[row] ?? []) {
|
|
2259
|
+
const definition = view.glyphDefinitions.get(glyph.glyphId);
|
|
2260
|
+
if (definition) (definition.format === GlyphFormat.Alpha8 ? mono : color).set(definition.id, definition);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
this.#monoAtlas.prepare(mono.values());
|
|
2264
|
+
this.#colorAtlas.prepare(color.values());
|
|
2265
|
+
const fallback = !view.nativeRows.some((row) => row.length > 0);
|
|
2266
|
+
if (fallback) {
|
|
2267
|
+
this.#fallbackAtlas.prepare(
|
|
2268
|
+
renderRows.flatMap((row) => splitGraphemes(view.rows[row] ?? "")),
|
|
2269
|
+
dpr
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
if (mono.size > 0) this.#monoAtlas.pages.pin();
|
|
2273
|
+
if (color.size > 0) this.#colorAtlas.pages.pin();
|
|
2274
|
+
if (fallback && renderRows.length > 0) this.#fallbackAtlas.pages.pin();
|
|
2275
|
+
}
|
|
1821
2276
|
render(id, view) {
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
const
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
2277
|
+
return this.renderBatch([{ id, view }])[0];
|
|
2278
|
+
}
|
|
2279
|
+
#canCopyScroll(surface2, view) {
|
|
2280
|
+
const shift = view.damage?.scrollRows ?? 0;
|
|
2281
|
+
const top = ORIGIN_Y * surface2.dpr;
|
|
2282
|
+
const line = LINE_HEIGHT * surface2.dpr;
|
|
2283
|
+
const count = view.rows.length;
|
|
2284
|
+
return surface2.sceneValid && !view.damage?.full && Number.isSafeInteger(shift) && shift !== 0 && Math.abs(shift) < count && !view.selection && !surface2.sceneHadSelection && Number.isInteger(top) && Number.isInteger(line) && top + count * line <= surface2.canvas.height;
|
|
2285
|
+
}
|
|
2286
|
+
#prepareScrollScratch(entries) {
|
|
2287
|
+
let width = 0, height = 0;
|
|
2288
|
+
for (const { id, view } of entries) {
|
|
2289
|
+
const surface2 = this.#surfaces.get(id);
|
|
2290
|
+
if (!surface2 || !this.#canCopyScroll(surface2, view)) continue;
|
|
2291
|
+
width = Math.max(width, surface2.canvas.width);
|
|
2292
|
+
height = Math.max(height, view.rows.length * LINE_HEIGHT * surface2.dpr);
|
|
2293
|
+
}
|
|
2294
|
+
if (width === 0) return false;
|
|
2295
|
+
if (width * height * 4 > 64 * 1024 * 1024) return false;
|
|
2296
|
+
if (!this.#scrollScratch || this.#scrollScratch.width < width || this.#scrollScratch.height < height) {
|
|
2297
|
+
this.#scrollScratch?.destroy();
|
|
2298
|
+
this.#scrollScratch = this.device.createTexture({
|
|
2299
|
+
label: "terminal shared scroll staging",
|
|
2300
|
+
size: [width, height],
|
|
2301
|
+
format: this.format,
|
|
2302
|
+
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2305
|
+
return true;
|
|
2306
|
+
}
|
|
2307
|
+
#copyScroll(surface2, view, encoder) {
|
|
2308
|
+
const dy = view.damage.scrollRows * LINE_HEIGHT * surface2.dpr;
|
|
2309
|
+
const top = ORIGIN_Y * surface2.dpr;
|
|
2310
|
+
const size = [surface2.canvas.width, view.rows.length * LINE_HEIGHT * surface2.dpr - Math.abs(dy)];
|
|
2311
|
+
encoder.copyTextureToTexture(
|
|
2312
|
+
{ texture: surface2.sceneTexture, origin: [0, top + Math.max(0, dy)] },
|
|
2313
|
+
{ texture: this.#scrollScratch },
|
|
2314
|
+
size
|
|
2315
|
+
);
|
|
2316
|
+
encoder.copyTextureToTexture(
|
|
2317
|
+
{ texture: this.#scrollScratch },
|
|
2318
|
+
{ texture: surface2.sceneTexture, origin: [0, top + Math.max(0, -dy)] },
|
|
2319
|
+
size
|
|
2320
|
+
);
|
|
1829
2321
|
}
|
|
1830
2322
|
renderBatch(entries) {
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
const
|
|
2323
|
+
this.#submission = { queueSubmits: 0, surfaceIds: [] };
|
|
2324
|
+
this.#releaseAtlasBatch();
|
|
2325
|
+
const prepared = entries.map(({ id, view }) => {
|
|
2326
|
+
const surface2 = this.#surfaces.get(id);
|
|
2327
|
+
if (surface2) this.#prepareAdaptation(surface2, view);
|
|
2328
|
+
const overhang = view.damage?.geometryChanged === false && surface2?.sceneValid ? surface2.sceneHadOverhang : view.nativeRows.some((row) => row.some((glyph) => glyph.y < 0 || glyph.y + glyph.height > LINE_HEIGHT));
|
|
2329
|
+
if (view.damage && !view.damage.full && (view.damage.scrollRows || view.damage.rows.size > 0) && (overhang || surface2?.sceneHadOverhang)) {
|
|
2330
|
+
view = { ...view, damage: { full: true, rows: /* @__PURE__ */ new Set(), geometryChanged: true } };
|
|
2331
|
+
}
|
|
2332
|
+
return { id, view, overhang };
|
|
2333
|
+
});
|
|
2334
|
+
const canStageScroll = this.#prepareScrollScratch(prepared);
|
|
2335
|
+
let encoder;
|
|
2336
|
+
let pendingIds = [];
|
|
1834
2337
|
const byId = /* @__PURE__ */ new Map();
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2338
|
+
const submit = () => {
|
|
2339
|
+
if (encoder && pendingIds.length > 0) {
|
|
2340
|
+
this.device.queue.submit([encoder.finish()]);
|
|
2341
|
+
this.#submission.queueSubmits += 1;
|
|
2342
|
+
this.#submission.surfaceIds.push(...pendingIds);
|
|
2343
|
+
const first = pendingIds.map((id) => byId.get(id)).find((metric) => metric !== void 0);
|
|
2344
|
+
if (first) first.queueSubmits += 1;
|
|
2345
|
+
}
|
|
2346
|
+
encoder = void 0;
|
|
2347
|
+
pendingIds = [];
|
|
2348
|
+
this.#releaseAtlasBatch();
|
|
2349
|
+
};
|
|
2350
|
+
try {
|
|
2351
|
+
for (const { id, view: originalView, overhang } of prepared) {
|
|
2352
|
+
const surface2 = this.#surfaces.get(id);
|
|
2353
|
+
if (!surface2) continue;
|
|
2354
|
+
let view = originalView;
|
|
2355
|
+
const copyScroll = canStageScroll && this.#canCopyScroll(surface2, view);
|
|
2356
|
+
if (view.damage?.scrollRows) {
|
|
2357
|
+
if (copyScroll) {
|
|
2358
|
+
const rows2 = new Set(view.damage.rows);
|
|
2359
|
+
const shift = view.damage.scrollRows;
|
|
2360
|
+
rows2.add(0);
|
|
2361
|
+
rows2.add(view.rows.length - 1);
|
|
2362
|
+
for (let row = 0; row < view.rows.length; row++) {
|
|
2363
|
+
if (row + shift < 0 || row + shift >= view.rows.length) rows2.add(row);
|
|
2364
|
+
}
|
|
2365
|
+
if (surface2.sceneCursorRow !== null) rows2.add(surface2.sceneCursorRow - shift);
|
|
2366
|
+
if (effectiveCursorStyle(view) !== null) rows2.add(view.cursor.y);
|
|
2367
|
+
view = { ...view, damage: { ...view.damage, rows: rows2, geometryChanged: true } };
|
|
2368
|
+
} else {
|
|
2369
|
+
view = { ...view, damage: { full: true, rows: /* @__PURE__ */ new Set(), geometryChanged: true } };
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
const count = Math.max(view.rows.length, view.nativeRows.length, view.nativeStyleRows.length);
|
|
2373
|
+
const { rows } = rowsForDamage(count, view.damage, surface2.sceneValid);
|
|
2374
|
+
try {
|
|
2375
|
+
try {
|
|
2376
|
+
this.#prepareAtlases(view, rows, surface2.dpr);
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
if (!(error instanceof AtlasBatchFullError) || pendingIds.length === 0) throw error;
|
|
2379
|
+
submit();
|
|
2380
|
+
this.#prepareAtlases(view, rows, surface2.dpr);
|
|
2381
|
+
}
|
|
2382
|
+
} catch (error) {
|
|
2383
|
+
if (!(error instanceof AtlasCapacityError)) throw error;
|
|
2384
|
+
console.error(`[terminal-renderer] cannot render ${id}`, error);
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
encoder ??= this.device.createCommandEncoder({ label: "terminal frame batch" });
|
|
2388
|
+
if (copyScroll) this.#copyScroll(surface2, view, encoder);
|
|
2389
|
+
byId.set(
|
|
2390
|
+
id,
|
|
2391
|
+
view.damage?.geometryChanged === false ? this.#encodeCachedRender(id, view, encoder) : this.#encodeRender(id, view, encoder)
|
|
2392
|
+
);
|
|
2393
|
+
surface2.sceneHadSelection = view.selection !== null;
|
|
2394
|
+
surface2.sceneHadOverhang = overhang;
|
|
2395
|
+
surface2.sceneCursorRow = effectiveCursorStyle(view) === null ? null : view.cursor.y;
|
|
2396
|
+
pendingIds.push(id);
|
|
2397
|
+
}
|
|
2398
|
+
submit();
|
|
2399
|
+
} finally {
|
|
2400
|
+
this.#releaseAtlasBatch();
|
|
1840
2401
|
}
|
|
1841
|
-
this.device.queue.submit([encoder.finish()]);
|
|
1842
|
-
const firstMetrics = [...byId.values()].find((metrics) => metrics !== void 0);
|
|
1843
|
-
if (firstMetrics) firstMetrics.queueSubmits = 1;
|
|
1844
2402
|
return entries.map(({ id }) => byId.get(id));
|
|
1845
2403
|
}
|
|
1846
|
-
#buildGeometry(view, renderRows, rowCount, fullRedraw, hasNativeRows, scale, viewportWidth, viewportHeight) {
|
|
2404
|
+
#buildGeometry(view, renderRows, rowCount, fullRedraw, hasNativeRows, scale, viewportWidth, viewportHeight, adaptation) {
|
|
1847
2405
|
const rectangleVertices = [];
|
|
1848
2406
|
const glyphVertices = [];
|
|
1849
2407
|
const colorGlyphVertices = [];
|
|
@@ -1852,24 +2410,10 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1852
2410
|
const styleFor = (styleId) => {
|
|
1853
2411
|
const cached = resolvedStyles.get(styleId);
|
|
1854
2412
|
if (cached) return cached;
|
|
1855
|
-
const resolved = resolveStyle(view.styleDefinitions.get(styleId), view.theme);
|
|
2413
|
+
const resolved = resolveStyle(view.styleDefinitions.get(styleId), view.theme, adaptation);
|
|
1856
2414
|
resolvedStyles.set(styleId, resolved);
|
|
1857
2415
|
return resolved;
|
|
1858
2416
|
};
|
|
1859
|
-
const monoDefinitions = /* @__PURE__ */ new Map();
|
|
1860
|
-
const colorDefinitions = /* @__PURE__ */ new Map();
|
|
1861
|
-
for (const row of renderRows) {
|
|
1862
|
-
for (const glyph of view.nativeRows[row] ?? []) {
|
|
1863
|
-
const definition = view.glyphDefinitions.get(glyph.glyphId);
|
|
1864
|
-
if (!definition) continue;
|
|
1865
|
-
(definition.format === GlyphFormat.Rgba8Premultiplied ? colorDefinitions : monoDefinitions).set(
|
|
1866
|
-
definition.id,
|
|
1867
|
-
definition
|
|
1868
|
-
);
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
this.#monoAtlas.prepare(monoDefinitions.values());
|
|
1872
|
-
this.#colorAtlas.prepare(colorDefinitions.values());
|
|
1873
2417
|
if (!fullRedraw) {
|
|
1874
2418
|
for (const row of renderRows) {
|
|
1875
2419
|
const top = row === 0 ? 0 : (ORIGIN_Y + row * LINE_HEIGHT) * scale;
|
|
@@ -1891,12 +2435,14 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1891
2435
|
for (const run of view.nativeStyleRows[row] ?? []) {
|
|
1892
2436
|
const style = styleFor(run.styleId);
|
|
1893
2437
|
if (!style.background) continue;
|
|
2438
|
+
const bounds = backgroundRunBounds(run, row, view.cols, rowCount, scale, viewportWidth, viewportHeight);
|
|
2439
|
+
if (!bounds) continue;
|
|
1894
2440
|
pushRectangle(
|
|
1895
2441
|
rectangleVertices,
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
2442
|
+
bounds.x,
|
|
2443
|
+
bounds.y,
|
|
2444
|
+
bounds.width,
|
|
2445
|
+
bounds.height,
|
|
1900
2446
|
style.background,
|
|
1901
2447
|
viewportWidth,
|
|
1902
2448
|
viewportHeight
|
|
@@ -1925,12 +2471,6 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1925
2471
|
}
|
|
1926
2472
|
pushBlockCursorBackground(rectangleVertices, view, renderRows, scale, viewportWidth, viewportHeight);
|
|
1927
2473
|
const selectionInstanceCount = rectangleVertices.length / 10 - backgroundInstanceCount;
|
|
1928
|
-
if (!hasNativeRows) {
|
|
1929
|
-
this.#fallbackAtlas.prepare(
|
|
1930
|
-
renderRows.flatMap((row) => splitGraphemes(view.rows[row] ?? "")),
|
|
1931
|
-
scale
|
|
1932
|
-
);
|
|
1933
|
-
}
|
|
1934
2474
|
if (hasNativeRows) {
|
|
1935
2475
|
for (const row of renderRows) {
|
|
1936
2476
|
const { box: boxCells, block: blockCells } = specialDrawingCells(view.rows[row] ?? "");
|
|
@@ -1961,20 +2501,22 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1961
2501
|
if (blockElement) {
|
|
1962
2502
|
let backdrop = style.background ?? view.theme.background;
|
|
1963
2503
|
if (isSelected) backdrop = view.theme.selection;
|
|
2504
|
+
const blockForeground = foreground === style.foreground ? style.fill : foreground;
|
|
1964
2505
|
if (pushBlockElement(
|
|
1965
2506
|
rectangleVertices,
|
|
1966
2507
|
blockElement,
|
|
1967
2508
|
instance.cellStart,
|
|
1968
2509
|
row,
|
|
1969
2510
|
scale,
|
|
1970
|
-
over(
|
|
2511
|
+
over(blockForeground, backdrop),
|
|
1971
2512
|
viewportWidth,
|
|
1972
2513
|
viewportHeight
|
|
1973
2514
|
))
|
|
1974
2515
|
continue;
|
|
1975
2516
|
}
|
|
1976
|
-
const
|
|
1977
|
-
const
|
|
2517
|
+
const mono = definition.format === GlyphFormat.Alpha8;
|
|
2518
|
+
const atlas = mono ? this.#monoAtlas : this.#colorAtlas;
|
|
2519
|
+
const vertices = mono ? glyphVertices : colorGlyphVertices;
|
|
1978
2520
|
pushGlyph(
|
|
1979
2521
|
vertices,
|
|
1980
2522
|
(ORIGIN_X + instance.x) * scale,
|
|
@@ -1982,7 +2524,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1982
2524
|
instance.width * scale,
|
|
1983
2525
|
instance.height * scale,
|
|
1984
2526
|
atlas.glyph(definition),
|
|
1985
|
-
foreground,
|
|
2527
|
+
mono && foreground === style.foreground ? style.text : foreground,
|
|
1986
2528
|
viewportWidth,
|
|
1987
2529
|
viewportHeight
|
|
1988
2530
|
);
|
|
@@ -2027,7 +2569,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2027
2569
|
Math.round(rowTop + 16 * scale),
|
|
2028
2570
|
width,
|
|
2029
2571
|
stroke,
|
|
2030
|
-
style.
|
|
2572
|
+
style.text,
|
|
2031
2573
|
viewportWidth,
|
|
2032
2574
|
viewportHeight
|
|
2033
2575
|
);
|
|
@@ -2039,7 +2581,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2039
2581
|
Math.round(rowTop + 9 * scale),
|
|
2040
2582
|
width,
|
|
2041
2583
|
stroke,
|
|
2042
|
-
style.
|
|
2584
|
+
style.text,
|
|
2043
2585
|
viewportWidth,
|
|
2044
2586
|
viewportHeight
|
|
2045
2587
|
);
|
|
@@ -2084,24 +2626,10 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2084
2626
|
const styleFor = (styleId) => {
|
|
2085
2627
|
const cached = resolvedStyles.get(styleId);
|
|
2086
2628
|
if (cached) return cached;
|
|
2087
|
-
const resolved = resolveStyle(view.styleDefinitions.get(styleId), view.theme);
|
|
2629
|
+
const resolved = resolveStyle(view.styleDefinitions.get(styleId), view.theme, surface2.adaptation);
|
|
2088
2630
|
resolvedStyles.set(styleId, resolved);
|
|
2089
2631
|
return resolved;
|
|
2090
2632
|
};
|
|
2091
|
-
const monoDefinitions = /* @__PURE__ */ new Map();
|
|
2092
|
-
const colorDefinitions = /* @__PURE__ */ new Map();
|
|
2093
|
-
for (const row of renderRows) {
|
|
2094
|
-
for (const glyph of view.nativeRows[row] ?? []) {
|
|
2095
|
-
const definition = view.glyphDefinitions.get(glyph.glyphId);
|
|
2096
|
-
if (!definition) continue;
|
|
2097
|
-
(definition.format === GlyphFormat.Rgba8Premultiplied ? colorDefinitions : monoDefinitions).set(
|
|
2098
|
-
definition.id,
|
|
2099
|
-
definition
|
|
2100
|
-
);
|
|
2101
|
-
}
|
|
2102
|
-
}
|
|
2103
|
-
this.#monoAtlas.prepare(monoDefinitions.values());
|
|
2104
|
-
this.#colorAtlas.prepare(colorDefinitions.values());
|
|
2105
2633
|
if (!fullRedraw) {
|
|
2106
2634
|
for (const row of renderRows) {
|
|
2107
2635
|
const top = row === 0 ? 0 : (ORIGIN_Y + row * LINE_HEIGHT) * scale;
|
|
@@ -2123,12 +2651,14 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2123
2651
|
for (const run of view.nativeStyleRows[row] ?? []) {
|
|
2124
2652
|
const style = styleFor(run.styleId);
|
|
2125
2653
|
if (!style.background) continue;
|
|
2654
|
+
const bounds = backgroundRunBounds(run, row, view.cols, rowCount, scale, viewportWidth, viewportHeight);
|
|
2655
|
+
if (!bounds) continue;
|
|
2126
2656
|
pushRectangle(
|
|
2127
2657
|
rectangleVertices,
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2658
|
+
bounds.x,
|
|
2659
|
+
bounds.y,
|
|
2660
|
+
bounds.width,
|
|
2661
|
+
bounds.height,
|
|
2132
2662
|
style.background,
|
|
2133
2663
|
viewportWidth,
|
|
2134
2664
|
viewportHeight
|
|
@@ -2158,12 +2688,6 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2158
2688
|
pushBlockCursorBackground(rectangleVertices, view, renderRows, scale, viewportWidth, viewportHeight);
|
|
2159
2689
|
const selectionInstanceCount = rectangleVertices.length / 10 - backgroundInstanceCount;
|
|
2160
2690
|
const hasNativeRows = view.nativeRows.some((row) => row.length > 0);
|
|
2161
|
-
if (!hasNativeRows) {
|
|
2162
|
-
this.#fallbackAtlas.prepare(
|
|
2163
|
-
renderRows.flatMap((row) => splitGraphemes(view.rows[row] ?? "")),
|
|
2164
|
-
scale
|
|
2165
|
-
);
|
|
2166
|
-
}
|
|
2167
2691
|
if (hasNativeRows) {
|
|
2168
2692
|
for (const row of renderRows) {
|
|
2169
2693
|
const { box: boxCells, block: blockCells } = specialDrawingCells(view.rows[row] ?? "");
|
|
@@ -2195,7 +2719,8 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2195
2719
|
if (blockElement) {
|
|
2196
2720
|
let backdrop = style.background ?? view.theme.background;
|
|
2197
2721
|
if (isSelected) backdrop = view.theme.selection;
|
|
2198
|
-
const
|
|
2722
|
+
const blockForeground = foreground === style.foreground ? style.fill : foreground;
|
|
2723
|
+
const blockColor = over(blockForeground, backdrop);
|
|
2199
2724
|
if (pushBlockElement(
|
|
2200
2725
|
rectangleVertices,
|
|
2201
2726
|
blockElement,
|
|
@@ -2208,8 +2733,9 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2208
2733
|
))
|
|
2209
2734
|
continue;
|
|
2210
2735
|
}
|
|
2211
|
-
const
|
|
2212
|
-
const
|
|
2736
|
+
const mono = definition.format === GlyphFormat.Alpha8;
|
|
2737
|
+
const atlas = mono ? this.#monoAtlas : this.#colorAtlas;
|
|
2738
|
+
const vertices = mono ? glyphVertices : colorGlyphVertices;
|
|
2213
2739
|
pushGlyph(
|
|
2214
2740
|
vertices,
|
|
2215
2741
|
(ORIGIN_X + instance.x) * scale,
|
|
@@ -2217,7 +2743,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2217
2743
|
instance.width * scale,
|
|
2218
2744
|
instance.height * scale,
|
|
2219
2745
|
atlas.glyph(definition),
|
|
2220
|
-
foreground,
|
|
2746
|
+
mono && foreground === style.foreground ? style.text : foreground,
|
|
2221
2747
|
viewportWidth,
|
|
2222
2748
|
viewportHeight
|
|
2223
2749
|
);
|
|
@@ -2262,7 +2788,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2262
2788
|
Math.round(rowTop + 16 * scale),
|
|
2263
2789
|
width,
|
|
2264
2790
|
stroke,
|
|
2265
|
-
style.
|
|
2791
|
+
style.text,
|
|
2266
2792
|
viewportWidth,
|
|
2267
2793
|
viewportHeight
|
|
2268
2794
|
);
|
|
@@ -2274,7 +2800,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2274
2800
|
Math.round(rowTop + 9 * scale),
|
|
2275
2801
|
width,
|
|
2276
2802
|
stroke,
|
|
2277
|
-
style.
|
|
2803
|
+
style.text,
|
|
2278
2804
|
viewportWidth,
|
|
2279
2805
|
viewportHeight
|
|
2280
2806
|
);
|
|
@@ -2464,7 +2990,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2464
2990
|
this.#colorAtlas.generation,
|
|
2465
2991
|
this.#fallbackAtlas.generation
|
|
2466
2992
|
];
|
|
2467
|
-
let key = geometryCacheKey(view, damage.rows, hasNativeRows, generations());
|
|
2993
|
+
let key = geometryCacheKey(view, damage.rows, hasNativeRows, generations(), surface2.adaptation?.key ?? "-");
|
|
2468
2994
|
const cached = surface2.geometryCache.get(key);
|
|
2469
2995
|
const cacheHit = cached !== void 0;
|
|
2470
2996
|
if (cached) {
|
|
@@ -2482,9 +3008,10 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2482
3008
|
hasNativeRows,
|
|
2483
3009
|
scale,
|
|
2484
3010
|
viewportWidth,
|
|
2485
|
-
viewportHeight
|
|
3011
|
+
viewportHeight,
|
|
3012
|
+
surface2.adaptation
|
|
2486
3013
|
);
|
|
2487
|
-
key = geometryCacheKey(view, damage.rows, hasNativeRows, generations());
|
|
3014
|
+
key = geometryCacheKey(view, damage.rows, hasNativeRows, generations(), surface2.adaptation?.key ?? "-");
|
|
2488
3015
|
const promote = admitted && key === candidateKey;
|
|
2489
3016
|
geometry = {
|
|
2490
3017
|
rectangleBuffer: promote ? createCachedVertexBuffer(this.device, `cached rectangles ${id}`, cpu.rectangleData) : surface2.rectangleBuffer.write(cpu.rectangleData),
|
|
@@ -2642,13 +3169,72 @@ function cursorActivityChangesPixels(cursor, focused, cursorBlinkVisible) {
|
|
|
2642
3169
|
return focused && cursor.visible && cursor.blinking && !cursorBlinkVisible;
|
|
2643
3170
|
}
|
|
2644
3171
|
|
|
3172
|
+
// src/row-damage.ts
|
|
3173
|
+
function sameRow(before, from, after, to) {
|
|
3174
|
+
if (from < 0 || from >= before.rows.length || before.rows[from] !== after.rows[to]) return false;
|
|
3175
|
+
const a = before.nativeRows[from] ?? [], b = after.nativeRows[to] ?? [];
|
|
3176
|
+
if (a.length !== b.length) return false;
|
|
3177
|
+
for (let i = 0; i < a.length; i++) {
|
|
3178
|
+
const x = a[i], y = b[i];
|
|
3179
|
+
if (x.glyphId !== y.glyphId || x.styleId !== y.styleId || x.x !== y.x || x.y !== y.y || x.width !== y.width || x.height !== y.height || x.cellStart !== y.cellStart || x.cellSpan !== y.cellSpan)
|
|
3180
|
+
return false;
|
|
3181
|
+
}
|
|
3182
|
+
const as = before.nativeStyleRows[from] ?? [], bs = after.nativeStyleRows[to] ?? [];
|
|
3183
|
+
if (as.length !== bs.length) return false;
|
|
3184
|
+
return as.every(
|
|
3185
|
+
(x, i) => x.styleId === bs[i].styleId && x.cellStart === bs[i].cellStart && x.cellSpan === bs[i].cellSpan
|
|
3186
|
+
);
|
|
3187
|
+
}
|
|
3188
|
+
function rowDamage(before, after) {
|
|
3189
|
+
const positions = /* @__PURE__ */ new Map();
|
|
3190
|
+
for (let row = 0; row < before.rows.length; row++) {
|
|
3191
|
+
const text = before.rows[row];
|
|
3192
|
+
positions.set(text, positions.has(text) ? -1 : row);
|
|
3193
|
+
}
|
|
3194
|
+
const votes = /* @__PURE__ */ new Map();
|
|
3195
|
+
for (let row = 0; row < after.rows.length; row++) {
|
|
3196
|
+
const from = positions.get(after.rows[row]);
|
|
3197
|
+
if (from !== void 0 && from >= 0) {
|
|
3198
|
+
const delta = from - row;
|
|
3199
|
+
votes.set(delta, (votes.get(delta) ?? 0) + 1);
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
let scrollRows = 0, best = votes.get(0) ?? 0;
|
|
3203
|
+
for (const [delta, count] of votes)
|
|
3204
|
+
if (count > best) {
|
|
3205
|
+
best = count;
|
|
3206
|
+
scrollRows = delta;
|
|
3207
|
+
}
|
|
3208
|
+
const changed = (delta) => after.rows.flatMap((_, row) => sameRow(before, row + delta, after, row) ? [] : [row]);
|
|
3209
|
+
if (scrollRows !== 0) {
|
|
3210
|
+
const rows = changed(scrollRows);
|
|
3211
|
+
if (after.rows.length - rows.length >= Math.ceil(after.rows.length / 2)) return { scrollRows, rows };
|
|
3212
|
+
}
|
|
3213
|
+
return { scrollRows: 0, rows: changed(0) };
|
|
3214
|
+
}
|
|
3215
|
+
function shiftDamage(rows, shift, rowCount) {
|
|
3216
|
+
const moved = [...rows].map((row) => row - shift).filter((row) => row >= 0 && row < rowCount);
|
|
3217
|
+
rows.clear();
|
|
3218
|
+
for (const row of moved) rows.add(row);
|
|
3219
|
+
}
|
|
3220
|
+
function selectionDamageRows(before, after, count) {
|
|
3221
|
+
const rows = /* @__PURE__ */ new Set();
|
|
3222
|
+
for (const selection of [before, after]) {
|
|
3223
|
+
if (!selection) continue;
|
|
3224
|
+
const start = Math.max(0, Math.min(selection.anchor.row, selection.focus.row));
|
|
3225
|
+
const end = Math.min(count - 1, Math.max(selection.anchor.row, selection.focus.row));
|
|
3226
|
+
for (let row = start; row <= end; row++) rows.add(row);
|
|
3227
|
+
}
|
|
3228
|
+
return [...rows];
|
|
3229
|
+
}
|
|
3230
|
+
|
|
2645
3231
|
// src/catalog-budget.ts
|
|
2646
3232
|
function catalogAdmission(resetsCatalog, alreadyUsingFallback, fits) {
|
|
2647
3233
|
if (alreadyUsingFallback && !resetsCatalog) return "text-fallback";
|
|
2648
3234
|
if (fits) return "install";
|
|
2649
3235
|
return resetsCatalog ? "text-fallback" : "request-full";
|
|
2650
3236
|
}
|
|
2651
|
-
function glyphCatalogFits(sessionDefinitions, sessionPixelBytes, sharedDefinitions, sharedPixelBytes, definitions, budget) {
|
|
3237
|
+
function glyphCatalogFits(sessionDefinitions, sessionPixelBytes, sharedDefinitions, sharedPixelBytes, definitions, budget, sharedKey = (id) => id) {
|
|
2652
3238
|
if (definitions.length === 0) return true;
|
|
2653
3239
|
let nextDefinitionCount = sessionDefinitions.size;
|
|
2654
3240
|
let nextSharedDefinitionCount = sharedDefinitions.size;
|
|
@@ -2658,7 +3244,7 @@ function glyphCatalogFits(sessionDefinitions, sessionPixelBytes, sharedDefinitio
|
|
|
2658
3244
|
for (const definition of definitions) {
|
|
2659
3245
|
if (sessionDefinitions.has(definition.id) || pending.has(definition.id)) continue;
|
|
2660
3246
|
pending.add(definition.id);
|
|
2661
|
-
const shared = sharedDefinitions.get(definition.id);
|
|
3247
|
+
const shared = sharedDefinitions.get(sharedKey(definition.id));
|
|
2662
3248
|
const pixelBytes = shared?.definition.pixels.byteLength ?? definition.pixels.byteLength;
|
|
2663
3249
|
nextDefinitionCount += 1;
|
|
2664
3250
|
nextSessionPixelBytes += pixelBytes;
|
|
@@ -3033,10 +3619,6 @@ var socketClosing = 2;
|
|
|
3033
3619
|
function exactStamp(left, right) {
|
|
3034
3620
|
return left !== void 0 && compareRoutedSceneContent(left, right) === 0;
|
|
3035
3621
|
}
|
|
3036
|
-
function copyArrayBuffer(bytes) {
|
|
3037
|
-
const copy = bytes.slice();
|
|
3038
|
-
return copy.buffer;
|
|
3039
|
-
}
|
|
3040
3622
|
function capacitiesFitWithin(accepted, advertised) {
|
|
3041
3623
|
return accepted.connectionCreditBytes <= advertised.connectionCreditBytes && accepted.perActivationCreditBytes <= advertised.perActivationCreditBytes && accepted.stagingBytesPerSession <= advertised.stagingBytesPerSession && accepted.stagingBytesTotal <= advertised.stagingBytesTotal && accepted.maxConcurrentActivations <= advertised.maxConcurrentActivations && accepted.maxConcurrentSeeds <= advertised.maxConcurrentSeeds;
|
|
3042
3624
|
}
|
|
@@ -3344,13 +3926,13 @@ var RoutedFramesTransport = class {
|
|
|
3344
3926
|
}
|
|
3345
3927
|
}
|
|
3346
3928
|
#incremental(connection, activation, header, payload, chargedBytes) {
|
|
3347
|
-
if (payload.byteLength > connection.accepted.protocolLimits.
|
|
3929
|
+
if (payload.byteLength > connection.accepted.protocolLimits.maxUrgentPresentationUnitBytes || !header.resultContent || header.baseContent === void 0 || header.baseContent !== null && !exactStamp(activation.appliedContent, header.baseContent)) {
|
|
3348
3930
|
this.#protocolFailure(connection, header.activationId, chargedBytes, "incremental base mismatch");
|
|
3349
3931
|
return;
|
|
3350
3932
|
}
|
|
3351
3933
|
try {
|
|
3352
3934
|
this.#emit({ type: "frames-state", activationId: header.activationId, state: "applying" });
|
|
3353
|
-
this.#applyFrame(
|
|
3935
|
+
this.#applyFrame(payload, activation.identity, void 0, header.resultContent.sceneEpoch.cellBootId);
|
|
3354
3936
|
activation.appliedContent = header.resultContent;
|
|
3355
3937
|
this.#returnCredit(connection, header.activationId, chargedBytes);
|
|
3356
3938
|
this.#sceneApplied(connection, activation);
|
|
@@ -3430,7 +4012,12 @@ var RoutedFramesTransport = class {
|
|
|
3430
4012
|
}
|
|
3431
4013
|
try {
|
|
3432
4014
|
this.#emit({ type: "frames-state", activationId: header.activationId, state: "applying" });
|
|
3433
|
-
this.#applyFrame(
|
|
4015
|
+
this.#applyFrame(
|
|
4016
|
+
staging.bytes.buffer,
|
|
4017
|
+
activation.identity,
|
|
4018
|
+
staging.targetLayout,
|
|
4019
|
+
staging.resultContent.sceneEpoch.cellBootId
|
|
4020
|
+
);
|
|
3434
4021
|
activation.appliedContent = staging.resultContent;
|
|
3435
4022
|
this.#releaseTransfer(activation);
|
|
3436
4023
|
this.#sceneApplied(connection, activation);
|
|
@@ -3635,7 +4222,10 @@ var MAX_PERFORMANCE_SAMPLES = 1e5;
|
|
|
3635
4222
|
var FRAME_CREDIT_BATCH_BYTES = 4 * 1024 * 1024;
|
|
3636
4223
|
var FRAME_CREDIT_MAX_DELAY_MS = 8;
|
|
3637
4224
|
var snapshots = /* @__PURE__ */ new Map();
|
|
4225
|
+
var drawnSessions = /* @__PURE__ */ new Set();
|
|
4226
|
+
var routedMouseTracking = /* @__PURE__ */ new Map();
|
|
3638
4227
|
var sharedGlyphDefinitions = /* @__PURE__ */ new Map();
|
|
4228
|
+
var nextRendererGlyphId = 1;
|
|
3639
4229
|
var sharedGlyphPixelBytes = 0;
|
|
3640
4230
|
var retainedStyleDefinitions = 0;
|
|
3641
4231
|
var surfaces = /* @__PURE__ */ new Map();
|
|
@@ -3826,10 +4416,12 @@ function postToRenderer(message) {
|
|
|
3826
4416
|
}
|
|
3827
4417
|
function emptySessionSnapshot() {
|
|
3828
4418
|
return {
|
|
4419
|
+
cols: 0,
|
|
3829
4420
|
rows: [],
|
|
3830
4421
|
nativeRows: [],
|
|
3831
4422
|
nativeStyleRows: [],
|
|
3832
4423
|
glyphDefinitions: /* @__PURE__ */ new Map(),
|
|
4424
|
+
glyphNamespace: "",
|
|
3833
4425
|
glyphPixelBytes: 0,
|
|
3834
4426
|
styleDefinitions: /* @__PURE__ */ new Map(),
|
|
3835
4427
|
rowRevisions: [],
|
|
@@ -3839,7 +4431,8 @@ function emptySessionSnapshot() {
|
|
|
3839
4431
|
sequence: 0n,
|
|
3840
4432
|
awaitingResync: false,
|
|
3841
4433
|
catalogFallback: false,
|
|
3842
|
-
scrollbar: null
|
|
4434
|
+
scrollbar: null,
|
|
4435
|
+
linkSignature: ""
|
|
3843
4436
|
};
|
|
3844
4437
|
}
|
|
3845
4438
|
function snapshot(sessionHandle) {
|
|
@@ -3852,29 +4445,34 @@ function snapshot(sessionHandle) {
|
|
|
3852
4445
|
}
|
|
3853
4446
|
function clearSessionCatalog(session) {
|
|
3854
4447
|
for (const glyphId of session.glyphDefinitions.keys()) {
|
|
3855
|
-
const
|
|
4448
|
+
const key = `${session.glyphNamespace}:${glyphId}`;
|
|
4449
|
+
const shared = sharedGlyphDefinitions.get(key);
|
|
3856
4450
|
if (!shared) continue;
|
|
3857
4451
|
shared.references -= 1;
|
|
3858
4452
|
if (shared.references <= 0) {
|
|
3859
|
-
sharedGlyphDefinitions.delete(
|
|
4453
|
+
sharedGlyphDefinitions.delete(key);
|
|
3860
4454
|
sharedGlyphPixelBytes -= shared.definition.pixels.byteLength;
|
|
3861
4455
|
}
|
|
3862
4456
|
}
|
|
3863
|
-
session.glyphDefinitions
|
|
4457
|
+
session.glyphDefinitions = /* @__PURE__ */ new Map();
|
|
3864
4458
|
session.glyphPixelBytes = 0;
|
|
3865
4459
|
retainedStyleDefinitions -= session.styleDefinitions.size;
|
|
3866
4460
|
session.styleDefinitions.clear();
|
|
3867
4461
|
}
|
|
3868
|
-
function installGlyphDefinitions(session, definitions) {
|
|
4462
|
+
function installGlyphDefinitions(session, definitions, previousDefinitions) {
|
|
3869
4463
|
for (const decoded of definitions) {
|
|
3870
4464
|
if (session.glyphDefinitions.has(decoded.id)) continue;
|
|
3871
|
-
|
|
4465
|
+
const key = `${session.glyphNamespace}:${decoded.id}`;
|
|
4466
|
+
let shared = sharedGlyphDefinitions.get(key);
|
|
3872
4467
|
const pixelBytes = shared?.definition.pixels.byteLength ?? decoded.pixels.byteLength;
|
|
3873
4468
|
if (shared) {
|
|
3874
4469
|
shared.references += 1;
|
|
3875
4470
|
} else {
|
|
3876
|
-
shared = {
|
|
3877
|
-
|
|
4471
|
+
shared = {
|
|
4472
|
+
definition: { ...decoded, id: previousDefinitions?.get(decoded.id)?.id ?? nextRendererGlyphId++ },
|
|
4473
|
+
references: 1
|
|
4474
|
+
};
|
|
4475
|
+
sharedGlyphDefinitions.set(key, shared);
|
|
3878
4476
|
sharedGlyphPixelBytes += decoded.pixels.byteLength;
|
|
3879
4477
|
}
|
|
3880
4478
|
session.glyphDefinitions.set(decoded.id, shared.definition);
|
|
@@ -3892,6 +4490,10 @@ function dropSessionSnapshot(sessionHandle) {
|
|
|
3892
4490
|
const session = snapshots.get(sessionHandle);
|
|
3893
4491
|
if (session) clearSessionCatalog(session);
|
|
3894
4492
|
snapshots.delete(sessionHandle);
|
|
4493
|
+
drawnSessions.delete(sessionHandle);
|
|
4494
|
+
}
|
|
4495
|
+
function rowShowsContent(row) {
|
|
4496
|
+
return row.glyphs.length > 0 || /\S/.test(row.text);
|
|
3895
4497
|
}
|
|
3896
4498
|
function defaults(sessionHandle) {
|
|
3897
4499
|
let value = presentationDefaults.get(sessionHandle);
|
|
@@ -3941,6 +4543,7 @@ function deleteSurface(surfaceId) {
|
|
|
3941
4543
|
}
|
|
3942
4544
|
function renderView(session, presentation) {
|
|
3943
4545
|
return {
|
|
4546
|
+
cols: session.cols,
|
|
3944
4547
|
rows: session.rows,
|
|
3945
4548
|
nativeRows: session.nativeRows,
|
|
3946
4549
|
nativeStyleRows: session.nativeStyleRows,
|
|
@@ -4015,6 +4618,7 @@ async function recoverDevice(info) {
|
|
|
4015
4618
|
try {
|
|
4016
4619
|
const next = await rendererPromise;
|
|
4017
4620
|
renderer = next;
|
|
4621
|
+
next.setPerformanceMeasurementEnabled?.(performanceMeasurement !== void 0);
|
|
4018
4622
|
recoveryAttempts = 0;
|
|
4019
4623
|
recovering = false;
|
|
4020
4624
|
for (const [id, mount2] of mounts) {
|
|
@@ -4065,7 +4669,10 @@ function scheduleShaderAnimation() {
|
|
|
4065
4669
|
}
|
|
4066
4670
|
async function flush() {
|
|
4067
4671
|
const backend = await ensureRenderer();
|
|
4068
|
-
const ids = [...dirty].filter((id) =>
|
|
4672
|
+
const ids = [...dirty].filter((id) => {
|
|
4673
|
+
const size = mounts.get(id)?.size;
|
|
4674
|
+
return !occluded.has(id) && size !== void 0 && size.width > 0 && size.height > 0;
|
|
4675
|
+
});
|
|
4069
4676
|
if (performanceMeasurement && ids.length > 0) {
|
|
4070
4677
|
performanceMeasurement.scheduling.flushes += 1;
|
|
4071
4678
|
appendPerformanceSample(performanceMeasurement.scheduling.panesPerFlush, ids.length);
|
|
@@ -4085,14 +4692,23 @@ async function flush() {
|
|
|
4085
4692
|
const active = performanceMeasurement;
|
|
4086
4693
|
const beforeRender = active ? performance.now() : 0;
|
|
4087
4694
|
const metrics = backend.renderBatch ? backend.renderBatch(entries) : entries.map(({ id, view }) => backend.render(id, view));
|
|
4088
|
-
|
|
4695
|
+
const submission = backend.submission?.();
|
|
4696
|
+
if (submission) {
|
|
4697
|
+
lifetimeRenderer.queueSubmits += submission.queueSubmits;
|
|
4698
|
+
lifetimeRenderer.presents += submission.surfaceIds.length;
|
|
4699
|
+
} else {
|
|
4700
|
+
for (const metric of metrics) recordLifetimeRenderMetrics(metric ?? emptyRenderMetrics());
|
|
4701
|
+
}
|
|
4702
|
+
const submitted = submission ? new Set(submission.surfaceIds) : void 0;
|
|
4089
4703
|
const renderedAt = active ? performance.now() : 0;
|
|
4090
4704
|
for (const { id } of entries) {
|
|
4705
|
+
if (submitted && !submitted.has(id)) continue;
|
|
4091
4706
|
const damage = surfaces.get(id)?.damage;
|
|
4092
4707
|
if (!damage) continue;
|
|
4093
4708
|
damage.full = false;
|
|
4094
4709
|
damage.rows.clear();
|
|
4095
4710
|
damage.geometryChanged = false;
|
|
4711
|
+
delete damage.scrollRows;
|
|
4096
4712
|
}
|
|
4097
4713
|
if (active && performanceMeasurement === active) {
|
|
4098
4714
|
active.scheduling.renderCalls += entries.length;
|
|
@@ -4131,6 +4747,7 @@ function invalidateFull(id) {
|
|
|
4131
4747
|
damage.full = true;
|
|
4132
4748
|
damage.rows.clear();
|
|
4133
4749
|
damage.geometryChanged = true;
|
|
4750
|
+
delete damage.scrollRows;
|
|
4134
4751
|
markDirty(id);
|
|
4135
4752
|
}
|
|
4136
4753
|
function invalidateRows(id, rows, geometryChanged = false) {
|
|
@@ -4152,15 +4769,35 @@ function invalidateRows(id, rows, geometryChanged = false) {
|
|
|
4152
4769
|
function invalidateSessionFull(sessionHandle) {
|
|
4153
4770
|
for (const surfaceId of surfaceIdsForSession(sessionHandle)) invalidateFull(surfaceId);
|
|
4154
4771
|
}
|
|
4772
|
+
function invalidateScroll(id, shift, rows, rowCount) {
|
|
4773
|
+
const value = surfaces.get(id);
|
|
4774
|
+
if (!value) return;
|
|
4775
|
+
const damage = value.damage;
|
|
4776
|
+
if (!partialRenderingEnabled || damage.full || value.selection || Math.abs((damage.scrollRows ?? 0) + shift) >= rowCount) {
|
|
4777
|
+
invalidateFull(id);
|
|
4778
|
+
return;
|
|
4779
|
+
}
|
|
4780
|
+
shiftDamage(damage.rows, shift, rowCount);
|
|
4781
|
+
damage.scrollRows = (damage.scrollRows ?? 0) + shift;
|
|
4782
|
+
invalidateRows(id, rows, true);
|
|
4783
|
+
}
|
|
4784
|
+
function changeSelection(id, selection) {
|
|
4785
|
+
const value = surfaces.get(id);
|
|
4786
|
+
if (!value || value.selection === null && selection === null) return;
|
|
4787
|
+
const rows = selectionDamageRows(value.selection, selection, snapshots.get(value.sessionHandle)?.rows.length ?? 0);
|
|
4788
|
+
value.selection = selection;
|
|
4789
|
+
if (value.damage.scrollRows) invalidateFull(id);
|
|
4790
|
+
else invalidateRows(id, rows, true);
|
|
4791
|
+
}
|
|
4155
4792
|
function invalidateSessionRows(sessionHandle, rows, geometryChanged = false) {
|
|
4156
4793
|
const damagedRows = [...rows];
|
|
4157
4794
|
for (const surfaceId of surfaceIdsForSession(sessionHandle)) invalidateRows(surfaceId, damagedRows, geometryChanged);
|
|
4158
4795
|
}
|
|
4159
|
-
async function mount(surfaceId, sessionHandle, canvas) {
|
|
4796
|
+
async function mount(surfaceId, sessionHandle, canvas, size) {
|
|
4160
4797
|
if (mounts.has(surfaceId)) renderer?.unmount(surfaceId);
|
|
4161
4798
|
snapshot(sessionHandle);
|
|
4162
4799
|
surface(surfaceId, sessionHandle);
|
|
4163
|
-
const entry = { canvas, size
|
|
4800
|
+
const entry = { canvas, size, sessionHandle };
|
|
4164
4801
|
mounts.set(surfaceId, entry);
|
|
4165
4802
|
const backend = await ensureRenderer();
|
|
4166
4803
|
if (mounts.get(surfaceId) !== entry) return;
|
|
@@ -4169,7 +4806,7 @@ async function mount(surfaceId, sessionHandle, canvas) {
|
|
|
4169
4806
|
invalidateFull(surfaceId);
|
|
4170
4807
|
scheduleShaderAnimation();
|
|
4171
4808
|
}
|
|
4172
|
-
function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
4809
|
+
function applyFrame(packet, expectedIdentity, expectedLayout, glyphNamespace) {
|
|
4173
4810
|
const active = performanceMeasurement;
|
|
4174
4811
|
const applyStarted = active ? performance.now() : 0;
|
|
4175
4812
|
lifetimeFrames.received += 1;
|
|
@@ -4266,11 +4903,15 @@ function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
|
4266
4903
|
throw new Error("routed transfer row section is not a full snapshot");
|
|
4267
4904
|
}
|
|
4268
4905
|
const glyphDefinitions = glyphSection ? decodeGlyphDefinitions(glyphSection) : NO_GLYPH_DEFINITIONS;
|
|
4269
|
-
const
|
|
4906
|
+
const paletteSection = frame.sections.find((candidate) => candidate.kind === SectionKind.StylePalette);
|
|
4907
|
+
const styleDefinitions = styleSection ? decodeStyleDefinitions(styleSection, paletteSection) : NO_STYLE_DEFINITIONS;
|
|
4270
4908
|
const replacements = decodeRowReplacements(rowSection);
|
|
4271
4909
|
for (const replacement of replacements) {
|
|
4272
4910
|
if (replacement.row >= frame.rows) throw new RangeError("Row replacement exceeds viewport");
|
|
4273
4911
|
}
|
|
4912
|
+
const firstDrawn = !drawnSessions.has(id) && replacements.some(rowShowsContent);
|
|
4913
|
+
const linkSection = frame.sections.find((candidate) => candidate.kind === SectionKind.LinkTargets);
|
|
4914
|
+
const links = linkSection ? decodeLinkTargets(linkSection, frame.cols, frame.rows) : [];
|
|
4274
4915
|
const nextCursor = decodeCursorState(cursorSection);
|
|
4275
4916
|
const clipboardText = clipboardSection ? decodeClipboardWrite(clipboardSection) : void 0;
|
|
4276
4917
|
let scrollbar;
|
|
@@ -4289,13 +4930,21 @@ function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
|
4289
4930
|
throw new Error("transfer scrollback layout does not match TRF1");
|
|
4290
4931
|
}
|
|
4291
4932
|
if (active) active.frames.glyphDefinitions += glyphDefinitions.length;
|
|
4292
|
-
const
|
|
4933
|
+
const namespace = glyphNamespace === void 0 ? `session:${id}:${frame.sessionEpoch}` : `cell:${glyphNamespace}`;
|
|
4934
|
+
const resetsCatalog = replacingScene || changedSession || completingResync || catalogReset || previous.glyphNamespace !== namespace;
|
|
4935
|
+
const styleChanged = styleDefinitions.some((definition) => {
|
|
4936
|
+
const old = previous.styleDefinitions.get(definition.id);
|
|
4937
|
+
return old !== void 0 && JSON.stringify(old) !== JSON.stringify(definition);
|
|
4938
|
+
});
|
|
4939
|
+
const canReuseRows = !resetsCatalog && !styleChanged && previous.cols === frame.cols && previous.rows.length === frame.rows && previous.layoutEpoch === frame.layoutEpoch;
|
|
4293
4940
|
const wasCatalogFallback = previous.catalogFallback;
|
|
4294
4941
|
if (resetsCatalog) {
|
|
4942
|
+
const previousDefinitions = previous.glyphNamespace === namespace ? previous.glyphDefinitions : void 0;
|
|
4295
4943
|
previous.rows = [];
|
|
4296
4944
|
previous.nativeRows = [];
|
|
4297
4945
|
previous.nativeStyleRows = [];
|
|
4298
4946
|
clearSessionCatalog(previous);
|
|
4947
|
+
previous.glyphNamespace = namespace;
|
|
4299
4948
|
previous.rowRevisions = [];
|
|
4300
4949
|
const catalogFits = glyphCatalogFits(
|
|
4301
4950
|
previous.glyphDefinitions,
|
|
@@ -4303,7 +4952,8 @@ function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
|
4303
4952
|
sharedGlyphDefinitions,
|
|
4304
4953
|
sharedGlyphPixelBytes,
|
|
4305
4954
|
glyphDefinitions,
|
|
4306
|
-
GLYPH_CATALOG_BUDGET
|
|
4955
|
+
GLYPH_CATALOG_BUDGET,
|
|
4956
|
+
(glyphId) => `${namespace}:${glyphId}`
|
|
4307
4957
|
) && definitionCatalogFits(
|
|
4308
4958
|
previous.styleDefinitions,
|
|
4309
4959
|
styleDefinitions,
|
|
@@ -4314,7 +4964,7 @@ function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
|
4314
4964
|
const admission = catalogAdmission(true, wasCatalogFallback, catalogFits);
|
|
4315
4965
|
previous.catalogFallback = admission === "text-fallback";
|
|
4316
4966
|
if (admission === "install") {
|
|
4317
|
-
installGlyphDefinitions(previous, glyphDefinitions);
|
|
4967
|
+
installGlyphDefinitions(previous, glyphDefinitions, previousDefinitions);
|
|
4318
4968
|
installStyleDefinitions(previous, styleDefinitions);
|
|
4319
4969
|
} else if (!wasCatalogFallback) {
|
|
4320
4970
|
postToRenderer({
|
|
@@ -4330,7 +4980,8 @@ function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
|
4330
4980
|
sharedGlyphDefinitions,
|
|
4331
4981
|
sharedGlyphPixelBytes,
|
|
4332
4982
|
glyphDefinitions,
|
|
4333
|
-
GLYPH_CATALOG_BUDGET
|
|
4983
|
+
GLYPH_CATALOG_BUDGET,
|
|
4984
|
+
(glyphId) => `${namespace}:${glyphId}`
|
|
4334
4985
|
) && definitionCatalogFits(
|
|
4335
4986
|
previous.styleDefinitions,
|
|
4336
4987
|
styleDefinitions,
|
|
@@ -4370,7 +5021,7 @@ function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
|
4370
5021
|
const nativeRows = fullRows ? Array.from({ length: frame.rows }, () => []) : previous.nativeRows.slice();
|
|
4371
5022
|
const nativeStyleRows = fullRows ? Array.from({ length: frame.rows }, () => []) : previous.nativeStyleRows.slice();
|
|
4372
5023
|
const rowRevisions = fullRows ? Array(frame.rows).fill(0n) : previous.rowRevisions.slice();
|
|
4373
|
-
|
|
5024
|
+
let damagedRows = [];
|
|
4374
5025
|
if (active) active.frames.rowsDecoded += replacements.length;
|
|
4375
5026
|
for (const replacement of replacements) {
|
|
4376
5027
|
if (replacement.revision < (rowRevisions[replacement.row] ?? 0n)) continue;
|
|
@@ -4380,11 +5031,24 @@ function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
|
4380
5031
|
rowRevisions[replacement.row] = replacement.revision;
|
|
4381
5032
|
damagedRows.push(replacement.row);
|
|
4382
5033
|
}
|
|
5034
|
+
let scrollRows = 0;
|
|
5035
|
+
if (fullRows && canReuseRows) {
|
|
5036
|
+
const damage = rowDamage(previous, { rows, nativeRows, nativeStyleRows });
|
|
5037
|
+
scrollRows = damage.scrollRows;
|
|
5038
|
+
damagedRows = damage.rows;
|
|
5039
|
+
const changed = new Set(damagedRows);
|
|
5040
|
+
for (let row = 0; row < rows.length; row++) {
|
|
5041
|
+
if (changed.has(row)) continue;
|
|
5042
|
+
nativeRows[row] = previous.nativeRows[row + scrollRows];
|
|
5043
|
+
nativeStyleRows[row] = previous.nativeStyleRows[row + scrollRows];
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
5046
|
+
previous.cols = frame.cols;
|
|
4383
5047
|
previous.rows = rows;
|
|
4384
5048
|
previous.nativeRows = nativeRows;
|
|
4385
5049
|
previous.nativeStyleRows = nativeStyleRows;
|
|
4386
5050
|
previous.rowRevisions = rowRevisions;
|
|
4387
|
-
const geometryChanged = damagedRows.length > 0;
|
|
5051
|
+
const geometryChanged = damagedRows.length > 0 || scrollRows !== 0;
|
|
4388
5052
|
const previousCursor = previous.cursor;
|
|
4389
5053
|
const cursorChanged = nextCursor.x !== previousCursor.x || nextCursor.y !== previousCursor.y || nextCursor.visible !== previousCursor.visible || nextCursor.style !== previousCursor.style || nextCursor.blinking !== previousCursor.blinking;
|
|
4390
5054
|
previous.cursor = nextCursor;
|
|
@@ -4396,15 +5060,22 @@ function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
|
4396
5060
|
snapshots.set(id, previous);
|
|
4397
5061
|
if (installedScene) clearSessionCatalog(installedScene);
|
|
4398
5062
|
}
|
|
5063
|
+
const linkSignature = JSON.stringify(links);
|
|
5064
|
+
if (previous.linkSignature !== linkSignature || changedSession || completingResync) {
|
|
5065
|
+
previous.linkSignature = linkSignature;
|
|
5066
|
+
postToRenderer({ type: "link-targets", sessionHandle: id, links });
|
|
5067
|
+
}
|
|
4399
5068
|
if (clipboardText !== void 0) postToRenderer({ type: "clipboard-write", text: clipboardText });
|
|
4400
5069
|
if (scrollbarChanged && scrollbar) postToRenderer({ type: "scrollbar-state", sessionHandle: id, scrollbar });
|
|
4401
5070
|
if (completingResync) postToRenderer({ type: "frame-resync-complete", sessionHandle: id });
|
|
4402
|
-
const requiresFullRedraw =
|
|
4403
|
-
if (!requiresFullRedraw && cursorChanged
|
|
5071
|
+
const requiresFullRedraw = !canReuseRows;
|
|
5072
|
+
if (!requiresFullRedraw && (cursorChanged || scrollRows !== 0))
|
|
5073
|
+
damagedRows.push(previousCursor.y - scrollRows, nextCursor.y);
|
|
4404
5074
|
const hasRowDamage = damagedRows.length > 0;
|
|
4405
5075
|
for (const surfaceId of surfaceIdsForSession(id)) {
|
|
4406
5076
|
if (cursorChanged) scheduleCursorBlink(surfaceId, true);
|
|
4407
5077
|
if (requiresFullRedraw) invalidateFull(surfaceId);
|
|
5078
|
+
else if (scrollRows !== 0) invalidateScroll(surfaceId, scrollRows, damagedRows, rows.length);
|
|
4408
5079
|
else if (hasRowDamage) invalidateRows(surfaceId, damagedRows, geometryChanged);
|
|
4409
5080
|
}
|
|
4410
5081
|
postToRenderer({
|
|
@@ -4417,6 +5088,17 @@ function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
|
4417
5088
|
// recovered screen apart from a partial update of the stale one.
|
|
4418
5089
|
fullSnapshot: fullFrame
|
|
4419
5090
|
});
|
|
5091
|
+
if (expectedIdentity) {
|
|
5092
|
+
const mouseTracking = (frame.flags & FrameFlag.MouseTracking) !== 0;
|
|
5093
|
+
if (routedMouseTracking.get(id) !== mouseTracking) {
|
|
5094
|
+
routedMouseTracking.set(id, mouseTracking);
|
|
5095
|
+
postToRenderer({ type: "terminal-modes", sessionHandle: id, mouseTracking });
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
if (firstDrawn) {
|
|
5099
|
+
drawnSessions.add(id);
|
|
5100
|
+
postToRenderer({ type: "session-drawn", sessionHandle: id });
|
|
5101
|
+
}
|
|
4420
5102
|
lifetimeFrames.applies += 1;
|
|
4421
5103
|
counters.applies += 1;
|
|
4422
5104
|
if (active) appendPerformanceSample(active.samples.frameApplyMs, performance.now() - applyStarted);
|
|
@@ -4428,8 +5110,8 @@ function applyFrame(packet, expectedIdentity, expectedLayout) {
|
|
|
4428
5110
|
};
|
|
4429
5111
|
}
|
|
4430
5112
|
var routedFrames = new RoutedFramesTransport({
|
|
4431
|
-
applyFrame: (packet, identity, expectedLayout) => {
|
|
4432
|
-
const applied = applyFrame(packet, identity, expectedLayout);
|
|
5113
|
+
applyFrame: (packet, identity, expectedLayout, glyphNamespace) => {
|
|
5114
|
+
const applied = applyFrame(packet, identity, expectedLayout, glyphNamespace);
|
|
4433
5115
|
if (!applied) throw new Error("routed TRF1 frame was not applied");
|
|
4434
5116
|
return applied;
|
|
4435
5117
|
},
|
|
@@ -4443,7 +5125,7 @@ self.onmessage = (event) => {
|
|
|
4443
5125
|
const message = event.data;
|
|
4444
5126
|
try {
|
|
4445
5127
|
if (message.type === "mount") {
|
|
4446
|
-
void mount(message.surfaceId, message.sessionHandle, message.canvas).catch((error) => {
|
|
5128
|
+
void mount(message.surfaceId, message.sessionHandle, message.canvas, message.size).catch((error) => {
|
|
4447
5129
|
console.error("[terminal-renderer] mount failed", error);
|
|
4448
5130
|
});
|
|
4449
5131
|
} else if (message.type === "renderer-config") {
|
|
@@ -4526,13 +5208,12 @@ self.onmessage = (event) => {
|
|
|
4526
5208
|
scheduleShaderAnimation();
|
|
4527
5209
|
} else if (message.type === "selection") {
|
|
4528
5210
|
if (message.surfaceId) {
|
|
4529
|
-
surface(message.surfaceId, message.sessionHandle)
|
|
4530
|
-
|
|
5211
|
+
surface(message.surfaceId, message.sessionHandle);
|
|
5212
|
+
changeSelection(message.surfaceId, message.selection);
|
|
4531
5213
|
} else {
|
|
4532
5214
|
defaults(message.sessionHandle).selection = message.selection;
|
|
4533
5215
|
for (const surfaceId of surfaceIdsForSession(message.sessionHandle)) {
|
|
4534
|
-
|
|
4535
|
-
invalidateFull(surfaceId);
|
|
5216
|
+
changeSelection(surfaceId, message.selection);
|
|
4536
5217
|
}
|
|
4537
5218
|
}
|
|
4538
5219
|
} else if (message.type === "visibility") {
|
|
@@ -4598,6 +5279,7 @@ self.onmessage = (event) => {
|
|
|
4598
5279
|
} else if (message.type === "performance-counters") {
|
|
4599
5280
|
postToRenderer({ type: "performance-counters", requestId: message.requestId, snapshot: counterSnapshot() });
|
|
4600
5281
|
} else if (message.type === "routed-frames-attach") {
|
|
5282
|
+
routedMouseTracking.delete(message.request.sessionHandle);
|
|
4601
5283
|
routedFrames.attach(message.request);
|
|
4602
5284
|
} else if (message.type === "routed-frames-detach") {
|
|
4603
5285
|
routedFrames.detach(message.activationId);
|