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

@ -69,6 +69,26 @@ export namespace Crafter {
return stages;
}
// The access mask matching a swapchain-writer stage union, for the
// frame-edge barriers (acquire dst / present src). A barrier's access mask
// must only contain access flags supported by its accompanying stage mask
// (VUID-vkCmdPipelineBarrier-pImageMemoryBarriers-02820): TRANSFER_WRITE is
// not a valid access for COMPUTE/RAY_TRACING stages, so hardcoding both
// SHADER_WRITE and TRANSFER_WRITE fires the VUID every frame on an
// all-compute frame. The swapchain image is written as a storage image by
// compute/RT passes (SHADER_WRITE) and only via TRANSFER_WRITE when a
// transfer-stage pass is present, so derive the access bits from the same
// per-pass stage union the barrier's stage mask uses instead of hardcoding.
inline VkAccessFlags SwapchainWriterAccess(VkPipelineStageFlags stages) {
VkAccessFlags access = 0;
if (stages & (VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
| VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR))
access |= VK_ACCESS_SHADER_WRITE_BIT;
if (stages & VK_PIPELINE_STAGE_TRANSFER_BIT)
access |= VK_ACCESS_TRANSFER_WRITE_BIT;
return access;
}
// The inter-pass swapchain barrier (replacing the old queue-wide
// VkMemoryBarrier at the #115 location). Scoped to the swapchain image's
// single colour subresource so only that image's shader caches round-trip —