渲染过程
设置
在完成管线的创建之前,我们需要告诉 Vulkan 将在渲染时使用的帧缓冲附件。我们需要指定将有多少颜色和深度缓冲区,每个缓冲区使用多少个采样,以及它们的内容在整个渲染操作中应该如何处理。所有这些信息都封装在一个渲染过程对象中,我们将为此创建一个新的 createRenderPass
函数。在 createGraphicsPipeline
之前,从 initVulkan
调用此函数。
void initVulkan() {
createInstance();
setupDebugMessenger();
createSurface();
pickPhysicalDevice();
createLogicalDevice();
createSwapChain();
createImageViews();
createRenderPass();
createGraphicsPipeline();
}
...
void createRenderPass() {
}
附件描述
在我们的例子中,我们将只有一个颜色缓冲区附件,由交换链中的一个图像表示。
void createRenderPass() {
VkAttachmentDescription colorAttachment{};
colorAttachment.format = swapChainImageFormat;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
}
颜色附件的 format
应该与交换链图像的格式匹配,而且我们还没有进行多重采样,所以我们将坚持使用 1 个采样。
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
loadOp
和 storeOp
决定在渲染之前和渲染之后如何处理附件中的数据。loadOp
有以下选择
-
VK_ATTACHMENT_LOAD_OP_LOAD
:保留附件的现有内容 -
VK_ATTACHMENT_LOAD_OP_CLEAR
:在开始时将值清除为常量 -
VK_ATTACHMENT_LOAD_OP_DONT_CARE
:现有内容未定义;我们不在乎它们
在我们的例子中,我们将使用清除操作在绘制新帧之前将帧缓冲区清除为黑色。对于 storeOp
只有两种可能性
-
VK_ATTACHMENT_STORE_OP_STORE
:渲染的内容将存储在内存中,并且可以稍后读取 -
VK_ATTACHMENT_STORE_OP_DONT_CARE
:渲染操作后帧缓冲区的内容将未定义
我们有兴趣在屏幕上看到渲染的三角形,所以我们在这里使用存储操作。
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
loadOp
和 storeOp
应用于颜色和深度数据,而 stencilLoadOp
/ stencilStoreOp
应用于模板数据。我们的应用程序不会对模板缓冲区执行任何操作,因此加载和存储的结果无关紧要。
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
Vulkan 中的纹理和帧缓冲区由具有特定像素格式的 VkImage
对象表示,但是内存中像素的布局可能会根据您尝试对图像执行的操作而改变。
一些最常见的布局是
-
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
:用作颜色附件的图像 -
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
:在交换链中呈现的图像 -
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
:用作内存复制操作目标的图像
我们将在纹理章节中更深入地讨论这个主题,但现在需要知道的是,图像需要转换为特定的布局,这些布局适合它们接下来要参与的操作。
initialLayout
指定图像在渲染过程开始之前将具有的布局。finalLayout
指定渲染过程完成时自动转换到的布局。对 initialLayout
使用 VK_IMAGE_LAYOUT_UNDEFINED
意味着我们不在乎图像之前的布局是什么。这个特殊值的警告是不能保证图像的内容会被保留,但这没关系,因为无论如何我们都要清除它。我们希望图像在渲染后可以使用交换链进行呈现,这就是为什么我们使用 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
作为 finalLayout
。
子过程和附件引用
单个渲染过程可以由多个子过程组成。子过程是后续渲染操作,依赖于前一个过程中的帧缓冲区内容,例如依次应用的一系列后处理效果。如果将这些渲染操作分组到一个渲染过程中,那么 Vulkan 就可以重新排序这些操作并节省内存带宽,从而可能获得更好的性能。但是,对于我们的第一个三角形,我们将坚持使用单个子过程。
每个子过程都引用我们在前几节中使用结构描述的一个或多个附件。这些引用本身是 VkAttachmentReference
结构,如下所示
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment
参数通过附件描述数组中的索引指定要引用的附件。我们的数组由单个 VkAttachmentDescription
组成,因此其索引为 0
。layout
指定我们希望附件在使用此引用的子过程期间具有的布局。Vulkan 将在子过程启动时自动将附件转换为此布局。我们打算使用附件作为颜色缓冲区,而 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
布局将为我们提供最佳性能,正如其名称所暗示的那样。
子过程使用 VkSubpassDescription
结构描述
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
Vulkan 未来可能还支持计算子过程,因此我们必须明确说明这是一个图形子过程。接下来,我们指定对颜色附件的引用
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
此数组中附件的索引直接从片段着色器使用 layout(location = 0) out vec4 outColor
指令引用!
以下其他类型的附件可以由子过程引用
-
pInputAttachments
:从着色器读取的附件 -
pResolveAttachments
:用于多重采样颜色附件的附件 -
pDepthStencilAttachment
:用于深度和模板数据的附件 -
pPreserveAttachments
:此子过程未使用但必须保留数据的附件
渲染过程
现在已经描述了附件和引用它的基本子过程,我们可以创建渲染过程本身。在 pipelineLayout
变量的正上方创建一个新的类成员变量来保存 VkRenderPass
对象
VkRenderPass renderPass;
VkPipelineLayout pipelineLayout;
然后可以通过使用附件和子通道数组填充 VkRenderPassCreateInfo
结构体来创建渲染通道对象。 VkAttachmentReference
对象使用此数组的索引来引用附件。
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
throw std::runtime_error("failed to create render pass!");
}
就像管线布局一样,渲染通道将在整个程序中被引用,因此它应该只在最后清理。
void cleanup() {
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyRenderPass(device, renderPass, nullptr);
...
}
这做了很多工作,但是在下一章中,所有这些将会组合在一起,最终创建图形管线对象!