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:
catbot 2026-06-17 19:44:33 +00:00
commit 9414863cff
6 changed files with 316 additions and 8 deletions

View file

@ -473,6 +473,35 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
mbc.GetInterfacesAndImplementations(ifaces, mipBarrierImpls);
cfg.tests.push_back(std::move(mipBarrierTest));
// Issue #115: the frame loop's inter-pass and acquire/present barriers
// no longer use ALL_COMMANDS / a queue-wide VkMemoryBarrier. The stage
// masks are derived per pass via RenderPass::SwapchainStage() (compute
// vs ray-tracing) and unioned across the frame by SwapchainStageUnion,
// and the inter-pass dependency is scoped to the swapchain image by
// BuildSwapchainInterPassBarrier — all pure CPU logic over the pass
// list, so this test drives them directly with no GPU at runtime.
Test swapBarrierTest;
Configuration& sbc = swapBarrierTest.config;
sbc.path = cfg.path;
sbc.name = "SwapchainBarrierScope";
sbc.outputName = "SwapchainBarrierScope";
sbc.type = ConfigurationType::Executable;
sbc.target = cfg.target;
sbc.march = cfg.march;
sbc.mtune = cfg.mtune;
sbc.debug = cfg.debug;
sbc.sysroot = cfg.sysroot;
sbc.dependencies = cfg.dependencies;
sbc.externalDependencies = cfg.externalDependencies;
sbc.compileFlags = cfg.compileFlags;
sbc.linkFlags = cfg.linkFlags;
sbc.defines = cfg.defines;
sbc.cFiles = cfg.cFiles;
std::vector<fs::path> swapBarrierImpls(impls.begin(), impls.end());
swapBarrierImpls.emplace_back("tests/SwapchainBarrierScope/main");
sbc.GetInterfacesAndImplementations(ifaces, swapBarrierImpls);
cfg.tests.push_back(std::move(swapBarrierTest));
// Issue #47: the fused UI uber-kernel (shaders/ui-fused.comp.glsl) and
// its C++ push-constant mirror UIFusedHeader. Compiles the real shader
// with glslang, validates with spirv-val, and pins the push-constant