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 
6 
7 #include <memory>
8 #include <optional>
9 #include <utility>
10 
11 #include "fml/thread_local.h"
12 #include "fml/trace_event.h"
14 #include "impeller/renderer/backend/vulkan/vk.h" // IWYU pragma: keep.
15 #include "vulkan/vulkan_structs.hpp"
16 
17 namespace impeller {
18 
19 // Holds the command pool in a background thread, recyling it when not in use.
21  public:
23 
25  vk::UniqueCommandPool&& pool,
26  std::vector<vk::UniqueCommandBuffer>&& buffers,
27  std::weak_ptr<CommandPoolRecyclerVK> recycler)
28  : pool_(std::move(pool)),
29  buffers_(std::move(buffers)),
30  recycler_(std::move(recycler)) {}
31 
33  auto const recycler = recycler_.lock();
34 
35  // Not only does this prevent recycling when the context is being destroyed,
36  // but it also prevents the destructor from effectively being called twice;
37  // once for the original BackgroundCommandPoolVK() and once for the moved
38  // BackgroundCommandPoolVK().
39  if (!recycler) {
40  return;
41  }
42  buffers_.clear();
43 
44  recycler->Reclaim(std::move(pool_));
45  }
46 
47  private:
49 
50  BackgroundCommandPoolVK& operator=(const BackgroundCommandPoolVK&) = delete;
51 
52  vk::UniqueCommandPool pool_;
53 
54  // These are retained because the destructor of the C++ UniqueCommandBuffer
55  // wrapper type will attempt to reset the cmd buffer, and doing so may be a
56  // thread safety violation as this may happen on the fence waiter thread.
57  std::vector<vk::UniqueCommandBuffer> buffers_;
58  std::weak_ptr<CommandPoolRecyclerVK> recycler_;
59 };
60 
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 
79  context->GetResourceManager(), std::move(reset_pool_when_dropped));
80 }
81 
82 // TODO(matanlurey): Return a status_or<> instead of {} when we have one.
83 vk::UniqueCommandBuffer CommandPoolVK::CreateCommandBuffer() {
84  auto const context = context_.lock();
85  if (!context) {
86  return {};
87  }
88 
89  Lock lock(pool_mutex_);
90  if (!pool_) {
91  return {};
92  }
93 
94  auto const device = context->GetDevice();
95  vk::CommandBufferAllocateInfo info;
96  info.setCommandPool(pool_.get());
97  info.setCommandBufferCount(1u);
98  info.setLevel(vk::CommandBufferLevel::ePrimary);
99  auto [result, buffers] = device.allocateCommandBuffersUnique(info);
100  if (result != vk::Result::eSuccess) {
101  return {};
102  }
103  return std::move(buffers[0]);
104 }
105 
106 void CommandPoolVK::CollectCommandBuffer(vk::UniqueCommandBuffer&& buffer) {
107  Lock lock(pool_mutex_);
108  if (!pool_) {
109  // If the command pool has already been destroyed, then its buffers have
110  // already been freed.
111  buffer.release();
112  return;
113  }
114  collected_buffers_.push_back(std::move(buffer));
115 }
116 
118  Lock lock(pool_mutex_);
119  pool_.reset();
120 
121  // When the command pool is destroyed, all of its command buffers are freed.
122  // Handles allocated from that pool are now invalid and must be discarded.
123  for (auto& buffer : collected_buffers_) {
124  buffer.release();
125  }
126  collected_buffers_.clear();
127 }
128 
129 // Associates a resource with a thread and context.
130 using CommandPoolMap =
131  std::unordered_map<uint64_t, std::shared_ptr<CommandPoolVK>>;
132 FML_THREAD_LOCAL fml::ThreadLocalUniquePtr<CommandPoolMap> tls_command_pool_map;
133 
134 // Map each context to a list of all thread-local command pools associated
135 // with that context.
137 static std::unordered_map<const ContextVK*,
138  std::vector<std::weak_ptr<CommandPoolVK>>>
139  g_all_pools_map IPLR_GUARDED_BY(g_all_pools_map_mutex);
140 
141 // TODO(matanlurey): Return a status_or<> instead of nullptr when we have one.
142 std::shared_ptr<CommandPoolVK> CommandPoolRecyclerVK::Get() {
143  auto const strong_context = context_.lock();
144  if (!strong_context) {
145  return nullptr;
146  }
147 
148  // If there is a resource in used for this thread and context, return it.
149  if (!tls_command_pool_map.get()) {
151  }
152  CommandPoolMap& pool_map = *tls_command_pool_map.get();
153  auto const hash = strong_context->GetHash();
154  auto const it = pool_map.find(hash);
155  if (it != pool_map.end()) {
156  return it->second;
157  }
158 
159  // Otherwise, create a new resource and return it.
160  auto pool = Create();
161  if (!pool) {
162  return nullptr;
163  }
164 
165  auto const resource =
166  std::make_shared<CommandPoolVK>(std::move(*pool), context_);
167  pool_map.emplace(hash, resource);
168 
169  {
170  Lock all_pools_lock(g_all_pools_map_mutex);
171  g_all_pools_map[strong_context.get()].push_back(resource);
172  }
173 
174  return resource;
175 }
176 
177 // TODO(matanlurey): Return a status_or<> instead of nullopt when we have one.
178 std::optional<vk::UniqueCommandPool> CommandPoolRecyclerVK::Create() {
179  // If we can reuse a command pool, do so.
180  if (auto pool = Reuse()) {
181  return pool;
182  }
183 
184  // Otherwise, create a new one.
185  auto context = context_.lock();
186  if (!context) {
187  return std::nullopt;
188  }
189  vk::CommandPoolCreateInfo info;
190  info.setQueueFamilyIndex(context->GetGraphicsQueue()->GetIndex().family);
191  info.setFlags(vk::CommandPoolCreateFlagBits::eTransient);
192 
193  auto device = context->GetDevice();
194  auto [result, pool] = device.createCommandPoolUnique(info);
195  if (result != vk::Result::eSuccess) {
196  return std::nullopt;
197  }
198  return std::move(pool);
199 }
200 
201 std::optional<vk::UniqueCommandPool> CommandPoolRecyclerVK::Reuse() {
202  // If there are no recycled pools, return nullopt.
203  Lock recycled_lock(recycled_mutex_);
204  if (recycled_.empty()) {
205  return std::nullopt;
206  }
207 
208  // Otherwise, remove and return a recycled pool.
209  auto pool = std::move(recycled_.back());
210  recycled_.pop_back();
211  return std::move(pool);
212 }
213 
214 void CommandPoolRecyclerVK::Reclaim(vk::UniqueCommandPool&& pool) {
215  // Reset the pool on a background thread.
216  auto strong_context = context_.lock();
217  if (!strong_context) {
218  return;
219  }
220  auto device = strong_context->GetDevice();
221  device.resetCommandPool(pool.get());
222 
223  // Move the pool to the recycled list.
224  Lock recycled_lock(recycled_mutex_);
225  recycled_.push_back(std::move(pool));
226 }
227 
229  // Ensure all recycled pools are reclaimed before this is destroyed.
230  Dispose();
231 }
232 
234  CommandPoolMap* pool_map = tls_command_pool_map.get();
235  if (pool_map) {
236  pool_map->clear();
237  }
238 }
239 
241  // Delete the context's entry in this thread's command pool map.
242  if (tls_command_pool_map.get()) {
243  tls_command_pool_map.get()->erase(context->GetHash());
244  }
245 
246  // Destroy all other thread-local CommandPoolVK instances associated with
247  // this context.
248  Lock all_pools_lock(g_all_pools_map_mutex);
249  auto found = g_all_pools_map.find(context);
250  if (found != g_all_pools_map.end()) {
251  for (auto& weak_pool : found->second) {
252  auto pool = weak_pool.lock();
253  if (!pool) {
254  continue;
255  }
256  // Delete all objects held by this pool. The destroyed pool will still
257  // remain in its thread's TLS map until that thread exits.
258  pool->Destroy();
259  }
260  g_all_pools_map.erase(found);
261  }
262 }
263 
264 } // namespace impeller
impeller::CommandPoolMap
std::unordered_map< uint64_t, std::shared_ptr< CommandPoolVK > > CommandPoolMap
Definition: command_pool_vk.cc:131
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:240
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:32
impeller::UniqueResourceVKT
A unique handle to a resource which will be reclaimed by the specified resource manager.
Definition: resource_manager_vk.h:145
impeller::BackgroundCommandPoolVK::BackgroundCommandPoolVK
BackgroundCommandPoolVK(vk::UniqueCommandPool &&pool, std::vector< vk::UniqueCommandBuffer > &&buffers, std::weak_ptr< CommandPoolRecyclerVK > recycler)
Definition: command_pool_vk.cc:24
impeller::Lock
Definition: thread.h:75
impeller::BackgroundCommandPoolVK::BackgroundCommandPoolVK
BackgroundCommandPoolVK(BackgroundCommandPoolVK &&)=default
impeller::ContextVK::GetHash
uint64_t GetHash() const
Definition: context_vk.h:57
vk.h
impeller::CommandPoolVK::CollectCommandBuffer
void CollectCommandBuffer(vk::UniqueCommandBuffer &&buffer)
Collects the given |vk::CommandBuffer| to be retained.
Definition: command_pool_vk.cc:106
command_pool_vk.h
impeller::g_all_pools_map_mutex
static Mutex g_all_pools_map_mutex
Definition: command_pool_vk.cc:136
impeller::CommandPoolRecyclerVK::Get
std::shared_ptr< CommandPoolVK > Get()
Gets a command pool for the current thread.
Definition: command_pool_vk.cc:142
impeller::CommandPoolVK::Destroy
void Destroy()
Delete all Vulkan objects in this command pool.
Definition: command_pool_vk.cc:117
impeller::CommandPoolRecyclerVK::Reclaim
void Reclaim(vk::UniqueCommandPool &&pool)
Returns a command pool to be reset on a background thread.
Definition: command_pool_vk.cc:214
impeller::CommandPoolRecyclerVK::~CommandPoolRecyclerVK
~CommandPoolRecyclerVK()
Definition: command_pool_vk.cc:228
impeller::CommandPoolRecyclerVK::Dispose
void Dispose()
Clears all recycled command pools to let them be reclaimed.
Definition: command_pool_vk.cc:233
impeller::CommandPoolVK::CreateCommandBuffer
vk::UniqueCommandBuffer CreateCommandBuffer()
Creates and returns a new |vk::CommandBuffer|.
Definition: command_pool_vk.cc:83
impeller::ContextVK
Definition: context_vk.h:40
impeller::tls_command_pool_map
FML_THREAD_LOCAL fml::ThreadLocalUniquePtr< CommandPoolMap > tls_command_pool_map
Definition: command_pool_vk.cc:132
resource_manager_vk.h
std
Definition: comparable.h:95
impeller::BackgroundCommandPoolVK
Definition: command_pool_vk.cc:20
impeller
Definition: aiks_context.cc:10