Flutter Impeller
descriptor_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 #include <optional>
7 
8 #include "flutter/fml/trace_event.h"
12 #include "vulkan/vulkan_enums.hpp"
13 #include "vulkan/vulkan_handles.hpp"
14 
15 namespace impeller {
16 
17 // Holds the command pool in a background thread, recyling it when not in use.
19  public:
21 
23  vk::UniqueDescriptorPool&& pool,
24  uint32_t allocated_capacity,
25  std::weak_ptr<DescriptorPoolRecyclerVK> recycler)
26  : pool_(std::move(pool)),
27  allocated_capacity_(allocated_capacity),
28  recycler_(std::move(recycler)) {}
29 
31  auto const recycler = recycler_.lock();
32 
33  // Not only does this prevent recycling when the context is being destroyed,
34  // but it also prevents the destructor from effectively being called twice;
35  // once for the original BackgroundCommandPoolVK() and once for the moved
36  // BackgroundCommandPoolVK().
37  if (!recycler) {
38  return;
39  }
40 
41  recycler->Reclaim(std::move(pool_), allocated_capacity_);
42  }
43 
44  private:
46 
48  delete;
49 
50  vk::UniqueDescriptorPool pool_;
51  uint32_t allocated_capacity_;
52  std::weak_ptr<DescriptorPoolRecyclerVK> recycler_;
53 };
54 
56  const std::weak_ptr<const ContextVK>& context)
57  : context_(context) {
58  FML_DCHECK(context.lock());
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->GetDescriptorPoolRecycler();
71  if (!recycler) {
72  return;
73  }
74 
75  auto reset_pool_when_dropped = BackgroundDescriptorPoolVK(
76  std::move(pool_), allocated_capacity_, recycler);
77 
79  context->GetResourceManager(), std::move(reset_pool_when_dropped));
80 }
81 
82 fml::StatusOr<std::vector<vk::DescriptorSet>>
84  uint32_t buffer_count,
85  uint32_t sampler_count,
86  uint32_t subpass_count,
87  const std::vector<vk::DescriptorSetLayout>& layouts) {
88  std::shared_ptr<const ContextVK> strong_context = context_.lock();
89  if (!strong_context) {
90  return fml::Status(fml::StatusCode::kUnknown, "No device");
91  }
92  auto minimum_capacity =
93  std::max(std::max(sampler_count, buffer_count), subpass_count);
94  auto [new_pool, capacity] =
95  strong_context->GetDescriptorPoolRecycler()->Get(minimum_capacity);
96  if (!new_pool) {
97  return fml::Status(fml::StatusCode::kUnknown,
98  "Failed to create descriptor pool");
99  }
100  pool_ = std::move(new_pool);
101  allocated_capacity_ = capacity;
102 
103  vk::DescriptorSetAllocateInfo set_info;
104  set_info.setDescriptorPool(pool_.get());
105  set_info.setSetLayouts(layouts);
106 
107  auto [result, sets] =
108  strong_context->GetDevice().allocateDescriptorSets(set_info);
109  if (result != vk::Result::eSuccess) {
110  VALIDATION_LOG << "Could not allocate descriptor sets: "
111  << vk::to_string(result);
112  return fml::Status(fml::StatusCode::kUnknown, "");
113  }
114  return sets;
115 }
116 
117 void DescriptorPoolRecyclerVK::Reclaim(vk::UniqueDescriptorPool&& pool,
118  uint32_t allocated_capacity) {
119  // Reset the pool on a background thread.
120  auto strong_context = context_.lock();
121  if (!strong_context) {
122  return;
123  }
124  auto device = strong_context->GetDevice();
125  device.resetDescriptorPool(pool.get());
126 
127  // Move the pool to the recycled list.
128  Lock recycled_lock(recycled_mutex_);
129 
130  if (recycled_.size() < kMaxRecycledPools) {
131  recycled_.push_back(std::make_pair(std::move(pool), allocated_capacity));
132  return;
133  }
134 
135  // If recycled has exceeded the max size of 32, then we need to remove a pool
136  // from the list. If we were to drop this pool, then there is a risk that
137  // the list of recycled descriptor pools could fill up with descriptors that
138  // are too small to reuse. This would lead to all subsequent descriptor
139  // allocations no longer being recycled. Instead, we pick the first
140  // descriptor pool with a smaller capacity than the reseting pool to drop.
141  // This may result in us dropping the current pool instead.
142  std::optional<size_t> selected_index = std::nullopt;
143  for (auto i = 0u; i < recycled_.size(); i++) {
144  const auto& [_, capacity] = recycled_[i];
145  if (capacity < allocated_capacity) {
146  selected_index = i;
147  break;
148  }
149  }
150  if (selected_index.has_value()) {
151  recycled_[selected_index.value()] =
152  std::make_pair(std::move(pool), allocated_capacity);
153  }
154  // If selected index has no value, then no pools had a smaller capacity than
155  // this one and we drop it instead.
156 }
157 
159  // See note on DescriptorPoolRecyclerVK doc comment.
160  auto rounded_capacity =
161  std::max(Allocation::NextPowerOfTwoSize(minimum_capacity), 64u);
162 
163  // Recycle a pool with a matching minumum capcity if it is available.
164  auto recycled_pool = Reuse(rounded_capacity);
165  if (recycled_pool.has_value()) {
166  return std::move(recycled_pool.value());
167  }
168  return Create(rounded_capacity);
169 }
170 
171 DescriptorPoolAndSize DescriptorPoolRecyclerVK::Create(
172  uint32_t minimum_capacity) {
173  FML_DCHECK(Allocation::NextPowerOfTwoSize(minimum_capacity) ==
174  minimum_capacity);
175  auto strong_context = context_.lock();
176  if (!strong_context) {
177  VALIDATION_LOG << "Unable to create a descriptor pool";
178  return {};
179  }
180 
181  std::vector<vk::DescriptorPoolSize> pools = {
182  vk::DescriptorPoolSize{vk::DescriptorType::eCombinedImageSampler,
183  minimum_capacity},
184  vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer,
185  minimum_capacity},
186  vk::DescriptorPoolSize{vk::DescriptorType::eStorageBuffer,
187  minimum_capacity},
188  vk::DescriptorPoolSize{vk::DescriptorType::eInputAttachment,
189  minimum_capacity}};
190  vk::DescriptorPoolCreateInfo pool_info;
191  pool_info.setMaxSets(minimum_capacity + minimum_capacity);
192  pool_info.setPoolSizes(pools);
193  auto [result, pool] =
194  strong_context->GetDevice().createDescriptorPoolUnique(pool_info);
195  if (result != vk::Result::eSuccess) {
196  VALIDATION_LOG << "Unable to create a descriptor pool";
197  }
198  return std::make_pair(std::move(pool), minimum_capacity);
199 }
200 
201 std::optional<DescriptorPoolAndSize> DescriptorPoolRecyclerVK::Reuse(
202  uint32_t minimum_capacity) {
203  FML_DCHECK(Allocation::NextPowerOfTwoSize(minimum_capacity) ==
204  minimum_capacity);
205  Lock lock(recycled_mutex_);
206 
207  std::optional<size_t> found_index = std::nullopt;
208  for (auto i = 0u; i < recycled_.size(); i++) {
209  const auto& [_, capacity] = recycled_[i];
210  if (capacity >= minimum_capacity) {
211  found_index = i;
212  break;
213  }
214  }
215  if (!found_index.has_value()) {
216  return std::nullopt;
217  }
218  auto pair = std::move(recycled_[found_index.value()]);
219  recycled_.erase(recycled_.begin() + found_index.value());
220  return pair;
221 }
222 
223 } // namespace impeller
allocation.h
impeller::Allocation::NextPowerOfTwoSize
static uint32_t NextPowerOfTwoSize(uint32_t x)
Definition: allocation.cc:41
impeller::UniqueResourceVKT
A unique handle to a resource which will be reclaimed by the specified resource manager.
Definition: resource_manager_vk.h:145
impeller::DescriptorPoolVK::~DescriptorPoolVK
~DescriptorPoolVK()
Definition: descriptor_pool_vk.cc:61
impeller::Lock
Definition: thread.h:75
impeller::BackgroundDescriptorPoolVK::BackgroundDescriptorPoolVK
BackgroundDescriptorPoolVK(BackgroundDescriptorPoolVK &&)=default
impeller::DescriptorPoolRecyclerVK::Reclaim
void Reclaim(vk::UniqueDescriptorPool &&pool, uint32_t allocated_capacity)
Returns the descriptor pool to be reset on a background thread.
Definition: descriptor_pool_vk.cc:117
validation.h
impeller::DescriptorPoolVK::DescriptorPoolVK
DescriptorPoolVK(const std::weak_ptr< const ContextVK > &context)
Definition: descriptor_pool_vk.cc:55
impeller::DescriptorPoolRecyclerVK::kMaxRecycledPools
static constexpr size_t kMaxRecycledPools
The maximum number of descriptor pools this recycler will hold onto.
Definition: descriptor_pool_vk.h:73
impeller::BackgroundDescriptorPoolVK::~BackgroundDescriptorPoolVK
~BackgroundDescriptorPoolVK()
Definition: descriptor_pool_vk.cc:30
impeller::DescriptorPoolAndSize
std::pair< vk::UniqueDescriptorPool, uint32_t > DescriptorPoolAndSize
Definition: descriptor_pool_vk.h:50
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:67
resource_manager_vk.h
std
Definition: comparable.h:95
impeller::DescriptorPoolRecyclerVK::Get
DescriptorPoolAndSize Get(uint32_t minimum_capacity)
Gets a descriptor pool with at least [minimum_capacity] sampler and slots.
Definition: descriptor_pool_vk.cc:158
descriptor_pool_vk.h
impeller::DescriptorPoolVK::AllocateDescriptorSets
fml::StatusOr< std::vector< vk::DescriptorSet > > AllocateDescriptorSets(uint32_t buffer_count, uint32_t sampler_count, uint32_t subpass_count, const std::vector< vk::DescriptorSetLayout > &layouts)
Definition: descriptor_pool_vk.cc:83
impeller::BackgroundDescriptorPoolVK::BackgroundDescriptorPoolVK
BackgroundDescriptorPoolVK(vk::UniqueDescriptorPool &&pool, uint32_t allocated_capacity, std::weak_ptr< DescriptorPoolRecyclerVK > recycler)
Definition: descriptor_pool_vk.cc:22
impeller
Definition: aiks_context.cc:10
impeller::BackgroundDescriptorPoolVK
Definition: descriptor_pool_vk.cc:18