VK_KHR_synchronization2
在 Vulkan 1.3 中提升为核心 |
VK_KHR_synchronization2
扩展提供了对管线屏障、事件、图像布局转换和队列提交的改进。本文档展示了原始 Vulkan 同步操作与扩展提供的同步操作之间的区别。还有如何更新应用程序代码以利用扩展的示例。
重新思考管线阶段和访问标志
扩展的一个主要变化是将管线阶段和访问标志现在一起指定在内存屏障结构中。这使得两者之间的联系更加明显。
唯一需要的新结构类型是 VkDependencyInfoKHR
,它将所有屏障包装到单个位置。

重用相同的管线阶段和访问标志名称
由于 VkAccessFlag
的 32 位用完了,因此创建了具有 64 位范围的 VkAccessFlags2KHR
类型。为了防止 VkPipelineStageFlags
出现相同的问题,还创建了具有 64 位范围的 VkPipelineStageFlags2KHR
类型。
并非所有 C/C++ 编译器都提供 64 位枚举类型,因此新字段的代码使用 static const
值而不是枚举。因此,没有与 VkPipelineStageFlagBits
和 VkAccessFlagBits
等效的类型。一些代码(包括 Vulkan 函数,例如 vkCmdWriteTimestamp()
)使用 Bits
类型来指示调用方只能传入单个位值,而不是多个位的掩码。这些调用需要转换为采用 Flags
类型,并通过有效使用或您自己的代码的适当编码约定来强制执行“仅限 1 位”的限制,就像为 vkCmdWriteTimestamp2KHR()
所做的那样。
新的标志包含与原始同步标志相同的位,具有相同的基本名称和相同的值。旧标志可以直接在新 API 中使用,但需要遵守编码环境的任何类型转换约束。以下 2 个示例显示了命名差异
-
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
到VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR
-
VK_ACCESS_SHADER_READ_BIT
到VK_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
};
拆分管线阶段和访问掩码
一些 VkAccessFlags
和 VkPipelineStageFlags
的值对于其在硬件中针对的目标不明确。新的 VkAccessFlags2KHR
和 VkPipelineStageFlags2KHR
在某些情况下将其分解,同时保留旧值以实现可维护性。
拆分 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_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_BIT
和 VK_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_KHR
和 VK_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
通过根据使用的图像格式上下文应用自身。因此,只要在颜色格式上使用 colorImageMemoryBarrier
,VK_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_group
和 VK_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 测试用例。
|