Replaying a trace in frame loop mode repeatedly submits the same command streams to the GPU. Since Vulkan Fences (VkFence) and Events (VkEvent) synchronize execution between the host (CPU) and device (GPU), their state must be restored at the loop boundary. If left unmanaged, fences and events remain in incorrect states, triggering validation errors (such as double-signaling) or CPU hangs (waiting for a signal that will never be triggered).
This document describes the design for restoring the states of Fences and Events at loop boundaries.
Fences are mainly used for GPU-to-CPU synchronization, signaling the host when queue submissions or swapchain image acquisitions are finished.
During loop setup (OnLoopStart), the replayer queries the host-side state of all existing fences:
- Iterate through all active fences in the object tracking table (
VisitVkFenceInfo). - Use
vkGetFenceStatusto query if the fence is currently in theSIGNALED(VK_SUCCESS) orUNSIGNALED(VK_NOT_READY) state. - Store the status in a map
initial_fence_states_keyed by the fence capture ID.
At the loop boundary (ResetLoopBoundary), before restarting the next iteration, GFXReconstruct checks if the current runtime state of each fence has drifted from its initial recorded state:
- Query
vkGetFenceStatusfor the current runtime status. - Compare it against the initial captured status:
- Case A (Unsignaled -> Signaled): If the fence was initially unsignaled (
VK_NOT_READY) but is now signaled (VK_SUCCESS), the replayer callsvkResetFencesto reset it back to the unsignaled state. - Case B (Signaled -> Unsignaled): If the fence was initially signaled (
VK_SUCCESS) but is now unsignaled (VK_NOT_READY), the replayer must synthetically signal it. This is done by callingvkQueueSubmiton an active queue with zero command buffers but specifying the fence as the signal target.
- Case A (Unsignaled -> Signaled): If the fence was initially unsignaled (
- Fences that did not change state are skipped.
While boundary restoration handles general fence states, strict Vulkan drivers (e.g., Qualcomm Adreno) and the Khronos Validation Layer will abort if a fence is submitted in a signaled state (VUID-vkQueueSubmit-fence-00063). Because an application's original vkResetFences call might fall outside the captured loop range, fences can remain signaled on subsequent iterations.
To provide a robust safeguard against this undefined behavior:
- Immediately before executing any queue submission command (
vkQueueSubmit,vkQueueSubmit2, orvkQueueBindSparse), GFXReconstruct explicitly intercepts the provided fence parameter. - If a valid fence handle is provided,
vkResetFencesis invoked on that fence right before the submission call. - Since
vkResetFenceshas no effect on already-unsignaled fences, this safely guarantees the fence is strictly unsignaled before the driver processes the submission, preventing driver hangs and validation layer crashes during frame looping.
Events provide fine-grained synchronization within command buffers (GPU-to-GPU) or between host and device.
During loop setup (OnLoopStart), the host-side state of events is queried:
- Iterate through all tracked events in
VisitVkEventInfo. - Filter Device-Only Events: If an event was created with
VK_EVENT_CREATE_DEVICE_ONLY_BIT, it is not host-accessible. GFXReconstruct avoids calling host-side event queries on these to prevent driver faults and filters them out. - For host-accessible events, query their status via
vkGetEventStatus. - Store if the event is set (
VK_EVENT_SET) ininitial_event_states_.
At the loop boundary:
- For each tracked host-accessible event, compare its state to the initial captured state.
- Call
vkSetEventon the host if it was initially set, orvkResetEventif it was initially reset.
For events created or destroyed inside the loop range:
- Destruction Protection: If an event is destroyed inside the loop, the replayer intercepts
Process_vkDestroyEventand skips the actual destruction on repeated loop iterations to prevent invalid handle errors when the loop restarts. - Creation Protection: If an event creation call
Process_vkCreateEventis replayed and an event with the same capture ID already exists (from a previous iteration), GFXReconstruct first callsvkDestroyEventon the existing handle to release the driver resources and removes its tracked metadata before creating a new event handle. This prevents handle leaks.