Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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
13
14#include "impeller/renderer/backend/vulkan/vk.h" // IWYU pragma: keep.
15#include "vulkan/vulkan_enums.hpp"
16#include "vulkan/vulkan_handles.hpp"
17#include "vulkan/vulkan_structs.hpp"
18
19namespace impeller {
20
21// Holds the command pool in a background thread, recyling it when not in use.
23 public:
25
26 // The recycler also recycles command buffers that were never used, up to a
27 // limit of 16 per frame. This number was somewhat arbitrarily chosen.
28 static constexpr size_t kUnusedCommandBufferLimit = 16u;
29
31 vk::UniqueCommandPool&& pool,
32 std::vector<vk::UniqueCommandBuffer>&& buffers,
33 size_t unused_count,
34 std::weak_ptr<CommandPoolRecyclerVK> recycler)
35 : pool_(std::move(pool)),
36 buffers_(std::move(buffers)),
37 unused_count_(unused_count),
38 recycler_(std::move(recycler)) {}
39
41 auto const recycler = recycler_.lock();
42
43 // Not only does this prevent recycling when the context is being destroyed,
44 // but it also prevents the destructor from effectively being called twice;
45 // once for the original BackgroundCommandPoolVK() and once for the moved
46 // BackgroundCommandPoolVK().
47 if (!recycler) {
48 return;
49 }
50 // If there are many unused command buffers, release some of them and
51 // trim the command pool.
52 bool should_trim = unused_count_ > kUnusedCommandBufferLimit;
53 recycler->Reclaim(std::move(pool_), std::move(buffers_),
54 /*should_trim=*/should_trim);
55 }
56
57 private:
59
60 BackgroundCommandPoolVK& operator=(const BackgroundCommandPoolVK&) = delete;
61
62 vk::UniqueCommandPool pool_;
63
64 // These are retained because the destructor of the C++ UniqueCommandBuffer
65 // wrapper type will attempt to reset the cmd buffer, and doing so may be a
66 // thread safety violation as this may happen on the fence waiter thread.
67 std::vector<vk::UniqueCommandBuffer> buffers_;
68 const size_t unused_count_;
69 std::weak_ptr<CommandPoolRecyclerVK> recycler_;
70};
71
73 if (!pool_) {
74 return;
75 }
76
77 std::shared_ptr<DeviceHolderVK> device_holder = device_holder_.lock();
78 if (!device_holder) {
79 pool_.release();
80 for (auto& buffer : collected_buffers_) {
81 buffer.release();
82 }
83 for (auto& buffer : unused_command_buffers_) {
84 buffer.release();
85 }
86 return;
87 }
88
89 auto const context = context_.lock();
90 if (!context) {
91 return;
92 }
93 auto const recycler = context->GetCommandPoolRecycler();
94 if (!recycler) {
95 return;
96 }
97 // Any unused command buffers are added to the set of used command buffers.
98 // both will be reset to the initial state when the pool is reset.
99 size_t unused_count = unused_command_buffers_.size();
100 for (auto i = 0u; i < unused_command_buffers_.size(); i++) {
101 collected_buffers_.push_back(std::move(unused_command_buffers_[i]));
102 }
103 unused_command_buffers_.clear();
104
105 auto reset_pool_when_dropped = BackgroundCommandPoolVK(
106 std::move(pool_), std::move(collected_buffers_), unused_count, recycler);
107
109 context->GetResourceManager(), std::move(reset_pool_when_dropped));
110}
111
112// TODO(matanlurey): Return a status_or<> instead of {} when we have one.
113vk::UniqueCommandBuffer CommandPoolVK::CreateCommandBuffer() {
114 auto const context = context_.lock();
115 if (!context) {
116 return {};
117 }
118
119 Lock lock(pool_mutex_);
120 if (!pool_) {
121 return {};
122 }
123 if (!unused_command_buffers_.empty()) {
124 vk::UniqueCommandBuffer buffer = std::move(unused_command_buffers_.back());
125 unused_command_buffers_.pop_back();
126 return buffer;
127 }
128
129 auto const device = context->GetDevice();
130 vk::CommandBufferAllocateInfo info;
131 info.setCommandPool(pool_.get());
132 info.setCommandBufferCount(1u);
133 info.setLevel(vk::CommandBufferLevel::ePrimary);
134 auto [result, buffers] = device.allocateCommandBuffersUnique(info);
135 if (result != vk::Result::eSuccess) {
136 return {};
137 }
138 return std::move(buffers[0]);
139}
140
141void CommandPoolVK::CollectCommandBuffer(vk::UniqueCommandBuffer&& buffer) {
142 Lock lock(pool_mutex_);
143 if (!pool_) {
144 // If the command pool has already been destroyed, then its buffers have
145 // already been freed.
146 buffer.release();
147 return;
148 }
149 collected_buffers_.push_back(std::move(buffer));
150}
151
152void CommandPoolVK::Destroy() {
153 FML_DCHECK(device_holder_.lock());
154 Lock lock(pool_mutex_);
155 pool_.reset();
156
157 // When the command pool is destroyed, all of its command buffers are freed.
158 // Handles allocated from that pool are now invalid and must be discarded.
159 for (auto& buffer : collected_buffers_) {
160 buffer.release();
161 }
162 for (auto& buffer : unused_command_buffers_) {
163 buffer.release();
164 }
165 unused_command_buffers_.clear();
166 collected_buffers_.clear();
167}
168
169// Associates a resource with a thread and context.
171 std::unordered_map<uint64_t, std::shared_ptr<CommandPoolVK>>;
172
173// CommandPoolVK Lifecycle:
174// 1. End of frame will reset the command pool (clearing this on a thread).
175// There will still be references to the command pool from the uncompleted
176// command buffers.
177// 2. The last reference to the command pool will be released from the fence
178// waiter thread, which will schedule a task on the resource
179// manager thread, which in turn will reset the command pool and make it
180// available for reuse ("recycle").
181static thread_local std::unique_ptr<CommandPoolMap> tls_command_pool_map;
182
183// Map each context to a list of all thread-local command pools associated
184// with that context.
186static std::unordered_map<
187 uint64_t,
188 std::unordered_map<std::thread::id, std::weak_ptr<CommandPoolVK>>>
190
192 const std::shared_ptr<ContextVK>& context)
193 : context_(context), context_hash_(context->GetHash()) {}
194
195// Visible for testing.
196// Returns the number of pools in g_all_pools_map for the given context.
198 Lock all_pools_lock(g_all_pools_map_mutex);
199 auto it = g_all_pools_map.find(context.GetHash());
200 return it != g_all_pools_map.end() ? it->second.size() : 0;
201}
202
203// TODO(matanlurey): Return a status_or<> instead of nullptr when we have one.
204std::shared_ptr<CommandPoolVK> CommandPoolRecyclerVK::Get() {
205 auto const strong_context = context_.lock();
206 if (!strong_context) {
207 return nullptr;
208 }
209
210 // If there is a resource in used for this thread and context, return it.
211 if (!tls_command_pool_map.get()) {
213 }
214 CommandPoolMap& pool_map = *tls_command_pool_map.get();
215 auto const it = pool_map.find(context_hash_);
216 if (it != pool_map.end()) {
217 return it->second;
218 }
219
220 // Otherwise, create a new resource and return it.
221 auto data = Create();
222 if (!data || !data->pool) {
223 return nullptr;
224 }
225
226 auto const resource = std::make_shared<CommandPoolVK>(
227 std::move(data->pool), std::move(data->buffers), context_,
228 strong_context->GetDeviceHolder());
229 pool_map.emplace(context_hash_, resource);
230
231 {
232 Lock all_pools_lock(g_all_pools_map_mutex);
233 g_all_pools_map[context_hash_][std::this_thread::get_id()] = resource;
234 }
235
236 return resource;
237}
238
239// TODO(matanlurey): Return a status_or<> instead of nullopt when we have one.
240std::optional<CommandPoolRecyclerVK::RecycledData>
241CommandPoolRecyclerVK::Create() {
242 // If we can reuse a command pool and its buffers, do so.
243 if (auto data = Reuse()) {
244 return data;
245 }
246
247 // Otherwise, create a new one.
248 auto context = context_.lock();
249 if (!context) {
250 return std::nullopt;
251 }
252 vk::CommandPoolCreateInfo info;
253 info.setQueueFamilyIndex(context->GetGraphicsQueue()->GetIndex().family);
254 info.setFlags(vk::CommandPoolCreateFlagBits::eTransient);
255
256 auto device = context->GetDevice();
257 auto [result, pool] = device.createCommandPoolUnique(info);
258 if (result != vk::Result::eSuccess) {
259 return std::nullopt;
260 }
261 return CommandPoolRecyclerVK::RecycledData{.pool = std::move(pool),
262 .buffers = {}};
263}
264
265std::optional<CommandPoolRecyclerVK::RecycledData>
266CommandPoolRecyclerVK::Reuse() {
267 // If there are no recycled pools, return nullopt.
268 Lock recycled_lock(recycled_mutex_);
269 if (recycled_.empty()) {
270 return std::nullopt;
271 }
272
273 // Otherwise, remove and return a recycled pool.
274 auto data = std::move(recycled_.back());
275 recycled_.pop_back();
276 return std::move(data);
277}
278
280 vk::UniqueCommandPool&& pool,
281 std::vector<vk::UniqueCommandBuffer>&& buffers,
282 bool should_trim) {
283 // Reset the pool on a background thread.
284 auto strong_context = context_.lock();
285 if (!strong_context) {
286 return;
287 }
288 auto device = strong_context->GetDevice();
289 vk::CommandPoolResetFlags flags;
290 if (should_trim) {
291 buffers.clear();
292 flags = vk::CommandPoolResetFlagBits::eReleaseResources;
293 }
294 const auto result = device.resetCommandPool(pool.get(), flags);
295 if (result != vk::Result::eSuccess) {
296 VALIDATION_LOG << "Could not reset command pool: " << vk::to_string(result);
297 }
298
299 // Move the pool to the recycled list.
300 Lock recycled_lock(recycled_mutex_);
301 recycled_.push_back(
302 RecycledData{.pool = std::move(pool), .buffers = std::move(buffers)});
303}
304
306 CommandPoolMap* pool_map = tls_command_pool_map.get();
307 if (pool_map) {
308 pool_map->erase(context_hash_);
309 }
310
311 {
312 Lock all_pools_lock(g_all_pools_map_mutex);
313 auto found = g_all_pools_map.find(context_hash_);
314 if (found != g_all_pools_map.end()) {
315 found->second.erase(std::this_thread::get_id());
316 }
317 }
318}
319
321 // Delete the context's entry in this thread's command pool map.
322 if (tls_command_pool_map.get()) {
323 tls_command_pool_map.get()->erase(context_hash_);
324 }
325
326 // Destroy all other thread-local CommandPoolVK instances associated with
327 // this context.
328 Lock all_pools_lock(g_all_pools_map_mutex);
329 auto found = g_all_pools_map.find(context_hash_);
330 if (found != g_all_pools_map.end()) {
331 for (auto& [thread_id, weak_pool] : found->second) {
332 auto pool = weak_pool.lock();
333 if (!pool) {
334 continue;
335 }
336 // Delete all objects held by this pool. The destroyed pool will still
337 // remain in its thread's TLS map until that thread exits.
338 pool->Destroy();
339 }
340 g_all_pools_map.erase(found);
341 }
342}
343
344} // namespace impeller
BackgroundCommandPoolVK(BackgroundCommandPoolVK &&)=default
BackgroundCommandPoolVK(vk::UniqueCommandPool &&pool, std::vector< vk::UniqueCommandBuffer > &&buffers, size_t unused_count, std::weak_ptr< CommandPoolRecyclerVK > recycler)
static constexpr size_t kUnusedCommandBufferLimit
CommandPoolRecyclerVK(const std::shared_ptr< ContextVK > &context)
Creates a recycler for the given |ContextVK|.
void Dispose()
Clears this context's thread-local command pool.
static int GetGlobalPoolCount(const ContextVK &context)
std::shared_ptr< CommandPoolVK > Get()
Gets a command pool for the current thread.
void DestroyThreadLocalPools()
Clean up resources held by all per-thread command pools associated with the context.
void Reclaim(vk::UniqueCommandPool &&pool, std::vector< vk::UniqueCommandBuffer > &&buffers, bool should_trim=false)
Returns a command pool to be reset on a background thread.
void CollectCommandBuffer(vk::UniqueCommandBuffer &&buffer)
Collects the given |vk::CommandBuffer| to be retained.
vk::UniqueCommandBuffer CreateCommandBuffer()
Creates and returns a new |vk::CommandBuffer|.
uint64_t GetHash() const
Definition context_vk.h:104
A unique handle to a resource which will be reclaimed by the specified resource manager.
VkDevice device
Definition main.cc:69
#define FML_DCHECK(condition)
Definition logging.h:122
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switch_defs.h:36
static Mutex g_all_pools_map_mutex
static thread_local std::unique_ptr< CommandPoolMap > tls_command_pool_map
std::unordered_map< uint64_t, std::shared_ptr< CommandPoolVK > > CommandPoolMap
Definition ref_ptr.h:261
A unique command pool and zero or more recycled command buffers.
#define IPLR_GUARDED_BY(x)
#define VALIDATION_LOG
Definition validation.h:91