Vulkan教程翻译之十一 创建 Render Pass

原文链接:https://vulkan.lunarg.com/doc/sdk/1.2.131.2/windows/tutorial/html/10-init_render_pass.html

创建 Render Pass

这一章节的代码文件是 10-init_render_pass.cpp

render pass 通过指定在渲染操作中使用的 attachment,subpass,dependency 的集合,来描述渲染操作的范围。render pass 至少包含一个 subpass 。这些信息传给驱动,使得驱动在渲染开始时知道期望的是什么,并为渲染操作做出最佳的硬件设置。

你通过调用 vkCreateRenderPass() 开始定义 render pass,然后调用 vkCmdBeginRenderPass() 和 vkCmdEndRenderPass() 插入一个 render pass 实例到 command buffer 中。

在本节中,你将仅仅创建和定义 render pass 而不会用到 command buffer 中去。之后会有。

在本示例的上下文中 render pass 附件的例子包括 color attachment,就是 swapchain 里的 image,和 depth/stencil attachment,就是你在前面示例中分配的 depth buffer。

在 image attachment 被用作在 command buffer 里执行的 render pass 实例中的附件时,它们必须已经准备好被使用了。这些准备涉及把图像 layout 从它们初始的未定义状态过渡到最适合在 render pass 里使用的状态。因为这些 layout 转换相当复杂,在继续创建 render pass 之前,你将在这里学习它们。

Image Layout 转换

对备用内存访问模式的需求

图像的 layout 指的是图像纹理像素如何从网格坐标系的形式映射到图像内存中的偏移量。通常,图像数据以这种方式线性映射,对 2D图像来说,可能意味着一行纹理像素存在连续的内存中,下一行在该行之后连续存储,然后以此类推。

换种方式来说:

offset = rowCoord * pitch + colCoord

其中 pitch 是一行的大小。pitch 通常和图像的宽度是相同的,但是也可能包括一些额外的字节,预留出来为了确保图像每一行都从满足GPU对齐需求的内存地址开始。

线性布局适合沿单行进行连续地像素读写,通过改变 colCoord。但是大多数图像操作涉及跨行访问纹理像素,通过改变 rowCoord。如果图像宽度足够宽,那么访问这些相邻的行会在线性存储地址空间中引入相当大的跳转。这可能造成性能问题,比如由于多级缓存存储系统中的TLB缺失和cache缺失造成的内存地址转换缓慢。

为了解决这些低效问题,许多GPU硬件实现支持 "最佳" 或平铺内存访问模式。在最佳布局中,显示在图像中间的一块矩形的纹理像素存储在内存中,以使所有纹理像素都位于一块连续的内存中。例如,组成一个矩形的纹理像素,左上角在[16,32],右下角在[31,47],可能看到这个 16 x 16 的像素块从一个地址开始连续存储。行之间没有长间隙。

如果GPU想填充这些块,比如用纯色,那就能够以少量的内存系统开销来写入这256个像素块。

这是一个简单的 2 x 2 平铺方案的例子。注意,蓝色的纹理像素在线性方式下会彼此分隔很远,在平铺方式下它们是相邻的。

大多数实现使用更复杂的平铺方案,并且平铺尺寸也比 2 x 2 要大。

Vulkan 对 Layout 的控制

GPU 硬件为了更高效的渲染通常更喜欢用最佳布局,像上面解释的那样。Optimal Layout 通常是"不透明的",这意味着最佳布局格式的细节对需要读写图像数据的其他组件是不公开的或者说不可知的。

例如,你可能想让GPU来渲染一个使用最佳布局的图像。但是你希望把生成的图像拷贝到一个CPU可以读取和理解的缓存区里,在你尝试读取之前得把布局从最佳改成普通的。

从一个布局到另一个的转化在Vulkan里叫做 layout transitions. 你可以控制这些转换,并且可以用这三种方式之一实现:

  1. Memory Barrier Command(通过vkCmdPipelineBarrier)
  2. Render Pass 最终布局规范
  3. Render Pass 子通道布局规范

memory barrier command 是放到命令缓冲区里的显示布局转换命令。比如,你将使用该命令来在更复杂的环境下同步内存访问。因为在这里你将使用其他两种方法来实现布局转换,你在本教程里不会用到 barrier command。

更常见的情况是需要在渲染开始前和渲染完成后对渲染中使用的图像执行布局转换。第一次转换把图像为GPU渲染准备好,后一次把图像为了显示到显示器上准备好。在这些情况下,你指定布局转换作为 render pass 定义的一部分。在本节稍后你将看到如何来做。

layout transition 可能会也可能不会触发一次真正的 GPU 布局转化操作。比如,如果旧的布局是未定义的,新的布局是最佳的,除了可能编程让GPU硬件以最佳模式访问内存以外,GPU就不会做任何工作。这是因为该图像的内容是未定义的,不需要通过转换来保留。另一方面,如果旧的布局是通用的(非最佳),并且表明了图像数据需要被保留,转换到最佳可能涉及 GPU 的一些工作,以移动周围的纹理像素。

即使你知道或者认为一次 layout transition 不会做任何实际的工作,无论如何,最好的做法始终还是要这样做,因为它给了驱动更多信息,并且帮助确保你的应用能在更多设备上工作。

示例中的 Image Layout Transitions

示例代码中使用 subpass 定义和 render pass 定义来指定需要的 image layout transitions,而不是使用 memory barrier commands。

初始的 render pass layout 是未定义的,意味着不关心,因为当 render pass 开始时,你不关心图像里已经有的是什么,因为你将会绘制覆盖它。在这里,你仅告诉驱动在 render pass 开始时什么布局是对图像有效的。驱动不会做任何转换,直到 subpass 或 render pass 结束。

subpass layout 给 color buffer 设置成最佳,这表明驱动应该把发生在 subpass 阶段的渲染操作期间的布局转换为最佳。示例代码对 depth buffer 也设定了类似的设置。

最终要显示的 render pass layout 告诉驱动把布局转换成呈现到显示器上的最好的布局。

创建 Render Pass

现在你知道了如何把 image layout 置成正确的状态,你可以继续定义 render pass 其余的部分。

Attachments

有两个attachment,一个是 color,一个是 depth:

VkAttachmentDescription attachments[2];
attachments[0].format = info.format;
attachments[0].samples = NUM_SAMPLES;
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
attachments[0].flags = 0;

attachments[1].format = info.depth.format;
attachments[1].samples = NUM_SAMPLES;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachments[1].flags = 0;

把两个attachment里的 loadOp 成员都设置成 CLEAR,表明了你想要缓冲区在 render pass 实例最开始是被清空的。

把 color attachment 的 storeOp 成员设置成 STORE,意味着你想把渲染结果留在这个缓冲区里,好让它可以被呈现到显示器上。

把 depth attachment 的 storeOp 成员设置成 DONT_CARE,意味着当 render pass 实例结束的时候,你不需要缓冲区的内容。告诉驱动你在用完缓冲区后不关心它的内容,这是很有用的,因为这允许驱动在不需要保存内容时丢弃或者翻页这部分内存。

如上所述,对 image layout 来说,你可以同时指定 color 和 depth buffer 以未定义的布局开始。

处在 initialLayout 和 finalLayout 之间的 subpass,把 color attachment 置为 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ,把 depth attachment 置为 VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL 。

对 color attachment 来说,你指定最终布局为 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 布局,该布局适合发生在 render pass 完成后的显示操作。你可以把 depth 布局置成和 subpass 设置的布局一样,因为 depth buffer 不会用来作为显示操作的一部分。

Subpass

subpass 定义很简单,如果你用多子通道,将会更有趣。也许因为环境遮挡或某些其他效果,可能你会对图形数据做一些预处理或者后处理,那么你可能会对多子通道感兴趣。但是在这里,subpass 定义可以帮助表明在 subpass 里哪一个 attachment 是激活的,并且可以帮助指定在 subpass 里渲染时使用的布局。

VkAttachmentReference color_reference = {};
color_reference.attachment = 0;
color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

VkAttachmentReference depth_reference = {};
depth_reference.attachment = 1;
depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

attachment 成员是在 你刚刚在上面为 render pass 定义的 attachment 数组里的索引号。

VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.flags = 0;
subpass.inputAttachmentCount = 0;
subpass.pInputAttachments = NULL;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_reference;
subpass.pResolveAttachments = NULL;
subpass.pDepthStencilAttachment = &depth_reference;
subpass.preserveAttachmentCount = 0;
subpass.pPreserveAttachments = NULL;

pipelineBindPoint 成员意在表明这是一个图形或计算 subpass。目前,只有图形 subpass 是有效的。

Dependency

我们要求 renderpass 把我们的图像布局从 UNDEFINED 转换为 COLORATTACHMENTOPTIMAL。我们将会使用在COLORATTACHMENTOUTPUT 阶段标记的信号量来表明当前引擎已经使 swapchain image 可用,所以我们想在开始我们的 subpass 之前的阶段创建一个 dependency。

VkSubpassDependency subpass_dependency = {};
subpass_dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
subpass_dependency.dstSubpass = 0;
subpass_dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
subpass_dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
subpass_dependency.srcAccessMask = 0;
subpass_dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
subpass_dependency.dependencyFlags = 0;

Render Pass

现在你有了定义 render pass 所需要的所有东西:

VkRenderPassCreateInfo rp_info = {};
rp_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
rp_info.pNext = NULL;
rp_info.attachmentCount = 2;
rp_info.pAttachments = attachments;
rp_info.subpassCount = 1;
rp_info.pSubpasses = &subpass;
rp_info.dependencyCount = 1;
rp_info.pDependencies = &subpass_dependency;
res = vkCreateRenderPass(info.device, &rp_info, NULL, &info.render_pass);

你将在接下来的几个示例中使用该 render pass。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值