diff --git a/src/font/CodepointResolver.zig b/src/font/CodepointResolver.zig index f902c2275..370d933cb 100644 --- a/src/font/CodepointResolver.zig +++ b/src/font/CodepointResolver.zig @@ -15,13 +15,16 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const ziglyph = @import("ziglyph"); const font = @import("main.zig"); +const Atlas = font.Atlas; const CodepointMap = font.CodepointMap; const Collection = font.Collection; const Discover = font.Discover; const DiscoveryDescriptor = font.discovery.Descriptor; const Face = font.Face; +const Glyph = font.Glyph; const Library = font.Library; const Presentation = font.Presentation; +const RenderOptions = font.face.RenderOptions; const SpriteFace = font.SpriteFace; const Style = font.Style; @@ -286,6 +289,52 @@ fn getIndexCodepointOverride( return null; } +/// Returns the presentation for a specific font index. This is useful for +/// determining what atlas is needed. +pub fn getPresentation(self: *CodepointResolver, index: Collection.Index) !Presentation { + if (index.special()) |sp| return switch (sp) { + .sprite => .text, + }; + + const face = try self.collection.getFace(index); + return face.presentation; +} + +/// Render a glyph by glyph index into the given font atlas and return +/// metadata about it. +/// +/// This performs no caching, it is up to the caller to cache calls to this +/// if they want. This will also not resize the atlas if it is full. +/// +/// IMPORTANT: this renders by /glyph index/ and not by /codepoint/. The caller +/// is expected to translate codepoints to glyph indexes in some way. The most +/// trivial way to do this is to get the Face and call glyphIndex. If you're +/// doing text shaping, the text shaping library (i.e. HarfBuzz) will automatically +/// determine glyph indexes for a text run. +pub fn renderGlyph( + self: *CodepointResolver, + alloc: Allocator, + atlas: *Atlas, + index: Collection.Index, + glyph_index: u32, + opts: RenderOptions, +) !Glyph { + // Special-case fonts are rendered directly. + if (index.special()) |sp| switch (sp) { + .sprite => return try self.sprite.?.renderGlyph( + alloc, + atlas, + glyph_index, + opts, + ), + }; + + const face = try self.collection.getFace(index); + const glyph = try face.renderGlyph(alloc, atlas, glyph_index, opts); + // log.warn("GLYPH={}", .{glyph}); + return glyph; +} + /// Packed array of booleans to indicate if a style is enabled or not. pub const StyleStatus = std.EnumArray(Style, bool); diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig index 8c1537537..c24ac08f5 100644 --- a/src/font/SharedGrid.zig +++ b/src/font/SharedGrid.zig @@ -43,7 +43,7 @@ const log = std.log.scoped(.font_shared_grid); codepoints: std.AutoHashMapUnmanaged(CodepointKey, ?Collection.Index) = .{}, /// Cache for glyph renders into the atlas. -glyphs: std.AutoHashMapUnmanaged(GlyphKey, Glyph) = .{}, +glyphs: std.AutoHashMapUnmanaged(GlyphKey, Render) = .{}, /// The texture atlas to store renders in. The Glyph data in the glyphs /// cache is dependent on the atlas matching. @@ -59,7 +59,9 @@ resolver: CodepointResolver, metrics: Metrics, /// The RwLock used to protect the shared grid. Callers are expected to use -/// this directly if they need to i.e. access the atlas directly. +/// this directly if they need to i.e. access the atlas directly. Because +/// callers can use this lock directly, maintainers need to be extra careful +/// to review call sites to ensure they are using the lock correctly. lock: std.Thread.RwLock, /// Initialize the grid. @@ -192,6 +194,76 @@ pub fn hasCodepoint( ); } +pub const Render = struct { + glyph: Glyph, + presentation: Presentation, +}; + +/// Render a glyph. This automatically determines the correct texture +/// atlas to use and caches the result. +pub fn renderGlyph( + self: *SharedGrid, + alloc: Allocator, + index: Collection.Index, + glyph_index: u32, + opts: RenderOptions, +) !Render { + const key: GlyphKey = .{ .index = index, .glyph = glyph_index, .opts = opts }; + + // Fast path: the cache has the value. This is almost always true and + // only requires a read lock. + { + self.lock.lockShared(); + defer self.lock.unlockShared(); + if (self.glyphs.get(key)) |v| return v; + } + + // Slow path: we need to search this codepoint + self.lock.lock(); + defer self.lock.unlock(); + + const gop = try self.glyphs.getOrPut(alloc, key); + if (gop.found_existing) return gop.value_ptr.*; + + // Get the presentation to determine what atlas to use + const p = try self.resolver.getPresentation(index); + const atlas: *font.Atlas = switch (p) { + .text => &self.atlas_greyscale, + .emoji => &self.atlas_color, + }; + + // Render into the atlas + const glyph = self.resolver.renderGlyph( + alloc, + atlas, + index, + glyph_index, + opts, + ) catch |err| switch (err) { + // If the atlas is full, we resize it + error.AtlasFull => blk: { + try atlas.grow(alloc, atlas.size * 2); + break :blk try self.resolver.renderGlyph( + alloc, + atlas, + index, + glyph_index, + opts, + ); + }, + + else => return err, + }; + + // Cache and return + gop.value_ptr.* = .{ + .glyph = glyph, + .presentation = p, + }; + + return gop.value_ptr.*; +} + const CodepointKey = struct { style: Style, codepoint: u32, diff --git a/src/font/main.zig b/src/font/main.zig index be1888822..0a0d376e9 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -152,7 +152,7 @@ pub const Presentation = enum(u1) { }; /// A FontIndex that can be used to use the sprite font directly. -pub const sprite_index = Group.FontIndex.initSpecial(.sprite); +pub const sprite_index = Collection.Index.initSpecial(.sprite); test { // For non-wasm we want to test everything we can diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index f5db7471c..b7bb85c4c 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1874,7 +1874,7 @@ fn updateCell( // If the cell has a character, draw it if (cell.hasText()) fg: { // Render - const glyph = try self.font_group.renderGlyph( + const render = try self.font_grid.renderGlyph( self.alloc, shaper_run.font_index, shaper_cell.glyph_index orelse break :fg, @@ -1885,9 +1885,8 @@ fn updateCell( ); const mode: mtl_shaders.Cell.Mode = switch (try fgMode( - &self.font_group.group, + render.presentation, cell_pin, - shaper_run, )) { .normal => .fg, .color => .fg_color, @@ -1900,11 +1899,11 @@ fn updateCell( .cell_width = cell.gridWidth(), .color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha }, .bg_color = bg, - .glyph_pos = .{ glyph.atlas_x, glyph.atlas_y }, - .glyph_size = .{ glyph.width, glyph.height }, + .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, + .glyph_size = .{ render.glyph.width, render.glyph.height }, .glyph_offset = .{ - glyph.offset_x + shaper_cell.x_offset, - glyph.offset_y + shaper_cell.y_offset, + render.glyph.offset_x + shaper_cell.x_offset, + render.glyph.offset_y + shaper_cell.y_offset, }, }); } @@ -1919,7 +1918,7 @@ fn updateCell( .curly => .underline_curly, }; - const glyph = try self.font_group.renderGlyph( + const render = try self.font_grid.renderGlyph( self.alloc, font.sprite_index, @intFromEnum(sprite), @@ -1937,9 +1936,9 @@ fn updateCell( .cell_width = cell.gridWidth(), .color = .{ color.r, color.g, color.b, alpha }, .bg_color = bg, - .glyph_pos = .{ glyph.atlas_x, glyph.atlas_y }, - .glyph_size = .{ glyph.width, glyph.height }, - .glyph_offset = .{ glyph.offset_x, glyph.offset_y }, + .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, + .glyph_size = .{ render.glyph.width, render.glyph.height }, + .glyph_offset = .{ render.glyph.offset_x, render.glyph.offset_y }, }); } @@ -1988,7 +1987,7 @@ fn addCursor( .underline => .underline, }; - const glyph = self.font_group.renderGlyph( + const render = self.font_grid.renderGlyph( self.alloc, font.sprite_index, @intFromEnum(sprite), @@ -2010,9 +2009,9 @@ fn addCursor( .cell_width = if (wide) 2 else 1, .color = .{ color.r, color.g, color.b, alpha }, .bg_color = .{ 0, 0, 0, 0 }, - .glyph_pos = .{ glyph.atlas_x, glyph.atlas_y }, - .glyph_size = .{ glyph.width, glyph.height }, - .glyph_offset = .{ glyph.offset_x, glyph.offset_y }, + .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, + .glyph_size = .{ render.glyph.width, render.glyph.height }, + .glyph_offset = .{ render.glyph.offset_x, render.glyph.offset_y }, }); return &self.cells.items[self.cells.items.len - 1]; @@ -2024,6 +2023,8 @@ fn addPreeditCell( x: usize, y: usize, ) !void { + if (true) @panic("TODO"); // TODO(fontmem) + // Preedit is rendered inverted const bg = self.foreground_color; const fg = self.background_color; diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index 44087da44..92993f660 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -20,11 +20,9 @@ pub const FgMode = enum { /// meant to be called from the typical updateCell function within a /// renderer. pub fn fgMode( - group: *font.Group, + presentation: font.Presentation, cell_pin: terminal.Pin, - shaper_run: font.shape.TextRun, ) !FgMode { - const presentation = try group.presentationFromIndex(shaper_run.font_index); return switch (presentation) { // Emoji is always full size and color. .emoji => .color,