expose dirty on render state for incremental rendering
This commit is contained in:
@@ -29,10 +29,17 @@ public final class RenderState implements AutoCloseable {
|
||||
GhosttyLibrary.RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE
|
||||
);
|
||||
|
||||
// When only some rows changed (PARTIAL), skip marshalling cells for clean rows;
|
||||
// FULL means the whole frame must be redrawn, so every row's cells are needed. A
|
||||
// throwaway render state (a single update) always reports FULL, so callers that do
|
||||
// not keep the state around keep getting fully-populated rows exactly as before.
|
||||
int dirty = library.renderStateGetI32(handle, GhosttyLibrary.RENDER_STATE_DATA_DIRTY);
|
||||
boolean dirtyRowsOnly = dirty != GhosttyLibrary.RENDER_STATE_DIRTY_FULL;
|
||||
|
||||
return new RenderStateSnapshot(
|
||||
library.renderStateGetU16(handle, GhosttyLibrary.RENDER_STATE_DATA_COLS),
|
||||
library.renderStateGetU16(handle, GhosttyLibrary.RENDER_STATE_DATA_ROWS),
|
||||
library.renderStateGetI32(handle, GhosttyLibrary.RENDER_STATE_DATA_DIRTY),
|
||||
dirty,
|
||||
RenderCursorStyle.fromNative(
|
||||
library.renderStateGetI32(handle, GhosttyLibrary.RENDER_STATE_DATA_CURSOR_VISUAL_STYLE)
|
||||
),
|
||||
@@ -48,7 +55,7 @@ public final class RenderState implements AutoCloseable {
|
||||
: -1,
|
||||
cursorViewportHasValue
|
||||
&& library.renderStateGetBoolean(handle, GhosttyLibrary.RENDER_STATE_DATA_CURSOR_VIEWPORT_WIDE_TAIL),
|
||||
rows()
|
||||
library.renderStateRows(handle, dirtyRowsOnly)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,6 +64,16 @@ public final class RenderState implements AutoCloseable {
|
||||
return library.renderStateRows(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the global and per-row dirty flags. Call after rendering a frame so the next
|
||||
* {@link #update} reports only the rows that change after this point. Required for
|
||||
* incremental rendering with a render state reused across frames.
|
||||
*/
|
||||
public void resetDirty() {
|
||||
ensureOpen();
|
||||
library.renderStateResetDirty(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (closed.compareAndSet(false, true)) {
|
||||
|
||||
@@ -94,6 +94,15 @@ public final class GhosttyLibrary {
|
||||
public static final int RENDER_STATE_ROW_DATA_DIRTY = 1;
|
||||
public static final int RENDER_STATE_ROW_DATA_CELLS = 3;
|
||||
|
||||
// ghostty_render_state_set / _row_set option selectors (both DIRTY == 0).
|
||||
public static final int RENDER_STATE_OPTION_DIRTY = 0;
|
||||
public static final int RENDER_STATE_ROW_OPTION_DIRTY = 0;
|
||||
|
||||
// GhosttyRenderStateDirty values returned by RENDER_STATE_DATA_DIRTY.
|
||||
public static final int RENDER_STATE_DIRTY_FALSE = 0;
|
||||
public static final int RENDER_STATE_DIRTY_PARTIAL = 1;
|
||||
public static final int RENDER_STATE_DIRTY_FULL = 2;
|
||||
|
||||
public static final int RENDER_STATE_ROW_CELLS_DATA_STYLE = 2;
|
||||
public static final int RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN = 3;
|
||||
public static final int RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF = 4;
|
||||
@@ -330,6 +339,8 @@ public final class GhosttyLibrary {
|
||||
private final MethodHandle renderStateFree;
|
||||
private final MethodHandle renderStateUpdate;
|
||||
private final MethodHandle renderStateGet;
|
||||
private final MethodHandle renderStateSet;
|
||||
private final MethodHandle renderStateRowSet;
|
||||
private final MethodHandle renderStateRowIteratorNew;
|
||||
private final MethodHandle renderStateRowIteratorFree;
|
||||
private final MethodHandle renderStateRowIteratorNext;
|
||||
@@ -409,6 +420,10 @@ public final class GhosttyLibrary {
|
||||
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER));
|
||||
renderStateGet = downcall(symbols, "ghostty_render_state_get",
|
||||
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
|
||||
renderStateSet = downcall(symbols, "ghostty_render_state_set",
|
||||
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
|
||||
renderStateRowSet = downcall(symbols, "ghostty_render_state_row_set",
|
||||
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
|
||||
renderStateRowIteratorNew = downcall(symbols, "ghostty_render_state_row_iterator_new",
|
||||
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER));
|
||||
renderStateRowIteratorFree = downcall(symbols, "ghostty_render_state_row_iterator_free",
|
||||
@@ -855,6 +870,16 @@ public final class GhosttyLibrary {
|
||||
}
|
||||
|
||||
public List<RenderRow> renderStateRows(MemorySegment state) {
|
||||
return renderStateRows(state, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the row list. When {@code dirtyRowsOnly} is true, cells are marshalled only
|
||||
* for rows ghostty flagged dirty; clean rows get an empty cell list. This lets callers
|
||||
* that keep a persistent render state skip the (expensive) per-cell marshalling for
|
||||
* rows that have not changed since the last {@link #renderStateResetDirty}.
|
||||
*/
|
||||
public List<RenderRow> renderStateRows(MemorySegment state, boolean dirtyRowsOnly) {
|
||||
MemorySegment iterator = renderStateRowIteratorNew();
|
||||
try {
|
||||
renderStatePopulateRowIterator(state, iterator);
|
||||
@@ -862,7 +887,10 @@ public final class GhosttyLibrary {
|
||||
int rowIndex = 0;
|
||||
while (renderStateRowIteratorNext(iterator)) {
|
||||
boolean dirty = renderStateRowGetBoolean(iterator, RENDER_STATE_ROW_DATA_DIRTY);
|
||||
rows.add(new RenderRow(rowIndex, dirty, renderStateRowCells(iterator)));
|
||||
List<RenderCell> cells = (dirtyRowsOnly && !dirty)
|
||||
? List.of()
|
||||
: renderStateRowCells(iterator);
|
||||
rows.add(new RenderRow(rowIndex, dirty, cells));
|
||||
rowIndex++;
|
||||
}
|
||||
return List.copyOf(rows);
|
||||
@@ -871,6 +899,41 @@ public final class GhosttyLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
public void renderStateSetDirty(MemorySegment state, int dirtyValue) {
|
||||
try (Arena arena = Arena.ofConfined()) {
|
||||
MemorySegment value = arena.allocate(C_INT);
|
||||
value.set(C_INT, 0, dirtyValue);
|
||||
int result = (int) renderStateSet.invoke(state, RENDER_STATE_OPTION_DIRTY, value);
|
||||
checkResult("ghostty_render_state_set", result);
|
||||
} catch (Throwable t) {
|
||||
rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the global dirty flag and every per-row dirty flag. The {@code update} call
|
||||
* does not reset dirty state, so a caller reusing a render state across frames must
|
||||
* call this after consuming a frame; the next {@code update} then reports only what
|
||||
* changed since now.
|
||||
*/
|
||||
public void renderStateResetDirty(MemorySegment state) {
|
||||
MemorySegment iterator = renderStateRowIteratorNew();
|
||||
try (Arena arena = Arena.ofConfined()) {
|
||||
renderStatePopulateRowIterator(state, iterator);
|
||||
MemorySegment falseValue = arena.allocate(C_BOOL);
|
||||
falseValue.set(C_BOOL, 0, false);
|
||||
while (renderStateRowIteratorNext(iterator)) {
|
||||
int result = (int) renderStateRowSet.invoke(iterator, RENDER_STATE_ROW_OPTION_DIRTY, falseValue);
|
||||
checkResult("ghostty_render_state_row_set", result);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
rethrow(t);
|
||||
} finally {
|
||||
renderStateRowIteratorFree(iterator);
|
||||
}
|
||||
renderStateSetDirty(state, RENDER_STATE_DIRTY_FALSE);
|
||||
}
|
||||
|
||||
private MemorySegment renderStateRowIteratorNew() {
|
||||
try (Arena arena = Arena.ofConfined()) {
|
||||
MemorySegment out = arena.allocate(C_POINTER);
|
||||
|
||||
Reference in New Issue
Block a user