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/macros.h"
12 #include "fml/thread_local.h"
13 #include "fml/trace_event.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.
22  public:
24 
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 
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 
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 
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 
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.
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()) {
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 
233  // Ensure all recycled pools are reclaimed before this is destroyed.
234  Dispose();
235 }
236 
238  CommandPoolMap* pool_map = tls_command_pool_map.get();
239  if (pool_map) {
240  pool_map->clear();
241  }
242 }
243 
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