a73x

e4c26d38

Handle window resize

a73x   2026-04-08 11:40

Commit message
Handle window resize

src/main.zig
Old New
@@ -9,6 +9,11 @@ const c = @cImport({
9 @cInclude("xkbcommon/xkbcommon-keysyms.h"); 9 @cInclude("xkbcommon/xkbcommon-keysyms.h");
10 }); 10 });
11 11
12 const GridSize = struct {
13 cols: u16,
14 rows: u16,
15 };
16
12 pub fn main() !void { 17 pub fn main() !void {
13 var gpa: std.heap.DebugAllocator(.{}) = .init; 18 var gpa: std.heap.DebugAllocator(.{}) = .init;
14 defer _ = gpa.deinit(); 19 defer _ = gpa.deinit();
@@ -53,8 +58,9 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
53 const cell_h = face.cellHeight(); 58 const cell_h = face.cellHeight();
54 59
55 // === grid size === 60 // === grid size ===
56 const cols: u16 = 80; 61 const initial_grid: GridSize = .{ .cols = 80, .rows = 24 };
57 const rows: u16 = 24; 62 var cols: u16 = initial_grid.cols;
63 var rows: u16 = initial_grid.rows;
58 const initial_w: u32 = @as(u32, cols) * cell_w; 64 const initial_w: u32 = @as(u32, cols) * cell_w;
59 const initial_h: u32 = @as(u32, rows) * cell_h; 65 const initial_h: u32 = @as(u32, rows) * cell_h;
60 66
@@ -124,6 +130,8 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
124 130
125 var read_buf: [8192]u8 = undefined; 131 var read_buf: [8192]u8 = undefined;
126 var key_buf: [32]u8 = undefined; 132 var key_buf: [32]u8 = undefined;
133 var last_window_w = window.width;
134 var last_window_h = window.height;
127 135
128 while (!window.should_close and p.isChildAlive()) { 136 while (!window.should_close and p.isChildAlive()) {
129 // Flush any pending wayland requests 137 // Flush any pending wayland requests
@@ -165,6 +173,23 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
165 } 173 }
166 keyboard.event_queue.clearRetainingCapacity(); 174 keyboard.event_queue.clearRetainingCapacity();
167 175
176 if (window.width != last_window_w or window.height != last_window_h) {
177 const new_grid = gridSizeForWindow(window.width, window.height, cell_w, cell_h);
178 if (new_grid.cols != cols or new_grid.rows != rows) {
179 _ = try ctx.vkd.deviceWaitIdle(ctx.device);
180 try ctx.recreateSwapchain(window.width, window.height);
181 try term.resize(new_grid.cols, new_grid.rows);
182 try p.resize(new_grid.cols, new_grid.rows);
183 cols = new_grid.cols;
184 rows = new_grid.rows;
185 } else {
186 _ = try ctx.vkd.deviceWaitIdle(ctx.device);
187 try ctx.recreateSwapchain(window.width, window.height);
188 }
189 last_window_w = window.width;
190 last_window_h = window.height;
191 }
192
168 // === render === 193 // === render ===
169 try term.snapshot(); 194 try term.snapshot();
170 195
@@ -216,6 +241,15 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
216 _ = try ctx.vkd.deviceWaitIdle(ctx.device); 241 _ = try ctx.vkd.deviceWaitIdle(ctx.device);
217 } 242 }
218 243
244 fn gridSizeForWindow(window_w: u32, window_h: u32, cell_w: u32, cell_h: u32) GridSize {
245 const cols = @max(@as(u32, 1), if (cell_w == 0) 1 else window_w / cell_w);
246 const rows = @max(@as(u32, 1), if (cell_h == 0) 1 else window_h / cell_h);
247 return .{
248 .cols = @intCast(cols),
249 .rows = @intCast(rows),
250 };
251 }
252
219 fn encodeKeyboardEvent( 253 fn encodeKeyboardEvent(
220 term: *const vt.Terminal, 254 term: *const vt.Terminal,
221 ev: wayland_client.KeyboardEvent, 255 ev: wayland_client.KeyboardEvent,
@@ -374,6 +408,12 @@ test "encodeKeyboardEvent encodes left arrow" {
374 try std.testing.expectEqualStrings("\x1b[D", encoded); 408 try std.testing.expectEqualStrings("\x1b[D", encoded);
375 } 409 }
376 410
411 test "gridSizeForWindow clamps to at least one cell" {
412 const grid = gridSizeForWindow(10, 5, 16, 20);
413 try std.testing.expectEqual(@as(u16, 1), grid.cols);
414 try std.testing.expectEqual(@as(u16, 1), grid.rows);
415 }
416
377 fn runRenderSmokeTest(alloc: std.mem.Allocator) !void { 417 fn runRenderSmokeTest(alloc: std.mem.Allocator) !void {
378 var conn = try wayland_client.Connection.init(); 418 var conn = try wayland_client.Connection.init();
379 defer conn.deinit(); 419 defer conn.deinit();
src/renderer.zig
Old New
@@ -198,6 +198,36 @@ fn createSwapchain(
198 }; 198 };
199 } 199 }
200 200
201 fn createFramebuffers(
202 alloc: std.mem.Allocator,
203 vkd: vk.DeviceWrapper,
204 device: vk.Device,
205 render_pass: vk.RenderPass,
206 extent: vk.Extent2D,
207 image_views: []vk.ImageView,
208 ) ![]vk.Framebuffer {
209 const framebuffers = try alloc.alloc(vk.Framebuffer, image_views.len);
210 errdefer alloc.free(framebuffers);
211 var created: usize = 0;
212 errdefer {
213 for (framebuffers[0..created]) |fb| vkd.destroyFramebuffer(device, fb, null);
214 }
215
216 for (image_views, 0..) |view, i| {
217 framebuffers[i] = try vkd.createFramebuffer(device, &vk.FramebufferCreateInfo{
218 .render_pass = render_pass,
219 .attachment_count = 1,
220 .p_attachments = @ptrCast(&view),
221 .width = extent.width,
222 .height = extent.height,
223 .layers = 1,
224 }, null);
225 created += 1;
226 }
227
228 return framebuffers;
229 }
230
201 /// Push constants layout matching cell.vert 231 /// Push constants layout matching cell.vert
202 pub const PushConstants = extern struct { 232 pub const PushConstants = extern struct {
203 viewport_size: [2]f32, 233 viewport_size: [2]f32,
@@ -454,23 +484,14 @@ pub const Context = struct {
454 errdefer vkd.destroyRenderPass(device, render_pass, null); 484 errdefer vkd.destroyRenderPass(device, render_pass, null);
455 485
456 // Create framebuffers (one per swapchain image view) 486 // Create framebuffers (one per swapchain image view)
457 const framebuffers = try alloc.alloc(vk.Framebuffer, sc.image_views.len); 487 const framebuffers = try createFramebuffers(
458 errdefer alloc.free(framebuffers); 488 alloc,
459 var fbs_created: usize = 0; 489 vkd,
460 errdefer { 490 device,
461 for (framebuffers[0..fbs_created]) |fb| vkd.destroyFramebuffer(device, fb, null); 491 render_pass,
462 } 492 sc.extent,
463 for (sc.image_views, 0..) |view, i| { 493 sc.image_views,
464 framebuffers[i] = try vkd.createFramebuffer(device, &vk.FramebufferCreateInfo{ 494 );
465 .render_pass = render_pass,
466 .attachment_count = 1,
467 .p_attachments = @ptrCast(&view),
468 .width = sc.extent.width,
469 .height = sc.extent.height,
470 .layers = 1,
471 }, null);
472 fbs_created += 1;
473 }
474 495
475 // Create descriptor set layout (single combined image sampler for glyph atlas) 496 // Create descriptor set layout (single combined image sampler for glyph atlas)
476 const dsl_binding = vk.DescriptorSetLayoutBinding{ 497 const dsl_binding = vk.DescriptorSetLayoutBinding{
@@ -856,22 +877,69 @@ pub const Context = struct {
856 self.vkd.destroyDescriptorPool(self.device, self.descriptor_pool, null); 877 self.vkd.destroyDescriptorPool(self.device, self.descriptor_pool, null);
857 self.vkd.destroyDescriptorSetLayout(self.device, self.descriptor_set_layout, null); 878 self.vkd.destroyDescriptorSetLayout(self.device, self.descriptor_set_layout, null);
858 879
859 // Framebuffers 880 self.destroySwapchainResources();
860 for (self.framebuffers) |fb| self.vkd.destroyFramebuffer(self.device, fb, null);
861 self.alloc.free(self.framebuffers);
862 881
863 // Render pass 882 // Render pass
864 self.vkd.destroyRenderPass(self.device, self.render_pass, null); 883 self.vkd.destroyRenderPass(self.device, self.render_pass, null);
865 884
866 // Swapchain 885 self.vkd.destroyDevice(self.device, null);
886 self.vki.destroySurfaceKHR(self.instance, self.surface, null);
887 self.vki.destroyInstance(self.instance, null);
888 }
889
890 fn destroySwapchainResources(self: *Context) void {
891 for (self.framebuffers) |fb| self.vkd.destroyFramebuffer(self.device, fb, null);
892 self.alloc.free(self.framebuffers);
893
867 for (self.swapchain_image_views) |view| self.vkd.destroyImageView(self.device, view, null); 894 for (self.swapchain_image_views) |view| self.vkd.destroyImageView(self.device, view, null);
868 self.alloc.free(self.swapchain_image_views); 895 self.alloc.free(self.swapchain_image_views);
869 self.alloc.free(self.swapchain_images); 896 self.alloc.free(self.swapchain_images);
870 self.vkd.destroySwapchainKHR(self.device, self.swapchain, null); 897 self.vkd.destroySwapchainKHR(self.device, self.swapchain, null);
898 }
871 899
872 self.vkd.destroyDevice(self.device, null); 900 pub fn recreateSwapchain(self: *Context, width: u32, height: u32) !void {
873 self.vki.destroySurfaceKHR(self.instance, self.surface, null); 901 self.destroySwapchainResources();
874 self.vki.destroyInstance(self.instance, null); 902
903 const sc = try createSwapchain(
904 self.alloc,
905 self.vki,
906 self.vkd,
907 .{
908 .physical = self.physical_device,
909 .graphics_queue_family = self.graphics_queue_family,
910 .present_queue_family = self.present_queue_family,
911 },
912 self.surface,
913 self.device,
914 width,
915 height,
916 );
917 errdefer {
918 for (sc.image_views) |view| self.vkd.destroyImageView(self.device, view, null);
919 self.alloc.free(sc.image_views);
920 self.alloc.free(sc.images);
921 self.vkd.destroySwapchainKHR(self.device, sc.swapchain, null);
922 }
923
924 const framebuffers = try createFramebuffers(
925 self.alloc,
926 self.vkd,
927 self.device,
928 self.render_pass,
929 sc.extent,
930 sc.image_views,
931 );
932 errdefer {
933 for (framebuffers) |fb| self.vkd.destroyFramebuffer(self.device, fb, null);
934 self.alloc.free(framebuffers);
935 }
936
937 self.swapchain = sc.swapchain;
938 self.swapchain_format = sc.format;
939 self.swapchain_extent = sc.extent;
940 self.swapchain_images = sc.images;
941 self.swapchain_image_views = sc.image_views;
942 self.framebuffers = framebuffers;
875 } 943 }
876 944
877 /// Record a command buffer that begins the render pass with the given clear color and presents. 945 /// Record a command buffer that begins the render pass with the given clear color and presents.