| Flutter Impeller |

command_pool_vk.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "impeller/renderer/backend/vulkan/command_pool_vk.h"
6
7 #include <memory>
8 #include <optional>
9 #include <utility>
10
11 #include "fml/macros.h"
12 #include "fml/thread_local.h"
13 #include "fml/trace_event.h"
14 #include "impeller/renderer/backend/vulkan/resource_manager_vk.h"
15 #include "impeller/renderer/backend/vulkan/vk.h" // IWYU pragma: keep.
16 #include "vulkan/vulkan_structs.hpp"
17
18 namespace impeller {
19
20 // Holds the command pool in a background thread, recyling it when not in use.
21 class BackgroundCommandPoolVK final {
22 public:
23 BackgroundCommandPoolVK(BackgroundCommandPoolVK&&) = default;
24
25 explicit BackgroundCommandPoolVK(
26 vk::UniqueCommandPool&& pool,
27 std::vector<vk::UniqueCommandBuffer>&& buffers,
28 std::weak_ptr<CommandPoolRecyclerVK> recycler)
29 : pool_(std::move(pool)),
30 buffers_(std::move(buffers)),
31 recycler_(std::move(recycler)) {}
32
33 ~BackgroundCommandPoolVK() {
34 auto const recycler = recycler_.lock();
35
36 // Not only does this prevent recycling when the context is being destroyed,
37 // but it also prevents the destructor from effectively being called twice;
38 // once for the original BackgroundCommandPoolVK() and once for the moved
39 // BackgroundCommandPoolVK().
40 if (!recycler) {
41 return;
42 }
43
44 recycler->Reclaim(std::move(pool_));
45 }
46
47 private:
48 FML_DISALLOW_COPY_AND_ASSIGN(BackgroundCommandPoolVK);
49
50 vk::UniqueCommandPool pool_;
51
52 // These are retained because the destructor of the C++ UniqueCommandBuffer
53 // wrapper type will attempt to reset the cmd buffer, and doing so may be a
54 // thread safety violation as this may happen on the fence waiter thread.
55 std::vector<vk::UniqueCommandBuffer> buffers_;
56 std::weak_ptr<CommandPoolRecyclerVK> recycler_;
57 };
58
59 static bool kResetOnBackgroundThread = false;
60
61 CommandPoolVK::~CommandPoolVK() {
62 if (!pool_) {
63 return;
64 }
65
66 auto const context = context_.lock();
67 if (!context) {
68 return;
69 }
70 auto const recycler = context->GetCommandPoolRecycler();
71 if (!recycler) {
72 return;
73 }
74
75 auto reset_pool_when_dropped = BackgroundCommandPoolVK(
76 std::move(pool_), std::move(collected_buffers_), recycler);
77
78 if (kResetOnBackgroundThread) {
79 UniqueResourceVKT<BackgroundCommandPoolVK> pool(
80 context->GetResourceManager(), std::move(reset_pool_when_dropped));
81 }
82 }
83
84 // TODO(matanlurey): Return a status_or<> instead of {} when we have one.
85 vk::UniqueCommandBuffer CommandPoolVK::CreateCommandBuffer() {
86 auto const context = context_.lock();
87 if (!context) {
88 return {};
89 }
90
91 Lock lock(pool_mutex_);
92 if (!pool_) {
93 return {};
94 }
95
96 auto const device = context->GetDevice();
97 vk::CommandBufferAllocateInfo info;
98 info.setCommandPool(pool_.get());
99 info.setCommandBufferCount(1u);
100 info.setLevel(vk::CommandBufferLevel::ePrimary);
101 auto [result, buffers] = device.allocateCommandBuffersUnique(info);
102 if (result != vk::Result::eSuccess) {
103 return {};
104 }
105 return std::move(buffers[0]);
106 }
107
108 void CommandPoolVK::CollectCommandBuffer(vk::UniqueCommandBuffer&& buffer) {
109 Lock lock(pool_mutex_);
110 if (!pool_) {
111 // If the command pool has already been destroyed, then its buffers have
112 // already been freed.
113 buffer.release();
114 return;
115 }
116 collected_buffers_.push_back(std::move(buffer));
117 }
118
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
131 // Associates a resource with a thread and context.
132 using CommandPoolMap =
133 std::unordered_map<uint64_t, std::shared_ptr<CommandPoolVK>>;
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);
142
143 // TODO(matanlurey): Return a status_or<> instead of nullptr when we have one.
144 std::shared_ptr<CommandPoolVK> CommandPoolRecyclerVK::Get() {
145 auto const strong_context = context_.lock();
146 if (!strong_context) {
147 return nullptr;
148 }
149
150 // If there is a resource in used for this thread and context, return it.
151 if (!tls_command_pool_map.get()) {
152 tls_command_pool_map.reset(new CommandPoolMap());
153 }
154 CommandPoolMap& pool_map = *tls_command_pool_map.get();
155 auto const hash = strong_context->GetHash();
156 auto const it = pool_map.find(hash);
157 if (it != pool_map.end()) {
158 return it->second;
159 }
160
161 // Otherwise, create a new resource and return it.
162 auto pool = Create();
163 if (!pool) {
164 return nullptr;
165 }
166
167 auto const resource =
168 std::make_shared<CommandPoolVK>(std::move(*pool), context_);
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
176 return resource;
177 }
178
179 // TODO(matanlurey): Return a status_or<> instead of nullopt when we have one.
180 std::optional<vk::UniqueCommandPool> CommandPoolRecyclerVK::Create() {
181 // If we can reuse a command pool, do so.
182 if (auto pool = Reuse()) {
183 return pool;
184 }
185
186 // Otherwise, create a new one.
187 auto context = context_.lock();
188 if (!context) {
189 return std::nullopt;
190 }
191 vk::CommandPoolCreateInfo info;
192 info.setQueueFamilyIndex(context->GetGraphicsQueue()->GetIndex().family);
193 info.setFlags(vk::CommandPoolCreateFlagBits::eTransient);
194
195 auto device = context->GetDevice();
196 auto [result, pool] = device.createCommandPoolUnique(info);
197 if (result != vk::Result::eSuccess) {
198 return std::nullopt;
199 }
200 return std::move(pool);
201 }
202
203 std::optional<vk::UniqueCommandPool> CommandPoolRecyclerVK::Reuse() {
204 // If there are no recycled pools, return nullopt.
205 Lock recycled_lock(recycled_mutex_);
206 if (recycled_.empty()) {
207 return std::nullopt;
208 }
209
210 // Otherwise, remove and return a recycled pool.
211 auto pool = std::move(recycled_.back());
212 recycled_.pop_back();
213 return std::move(pool);
214 }
215
216 void CommandPoolRecyclerVK::Reclaim(vk::UniqueCommandPool&& pool) {
217 TRACE_EVENT0("impeller", "ReclaimCommandPool");
218
219 // Reset the pool on a background thread.
220 auto strong_context = context_.lock();
221 if (!strong_context) {
222 return;
223 }
224 auto device = strong_context->GetDevice();
225 device.resetCommandPool(pool.get());
226
227 // Move the pool to the recycled list.
228 Lock recycled_lock(recycled_mutex_);
229 recycled_.push_back(std::move(pool));
230 }
231
232 CommandPoolRecyclerVK::~CommandPoolRecyclerVK() {
233 // Ensure all recycled pools are reclaimed before this is destroyed.
234 Dispose();
235 }
236
237 void CommandPoolRecyclerVK::Dispose() {
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);
265 }
266 }
267
268 } // namespace impeller
impeller::CommandPoolMap
std::unordered_map< uint64_t, std::shared_ptr< CommandPoolVK > > CommandPoolMap
Definition: command_pool_vk.cc:133
impeller::CommandPoolVK::~CommandPoolVK
~CommandPoolVK()
Definition: command_pool_vk.cc:61
impeller::CommandPoolRecyclerVK::DestroyThreadLocalPools
static void DestroyThreadLocalPools(const ContextVK *context)
Clean up resources held by all per-thread command pools associated with the given context.
Definition: command_pool_vk.cc:244
impeller::IPLR_GUARDED_BY
static std::unordered_map< const ContextVK *, std::vector< std::weak_ptr< CommandPoolVK > > > g_all_pools_map IPLR_GUARDED_BY(g_all_pools_map_mutex)
impeller::BackgroundCommandPoolVK::~BackgroundCommandPoolVK
~BackgroundCommandPoolVK()
Definition: command_pool_vk.cc:33
impeller::UniqueResourceVKT
A unique handle to a resource which will be reclaimed by the specified resource manager.
Definition: resource_manager_vk.h:140
impeller::BackgroundCommandPoolVK::BackgroundCommandPoolVK
BackgroundCommandPoolVK(vk::UniqueCommandPool &&pool, std::vector< vk::UniqueCommandBuffer > &&buffers, std::weak_ptr< CommandPoolRecyclerVK > recycler)
Definition: command_pool_vk.cc:25
impeller::Lock
Definition: thread.h:54
impeller::BackgroundCommandPoolVK::BackgroundCommandPoolVK
BackgroundCommandPoolVK(BackgroundCommandPoolVK &&)=default
impeller::ContextVK::GetHash
uint64_t GetHash() const
Definition: context_vk.h:53
vk.h
impeller::CommandPoolVK::CollectCommandBuffer
void CollectCommandBuffer(vk::UniqueCommandBuffer &&buffer)
Collects the given |vk::CommandBuffer| to be retained.
Definition: command_pool_vk.cc:108
command_pool_vk.h
impeller::g_all_pools_map_mutex
static Mutex g_all_pools_map_mutex
Definition: command_pool_vk.cc:138
impeller::CommandPoolRecyclerVK::Get
std::shared_ptr< CommandPoolVK > Get()
Gets a command pool for the current thread.
Definition: command_pool_vk.cc:144
impeller::CommandPoolVK::Destroy
void Destroy()
Delete all Vulkan objects in this command pool.
Definition: command_pool_vk.cc:119
impeller::CommandPoolRecyclerVK::Reclaim
void Reclaim(vk::UniqueCommandPool &&pool)
Returns a command pool to be reset on a background thread.
Definition: command_pool_vk.cc:216
impeller::CommandPoolRecyclerVK::~CommandPoolRecyclerVK
~CommandPoolRecyclerVK()
Definition: command_pool_vk.cc:232
impeller::CommandPoolRecyclerVK::Dispose
void Dispose()
Clears all recycled command pools to let them be reclaimed.
Definition: command_pool_vk.cc:237
impeller::CommandPoolVK::CreateCommandBuffer
vk::UniqueCommandBuffer CreateCommandBuffer()
Creates and returns a new |vk::CommandBuffer|.
Definition: command_pool_vk.cc:85
impeller::ContextVK
Definition: context_vk.h:36
impeller::tls_command_pool_map
FML_THREAD_LOCAL fml::ThreadLocalUniquePtr< CommandPoolMap > tls_command_pool_map
Definition: command_pool_vk.cc:134
resource_manager_vk.h
std
Definition: comparable.h:98
impeller::BackgroundCommandPoolVK
Definition: command_pool_vk.cc:21
impeller::kResetOnBackgroundThread
static bool kResetOnBackgroundThread
Definition: command_pool_vk.cc:59
impeller
Definition: aiks_context.cc:10