a73x

0615931a

Extract row instance rebuild logic

a73x   2026-04-08 19:09

Commit message
Extract row instance rebuild logic

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 }