这个系列文章我们来介绍一位海外工程师如何探索 Vulkan 音视频技术,对于想要开始学习音视频技术的朋友,这些文章是份不错的入门资料,本篇介绍 Vulkan 交换链与呈现。
——来自公众号“关键帧Keyframe”的分享
交换链是 Vulkan 渲染管线与窗口系统之间的基本桥梁,使应用程序能够向用户显示渲染内容。在本综合指南中,我们将探讨 SaschaWillems/Vulkan 仓库如何实现交换链管理和呈现,为你提供将这些概念集成到自己的 Vulkan 应用程序中的知识。
交换链本质上是一组图像(帧缓冲区)的集合,Vulkan 在将其呈现到屏幕之前使用这些图像来渲染内容。可以将其视为增强版的双重缓冲系统——当一个图像正在显示时,你可以渲染到另一个图像,然后无缝地交换它们。这可以防止撕裂等视觉伪影,并确保流畅的动画。
该仓库的实现将这一复杂功能封装在 VulkanSwapChain 类中,该类处理所有平台特定的细节,同时为应用程序提供干净、统一的接口。来源:base/VulkanSwapChain.h#L4
1、平台特定的表面创建
在创建交换链之前,Vulkan 需要一个表面——一个连接到窗口系统的平台特定抽象。该仓库展示了卓越的跨平台支持,实现了以下平台的实现:
- Windows (Win32)
- Linux (XCB, Wayland, DirectFB)
- macOS/iOS (MoltenVK, Metal)
- Android
- QNX Screen
- 无头渲染
// Windows 表面创建示例
VkWin32SurfaceCreateInfoKHR surfaceCreateInfo = {};
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
surfaceCreateInfo.hinstance = (HINSTANCE)platformHandle;
surfaceCreateInfo.hwnd = (HWND)platformWindow;
err = vkCreateWin32SurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
来源:base/VulkanSwapChain.cpp#L38-L42
2、队列族选择
创建表面后,下一个关键步骤是找到支持图形操作和呈现的队列族。该实现智能地搜索可以处理两者的队列:
// 查找具有呈现支持的队列
for (uint32_t i = 0; i < queueCount; i++) {
if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
if (supportsPresent[i] == VK_TRUE) {
graphicsQueueNodeIndex = i;
presentQueueNodeIndex = i;
break;
}
}
}
来源:base/VulkanSwapChain.cpp#L134-L146
这通过尽可能使用单个队列进行图形和呈现来确保最佳性能,避免了多个队列同步的开销。
3、交换链创建过程
交换链创建过程涉及几个影响渲染质量和性能的重要决策:
3.1、表面格式选择
该实现优先考虑特定颜色格式以获得最佳兼容性:
std::vector<VkFormat> preferredImageFormats = {
VK_FORMAT_B8G8R8A8_UNORM,
VK_FORMAT_R8G8B8A8_UNORM,
VK_FORMAT_A8B8G8R8_UNORM_PACK32
};
来源:base/VulkanSwapChain.cpp#L187-L191
3.2、呈现模式选择
该仓库根据垂直同步需求实现了智能呈现模式选择:
// 如果未请求垂直同步,尝试查找邮箱模式
if (!vsync) {
for (size_t i = 0; i < presentModeCount; i++) {
if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
break;
}
}
}
来源:base/VulkanSwapChain.cpp#L255-L269
可用的呈现模式包括:
- VK_PRESENT_MODE_FIFO_KHR: 启用垂直同步(默认)
- VK_PRESENT_MODE_MAILBOX_KHR: 三重缓冲,防止撕裂
- VK_PRESENT_MODE_IMMEDIATE_KHR: 无垂直同步,可能显示撕裂
3.3、图像数量和大小
该实现计算最佳图像数量并处理表面大小调整:
// 确定图像数量
uint32_t desiredNumberOfSwapchainImages = surfCaps.minImageCount + 1;
if ((surfCaps.maxImageCount > 0) && (desiredNumberOfSwapchainImages > surfCaps.maxImageCount)) {
desiredNumberOfSwapchainImages = surfCaps.maxImageCount;
}
来源:base/VulkanSwapChain.cpp#L272-L276
4、渲染循环
实际的渲染过程遵循在基础示例类中实现的明确定义的模式:
4.1、图像获取
每帧从交换链获取下一个可用图像开始:
VkResult VulkanSwapChain::acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t& imageIndex) {
return vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, presentCompleteSemaphore, (VkFence)nullptr, &imageIndex);
}
来源:base/VulkanSwapChain.cpp#L377-L380
此函数会阻塞,直到图像可用,确保正确的同步。
4.2、命令缓冲区提交
渲染到获取的图像后,命令缓冲区将随适当的信号量同步一起提交到队列:
// 设置提交信息结构
submitInfo = vks::initializers::submitInfo();
submitInfo.pWaitDstStageMask = &submitPipelineStages;
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &semaphores.presentComplete;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &semaphores.renderComplete;
4.3、图像呈现
最后,渲染的图像将呈现到屏幕:
VkResult VulkanSwapChain::queuePresent(VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore) {
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapChain;
presentInfo.pImageIndices = &imageIndex;
if (waitSemaphore != VK_NULL_HANDLE) {
presentInfo.pWaitSemaphores = &waitSemaphore;
presentInfo.waitSemaphoreCount = 1;
}
return vkQueuePresentKHR(queue, &presentInfo);
}
来源:base/VulkanSwapChain.cpp#L383-L398
5、交换链重新创建
交换链管理的一个关键方面是处理表面变化,例如窗口大小调整。该实现包括健壮的重新创建逻辑:
// 存储当前交换链句柄,以便稍后使用它来简化重新创建
VkSwapchainKHR oldSwapchain = swapChain;
// 使用 oldSwapchain 参数创建新的交换链
swapchainCI.oldSwapchain = oldSwapchain;
来源:base/VulkanSwapChain.cpp#L218-L320
这种方法确保在创建新交换链时仍可呈现现有图像,从而防止视觉中断。
6、资源清理
在 Vulkan 中,正确的资源管理至关重要。该实现包括全面的清理:
void VulkanSwapChain::cleanup() {
if (swapChain != VK_NULL_HANDLE) {
for (auto i = 0; i < images.size(); i++) {
vkDestroyImageView(device, imageViews[i], nullptr);
}
vkDestroySwapchainKHR(device, swapChain, nullptr);
}
if (surface != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(instance, surface, nullptr);
}
}
来源:base/VulkanSwapChain.cpp#L401-L414
7、最佳实践和性能考虑
该仓库实现展示了交换链管理的几个最佳实践:
- 最佳图像数量:使用
minImageCount + 1以获得更好的流水线化 - 传输支持:在可用时启用传输功能以进行屏幕截图和后处理
- 合成 Alpha:处理透明度以实现正确的窗口合成
- 资源重用:在重新创建期间使用旧交换链以最小化中断
Syntax error in textmermaid version 11.6.0
8、与示例集成
基础类结构使各个示例能够专注于其特定的渲染逻辑,而交换链管理则自动处理:
class VulkanExampleBase {
private:
// 包装交换链以向窗口系统呈现图像
VulkanSwapChain swapChain;
// 同步信号量
struct {
VkSemaphore presentComplete; // 交换链图像呈现
VkSemaphore renderComplete; // 命令缓冲区提交
} semaphores{};
};
来源:base/vulkanexamplebase.h#L151-L159
这种架构允许示例继承自 VulkanExampleBase 并立即访问功能齐全的交换链和呈现系统。
9、结论
SaschaWillems/Vulkan 仓库为 Vulkan 中的交换链管理和呈现提供了出色的参考实现。通过封装平台特定表面创建、队列族选择和交换链配置的复杂性,它使开发者能够专注于应用程序的独特渲染需求,同时确保健壮的跨平台兼容性。
无论您是构建简单的三角形渲染器还是复杂的 3D 应用程序,理解这些交换链概念对于创建流畅、高性能的 Vulkan 应用程序至关重要。该仓库的实现既可作为学习资源,也可作为您自己 Vulkan 项目的实用基础。
学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。