本文提供 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_OPTIMAL 到 VK_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 转换,图像内容明确不保留),仍需内存依赖确保图像写入不被重排序。
此外,使用自动布局转换(initialLayout 与 layout 不同)时,必须确保转换不会过早发生。通常需要显式指定 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 依赖的额外同步可减少或移除。仅列出受信号量依赖影响的参数。
仅涉及缓冲、或图像布局不变的依赖
// 无需额外内容——仅信号量已足够
// 无需额外同步,移除这些屏障
触发信号量会等待所有阶段完成,所有内存访问自动可用。同理,等待信号量会使所有内存访问可用,并阻止后续工作开始直到触发。注意:QueueSubmit 中 VkSubmitInfo::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);

1065

被折叠的 条评论
为什么被折叠?



