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 macos = @import("macos");
const trace = @import("tracy").trace; const trace = @import("tracy").trace;
const font = @import("../main.zig"); const font = @import("../main.zig");
const os = @import("../../os/main.zig");
const terminal = @import("../../terminal/main.zig");
const Face = font.Face; const Face = font.Face;
const Collection = font.Collection; const Collection = font.Collection;
const DeferredFace = font.DeferredFace; const DeferredFace = font.DeferredFace;
@ -14,7 +16,7 @@ const Library = font.Library;
const SharedGrid = font.SharedGrid; const SharedGrid = font.SharedGrid;
const Style = font.Style; const Style = font.Style;
const Presentation = font.Presentation; const Presentation = font.Presentation;
const terminal = @import("../../terminal/main.zig"); const CFReleaseThread = os.CFReleaseThread;
const log = std.log.scoped(.font_shaper); const log = std.log.scoped(.font_shaper);
@ -62,6 +64,12 @@ pub const Shaper = struct {
/// sent to the release thread when endFrame is called. /// sent to the release thread when endFrame is called.
cf_release_pool: std.ArrayListUnmanaged(*anyopaque), 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 CellBuf = std.ArrayListUnmanaged(font.shape.Cell);
const CodepointList = std.ArrayListUnmanaged(Codepoint); const CodepointList = std.ArrayListUnmanaged(Codepoint);
const Codepoint = struct { const Codepoint = struct {
@ -215,6 +223,20 @@ pub const Shaper = struct {
const cached_fonts = std.ArrayList(?*macos.foundation.Dictionary).init(alloc); const cached_fonts = std.ArrayList(?*macos.foundation.Dictionary).init(alloc);
errdefer cached_fonts.deinit(); 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 .{ return .{
.alloc = alloc, .alloc = alloc,
.cell_buf = .{}, .cell_buf = .{},
@ -223,6 +245,8 @@ pub const Shaper = struct {
.writing_direction = writing_direction, .writing_direction = writing_direction,
.cached_fonts = cached_fonts, .cached_fonts = cached_fonts,
.cf_release_pool = .{}, .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); 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. /// 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( pub fn runIterator(
self: *Shaper, self: *Shaper,
grid: *SharedGrid, grid: *SharedGrid,

View File

@ -136,12 +136,6 @@ layer: objc.Object, // CAMetalLayer
/// a display link. /// a display link.
display_link: ?DisplayLink = null, 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. This is only set if we have custom shaders.
custom_shader_state: ?CustomShaderState = null, custom_shader_state: ?CustomShaderState = null,
@ -598,9 +592,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
.cursor_color = options.config.cursor_color, .cursor_color = options.config.cursor_color,
.current_background_color = options.config.background, .current_background_color = options.config.background,
.cf_release_thread = undefined,
.cf_release_thr = undefined,
// Render state // Render state
.cells = .{}, .cells = .{},
.uniforms = .{ .uniforms = .{
@ -698,18 +689,6 @@ pub fn loopEnter(self: *Metal, thr: *renderer.Thread) !void {
&thr.draw_now, &thr.draw_now,
); );
display_link.start() catch {}; 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. /// 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. // is gone which is fine.
const display_link = self.display_link orelse return; const display_link = self.display_link orelse return;
display_link.stop() catch {}; 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( fn displayLinkCallback(
@ -1028,22 +998,9 @@ pub fn updateFrame(
&critical.color_palette, &critical.color_palette,
); );
if (self.font_shaper.cf_release_pool.items.len > 0) { // Notify our shaper we're done for the frame. For some shapers like
const alloc = self.font_shaper.alloc; // CoreText this triggers off-thread cleanup logic.
const items = try self.font_shaper.cf_release_pool.toOwnedSlice(alloc); self.font_shaper.endFrame();
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);
}
}
// Update our viewport pin // Update our viewport pin
self.cells_viewport = critical.viewport_pin; self.cells_viewport = critical.viewport_pin;

View File

@ -732,6 +732,10 @@ pub fn updateFrame(
critical.cursor_style, critical.cursor_style,
&critical.color_palette, &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();
} }
} }