From 1ba8dc7ab865d28b3a44c922c13f897686afa61f Mon Sep 17 00:00:00 2001 From: Oleh Kuznetsov Date: Thu, 13 Aug 2026 17:59:04 +0100 Subject: [PATCH] [frame looping] Update buffers handling --- frame_loop_buffers_content.md | 69 +- framework/decode/vulkan_object_info.h | 1 + .../decode/vulkan_replay_consumer_base.cpp | 1 + .../vulkan_replay_frame_loop_consumer.cpp | 659 +++++++++++++----- .../vulkan_replay_frame_loop_consumer.h | 59 +- 5 files changed, 588 insertions(+), 201 deletions(-) diff --git a/frame_loop_buffers_content.md b/frame_loop_buffers_content.md index 4891b1bbc6..ac9d27c0c2 100644 --- a/frame_loop_buffers_content.md +++ b/frame_loop_buffers_content.md @@ -1,14 +1,14 @@ # Buffer Content Restoration during Frame Looping -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. +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, copy destinations) 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. +This document describes the design for tracking, backing up, and restoring Vulkan buffer contents across loop iterations. --- -## Proposed Design: Shadow Buffer Backup and Restore +## Design: Lazy Shadow Buffer Backup and Restore -To restore buffer contents at the loop boundary, GFXReconstruct implements a **Shadow Buffer** allocation and copy mechanism. +To restore buffer contents at the loop boundary, GFXReconstruct implements a **Lazy Shadow Buffer** allocation and copy mechanism that mirrors the design used for images. ### 1. Enabling Copy Capability on All Buffers 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`. @@ -19,39 +19,54 @@ To ensure we can copy any buffer at runtime, GFXReconstruct intercepts buffer cr This guarantees that all buffers created during setup or replay can legally participate in shadow copy operations. -### 2. Initial Buffer State Backup (At Loop Start) -When entering the loop (`OnLoopStart`), before replaying the loop range, GFXReconstruct captures the initial contents of all active buffers: - -1. **Initialize Restoration Resources**: Ensure a dedicated restoration command pool and command buffer (`restoration_command_buffer_`) are created for the target Vulkan logical device. -2. **Pipeline Barrier Injection**: Begin recording the restoration command buffer and insert a execution barrier: - * `srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT` - * `dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT` - * `srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT` - * `dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT` - This guarantees any previous GPU writes to the buffers are flushed and visible before we copy them. -3. **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 and `VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT`. - * Allocate memory and bind it to the shadow buffer handle. -4. **Record Copies**: Call `vkCmdCopyBuffer` to copy the original buffer contents to the shadow buffer. -5. **Submit and Synchronize**: Close the command buffer, submit it to the active queue, and wait for completion (`vkQueueWaitIdle` or device wait idle). This ensures backup is fully completed on the GPU before replay begins. - -### 3. Buffer Content Restoration (At Loop Boundary, Iteration 2+) +### 2. Tracking Buffer Usage Across Command Buffers +Instead of snapshotting every buffer created in the entire application (including inactive auxiliary devices or unused buffers), GFXReconstruct tracks which buffers are actually referenced by submitted command buffers: + +* **Command Recording Interception**: Intercepts commands referencing or mutating buffers to populate `VulkanCommandBufferInfo::bound_buffers`: + * `vkCmdBindVertexBuffers`, `vkCmdBindVertexBuffers2`, `vkCmdBindVertexBuffers2EXT` (vertex buffers) + * `vkCmdBindIndexBuffer`, `vkCmdBindIndexBuffer2` (index buffers) + * `vkCmdDrawIndirect`, `vkCmdDrawIndexedIndirect`, `vkCmdDispatchIndirect` (indirect arguments) + * `vkCmdCopyBuffer`, `vkCmdCopyBufferToImage`, `vkCmdCopyImageToBuffer` (source and destination buffers) + * `vkCmdUpdateBuffer`, `vkCmdFillBuffer` (destination buffer) + * `vkCmdPipelineBarrier` (buffer memory barriers) +* **Descriptor Set Tracking**: Intercepts `vkCmdBindDescriptorSets` / `vkCmdBindDescriptorSets2` to inspect bound descriptor sets and track all referenced buffer descriptors (storage buffers, uniform buffers, and texel buffer views). +* **Secondary Command Buffer Propagation**: When `vkCmdExecuteCommands` is called, secondary command buffers are tracked and their `bound_buffers`, `bound_descriptor_sets`, and layout transitions are propagated to the primary command buffer. All executed secondary command buffers are also recursively traversed when collecting touched buffers. + +### 3. Lazy Buffer State Backup (Before Queue Submit in Iteration 1) +When `vkQueueSubmit` / `vkQueueSubmit2` is called during the first loop iteration (`IsLoopFirstIteration()`): + +1. **Extract Submitted Command Buffers**: GFXReconstruct extracts the command buffers in the submit and calls `CollectTouchedBuffersFromCommandBuffer` to populate `loop_touched_buffers_`. +2. **Filter Buffers**: For each touched buffer: + * Verify the buffer has a valid handle, non-zero size, and a valid device allocator. + * Verify the buffer belongs to the submitting queue's logical device (`dev_info->handle == device`). + * Verify the buffer has not already been snapshotted (`shadow_buffers_.find(buffer_id) == shadow_buffers_.end()`). +3. **Allocate Shadow Buffers**: For each eligible buffer: + * Create a GPU-only shadow buffer (`shadow_buffer`) with matching size and transfer usage flags. + * Allocate and bind device local memory (`shadow_memory`). + * Track the allocation in `shadow_buffers_`. +4. **Record and Submit Snapshot Copies**: + * Record a pipeline barrier ensuring previous writes are complete (`srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT`, `dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT`). + * Record `vkCmdCopyBuffer` from the original buffer to the shadow buffer. + * Submit the restoration command buffer and wait for idle before the workload command buffers execute. + +This guarantees that pristine initial buffer contents are captured just before the GPU first touches them. + +### 4. Buffer Content Restoration (At Loop Boundary, Iteration 2+) At the end of each iteration (in `ResetLoopBoundary`), before replaying the loop range again: -1. Record a new set of copy commands onto `restoration_command_buffer_`. -2. For each backed up buffer, record a copy back from the shadow buffer to the original buffer: +1. Begin recording the restoration command buffer (`restoration_command_buffer_`). +2. For each backed up buffer in `shadow_buffers_`, record a copy from the shadow buffer back to the original buffer: * `vkCmdCopyBuffer(shadow_buffer -> original_buffer)` 3. Insert a post-transfer barrier to guarantee memory visibility for subsequent stages: * `srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT` * `dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT` * `srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT` * `dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT` -4. Submit the restoration command buffer and wait for completion. +4. Submit the restoration command buffer and wait for completion (`QueueWaitIdle`). --- ## Resource Cleanup To prevent memory leaks: -* All allocated shadow buffer handles (`shadow_buffer`) and their memory bindings are tracked in a `shadow_buffers_` map. -* When the consumer is destroyed (`~VulkanReplayFrameLoopConsumer`), it calls `DestroyShadowBuffers()` to free all allocated memory and destroy all shadow buffer handles. +* All allocated shadow buffer handles (`shadow_buffer`) and their memory bindings (`shadow_memory`) are tracked in `shadow_buffers_`. +* When the consumer is destroyed (`~VulkanReplayFrameLoopConsumer`), it calls `DestroyShadowBuffers()` to free all allocated memory and destroy all shadow buffer handles via their respective device allocators. diff --git a/framework/decode/vulkan_object_info.h b/framework/decode/vulkan_object_info.h index e4c6049235..27737d7d6e 100644 --- a/framework/decode/vulkan_object_info.h +++ b/framework/decode/vulkan_object_info.h @@ -918,6 +918,7 @@ struct VulkanCommandBufferInfo : public VulkanPoolObjectInfo }; std::unordered_map> image_layout_barriers; std::vector bound_descriptor_sets; + std::vector bound_buffers; std::vector executed_secondary_command_buffers; std::unordered_map bound_pipelines; std::vector push_constant_data; diff --git a/framework/decode/vulkan_replay_consumer_base.cpp b/framework/decode/vulkan_replay_consumer_base.cpp index d60b24359e..f4033251f3 100644 --- a/framework/decode/vulkan_replay_consumer_base.cpp +++ b/framework/decode/vulkan_replay_consumer_base.cpp @@ -10389,6 +10389,7 @@ void VulkanReplayConsumerBase::ClearCommandBufferInfo(VulkanCommandBufferInfo* c command_buffer_info->active_render_pass_attachment_image_view_ids.clear(); command_buffer_info->dynamic_rendering_image_view_ids.clear(); command_buffer_info->image_layout_barriers.clear(); + command_buffer_info->bound_buffers.clear(); command_buffer_info->bound_pipelines.clear(); command_buffer_info->push_constant_data.clear(); command_buffer_info->push_constant_stage_flags = 0; diff --git a/framework/decode/vulkan_replay_frame_loop_consumer.cpp b/framework/decode/vulkan_replay_frame_loop_consumer.cpp index d9e8430e4b..16b7058d82 100644 --- a/framework/decode/vulkan_replay_frame_loop_consumer.cpp +++ b/framework/decode/vulkan_replay_frame_loop_consumer.cpp @@ -672,6 +672,7 @@ void VulkanReplayFrameLoopConsumer::OnLoopStart() CaptureInitialFenceStates(); CaptureInitialEventStates(); RecordInitialLayouts(); + loop_touched_buffers_.clear(); if (active_queue_info_ == nullptr) { @@ -685,21 +686,6 @@ void VulkanReplayFrameLoopConsumer::OnLoopStart() }); } - std::unordered_set visited_devices; - GetObjectInfoTable().VisitVkQueueInfo([this, &visited_devices](const VulkanQueueInfo* q_info) { - if (q_info != nullptr && q_info->handle != VK_NULL_HANDLE && q_info->parent != VK_NULL_HANDLE) - { - if (visited_devices.insert(q_info->parent).second) - { - const graphics::VulkanDeviceTable* dev_table = GetDeviceTable(q_info->parent); - if (dev_table != nullptr) - { - RecordInitialBufferStates(q_info->parent, dev_table, const_cast(q_info)); - } - } - } - }); - // Wiping command buffers at loop repeats that span the boundary would destroy their state // (pipeline binds, render passes, dynamic rendering, etc.) and trigger validation errors or crashes. initial_loop_recording_cbs_ = recording_cbs_; @@ -1074,6 +1060,7 @@ void VulkanReplayFrameLoopConsumer::BackupImagesForSubmit(format::HandleId queue if (queue_info != nullptr && queue_info->handle != VK_NULL_HANDLE) { LazyBackupImagesForSubmit(queue_info->handle, static_cast(cb_ids.size()), cb_ids.data()); + LazyBackupBuffersForSubmit(queue_info->handle, static_cast(cb_ids.size()), cb_ids.data()); } } @@ -1382,6 +1369,7 @@ void VulkanReplayFrameLoopConsumer::Process_vkBeginCommandBuffer( if (cb_info != nullptr) { cb_info->image_layout_barriers.clear(); + cb_info->bound_buffers.clear(); auto device_info = GetObjectInfoTable().GetVkDeviceInfo(cb_info->parent_id); if (device_info != nullptr && device_info->handle != VK_NULL_HANDLE) { @@ -1517,7 +1505,7 @@ bool VulkanReplayFrameLoopConsumer::InitializeRestorationResources(VkDevice devi { if (restoration_command_pool_ != VK_NULL_HANDLE) { - if (restoration_device_ == device) + if (restoration_device_ == device && restoration_queue_family_index_ == queue_family_index) { return true; } @@ -1529,9 +1517,10 @@ bool VulkanReplayFrameLoopConsumer::InitializeRestorationResources(VkDevice devi { old_dev_table->DestroyCommandPool(restoration_device_, restoration_command_pool_, nullptr); } - restoration_command_pool_ = VK_NULL_HANDLE; - restoration_command_buffer_ = VK_NULL_HANDLE; - restoration_device_ = VK_NULL_HANDLE; + restoration_command_pool_ = VK_NULL_HANDLE; + restoration_command_buffer_ = VK_NULL_HANDLE; + restoration_device_ = VK_NULL_HANDLE; + restoration_queue_family_index_ = std::numeric_limits::max(); } } @@ -1551,7 +1540,8 @@ bool VulkanReplayFrameLoopConsumer::InitializeRestorationResources(VkDevice devi return false; } - restoration_device_ = device; + restoration_device_ = device; + restoration_queue_family_index_ = queue_family_index; VkCommandBufferAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; @@ -1564,9 +1554,10 @@ bool VulkanReplayFrameLoopConsumer::InitializeRestorationResources(VkDevice devi { GFXRECON_LOG_ERROR("Failed to allocate restoration command buffer, result=%d", result); device_table->DestroyCommandPool(device, restoration_command_pool_, nullptr); - restoration_command_pool_ = VK_NULL_HANDLE; - restoration_command_buffer_ = VK_NULL_HANDLE; - restoration_device_ = VK_NULL_HANDLE; + restoration_command_pool_ = VK_NULL_HANDLE; + restoration_command_buffer_ = VK_NULL_HANDLE; + restoration_device_ = VK_NULL_HANDLE; + restoration_queue_family_index_ = std::numeric_limits::max(); return false; } @@ -1728,7 +1719,12 @@ void VulkanReplayFrameLoopConsumer::RestoreImageLayouts(VkDevice begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - device_table->BeginCommandBuffer(restoration_command_buffer_, &begin_info); + VkResult res = device_table->BeginCommandBuffer(restoration_command_buffer_, &begin_info); + if (res != VK_SUCCESS) + { + GFXRECON_LOG_ERROR("RestoreImageLayouts: BeginCommandBuffer failed with %d", res); + return; + } device_table->CmdPipelineBarrier(restoration_command_buffer_, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, @@ -1761,136 +1757,284 @@ void VulkanReplayFrameLoopConsumer::RestoreImageLayouts(VkDevice } } -void VulkanReplayFrameLoopConsumer::RecordInitialBufferStates(VkDevice device, - const graphics::VulkanDeviceTable* device_table, - VulkanQueueInfo* queue_info) +void VulkanReplayFrameLoopConsumer::CollectTouchedBuffersFromCommandBuffer( + format::HandleId cb_id, std::unordered_set& visited_cbs) { - if (queue_info == nullptr || !InitializeRestorationResources(device, queue_info->family_index)) + if (!visited_cbs.insert(cb_id).second) { - GFXRECON_LOG_ERROR("RecordInitialBufferStates: Failed to initialize restoration resources."); return; } - - VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - VkResult res = device_table->BeginCommandBuffer(restoration_command_buffer_, &begin_info); - if (res != VK_SUCCESS) + auto cb_info = GetObjectInfoTable().GetVkCommandBufferInfo(cb_id); + if (cb_info == nullptr) { - GFXRECON_LOG_ERROR("RecordInitialBufferStates: BeginCommandBuffer failed with %d", res); return; } - VkMemoryBarrier memory_barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; - memory_barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; - memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - device_table->CmdPipelineBarrier(restoration_command_buffer_, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, - 1, - &memory_barrier, - 0, - nullptr, - 0, - nullptr); - - uint32_t copy_count = 0; - GetObjectInfoTable().VisitVkBufferInfo([this, device, device_table, ©_count](const VulkanBufferInfo* info) { - if (info == nullptr || info->handle == VK_NULL_HANDLE || info->size == 0) + auto add_buffer = [this](format::HandleId buffer_id) { + if (buffer_id != format::kNullHandleId && !loop_touched_buffers_.contains(buffer_id)) { - return; + auto buf_info = GetObjectInfoTable().GetVkBufferInfo(buffer_id); + if (buf_info != nullptr && buf_info->handle != VK_NULL_HANDLE) + { + loop_touched_buffers_.insert(buffer_id); + } } + }; - auto dev_info = GetObjectInfoTable().GetVkDeviceInfo(info->parent_id); - if (dev_info == nullptr || dev_info->handle != device || dev_info->allocator == nullptr) - { - return; - } + for (auto buf_id : cb_info->bound_buffers) + { + add_buffer(buf_id); + } - auto phys_info = GetObjectInfoTable().GetVkPhysicalDeviceInfo(dev_info->parent_id); - if (phys_info == nullptr) + for (auto set_id : cb_info->bound_descriptor_sets) + { + auto set_info = GetObjectInfoTable().GetVkDescriptorSetInfo(set_id); + if (set_info != nullptr) { - return; + for (const auto& [binding_idx, binding] : set_info->descriptors) + { + if (binding.desc_type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || + binding.desc_type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC || + binding.desc_type == VK_DESCRIPTOR_TYPE_MUTABLE_EXT || + binding.desc_type == VK_DESCRIPTOR_TYPE_MAX_ENUM) + { + for (const auto& [arr_idx, buf_desc] : binding.buffer_info) + { + if (buf_desc.buffer_info != nullptr) + { + add_buffer(buf_desc.buffer_info->capture_id); + } + } + } + if (binding.desc_type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER || + binding.desc_type == VK_DESCRIPTOR_TYPE_MUTABLE_EXT || + binding.desc_type == VK_DESCRIPTOR_TYPE_MAX_ENUM) + { + for (const auto& [arr_idx, view_info] : binding.texel_buffer_view_info) + { + if (view_info != nullptr) + { + add_buffer(view_info->buffer_id); + } + } + } + } } + } - const VkPhysicalDeviceMemoryProperties* mem_props = &phys_info->capture_memory_properties; - if (phys_info->replay_device_info != nullptr && phys_info->replay_device_info->memory_properties.has_value()) - { - mem_props = &phys_info->replay_device_info->memory_properties.value(); - } + for (auto sec_id : cb_info->executed_secondary_command_buffers) + { + CollectTouchedBuffersFromCommandBuffer(sec_id, visited_cbs); + } +} - if (shadow_buffers_.find(info->capture_id) != shadow_buffers_.end()) - { - return; - } +VkResult VulkanReplayFrameLoopConsumer::CreateShadowBuffer(VkDevice device, + const VulkanBufferInfo* orig_info, + VkBuffer* shadow_buffer, + VkDeviceMemory* shadow_memory, + VulkanResourceAllocator::ResourceData* alloc_data, + VulkanResourceAllocator::MemoryData* mem_data) +{ + if (orig_info == nullptr || orig_info->size == 0) + { + return VK_ERROR_INITIALIZATION_FAILED; + } - VkBufferCreateInfo create_info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - create_info.size = info->size; - create_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + auto dev_info = GetObjectInfoTable().GetVkDeviceInfo(orig_info->parent_id); + if (dev_info == nullptr || dev_info->allocator == nullptr) + { + return VK_ERROR_INITIALIZATION_FAILED; + } - VkBuffer shadow_buf = VK_NULL_HANDLE; - VulkanResourceAllocator::ResourceData alloc_data = 0; - VkResult create_res = dev_info->allocator->CreateBufferDirect(&create_info, nullptr, &shadow_buf, &alloc_data); - if (create_res != VK_SUCCESS || shadow_buf == VK_NULL_HANDLE) - { - return; - } + auto phys_info = GetObjectInfoTable().GetVkPhysicalDeviceInfo(dev_info->parent_id); + if (phys_info == nullptr) + { + return VK_ERROR_INITIALIZATION_FAILED; + } - VkMemoryRequirements mem_reqs; - device_table->GetBufferMemoryRequirements(device, shadow_buf, &mem_reqs); + const VkPhysicalDeviceMemoryProperties* mem_props = &phys_info->capture_memory_properties; + if (phys_info->replay_device_info != nullptr && phys_info->replay_device_info->memory_properties.has_value()) + { + mem_props = &phys_info->replay_device_info->memory_properties.value(); + } - uint32_t mem_idx = - graphics::GetMemoryTypeIndex(*mem_props, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - if (mem_idx == std::numeric_limits::max()) + const graphics::VulkanDeviceTable* device_table = GetDeviceTable(device); + GFXRECON_ASSERT(device_table); + + VkBufferCreateInfo create_info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + create_info.size = orig_info->size; + create_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkBuffer shadow_buf = VK_NULL_HANDLE; + VulkanResourceAllocator::ResourceData shadow_alloc = 0; + VkResult res = dev_info->allocator->CreateBufferDirect(&create_info, nullptr, &shadow_buf, &shadow_alloc); + if (res != VK_SUCCESS || shadow_buf == VK_NULL_HANDLE) + { + return res; + } + + VkMemoryRequirements mem_reqs; + device_table->GetBufferMemoryRequirements(device, shadow_buf, &mem_reqs); + + uint32_t mem_idx = + graphics::GetMemoryTypeIndex(*mem_props, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (mem_idx == std::numeric_limits::max()) + { + mem_idx = graphics::GetMemoryTypeIndex(*mem_props, mem_reqs.memoryTypeBits, 0); + } + if (mem_idx == std::numeric_limits::max()) + { + dev_info->allocator->DestroyBufferDirect(shadow_buf, nullptr, shadow_alloc); + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + + VkMemoryAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + alloc_info.allocationSize = mem_reqs.size; + alloc_info.memoryTypeIndex = mem_idx; + + VkDeviceMemory shadow_mem = VK_NULL_HANDLE; + VulkanResourceAllocator::MemoryData shadow_mem_data = 0; + res = dev_info->allocator->AllocateMemoryDirect(&alloc_info, nullptr, &shadow_mem, &shadow_mem_data); + if (res != VK_SUCCESS || shadow_mem == VK_NULL_HANDLE) + { + dev_info->allocator->DestroyBufferDirect(shadow_buf, nullptr, shadow_alloc); + return res; + } + + VkMemoryPropertyFlags bind_props = 0; + res = dev_info->allocator->BindBufferMemoryDirect( + shadow_buf, shadow_mem, 0, shadow_alloc, shadow_mem_data, &bind_props); + if (res != VK_SUCCESS) + { + dev_info->allocator->FreeMemoryDirect(shadow_mem, nullptr, shadow_mem_data); + dev_info->allocator->DestroyBufferDirect(shadow_buf, nullptr, shadow_alloc); + return res; + } + + *shadow_buffer = shadow_buf; + *shadow_memory = shadow_mem; + *alloc_data = shadow_alloc; + *mem_data = shadow_mem_data; + + return VK_SUCCESS; +} + +void VulkanReplayFrameLoopConsumer::LazyBackupBuffersForSubmit(VkQueue queue, + uint32_t cb_count, + const format::HandleId* cb_ids) +{ + if (cb_count == 0 || cb_ids == nullptr) + { + return; + } + + VulkanQueueInfo* queue_info = nullptr; + GetObjectInfoTable().VisitVkQueueInfo([queue, &queue_info](const VulkanQueueInfo* q_info) { + if (q_info->handle == queue) { - mem_idx = graphics::GetMemoryTypeIndex(*mem_props, mem_reqs.memoryTypeBits, 0); + queue_info = const_cast(q_info); } - if (mem_idx == std::numeric_limits::max()) + }); + + if (queue_info == nullptr) + { + return; + } + + auto dev_info = GetObjectInfoTable().GetVkDeviceInfo(queue_info->parent_id); + if (dev_info == nullptr || dev_info->handle == VK_NULL_HANDLE) + { + return; + } + + VkDevice device = dev_info->handle; + const graphics::VulkanDeviceTable* device_table = GetDeviceTable(device); + + if (!InitializeRestorationResources(device, queue_info->family_index)) + { + return; + } + + std::unordered_set visited_cbs; + for (uint32_t i = 0; i < cb_count; ++i) + { + CollectTouchedBuffersFromCommandBuffer(cb_ids[i], visited_cbs); + } + + std::vector buffers_to_backup; + for (auto buffer_id : loop_touched_buffers_) + { + if (shadow_buffers_.find(buffer_id) != shadow_buffers_.end()) { - dev_info->allocator->DestroyBufferDirect(shadow_buf, nullptr, alloc_data); - return; + continue; } - VkMemoryAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; - alloc_info.allocationSize = mem_reqs.size; - alloc_info.memoryTypeIndex = mem_idx; - - VkDeviceMemory shadow_mem = VK_NULL_HANDLE; - VulkanResourceAllocator::MemoryData mem_data = 0; - VkResult alloc_res = dev_info->allocator->AllocateMemoryDirect(&alloc_info, nullptr, &shadow_mem, &mem_data); - if (alloc_res != VK_SUCCESS || shadow_mem == VK_NULL_HANDLE) + auto buf_info = GetObjectInfoTable().GetVkBufferInfo(buffer_id); + if (buf_info == nullptr || buf_info->handle == VK_NULL_HANDLE || buf_info->size == 0) { - dev_info->allocator->DestroyBufferDirect(shadow_buf, nullptr, alloc_data); - return; + continue; } - VkMemoryPropertyFlags bind_props = 0; - VkResult bind_res = - dev_info->allocator->BindBufferMemoryDirect(shadow_buf, shadow_mem, 0, alloc_data, mem_data, &bind_props); - if (bind_res != VK_SUCCESS) + auto buf_dev_info = GetObjectInfoTable().GetVkDeviceInfo(buf_info->parent_id); + if (buf_dev_info == nullptr || buf_dev_info->handle != device || buf_dev_info->allocator == nullptr) { - dev_info->allocator->FreeMemoryDirect(shadow_mem, nullptr, mem_data); - dev_info->allocator->DestroyBufferDirect(shadow_buf, nullptr, alloc_data); - return; + continue; } + buffers_to_backup.push_back(buf_info); + } + + if (buffers_to_backup.empty()) + { + return; + } + + VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VkResult res = device_table->BeginCommandBuffer(restoration_command_buffer_, &begin_info); + if (res != VK_SUCCESS) + { + GFXRECON_LOG_ERROR("LazyBackupBuffersForSubmit: BeginCommandBuffer failed with %d", res); + return; + } + + VkMemoryBarrier memory_barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; + memory_barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT; + memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + device_table->CmdPipelineBarrier(restoration_command_buffer_, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 1, + &memory_barrier, + 0, + nullptr, + 0, + nullptr); + + uint32_t copy_count = 0; + std::unordered_map staged_shadows; + for (const auto* buf_info : buffers_to_backup) + { ShadowBufferInfo shadow; - shadow.parent_id = info->parent_id; - shadow.shadow_buffer = shadow_buf; - shadow.shadow_memory = shadow_mem; - shadow.size = info->size; - shadow.alloc_data = alloc_data; - shadow.mem_data = mem_data; - shadow_buffers_[info->capture_id] = shadow; + shadow.parent_id = buf_info->parent_id; + shadow.size = buf_info->size; + VkResult create_res = CreateShadowBuffer( + device, buf_info, &shadow.shadow_buffer, &shadow.shadow_memory, &shadow.alloc_data, &shadow.mem_data); + if (create_res != VK_SUCCESS) + { + continue; + } + staged_shadows[buf_info->capture_id] = shadow; VkBufferCopy region = {}; region.srcOffset = 0; region.dstOffset = 0; - region.size = info->size; - device_table->CmdCopyBuffer(restoration_command_buffer_, info->handle, shadow_buf, 1, ®ion); + region.size = buf_info->size; + device_table->CmdCopyBuffer(restoration_command_buffer_, buf_info->handle, shadow.shadow_buffer, 1, ®ion); copy_count++; - }); + } res = device_table->EndCommandBuffer(restoration_command_buffer_); if (res == VK_SUCCESS && copy_count > 0 && queue_info != nullptr) @@ -1903,7 +2047,24 @@ void VulkanReplayFrameLoopConsumer::RecordInitialBufferStates(VkDevice if (submit_res == VK_SUCCESS) { device_table->QueueWaitIdle(queue_info->handle); - GFXRECON_LOG_INFO("RecordInitialBufferStates: Snapshotted %u buffers into shadow memory.", copy_count); + for (auto& [id, shadow] : staged_shadows) + { + shadow_buffers_[id] = shadow; + } + GFXRECON_LOG_INFO("LazyBackupBuffersForSubmit: Snapshotted %u buffers into shadow memory.", copy_count); + } + else + { + GFXRECON_LOG_ERROR("LazyBackupBuffersForSubmit: QueueSubmit failed with %d", submit_res); + for (auto& [id, shadow] : staged_shadows) + { + auto b_dev_info = GetObjectInfoTable().GetVkDeviceInfo(shadow.parent_id); + if (b_dev_info != nullptr && b_dev_info->allocator != nullptr) + { + b_dev_info->allocator->FreeMemoryDirect(shadow.shadow_memory, nullptr, shadow.mem_data); + b_dev_info->allocator->DestroyBufferDirect(shadow.shadow_buffer, nullptr, shadow.alloc_data); + } + } } } } @@ -1917,20 +2078,14 @@ void VulkanReplayFrameLoopConsumer::RestoreBufferStates(VkDevice return; } - if (!InitializeRestorationResources(device, queue_info->family_index)) + struct BufferRestoreData { - return; - } - - VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - VkResult res = device_table->BeginCommandBuffer(restoration_command_buffer_, &begin_info); - if (res != VK_SUCCESS) - { - return; - } + VkBuffer original_buffer; + VkBuffer shadow_buffer; + VkDeviceSize size; + }; + std::vector restores; - uint32_t restore_count = 0; for (const auto& [buf_id, shadow] : shadow_buffers_) { auto buf_info = GetObjectInfoTable().GetVkBufferInfo(buf_id); @@ -1942,36 +2097,59 @@ void VulkanReplayFrameLoopConsumer::RestoreBufferStates(VkDevice continue; } - VkBufferCopy region = {}; - region.srcOffset = 0; - region.dstOffset = 0; - region.size = shadow.size; - device_table->CmdCopyBuffer( - restoration_command_buffer_, shadow.shadow_buffer, buf_info->handle, 1, ®ion); - restore_count++; + BufferRestoreData data; + data.original_buffer = buf_info->handle; + data.shadow_buffer = shadow.shadow_buffer; + data.size = shadow.size; + restores.push_back(data); } } - if (restore_count > 0) + if (restores.empty()) { - VkMemoryBarrier memory_barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; - memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + return; + } - device_table->CmdPipelineBarrier(restoration_command_buffer_, - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - 0, - 1, - &memory_barrier, - 0, - nullptr, - 0, - nullptr); + if (!InitializeRestorationResources(device, queue_info->family_index)) + { + return; + } + + VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VkResult res = device_table->BeginCommandBuffer(restoration_command_buffer_, &begin_info); + if (res != VK_SUCCESS) + { + return; } + for (const auto& restore : restores) + { + VkBufferCopy region = {}; + region.srcOffset = 0; + region.dstOffset = 0; + region.size = restore.size; + device_table->CmdCopyBuffer( + restoration_command_buffer_, restore.shadow_buffer, restore.original_buffer, 1, ®ion); + } + + VkMemoryBarrier memory_barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; + memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + + device_table->CmdPipelineBarrier(restoration_command_buffer_, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, + 1, + &memory_barrier, + 0, + nullptr, + 0, + nullptr); + res = device_table->EndCommandBuffer(restoration_command_buffer_); - if (res == VK_SUCCESS && restore_count > 0) + if (res == VK_SUCCESS) { VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; submit_info.commandBufferCount = 1; @@ -2241,6 +2419,18 @@ void VulkanReplayFrameLoopConsumer::RecreateAndRebeginCommandBuffer(format::Hand } } +void VulkanReplayFrameLoopConsumer::TrackBoundBuffer(format::HandleId commandBuffer, format::HandleId buffer_id) +{ + if (buffer_id != format::kNullHandleId) + { + auto cb_info = GetObjectInfoTable().GetVkCommandBufferInfo(commandBuffer); + if (cb_info != nullptr) + { + cb_info->bound_buffers.push_back(buffer_id); + } + } +} + void VulkanReplayFrameLoopConsumer::Process_vkCmdExecuteCommands(const ApiCallInfo& call_info, format::HandleId commandBuffer, uint32_t commandBufferCount, @@ -2280,12 +2470,119 @@ void VulkanReplayFrameLoopConsumer::Process_vkCmdExecuteCommands(const ApiCallIn primary_cb_info->bound_descriptor_sets.insert(primary_cb_info->bound_descriptor_sets.end(), secondary_cb_info->bound_descriptor_sets.begin(), secondary_cb_info->bound_descriptor_sets.end()); + primary_cb_info->bound_buffers.insert(primary_cb_info->bound_buffers.end(), + secondary_cb_info->bound_buffers.begin(), + secondary_cb_info->bound_buffers.end()); } } } } } +void VulkanReplayFrameLoopConsumer::Process_vkCmdCopyBuffer(const ApiCallInfo& call_info, + format::HandleId commandBuffer, + format::HandleId srcBuffer, + format::HandleId dstBuffer, + uint32_t regionCount, + StructPointerDecoder* pRegions) +{ + if (ShouldIgnoreRecordingCommand(commandBuffer)) + { + return; + } + TrackBoundBuffer(commandBuffer, dstBuffer); + VulkanReplayConsumer::Process_vkCmdCopyBuffer( + call_info, commandBuffer, srcBuffer, dstBuffer, regionCount, pRegions); +} + +void VulkanReplayFrameLoopConsumer::Process_vkCmdCopyImageToBuffer( + const ApiCallInfo& call_info, + format::HandleId commandBuffer, + format::HandleId srcImage, + VkImageLayout srcImageLayout, + format::HandleId dstBuffer, + uint32_t regionCount, + StructPointerDecoder* pRegions) +{ + if (ShouldIgnoreRecordingCommand(commandBuffer)) + { + return; + } + TrackBoundBuffer(commandBuffer, dstBuffer); + VulkanReplayConsumer::Process_vkCmdCopyImageToBuffer( + call_info, commandBuffer, srcImage, srcImageLayout, dstBuffer, regionCount, pRegions); +} + +void VulkanReplayFrameLoopConsumer::Process_vkCmdUpdateBuffer(const ApiCallInfo& call_info, + format::HandleId commandBuffer, + format::HandleId dstBuffer, + VkDeviceSize dstOffset, + VkDeviceSize dataSize, + PointerDecoder* pData) +{ + if (ShouldIgnoreRecordingCommand(commandBuffer)) + { + return; + } + TrackBoundBuffer(commandBuffer, dstBuffer); + VulkanReplayConsumer::Process_vkCmdUpdateBuffer(call_info, commandBuffer, dstBuffer, dstOffset, dataSize, pData); +} + +void VulkanReplayFrameLoopConsumer::Process_vkCmdFillBuffer(const ApiCallInfo& call_info, + format::HandleId commandBuffer, + format::HandleId dstBuffer, + VkDeviceSize dstOffset, + VkDeviceSize size, + uint32_t data) +{ + if (ShouldIgnoreRecordingCommand(commandBuffer)) + { + return; + } + TrackBoundBuffer(commandBuffer, dstBuffer); + VulkanReplayConsumer::Process_vkCmdFillBuffer(call_info, commandBuffer, dstBuffer, dstOffset, size, data); +} + +void VulkanReplayFrameLoopConsumer::Process_vkCmdPipelineBarrier( + const ApiCallInfo& call_info, + format::HandleId commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + uint32_t memoryBarrierCount, + StructPointerDecoder* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + StructPointerDecoder* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + StructPointerDecoder* pImageMemoryBarriers) +{ + if (ShouldIgnoreRecordingCommand(commandBuffer)) + { + return; + } + if (pBufferMemoryBarriers != nullptr && pBufferMemoryBarriers->GetMetaStructPointer() != nullptr) + { + const auto* barriers = pBufferMemoryBarriers->GetMetaStructPointer(); + const size_t bound_count = + std::min(static_cast(bufferMemoryBarrierCount), pBufferMemoryBarriers->GetLength()); + for (size_t i = 0; i < bound_count; ++i) + { + TrackBoundBuffer(commandBuffer, barriers[i].buffer); + } + } + VulkanReplayConsumer::Process_vkCmdPipelineBarrier(call_info, + commandBuffer, + srcStageMask, + dstStageMask, + dependencyFlags, + memoryBarrierCount, + pMemoryBarriers, + bufferMemoryBarrierCount, + pBufferMemoryBarriers, + imageMemoryBarrierCount, + pImageMemoryBarriers); +} + void VulkanReplayFrameLoopConsumer::Process_vkCreateEvent( const ApiCallInfo& call_info, VkResult returnValue, @@ -2510,7 +2807,13 @@ void VulkanReplayFrameLoopConsumer::LazyBackupImagesForSubmit(VkQueue } VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - device_table->BeginCommandBuffer(restoration_command_buffer_, &begin_info); + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VkResult begin_res = device_table->BeginCommandBuffer(restoration_command_buffer_, &begin_info); + if (begin_res != VK_SUCCESS) + { + GFXRECON_LOG_ERROR("LazyBackupImagesForSubmit: BeginCommandBuffer failed with %d", begin_res); + return; + } for (auto image_id : images_to_backup) { @@ -2661,7 +2964,13 @@ void VulkanReplayFrameLoopConsumer::RestoreImageContents(VkDevice } VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - device_table->BeginCommandBuffer(restoration_command_buffer_, &begin_info); + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VkResult begin_res = device_table->BeginCommandBuffer(restoration_command_buffer_, &begin_info); + if (begin_res != VK_SUCCESS) + { + GFXRECON_LOG_ERROR("RestoreImageContents: BeginCommandBuffer failed with %d", begin_res); + return; + } std::vector pre_barriers; std::vector post_barriers; @@ -3050,26 +3359,34 @@ void VulkanReplayFrameLoopConsumer::FixupLoopBoundarySemaphores() VkCommandBuffer boundary_cb = VK_NULL_HANDLE; if (restoration_command_pool_ != VK_NULL_HANDLE) { - device_table->AllocateCommandBuffers(active_device_, &alloc_info, &boundary_cb); - if (boundary_cb != VK_NULL_HANDLE) + VkResult alloc_res = device_table->AllocateCommandBuffers(active_device_, &alloc_info, &boundary_cb); + if (alloc_res == VK_SUCCESS && boundary_cb != VK_NULL_HANDLE) { VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - device_table->BeginCommandBuffer(boundary_cb, &begin_info); - VkMemoryBarrier barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = 0; - device_table->CmdPipelineBarrier(boundary_cb, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, - 1, - &barrier, - 0, - nullptr, - 0, - nullptr); - device_table->EndCommandBuffer(boundary_cb); + VkResult begin_res = device_table->BeginCommandBuffer(boundary_cb, &begin_info); + if (begin_res == VK_SUCCESS) + { + VkMemoryBarrier barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = 0; + device_table->CmdPipelineBarrier(boundary_cb, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, + 1, + &barrier, + 0, + nullptr, + 0, + nullptr); + device_table->EndCommandBuffer(boundary_cb); + } + else + { + device_table->FreeCommandBuffers(active_device_, restoration_command_pool_, 1, &boundary_cb); + boundary_cb = VK_NULL_HANDLE; + } } } diff --git a/framework/decode/vulkan_replay_frame_loop_consumer.h b/framework/decode/vulkan_replay_frame_loop_consumer.h index b5120b9ba1..076b7229fc 100644 --- a/framework/decode/vulkan_replay_frame_loop_consumer.h +++ b/framework/decode/vulkan_replay_frame_loop_consumer.h @@ -186,6 +186,48 @@ class VulkanReplayFrameLoopConsumer : public VulkanReplayFrameLoopConsumerBase, uint32_t commandBufferCount, HandlePointerDecoder* pCommandBuffers) override; + void Process_vkCmdCopyBuffer(const ApiCallInfo& call_info, + format::HandleId commandBuffer, + format::HandleId srcBuffer, + format::HandleId dstBuffer, + uint32_t regionCount, + StructPointerDecoder* pRegions) override; + + void Process_vkCmdCopyImageToBuffer(const ApiCallInfo& call_info, + format::HandleId commandBuffer, + format::HandleId srcImage, + VkImageLayout srcImageLayout, + format::HandleId dstBuffer, + uint32_t regionCount, + StructPointerDecoder* pRegions) override; + + void Process_vkCmdUpdateBuffer(const ApiCallInfo& call_info, + format::HandleId commandBuffer, + format::HandleId dstBuffer, + VkDeviceSize dstOffset, + VkDeviceSize dataSize, + PointerDecoder* pData) override; + + void Process_vkCmdFillBuffer(const ApiCallInfo& call_info, + format::HandleId commandBuffer, + format::HandleId dstBuffer, + VkDeviceSize dstOffset, + VkDeviceSize size, + uint32_t data) override; + + void Process_vkCmdPipelineBarrier( + const ApiCallInfo& call_info, + format::HandleId commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + uint32_t memoryBarrierCount, + StructPointerDecoder* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + StructPointerDecoder* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + StructPointerDecoder* pImageMemoryBarriers) override; + virtual void Process_vkCreateEvent(const ApiCallInfo& call_info, VkResult returnValue, format::HandleId device, @@ -289,6 +331,7 @@ class VulkanReplayFrameLoopConsumer : public VulkanReplayFrameLoopConsumerBase, VkCommandPool restoration_command_pool_{ VK_NULL_HANDLE }; VkCommandBuffer restoration_command_buffer_{ VK_NULL_HANDLE }; VkDevice restoration_device_{ VK_NULL_HANDLE }; + uint32_t restoration_queue_family_index_{ std::numeric_limits::max() }; // Buffer restoration data struct ShadowBufferInfo @@ -331,9 +374,19 @@ class VulkanReplayFrameLoopConsumer : public VulkanReplayFrameLoopConsumerBase, VulkanResourceAllocator::ResourceData* alloc_data, VulkanResourceAllocator::MemoryData* mem_data); - void RecordInitialBufferStates(VkDevice device, - const graphics::VulkanDeviceTable* device_table, - VulkanQueueInfo* queue_info); + VkResult CreateShadowBuffer(VkDevice device, + const VulkanBufferInfo* orig_info, + VkBuffer* shadow_buffer, + VkDeviceMemory* shadow_memory, + VulkanResourceAllocator::ResourceData* alloc_data, + VulkanResourceAllocator::MemoryData* mem_data); + + std::unordered_set loop_touched_buffers_; + + void TrackBoundBuffer(format::HandleId commandBuffer, format::HandleId buffer_id); + void CollectTouchedBuffersFromCommandBuffer(format::HandleId cb_id, + std::unordered_set& visited_cbs); + void LazyBackupBuffersForSubmit(VkQueue queue, uint32_t cb_count, const format::HandleId* cb_ids); void RestoreBufferStates(VkDevice device, const graphics::VulkanDeviceTable* device_table, VulkanQueueInfo* queue_info);