a73x

66ece494

renderer: reorder resetFences after acquire, add errdefer re-signal

a73x   2026-04-18 12:35

Commit message
renderer: reorder resetFences after acquire, add errdefer re-signal

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>

src/renderer.zig
Old New
@@ -1267,15 +1267,31 @@ pub const Context = struct {
1267 self.instance_capacity = new_capacity; 1267 self.instance_capacity = new_capacity;
1268 } 1268 }
1269 1269
1270 /// Submit an empty command batch that signals `fence`. Used as an
1271 /// errdefer recovery path when acquire has succeeded, resetFences has
1272 /// run, but we failed before queueSubmit — we need to put the fence
1273 /// back in the signaled state so the next frame's wait succeeds.
1274 fn resignalFence(self: *Context, fence: vk.Fence) void {
1275 const submit_info = vk.SubmitInfo{};
1276 _ = self.vkd.queueSubmit(
1277 self.graphics_queue,
1278 1,
1279 @ptrCast(&submit_info),
1280 fence,
1281 ) catch |err| {
1282 std.log.warn("resignalFence: {s}", .{@errorName(err)});
1283 };
1284 }
1285
1270 /// Record a command buffer that begins the render pass with the given clear color and presents. 1286 /// Record a command buffer that begins the render pass with the given clear color and presents.
1271 /// Does not bind the pipeline or draw — just clear + present. 1287 /// Does not bind the pipeline or draw — just clear + present.
1272 /// Blocks until the previous frame's fence signals. 1288 /// Blocks until the previous frame's fence signals.
1273 pub fn drawClear(self: *Context, clear_color: [4]f32) !void { 1289 pub fn drawClear(self: *Context, clear_color: [4]f32) !void {
1274 // Wait for previous frame to finish 1290 // Wait for previous frame to finish
1275 _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64)); 1291 _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64));
1276 try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence));
1277 1292
1278 // Acquire next image 1293 // Acquire next image BEFORE reset, so an acquire failure leaves the
1294 // fence in a safe state (signaled from the prior frame).
1279 const acquire = self.vkd.acquireNextImageKHR( 1295 const acquire = self.vkd.acquireNextImageKHR(
1280 self.device, 1296 self.device,
1281 self.swapchain, 1297 self.swapchain,
@@ -1289,6 +1305,9 @@ pub const Context = struct {
1289 if (swapchainNeedsRebuild(acquire.result)) return error.OutOfDateKHR; 1305 if (swapchainNeedsRebuild(acquire.result)) return error.OutOfDateKHR;
1290 const image_index = acquire.image_index; 1306 const image_index = acquire.image_index;
1291 1307
1308 try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence));
1309 errdefer self.resignalFence(self.in_flight_fence);
1310
1292 // Record command buffer 1311 // Record command buffer
1293 try self.vkd.resetCommandBuffer(self.command_buffer, .{}); 1312 try self.vkd.resetCommandBuffer(self.command_buffer, .{});
1294 try self.vkd.beginCommandBuffer(self.command_buffer, &vk.CommandBufferBeginInfo{ 1313 try self.vkd.beginCommandBuffer(self.command_buffer, &vk.CommandBufferBeginInfo{
@@ -1708,13 +1727,13 @@ pub const Context = struct {
1708 1727
1709 // Wait for previous frame to finish 1728 // Wait for previous frame to finish
1710 _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64)); 1729 _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64));
1711 try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence));
1712 if (timing_out) |t| { 1730 if (timing_out) |t| {
1713 t.wait_fences_us = readTimer(&timer); 1731 t.wait_fences_us = readTimer(&timer);
1714 timer.reset(); 1732 timer.reset();
1715 } 1733 }
1716 1734
1717 // Acquire next image 1735 // Acquire next image BEFORE reset, so an acquire failure leaves the
1736 // fence in a safe state (signaled from the prior frame).
1718 const acquire = self.vkd.acquireNextImageKHR( 1737 const acquire = self.vkd.acquireNextImageKHR(
1719 self.device, 1738 self.device,
1720 self.swapchain, 1739 self.swapchain,
@@ -1732,6 +1751,9 @@ pub const Context = struct {
1732 timer.reset(); 1751 timer.reset();
1733 } 1752 }
1734 1753
1754 try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence));
1755 errdefer self.resignalFence(self.in_flight_fence);
1756
1735 // Record command buffer 1757 // Record command buffer
1736 try self.vkd.resetCommandBuffer(self.command_buffer, .{}); 1758 try self.vkd.resetCommandBuffer(self.command_buffer, .{});
1737 try self.vkd.beginCommandBuffer(self.command_buffer, &vk.CommandBufferBeginInfo{ 1759 try self.vkd.beginCommandBuffer(self.command_buffer, &vk.CommandBufferBeginInfo{