Window::SaveFrame touches the swapchain image after it was presented (validation error on every capture) #157

Open
opened 2026-10-06 13:25:37 +00:00 by jorijnvdgraaf · 0 comments

Summary

Window::SaveFrame reads back images[currentBuffer] after Render() has already handed that image to vkQueuePresentKHR. With the validation layer on, every call logs one error:

vkQueueSubmit(): pSubmits[0] performs a layout transition on presentable VkImage 0x…, but the image
has not been acquired from VkSwapchainKHR 0x… (either never or since the last present operation).
The Vulkan spec states: Use of a presentable image must occur only after the image is returned by
vkAcquireNextImageKHR, and before it is released by vkQueuePresentKHR. This includes transitioning
the image layout and rendering commands

Reproduced on an RTX 4090 (driver 610.57.04, Wayland) with CRAFTER_GRAPHICS_VALIDATION=1: render a few frames, call SaveFrame. Line numbers below refer to 6aa0338.

Root cause

The documented contract is "state after Render() returns" (interfaces/Crafter.Graphics-Window.cppm:246-250). Render() presents at implementations/Crafter.Graphics-Window.cpp:956. SaveFrame (:1268) then records, on that same image:

  • a PRESENT_SRC_KHR → TRANSFER_SRC_OPTIMAL barrier (:1335-1346),
  • vkCmdCopyImageToBuffer (:1359),
  • a barrier back to PRESENT_SRC_KHR (:1370).

At that point the image belongs to the presentation engine.

Impact

  • The readback returns the expected pixels on NVIDIA and on Mesa Turnip (Adreno 810), but that is luck: the compositor or driver may be scanning the image out or may have recycled it.
  • A test that asserts Device::validationErrorCount == 0 cannot call SaveFrame before the assertion. tests/UIFallbackParity (pending, part of the descriptor-set fallback change) reads the counter before the capture for this reason.

Suggested fix

Capture inside the frame, before the image is released: a one-shot "capture this frame" request that makes Render() copy the swapchain image to a staging buffer after the last pass and before the GENERAL → PRESENT_SRC_KHR transition, with the PNG written once that frame's fence has signalled. SaveFrame can keep its signature and become "render one more frame and capture it".

## Summary `Window::SaveFrame` reads back `images[currentBuffer]` after `Render()` has already handed that image to `vkQueuePresentKHR`. With the validation layer on, every call logs one error: ``` vkQueueSubmit(): pSubmits[0] performs a layout transition on presentable VkImage 0x…, but the image has not been acquired from VkSwapchainKHR 0x… (either never or since the last present operation). The Vulkan spec states: Use of a presentable image must occur only after the image is returned by vkAcquireNextImageKHR, and before it is released by vkQueuePresentKHR. This includes transitioning the image layout and rendering commands ``` Reproduced on an RTX 4090 (driver 610.57.04, Wayland) with `CRAFTER_GRAPHICS_VALIDATION=1`: render a few frames, call `SaveFrame`. Line numbers below refer to `6aa0338`. ## Root cause The documented contract is "state after Render() returns" (`interfaces/Crafter.Graphics-Window.cppm:246-250`). `Render()` presents at `implementations/Crafter.Graphics-Window.cpp:956`. `SaveFrame` (`:1268`) then records, on that same image: - a `PRESENT_SRC_KHR → TRANSFER_SRC_OPTIMAL` barrier (`:1335-1346`), - `vkCmdCopyImageToBuffer` (`:1359`), - a barrier back to `PRESENT_SRC_KHR` (`:1370`). At that point the image belongs to the presentation engine. ## Impact - The readback returns the expected pixels on NVIDIA and on Mesa Turnip (Adreno 810), but that is luck: the compositor or driver may be scanning the image out or may have recycled it. - A test that asserts `Device::validationErrorCount == 0` cannot call `SaveFrame` before the assertion. `tests/UIFallbackParity` (pending, part of the descriptor-set fallback change) reads the counter before the capture for this reason. ## Suggested fix Capture inside the frame, before the image is released: a one-shot "capture this frame" request that makes `Render()` copy the swapchain image to a staging buffer after the last pass and before the `GENERAL → PRESENT_SRC_KHR` transition, with the PNG written once that frame's fence has signalled. `SaveFrame` can keep its signature and become "render one more frame and capture it".
Sign in to join this conversation.
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#157
No description provided.