a73x

9c669d38

Draw cell backgrounds

a73x   2026-04-08 11:57


diff --git a/build.zig b/build.zig
index 97ded6d..1fb9743 100644
--- a/build.zig
+++ b/build.zig
@@ -182,6 +182,8 @@ pub fn build(b: *std.Build) void {
    renderer_mod.addImport("vulkan", vulkan_module);
    renderer_mod.linkSystemLibrary("dl", .{});
    exe_mod.addImport("renderer", renderer_mod);
    main_test_mod.addImport("renderer", renderer_mod);
    main_test_mod.addImport("font", font_mod);

    // Test renderer.zig
    const renderer_test_mod = b.createModule(.{
diff --git a/src/main.zig b/src/main.zig
index 02f4d78..591cb85 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -194,6 +194,8 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
        try term.snapshot();

        instances.clearRetainingCapacity();
        const default_bg = term.backgroundColor();
        const bg_uv = atlas.cursorUV();

        const term_rows = term.render_state.row_data.items(.cells);
        var row_idx: u32 = 0;
@@ -203,24 +205,24 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
            var col_idx: u32 = 0;
            while (col_idx < raw_cells.len) : (col_idx += 1) {
                const cp = raw_cells[col_idx].codepoint();
                if (cp == 0 or cp == ' ') continue;

                const uv = atlas.getOrInsert(&face, @intCast(cp)) catch continue;
                const colors = term.cellColors(row_cells.get(col_idx));

                try instances.append(alloc, .{
                    .cell_pos = .{ @floatFromInt(col_idx), @floatFromInt(row_idx) },
                    .glyph_size = .{ @floatFromInt(uv.width), @floatFromInt(uv.height) },
                    .glyph_bearing = .{
                        @floatFromInt(uv.bearing_x),
                        // bearing_y in freetype is from baseline going up; our quad origin is top-left going down
                        // cell_h - bearing_y gives the offset from the top of the cell
                        @as(f32, @floatFromInt(cell_h)) - @as(f32, @floatFromInt(uv.bearing_y)),
                    },
                    .uv_rect = .{ uv.u0, uv.v0, uv.u1, uv.v1 },
                    .fg = colors.fg,
                    .bg = colors.bg,
                });
                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,
                    glyph_uv,
                    bg_uv,
                    colors,
                    default_bg,
                );
            }
        }

@@ -277,6 +279,43 @@ fn gridSizeForWindow(window_w: u32, window_h: u32, cell_w: u32, cell_h: u32) Gri
    };
}

fn appendCellInstances(
    alloc: std.mem.Allocator,
    instances: *std.ArrayListUnmanaged(renderer.Instance),
    row_idx: u32,
    col_idx: u32,
    cell_w: u32,
    cell_h: u32,
    glyph_uv: ?font.GlyphUV,
    bg_uv: font.GlyphUV,
    colors: vt.CellColors,
    default_bg: [4]f32,
) !void {
    if (!std.meta.eql(colors.bg, default_bg)) {
        try instances.append(alloc, .{
            .cell_pos = .{ @floatFromInt(col_idx), @floatFromInt(row_idx) },
            .glyph_size = .{ @floatFromInt(cell_w), @floatFromInt(cell_h) },
            .glyph_bearing = .{ 0, 0 },
            .uv_rect = .{ bg_uv.u0, bg_uv.v0, bg_uv.u1, bg_uv.v1 },
            .fg = colors.bg,
            .bg = colors.bg,
        });
    }

    const uv = glyph_uv orelse return;
    try instances.append(alloc, .{
        .cell_pos = .{ @floatFromInt(col_idx), @floatFromInt(row_idx) },
        .glyph_size = .{ @floatFromInt(uv.width), @floatFromInt(uv.height) },
        .glyph_bearing = .{
            @floatFromInt(uv.bearing_x),
            @as(f32, @floatFromInt(cell_h)) - @as(f32, @floatFromInt(uv.bearing_y)),
        },
        .uv_rect = .{ uv.u0, uv.v0, uv.u1, uv.v1 },
        .fg = colors.fg,
        .bg = colors.bg,
    });
}

fn encodeKeyboardEvent(
    term: *const vt.Terminal,
    ev: wayland_client.KeyboardEvent,
@@ -441,6 +480,94 @@ test "gridSizeForWindow clamps to at least one cell" {
    try std.testing.expectEqual(@as(u16, 1), grid.rows);
}

test "appendCellInstances emits a background quad for colored space" {
    var instances: std.ArrayListUnmanaged(renderer.Instance) = .empty;
    defer instances.deinit(std.testing.allocator);

    const bg_uv: font.GlyphUV = .{
        .u0 = 0.0,
        .v0 = 0.0,
        .u1 = 0.1,
        .v1 = 0.1,
        .width = 1,
        .height = 1,
        .bearing_x = 0,
        .bearing_y = 0,
        .advance_x = 1,
    };

    try appendCellInstances(
        std.testing.allocator,
        &instances,
        2,
        3,
        8,
        16,
        null,
        bg_uv,
        .{
            .fg = .{ 1.0, 1.0, 1.0, 1.0 },
            .bg = .{ 0.2, 0.3, 0.4, 1.0 },
        },
        .{ 0.0, 0.0, 0.0, 1.0 },
    );

    try std.testing.expectEqual(@as(usize, 1), instances.items.len);
    try std.testing.expectEqualDeep([4]f32{ 0.2, 0.3, 0.4, 1.0 }, instances.items[0].fg);
    try std.testing.expectEqualDeep([4]f32{ 0.2, 0.3, 0.4, 1.0 }, instances.items[0].bg);
    try std.testing.expectEqualDeep([2]f32{ 8.0, 16.0 }, instances.items[0].glyph_size);
}

test "appendCellInstances emits background before glyph" {
    var instances: std.ArrayListUnmanaged(renderer.Instance) = .empty;
    defer instances.deinit(std.testing.allocator);

    const bg_uv: font.GlyphUV = .{
        .u0 = 0.0,
        .v0 = 0.0,
        .u1 = 0.1,
        .v1 = 0.1,
        .width = 1,
        .height = 1,
        .bearing_x = 0,
        .bearing_y = 0,
        .advance_x = 1,
    };
    const glyph_uv: font.GlyphUV = .{
        .u0 = 0.2,
        .v0 = 0.3,
        .u1 = 0.4,
        .v1 = 0.5,
        .width = 7,
        .height = 11,
        .bearing_x = 1,
        .bearing_y = 13,
        .advance_x = 8,
    };

    try appendCellInstances(
        std.testing.allocator,
        &instances,
        0,
        1,
        8,
        16,
        glyph_uv,
        bg_uv,
        .{
            .fg = .{ 0.9, 0.8, 0.7, 1.0 },
            .bg = .{ 0.1, 0.2, 0.3, 1.0 },
        },
        .{ 0.0, 0.0, 0.0, 1.0 },
    );

    try std.testing.expectEqual(@as(usize, 2), instances.items.len);
    try std.testing.expectEqualDeep([2]f32{ 8.0, 16.0 }, instances.items[0].glyph_size);
    try std.testing.expectEqualDeep([2]f32{ 7.0, 11.0 }, instances.items[1].glyph_size);
    try std.testing.expectEqualDeep([4]f32{ 0.1, 0.2, 0.3, 1.0 }, instances.items[0].fg);
    try std.testing.expectEqualDeep([4]f32{ 0.9, 0.8, 0.7, 1.0 }, instances.items[1].fg);
}

fn runRenderSmokeTest(alloc: std.mem.Allocator) !void {
    var conn = try wayland_client.Connection.init();
    defer conn.deinit();
diff --git a/src/vt.zig b/src/vt.zig
index cf18589..e0f67b4 100644
--- a/src/vt.zig
+++ b/src/vt.zig
@@ -158,6 +158,10 @@ pub const Terminal = struct {
        try self.render_state.update(self.alloc, &self.inner);
    }

    pub fn backgroundColor(self: *const Terminal) [4]f32 {
        return rgbToFloat4(self.render_state.colors.background);
    }

    /// Number of columns in the terminal grid.
    pub fn cols(self: *const Terminal) u16 {
        return @intCast(self.render_state.cols);