libghostty: faster render state reads and updates on wasm targets

This makes the `ghostty_render_state_*` C API significantly faster on
wasm32-freestanding, measured in V8 via Node for Chrome. Also verified
in `jsc` for Safari.

The major change is a new bulk row read API that makes full-screen cell reads
roughly 10x faster for wasm embedders. This should help any embedder with
high FFI overhead, such as Go, Python, etc. too.

Non-wasm performance is not impacted, all benchmarks were run on my mac
too w/ no regressions (two of the changes are native wins as well).

## Changes

* color: the "vectorized" palette conversion loop was silently
  scalarized by LLVM into per-byte ops because it loaded/stored through
  array-typed pointers. Zig 0.16 disables the LLVM loop vectorizer, so
  manually vectorized loops must go through vector-typed pointers.
* C styles: major optimizations to converting Zig styles to C styles.
  This is a heavy operation for render state.
* render: `endUpdate`'s style-run fill (`@memset` with a struct value)
  re-loaded its source every iteration and stored field by field. Now
  manually vectorized.
* render: new `GHOSTTY_RENDER_STATE_ROW_DATA_CELLS_RAW` returns a
  borrowed `GhosttyCellsView` of the current row's raw cell values, valid
  until the next update. One call per row instead of 3-6 calls per cell.

## Benchmarks

| Benchmark | Before | After | Speedup |
|---|---|---|---|
| colors_get | 114 ns | 35 ns | 3.3x |
| style get, per styled cell | 7.8 ns | 6.7 ns | 1.2x |
| raw+style read, per cell | 8.6 ns | 7.7 ns | 1.1x |
| full-screen text read, per cell | 7.5 ns | 0.7 ns | 10.7x |
| full-screen text+style read, per cell | 8.6 ns | 1.7 ns | 5.1x |
| render state update, styled full frame | 3.4 us | 2.6 us | 1.3x |

**AI usage:** Fable did the implementation and benchmarking and drafted
this message. Comments were partially rewritten by me.
This commit is contained in:
Mitchell Hashimoto
2026-08-14 10:36:56 -07:00
parent 8f485a7f47
commit 74a233b543
7 changed files with 258 additions and 40 deletions

View File

@@ -236,6 +236,19 @@ typedef enum GHOSTTY_ENUM_TYPED {
/** Row-local selected cell range (GhosttyRenderStateRowSelection). */
GHOSTTY_RENDER_STATE_ROW_DATA_SELECTION = 4,
/** A borrowed view of the raw cell values for the current row
* (GhosttyCellsView). One value per column, identical to querying
* GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_RAW for each cell. The view
* is only valid as long as the underlying render state is not
* updated; it is unsafe to use after updating the render state.
*
* This is the bulk alternative to iterating cells one at a time.
* It lets callers with expensive call boundaries (e.g. WebAssembly
* embedders) read an entire row with a single call, then drill
* into the cells iterator only for cells that need managed data
* (styles, graphemes). */
GHOSTTY_RENDER_STATE_ROW_DATA_CELLS_RAW = 5,
GHOSTTY_RENDER_STATE_ROW_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyRenderStateRowData;

View File

@@ -50,6 +50,23 @@ typedef uint64_t GhosttyCell;
*/
typedef uint64_t GhosttyRow;
/**
* A borrowed view of contiguous raw cell values.
*
* The memory is not owned by this struct. The pointer is only valid
* for the lifetime documented by the API that produces it. Each value
* is queried via ghostty_cell_get() like any other GhosttyCell.
*
* @ingroup screen
*/
typedef struct {
/** Pointer to len contiguous cell values. */
const GhosttyCell* ptr;
/** Number of cells. */
size_t len;
} GhosttyCellsView;
/**
* Cell content tag.
*