coretext shaper owns CFReleaseThread, works on both Metal and OpenGL now

This commit is contained in:
Mitchell Hashimoto
2024-06-22 20:42:59 -07:00
parent 4325dc51bc
commit 71353d016e
3 changed files with 73 additions and 47 deletions

View File

@ -5,6 +5,8 @@ const Allocator = std.mem.Allocator;
const macos = @import("macos");
const trace = @import("tracy").trace;
const font = @import("../main.zig");
const os = @import("../../os/main.zig");
const terminal = @import("../../terminal/main.zig");
const Face = font.Face;
const Collection = font.Collection;
const DeferredFace = font.DeferredFace;
@ -14,7 +16,7 @@ const Library = font.Library;
const SharedGrid = font.SharedGrid;
const Style = font.Style;
const Presentation = font.Presentation;
const terminal = @import("../../terminal/main.zig");
const CFReleaseThread = os.CFReleaseThread;
const log = std.log.scoped(.font_shaper);
@ -62,6 +64,12 @@ pub const Shaper = struct {
/// sent to the release thread when endFrame is called.
cf_release_pool: std.ArrayListUnmanaged(*anyopaque),
/// Dedicated thread for releasing CoreFoundation objects. Some objects,
/// such as those produced by CoreText, have excessively slow release
/// callback logic.
cf_release_thread: *CFReleaseThread,
cf_release_thr: std.Thread,
const CellBuf = std.ArrayListUnmanaged(font.shape.Cell);
const CodepointList = std.ArrayListUnmanaged(Codepoint);
const Codepoint = struct {
@ -215,6 +223,20 @@ pub const Shaper = struct {
const cached_fonts = std.ArrayList(?*macos.foundation.Dictionary).init(alloc);
errdefer cached_fonts.deinit();
// Create the CF release thread.
var cf_release_thread = try alloc.create(CFReleaseThread);
errdefer alloc.destroy(cf_release_thread);
cf_release_thread.* = try CFReleaseThread.init(alloc);
errdefer cf_release_thread.deinit();
// Start the CF release thread.
var cf_release_thr = try std.Thread.spawn(
.{},
CFReleaseThread.threadMain,
.{cf_release_thread},
);
cf_release_thr.setName("cf_release") catch {};
return .{
.alloc = alloc,
.cell_buf = .{},
@ -223,6 +245,8 @@ pub const Shaper = struct {
.writing_direction = writing_direction,
.cached_fonts = cached_fonts,
.cf_release_pool = .{},
.cf_release_thread = cf_release_thread,
.cf_release_thr = cf_release_thr,
};
}
@ -247,6 +271,15 @@ pub const Shaper = struct {
);
}
self.cf_release_pool.deinit(self.alloc);
// Stop the CF release thread
{
self.cf_release_thread.stop.notify() catch |err|
log.err("error notifying cf release thread to stop, may stall err={}", .{err});
self.cf_release_thr.join();
}
self.cf_release_thread.deinit();
self.alloc.destroy(self.cf_release_thread);
}
/// Release all cached fonts.
@ -258,6 +291,38 @@ pub const Shaper = struct {
}
}
pub fn endFrame(self: *Shaper) void {
if (self.cf_release_pool.items.len == 0) return;
// Get all the items in the pool as an owned slice so we can
// send it to the dedicated release thread.
const items = self.cf_release_pool.toOwnedSlice(self.alloc) catch |err| {
log.warn("error converting release pool to owned slice, slow release err={}", .{err});
for (self.cf_release_pool.items) |ref| macos.foundation.CFRelease(ref);
self.cf_release_pool.clearRetainingCapacity();
return;
};
// Send the items. If the send succeeds then we wake up the
// thread to process the items. If the send fails then do a manual
// cleanup.
if (self.cf_release_thread.mailbox.push(.{ .release = .{
.refs = items,
.alloc = self.alloc,
} }, .{ .forever = {} }) != 0) {
self.cf_release_thread.wakeup.notify() catch |err| {
log.warn(
"error notifying cf release thread to wake up, may stall err={}",
.{err},
);
};
return;
}
for (items) |ref| macos.foundation.CFRelease(ref);
self.alloc.free(items);
}
pub fn runIterator(
self: *Shaper,
grid: *SharedGrid,

View File

@ -136,12 +136,6 @@ layer: objc.Object, // CAMetalLayer
/// a display link.
display_link: ?DisplayLink = null,
/// Dedicated thread for releasing CoreFoundation objects some objects,
/// such as those produced by CoreText, have excessively slow release
/// callback logic.
cf_release_thread: CFReleaseThread,
cf_release_thr: std.Thread,
/// Custom shader state. This is only set if we have custom shaders.
custom_shader_state: ?CustomShaderState = null,
@ -598,9 +592,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
.cursor_color = options.config.cursor_color,
.current_background_color = options.config.background,
.cf_release_thread = undefined,
.cf_release_thr = undefined,
// Render state
.cells = .{},
.uniforms = .{
@ -698,18 +689,6 @@ pub fn loopEnter(self: *Metal, thr: *renderer.Thread) !void {
&thr.draw_now,
);
display_link.start() catch {};
// Create the CF release thread.
self.cf_release_thread = try CFReleaseThread.init(self.alloc);
errdefer self.cf_release_thread.deinit();
// Start the CF release thread.
self.cf_release_thr = try std.Thread.spawn(
.{},
CFReleaseThread.threadMain,
.{&self.cf_release_thread},
);
self.cf_release_thr.setName("cf_release") catch {};
}
/// Called by renderer.Thread when it exits the main loop.
@ -722,15 +701,6 @@ pub fn loopExit(self: *Metal) void {
// is gone which is fine.
const display_link = self.display_link orelse return;
display_link.stop() catch {};
// Stop the CF release thread
{
self.cf_release_thread.stop.notify() catch |err|
log.err("error notifying cf release thread to stop, may stall err={}", .{err});
self.cf_release_thr.join();
}
self.cf_release_thread.deinit();
}
fn displayLinkCallback(
@ -1028,22 +998,9 @@ pub fn updateFrame(
&critical.color_palette,
);
if (self.font_shaper.cf_release_pool.items.len > 0) {
const alloc = self.font_shaper.alloc;
const items = try self.font_shaper.cf_release_pool.toOwnedSlice(alloc);
if (self.cf_release_thread.mailbox.push(
.{ .release = .{
.refs = items,
.alloc = alloc,
} },
.{ .forever = {} },
) != 0) {
try self.cf_release_thread.wakeup.notify();
} else {
for (items) |ref| macos.foundation.CFRelease(ref);
alloc.free(items);
}
}
// Notify our shaper we're done for the frame. For some shapers like
// CoreText this triggers off-thread cleanup logic.
self.font_shaper.endFrame();
// Update our viewport pin
self.cells_viewport = critical.viewport_pin;

View File

@ -732,6 +732,10 @@ pub fn updateFrame(
critical.cursor_style,
&critical.color_palette,
);
// Notify our shaper we're done for the frame. For some shapers like
// CoreText this triggers off-thread cleanup logic.
self.font_shaper.endFrame();
}
}