This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6k
[Impeller] vk::CommandPool resets via ResourceManager
#45654
Merged
auto-submit
merged 33 commits into
flutter:main
from
matanlurey:impeller-command-pool-vk-recycle
Sep 16, 2023
Merged
Changes from 9 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
bf2238a
WIP for discussion.
matanlurey 4f8d143
The hackiest hacks that ever hacked, but it works.
matanlurey 2603a87
Checkpoint.
matanlurey 3e7cb5e
Checkpoint: Runs, but segfaults in EncoderVK::Track.
matanlurey f35dc1f
Checkpoint: Runs, but segfaults in EncoderVK::Track.
matanlurey bfe727e
Ah, the old forgot to finish implementing... Fun.
matanlurey 1af1878
Back in action. Good night for now.
matanlurey df64736
Remove FML_LOG statements.
matanlurey 2264669
Merge remote-tracking branch 'upstream/main' into impeller-command-po…
matanlurey 8d22f90
Rewrite comment, fix race condition in destruction of ContextVK.
matanlurey 12d456d
Restore commented-out test.
matanlurey 443ac71
Add unit tests, fix bugs.
matanlurey 8fdd836
Merge branch 'main' into impeller-command-pool-vk-recycle
matanlurey 2ca3c67
Fix licenses.
matanlurey 538e5a3
Merge remote-tracking branch 'upstream/main' into impeller-command-po…
matanlurey be87e63
Merge.
matanlurey adb61c1
It appears... to be working.
matanlurey 7251349
Fix race condition by using context->Shutdown().
matanlurey 2a3922b
Address feedback, fix race conditions.
matanlurey 8f6ba9c
Merge remote-tracking branch 'upstream/main' into impeller-command-po…
matanlurey 74438e6
Fix renames.
matanlurey a4c6c1d
Re-order ptr resets.
matanlurey 852482f
Re-order fence waiter loop to termintae last.
matanlurey c27bedd
Address feedback, tweak the waiter.
matanlurey 15472ac
Tweak fence waiter and try on CI again.
matanlurey 82a8944
Try another CI tweak.
matanlurey d2ab1c4
Command encoder tests are passing.
matanlurey 869d5c0
Try another variant.
matanlurey 3bc4f0a
Avoid holding on to buffers on collected pools.
matanlurey 692b85d
Merge branch 'main' into impeller-command-pool-vk-recycle
matanlurey 46e6d80
Merge.
matanlurey 6e1a89b
Fix test.
matanlurey 2d04529
Revert fence waiter changes.
matanlurey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,166 +3,190 @@ | |
| // found in the LICENSE file. | ||
|
|
||
| #include "impeller/renderer/backend/vulkan/command_pool_vk.h" | ||
| #include <memory> | ||
| #include <optional> | ||
| #include <utility> | ||
| #include "fml/macros.h" | ||
| #include "fml/thread_local.h" | ||
| #include "fml/trace_event.h" | ||
| #include "impeller/renderer/backend/vulkan/resource_manager_vk.h" | ||
| #include "vulkan/vulkan_structs.hpp" | ||
matanlurey marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #include <map> | ||
| #include <unordered_map> | ||
| #include <vector> | ||
| namespace impeller { | ||
|
|
||
| #include "flutter/fml/thread_local.h" | ||
| #include "impeller/base/thread.h" | ||
| #include "impeller/renderer/backend/vulkan/context_vk.h" | ||
| // Holds the command pool in a background thread, recyling it when not in use. | ||
| class BackgroundCommandPoolVK { | ||
| public: | ||
| BackgroundCommandPoolVK(BackgroundCommandPoolVK&&) = default; | ||
|
|
||
| explicit BackgroundCommandPoolVK( | ||
| vk::UniqueCommandPool&& pool, | ||
| std::vector<vk::UniqueCommandBuffer>&& buffers, | ||
| std::weak_ptr<CommandPoolRecyclerVK> recycler) | ||
| : pool_(std::move(pool)), | ||
| buffers_(std::move(buffers)), | ||
| recycler_(std::move(recycler)) {} | ||
|
|
||
| ~BackgroundCommandPoolVK() { | ||
| auto const recycler = recycler_.lock(); | ||
| if (!recycler) { | ||
| return; | ||
| } | ||
| recycler->Reclaim(std::move(pool_)); | ||
| } | ||
|
|
||
| namespace impeller { | ||
| private: | ||
| FML_DISALLOW_COPY_AND_ASSIGN(BackgroundCommandPoolVK); | ||
|
|
||
| using CommandPoolMap = std::map<uint64_t, std::shared_ptr<CommandPoolVK>>; | ||
| FML_THREAD_LOCAL fml::ThreadLocalUniquePtr<CommandPoolMap> tls_command_pool; | ||
| vk::UniqueCommandPool pool_; | ||
|
|
||
| static Mutex g_all_pools_mutex; | ||
| static std::unordered_map<const ContextVK*, | ||
| std::vector<std::weak_ptr<CommandPoolVK>>> | ||
| g_all_pools IPLR_GUARDED_BY(g_all_pools_mutex); | ||
| // These are retained otherwise it is a Vulkan thread-safety violation | ||
| // because the buffers and the originating pool do not belong to the same | ||
| // thread. | ||
matanlurey marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| std::vector<vk::UniqueCommandBuffer> buffers_; | ||
| std::weak_ptr<CommandPoolRecyclerVK> recycler_; | ||
| }; | ||
|
|
||
| std::shared_ptr<CommandPoolVK> CommandPoolVK::GetThreadLocal( | ||
| const ContextVK* context) { | ||
| CommandPoolVK::~CommandPoolVK() { | ||
| auto const context = context_.lock(); | ||
| if (!context) { | ||
| return nullptr; | ||
| } | ||
| if (tls_command_pool.get() == nullptr) { | ||
| tls_command_pool.reset(new CommandPoolMap()); | ||
| } | ||
| CommandPoolMap& pool_map = *tls_command_pool.get(); | ||
| auto found = pool_map.find(context->GetHash()); | ||
| if (found != pool_map.end() && found->second->IsValid()) { | ||
| return found->second; | ||
| } | ||
| auto pool = std::shared_ptr<CommandPoolVK>(new CommandPoolVK(context)); | ||
| if (!pool->IsValid()) { | ||
| return nullptr; | ||
| return; | ||
| } | ||
| pool_map[context->GetHash()] = pool; | ||
| { | ||
| Lock pool_lock(g_all_pools_mutex); | ||
| g_all_pools[context].push_back(pool); | ||
| auto const recycler = context->GetCommandPoolRecycler(); | ||
| if (!recycler) { | ||
| return; | ||
| } | ||
| return pool; | ||
| UniqueResourceVKT<BackgroundCommandPoolVK> pool( | ||
| context->GetResourceManager(), | ||
| BackgroundCommandPoolVK(std::move(pool_), std::move(collected_buffers_), | ||
| recycler)); | ||
| } | ||
|
|
||
| void CommandPoolVK::ClearAllPools(const ContextVK* context) { | ||
| if (tls_command_pool.get()) { | ||
| tls_command_pool.get()->erase(context->GetHash()); | ||
| } | ||
| Lock pool_lock(g_all_pools_mutex); | ||
| if (auto found = g_all_pools.find(context); found != g_all_pools.end()) { | ||
| for (auto& weak_pool : found->second) { | ||
| auto pool = weak_pool.lock(); | ||
| if (!pool) { | ||
| // The pool has already died because the thread died. | ||
| continue; | ||
| } | ||
| // The pool is reset but its reference in the TLS map remains till the | ||
| // thread dies. | ||
| pool->Reset(); | ||
| } | ||
| g_all_pools.erase(found); | ||
| // TODO(matanlurey): Return a status_or<> instead of {} when we have one. | ||
| vk::UniqueCommandBuffer CommandPoolVK::CreateBuffer() { | ||
| auto const context = context_.lock(); | ||
| if (!context) { | ||
| return {}; | ||
| } | ||
| } | ||
|
|
||
| CommandPoolVK::CommandPoolVK(const ContextVK* context) | ||
| : owner_id_(std::this_thread::get_id()) { | ||
| vk::CommandPoolCreateInfo pool_info; | ||
|
|
||
| pool_info.queueFamilyIndex = context->GetGraphicsQueue()->GetIndex().family; | ||
| pool_info.flags = vk::CommandPoolCreateFlagBits::eTransient | | ||
| vk::CommandPoolCreateFlagBits::eResetCommandBuffer; | ||
| auto pool = context->GetDevice().createCommandPoolUnique(pool_info); | ||
| if (pool.result != vk::Result::eSuccess) { | ||
| return; | ||
| auto const device = context->GetDevice(); | ||
| vk::CommandBufferAllocateInfo info; | ||
| info.setCommandPool(pool_.get()); | ||
| info.setCommandBufferCount(1u); | ||
| info.setLevel(vk::CommandBufferLevel::ePrimary); | ||
| auto [result, buffers] = device.allocateCommandBuffersUnique(info); | ||
| if (result != vk::Result::eSuccess) { | ||
| return {}; | ||
| } | ||
|
|
||
| device_holder_ = context->GetDeviceHolder(); | ||
| graphics_pool_ = std::move(pool.value); | ||
| is_valid_ = true; | ||
| return std::move(buffers[0]); | ||
| } | ||
|
|
||
| CommandPoolVK::~CommandPoolVK() = default; | ||
|
|
||
| bool CommandPoolVK::IsValid() const { | ||
| return is_valid_; | ||
| void CommandPoolVK::CollectBuffer(vk::UniqueCommandBuffer&& buffer) { | ||
| collected_buffers_.push_back(std::move(buffer)); | ||
| } | ||
|
|
||
| void CommandPoolVK::Reset() { | ||
| { | ||
| Lock lock(buffers_to_collect_mutex_); | ||
| graphics_pool_.reset(); | ||
| // Associates a resource with a thread and context. | ||
| using CommandPoolMap = | ||
| std::unordered_map<uint64_t, std::shared_ptr<CommandPoolVK>>; | ||
| FML_THREAD_LOCAL fml::ThreadLocalUniquePtr<CommandPoolMap> resources_; | ||
|
|
||
| // When the command pool is destroyed, all of its command buffers are freed. | ||
| // Handles allocated from that pool are now invalid and must be discarded. | ||
| for (vk::UniqueCommandBuffer& buffer : buffers_to_collect_) { | ||
| buffer.release(); | ||
| } | ||
| buffers_to_collect_.clear(); | ||
| // TODO(matanlurey): Return a status_or<> instead of nullptr when we have one. | ||
| std::shared_ptr<CommandPoolVK> CommandPoolRecyclerVK::Get() { | ||
| auto const strong_context = context_.lock(); | ||
| if (!strong_context) { | ||
| return nullptr; | ||
| } | ||
|
|
||
| for (vk::UniqueCommandBuffer& buffer : recycled_buffers_) { | ||
| buffer.release(); | ||
| // If there is a resource in used for this thread and context, return it. | ||
| auto resources = resources_.get(); | ||
| if (!resources) { | ||
| resources = new CommandPoolMap(); | ||
| resources_.reset(resources); | ||
| } | ||
| auto map = *resources; | ||
| auto const hash = strong_context->GetHash(); | ||
| auto const it = map.find(hash); | ||
| if (it != map.end()) { | ||
| return it->second; | ||
| } | ||
| recycled_buffers_.clear(); | ||
|
|
||
| is_valid_ = false; | ||
| // Otherwise, create a new resource and return it. | ||
| auto pool = Create(); | ||
| if (!pool) { | ||
| return nullptr; | ||
| } | ||
|
|
||
| auto const resource = | ||
| std::make_shared<CommandPoolVK>(std::move(*pool), context_); | ||
| map[hash] = resource; | ||
| return resource; | ||
| } | ||
|
|
||
| vk::UniqueCommandBuffer CommandPoolVK::CreateGraphicsCommandBuffer() { | ||
| std::shared_ptr<const DeviceHolder> strong_device = device_holder_.lock(); | ||
| if (!strong_device) { | ||
| return {}; | ||
| } | ||
| FML_DCHECK(std::this_thread::get_id() == owner_id_); | ||
| { | ||
| Lock lock(buffers_to_collect_mutex_); | ||
| GarbageCollectBuffersIfAble(); | ||
| // TODO(matanlurey): Return a status_or<> instead of nullopt when we have one. | ||
| std::optional<vk::UniqueCommandPool> CommandPoolRecyclerVK::Create() { | ||
| // If we can reuse a command pool, do so. | ||
| if (auto pool = Reuse()) { | ||
| return pool; | ||
| } | ||
|
|
||
| if (!recycled_buffers_.empty()) { | ||
| vk::UniqueCommandBuffer result = std::move(recycled_buffers_.back()); | ||
| recycled_buffers_.pop_back(); | ||
| return result; | ||
| // Otherwise, create a new one. | ||
| auto context = context_.lock(); | ||
| if (!context) { | ||
| return std::nullopt; | ||
| } | ||
| vk::CommandPoolCreateInfo info; | ||
| info.setQueueFamilyIndex(context->GetGraphicsQueue()->GetIndex().family); | ||
| info.setFlags(vk::CommandPoolCreateFlagBits::eTransient); | ||
|
|
||
| vk::CommandBufferAllocateInfo alloc_info; | ||
| alloc_info.commandPool = graphics_pool_.get(); | ||
| alloc_info.commandBufferCount = 1u; | ||
| alloc_info.level = vk::CommandBufferLevel::ePrimary; | ||
| auto [result, buffers] = | ||
| strong_device->GetDevice().allocateCommandBuffersUnique(alloc_info); | ||
| auto device = context->GetDevice(); | ||
| auto [result, pool] = device.createCommandPoolUnique(info); | ||
| if (result != vk::Result::eSuccess) { | ||
| return {}; | ||
| return std::nullopt; | ||
| } | ||
| return std::move(buffers[0]); | ||
| return std::move(pool); | ||
| } | ||
|
|
||
| void CommandPoolVK::CollectGraphicsCommandBuffer( | ||
| vk::UniqueCommandBuffer buffer) { | ||
| Lock lock(buffers_to_collect_mutex_); | ||
| if (!graphics_pool_) { | ||
| // If the command pool has already been destroyed, then its command buffers | ||
| // have been freed and are now invalid. | ||
| buffer.release(); | ||
| std::optional<vk::UniqueCommandPool> CommandPoolRecyclerVK::Reuse() { | ||
| // If there are no recycled pools, return nullopt. | ||
| Lock _(recycled_mutex_); | ||
matanlurey marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (recycled_.empty()) { | ||
| return std::nullopt; | ||
| } | ||
| buffers_to_collect_.emplace_back(std::move(buffer)); | ||
| GarbageCollectBuffersIfAble(); | ||
|
|
||
| // Otherwise, remove and return a recycled pool. | ||
| auto pool = std::move(recycled_.back()); | ||
| recycled_.pop_back(); | ||
| return std::move(pool); | ||
| } | ||
|
|
||
| void CommandPoolVK::GarbageCollectBuffersIfAble() { | ||
| if (std::this_thread::get_id() != owner_id_) { | ||
| void CommandPoolRecyclerVK::Reclaim(vk::UniqueCommandPool&& pool) { | ||
| TRACE_EVENT0("impeller", "ReclaimCommandPool"); | ||
| // FIXME: Assert that this is called on a background thread. | ||
|
|
||
| // Reset the pool on a background thread. | ||
| auto strong_context = context_.lock(); | ||
| if (!strong_context) { | ||
| return; | ||
| } | ||
| auto device = strong_context->GetDevice(); | ||
| device.resetCommandPool(pool.get()); | ||
|
|
||
| for (auto& buffer : buffers_to_collect_) { | ||
| buffer->reset(); | ||
| recycled_buffers_.emplace_back(std::move(buffer)); | ||
| } | ||
| // Move the pool to the recycled list. | ||
| Lock _(recycled_mutex_); | ||
| recycled_.push_back(std::move(pool)); | ||
| } | ||
|
|
||
| CommandPoolRecyclerVK::~CommandPoolRecyclerVK() { | ||
| // Ensure all recycled pools are reclaimed before this is destroyed. | ||
| Recycle(); | ||
| } | ||
|
|
||
| buffers_to_collect_.clear(); | ||
| void CommandPoolRecyclerVK::Recycle() { | ||
|
||
| auto const resources = resources_.get(); | ||
| if (!resources) { | ||
| return; | ||
| } | ||
| resources->clear(); | ||
| } | ||
|
|
||
| } // namespace impeller | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.