我们现在可以结合前面章节中的所有结构和对象来创建图形管道!以下是我们现在拥有的对象类型,作为快速回顾:
- 着色器阶段:定义图形管道可编程阶段功能的着色器模块
- 固定功能状态:定义管道固定功能阶段的所有结构,如输入装配、光栅化器、视口和颜色混合
- 管道布局:着色器引用的可在绘制时更新的统一值和推送值
- 渲染过程:管道阶段引用的附件及其用法
所有这些组合完全定义了图形管道的功能,因此我们现在可以在createGraphicsPipeline函数的末尾填充VkGraphicsPipelineCreateInfo结构。但在调用vkDestroyShaderModule之前,因为在创建过程中仍将使用这些。
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
我们首先引用VkPipelineShaderStageCreateInfo结构的数组。
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pDepthStencilState = nullptr; // Optional
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
然后我们引用描述固定功能阶段的所有结构。
pipelineInfo.layout = pipelineLayout;
之后是管道布局,它是一个Vulkan句柄,而不是一个结构指针。
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
最后,我们有了对渲染过程的引用和将使用此图形管道的子过程的索引。也可以将其他渲染过程用于此管道而不是此特定实例,但它们必须与renderPass兼容。这里描述了兼容性的要求,但我们不会在本教程中使用该功能。
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
pipelineInfo.basePipelineIndex = -1; // Optional
实际上还有两个参数:basePipelineHandle和basePipelineIndex。Vulkan允许您通过从现有管道派生来创建新的图形管道。管道衍生产品的理念是,当管道与现有管道具有许多共同功能时,设置管道的成本较低,并且在来自同一父管道的管道之间进行切换也可以更快。您可以使用basePipelineHandle指定现有管道的句柄,也可以使用basePipelineIndex引用将由索引创建的另一个管道。现在只有一个管道,所以我们只需指定一个空句柄和一个无效索引。只有在VkGraphicsPipelineCreateInfo的标志字段中也指定了VK_PIPELINE_CREATE_DERIVATIVE_BIT标志时,才使用这些值。
现在通过创建一个类成员来保存VkPipeline对象来准备最后一步:
VkPipeline graphicsPipeline;
最后创建图形管道:
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
throw std::runtime_error("failed to create graphics pipeline!");
}
vkCreateGraphicsPipelines函数实际上比Vulkan中常用的对象创建函数具有更多的参数。它被设计为在一次调用中获取多个VkGraphicsPipelineCreateInfo对象并创建多个VkPipeline对象。
第二个参数(我们已为其传递了VK_NULL_HANDLE参数)引用了可选的VkPipelineCache对象。管道缓存可用于存储和重用与多个调用vkCreateGraphicsPPipelines的管道创建相关的数据,如果缓存存储到文件中,甚至可以跨程序执行。这使得以后可以显著加快管道创建速度。我们将在管道缓存一章中讨论这个问题。
所有常见的绘图操作都需要图形管道,因此它也只能在程序结束时销毁:
void cleanup() {
vkDestroyPipeline(device, graphicsPipeline, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
...
}
现在运行您的程序,以确认所有这些艰苦的工作都已成功创建了管道!我们已经很接近看到屏幕上出现了一些东西。在接下来的几章中,我们将从交换链图像设置实际的帧缓冲区,并准备绘制命令。