66ece494
renderer: reorder resetFences after acquire, add errdefer re-signal
a73x 2026-04-18 12:35
Preparatory refactor for bounded acquire timeouts. When acquireNextImageKHR gains a finite timeout (next commit), the existing ordering (reset → acquire) would leave in_flight_fence unsignaled with no submit pending on a timeout, deadlocking future waits. Reorder to (acquire → reset) and add a private resignalFence helper that the errdefer path uses to cover the tiny post-reset / pre-submit failure window. Part of issue ab6c92f0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
diff --git a/src/renderer.zig b/src/renderer.zig index 7ee6e29..b6fe31d 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -1267,15 +1267,31 @@ pub const Context = struct { self.instance_capacity = new_capacity; } /// Submit an empty command batch that signals `fence`. Used as an /// errdefer recovery path when acquire has succeeded, resetFences has /// run, but we failed before queueSubmit — we need to put the fence /// back in the signaled state so the next frame's wait succeeds. fn resignalFence(self: *Context, fence: vk.Fence) void { const submit_info = vk.SubmitInfo{}; _ = self.vkd.queueSubmit( self.graphics_queue, 1, @ptrCast(&submit_info), fence, ) catch |err| { std.log.warn("resignalFence: {s}", .{@errorName(err)}); }; } /// Record a command buffer that begins the render pass with the given clear color and presents. /// Does not bind the pipeline or draw — just clear + present. /// Blocks until the previous frame's fence signals. pub fn drawClear(self: *Context, clear_color: [4]f32) !void { // Wait for previous frame to finish _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64)); try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence)); // Acquire next image // Acquire next image BEFORE reset, so an acquire failure leaves the // fence in a safe state (signaled from the prior frame). const acquire = self.vkd.acquireNextImageKHR( self.device, self.swapchain, @@ -1289,6 +1305,9 @@ pub const Context = struct { if (swapchainNeedsRebuild(acquire.result)) return error.OutOfDateKHR; const image_index = acquire.image_index; try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence)); errdefer self.resignalFence(self.in_flight_fence); // Record command buffer try self.vkd.resetCommandBuffer(self.command_buffer, .{}); try self.vkd.beginCommandBuffer(self.command_buffer, &vk.CommandBufferBeginInfo{ @@ -1708,13 +1727,13 @@ pub const Context = struct { // Wait for previous frame to finish _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64)); try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence)); if (timing_out) |t| { t.wait_fences_us = readTimer(&timer); timer.reset(); } // Acquire next image // Acquire next image BEFORE reset, so an acquire failure leaves the // fence in a safe state (signaled from the prior frame). const acquire = self.vkd.acquireNextImageKHR( self.device, self.swapchain, @@ -1732,6 +1751,9 @@ pub const Context = struct { timer.reset(); } try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence)); errdefer self.resignalFence(self.in_flight_fence); // Record command buffer try self.vkd.resetCommandBuffer(self.command_buffer, .{}); try self.vkd.beginCommandBuffer(self.command_buffer, &vk.CommandBufferBeginInfo{