总结

原文链接:https://kylemayes.github.io/vulkanalia/pipeline/conclusion.html

Commit Hash: ceb4a3fc6d8ca565af4f8679c4889bcad7941338

本章代码:main.rs

现在我们可以开始组合前几章中创建的所有结构和对象来创建图形管线了!回顾一下我们现在有的对象:

  • 着色器阶段 – 着色器单元定义的图形管线中的可编程阶段
  • 固定功能状态 – 管线中所有定义固定功能阶段的结构,例如输入组装、光栅化器、视口和颜色混合
  • 管线布局 – 定义着色器引用的可以在绘制时更新的 uniform 值和推送常量
  • 渲染流程 – 管线阶段中引用的附件,以及它们的用途

这些对象定义了图形管线的方方面面。现在我们可以在 create_pipeline 函数的末尾(但要在着色器模块销毁之前)开始填充 vk::GraphicsPipelineCreateInfo 了。

let stages = &[vert_stage, frag_stage];
let info = vk::GraphicsPipelineCreateInfo::builder()
    .stages(stages)
    // continued...

我们首先提供一个 vk::PipelineShaderStageCreateInfo 结构体的数组。

    .vertex_input_state(&vertex_input_state)
    .input_assembly_state(&input_assembly_state)
    .viewport_state(&viewport_state)
    .rasterization_state(&rasterization_state)
    .multisample_state(&multisample_state)
    .color_blend_state(&color_blend_state)

接着我们引用所有描述固定功能阶段的结构体。

    .layout(data.pipeline_layout)

然后是管线布局。

    .render_pass(data.render_pass)
    .subpass(0);

最后引用之前创建的渲染流程,以及图形管线将要使用的子流程在子流程数组中的索引。在这个渲染管线上使用其他的渲染流程也是可以的,但这些渲染流程之间必须相互兼容这里给出了关于兼容性的描述,不过本教程中我们不会使用这个特性。

    .base_pipeline_handle(vk::Pipeline::null()) // 可选.
    .base_pipeline_index(-1)                    // 可选.

实际上还有两个参数:base_pipeline_handlebase_pipeline_index。Vulkan 允许你派生一个现有的图形管线来创建新的图形管线。管线派生的意义在于,如果新的管线和旧的管线有很多相似之处,这样做就能减少很多开销;在同一个亲代(parent)派生出的图形管线之间切换也更快。你可以使用 base_pipeline_handle 通过句柄来指定一个现有的管线,或者使用 base_pipeline_index 通过索引来指定一个即将创建的管线。现在我们只有一个管线,所以我们会简单地指定一个空句柄和一个无效索引。只有在 vk::GraphicsPipelineCreateInfoflags 字段中也指定了 vk::PipelineCreateFlags::DERIVATIVE 标志时,这些值才会被使用。

现在,在 AppData 中添加一个字段来存储 vk::Pipeline 对象:

struct AppData {
    // ...
    pipeline: vk::Pipeline,
}

然后在 App::create 中创建图形管线:

data.pipeline = device.create_graphics_pipelines(
    vk::PipelineCache::null(), &[info], None)?.0[0];

create_graphics_pipelines 函数的参数比 Vulkan 中通常的对象创建函数要多。它被设计为可以一次性接受多个 vk::GraphicsPipelineCreateInfo 对象并创建多个 vk::Pipeline 对象。

第一个参数是一个对 vk::PipelineCache 的引用,这个参数是可选的,我们为其传递 vk::PipelineCache::null()。管线缓存可以用来在多次调用 create_graphics_pipelines 时存储和重用管线创建相关的数据,甚至可以在程序执行结束后从文件中读取缓存。这样可以显著提高管线创建的速度。

图形管线会在所有的绘制操作中使用,所以它也应该在 App::destroy 中被销毁:

unsafe fn destroy(&mut self) {
    self.device.destroy_pipeline(self.data.pipeline, None);
    // ...
}

现在运行程序,来确定我们一直以来的努力并非全部白费。现在,我们离看到屏幕上有东西出现不远了。在接下来的几章中,我们将设置交换链图像的实际帧缓冲,并准备绘制指令。