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