第六章的核心目标,是学习 Vulkan 1.1、Vulkan 1.2 和 Vulkan 1.3 中加入的新特性,以及如何在程序中查询、启用和使用这些特性。
- 如何查询 Vulkan Loader 支持的最高版本
- 如何查询物理设备支持的特性
- 如何使用
pNext扩展结构体 - 如何启用新版本 Vulkan 功能
- 如何使用无图像帧缓冲
- 如何使用动态渲染
- 如何在不依赖传统 Render Pass 和 Framebuffer 的情况下完成绘制
第六章主要解决:如何使用现代 Vulkan 功能组织渲染流程
1Ch6-0 新版本特性查询
2Ch6-2 动态渲染
一、Ch6-0 使用新版本特性#
1确认 Loader 支持
2→ 确认物理设备支持
3→ 查询对应 Feature
4→ 创建逻辑设备时启用
5→ 获取对应函数
6→ 正式使用
1. Vulkan Loader 版本#
Vulkan 程序通常不会直接与显卡驱动中的 Vulkan 实现交互。
程序首先通过 Vulkan Loader 加载 Vulkan API。
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 版本#
1VkApplicationInfo
1apiVersion
1VkApplicationInfo applicationInfo{};
2applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
3applicationInfo.apiVersion = VK_API_VERSION_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(...)
1Loader Version
2= 当前系统最多能够加载哪个 Vulkan 版本
3
4Physical Device Version
5= 当前 GPU 和驱动实际实现了哪个 Vulkan 版本
6
7Application Version
8= 程序希望使用哪个 Vulkan 版本
二、Feature、Property 与 Extension#
1Feature
2Property
3Extension
1. Feature#
1samplerAnisotropy
2geometryShader
3tessellationShader
4dynamicRendering
5imagelessFramebuffer
1VK_TRUE
2VK_FALSE
1samplerAnisotropy = VK_TRUE
1支持某项 Feature
2不等于已经启用某项 Feature
2. Property#
1maxImageDimension2D
2maxPushConstantsSize
3minUniformBufferOffsetAlignment
4maxSamplerAnisotropy
1maxPushConstantsSize = 256
表示设备允许的 Push Constant 最大容量为 256 字节。
3. Extension#
Extension 表示 Vulkan 核心版本之外提供的额外功能。
1VK_KHR_swapchain
2VK_KHR_dynamic_rendering
3VK_KHR_create_renderpass2
1VK_KHR_dynamic_rendering
三、VkPhysicalDeviceFeatures2#
1VkPhysicalDeviceFeatures
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 链#
1const void* pNext;
1void* pNext;
pNext 用于在不修改原有结构体定义的情况下,为 API 添加新功能。
1基础结构体
2→ 扩展结构体 A
3→ 扩展结构体 B
4→ 扩展结构体 C
1sType
2pNext
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 新功能,都可能需要修改原有结构体。
通过 pNext,Vulkan 可以保持旧结构体不变,同时不断加入新功能。
1pNext 是 Vulkan 的结构体扩展机制
- Dynamic Rendering
- Synchronization2
- Descriptor Indexing
- Buffer Device Address
- Ray Tracing
- Mesh Shader
- Timeline Semaphore
3. pNext 链注意事项#
- 每个结构体必须填写正确的
sType - 最后一个结构体的
pNext必须为nullptr - 结构体生命周期必须覆盖 API 调用过程
- 不要将同一个结构体重复加入链
- 不要错误地形成循环链表
- 不要使用已经离开作用域的局部变量
- 查询结构和启用结构不能随意混用
- 同一类结构通常不应重复出现
1VkPhysicalDeviceFeatures2 features2;
2VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures;
五、查询与启用的区别#
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
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
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#
1VkFramebufferCreateInfo
1pAttachments
1framebufferCreateInfo.attachmentCount = 1;
2framebufferCreateInfo.pAttachments =
3 &swapchainImageView;
因此 Framebuffer 与具体 Image View 绑定。
2. Imageless Framebuffer#
1Imageless Framebuffer
1创建 Framebuffer 时
2不立即绑定具体 Image View
真正开始 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#
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
1这个附件必须是颜色附件
2格式必须与交换链格式一致
3尺寸必须与交换链尺寸一致
4图层数为 1
5. 开始 Render Pass 时提供 Image View#
1VkRenderPassAttachmentBeginInfo
1VkRenderPassBeginInfo::pNext
1创建 Framebuffer
2→ 只描述附件要求
3→ 不绑定具体 Image View
4
5开始 Render Pass
6→ 提供当前交换链 Image View
7→ 使用同一个 Framebuffer
因此多个交换链图像理论上可以共用同一个 Imageless Framebuffer。
6. Imageless Framebuffer 优点#
7. Imageless Framebuffer 缺点#
1Imageless Framebuffer
2只是让 Framebuffer 不再固定绑定 Image View
3
4Dynamic Rendering
5则直接取消了 Framebuffer 和 Render Pass 对象
1学习新特性启用和 pNext 链的练习
九、Ch6-2 动态渲染#
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
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
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
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);
十五、启用动态渲染#
1VkPhysicalDeviceVulkan13Features
1VkPhysicalDeviceVulkan13Features features13{};
2features13.sType =
3 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
4features13.dynamicRendering = VK_TRUE;
1VK_KHR_dynamic_rendering
十六、核心函数与扩展函数#
1vkCmdBeginRendering
2vkCmdEndRendering
1vkCmdBeginRenderingKHR
2vkCmdEndRenderingKHR
十七、动态渲染中的图像布局#
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
1VkImageMemoryBarrier2
1vkCmdPipelineBarrier(...)
1vkCmdPipelineBarrier2(...)
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
二十、动态渲染与深度附件#
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;
1depthAttachmentFormat
二十一、Render Pass 与 Dynamic Rendering 的区别#
1. 传统 Render Pass#
1VkRenderPass
2VkFramebuffer
3VkRenderPassBeginInfo
4vkCmdBeginRenderPass
5vkCmdEndRenderPass
2. Imageless Framebuffer#
1VkRenderPass
2VkFramebuffer
3VkRenderPassAttachmentBeginInfo
4vkCmdBeginRenderPass
5vkCmdEndRenderPass
1Framebuffer 创建时不固定 Image View
3. Dynamic Rendering#
1VkPipelineRenderingCreateInfo
2VkRenderingAttachmentInfo
3VkRenderingInfo
4vkCmdBeginRendering
5vkCmdEndRendering
1VkRenderPass
2VkFramebuffer
二十二、三种方式的整体对比#
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#
如果交换链格式没有变化,Render Pass 和 Pipeline 有时可以保留。
2. Imageless Framebuffer#
即使 Framebuffer 不绑定具体 Image View,它仍然记录:
3. Dynamic Rendering#
因此动态渲染只是减少了一部分对象,并不代表交换链重建完全消失。
二十四、第六章常见问题#
1. 只查询 Feature,但没有启用#
1设备支持 Dynamic Rendering
2所以可以直接使用
1查询支持
2→ 创建逻辑设备时启用
3→ 再使用
2. pNext 指向无效内存#
1void Setup() {
2 VkPhysicalDeviceDynamicRenderingFeatures features{};
3 deviceCreateInfo.pNext = &features;
4}
1deviceCreateInfo
3. sType 填写错误#
1sType
4. 动态渲染 Pipeline 没有指定附件格式#
Dynamic Rendering 创建 Pipeline 时没有 Render Pass。
1VkPipelineRenderingCreateInfo
5. 没有进行交换链图像布局转换#
1PRESENT_SRC_KHR
2→ COLOR_ATTACHMENT_OPTIMAL
3→ PRESENT_SRC_KHR
6. Image Layout 与实际用法不一致#
1RenderingAttachmentInfo 中声明:
2COLOR_ATTACHMENT_OPTIMAL
3
4但图像实际仍然是:
5PRESENT_SRC_KHR
7. 使用 vkDeviceWaitIdle 掩盖问题#
1vkDeviceWaitIdle(...)
二十五、第六章整体关系#
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→ 显式处理附件、布局和同步
二十六、第六章总结#
第六章主要学习了 Vulkan 新版本功能的查询、启用和使用方法。
1Vulkan 版本查询
2Feature 查询
3Property 查询
4Memory Property 查询
5pNext 链
6逻辑设备 Feature 启用
1Imageless Framebuffer
2延迟指定 Image View
3Framebuffer 扩展结构体
4Render Pass Begin 扩展结构体
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 完成绘制的问题。