e4c26d38
Handle window resize
a73x 2026-04-08 11:40
diff --git a/src/main.zig b/src/main.zig index 0b97c28..2096ccc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,6 +9,11 @@ const c = @cImport({ @cInclude("xkbcommon/xkbcommon-keysyms.h"); }); const GridSize = struct { cols: u16, rows: u16, }; pub fn main() !void { var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); @@ -53,8 +58,9 @@ fn runTerminal(alloc: std.mem.Allocator) !void { const cell_h = face.cellHeight(); // === grid size === const cols: u16 = 80; const rows: u16 = 24; const initial_grid: GridSize = .{ .cols = 80, .rows = 24 }; var cols: u16 = initial_grid.cols; var rows: u16 = initial_grid.rows; const initial_w: u32 = @as(u32, cols) * cell_w; const initial_h: u32 = @as(u32, rows) * cell_h; @@ -124,6 +130,8 @@ fn runTerminal(alloc: std.mem.Allocator) !void { var read_buf: [8192]u8 = undefined; var key_buf: [32]u8 = undefined; var last_window_w = window.width; var last_window_h = window.height; while (!window.should_close and p.isChildAlive()) { // Flush any pending wayland requests @@ -165,6 +173,23 @@ fn runTerminal(alloc: std.mem.Allocator) !void { } keyboard.event_queue.clearRetainingCapacity(); if (window.width != last_window_w or window.height != last_window_h) { const new_grid = gridSizeForWindow(window.width, window.height, cell_w, cell_h); if (new_grid.cols != cols or new_grid.rows != rows) { _ = try ctx.vkd.deviceWaitIdle(ctx.device); try ctx.recreateSwapchain(window.width, window.height); try term.resize(new_grid.cols, new_grid.rows); try p.resize(new_grid.cols, new_grid.rows); cols = new_grid.cols; rows = new_grid.rows; } else { _ = try ctx.vkd.deviceWaitIdle(ctx.device); try ctx.recreateSwapchain(window.width, window.height); } last_window_w = window.width; last_window_h = window.height; } // === render === try term.snapshot(); @@ -216,6 +241,15 @@ fn runTerminal(alloc: std.mem.Allocator) !void { _ = try ctx.vkd.deviceWaitIdle(ctx.device); } fn gridSizeForWindow(window_w: u32, window_h: u32, cell_w: u32, cell_h: u32) GridSize { const cols = @max(@as(u32, 1), if (cell_w == 0) 1 else window_w / cell_w); const rows = @max(@as(u32, 1), if (cell_h == 0) 1 else window_h / cell_h); return .{ .cols = @intCast(cols), .rows = @intCast(rows), }; } fn encodeKeyboardEvent( term: *const vt.Terminal, ev: wayland_client.KeyboardEvent, @@ -374,6 +408,12 @@ test "encodeKeyboardEvent encodes left arrow" { try std.testing.expectEqualStrings("\x1b[D", encoded); } test "gridSizeForWindow clamps to at least one cell" { const grid = gridSizeForWindow(10, 5, 16, 20); try std.testing.expectEqual(@as(u16, 1), grid.cols); try std.testing.expectEqual(@as(u16, 1), grid.rows); } fn runRenderSmokeTest(alloc: std.mem.Allocator) !void { var conn = try wayland_client.Connection.init(); defer conn.deinit(); diff --git a/src/renderer.zig b/src/renderer.zig index fc2def4..12003ef 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -198,6 +198,36 @@ fn createSwapchain( }; } fn createFramebuffers( alloc: std.mem.Allocator, vkd: vk.DeviceWrapper, device: vk.Device, render_pass: vk.RenderPass, extent: vk.Extent2D, image_views: []vk.ImageView, ) ![]vk.Framebuffer { const framebuffers = try alloc.alloc(vk.Framebuffer, image_views.len); errdefer alloc.free(framebuffers); var created: usize = 0; errdefer { for (framebuffers[0..created]) |fb| vkd.destroyFramebuffer(device, fb, null); } for (image_views, 0..) |view, i| { framebuffers[i] = try vkd.createFramebuffer(device, &vk.FramebufferCreateInfo{ .render_pass = render_pass, .attachment_count = 1, .p_attachments = @ptrCast(&view), .width = extent.width, .height = extent.height, .layers = 1, }, null); created += 1; } return framebuffers; } /// Push constants layout matching cell.vert pub const PushConstants = extern struct { viewport_size: [2]f32, @@ -454,23 +484,14 @@ pub const Context = struct { errdefer vkd.destroyRenderPass(device, render_pass, null); // Create framebuffers (one per swapchain image view) const framebuffers = try alloc.alloc(vk.Framebuffer, sc.image_views.len); errdefer alloc.free(framebuffers); var fbs_created: usize = 0; errdefer { for (framebuffers[0..fbs_created]) |fb| vkd.destroyFramebuffer(device, fb, null); } for (sc.image_views, 0..) |view, i| { framebuffers[i] = try vkd.createFramebuffer(device, &vk.FramebufferCreateInfo{ .render_pass = render_pass, .attachment_count = 1, .p_attachments = @ptrCast(&view), .width = sc.extent.width, .height = sc.extent.height, .layers = 1, }, null); fbs_created += 1; } const framebuffers = try createFramebuffers( alloc, vkd, device, render_pass, sc.extent, sc.image_views, ); // Create descriptor set layout (single combined image sampler for glyph atlas) const dsl_binding = vk.DescriptorSetLayoutBinding{ @@ -856,22 +877,69 @@ pub const Context = struct { self.vkd.destroyDescriptorPool(self.device, self.descriptor_pool, null); self.vkd.destroyDescriptorSetLayout(self.device, self.descriptor_set_layout, null); // Framebuffers for (self.framebuffers) |fb| self.vkd.destroyFramebuffer(self.device, fb, null); self.alloc.free(self.framebuffers); self.destroySwapchainResources(); // Render pass self.vkd.destroyRenderPass(self.device, self.render_pass, null); // Swapchain self.vkd.destroyDevice(self.device, null); self.vki.destroySurfaceKHR(self.instance, self.surface, null); self.vki.destroyInstance(self.instance, null); } fn destroySwapchainResources(self: *Context) void { for (self.framebuffers) |fb| self.vkd.destroyFramebuffer(self.device, fb, null); self.alloc.free(self.framebuffers); for (self.swapchain_image_views) |view| self.vkd.destroyImageView(self.device, view, null); self.alloc.free(self.swapchain_image_views); self.alloc.free(self.swapchain_images); self.vkd.destroySwapchainKHR(self.device, self.swapchain, null); } self.vkd.destroyDevice(self.device, null); self.vki.destroySurfaceKHR(self.instance, self.surface, null); self.vki.destroyInstance(self.instance, null); pub fn recreateSwapchain(self: *Context, width: u32, height: u32) !void { self.destroySwapchainResources(); const sc = try createSwapchain( self.alloc, self.vki, self.vkd, .{ .physical = self.physical_device, .graphics_queue_family = self.graphics_queue_family, .present_queue_family = self.present_queue_family, }, self.surface, self.device, width, height, ); errdefer { for (sc.image_views) |view| self.vkd.destroyImageView(self.device, view, null); self.alloc.free(sc.image_views); self.alloc.free(sc.images); self.vkd.destroySwapchainKHR(self.device, sc.swapchain, null); } const framebuffers = try createFramebuffers( self.alloc, self.vkd, self.device, self.render_pass, sc.extent, sc.image_views, ); errdefer { for (framebuffers) |fb| self.vkd.destroyFramebuffer(self.device, fb, null); self.alloc.free(framebuffers); } self.swapchain = sc.swapchain; self.swapchain_format = sc.format; self.swapchain_extent = sc.extent; self.swapchain_images = sc.images; self.swapchain_image_views = sc.image_views; self.framebuffers = framebuffers; } /// Record a command buffer that begins the render pass with the given clear color and presents.