summaryrefslogtreecommitdiff
path: root/src/gui/gui.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/gui.zig')
-rw-r--r--src/gui/gui.zig93
1 files changed, 92 insertions, 1 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 5bcb2867..a4cc7672 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -133,6 +133,85 @@ fn taglineRaster(font: *c.UIFont, body_px: f32, body_cell_w: u32, body_cell_h: u
.height = @intCast(band_h),
};
}
+/// What FreeType and HarfBuzz allocate from, through ui_malloc and friends
+/// below. runNative points it at the gui's gpa once HarfBuzz's process-wide
+/// objects exist (ui_font_prime), so a Debug build's leak report is about
+/// fonts; a test that counts what fonts leak points it at
+/// std.testing.allocator the same way. Only the render thread calls into
+/// fonts, and every allocator it is pointed at is thread-safe anyway.
+var font_allocator: std.mem.Allocator = std.heap.smp_allocator;
+
+/// C's free and realloc pass no size, so a block starts with its size and the
+/// allocator that made it: a block made before font_allocator was repointed
+/// goes back where it came from. 32 bytes keep the caller's pointer 16-byte
+/// (max_align_t) aligned.
+const FontBlock = struct { size: usize, allocator: std.mem.Allocator };
+const font_block_header = 32;
+comptime {
+ std.debug.assert(@sizeOf(FontBlock) <= font_block_header);
+}
+
+export fn ui_malloc(size: usize) ?*anyopaque {
+ const total = std.math.add(usize, font_block_header, size) catch return null;
+ const bytes = font_allocator.alignedAlloc(u8, .@"16", total) catch return null;
+ @as(*FontBlock, @ptrCast(bytes.ptr)).* = .{ .size = size, .allocator = font_allocator };
+ return bytes.ptr + font_block_header;
+}
+
+export fn ui_calloc(count: usize, size: usize) ?*anyopaque {
+ const total = std.math.mul(usize, count, size) catch return null;
+ const block: [*]u8 = @ptrCast(ui_malloc(total) orelse return null);
+ @memset(block[0..total], 0);
+ return block;
+}
+
+export fn ui_realloc(block: ?*anyopaque, size: usize) ?*anyopaque {
+ const old = block orelse return ui_malloc(size);
+ const base: [*]align(16) u8 = @alignCast(@as([*]u8, @ptrCast(old)) - font_block_header);
+ const header: *FontBlock = @ptrCast(base);
+ const total = std.math.add(usize, font_block_header, size) catch return null;
+ const bytes = header.allocator.realloc(base[0 .. font_block_header + header.size], total) catch return null;
+ @as(*FontBlock, @ptrCast(bytes.ptr)).size = size;
+ return bytes.ptr + font_block_header;
+}
+
+export fn ui_free(block: ?*anyopaque) void {
+ const old = block orelse return;
+ const base: [*]align(16) u8 = @alignCast(@as([*]u8, @ptrCast(old)) - font_block_header);
+ const header: *FontBlock = @ptrCast(base);
+ header.allocator.free(base[0 .. font_block_header + header.size]);
+}
+
+test "FreeType and HarfBuzz allocate from the gui allocator and give it all back" {
+ // HarfBuzz's process-wide objects come from the default heap, as in runNative.
+ const primer = c.ui_font_new(font_ttf.ptr, @intCast(font_ttf.len)).?;
+ c.ui_font_prime(primer);
+ c.ui_font_free(primer);
+ font_allocator = std.testing.allocator;
+ defer font_allocator = std.heap.smp_allocator;
+
+ const font = c.ui_font_new(font_ttf.ptr, @intCast(font_ttf.len)).?;
+ defer c.ui_font_free(font);
+ var mask: [64 * 64]u8 = undefined;
+ try std.testing.expectEqual(@as(c_int, 1), c.ui_font_raster(font, 27, c.ui_font_glyph(font, 'g'), 1, &mask, 64, 32, 48, 0, 32, 30));
+ try std.testing.expectEqual(@as(c_int, 0), c.ui_font_substitutes(font, c.ui_font_glyph(font, '-')));
+
+ // A font with ligatures runs the whole HarfBuzz pass.
+ const bytes = filesystem.readFile(std.testing.allocator, "assets/MapleMono-NF-Regular.ttf") catch return;
+ defer std.testing.allocator.free(bytes);
+ const maple = c.ui_font_new(bytes.ptr, @intCast(bytes.len)) orelse return error.FontInit;
+ defer c.ui_font_free(maple);
+ var cell_w: c_int = 0;
+ var cell_h: c_int = 0;
+ var ascent: c_int = 0;
+ c.ui_font_cell_metrics(maple, 27, &cell_w, &cell_h, &ascent);
+ const text = [_]u32{ 'a', '-', '>', 'b', '=', '=' };
+ var shaped: [text.len]c.UIShapedCell = undefined;
+ c.ui_font_shape(maple, 27, cell_w, &text, text.len, &shaped);
+ try std.testing.expectEqual(@as(u8, 2), shaped[1].span);
+ try std.testing.expectEqual(@as(u8, 2), shaped[4].span);
+}
+
const max_fallback_fonts = 1 + fonts.fallback_names.len;
const LoadedFallback = struct {
face: *c.UIFont,
@@ -2181,10 +2260,16 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u
else
c.SDL_GetGPUSwapchainTextureFormat(device, window);
+ font_allocator = gpa;
const font = c.ui_font_new(font_ttf.ptr, @intCast(font_ttf.len)) orelse {
log.err("ui_font_new failed", .{});
return error.FontInit;
};
+ // HarfBuzz's process-wide objects are made once and never freed: made
+ // from the default heap, they stay out of a Debug build's leak report.
+ font_allocator = std.heap.smp_allocator;
+ c.ui_font_prime(font);
+ font_allocator = gpa;
const px: f32 = 27.0;
var cw: c_int = 10;
var chh: c_int = 20;
@@ -6106,8 +6191,12 @@ fn cellSlot(g: *Gui, line: []const pardes.Cell, at: usize, cursor: ?usize, role:
return words.slots.items[text_start + at - start];
}
-/// A Gui that can shape and rasterize `font` at `px`, and nothing else.
+/// A Gui that can shape and rasterize `font` at `px`, and nothing else. From
+/// here on the fonts allocate from std.testing.allocator (the caller puts
+/// font_allocator back), so the test counts what they leak.
fn textTestGui(font: *c.UIFont, px: f32) !Gui {
+ c.ui_font_prime(font);
+ font_allocator = std.testing.allocator;
var g: Gui = undefined;
g.font = font;
g.fallback_count = 0;
@@ -6141,6 +6230,7 @@ test "a ligature draws across its cells, one slice each, and comes apart under t
const font = c.ui_font_new(bytes.ptr, @intCast(bytes.len)) orelse return error.FontInit;
defer c.ui_font_free(font);
var g = try textTestGui(font, 27);
+ defer font_allocator = std.heap.smp_allocator;
defer std.testing.allocator.free(g.atlas_stage);
defer g.glyphs.deinit();
defer g.words.deinit(std.testing.allocator);
@@ -6199,6 +6289,7 @@ test "a font without ligatures draws every cell as its own glyph, exactly as bef
const font = c.ui_font_new(font_ttf.ptr, @intCast(font_ttf.len)).?;
defer c.ui_font_free(font);
var g = try textTestGui(font, 18);
+ defer font_allocator = std.heap.smp_allocator;
defer std.testing.allocator.free(g.atlas_stage);
defer g.glyphs.deinit();
defer g.words.deinit(std.testing.allocator);