ce21fd10
Honor wl_output buffer scale in terminal render loop
a73x 2026-04-09 13:24
runTerminal now reacts to wl_surface.enter/leave scale changes by: - Rebuilding font.Face at px_size * buffer_scale via rebuildFaceForScale - Resetting the glyph atlas and marking the render cache + terminal fully dirty so all rows get re-rasterized against the fresh atlas - Calling wl_surface.set_buffer_scale() and recreating the Vulkan swapchain at buffer-pixel dimensions - Computing the terminal grid from surface-pixel cell sizes (cell_w / buffer_scale) so drag-between-monitors preserves cols/rows cell_w/cell_h/baseline are now mutable aliases for geom fields, kept in sync after each rebuildFaceForScale call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
diff --git a/src/main.zig b/src/main.zig index cc30caa..7baa943 100644 --- a/src/main.zig +++ b/src/main.zig @@ -102,9 +102,19 @@ fn runTerminal(alloc: std.mem.Allocator) !void { var face = try font.Face.init(alloc, font_lookup.path, font_lookup.index, font_size); defer face.deinit(); const cell_w = face.cellWidth(); const cell_h = face.cellHeight(); const baseline = face.baseline(); var geom: ScaledGeometry = .{ .buffer_scale = 1, .px_size = font_size, .cell_w_px = face.cellWidth(), .cell_h_px = face.cellHeight(), .baseline_px = face.baseline(), }; // Mutable aliases so the scale-change path inside the main loop can // refresh them after a rebuildFaceForScale call without renaming every // downstream reference. var cell_w = geom.cell_w_px; var cell_h = geom.cell_h_px; var baseline = geom.baseline_px; // === grid size === const initial_grid: GridSize = .{ .cols = 80, .rows = 24 }; @@ -196,6 +206,7 @@ fn runTerminal(alloc: std.mem.Allocator) !void { var key_buf: [32]u8 = undefined; var last_window_w = window.width; var last_window_h = window.height; var last_scale: i32 = geom.buffer_scale; var render_pending = true; while (!window.should_close and p.isChildAlive()) { @@ -252,11 +263,48 @@ fn runTerminal(alloc: std.mem.Allocator) !void { } keyboard.event_queue.clearRetainingCapacity(); const current_scale = window.bufferScale(); if (current_scale != last_scale) { _ = try ctx.vkd.deviceWaitIdle(ctx.device); geom = try rebuildFaceForScale( &face, &atlas, font_lookup.path, font_lookup.index, font_size, current_scale, ); cell_w = geom.cell_w_px; cell_h = geom.cell_h_px; baseline = geom.baseline_px; // Wipe all cached row instances + mark the terminal fully dirty so that // every glyph gets re-inserted into the freshly reset atlas next frame. render_cache.invalidateAfterResize(); term.render_state.dirty = .full; window.surface.setBufferScale(geom.buffer_scale); const buf_w = window.width * @as(u32, @intCast(geom.buffer_scale)); const buf_h = window.height * @as(u32, @intCast(geom.buffer_scale)); try ctx.recreateSwapchain(buf_w, buf_h); last_scale = current_scale; render_pending = true; } if (window.width != last_window_w or window.height != last_window_h) { const new_grid = gridSizeForWindow(window.width, window.height, cell_w, cell_h); // Grid is sized in surface coordinates. cell_w/cell_h are in buffer // pixels, so divide by buffer_scale to get surface-pixel cell dims. const surf_cell_w = cell_w / @as(u32, @intCast(geom.buffer_scale)); const surf_cell_h = cell_h / @as(u32, @intCast(geom.buffer_scale)); const new_grid = gridSizeForWindow(window.width, window.height, surf_cell_w, surf_cell_h); const buf_w = window.width * @as(u32, @intCast(geom.buffer_scale)); const buf_h = window.height * @as(u32, @intCast(geom.buffer_scale)); if (new_grid.cols != cols or new_grid.rows != rows) { _ = try ctx.vkd.deviceWaitIdle(ctx.device); try ctx.recreateSwapchain(window.width, window.height); try ctx.recreateSwapchain(buf_w, buf_h); try term.resize(new_grid.cols, new_grid.rows); try p.resize(new_grid.cols, new_grid.rows); cols = new_grid.cols; @@ -269,7 +317,7 @@ fn runTerminal(alloc: std.mem.Allocator) !void { }); } else { _ = try ctx.vkd.deviceWaitIdle(ctx.device); try ctx.recreateSwapchain(window.width, window.height); try ctx.recreateSwapchain(buf_w, buf_h); } last_window_w = window.width; last_window_h = window.height; @@ -445,7 +493,9 @@ fn runTerminal(alloc: std.mem.Allocator) !void { ) catch |err| switch (err) { error.OutOfDateKHR => { _ = try ctx.vkd.deviceWaitIdle(ctx.device); try ctx.recreateSwapchain(window.width, window.height); const buf_w = window.width * @as(u32, @intCast(geom.buffer_scale)); const buf_h = window.height * @as(u32, @intCast(geom.buffer_scale)); try ctx.recreateSwapchain(buf_w, buf_h); render_pending = true; continue; },