Vulkan【11】渲染过程

创建一个渲染过程

本节的代码是 10-init_render_pass.cpp

渲染过程通过指定在渲染操作期间使用的附件、子过程和依赖项的集合来描述渲染操作的范围。一个渲染过程由至少一个子过程组成。将这些信息与驱动程序通信,使驱动程序能够知道在呈现开始时将会发生什么,并为呈现操作设置最优的硬件。

首先使用vkCreateRenderPass()来定义渲染过程,然后使用vkCmdBeginRenderPass()vkCmdEndRenderPass()来将呈现过程实例插入到命令缓冲区中。

在本节中,您将只创建并定义渲染过程,而不是在命令缓冲区中使用它。

在本例的上下文中,渲染过程附件包括颜色附件,颜色附件是交换链里的图像,以及深度/模板附件,它是在之前的样例中分配的深度缓冲。

图像附件必须准备好,当它们在一个命令缓冲区中执行的渲染过程实例中用作附件时使用。这个准备过程包括将图像布局从最初的未定义状态转换为在渲染过程中使用的最优状态。由于这些布局转换是相当复杂的,您将在这里了解它们,然后继续创建渲染传递。

图像布局转换

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

图像的布局是指图像纹理如何从网格坐标表示映射到图像内存中的偏移量。通常情况下,图像数据是以这种方式线性映射的,对于2D图像来说,这可能意味着一排texel存储在相邻的内存中,下一行连续存储在这一行之后,以此类推。

换句话说:

offset = rowCoord * pitch + colCoord

pitch是指行的大小。 pitch通常与图像的宽度相同,但是可能包含一些额外的填充字节,以确保图像的每一行从满足GPU的对齐需求的内存地址开始。

通过改变colCoord,线性布局对于连续的texel读或写在一行中是可以的。但是大多数图形操作都涉及到通过改变rowCoord来访问多个相邻行的texel。如果图像宽度相当宽,那么这些相邻的行就会在线性内存地址空间中引入相当大的跳跃。这可能会导致性能问题,比如由于TLB遗漏和多层缓存内存系统中的缓存遗漏而导致的内存地址转换较慢。

为了解决这些低效率问题,许多GPU硬件实现支持“最佳”或平铺内存访问方案。在一个最优的布局中,出现在图像中间的一个矩形的纹理被存储在内存中,这样所有的texel都处于一个连续的内存段中。例如,构成一个矩形的texel,在16,32和右下角的31,47,可能会看到这个16 x 16块的texel,连续地从一个地址开始存储。行之间没有长间隙。

如果GPU想要填充这个块,例如,用一个纯色的颜色,它就可以用少量的内存系统开销来写这个256个texel block。

这里有一个简单的2x2网格方案的例子。注意,在线性方案中,蓝色的texel可以彼此相隔很远,而它们在平铺的模式中是相邻的。

Vulkan【11】渲染过程_第1张图片

大多数实现使用更复杂的平铺模式和大于2x2的网格大小。

Vulkan对布局的控制

正如上面所解释的那样,GPU硬件通常倾向于优化布局,以实现更高效的渲染。最优布局通常是“不透明的”,这意味着优化布局格式的细节不会被发布,也不会让其他需要读取或写入图像数据的组件知道。

例如,您可能希望GPU使用最优布局呈现给图像。但是,如果您希望将生成的图像复制到一个您可以使用CPU读取和理解的缓冲区中,那么在尝试读取之前,您可以将布局从最优变为一般。

从一个布局到另一个布局的转换称为Vulkan中的布局转换。您可以控制这些转换,并且可以通过以下三种方式调用它们:

  1. 内存屏障指令 (通过vkCmdPipelineBarrier)
  2. 渲染最终布局规范
  3. 渲染过程子过程规范

内存屏障命令是一个显式的布局转换命令,您可以在命令缓冲区中设置。例如,您将使用这个命令在更复杂的情况下同步内存访问。由于您将使用另外两种方法来执行布局转换,所以您不会在本教程中使用这个barrier命令。

更常见的情况是,在渲染开始之前和渲染完成后,需要对渲染的图像进行布局转换。第一个转换为GPU的渲染准备了图像,最后一个转换为显示图像到显示器做准备。在这些情况下,您指定布局转换作为渲染过程定义的一部分。稍后您将在本节中看到如何做到这一点。

布局转换可能会,也可能不会触发实际的GPU布局转换操作。例如,如果旧的布局没有定义,而新的布局是最优的,那么GPU将不得不做其他的工作,而不是通过编程GPU硬件来访问最优模式的内存。这是因为图像的内容没有定义,不需要通过转换来保存。另一方面,如果旧的布局是通用的(非最优的)并且有迹象表明图像中的数据需要被保留,那么向优化布局的转换可能涉及到GPU对图像的一些工作。

即使您知道或认为布局转换实际上不会做任何工作,但最好还是去做一下,因为它给了驱动程序更多的信息,并帮助确保您的应用程序在更多的设备上运行。

例子中的图像布局转换

样例代码使用子过程定义和渲染过程定义来指定所需的图像布局转换,而不是使用内存屏障命令。

Vulkan【11】渲染过程_第2张图片

初始的渲染过程的布局是没有定义的,意思是“不关心”,因为当渲染过程开始时,你不会关心图像中已经存在的内容,因为无论如何你都要把它画出来。在这里,您只是告诉驱动程序在渲染过程开始时对图像的布局是什么。驱动程序在子过程或直到渲染结束时才会进行转换。

子过程布局被设置为颜色缓冲区的最优选择,这表明在子过程的渲染操作期间驱动程序应该将布局转换为最优。样例代码为深度缓冲设置了类似的设置。

最后的渲染过程布局告诉驱动将布局转换为适合显示器显示的布局。

创建渲染过程

现在您已经知道了如何将图像布局转换为正确的状态,您可以继续定义剩余的渲染过程。

附件(Attachments)

有两个附件,一个用于颜色,一个用于深度:

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;

在两个附件中设置loadOp成员为CLEAR,表明您希望缓冲区在渲染过程实例的开始处被清空。

为颜色附件设置storeOp成员为STORE意味着您想要将渲染结果留在这个缓冲区中,这样它就可以显示给显示器了。

storeOp成员设置为 DONT_CARE的深度附件意味着当渲染过程实例完成时,您不需要缓冲区的内容。告诉驱动程序,在使用缓冲区后,您不关心缓冲区的内容,这是很有用的,因为它允许驱动程序在不保存内容的情况下丢弃或删除该内存。

对于图像布局,您可以指定颜色和深度缓冲,以开始未定义的布局,如前所述。

子过程发生在初始布局和最终布局之间,将颜色附件设置为VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,深度附件设置为VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL

对于颜色附件,您可以指定最终的布局为VK_IMAGE_LAYOUT_PRESENT_SRC_KHR布局,这是最适合显示操作的,该操作是在渲染过程完成之后发生的。您可以将深度布局与子过程设置的布局相同,因为深度缓冲不作为显示操作的一部分使用。

子过程

子过程的定义很简单,如果你在做多个子过程,会更有趣。如果你正在对你的图形数据进行预处理或后期处理,可能会对环境光遮蔽或其他一些效果进行处理,你可能会对多次子过程感兴趣。但是在这里,子过程定义对于在子过程表示哪些附件是活动的,以及在子过程中渲染时使用的布局是很有用的。

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成员是您刚才为渲染过程所定义的附件数组中的附件的索引。

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成员的意思是表示这是一个图形还是一个计算子过程。目前,只有图形子过程是有效的。

渲染过程

现在你已经拥有所有用来定义渲染过程的东西了:

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 = 0;
rp_info.pDependencies = NULL;
res = vkCreateRenderPass(info.device, &rp_info, NULL, &info.render_pass);

您将在几个即将到来的示例中使用渲染过程。

© Copyright 2016 LunarG, Inc

你可能感兴趣的:(Vulkan)