mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
perf(shaper/coretext): cache fonts between shape calls
This commit is contained in:
@ -31,7 +31,7 @@ const log = std.log.scoped(.font_shaper);
|
|||||||
/// See: https://github.com/mitchellh/ghostty/issues/1643
|
/// See: https://github.com/mitchellh/ghostty/issues/1643
|
||||||
///
|
///
|
||||||
pub const Shaper = struct {
|
pub const Shaper = struct {
|
||||||
/// The allocated used for the feature list and cell buf.
|
/// The allocated used for the feature list, font cache, and cell buf.
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
|
|
||||||
/// The string used for shaping the current run.
|
/// The string used for shaping the current run.
|
||||||
@ -49,6 +49,13 @@ pub const Shaper = struct {
|
|||||||
/// and releasing many objects when shaping.
|
/// and releasing many objects when shaping.
|
||||||
writing_direction: *macos.foundation.Array,
|
writing_direction: *macos.foundation.Array,
|
||||||
|
|
||||||
|
/// List where we cache fonts, so we don't have to
|
||||||
|
/// remake them for every single shaping operation.
|
||||||
|
///
|
||||||
|
/// Fonts are cached as attribute dictionaries to
|
||||||
|
/// be applied directly to attributed strings.
|
||||||
|
cached_fonts: std.ArrayList(?*macos.foundation.Dictionary),
|
||||||
|
|
||||||
const CellBuf = std.ArrayListUnmanaged(font.shape.Cell);
|
const CellBuf = std.ArrayListUnmanaged(font.shape.Cell);
|
||||||
const CodepointList = std.ArrayListUnmanaged(Codepoint);
|
const CodepointList = std.ArrayListUnmanaged(Codepoint);
|
||||||
const Codepoint = struct {
|
const Codepoint = struct {
|
||||||
@ -202,12 +209,16 @@ pub const Shaper = struct {
|
|||||||
};
|
};
|
||||||
errdefer writing_direction.release();
|
errdefer writing_direction.release();
|
||||||
|
|
||||||
|
const cached_fonts = std.ArrayList(?*macos.foundation.Dictionary).init(alloc);
|
||||||
|
errdefer cached_fonts.deinit();
|
||||||
|
|
||||||
return Shaper{
|
return Shaper{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.cell_buf = .{},
|
.cell_buf = .{},
|
||||||
.run_state = run_state,
|
.run_state = run_state,
|
||||||
.features = feats,
|
.features = feats,
|
||||||
.writing_direction = writing_direction,
|
.writing_direction = writing_direction,
|
||||||
|
.cached_fonts = cached_fonts,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,6 +227,18 @@ pub const Shaper = struct {
|
|||||||
self.run_state.deinit(self.alloc);
|
self.run_state.deinit(self.alloc);
|
||||||
self.features.deinit();
|
self.features.deinit();
|
||||||
self.writing_direction.release();
|
self.writing_direction.release();
|
||||||
|
|
||||||
|
self.releaseCachedFonts();
|
||||||
|
self.cached_fonts.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Release all cached fonts.
|
||||||
|
pub fn releaseCachedFonts(self: *Shaper) void {
|
||||||
|
for (self.cached_fonts.items) |ft| {
|
||||||
|
if (ft) |f| {
|
||||||
|
f.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn runIterator(
|
pub fn runIterator(
|
||||||
@ -267,55 +290,7 @@ pub const Shaper = struct {
|
|||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const alloc = arena.allocator();
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
// Get our font. We have to apply the font features we want for
|
const attr_dict: *macos.foundation.Dictionary = try self.getFont(run.grid, run.font_index);
|
||||||
// the font here.
|
|
||||||
const run_font: *macos.text.Font = font: {
|
|
||||||
// The CoreText shaper relies on CoreText and CoreText claims
|
|
||||||
// that CTFonts are threadsafe. See:
|
|
||||||
// https://developer.apple.com/documentation/coretext/
|
|
||||||
//
|
|
||||||
// Quote:
|
|
||||||
// All individual functions in Core Text are thread-safe. Font
|
|
||||||
// objects (CTFont, CTFontDescriptor, and associated objects) can
|
|
||||||
// be used simultaneously by multiple operations, work queues, or
|
|
||||||
// threads. However, the layout objects (CTTypesetter,
|
|
||||||
// CTFramesetter, CTRun, CTLine, CTFrame, and associated objects)
|
|
||||||
// should be used in a single operation, work queue, or thread.
|
|
||||||
//
|
|
||||||
// Because of this, we only acquire the read lock to grab the
|
|
||||||
// face and set it up, then release it.
|
|
||||||
run.grid.lock.lockShared();
|
|
||||||
defer run.grid.lock.unlockShared();
|
|
||||||
|
|
||||||
const face = try run.grid.resolver.collection.getFace(run.font_index);
|
|
||||||
const original = face.font;
|
|
||||||
|
|
||||||
const attrs = try self.features.attrsDict(face.quirks_disable_default_font_features);
|
|
||||||
defer attrs.release();
|
|
||||||
|
|
||||||
const desc = try macos.text.FontDescriptor.createWithAttributes(attrs);
|
|
||||||
defer desc.release();
|
|
||||||
|
|
||||||
const copied = try original.copyWithAttributes(0, null, desc);
|
|
||||||
errdefer copied.release();
|
|
||||||
break :font copied;
|
|
||||||
};
|
|
||||||
defer run_font.release();
|
|
||||||
|
|
||||||
// Get our font and use that get the attributes to set for the
|
|
||||||
// attributed string so the whole string uses the same font.
|
|
||||||
const attr_dict = dict: {
|
|
||||||
var keys = [_]?*const anyopaque{
|
|
||||||
macos.text.StringAttribute.font.key(),
|
|
||||||
macos.text.StringAttribute.writing_direction.key(),
|
|
||||||
};
|
|
||||||
var values = [_]?*const anyopaque{
|
|
||||||
run_font,
|
|
||||||
self.writing_direction,
|
|
||||||
};
|
|
||||||
break :dict try macos.foundation.Dictionary.create(&keys, &values);
|
|
||||||
};
|
|
||||||
defer attr_dict.release();
|
|
||||||
|
|
||||||
// Create an attributed string from our string
|
// Create an attributed string from our string
|
||||||
const attr_str = try macos.foundation.AttributedString.create(
|
const attr_str = try macos.foundation.AttributedString.create(
|
||||||
@ -416,6 +391,75 @@ pub const Shaper = struct {
|
|||||||
return self.cell_buf.items;
|
return self.cell_buf.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an attr dict for a font from a specific index.
|
||||||
|
/// These items are cached, do not retain or release them.
|
||||||
|
fn getFont(self: *Shaper, grid: *font.SharedGrid, index: font.Collection.Index) !*macos.foundation.Dictionary {
|
||||||
|
const index_int = index.int();
|
||||||
|
|
||||||
|
if (self.cached_fonts.items.len <= index_int) {
|
||||||
|
try self.cached_fonts.ensureTotalCapacity(index_int + 1);
|
||||||
|
while (self.cached_fonts.items.len <= index_int) {
|
||||||
|
self.cached_fonts.appendAssumeCapacity(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.cached_fonts.items[index_int]) |cached| {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const run_font = font: {
|
||||||
|
// The CoreText shaper relies on CoreText and CoreText claims
|
||||||
|
// that CTFonts are threadsafe. See:
|
||||||
|
// https://developer.apple.com/documentation/coretext/
|
||||||
|
//
|
||||||
|
// Quote:
|
||||||
|
// All individual functions in Core Text are thread-safe. Font
|
||||||
|
// objects (CTFont, CTFontDescriptor, and associated objects) can
|
||||||
|
// be used simultaneously by multiple operations, work queues, or
|
||||||
|
// threads. However, the layout objects (CTTypesetter,
|
||||||
|
// CTFramesetter, CTRun, CTLine, CTFrame, and associated objects)
|
||||||
|
// should be used in a single operation, work queue, or thread.
|
||||||
|
//
|
||||||
|
// Because of this, we only acquire the read lock to grab the
|
||||||
|
// face and set it up, then release it.
|
||||||
|
grid.lock.lockShared();
|
||||||
|
defer grid.lock.unlockShared();
|
||||||
|
|
||||||
|
const face = try grid.resolver.collection.getFace(index);
|
||||||
|
const original = face.font;
|
||||||
|
|
||||||
|
const attrs = try self.features.attrsDict(face.quirks_disable_default_font_features);
|
||||||
|
defer attrs.release();
|
||||||
|
|
||||||
|
const desc = try macos.text.FontDescriptor.createWithAttributes(attrs);
|
||||||
|
defer desc.release();
|
||||||
|
|
||||||
|
const copied = try original.copyWithAttributes(0, null, desc);
|
||||||
|
errdefer copied.release();
|
||||||
|
|
||||||
|
break :font copied;
|
||||||
|
};
|
||||||
|
defer run_font.release();
|
||||||
|
|
||||||
|
// Get our font and use that get the attributes to set for the
|
||||||
|
// attributed string so the whole string uses the same font.
|
||||||
|
const attr_dict = dict: {
|
||||||
|
var keys = [_]?*const anyopaque{
|
||||||
|
macos.text.StringAttribute.font.key(),
|
||||||
|
macos.text.StringAttribute.writing_direction.key(),
|
||||||
|
};
|
||||||
|
var values = [_]?*const anyopaque{
|
||||||
|
run_font,
|
||||||
|
self.writing_direction,
|
||||||
|
};
|
||||||
|
break :dict try macos.foundation.Dictionary.create(&keys, &values);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.cached_fonts.items[index_int] = attr_dict;
|
||||||
|
|
||||||
|
return attr_dict;
|
||||||
|
}
|
||||||
|
|
||||||
/// The hooks for RunIterator.
|
/// The hooks for RunIterator.
|
||||||
pub const RunIteratorHook = struct {
|
pub const RunIteratorHook = struct {
|
||||||
shaper: *Shaper,
|
shaper: *Shaper,
|
||||||
|
Reference in New Issue
Block a user