During frame looping, GFXReconstruct replays a specific range of frames repeatedly. Since GPU and compute shader workloads can mutate buffer contents (e.g., storage buffers, uniform texel buffers) during loop iterations, these mutations must be reverted at the loop boundary to prevent rendering drift or verification mismatch.
This document describes the design for backing up and restoring Vulkan buffer contents across loop iterations.
To restore buffer contents at the loop boundary, GFXReconstruct implements a Shadow Buffer allocation and copy mechanism.
Per the Vulkan specification, a buffer cannot be the source or destination of a transfer copy command unless it was created with VK_BUFFER_USAGE_TRANSFER_SRC_BIT and VK_BUFFER_USAGE_TRANSFER_DST_BIT.
To ensure we can copy any buffer at runtime, GFXReconstruct intercepts buffer creation:
- API Interception: In
Process_vkCreateBuffer, the replayer modifies theVkBufferCreateInfostruct before forwarding the call to the driver. - Flag injection: It dynamically appends
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BITto theusageflags.
This guarantees that all buffers created during setup or replay can legally participate in shadow copy operations.
When entering the loop (OnLoopStart), before replaying the loop range, GFXReconstruct captures the initial contents of all active buffers:
- Initialize Restoration Resources: Ensure a dedicated restoration command pool and command buffer (
restoration_command_buffer_) are created for the target Vulkan logical device. - Pipeline Barrier Injection: Begin recording the restoration command buffer and insert a execution barrier:
srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BITdstStageMask = VK_PIPELINE_STAGE_TRANSFER_BITsrcAccessMask = VK_ACCESS_MEMORY_WRITE_BITdstAccessMask = VK_ACCESS_TRANSFER_READ_BITThis guarantees any previous GPU writes to the buffers are flushed and visible before we copy them.
- Iterate and Create Shadow Buffers: Iterate through all tracked buffers in the
VulkanObjectInfoTable. For each active buffer:- Verify the buffer's device allocator is valid and that its size is greater than zero.
- Create a corresponding GPU-only Shadow Buffer (
shadow_buffer) with identical size andVK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT. - Allocate memory and bind it to the shadow buffer handle.
- Record Copies: Call
vkCmdCopyBufferto copy the original buffer contents to the shadow buffer. - Submit and Synchronize: Close the command buffer, submit it to the active queue, and wait for completion (
vkQueueWaitIdleor device wait idle). This ensures backup is fully completed on the GPU before replay begins.
At the end of each iteration (in ResetLoopBoundary), before replaying the loop range again:
- Record a new set of copy commands onto
restoration_command_buffer_. - For each backed up buffer, record a copy back from the shadow buffer to the original buffer:
vkCmdCopyBuffer(shadow_buffer -> original_buffer)
- Insert a post-transfer barrier to guarantee memory visibility for subsequent stages:
srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BITdstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BITsrcAccessMask = VK_ACCESS_TRANSFER_WRITE_BITdstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT
- Submit the restoration command buffer and wait for completion.
To prevent memory leaks:
- All allocated shadow buffer handles (
shadow_buffer) and their memory bindings are tracked in ashadow_buffers_map. - When the consumer is destroyed (
~VulkanReplayFrameLoopConsumer), it callsDestroyShadowBuffers()to free all allocated memory and destroy all shadow buffer handles.