fix(window): silence per-frame and setup-path Vulkan validation errors (#153)

Two distinct validation errors the native frame loop emitted, both
originating in Crafter.Graphics with no consumer-side influence.

Problem 1 — per-frame acquire-barrier access/stage mismatch. The
acquire->GENERAL barrier hardcoded dstAccessMask =
SHADER_WRITE|TRANSFER_WRITE but used the per-pass stage union as its dst
stage mask. For an all-compute frame the union narrows to COMPUTE_SHADER,
which does not support TRANSFER_WRITE, so VUID-02820 fired every frame.
Derive the access mask from the same stage union via a new
SwapchainWriterAccess() helper (mirroring SwapchainStageUnion), and apply
it to both the acquire dst and present src masks for symmetry.

Problem 2 — mid-session StartInit/FinishInit (and GetCmd/EndCmd) reuse the
shared drawCmdBuffers[currentBuffer]. With no steady-state wait-idle the
loop's last submission of that buffer may still be in flight when scene
setup runs (building map meshes / acceleration structures), so the old
code re-began (VUID-00049) and re-submitted (VUID-00071) a pending buffer,
and resources freed in the StartInit..FinishInit bracket could still be
referenced by it. Drain the queue at the start of StartInit/GetCmd before
re-recording; setup is rare, so a wait-idle is fine (FinishInit/EndCmd
already wait-idle at the end).

Tests: extend SwapchainBarrierScope with SwapchainWriterAccess coverage
(pure CPU), and add SetupCmdBufferReuse — a real-frame-loop regression
test driving a compute pass plus interleaved mid-session StartInit rounds,
asserting the validation layer stays silent. Verified both halves fail
(reproducing the exact VUIDs) when their respective fix is reverted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
catbot 2026-06-18 18:04:25 +00:00
commit 7316e51dca
5 changed files with 265 additions and 4 deletions

View file

@ -296,6 +296,41 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
frameLoopImpls.emplace_back("tests/FrameLoopSync/main");
fl.GetInterfacesAndImplementations(ifaces, frameLoopImpls);
cfg.tests.push_back(std::move(frameLoopTest));
// Issue #153: two frame-loop validation errors. (1) The acquire
// barrier hardcoded dstAccessMask = SHADER_WRITE|TRANSFER_WRITE but
// used the per-pass stage union (COMPUTE_SHADER for an all-compute
// frame) as its dst stage, so TRANSFER_WRITE was unsupported and
// VUID-02820 fired every frame — FrameLoopSync can't see it because
// it runs with no passes (the union falls back to the conservative
// writer set, which includes TRANSFER). (2) Mid-session
// StartInit/FinishInit reuse the shared draw command buffer while
// the loop's last submission of it is still in flight, re-beginning
// (00049) and re-submitting (00071) a pending buffer. Drives a real
// compute pass through the loop plus interleaved StartInit rounds
// and asserts the layer stays silent. Needs the Wayland backend + a
// real compositor, so it lives in the !windows block.
Test setupReuseTest;
Configuration& sr = setupReuseTest.config;
sr.path = cfg.path;
sr.name = "SetupCmdBufferReuse";
sr.outputName = "SetupCmdBufferReuse";
sr.type = ConfigurationType::Executable;
sr.target = cfg.target;
sr.march = cfg.march;
sr.mtune = cfg.mtune;
sr.debug = cfg.debug;
sr.sysroot = cfg.sysroot;
sr.dependencies = cfg.dependencies;
sr.externalDependencies = cfg.externalDependencies;
sr.compileFlags = cfg.compileFlags;
sr.linkFlags = cfg.linkFlags;
sr.defines = cfg.defines;
sr.cFiles = cfg.cFiles;
std::vector<fs::path> setupReuseImpls(impls.begin(), impls.end());
setupReuseImpls.emplace_back("tests/SetupCmdBufferReuse/main");
sr.GetInterfacesAndImplementations(ifaces, setupReuseImpls);
cfg.tests.push_back(std::move(setupReuseTest));
}
// Issue #36: BLAS build options. Drives the real hardware AS-build