[perf][HIGH] Frame-in-flight: per-frame fences + per-frame semaphores (remove per-frame vkQueueWaitIdle) #40
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Catcrafts/Crafter.Graphics#40
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Subsystem: Window / swapchain / frame loop
Location:
implementations/Crafter.Graphics-Window.cpp:417-431, 675-682, 805-817, 830Impact: HIGH · Effort: Medium
Problem
The renderer is effectively single-buffered despite allocating triple-buffered infrastructure (
numFrames=3command buffers, 3 swapchain images, per-image descriptor heaps). Two coupled causes:Render()ends with an unconditionalvkQueueWaitIdle(Device::queue)(line 830) aftervkQueuePresentKHR. The CPU blocks until all of frame N's GPU work and present retire before acquiring/recording frame N+1 — zero CPU/GPU overlap.presentComplete/renderCompletesemaphore pair exists for the Window's lifetime (lines 417-431, 675-682, 805-817), baked into the sharedsubmitInfo, and zero fences. With one pair you cannot have frame N's render-complete and frame N+1's acquire pending simultaneously.These are not independent: removing the wait-idle while reusing one binary semaphore pair across in-flight frames is a textbook Vulkan race, and per-frame semaphores without per-frame fences cause command-buffer use-after-free. The fix is one coherent change to the frame-pacing model.
Proposed fix (single deliverable)
VkFenceper frame slot, pass it tovkQueueSubmit. At the top ofRender(), wait on + reset only the fence for the slot about to be reused. Key the fences by the acquired image index (vkAcquireNextImageKHRreturns an arbitrary index — do not assume round-robin).std::array<Semaphores, numFrames>indexed by a free-running CPU frame counter % numFrames — not bycurrentBuffer(the image index isn't known until after acquire). Acquire signalsframeSemaphores[frame].presentComplete; submit waits on it and signalsframeSemaphores[frame].renderComplete; present waits onrenderComplete.vkQueueWaitIdleon the resize/OUT_OF_DATEand teardown paths.Impact
High. At vsync-bound (FIFO) steady state the win is reduced latency/jitter; the throughput win is largest when GPU or CPU record/update time is the limiter.
Correctness caveats
[perf][HIGH] Per-frame vkQueueWaitIdle fully serializes CPU and GPU (frame loop)to [perf][HIGH] Frame-in-flight: per-frame fences + per-frame semaphores (remove per-frame vkQueueWaitIdle)