revert row diffing
This commit is contained in:
@@ -51,8 +51,6 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
private final TerminalMetrics metrics;
|
private final TerminalMetrics metrics;
|
||||||
// Decoded kitty images for this renderer's pane (kitty graphics state is per-terminal).
|
// Decoded kitty images for this renderer's pane (kitty graphics state is per-terminal).
|
||||||
private final Map<KittyImageKey, Image> kittyImageCache = new HashMap<>();
|
private final Map<KittyImageKey, Image> kittyImageCache = new HashMap<>();
|
||||||
private long[] rowHashes = new long[0];
|
|
||||||
private CursorState lastCursor = CursorState.none();
|
|
||||||
|
|
||||||
GhosttyTerminalRenderer(TerminalMetrics metrics) {
|
GhosttyTerminalRenderer(TerminalMetrics metrics) {
|
||||||
this.metrics = metrics;
|
this.metrics = metrics;
|
||||||
@@ -66,10 +64,8 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
double height = target.height();
|
double height = target.height();
|
||||||
gc.save();
|
gc.save();
|
||||||
clip(gc, px, py, width, height, target.clip());
|
clip(gc, px, py, width, height, target.clip());
|
||||||
RenderStateSnapshot snapshot = target.snapshotFull();
|
drawContent(gc, target, target.snapshotFull(), px, py, width, height, active,
|
||||||
drawContent(gc, target, snapshot, px, py, width, height, active,
|
|
||||||
target.kittyEnabled() && hasKittyGraphics(target));
|
target.kittyEnabled() && hasKittyGraphics(target));
|
||||||
rememberSnapshot(snapshot);
|
|
||||||
gc.restore();
|
gc.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,18 +79,12 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
clip(gc, px, py, width, height, target.clip());
|
clip(gc, px, py, width, height, target.clip());
|
||||||
if (target.kittyEnabled() && hasKittyGraphics(target)) {
|
if (target.kittyEnabled() && hasKittyGraphics(target)) {
|
||||||
// Kitty placements can move without a per-row dirty flag, so always redraw whole.
|
// Kitty placements can move without a per-row dirty flag, so always redraw whole.
|
||||||
RenderStateSnapshot snapshot = target.snapshotFull();
|
drawContent(gc, target, target.snapshotFull(), px, py, width, height, active, true);
|
||||||
drawContent(gc, target, snapshot, px, py, width, height, active, true);
|
|
||||||
rememberSnapshot(snapshot);
|
|
||||||
} else {
|
} else {
|
||||||
RenderStateSnapshot snapshot = target.snapshot();
|
RenderStateSnapshot snapshot = target.snapshot();
|
||||||
int dirty = snapshot == null ? DIRTY_FULL : snapshot.dirty();
|
int dirty = snapshot == null ? DIRTY_FULL : snapshot.dirty();
|
||||||
if (dirty == DIRTY_FULL) {
|
if (dirty == DIRTY_FULL) {
|
||||||
if (!drawChangedRows(gc, snapshot, px, py, width, height, active)) {
|
drawContent(gc, target, snapshot, px, py, width, height, active, false);
|
||||||
RenderStateSnapshot fullSnapshot = target.snapshotFull();
|
|
||||||
drawContent(gc, target, fullSnapshot, px, py, width, height, active, false);
|
|
||||||
rememberSnapshot(fullSnapshot);
|
|
||||||
}
|
|
||||||
} else if (dirty == DIRTY_PARTIAL) {
|
} else if (dirty == DIRTY_PARTIAL) {
|
||||||
drawDirtyRows(gc, snapshot, px, py, width, height, active);
|
drawDirtyRows(gc, snapshot, px, py, width, height, active);
|
||||||
}
|
}
|
||||||
@@ -103,86 +93,6 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
gc.restore();
|
gc.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some TUIs repaint the whole viewport for small logical changes. When ghostty gives us
|
|
||||||
// a full snapshot, compare row content with what we last painted and only redraw rows
|
|
||||||
// whose cells changed, plus old/new cursor rows because the cursor is an overlay.
|
|
||||||
private boolean drawChangedRows(
|
|
||||||
GraphicsContext gc,
|
|
||||||
RenderStateSnapshot snapshot,
|
|
||||||
double px,
|
|
||||||
double py,
|
|
||||||
double pw,
|
|
||||||
double ph,
|
|
||||||
boolean active
|
|
||||||
) {
|
|
||||||
if (snapshot == null || rowHashes.length != snapshot.rows() || snapshot.renderRows().size() != snapshot.rows()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
double cellWidth = metrics.cellWidth();
|
|
||||||
double lineHeight = metrics.lineHeight();
|
|
||||||
gc.setFontSmoothingType(FontSmoothingType.LCD);
|
|
||||||
gc.setFont(metrics.font());
|
|
||||||
double left = px + TerminalMetrics.PADDING;
|
|
||||||
double top = py + TerminalMetrics.PADDING;
|
|
||||||
double baseline = top + metrics.baselineOffset();
|
|
||||||
double contentBottom = top + snapshot.rows() * lineHeight;
|
|
||||||
int lastRow = snapshot.rows() - 1;
|
|
||||||
|
|
||||||
CursorState cursor = CursorState.from(snapshot);
|
|
||||||
long oldCursorRow = lastCursor.viewportRow();
|
|
||||||
long newCursorRow = cursor.viewportRow();
|
|
||||||
boolean cursorChanged = !cursor.equals(lastCursor);
|
|
||||||
double bandMin = Double.POSITIVE_INFINITY;
|
|
||||||
double bandMax = Double.NEGATIVE_INFINITY;
|
|
||||||
|
|
||||||
for (RenderRow row : snapshot.renderRows()) {
|
|
||||||
int rowIndex = row.row();
|
|
||||||
if (rowIndex < 0 || rowIndex >= rowHashes.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long hash = rowHash(row);
|
|
||||||
boolean repaint = hash != rowHashes[rowIndex]
|
|
||||||
|| (cursorChanged && (rowIndex == oldCursorRow || rowIndex == newCursorRow));
|
|
||||||
if (!repaint) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
double y0 = Math.floor(top + (rowIndex * lineHeight));
|
|
||||||
double y1 = Math.ceil(top + ((rowIndex + 1) * lineHeight));
|
|
||||||
gc.setFill(PANE_BACKGROUND);
|
|
||||||
gc.fillRect(px, y0, pw, y1 - y0);
|
|
||||||
paintSidePadding(gc, row, px, pw, left, cellWidth, y0, y1 - y0);
|
|
||||||
drawRow(gc, row, left, top, baseline, cellWidth, lineHeight);
|
|
||||||
rowHashes[rowIndex] = hash;
|
|
||||||
bandMin = Math.min(bandMin, y0);
|
|
||||||
bandMax = Math.max(bandMax, y1);
|
|
||||||
|
|
||||||
if (rowIndex == 0) {
|
|
||||||
gc.setFill(rowEdgeBackground(row, true));
|
|
||||||
gc.fillRect(px, py, pw, top - py);
|
|
||||||
bandMin = Math.min(bandMin, py);
|
|
||||||
}
|
|
||||||
if (rowIndex == lastRow) {
|
|
||||||
gc.setFill(rowEdgeBackground(row, true));
|
|
||||||
gc.fillRect(px, contentBottom, pw, py + ph - contentBottom);
|
|
||||||
bandMax = Math.max(bandMax, py + ph);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastCursor = cursor;
|
|
||||||
if (bandMin > bandMax) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
drawCursor(gc, snapshot, left, top, cellWidth, lineHeight);
|
|
||||||
gc.save();
|
|
||||||
clipRect(gc, px, bandMin, pw, bandMax - bandMin);
|
|
||||||
drawBorder(gc, px, py, pw, ph, active);
|
|
||||||
gc.restore();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full content render: background, border, all rows, cursor, and (when enabled) kitty
|
// Full content render: background, border, all rows, cursor, and (when enabled) kitty
|
||||||
// graphics. Used by the kitty direct path and by full redraws.
|
// graphics. Used by the kitty direct path and by full redraws.
|
||||||
private void drawContent(
|
private void drawContent(
|
||||||
@@ -280,7 +190,6 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
}
|
}
|
||||||
paintSidePadding(gc, row, px, pw, left, cellWidth, y0, y1 - y0);
|
paintSidePadding(gc, row, px, pw, left, cellWidth, y0, y1 - y0);
|
||||||
drawRow(gc, row, left, top, baseline, cellWidth, lineHeight);
|
drawRow(gc, row, left, top, baseline, cellWidth, lineHeight);
|
||||||
rememberRow(row);
|
|
||||||
bandMin = Math.min(bandMin, y0);
|
bandMin = Math.min(bandMin, y0);
|
||||||
bandMax = Math.max(bandMax, y1);
|
bandMax = Math.max(bandMax, y1);
|
||||||
// Edge rows also own the top/bottom padding strip; repaint it and extend the
|
// Edge rows also own the top/bottom padding strip; repaint it and extend the
|
||||||
@@ -308,7 +217,6 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
if (cursorRowDirty) {
|
if (cursorRowDirty) {
|
||||||
drawCursor(gc, snapshot, left, top, cellWidth, lineHeight);
|
drawCursor(gc, snapshot, left, top, cellWidth, lineHeight);
|
||||||
}
|
}
|
||||||
lastCursor = CursorState.from(snapshot);
|
|
||||||
// Repainting rows clears the side borders within the band; restore just those
|
// Repainting rows clears the side borders within the band; restore just those
|
||||||
// segments, clipped to the band so we don't redraw the whole outline.
|
// segments, clipped to the band so we don't redraw the whole outline.
|
||||||
gc.save();
|
gc.save();
|
||||||
@@ -336,62 +244,6 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
gc.strokeRect(x + 0.5, y + 0.5, width - 1.0, height - 1.0);
|
gc.strokeRect(x + 0.5, y + 0.5, width - 1.0, height - 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rememberSnapshot(RenderStateSnapshot snapshot) {
|
|
||||||
if (snapshot == null) {
|
|
||||||
rowHashes = new long[0];
|
|
||||||
lastCursor = CursorState.none();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (rowHashes.length != snapshot.rows()) {
|
|
||||||
rowHashes = new long[snapshot.rows()];
|
|
||||||
}
|
|
||||||
for (RenderRow row : snapshot.renderRows()) {
|
|
||||||
rememberRow(row);
|
|
||||||
}
|
|
||||||
lastCursor = CursorState.from(snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rememberRow(RenderRow row) {
|
|
||||||
if (row.row() >= 0 && row.row() < rowHashes.length) {
|
|
||||||
rowHashes[row.row()] = rowHash(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long rowHash(RenderRow row) {
|
|
||||||
long hash = 0xcbf29ce484222325L;
|
|
||||||
hash = mix(hash, row.row());
|
|
||||||
for (RenderCell cell : row.cells()) {
|
|
||||||
hash = mix(hash, cell.column());
|
|
||||||
hash = mix(hash, cell.inverse() ? 1 : 0);
|
|
||||||
hash = mix(hash, cell.selected() ? 1 : 0);
|
|
||||||
hash = mixColor(hash, cell.foreground().orElse(null));
|
|
||||||
hash = mixColor(hash, cell.background().orElse(null));
|
|
||||||
for (int codepoint : cell.codepoints()) {
|
|
||||||
hash = mix(hash, codepoint);
|
|
||||||
}
|
|
||||||
if (cell.kittyPlaceholder().isPresent()) {
|
|
||||||
KittyPlaceholder placeholder = cell.kittyPlaceholder().get();
|
|
||||||
hash = mix(hash, placeholder.imageId());
|
|
||||||
hash = mix(hash, placeholder.placementId());
|
|
||||||
hash = mix(hash, placeholder.sourceRow());
|
|
||||||
hash = mix(hash, placeholder.sourceColumn());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long mixColor(long hash, RenderColor color) {
|
|
||||||
if (color == null) {
|
|
||||||
return mix(hash, -1);
|
|
||||||
}
|
|
||||||
return mix(hash, (color.red() << 16) | (color.green() << 8) | color.blue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long mix(long hash, long value) {
|
|
||||||
hash ^= value;
|
|
||||||
return hash * 0x100000001b3L;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Effective background colour of a cell as it is drawn (reverse video swaps fg/bg, an
|
// Effective background colour of a cell as it is drawn (reverse video swaps fg/bg, an
|
||||||
// unset colour falls back to the defaults).
|
// unset colour falls back to the defaults).
|
||||||
private static Color cellBackgroundColor(RenderCell cell) {
|
private static Color cellBackgroundColor(RenderCell cell) {
|
||||||
@@ -823,29 +675,6 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
private record SourceRect(double x, double y, double width, double height) {
|
private record SourceRect(double x, double y, double width, double height) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CursorState(boolean visible, boolean hasViewport, long viewportX, long viewportY, RenderCursorStyle style) {
|
|
||||||
private static CursorState none() {
|
|
||||||
return new CursorState(false, false, -1, -1, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CursorState from(RenderStateSnapshot snapshot) {
|
|
||||||
if (snapshot == null) {
|
|
||||||
return none();
|
|
||||||
}
|
|
||||||
boolean hasViewport = snapshot.cursorViewportHasValue();
|
|
||||||
return new CursorState(
|
|
||||||
snapshot.cursorVisible(),
|
|
||||||
hasViewport,
|
|
||||||
hasViewport ? snapshot.cursorViewportX() : -1,
|
|
||||||
hasViewport ? snapshot.cursorViewportY() : -1,
|
|
||||||
snapshot.cursorStyle());
|
|
||||||
}
|
|
||||||
|
|
||||||
private long viewportRow() {
|
|
||||||
return visible && hasViewport ? viewportY : -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class KittyPlaceholderBounds {
|
private static final class KittyPlaceholderBounds {
|
||||||
private int minRow = Integer.MAX_VALUE;
|
private int minRow = Integer.MAX_VALUE;
|
||||||
private int maxRow = Integer.MIN_VALUE;
|
private int maxRow = Integer.MIN_VALUE;
|
||||||
|
|||||||
Reference in New Issue
Block a user