Create a unique command pool per render pass (#37965)

This ensures that the command pool is not accessed from multiple threads
at the same time. This fixes the validation errors:

```
Check failed: false. Error[337425955][UNASSIGNED-Threading-MultipleThreads] : Validation Error: [ UNASSIGNED-Threading-MultipleThreads ] Object 0: handle = 0x59ffe0000000003d, type = VK_OBJECT_TYPE_COMMAND_POOL; | MessageID = 0x141cb623 | THREADING ERROR : vkFreeCommandBuffers(): object of type VkCommandPool is simultaneously used in thread 471586061488 and thread 471275924656'
```
diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.cc b/impeller/renderer/backend/vulkan/command_buffer_vk.cc
index 6e6096a..98f1e86 100644
--- a/impeller/renderer/backend/vulkan/command_buffer_vk.cc
+++ b/impeller/renderer/backend/vulkan/command_buffer_vk.cc
@@ -20,15 +20,15 @@
 
 std::shared_ptr<CommandBufferVK> CommandBufferVK::Create(
     const std::weak_ptr<const Context>& context_arg,
-    vk::Device device,
-    vk::CommandPool command_pool) {
+    vk::Device device) {
   if (auto context = context_arg.lock()) {
-    auto queue =
-        reinterpret_cast<const ContextVK*>(context.get())->GetGraphicsQueue();
-    auto fenced_command_buffer =
-        std::make_shared<FencedCommandBufferVK>(device, queue, command_pool);
-    return std::make_shared<CommandBufferVK>(context, device, command_pool,
-                                             fenced_command_buffer);
+    auto context_vk = reinterpret_cast<const ContextVK*>(context.get());
+    auto queue = context_vk->GetGraphicsQueue();
+    auto command_pool = context_vk->CreateGraphicsCommandPool();
+    auto fenced_command_buffer = std::make_shared<FencedCommandBufferVK>(
+        device, queue, command_pool->Get());
+    return std::make_shared<CommandBufferVK>(
+        context, device, std::move(command_pool), fenced_command_buffer);
   } else {
     return nullptr;
   }
@@ -37,11 +37,11 @@
 CommandBufferVK::CommandBufferVK(
     std::weak_ptr<const Context> context,
     vk::Device device,
-    vk::CommandPool command_pool,
+    std::unique_ptr<CommandPoolVK> command_pool,
     std::shared_ptr<FencedCommandBufferVK> command_buffer)
     : CommandBuffer(std::move(context)),
       device_(device),
-      command_pool_(command_pool),
+      command_pool_(std::move(command_pool)),
       fenced_command_buffer_(std::move(command_buffer)) {
   is_valid_ = true;
 }
diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.h b/impeller/renderer/backend/vulkan/command_buffer_vk.h
index 941c110..74be5d9 100644
--- a/impeller/renderer/backend/vulkan/command_buffer_vk.h
+++ b/impeller/renderer/backend/vulkan/command_buffer_vk.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include "flutter/fml/macros.h"
+#include "impeller/renderer/backend/vulkan/command_pool_vk.h"
 #include "impeller/renderer/backend/vulkan/fenced_command_buffer_vk.h"
 #include "impeller/renderer/backend/vulkan/surface_producer_vk.h"
 #include "impeller/renderer/backend/vulkan/vk.h"
@@ -16,12 +17,11 @@
  public:
   static std::shared_ptr<CommandBufferVK> Create(
       const std::weak_ptr<const Context>& context,
-      vk::Device device,
-      vk::CommandPool command_pool);
+      vk::Device device);
 
   CommandBufferVK(std::weak_ptr<const Context> context,
                   vk::Device device,
-                  vk::CommandPool command_pool,
+                  std::unique_ptr<CommandPoolVK> command_pool,
                   std::shared_ptr<FencedCommandBufferVK> command_buffer);
 
   // |CommandBuffer|
@@ -31,7 +31,7 @@
   friend class ContextVK;
 
   vk::Device device_;
-  vk::CommandPool command_pool_;
+  std::unique_ptr<CommandPoolVK> command_pool_;
   vk::UniqueRenderPass render_pass_;
   std::shared_ptr<FencedCommandBufferVK> fenced_command_buffer_;
   bool is_valid_ = false;
diff --git a/impeller/renderer/backend/vulkan/context_vk.cc b/impeller/renderer/backend/vulkan/context_vk.cc
index 6a9ceca..db5574c 100644
--- a/impeller/renderer/backend/vulkan/context_vk.cc
+++ b/impeller/renderer/backend/vulkan/context_vk.cc
@@ -398,6 +398,7 @@
 
   auto graphics_queue =
       PickQueue(physical_device.value(), vk::QueueFlagBits::eGraphics);
+  graphics_queue_idx_ = graphics_queue->index;
   auto transfer_queue =
       PickQueue(physical_device.value(), vk::QueueFlagBits::eTransfer);
   auto compute_queue =
@@ -491,8 +492,6 @@
       device_->getQueue(compute_queue->family, compute_queue->index);
   transfer_queue_ =
       device_->getQueue(transfer_queue->family, transfer_queue->index);
-  graphics_command_pool_ =
-      CommandPoolVK::Create(*device_, graphics_queue->index);
 
   is_valid_ = true;
 }
@@ -525,8 +524,7 @@
 }
 
 std::shared_ptr<CommandBuffer> ContextVK::CreateCommandBuffer() const {
-  return CommandBufferVK::Create(weak_from_this(), *device_,
-                                 graphics_command_pool_->Get());
+  return CommandBufferVK::Create(weak_from_this(), *device_);
 }
 
 vk::Instance ContextVK::GetInstance() const {
@@ -605,4 +603,8 @@
   return graphics_queue_;
 }
 
+std::unique_ptr<CommandPoolVK> ContextVK::CreateGraphicsCommandPool() const {
+  return CommandPoolVK::Create(*device_, graphics_queue_idx_);
+}
+
 }  // namespace impeller
diff --git a/impeller/renderer/backend/vulkan/context_vk.h b/impeller/renderer/backend/vulkan/context_vk.h
index 207d5ee..4ed7ddf 100644
--- a/impeller/renderer/backend/vulkan/context_vk.h
+++ b/impeller/renderer/backend/vulkan/context_vk.h
@@ -94,6 +94,8 @@
 
   vk::Queue GetGraphicsQueue() const;
 
+  std::unique_ptr<CommandPoolVK> CreateGraphicsCommandPool() const;
+
  private:
   std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner_;
   vk::UniqueInstance instance_;
@@ -104,6 +106,7 @@
   std::shared_ptr<ShaderLibraryVK> shader_library_;
   std::shared_ptr<SamplerLibraryVK> sampler_library_;
   std::shared_ptr<PipelineLibraryVK> pipeline_library_;
+  uint32_t graphics_queue_idx_;
   vk::Queue graphics_queue_;
   vk::Queue compute_queue_;
   vk::Queue transfer_queue_;
@@ -111,7 +114,6 @@
   vk::UniqueSurfaceKHR surface_;
   vk::Format surface_format_;
   std::unique_ptr<SwapchainVK> swapchain_;
-  std::unique_ptr<CommandPoolVK> graphics_command_pool_;
   std::unique_ptr<SurfaceProducerVK> surface_producer_;
   std::shared_ptr<WorkQueue> work_queue_;
   bool is_valid_ = false;
diff --git a/impeller/renderer/backend/vulkan/fenced_command_buffer_vk.cc b/impeller/renderer/backend/vulkan/fenced_command_buffer_vk.cc
index 895c0f6..4ebda5e 100644
--- a/impeller/renderer/backend/vulkan/fenced_command_buffer_vk.cc
+++ b/impeller/renderer/backend/vulkan/fenced_command_buffer_vk.cc
@@ -50,8 +50,9 @@
 
 FencedCommandBufferVK::~FencedCommandBufferVK() {
   if (!submitted_) {
-    VALIDATION_LOG
+    FML_LOG(WARNING)
         << "FencedCommandBufferVK is being destroyed without being submitted.";
+    children_.push_back(command_buffer_);
   }
   device_.freeCommandBuffers(command_pool_, children_);
 }
diff --git a/impeller/renderer/backend/vulkan/render_pass_vk.cc b/impeller/renderer/backend/vulkan/render_pass_vk.cc
index 7006dd2..5c3e398 100644
--- a/impeller/renderer/backend/vulkan/render_pass_vk.cc
+++ b/impeller/renderer/backend/vulkan/render_pass_vk.cc
@@ -346,7 +346,9 @@
   }
 
   std::array<vk::CopyDescriptorSet, 0> copies;
-  device_.updateDescriptorSets(writes, copies);
+  if (!writes.empty()) {
+    device_.updateDescriptorSets(writes, copies);
+  }
 
   return true;
 }