only initialize font discovery mechanism once, cache on App

Fontconfig in particular appears unsafe to initialize multiple times.

Font discovery is a singleton object in an application and only ever
accessed from the main thread so we can work around this by only
initializing and caching the font discovery mechanism exactly once on
the app singleton.
This commit is contained in:
Mitchell Hashimoto
2023-08-13 08:01:33 -07:00
parent a8426a90dd
commit 619d2ade3e
6 changed files with 52 additions and 18 deletions

View File

@ -45,6 +45,11 @@ quit: bool,
/// from source. This is null if we can't detect it. /// from source. This is null if we can't detect it.
resources_dir: ?[]const u8 = null, resources_dir: ?[]const u8 = null,
/// Font discovery mechanism. This is only safe to use from the main thread.
/// This is lazily initialized on the first call to fontDiscover so do
/// not access this directly.
font_discover: ?font.Discover = null,
/// Initialize the main app instance. This creates the main window, sets /// Initialize the main app instance. This creates the main window, sets
/// up the renderer state, compiles the shaders, etc. This is the primary /// up the renderer state, compiles the shaders, etc. This is the primary
/// "startup" logic. /// "startup" logic.
@ -80,6 +85,8 @@ pub fn destroy(self: *App) void {
self.surfaces.deinit(self.alloc); self.surfaces.deinit(self.alloc);
if (self.resources_dir) |dir| self.alloc.free(dir); if (self.resources_dir) |dir| self.alloc.free(dir);
if (self.font_discover) |*v| v.deinit();
self.alloc.destroy(self); self.alloc.destroy(self);
} }
@ -151,6 +158,20 @@ pub fn focusedSurface(self: *const App) ?*Surface {
return surface; return surface;
} }
/// Initialize once and return the font discovery mechanism. This remains
/// initialized throughout the lifetime of the application because some
/// font discovery mechanisms (i.e. fontconfig) are unsafe to reinit.
pub fn fontDiscover(self: *App) !?font.Discover {
// If we're built without a font discovery mechanism, return null
if (comptime font.Discover == void) return null;
// If we initialized, use it
if (self.font_discover) |v| return v;
self.font_discover = font.Discover.init();
return self.font_discover.?;
}
/// Drain the mailbox. /// Drain the mailbox.
fn drainMailbox(self: *App, rt_app: *apprt.App) !void { fn drainMailbox(self: *App, rt_app: *apprt.App) !void {
while (self.mailbox.pop()) |message| { while (self.mailbox.pop()) |message| {

View File

@ -177,8 +177,8 @@ pub fn init(
self: *Surface, self: *Surface,
alloc: Allocator, alloc: Allocator,
config: *const configpkg.Config, config: *const configpkg.Config,
app: *App,
app_mailbox: App.Mailbox, app_mailbox: App.Mailbox,
app_resources_dir: ?[]const u8,
rt_surface: *apprt.runtime.Surface, rt_surface: *apprt.runtime.Surface,
) !void { ) !void {
// Initialize our renderer with our initialized surface. // Initialize our renderer with our initialized surface.
@ -217,8 +217,11 @@ pub fn init(
errdefer group.deinit(); errdefer group.deinit();
// Search for fonts // Search for fonts
if (font.Discover != void) { if (font.Discover != void) discover: {
var disco = font.Discover.init(); const disco = try app.fontDiscover() orelse {
log.warn("font discovery not available, cannot search for fonts", .{});
break :discover;
};
group.discover = disco; group.discover = disco;
if (config.@"font-family") |family| { if (config.@"font-family") |family| {
@ -320,16 +323,16 @@ pub fn init(
// If we're on Mac, then we try to use the Apple Emoji font for Emoji. // If we're on Mac, then we try to use the Apple Emoji font for Emoji.
if (builtin.os.tag == .macos and font.Discover != void) { if (builtin.os.tag == .macos and font.Discover != void) {
var disco = font.Discover.init(); if (try app.fontDiscover()) |disco| {
defer disco.deinit(); var disco_it = try disco.discover(.{
var disco_it = try disco.discover(.{ .family = "Apple Color Emoji",
.family = "Apple Color Emoji", .size = font_size.points,
.size = font_size.points, });
}); defer disco_it.deinit();
defer disco_it.deinit(); if (try disco_it.next()) |face| {
if (try disco_it.next()) |face| { log.info("font emoji: {s}", .{try face.name()});
log.info("font emoji: {s}", .{try face.name()}); try group.addFace(alloc, .regular, face);
try group.addFace(alloc, .regular, face); }
} }
} }
@ -402,7 +405,7 @@ pub fn init(
.screen_size = screen_size, .screen_size = screen_size,
.full_config = config, .full_config = config,
.config = try termio.Impl.DerivedConfig.init(alloc, config), .config = try termio.Impl.DerivedConfig.init(alloc, config),
.resources_dir = app_resources_dir, .resources_dir = app.resources_dir,
.renderer_state = &self.renderer_state, .renderer_state = &self.renderer_state,
.renderer_wakeup = render_thread.wakeup, .renderer_wakeup = render_thread.wakeup,
.renderer_mailbox = render_thread.mailbox, .renderer_mailbox = render_thread.mailbox,

View File

@ -196,8 +196,8 @@ pub const Surface = struct {
try self.core_surface.init( try self.core_surface.init(
app.core_app.alloc, app.core_app.alloc,
&config, &config,
app.core_app,
.{ .rt_app = app, .mailbox = &app.core_app.mailbox }, .{ .rt_app = app, .mailbox = &app.core_app.mailbox },
app.core_app.resources_dir,
self, self,
); );
errdefer self.core_surface.deinit(); errdefer self.core_surface.deinit();

View File

@ -374,8 +374,8 @@ pub const Surface = struct {
try self.core_surface.init( try self.core_surface.init(
app.app.alloc, app.app.alloc,
&config, &config,
app.app,
.{ .rt_app = app, .mailbox = &app.app.mailbox }, .{ .rt_app = app, .mailbox = &app.app.mailbox },
app.app.resources_dir,
self, self,
); );
errdefer self.core_surface.deinit(); errdefer self.core_surface.deinit();

View File

@ -789,8 +789,8 @@ pub const Surface = struct {
try self.core_surface.init( try self.core_surface.init(
self.app.core_app.alloc, self.app.core_app.alloc,
&config, &config,
self.app.core_app,
.{ .rt_app = self.app, .mailbox = &self.app.core_app.mailbox }, .{ .rt_app = self.app, .mailbox = &self.app.core_app.mailbox },
self.app.core_app.resources_dir,
self, self,
); );
errdefer self.core_surface.deinit(); errdefer self.core_surface.deinit();
@ -1203,6 +1203,14 @@ pub const Surface = struct {
break :key input.Key.fromASCII(self.im_buf[0]) orelse physical_key; break :key input.Key.fromASCII(self.im_buf[0]) orelse physical_key;
} else .invalid; } else .invalid;
// log.debug("key pressed key={} physical_key={} composing={} text_len={} mods={}", .{
// key,
// physical_key,
// self.im_composing,
// self.im_len,
// mods,
// });
// If both keys are invalid then we won't call the key callback. But // If both keys are invalid then we won't call the key callback. But
// if either one is valid, we want to give it a chance. // if either one is valid, we want to give it a chance.
if (key != .invalid or physical_key != .invalid) { if (key != .invalid or physical_key != .invalid) {
@ -1342,6 +1350,8 @@ pub const Surface = struct {
if (str.len <= self.im_buf.len) { if (str.len <= self.im_buf.len) {
@memcpy(self.im_buf[0..str.len], str); @memcpy(self.im_buf[0..str.len], str);
self.im_len = @intCast(str.len); self.im_len = @intCast(str.len);
// log.debug("input commit: {x}", .{self.im_buf[0]});
} else { } else {
log.warn("not enough buffer space for input method commit", .{}); log.warn("not enough buffer space for input method commit", .{});
} }

View File

@ -168,7 +168,7 @@ pub const Fontconfig = struct {
/// Discover fonts from a descriptor. This returns an iterator that can /// Discover fonts from a descriptor. This returns an iterator that can
/// be used to build up the deferred fonts. /// be used to build up the deferred fonts.
pub fn discover(self: *Fontconfig, desc: Descriptor) !DiscoverIterator { pub fn discover(self: *const Fontconfig, desc: Descriptor) !DiscoverIterator {
// Build our pattern that we'll search for // Build our pattern that we'll search for
const pat = desc.toFcPattern(); const pat = desc.toFcPattern();
errdefer pat.destroy(); errdefer pat.destroy();