a73x

0615931a

Extract row instance rebuild logic

a73x   2026-04-08 19:09


diff --git a/src/main.zig b/src/main.zig
index 22c217c..f15c3be 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -149,6 +149,8 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
    var instances: std.ArrayListUnmanaged(renderer.Instance) = .empty;
    defer instances.deinit(alloc);
    try instances.ensureTotalCapacity(alloc, @as(usize, cols) * rows);
    var row_cache = RowInstanceCache{};
    defer row_cache.deinit(alloc);

    // === main loop ===
    const wl_fd = conn.display.getFd();
@@ -253,31 +255,21 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
        const term_rows = term.render_state.row_data.items(.cells);
        var row_idx: u32 = 0;
        while (row_idx < term_rows.len) : (row_idx += 1) {
            const row_cells = term_rows[row_idx];
            const raw_cells = row_cells.items(.raw);
            var col_idx: u32 = 0;
            while (col_idx < raw_cells.len) : (col_idx += 1) {
                const cp = raw_cells[col_idx].codepoint();
                const colors = term.cellColors(row_cells.get(col_idx));
                const glyph_uv = if (cp == 0 or cp == ' ')
                    null
                else
                    atlas.getOrInsert(&face, @intCast(cp)) catch null;

                try appendCellInstances(
                    alloc,
                    &instances,
                    row_idx,
                    col_idx,
                    cell_w,
                    cell_h,
                    baseline,
                    glyph_uv,
                    bg_uv,
                    colors,
                    default_bg,
                );
            }
            _ = try rebuildRowInstances(
                alloc,
                &row_cache,
                row_idx,
                term_rows[row_idx],
                term,
                &face,
                &atlas,
                cell_w,
                cell_h,
                baseline,
                default_bg,
                bg_uv,
            );
            try instances.appendSlice(alloc, row_cache.instances.items);
        }

        if (term.render_state.cursor.visible) {
@@ -719,6 +711,118 @@ test "repackRowCaches keeps cursor span explicit for empty and non-empty cursor 
    try std.testing.expectEqualDeep([2]f32{ 9.0, 10.0 }, packed_instances.items[2].cell_pos);
}

test "rebuildRowInstances emits expected instances for a colored glyph row" {
    var term = try vt.Terminal.init(std.testing.allocator, .{
        .cols = 80,
        .rows = 24,
    });
    defer term.deinit();

    term.write("\x1b[31;44mA\x1b[0m");
    try term.snapshot();

    var lookup = try font.lookupMonospace(std.testing.allocator);
    defer lookup.deinit(std.testing.allocator);

    var face = try font.Face.init(std.testing.allocator, lookup.path, lookup.index, 16);
    defer face.deinit();

    var atlas = try font.Atlas.init(std.testing.allocator, 256, 256);
    defer atlas.deinit();

    var cache = RowInstanceCache{};
    defer cache.instances.deinit(std.testing.allocator);

    const row_cells = term.render_state.row_data.get(0).cells;
    const default_bg = term.backgroundColor();
    const rebuilt = try rebuildRowInstances(
        std.testing.allocator,
        &cache,
        0,
        row_cells,
        term,
        &face,
        &atlas,
        face.cellWidth(),
        face.cellHeight(),
        face.baseline(),
        default_bg,
        atlas.cursorUV(),
    );

    try std.testing.expect(rebuilt);
    try std.testing.expectEqual(@as(usize, 2), cache.instances.items.len);

    const colors = term.cellColors(row_cells.get(0));
    try std.testing.expectEqualDeep([2]f32{ 0.0, 0.0 }, cache.instances.items[0].cell_pos);
    try std.testing.expectEqualDeep([2]f32{ @floatFromInt(face.cellWidth()), @floatFromInt(face.cellHeight()) }, cache.instances.items[0].glyph_size);
    try std.testing.expectEqualDeep(colors.bg, cache.instances.items[0].fg);
    try std.testing.expectEqualDeep(colors.bg, cache.instances.items[0].bg);
    try std.testing.expectEqualDeep([2]f32{ 0.0, 0.0 }, cache.instances.items[1].cell_pos);
    try std.testing.expectEqualDeep(colors.fg, cache.instances.items[1].fg);
    try std.testing.expectEqualDeep(colors.bg, cache.instances.items[1].bg);
}

test "rebuildRowInstances replaces stale cached contents without layout dirtiness when count is unchanged" {
    var term = try vt.Terminal.init(std.testing.allocator, .{
        .cols = 80,
        .rows = 24,
    });
    defer term.deinit();

    term.write("\x1b[31;44mA\x1b[0m");
    try term.snapshot();

    var lookup = try font.lookupMonospace(std.testing.allocator);
    defer lookup.deinit(std.testing.allocator);

    var face = try font.Face.init(std.testing.allocator, lookup.path, lookup.index, 16);
    defer face.deinit();

    var atlas = try font.Atlas.init(std.testing.allocator, 256, 256);
    defer atlas.deinit();

    var cache = RowInstanceCache{};
    defer cache.instances.deinit(std.testing.allocator);
    try cache.instances.append(std.testing.allocator, .{
        .cell_pos = .{ 99.0, 99.0 },
        .glyph_size = .{ 1.0, 1.0 },
        .glyph_bearing = .{ 0.0, 0.0 },
        .uv_rect = .{ 0.0, 0.0, 0.0, 0.0 },
        .fg = .{ 0.0, 0.0, 0.0, 0.0 },
        .bg = .{ 0.0, 0.0, 0.0, 0.0 },
    });
    try cache.instances.append(std.testing.allocator, .{
        .cell_pos = .{ 98.0, 98.0 },
        .glyph_size = .{ 1.0, 1.0 },
        .glyph_bearing = .{ 0.0, 0.0 },
        .uv_rect = .{ 0.0, 0.0, 0.0, 0.0 },
        .fg = .{ 0.0, 0.0, 0.0, 0.0 },
        .bg = .{ 0.0, 0.0, 0.0, 0.0 },
    });

    const row_cells = term.render_state.row_data.get(0).cells;
    const rebuilt = try rebuildRowInstances(
        std.testing.allocator,
        &cache,
        0,
        row_cells,
        term,
        &face,
        &atlas,
        face.cellWidth(),
        face.cellHeight(),
        face.baseline(),
        term.backgroundColor(),
        atlas.cursorUV(),
    );

    try std.testing.expect(!rebuilt);
    try std.testing.expectEqual(@as(usize, 2), cache.instances.items.len);
    try std.testing.expectEqualDeep([2]f32{ 0.0, 0.0 }, cache.instances.items[0].cell_pos);
    try std.testing.expectEqualDeep([2]f32{ 0.0, 0.0 }, cache.instances.items[1].cell_pos);
}

test "RenderCache resizeRows preserves surviving row caches" {
    var cache = RenderCache.empty;
    defer cache.deinit(std.testing.allocator);
@@ -939,6 +1043,51 @@ fn repackRowCaches(
    };
}

fn rebuildRowInstances(
    alloc: std.mem.Allocator,
    cache: *RowInstanceCache,
    row_idx: u32,
    row_cells: anytype,
    term: *const vt.Terminal,
    face: *font.Face,
    atlas: *font.Atlas,
    cell_w: u32,
    cell_h: u32,
    baseline: u32,
    default_bg: [4]f32,
    bg_uv: font.GlyphUV,
) !bool {
    const old_len = cache.instances.items.len;
    cache.instances.clearRetainingCapacity();

    const raw_cells = row_cells.items(.raw);
    var col_idx: u32 = 0;
    while (col_idx < raw_cells.len) : (col_idx += 1) {
        const cp = raw_cells[col_idx].codepoint();
        const colors = term.cellColors(row_cells.get(col_idx));
        const glyph_uv = if (cp == 0 or cp == ' ')
            null
        else
            atlas.getOrInsert(face, @intCast(cp)) catch null;

        try appendCellInstances(
            alloc,
            &cache.instances,
            row_idx,
            col_idx,
            cell_w,
            cell_h,
            baseline,
            glyph_uv,
            bg_uv,
            colors,
            default_bg,
        );
    }

    return markLayoutDirtyOnLenChange(old_len, cache.instances.items.len);
}

fn markLayoutDirtyOnLenChange(old_len: usize, new_len: usize) bool {
    return old_len != new_len;
}