perf(sync): scope frame-loop barriers to swapchain image + real per-pass stages (#115)
The inter-pass and acquire/present barriers in the frame loop set both stage masks to ALL_COMMANDS, and the inter-pass dependency used a queue-wide VkMemoryBarrier — fully serialising against every pipeline stage and flushing all caches every frame, when all the next pass needs is the swapchain image the previous one wrote. Replace the inter-pass global VkMemoryBarrier with an image memory barrier scoped to the swapchain image's single colour subresource (as the intra-pass UI barrier already does), and derive the barrier stage masks per pass: RenderPass::SwapchainStage() is overridden by UIRenderer (COMPUTE_SHADER) and RTPass (RAY_TRACING_SHADER), so a compute->compute edge only serialises COMPUTE while an RT pass pulls in RAY_TRACING — the acquire/present frame-edge masks use the real union of the frame's passes (SwapchainStageUnion). The base default and the empty-passes fallback are the conservative COMPUTE | RAY_TRACING | TRANSFER union, so a polymorphic or un-overridden pass can only be over- not under-synchronised. Adds SwapchainBarrierScope (pure CPU) pinning the per-pass derivation, the union narrowing, and the inter-pass barrier scope; FrameLoopSync already drives the real GPU frame loop with validation enabled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9d9f9d9d2c
commit
9414863cff
6 changed files with 316 additions and 8 deletions
|
|
@ -815,7 +815,16 @@ void Window::Render() {
|
|||
: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
acquireBarrier.image = images[currentBuffer];
|
||||
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &acquireBarrier);
|
||||
// Real per-pass stage union for this frame's passes — the swapchain image
|
||||
// is only ever written by compute/RT (and possibly transfer), never by the
|
||||
// whole pipeline, so the frame-edge barriers wait on exactly those stages
|
||||
// instead of ALL_COMMANDS. Derived once here and reused for the present
|
||||
// barrier below (passes don't change within a Render()).
|
||||
const VkPipelineStageFlags swapWriterStages = SwapchainStageUnion(passes);
|
||||
|
||||
// The acquire->GENERAL transition only needs to be visible before the first
|
||||
// pass writes the image, i.e. at the writer stages — not ALL_COMMANDS.
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, swapWriterStages, 0, 0, nullptr, 0, nullptr, 1, &acquireBarrier);
|
||||
|
||||
// Synthesise key-repeat events before listeners run, so the focused
|
||||
// widget's OnTextInput / OnKeyDown sees them in the same frame.
|
||||
|
|
@ -879,18 +888,28 @@ void Window::Render() {
|
|||
passes[i]->Record(drawCmdBuffers[currentBuffer], currentBuffer, *this);
|
||||
|
||||
if (i + 1 < passes.size()) {
|
||||
VkMemoryBarrier mb {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||
};
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
// Make pass i's writes to the swapchain image visible to pass i+1.
|
||||
// The only resource the inter-pass hazard touches is that one image
|
||||
// (later passes overdraw earlier ones), so use an image memory
|
||||
// barrier scoped to its subresource instead of a queue-wide
|
||||
// VkMemoryBarrier — only this image's caches round-trip, exactly as
|
||||
// the intra-pass UI barrier already does. The stage masks come from
|
||||
// each pass's own SwapchainStage(), so a compute->compute edge only
|
||||
// serialises COMPUTE_SHADER while an RT pass on either side pulls in
|
||||
// RAY_TRACING_SHADER — never the ALL_COMMANDS full-pipeline stall.
|
||||
VkImageMemoryBarrier ib = BuildSwapchainInterPassBarrier(images[currentBuffer]);
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer],
|
||||
passes[i]->SwapchainStage(), passes[i + 1]->SwapchainStage(),
|
||||
0, 0, nullptr, 0, nullptr, 1, &ib);
|
||||
}
|
||||
}
|
||||
|
||||
presentBarrier.image = images[currentBuffer];
|
||||
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &presentBarrier);
|
||||
// The ->PRESENT_SRC transition only needs to wait on the stages that
|
||||
// actually wrote the image (the per-pass union), not ALL_COMMANDS; present
|
||||
// itself is gated by the render-finished semaphore, so dst stays BOTTOM.
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], swapWriterStages, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &presentBarrier);
|
||||
|
||||
Device::CheckVkResult(vkEndCommandBuffer(drawCmdBuffers[currentBuffer]));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue