diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig index 5da08b214..19ed2d7bb 100644 --- a/src/font/shaper/coretext.zig +++ b/src/font/shaper/coretext.zig @@ -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, diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 73afdbd6e..fc5ad382c 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -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; diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 8389dbae6..3443fb316 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -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(); } }