[Impeller] Create a global Vulkan instance in PlaygroundImplVK to prevent SwiftShader from being unloaded after a test completes (#47781)

Libcxx is leaking a thread-local storage key each time SwiftShader is
loaded and unloaded. If a test's Vulkan instance is the only one in the
process, then SwiftShader will be unloaded after the test ends. If many
Vulkan playground tests run in a suite, then eventually the leak will
cause the process to exceed its limit of TLS keys and the suite will
fail.

The process can ensure that SwiftShader remains loaded by holding
another Vulkan instance that persists across all tests in the suite.

Fixes https://github.com/flutter/flutter/issues/138028
diff --git a/impeller/playground/backend/vulkan/playground_impl_vk.cc b/impeller/playground/backend/vulkan/playground_impl_vk.cc
index 82d8d0e..675558f 100644
--- a/impeller/playground/backend/vulkan/playground_impl_vk.cc
+++ b/impeller/playground/backend/vulkan/playground_impl_vk.cc
@@ -45,6 +45,8 @@
   };
 }
 
+vk::UniqueInstance PlaygroundImplVK::global_instance_;
+
 void PlaygroundImplVK::DestroyWindowHandle(WindowHandle handle) {
   if (!handle) {
     return;
@@ -67,6 +69,8 @@
     return;
   }
 
+  InitGlobalVulkanInstance();
+
   ::glfwDefaultWindowHints();
   ::glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
   ::glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
@@ -145,4 +149,33 @@
   return surface_context_vk->AcquireNextSurface();
 }
 
+// Create a global instance of Vulkan in order to prevent unloading of the
+// Vulkan library.
+// A test suite may repeatedly create and destroy PlaygroundImplVK instances,
+// and if the PlaygroundImplVK's Vulkan instance is the only one in the
+// process then the Vulkan library will be unloaded when the instance is
+// destroyed.  Repeated loading and unloading of SwiftShader was leaking
+// resources, so this will work around that leak.
+// (see https://github.com/flutter/flutter/issues/138028)
+void PlaygroundImplVK::InitGlobalVulkanInstance() {
+  if (global_instance_) {
+    return;
+  }
+
+  VULKAN_HPP_DEFAULT_DISPATCHER.init(::glfwGetInstanceProcAddress);
+
+  vk::ApplicationInfo application_info;
+  application_info.setApplicationVersion(VK_API_VERSION_1_0);
+  application_info.setApiVersion(VK_API_VERSION_1_1);
+  application_info.setEngineVersion(VK_API_VERSION_1_0);
+  application_info.setPEngineName("PlaygroundImplVK");
+  application_info.setPApplicationName("PlaygroundImplVK");
+
+  auto instance_result =
+      vk::createInstanceUnique(vk::InstanceCreateInfo({}, &application_info));
+  FML_CHECK(instance_result.result == vk::Result::eSuccess)
+      << "Unable to initialize global Vulkan instance";
+  global_instance_ = std::move(instance_result.value);
+}
+
 }  // namespace impeller
diff --git a/impeller/playground/backend/vulkan/playground_impl_vk.h b/impeller/playground/backend/vulkan/playground_impl_vk.h
index 7c72326..0fa6142 100644
--- a/impeller/playground/backend/vulkan/playground_impl_vk.h
+++ b/impeller/playground/backend/vulkan/playground_impl_vk.h
@@ -24,6 +24,10 @@
   using UniqueHandle = std::unique_ptr<void, decltype(&DestroyWindowHandle)>;
   UniqueHandle handle_;
 
+  // A global Vulkan instance which ensures that the Vulkan library will remain
+  // loaded throughout the lifetime of the process.
+  static vk::UniqueInstance global_instance_;
+
   // |PlaygroundImpl|
   std::shared_ptr<Context> GetContext() const override;
 
@@ -37,6 +41,8 @@
   PlaygroundImplVK(const PlaygroundImplVK&) = delete;
 
   PlaygroundImplVK& operator=(const PlaygroundImplVK&) = delete;
+
+  static void InitGlobalVulkanInstance();
 };
 
 }  // namespace impeller