From dac0262f484bf6bb9435ac7ce06a35c12a979a5e Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Mon, 7 Sep 2026 14:53:08 +0100 Subject: [PATCH] vulkan: Fix crash during async pipeline compilation and handle destruction - Track handles used by asynchronous compilation tasks using reference-counted task IDs, synchronizing all referencing tasks in DestroyAsyncHandle and only releasing shared handles when all tasks finish to prevent use-after-free. - Consolidate handle destruction across VkPipeline, VkPipelineLayout, VkRenderPass, and VkShaderModule using DestroyTrackedHandle to defer destruction until referencing async tasks complete. - Safely track and destroy associated VkPipelineCache instances across batched and shared pipeline creations, avoiding premature destruction. - Enforce bounds assertions in sync_handle and log fatal on worker exceptions to prevent silent invalid handle propagation. - Capture pipeline handles by value in completion callbacks to avoid dangling pointer references when futures are cleared. Bug: Test: existing Change-Id: I95e28781a53afd8d2d409d2a8ce343196a6a6964 --- framework/decode/common_handle_mapping_util.h | 9 - .../decode/vulkan_object_info_table_base.h | 18 +- .../decode/vulkan_replay_consumer_base.cpp | 306 ++++++++++-------- .../decode/vulkan_replay_consumer_base.h | 71 +++- .../generated_vulkan_replay_consumer.cpp | 7 +- .../vulkan_generators/replay_overrides.json | 1 + 6 files changed, 247 insertions(+), 165 deletions(-) diff --git a/framework/decode/common_handle_mapping_util.h b/framework/decode/common_handle_mapping_util.h index d6f62d178b..cd4ea4dd43 100644 --- a/framework/decode/common_handle_mapping_util.h +++ b/framework/decode/common_handle_mapping_util.h @@ -83,15 +83,6 @@ static typename T::HandleType* MapHandleArray(HandlePointerDecoderhandle; - - if constexpr (has_handle_future_v) - { - if (info->handle == VK_NULL_HANDLE && info->future.valid()) - { - const auto& [result, async_handles] = info->future.get(); - handles[i] = async_handles[info->future_handle_index]; - } - } } else { diff --git a/framework/decode/vulkan_object_info_table_base.h b/framework/decode/vulkan_object_info_table_base.h index 479d4cf8c2..73cf150dfe 100644 --- a/framework/decode/vulkan_object_info_table_base.h +++ b/framework/decode/vulkan_object_info_table_base.h @@ -27,6 +27,7 @@ #include "decode/vulkan_object_info.h" #include "format/format.h" #include "util/defines.h" +#include "util/logging.h" #include "vulkan/vulkan.h" @@ -62,8 +63,21 @@ static inline void sync_handle(T* object) static_assert(has_handle_future_v); if (object != nullptr && object->handle == VK_NULL_HANDLE && object->future.valid()) { - const auto& [result, async_handles] = object->future.get(); - object->handle = async_handles[object->future_handle_index]; + try + { + const auto& [result, async_handles] = object->future.get(); + GFXRECON_ASSERT(object->future_handle_index < async_handles.size()); + object->handle = async_handles[object->future_handle_index]; + } + catch (const std::exception& e) + { + // Catch std::exception to prevent worker-thread exceptions from escaping silently + // or causing undefined behavior during subsequent replay. + GFXRECON_LOG_FATAL("Future error while synchronizing handle: %s", e.what()); + } + + // Release shared state and prevent redundant future.get() synchronization on subsequent lookups. + object->future = {}; } } diff --git a/framework/decode/vulkan_replay_consumer_base.cpp b/framework/decode/vulkan_replay_consumer_base.cpp index d60b24359e..124d8f1b6c 100644 --- a/framework/decode/vulkan_replay_consumer_base.cpp +++ b/framework/decode/vulkan_replay_consumer_base.cpp @@ -7957,13 +7957,12 @@ VkResult VulkanReplayConsumerBase::OverrideSetDebugUtilsObjectNameEXT( size_t info_size = graphics::vulkan_struct_deep_copy(info, 1, nullptr); std::vector info_copy(info_size); graphics::vulkan_struct_deep_copy(info, 1, info_copy.data()); - auto& async_handle_asset = async_tracked_handles_[pipeline_id]; - async_handle_asset.post_build_fn = [this, - device = device_info->handle, - pipeline_id, - func, - info_copy = std::move(info_copy), - original_result]() mutable { + async_tracked_handles_[pipeline_id].post_build_fn = [this, + device = device_info->handle, + pipeline_id, + func, + info_copy = std::move(info_copy), + original_result]() mutable { auto* info = reinterpret_cast(info_copy.data()); // correct referenced handle in info. this would sync, but task is already done @@ -12740,69 +12739,69 @@ VkResult VulkanReplayConsumerBase::OverrideCreateShadersEXT( return replay_result; } -void VulkanReplayConsumerBase::OverrideDestroyPipeline( - PFN_vkDestroyPipeline func, - const VulkanDeviceInfo* device_info, - const VulkanPipelineInfo* pipeline_info, - const StructPointerDecoder* pAllocator) +void VulkanReplayConsumerBase::DestroyAssociatedPipelineCache(const VulkanDeviceInfo* device_info, VkPipeline pipeline) { - GFXRECON_ASSERT(device_info != nullptr); - VkDevice in_device = device_info->handle; - VkPipeline in_pipeline = VK_NULL_HANDLE; - const VkAllocationCallbacks* in_pAllocator = GetAllocationCallbacks(pAllocator); + if (pipeline == VK_NULL_HANDLE) + { + return; + } - if (pipeline_info != nullptr) + auto itCorresp = pipeline_cache_correspondances_.find(pipeline); + if (itCorresp != pipeline_cache_correspondances_.end()) { - in_pipeline = - MapHandle(pipeline_info->capture_id, &VulkanObjectInfoTable::GetVkPipelineInfo); + format::HandleId id = itCorresp->second; + pipeline_cache_correspondances_.erase(itCorresp); - if (IsUsedByAsyncTask(pipeline_info->capture_id)) + // For batched pipeline creations or shared pipeline caches, multiple pipelines + // reference the same pipeline cache ID. Do not destroy the driver cache until the last pipeline is destroyed. + bool sameIdFound = false; + for (const auto& elt : pipeline_cache_correspondances_) { - // schedule deletion - DestroyAsyncHandle(pipeline_info->capture_id, [func, in_device, in_pipeline, in_pAllocator]() { - func(in_device, in_pipeline, in_pAllocator); - }); - return; + if (elt.second == id) + { + sameIdFound = true; + break; + } } - // Check if the pipeline has been created with a specially created pipeline cache - auto itCorresp = pipeline_cache_correspondances_.find(pipeline_info->handle); - if (itCorresp != pipeline_cache_correspondances_.end()) + // If this is the only remaining pipeline bound to the pipeline cache, save and destroy the pipeline cache + if (!sameIdFound) { - format::HandleId id = itCorresp->second; - pipeline_cache_correspondances_.erase(itCorresp); + auto itTracked = tracked_pipeline_caches_.find(id); + GFXRECON_ASSERT(itTracked != tracked_pipeline_caches_.end()); + GFXRECON_ASSERT(itTracked->second.device_info != nullptr); + GFXRECON_ASSERT(itTracked->second.vk_cache != VK_NULL_HANDLE); - // Find if other pipelines have been created with the same pipeline cache - bool sameIdFound = false; - for (const auto& elt : pipeline_cache_correspondances_) - { - if (elt.second == id) - { - sameIdFound = true; - break; - } - } - - // If this is the only remaining pipeline bound to the pipeline cache, save and destroy the pipeline cache - if (!sameIdFound) + if (save_pipeline_caches_to_file) { - auto itTracked = tracked_pipeline_caches_.find(id); - GFXRECON_ASSERT(itTracked != tracked_pipeline_caches_.end()); - GFXRECON_ASSERT(itTracked->second.device_info != nullptr); - GFXRECON_ASSERT(itTracked->second.vk_cache != VK_NULL_HANDLE); - - if (save_pipeline_caches_to_file) - { - SavePipelineCache(id, itTracked->second); - } - auto device_table = GetDeviceTable(device_info->handle); - device_table->DestroyPipelineCache( - itTracked->second.device_info->handle, itTracked->second.vk_cache, nullptr); - tracked_pipeline_caches_.erase(itTracked); + SavePipelineCache(id, itTracked->second); } + auto device_table = GetDeviceTable(device_info->handle); + device_table->DestroyPipelineCache( + itTracked->second.device_info->handle, itTracked->second.vk_cache, nullptr); + tracked_pipeline_caches_.erase(itTracked); } } - func(in_device, in_pipeline, in_pAllocator); +} + +void VulkanReplayConsumerBase::OverrideDestroyPipeline( + PFN_vkDestroyPipeline func, + const VulkanDeviceInfo* device_info, + const VulkanPipelineInfo* pipeline_info, + const StructPointerDecoder* pAllocator) +{ + DestroyTrackedHandle(func, device_info, pipeline_info, pAllocator, [this, device_info](VkPipeline p) { + DestroyAssociatedPipelineCache(device_info, p); + }); +} + +void VulkanReplayConsumerBase::OverrideDestroyPipelineLayout( + PFN_vkDestroyPipelineLayout func, + const VulkanDeviceInfo* device_info, + VulkanPipelineLayoutInfo* pipeline_layout_info, + const StructPointerDecoder* pAllocator) +{ + DestroyTrackedHandle(func, device_info, pipeline_layout_info, pAllocator); } void VulkanReplayConsumerBase::OverrideDestroyRenderPass( @@ -12811,24 +12810,7 @@ void VulkanReplayConsumerBase::OverrideDestroyRenderPass( VulkanRenderPassInfo* renderpass_info, const StructPointerDecoder* pAllocator) { - VkDevice in_device = device_info->handle; - VkRenderPass in_renderpass = VK_NULL_HANDLE; - const VkAllocationCallbacks* in_pAllocator = GetAllocationCallbacks(pAllocator); - - if (renderpass_info != nullptr) - { - in_renderpass = renderpass_info->handle; - - if (IsUsedByAsyncTask(renderpass_info->capture_id)) - { - // schedule deletion - DestroyAsyncHandle(renderpass_info->capture_id, [func, in_device, in_renderpass, in_pAllocator]() { - func(in_device, in_renderpass, in_pAllocator); - }); - return; - } - } - func(in_device, in_renderpass, in_pAllocator); + DestroyTrackedHandle(func, device_info, renderpass_info, pAllocator); } void VulkanReplayConsumerBase::OverrideDestroyShaderModule( @@ -12837,24 +12819,7 @@ void VulkanReplayConsumerBase::OverrideDestroyShaderModule( VulkanShaderModuleInfo* shader_module_info, const StructPointerDecoder* pAllocator) { - VkDevice in_device = device_info->handle; - VkShaderModule in_shader_module = VK_NULL_HANDLE; - const VkAllocationCallbacks* in_pAllocator = GetAllocationCallbacks(pAllocator); - - if (shader_module_info != nullptr) - { - in_shader_module = shader_module_info->handle; - - if (IsUsedByAsyncTask(shader_module_info->capture_id)) - { - // schedule deletion - DestroyAsyncHandle(shader_module_info->capture_id, [func, in_device, in_shader_module, in_pAllocator]() { - func(in_device, in_shader_module, in_pAllocator); - }); - return; - } - } - func(in_device, in_shader_module, in_pAllocator); + DestroyTrackedHandle(func, device_info, shader_module_info, pAllocator); } VkResult VulkanReplayConsumerBase::OverrideGetPastPresentationTimingGOOGLE( @@ -13016,7 +12981,7 @@ std::function()> VulkanReplayConsumer MapHandle(parent_id, &VulkanObjectInfoTable::GetVkPipelineInfo); }; } - TrackAsyncHandles(handle_deps, sync_fn); + uint64_t async_task_id = TrackAsyncHandles(handle_deps, sync_fn); // define pipeline-creation task, assert object-lifetimes by copying/moving into closure auto task = [this, @@ -13028,6 +12993,7 @@ std::function()> VulkanReplayConsumer call_info, in_pAllocator, createInfoCount, + async_task_id, create_info_data = std::move(create_info_data), handle_deps = std::move(handle_deps), pipeline_ids = std::move(pipeline_ids)]() mutable -> handle_create_result_t { @@ -13052,16 +13018,18 @@ std::function()> VulkanReplayConsumer pipeline_cache, cache_pipeline_id, replay_result, - pipeline_handles = out_pipelines.data(), - num_pipelines = out_pipelines.size(), - handle_deps = std::move(handle_deps)] { + pipeline_handles = out_pipelines, + async_task_id, + handle_deps = std::move(handle_deps)]() mutable { // asynchronous operation is done. clear tracked handles, call deferred deletes - ClearAsyncHandles(handle_deps); + ClearAsyncHandles(async_task_id, handle_deps); // if a pipeline cache was created, track it to know when to destroy it/save it to file if (cache_pipeline_id != format::kNullHandleId && replay_result == VK_SUCCESS) { - TrackNewPipelineCache(device_info, cache_pipeline_id, pipeline_cache, pipeline_handles, num_pipelines); + // Copy pipeline_handles: the result vector is freed once every info in the batch has synced its future. + TrackNewPipelineCache( + device_info, cache_pipeline_id, pipeline_cache, pipeline_handles.data(), pipeline_handles.size()); } }); return { replay_result, std::move(out_pipelines) }; @@ -13140,7 +13108,7 @@ std::function()> VulkanReplayConsumerBase::As MapHandle(parent_id, &VulkanObjectInfoTable::GetVkPipelineInfo); }; } - TrackAsyncHandles(handle_deps, sync_fn); + uint64_t async_task_id = TrackAsyncHandles(handle_deps, sync_fn); // define pipeline-creation task, assert object-lifetimes by copying/moving into closure auto task = [this, @@ -13152,6 +13120,7 @@ std::function()> VulkanReplayConsumerBase::As call_info, in_pAllocator, createInfoCount, + async_task_id, create_info_data = std::move(create_info_data), handle_deps = std::move(handle_deps), pipeline_ids = std::move(pipeline_ids)]() mutable -> handle_create_result_t { @@ -13169,16 +13138,18 @@ std::function()> VulkanReplayConsumerBase::As pipeline_cache, cache_pipeline_id, replay_result, - pipeline_handles = out_pipelines.data(), - num_pipelines = out_pipelines.size(), - handle_deps = std::move(handle_deps)] { + pipeline_handles = out_pipelines, + async_task_id, + handle_deps = std::move(handle_deps)]() mutable { // asynchronous operation is done. clear tracked handles, call deferred deletes - ClearAsyncHandles(handle_deps); + ClearAsyncHandles(async_task_id, handle_deps); // if a pipeline cache was created, track it to know when to destroy it/save it to file if (cache_pipeline_id != format::kNullHandleId && replay_result == VK_SUCCESS) { - TrackNewPipelineCache(device_info, cache_pipeline_id, pipeline_cache, pipeline_handles, num_pipelines); + // Copy pipeline_handles: the result vector is freed once every info in the batch has synced its future. + TrackNewPipelineCache( + device_info, cache_pipeline_id, pipeline_cache, pipeline_handles.data(), pipeline_handles.size()); } }); return { replay_result, std::move(out_pipelines) }; @@ -13217,7 +13188,7 @@ VulkanReplayConsumerBase::AsyncCreateShadersEXT(PFN_vkCreateShadersEXT MapHandle(parent_id, &VulkanObjectInfoTable::GetVkShaderEXTInfo); }; } - TrackAsyncHandles(handle_deps, sync_fn); + uint64_t async_task_id = TrackAsyncHandles(handle_deps, sync_fn); // assemble array of info-structs std::vector shader_ext_infos(pShaders->GetLength()); @@ -13237,6 +13208,7 @@ VulkanReplayConsumerBase::AsyncCreateShadersEXT(PFN_vkCreateShadersEXT in_pAllocator, use_address_replacement, createInfoCount, + async_task_id, create_info_data = std::move(create_info_data), handle_deps = std::move(handle_deps), shaders = std::move(shaders), @@ -13269,39 +13241,63 @@ VulkanReplayConsumerBase::AsyncCreateShadersEXT(PFN_vkCreateShadersEXT } // schedule dependency-clear on main-thread - MainThreadQueue().post([this, handle_deps = std::move(handle_deps)] { ClearAsyncHandles(handle_deps); }); + MainThreadQueue().post([this, async_task_id, handle_deps = std::move(handle_deps)] { + ClearAsyncHandles(async_task_id, handle_deps); + }); return { replay_result, std::move(out_shaders) }; }; return task; } -void VulkanReplayConsumerBase::TrackAsyncHandles(const std::unordered_set& async_handles, - const std::function& sync_fn) +bool VulkanReplayConsumerBase::IsUsedByAsyncTask(format::HandleId handle) const { + auto it = async_tracked_handles_.find(handle); + return it != async_tracked_handles_.end() && !it->second.async_task_ids.empty(); +} + +uint64_t VulkanReplayConsumerBase::TrackAsyncHandles(const std::unordered_set& async_handles, + const std::function& sync_fn) +{ + uint64_t async_task_id = next_async_task_id_++; + if (sync_fn) + { + async_task_sync_fns_[async_task_id] = sync_fn; + } + for (const auto& handle : async_handles) { - // check to avoid overwriting existing handle-destructors - if (async_tracked_handles_.count(handle) == 0) + if (handle != format::kNullHandleId) { - async_tracked_handles_[handle] = { sync_fn, {} }; + async_tracked_handles_[handle].async_task_ids.insert(async_task_id); } } + return async_task_id; } -void VulkanReplayConsumerBase::ClearAsyncHandles(const std::unordered_set& async_handles) +void VulkanReplayConsumerBase::ClearAsyncHandles(uint64_t async_task_id, + const std::unordered_set& async_handles) { + async_task_sync_fns_.erase(async_task_id); + for (const auto& handle : async_handles) { + if (handle == format::kNullHandleId) + { + continue; + } + auto it = async_tracked_handles_.find(handle); if (it != async_tracked_handles_.end()) { - const auto& [tracked_handle, handle_asset] = *it; - - if (handle_asset.post_build_fn) + it->second.async_task_ids.erase(async_task_id); + if (it->second.async_task_ids.empty()) { - handle_asset.post_build_fn(); + if (it->second.post_build_fn) + { + it->second.post_build_fn(); + } + async_tracked_handles_.erase(it); } - async_tracked_handles_.erase(it); } } } @@ -13312,29 +13308,69 @@ void VulkanReplayConsumerBase::DestroyAsyncHandle(format::HandleId handle, std:: if (it != async_tracked_handles_.end()) { - async_tracked_handle_asset_t& handle_asset = it->second; + // Snapshot the set of active async task IDs referencing this handle + auto task_ids = it->second.async_task_ids; + for (uint64_t tid : task_ids) + { + auto sync_it = async_task_sync_fns_.find(tid); + if (sync_it != async_task_sync_fns_.end() && sync_it->second) + { + sync_it->second(); + } + } + + // Synchronizing tasks above signals completion, but post-creation tasks (TrackNewPipelineCache, + // ClearAsyncHandles) were dispatched to MainThreadQueue(). Draining the queue guarantees that + // pipeline-to-cache correspondences are registered before destroy_fn() executes. + main_thread_queue_.poll(); + + // Every synced task has drained and erased its IDs by now. + GFXRECON_ASSERT(async_tracked_handles_.find(handle) == async_tracked_handles_.end()); - if constexpr (async_defer_deletion_) + if (destroy_fn) { - handle_asset.post_build_fn = std::move(destroy_fn); + destroy_fn(); } - else + } +} + +template +void VulkanReplayConsumerBase::DestroyTrackedHandle( + DestroyFunc func, + const VulkanDeviceInfo* device_info, + const InfoType* object_info, + const StructPointerDecoder* pAllocator, + PreDestroyHook pre_destroy_hook) +{ + GFXRECON_ASSERT(device_info != nullptr); + VkDevice in_device = device_info->handle; + typename InfoType::HandleType in_handle = VK_NULL_HANDLE; + const VkAllocationCallbacks* in_pAllocator = GetAllocationCallbacks(pAllocator); + + if (object_info != nullptr) + { + in_handle = object_info->handle; + + if (IsUsedByAsyncTask(object_info->capture_id)) { - if (handle_asset.sync_fn) - { - handle_asset.sync_fn(); - } - if (handle_asset.post_build_fn) - { - handle_asset.post_build_fn(); - } - if (destroy_fn) - { - destroy_fn(); - } - async_tracked_handles_.erase(it); + auto capture_id = object_info->capture_id; + DestroyAsyncHandle(capture_id, [func, in_device, in_handle, in_pAllocator, pre_destroy_hook]() { + if constexpr (!std::is_same_v) + { + pre_destroy_hook(in_handle); + } + func(in_device, in_handle, in_pAllocator); + }); + return; + } + + if constexpr (!std::is_same_v) + { + pre_destroy_hook(in_handle); } } + + func(in_device, in_handle, in_pAllocator); } void VulkanReplayConsumerBase::SetCurrentBlockIndex(uint64_t block_index) diff --git a/framework/decode/vulkan_replay_consumer_base.h b/framework/decode/vulkan_replay_consumer_base.h index 70b3c79823..e28b796ece 100644 --- a/framework/decode/vulkan_replay_consumer_base.h +++ b/framework/decode/vulkan_replay_consumer_base.h @@ -447,18 +447,19 @@ class VulkanReplayConsumerBase : public VulkanConsumer } //! track arbitrary handles that are currently used by asynchronous operations - void TrackAsyncHandles(const std::unordered_set& async_handles, - const std::function& sync_fn); + uint64_t TrackAsyncHandles(const std::unordered_set& async_handles, + const std::function& sync_fn); - //! clear handles that are currently used by asynchronous operations, - //! invoke stored deletion-functions - void ClearAsyncHandles(const std::unordered_set& async_handles); + //! Clear handle references for the given async task, and invoke deferred post-build + //! callbacks once all referencing tasks have finished. + void ClearAsyncHandles(uint64_t async_task_id, const std::unordered_set& async_handles); //! schedules deletion of already tracked handles + //! synchronizes all active async tasks referencing the handle and invokes the destroy function void DestroyAsyncHandle(format::HandleId handle, std::function destroy_fn); //! return true if this handle is currently being tracked (was passed to 'TrackAsyncHandles' earlier) - bool IsUsedByAsyncTask(uint64_t handle) const { return async_tracked_handles_.count(handle) > 0; } + bool IsUsedByAsyncTask(format::HandleId handle) const; //! returns true if asynchronous operations should be used at all bool UseAsyncOperations() { return options_.num_pipeline_creation_jobs != 0; } @@ -1529,6 +1530,11 @@ class VulkanReplayConsumerBase : public VulkanConsumer const VulkanPipelineInfo* pipeline_info, const StructPointerDecoder* pAllocator); + void OverrideDestroyPipelineLayout(PFN_vkDestroyPipelineLayout func, + const VulkanDeviceInfo* device_info, + VulkanPipelineLayoutInfo* pipeline_layout_info, + const StructPointerDecoder* pAllocator); + void OverrideDestroyRenderPass(PFN_vkDestroyRenderPass func, const VulkanDeviceInfo* device_info, VulkanRenderPassInfo* renderpass_info, @@ -1973,6 +1979,34 @@ class VulkanReplayConsumerBase : public VulkanConsumer template static void RemoveFailOnCompileRequiredFlags(CreateInfo* create_infos, uint32_t create_info_count); + /** + * @brief DestroyTrackedHandle handles synchronous or deferred destruction of Vulkan handles + * (e.g., VkPipeline, VkPipelineLayout, VkRenderPass, VkShaderModule) tracked across replay. + * + * If the handle is referenced by active asynchronous tasks (e.g. concurrent pipeline compilation), + * destruction is deferred via DestroyAsyncHandle until all referencing tasks complete. Otherwise, + * it is destroyed immediately. + * + * For pipelines, if compilation completed asynchronously in the background, the handle is resolved + * via MapHandle prior to destruction. An optional PreDestroyHook can be provided to execute cleanup + * (such as destroying associated pipeline caches) immediately before driver destruction. + * + * @tparam InfoType Object info wrapper struct (e.g., VulkanPipelineInfo) + * @tparam DestroyFunc Vulkan API destroy function pointer type + * @tparam PreDestroyHook Optional callable invoked with the resolved handle prior to driver destruction + * @param func Vulkan API destroy function (e.g., vkDestroyPipeline) + * @param device_info VulkanDeviceInfo struct for the owning logical device + * @param object_info Object info struct containing the handle and capture ID + * @param pAllocator Optional Vulkan allocation callbacks + * @param pre_destroy_hook Optional callback invoked before calling func + */ + template + void DestroyTrackedHandle(DestroyFunc func, + const VulkanDeviceInfo* device_info, + const InfoType* object_info, + const StructPointerDecoder* pAllocator, + PreDestroyHook pre_destroy_hook = nullptr); + private: util::platform::LibraryHandle loader_handle_; PFN_vkGetInstanceProcAddr get_instance_proc_addr_; @@ -2003,20 +2037,26 @@ class VulkanReplayConsumerBase : public VulkanConsumer util::ThreadPool main_thread_queue_; util::ThreadPool background_queue_; - //! async_tracked_handle_asset_t groups assets used by tracked async-dependencies - struct async_tracked_handle_asset_t + //! Asynchronous task and handle tracking: + //! Multiple background compilation tasks can concurrently reference shared handles + //! (e.g. VkPipelineLayout, VkRenderPass, VkShaderModule). Handles track the set of + //! active async task IDs referencing them to maintain reference counts and allow + //! synchronizing all referencing tasks prior to handle destruction. + uint64_t next_async_task_id_{ 1 }; + std::unordered_map> async_task_sync_fns_; + + //! Tracks active asynchronous tasks and deferred operations for a Vulkan handle + struct AsyncTrackedHandle { - //! function to synchronize (blocking wait) with parent asynchronous-task - std::function sync_fn; + //! Active async task IDs referencing this handle + std::unordered_set async_task_ids; - //! function used to defer deletion of a tracked async-dependency + //! Deferred action invoked on the main thread after all referencing async tasks complete std::function post_build_fn; }; - //! stores handles used/referenced by currently running async tasks - std::unordered_map async_tracked_handles_; - //! decide whether to sync/wait or defer deletion of handles used by currently running async tasks - static constexpr bool async_defer_deletion_ = false; + //! Maps a tracked handle ID to its active referencing async tasks and optional post-build callback. + std::unordered_map async_tracked_handles_; // Imported semaphores are semaphores that are used to track external memory. // During replay, the external memory is not present (we have no Fds or handles to valid @@ -2085,6 +2125,7 @@ class VulkanReplayConsumerBase : public VulkanConsumer VkPipelineCache pipelineCache, VkPipeline* pipelines, size_t pipelineCount); + void DestroyAssociatedPipelineCache(const VulkanDeviceInfo* device_info, VkPipeline pipeline); const bool save_pipeline_caches_to_file; const bool load_pipeline_caches_from_file; diff --git a/framework/generated/generated_vulkan_replay_consumer.cpp b/framework/generated/generated_vulkan_replay_consumer.cpp index 18f1b2cdd7..529163f877 100644 --- a/framework/generated/generated_vulkan_replay_consumer.cpp +++ b/framework/generated/generated_vulkan_replay_consumer.cpp @@ -1498,11 +1498,10 @@ void VulkanReplayConsumer::Process_vkDestroyPipelineLayout( format::HandleId pipelineLayout, StructPointerDecoder* pAllocator) { - VkDevice in_device = MapHandle(device, &CommonObjectInfoTable::GetVkDeviceInfo); - VkPipelineLayout in_pipelineLayout = MapHandle(pipelineLayout, &CommonObjectInfoTable::GetVkPipelineLayoutInfo); - const VkAllocationCallbacks* in_pAllocator = GetAllocationCallbacks(pAllocator); + auto in_device = GetObjectInfoTable().GetVkDeviceInfo(device); + auto in_pipelineLayout = GetObjectInfoTable().GetVkPipelineLayoutInfo(pipelineLayout); - GetDeviceTable(in_device)->DestroyPipelineLayout(in_device, in_pipelineLayout, in_pAllocator); + OverrideDestroyPipelineLayout(GetDeviceTable(in_device->handle)->DestroyPipelineLayout, in_device, in_pipelineLayout, pAllocator); RemoveHandle(pipelineLayout, &CommonObjectInfoTable::RemoveVkPipelineLayoutInfo); } diff --git a/framework/generated/khronos_generators/vulkan_generators/replay_overrides.json b/framework/generated/khronos_generators/vulkan_generators/replay_overrides.json index 74061e4ed4..cfbcaddc97 100644 --- a/framework/generated/khronos_generators/vulkan_generators/replay_overrides.json +++ b/framework/generated/khronos_generators/vulkan_generators/replay_overrides.json @@ -134,6 +134,7 @@ "vkCreateComputePipelines": "OverrideCreateComputePipelines", "vkDestroyShaderModule": "OverrideDestroyShaderModule", "vkDestroyPipeline": "OverrideDestroyPipeline", + "vkDestroyPipelineLayout": "OverrideDestroyPipelineLayout", "vkDestroyRenderPass": "OverrideDestroyRenderPass", "vkCreateVideoSessionKHR": "OverrideCreateVideoSessionKHR", "vkDestroyVideoSessionKHR": "OverrideDestroyVideoSessionKHR",