c1de8b6f
renderer: gate fence re-signal on pre-submit errors only
a73x 2026-04-18 15:16
Code review of the prior commit caught that the errdefer re-signal in drawClear, drawCells, and renderToOffscreen was scoped too widely. It remained active through the post-submit operations (queuePresentKHR for draw paths, waitFenceBounded for capture path), so a failure or timeout *after* a successful queueSubmit would re-signal a fence the GPU was about to signal naturally — racing the driver and corrupting fence state. Add a submit_done sentinel and gate the errdefer on it. Once queueSubmit succeeds, submit_done flips true and the errdefer becomes a no-op for any subsequent error. uploadAtlasRegion already ends immediately after queueSubmit, so its errdefer needed no change. 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 b30d897..5c15fc4 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -1292,7 +1292,10 @@ pub const Context = struct { const image_index = try vk_sync.acquireImageBounded(self.vkd, self.device, self.swapchain, self.image_available); try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence)); errdefer self.resignalFence(self.in_flight_fence); // Re-signal only on errors before queueSubmit; once submit succeeds the // GPU will signal the fence on completion and a manual signal would race. var submit_done = false; errdefer if (!submit_done) self.resignalFence(self.in_flight_fence); // Record command buffer try self.vkd.resetCommandBuffer(self.command_buffer, .{}); @@ -1331,6 +1334,7 @@ pub const Context = struct { .signal_semaphore_count = 1, .p_signal_semaphores = @ptrCast(&self.render_finished), }), self.in_flight_fence); submit_done = true; // Present const present_result = self.vkd.queuePresentKHR(self.present_queue, &vk.PresentInfoKHR{ @@ -1725,7 +1729,10 @@ pub const Context = struct { } try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence)); errdefer self.resignalFence(self.in_flight_fence); // Re-signal only on errors before queueSubmit; once submit succeeds the // GPU will signal the fence on completion and a manual signal would race. var submit_done = false; errdefer if (!submit_done) self.resignalFence(self.in_flight_fence); // Record command buffer try self.vkd.resetCommandBuffer(self.command_buffer, .{}); @@ -1774,6 +1781,7 @@ pub const Context = struct { .signal_semaphore_count = 1, .p_signal_semaphores = @ptrCast(&self.render_finished), }), self.in_flight_fence); submit_done = true; if (timing_out) |t| { t.submit_us = readTimer(&timer); timer.reset(); @@ -1818,7 +1826,12 @@ pub const Context = struct { // 2. Reset + begin capture command buffer try vk_sync.waitFenceBounded(self.vkd, self.device, self.capture_fence); try self.vkd.resetFences(self.device, 1, @ptrCast(&self.capture_fence)); errdefer self.resignalFence(self.capture_fence); // Re-signal only on errors before queueSubmit; once submit succeeds the // GPU will signal the fence on completion and a manual signal would race // (in particular, the post-submit waitFenceBounded below could time out // on a driver flake while the GPU work is still legitimately in flight). var submit_done = false; errdefer if (!submit_done) self.resignalFence(self.capture_fence); try self.vkd.resetCommandBuffer(self.capture_cmd, .{}); try self.vkd.beginCommandBuffer(self.capture_cmd, &vk.CommandBufferBeginInfo{ @@ -1934,6 +1947,7 @@ pub const Context = struct { .command_buffer_count = 1, .p_command_buffers = @ptrCast(&self.capture_cmd), }), self.capture_fence); submit_done = true; try vk_sync.waitFenceBounded(self.vkd, self.device, self.capture_fence); }