Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 42c2a40

Browse files
author
Jonah Williams
authored
[Impeller] one descriptor pool per frame. (#55939)
Creating descriptor pools is expensive, no need to have more than one per frame. The lifecycle of the descriptor pool is exactly the same as the cmd pool, which indicates that we can probably do some consolidation/refactoring in the future. Fixes flutter/flutter#157115
1 parent ad9e4fe commit 42c2a40

File tree

10 files changed

+138
-40
lines changed

10 files changed

+138
-40
lines changed

impeller/renderer/backend/vulkan/command_buffer_vk.cc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,10 @@ namespace impeller {
2222
CommandBufferVK::CommandBufferVK(
2323
std::weak_ptr<const Context> context,
2424
std::weak_ptr<const DeviceHolderVK> device_holder,
25-
std::shared_ptr<TrackedObjectsVK> tracked_objects,
26-
std::shared_ptr<FenceWaiterVK> fence_waiter)
25+
std::shared_ptr<TrackedObjectsVK> tracked_objects)
2726
: CommandBuffer(std::move(context)),
2827
device_holder_(std::move(device_holder)),
29-
tracked_objects_(std::move(tracked_objects)),
30-
fence_waiter_(std::move(fence_waiter)) {}
28+
tracked_objects_(std::move(tracked_objects)) {}
3129

3230
CommandBufferVK::~CommandBufferVK() = default;
3331

@@ -212,4 +210,8 @@ void CommandBufferVK::InsertDebugMarker(std::string_view label) const {
212210
}
213211
}
214212

213+
DescriptorPoolVK& CommandBufferVK::GetDescriptorPool() const {
214+
return tracked_objects_->GetDescriptorPool();
215+
}
216+
215217
} // namespace impeller

impeller/renderer/backend/vulkan/command_buffer_vk.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "fml/status_or.h"
99
#include "impeller/base/backend_cast.h"
1010
#include "impeller/renderer/backend/vulkan/command_queue_vk.h"
11+
#include "impeller/renderer/backend/vulkan/descriptor_pool_vk.h"
1112
#include "impeller/renderer/backend/vulkan/device_holder_vk.h"
1213
#include "impeller/renderer/backend/vulkan/texture_source_vk.h"
1314
#include "impeller/renderer/backend/vulkan/tracked_objects_vk.h"
@@ -81,18 +82,19 @@ class CommandBufferVK final
8182
// Visible for testing.
8283
bool IsTracking(const std::shared_ptr<const Texture>& texture) const;
8384

85+
// Visible for testing.
86+
DescriptorPoolVK& GetDescriptorPool() const;
87+
8488
private:
8589
friend class ContextVK;
8690
friend class CommandQueueVK;
8791

8892
std::weak_ptr<const DeviceHolderVK> device_holder_;
8993
std::shared_ptr<TrackedObjectsVK> tracked_objects_;
90-
std::shared_ptr<FenceWaiterVK> fence_waiter_;
9194

9295
CommandBufferVK(std::weak_ptr<const Context> context,
9396
std::weak_ptr<const DeviceHolderVK> device_holder,
94-
std::shared_ptr<TrackedObjectsVK> tracked_objects,
95-
std::shared_ptr<FenceWaiterVK> fence_waiter);
97+
std::shared_ptr<TrackedObjectsVK> tracked_objects);
9698

9799
// |CommandBuffer|
98100
void SetLabel(std::string_view label) const override;

impeller/renderer/backend/vulkan/command_pool_vk.cc

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <utility>
1010

1111
#include "impeller/renderer/backend/vulkan/context_vk.h"
12+
#include "impeller/renderer/backend/vulkan/descriptor_pool_vk.h"
1213
#include "impeller/renderer/backend/vulkan/resource_manager_vk.h"
1314

1415
#include "impeller/renderer/backend/vulkan/vk.h" // IWYU pragma: keep.
@@ -155,10 +156,6 @@ void CommandPoolVK::Destroy() {
155156
collected_buffers_.clear();
156157
}
157158

158-
// Associates a resource with a thread and context.
159-
using CommandPoolMap =
160-
std::unordered_map<uint64_t, std::shared_ptr<CommandPoolVK>>;
161-
162159
// CommandPoolVK Lifecycle:
163160
// 1. End of frame will reset the command pool (clearing this on a thread).
164161
// There will still be references to the command pool from the uncompleted
@@ -169,17 +166,47 @@ using CommandPoolMap =
169166
// available for reuse ("recycle").
170167
static thread_local std::unique_ptr<CommandPoolMap> tls_command_pool_map;
171168

169+
struct WeakThreadLocalData {
170+
std::weak_ptr<CommandPoolVK> command_pool;
171+
std::weak_ptr<DescriptorPoolVK> descriptor_pool;
172+
};
173+
172174
// Map each context to a list of all thread-local command pools associated
173175
// with that context.
174176
static Mutex g_all_pools_map_mutex;
175-
static std::unordered_map<
176-
const ContextVK*,
177-
std::vector<std::weak_ptr<CommandPoolVK>>> g_all_pools_map
177+
static std::unordered_map<const ContextVK*,
178+
std::vector<WeakThreadLocalData>> g_all_pools_map
178179
IPLR_GUARDED_BY(g_all_pools_map_mutex);
179180

181+
std::shared_ptr<DescriptorPoolVK> CommandPoolRecyclerVK::GetDescriptorPool() {
182+
const auto& strong_context = context_.lock();
183+
if (!strong_context) {
184+
return nullptr;
185+
}
186+
187+
// If there is a resource in used for this thread and context, return it.
188+
if (!tls_command_pool_map.get()) {
189+
tls_command_pool_map.reset(new CommandPoolMap());
190+
}
191+
CommandPoolMap& pool_map = *tls_command_pool_map.get();
192+
auto const hash = strong_context->GetHash();
193+
auto const it = pool_map.find(hash);
194+
if (it != pool_map.end()) {
195+
return it->second.descriptor_pool;
196+
}
197+
198+
const auto& result =
199+
InitializeThreadLocalResources(strong_context, pool_map, hash);
200+
if (result.has_value()) {
201+
return result->descriptor_pool;
202+
}
203+
204+
return nullptr;
205+
}
206+
180207
// TODO(matanlurey): Return a status_or<> instead of nullptr when we have one.
181208
std::shared_ptr<CommandPoolVK> CommandPoolRecyclerVK::Get() {
182-
auto const strong_context = context_.lock();
209+
const auto& strong_context = context_.lock();
183210
if (!strong_context) {
184211
return nullptr;
185212
}
@@ -192,25 +219,40 @@ std::shared_ptr<CommandPoolVK> CommandPoolRecyclerVK::Get() {
192219
auto const hash = strong_context->GetHash();
193220
auto const it = pool_map.find(hash);
194221
if (it != pool_map.end()) {
195-
return it->second;
222+
return it->second.command_pool;
196223
}
197224

198-
// Otherwise, create a new resource and return it.
225+
const auto& result =
226+
InitializeThreadLocalResources(strong_context, pool_map, hash);
227+
if (result.has_value()) {
228+
return result->command_pool;
229+
}
230+
231+
return nullptr;
232+
}
233+
234+
std::optional<ThreadLocalData>
235+
CommandPoolRecyclerVK::InitializeThreadLocalResources(
236+
const std::shared_ptr<ContextVK>& context,
237+
CommandPoolMap& pool_map,
238+
uint64_t pool_key) {
199239
auto data = Create();
200240
if (!data || !data->pool) {
201-
return nullptr;
241+
return std::nullopt;
202242
}
203243

204-
auto const resource = std::make_shared<CommandPoolVK>(
244+
auto command_pool = std::make_shared<CommandPoolVK>(
205245
std::move(data->pool), std::move(data->buffers), context_);
206-
pool_map.emplace(hash, resource);
207-
246+
auto descriptor_pool = std::make_shared<DescriptorPoolVK>(context_);
208247
{
209248
Lock all_pools_lock(g_all_pools_map_mutex);
210-
g_all_pools_map[strong_context.get()].push_back(resource);
249+
g_all_pools_map[context.get()].push_back(WeakThreadLocalData{
250+
.command_pool = command_pool, .descriptor_pool = descriptor_pool});
211251
}
212252

213-
return resource;
253+
return pool_map[pool_key] =
254+
ThreadLocalData{.command_pool = std::move(command_pool),
255+
.descriptor_pool = std::move(descriptor_pool)};
214256
}
215257

216258
// TODO(matanlurey): Return a status_or<> instead of nullopt when we have one.
@@ -293,14 +335,13 @@ void CommandPoolRecyclerVK::DestroyThreadLocalPools(const ContextVK* context) {
293335
Lock all_pools_lock(g_all_pools_map_mutex);
294336
auto found = g_all_pools_map.find(context);
295337
if (found != g_all_pools_map.end()) {
296-
for (auto& weak_pool : found->second) {
297-
auto pool = weak_pool.lock();
298-
if (!pool) {
299-
continue;
338+
for (auto& [command_pool, desc_pool] : found->second) {
339+
const auto& strong_pool = command_pool.lock();
340+
if (strong_pool) {
341+
// Delete all objects held by this pool. The destroyed pool will still
342+
// remain in its thread's TLS map until that thread exits.
343+
strong_pool->Destroy();
300344
}
301-
// Delete all objects held by this pool. The destroyed pool will still
302-
// remain in its thread's TLS map until that thread exits.
303-
pool->Destroy();
304345
}
305346
g_all_pools_map.erase(found);
306347
}

impeller/renderer/backend/vulkan/command_pool_vk.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <utility>
1111

1212
#include "impeller/base/thread.h"
13+
#include "impeller/renderer/backend/vulkan/descriptor_pool_vk.h"
1314
#include "impeller/renderer/backend/vulkan/vk.h" // IWYU pragma: keep.
1415
#include "vulkan/vulkan_handles.hpp"
1516

@@ -76,6 +77,14 @@ class CommandPoolVK final {
7677
pool_mutex_);
7778
};
7879

80+
struct ThreadLocalData {
81+
std::shared_ptr<CommandPoolVK> command_pool;
82+
std::shared_ptr<DescriptorPoolVK> descriptor_pool;
83+
};
84+
85+
// Associates a resource with a thread and context.
86+
using CommandPoolMap = std::unordered_map<uint64_t, ThreadLocalData>;
87+
7988
//------------------------------------------------------------------------------
8089
/// @brief Creates and manages the lifecycle of |vk::CommandPool| objects.
8190
///
@@ -128,6 +137,11 @@ class CommandPoolRecyclerVK final
128137
/// @warning Returns a |nullptr| if a pool could not be created.
129138
std::shared_ptr<CommandPoolVK> Get();
130139

140+
/// @brief Gets a descriptor pool for the current thread.
141+
///
142+
/// @warning Returns a |nullptr| if a pool could not be created.
143+
std::shared_ptr<DescriptorPoolVK> GetDescriptorPool();
144+
131145
/// @brief Returns a command pool to be reset on a background thread.
132146
///
133147
/// @param[in] pool The pool to recycler.
@@ -153,6 +167,13 @@ class CommandPoolRecyclerVK final
153167
/// @returns Returns a |std::nullopt| if a pool was not available.
154168
std::optional<RecycledData> Reuse();
155169

170+
/// Create a DescriptorPoolVK and a CommandPoolVK and stash them in the TLS
171+
/// map.
172+
std::optional<ThreadLocalData> InitializeThreadLocalResources(
173+
const std::shared_ptr<ContextVK>& context,
174+
CommandPoolMap& pool_map,
175+
uint64_t pool_key);
176+
156177
CommandPoolRecyclerVK(const CommandPoolRecyclerVK&) = delete;
157178

158179
CommandPoolRecyclerVK& operator=(const CommandPoolRecyclerVK&) = delete;

impeller/renderer/backend/vulkan/context_vk.cc

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "fml/concurrent_message_loop.h"
88
#include "impeller/renderer/backend/vulkan/command_queue_vk.h"
9+
#include "impeller/renderer/backend/vulkan/descriptor_pool_vk.h"
910
#include "impeller/renderer/backend/vulkan/render_pass_builder_vk.h"
1011
#include "impeller/renderer/render_target.h"
1112

@@ -495,9 +496,14 @@ std::shared_ptr<CommandBuffer> ContextVK::CreateCommandBuffer() const {
495496
if (!tls_pool) {
496497
return nullptr;
497498
}
499+
auto tls_desc_pool = recycler->GetDescriptorPool();
500+
if (!tls_desc_pool) {
501+
return nullptr;
502+
}
498503

499504
auto tracked_objects = std::make_shared<TrackedObjectsVK>(
500-
weak_from_this(), std::move(tls_pool), GetGPUTracer()->CreateGPUProbe());
505+
weak_from_this(), std::move(tls_pool), tls_desc_pool,
506+
GetGPUTracer()->CreateGPUProbe());
501507
auto queue = GetGraphicsQueue();
502508

503509
if (!tracked_objects || !tracked_objects->IsValid() || !queue) {
@@ -516,10 +522,9 @@ std::shared_ptr<CommandBuffer> ContextVK::CreateCommandBuffer() const {
516522
tracked_objects->GetCommandBuffer());
517523

518524
return std::shared_ptr<CommandBufferVK>(new CommandBufferVK(
519-
shared_from_this(), //
520-
GetDeviceHolder(), //
521-
std::move(tracked_objects), //
522-
GetFenceWaiter() //
525+
shared_from_this(), //
526+
GetDeviceHolder(), //
527+
std::move(tracked_objects) //
523528
));
524529
}
525530

impeller/renderer/backend/vulkan/context_vk.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,9 @@
1010
#include "flutter/fml/concurrent_message_loop.h"
1111
#include "flutter/fml/mapping.h"
1212
#include "flutter/fml/unique_fd.h"
13-
#include "fml/thread.h"
1413
#include "impeller/base/backend_cast.h"
1514
#include "impeller/base/strings.h"
1615
#include "impeller/core/formats.h"
17-
#include "impeller/renderer/backend/vulkan/command_pool_vk.h"
1816
#include "impeller/renderer/backend/vulkan/device_holder_vk.h"
1917
#include "impeller/renderer/backend/vulkan/driver_info_vk.h"
2018
#include "impeller/renderer/backend/vulkan/pipeline_library_vk.h"

impeller/renderer/backend/vulkan/descriptor_pool_vk_unittests.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "flutter/testing/testing.h" // IWYU pragma: keep.
66
#include "fml/closure.h"
77
#include "fml/synchronization/waitable_event.h"
8+
#include "impeller/renderer/backend/vulkan/command_buffer_vk.h"
89
#include "impeller/renderer/backend/vulkan/descriptor_pool_vk.h"
910
#include "impeller/renderer/backend/vulkan/resource_manager_vk.h"
1011
#include "impeller/renderer/backend/vulkan/test/mock_vulkan.h"
@@ -123,5 +124,27 @@ TEST(DescriptorPoolRecyclerVKTest, ReclaimDropsDescriptorPoolIfSizeIsExceeded) {
123124
context->Shutdown();
124125
}
125126

127+
TEST(DescriptorPoolRecyclerVKTest, MultipleCommandBuffersShareDescriptorPool) {
128+
auto const context = MockVulkanContextBuilder().Build();
129+
130+
auto cmd_buffer_1 = context->CreateCommandBuffer();
131+
auto cmd_buffer_2 = context->CreateCommandBuffer();
132+
133+
CommandBufferVK& vk_1 = CommandBufferVK::Cast(*cmd_buffer_1);
134+
CommandBufferVK& vk_2 = CommandBufferVK::Cast(*cmd_buffer_2);
135+
136+
EXPECT_EQ(&vk_1.GetDescriptorPool(), &vk_2.GetDescriptorPool());
137+
138+
// Resetting resources creates a new pool.
139+
context->DisposeThreadLocalCachedResources();
140+
141+
auto cmd_buffer_3 = context->CreateCommandBuffer();
142+
CommandBufferVK& vk_3 = CommandBufferVK::Cast(*cmd_buffer_3);
143+
144+
EXPECT_NE(&vk_1.GetDescriptorPool(), &vk_3.GetDescriptorPool());
145+
146+
context->Shutdown();
147+
}
148+
126149
} // namespace testing
127150
} // namespace impeller

impeller/renderer/backend/vulkan/test/mock_vulkan_unittests.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "flutter/testing/testing.h" // IWYU pragma: keep
66
#include "gtest/gtest.h"
7+
#include "impeller/renderer/backend/vulkan/command_pool_vk.h"
78
#include "impeller/renderer/backend/vulkan/test/mock_vulkan.h"
89
#include "vulkan/vulkan_enums.hpp"
910

impeller/renderer/backend/vulkan/tracked_objects_vk.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@
44

55
#include "impeller/renderer/backend/vulkan/tracked_objects_vk.h"
66

7+
#include "impeller/renderer/backend/vulkan/command_pool_vk.h"
78
#include "impeller/renderer/backend/vulkan/gpu_tracer_vk.h"
89

910
namespace impeller {
1011

1112
TrackedObjectsVK::TrackedObjectsVK(
1213
const std::weak_ptr<const ContextVK>& context,
1314
const std::shared_ptr<CommandPoolVK>& pool,
15+
std::shared_ptr<DescriptorPoolVK> descriptor_pool,
1416
std::unique_ptr<GPUProbe> probe)
15-
: desc_pool_(context), probe_(std::move(probe)) {
17+
: desc_pool_(std::move(descriptor_pool)), probe_(std::move(probe)) {
1618
if (!pool) {
1719
return;
1820
}
@@ -78,7 +80,7 @@ vk::CommandBuffer TrackedObjectsVK::GetCommandBuffer() const {
7880
}
7981

8082
DescriptorPoolVK& TrackedObjectsVK::GetDescriptorPool() {
81-
return desc_pool_;
83+
return *desc_pool_;
8284
}
8385

8486
GPUProbe& TrackedObjectsVK::GetGPUProbe() const {

impeller/renderer/backend/vulkan/tracked_objects_vk.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@
1414

1515
namespace impeller {
1616

17+
class CommandPoolVK;
18+
1719
/// @brief A per-frame object used to track resource lifetimes and allocate
1820
/// command buffers and descriptor sets.
1921
class TrackedObjectsVK {
2022
public:
2123
explicit TrackedObjectsVK(const std::weak_ptr<const ContextVK>& context,
2224
const std::shared_ptr<CommandPoolVK>& pool,
25+
std::shared_ptr<DescriptorPoolVK> descriptor_pool,
2326
std::unique_ptr<GPUProbe> probe);
2427

2528
~TrackedObjectsVK();
@@ -43,7 +46,7 @@ class TrackedObjectsVK {
4346
GPUProbe& GetGPUProbe() const;
4447

4548
private:
46-
DescriptorPoolVK desc_pool_;
49+
std::shared_ptr<DescriptorPoolVK> desc_pool_;
4750
// `shared_ptr` since command buffers have a link to the command pool.
4851
std::shared_ptr<CommandPoolVK> pool_;
4952
vk::UniqueCommandBuffer buffer_;

0 commit comments

Comments
 (0)