结论
现在我们可以结合之前章节中的所有结构和对象来创建图形管线了!以下是我们现在拥有的对象类型,作为一个快速回顾
- 着色器阶段:定义图形管线可编程阶段功能的着色器模块
- 固定功能状态:定义管线固定功能阶段的所有结构,例如输入汇编、光栅化器、视口和颜色混合
- 管线布局:着色器引用的 uniform 值和 push 值,可以在绘制时更新
- 渲染通道:管线阶段引用的附件及其用途
所有这些结合在一起完全定义了图形管线的功能,所以我们现在可以开始填写 VkGraphicsPipelineCreateInfo
结构体,在 createGraphicsPipeline
函数的末尾。但在调用 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
引用另一个即将通过索引创建的管线。现在只有一个管线,所以我们将简单地指定一个空句柄和一个无效索引。只有当 VK_PIPELINE_CREATE_DERIVATIVE_BIT
标志也在 VkGraphicsPipelineCreateInfo
的 flags
字段中指定时,这些值才会被使用。
现在准备好最后一步,创建一个类成员来保存 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
对象。管线缓存可以用于存储和重用与管线创建相关的数据,这些数据可以在多次调用 vkCreateGraphicsPipelines
之间,甚至跨程序执行(如果缓存存储到文件)。这使得在稍后时间显著加快管线创建成为可能。我们将在管线缓存章节中深入探讨这一点。
图形管线是所有常见的绘制操作所必需的,因此它也应该只在程序结束时销毁
void cleanup() {
vkDestroyPipeline(device, graphicsPipeline, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
...
}
现在运行你的程序,确认所有这些辛勤工作已经成功创建了管线!我们已经非常接近在屏幕上看到一些东西弹出来了。在接下来的几章中,我们将从交换链图像中设置实际的帧缓冲,并准备绘制命令。