| 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
5 #include "impeller/renderer/backend/vulkan/descriptor_pool_vk.h"
6 #include <optional>
7
8 #include "flutter/fml/trace_event.h"
9 #include "impeller/base/allocation.h"
10 #include "impeller/base/validation.h"
11 #include "impeller/renderer/backend/vulkan/resource_manager_vk.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.
18 class BackgroundDescriptorPoolVK final {
19 public:
20 BackgroundDescriptorPoolVK(BackgroundDescriptorPoolVK&&) = default;
21
22 explicit BackgroundDescriptorPoolVK(
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
30 ~BackgroundDescriptorPoolVK() {
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:
45 BackgroundDescriptorPoolVK(const BackgroundDescriptorPoolVK&) = delete;
46
47 BackgroundDescriptorPoolVK& operator=(const BackgroundDescriptorPoolVK&) =
48 delete;
49
50 vk::UniqueDescriptorPool pool_;
51 uint32_t allocated_capacity_;
52 std::weak_ptr<DescriptorPoolRecyclerVK> recycler_;
53 };
54
55 DescriptorPoolVK::DescriptorPoolVK(
56 const std::weak_ptr<const ContextVK>& context)
57 : context_(context) {
58 FML_DCHECK(context.lock());
59 }
60
61 DescriptorPoolVK::~DescriptorPoolVK() {
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
78 UniqueResourceVKT<BackgroundDescriptorPoolVK> pool(
79 context->GetResourceManager(), std::move(reset_pool_when_dropped));
80 }
81
82 fml::StatusOr<std::vector<vk::DescriptorSet>>
83 DescriptorPoolVK::AllocateDescriptorSets(
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
158 DescriptorPoolAndSize DescriptorPoolRecyclerVK::Get(uint32_t minimum_capacity) {
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