Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 3929861

Browse files
[Impeller] Destroy all per-thread command pools tied to a context before deleting the context (#46286)
Vulkan requires that all objects created using a device be destroyed before the device is destroyed. The CommandPoolRecyclerVK maintains a thread-local map of each context's currently active command pool. Before the context deletes the device, it must force cleanup of all such command pools associated with that context. This PR reinstates a mechanism that was used prior to the refactoring that introduced command pool recycling.
1 parent 0282d68 commit 3929861

File tree

4 files changed

+118
-19
lines changed

4 files changed

+118
-19
lines changed

impeller/renderer/backend/vulkan/command_pool_vk.cc

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ class BackgroundCommandPoolVK final {
5959
static bool kResetOnBackgroundThread = false;
6060

6161
CommandPoolVK::~CommandPoolVK() {
62+
if (!pool_) {
63+
return;
64+
}
65+
6266
auto const context = context_.lock();
6367
if (!context) {
6468
return;
@@ -84,6 +88,11 @@ vk::UniqueCommandBuffer CommandPoolVK::CreateCommandBuffer() {
8488
return {};
8589
}
8690

91+
Lock lock(pool_mutex_);
92+
if (!pool_) {
93+
return {};
94+
}
95+
8796
auto const device = context->GetDevice();
8897
vk::CommandBufferAllocateInfo info;
8998
info.setCommandPool(pool_.get());
@@ -97,17 +106,39 @@ vk::UniqueCommandBuffer CommandPoolVK::CreateCommandBuffer() {
97106
}
98107

99108
void CommandPoolVK::CollectCommandBuffer(vk::UniqueCommandBuffer&& buffer) {
109+
Lock lock(pool_mutex_);
100110
if (!pool_) {
101-
// If the command pool has already been destroyed, just free the buffer.
111+
// If the command pool has already been destroyed, then its buffers have
112+
// already been freed.
113+
buffer.release();
102114
return;
103115
}
104116
collected_buffers_.push_back(std::move(buffer));
105117
}
106118

119+
void CommandPoolVK::Destroy() {
120+
Lock lock(pool_mutex_);
121+
pool_.reset();
122+
123+
// When the command pool is destroyed, all of its command buffers are freed.
124+
// Handles allocated from that pool are now invalid and must be discarded.
125+
for (auto& buffer : collected_buffers_) {
126+
buffer.release();
127+
}
128+
collected_buffers_.clear();
129+
}
130+
107131
// Associates a resource with a thread and context.
108132
using CommandPoolMap =
109133
std::unordered_map<uint64_t, std::shared_ptr<CommandPoolVK>>;
110-
FML_THREAD_LOCAL fml::ThreadLocalUniquePtr<CommandPoolMap> resources_;
134+
FML_THREAD_LOCAL fml::ThreadLocalUniquePtr<CommandPoolMap> tls_command_pool_map;
135+
136+
// Map each context to a list of all thread-local command pools associated
137+
// with that context.
138+
static Mutex g_all_pools_map_mutex;
139+
static std::unordered_map<const ContextVK*,
140+
std::vector<std::weak_ptr<CommandPoolVK>>>
141+
g_all_pools_map IPLR_GUARDED_BY(g_all_pools_map_mutex);
111142

112143
// TODO(matanlurey): Return a status_or<> instead of nullptr when we have one.
113144
std::shared_ptr<CommandPoolVK> CommandPoolRecyclerVK::Get() {
@@ -117,14 +148,13 @@ std::shared_ptr<CommandPoolVK> CommandPoolRecyclerVK::Get() {
117148
}
118149

119150
// If there is a resource in used for this thread and context, return it.
120-
auto resources = resources_.get();
121-
if (!resources) {
122-
resources = new CommandPoolMap();
123-
resources_.reset(resources);
151+
if (!tls_command_pool_map.get()) {
152+
tls_command_pool_map.reset(new CommandPoolMap());
124153
}
154+
CommandPoolMap& pool_map = *tls_command_pool_map.get();
125155
auto const hash = strong_context->GetHash();
126-
auto const it = resources->find(hash);
127-
if (it != resources->end()) {
156+
auto const it = pool_map.find(hash);
157+
if (it != pool_map.end()) {
128158
return it->second;
129159
}
130160

@@ -136,7 +166,13 @@ std::shared_ptr<CommandPoolVK> CommandPoolRecyclerVK::Get() {
136166

137167
auto const resource =
138168
std::make_shared<CommandPoolVK>(std::move(*pool), context_);
139-
resources->emplace(hash, resource);
169+
pool_map.emplace(hash, resource);
170+
171+
{
172+
Lock all_pools_lock(g_all_pools_map_mutex);
173+
g_all_pools_map[strong_context.get()].push_back(resource);
174+
}
175+
140176
return resource;
141177
}
142178

@@ -199,11 +235,34 @@ CommandPoolRecyclerVK::~CommandPoolRecyclerVK() {
199235
}
200236

201237
void CommandPoolRecyclerVK::Dispose() {
202-
auto const resources = resources_.get();
203-
if (!resources) {
204-
return;
238+
CommandPoolMap* pool_map = tls_command_pool_map.get();
239+
if (pool_map) {
240+
pool_map->clear();
241+
}
242+
}
243+
244+
void CommandPoolRecyclerVK::DestroyThreadLocalPools(const ContextVK* context) {
245+
// Delete the context's entry in this thread's command pool map.
246+
if (tls_command_pool_map.get()) {
247+
tls_command_pool_map.get()->erase(context->GetHash());
248+
}
249+
250+
// Destroy all other thread-local CommandPoolVK instances associated with
251+
// this context.
252+
Lock all_pools_lock(g_all_pools_map_mutex);
253+
auto found = g_all_pools_map.find(context);
254+
if (found != g_all_pools_map.end()) {
255+
for (auto& weak_pool : found->second) {
256+
auto pool = weak_pool.lock();
257+
if (!pool) {
258+
continue;
259+
}
260+
// Delete all objects held by this pool. The destroyed pool will still
261+
// remain in its thread's TLS map until that thread exits.
262+
pool->Destroy();
263+
}
264+
g_all_pools_map.erase(found);
205265
}
206-
resources->clear();
207266
}
208267

209268
} // namespace impeller

impeller/renderer/backend/vulkan/command_pool_vk.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ class CommandPoolRecyclerVK;
2929
/// @see |CommandPoolRecyclerVK|
3030
class CommandPoolVK final {
3131
public:
32-
CommandPoolVK(CommandPoolVK&&) = default;
3332
~CommandPoolVK();
3433

3534
/// @brief Creates a resource that manages the life of a command pool.
@@ -54,14 +53,19 @@ class CommandPoolVK final {
5453
/// @see |GarbageCollectBuffersIfAble|
5554
void CollectCommandBuffer(vk::UniqueCommandBuffer&& buffer);
5655

56+
/// @brief Delete all Vulkan objects in this command pool.
57+
void Destroy();
58+
5759
private:
5860
FML_DISALLOW_COPY_AND_ASSIGN(CommandPoolVK);
5961

60-
vk::UniqueCommandPool pool_;
62+
Mutex pool_mutex_;
63+
vk::UniqueCommandPool pool_ IPLR_GUARDED_BY(pool_mutex_);
6164
std::weak_ptr<ContextVK>& context_;
6265

6366
// Used to retain a reference on these until the pool is reset.
64-
std::vector<vk::UniqueCommandBuffer> collected_buffers_;
67+
std::vector<vk::UniqueCommandBuffer> collected_buffers_
68+
IPLR_GUARDED_BY(pool_mutex_);
6569
};
6670

6771
//------------------------------------------------------------------------------
@@ -93,6 +97,12 @@ class CommandPoolRecyclerVK final
9397
public:
9498
~CommandPoolRecyclerVK();
9599

100+
/// @brief Clean up resources held by all per-thread command pools
101+
/// associated with the given context.
102+
///
103+
/// @param[in] context The context.
104+
static void DestroyThreadLocalPools(const ContextVK* context);
105+
96106
/// @brief Creates a recycler for the given |ContextVK|.
97107
///
98108
/// @param[in] context The context to create the recycler for.

impeller/renderer/backend/vulkan/context_vk.cc

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,7 @@ ContextVK::~ContextVK() {
113113
if (device_holder_ && device_holder_->device) {
114114
[[maybe_unused]] auto result = device_holder_->device->waitIdle();
115115
}
116-
if (command_pool_recycler_) {
117-
command_pool_recycler_.get()->Dispose();
118-
}
116+
CommandPoolRecyclerVK::DestroyThreadLocalPools(this);
119117
}
120118

121119
Context::BackendType ContextVK::GetBackendType() const {

impeller/renderer/backend/vulkan/context_vk_unittests.cc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#include "flutter/fml/synchronization/waitable_event.h"
56
#include "flutter/testing/testing.h" // IWYU pragma: keep
67
#include "impeller/renderer/backend/vulkan/command_pool_vk.h"
78
#include "impeller/renderer/backend/vulkan/context_vk.h"
@@ -25,6 +26,37 @@ TEST(ContextVKTest, DeletesCommandPools) {
2526
ASSERT_FALSE(weak_context.lock());
2627
}
2728

29+
TEST(ContextVKTest, DeletesCommandPoolsOnAllThreads) {
30+
std::weak_ptr<ContextVK> weak_context;
31+
std::weak_ptr<CommandPoolVK> weak_pool_main;
32+
33+
std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
34+
weak_pool_main = context->GetCommandPoolRecycler()->Get();
35+
weak_context = context;
36+
ASSERT_TRUE(weak_pool_main.lock());
37+
ASSERT_TRUE(weak_context.lock());
38+
39+
// Start a second thread that obtains a command pool.
40+
fml::AutoResetWaitableEvent latch1, latch2;
41+
std::weak_ptr<CommandPoolVK> weak_pool_thread;
42+
std::thread thread([&]() {
43+
weak_pool_thread = context->GetCommandPoolRecycler()->Get();
44+
latch1.Signal();
45+
latch2.Wait();
46+
});
47+
48+
// Delete the ContextVK on the main thread.
49+
latch1.Wait();
50+
context.reset();
51+
ASSERT_FALSE(weak_pool_main.lock());
52+
ASSERT_FALSE(weak_context.lock());
53+
54+
// Stop the second thread and check that its command pool has been deleted.
55+
latch2.Signal();
56+
thread.join();
57+
ASSERT_FALSE(weak_pool_thread.lock());
58+
}
59+
2860
TEST(ContextVKTest, DeletePipelineAfterContext) {
2961
std::shared_ptr<Pipeline<PipelineDescriptor>> pipeline;
3062
std::shared_ptr<std::vector<std::string>> functions;

0 commit comments

Comments
 (0)