Flutter Impeller
context_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/fml/synchronization/waitable_event.h"
6 #include "flutter/testing/testing.h" // IWYU pragma: keep
11 #include "impeller/renderer/backend/vulkan/test/mock_vulkan.h"
12 #include "vulkan/vulkan_core.h"
13 
14 namespace impeller {
15 namespace testing {
16 
17 TEST(ContextVKTest, CommonHardwareConcurrencyConfigurations) {
18  EXPECT_EQ(ContextVK::ChooseThreadCountForWorkers(100u), 4u);
19  EXPECT_EQ(ContextVK::ChooseThreadCountForWorkers(9u), 4u);
20  EXPECT_EQ(ContextVK::ChooseThreadCountForWorkers(8u), 4u);
21  EXPECT_EQ(ContextVK::ChooseThreadCountForWorkers(7u), 3u);
22  EXPECT_EQ(ContextVK::ChooseThreadCountForWorkers(6u), 3u);
23  EXPECT_EQ(ContextVK::ChooseThreadCountForWorkers(5u), 2u);
24  EXPECT_EQ(ContextVK::ChooseThreadCountForWorkers(4u), 2u);
25  EXPECT_EQ(ContextVK::ChooseThreadCountForWorkers(3u), 1u);
26  EXPECT_EQ(ContextVK::ChooseThreadCountForWorkers(2u), 1u);
27  EXPECT_EQ(ContextVK::ChooseThreadCountForWorkers(1u), 1u);
28 }
29 
30 TEST(ContextVKTest, DeletesCommandPools) {
31  std::weak_ptr<ContextVK> weak_context;
32  std::weak_ptr<CommandPoolVK> weak_pool;
33  {
34  std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
35  auto const pool = context->GetCommandPoolRecycler()->Get();
36  weak_pool = pool;
37  weak_context = context;
38  ASSERT_TRUE(weak_pool.lock());
39  ASSERT_TRUE(weak_context.lock());
40  }
41  ASSERT_FALSE(weak_pool.lock());
42  ASSERT_FALSE(weak_context.lock());
43 }
44 
45 TEST(ContextVKTest, DeletesCommandPoolsOnAllThreads) {
46  std::weak_ptr<ContextVK> weak_context;
47  std::weak_ptr<CommandPoolVK> weak_pool_main;
48 
49  std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
50  weak_pool_main = context->GetCommandPoolRecycler()->Get();
51  weak_context = context;
52  ASSERT_TRUE(weak_pool_main.lock());
53  ASSERT_TRUE(weak_context.lock());
54 
55  // Start a second thread that obtains a command pool.
56  fml::AutoResetWaitableEvent latch1, latch2;
57  std::weak_ptr<CommandPoolVK> weak_pool_thread;
58  std::thread thread([&]() {
59  weak_pool_thread = context->GetCommandPoolRecycler()->Get();
60  latch1.Signal();
61  latch2.Wait();
62  });
63 
64  // Delete the ContextVK on the main thread.
65  latch1.Wait();
66  context.reset();
67  ASSERT_FALSE(weak_pool_main.lock());
68  ASSERT_FALSE(weak_context.lock());
69 
70  // Stop the second thread and check that its command pool has been deleted.
71  latch2.Signal();
72  thread.join();
73  ASSERT_FALSE(weak_pool_thread.lock());
74 }
75 
76 TEST(ContextVKTest, ThreadLocalCleanupDeletesCommandPool) {
77  std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
78 
79  fml::AutoResetWaitableEvent latch1, latch2;
80  std::weak_ptr<CommandPoolVK> weak_pool;
81  std::thread thread([&]() {
82  weak_pool = context->GetCommandPoolRecycler()->Get();
83  context->DisposeThreadLocalCachedResources();
84  latch1.Signal();
85  latch2.Wait();
86  });
87 
88  latch1.Wait();
89  ASSERT_FALSE(weak_pool.lock());
90 
91  latch2.Signal();
92  thread.join();
93 }
94 
95 TEST(ContextVKTest, DeletePipelineAfterContext) {
96  std::shared_ptr<Pipeline<PipelineDescriptor>> pipeline;
97  std::shared_ptr<std::vector<std::string>> functions;
98  {
99  std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
100  PipelineDescriptor pipeline_desc;
101  pipeline_desc.SetVertexDescriptor(std::make_shared<VertexDescriptor>());
102  PipelineFuture<PipelineDescriptor> pipeline_future =
103  context->GetPipelineLibrary()->GetPipeline(pipeline_desc);
104  pipeline = pipeline_future.Get();
105  ASSERT_TRUE(pipeline);
106  functions = GetMockVulkanFunctions(context->GetDevice());
107  ASSERT_TRUE(std::find(functions->begin(), functions->end(),
108  "vkCreateGraphicsPipelines") != functions->end());
109  }
110  ASSERT_TRUE(std::find(functions->begin(), functions->end(),
111  "vkDestroyDevice") != functions->end());
112 }
113 
114 TEST(ContextVKTest, DeleteShaderFunctionAfterContext) {
115  std::shared_ptr<const ShaderFunction> shader_function;
116  std::shared_ptr<std::vector<std::string>> functions;
117  {
118  std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
119  PipelineDescriptor pipeline_desc;
120  pipeline_desc.SetVertexDescriptor(std::make_shared<VertexDescriptor>());
121  std::vector<uint8_t> data = {0x03, 0x02, 0x23, 0x07};
122  context->GetShaderLibrary()->RegisterFunction(
123  "foobar_fragment_main", ShaderStage::kFragment,
124  std::make_shared<fml::DataMapping>(data), [](bool) {});
125  shader_function = context->GetShaderLibrary()->GetFunction(
126  "foobar_fragment_main", ShaderStage::kFragment);
127  ASSERT_TRUE(shader_function);
128  functions = GetMockVulkanFunctions(context->GetDevice());
129  ASSERT_TRUE(std::find(functions->begin(), functions->end(),
130  "vkCreateShaderModule") != functions->end());
131  }
132  ASSERT_TRUE(std::find(functions->begin(), functions->end(),
133  "vkDestroyDevice") != functions->end());
134 }
135 
136 TEST(ContextVKTest, DeletePipelineLibraryAfterContext) {
137  std::shared_ptr<PipelineLibrary> pipeline_library;
138  std::shared_ptr<std::vector<std::string>> functions;
139  {
140  std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
141  PipelineDescriptor pipeline_desc;
142  pipeline_desc.SetVertexDescriptor(std::make_shared<VertexDescriptor>());
143  pipeline_library = context->GetPipelineLibrary();
144  functions = GetMockVulkanFunctions(context->GetDevice());
145  ASSERT_TRUE(std::find(functions->begin(), functions->end(),
146  "vkCreatePipelineCache") != functions->end());
147  }
148  ASSERT_TRUE(std::find(functions->begin(), functions->end(),
149  "vkDestroyDevice") != functions->end());
150 }
151 
152 TEST(ContextVKTest, CanCreateContextInAbsenceOfValidationLayers) {
153  // The mocked methods don't report the presence of a validation layer but we
154  // explicitly ask for validation. Context creation should continue anyway.
155  auto context = MockVulkanContextBuilder()
156  .SetSettingsCallback([](auto& settings) {
157  settings.enable_validation = true;
158  })
159  .Build();
160  ASSERT_NE(context, nullptr);
161  const CapabilitiesVK* capabilites_vk =
162  reinterpret_cast<const CapabilitiesVK*>(context->GetCapabilities().get());
163  ASSERT_FALSE(capabilites_vk->AreValidationsEnabled());
164 }
165 
166 TEST(ContextVKTest, CanCreateContextWithValidationLayers) {
167  auto context =
168  MockVulkanContextBuilder()
169  .SetSettingsCallback(
170  [](auto& settings) { settings.enable_validation = true; })
171  .SetInstanceExtensions(
172  {"VK_KHR_surface", "VK_MVK_macos_surface", "VK_EXT_debug_utils"})
173  .SetInstanceLayers({"VK_LAYER_KHRONOS_validation"})
174  .Build();
175  ASSERT_NE(context, nullptr);
176  const CapabilitiesVK* capabilites_vk =
177  reinterpret_cast<const CapabilitiesVK*>(context->GetCapabilities().get());
178  ASSERT_TRUE(capabilites_vk->AreValidationsEnabled());
179 }
180 
181 // In Impeller's 2D renderer, we no longer use stencil-only formats. They're
182 // less widely supported than combined depth-stencil formats, so make sure we
183 // don't fail initialization if we can't find a suitable stencil format.
184 TEST(CapabilitiesVKTest, ContextInitializesWithNoStencilFormat) {
185  const std::shared_ptr<ContextVK> context =
186  MockVulkanContextBuilder()
187  .SetPhysicalDeviceFormatPropertiesCallback(
188  [](VkPhysicalDevice physicalDevice, VkFormat format,
189  VkFormatProperties* pFormatProperties) {
190  if (format == VK_FORMAT_R8G8B8A8_UNORM) {
191  pFormatProperties->optimalTilingFeatures =
192  static_cast<VkFormatFeatureFlags>(
193  vk::FormatFeatureFlagBits::eColorAttachment);
194  } else if (format == VK_FORMAT_D32_SFLOAT_S8_UINT) {
195  pFormatProperties->optimalTilingFeatures =
196  static_cast<VkFormatFeatureFlags>(
197  vk::FormatFeatureFlagBits::eDepthStencilAttachment);
198  }
199  // Ignore just the stencil format.
200  })
201  .Build();
202  ASSERT_NE(context, nullptr);
203  const CapabilitiesVK* capabilites_vk =
204  reinterpret_cast<const CapabilitiesVK*>(context->GetCapabilities().get());
205  ASSERT_EQ(capabilites_vk->GetDefaultDepthStencilFormat(),
207  ASSERT_EQ(capabilites_vk->GetDefaultStencilFormat(),
209 }
210 
211 // Impeller's 2D renderer relies on hardware support for a combined
212 // depth-stencil format (widely supported). So fail initialization if a suitable
213 // one couldn't be found. That way we have an opportunity to fallback to
214 // OpenGLES.
215 TEST(CapabilitiesVKTest,
216  ContextFailsInitializationForNoCombinedDepthStencilFormat) {
217  ScopedValidationDisable disable_validation;
218  const std::shared_ptr<ContextVK> context =
219  MockVulkanContextBuilder()
220  .SetPhysicalDeviceFormatPropertiesCallback(
221  [](VkPhysicalDevice physicalDevice, VkFormat format,
222  VkFormatProperties* pFormatProperties) {
223  if (format == VK_FORMAT_R8G8B8A8_UNORM) {
224  pFormatProperties->optimalTilingFeatures =
225  static_cast<VkFormatFeatureFlags>(
226  vk::FormatFeatureFlagBits::eColorAttachment);
227  }
228  // Ignore combined depth-stencil formats.
229  })
230  .Build();
231  ASSERT_EQ(context, nullptr);
232 }
233 
234 TEST(ContextVKTest, WarmUpFunctionCreatesRenderPass) {
235  const std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
236 
237  context->SetOffscreenFormat(PixelFormat::kR8G8B8A8UNormInt);
238  context->InitializeCommonlyUsedShadersIfNeeded();
239 
240  auto functions = GetMockVulkanFunctions(context->GetDevice());
241  ASSERT_TRUE(std::find(functions->begin(), functions->end(),
242  "vkCreateRenderPass") != functions->end());
243 }
244 
245 TEST(ContextVKTest, FatalMissingValidations) {
246  EXPECT_DEATH(const std::shared_ptr<ContextVK> context =
247  MockVulkanContextBuilder()
248  .SetSettingsCallback([](ContextVK::Settings& settings) {
249  settings.enable_validation = true;
250  settings.fatal_missing_validations = true;
251  })
252  .Build(),
253  "");
254 }
255 
256 TEST(ContextVKTest, HasDefaultColorFormat) {
257  std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
258 
259  const CapabilitiesVK* capabilites_vk =
260  reinterpret_cast<const CapabilitiesVK*>(context->GetCapabilities().get());
261  ASSERT_NE(capabilites_vk->GetDefaultColorFormat(), PixelFormat::kUnknown);
262 }
263 
264 TEST(ContextVKTest, EmbedderOverridesUsesInstanceExtensions) {
266  auto other_context = MockVulkanContextBuilder().Build();
267 
268  data.instance = other_context->GetInstance();
269  data.device = other_context->GetDevice();
270  data.physical_device = other_context->GetPhysicalDevice();
271  data.queue = VkQueue{};
272  data.queue_family_index = 0;
273  // Missing surface extension.
274  data.instance_extensions = {};
275  data.device_extensions = {"VK_KHR_swapchain"};
276 
278  auto context = MockVulkanContextBuilder().SetEmbedderData(data).Build();
279 
280  EXPECT_EQ(context, nullptr);
281 }
282 
283 TEST(ContextVKTest, EmbedderOverrides) {
285  auto other_context = MockVulkanContextBuilder().Build();
286 
287  data.instance = other_context->GetInstance();
288  data.device = other_context->GetDevice();
289  data.physical_device = other_context->GetPhysicalDevice();
290  data.queue = VkQueue{};
291  data.queue_family_index = 0;
292  data.instance_extensions = {"VK_KHR_surface",
293  "VK_KHR_portability_enumeration"};
294  data.device_extensions = {"VK_KHR_swapchain"};
295 
296  auto context = MockVulkanContextBuilder().SetEmbedderData(data).Build();
297 
298  EXPECT_TRUE(context->IsValid());
299  EXPECT_EQ(context->GetInstance(), other_context->GetInstance());
300  EXPECT_EQ(context->GetDevice(), other_context->GetDevice());
301  EXPECT_EQ(context->GetPhysicalDevice(), other_context->GetPhysicalDevice());
302  EXPECT_EQ(context->GetGraphicsQueue()->GetIndex().index, 0u);
303  EXPECT_EQ(context->GetGraphicsQueue()->GetIndex().family, 0u);
304 }
305 
306 TEST(ContextVKTest, BatchSubmitCommandBuffersOnArm) {
307  std::shared_ptr<ContextVK> context =
308  MockVulkanContextBuilder()
309  .SetPhysicalPropertiesCallback(
310  [](VkPhysicalDevice device, VkPhysicalDeviceProperties* prop) {
311  prop->vendorID = 0x13B5; // ARM
312  prop->deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU;
313  })
314  .Build();
315 
316  EXPECT_TRUE(context->EnqueueCommandBuffer(context->CreateCommandBuffer()));
317  EXPECT_TRUE(context->EnqueueCommandBuffer(context->CreateCommandBuffer()));
318 
319  // If command buffers are batch submitted, we should have created them but not
320  // created the fence to track them after enqueing.
321  auto functions = GetMockVulkanFunctions(context->GetDevice());
322  EXPECT_TRUE(std::find(functions->begin(), functions->end(),
323  "vkAllocateCommandBuffers") != functions->end());
324  EXPECT_TRUE(std::find(functions->begin(), functions->end(),
325  "vkCreateFence") == functions->end());
326 
327  context->FlushCommandBuffers();
328 
329  // After flushing, the fence should be created.
330  functions = GetMockVulkanFunctions(context->GetDevice());
331  EXPECT_TRUE(std::find(functions->begin(), functions->end(),
332  "vkCreateFence") != functions->end());
333 }
334 
335 TEST(ContextVKTest, BatchSubmitCommandBuffersOnNonArm) {
336  std::shared_ptr<ContextVK> context =
337  MockVulkanContextBuilder()
338  .SetPhysicalPropertiesCallback(
339  [](VkPhysicalDevice device, VkPhysicalDeviceProperties* prop) {
340  prop->vendorID = 0x8686; // Made up ID
341  prop->deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU;
342  })
343  .Build();
344 
345  EXPECT_TRUE(context->EnqueueCommandBuffer(context->CreateCommandBuffer()));
346  EXPECT_TRUE(context->EnqueueCommandBuffer(context->CreateCommandBuffer()));
347 
348  // If command buffers are batched and not submitted, we should have created
349  // them and a corresponding fence immediately.
350  auto functions = GetMockVulkanFunctions(context->GetDevice());
351  EXPECT_TRUE(std::find(functions->begin(), functions->end(),
352  "vkAllocateCommandBuffers") != functions->end());
353  EXPECT_FALSE(std::find(functions->begin(), functions->end(),
354  "vkCreateFence") != functions->end());
355 }
356 
357 TEST(ContextVKTest, AHBSwapchainCapabilitiesCanBeMissing) {
358  {
359  std::shared_ptr<ContextVK> context =
360  MockVulkanContextBuilder()
361  .SetSettingsCallback([](ContextVK::Settings& settings) {
362  settings.enable_surface_control = true;
363  })
364  .Build();
365 
366  EXPECT_FALSE(context->GetShouldEnableSurfaceControlSwapchain());
367  }
368 
370  auto other_context = MockVulkanContextBuilder().Build();
371 
372  data.instance = other_context->GetInstance();
373  data.device = other_context->GetDevice();
374  data.physical_device = other_context->GetPhysicalDevice();
375  data.queue = VkQueue{};
376  data.queue_family_index = 0;
377  data.instance_extensions = {"VK_KHR_surface", "VK_KHR_android_surface"};
378  data.device_extensions = {"VK_KHR_swapchain",
379  VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME,
380  VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME,
381  VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
382  VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME};
383 
384  auto context = MockVulkanContextBuilder()
385  .SetSettingsCallback([](ContextVK::Settings& settings) {
386  settings.enable_surface_control = true;
387  })
388  .SetEmbedderData(data)
389  .Build();
390 
391  EXPECT_TRUE(context->GetShouldEnableSurfaceControlSwapchain());
392 
393 } // namespace impeller
394 
395 TEST(ContextVKTest, HashIsUniqueAcrossThreads) {
396  uint64_t hash1, hash2;
397  std::thread thread1([&]() {
398  auto context = MockVulkanContextBuilder().Build();
399  hash1 = context->GetHash();
400  });
401  std::thread thread2([&]() {
402  auto context = MockVulkanContextBuilder().Build();
403  hash2 = context->GetHash();
404  });
405  thread1.join();
406  thread2.join();
407 
408  EXPECT_NE(hash1, hash2);
409 }
410 
411 } // namespace testing
412 } // namespace impeller
The Vulkan layers and extensions wrangler.
bool AreValidationsEnabled() const
PixelFormat GetDefaultStencilFormat() const override
Returns a supported PixelFormat for textures that store stencil information. May include a depth chan...
PixelFormat GetDefaultDepthStencilFormat() const override
Returns a supported PixelFormat for textures that store both a stencil and depth component....
PixelFormat GetDefaultColorFormat() const override
Returns a supported PixelFormat for textures that store 4-channel colors (red/green/blue/alpha).
static size_t ChooseThreadCountForWorkers(size_t hardware_concurrency)
Definition: context_vk.cc:115
PipelineDescriptor & SetVertexDescriptor(std::shared_ptr< VertexDescriptor > vertex_descriptor)
TEST(AllocationSizeTest, CanCreateTypedAllocations)
bool fatal_missing_validations
If validations are requested but cannot be enabled, log a fatal error.
Definition: context_vk.h:87
const std::shared_ptr< Pipeline< T > > Get() const
Definition: pipeline.h:32
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:68