9c669d38
Draw cell backgrounds
a73x 2026-04-08 11:57
Commit message
build.zig
| Old | New | ||
|---|---|---|---|
| @@ -182,6 +182,8 @@ pub fn build(b: *std.Build) void { | |||
| 182 | renderer_mod.addImport("vulkan", vulkan_module); | 182 | renderer_mod.addImport("vulkan", vulkan_module); |
| 183 | renderer_mod.linkSystemLibrary("dl", .{}); | 183 | renderer_mod.linkSystemLibrary("dl", .{}); |
| 184 | exe_mod.addImport("renderer", renderer_mod); | 184 | exe_mod.addImport("renderer", renderer_mod); |
| 185 | main_test_mod.addImport("renderer", renderer_mod); | ||
| 186 | main_test_mod.addImport("font", font_mod); | ||
| 185 | 187 | ||
| 186 | // Test renderer.zig | 188 | // Test renderer.zig |
| 187 | const renderer_test_mod = b.createModule(.{ | 189 | const renderer_test_mod = b.createModule(.{ |
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -194,6 +194,8 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 194 | try term.snapshot(); | 194 | try term.snapshot(); |
| 195 | 195 | ||
| 196 | instances.clearRetainingCapacity(); | 196 | instances.clearRetainingCapacity(); |
| 197 | const default_bg = term.backgroundColor(); | ||
| 198 | const bg_uv = atlas.cursorUV(); | ||
| 197 | 199 | ||
| 198 | const term_rows = term.render_state.row_data.items(.cells); | 200 | const term_rows = term.render_state.row_data.items(.cells); |
| 199 | var row_idx: u32 = 0; | 201 | var row_idx: u32 = 0; |
| @@ -203,24 +205,24 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 203 | var col_idx: u32 = 0; | 205 | var col_idx: u32 = 0; |
| 204 | while (col_idx < raw_cells.len) : (col_idx += 1) { | 206 | while (col_idx < raw_cells.len) : (col_idx += 1) { |
| 205 | const cp = raw_cells[col_idx].codepoint(); | 207 | const cp = raw_cells[col_idx].codepoint(); |
| 206 | if (cp == 0 or cp == ' ') continue; | ||
| 207 | |||
| 208 | const uv = atlas.getOrInsert(&face, @intCast(cp)) catch continue; | ||
| 209 | const colors = term.cellColors(row_cells.get(col_idx)); | 208 | const colors = term.cellColors(row_cells.get(col_idx)); |
| 210 | 209 | const glyph_uv = if (cp == 0 or cp == ' ') | |
| 211 | try instances.append(alloc, .{ | 210 | null |
| 212 | .cell_pos = .{ @floatFromInt(col_idx), @floatFromInt(row_idx) }, | 211 | else |
| 213 | .glyph_size = .{ @floatFromInt(uv.width), @floatFromInt(uv.height) }, | 212 | atlas.getOrInsert(&face, @intCast(cp)) catch null; |
| 214 | .glyph_bearing = .{ | 213 | |
| 215 | @floatFromInt(uv.bearing_x), | 214 | try appendCellInstances( |
| 216 | // bearing_y in freetype is from baseline going up; our quad origin is top-left going down | 215 | alloc, |
| 217 | // cell_h - bearing_y gives the offset from the top of the cell | 216 | &instances, |
| 218 | @as(f32, @floatFromInt(cell_h)) - @as(f32, @floatFromInt(uv.bearing_y)), | 217 | row_idx, |
| 219 | }, | 218 | col_idx, |
| 220 | .uv_rect = .{ uv.u0, uv.v0, uv.u1, uv.v1 }, | 219 | cell_w, |
| 221 | .fg = colors.fg, | 220 | cell_h, |
| 222 | .bg = colors.bg, | 221 | glyph_uv, |
| 223 | }); | 222 | bg_uv, |
| 223 | colors, | ||
| 224 | default_bg, | ||
| 225 | ); | ||
| 224 | } | 226 | } |
| 225 | } | 227 | } |
| 226 | 228 | ||
| @@ -277,6 +279,43 @@ fn gridSizeForWindow(window_w: u32, window_h: u32, cell_w: u32, cell_h: u32) Gri | |||
| 277 | }; | 279 | }; |
| 278 | } | 280 | } |
| 279 | 281 | ||
| 282 | fn appendCellInstances( | ||
| 283 | alloc: std.mem.Allocator, | ||
| 284 | instances: *std.ArrayListUnmanaged(renderer.Instance), | ||
| 285 | row_idx: u32, | ||
| 286 | col_idx: u32, | ||
| 287 | cell_w: u32, | ||
| 288 | cell_h: u32, | ||
| 289 | glyph_uv: ?font.GlyphUV, | ||
| 290 | bg_uv: font.GlyphUV, | ||
| 291 | colors: vt.CellColors, | ||
| 292 | default_bg: [4]f32, | ||
| 293 | ) !void { | ||
| 294 | if (!std.meta.eql(colors.bg, default_bg)) { | ||
| 295 | try instances.append(alloc, .{ | ||
| 296 | .cell_pos = .{ @floatFromInt(col_idx), @floatFromInt(row_idx) }, | ||
| 297 | .glyph_size = .{ @floatFromInt(cell_w), @floatFromInt(cell_h) }, | ||
| 298 | .glyph_bearing = .{ 0, 0 }, | ||
| 299 | .uv_rect = .{ bg_uv.u0, bg_uv.v0, bg_uv.u1, bg_uv.v1 }, | ||
| 300 | .fg = colors.bg, | ||
| 301 | .bg = colors.bg, | ||
| 302 | }); | ||
| 303 | } | ||
| 304 | |||
| 305 | const uv = glyph_uv orelse return; | ||
| 306 | try instances.append(alloc, .{ | ||
| 307 | .cell_pos = .{ @floatFromInt(col_idx), @floatFromInt(row_idx) }, | ||
| 308 | .glyph_size = .{ @floatFromInt(uv.width), @floatFromInt(uv.height) }, | ||
| 309 | .glyph_bearing = .{ | ||
| 310 | @floatFromInt(uv.bearing_x), | ||
| 311 | @as(f32, @floatFromInt(cell_h)) - @as(f32, @floatFromInt(uv.bearing_y)), | ||
| 312 | }, | ||
| 313 | .uv_rect = .{ uv.u0, uv.v0, uv.u1, uv.v1 }, | ||
| 314 | .fg = colors.fg, | ||
| 315 | .bg = colors.bg, | ||
| 316 | }); | ||
| 317 | } | ||
| 318 | |||
| 280 | fn encodeKeyboardEvent( | 319 | fn encodeKeyboardEvent( |
| 281 | term: *const vt.Terminal, | 320 | term: *const vt.Terminal, |
| 282 | ev: wayland_client.KeyboardEvent, | 321 | ev: wayland_client.KeyboardEvent, |
| @@ -441,6 +480,94 @@ test "gridSizeForWindow clamps to at least one cell" { | |||
| 441 | try std.testing.expectEqual(@as(u16, 1), grid.rows); | 480 | try std.testing.expectEqual(@as(u16, 1), grid.rows); |
| 442 | } | 481 | } |
| 443 | 482 | ||
| 483 | test "appendCellInstances emits a background quad for colored space" { | ||
| 484 | var instances: std.ArrayListUnmanaged(renderer.Instance) = .empty; | ||
| 485 | defer instances.deinit(std.testing.allocator); | ||
| 486 | |||
| 487 | const bg_uv: font.GlyphUV = .{ | ||
| 488 | .u0 = 0.0, | ||
| 489 | .v0 = 0.0, | ||
| 490 | .u1 = 0.1, | ||
| 491 | .v1 = 0.1, | ||
| 492 | .width = 1, | ||
| 493 | .height = 1, | ||
| 494 | .bearing_x = 0, | ||
| 495 | .bearing_y = 0, | ||
| 496 | .advance_x = 1, | ||
| 497 | }; | ||
| 498 | |||
| 499 | try appendCellInstances( | ||
| 500 | std.testing.allocator, | ||
| 501 | &instances, | ||
| 502 | 2, | ||
| 503 | 3, | ||
| 504 | 8, | ||
| 505 | 16, | ||
| 506 | null, | ||
| 507 | bg_uv, | ||
| 508 | .{ | ||
| 509 | .fg = .{ 1.0, 1.0, 1.0, 1.0 }, | ||
| 510 | .bg = .{ 0.2, 0.3, 0.4, 1.0 }, | ||
| 511 | }, | ||
| 512 | .{ 0.0, 0.0, 0.0, 1.0 }, | ||
| 513 | ); | ||
| 514 | |||
| 515 | try std.testing.expectEqual(@as(usize, 1), instances.items.len); | ||
| 516 | try std.testing.expectEqualDeep([4]f32{ 0.2, 0.3, 0.4, 1.0 }, instances.items[0].fg); | ||
| 517 | try std.testing.expectEqualDeep([4]f32{ 0.2, 0.3, 0.4, 1.0 }, instances.items[0].bg); | ||
| 518 | try std.testing.expectEqualDeep([2]f32{ 8.0, 16.0 }, instances.items[0].glyph_size); | ||
| 519 | } | ||
| 520 | |||
| 521 | test "appendCellInstances emits background before glyph" { | ||
| 522 | var instances: std.ArrayListUnmanaged(renderer.Instance) = .empty; | ||
| 523 | defer instances.deinit(std.testing.allocator); | ||
| 524 | |||
| 525 | const bg_uv: font.GlyphUV = .{ | ||
| 526 | .u0 = 0.0, | ||
| 527 | .v0 = 0.0, | ||
| 528 | .u1 = 0.1, | ||
| 529 | .v1 = 0.1, | ||
| 530 | .width = 1, | ||
| 531 | .height = 1, | ||
| 532 | .bearing_x = 0, | ||
| 533 | .bearing_y = 0, | ||
| 534 | .advance_x = 1, | ||
| 535 | }; | ||
| 536 | const glyph_uv: font.GlyphUV = .{ | ||
| 537 | .u0 = 0.2, | ||
| 538 | .v0 = 0.3, | ||
| 539 | .u1 = 0.4, | ||
| 540 | .v1 = 0.5, | ||
| 541 | .width = 7, | ||
| 542 | .height = 11, | ||
| 543 | .bearing_x = 1, | ||
| 544 | .bearing_y = 13, | ||
| 545 | .advance_x = 8, | ||
| 546 | }; | ||
| 547 | |||
| 548 | try appendCellInstances( | ||
| 549 | std.testing.allocator, | ||
| 550 | &instances, | ||
| 551 | 0, | ||
| 552 | 1, | ||
| 553 | 8, | ||
| 554 | 16, | ||
| 555 | glyph_uv, | ||
| 556 | bg_uv, | ||
| 557 | .{ | ||
| 558 | .fg = .{ 0.9, 0.8, 0.7, 1.0 }, | ||
| 559 | .bg = .{ 0.1, 0.2, 0.3, 1.0 }, | ||
| 560 | }, | ||
| 561 | .{ 0.0, 0.0, 0.0, 1.0 }, | ||
| 562 | ); | ||
| 563 | |||
| 564 | try std.testing.expectEqual(@as(usize, 2), instances.items.len); | ||
| 565 | try std.testing.expectEqualDeep([2]f32{ 8.0, 16.0 }, instances.items[0].glyph_size); | ||
| 566 | try std.testing.expectEqualDeep([2]f32{ 7.0, 11.0 }, instances.items[1].glyph_size); | ||
| 567 | try std.testing.expectEqualDeep([4]f32{ 0.1, 0.2, 0.3, 1.0 }, instances.items[0].fg); | ||
| 568 | try std.testing.expectEqualDeep([4]f32{ 0.9, 0.8, 0.7, 1.0 }, instances.items[1].fg); | ||
| 569 | } | ||
| 570 | |||
| 444 | fn runRenderSmokeTest(alloc: std.mem.Allocator) !void { | 571 | fn runRenderSmokeTest(alloc: std.mem.Allocator) !void { |
| 445 | var conn = try wayland_client.Connection.init(); | 572 | var conn = try wayland_client.Connection.init(); |
| 446 | defer conn.deinit(); | 573 | defer conn.deinit(); |
src/vt.zig
| Old | New | ||
|---|---|---|---|
| @@ -158,6 +158,10 @@ pub const Terminal = struct { | |||
| 158 | try self.render_state.update(self.alloc, &self.inner); | 158 | try self.render_state.update(self.alloc, &self.inner); |
| 159 | } | 159 | } |
| 160 | 160 | ||
| 161 | pub fn backgroundColor(self: *const Terminal) [4]f32 { | ||
| 162 | return rgbToFloat4(self.render_state.colors.background); | ||
| 163 | } | ||
| 164 | |||
| 161 | /// Number of columns in the terminal grid. | 165 | /// Number of columns in the terminal grid. |
| 162 | pub fn cols(self: *const Terminal) u16 { | 166 | pub fn cols(self: *const Terminal) u16 { |
| 163 | return @intCast(self.render_state.cols); | 167 | return @intCast(self.render_state.cols); |