VK_KHR_synchronization2

在 Vulkan 1.3 中提升为核心

VK_KHR_synchronization2 扩展提供了对管线屏障、事件、图像布局转换和队列提交的改进。本文档展示了原始 Vulkan 同步操作与扩展提供的同步操作之间的区别。还有如何更新应用程序代码以利用扩展的示例。

重新思考管线阶段和访问标志

扩展的一个主要变化是将管线阶段和访问标志现在一起指定在内存屏障结构中。这使得两者之间的联系更加明显。

唯一需要的新结构类型是 VkDependencyInfoKHR,它将所有屏障包装到单个位置。

VK_KHR_synchronization2_stage_access

为设置事件添加屏障

请注意,随着 VkDependencyInfoKHR 的引入,vkCmdSetEvent2KHRvkCmdSetEvent 不同,它具有添加屏障的能力。添加此功能是为了使 VkEvent 更有用。由于 synchronization2 VkEvent 的实现可能与 Vulkan 1.2 VkEvent 大不相同,因此您不得为单个 VkEvent 混合使用扩展和核心 API 调用。例如,您不得调用 vkCmdSetEvent2KHR(),然后调用 vkCmdWaitEvents()

重用相同的管线阶段和访问标志名称

由于 VkAccessFlag 的 32 位用完了,因此创建了具有 64 位范围的 VkAccessFlags2KHR 类型。为了防止 VkPipelineStageFlags 出现相同的问题,还创建了具有 64 位范围的 VkPipelineStageFlags2KHR 类型。

并非所有 C/C++ 编译器都提供 64 位枚举类型,因此新字段的代码使用 static const 值而不是枚举。因此,没有与 VkPipelineStageFlagBitsVkAccessFlagBits 等效的类型。一些代码(包括 Vulkan 函数,例如 vkCmdWriteTimestamp())使用 Bits 类型来指示调用方只能传入单个位值,而不是多个位的掩码。这些调用需要转换为采用 Flags 类型,并通过有效使用或您自己的代码的适当编码约定来强制执行“仅限 1 位”的限制,就像为 vkCmdWriteTimestamp2KHR() 所做的那样。

新的标志包含与原始同步标志相同的位,具有相同的基本名称和相同的值。旧标志可以直接在新 API 中使用,但需要遵守编码环境的任何类型转换约束。以下 2 个示例显示了命名差异

  • VK_PIPELINE_STAGE_COMPUTE_SHADER_BITVK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR

  • VK_ACCESS_SHADER_READ_BITVK_ACCESS_2_SHADER_READ_BIT_KHR

VkSubpassDependency

更新 VkSubpassDependency 中管线阶段和访问标志的使用只需要使用 VkSubpassDependency2,该结构可以将 VkMemoryBarrier2KHR 传递到 pNext

示例为

// Without VK_KHR_synchronization2
VkSubpassDependency dependency = {
    .srcSubpass = 0,
    .dstSubpass = 1,
    .srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
                    VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
    .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
    .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
    .dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT,
    .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT
};

并将其转换为

// With VK_KHR_synchronization2
VkMemoryBarrier2KHR memoryBarrier = {
    .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR,
    .pNext = nullptr,
    .srcStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR |
                    VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR,
    .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR,
    .srcAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR,
    .dstAccessMask = VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT_KHR
};

// The 4 fields unset are ignored according to the spec
// When VkMemoryBarrier2KHR is passed into pNext
VkSubpassDependency2 dependency = {
    .sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2,
    .pNext = &memoryBarrier,
    .srcSubpass = 0,
    .dstSubpass = 1,
    .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT
};

拆分管线阶段和访问掩码

一些 VkAccessFlagsVkPipelineStageFlags 的值对于其在硬件中针对的目标不明确。新的 VkAccessFlags2KHRVkPipelineStageFlags2KHR 在某些情况下将其分解,同时保留旧值以实现可维护性。

拆分 VK_PIPELINE_STAGE_VERTEX_INPUT_BIT

VK_PIPELINE_STAGE_VERTEX_INPUT_BIT(现在是 VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT_KHR)被拆分为 2 个新的阶段标志,它们为索引输入和顶点输入指定了专用阶段,而不是将它们组合到单个管线阶段标志中。

  • VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT_KHR

  • VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT_KHR

拆分 VK_PIPELINE_STAGE_ALL_TRANSFER_BIT

VK_PIPELINE_STAGE_ALL_TRANSFER_BIT(现在是 VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT_KHR)被拆分为 4 个新的阶段标志,它们为各种暂存命令指定了专用阶段,而不是将它们组合到单个管线阶段标志中。

  • VK_PIPELINE_STAGE_2_COPY_BIT_KHR

  • VK_PIPELINE_STAGE_2_RESOLVE_BIT_KHR

  • VK_PIPELINE_STAGE_2_BLIT_BIT_KHR

  • VK_PIPELINE_STAGE_2_CLEAR_BIT_KHR

拆分 VK_ACCESS_SHADER_READ_BIT

VK_ACCESS_SHADER_READ_BIT(现在是 VK_ACCESS_2_SHADER_READ_BIT_KHR)被拆分为 3 个新的访问标志,它们为各种情况指定了专用访问,而不是将它们组合到单个访问标志中。

  • VK_ACCESS_2_UNIFORM_READ_BIT_KHR

  • VK_ACCESS_2_SHADER_SAMPLED_READ_BIT_KHR

  • VK_ACCESS_2_SHADER_STORAGE_READ_BIT_KHR

合并预光栅化的着色器阶段

除了拆分标志外,还添加了 VK_PIPELINE_STAGE_2_PRE_RASTERIZATION_SHADERS_BIT_KHR,以将光栅化之前发生的着色器阶段合并为一个方便的标志。

VK_ACCESS_SHADER_WRITE_BIT 别名

VK_ACCESS_SHADER_WRITE_BIT(现在是 VK_ACCESS_2_SHADER_WRITE_BIT_KHR)提供了 VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT_KHR 的别名,以更好地描述着色器中哪些资源由访问标志描述的范围。

TOP_OF_PIPE 和 BOTTOM_OF_PIPE 弃用

现在不建议使用 VK_PIPELINE_STAGE_TOP_OF_PIPE_BITVK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,并且更新很简单,只需按照以下 4 种情况及其新的等效项即可。

  • VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT 在第一个同步范围中

    // From
      .srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    
    // To
      .srcStageMask = VK_PIPELINE_STAGE_2_NONE_KHR;
      .srcAccessMask = VK_ACCESS_2_NONE_KHR;
  • 在第二个同步作用域中使用 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT

    // From
      .dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    
    // To
      .dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR;
      .dstAccessMask = VK_ACCESS_2_NONE_KHR;
  • 在第一个同步作用域中使用 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT

    // From
      .srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    
    // To
      .srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR;
      .srcAccessMask = VK_ACCESS_2_NONE_KHR;
  • 在第二个同步作用域中使用 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT

    // From
      .dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    
    // To
      .dstStageMask = VK_PIPELINE_STAGE_2_NONE_KHR;
      .dstAccessMask = VK_ACCESS_2_NONE_KHR;

使用新的图像布局

VK_KHR_synchronization2 添加了 2 个新的图像布局 VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHRVK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL_KHR,以帮助简化布局转换。

以下示例演示了执行一次绘制,该绘制会写入颜色附件和深度/模板附件,然后在下一次绘制中对它们进行采样。以前,开发人员需要确保它们正确匹配布局和访问掩码,如下所示:

VkImageMemoryBarrier colorImageMemoryBarrier = {
  .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
  .dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
  .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
  .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
};

VkImageMemoryBarrier depthStencilImageMemoryBarrier = {
  .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,,
  .dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
  .oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
  .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
};

但是使用 VK_KHR_synchronization2,这一切都变得简单了

VkImageMemoryBarrier colorImageMemoryBarrier = {
  .srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHR, // new layout from VK_KHR_synchronization2
  .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL_KHR   // new layout from VK_KHR_synchronization2
};

VkImageMemoryBarrier depthStencilImageMemoryBarrier = {
  .srcAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHR, // new layout from VK_KHR_synchronization2
  .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL_KHR   // new layout from VK_KHR_synchronization2
};

在新方案中,VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHR 通过根据使用的图像格式上下文应用自身。因此,只要在颜色格式上使用 colorImageMemoryBarrierVK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHR 就会映射到 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL

此外,使用 VK_KHR_synchronization2,如果 oldLayout 等于 newLayout,则不会执行布局转换,并且会保留图像内容。使用的布局甚至不需要与图像的布局匹配,因此以下屏障是有效的:

VkImageMemoryBarrier depthStencilImageMemoryBarrier = {
  // other fields omitted
  .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
  .newLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};

新的提交流程

VK_KHR_synchronization2 添加了 vkQueueSubmit2KHR 命令,其主要目标是清理函数的语法,以便将命令缓冲区和信号量封装在可扩展的结构中,这些结构结合了 Vulkan 1.1、VK_KHR_device_groupVK_KHR_timeline_semaphore 的更改。

以下面一个普通的队列提交调用为例:

VkSemaphore waitSemaphore;
VkSemaphore signalSemaphore;
VkCommandBuffer commandBuffers[8];

// Possible pNext from VK_KHR_timeline_semaphore
VkTimelineSemaphoreSubmitInfo timelineSemaphoreSubmitInfo = {
    // ...
    .pNext = nullptr
};

// Possible pNext from VK_KHR_device_group
VkDeviceGroupSubmitInfo deviceGroupSubmitInfo = {
    // ...
    .pNext = &timelineSemaphoreSubmitInfo
};

// Possible pNext from Vulkan 1.1
VkProtectedSubmitInfo = protectedSubmitInfo {
    // ...
    .pNext = &deviceGroupSubmitInfo
};

VkSubmitInfo submitInfo = {
    .pNext = &protectedSubmitInfo, // Chains all 3 extensible structures
    .waitSemaphoreCount = 1,
    .pWaitSemaphores = &waitSemaphore,
    .pWaitDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
    .commandBufferCount = 8,
    .pCommandBuffers = commandBuffers,
    .signalSemaphoreCount = 1,
    .pSignalSemaphores = signalSemaphore
};

vkQueueSubmit(queue, 1, submitInfo, fence);

现在可以将其转换为 vkQueueSubmit2KHR,如下所示:

// Uses same semaphore and command buffer handles
VkSemaphore waitSemaphore;
VkSemaphore signalSemaphore;
VkCommandBuffer commandBuffers[8];

VkSemaphoreSubmitInfoKHR waitSemaphoreSubmitInfo = {
    .semaphore = waitSemaphore,
    .value = 1, // replaces VkTimelineSemaphoreSubmitInfo
    .stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
    .deviceIndex = 0, // replaces VkDeviceGroupSubmitInfo
};

// Note this is allowing a stage to set the signal operation
VkSemaphoreSubmitInfoKHR signalSemaphoreSubmitInfo = {
    .semaphore = signalSemaphore,
    .value = 2, // replaces VkTimelineSemaphoreSubmitInfo
    .stageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR, // when to signal semaphore
    .deviceIndex = 0, // replaces VkDeviceGroupSubmitInfo
};

// Need one for each VkCommandBuffer
VkCommandBufferSubmitInfoKHR = commandBufferSubmitInfos[8] {
    // ...
    {
        .commandBuffer = commandBuffers[i],
        .deviceMask = 0 // replaces VkDeviceGroupSubmitInfo
    },
};

VkSubmitInfo2KHR submitInfo = {
    .pNext = nullptr, // All 3 struct above are built into VkSubmitInfo2KHR
    .flags = VK_SUBMIT_PROTECTED_BIT_KHR, // also can be zero, replaces VkProtectedSubmitInfo
    .waitSemaphoreInfoCount = 1,
    .pWaitSemaphoreInfos = waitSemaphoreSubmitInfo,
    .commandBufferInfoCount = 8,
    .pCommandBufferInfos = commandBufferSubmitInfos,
    .signalSemaphoreInfoCount = 1,
    .pSignalSemaphoreInfos = signalSemaphoreSubmitInfo
}

vkQueueSubmit2KHR(queue, 1, submitInfo, fence);

上面两个示例代码片段之间的区别在于,vkQueueSubmit2KHR 将在顶点着色器阶段完成时发出 VkSemaphore signalSemaphore 信号,而 vkQueueSubmit 调用则会等待到提交结束。

为了在 vkQueueSubmit2KHR 中模拟 vkQueueSubmit 的信号量信号行为,可以将 stageMask 设置为 VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT

// Waits until everything is done
VkSemaphoreSubmitInfoKHR signalSemaphoreSubmitInfo = {
    // ...
    .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
    // ...
};

模拟层

对于本机不支持此扩展的设备,在 Vulkan-Extensionlayer 存储库中有一个可移植的实现。此层应适用于任何 Vulkan 设备。有关更多信息,请参见 图层文档Sync2Compat.Vulkan10 测试用例。

VK_KHR_synchronization2 规范将 VK_KHR_create_renderpass2VK_KHR_get_physical_device_properties2 列为要求。因此,在没有这些扩展的情况下使用 synchronization2 可能会导致验证错误。扩展要求正在重新评估,一旦完成,将调整验证。