Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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.
11
12namespace impeller {
13namespace testing {
14
15TEST(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
44TEST(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
56namespace {
57
58// Invokes the provided callback when the destructor is called.
59//
60// Can be moved, but not copied.
61class 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.
76void 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.
100std::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
108TEST(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
138TEST(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
179TEST(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
223TEST(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.
228 EXPECT_EQ(CommandPoolRecyclerVK::GetGlobalPoolCount(*context), 0);
229
230 // Creating a pool for this thread should insert the pool into the global map.
231 auto pool = recycler->Get();
232 EXPECT_EQ(CommandPoolRecyclerVK::GetGlobalPoolCount(*context), 1);
233
234 // Disposing this thread's pool should remove it from the global map.
235 pool.reset();
236 recycler->Dispose();
237 EXPECT_EQ(CommandPoolRecyclerVK::GetGlobalPoolCount(*context), 0);
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
255TEST(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
std::shared_ptr< ContextVK > Build()
Create a Vulkan context with Vulkan functions mocked. The caller is given a chance to tinker on the s...
VkDevice device
Definition main.cc:69
FlutterDesktopBinaryReply callback
TEST(FrameTimingsRecorderTest, RecordVsync)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set profile Make the profiler discard new samples once the profiler sample buffer is full When this flag is not the profiler sample buffer is used as a ring buffer
Definition switch_defs.h:98
std::shared_ptr< std::vector< std::string > > GetMockVulkanFunctions(VkDevice device)
Definition ref_ptr.h:261