aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/font/shaper/Cache.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/font/shaper/Cache.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/font/shaper/Cache.zig')
-rw-r--r--vendor/libghostty-vt/src/font/shaper/Cache.zig106
1 files changed, 106 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/font/shaper/Cache.zig b/vendor/libghostty-vt/src/font/shaper/Cache.zig
new file mode 100644
index 00000000..2696985a
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/shaper/Cache.zig
@@ -0,0 +1,106 @@
+//! This structure caches the shaped cells for a given text run.
+//!
+//! At one point, shaping was the most expensive part of rendering text
+//! (accounting for 96% of frame time on my machine). To speed it up, this
+//! was introduced so that shaping results can be cached depending on the
+//! run.
+//!
+//! The cache key is the text run. The text run builds its own hash value
+//! based on the font, style, codepoint, etc. This just utilizes the hash that
+//! the text run provides.
+pub const Cache = @This();
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const font = @import("../main.zig");
+const CacheTable = @import("../../datastruct/main.zig").CacheTable;
+
+const log = std.log.scoped(.font_shaper_cache);
+
+/// Context for cache table.
+const CellCacheTableContext = struct {
+ pub fn hash(self: *const CellCacheTableContext, key: u64) u64 {
+ _ = self;
+ return key;
+ }
+ pub fn eql(self: *const CellCacheTableContext, a: u64, b: u64) bool {
+ _ = self;
+ return a == b;
+ }
+};
+
+/// Cache table for run hash -> shaped cells.
+const CellCacheTable = CacheTable(
+ u64,
+ []font.shape.Cell,
+ CellCacheTableContext,
+
+ // Capacity is slightly arbitrary. These numbers are guesses.
+ //
+ // I'd expect then an average of 256 frequently cached runs is a
+ // safe guess most terminal screens.
+ 256,
+ // 8 items per bucket to give decent resiliency to important runs.
+ 8,
+);
+
+/// The cache table of shaped cells.
+map: CellCacheTable,
+
+pub fn init() Cache {
+ return .{ .map = .{ .context = .{} } };
+}
+
+pub fn deinit(self: *Cache, alloc: Allocator) void {
+ self.clear(alloc);
+}
+
+/// Get the shaped cells for the given text run,
+/// or null if they are not in the cache.
+pub fn get(self: *Cache, run: font.shape.TextRun) ?[]const font.shape.Cell {
+ return self.map.get(run.hash);
+}
+
+/// Insert the shaped cells for the given text run into the cache.
+///
+/// The cells will be duplicated.
+pub fn put(
+ self: *Cache,
+ alloc: Allocator,
+ run: font.shape.TextRun,
+ cells: []const font.shape.Cell,
+) Allocator.Error!void {
+ const copy = try alloc.dupe(font.shape.Cell, cells);
+ const evicted = self.map.put(run.hash, copy);
+ if (evicted) |kv| {
+ alloc.free(kv.value);
+ }
+}
+
+fn clear(self: *Cache, alloc: Allocator) void {
+ for (self.map.buckets, self.map.lengths) |b, l| {
+ for (b[0..l]) |kv| {
+ alloc.free(kv.value);
+ }
+ }
+ self.map.clear();
+}
+
+test Cache {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var c = Cache.init();
+ defer c.deinit(alloc);
+
+ var run: font.shape.TextRun = undefined;
+ run.hash = 1;
+ try testing.expect(c.get(run) == null);
+ try c.put(alloc, run, &.{
+ .{ .x = 0, .glyph_index = 0 },
+ .{ .x = 1, .glyph_index = 1 },
+ });
+
+ const actual = c.get(run).?;
+ try testing.expect(actual.len == 2);
+}