mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-29 14:38:35 +00:00
Position-independent font shaper caching (#8555)
Use relative cluster positioning to allow identical texts runs in different row positions to share the same cache entry. I am opening this PR clean w/o the cache size change. There could be some benefit to a larger 256->512 shaper cache, but this still performs amazingly well and I don't know the full memory impacts of moving the cache size up. https://github.com/ghostty-org/ghostty/discussions/8547#discussioncomment-14329590
This commit is contained in:
@@ -38,9 +38,11 @@ pub const Shaper = switch (options.backend) {
|
|||||||
/// for a shaping call. Note all terminal cells may be present; only
|
/// for a shaping call. Note all terminal cells may be present; only
|
||||||
/// cells that have a glyph that needs to be rendered.
|
/// cells that have a glyph that needs to be rendered.
|
||||||
pub const Cell = struct {
|
pub const Cell = struct {
|
||||||
/// The column that this cell occupies. Since a set of shaper cells is
|
/// The X position of this shaper cell relative to the offset of the
|
||||||
/// always on the same line, only the X is stored. It is expected the
|
/// run. Because runs are always within a single row, it is expected
|
||||||
/// caller has access to the original screen cell.
|
/// that the caller can reconstruct the full position of the cell by
|
||||||
|
/// using the known Y position of the cell and adding the X position
|
||||||
|
/// to the run offset.
|
||||||
x: u16,
|
x: u16,
|
||||||
|
|
||||||
/// An additional offset to apply to the rendering.
|
/// An additional offset to apply to the rendering.
|
||||||
|
@@ -17,9 +17,15 @@ pub const TextRun = struct {
|
|||||||
/// lower the chance of hash collisions if they become a problem. If
|
/// lower the chance of hash collisions if they become a problem. If
|
||||||
/// there are hash collisions, it would result in rendering issues but
|
/// there are hash collisions, it would result in rendering issues but
|
||||||
/// the core data would be correct.
|
/// the core data would be correct.
|
||||||
|
///
|
||||||
|
/// The hash is position-independent within the row by using relative
|
||||||
|
/// cluster positions. This allows identical runs in different positions
|
||||||
|
/// to share the same cache entry, improving cache efficiency.
|
||||||
hash: u64,
|
hash: u64,
|
||||||
|
|
||||||
/// The offset in the row where this run started
|
/// The offset in the row where this run started. This is added to the
|
||||||
|
/// X position of the final shaped cells to get the absolute position
|
||||||
|
/// in the row where they belong.
|
||||||
offset: u16,
|
offset: u16,
|
||||||
|
|
||||||
/// The total number of cells produced by this run.
|
/// The total number of cells produced by this run.
|
||||||
@@ -77,7 +83,11 @@ pub const RunIterator = struct {
|
|||||||
// Go through cell by cell and accumulate while we build our run.
|
// Go through cell by cell and accumulate while we build our run.
|
||||||
var j: usize = self.i;
|
var j: usize = self.i;
|
||||||
while (j < max) : (j += 1) {
|
while (j < max) : (j += 1) {
|
||||||
const cluster = j;
|
// Use relative cluster positions (offset from run start) to make
|
||||||
|
// the shaping cache position-independent. This ensures that runs
|
||||||
|
// with identical content but different starting positions in the
|
||||||
|
// row produce the same hash, enabling cache reuse.
|
||||||
|
const cluster = j - self.i;
|
||||||
const cell = &cells[j];
|
const cell = &cells[j];
|
||||||
|
|
||||||
// If we have a selection and we're at a boundary point, then
|
// If we have a selection and we're at a boundary point, then
|
||||||
|
@@ -2528,9 +2528,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||||||
break :cache cells;
|
break :cache cells;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cells = shaper_cells.?;
|
||||||
|
|
||||||
// Advance our index until we reach or pass
|
// Advance our index until we reach or pass
|
||||||
// our current x position in the shaper cells.
|
// our current x position in the shaper cells.
|
||||||
while (shaper_cells.?[shaper_cells_i].x < x) {
|
while (run.offset + cells[shaper_cells_i].x < x) {
|
||||||
shaper_cells_i += 1;
|
shaper_cells_i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2769,13 +2771,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||||||
// If we encounter a shaper cell to the left of the current
|
// If we encounter a shaper cell to the left of the current
|
||||||
// cell then we have some problems. This logic relies on x
|
// cell then we have some problems. This logic relies on x
|
||||||
// position monotonically increasing.
|
// position monotonically increasing.
|
||||||
assert(cells[shaper_cells_i].x >= x);
|
assert(run.offset + cells[shaper_cells_i].x >= x);
|
||||||
|
|
||||||
// NOTE: An assumption is made here that a single cell will never
|
// NOTE: An assumption is made here that a single cell will never
|
||||||
// be present in more than one shaper run. If that assumption is
|
// be present in more than one shaper run. If that assumption is
|
||||||
// violated, this logic breaks.
|
// violated, this logic breaks.
|
||||||
|
|
||||||
while (shaper_cells_i < cells.len and cells[shaper_cells_i].x == x) : ({
|
while (shaper_cells_i < cells.len and run.offset + cells[shaper_cells_i].x == x) : ({
|
||||||
shaper_cells_i += 1;
|
shaper_cells_i += 1;
|
||||||
}) {
|
}) {
|
||||||
self.addGlyph(
|
self.addGlyph(
|
||||||
|
Reference in New Issue
Block a user