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=
|
connection.project.dir=
|
||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
gradle.user.home=
|
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=
|
jvm.arguments=
|
||||||
offline.mode=false
|
offline.mode=false
|
||||||
override.workspace.settings=true
|
override.workspace.settings=true
|
||||||
|
|||||||
8
flake.lock
generated
8
flake.lock
generated
@@ -70,11 +70,11 @@
|
|||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780079529,
|
"lastModified": 1780258814,
|
||||||
"narHash": "sha256-AxlGTL8c5xSLcQHvWlm994IdOqxsN8iKrA02Cpv7vso=",
|
"narHash": "sha256-8rxL7xaZ/loYg3zdt0w5+hfNyHFVknDZN360NzrtCsQ=",
|
||||||
"ref": "refs/heads/main",
|
"ref": "refs/heads/main",
|
||||||
"rev": "68121d50b52fb56038871c97c97e7a12ffe987c2",
|
"rev": "6a3d5aa0b0b1f738c958e2a2f0249574c07d9c4d",
|
||||||
"revCount": 20,
|
"revCount": 23,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.gregorlohaus.com/gregor/jlibghostty.git"
|
"url": "https://gitea.gregorlohaus.com/gregor/jlibghostty.git"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -244,8 +244,7 @@ public final class Compositor {
|
|||||||
drawTabBar(gc, canvas.getWidth(), topInset);
|
drawTabBar(gc, canvas.getWidth(), topInset);
|
||||||
}
|
}
|
||||||
for (TerminalPane pane : panes) {
|
for (TerminalPane pane : panes) {
|
||||||
pane.paintFull(gc, isActive(pane));
|
paneContentVersion.put(pane, pane.paintFull(gc, isActive(pane)));
|
||||||
paneContentVersion.put(pane, pane.contentVersion());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,8 +261,7 @@ public final class Compositor {
|
|||||||
if (drawn != null && drawn == pane.contentVersion()) {
|
if (drawn != null && drawn == pane.contentVersion()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
pane.paintIncremental(gc, isActive(pane));
|
paneContentVersion.put(pane, pane.paintIncremental(gc, isActive(pane)));
|
||||||
paneContentVersion.put(pane, pane.contentVersion());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import dev.jlibghostty.RenderColor;
|
|||||||
import dev.jlibghostty.RenderCursorStyle;
|
import dev.jlibghostty.RenderCursorStyle;
|
||||||
import dev.jlibghostty.RenderRow;
|
import dev.jlibghostty.RenderRow;
|
||||||
import dev.jlibghostty.RenderStateSnapshot;
|
import dev.jlibghostty.RenderStateSnapshot;
|
||||||
|
import javafx.geometry.Rectangle2D;
|
||||||
import javafx.scene.SnapshotParameters;
|
import javafx.scene.SnapshotParameters;
|
||||||
import javafx.scene.canvas.Canvas;
|
import javafx.scene.canvas.Canvas;
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
@@ -101,11 +102,7 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
if (dirty == DIRTY_FULL) {
|
if (dirty == DIRTY_FULL) {
|
||||||
software.paintFullOrShifted(gc, target.snapshotFull(), px, py, width, height, active);
|
software.paintFullOrShifted(gc, target.snapshotFull(), px, py, width, height, active);
|
||||||
} else if (dirty == DIRTY_PARTIAL) {
|
} else if (dirty == DIRTY_PARTIAL) {
|
||||||
if (snapshot != null && snapshot.renderRows().size() == snapshot.rows()) {
|
software.paintDirty(gc, target, snapshot, px, py, width, height, active);
|
||||||
software.paintFullOrShifted(gc, snapshot, px, py, width, height, active);
|
|
||||||
} else {
|
|
||||||
software.paintDirty(gc, snapshot, px, py, width, height, active);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// dirty == FALSE: nothing visible changed.
|
// dirty == FALSE: nothing visible changed.
|
||||||
}
|
}
|
||||||
@@ -679,12 +676,42 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
private long[] rowHashes = new long[0];
|
private long[] rowHashes = new long[0];
|
||||||
private CursorState lastCursor = CursorState.none();
|
private CursorState lastCursor = CursorState.none();
|
||||||
private GlyphCache glyphs;
|
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() {
|
private void invalidate() {
|
||||||
rowHashes = new long[0];
|
rowHashes = new long[0];
|
||||||
lastCursor = CursorState.none();
|
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,
|
private void paintFull(GraphicsContext gc, RenderStateSnapshot snapshot,
|
||||||
double px, double py, double paneWidth, double paneHeight, boolean active) {
|
double px, double py, double paneWidth, double paneHeight, boolean active) {
|
||||||
ensure(paneWidth, paneHeight);
|
ensure(paneWidth, paneHeight);
|
||||||
@@ -735,14 +762,14 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
present(gc, px, py);
|
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) {
|
double px, double py, double paneWidth, double paneHeight, boolean active) {
|
||||||
ensure(paneWidth, paneHeight);
|
ensure(paneWidth, paneHeight);
|
||||||
if (snapshot == null) {
|
if (snapshot == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rowHashes.length != snapshot.rows()) {
|
if (rowHashes.length != snapshot.rows()) {
|
||||||
paintFull(gc, snapshot, px, py, paneWidth, paneHeight, active);
|
paintFull(gc, target.snapshotFull(), px, py, paneWidth, paneHeight, active);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,6 +778,7 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
int newCursorRow = cursor.viewportRow();
|
int newCursorRow = cursor.viewportRow();
|
||||||
boolean cursorChanged = !cursor.equals(lastCursor);
|
boolean cursorChanged = !cursor.equals(lastCursor);
|
||||||
boolean[] repainted = new boolean[snapshot.rows()];
|
boolean[] repainted = new boolean[snapshot.rows()];
|
||||||
|
boolean needsCursorDraw = cursorChanged;
|
||||||
|
|
||||||
for (RenderRow row : snapshot.renderRows()) {
|
for (RenderRow row : snapshot.renderRows()) {
|
||||||
if (!row.dirty()) {
|
if (!row.dirty()) {
|
||||||
@@ -759,28 +787,46 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
paintRow(row);
|
paintRow(row);
|
||||||
rowHashes[row.row()] = rowHash(row);
|
rowHashes[row.row()] = rowHash(row);
|
||||||
repainted[row.row()] = true;
|
repainted[row.row()] = true;
|
||||||
|
if (row.row() == newCursorRow) {
|
||||||
|
needsCursorDraw = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursorChanged) {
|
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;
|
lastCursor = cursor;
|
||||||
drawCursor(snapshot);
|
if (needsCursorDraw) {
|
||||||
|
drawCursor(snapshot);
|
||||||
|
}
|
||||||
drawBorder(active);
|
drawBorder(active);
|
||||||
present(gc, px, py);
|
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]) {
|
if (rowIndex < 0 || rowIndex >= repainted.length || repainted[rowIndex]) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
RenderRow row = rowByIndex(snapshot, rowIndex);
|
RenderRow row = rowByIndex(snapshot, rowIndex);
|
||||||
if (row != null) {
|
if (row == null || !row.dirty()) {
|
||||||
paintRow(row);
|
return false;
|
||||||
rowHashes[rowIndex] = rowHash(row);
|
|
||||||
repainted[rowIndex] = true;
|
|
||||||
}
|
}
|
||||||
|
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) {
|
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) {
|
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);
|
gc.drawImage(image, px, py);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,6 +950,9 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
int dy = rows * lineHeight();
|
int dy = rows * lineHeight();
|
||||||
int top = contentTop();
|
int top = contentTop();
|
||||||
int contentHeight = rowHashes.length * lineHeight();
|
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) {
|
if (dy == 0 || Math.abs(dy) >= contentHeight) {
|
||||||
fillRect(0, top, width, contentHeight, argbPre(PANE_BACKGROUND));
|
fillRect(0, top, width, contentHeight, argbPre(PANE_BACKGROUND));
|
||||||
return;
|
return;
|
||||||
@@ -1020,26 +1073,28 @@ final class GhosttyTerminalRenderer extends TerminalRenderer {
|
|||||||
int red = (rgb >> 16) & 0xff;
|
int red = (rgb >> 16) & 0xff;
|
||||||
int green = (rgb >> 8) & 0xff;
|
int green = (rgb >> 8) & 0xff;
|
||||||
int blue = rgb & 0xff;
|
int blue = rgb & 0xff;
|
||||||
for (int gy = 0; gy < glyph.height; gy++) {
|
// Clamp the glyph rectangle to the buffer once, so the inner loops carry no
|
||||||
int py = y + gy;
|
// per-pixel bounds check (this is the hottest pixel loop on a text repaint).
|
||||||
if (py < 0 || py >= height) {
|
int gyStart = Math.max(0, -y);
|
||||||
continue;
|
int gyEnd = Math.min(glyph.height, height - y);
|
||||||
}
|
int gxStart = Math.max(0, -x);
|
||||||
int rowOffset = py * width;
|
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;
|
int glyphOffset = gy * glyph.width;
|
||||||
for (int gx = 0; gx < glyph.width; gx++) {
|
for (int gx = gxStart; gx < gxEnd; gx++) {
|
||||||
int px = x + gx;
|
|
||||||
if (px < 0 || px >= width) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int alpha = glyph.alpha[glyphOffset + gx] & 0xff;
|
int alpha = glyph.alpha[glyphOffset + gx] & 0xff;
|
||||||
if (alpha == 0) {
|
if (alpha == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int index = rowOffset + px;
|
int index = rowOffset + x + gx;
|
||||||
pixels[index] = blendOpaque(pixels[index], red, green, blue, alpha);
|
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) {
|
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) {
|
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++) {
|
for (int i = 0; i < lineWidth; i++) {
|
||||||
fillRect(x + i, y + i, w - (2 * i), 1, color);
|
fillRectRaw(x + i, y + i, w - (2 * i), 1, color);
|
||||||
fillRect(x + i, y + h - 1 - i, w - (2 * i), 1, color);
|
fillRectRaw(x + i, y + h - 1 - i, w - (2 * i), 1, color);
|
||||||
fillRect(x + i, y + i, 1, h - (2 * i), color);
|
fillRectRaw(x + i, y + i, 1, h - (2 * i), color);
|
||||||
fillRect(x + w - 1 - 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);
|
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) {
|
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 x0 = Math.max(0, x);
|
||||||
int y0 = Math.max(0, y);
|
int y0 = Math.max(0, y);
|
||||||
int x1 = Math.min(width, x + w);
|
int x1 = Math.min(width, x + w);
|
||||||
int y1 = Math.min(height, y + h);
|
int y1 = Math.min(height, y + h);
|
||||||
if (x0 >= x1 || y0 >= y1) {
|
if (x0 >= x1 || y0 >= y1) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
for (int py = y0; py < y1; py++) {
|
for (int py = y0; py < y1; py++) {
|
||||||
Arrays.fill(pixels, (py * width) + x0, (py * width) + x1, color);
|
Arrays.fill(pixels, (py * width) + x0, (py * width) + x1, color);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int contentLeft() {
|
private int contentLeft() {
|
||||||
|
|||||||
@@ -283,13 +283,15 @@ public final class TerminalPane implements AutoCloseable, RenderTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Paint the whole pane; see {@link TerminalRenderer#paintFull}. */
|
/** 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);
|
renderer.paintFull(gc, this, active);
|
||||||
|
return snapshotVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Repaint what changed; see {@link TerminalRenderer#paintIncremental}. */
|
/** 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);
|
renderer.paintIncremental(gc, this, active);
|
||||||
|
return snapshotVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user