Vulkan学习笔记
在过去,我以为Vulkan只有高端安卓才会用,相当复杂,没有下定决心去学。后来发现很多桌面和主机游戏也在用Vulkan,Vulkan真的很出色很重要,于是这几天开始看英伟达nvpro和Vulkan官方教程,打算自己敲一个小demo
我的Vulkan项目
最近搓了一个Vulkan项目,记录一下学习过程
Vulkan API
Queue
Queue是将命令提交到GPU的入口,Command buffer会被提交到Queue中按顺序执行
你可以使用多个线程分别提交命令到多个Queue中
提交到不同Queue的命令,其执行顺序不确定,但可以使用Semaphore进行同步
Vulkan的命令有:
- vkCmdDraw
- vkCmdDispatch:执行Compute Shader
- vkCmdCopy
- vkQueueBindSparse
通常一个Queue只能处理某几种命令
一个硬件往往只有有限个Queue
Render Pass
画到哪
Vulkan相较于OpenGL、DX11/12,一大特点就是有Render Pass这个概念
Render Pass的作用是描述绘制的目标(attachments,很像RT),比如color,比如depth
比如一个Forward Pass,他的目标可能就是一张color和一张depth,一个Deferred Pass,他的目标可能是GBuffer(一组color)和一个depth
VkAttachmentDescription
值得注意的参数
- format:image view格式
- samples:多采样次数
- finalLayout:当renderpass绘制结束时,会将color转化为该格式
- 画向Swapchain Image,finalLayout配置为
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
- 给其他pass的shader采样,设置为
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
- 画向Swapchain Image,finalLayout配置为
Subpass
一个像素在绘制单元绘制后,通常需要被拷贝出去,形成一张完整的RT,然后再由另一个绘制单元绘制
Subpass的一大特点是一个像素被一个Subpass绘制后,并不会立刻被拷贝出去,而是继续被另一个Subpass绘制
优点:
- 减少了拷贝带来的带宽,减少延迟和发热,移动端延迟渲染常用该技术
- 像素间绘制彼此独立,不需要一整张RT绘制后再绘制下一张,提高并行效率,还能用来实现OIT和Zero Overdraw
缺点:
- 拿不到其他位置的RT的绘制内容,无法实现一些依赖RT的屏幕后效
Pipeline
怎么画
用于设置管线的状态
参数 | 主要作用 |
---|---|
管线类型 | 图形管线还是计算管线 |
Shader Stage | 指定绘制用的Shader |
Vertex Input | 顶点缓冲的结构 |
Input Assembly | 输入的结构,比如三角形的拓扑规则 |
Viewport & Scissors | |
Rasterizer | 光栅化的规则,比如用线、用三角面绘制,是否背面剔除 |
Multisampling | 多采样的规则 |
Depth Stencil | 深度测试、模板测试的规则 |
Blending | 颜色混合规则(半透明) |
Pipeline Layout | 管线的结构,比如UBO binding规则 |
在Vulkan中,Render Pass是比Pipeline大的,这也很好理解,毕竟只要绘制目标没有改变,自然不需要改动Render Pass
而一个场景中有大量不同材质、Shader的对象,而且有的可能是线,有的是三角形,于是需要经常调整Pipeline,于是Vulkan的Pipeline就像Descriptor Set一样,可以随时改绑定的
Vulkan的Pipeline是很大的东西,实时创建大量开销过大,而大部分的Pipeline对象都很接近,可能就只有一小部分有差异。为了实现复用,Vulkan的Pipeline是可以动态修改、可以Cache、可以生成子类
Descriptor Set Layout
Shader中的资源位置
Shader中会使用很多资源,比如UBO,比如贴图Sampler,需要指定绑定的位置
layout(binding = 0) uniform GlobalUBO { |
layout(binding = 1) uniform sampler2D texSampler; |
Descriptor Set
资源
简单理解为Vulkan管线能理解、使用的资源,比如UBO,比如贴图Sampler
在绘制时,我们可以绑定一个Descriptor Sets,里面包含多个Descriptor Set,其结构是Layout决定的
通常我们为每一个材质实例创建一个Descriptor Set(一个UBO或者贴图可以Update到多个Descriptor Set上)
我们可以单独更新某一个Set
VkDescriptorType | 含义 | 类似DX12中的 |
---|---|---|
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE | 读图片 | SRV |
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE | 读写图片 | UAV |
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER | 读图片,并包含各种采样配置 | SRV + Sampler |
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER | 常量缓冲 | UBO |
Image
Texure
Usage
图片的使用目的
VkImageUsageFlagBits | 含义 |
---|---|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | Color渲染目标 |
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | 深度缓冲、模板缓冲渲染目标 |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | 图像拷贝的源 |
VK_IMAGE_USAGE_TRANSFER_DST_BIT | 图形拷贝的目标 |
VK_IMAGE_USAGE_SAMPLED_BIT | 可以被着色器读 |
VK_IMAGE_USAGE_STORAGE_BIT | 可以被着色器写 |
Layout
图形在内存的布局和排列,会影响拷贝、渲染等行为的可用性
VkImageLayout | 用途 |
---|---|
VK_IMAGE_LAYOUT_UNDEFINED | 图形初始化 |
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL | Color渲染目标 |
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL | 深度缓冲、模板缓冲渲染目标 |
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL | Shader可读的贴图 |
Image的Layout转化需要使用Pipeline Barrier
Image View
Fence
用于隔离不同帧
Semaphore
用于隔离渲染和呈现
Shader
Vulkan使用SPIR-V作为着色语言,这是一种底层的二进制着色语言,可以使用glslang编译GLSL得到
# 将vert_shader.vert编译为vert_spv |
可以使用spirv-dis
查看一个spv文件的内容(如果编译时带有调试信息,可以看到源码)
spirv-dis test.spv |
Vertex
|
Fragment
|
集成到cmake
在VS文件夹中显示
Visual Studio安装glsl插件后,shader可以高亮和智能提示
file(GLOB_RECURSE HEADERS "*.h") |
项目构建时编译
# Compile shaders |
# Copy Shader |
ImGui
Vulkan ImGui本质是使用Vulkan API画平面,ImGui会帮你创建一个Pipeline,但是你需要自己准备Render Pass、Frame Buffer、Vulkan Context等内容
ImGui版本选择
ImGui不同分支功能不同,其中最多人使用的docking分支,这个分支下ImGui可以用来做Editor
Vulkan初始化
核心是需要一个Render Pass,这个Render Pass需要有一个Color Attachment,我建议单独开一个pass
ImGui初始化
可以认为这个过程是帮你创建以Render Pipeline
ImGui::CreateContext(); |
绘制
需要传入一个command buffer
ImGui_ImplVulkan_NewFrame(); |
销毁
ImGui_ImplVulkan_Shutdown(); |
Vulkan拓展
可以通过函数指针的方式引入一些Vulkan拓展
Debug Label
在使用RenderDoc截帧时,我们可以看到一些绘制命令被分类命名,一些贴图也有调试名称
PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT = nullptr; |