mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-24 16:11:43 +00:00
terminal/snapshot: resolve wide pairs in a per-row pass
Cell decoding ran wide-pair normalization inline for every decoded cell:
two neighbor loads and a switch per cell, even though the overwhelming
majority of rows contain no wide cells at all. Normalization is defined
against already-stored predecessors, so running it as an ordered pass
over the stored row afterward is exactly equivalent to interleaving it.
The word-cell decoders now accumulate the bitwise OR of the row's wire
words as they apply cells, and the pass is gated on it: rows without
wide bits are already normalized (every cell narrow), and width-four and
narrower transports cannot encode wide bits at all, so their rows skip
the check at comptime. That removes the per-cell neighbor traffic from
all styled text, which decodes through the four-byte width.
Benchmarks ("prev" is the parent commit):
| wasm | encode prev | encode | decode prev | decode |
|-----------|------------:|---------:|------------:|---------:|
| ascii | 2.18 ms | 2.06 ms | 2.68 ms | 2.70 ms |
| styled | 2.28 ms | 2.41 ms | 6.93 ms | 6.56 ms |
| truecolor | 4.93 ms | 5.02 ms | 11.91 ms | 11.86 ms |
| cjk | 6.11 ms | 6.03 ms | 11.72 ms | 11.60 ms |
| grapheme | 8.28 ms | 8.36 ms | 11.38 ms | 11.39 ms |
| native | mode | prev | this |
|--------|--------|--------:|--------:|
| ascii | encode | 24.3 ms | 25.6 ms |
| ascii | decode | 46.7 ms | 48.1 ms |
| utf8 | encode | 41.0 ms | 41.7 ms |
| utf8 | decode | 59.5 ms | 58.5 ms |
This commit is contained in:
@@ -893,7 +893,7 @@ pub fn decode(
|
||||
reader,
|
||||
cells[0..count],
|
||||
),
|
||||
.four => try decodeWordCells(
|
||||
.four => _ = try decodeWordCells(
|
||||
.four,
|
||||
page,
|
||||
row,
|
||||
@@ -913,7 +913,9 @@ pub fn decode(
|
||||
try reader.readSliceAll(
|
||||
std.mem.sliceAsBytes(cells[0..count]),
|
||||
);
|
||||
var row_or: u64 = 0;
|
||||
for (0..count) |x| {
|
||||
row_or |= words[x];
|
||||
applyCell(
|
||||
page,
|
||||
row,
|
||||
@@ -924,8 +926,9 @@ pub fn decode(
|
||||
hyperlink_remap,
|
||||
);
|
||||
}
|
||||
normalizeWideRow(.eight, row, cells, count, row_or);
|
||||
} else {
|
||||
try decodeWordCells(
|
||||
_ = try decodeWordCells(
|
||||
.eight,
|
||||
page,
|
||||
row,
|
||||
@@ -1102,6 +1105,9 @@ inline fn widenValue(
|
||||
|
||||
/// Decode one row of width-four or fallback full-width cells through the
|
||||
/// complete per-cell normalization path.
|
||||
///
|
||||
/// Returns the bitwise OR of every decoded wire word so callers can gate
|
||||
/// the trailing wide-pair resolution pass without a second scan.
|
||||
fn decodeWordCells(
|
||||
comptime width: Cell.EncodedWidth,
|
||||
page: *TerminalPage,
|
||||
@@ -1111,8 +1117,9 @@ fn decodeWordCells(
|
||||
reader: *std.Io.Reader,
|
||||
style_remap: *const StyleRemap,
|
||||
hyperlink_remap: *const HyperlinkRemap,
|
||||
) DecodeError!void {
|
||||
) DecodeError!u64 {
|
||||
const size = comptime width.size();
|
||||
var row_or: u64 = 0;
|
||||
|
||||
// The staged payload path has the complete row buffered.
|
||||
const total = count * size;
|
||||
@@ -1124,6 +1131,7 @@ fn decodeWordCells(
|
||||
bytes[x * size ..][0..size],
|
||||
.little,
|
||||
));
|
||||
row_or |= bits;
|
||||
applyCell(
|
||||
page,
|
||||
row,
|
||||
@@ -1135,12 +1143,14 @@ fn decodeWordCells(
|
||||
);
|
||||
}
|
||||
reader.toss(total);
|
||||
return;
|
||||
normalizeWideRow(width, row, cells, count, row_or);
|
||||
return row_or;
|
||||
}
|
||||
|
||||
// Streaming sources fall back to per-cell reads.
|
||||
for (0..count) |x| {
|
||||
const bits = width.extend(try io.readInt(reader, width.Int()));
|
||||
row_or |= bits;
|
||||
applyCell(
|
||||
page,
|
||||
row,
|
||||
@@ -1151,6 +1161,28 @@ fn decodeWordCells(
|
||||
hyperlink_remap,
|
||||
);
|
||||
}
|
||||
normalizeWideRow(width, row, cells, count, row_or);
|
||||
return row_or;
|
||||
}
|
||||
|
||||
/// Resolve wide-pair relationships for one decoded row.
|
||||
///
|
||||
/// Cell decoding stores every cell unresolved, so this pass applies
|
||||
/// `normalizeWide` in order, which is equivalent to interleaving it with
|
||||
/// the stores. Rows whose word OR carries no wide bits are already
|
||||
/// normalized: every cell is narrow. Width four and narrower transports
|
||||
/// cannot encode wide bits at all, so those rows skip the check entirely.
|
||||
inline fn normalizeWideRow(
|
||||
comptime width: Cell.EncodedWidth,
|
||||
row: *const TerminalRow,
|
||||
cells: []TerminalCell,
|
||||
count: usize,
|
||||
row_or: u64,
|
||||
) void {
|
||||
if (comptime width != .eight) return;
|
||||
const wide_mask: u64 = comptime @bitCast(Cell{ .width = 3 });
|
||||
if (row_or & wide_mask == 0) return;
|
||||
for (0..count) |x| normalizeWide(row, cells, x);
|
||||
}
|
||||
|
||||
/// Whether the value is a valid Unicode scalar value.
|
||||
@@ -1160,10 +1192,10 @@ inline fn validScalar(cp: u32) bool {
|
||||
|
||||
/// Normalize one encoded cell word and store it at `cells[x]`.
|
||||
///
|
||||
/// This owns every per-cell decode rule except grapheme suffixes: content
|
||||
/// validation, reserved-value degradation, style and hyperlink remapping
|
||||
/// with reference counting, and wide-pair normalization against already
|
||||
/// decoded neighbors.
|
||||
/// This owns every per-cell decode rule except grapheme suffixes and
|
||||
/// wide-pair resolution: content validation, reserved-value degradation,
|
||||
/// and style and hyperlink remapping with reference counting. Callers run
|
||||
/// `normalizeWideRow` over the stored row afterward.
|
||||
fn applyCell(
|
||||
page: *TerminalPage,
|
||||
row: *TerminalRow,
|
||||
@@ -1179,7 +1211,6 @@ fn applyCell(
|
||||
// reference counting, or table lookups.
|
||||
if (bits_wire == 0) {
|
||||
storeCell(cell, 0);
|
||||
normalizeWide(row, cells, x);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1247,8 +1278,6 @@ fn applyCell(
|
||||
page.hyperlink_set.release(page.memory, link_native);
|
||||
};
|
||||
}
|
||||
|
||||
normalizeWide(row, cells, x);
|
||||
}
|
||||
|
||||
/// Resolve wide-pair relationships for the cell at `x` against its already
|
||||
|
||||
Reference in New Issue
Block a user