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

Merged
catbot merged 1 commit from claude/issue-153 into master 2026-06-18 20:05:06 +02:00
Member

Resolves #153.

Two distinct Vulkan validation errors the native frame loop emitted, both originating in Crafter.Graphics.

Problem 1 — per-frame acquire-barrier access/stage mismatch (the spammy one)

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-vkCmdPipelineBarrier-pImageMemoryBarriers-02820 fired every frame.

Fix: derive the access mask from the same stage union via a new SwapchainWriterAccess() helper (mirroring the existing SwapchainStageUnion), applied to both the acquire dstAccessMask and the present srcAccessMask (the latter had the mirror-image latent inconsistency).

Problem 2 — setup-path command-buffer/buffer lifecycle errors

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 mid-session scene setup runs (building map meshes / acceleration structures), so the old code re-began (VUID-vkBeginCommandBuffer-commandBuffer-00049) and re-submitted (VUID-vkQueueSubmit-pCommandBuffers-00071) a pending buffer, and resources freed in the StartInit..FinishInit bracket could still be referenced by it (vkDestroyBuffer-in-use).

Fix: drain the queue at the start of StartInit/GetCmd before re-recording. Setup is rare (not the per-frame path), so a wait-idle is fine — FinishInit/EndCmd already wait-idle at the end.

Tests

  • SwapchainBarrierScope (pure CPU) extended with SwapchainWriterAccess coverage: compute-only access is SHADER_WRITE only (no TRANSFER_WRITE — the VUID-02820 trap), transfer pulls in TRANSFER_WRITE, conservative union folds in both.
  • New SetupCmdBufferReuse — a real-frame-loop regression test (same harness as FrameLoopSync) driving a compute pass plus interleaved mid-session StartInit/FinishInit rounds, asserting the validation layer stays silent. FrameLoopSync cannot catch problem 1 because it runs with no passes (the union falls back to the conservative writer set, which includes TRANSFER).

Verified both halves of SetupCmdBufferReuse fail — reproducing the exact VUIDs from the issue — when their respective fix is reverted. Full suite: 25 passed (crafter-build test --jobs=1).

🤖 Generated with Claude Code

Resolves #153. Two distinct Vulkan validation errors the native frame loop emitted, both originating in Crafter.Graphics. ## Problem 1 — per-frame acquire-barrier access/stage mismatch (the spammy one) 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-vkCmdPipelineBarrier-pImageMemoryBarriers-02820` fired every frame. **Fix:** derive the access mask from the same stage union via a new `SwapchainWriterAccess()` helper (mirroring the existing `SwapchainStageUnion`), applied to both the acquire `dstAccessMask` and the present `srcAccessMask` (the latter had the mirror-image latent inconsistency). ## Problem 2 — setup-path command-buffer/buffer lifecycle errors `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 mid-session scene setup runs (building map meshes / acceleration structures), so the old code re-began (`VUID-vkBeginCommandBuffer-commandBuffer-00049`) and re-submitted (`VUID-vkQueueSubmit-pCommandBuffers-00071`) a pending buffer, and resources freed in the `StartInit..FinishInit` bracket could still be referenced by it (`vkDestroyBuffer`-in-use). **Fix:** drain the queue at the start of `StartInit`/`GetCmd` before re-recording. Setup is rare (not the per-frame path), so a wait-idle is fine — `FinishInit`/`EndCmd` already wait-idle at the end. ## Tests - `SwapchainBarrierScope` (pure CPU) extended with `SwapchainWriterAccess` coverage: compute-only access is `SHADER_WRITE` only (no `TRANSFER_WRITE` — the VUID-02820 trap), transfer pulls in `TRANSFER_WRITE`, conservative union folds in both. - New `SetupCmdBufferReuse` — a real-frame-loop regression test (same harness as `FrameLoopSync`) driving a compute pass plus interleaved mid-session `StartInit`/`FinishInit` rounds, asserting the validation layer stays silent. `FrameLoopSync` cannot catch problem 1 because it runs with no passes (the union falls back to the conservative writer set, which includes TRANSFER). Verified both halves of `SetupCmdBufferReuse` fail — reproducing the exact VUIDs from the issue — when their respective fix is reverted. Full suite: **25 passed** (`crafter-build test --jobs=1`). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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>
catbot merged commit fc71eb36b9 into master 2026-06-18 20:05:06 +02:00
catbot deleted branch claude/issue-153 2026-06-18 20:05:06 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Catcrafts/Crafter.Graphics!154
No description provided.