From bfcd5f380a91a87f2c2cd982747d6fb5946f78db Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 1 Apr 2024 12:25:21 -0700 Subject: [PATCH] font: introduce GroupCacheSet, use it for descriptors to start --- src/Surface.zig | 101 ++++++++--------------- src/font/GroupCacheSet.zig | 164 +++++++++++++++++++++++++++++++++++++ src/font/main.zig | 1 + 3 files changed, 197 insertions(+), 69 deletions(-) create mode 100644 src/font/GroupCacheSet.zig diff --git a/src/Surface.zig b/src/Surface.zig index 39f899d1b..dac8ba705 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -320,6 +320,12 @@ pub fn init( .ydpi = @intFromFloat(y_dpi), }; + // Create our font group key. This is used to determine if we have + // a cached font group we can use already. Otherwise, this can be + // used to build the group. + var font_group_key = try font.GroupCacheSet.Key.init(alloc, config); + defer font_group_key.deinit(); + // Find all the fonts for this surface // // Future: we can share the font group amongst all surfaces to save @@ -368,84 +374,41 @@ pub fn init( // A buffer we use to store the font names for logging. var name_buf: [256]u8 = undefined; - for (config.@"font-family".list.items) |family| { - var disco_it = try disco.discover(alloc, .{ - .family = family, - .style = config.@"font-style".nameValue(), - .size = font_size.points, - .variations = config.@"font-variation".list.items, - }); - defer disco_it.deinit(); - if (try disco_it.next()) |face| { - log.info("font regular: {s}", .{try face.name(&name_buf)}); - _ = try group.addFace(.regular, .{ .deferred = face }); - } else log.warn("font-family not found: {s}", .{family}); - } - - // In all the styled cases below, we prefer to specify an exact - // style via the `font-style` configuration. If a style is not - // specified, we use the discovery mechanism to search for a - // style category such as bold, italic, etc. We can't specify both - // because the latter will restrict the search to only that. If - // a user says `font-style = italic` for the bold face for example, - // no results would be found if we restrict to ALSO searching for - // italic. - for (config.@"font-family-bold".list.items) |family| { - const style = config.@"font-style-bold".nameValue(); - var disco_it = try disco.discover(alloc, .{ - .family = family, - .style = style, - .size = font_size.points, - .bold = style == null, - .variations = config.@"font-variation-bold".list.items, - }); - defer disco_it.deinit(); - if (try disco_it.next()) |face| { - log.info("font bold: {s}", .{try face.name(&name_buf)}); - _ = try group.addFace(.bold, .{ .deferred = face }); - } else log.warn("font-family-bold not found: {s}", .{family}); - } - for (config.@"font-family-italic".list.items) |family| { - const style = config.@"font-style-italic".nameValue(); - var disco_it = try disco.discover(alloc, .{ - .family = family, - .style = style, - .size = font_size.points, - .italic = style == null, - .variations = config.@"font-variation-italic".list.items, - }); - defer disco_it.deinit(); - if (try disco_it.next()) |face| { - log.info("font italic: {s}", .{try face.name(&name_buf)}); - _ = try group.addFace(.italic, .{ .deferred = face }); - } else log.warn("font-family-italic not found: {s}", .{family}); - } - for (config.@"font-family-bold-italic".list.items) |family| { - const style = config.@"font-style-bold-italic".nameValue(); - var disco_it = try disco.discover(alloc, .{ - .family = family, - .style = style, - .size = font_size.points, - .bold = style == null, - .italic = style == null, - .variations = config.@"font-variation-bold-italic".list.items, - }); - defer disco_it.deinit(); - if (try disco_it.next()) |face| { - log.info("font bold+italic: {s}", .{try face.name(&name_buf)}); - _ = try group.addFace(.bold_italic, .{ .deferred = face }); - } else log.warn("font-family-bold-italic not found: {s}", .{family}); + inline for (@typeInfo(font.Style).Enum.fields) |field| { + const style = @field(font.Style, field.name); + for (font_group_key.descriptorsForStyle(style)) |desc| { + var disco_it = try disco.discover(alloc, desc); + defer disco_it.deinit(); + if (try disco_it.next()) |face| { + log.info("font {s}: {s}", .{ + field.name, + try face.name(&name_buf), + }); + _ = try group.addFace(style, .{ .deferred = face }); + } else log.warn("font-family {s} not found: {s}", .{ + field.name, + desc.family.?, + }); + } } } // Our built-in font will be used as a backup _ = try group.addFace( .regular, - .{ .fallback_loaded = try font.Face.init(font_lib, face_ttf, group.faceOptions()) }, + .{ .fallback_loaded = try font.Face.init( + font_lib, + face_ttf, + group.faceOptions(), + ) }, ); _ = try group.addFace( .bold, - .{ .fallback_loaded = try font.Face.init(font_lib, face_bold_ttf, group.faceOptions()) }, + .{ .fallback_loaded = try font.Face.init( + font_lib, + face_bold_ttf, + group.faceOptions(), + ) }, ); // Auto-italicize if we have to. diff --git a/src/font/GroupCacheSet.zig b/src/font/GroupCacheSet.zig new file mode 100644 index 000000000..c4786b216 --- /dev/null +++ b/src/font/GroupCacheSet.zig @@ -0,0 +1,164 @@ +//! This structure contains a set of GroupCache instances keyed by +//! unique font configuration. +//! +//! Most terminals (surfaces) will share the same font configuration. +//! This structure allows expensive font information such as +//! the font atlas, glyph cache, font faces, etc. to be shared. +//! +//! The Ghostty renderers which use this information run on their +//! own threads so this structure is thread-safe. It expects that +//! the case where all glyphs are cached is the common case and +//! optimizes for that. When a glyph is not cached, all renderers +//! that share the same font configuration will be blocked until +//! the glyph is cached. +const GroupCacheSet = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; +const Style = @import("main.zig").Style; +const discovery = @import("discovery.zig"); +const configpkg = @import("../config.zig"); +const Config = configpkg.Config; + +/// The key used to uniquely identify a font configuration. +pub const Key = struct { + arena: ArenaAllocator, + + /// The descriptors used for all the fonts added to the + /// initial group, including all styles. This is hashed + /// in order so the order matters. All users of the struct + /// should ensure that the order is consistent. + descriptors: []const discovery.Descriptor, + + /// These are the offsets into the descriptors array for + /// each style. For example, bold is from + /// offsets[@intFromEnum(.bold) - 1] to + /// offsets[@intFromEnum(.bold)]. + style_offsets: StyleOffsets = .{0} ** style_offsets_len, + + const style_offsets_len = std.enums.directEnumArrayLen(Style, 0); + const StyleOffsets = [style_offsets_len]usize; + + comptime { + // We assume this throughout this structure. If this changes + // we may need to change this structure. + assert(@intFromEnum(Style.regular) == 0); + assert(@intFromEnum(Style.bold) == 1); + assert(@intFromEnum(Style.italic) == 2); + assert(@intFromEnum(Style.bold_italic) == 3); + } + + pub fn init( + alloc_gpa: Allocator, + config: *const Config, + ) !Key { + var arena = ArenaAllocator.init(alloc_gpa); + errdefer arena.deinit(); + const alloc = arena.allocator(); + + var descriptors = std.ArrayList(discovery.Descriptor).init(alloc); + defer descriptors.deinit(); + for (config.@"font-family".list.items) |family| { + try descriptors.append(.{ + .family = family, + .style = config.@"font-style".nameValue(), + .size = config.@"font-size", + .variations = config.@"font-variation".list.items, + }); + } + + // In all the styled cases below, we prefer to specify an exact + // style via the `font-style` configuration. If a style is not + // specified, we use the discovery mechanism to search for a + // style category such as bold, italic, etc. We can't specify both + // because the latter will restrict the search to only that. If + // a user says `font-style = italic` for the bold face for example, + // no results would be found if we restrict to ALSO searching for + // italic. + for (config.@"font-family-bold".list.items) |family| { + const style = config.@"font-style-bold".nameValue(); + try descriptors.append(.{ + .family = family, + .style = style, + .size = config.@"font-size", + .bold = style == null, + .variations = config.@"font-variation".list.items, + }); + } + for (config.@"font-family-italic".list.items) |family| { + const style = config.@"font-style-italic".nameValue(); + try descriptors.append(.{ + .family = family, + .style = style, + .size = config.@"font-size", + .italic = style == null, + .variations = config.@"font-variation".list.items, + }); + } + for (config.@"font-family-bold-italic".list.items) |family| { + const style = config.@"font-style-bold-italic".nameValue(); + try descriptors.append(.{ + .family = family, + .style = style, + .size = config.@"font-size", + .bold = style == null, + .italic = style == null, + .variations = config.@"font-variation".list.items, + }); + } + + return .{ + .arena = arena, + .descriptors = try descriptors.toOwnedSlice(), + .style_offsets = .{ + config.@"font-family".list.items.len, + config.@"font-family-bold".list.items.len, + config.@"font-family-italic".list.items.len, + config.@"font-family-bold-italic".list.items.len, + }, + }; + } + + pub fn deinit(self: *Key) void { + self.arena.deinit(); + } + + /// Get the descriptors for the given font style that can be + /// used with discovery. + pub fn descriptorsForStyle( + self: Key, + style: Style, + ) []const discovery.Descriptor { + const idx = @intFromEnum(style); + const start: usize = if (idx == 0) 0 else self.style_offsets[idx - 1]; + const end = self.style_offsets[idx]; + return self.descriptors[start..end]; + } + + /// Hash the key with the given hasher. + pub fn hash(self: Key, hasher: anytype) void { + const autoHash = std.hash.autoHash; + autoHash(hasher, self.descriptors.len); + for (self.descriptors) |d| d.hash(hasher); + } + + /// Returns a hash code that can be used to uniquely identify this + /// action. + pub fn hashcode(self: Key) u64 { + var hasher = std.hash.Wyhash.init(0); + self.hash(&hasher); + return hasher.final(); + } +}; + +test "Key" { + const testing = std.testing; + const alloc = testing.allocator; + var cfg = try Config.default(alloc); + defer cfg.deinit(); + + var k = try Key.init(alloc, &cfg); + defer k.deinit(); +} diff --git a/src/font/main.zig b/src/font/main.zig index 383f2da74..feff26bfe 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -10,6 +10,7 @@ pub const DeferredFace = @import("DeferredFace.zig"); pub const Face = face.Face; pub const Group = @import("Group.zig"); pub const GroupCache = @import("GroupCache.zig"); +pub const GroupCacheSet = @import("GroupCacheSet.zig"); pub const Glyph = @import("Glyph.zig"); pub const shape = @import("shape.zig"); pub const Shaper = shape.Shaper;