结论

现在,我们可以将前面章节中的所有结构和对象组合起来,创建图形管线!下面快速回顾一下我们现在拥有的对象类型:

  • 着色器阶段:定义图形管线可编程阶段功能的着色器模块

  • 固定功能状态:定义管线固定功能阶段的所有结构,如输入汇编、光栅化器、视口和颜色混合

  • 管线布局:着色器引用的、可在绘制时更新的 uniform 和 push 值

  • 渲染通道:管线阶段引用的附件及其用法

所有这些组合起来完整定义了图形管线的功能,所以我们现在可以开始填写 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

实际上还有两个参数:basePipelineHandlebasePipelineIndex。Vulkan 允许您通过派生自现有管线来创建新的图形管线。管线派生的思想是,当它们与现有管线具有许多共同功能时,设置管线的成本较低,并且在同一父级管线之间切换也可以更快地完成。您可以使用 basePipelineHandle 指定现有管线的句柄,也可以使用 basePipelineIndex 引用即将创建的另一个管线的索引。目前只有一个管线,所以我们只需指定一个空句柄和一个无效索引。只有在 VkGraphicsPipelineCreateInfoflags 字段中也指定了 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 对象。管线缓存可用于存储和重用与多次调用 vkCreateGraphicsPipelines 相关的管线创建数据,甚至可以在将缓存存储到文件的情况下跨程序执行。这使得以后可以显著加快管线创建速度。我们将在管线缓存章节中讨论这个问题。

图形管线是所有常用绘图操作所必需的,因此也应仅在程序结束时销毁

void cleanup() {
    vkDestroyPipeline(device, graphicsPipeline, nullptr);
    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
    ...
}

现在运行您的程序,以确认所有这些努力工作已成功创建管线!我们已经很接近在屏幕上看到一些东西了。在接下来的几章中,我们将设置交换链图像中的实际帧缓冲区,并准备绘制命令。