交换链 (Swap Chain) 是连接 Vulkan 和窗口系统的桥梁,主要用于管理用于显示的图像缓冲区。它的核心功能是处理渲染图像的显示时机,确保画面平滑且无撕裂。
让我详细解释:
- 基本概念:
// 创建交换链的基本结构
VkSwapchainCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface; // 渲染表面
createInfo.minImageCount = imageCount; // 最小图像数量
createInfo.imageFormat = surfaceFormat.format; // 图像格式
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent; // 图像范围
createInfo.imageArrayLayers = 1; // 图像层数
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; // 图像用途- 主要功能:
- 管理多个图像缓冲区
- 处理显示时机
- 支持垂直同步 (VSync)
- 处理窗口大小改变
- 工作流程:
- 获取下一个可用的图像 (vkAcquireNextImageKHR)
- 渲染到该图像
- 提交图像进行显示 (vkQueuePresentKHR)
- 呈现模式:
- FIFO (先进先出,支持垂直同步)
- MAILBOX (三重缓冲)
- IMMEDIATE (立即显示,可能撕裂)
- FIFO_RELAXED (当延迟时立即显示)
- 交换链的重建:
- 窗口大小改变时
- 设备丢失时
- 其他需要重新创建的场景
Q&A
游戏设置中的 2 倍缓存 3 倍缓存是不是就是指的交换链相关参数
是的,2 倍缓存(Double Buffering)和 3 倍缓存(Triple Buffering)就是指的交换链中的图像缓冲数量。让我详细解释:
- 2 倍缓存(Double Buffering):
-
使用两个缓冲区:前缓冲 (Front Buffer) 和后缓冲 (Back Buffer)
-
前缓冲用于显示,后缓冲用于渲染
-
工作流程:
后缓冲:正在渲染帧 N+1 前缓冲:显示帧 N 完成后交换位置 -
优点:减少撕裂
-
缺点:可能造成等待,因为需要等前缓冲显示完才能交换
- 3 倍缓存(Triple Buffering):
-
使用三个缓冲区
-
一个用于显示,一个用于渲染,一个作为备用
-
工作流程:
缓冲A:显示帧 N 缓冲B:渲染帧 N+1 缓冲C:已完成帧 N+2,等待显示 -
优点:
- 减少等待时间
- 提高帧率
- 更流畅的画面
-
缺点:
- 占用更多显存
- 可能增加输入延迟
在 Vulkan 中,你可以通过设置交换链的 minImageCount 来控制缓冲数量:
VkSwapchainCreateInfoKHR createInfo = {};
createInfo.minImageCount = 3; // 3倍缓存为什么要引入一个 交换链的概念,不能直接将 vkImage 的内容显示到窗口么
引入交换链概念主要是为了解决显示同步和性能问题。让我解释为什么不能直接显示:
- 显示器刷新机制:
-
显示器按固定频率 (如 60Hz) 更新画面
-
直接写入显示内容会导致画面撕裂 (Tearing):
显示器正在显示帧A的上半部分 GPU 突然写入了新的帧B 导致上半部分显示A,下半部分显示B
- 性能考虑:
-
GPU 渲染速度和显示器刷新率通常不同步
-
直接显示会导致 GPU 等待显示器准备好,或显示器等待 GPU 完成渲染
-
交换链通过维护多个缓冲区来解决这个问题:
GPU 可以渲染到缓冲区A 显示器正在显示缓冲区B的内容 互不干扰,提高效率
- 平台兼容性:
- 不同操作系统有不同的显示机制
- 交换链提供了统一的抽象层,处理平台特定的显示细节
- 开发者只需要和交换链交互,不用关心底层实现
所以交换链实际上是一个必要的 ” 中间层 “,用于协调 GPU 渲染和显示器显示之间的关系。
交换链本质是一个 vkImage 池?
交换链本质上就是一个管理多个 VkImage 的池子。
- 交换链结构:
// 获取交换链图像
std::vector<VkImage> swapChainImages;
uint32_t imageCount;
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());- 主要特点:
- 其中的每个 VkImage 都是由系统管理的
- 这些图像可以直接显示到窗口系统
- 系统自动处理这些图像和显示器之间的同步
- 与普通 VkImage 的区别:
- 交换链中的图像是由系统创建和管理的
- 这些图像有特殊的内存要求,确保能高效地显示到屏幕
- 不能像普通 VkImage 那样直接创建和销毁
- 使用流程:
// 获取下一个可用的图像索引
uint32_t imageIndex;
vkAcquireNextImageKHR(device, swapChain, UINT64_MAX,
imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
// 使用这个图像进行渲染
// ...
// 提交显示
VkPresentInfoKHR presentInfo = {};
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapChain;
presentInfo.pImageIndices = &imageIndex;
vkQueuePresentKHR(presentQueue, &presentInfo);