Synchronization -- Synchronization Examples

本文提供 VK_KHR_synchronization2API 的示例,原始同步 API 示例可在此处查看:Legacy-synchronization-APIs

Vulkan 同步容易令人困惑,需要大量时间理解,即便理解后也容易在细节上出错。Vulkan 同步最常见的用法可归纳为少数几种场景,本文列出大量示例。

注意:示例通常以管线屏障形式编写,但事件或子 pass 依赖也可类似使用。


计算到计算依赖

第一次调度写入存储缓冲,第二次调度读取该存储缓冲

vkCmdDispatch(...);

VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR };

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

vkCmdDispatch(...);

第一次调度读取存储缓冲,第二次调度写入该存储缓冲

写后读(WAR)冲突之间不需要可用性与可见性操作,执行依赖已足够。不带任何访问标识的管线屏障或事件即为执行依赖。

vkCmdDispatch(...);

VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR };

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

vkCmdDispatch(...);

第一次调度写入存储图像,第二次调度读取该存储图像

vkCmdDispatch(...);

// 存储图像到存储图像的依赖始终使用 GENERAL 布局,无需布局转换
VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

vkCmdDispatch(...);

三次调度:第一次写入存储缓冲,第二次写入同一缓冲不重叠区域,第三次读取两个区域

vkCmdDispatch(...);
vkCmdDispatch(...);

VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR };

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

vkCmdDispatch(...);

三次调度:第一次写入一个存储缓冲,第二次写入另一个存储缓冲,第三次读取两个

与上例相同,全局内存屏障覆盖所有资源。通常全局内存屏障比单资源屏障更高效;单资源屏障通常仅用于队列所有权转移与图像布局转换,其余场景使用全局屏障。

vkCmdDispatch(...);
vkCmdDispatch(...);

VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR };

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

vkCmdDispatch(...);

计算到图形依赖

注意:与图形的交互理想情况下应使用子 pass 依赖(外部或内部)而非管线屏障,但为简洁起见,以下多数示例仍写为管线屏障。

调度写入存储缓冲,绘制将其作为索引缓冲使用

vkCmdDispatch(...);

VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT_KHR };

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

... // 渲染流程设置等

vkCmdDraw(...);

调度写入存储缓冲,绘制作为索引缓冲使用;后续计算着色器作为 uniform 缓冲读取

vkCmdDispatch(...);

// 不影响同步逻辑时尽量批量屏障
VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT_KHR | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_INDEX_READ_BIT_KHR | VK_ACCESS_2_UNIFORM_READ_BIT_KHR};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

... // 渲染流程设置等

vkCmdDraw(...);

... // 渲染流程结束等

vkCmdDispatch(...);

调度写入存储缓冲,绘制作为间接绘制缓冲使用

vkCmdDispatch(...);

VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT_KHR };

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

... // 渲染流程设置等

vkCmdDrawIndirect(...);

调度写入存储图像,绘制在片段着色器中采样该图像

vkCmdDispatch(...);

VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
  .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL
  /* .image 与 .subresourceRange 应标识访问的图像子资源 */};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

... // 渲染流程设置等

vkCmdDraw(...);

调度写入存储纹素缓冲,绘制作为间接绘制缓冲使用,再在片段着色器中作为 uniform 缓冲使用

vkCmdDispatch(...);

VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT_KHR | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT_KHR | VK_ACCESS_2_UNIFORM_READ_BIT_KHR};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

vkCmdDrawIndirect(...);

图形到计算依赖

绘制写入颜色附件,调度采样该图像

注意:颜色附件写入不在片段着色器中,它有专属管线阶段!

vkCmdDraw(...);

... // 渲染流程结束等

VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
  .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL
  /* .image 与 .subresourceRange 应标识访问的图像子资源 */};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

vkCmdDispatch(...);

绘制写入深度附件,调度采样该图像

注意:深度附件写入不在片段着色器中,它有专属管线阶段!

vkCmdDraw(...);

... // 渲染流程结束等

VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
  .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL
  /* .image 与 .subresourceRange 应标识访问的图像子资源 */};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

vkCmdDispatch(...);

图形到图形依赖

多数图形到图形依赖可在渲染流程内表示为子 pass 依赖,通常比管线屏障或事件更高效。以下示例中可行时均采用子 pass 依赖写法。

第一次绘制写入深度附件,第二次绘制在片段着色器中作为输入附件读取

VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMALVK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL 的转换会在渲染流程执行时自动完成。

// 设置为深度图像在 VkRenderPassCreateInfo::pAttachments 中的索引
uint32_t depthAttachmentIndex = ...;

VkSubpassDescription subpasses[2];

VkAttachmentReference depthAttachment = {
    .attachment = depthAttachmentIndex,
    .layout     = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL};

// 包含第一次绘制的子pass
subpasses[0] = {
    ...
    .pDepthStencilAttachment = &depthAttachment,
    ...};

VkAttachmentReference depthAsInputAttachment = {
    .attachment = depthAttachmentIndex,
    .layout     = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL};

// 包含第二次绘制的子pass
subpasses[1] = {
    ...
    .inputAttachmentCount = 1,
    .pInputAttachments = &depthAsInputAttachment,
    ...};

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};

// 若 initialLayout 与第一个子pass中附件引用的布局不匹配,渲染流程开始前会隐式转换
// 若 finalLayout 与最后一个子pass中附件引用的布局不匹配,结束时会隐式转换
VkAttachmentDescription depthFramebufferAttachment = {
    ...
    .initialLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
    .finalLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL};

VkRenderPassCreateInfo renderPassCreateInfo = {
    ...
    .attachmentCount = 1,
    .pAttachments = &depthFramebufferAttachment,
    .subpassCount = 2,
    .pSubpasses = subpasses,
    .dependencyCount = 1,
    .pDependencies = &dependency};

vkCreateRenderPass(...);

...

第一次绘制写入深度附件,第二次绘制在片段着色器中采样该深度图像(如阴影贴图渲染)

vkCmdDraw(...);

... // 第一个渲染流程结束等

VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
  .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL
  /* .image 与 .subresourceRange 应标识访问的图像子资源 */};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

... // 第二个渲染流程设置等

vkCmdDraw(...);

第一次绘制写入颜色附件,第二次绘制在片段着色器中作为输入附件读取

// 设置为颜色图像在 VkRenderPassCreateInfo::pAttachments 中的索引
uint32_t colorAttachmentIndex = ...;

VkSubpassDescription subpasses[2];

VkAttachmentReference colorAttachment = {
    .attachment = colorAttachmentIndex,
    .layout     = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL};

// 包含第一次绘制的子pass
subpasses[0] = {
    ...
    .colorAttachmentCount = 1,
    .pColorAttachments = &colorAttachment,
    ...};

VkAttachmentReference colorAsInputAttachment = {
    .attachment = colorAttachmentIndex,
    .layout     = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL};

// 包含第二次绘制的子pass
subpasses[1] = {
    ...
    .inputAttachmentCount = 1,
    .pInputAttachments = &colorAsInputAttachment,
    ...};

VkSubpassDependency dependency = {
    .srcSubpass = 0,
    .dstSubpass = 1,
    .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
    .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
    .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
    .dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT,
    .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT};

// 若 initialLayout 与第一个子pass中附件引用的布局不匹配,渲染流程开始前会隐式转换
// 若 finalLayout 与最后一个子pass中附件引用的布局不匹配,结束时会隐式转换
VkAttachmentDescription colorFramebufferAttachment = {
    ...
    .initialLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
    .finalLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL};

VkRenderPassCreateInfo renderPassCreateInfo = {
    ...
    .attachmentCount = 1,
    .pAttachments = &colorFramebufferAttachment,
    .subpassCount = 2,
    .pSubpasses = subpasses,
    .dependencyCount = 1,
    .pDependencies = &dependency};

vkCreateRenderPass(...);

...

第一次绘制写入颜色附件,第二次绘制在片段着色器中采样该颜色图像

vkCmdDraw(...);

... // 第一个渲染流程结束等

VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
  .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL
  /* .image 与 .subresourceRange 应标识访问的图像子资源 */};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

... // 第二个渲染流程设置等

vkCmdDraw(...);

第一次绘制写入颜色附件,第二次绘制在顶点着色器中采样该颜色图像

vkCmdDraw(...);

... // 第一个渲染流程结束等

VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
  .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL
  /* .image 与 .subresourceRange 应标识访问的图像子资源 */};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

... // 第二个渲染流程设置等

vkCmdDraw(...);

第一次绘制在片段着色器中采样纹理,第二次绘制将该纹理作为颜色附件写入

这是 WAR 冲突,通常仅需执行依赖,即不需要内存屏障。但本例中仍需内存屏障完成布局转换;源访问掩码无需填写访问类型。布局转换本身视为写操作,因此目标访问掩码必须正确,否则布局转换与颜色附件写入间会出现 WAW 冲突。

vkCmdDraw(...);

... // 第一个渲染流程结束等

VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL,
  .newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL
  /* .image 与 .subresourceRange 应标识访问的图像子资源 */};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

... // 第二个渲染流程设置等

vkCmdDraw(...);

第一个渲染流程写入深度附件,第二个渲染流程复用同一深度附件

这是 WAW(写后写)冲突示例,始终需要内存依赖。即便渲染流程不读取上一流程输出(本例中因从 UNDEFINED 转换,图像内容明确不保留),仍需内存依赖确保图像写入不被重排序。

此外,使用自动布局转换(initialLayoutlayout 不同)时,必须确保转换不会过早发生。通常需要显式指定 VK_SUBPASS_EXTERNAL 子 pass 依赖,默认隐式依赖(srcStageMask = TOP)不足。(另见 “交换链图像获取与呈现”。)

本例使用 VK_SUBPASS_EXTERNAL 子 pass 依赖实现两个目标:解决 WAW 冲突、推迟自动布局转换;当然也可使用管线屏障。

// 将深度缓冲用作深度模板附件
VkAttachmentReference depthAttachment = {
    .attachment = 0,
    .layout     = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL};

VkAttachmentDescription depthFramebufferAttachment = {
    ...
    .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, // 子pass开始时清空缓冲
    .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, // 无需保留之前图像内容
    .finalLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL // 完成后保留子pass所用布局(结束时不转换)
};

// 使用深度缓冲的子pass
VkSubpassDescription subpass = {
    ...
    .pDepthStencilAttachment = &depthAttachment,
    ...};

// 使用入式子pass依赖确保:
// * 深度缓冲上一使用已完成(执行依赖)
// * WAW 冲突已解决(缓存刷新与无效,新旧写入不重排序)
// * 从 UNDEFINED 到 VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL 的转换发生在之前 EARLY/LATE_FRAGMENT_TESTS 使用之后
// * 转换对图像的修改通过正确设置 dstAccessMask 体现
VkSubpassDependency dependency = {
    .srcSubpass = VK_SUBPASS_EXTERNAL,
    .dstSubpass = 0,
    .srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,  // 存储操作始终在片段后测试中执行,晚于子pass访问
    .dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, // 加载操作始终在片段前测试中执行,早于子pass访问
    .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
    .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,
    .dependencyFlags = 0};

VkRenderPassCreateInfo renderPassCreateInfo = {
    ...
    .attachmentCount = 1,
    .pAttachments = &depthFramebufferAttachment,
    .subpassCount = 1,
    .pSubpasses = &subpass
    .dependencyCount = 1,
    .pDependencies = &dependency};

vkCreateRenderPass(...);

...

// 第一个渲染流程
vkCmdBeginRenderPass();
...
vkCmdEndRenderPass();

...

// 第二个渲染流程,可同帧或不同帧
vkCmdBeginRenderPass();
...
vkCmdEndRenderPass();

传输依赖

从 CPU 上传数据到顶点缓冲

独立主机与设备内存

若存在带 HOST_VISIBLE 且无 DEVICE_LOCAL 的内存类型,以及单独带 DEVICE_LOCAL 的类型,使用以下流程。UMA 系统见下一代码块,此代码可运行但会增加内存开销。

设置

// 数据与大小
const uint32_t vertexDataSize = ... ;
const void* pData = ... ;

// 创建上传暂存缓冲
VkBufferCreateInfo stagingCreateInfo = {
    ...
    .size = vertexDataSize,
    .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
    ... };

VkBuffer stagingBuffer;
vkCreateBuffer(device, &stagingCreateInfo, NULL, &stagingBuffer);

// 创建顶点缓冲
VkBufferCreateInfo vertexCreateInfo = {
    ...
    .size = vertexDataSize,
    .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
    ... };

VkBuffer vertexBuffer;
vkCreateBuffer(device, &vertexCreateInfo, NULL, &vertexBuffer);

...

// 为这些缓冲分配并绑定内存
// 确保暂存缓冲使用带
// VK_MEMORY_PROPERTY_HOST_VISIBLE 且不带
// VK_MEMORY_PROPERTY_DEVICE_LOCAL 的内存类型
// 顶点缓冲内存相反——应包含
// VK_MEMORY_PROPERTY_DEVICE_LOCAL 且不带
// VK_MEMORY_PROPERTY_HOST_VISIBLE
// 使用 VkPhysicalDeviceMemoryProperties 描述中的示例代码:
// https://www.khronos.org/registry/vulkan/spec/latest/man/html/VkPhysicalDeviceMemoryProperties.html

...

// 映射暂存缓冲——若计划复用(推荐),保持映射
// 理想情况下一次性映射整个范围

void* stagingData;

vkMapMemory(
    ...
    stagingMemory,
    stagingMemoryOffset,
    vertexDataSize,
    0,
    &stagingData);

// 直接写入映射指针
fread(stagingData, vertexDataSize, 1, vertexFile);

// 刷新内存范围
// 若暂存内存类型包含 VK_MEMORY_PROPERTY_HOST_COHERENT,跳过此步

// 按 VkPhysicalDeviceProperties::nonCoherentAtomSize 对齐
uint32_t alignedSize = (vertexDataSize-1) - ((vertexDataSize-1) % nonCoherentAtomSize) + nonCoherentAtomSize;

// 设置范围
VkMappedMemoryRange stagingRange = {
    ...
    .memory = stagingMemory,
    .offset = stagingMemoryOffset,
    .size   = alignedSize};

// 刷新范围
vkFlushMappedMemoryRanges(device, 1, &stagingRange);

统一传输 / 图形队列的命令缓冲录制与提交

vkBeginCommandBuffer(...);

// 提交保证主机写入完成,见
// https://docs.vulkan.org/spec/latest/chapters/synchronization.html#synchronization-submission-host-writes
// 因此传输前无需屏障

// 将暂存缓冲内容复制到顶点缓冲
VkBufferCopy vertexCopyRegion = {
    .srcOffset = stagingMemoryOffset,
    .dstOffset = vertexMemoryOffset,
    .size      = vertexDataSize};

vkCmdCopyBuffer(
    commandBuffer,
    stagingBuffer,
    vertexBuffer,
    1,
    &vertexCopyRegion);


// 若图形队列与传输队列为同一队列
if (isUnifiedGraphicsAndTransferQueue)
{
    // 若此提交与顶点缓冲使用之间有信号量触发+等待,跳过此管线屏障

    // 使用顶点数据前的管线屏障
    // 可适用于所有以此方式上传的缓冲,理想情况下在之前批量所有复制
    VkMemoryBarrier2KHR memoryBarrier = {
        ...
        .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR,
        .srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT_KHR,
        .dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT_KHR,
        .dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT_KHR};

    VkDependencyInfoKHR dependencyInfo = {
        ...
        1,                 // memoryBarrierCount
        &memoryBarrier,    // pMemoryBarriers
        ...
    }

    vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);


    vkEndCommandBuffer(...);

    vkQueueSubmit2KHR(unifiedQueue, ...);
}
else
{
    // 复制后开始队列所有权转移的管线屏障
    VkBufferMemoryBarrier2KHR bufferMemoryBarrier = {
        ...
        .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR,
        .srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT_KHR,
        .srcQueueFamilyIndex = transferQueueFamilyIndex,
        .dstQueueFamilyIndex = graphicsQueueFamilyIndex,
        .buffer = vertexBuffer,
        ...};

    VkDependencyInfoKHR dependencyInfo = {
        ...
        1,                      // bufferMemoryBarrierCount
        &bufferMemoryBarrier,    // pBufferMemoryBarriers
        ...
    }

    vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

    vkEndCommandBuffer(...);

    // 确保此处触发图形队列等待的信号量
    vkQueueSubmit2KHR(transferQueue, ...);

    // 为图形队列录制命令缓冲
    vkBeginCommandBuffer(...);

    // 完成所有权转移后使用顶点缓冲前的管线屏障
    VkBufferMemoryBarrier2KHR bufferMemoryBarrier = {
        ...
        .dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT_KHR,
        .dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT_KHR,
        .srcQueueFamilyIndex = transferQueueFamilyIndex,
        .dstQueueFamilyIndex = graphicsQueueFamilyIndex,
        .buffer = vertexBuffer,
        ...};

    VkDependencyInfoKHR dependencyInfo = {
        ...
        1,                       // bufferMemoryBarrierCount
        &bufferMemoryBarrier,    // pBufferMemoryBarriers
        ...
    }

    vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

    vkEndCommandBuffer(...);

    vkQueueSubmit2KHR(graphicsQueue, ...);
}
统一内存(UMA)

UMA 系统可使用上述代码,但避免暂存缓冲更省内存,如下设置。假设上传后提交首次使用命令,无需设备端同步(不推荐使用 VkEvents,此处不描述)。

设置

// 数据与大小
const uint32_t vertexDataSize = ... ;
const void* pData = ... ;

// 创建顶点缓冲
VkBufferCreateInfo vertexCreateInfo = {
    ...
    .size = vertexDataSize,
    .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
    ... };

VkBuffer vertexBuffer;
vkCreateBuffer(device, &vertexCreateInfo, NULL, &vertexBuffer);

...

// 为此缓冲分配并绑定内存
// 应使用包含 HOST_VISIBLE 的内存类型,若可用理想也包含 DEVICE_LOCAL
// 使用 VkPhysicalDeviceMemoryProperties 描述中的示例代码:
// https://www.khronos.org/registry/vulkan/spec/latest/man/html/VkPhysicalDeviceMemoryProperties.html

...

// 映射顶点缓冲

void* vertexData;

vkMapMemory(
    ...
    vertexMemory,
    vertexMemoryOffset,
    vertexDataSize,
    0,
    &vertexData);

// 直接写入映射指针
fread(vertexData, vertexDataSize, 1, vertexFile);

// 刷新内存范围
// 若顶点内存类型包含 VK_MEMORY_PROPERTY_HOST_COHERENT,跳过此步

// 按 VkPhysicalDeviceProperties::nonCoherentAtomSize 对齐
uint32_t alignedSize = (vertexDataSize-1) - ((vertexDataSize-1) % nonCoherentAtomSize) + nonCoherentAtomSize;

// 设置范围
VkMappedMemoryRange vertexRange = {
    ...
    .memory = vertexMemory,
    .offset = vertexMemoryOffset,
    .size   = alignedSize};

// 刷新范围
vkFlushMappedMemoryRanges(device, 1, &vertexRange);

// 若计划再次修改数据可跳过
vkUnmapMemory(device, vertexMemory);

从 CPU 上传数据到片段着色器中采样的图像

此流程通用适用于 UMA 与独立显卡系统,图像上传时应转为最优平铺。

设置

// 数据与大小
const uint32_t imageDataSize = ... ;

// 创建上传暂存缓冲
VkBufferCreateInfo stagingCreateInfo = {
    ...
    .size = imageDataSize,
    .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
    ... };

VkBuffer stagingBuffer;
vkCreateBuffer(device, &stagingCreateInfo, NULL, &stagingBuffer);

// 创建采样图像
VkImageCreateInfo imageCreateInfo = {
    ...
    // 适当设置图像尺寸
    .tiling = VK_IMAGE_TILING_OPTIMAL,
    .usage  = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
    ... };

VkImage image;
vkCreateImage(device, &imageCreateInfo, NULL, &image);

...

// 为这些资源分配并绑定内存
// 确保暂存缓冲使用带
// VK_MEMORY_PROPERTY_HOST_VISIBLE 且不带
// VK_MEMORY_PROPERTY_DEVICE_LOCAL 的内存类型
// 图像内存相反——应包含
// VK_MEMORY_PROPERTY_DEVICE_LOCAL 且不带
// VK_MEMORY_PROPERTY_HOST_VISIBLE
// 使用 VkPhysicalDeviceMemoryProperties 描述中的示例代码:
// https://www.khronos.org/registry/vulkan/spec/latest/man/html/VkPhysicalDeviceMemoryProperties.html

...

// 映射暂存缓冲——若计划复用(推荐),保持映射
// 理想情况下一次性映射整个范围

void* stagingData;

vkMapMemory(
    ...
    stagingMemory,
    stagingMemoryOffset,
    imageDataSize,
    0,
    &stagingData);

// 直接写入映射指针
fread(stagingData, imageDataSize, 1, imageFile);

// 刷新内存范围
// 若暂存内存类型包含 VK_MEMORY_PROPERTY_HOST_COHERENT,跳过此步

// 按 VkPhysicalDeviceProperties::nonCoherentAtomSize 对齐
uint32_t alignedSize = (imageDataSize-1) - ((imageDataSize-1) % nonCoherentAtomSize) + nonCoherentAtomSize;

// 设置范围
VkMappedMemoryRange stagingRange = {
    ...
    .memory = stagingMemory,
    .offset = stagingMemoryOffset,
    .size   = alignedSize};

// 刷新范围
vkFlushMappedMemoryRanges(device, 1, &stagingRange);

命令缓冲录制与提交

vkBeginCommandBuffer(...);

// 提交保证主机写入完成,见
// https://docs.vulkan.org/spec/latest/chapters/synchronization.html#synchronization-submission-host-writes
// 因此传输前无需为此目的设置屏障,但图像布局变化需要屏障

// 复制前执行布局转换的管线屏障
VkImageMemoryBarrier2KHR preCopyMemoryBarrier = {
    ...
    .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR,
    .dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT_KHR,
    .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
    .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
    .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
    .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
    .image = image,
    .subresourceRange = ... }; // 一次性转换尽可能多的图像

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &preCopyMemoryBarrier,  // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

// 设置所有需要区域的复制(尽可能批量单次调用)
vkCmdCopyBufferToImage(
    commandBuffer,
    stagingBuffer,
    image,
    ... };

// 若图形队列与传输队列为同一队列
if (isUnifiedGraphicsAndTransferQueue)
{
    // 使用顶点数据前的管线屏障
    VkImageMemoryBarrier2KHR postCopyMemoryBarrier = {
        ...
        .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR,
        .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT_KHR,
        .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR,
        .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR,
        .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL,
        .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .image = image,
        .subresourceRange = ... }; // 一次性转换尽可能多的图像

    VkDependencyInfoKHR dependencyInfo = {
        ...
        1,                      // imageMemoryBarrierCount
        &postCopyMemoryBarrier, // pImageMemoryBarriers
        ...
    }

    vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

    vkEndCommandBuffer(...);

    vkQueueSubmit2KHR(unifiedQueue, ...);
}
else
{
    // 使用顶点数据前的管线屏障
    VkImageMemoryBarrier2KHR postCopyTransferMemoryBarrier = {
        ...
        .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR,
        .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT_KHR,
        .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL,
        .srcQueueFamilyIndex = transferQueueFamilyIndex,
        .dstQueueFamilyIndex = graphicsQueueFamilyIndex,
        .image = image,
        .subresourceRange = ... }; // 一次性转换尽可能多的图像

    VkDependencyInfoKHR dependencyInfo = {
        ...
        1,                              // imageMemoryBarrierCount
        &postCopyTransferMemoryBarrier, // pImageMemoryBarriers
        ...
    }

    vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

    vkEndCommandBuffer(...);

    vkQueueSubmit2KHR(transferQueue, ...);

    vkBeginCommandBuffer(...);

    // 使用顶点数据前的管线屏障
    VkImageMemoryBarrier2KHR postCopyGraphicsMemoryBarrier = {
        ...
        .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR,
        .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR,
        .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL,
        .srcQueueFamilyIndex = transferQueueFamilyIndex,
        .dstQueueFamilyIndex = graphicsQueueFamilyIndex,
        .image = image,
        .subresourceRange = ... }; // 一次性转换尽可能多的图像

    VkDependencyInfoKHR dependencyInfo = {
        ...
        1,                              // imageMemoryBarrierCount
        &postCopyGraphicsMemoryBarrier, // pImageMemoryBarriers
        ...
    }

    vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

    vkEndCommandBuffer(...);

    vkQueueSubmit2KHR(graphicsQueue, ...);
}

CPU 回读计算着色器写入的数据

本例展示将计算着色器写入缓冲的数据取回 CPU 的步骤。

vkCmdDispatch(...);

VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_HOST_READ_BIT_KHR};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

vkEndCommandBuffer(...);

vkQueueSubmit2KHR(..., fence); // 带 fence 提交命令缓冲

GPU 处理需要时间,应与其他资源管理(如交换链图像)流水线化:

vkWaitForFences(fence);

// 若内存为主机一致,跳过此步——否则需要无效化
if (memoryIsNotHostCoherent) {
    VkMappedMemoryRange mappedMemoryRange = {
        ...
        mappedMemory,       // 缓冲对应 VkDeviceMemory 分配的映射指针
        ...
    };

    vkInvalidateMappedMemoryRanges(..., 1, &mappedMemoryRange);
}

// 从映射指针回读数值
value = mappedMemory[...];

与信号量的交互

若被同步的两个命令之间有信号量触发 / 等待,管线屏障 / 事件 / 子 pass 依赖的额外同步可减少或移除。仅列出受信号量依赖影响的参数。

仅涉及缓冲、或图像布局不变的依赖

// 无需额外内容——仅信号量已足够
// 无需额外同步,移除这些屏障

触发信号量会等待所有阶段完成,所有内存访问自动可用。同理,等待信号量会使所有内存访问可用,并阻止后续工作开始直到触发。注意:QueueSubmitVkSubmitInfo::pWaitDstStageMask 显式指定阻止运行的阶段;其他信号量使用会阻止所有工作执行。

需要布局转换的图像依赖,在信号量触发前表达

vkCmdDispatch(...);

VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .dstStageMask = VK_PIPELINE_STAGE_2_NONE_KHR
  .dstAccessMask = VK_ACCESS_2_NONE_KHR};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

... // 信号量触发/等待在此处

vkCmdDispatch(...);

需要布局转换的图像依赖,在信号量触发后表达

vkCmdDispatch(...);

... // 信号量触发/等待在此处

VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_NONE_KHR
  .srcAccessMask = VK_ACCESS_2_NONE_KHR};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

vkCmdDispatch(...);

源访问掩码所用阶段必须等于(或逻辑晚于)相关信号量等待操作的 VkSubmitInfo::pWaitDstStageMask,否则不保证屏障在信号量等待后发生。本例假设相关 pWaitDstStageMask 等于 VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT_KHR


交换链图像获取与呈现

统一图形 / 呈现队列

VkAttachmentReference attachmentReference = {
    .attachment = 0,
    .layout     = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL};

// 包含第一次绘制的子pass
VkSubpassDescription subpass = {
    ...
    .colorAttachmentCount = 1,
    .pColorAttachments = &attachmentReference,
    ...};

/* 添加外部依赖确保布局
   转换在正确时机发生。
   与同步1不同,此处插入2个依赖,
   因为信号量等待与触发操作在
   COLOR_ATTACHMENT_OUTPUT 阶段,将范围缩到最小;
   子pass依赖随之调整匹配 */
VkSubpassDependency dependencies[2] = {
    {
        .srcSubpass = VK_SUBPASS_EXTERNAL,
        .dstSubpass = 0,
        .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
        .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
        .srcAccessMask = VK_ACCESS_NONE_KHR,
        .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
        .dependencyFlags = 0
    },
    /* 若应用以
       VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT 触发信号量,或使用 vkQueueSubmit,
       可省略第二个依赖 */
    {
        .srcSubpass = 0,
        .dstSubpass = VK_SUBPASS_EXTERNAL,
        .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
        .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
        .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
        .dstAccessMask = VK_ACCESS_NONE_KHR,
        .dependencyFlags = 0
    }
};


VkAttachmentDescription attachmentDescription = {
    ...
    .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
    .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
    ...
    // 图像会自动从 UNDEFINED 转为 COLOR_ATTACHMENT_OPTIMAL 用于渲染,结束时转为 PRESENT_SRC_KHR
    .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
    // Vulkan 中呈现图像需要特殊布局
    .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR};

VkRenderPassCreateInfo renderPassCreateInfo = {
    ...
    .attachmentCount = 1,
    .pAttachments    = &attachmentDescription,
    .subpassCount    = 1,
    .pSubpasses      = &subpass,
    .dependencyCount = 2,
    .pDependencies   = dependencies};

vkCreateRenderPass(...);

...

vkAcquireNextImageKHR(
    ...
    acquireCompleteSemaphore,   // 信号量
    ...
    &imageIndex);       // 图像索引

VkSemaphoreSubmitInfoKHR acquireCompleteInfo = {
    ...
    .semaphore = acquireCompleteSemaphore,
    .stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR};

VkSemaphoreSubmitInfoKHR renderingCompleteInfo = {
    ...
    .semaphore = renderingCompleteSemaphore,
    .stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR};

VkSubmitInfo2KHR submitInfo = {
    ...
    .waitSemaphoreInfoCount     = 1,
    .pWaitSemaphoreInfos        = &acquireCompleteInfo,
    ...
    .signalSemaphoreInfoCount   = 1,
    .pSignalSemaphoreInfos      = &renderingCompleteInfo};


vkQueueSubmit2KHR(..., &submitInfo, ...);

VkPresentInfoKHR presentInfo = {
    .waitSemaphoreCount = 1,
    .pWaitSemaphores = &renderingCompleteSemaphore,
    ...};

vkQueuePresentKHR(..., &presentInfo);

多队列

若呈现队列与渲染队列不同,获取与呈现时需在两队列间额外执行队列所有权转移,需要更多同步。

渲染流程设置

VkAttachmentReference attachmentReference = {
    .attachment = 0,
    .layout     = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL};

// 包含第一次绘制的子pass
VkSubpassDescription subpass = {
    ...
    .colorAttachmentCount = 1,
    .pColorAttachments = &attachmentReference,
    ...};

VkAttachmentDescription attachmentDescription = {
    ...
    .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
    .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
    ...
    .initialLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
    .finalLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL};

/* 由于这些必要的额外同步点,
   更适合省略子pass外部依赖(无法表达队列转移),
   并将相关操作与新引入的管线屏障批量 */

VkRenderPassCreateInfo renderPassCreateInfo = {
    ...
    .attachmentCount = 1,
    .pAttachments    = &attachmentDescription,
    .subpassCount    = 1,
    .pSubpasses      = &subpass,
    .dependencyCount = 0,
    .pDependencies   = NULL};

vkCreateRenderPass(...);

渲染命令缓冲 —— 图形队列

/* Queue ownership transfer is only required when we need the content to remain valid across queues.
   Since we are transitioning from UNDEFINED -- and therefore discarding the image contents to begin with --
   we are not required to perform an ownership transfer from the presentation queue to graphics.

   This transition could also be made as an EXTERNAL -> subpass #0 render pass dependency as shown earlier. */

VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
  .newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
  /* .image and .subresourceRange should identify image subresource accessed */};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

... // Render pass submission.

// Queue release operation. dstAccessMask should always be 0.
VkImageMemoryBarrier2KHR imageMemoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR,
  .oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
  .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
  .srcQueueFamilyIndex = graphicsQueueFamilyIndex, // index of the graphics queue family
  .dstQueueFamilyIndex = presentQueueFamilyIndex,  // index of the present queue family
  /* .image and .subresourceRange should identify image subresource accessed */};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,                      // imageMemoryBarrierCount
    &imageMemoryBarrier,    // pImageMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);

完整管线屏障

你应该仅将此用于调试—— 这绝不应出现在正式发布的代码中。它会刷新并无效化所有缓存,并阻塞所有任务,是一个不可随意使用的工具!

话虽如此,如果你认为应用中存在竞态条件(race condition),并且只想将所有操作串行化以便调试,它会非常有用。

注意:这不会处理图像布局(image layouts)。如果你在调试,可以将所有图像的布局设置为 VK_IMAGE_LAYOUT_GENERAL 来规避此问题,但再次强调 —— 不要在发布版本中这样做!

VkMemoryBarrier2KHR memoryBarrier = {
  ...
  .srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR,
  .srcAccessMask = VK_ACCESS_2_MEMORY_READ_BIT_KHR |
                   VK_ACCESS_2_MEMORY_WRITE_BIT_KHR,
  .dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR,
  .dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT_KHR |
                   VK_ACCESS_2_MEMORY_WRITE_BIT_KHR};

VkDependencyInfoKHR dependencyInfo = {
    ...
    1,              // memoryBarrierCount
    &memoryBarrier, // pMemoryBarriers
    ...
}

vkCmdPipelineBarrier2KHR(commandBuffer, &dependencyInfo);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值