diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/font/shaper/Cache.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (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.zig | 106 |
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); +} |
