简述
Render passes, 即渲染过程。在完成创建管道之前,我们需要告诉Vulkan渲染时将使用的帧缓冲区附件。
我们需要指定将有多少颜色和深度缓冲区,为每个缓冲区使用多少个样本,以及在整个渲染操作中应如何处理它们的内容。
所有这些信息都包装在一个render pass对象中,为其创建一个新的createRenderPass函数。在createGraphicsPipeline之前从initVulkan调用此函数。
1 | void initVulkan() { |
Vulkan图形管线和计算管线的区别之一是,你使用图形管线来渲染出像素,组成图像以供处理或显示给用户。在复杂的图形应用程序中,图片经过很多遍构建,每一遍都生成场景的一部分,应用全帧效果如后期处理、合成、渲染用户界面元素等等。这样的一遍可以使用Vulkan 中 renderpass 对象表示。
一个单一的的renderpass对象封装了多个pass或者一系列最终图像的几个渲染阶段,renderpass对象包含输出图像所需的信息。
所有的绘制必须被包含在一个renderpass中。甚至,图形管线需要知道他们把渲染结果发往哪儿,因此,有必要在创建图形管线之前创建一个renderpass对象,告知正在生成图像的管线有关图像的信息。
参考资料
一. Attachment description 附件说明
在这里,我们仅有一个颜色缓冲区附件,由交换链中的一个图像表示。
1 | void createRenderPass() { |
颜色附件的格式应该与交换链图像的格式匹配,而且我们还没有对多重采样做任何事情,所以只要使用1个样本。
1 | colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; |
loadOp和storeOp决定在呈现之前和呈现之后如何处理附件中的数据。loadOp和storeOp应用于颜色和深度数据,以及stencilLoadOp/
stencilStoreOp应用于模具数据。这里不会对模板缓冲区执行任何操作,因此加载和存储的结果是无关的。
1.1 loadOp
- VK_ATTACHMENT_LOAD_OP_LOAD: 指定将保留渲染区域中图像的先前内容。对于深度/模具格式的附件,这将使用访问类型VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ位。对于颜色格式的附件,这将使用访问类型VK_ACCESS_COLOR_ATTACHMENT_READ位。
- VK_ATTACHMENT_LOAD_OP_CLEAR: 指定将渲染区域中的内容清除为统一值,该值在渲染过程实例开始时指定。对于深度/模板格式的附件,这将使用访问类型VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE位。对于颜色格式的附件,这将使用访问类型VK_ACCESS_COLOR_ATTACHMENT_WRITE位。
- VK_ATTACHMENT_LOAD_OP_DONT_CARE: 指定不需要保留区域中的前一个内容;附件的内容将在渲染区域内未定义。对于深度/模板格式的附件,这将使用访问类型VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE位。对于颜色格式的附件,这将使用访问类型VK_ACCESS_COLOR_ATTACHMENT_WRITE位。
1.2 storeOp
- VK_ATTACHMENT_STORE_OP_STORE: 指定在渲染过程中和渲染区域内生成的内容写入内存,以后可以读取
- VK_ATTACHMENT_STORE_OP_DONT_CARE: 指定渲染后不需要渲染区域内的内容,这些内容可能会被丢弃;附件的内容将在渲染区域内未定义。
1.3 内存中像素的布局
1 | colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
Vulkan中的纹理和帧缓冲区由具有特定像素格式的VkImage对象表示,但是,内存中像素的布局可能会根据您尝试对图像执行的操作而改变。
initialLayout指定在渲染过程开始之前图像将具有的布局。finalLayout最终布局指定渲染过程完成时自动转换到的布局。
对initialLayout使用VK_IMAGE_LAYOUT_UNDEFINED意味着我们不关心图像在以前的布局中是什么。这个特殊值的警告是图像的内容不能保证被保存,但这并不重要,因为我们无论如何都要清除它。我们希望图像在渲染后可以使用交换链进行显示,这就是为什么我们使用VK_IMAGE_LAYOUT_PRESENT_SRC_KHR作为finalLayout。
一些最常见的布局有:
- VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: 用作颜色附件的图像
- VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: 交换链中要显示的图像
- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: 要用作内存复制操作目标的图像
我们将在“纹理”一章中更深入地讨论这个主题,但现在需要知道的是,图像需要转换到适合其下一步将要涉及的操作的特定布局。
二. Subpasses and attachment references 子过程和附件引用
单个渲染过程可以由多个子过程组成。子过程是依赖于先前过程中帧缓冲区的内容的后续呈现操作。
例如一系列相继应用的后处理效果。如果将这些渲染操作分组到一个渲染过程中,则Vulkan能够对操作重新排序,并节省内存带宽以获得可能更好的性能。
这里对于一个三角形,使用一个子过程。
每个子过程引用一个或多个附件,这些附件是我们使用前面部分中的结构描述的。这些引用本身就是VKatAttchmentReference结构,如下:
1 | VkAttachmentReference colorAttachmentRef = {}; |
- attachment参数通过附件描述数组中的索引指定要引用的附件。我们的数组由一个VkAttachmentDescription组成,所以它的索引是0。
- layout指定在使用此引用的子过程期间希望附件具有的布局。当子进程启动时,Vulkan将自动将附件转换到此布局。使用附件作为一个颜色缓冲区,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL LAYOUT会给我们带来最好的性能,顾名思义。
子过程使用VkSubpassDescription结构进行描述:
1 | VkSubpassDescription subpass = {}; |
Vulkan将来也可能支持计算子过程,所以我们必须明确表示这是一个图形子过程。
此数组中附件的索引直接从具有layout(location=0)out vec4 outColor指令的片段着色器引用!
子过程可以引用以下类型的附件:
- pColorAttachments: 颜色附件
- pInputAttachments: 从着色器读取的附件
- pResolveAttachments: 用于多重采样颜色附件的附件
- pDepthStencilAttachment: 深度和模板数据附件
- pPreserveAttachments: 此子过程未使用但必须保留数据的附件
三. Render pass 渲染过程
上面已经描述了附件和引用它的基本子过程,我们就可以创建渲染过程本身了。
渲染过程表示附件、子过程和子过程之间的依赖关系的集合,并描述如何在子过程中使用附件。
创建一个新的类成员变量,将VkRenderPass对象保持在pipelineLayout变量的正上方:
1 | VkRenderPass renderPass; |
可以通过使用附件和子过程数组填充VkRenderPassCreateInfo结构来创建渲染过程对象。VkAttachmentReference对象使用此数组的索引引用附件。
1 | // 创建渲染过程 |
与管道布局一样,渲染过程将在整个程序中被引用,因此只应在结束时进行清理:
1 | void cleanup() { |
目前我们以及做了很多工作了,下一步就是创建图形管道对象了!
四. 创建 pipeline
快速回顾现在拥有的对象类型:
- shader stages: 定义图形管道可编程阶段功能的着色器模块
- Fixed-function state: 定义管道固定函数阶段的所有结构,如输入程序集、光栅化器、视口和颜色混合
- Pipeline layout: 可在绘制时更新的着色器引用的统一值和推送值
- Render pass: 管道阶段引用的附件及其用法
所有这些组合都充分定义了图形管道的功能,因此现在可以开始在createGraphicsPipeline函数末尾填充VkGraphicsPipelineCreateInfo结构。
但是是在调用vkDestroyShaderModule之前,因为在创建过程中使用仍然需要使用这些着色器!
VkGraphicsPipelineCreateInfo结构包括一个shader-reate-info结构数组,其中包含所有所需的活动着色器阶段、定义所有相关固定函数阶段的创建信息以及管道布局。
1 | VkGraphicsPipelineCreateInfo pipelineInfo = {}; |
最后,我们有了渲染过程的引用以及使用这个图形管道的子过程的索引。也可以将其他渲染过程用于此管道而不是此特定实例,但它们必须与renderPass兼容。下面描述了兼容性的要求,但这里不使用该特性。
实际上还有两个参数:basePipelineHandle和basePipelineIndex。
Vulkan允许通过从现有管道派生来创建新的图形管道。管道衍生品的想法是,当管道具有与现有管道具有许多相同功能时,设置管道成本更低,并且可以更快地在同一父管道之间切换。
可以指定现有管道的句柄(带有basePipelineHandle),也可以引用另一个将由basePipelineIndex的索引创建的管道。
现在只有一个管道,所以只需要指定一个null句柄和一个无效的索引。只有在VkGraphicsPipelineCreateInfo的flags字段中指定VK_PIPELINE_CREATE_UBIT标志时,才使用这些值。
1 | pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional |
通过创建一个类成员来保存VkPipeline对象:
1 | VkPipeline graphicsPipeline; |
vkCreateGraphicsPipelines实际上比Vulkan中常用的对象创建函数有更多的参数。
它的设计是支持在一次调用中获取多个VkGraphicsPipelineCreateInfo对象并创建多个VkPipeline对象。
第二个参数(我们为其传递了VK_NULL_HANDLE参数)引用了一个可选的VkPipelineCache对象。管道缓存可用于在对vkCreateGraphicsPipelines的多个调用中存储和重用与管道创建相关的数据,如果缓存存储到文件中,甚至可以跨程序执行。这使得以后可以大大加快管道的创建速度。我们将在管道缓存中讨论这个问题。
所有常见的绘图操作都需要图形管道,因此也只能在程序结束时销毁:
1 | void cleanup() { |
现在运行程序来确认所有这些艰苦的工作已经成功地创建了管道!我们已经非常接近看到屏幕上弹出一些东西(实际现在还没有看到任何东西,黑乎乎一片)。
接下来将从交换链图像设置实际的帧缓冲区,并准备绘图命令。