第七章的核心目标,是在第二章已经能够绘制三角形的基础上,继续完善 Vulkan 中最基础的数据传递流程。

这一章主要解决以下问题:

  • 如何将 CPU 中的顶点数据传入 GPU
  • 如何使用索引复用顶点
  • 如何一次绘制多个相同物体
  • 如何向着色器传递少量数据
  • 如何向着色器传递较大数据
  • 如何将图片数据上传为 Vulkan 图像
  • 如何在着色器中使用贴图

第七章完成后,整个程序将从“顶点写死在着色器中的简单三角形”,逐步扩展为一个具备顶点、索引、实例化、Uniform、Push Constant 和贴图能力的基础 Vulkan 渲染框架。


一、Ch7-1 初识顶点缓冲区#

在第二章中,三角形的顶点位置通常直接写在顶点着色器中,并通过 gl_VertexIndex 进行读取。

这种方式只适合绘制极其简单的测试图形。

真正的渲染程序中,顶点数据通常保存在 CPU 内存中,然后上传到 GPU 的顶点缓冲区中。

整体流程为:

1CPU 顶点数组
2→ 创建 VkBuffer
3→ 分配 VkDeviceMemory
4→ 上传顶点数据
5→ 绑定顶点缓冲区
6→ 顶点着色器读取数据

1. 顶点结构#

顶点可以包含多个属性,例如:

1struct Vertex
2{
3    glm::vec2 position;
4    glm::vec3 color;
5    glm::vec2 texCoord;
6};

这些数据通常交错存储在同一个顶点数组中。

例如:

1顶点0:位置、颜色、UV
2顶点1:位置、颜色、UV
3顶点2:位置、颜色、UV


2. VkBuffer 与 VkDeviceMemory#

Vulkan 中,Buffer 对象与实际内存是分离的。

创建顶点缓冲区时,需要完成以下步骤:

1创建 VkBuffer
2→ 查询内存需求
3→ 查找合适的内存类型
4→ 分配 VkDeviceMemory
5→ 将内存绑定到 Buffer

VkBuffer 只描述缓冲区本身,例如:

  • 缓冲区大小
  • 缓冲区用途
  • 资源共享模式

真正存储数据的是 VkDeviceMemory。

因此:

1VkBuffer = 资源对象
2VkDeviceMemory = 实际内存

二者必须通过:

1vkBindBufferMemory(...)

进行绑定。


3. 顶点缓冲区用途#

创建顶点缓冲区时,需要指定:

1VK_BUFFER_USAGE_VERTEX_BUFFER_BIT

如果使用 Staging Buffer,则最终的顶点缓冲区通常还需要:

1VK_BUFFER_USAGE_TRANSFER_DST_BIT

例如:

1VK_BUFFER_USAGE_TRANSFER_DST_BIT |
2VK_BUFFER_USAGE_VERTEX_BUFFER_BIT


4. 顶点输入描述#

Graphics Pipeline 必须知道顶点缓冲区中的数据如何排列。

主要通过以下两个结构体进行描述:

1VkVertexInputBindingDescription
2VkVertexInputAttributeDescription

Binding Description#

Binding 描述整条顶点数据流。

主要包含:

1binding
2stride
3inputRate

例如:

1binding = 0;
2stride = sizeof(Vertex);
3inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

其中:

  • binding 表示当前数据使用哪个 Binding
  • stride 表示相邻两个顶点之间的字节间隔
  • inputRate 表示数据按顶点更新还是按实例更新


Attribute Description#

Attribute 描述顶点结构中的某一个具体属性。

主要包含:

1location
2binding
3format
4offset

例如:

1location = 0;
2binding = 0;
3format = VK_FORMAT_R32G32_SFLOAT;
4offset = offsetof(Vertex, position);

这里的 location 必须与顶点着色器中的输入位置一致:

1layout(location = 0) in vec2 inPosition;
2layout(location = 1) in vec3 inColor;
3layout(location = 2) in vec2 inTexCoord;

因此:

1Binding 描述一整条数据
2Attribute 描述数据中的某个成员
3Location 对应 Shader 中的输入位置


5. 绑定顶点缓冲区#

绘制之前,需要调用:

1vkCmdBindVertexBuffers(...)

之后再调用:

1vkCmdDraw(...)

最终流程为:

1创建顶点缓冲区
2→ 配置 Pipeline Vertex Input
3→ vkCmdBindVertexBuffers
4→ vkCmdDraw


6. Staging Buffer#

性能较高的顶点缓冲区通常放在 Device Local 内存中。

但是 Device Local 内存通常无法直接被 CPU 写入。

因此需要使用 Staging Buffer。

整体流程为:

1CPU 数据
2→ Host Visible Staging Buffer
3→ vkCmdCopyBuffer
4→ Device Local Vertex Buffer

Staging Buffer 通常使用:

1VK_BUFFER_USAGE_TRANSFER_SRC_BIT

最终顶点缓冲区使用:

1VK_BUFFER_USAGE_TRANSFER_DST_BIT |
2VK_BUFFER_USAGE_VERTEX_BUFFER_BIT

Staging Buffer 只负责临时上传数据,上传完成后即可销毁。


二、Ch7-2 初识索引缓冲区#

索引缓冲区用于复用顶点。

例如,一个矩形由两个三角形组成。如果不使用索引缓冲区,需要六个顶点:

1三角形1:0、1、2
2三角形2:3、4、5

其中部分顶点的位置实际上是重复的。使用索引缓冲区后,只需要四个顶点:

1Vertex vertices[] =
2{
3    左上,
4    右上,
5    左下,
6    右下
7};

再使用六个索引:

1uint16_t indices[] =
2{
3    0, 1, 2,
4    1, 3, 2
5};


1. 索引缓冲区用途#

索引缓冲区需要指定:

1VK_BUFFER_USAGE_INDEX_BUFFER_BIT

如果使用 Staging Buffer,则通常为:

1VK_BUFFER_USAGE_TRANSFER_DST_BIT |
2VK_BUFFER_USAGE_INDEX_BUFFER_BIT


2. 绑定索引缓冲区#

绘制前调用:

1vkCmdBindIndexBuffer(...)

其中必须指定索引类型:

1VK_INDEX_TYPE_UINT16

或者:

1VK_INDEX_TYPE_UINT32

索引类型必须与 CPU 中的索引数组类型保持一致。

例如:

1std::vector<uint16_t>

对应:

1VK_INDEX_TYPE_UINT16


3. 索引绘制#

绑定索引缓冲区后,使用:

1vkCmdDrawIndexed(...)

代替:

1vkCmdDraw(...)

整体流程为:

1Vertex Buffer 提供顶点数据
2Index Buffer 提供顶点编号
3→ vkCmdBindVertexBuffers
4→ vkCmdBindIndexBuffer
5→ vkCmdDrawIndexed

索引缓冲区中保存的并不是顶点数据,而是顶点在顶点缓冲区中的编号。


三、Ch7-3 实例化绘制#

实例化绘制用于一次 Draw Call 绘制多个相同结构的物体。

例如:

  • 大量草
  • 大量树木
  • 粒子
  • 子弹
  • 重复模型
  • 大量相同网格

如果每个物体都单独调用一次 Draw,会产生大量 CPU 到 GPU 的绘制调用开销。

实例化绘制可以通过一次调用绘制多个实例:

1vkCmdDrawIndexed(
2    commandBuffer,
3    indexCount,
4    instanceCount,
5    0,
6    0,
7    0);

其中:

1indexCount = 每个实例使用多少索引
2instanceCount = 绘制多少个实例


1. 顶点数据与实例数据#

实例化绘制中,数据通常分为两类:

1逐顶点数据
2逐实例数据

逐顶点数据包括:

  • 顶点位置
  • 法线
  • UV
  • 顶点颜色

逐实例数据包括:

  • 实例位置
  • 实例颜色
  • 实例缩放
  • 模型矩阵
  • 实例编号


2. Input Rate#

顶点数据使用:

1VK_VERTEX_INPUT_RATE_VERTEX

实例数据使用:

1VK_VERTEX_INPUT_RATE_INSTANCE

例如:

1VkVertexInputBindingDescription vertexBinding{};
2vertexBinding.binding = 0;
3vertexBinding.stride = sizeof(Vertex);
4vertexBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

实例数据:

1VkVertexInputBindingDescription instanceBinding{};
2instanceBinding.binding = 1;
3instanceBinding.stride = sizeof(InstanceData);
4instanceBinding.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE;

其中:

1VERTEX:每处理一个顶点读取下一条数据
2INSTANCE:每开始一个新实例读取下一条数据


3. gl_InstanceIndex#

Shader 中可以使用:

1gl_InstanceIndex

获取当前实例编号。

例如:

1vec2 offset = offsets[gl_InstanceIndex];

这样可以根据实例编号给每个实例设置不同位置。

实例化绘制的核心意义是:

1一次 Draw Call
2→ 绘制多个相同网格
3→ 每个实例拥有不同数据


四、Ch7-4 初识 Push Constant#

Push Constant 用于从 CPU 向 Shader 传递少量数据。

它不需要:

  • 创建 Buffer
  • 分配 Device Memory
  • 创建 Descriptor Set
  • 更新 Descriptor

只需要在 Pipeline Layout 中声明,然后在录制命令时写入。

整体流程为:

1定义 Push Constant 数据
2→ 创建 Push Constant Range
3→ 加入 Pipeline Layout
4→ Shader 声明 Push Constant
5→ vkCmdPushConstants


1. Push Constant Range#

创建 Pipeline Layout 时,需要声明:

1VkPushConstantRange pushConstantRange{};

主要包含:

1stageFlags
2offset
3size

例如:

1pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
2pushConstantRange.offset = 0;
3pushConstantRange.size = sizeof(PushConstantData);


2. Shader 中声明#

Shader 中使用:

1layout(push_constant) uniform PushConstantBlock
2{
3    vec2 offset;
4    vec4 color;
5} pushConstant;

C++ 中的结构体必须与 GLSL 中的数据布局对应。

例如:

1struct PushConstantData
2{
3    glm::vec2 offset;
4    glm::vec4 color;
5};

需要特别注意内存对齐。


3. 写入 Push Constant#

录制命令时调用:

1vkCmdPushConstants(...)

Push Constant 可以在 Draw Call 之间频繁修改。

例如:

1Push Constant A
2→ Draw Object A
3
4Push Constant B
5→ Draw Object B

因此它适合:

  • 每个物体的颜色
  • 每个物体的位置
  • 对象索引
  • 少量模型参数
  • 少量材质参数


4. Push Constant 特点#

优点:

  • 使用简单
  • 不需要 Descriptor
  • 更新方便
  • 适合每次绘制都变化的数据

缺点:

  • 容量较小
  • 不适合传递大量数据
  • 大量 Push Constant 更新会增加命令缓冲区内容

Vulkan 保证至少支持:

1128 Bytes

实际限制可以通过:

1maxPushConstantsSize

查询。


五、Ch7-5 初识 Uniform Buffer#

Uniform Buffer 用于向 Shader 传递较大的只读数据。

常见用途包括:

  • Model 矩阵
  • View 矩阵
  • Projection 矩阵
  • 相机位置
  • 时间
  • 灯光信息
  • 全局参数

Uniform Buffer 本质上仍然是一个普通的 VkBuffer,但是用途被指定为:

1VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT


1. Uniform Buffer 完整流程#

Uniform Buffer 需要配合描述符使用。

整体流程为:

1创建 Uniform Buffer
2→ 创建 Descriptor Set Layout
3→ 创建 Descriptor Pool
4→ 分配 Descriptor Set
5→ 更新 Descriptor Set
6→ 绑定 Descriptor Set
7→ Shader 读取 Uniform Buffer


2. Descriptor Set Layout#

Descriptor Set Layout 用于描述 Shader 将会使用什么资源。

例如:

1VkDescriptorSetLayoutBinding uboLayoutBinding{};

主要包含:

1binding
2descriptorType
3descriptorCount
4stageFlags

例如:

1uboLayoutBinding.binding = 0;
2uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
3uboLayoutBinding.descriptorCount = 1;
4uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

这必须与 Shader 保持一致:

1layout(set = 0, binding = 0) uniform UniformBufferObject
2{
3    mat4 model;
4    mat4 view;
5    mat4 projection;
6} ubo;


3. Descriptor Pool#

Descriptor Pool 用于分配 Descriptor Set。

创建时需要说明:

  • 可以分配多少个 Descriptor Set
  • 每种 Descriptor 类型有多少个

例如:

1VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER


4. Descriptor Set#

Descriptor Set 是实际绑定 Buffer 和 Shader 的对象。

更新 Descriptor Set 时需要提供:

1VkDescriptorBufferInfo

其中包含:

1buffer
2offset
3range

然后通过:

1vkUpdateDescriptorSets(...)

将 Uniform Buffer 写入 Descriptor Set。


5. 绑定 Descriptor Set#

绘制之前调用:

1vkCmdBindDescriptorSets(...)

之后 Shader 才能读取对应 Uniform Buffer。

整体关系为:

1Shader Binding
2↕
3Descriptor Set Layout
4↕
5Descriptor Set
6↕
7VkBuffer


6. 每帧 Uniform Buffer#

如果程序存在多个 Frames In Flight,不建议所有帧共用同一个正在频繁修改的 Uniform Buffer。

常见做法是:

1Frame 0 → Uniform Buffer 0
2Frame 1 → Uniform Buffer 1

每帧拥有:

  • 一个 Uniform Buffer
  • 一块 Memory
  • 一个 Descriptor Set

这样可以避免 CPU 修改数据时,GPU 仍在读取同一块内存。


7. Dynamic Uniform Buffer#

Dynamic Uniform Buffer 可以让多个对象共用同一个大 Buffer。

数据排列为:

1Object 0 Data
2Object 1 Data
3Object 2 Data

绑定 Descriptor Set 时,再通过动态偏移指定当前读取哪一段数据。

动态偏移必须满足:

1minUniformBufferOffsetAlignment

因此每段数据通常需要进行对齐。

例如:

1alignedSize = (dataSize + alignment - 1) & ~(alignment - 1);


8. Push Constant 与 Uniform Buffer 区别#

 1Push Constant:
 2少量数据
 3更新频繁
 4不需要 Descriptor
 5适合每次 Draw 不同的数据
 6
 7Uniform Buffer:
 8数据较大
 9需要 Descriptor
10适合矩阵、相机、灯光等全局数据

可以简单理解为:

1Push Constant = 小而频繁
2Uniform Buffer = 大而稳定


六、Ch7-6 拷贝图像到屏幕#

这一节开始正式接触 Vulkan Image。

Buffer 与 Image 的区别:

1Buffer:
2线性数据
3没有宽高
4适合顶点、索引、Uniform
5
6Image:
7二维或三维图像数据
8具有宽、高、格式、Mip Level
9适合贴图、渲染目标、深度缓冲

图像上传的整体流程为:

1CPU 图片数据
2→ Staging Buffer
3→ VkImage
4→ 图像布局转换
5→ Copy 或 Blit
6→ 交换链图像


1. 创建 VkImage#

创建 Image 时需要指定:

  • Image Type
  • Format
  • Extent
  • Mip Levels
  • Array Layers
  • Tiling
  • Usage
  • Initial Layout

例如:

1VK_IMAGE_USAGE_TRANSFER_SRC_BIT
2VK_IMAGE_USAGE_TRANSFER_DST_BIT
3VK_IMAGE_USAGE_SAMPLED_BIT

Image 与 Buffer 一样,也需要:

1创建 VkImage
2→ 查询内存需求
3→ 分配 VkDeviceMemory
4→ vkBindImageMemory


2. Image Layout#

Vulkan Image 在不同用途下需要处于不同 Layout。

常见 Layout 包括:

1VK_IMAGE_LAYOUT_UNDEFINED
2VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
3VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
4VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
5VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
6VK_IMAGE_LAYOUT_PRESENT_SRC_KHR

例如:

 1准备接收复制
 2→ TRANSFER_DST_OPTIMAL
 3
 4作为复制源
 5→ TRANSFER_SRC_OPTIMAL
 6
 7被 Shader 读取
 8→ SHADER_READ_ONLY_OPTIMAL
 9
10用于屏幕显示
11→ PRESENT_SRC_KHR

Image Layout 并不只是一个普通状态变量。

它同时与以下内容有关:

  • 图像当前用途
  • GPU 内部存储方式
  • 访问权限
  • 同步关系


3. Image Memory Barrier#

图像布局转换通常通过:

1VkImageMemoryBarrier

配合:

1vkCmdPipelineBarrier(...)

实现。

Barrier 需要描述:

  • 旧布局
  • 新布局
  • 源访问权限
  • 目标访问权限
  • 源管线阶段
  • 目标管线阶段
  • 图像范围

例如:

1UNDEFINED
2→ TRANSFER_DST_OPTIMAL

表示图像之后将作为复制目标。

再例如:

1TRANSFER_DST_OPTIMAL
2→ SHADER_READ_ONLY_OPTIMAL

表示图像复制完成后,将交给片段着色器读取。


4. Copy Buffer To Image#

通过:

1vkCmdCopyBufferToImage(...)

可以将 Staging Buffer 中的像素数据复制到 Image。

要求目标 Image 处于:

1VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL


5. Copy Image 与 Blit Image#

vkCmdCopyImage 用于直接复制图像。

vkCmdBlitImage 除了复制外,还可以:

  • 缩放
  • 翻转
  • 过滤
  • 在不同尺寸之间转换

例如,通过交换源图像上下坐标,可以实现垂直翻转。


6. 拷贝到交换链#

如果需要将图像复制到交换链图像,需要确保:

1源图像 → TRANSFER_SRC_OPTIMAL
2交换链图像 → TRANSFER_DST_OPTIMAL

复制完成后,再将交换链图像转换为:

1VK_IMAGE_LAYOUT_PRESENT_SRC_KHR

才能进行显示。

整体流程为:

1Swapchain Image
2PRESENT_SRC_KHR
3→ TRANSFER_DST_OPTIMAL
4→ 接收图像
5→ PRESENT_SRC_KHR


七、Ch7-7 使用贴图#

贴图本质上是一个可以被 Shader 读取的 Vulkan Image。

完整流程为:

1读取图片文件
2→ Staging Buffer
3→ Texture Image
4→ 生成 Mipmap
5→ 创建 Image View
6→ 创建 Sampler
7→ 创建 Combined Image Sampler Descriptor
8→ Shader 采样


1. Texture Image#

贴图 Image 通常需要:

1VK_IMAGE_USAGE_TRANSFER_DST_BIT
2VK_IMAGE_USAGE_SAMPLED_BIT

如果需要生成 Mipmap,还需要:

1VK_IMAGE_USAGE_TRANSFER_SRC_BIT

因此常见用途为:

1VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
2VK_IMAGE_USAGE_TRANSFER_DST_BIT |
3VK_IMAGE_USAGE_SAMPLED_BIT


2. Image View#

Shader 不能直接使用 VkImage。

必须先创建:

1VkImageView

Image View 用于描述:

  • 如何解释 Image
  • 使用什么格式
  • 使用哪些 Mip Level
  • 使用哪些 Array Layer
  • 使用哪个 Aspect

可以简单理解为:

1VkImage = 实际图像资源
2VkImageView = 访问图像资源的视图

同一个 Image 可以创建多个不同的 Image View。


3. Sampler#

Sampler 描述如何采样图像。

包括:

  • 放大过滤
  • 缩小过滤
  • Mipmap 过滤
  • UV 寻址模式
  • 各向异性过滤
  • LOD 范围

常用参数包括:

1magFilter
2minFilter
3mipmapMode
4addressModeU
5addressModeV
6addressModeW
7minLod
8maxLod
9maxAnisotropy

例如:

1Image = 贴图数据本身
2Image View = 如何访问贴图
3Sampler = 如何对贴图进行采样


4. Combined Image Sampler#

贴图通常通过:

1VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER

传递给 Shader。

需要创建:

1VkDescriptorImageInfo

其中包含:

1sampler
2imageView
3imageLayout

然后通过:

1vkUpdateDescriptorSets(...)

写入 Descriptor Set。


5. Shader 采样#

片段着色器中声明:

1layout(set = 0, binding = 1) uniform sampler2D textureSampler;

采样时使用:

1vec4 color = texture(textureSampler, inTexCoord);

其中:

  • textureSampler 是贴图与采样器
  • inTexCoord 是 UV
  • 返回值是采样得到的颜色


6. 纹理坐标#

UV 通常在以下范围内:

1U:0 到 1
2V:0 到 1

顶点结构中加入:

1glm::vec2 texCoord;

顶点着色器将 UV 传给片段着色器:

1layout(location = 2) in vec2 inTexCoord;
2layout(location = 0) out vec2 outTexCoord;

片段着色器接收:

1layout(location = 0) in vec2 inTexCoord;


7. Mipmap#

Mipmap 是同一张贴图的多级缩小版本。

例如:

11024 × 1024
2512 × 512
3256 × 256
4128 × 128
5...
61 × 1

Mipmap 数量计算方式为:

1floor(log2(max(width, height))) + 1

生成 Mipmap 时,可以通过:

1vkCmdBlitImage(...)

逐级缩小。

例如:

1Mip 0 → Mip 1
2Mip 1 → Mip 2
3Mip 2 → Mip 3

生成过程中需要不断进行 Image Layout 转换。

每一级在作为 Blit 源之前,需要转换为:

1VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL

生成完成后,再转换为:

1VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL

Mipmap 的主要作用是:

  • 减少远处纹理闪烁
  • 降低纹理缓存压力
  • 提高缩小采样质量
  • 提升性能


八、第七章整体关系#

第七章的内容并不是相互独立的。

整体数据流程为:

 1Ch7-1
 2CPU 顶点数据
 3→ Vertex Buffer
 4→ Vertex Shader
 5
 6Ch7-2
 7Index Buffer
 8→ 复用顶点
 9→ Indexed Draw
10
11Ch7-3
12Instance Data
13→ 一次绘制多个实例
14
15Ch7-4
16Push Constant
17→ 传递少量高频数据
18
19Ch7-5
20Uniform Buffer
21→ Descriptor Set
22→ 传递矩阵和全局数据
23
24Ch7-6
25Staging Buffer
26→ VkImage
27→ 图像复制和布局转换
28
29Ch7-7
30Texture Image
31→ Image View
32→ Sampler
33→ Descriptor Set
34→ Fragment Shader


九、各类数据的选择#

1. 顶点缓冲区#

适合:

  • 顶点位置
  • 法线
  • UV
  • 切线
  • 顶点颜色

特点:

1每个顶点读取一次


2. 索引缓冲区#

适合:

  • 复用顶点
  • 减少重复几何数据

特点:

1保存顶点编号


3. 实例缓冲区#

适合:

  • 大量相同网格
  • 不同实例位置
  • 不同实例颜色
  • 不同实例模型矩阵

特点:

1每个实例读取一次


4. Push Constant#

适合:

  • 少量数据
  • 高频修改
  • 每个 Draw Call 都不同

例如:

  • 对象编号
  • 小型变换参数
  • 颜色
  • 材质索引


5. Uniform Buffer#

适合:

  • 矩阵
  • 相机数据
  • 灯光参数
  • 全局参数
  • 较大只读数据


6. Texture Image#

适合:

  • 颜色贴图
  • 法线贴图
  • 深度贴图
  • 渲染结果
  • 各种二维图像数据


十、资源生命周期#

Vulkan 中资源的销毁顺序非常重要。

Buffer 的销毁顺序:

1vkDestroyBuffer
2→ vkFreeMemory

Image 的销毁顺序:

1vkDestroyImageView
2→ vkDestroyImage
3→ vkFreeMemory

Sampler:

1vkDestroySampler

Descriptor:

1vkDestroyDescriptorPool
2vkDestroyDescriptorSetLayout

Pipeline:

1vkDestroyPipeline
2→ vkDestroyPipelineLayout

需要保证:

1使用某资源的对象先销毁
2被依赖的资源后销毁

同时,在 GPU 仍可能使用资源时,不能直接销毁。

通常需要等待:

  • 对应 Fence
  • Queue Idle
  • Device Idle

但不应该在每一帧都使用:

1vkDeviceWaitIdle(...)

来掩盖同步问题。


十一、总结#

第七章主要完成了 Vulkan 中最基础的数据输入系统。

从第二章中写死在 Shader 内部的三角形开始,逐步加入了:

1顶点缓冲区
2索引缓冲区
3实例化绘制
4Push Constant
5Uniform Buffer
6Descriptor Set
7图像复制
8纹理采样
9Mipmap

最终形成了一条完整的数据传递链:

1CPU 数据
2→ Vulkan Buffer 或 Image
3→ Device Memory
4→ Descriptor 或 Vertex Input
5→ Graphics Pipeline
6→ Shader
7→ 屏幕

可以将第七章概括为:

第七章解决了几何数据、实例数据、常量数据和图像数据如何从 CPU 传递到 GPU,并最终被 Vulkan Graphics Pipeline 和 Shader 使用的问题。

原文:EasyVulkan 第七章学习笔记