| Flutter Impeller |

command_pool_vk_unittests.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 "flutter/testing/testing.h" // IWYU pragma: keep.
6 #include "fml/synchronization/waitable_event.h"
7 #include "impeller/renderer/backend/vulkan/command_pool_vk.h"
8 #include "impeller/renderer/backend/vulkan/resource_manager_vk.h"
9 #include "impeller/renderer/backend/vulkan/test/mock_vulkan.h"
10
11 namespace impeller {
12 namespace testing {
13
14 TEST(CommandPoolRecyclerVKTest, GetsACommandPoolPerThread) {
15 auto const context = MockVulkanContextBuilder().Build();
16
17 {
18 // Record the memory location of each pointer to a command pool.
19 //
20 // These pools have to be held at this context, otherwise they will be
21 // dropped and recycled and potentially reused by another thread, causing
22 // flaky tests.
23 std::shared_ptr<CommandPoolVK> pool1;
24 std::shared_ptr<CommandPoolVK> pool2;
25
26 // Create a command pool in two threads and record the memory location.
27 std::thread thread1(
28 [&]() { pool1 = context->GetCommandPoolRecycler()->Get(); });
29
30 std::thread thread2(
31 [&]() { pool2 = context->GetCommandPoolRecycler()->Get(); });
32
33 thread1.join();
34 thread2.join();
35
36 // The two command pools should be different.
37 EXPECT_NE(pool1, pool2);
38 }
39
40 context->Shutdown();
41 }
42
43 TEST(CommandPoolRecyclerVKTest, GetsTheSameCommandPoolOnSameThread) {
44 auto const context = MockVulkanContextBuilder().Build();
45
46 auto const pool1 = context->GetCommandPoolRecycler()->Get();
47 auto const pool2 = context->GetCommandPoolRecycler()->Get();
48
49 // The two command pools should be the same.
50 EXPECT_EQ(pool1.get(), pool2.get());
51
52 context->Shutdown();
53 }
54
55 namespace {
56
57 // Invokes the provided callback when the destructor is called.
58 //
59 // Can be moved, but not copied.
60 class DeathRattle final {
61 public:
62 explicit DeathRattle(std::function<void()> callback)
63 : callback_(std::move(callback)) {}
64
65 DeathRattle(DeathRattle&&) = default;
66 DeathRattle& operator=(DeathRattle&&) = default;
67
68 ~DeathRattle() { callback_(); }
69
70 private:
71 std::function<void()> callback_;
72 };
73
74 // Wait for reclaim of recycled command pools.
75 void WaitForReclaim(const std::shared_ptr<ContextVK>& context) {
76 // Add a resource to the resource manager and wait for its destructor to
77 // signal an event.
78 //
79 // This must be done twice because the resource manager does not guarantee
80 // the order in which resources are handled within the set of reclaimable
81 // resources. When the first DeathRattle is signaled there may be pools
82 // within the pending set that have not yet been reclaimed. After the second
83 // DeathRattle is signaled all resources in the original set will have been
84 // reclaimed.
85 for (int i = 0; i < 2; i++) {
86 auto waiter = fml::AutoResetWaitableEvent();
87 auto rattle = DeathRattle([&waiter]() { waiter.Signal(); });
88 {
89 UniqueResourceVKT<DeathRattle> resource(context->GetResourceManager(),
90 std::move(rattle));
91 }
92 waiter.Wait();
93 }
94 }
95
96 // The list of function calls returned by the mock Vulkan device is not thread
97 // safe. Wait for the background thread to finish any pending reclaim
98 // operations before obtaining the list.
99 std::shared_ptr<std::vector<std::string>> ReclaimAndGetMockVulkanFunctions(
100 const std::shared_ptr<ContextVK>& context) {
101 WaitForReclaim(context);
102 return GetMockVulkanFunctions(context->GetDevice());
103 }
104
105 } // namespace
106
107 TEST(CommandPoolRecyclerVKTest, ReclaimMakesCommandPoolAvailable) {
108 auto const context = MockVulkanContextBuilder().Build();
109
110 {
111 // Fetch a pool (which will be created).
112 auto const recycler = context->GetCommandPoolRecycler();
113 auto const pool = recycler->Get();
114
115 // This normally is called at the end of a frame.
116 recycler->Dispose();
117 }
118
119 WaitForReclaim(context);
120
121 // On another thread explicitly, request a new pool.
122 std::thread thread([&]() {
123 auto const pool = context->GetCommandPoolRecycler()->Get();
124 EXPECT_NE(pool.get(), nullptr);
125 });
126
127 thread.join();
128
129 // Now check that we only ever created one pool.
130 auto const called = ReclaimAndGetMockVulkanFunctions(context);
131 EXPECT_EQ(std::count(called->begin(), called->end(), "vkCreateCommandPool"),
132 1u);
133
134 context->Shutdown();
135 }
136
137 TEST(CommandPoolRecyclerVKTest, CommandBuffersAreRecycled) {
138 auto const context = MockVulkanContextBuilder().Build();
139
140 {
141 // Fetch a pool (which will be created).
142 auto const recycler = context->GetCommandPoolRecycler();
143 auto pool = recycler->Get();
144
145 auto buffer = pool->CreateCommandBuffer();
146 pool->CollectCommandBuffer(std::move(buffer));
147
148 // This normally is called at the end of a frame.
149 recycler->Dispose();
150 }
151
152 WaitForReclaim(context);
153
154 {
155 // Create a second pool and command buffer, which should reused the existing
156 // pool and cmd buffer.
157 auto const recycler = context->GetCommandPoolRecycler();
158 auto pool = recycler->Get();
159
160 auto buffer = pool->CreateCommandBuffer();
161 pool->CollectCommandBuffer(std::move(buffer));
162
163 // This normally is called at the end of a frame.
164 recycler->Dispose();
165 }
166
167 // Now check that we only ever created one pool and one command buffer.
168 auto const called = ReclaimAndGetMockVulkanFunctions(context);
169 EXPECT_EQ(std::count(called->begin(), called->end(), "vkCreateCommandPool"),
170 1u);
171 EXPECT_EQ(
172 std::count(called->begin(), called->end(), "vkAllocateCommandBuffers"),
173 1u);
174
175 context->Shutdown();
176 }
177
178 TEST(CommandPoolRecyclerVKTest, ExtraCommandBufferAllocationsTriggerTrim) {
179 auto const context = MockVulkanContextBuilder().Build();
180
181 {
182 // Fetch a pool (which will be created).
183 auto const recycler = context->GetCommandPoolRecycler();
184 auto pool = recycler->Get();
185
186 // Allocate a large number of command buffers
187 for (auto i = 0; i < 64; i++) {
188 auto buffer = pool->CreateCommandBuffer();
189 pool->CollectCommandBuffer(std::move(buffer));
190 }
191
192 // This normally is called at the end of a frame.
193 recycler->Dispose();
194 }
195
196 // Command pool is reset but does not release resources.
197 auto called = ReclaimAndGetMockVulkanFunctions(context);
198 EXPECT_EQ(std::count(called->begin(), called->end(), "vkResetCommandPool"),
199 1u);
200
201 // Create the pool a second time, but dont use any command buffers.
202 {
203 // Fetch a pool (which will be created).
204 auto const recycler = context->GetCommandPoolRecycler();
205 auto pool = recycler->Get();
206
207 // This normally is called at the end of a frame.
208 recycler->Dispose();
209 }
210
211 // Verify that the cmd pool was trimmed.
212
213 // Now check that we only ever created one pool and one command buffer.
214 called = ReclaimAndGetMockVulkanFunctions(context);
215 EXPECT_EQ(std::count(called->begin(), called->end(),
216 "vkResetCommandPoolReleaseResources"),
217 1u);
218
219 context->Shutdown();
220 }
221
222 TEST(CommandPoolRecyclerVKTest, RecyclerGlobalPoolMapSize) {
223 auto context = MockVulkanContextBuilder().Build();
224 auto const recycler = context->GetCommandPoolRecycler();
225
226 // The global pool list for this context should initially be empty.
227 EXPECT_EQ(CommandPoolRecyclerVK::GetGlobalPoolCount(*context), 0);
228
229 // Creating a pool for this thread should insert the pool into the global map.
230 auto pool = recycler->Get();
231 EXPECT_EQ(CommandPoolRecyclerVK::GetGlobalPoolCount(*context), 1);
232
233 // Disposing this thread's pool should remove it from the global map.
234 pool.reset();
235 recycler->Dispose();
236 EXPECT_EQ(CommandPoolRecyclerVK::GetGlobalPoolCount(*context), 0);
237
238 context->Shutdown();
239 }
240
241 } // namespace testing
242 } // namespace impeller
impeller::CommandPoolRecyclerVK::GetGlobalPoolCount
static int GetGlobalPoolCount(const ContextVK &context)
Definition: command_pool_vk.cc:185
command_pool_vk.h
impeller::testing::TEST
TEST(AllocationSizeTest, CanCreateTypedAllocations)
Definition: allocation_size_unittests.cc:10
impeller
Definition: allocation.cc:12
std
Definition: comparable.h:95
resource_manager_vk.h