Vulkan 学习笔记(十六)图形管线大结局
好了,各位受苦受难的同行们,坚持到现在不容易啊!是时候把之前搞出来的那些零零碎碎的结构体和对象,像拼乐高一样拼起来,正式造出那个传说中的图形管线 (Graphics Pipeline) 了!
为了防止你已经忘了咱们攒了哪些家当,我帮你捋一捋:
- • 着色器阶段 (Shader stages):那几个定义了可编程管线功能的着色器模块。
- • 固定功能状态 (Fixed-function state):那堆定义了不可编程管线阶段(比如输入装配、光栅化、视口、颜色混合)的结构体。
- • 管线布局 (Pipeline layout):用来在绘制时更新着色器里用到的Uniform和Push常量。
- • 渲染通道 (Render pass):定义了管线要用到的附件以及它们的用途。
有了这四大件,图形管线的功能就齐活了。咱们现在就去 createGraphicsPipeline 函数的末尾,填好那个重量级的 VkGraphicsPipelineCreateInfo 结构体。
注意啊,兄弟,千万别急着调用 vkDestroyShaderModule! 因为创建管线的时候还要用到它们,清理工作请放到最后再做。
1. 组装“申请表”
首先,咱们得把之前写好的结构体指针填进这张“申请表”里。
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages; // 引用着色器数组
先填上着色器阶段的信息。
// 接着是一堆固定功能状态的结构体
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pDepthStencilState = nullptr; // 暂时不用深度模板测试
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
这一长串都是咱们之前配置好的固定功能管线状态,直接引用就完事了。
pipelineInfo.layout = pipelineLayout; // 管线布局
然后是管线布局,注意这货是个Vulkan句柄,不是结构体指针。
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
最后是重头戏:引用咱们之前创建的渲染通道,以及指定这个管线要在第几个子通道(Subpass)里跑。Vulkan允许你用别的渲染通道,只要它们和当前这个“兼容”就行。虽然这功能很牛,但咱们教程里就不整这么复杂了。
2. 关于“继承”的那点事
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // 可选
pipelineInfo.basePipelineIndex = -1; // 可选
看到这俩参数别慌,它们是用来搞管线继承 (Pipeline Derivatives) 的。简单说,Vulkan允许你基于一个已有的管线创建新管线,这样能省性能,切换起来也快。
咱们现在的想法是:“不搞特殊,老子单干”。所以直接把句柄设为空,索引设为无效值。除非你在 flags 里加了 VK_PIPELINE_CREATE_DERIVATIVE_BIT 这个标记,否则这俩参数就是摆设。
3. 最后的仪式
现在,去类里加个成员变量,用来供奉这个即将诞生的管线对象,就放在 pipelineLayout 下面:
VkPipeline graphicsPipeline;
好了,深呼吸,准备见证历史!
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
throw std::runtime_error("failed to create graphics pipeline!");
}
这里插一嘴,vkCreateGraphicsPipelines 这个函数设计得比较豪放,它允许你一次性传入多个 VkGraphicsPipelineCreateInfo 对象,批量创建管线。咱们第二个参数传了 VK_NULL_HANDLE,这是在说咱们暂时不用管线缓存 (Pipeline Cache)。这玩意儿能把管线数据存到硬盘上,下次启动程序直接读,能省不少初始化时间。这属于“高级养生技巧”,后面章节再细聊。
4. 尾声
图形管线是咱们画图的命根子,所以清理工作得放到程序退出时的 cleanup 函数里:
void cleanup(){
vkDestroyPipeline(device, graphicsPipeline, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
// ... 其他清理工作
}
好了,现在跑一下程序。如果没报错,恭喜你!这意味着咱们已经成功把所有配置信息喂给了驱动,那个复杂的图形管线对象已经诞生了。
虽然现在屏幕上还是一片黑,啥也没有,但咱们已经做好了所有的准备工作。接下来的章节,咱们就要搞来交换链里的图像,把它们变成帧缓冲,然后下达绘制命令,让你的三角形终于能在屏幕上“露个脸”了!
[源码]https://vulkan-tutorial.com/code/12_graphics_pipeline_complete.cpp