Compare commits
10 Commits
494d2c40cf
...
6bf69e8572
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bf69e8572 | |||
| 07585a314c | |||
| bdb33450f1 | |||
|
|
2c020bb6cb | ||
|
|
71a533ec34 | ||
|
|
54b08c7eca | ||
|
|
2fcdb286af | ||
|
|
e6848ec684 | ||
|
|
38822d66b8 | ||
|
|
586150de59 |
@@ -5,7 +5,7 @@ connection.gradle.distribution=GRADLE_DISTRIBUTION(LOCAL_INSTALLATION(/home/anon
|
||||
connection.project.dir=
|
||||
eclipse.preferences.version=1
|
||||
gradle.user.home=
|
||||
java.home=/nix/store/c3pl7bqrx3d2rc3dh98z6yaj0mv1p52g-openjdk-21.0.10+7/lib/openjdk
|
||||
java.home=/home/anon/.local/lib/graalvm-jdk-21.0.9+7.1
|
||||
jvm.arguments=
|
||||
offline.mode=false
|
||||
override.workspace.settings=true
|
||||
|
||||
8
flake.lock
generated
8
flake.lock
generated
@@ -70,11 +70,11 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1780079529,
|
||||
"narHash": "sha256-AxlGTL8c5xSLcQHvWlm994IdOqxsN8iKrA02Cpv7vso=",
|
||||
"lastModified": 1780258814,
|
||||
"narHash": "sha256-8rxL7xaZ/loYg3zdt0w5+hfNyHFVknDZN360NzrtCsQ=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "68121d50b52fb56038871c97c97e7a12ffe987c2",
|
||||
"revCount": 20,
|
||||
"rev": "6a3d5aa0b0b1f738c958e2a2f0249574c07d9c4d",
|
||||
"revCount": 23,
|
||||
"type": "git",
|
||||
"url": "https://gitea.gregorlohaus.com/gregor/jlibghostty.git"
|
||||
},
|
||||
|
||||
@@ -244,8 +244,7 @@ public final class Compositor {
|
||||
drawTabBar(gc, canvas.getWidth(), topInset);
|
||||
}
|
||||
for (TerminalPane pane : panes) {
|
||||
pane.paintFull(gc, isActive(pane));
|
||||
paneContentVersion.put(pane, pane.contentVersion());
|
||||
paneContentVersion.put(pane, pane.paintFull(gc, isActive(pane)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,8 +261,7 @@ public final class Compositor {
|
||||
if (drawn != null && drawn == pane.contentVersion()) {
|
||||
continue;
|
||||
}
|
||||
pane.paintIncremental(gc, isActive(pane));
|
||||
paneContentVersion.put(pane, pane.contentVersion());
|
||||
paneContentVersion.put(pane, pane.paintIncremental(gc, isActive(pane)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import dev.jlibghostty.RenderColor;
|
||||
import dev.jlibghostty.RenderCursorStyle;
|
||||
import dev.jlibghostty.RenderRow;
|
||||
import dev.jlibghostty.RenderStateSnapshot;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.SnapshotParameters;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
@@ -101,11 +102,7 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
||||
if (dirty == DIRTY_FULL) {
|
||||
software.paintFullOrShifted(gc, target.snapshotFull(), px, py, width, height, active);
|
||||
} else if (dirty == DIRTY_PARTIAL) {
|
||||
if (snapshot != null && snapshot.renderRows().size() == snapshot.rows()) {
|
||||
software.paintFullOrShifted(gc, snapshot, px, py, width, height, active);
|
||||
} else {
|
||||
software.paintDirty(gc, snapshot, px, py, width, height, active);
|
||||
}
|
||||
software.paintDirty(gc, target, snapshot, px, py, width, height, active);
|
||||
}
|
||||
// dirty == FALSE: nothing visible changed.
|
||||
}
|
||||
@@ -679,12 +676,42 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
||||
private long[] rowHashes = new long[0];
|
||||
private CursorState lastCursor = CursorState.none();
|
||||
private GlyphCache glyphs;
|
||||
// Half-open [min, max) vertical span of buffer rows written since the last present, so
|
||||
// present() can upload only that band to the GPU instead of the whole pane texture.
|
||||
private int dirtyMinY = Integer.MAX_VALUE;
|
||||
private int dirtyMaxY = Integer.MIN_VALUE;
|
||||
|
||||
private void invalidate() {
|
||||
rowHashes = new long[0];
|
||||
lastCursor = CursorState.none();
|
||||
}
|
||||
|
||||
// Record that buffer rows [y0, y1) changed; clamped to the buffer in dirtyRegion().
|
||||
private void markDirtyRows(int y0, int y1) {
|
||||
if (y0 < dirtyMinY) {
|
||||
dirtyMinY = y0;
|
||||
}
|
||||
if (y1 > dirtyMaxY) {
|
||||
dirtyMaxY = y1;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetDirty() {
|
||||
dirtyMinY = Integer.MAX_VALUE;
|
||||
dirtyMaxY = Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
// The region to hand PixelBuffer.updateBuffer: a full-width band covering the rows
|
||||
// written this frame (clamped to the buffer), or EMPTY when nothing changed.
|
||||
private Rectangle2D dirtyRegion() {
|
||||
int y0 = Math.max(0, dirtyMinY);
|
||||
int y1 = Math.min(height, dirtyMaxY);
|
||||
if (y0 >= y1) {
|
||||
return Rectangle2D.EMPTY;
|
||||
}
|
||||
return new Rectangle2D(0, y0, width, y1 - y0);
|
||||
}
|
||||
|
||||
private void paintFull(GraphicsContext gc, RenderStateSnapshot snapshot,
|
||||
double px, double py, double paneWidth, double paneHeight, boolean active) {
|
||||
ensure(paneWidth, paneHeight);
|
||||
@@ -735,14 +762,14 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
||||
present(gc, px, py);
|
||||
}
|
||||
|
||||
private void paintDirty(GraphicsContext gc, RenderStateSnapshot snapshot,
|
||||
private void paintDirty(GraphicsContext gc, RenderTarget target, RenderStateSnapshot snapshot,
|
||||
double px, double py, double paneWidth, double paneHeight, boolean active) {
|
||||
ensure(paneWidth, paneHeight);
|
||||
if (snapshot == null) {
|
||||
return;
|
||||
}
|
||||
if (rowHashes.length != snapshot.rows()) {
|
||||
paintFull(gc, snapshot, px, py, paneWidth, paneHeight, active);
|
||||
paintFull(gc, target.snapshotFull(), px, py, paneWidth, paneHeight, active);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -751,6 +778,7 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
||||
int newCursorRow = cursor.viewportRow();
|
||||
boolean cursorChanged = !cursor.equals(lastCursor);
|
||||
boolean[] repainted = new boolean[snapshot.rows()];
|
||||
boolean needsCursorDraw = cursorChanged;
|
||||
|
||||
for (RenderRow row : snapshot.renderRows()) {
|
||||
if (!row.dirty()) {
|
||||
@@ -759,28 +787,46 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
||||
paintRow(row);
|
||||
rowHashes[row.row()] = rowHash(row);
|
||||
repainted[row.row()] = true;
|
||||
if (row.row() == newCursorRow) {
|
||||
needsCursorDraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursorChanged) {
|
||||
repaintCursorRow(snapshot, oldCursorRow, repainted);
|
||||
if (!repaintCursorRow(snapshot, oldCursorRow, repainted)) {
|
||||
paintFullOrShifted(gc, target.snapshotFull(), px, py, paneWidth, paneHeight, active);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (repaintedRowHasCursor(newCursorRow, repainted)
|
||||
&& !repaintCursorRow(snapshot, newCursorRow, repainted)) {
|
||||
paintFullOrShifted(gc, target.snapshotFull(), px, py, paneWidth, paneHeight, active);
|
||||
return;
|
||||
}
|
||||
repaintCursorRow(snapshot, newCursorRow, repainted);
|
||||
lastCursor = cursor;
|
||||
drawCursor(snapshot);
|
||||
if (needsCursorDraw) {
|
||||
drawCursor(snapshot);
|
||||
}
|
||||
drawBorder(active);
|
||||
present(gc, px, py);
|
||||
}
|
||||
|
||||
private void repaintCursorRow(RenderStateSnapshot snapshot, int rowIndex, boolean[] repainted) {
|
||||
private boolean repaintCursorRow(RenderStateSnapshot snapshot, int rowIndex, boolean[] repainted) {
|
||||
if (rowIndex < 0 || rowIndex >= repainted.length || repainted[rowIndex]) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
RenderRow row = rowByIndex(snapshot, rowIndex);
|
||||
if (row != null) {
|
||||
paintRow(row);
|
||||
rowHashes[rowIndex] = rowHash(row);
|
||||
repainted[rowIndex] = true;
|
||||
if (row == null || !row.dirty()) {
|
||||
return false;
|
||||
}
|
||||
paintRow(row);
|
||||
rowHashes[rowIndex] = rowHash(row);
|
||||
repainted[rowIndex] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean repaintedRowHasCursor(int rowIndex, boolean[] repainted) {
|
||||
return rowIndex >= 0 && rowIndex < repainted.length && repainted[rowIndex];
|
||||
}
|
||||
|
||||
private RenderRow rowByIndex(RenderStateSnapshot snapshot, int rowIndex) {
|
||||
@@ -820,7 +866,11 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
||||
}
|
||||
|
||||
private void present(GraphicsContext gc, double px, double py) {
|
||||
pixelBuffer.updateBuffer(ignored -> null);
|
||||
// Only re-upload the rows that actually changed; the unchanged remainder of the pane
|
||||
// texture is already correct on the GPU from the previous frame.
|
||||
Rectangle2D dirty = dirtyRegion();
|
||||
resetDirty();
|
||||
pixelBuffer.updateBuffer(ignored -> dirty);
|
||||
gc.drawImage(image, px, py);
|
||||
}
|
||||
|
||||
@@ -900,6 +950,9 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
||||
int dy = rows * lineHeight();
|
||||
int top = contentTop();
|
||||
int contentHeight = rowHashes.length * lineHeight();
|
||||
// The whole content region shifts; the arraycopy below moves pixels that the
|
||||
// per-strip fillRect calls don't touch, so mark the full content band for upload.
|
||||
markDirtyRows(top, top + contentHeight);
|
||||
if (dy == 0 || Math.abs(dy) >= contentHeight) {
|
||||
fillRect(0, top, width, contentHeight, argbPre(PANE_BACKGROUND));
|
||||
return;
|
||||
@@ -1020,26 +1073,28 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
||||
int red = (rgb >> 16) & 0xff;
|
||||
int green = (rgb >> 8) & 0xff;
|
||||
int blue = rgb & 0xff;
|
||||
for (int gy = 0; gy < glyph.height; gy++) {
|
||||
int py = y + gy;
|
||||
if (py < 0 || py >= height) {
|
||||
continue;
|
||||
}
|
||||
int rowOffset = py * width;
|
||||
// Clamp the glyph rectangle to the buffer once, so the inner loops carry no
|
||||
// per-pixel bounds check (this is the hottest pixel loop on a text repaint).
|
||||
int gyStart = Math.max(0, -y);
|
||||
int gyEnd = Math.min(glyph.height, height - y);
|
||||
int gxStart = Math.max(0, -x);
|
||||
int gxEnd = Math.min(glyph.width, width - x);
|
||||
if (gyStart >= gyEnd || gxStart >= gxEnd) {
|
||||
return;
|
||||
}
|
||||
for (int gy = gyStart; gy < gyEnd; gy++) {
|
||||
int rowOffset = (y + gy) * width;
|
||||
int glyphOffset = gy * glyph.width;
|
||||
for (int gx = 0; gx < glyph.width; gx++) {
|
||||
int px = x + gx;
|
||||
if (px < 0 || px >= width) {
|
||||
continue;
|
||||
}
|
||||
for (int gx = gxStart; gx < gxEnd; gx++) {
|
||||
int alpha = glyph.alpha[glyphOffset + gx] & 0xff;
|
||||
if (alpha == 0) {
|
||||
continue;
|
||||
}
|
||||
int index = rowOffset + px;
|
||||
int index = rowOffset + x + gx;
|
||||
pixels[index] = blendOpaque(pixels[index], red, green, blue, alpha);
|
||||
}
|
||||
}
|
||||
markDirtyRows(y + gyStart, y + gyEnd);
|
||||
}
|
||||
|
||||
private int blendOpaque(int dst, int red, int green, int blue, int alpha) {
|
||||
@@ -1078,11 +1133,16 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
||||
}
|
||||
|
||||
private void strokeRect(int x, int y, int w, int h, int color, int lineWidth) {
|
||||
// The border is redrawn every frame to restore the side edges over the rows we
|
||||
// repaint, but its pixels never change between incremental frames. Write it without
|
||||
// marking the dirty band: the segments inside a repainted row's band are already
|
||||
// covered by that band (and so re-uploaded), and the segments outside it are
|
||||
// identical to what is already on the GPU, so they need no upload.
|
||||
for (int i = 0; i < lineWidth; i++) {
|
||||
fillRect(x + i, y + i, w - (2 * i), 1, color);
|
||||
fillRect(x + i, y + h - 1 - i, w - (2 * i), 1, color);
|
||||
fillRect(x + i, y + i, 1, h - (2 * i), color);
|
||||
fillRect(x + w - 1 - i, y + i, 1, h - (2 * i), color);
|
||||
fillRectRaw(x + i, y + i, w - (2 * i), 1, color);
|
||||
fillRectRaw(x + i, y + h - 1 - i, w - (2 * i), 1, color);
|
||||
fillRectRaw(x + i, y + i, 1, h - (2 * i), color);
|
||||
fillRectRaw(x + w - 1 - i, y + i, 1, h - (2 * i), color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1103,19 +1163,30 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
||||
pixels[index] = blendOpaque(pixels[index], red, green, blue, alpha);
|
||||
}
|
||||
}
|
||||
markDirtyRows(y0, y1);
|
||||
}
|
||||
|
||||
private void fillRect(int x, int y, int w, int h, int color) {
|
||||
int y0 = Math.max(0, y);
|
||||
int y1 = Math.min(height, y + h);
|
||||
if (fillRectRaw(x, y, w, h, color)) {
|
||||
markDirtyRows(y0, y1);
|
||||
}
|
||||
}
|
||||
|
||||
// Raw fill with no dirty-band tracking; returns whether any pixels were written.
|
||||
private boolean fillRectRaw(int x, int y, int w, int h, int color) {
|
||||
int x0 = Math.max(0, x);
|
||||
int y0 = Math.max(0, y);
|
||||
int x1 = Math.min(width, x + w);
|
||||
int y1 = Math.min(height, y + h);
|
||||
if (x0 >= x1 || y0 >= y1) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
for (int py = y0; py < y1; py++) {
|
||||
Arrays.fill(pixels, (py * width) + x0, (py * width) + x1, color);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int contentLeft() {
|
||||
|
||||
@@ -283,13 +283,15 @@ public final class TerminalPane implements AutoCloseable, RenderTarget {
|
||||
}
|
||||
|
||||
/** Paint the whole pane; see {@link TerminalRenderer#paintFull}. */
|
||||
public void paintFull(GraphicsContext gc, boolean active) {
|
||||
public long paintFull(GraphicsContext gc, boolean active) {
|
||||
renderer.paintFull(gc, this, active);
|
||||
return snapshotVersion;
|
||||
}
|
||||
|
||||
/** Repaint what changed; see {@link TerminalRenderer#paintIncremental}. */
|
||||
public void paintIncremental(GraphicsContext gc, boolean active) {
|
||||
public long paintIncremental(GraphicsContext gc, boolean active) {
|
||||
renderer.paintIncremental(gc, this, active);
|
||||
return snapshotVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user