mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
avoid large shaper buffer allocation on every frame
This commit is contained in:
28
src/Grid.zig
28
src/Grid.zig
@ -43,9 +43,10 @@ vbo: gl.Buffer,
|
||||
texture: gl.Texture,
|
||||
texture_color: gl.Texture,
|
||||
|
||||
/// The font atlas.
|
||||
/// The font structures.
|
||||
font_lib: font.Library,
|
||||
font_group: font.GroupCache,
|
||||
font_shaper: font.Shaper,
|
||||
|
||||
/// Whether the cursor is visible or not. This is used to control cursor
|
||||
/// blinking.
|
||||
@ -176,6 +177,12 @@ pub fn init(
|
||||
});
|
||||
errdefer font_group.deinit(alloc);
|
||||
|
||||
// Create the initial font shaper
|
||||
var shape_buf = try alloc.alloc(font.Shaper.Cell, 1);
|
||||
errdefer alloc.free(shape_buf);
|
||||
var shaper = try font.Shaper.init(shape_buf);
|
||||
errdefer shaper.deinit();
|
||||
|
||||
// Load all visible ASCII characters and build our cell width based on
|
||||
// the widest character that we see.
|
||||
const metrics = try font_group.metrics(alloc);
|
||||
@ -306,6 +313,7 @@ pub fn init(
|
||||
.texture_color = tex_color,
|
||||
.font_lib = font_lib,
|
||||
.font_group = font_group,
|
||||
.font_shaper = shaper,
|
||||
.cursor_visible = true,
|
||||
.cursor_style = .box,
|
||||
.background = .{ .r = 0, .g = 0, .b = 0 },
|
||||
@ -314,6 +322,8 @@ pub fn init(
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Grid) void {
|
||||
self.font_shaper.deinit();
|
||||
self.alloc.free(self.font_shaper.cell_buf);
|
||||
self.font_group.deinit(self.alloc);
|
||||
self.font_lib.deinit();
|
||||
|
||||
@ -353,12 +363,6 @@ pub fn rebuildCells(self: *Grid, term: *Terminal) !void {
|
||||
// We've written no data to the GPU, refresh it all
|
||||
self.gl_cells_written = 0;
|
||||
|
||||
// Create a text shaper we'll use for the screen
|
||||
var shape_buf = try self.alloc.alloc(font.Shaper.Cell, term.screen.cols * 2);
|
||||
defer self.alloc.free(shape_buf);
|
||||
var shaper = try font.Shaper.init(&self.font_group, shape_buf);
|
||||
defer shaper.deinit();
|
||||
|
||||
// Build each cell
|
||||
var rowIter = term.screen.rowIterator(.viewport);
|
||||
var y: usize = 0;
|
||||
@ -366,9 +370,9 @@ pub fn rebuildCells(self: *Grid, term: *Terminal) !void {
|
||||
defer y += 1;
|
||||
|
||||
// Split our row into runs and shape each one.
|
||||
var iter = shaper.runIterator(row);
|
||||
var iter = self.font_shaper.runIterator(&self.font_group, row);
|
||||
while (try iter.next(self.alloc)) |run| {
|
||||
for (try shaper.shape(run)) |shaper_cell| {
|
||||
for (try self.font_shaper.shape(run)) |shaper_cell| {
|
||||
assert(try self.updateCell(
|
||||
term,
|
||||
row.getCell(shaper_cell.x),
|
||||
@ -621,6 +625,12 @@ pub fn setScreenSize(self: *Grid, dim: ScreenSize) !void {
|
||||
// Recalculate the rows/columns.
|
||||
self.size.update(dim, self.cell_size);
|
||||
|
||||
// Update our shaper
|
||||
var shape_buf = try self.alloc.alloc(font.Shaper.Cell, self.size.columns * 2);
|
||||
errdefer self.alloc.free(shape_buf);
|
||||
self.alloc.free(self.font_shaper.cell_buf);
|
||||
self.font_shaper.cell_buf = shape_buf;
|
||||
|
||||
log.debug("screen size screen={} grid={}, cell={}", .{ dim, self.size, self.cell_size });
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,6 @@ const terminal = @import("../terminal/main.zig");
|
||||
|
||||
const log = std.log.scoped(.font_shaper);
|
||||
|
||||
/// The font group to use under the covers
|
||||
group: *GroupCache,
|
||||
|
||||
/// The buffer used for text shaping. We reuse it across multiple shaping
|
||||
/// calls to prevent allocations.
|
||||
hb_buf: harfbuzz.Buffer,
|
||||
@ -29,9 +26,8 @@ cell_buf: []Cell,
|
||||
|
||||
/// The cell_buf argument is the buffer to use for storing shaped results.
|
||||
/// This should be at least the number of columns in the terminal.
|
||||
pub fn init(group: *GroupCache, cell_buf: []Cell) !Shaper {
|
||||
pub fn init(cell_buf: []Cell) !Shaper {
|
||||
return Shaper{
|
||||
.group = group,
|
||||
.hb_buf = try harfbuzz.Buffer.create(),
|
||||
.cell_buf = cell_buf,
|
||||
};
|
||||
@ -44,8 +40,8 @@ pub fn deinit(self: *Shaper) void {
|
||||
/// Returns an iterator that returns one text run at a time for the
|
||||
/// given terminal row. Note that text runs are are only valid one at a time
|
||||
/// for a Shaper struct since they share state.
|
||||
pub fn runIterator(self: *Shaper, row: terminal.Screen.Row) RunIterator {
|
||||
return .{ .shaper = self, .row = row };
|
||||
pub fn runIterator(self: *Shaper, group: *GroupCache, row: terminal.Screen.Row) RunIterator {
|
||||
return .{ .shaper = self, .group = group, .row = row };
|
||||
}
|
||||
|
||||
/// Shape the given text run. The text run must be the immediately previous
|
||||
@ -65,7 +61,7 @@ pub fn shape(self: *Shaper, run: TextRun) ![]Cell {
|
||||
harfbuzz.Feature.fromString("liga").?,
|
||||
};
|
||||
|
||||
const face = self.group.group.faceFromIndex(run.font_index);
|
||||
const face = run.group.group.faceFromIndex(run.font_index);
|
||||
harfbuzz.shape(face.hb_font, self.hb_buf, hb_feats);
|
||||
|
||||
// If our buffer is empty, we short-circuit the rest of the work
|
||||
@ -114,12 +110,16 @@ pub const TextRun = struct {
|
||||
/// The total number of cells produced by this run.
|
||||
cells: u16,
|
||||
|
||||
/// The font group that built this run.
|
||||
group: *GroupCache,
|
||||
|
||||
/// The font index to use for the glyphs of this run.
|
||||
font_index: Group.FontIndex,
|
||||
};
|
||||
|
||||
pub const RunIterator = struct {
|
||||
shaper: *Shaper,
|
||||
group: *GroupCache,
|
||||
row: terminal.Screen.Row,
|
||||
i: usize = 0,
|
||||
|
||||
@ -174,18 +174,18 @@ pub const RunIterator = struct {
|
||||
// Determine the font for this cell. We'll use fallbacks
|
||||
// manually here to try replacement chars and then a space
|
||||
// for unknown glyphs.
|
||||
const font_idx_opt = (try self.shaper.group.indexForCodepoint(
|
||||
const font_idx_opt = (try self.group.indexForCodepoint(
|
||||
alloc,
|
||||
if (cell.empty()) ' ' else cell.char,
|
||||
style,
|
||||
presentation,
|
||||
)) orelse (try self.shaper.group.indexForCodepoint(
|
||||
)) orelse (try self.group.indexForCodepoint(
|
||||
alloc,
|
||||
0xFFFD,
|
||||
style,
|
||||
.text,
|
||||
)) orelse
|
||||
try self.shaper.group.indexForCodepoint(alloc, ' ', style, .text);
|
||||
try self.group.indexForCodepoint(alloc, ' ', style, .text);
|
||||
const font_idx = font_idx_opt.?;
|
||||
//log.warn("char={x} idx={}", .{ cell.char, font_idx });
|
||||
if (j == self.i) current_font = font_idx;
|
||||
@ -216,6 +216,7 @@ pub const RunIterator = struct {
|
||||
return TextRun{
|
||||
.offset = @intCast(u16, self.i),
|
||||
.cells = @intCast(u16, j - self.i),
|
||||
.group = self.group,
|
||||
.font_index = current_font,
|
||||
};
|
||||
}
|
||||
@ -236,7 +237,7 @@ test "run iterator" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |_| count += 1;
|
||||
try testing.expectEqual(@as(usize, 1), count);
|
||||
@ -249,7 +250,7 @@ test "run iterator" {
|
||||
try screen.testWriteString("ABCD EFG");
|
||||
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |_| count += 1;
|
||||
try testing.expectEqual(@as(usize, 1), count);
|
||||
@ -263,7 +264,7 @@ test "run iterator" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |_| {
|
||||
count += 1;
|
||||
@ -295,7 +296,7 @@ test "shape" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -318,7 +319,7 @@ test "shape inconsolata ligs" {
|
||||
try screen.testWriteString(">=");
|
||||
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -335,7 +336,7 @@ test "shape inconsolata ligs" {
|
||||
try screen.testWriteString("===");
|
||||
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -360,7 +361,7 @@ test "shape emoji width" {
|
||||
try screen.testWriteString("👍");
|
||||
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -394,7 +395,7 @@ test "shape emoji width long" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -425,7 +426,7 @@ test "shape variation selector VS15" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -456,7 +457,7 @@ test "shape variation selector VS16" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -484,7 +485,7 @@ test "shape with empty cells in between" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -516,7 +517,7 @@ test "shape Chinese characters" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }));
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -569,7 +570,7 @@ fn testShaper(alloc: Allocator) !TestShaper {
|
||||
var cell_buf = try alloc.alloc(Cell, 80);
|
||||
errdefer alloc.free(cell_buf);
|
||||
|
||||
var shaper = try init(cache_ptr, cell_buf);
|
||||
var shaper = try init(cell_buf);
|
||||
errdefer shaper.deinit();
|
||||
|
||||
return TestShaper{
|
||||
|
Reference in New Issue
Block a user