将 `VkImage` 清除为单一颜色的最佳方法是什么?

What is the best way to clear a `VkImage` to a single color?

我正在学习 vulkan,作为一个(非常)简单的项目,我想简单地将单个交换链图像清除为单一颜色(红色)。我的代码有效,但出现两个验证错误。我想知道:

  1. 如何修复代码中的验证错误
  2. 有没有更好的方法来简单地清除交换链图像

关于 (2):我特别不想使用图形管道:将来我想使用计算着色器直接绘制到屏幕上。

我目前的做法

我的项目是用vk-bootstrap设置的,然后我尝试渲染单帧如下:

  1. 从交换链获取图像
  2. 使用以下命令记录命令缓冲区:
    • vkCmdPipelineBarrier
    • vkCmdClearColorImage
    • vkEndCommandBuffer
  3. 将命令缓冲区提交到图形队列
  4. 使用vkQueuePresentKHR
  5. 呈现先前获取的交换链图像

相关代码可以在下面找到,但似乎验证错误来自对vkCmdClearColorImagevkQueuePresentKHR的调用。

错误信息

第一个验证错误来自 vkCmdClearColorImage 调用,似乎是由我选择 layout:

触发的
VkImageLayout layout = VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR;
// (... snip ...)
vkCmdClearColorImage(commandBuffer, swapChainImages[nextImageIndex], layout, &color, 1, &imageSubresourceRange);

错误消息说:

[ERROR: Validation]
Validation Error: [ VUID-vkCmdClearColorImage-imageLayout-00005 ] Object 0: handle = 0xf56c9b0000000004, type = VK_OBJECT_TYPE_IMAGE; | MessageID = 0x9740ed23 | vkCmdClearColorImage(): Layout for cleared image is VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR but can only be TRANSFER_DST_OPTIMAL or GENERAL. The Vulkan spec states: imageLayout must be VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL or VK_IMAGE_LAYOUT_GENERAL (https://www.khronos.org/registry/vulkan/specs/1.2/html/vkspec.html#VUID-vkCmdClearColorImage-imageLayout-00005)

我对此消息感到困惑,因为尽管此错误消息中的 link 确实表示

imageLayout must be VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL or VK_IMAGE_LAYOUT_GENERAL

我还在规范中发现 this page

imageLayout specifies the current layout of the image subresource ranges to be cleared, and must be VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL

第二个错误是由调用 vkQueuePresentKHR 触发的,特别奇怪...

[ERROR: Validation]
Validation Error: [ VUID-VkPresentInfoKHR-pImageIndices-01296 ] Object 0: handle = 0x55a3c6b9e408, type = VK_OBJECT_TYPE_QUEUE; | MessageID = 0xc7aabc16 | vkQueuePresentKHR(): pSwapchains[0] images passed to present must be in layout VK_IMAGE_LAYOUT_PRESENT_SRC_KHR or VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR but is in VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR. The Vulkan spec states: Each element of pImageIndices must be the index of a presentable image acquired from the swapchain specified by the corresponding element of the pSwapchains array, and the presented image subresource must be in the VK_IMAGE_LAYOUT_PRESENT_SRC_KHR layout at the time the operation is executed on a VkDevice (https://github.com/KhronosGroup/Vulkan-Docs/search?q=)VUID-VkPresentInfoKHR-pImageIndices-01296)

...因为消息似乎自相矛盾(添加了换行符):

images passed to present must be in layout VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
or VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR but is
in VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR

步骤 (2) 的代码

  VkCommandBufferBeginInfo beginInfo{};
  beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
  beginInfo.flags = 0;
  beginInfo.pInheritanceInfo = nullptr;
  if(vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
    throw std::runtime_error("failed vkBeginCommandBuffer");
  }

  VkImageMemoryBarrier barrier{};
  barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
  barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  barrier.newLayout = VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR;
  barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
  barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
  barrier.image = swapChainImages[nextImageIndex];
  barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  barrier.subresourceRange.baseMipLevel = 0;
  barrier.subresourceRange.levelCount = 1;
  barrier.subresourceRange.baseArrayLayer = 0;
  barrier.subresourceRange.layerCount = 1;

  vkCmdPipelineBarrier(
      commandBuffer,
      VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
      VK_PIPELINE_STAGE_TRANSFER_BIT,
      0,
      0, nullptr,
      0, nullptr,
      1, &barrier);


  VkImageLayout layout = VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR;
  VkClearColorValue color = { .float32 = {1.0, 0.0, 0.0} };
  VkImageSubresourceRange imageSubresourceRange { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
  vkCmdClearColorImage(commandBuffer, swapChainImages[nextImageIndex], layout, &color, 1, &imageSubresourceRange);

  vkEndCommandBuffer(commandBuffer);

问题(再次)

重复一遍:我想知道:

  1. 如何修复代码中的两个验证错误?
  2. 是否有更好的方法来简单地清除交换链图像?

软件版本

vulkaninfo 命令报告 Vulkan Instance Version 1.2.194

我找到了一个有效但看起来有点恶心的解决方案。基本上我将步骤 (2) 修改为以下内容:

  1. 使用以下命令记录一个命令缓冲区:
    • vkCmdPipelineBarrier
    • vkCmdClearColorImage
    • vkCmdPipelineBarrier
    • vkEndCommandBuffer

这个管道的逻辑流程本质上是:

  1. 使用屏障,将图像从 VK_IMAGE_LAYOUT_UNDEFINED 转换为 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
  2. 使用vkCmdClearColorImage
  3. 清除图像
  4. 使用屏障,将图像转换为VK_IMAGE_LAYOUT_PRESENT_SRC_KHR以便呈现

之所以有效,是因为:

  • vkCmdClearColorImage 要求图像具有布局 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,但是
  • vkQueuePresent要求图像有布局VK_IMAGE_LAYOUT_PRESENT_SRC_KHR

因为这看起来有点 hacky/gross,我将暂时搁置这个问题,看看是否有人有更好的解决方案。为了完整起见,这里是步骤 (2)

的新代码

步骤 (2) 的修改代码

  VkCommandBufferBeginInfo beginInfo{};
  beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
  beginInfo.flags = 0;
  beginInfo.pInheritanceInfo = nullptr;
  if(vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
    throw std::runtime_error("failed vkBeginCommandBuffer");
  }

  //VkImageLayout clearLayout = VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR;
  VkImageLayout clearLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;

  VkImageMemoryBarrier barrier{};
  barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
  barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  barrier.newLayout = clearLayout;
  barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
  barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
  barrier.image = swapChainImages[nextImageIndex];
  barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  barrier.subresourceRange.baseMipLevel = 0;
  barrier.subresourceRange.levelCount = 1;
  barrier.subresourceRange.baseArrayLayer = 0;
  barrier.subresourceRange.layerCount = 1;

  vkCmdPipelineBarrier(
      commandBuffer,
      VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
      VK_PIPELINE_STAGE_TRANSFER_BIT,
      0,
      0, nullptr,
      0, nullptr,
      1, &barrier);


  VkImageLayout layout = clearLayout;
  VkClearColorValue color = { .float32 = {1.0, 0.0, 0.0} };
  VkImageSubresourceRange imageSubresourceRange { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
  vkCmdClearColorImage(commandBuffer, swapChainImages[nextImageIndex], layout, &color, 1, &imageSubresourceRange);

  // Add another barrier and put the image in VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
  VkImageLayout finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
  VkImageMemoryBarrier finalBarrier{};
  finalBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
  finalBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  finalBarrier.newLayout = finalLayout;
  finalBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
  finalBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
  finalBarrier.image = swapChainImages[nextImageIndex];
  finalBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  finalBarrier.subresourceRange.baseMipLevel = 0;
  finalBarrier.subresourceRange.levelCount = 1;
  finalBarrier.subresourceRange.baseArrayLayer = 0;
  finalBarrier.subresourceRange.layerCount = 1;

  vkCmdPipelineBarrier(
      commandBuffer,
      VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
      VK_PIPELINE_STAGE_TRANSFER_BIT,
      0,
      0, nullptr,
      0, nullptr,
      1, &finalBarrier);

  vkEndCommandBuffer(commandBuffer);