0615931a
Extract row instance rebuild logic
a73x 2026-04-08 19:09
Commit message
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -149,6 +149,8 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 149 | var instances: std.ArrayListUnmanaged(renderer.Instance) = .empty; | 149 | var instances: std.ArrayListUnmanaged(renderer.Instance) = .empty; |
| 150 | defer instances.deinit(alloc); | 150 | defer instances.deinit(alloc); |
| 151 | try instances.ensureTotalCapacity(alloc, @as(usize, cols) * rows); | 151 | try instances.ensureTotalCapacity(alloc, @as(usize, cols) * rows); |
| 152 | var row_cache = RowInstanceCache{}; | ||
| 153 | defer row_cache.deinit(alloc); | ||
| 152 | 154 | ||
| 153 | // === main loop === | 155 | // === main loop === |
| 154 | const wl_fd = conn.display.getFd(); | 156 | const wl_fd = conn.display.getFd(); |
| @@ -253,31 +255,21 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 253 | const term_rows = term.render_state.row_data.items(.cells); | 255 | const term_rows = term.render_state.row_data.items(.cells); |
| 254 | var row_idx: u32 = 0; | 256 | var row_idx: u32 = 0; |
| 255 | while (row_idx < term_rows.len) : (row_idx += 1) { | 257 | while (row_idx < term_rows.len) : (row_idx += 1) { |
| 256 | const row_cells = term_rows[row_idx]; | 258 | _ = try rebuildRowInstances( |
| 257 | const raw_cells = row_cells.items(.raw); | 259 | alloc, |
| 258 | var col_idx: u32 = 0; | 260 | &row_cache, |
| 259 | while (col_idx < raw_cells.len) : (col_idx += 1) { | 261 | row_idx, |
| 260 | const cp = raw_cells[col_idx].codepoint(); | 262 | term_rows[row_idx], |
| 261 | const colors = term.cellColors(row_cells.get(col_idx)); | 263 | term, |
| 262 | const glyph_uv = if (cp == 0 or cp == ' ') | 264 | &face, |
| 263 | null | 265 | &atlas, |
| 264 | else | 266 | cell_w, |
| 265 | atlas.getOrInsert(&face, @intCast(cp)) catch null; | 267 | cell_h, |
| 266 | 268 | baseline, | |
| 267 | try appendCellInstances( | 269 | default_bg, |
| 268 | alloc, | 270 | bg_uv, |
| 269 | &instances, | 271 | ); |
| 270 | row_idx, | 272 | try instances.appendSlice(alloc, row_cache.instances.items); |
| 271 | col_idx, | ||
| 272 | cell_w, | ||
| 273 | cell_h, | ||
| 274 | baseline, | ||
| 275 | glyph_uv, | ||
| 276 | bg_uv, | ||
| 277 | colors, | ||
| 278 | default_bg, | ||
| 279 | ); | ||
| 280 | } | ||
| 281 | } | 273 | } |
| 282 | 274 | ||
| 283 | if (term.render_state.cursor.visible) { | 275 | if (term.render_state.cursor.visible) { |
| @@ -719,6 +711,118 @@ test "repackRowCaches keeps cursor span explicit for empty and non-empty cursor | |||
| 719 | try std.testing.expectEqualDeep([2]f32{ 9.0, 10.0 }, packed_instances.items[2].cell_pos); | 711 | try std.testing.expectEqualDeep([2]f32{ 9.0, 10.0 }, packed_instances.items[2].cell_pos); |
| 720 | } | 712 | } |
| 721 | 713 | ||
| 714 | test "rebuildRowInstances emits expected instances for a colored glyph row" { | ||
| 715 | var term = try vt.Terminal.init(std.testing.allocator, .{ | ||
| 716 | .cols = 80, | ||
| 717 | .rows = 24, | ||
| 718 | }); | ||
| 719 | defer term.deinit(); | ||
| 720 | |||
| 721 | term.write("\x1b[31;44mA\x1b[0m"); | ||
| 722 | try term.snapshot(); | ||
| 723 | |||
| 724 | var lookup = try font.lookupMonospace(std.testing.allocator); | ||
| 725 | defer lookup.deinit(std.testing.allocator); | ||
| 726 | |||
| 727 | var face = try font.Face.init(std.testing.allocator, lookup.path, lookup.index, 16); | ||
| 728 | defer face.deinit(); | ||
| 729 | |||
| 730 | var atlas = try font.Atlas.init(std.testing.allocator, 256, 256); | ||
| 731 | defer atlas.deinit(); | ||
| 732 | |||
| 733 | var cache = RowInstanceCache{}; | ||
| 734 | defer cache.instances.deinit(std.testing.allocator); | ||
| 735 | |||
| 736 | const row_cells = term.render_state.row_data.get(0).cells; | ||
| 737 | const default_bg = term.backgroundColor(); | ||
| 738 | const rebuilt = try rebuildRowInstances( | ||
| 739 | std.testing.allocator, | ||
| 740 | &cache, | ||
| 741 | 0, | ||
| 742 | row_cells, | ||
| 743 | term, | ||
| 744 | &face, | ||
| 745 | &atlas, | ||
| 746 | face.cellWidth(), | ||
| 747 | face.cellHeight(), | ||
| 748 | face.baseline(), | ||
| 749 | default_bg, | ||
| 750 | atlas.cursorUV(), | ||
| 751 | ); | ||
| 752 | |||
| 753 | try std.testing.expect(rebuilt); | ||
| 754 | try std.testing.expectEqual(@as(usize, 2), cache.instances.items.len); | ||
| 755 | |||
| 756 | const colors = term.cellColors(row_cells.get(0)); | ||
| 757 | try std.testing.expectEqualDeep([2]f32{ 0.0, 0.0 }, cache.instances.items[0].cell_pos); | ||
| 758 | try std.testing.expectEqualDeep([2]f32{ @floatFromInt(face.cellWidth()), @floatFromInt(face.cellHeight()) }, cache.instances.items[0].glyph_size); | ||
| 759 | try std.testing.expectEqualDeep(colors.bg, cache.instances.items[0].fg); | ||
| 760 | try std.testing.expectEqualDeep(colors.bg, cache.instances.items[0].bg); | ||
| 761 | try std.testing.expectEqualDeep([2]f32{ 0.0, 0.0 }, cache.instances.items[1].cell_pos); | ||
| 762 | try std.testing.expectEqualDeep(colors.fg, cache.instances.items[1].fg); | ||
| 763 | try std.testing.expectEqualDeep(colors.bg, cache.instances.items[1].bg); | ||
| 764 | } | ||
| 765 | |||
| 766 | test "rebuildRowInstances replaces stale cached contents without layout dirtiness when count is unchanged" { | ||
| 767 | var term = try vt.Terminal.init(std.testing.allocator, .{ | ||
| 768 | .cols = 80, | ||
| 769 | .rows = 24, | ||
| 770 | }); | ||
| 771 | defer term.deinit(); | ||
| 772 | |||
| 773 | term.write("\x1b[31;44mA\x1b[0m"); | ||
| 774 | try term.snapshot(); | ||
| 775 | |||
| 776 | var lookup = try font.lookupMonospace(std.testing.allocator); | ||
| 777 | defer lookup.deinit(std.testing.allocator); | ||
| 778 | |||
| 779 | var face = try font.Face.init(std.testing.allocator, lookup.path, lookup.index, 16); | ||
| 780 | defer face.deinit(); | ||
| 781 | |||
| 782 | var atlas = try font.Atlas.init(std.testing.allocator, 256, 256); | ||
| 783 | defer atlas.deinit(); | ||
| 784 | |||
| 785 | var cache = RowInstanceCache{}; | ||
| 786 | defer cache.instances.deinit(std.testing.allocator); | ||
| 787 | try cache.instances.append(std.testing.allocator, .{ | ||
| 788 | .cell_pos = .{ 99.0, 99.0 }, | ||
| 789 | .glyph_size = .{ 1.0, 1.0 }, | ||
| 790 | .glyph_bearing = .{ 0.0, 0.0 }, | ||
| 791 | .uv_rect = .{ 0.0, 0.0, 0.0, 0.0 }, | ||
| 792 | .fg = .{ 0.0, 0.0, 0.0, 0.0 }, | ||
| 793 | .bg = .{ 0.0, 0.0, 0.0, 0.0 }, | ||
| 794 | }); | ||
| 795 | try cache.instances.append(std.testing.allocator, .{ | ||
| 796 | .cell_pos = .{ 98.0, 98.0 }, | ||
| 797 | .glyph_size = .{ 1.0, 1.0 }, | ||
| 798 | .glyph_bearing = .{ 0.0, 0.0 }, | ||
| 799 | .uv_rect = .{ 0.0, 0.0, 0.0, 0.0 }, | ||
| 800 | .fg = .{ 0.0, 0.0, 0.0, 0.0 }, | ||
| 801 | .bg = .{ 0.0, 0.0, 0.0, 0.0 }, | ||
| 802 | }); | ||
| 803 | |||
| 804 | const row_cells = term.render_state.row_data.get(0).cells; | ||
| 805 | const rebuilt = try rebuildRowInstances( | ||
| 806 | std.testing.allocator, | ||
| 807 | &cache, | ||
| 808 | 0, | ||
| 809 | row_cells, | ||
| 810 | term, | ||
| 811 | &face, | ||
| 812 | &atlas, | ||
| 813 | face.cellWidth(), | ||
| 814 | face.cellHeight(), | ||
| 815 | face.baseline(), | ||
| 816 | term.backgroundColor(), | ||
| 817 | atlas.cursorUV(), | ||
| 818 | ); | ||
| 819 | |||
| 820 | try std.testing.expect(!rebuilt); | ||
| 821 | try std.testing.expectEqual(@as(usize, 2), cache.instances.items.len); | ||
| 822 | try std.testing.expectEqualDeep([2]f32{ 0.0, 0.0 }, cache.instances.items[0].cell_pos); | ||
| 823 | try std.testing.expectEqualDeep([2]f32{ 0.0, 0.0 }, cache.instances.items[1].cell_pos); | ||
| 824 | } | ||
| 825 | |||
| 722 | test "RenderCache resizeRows preserves surviving row caches" { | 826 | test "RenderCache resizeRows preserves surviving row caches" { |
| 723 | var cache = RenderCache.empty; | 827 | var cache = RenderCache.empty; |
| 724 | defer cache.deinit(std.testing.allocator); | 828 | defer cache.deinit(std.testing.allocator); |
| @@ -939,6 +1043,51 @@ fn repackRowCaches( | |||
| 939 | }; | 1043 | }; |
| 940 | } | 1044 | } |
| 941 | 1045 | ||
| 1046 | fn rebuildRowInstances( | ||
| 1047 | alloc: std.mem.Allocator, | ||
| 1048 | cache: *RowInstanceCache, | ||
| 1049 | row_idx: u32, | ||
| 1050 | row_cells: anytype, | ||
| 1051 | term: *const vt.Terminal, | ||
| 1052 | face: *font.Face, | ||
| 1053 | atlas: *font.Atlas, | ||
| 1054 | cell_w: u32, | ||
| 1055 | cell_h: u32, | ||
| 1056 | baseline: u32, | ||
| 1057 | default_bg: [4]f32, | ||
| 1058 | bg_uv: font.GlyphUV, | ||
| 1059 | ) !bool { | ||
| 1060 | const old_len = cache.instances.items.len; | ||
| 1061 | cache.instances.clearRetainingCapacity(); | ||
| 1062 | |||
| 1063 | const raw_cells = row_cells.items(.raw); | ||
| 1064 | var col_idx: u32 = 0; | ||
| 1065 | while (col_idx < raw_cells.len) : (col_idx += 1) { | ||
| 1066 | const cp = raw_cells[col_idx].codepoint(); | ||
| 1067 | const colors = term.cellColors(row_cells.get(col_idx)); | ||
| 1068 | const glyph_uv = if (cp == 0 or cp == ' ') | ||
| 1069 | null | ||
| 1070 | else | ||
| 1071 | atlas.getOrInsert(face, @intCast(cp)) catch null; | ||
| 1072 | |||
| 1073 | try appendCellInstances( | ||
| 1074 | alloc, | ||
| 1075 | &cache.instances, | ||
| 1076 | row_idx, | ||
| 1077 | col_idx, | ||
| 1078 | cell_w, | ||
| 1079 | cell_h, | ||
| 1080 | baseline, | ||
| 1081 | glyph_uv, | ||
| 1082 | bg_uv, | ||
| 1083 | colors, | ||
| 1084 | default_bg, | ||
| 1085 | ); | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | return markLayoutDirtyOnLenChange(old_len, cache.instances.items.len); | ||
| 1089 | } | ||
| 1090 | |||
| 942 | fn markLayoutDirtyOnLenChange(old_len: usize, new_len: usize) bool { | 1091 | fn markLayoutDirtyOnLenChange(old_len: usize, new_len: usize) bool { |
| 943 | return old_len != new_len; | 1092 | return old_len != new_len; |
| 944 | } | 1093 | } |