| 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 } // namespace
75
76 TEST(CommandPoolRecyclerVKTest, ReclaimMakesCommandPoolAvailable) {
77 auto const context = MockVulkanContextBuilder().Build();
78
79 {
80 // Fetch a pool (which will be created).
81 auto const recycler = context->GetCommandPoolRecycler();
82 auto const pool = recycler->Get();
83
84 // This normally is called at the end of a frame.
85 recycler->Dispose();
86 }
87
88 // Add something to the resource manager and have it notify us when it's
89 // destroyed. That should give us a non-flaky signal that the pool has been
90 // reclaimed as well.
91 auto waiter = fml::AutoResetWaitableEvent();
92 auto rattle = DeathRattle([&waiter]() { waiter.Signal(); });
93 {
94 UniqueResourceVKT<DeathRattle> resource(context->GetResourceManager(),
95 std::move(rattle));
96 }
97 waiter.Wait();
98
99 // On another thread explicitly, request a new pool.
100 std::thread thread([&]() {
101 auto const pool = context->GetCommandPoolRecycler()->Get();
102 EXPECT_NE(pool.get(), nullptr);
103 });
104
105 thread.join();
106
107 // Now check that we only ever created one pool.
108 auto const called = GetMockVulkanFunctions(context->GetDevice());
109 EXPECT_EQ(std::count(called->begin(), called->end(), "vkCreateCommandPool"),
110 1u);
111
112 context->Shutdown();
113 }
114
115 TEST(CommandPoolRecyclerVKTest, CommandBuffersAreRecycled) {
116 auto const context = MockVulkanContextBuilder().Build();
117
118 {
119 // Fetch a pool (which will be created).
120 auto const recycler = context->GetCommandPoolRecycler();
121 auto pool = recycler->Get();
122
123 auto buffer = pool->CreateCommandBuffer();
124 pool->CollectCommandBuffer(std::move(buffer));
125
126 // This normally is called at the end of a frame.
127 recycler->Dispose();
128 }
129
130 // Wait for the pool to be reclaimed.
131 for (auto i = 0u; i < 2u; i++) {
132 auto waiter = fml::AutoResetWaitableEvent();
133 auto rattle = DeathRattle([&waiter]() { waiter.Signal(); });
134 {
135 UniqueResourceVKT<DeathRattle> resource(context->GetResourceManager(),
136 std::move(rattle));
137 }
138 waiter.Wait();
139 }
140
141 {
142 // Create a second pool and command buffer, which should reused the existing
143 // pool and cmd buffer.
144 auto const recycler = context->GetCommandPoolRecycler();
145 auto pool = recycler->Get();
146
147 auto buffer = pool->CreateCommandBuffer();
148 pool->CollectCommandBuffer(std::move(buffer));
149
150 // This normally is called at the end of a frame.
151 recycler->Dispose();
152 }
153
154 // Now check that we only ever created one pool and one command buffer.
155 auto const called = GetMockVulkanFunctions(context->GetDevice());
156 EXPECT_EQ(std::count(called->begin(), called->end(), "vkCreateCommandPool"),
157 1u);
158 EXPECT_EQ(
159 std::count(called->begin(), called->end(), "vkAllocateCommandBuffers"),
160 1u);
161
162 context->Shutdown();
163 }
164
165 TEST(CommandPoolRecyclerVKTest, ExtraCommandBufferAllocationsTriggerTrim) {
166 auto const context = MockVulkanContextBuilder().Build();
167
168 {
169 // Fetch a pool (which will be created).
170 auto const recycler = context->GetCommandPoolRecycler();
171 auto pool = recycler->Get();
172
173 // Allocate a large number of command buffers
174 for (auto i = 0; i < 64; i++) {
175 auto buffer = pool->CreateCommandBuffer();
176 pool->CollectCommandBuffer(std::move(buffer));
177 }
178
179 // This normally is called at the end of a frame.
180 recycler->Dispose();
181 }
182
183 // Wait for the pool to be reclaimed.
184 for (auto i = 0u; i < 2u; i++) {
185 auto waiter = fml::AutoResetWaitableEvent();
186 auto rattle = DeathRattle([&waiter]() { waiter.Signal(); });
187 {
188 UniqueResourceVKT<DeathRattle> resource(context->GetResourceManager(),
189 std::move(rattle));
190 }
191 waiter.Wait();
192 }
193
194 // Command pool is reset but does not release resources.
195 auto called = GetMockVulkanFunctions(context->GetDevice());
196 EXPECT_EQ(std::count(called->begin(), called->end(), "vkResetCommandPool"),
197 1u);
198
199 // Create the pool a second time, but dont use any command buffers.
200 {
201 // Fetch a pool (which will be created).
202 auto const recycler = context->GetCommandPoolRecycler();
203 auto pool = recycler->Get();
204
205 // This normally is called at the end of a frame.
206 recycler->Dispose();
207 }
208
209 // Wait for the pool to be reclaimed.
210 for (auto i = 0u; i < 2u; i++) {
211 auto waiter = fml::AutoResetWaitableEvent();
212 auto rattle = DeathRattle([&waiter]() { waiter.Signal(); });
213 {
214 UniqueResourceVKT<DeathRattle> resource(context->GetResourceManager(),
215 std::move(rattle));
216 }
217 waiter.Wait();
218 }
219
220 // Verify that the cmd pool was trimmed.
221
222 // Now check that we only ever created one pool and one command buffer.
223 called = GetMockVulkanFunctions(context->GetDevice());
224 EXPECT_EQ(std::count(called->begin(), called->end(),
225 "vkResetCommandPoolReleaseResources"),
226 1u);
227
228 context->Shutdown();
229 }
230
231 } // namespace testing
232 } // namespace impeller
impeller::UniqueResourceVKT
A unique handle to a resource which will be reclaimed by the specified resource manager.
Definition: resource_manager_vk.h:143
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