type
status
date
slug
summary
tags
icon
password
一、 什么是Pipeline?
Vulkan主要有三种类型的管线:
- ComputePipeline(计算管线):有关于计算的都走这条管线,支持的都是只计算的程序。
- GraphicsPipeline(图形管线):它包含了所有的参数,如顶点、片段、几何、计算和曲面细分着色器(如果启用了该功能的话),再加上像顶点属性、输入装配、图元结构、光栅化参数、深度/模板测试参数、面剔除和Blend Const、视口(ViewPort)参数还有RenderPass等,Vulkan会将这些状态固定成一个大的、不可更改的对象。 这样所有的状态组合的验证和编译都可以在初始化的时候完成,运行时不会再因为这些操作有任何性能上的浪费。对于渲染过程中需要的每一组不同的参数,必须创建一个新的Pipeline。可以通过调用函数vkCmdBindPipeline将其设置该CommandBuffer Scope内绑定该Pipeline。并且提供了Dynamic State给Pipeline提供了一定的灵活性,修改Dynamic State可以避免重新创建一个Pipeline以减少开销。
- RaytracingPipeline(光追管线):不做细讲
命令实际上是通过一个处理管线发送的,要么是图形管线,要么是光线追踪管线,要么是计算管线。
二、 为什么要设计Pipeline?
它封装了大多数GPU状态,包括顶点输入格式、渲染目标格式、所有阶段的状态和所有阶段的Shader Module。期望是在每一个支持的GPU上,这种状态足以建立最终的着色器微代码和设置状态所需的GPU命令,因此驱动程序永远不必在绘制时编译微代码,并尽可能地优化Pipeline的设置。
三、 一些前备知识
与传统图形API 不同,Vulkan 中的Shader代码必须以字节码格式指定,而不是像 GLSL 和 HLSL 这种让人可读语法。这种字节码格式称为 SPIR-V,旨在与 Vulkan 和 OpenCL(均为 Khronos API)一起使用。它是一种可用于编写图形和计算Shader的格式,在这里将编写Vulkan 图形管线的Shader。
使用字节码格式的优势在于GPU供应商编写的将Shader代码转换为本机代码的编译器的复杂性要低得多。过去表明,对于像 GLSL 这样的人类可读语法,一些 GPU 供应商对标准的解释相当灵活。如果您碰巧使用这些供应商之一的GPU编写了重要的Shader,那么您可能会冒其他供应商的驱动程序由于语法错误而拒绝您的代码的风险,或者更糟糕的是,您的Shader由于编译器错误而运行不同。使用像 SPIR-V 这样的简单字节码格式,有望避免。
然而,这并不意味着我们需要手动编写这个字节码。 Khronos 发布了他们自己的独立于供应商的编译器,可将 GLSL 编译为 SPIR-V。此编译器旨在验证您的Shader代码是否完全符合标准,并生成一个可以随程序一起提供的 SPIR-V 二进制文件。您还可以将此编译器作为库包含在运行时生成 SPIR-V,但我们不会在本教程中这样做。虽然我们可以通过 glslangValidator 直接使用此编译器,它们都已包含在 Vulkan SDK 中,因此您无需下载任何额外内容。

四、 Shader Module的一些模块
1、 顶点着色器(Vertex shader)GLSL格式
顶点着色器处理传入的每个顶点,它接收顶点的属性Attributes如世界坐标、颜色、法线和纹理坐标作为输入。同时,它输出裁剪空间下的顶点坐标,并且传递需要给片元着色器的顶点属性Attributes,如颜色和纹理坐标。这些值会在光栅化阶段的片元中被插值。
告诉显卡点应该被画在什么位置,对每一个顶点(这些顶点可能有很多属性(纹理,法线等等),也可能只有最基本的位置属性),假设有三个顶点,那么就应该被调用3次。
一个顶点着色器的文件
2、 片元着色器(Fragment Shader)GLSL格式
对每一个需要渲染的像素运行fragment shader,设置像素的颜色,并最终渲染在屏幕上,每个pixel都需要调用一次。
片元着色器就要通过GLSL代码进行运行,通过GLSL文件生成一个SPIR-V的二进制文件来渲染像素
3、 编译着色器(Compile Shader)
就是将GLSL文件变成一个SPIR-V的二进制文件
将顶点着色器代码和片元着色器代码保存到shader.vert和shader.frag文件中,接下来写一个bat文件来编译着色器,windows平台的bat代码如下。
glslcPath shader.vert -o vert.spv
glslcPath shader.frag -o frag.spv
pause
这两个指令告诉编译器读取GLSL源文件并且通过-o(output)标识来输出SPIR-V字节码文件。运行bat即可得到香喷喷的vert.spv和frag.spv文件。
如果咱的shader代码中包含语法错误,编译器会提示行号和问题。它还有些其他功能,比如可以将字节码翻译成人类可读的格式,我们可以从中看到shader具体在做些什么操作,并且其中做了什么优化。
从命令行编译shader是最直接的方式,Vulkan SDK也包含了libshaderc库让我们可以在程序内通过代码将GLSL编译成SPIR-V。
4、 加载着色器(Loading a shader)
将SPIR-V文件加载到我们的程序中,以便在某个时候将它们插入到图形管道中。
5、 着色器阶段创建(shader stage creation)
要使用这些shader的话,必须要通过vkPipelineShaderStageCreateInfo结构体(又是一个XXCreateInfo)将其分配到特定的管线里面。
需要指定shader被使用的管线阶段、shaderModule以及主函数入口entrypoint。可以指定entrypoint意味着我们可以在一个Shader资源文件中写多个不同的片元着色器函数,通过使用不同的entrypoint来作为区分。
五、 一些fix function
1、 动态状态(Dynamic state)
虽然大部分的管线状态都需要烘培到管线状态中,但还是有一小些状态是可以在绘制时改变的,不需要重建管线。举例来说,比如Viewport的尺寸、线宽、混合常量。如果我们想要使用动态状态并且保留这些属性,那么我们必须填充一个VkPipelineDynamicStateCreateInfo结构体。它会让这些值的配置被忽略,同时我们可以并且必须在绘制时明确这些数据。这会带来更加灵活的配置,对于视口或者裁剪状态来说非常常见。
2、输入装配器(input assembler)
会从指定的缓冲区收集原始顶点数据,同时也可能使用索引缓冲区来重复利用顶点。
- topology字段将决定从顶点绘制什么样的几何图形,如Point, Line, TriangleList等。
- primitiveRestartEnable字段决定是否应启用图元复用,可以配合Index Buffer使用来复用顶点数据。

3、Vertex input(顶点输入阶段)
VkPipelineVertexInputStateCreateInfo结构体如下所示,这个主要是指定顶点数据相关
- VkVertexInputBindingDescription:数据之间的间隔以及数据,按顶点还是按实例化通过VkVertexInputRate区分。
- VkVertexInputAttributeDescription:传递给顶点着色器的属性类型,从哪个绑定处加载它们,在哪个偏移处加载。

4、 Viewports and scissors(视口和裁剪)
视口viewport描述了渲染输出的framebuffer的范围。通常来说几乎都是从(0,0)到(width, height)。
区分一下视口和裁剪:视口就相当于对像素范围进行一个转换,是对图像的一个形变。而剪切就是一个裁剪。

由上图可以看出要想得到一个完整的图片,我们就必须需要完整的Viewport和Scissor rectangle。如果要使视口和裁剪动态的变化,在这个里面我们经常和Dynamic State进行联动,在VkPipelineDynamicStateCreateInfo结构体里面就可以进行设置
但是如果需要使图片处于静态状态的话,我们就需要在管线里面使用VkPipelineViewportStateCreateInfo。这会让管线的视口和裁剪矩形不可变。对于这些值的改变都需要完全重建一个新的管线。
5、 Rasterizer(光栅化器)
光栅化器Rasterizer将顶点着色器输出的几何图形转变成片元传递给片元着色器。它也会执行深度检测、面剔除和裁剪检测,同时它可以配置输出片元为填充模式还是线框模式。以上这些模式均由VkPipelineRasterizationStateCreateInfo配置。
6、 Multisampling(多重采样)
Multisampling是关于硬件抗锯齿的一些设置。它通过将光栅化到同一像素的多个多边形的片段着色器结果组合在一起来工作。这主要发生在边缘,这也是最明显的锯齿伪影发生的地方。因为如果只有一个多边形映射到一个像素,它不需要多次运行片段着色器,所以它比简单地渲染到更高分辨率然后缩小比例要便宜得多。启用它需要启用 GPU 功能。
7、 Color blending(颜色混合)
在片元着色器输出一个颜色之后,还需要在Framebuffer上的一个颜色进行混合(虽然还没有学习到Framebuffer,但是可以先说明的是,Framebuffer代表的是一个附件所有的VkImageView对象。)
在进行混合操作的时候有两个方法进行颜色的混合:
①混合新旧两个值产生一个最终的颜色
②使用位运算混合新旧俩值
六、 创建Render Pass(渲染通道)
1、 Render Pass的作用
在创建Pipeline之前,我们需要提前说明需要渲染的framebuffer attachments。在这个阶段说明我们会有多少color和depth buffer(深度缓存,代表的是三维图像里面Z轴),每个缓冲区要使用多少个样本,以及在整个渲染的过程中如何处理这些内容。将这些内容全部保存到一个RenderPass里面,为其创建一个新的createRenderPass函数。
让我们来看看这个参数都代表了什么。关于这个colorAttachment的Format应该与Swap Chain Image 的格式一致。并且在这里我们没有开启反走样之类的。于是将采样数保持为1。
loadOp 和 storeOp 确定在渲染之前和渲染之后如何处理Attachment中的数据。对于 loadOp,我们有以下选择:
- VK_ATTACHMENT_LOAD_OP_LOAD:保留附件的现有内容。
- VK_ATTACHMENT_LOAD_OP_CLEAR:在开始时将值清除为常量。
- VK_ATTACHMENT_LOAD_OP_DONT_CARE:现有内容未定义。
我们将在绘制新帧之前使用清除操作将Frame Buffer清除为黑色。 storeOp只有两种可能:
- VK_ATTACHMENT_STORE_OP_STORE:渲染的内容将存储在内存中,以后可以读取。
- VK_ATTACHMENT_STORE_OP_DONT_CARE:渲染操作后Frame Buffer的内容将不确定 。
我们有兴趣在屏幕上看到渲染的三角形,所以我们在这里进行Store操作。
loadOp 和 storeOp 适用于Color和Depth数据,stencilLoadOp / stencilStoreOp 适用于stencil值。我们的应用程序不会对模板缓冲做任何事情,因此加载和存储的结果是无关紧要的。
Vulkan 中的Texture和Frame Buffer由具有特定像素格式的 VkImage 表示,但是内存中像素的Layout可能会根据您尝试对图像执行的操作而改变。 一些最常见的布局是:
- VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:用作Color AttachMent的Image 。
- VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:要在Swap Chain中 用于Persent的Image。
- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:用作内存复制操作目标的Image。
但现在重要的是要知道图像需要转换为适合它们接下来将涉及的操作的特定布局。 initialLayout 指定在渲染过程开始之前图像将具有的布局。 finalLayout 指定RenderPass完成时自动转换到的布局。对 initialLayout 使用 VK_IMAGE_LAYOUT_UNDEFINED 意味着我们不关心图像之前的布局。这个特殊值的警告是图像的内容不能保证被保留,但这并不重要,因为我们要去无论如何都要清除它。我们希望图像在渲染后使用Swap Chain准备好用于Persent,这就是我们使用 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 作为 finalLayout 的原因。
2、 Subpass and attachment reference
一个单独的渲染通道render pass可以由多个子通道subpasses组成。Subpasses是紧随其后的渲染操作,其关联于先前passes中framebuffer的内容,例如依次应用的一系列后处理操作。如果我们将这些渲染操作group到一个render pass中,Vulkan就可以对这些操作重新排序并节省内存带宽,从而可能获得更好的性能。
每个subpass都引用我们之前描述过的一个或多个attachments。这些引用本身就是VkAttachmentReference结构。
attachment参数通过attachment在descriptions array中的索引指定了要引用的attachment。我们的array由一个单独的VkAttachmentDescription组成,所以索引为0。layout制定了在subpass中attachment被期望的layout。Vulkan会在subpass开始前自动将attachment转换成这个layout。我们期望attachment被用作color buffer,而VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL会给我们更好的性能,就如它的名字。
Vulkan未来会支持compute subpasses,所以我们需要显式告诉它是一个graphics subpass。
attachment array中的索引是通过layout(location = 0) out vec4 outColor指令直接从片元着色器中引用的。attachment array也可以理解成subpass中的shader需要in和out使用到的attachment吧。
以下是一个subpass可以引用的其他attachment类型:
- pInputAttachments:从shader中读取的attachments。 2. pResolveAttachments:用于多重采样color attachments的attachments 3. pDepthStencilAttachment:depth和stencil数据的attachment。 4. pPreserveAttachment:attachment不会被这个subpass引用,但是其数据需要保留。
3、创建渲染通道
在之前已经描述了subpass和attachment,现在就可以通过VkRenderPassCreateInfo函数来创建VkRenderPass对象。
七、 创建Pipeline
创建Pipeline需要VkGraphicsPipelineCreateInfo结构体。在最后还需要手动销毁这个结构体。
八、 总结
我们先快速回顾下我们拥有的对象:
- Shader stages:定义了管线中可编程部分功能的shader modules。 2. Fixed-function state:所有管线的固定功能状态结构体,比如输入装配、光栅化器、视口和颜色混合。
- Pipeline layout:绘制时可更改的shader的uniform和push值。
- Render pass:管线阶段引用的attachments和subrender的用途。
5.Pipeline:拿到以上4个部分就可以完整的建一个Pipeline
- 作者:JucanaYu
- 链接:https://jucanayu.top/article/82f107f9-343e-44a9-ba83-170ffc7624e2
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。