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

@ -165,6 +165,45 @@ int main() {
"inter-pass barrier is scoped to the single swapchain subresource");
}
// ─── SwapchainWriterAccess (issue #153) ─────────────────────────────────
// The frame-edge (acquire dst / present src) barriers must set an access
// mask supported by their accompanying stage mask
// (VUID-vkCmdPipelineBarrier-pImageMemoryBarriers-02820). The old code
// hardcoded SHADER_WRITE|TRANSFER_WRITE on a barrier whose stage mask is the
// per-pass union, so an all-compute frame carried TRANSFER_WRITE into a
// COMPUTE_SHADER dst stage and fired the VUID every frame. The fix derives
// the access from the same stage union, so each stage only pulls in the
// access flags it actually supports.
{
// All-compute frame: COMPUTE_SHADER supports SHADER_WRITE only — and
// crucially NOT TRANSFER_WRITE (the bit that fired the VUID).
VkAccessFlags compute = SwapchainWriterAccess(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
Check(compute == VK_ACCESS_SHADER_WRITE_BIT,
"compute-only writer access is SHADER_WRITE only");
Check((compute & VK_ACCESS_TRANSFER_WRITE_BIT) == 0,
"compute-only writer access does NOT include TRANSFER_WRITE (the VUID-02820 trap)");
// RT frame: RAY_TRACING_SHADER also writes via SHADER_WRITE, no transfer.
Check(SwapchainWriterAccess(VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR) == VK_ACCESS_SHADER_WRITE_BIT,
"RT-only writer access is SHADER_WRITE only");
// A transfer-stage writer pulls in TRANSFER_WRITE.
Check(SwapchainWriterAccess(VK_PIPELINE_STAGE_TRANSFER_BIT) == VK_ACCESS_TRANSFER_WRITE_BIT,
"transfer-only writer access is TRANSFER_WRITE only");
// The conservative writer union (un-overridden / empty frame) folds in
// both — and every bit it sets is supported by some stage in the union.
Check(SwapchainWriterAccess(kSwapchainWriterStages)
== (VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT),
"conservative writer union access is SHADER_WRITE | TRANSFER_WRITE");
// Mixed compute+RT (no transfer): still SHADER_WRITE only, no transfer.
VkAccessFlags mixed = SwapchainWriterAccess(
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR);
Check(mixed == VK_ACCESS_SHADER_WRITE_BIT,
"compute+RT writer access is SHADER_WRITE only (no spurious TRANSFER_WRITE)");
}
if (failures != 0) {
std::println("{} check(s) failed", failures);
return EXIT_FAILURE;