vulkan学习笔记第二篇_入门案例解析
vulkan
相比dx和gl:
1. 跨平台
2. 接口更复杂,更灵活。
3. 暴露了更高级特性api
4. 用得好比gl性能更高。
Vulkan应用程序的基本步骤
- 初始化Vulkan库
- 创建Vulkan实例
- 选择物理设备(GPU)
- 创建逻辑设备
- 创建交换链
- 创建图像视图
- 创建渲染通道
- 创建帧缓冲
- 创建命令池和命令缓冲
- 绘制和呈现
vulkan拓展
vulkan很多功能都围绕拓展实现。
Vulkan扩展主要分为
-
实例扩展(Instance Extensions),用于图形API的初始化。
如:VK_KHR_surface(跨平台表面创建)、VK_KHR_get_physical_device_properties2(查询设备属性)。 -
设备扩展(Device Extensions),用于硬件功能支持。
如:VK_KHR_ray_query(光线追踪)、VK_KHR_video_encode_av1(AV1编码)。
跨平台表面VK_KHR_surface拓展。
由于Vulkan是一个平台无关的API,因此它不能自己直接与窗口系统接口交互。为了在Vulkan和所在平台的窗口系统建立连接并将结果显示到屏幕上,我们需要使用WSI(窗口系统集成)扩展。
VK_KHR_surface拓展。它是一个实例扩展(Instance Extensions),公开了一个VkSurfaceKHR对象,该对象表示要呈现渲染图像的抽象surface类型。
它在GLFW的glfwGetRequiredInstanceExtensions返回的列表中,所以是默认启动了它。
物理设备
开发者需要选择支持vulkan的合适的物理设备(如足够大的VRAM或者带专业显卡的)
逻辑设备和队列簇拓展
概念:
绘图指令和内存操作,都是通过提交给 VkQueue(队列)来异步执行的。
队列是从队列簇(Queue Family)中分配,每一个队列簇中的队列支持一组特定操作。比如,有的队列簇中队列支持绘图,有的是专门做计算或内存转储操作。
在一个队列族内部包含一个或多个队列。
队列簇的不同能力可以作为选择物理设备的区分标准。
一个设备支持 Vulkan 但不具备任何图形功能情况也是有可能的。因为并不是所有的图形卡具备能力将绘制的图像直接显示到屏幕上。比如一个GPU卡是为服务器设计的,那就不会具备任何有关显示的输出。

可以通过下列函数对物理设备所支持的队列簇进行筛选。
struct QueueFamilyIndices {
public:
/// <summary>
/// 支持图形操作的队列簇索引
/// </summary>
std::optional<uint32_t> graphicsFamily;
/// <summary>
/// 支持通过向窗口表面(VkSurfaceKHR)提交呈现操作的队列簇索引
/// </summary>
std::optional<uint32_t> presentFamily;
/// <summary>
/// 是否初始化完毕
/// </summary>
/// <returns></returns>
bool isComplete() {
return graphicsFamily.has_value() && presentFamily.has_value();
}
};
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
QueueFamilyIndices indices;
// 从物理设备中获取所支持的队列簇
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
int i = 0;
for (const auto& queueFamily : queueFamilies) {
// 队列族是否支持图形操作
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
}
// 查询某个物理设备是否支持通过指定的队列族向窗口表面(VkSurfaceKHR)提交呈现操作。
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
if (presentSupport) {
indices.presentFamily = i;
}
if (indices.isComplete()) {
break;
}
++i;
}
return indices;
}
支持同一物理设备创建多个逻辑设备。
窗口面和交换链
什么是交换链:
交换链是一种图元绘制结果呈现的机制,它可以将绘制结果渲染到平台相关的展示窗口/展示层当中。
交换链内含一组图像(一般作为渲染目标),用于屏幕上渲染的帧画面。
交换链中有两幅图像,那么称为双缓存;如果有三幅图像,那么称作三缓存。
图像个数与驱动层有关。
- 1.初步检查:判断物理设备是否支持交换链拓展VK_KHR_swapchain。
Vulkan头文件提供给了一个方便的宏VK_KHR_SWAPCHAIN_EXTENSION_NAME,该宏定义为VK_KHR_swapchain。 - 2.细节检查:
- 2.1 基本的surface功能属性(min/max number of images in swap chain, min/max width and height of images)
- 2.2 Surface格式(pixel format, color space)
- 2.3 有效的presentation模式
以下是查询目标物理设备锁支持的surface格式和presentation模式信息,若干SwapChainSupportDetails的formats和presentModes不为空,则表示支持。
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
/// <summary>
/// 查询目标物理设备锁支持的surface格式和presentation模式信息
/// </summary>
/// <param name="device"></param>
/// <returns></returns>
SwapChainSupportDetails EditorApp::querySwapChainSupport(VkPhysicalDevice device) {
SwapChainSupportDetails details;
// 查询需要物理设备mPhysicalDevice和表面mSurface作为参数,查询结果保存在结构体VkSurfaceCapabilitiesKHR中
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
// 查询支持的surface格式的个数
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount);
// 查询交换链锁所支持的surface格式,并填充到details.formats这个VkSurfaceFormatKHR类型的数组。
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
// 查询支持的presentation模式的个数
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
// 查询交换链锁所支持的presentation模式,并填充到details.presentModes这个VkPresentModeKHR类型的数组。
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}
return details;
}
bool isDeviceSuitable(VkPhysicalDevice device) {
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
}
交换链
概念:
- Vulkan就没有像OpenGL一样的默认帧缓冲(frame buffer)。如果我们想把图像显示到屏幕上,必须先有一套由窗口系统持有的可显示图像,这个基础设施就是Swap chain
- SwapChain的作用主要用于解决多buffer渲染与显示的撕裂感问题。
- 创建vulkan交换链拓展,需要指定表面格式数据(像素格式、色彩空间)、提交呈现命令模式、画面尺寸、队列簇索引列表等等数据。
交换链特点:
- 相当于图像的缓冲区(可能是单缓冲、双缓冲、三缓冲)。应用程序从队列取出图像后提交给表面拓展进行渲染操作。交换链被用来同步图像呈现和屏幕刷新。
- SwapChain 用于解决多buffer渲染与显示的撕裂感问题。
Vulkan交换链特点:
- Vulkan交换链不是必需的拓展,例如有些显卡不具有将图像呈现到屏幕的能力,Vulkan的交换链必须显式地创建,不存在默认的交换链。
- 允许我们选择任意数量的显卡设备,并能够同时使用它们。
- DXVK支持多种像素格式,常见的有:
| 像素格式类型 | 说明 |
|---|---|
| VK_FORMAT_B8G8R8A8_SRGB | 8位标准RGB格式,适合SDR内容 |
| VK_FORMAT_A2B10G10R10_UNORM_PACK32 | 10位高动态范围格式,支持HDR |
| VK_FORMAT_R16G16B16A16_SFLOAT | 16位浮点格式,用于HDR渲染管线 |
- DXVK支持两类主要标准:
| 色彩空间类型 | Vulkan枚举值 | 应用场景 |
|---|---|---|
| 标准SDR | VK_COLOR_SPACE_SRGB_NONLINEAR_KHR | 普通游戏/视频 |
| 扩展SDR | VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT | 广色域SDR内容 |
| HDR10 | VK_COLOR_SPACE_HDR10_ST2084_EXT | 电影级HDR内容 |
| HDR10+ | VK_COLOR_SPACE_HDR10_PLUS_EXT | 动态元数据HDR |
- 呈现模式:
对于交换链对显示模式的设置应该是最重要的,因为它代表实际显示图像到屏幕的时机。在Vulkan中有四种显示模式:
| 呈现模式类型 | 说明 |
|---|---|
| VK_PRESENT_MODE_IMMEDIATE_KHR | 由应用提交的图像立刻被传输到屏幕。这种方式可能导致图像不完整。 |
| VK_PRESENT_MODE_FIFO_KHR | 交换链是一个队列,当显示器刷新时,显示器从队列头获取图像,程序将渲染后的图像从队尾插入。 若队列满则等待。这与现代游戏中的水平同步很类似。显示器刷新的时机称为“vertical blank”. |
| VK_PRESENT_MODE_FIFO_RELAXED_KHR | 此模式区别于上一个模式的地方在于在最后一个“vertical blank”的时候,应用程序图像未到来而队列是空的;当图像到队列时,不会等到下一个"vertical blank"的时候再显示到屏幕,而是立即显示到屏幕,这会导致可见的图形的不完整。 |
| VK_PRESENT_MODE_MAILBOX_KHR | 此模式为第二种模式的又一变种,当队列满的时候,应用程序并不会阻塞而是简单的将队尾的图像替换为新的。此模式可用于实现三缓冲。只有VK_PRESENT_MODE_FIFO_KHR模式被保证绝对存在,因此,我们需要查询最优 的可用模式。 |
图像视图和采样器
图像视图
交换链的图像数据不是直接访问,而是通过图像视图VkImage。
如下是创建图像视图代码:
void createImageViews() {
swapChainImageViews.resize(swapChainImages.size());
for (size_t i = 0; i < swapChainImages.size(); ++i) {
VkImageViewCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
createInfo.image = swapChainImages[i];
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
createInfo.format = swapChainImageFormat;
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
createInfo.subresourceRange.baseMipLevel = 0;
createInfo.subresourceRange.levelCount = 1;
createInfo.subresourceRange.baseArrayLayer = 0;
createInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create image views!");
}
}
}
创建渲染通道(VkRenderPass)
对于一个复杂的图形应用程序,需要多个过程的配合,以生成图像的各个部分。通常,各个过程间存在着依赖关系,例如某个过程生成的图像(输出)被另一个过程使用(作为此过程的输入)。在Vulkan中,每个过程被称为一个子通道(subpass), 所有的子通道构成了一个渲染通道(VkRenderPass).
RenderPass是Pipeline的一次执行。RenderPass本质是由CPU发起的一系列绘制任务,即DrawCall,其本身没有任何真正的数据,旨在明确渲染执行步骤,确保GPU执行的规范化。每次提交Draw call命令之后,到输出一张或多张图片结果。
Attachment
Attachment(RenderPass附着): 每个Pass设置图像输出的目标画布。一张画布就是一个Attachment。
- ColorAttachment:每一个Pass,负责承接颜色输出的画布;一个Pass可以有多个ColorAttachment,分别输出不同含义的颜色信息
- DepthStencilAttachment:每一个Pass,负责承接深度/模板信息输出的画布;一个Pass只能有一个此类Attachment
图片格式:一个Attachment其实就是一张图片,就得有自己的存储格式。如:
| 画布格式 | 说明 |
|---|---|
| VK_IMAGE_LAYOUT_UNDEFINED | 不关心其格式 |
| VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL | 适合颜色附着的优化格式 |
| VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL | 适合深度模板附着的优化格式 |
SubPass
共用FrameBuffer的存有依赖的子通道。
一次RenderPass可以包含多次SubPass。(比如第一次渲染模型,第二次渲染模型描边)

采样器
参考:
《vulkan官方文档》
https://docs.vulkan.org/refpages/latest/refpages/index.html
《vulkan官方教程》
https://vulkan-tutorial.com/
《Vulkan实战之Window surface》
https://blog.csdn.net/qq_30135687/article/details/130463663
《深入解析Vulkan设备队列与族的使用技巧》
https://www.baidu.com/link?url=lhqTv-Zn2P2rl8XG2HBsUBzmuveOezHZh2RCIVAPgvgb7jjhV3H4UFKGlTy1UUnFU28mcu9YSW6n-mtfumVSSETFwJR2F0YGcahKKZlGuLi&wd=&eqid=b46831d8001bce2000000006691ee358
《极客教程vulkan》
https://geek-docs.com/vulkan/vulkan-tutorial/vulkan-basic-types.html
《Vulkan学习苦旅05:马不停蹄地渲染(创建交换链VkSwapchainKHR)》
https://chuna2.787528.xyz/overxus/p/17997410/vulkan05
《Vulkan/VkPresentModeKHR》
https://chuna2.787528.xyz/FastEarth/p/17871614.html
《Vulkan学习苦旅06:创建渲染通道(VkRenderPass)》
https://chuna2.787528.xyz/overxus/p/18002865/vulkan06
《vulkan学习(五)——RenderPass与SubPass》
https://bbs.huaweicloud.com/blogs/345830

浙公网安备 33010602011771号