第六章的核心目标,是学习 Vulkan 1.1、Vulkan 1.2 和 Vulkan 1.3 中加入的新特性,以及如何在程序中查询、启用和使用这些特性。

这一章主要包含以下内容:

  • 如何查询 Vulkan Loader 支持的最高版本
  • 如何查询物理设备支持的特性
  • 如何使用 pNext 扩展结构体
  • 如何启用新版本 Vulkan 功能
  • 如何使用无图像帧缓冲
  • 如何使用动态渲染
  • 如何在不依赖传统 Render Pass 和 Framebuffer 的情况下完成绘制

第六章与第七章的侧重点不同,

第七章主要解决:数据如何进入 GPU

第六章主要解决:如何使用现代 Vulkan 功能组织渲染流程

这一章最重要的内容是:

1Ch6-0 新版本特性查询
2Ch6-2 动态渲染


一、Ch6-0 使用新版本特性#

Vulkan 在后续版本中不断增加新的功能。

例如:

  • Vulkan 1.1
  • Vulkan 1.2
  • Vulkan 1.3
  • 各种 KHR 扩展
  • 各种 EXT 扩展
  • 厂商扩展

这些新功能通常不能直接使用。

在使用之前需要完成:

1确认 Loader 支持
2→ 确认物理设备支持
3→ 查询对应 Feature
4→ 创建逻辑设备时启用
5→ 获取对应函数
6→ 正式使用


1. Vulkan Loader 版本#

Vulkan 程序通常不会直接与显卡驱动中的 Vulkan 实现交互。

程序首先通过 Vulkan Loader 加载 Vulkan API。

Loader 支持的 Vulkan 版本可以通过:

1vkEnumerateInstanceVersion(...)

查询。

例如:

1uint32_t apiVersion = VK_API_VERSION_1_0;
2
3if (vkEnumerateInstanceVersion != nullptr) {
4    vkEnumerateInstanceVersion(&apiVersion);
5}

版本号可以通过以下宏拆分:

1VK_API_VERSION_MAJOR(apiVersion)
2VK_API_VERSION_MINOR(apiVersion)
3VK_API_VERSION_PATCH(apiVersion)

需要注意:

1Loader 支持 Vulkan 1.3
2不代表当前 GPU 一定支持 Vulkan 1.3

Loader 版本表示当前运行环境能够识别和加载的 Vulkan API 版本。

物理设备版本表示具体 GPU 和驱动实际支持的 Vulkan 版本。


2. VkApplicationInfo 中的 API 版本#

创建 Vulkan Instance 时,需要填写:

1VkApplicationInfo

其中:

1apiVersion

表示程序希望使用的 Vulkan API 版本。

例如:

1VkApplicationInfo applicationInfo{};
2applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
3applicationInfo.apiVersion = VK_API_VERSION_1_3;

但是不能直接假设运行环境一定支持 Vulkan 1.3。

更合理的做法是:

1查询 Loader 最高版本
2→ 根据程序需求选择版本
3→ 不能超过 Loader 支持版本

例如:

1uint32_t requestedVersion = VK_API_VERSION_1_3;
2uint32_t finalVersion = std::min(loaderVersion, requestedVersion);


3. 物理设备 API 版本#

物理设备属性中包含:

1VkPhysicalDeviceProperties::apiVersion

可以通过:

1vkGetPhysicalDeviceProperties(...)

查询。

也可以使用新版接口:

1vkGetPhysicalDeviceProperties2(...)

设备支持版本与 Loader 支持版本必须同时考虑。

可以简单理解为:

1Loader Version
2= 当前系统最多能够加载哪个 Vulkan 版本
3
4Physical Device Version
5= 当前 GPU 和驱动实际实现了哪个 Vulkan 版本
6
7Application Version
8= 程序希望使用哪个 Vulkan 版本

最终可用版本受到三者共同限制。


二、Feature、Property 与 Extension#

Vulkan 中经常会遇到三类设备能力信息:

1Feature
2Property
3Extension


1. Feature#

Feature 表示某项功能是否受到支持。

例如:

1samplerAnisotropy
2geometryShader
3tessellationShader
4dynamicRendering
5imagelessFramebuffer

Feature 通常是布尔值:

1VK_TRUE
2VK_FALSE

例如:

1samplerAnisotropy = VK_TRUE

表示设备支持各向异性过滤。

但是:

1支持某项 Feature
2不等于已经启用某项 Feature

创建逻辑设备时,只有显式开启的 Feature 才能使用。


2. Property#

Property 表示设备的属性或限制。

例如:

1maxImageDimension2D
2maxPushConstantsSize
3minUniformBufferOffsetAlignment
4maxSamplerAnisotropy

这些通常不是开关,而是具体数值。

例如:

1maxPushConstantsSize = 256

表示设备允许的 Push Constant 最大容量为 256 字节。

Property 只能查询,不能开启。


3. Extension#

Extension 表示 Vulkan 核心版本之外提供的额外功能。

例如:

1VK_KHR_swapchain
2VK_KHR_dynamic_rendering
3VK_KHR_create_renderpass2

扩展通常需要:

  1. 查询是否支持
  2. 在创建 Instance 或 Device 时添加扩展名称
  3. 查询对应 Feature
  4. 开启对应 Feature
  5. 使用扩展函数

部分扩展会在后续 Vulkan 版本中提升为核心功能。

例如:

1VK_KHR_dynamic_rendering

后来成为 Vulkan 1.3 核心功能。


三、VkPhysicalDeviceFeatures2#

旧版 Vulkan 使用:

1VkPhysicalDeviceFeatures

查询和开启设备功能。

随着功能越来越多,单个结构体无法容纳所有新特性。

因此 Vulkan 引入:

1VkPhysicalDeviceFeatures2

其核心作用是:

1通过 pNext 链连接其他 Feature 结构体

例如:

1VkPhysicalDeviceFeatures2 features2{};
2features2.sType =
3    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;

然后通过 pNext 连接 Vulkan 1.1、1.2 和 1.3 Feature:

1VkPhysicalDeviceVulkan11Features features11{};
2VkPhysicalDeviceVulkan12Features features12{};
3VkPhysicalDeviceVulkan13Features features13{};

连接关系可以写成:

1VkPhysicalDeviceFeatures2
2→ VkPhysicalDeviceVulkan11Features
3→ VkPhysicalDeviceVulkan12Features
4→ VkPhysicalDeviceVulkan13Features

例如:

1features2.pNext = &features11;
2features11.pNext = &features12;
3features12.pNext = &features13;
4features13.pNext = nullptr;

最后调用:

1vkGetPhysicalDeviceFeatures2(
2    physicalDevice,
3    &features2
4);

设备会将支持情况写入每个结构体。


四、pNext 链#

pNext 是 Vulkan 中极其重要的扩展机制。

大量 Vulkan 结构体都包含:

1const void* pNext;

或者:

1void* pNext;

pNext 用于在不修改原有结构体定义的情况下,为 API 添加新功能。

可以理解为:

1基础结构体
2→ 扩展结构体 A
3→ 扩展结构体 B
4→ 扩展结构体 C

每个结构体通常包含:

1sType
2pNext

其中:

  • sType 表示当前结构体的真实类型
  • pNext 指向链中的下一个结构体


1. pNext 链示例#

例如查询动态渲染支持:

1VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures{};
2dynamicRenderingFeatures.sType =
3    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES;

然后连接到:

1VkPhysicalDeviceFeatures2 features2{};
2features2.sType =
3    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
4features2.pNext = &dynamicRenderingFeatures;

最后:

1vkGetPhysicalDeviceFeatures2(
2    physicalDevice,
3    &features2
4);

查询完成后:

1dynamicRenderingFeatures.dynamicRendering

会表示设备是否支持动态渲染。


2. 为什么需要 pNext#

如果没有 pNext,每增加一个 Vulkan 新功能,都可能需要修改原有结构体。

这样会破坏:

  • API 兼容性
  • 二进制兼容性
  • 旧代码
  • 旧驱动

通过 pNext,Vulkan 可以保持旧结构体不变,同时不断加入新功能。

因此:

1pNext 是 Vulkan 的结构体扩展机制

以后学习以下功能时都会频繁使用:

  • Dynamic Rendering
  • Synchronization2
  • Descriptor Indexing
  • Buffer Device Address
  • Ray Tracing
  • Mesh Shader
  • Timeline Semaphore


3. pNext 链注意事项#

构建 pNext 链时需要注意:

  1. 每个结构体必须填写正确的 sType
  2. 最后一个结构体的 pNext 必须为 nullptr
  3. 结构体生命周期必须覆盖 API 调用过程
  4. 不要将同一个结构体重复加入链
  5. 不要错误地形成循环链表
  6. 不要使用已经离开作用域的局部变量
  7. 查询结构和启用结构不能随意混用
  8. 同一类结构通常不应重复出现

例如,以下结构体不能在调用前被销毁:

1VkPhysicalDeviceFeatures2 features2;
2VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures;

因为 features2.pNext 指向后者。


五、查询与启用的区别#

Vulkan 中必须区分:

1查询功能支持
2创建设备时启用功能

查询过程通常使用:

1vkGetPhysicalDeviceFeatures2(...)

创建逻辑设备时则将需要开启的结构体连接到:

1VkDeviceCreateInfo::pNext

例如:

1VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures{};
2dynamicRenderingFeatures.sType =
3    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES;
4dynamicRenderingFeatures.dynamicRendering = VK_TRUE;

然后:

1VkDeviceCreateInfo deviceCreateInfo{};
2deviceCreateInfo.sType =
3    VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
4deviceCreateInfo.pNext =
5    &dynamicRenderingFeatures;

需要遵守:

1先查询
2→ 确认支持
3→ 再设置为 VK_TRUE
4→ 创建逻辑设备

不能在设备不支持时强行开启。


1. 支持但未启用#

例如设备返回:

1dynamicRendering = VK_TRUE;

只表示设备支持动态渲染。

如果创建逻辑设备时没有将:

1dynamicRendering = VK_TRUE;

加入启用链,那么逻辑设备仍然不能合法使用该功能。

因此:

1Supported Feature
2≠ Enabled Feature


六、VkPhysicalDeviceProperties2#

新版属性查询使用:

1VkPhysicalDeviceProperties2

与 VkPhysicalDeviceFeatures2 类似,它也可以通过 pNext 连接更多属性结构体。

例如:

1VkPhysicalDeviceProperties2 properties2{};
2properties2.sType =
3    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;

然后调用:

1vkGetPhysicalDeviceProperties2(
2    physicalDevice,
3    &properties2
4);

基础属性位于:

1properties2.properties

其中包括:

  • GPU 名称
  • GPU 类型
  • API 版本
  • 驱动版本
  • Vendor ID
  • Device ID
  • 设备限制

例如:

1properties2.properties.deviceName
2properties2.properties.apiVersion
3properties2.properties.deviceType
4properties2.properties.limits


1. Features2 与 Properties2 的区别#

1Features2
2用于查询功能是否受到支持
3
4Properties2
5用于查询设备属性和具体限制

例如:

1samplerAnisotropy
2属于 Feature
3
4maxSamplerAnisotropy
5属于 Property

使用各向异性过滤时,需要同时检查:

1Feature:是否支持各向异性过滤
2Property:最大允许使用多大的各向异性倍率


七、VkPhysicalDeviceMemoryProperties2#

新版内存属性查询使用:

1VkPhysicalDeviceMemoryProperties2

例如:

1VkPhysicalDeviceMemoryProperties2 memoryProperties2{};
2memoryProperties2.sType =
3    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2;

然后调用:

1vkGetPhysicalDeviceMemoryProperties2(
2    physicalDevice,
3    &memoryProperties2
4);

实际内存属性位于:

1memoryProperties2.memoryProperties

其中包含:

  • Memory Type
  • Memory Heap
  • Memory Property Flags
  • Heap Size

它与旧版:

1vkGetPhysicalDeviceMemoryProperties(...)

的核心功能相同,但可以通过 pNext 查询更多扩展内存信息。


八、Ch6-1 无图像帧缓冲#

传统 Vulkan Framebuffer 在创建时,必须直接指定具体的 Image View。

例如:

1Framebuffer 0
2→ Swapchain Image View 0
3
4Framebuffer 1
5→ Swapchain Image View 1
6
7Framebuffer 2
8→ Swapchain Image View 2

交换链有几张图像,通常就需要创建几个 Framebuffer。


1. 传统 Framebuffer#

创建普通 Framebuffer 时,需要提供:

1VkFramebufferCreateInfo

其中:

1pAttachments

直接指向具体的 Image View。

例如:

1framebufferCreateInfo.attachmentCount = 1;
2framebufferCreateInfo.pAttachments =
3    &swapchainImageView;

因此 Framebuffer 与具体 Image View 绑定。


2. Imageless Framebuffer#

无图像帧缓冲的英文名称为:

1Imageless Framebuffer

它并不是完全没有图像。

它只是:

1创建 Framebuffer 时
2不立即绑定具体 Image View

创建时只描述附件必须满足的条件:

  • 格式
  • Usage
  • 宽度
  • 高度
  • 图层数
  • View Format

真正开始 Render Pass 时,再提供当前实际使用的 Image View。


3. 启用 Imageless Framebuffer#

首先需要查询并开启:

1imagelessFramebuffer

对应结构体通常为:

1VkPhysicalDeviceImagelessFramebufferFeatures

例如:

1VkPhysicalDeviceImagelessFramebufferFeatures imagelessFeatures{};
2imagelessFeatures.sType =
3    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES;

查询支持后,在创建逻辑设备时设置:

1imagelessFeatures.imagelessFramebuffer = VK_TRUE;


4. 创建无图像 Framebuffer#

创建 Framebuffer 时,需要设置:

1VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT

例如:

1VkFramebufferCreateInfo framebufferCreateInfo{};
2framebufferCreateInfo.sType =
3    VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
4framebufferCreateInfo.flags =
5    VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT;

然后通过:

1VkFramebufferAttachmentsCreateInfo

描述所有附件。

每个附件使用:

1VkFramebufferAttachmentImageInfo

描述。

其中包含:

  • Image Usage
  • Width
  • Height
  • Layer Count
  • View Format

例如:

1这个附件必须是颜色附件
2格式必须与交换链格式一致
3尺寸必须与交换链尺寸一致
4图层数为 1


5. 开始 Render Pass 时提供 Image View#

开始 Render Pass 时,通过:

1VkRenderPassAttachmentBeginInfo

提供实际使用的 Image View。

然后将其连接到:

1VkRenderPassBeginInfo::pNext

整体流程为:

1创建 Framebuffer
2→ 只描述附件要求
3→ 不绑定具体 Image View
4
5开始 Render Pass
6→ 提供当前交换链 Image View
7→ 使用同一个 Framebuffer

因此多个交换链图像理论上可以共用同一个 Imageless Framebuffer。


6. Imageless Framebuffer 优点#

优点:

  • 减少 Framebuffer 对象数量
  • Framebuffer 不再固定绑定某个 Image View
  • 可以延迟决定实际附件
  • 某些场景下资源组织更灵活


7. Imageless Framebuffer 缺点#

缺点:

  • 配置更加复杂
  • 仍然需要 Render Pass
  • 仍然需要 Framebuffer 对象
  • 对现代渲染架构帮助有限
  • 很多情况下节省的对象数量并不重要
  • 后续 Dynamic Rendering 使用更直接

可以简单理解为:

1Imageless Framebuffer
2只是让 Framebuffer 不再固定绑定 Image View
3
4Dynamic Rendering
5则直接取消了 Framebuffer 和 Render Pass 对象

因此 Ch6-1 更适合作为:

1学习新特性启用和 pNext 链的练习

而不是后续渲染架构的重点。


九、Ch6-2 动态渲染#

动态渲染是第六章最重要的内容

传统 Vulkan 绘制前需要创建:

1VkRenderPass
2VkFramebuffer

录制命令时使用:

1vkCmdBeginRenderPass(...)
2vkCmdEndRenderPass(...)

动态渲染改为:

1vkCmdBeginRendering(...)
2vkCmdEndRendering(...)

不再需要传统的 Render Pass 和 Framebuffer 对象。


十、传统 Render Pass 渲染流程#

传统渲染流程通常为:

1创建 Render Pass
2→ 创建 Framebuffer
3→ 创建 Graphics Pipeline
4→ Begin Render Pass
5→ Bind Pipeline
6→ Draw
7→ End Render Pass

Render Pass 中提前定义:

  • 颜色附件
  • 深度附件
  • 模板附件
  • Load Operation
  • Store Operation
  • 初始布局
  • 最终布局
  • Subpass
  • Subpass Dependency

Framebuffer 则负责绑定具体的 Image View。


十一、动态渲染流程#

动态渲染流程为:

1创建 Graphics Pipeline
2→ 指定附件格式
3→ 准备 Rendering Attachment
4→ Begin Rendering
5→ Bind Pipeline
6→ Draw
7→ End Rendering

主要使用三个结构体:

1VkPipelineRenderingCreateInfo
2VkRenderingAttachmentInfo
3VkRenderingInfo


十二、VkPipelineRenderingCreateInfo#

使用动态渲染时,创建 Graphics Pipeline 不再提供传统 Render Pass。

传统方式:

1graphicsPipelineCreateInfo.renderPass = renderPass;

动态渲染中:

1graphicsPipelineCreateInfo.renderPass = VK_NULL_HANDLE;

但是 Graphics Pipeline 仍然需要知道附件格式。

因此使用:

1VkPipelineRenderingCreateInfo

例如:

1VkPipelineRenderingCreateInfo pipelineRenderingInfo{};
2pipelineRenderingInfo.sType =
3    VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
4pipelineRenderingInfo.colorAttachmentCount = 1;
5pipelineRenderingInfo.pColorAttachmentFormats =
6    &swapchainFormat;

然后连接到:

1VkGraphicsPipelineCreateInfo::pNext

整体关系为:

1VkGraphicsPipelineCreateInfo
2→ pNext
3→ VkPipelineRenderingCreateInfo

如果存在深度附件,还需要填写:

1depthAttachmentFormat

如果存在模板附件,还需要填写:

1stencilAttachmentFormat


十三、VkRenderingAttachmentInfo#

每一个实际渲染附件通过:

1VkRenderingAttachmentInfo

描述。

颜色附件通常包括:

  • Image View
  • Image Layout
  • Load Operation
  • Store Operation
  • Clear Value
  • Resolve 相关信息

例如:

 1VkRenderingAttachmentInfo colorAttachment{};
 2colorAttachment.sType =
 3    VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
 4colorAttachment.imageView =
 5    swapchainImageViews[imageIndex];
 6colorAttachment.imageLayout =
 7    VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 8colorAttachment.loadOp =
 9    VK_ATTACHMENT_LOAD_OP_CLEAR;
10colorAttachment.storeOp =
11    VK_ATTACHMENT_STORE_OP_STORE;


1. Load Operation#

Load Operation 决定渲染开始时如何处理附件原有内容。

常见值:

1VK_ATTACHMENT_LOAD_OP_LOAD
2VK_ATTACHMENT_LOAD_OP_CLEAR
3VK_ATTACHMENT_LOAD_OP_DONT_CARE

含义:

1LOAD
2保留并读取原有内容
3
4CLEAR
5使用 Clear Value 清空
6
7DONT_CARE
8不关心之前的内容


2. Store Operation#

Store Operation 决定渲染结束后是否保存结果。

常见值:

1VK_ATTACHMENT_STORE_OP_STORE
2VK_ATTACHMENT_STORE_OP_DONT_CARE

如果最终图像需要显示或继续使用,通常需要:

1VK_ATTACHMENT_STORE_OP_STORE


十四、VkRenderingInfo#

整个动态渲染过程通过:

1VkRenderingInfo

描述。

主要包含:

  • Render Area
  • Layer Count
  • View Mask
  • Color Attachment
  • Depth Attachment
  • Stencil Attachment

例如:

1VkRenderingInfo renderingInfo{};
2renderingInfo.sType =
3    VK_STRUCTURE_TYPE_RENDERING_INFO;
4renderingInfo.renderArea.offset = {0, 0};
5renderingInfo.renderArea.extent = swapchainExtent;
6renderingInfo.layerCount = 1;
7renderingInfo.colorAttachmentCount = 1;
8renderingInfo.pColorAttachments =
9    &colorAttachment;

开始渲染:

1vkCmdBeginRendering(
2    commandBuffer,
3    &renderingInfo
4);

结束渲染:

1vkCmdEndRendering(commandBuffer);


十五、启用动态渲染#

动态渲染在 Vulkan 1.3 中成为核心功能。

如果使用 Vulkan 1.3,可以通过:

1VkPhysicalDeviceVulkan13Features

启用。

例如:

1VkPhysicalDeviceVulkan13Features features13{};
2features13.sType =
3    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
4features13.dynamicRendering = VK_TRUE;

较低版本 Vulkan 中,可以通过:

1VK_KHR_dynamic_rendering

扩展使用。

此时需要:

  • 检查扩展是否支持
  • 启用扩展
  • 查询动态渲染 Feature
  • 开启动态渲染
  • 获取对应函数入口


十六、核心函数与扩展函数#

在 Vulkan 1.3 中,核心函数名称为:

1vkCmdBeginRendering
2vkCmdEndRendering

扩展版本可能为:

1vkCmdBeginRenderingKHR
2vkCmdEndRenderingKHR

如果程序同时支持核心和扩展路径,需要明确选择。

不能在未确认函数可用时直接调用。


十七、动态渲染中的图像布局#

传统 Render Pass 可以通过:

1initialLayout
2finalLayout
3Subpass Dependency

隐式完成部分图像布局转换和同步。

动态渲染没有传统 Render Pass,因此这些工作需要程序自己完成。

渲染开始前,交换链图像通常需要转换为:

1VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL

渲染结束后,需要转换为:

1VK_IMAGE_LAYOUT_PRESENT_SRC_KHR

整体流程为:

1PRESENT_SRC_KHR
2→ COLOR_ATTACHMENT_OPTIMAL
3→ 动态渲染
4→ PRESENT_SRC_KHR


十八、动态渲染中的 Image Barrier#

布局转换通常需要使用:

1VkImageMemoryBarrier

或者 Synchronization2 中的:

1VkImageMemoryBarrier2

传统 Barrier 调用:

1vkCmdPipelineBarrier(...)

Synchronization2 调用:

1vkCmdPipelineBarrier2(...)

Barrier 需要明确:

  • 旧布局
  • 新布局
  • 源管线阶段
  • 目标管线阶段
  • 源访问权限
  • 目标访问权限
  • Image Subresource Range


1. 呈现到颜色附件#

渲染开始前:

1旧布局:
2VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
3
4新布局:
5VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL

目标访问通常需要包含:

1VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT

目标阶段通常需要包含:

1VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT


2. 颜色附件到呈现#

渲染完成后:

1旧布局:
2VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
3
4新布局:
5VK_IMAGE_LAYOUT_PRESENT_SRC_KHR

需要确保颜色附件写入完成后,图像才能交给呈现系统。


十九、Dynamic Viewport 和 Scissor#

交换链尺寸发生变化后,如果 Viewport 和 Scissor 被固定写入 Pipeline,可能需要重新创建 Graphics Pipeline。

更灵活的方式是使用动态状态:

1VK_DYNAMIC_STATE_VIEWPORT
2VK_DYNAMIC_STATE_SCISSOR

录制命令时调用:

1vkCmdSetViewport(...)
2vkCmdSetScissor(...)

这样窗口尺寸变化后,只需要更新命令中的 Viewport 和 Scissor。

不过附件格式发生变化时,仍可能需要重新创建 Pipeline。

因为:

1VkPipelineRenderingCreateInfo

中记录了附件格式。


二十、动态渲染与深度附件#

如果动态渲染使用深度缓冲,需要额外创建:

  • Depth Image
  • Depth Memory
  • Depth Image View

深度附件使用另一个:

1VkRenderingAttachmentInfo

例如:

 1VkRenderingAttachmentInfo depthAttachment{};
 2depthAttachment.sType =
 3    VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
 4depthAttachment.imageView = depthImageView;
 5depthAttachment.imageLayout =
 6    VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
 7depthAttachment.loadOp =
 8    VK_ATTACHMENT_LOAD_OP_CLEAR;
 9depthAttachment.storeOp =
10    VK_ATTACHMENT_STORE_OP_DONT_CARE;

然后:

1renderingInfo.pDepthAttachment =
2    &depthAttachment;

Pipeline 创建时也必须填写正确的:

1depthAttachmentFormat


二十一、Render Pass 与 Dynamic Rendering 的区别#

1. 传统 Render Pass#

需要:

1VkRenderPass
2VkFramebuffer
3VkRenderPassBeginInfo
4vkCmdBeginRenderPass
5vkCmdEndRenderPass

优点:

  • 能够提前描述完整渲染流程
  • 适合复杂 Subpass
  • 某些移动 GPU 可利用 Subpass 优化
  • 教程和参考代码丰富

缺点:

  • 对象较多
  • 配置复杂
  • 与附件格式和 Framebuffer 绑定较紧
  • 交换链重建时涉及资源较多


2. Imageless Framebuffer#

需要:

1VkRenderPass
2VkFramebuffer
3VkRenderPassAttachmentBeginInfo
4vkCmdBeginRenderPass
5vkCmdEndRenderPass

区别是:

1Framebuffer 创建时不固定 Image View

优点:

  • 一个 Framebuffer 可以适配多个 Image View
  • 附件绑定更灵活

缺点:

  • 仍然需要 Render Pass
  • 仍然需要 Framebuffer
  • 配置并没有明显简化
  • 实际收益有限


3. Dynamic Rendering#

需要:

1VkPipelineRenderingCreateInfo
2VkRenderingAttachmentInfo
3VkRenderingInfo
4vkCmdBeginRendering
5vkCmdEndRendering

不需要:

1VkRenderPass
2VkFramebuffer

优点:

  • 对象更少
  • 渲染流程更直接
  • 附件可以在录制命令时指定
  • 适合现代渲染架构
  • 交换链重建更加简单
  • 更适合模块化渲染 Pass

缺点:

  • 需要自己处理布局转换
  • 需要更明确地处理同步
  • 复杂 Subpass 场景仍需考虑传统 Render Pass
  • 必须确认设备版本和功能支持


二十二、三种方式的整体对比#

1传统 Render Pass:
2
3提前创建 Render Pass
4提前创建 Framebuffer
5Render Pass 隐式处理部分布局和依赖
6适合传统固定渲染流程

1Imageless Framebuffer:
2
3保留 Render Pass
4保留 Framebuffer
5Framebuffer 不固定具体 Image View
6实际开始渲染时再提供附件

1Dynamic Rendering:
2
3不创建 Render Pass
4不创建 Framebuffer
5开始渲染时直接指定附件
6布局转换和同步由程序显式处理

可以概括为:

1Legacy Render Pass
2= 固定渲染描述 + 固定附件
3
4Imageless Framebuffer
5= 固定渲染描述 + 延迟指定附件
6
7Dynamic Rendering
8= 渲染时动态指定附件和操作


二十三、交换链重建差异#

1. 传统 Render Pass#

交换链重建时通常需要考虑:

  • Swapchain
  • Swapchain Image View
  • Framebuffer
  • Render Pass
  • Graphics Pipeline
  • Depth Buffer
  • Command Buffer

如果交换链格式没有变化,Render Pass 和 Pipeline 有时可以保留。


2. Imageless Framebuffer#

仍然需要考虑:

  • Swapchain
  • Swapchain Image View
  • Imageless Framebuffer 的尺寸和附件条件
  • Graphics Pipeline
  • Command Buffer

即使 Framebuffer 不绑定具体 Image View,它仍然记录:

  • Width
  • Height
  • Layer Count
  • Attachment Requirements

因此尺寸变化时通常仍需要重新创建。


3. Dynamic Rendering#

不需要重建:

  • Render Pass
  • Framebuffer

但仍需要处理:

  • Swapchain
  • Swapchain Image View
  • Viewport
  • Scissor
  • Depth Image
  • 屏幕尺寸相关附件
  • 附件格式变化时的 Pipeline

因此动态渲染只是减少了一部分对象,并不代表交换链重建完全消失。


二十四、第六章常见问题#

1. 只查询 Feature,但没有启用#

错误理解:

1设备支持 Dynamic Rendering
2所以可以直接使用

正确流程:

1查询支持
2→ 创建逻辑设备时启用
3→ 再使用


2. pNext 指向无效内存#

例如:

1void Setup() {
2    VkPhysicalDeviceDynamicRenderingFeatures features{};
3    deviceCreateInfo.pNext = &features;
4}

函数结束后,features 被销毁。

之后再使用:

1deviceCreateInfo

就会产生悬空指针。


3. sType 填写错误#

每个扩展结构体必须填写对应的:

1sType

错误的 sType 会导致驱动无法正确识别结构体。


4. 动态渲染 Pipeline 没有指定附件格式#

Dynamic Rendering 创建 Pipeline 时没有 Render Pass。

因此必须通过:

1VkPipelineRenderingCreateInfo

提供附件格式。

否则 Pipeline 与实际附件不兼容。


5. 没有进行交换链图像布局转换#

动态渲染不会自动将交换链图像从呈现布局转换为颜色附件布局。

必须显式完成:

1PRESENT_SRC_KHR
2→ COLOR_ATTACHMENT_OPTIMAL
3→ PRESENT_SRC_KHR


6. Image Layout 与实际用法不一致#

例如:

1RenderingAttachmentInfo 中声明:
2COLOR_ATTACHMENT_OPTIMAL
3
4但图像实际仍然是:
5PRESENT_SRC_KHR

这会触发 Validation Layer 错误。


7. 使用 vkDeviceWaitIdle 掩盖问题#

在每帧调用:

1vkDeviceWaitIdle(...)

虽然可能暂时避免资源竞争,但会严重降低并行能力。

正确做法是使用:

  • Fence
  • Semaphore
  • Pipeline Barrier
  • 正确的帧资源管理


二十五、第六章整体关系#

第六章各节的关系为:

 1Ch6-0
 2查询和启用新版本特性
 3→ 学习 Feature、Property、Extension 和 pNext
 4
 5Ch6-1
 6使用 Imageless Framebuffer
 7→ 练习新特性启用和扩展结构体
 8
 9Ch6-2
10使用 Dynamic Rendering
11→ 移除传统 Render Pass 和 Framebuffer
12→ 显式处理附件、布局和同步

Ch6-0 是基础。

Ch6-1 和 Ch6-2 都依赖:

  • 版本查询
  • Feature 查询
  • pNext 链
  • 逻辑设备 Feature 启用


二十六、第六章总结#

第六章主要学习了 Vulkan 新版本功能的查询、启用和使用方法。

首先通过 Ch6-0 学习:

1Vulkan 版本查询
2Feature 查询
3Property 查询
4Memory Property 查询
5pNext 链
6逻辑设备 Feature 启用

然后通过 Ch6-1 学习:

1Imageless Framebuffer
2延迟指定 Image View
3Framebuffer 扩展结构体
4Render Pass Begin 扩展结构体

最后通过 Ch6-2 学习:

1Dynamic Rendering
2不再创建 Render Pass
3不再创建 Framebuffer
4渲染时动态指定附件
5显式处理布局和同步

第六章最核心的数据关系可以概括为:

1查询设备能力
2→ 开启设备功能
3→ 创建兼容 Pipeline
4→ 准备渲染附件
5→ 转换图像布局
6→ Begin Rendering
7→ Draw
8→ End Rendering
9→ 转换为呈现布局

可以将第六章概括为:

第六章解决了如何查询和开启现代 Vulkan 功能,并使用 Dynamic Rendering 替代传统 Render Pass 与 Framebuffer 完成绘制的问题。

原文:EasyVulkan 第六章学习笔记