mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
font: implement many rendering, caching functions for SharedGrid
This commit is contained in:
@ -15,13 +15,16 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ziglyph = @import("ziglyph");
|
const ziglyph = @import("ziglyph");
|
||||||
const font = @import("main.zig");
|
const font = @import("main.zig");
|
||||||
|
const Atlas = font.Atlas;
|
||||||
const CodepointMap = font.CodepointMap;
|
const CodepointMap = font.CodepointMap;
|
||||||
const Collection = font.Collection;
|
const Collection = font.Collection;
|
||||||
const Discover = font.Discover;
|
const Discover = font.Discover;
|
||||||
const DiscoveryDescriptor = font.discovery.Descriptor;
|
const DiscoveryDescriptor = font.discovery.Descriptor;
|
||||||
const Face = font.Face;
|
const Face = font.Face;
|
||||||
|
const Glyph = font.Glyph;
|
||||||
const Library = font.Library;
|
const Library = font.Library;
|
||||||
const Presentation = font.Presentation;
|
const Presentation = font.Presentation;
|
||||||
|
const RenderOptions = font.face.RenderOptions;
|
||||||
const SpriteFace = font.SpriteFace;
|
const SpriteFace = font.SpriteFace;
|
||||||
const Style = font.Style;
|
const Style = font.Style;
|
||||||
|
|
||||||
@ -286,6 +289,52 @@ fn getIndexCodepointOverride(
|
|||||||
return null;
|
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.
|
/// Packed array of booleans to indicate if a style is enabled or not.
|
||||||
pub const StyleStatus = std.EnumArray(Style, bool);
|
pub const StyleStatus = std.EnumArray(Style, bool);
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ const log = std.log.scoped(.font_shared_grid);
|
|||||||
codepoints: std.AutoHashMapUnmanaged(CodepointKey, ?Collection.Index) = .{},
|
codepoints: std.AutoHashMapUnmanaged(CodepointKey, ?Collection.Index) = .{},
|
||||||
|
|
||||||
/// Cache for glyph renders into the atlas.
|
/// 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
|
/// The texture atlas to store renders in. The Glyph data in the glyphs
|
||||||
/// cache is dependent on the atlas matching.
|
/// cache is dependent on the atlas matching.
|
||||||
@ -59,7 +59,9 @@ resolver: CodepointResolver,
|
|||||||
metrics: Metrics,
|
metrics: Metrics,
|
||||||
|
|
||||||
/// The RwLock used to protect the shared grid. Callers are expected to use
|
/// 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,
|
lock: std.Thread.RwLock,
|
||||||
|
|
||||||
/// Initialize the grid.
|
/// 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 {
|
const CodepointKey = struct {
|
||||||
style: Style,
|
style: Style,
|
||||||
codepoint: u32,
|
codepoint: u32,
|
||||||
|
@ -152,7 +152,7 @@ pub const Presentation = enum(u1) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// A FontIndex that can be used to use the sprite font directly.
|
/// 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 {
|
test {
|
||||||
// For non-wasm we want to test everything we can
|
// For non-wasm we want to test everything we can
|
||||||
|
@ -1874,7 +1874,7 @@ fn updateCell(
|
|||||||
// If the cell has a character, draw it
|
// If the cell has a character, draw it
|
||||||
if (cell.hasText()) fg: {
|
if (cell.hasText()) fg: {
|
||||||
// Render
|
// Render
|
||||||
const glyph = try self.font_group.renderGlyph(
|
const render = try self.font_grid.renderGlyph(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
shaper_run.font_index,
|
shaper_run.font_index,
|
||||||
shaper_cell.glyph_index orelse break :fg,
|
shaper_cell.glyph_index orelse break :fg,
|
||||||
@ -1885,9 +1885,8 @@ fn updateCell(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const mode: mtl_shaders.Cell.Mode = switch (try fgMode(
|
const mode: mtl_shaders.Cell.Mode = switch (try fgMode(
|
||||||
&self.font_group.group,
|
render.presentation,
|
||||||
cell_pin,
|
cell_pin,
|
||||||
shaper_run,
|
|
||||||
)) {
|
)) {
|
||||||
.normal => .fg,
|
.normal => .fg,
|
||||||
.color => .fg_color,
|
.color => .fg_color,
|
||||||
@ -1900,11 +1899,11 @@ fn updateCell(
|
|||||||
.cell_width = cell.gridWidth(),
|
.cell_width = cell.gridWidth(),
|
||||||
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
|
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
|
||||||
.bg_color = bg,
|
.bg_color = bg,
|
||||||
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
|
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
|
||||||
.glyph_size = .{ glyph.width, glyph.height },
|
.glyph_size = .{ render.glyph.width, render.glyph.height },
|
||||||
.glyph_offset = .{
|
.glyph_offset = .{
|
||||||
glyph.offset_x + shaper_cell.x_offset,
|
render.glyph.offset_x + shaper_cell.x_offset,
|
||||||
glyph.offset_y + shaper_cell.y_offset,
|
render.glyph.offset_y + shaper_cell.y_offset,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1919,7 +1918,7 @@ fn updateCell(
|
|||||||
.curly => .underline_curly,
|
.curly => .underline_curly,
|
||||||
};
|
};
|
||||||
|
|
||||||
const glyph = try self.font_group.renderGlyph(
|
const render = try self.font_grid.renderGlyph(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
@ -1937,9 +1936,9 @@ fn updateCell(
|
|||||||
.cell_width = cell.gridWidth(),
|
.cell_width = cell.gridWidth(),
|
||||||
.color = .{ color.r, color.g, color.b, alpha },
|
.color = .{ color.r, color.g, color.b, alpha },
|
||||||
.bg_color = bg,
|
.bg_color = bg,
|
||||||
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
|
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
|
||||||
.glyph_size = .{ glyph.width, glyph.height },
|
.glyph_size = .{ render.glyph.width, render.glyph.height },
|
||||||
.glyph_offset = .{ glyph.offset_x, glyph.offset_y },
|
.glyph_offset = .{ render.glyph.offset_x, render.glyph.offset_y },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1988,7 +1987,7 @@ fn addCursor(
|
|||||||
.underline => .underline,
|
.underline => .underline,
|
||||||
};
|
};
|
||||||
|
|
||||||
const glyph = self.font_group.renderGlyph(
|
const render = self.font_grid.renderGlyph(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
@ -2010,9 +2009,9 @@ fn addCursor(
|
|||||||
.cell_width = if (wide) 2 else 1,
|
.cell_width = if (wide) 2 else 1,
|
||||||
.color = .{ color.r, color.g, color.b, alpha },
|
.color = .{ color.r, color.g, color.b, alpha },
|
||||||
.bg_color = .{ 0, 0, 0, 0 },
|
.bg_color = .{ 0, 0, 0, 0 },
|
||||||
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
|
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
|
||||||
.glyph_size = .{ glyph.width, glyph.height },
|
.glyph_size = .{ render.glyph.width, render.glyph.height },
|
||||||
.glyph_offset = .{ glyph.offset_x, glyph.offset_y },
|
.glyph_offset = .{ render.glyph.offset_x, render.glyph.offset_y },
|
||||||
});
|
});
|
||||||
|
|
||||||
return &self.cells.items[self.cells.items.len - 1];
|
return &self.cells.items[self.cells.items.len - 1];
|
||||||
@ -2024,6 +2023,8 @@ fn addPreeditCell(
|
|||||||
x: usize,
|
x: usize,
|
||||||
y: usize,
|
y: usize,
|
||||||
) !void {
|
) !void {
|
||||||
|
if (true) @panic("TODO"); // TODO(fontmem)
|
||||||
|
|
||||||
// Preedit is rendered inverted
|
// Preedit is rendered inverted
|
||||||
const bg = self.foreground_color;
|
const bg = self.foreground_color;
|
||||||
const fg = self.background_color;
|
const fg = self.background_color;
|
||||||
|
@ -20,11 +20,9 @@ pub const FgMode = enum {
|
|||||||
/// meant to be called from the typical updateCell function within a
|
/// meant to be called from the typical updateCell function within a
|
||||||
/// renderer.
|
/// renderer.
|
||||||
pub fn fgMode(
|
pub fn fgMode(
|
||||||
group: *font.Group,
|
presentation: font.Presentation,
|
||||||
cell_pin: terminal.Pin,
|
cell_pin: terminal.Pin,
|
||||||
shaper_run: font.shape.TextRun,
|
|
||||||
) !FgMode {
|
) !FgMode {
|
||||||
const presentation = try group.presentationFromIndex(shaper_run.font_index);
|
|
||||||
return switch (presentation) {
|
return switch (presentation) {
|
||||||
// Emoji is always full size and color.
|
// Emoji is always full size and color.
|
||||||
.emoji => .color,
|
.emoji => .color,
|
||||||
|
Reference in New Issue
Block a user