a73x

10cbcfdb

Handle out-of-date swapchains during draw

a73x   2026-04-08 15:33


diff --git a/src/main.zig b/src/main.zig
index 4375d32..c347b40 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -310,11 +310,18 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
            try ctx.uploadInstances(instances.items);
        }

        try ctx.drawCells(
        ctx.drawCells(
            @intCast(instances.items.len),
            .{ @floatFromInt(cell_w), @floatFromInt(cell_h) },
            default_bg,
        );
        ) catch |err| switch (err) {
            error.OutOfDateKHR => {
                _ = try ctx.vkd.deviceWaitIdle(ctx.device);
                try ctx.recreateSwapchain(window.width, window.height);
                continue;
            },
            else => return err,
        };
    }

    _ = try ctx.vkd.deviceWaitIdle(ctx.device);
@@ -509,7 +516,14 @@ fn runDrawSmokeTest(alloc: std.mem.Allocator) !void {
            _ = conn.display.readEvents();
        }
        _ = conn.display.dispatchPending();
        try ctx.drawCells(1, .{ cell_w, cell_h }, .{ 0.0, 0.0, 0.0, 1.0 });
        ctx.drawCells(1, .{ cell_w, cell_h }, .{ 0.0, 0.0, 0.0, 1.0 }) catch |err| switch (err) {
            error.OutOfDateKHR => {
                _ = try ctx.vkd.deviceWaitIdle(ctx.device);
                try ctx.recreateSwapchain(window.width, window.height);
                continue;
            },
            else => return err,
        };
        _ = conn.display.flush();
        std.Thread.sleep(16 * std.time.ns_per_ms);
    }
diff --git a/src/renderer.zig b/src/renderer.zig
index 682b8be..21603db 100644
--- a/src/renderer.zig
+++ b/src/renderer.zig
@@ -313,6 +313,10 @@ fn nextInstanceCapacity(current: u32, needed: u32) u32 {
    return capacity;
}

fn swapchainNeedsRebuild(result: vk.Result) bool {
    return result == .suboptimal_khr;
}

pub const Context = struct {
    alloc: std.mem.Allocator,
    vkb: vk.BaseWrapper,
@@ -998,13 +1002,17 @@ pub const Context = struct {
        try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence));

        // Acquire next image
        const acquire = try self.vkd.acquireNextImageKHR(
        const acquire = self.vkd.acquireNextImageKHR(
            self.device,
            self.swapchain,
            std.math.maxInt(u64),
            self.image_available,
            .null_handle,
        );
        ) catch |err| switch (err) {
            error.OutOfDateKHR => return error.OutOfDateKHR,
            else => return err,
        };
        if (swapchainNeedsRebuild(acquire.result)) return error.OutOfDateKHR;
        const image_index = acquire.image_index;

        // Record command buffer
@@ -1046,13 +1054,17 @@ pub const Context = struct {
        }), self.in_flight_fence);

        // Present
        _ = try self.vkd.queuePresentKHR(self.present_queue, &vk.PresentInfoKHR{
        const present_result = self.vkd.queuePresentKHR(self.present_queue, &vk.PresentInfoKHR{
            .wait_semaphore_count = 1,
            .p_wait_semaphores = @ptrCast(&self.render_finished),
            .swapchain_count = 1,
            .p_swapchains = @ptrCast(&self.swapchain),
            .p_image_indices = @ptrCast(&image_index),
        });
        }) catch |err| switch (err) {
            error.OutOfDateKHR => return error.OutOfDateKHR,
            else => return err,
        };
        if (swapchainNeedsRebuild(present_result)) return error.OutOfDateKHR;
    }

    /// Upload CPU R8 pixels into the GPU atlas image.
@@ -1193,13 +1205,17 @@ pub const Context = struct {
        try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence));

        // Acquire next image
        const acquire = try self.vkd.acquireNextImageKHR(
        const acquire = self.vkd.acquireNextImageKHR(
            self.device,
            self.swapchain,
            std.math.maxInt(u64),
            self.image_available,
            .null_handle,
        );
        ) catch |err| switch (err) {
            error.OutOfDateKHR => return error.OutOfDateKHR,
            else => return err,
        };
        if (swapchainNeedsRebuild(acquire.result)) return error.OutOfDateKHR;
        const image_index = acquire.image_index;

        // Record command buffer
@@ -1289,13 +1305,17 @@ pub const Context = struct {
        }), self.in_flight_fence);

        // Present
        _ = try self.vkd.queuePresentKHR(self.present_queue, &vk.PresentInfoKHR{
        const present_result = self.vkd.queuePresentKHR(self.present_queue, &vk.PresentInfoKHR{
            .wait_semaphore_count = 1,
            .p_wait_semaphores = @ptrCast(&self.render_finished),
            .swapchain_count = 1,
            .p_swapchains = @ptrCast(&self.swapchain),
            .p_image_indices = @ptrCast(&image_index),
        });
        }) catch |err| switch (err) {
            error.OutOfDateKHR => return error.OutOfDateKHR,
            else => return err,
        };
        if (swapchainNeedsRebuild(present_result)) return error.OutOfDateKHR;
    }
};

@@ -1320,3 +1340,8 @@ test "nextInstanceCapacity grows geometrically" {
    try std.testing.expectEqual(@as(u32, 32_000), nextInstanceCapacity(16_000, 16_001));
    try std.testing.expectEqual(@as(u32, 4), nextInstanceCapacity(1, 3));
}

test "swapchainNeedsRebuild flags suboptimal result" {
    try std.testing.expect(swapchainNeedsRebuild(.suboptimal_khr));
    try std.testing.expect(!swapchainNeedsRebuild(.success));
}