Vulkan Swapchain 通俗入门:渲染结果是怎么显示到屏幕上的?
一、什么是 Swapchain?
在 Vulkan 中,Swapchain 可以理解为:
一组用来显示画面的图像。
GPU 渲染出来的画面,并不是直接显示到屏幕上的,而是先画到一张图像里,然后再把这张图像交给窗口系统显示。
这个“管理多张显示图像”的东西,就是 Swapchain。

简单来说:
GPU 渲染画面
↓
写入 Swapchain 图像
↓
提交给窗口系统
↓
显示到屏幕
二、为什么需要 Swapchain?
如果 GPU 正在画一张图,而屏幕也正在显示同一张图,就可能出现画面撕裂、闪烁等问题。
所以 Vulkan 不会只使用一张图像,而是使用多张图像轮流工作。
例如三缓冲可以理解为:
图像 A:正在屏幕上显示
图像 B:GPU 正在渲染
图像 C:等待下一次使用
这样可以让显示和渲染互不干扰。
三、Surface 和 Swapchain 的关系
在 Vulkan 中,窗口本身不能直接拿来渲染。
我们需要先创建一个 Surface,它表示一个窗口表面。
然后基于这个 Surface 创建 Swapchain。
关系如下:
窗口
↓
VkSurfaceKHR
↓
VkSwapchainKHR
↓
Swapchain Images
↓
屏幕显示
可以这样理解:
-
VkSurfaceKHR:我要显示到哪个窗口; -
VkSwapchainKHR:我要用哪些图像轮流显示; -
VkImage:真正存放画面的图像。
四、Swapchain 每一帧做了什么?
Vulkan 每一帧的显示流程大致是:
1. 从 Swapchain 取一张可用图像
2. GPU 把画面渲染到这张图像上
3. 渲染完成后,把图像交给显示系统
4. 屏幕显示这张图像
对应的核心 Vulkan 函数是:
vkAcquireNextImageKHR(); // 从 Swapchain 获取图像
vkQueueSubmit(); // 提交 GPU 渲染命令
vkQueuePresentKHR(); // 把渲染好的图像显示到屏幕
可以记成一句话:
acquire 拿图,submit 渲染,present 显示。
五、创建 Swapchain 之前要检查什么?
创建 Swapchain 之前,一般要检查三件事。
1. 显卡是否支持 Swapchain 扩展
Swapchain 需要启用这个扩展:
VK_KHR_SWAPCHAIN_EXTENSION_NAME
创建设备时要把它加入扩展列表:
const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
2. 队列是否支持显示到窗口
Vulkan 中有不同类型的队列。
我们通常至少需要:
Graphics Queue:负责渲染
Present Queue :负责显示
有些显卡上这两个队列可能是同一个,有些平台上可能不是。
所以创建 Swapchain 前,需要确认当前设备既能渲染,又能显示到这个窗口。
3. Surface 是否支持可用格式
Swapchain 需要选择图像格式,例如:
VK_FORMAT_B8G8R8A8_SRGB
也要选择色彩空间,例如:
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
通俗理解:
这一步是在决定屏幕图像的颜色格式。
六、Swapchain 的几个重要参数
创建 Swapchain 时,最重要的是下面几个参数。
1. 图像格式 Format
决定每个像素怎么存颜色。
常见选择:
VK_FORMAT_B8G8R8A8_SRGB
它表示每个像素包含:
B:蓝色
G:绿色
R:红色
A:透明度
2. 显示模式 Present Mode
Present Mode 决定图像怎么显示到屏幕。
常见的有:
VK_PRESENT_MODE_FIFO_KHR
VK_PRESENT_MODE_MAILBOX_KHR
VK_PRESENT_MODE_IMMEDIATE_KHR
简单理解:
| 模式 | 特点 |
|---|---|
| FIFO | 类似垂直同步,稳定,不撕裂 |
| MAILBOX | 延迟较低,适合实时渲染 |
| IMMEDIATE | 立即显示,但可能画面撕裂 |
一般可以优先选择:
VK_PRESENT_MODE_MAILBOX_KHR
如果不支持,就使用:
VK_PRESENT_MODE_FIFO_KHR
因为 FIFO 是 Vulkan 保证支持的模式。
3. 图像大小 Extent
Extent 表示 Swapchain 图像的宽和高。
一般应该和窗口的 framebuffer 大小一致。
使用 GLFW 时,推荐这样获取:
glfwGetFramebufferSize(window, &width, &height);
不要简单使用窗口逻辑大小,因为高 DPI 屏幕上两者可能不一样。
4. 图像数量 Image Count
Swapchain 里面有多少张图像。
常见做法是:
uint32_t imageCount = minImageCount + 1;
这样比最少数量多一张,可以减少等待。
常见情况:
2 张图像:双缓冲
3 张图像:三缓冲
七、创建 Swapchain 的核心代码
下面是一个简化版创建流程:
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;
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.preTransform = capabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = VK_NULL_HANDLE;
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
throw std::runtime_error("failed to create swap chain!");
}
这里最重要的几个字段是:
surface
表示显示到哪个窗口。
imageFormat
表示图像格式。
imageExtent
表示图像大小。
presentMode
表示显示方式。
imageUsage
表示这张图像要用来做什么。
如果我们要把它作为渲染目标,一般使用:
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
八、获取 Swapchain 图像
创建 Swapchain 后,需要获取它内部的图像:
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(
device,
swapChain,
&imageCount,
swapChainImages.data()
);
这些图像是 Swapchain 自己创建的。
我们只需要使用它们,不需要自己销毁它们。
九、为什么还要创建 Image View?
在 Vulkan 中,VkImage 不能直接拿来当渲染目标。
通常需要为每张 Swapchain Image 创建一个 VkImageView。
可以理解为:
VkImage :真正的图像数据
VkImageView :访问这张图像的方式
创建 Image View 后,Swapchain 图像才能更方便地被 Render Pass 或 Framebuffer 使用。
十、Swapchain 和 Framebuffer 的关系
如果使用传统 Render Pass,一般每张 Swapchain Image 都会对应一个 Framebuffer。
关系如下:
Swapchain Image 0 → Image View 0 → Framebuffer 0
Swapchain Image 1 → Image View 1 → Framebuffer 1
Swapchain Image 2 → Image View 2 → Framebuffer 2
每一帧获取到哪张 Swapchain Image,就使用对应的 Framebuffer 渲染。
十一、每帧渲染流程
每一帧一般这样写:
uint32_t imageIndex;
vkAcquireNextImageKHR(
device,
swapChain,
UINT64_MAX,
imageAvailableSemaphore,
VK_NULL_HANDLE,
&imageIndex
);
// 记录命令,把画面渲染到 imageIndex 对应的 framebuffer
vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence);
vkQueuePresentKHR(presentQueue, &presentInfo);
核心逻辑是:
获取图像 → 渲染图像 → 显示图像
十二、Swapchain 里的同步对象
Swapchain 渲染通常会用到三个同步对象。
1. imageAvailableSemaphore
表示:
Swapchain 图像已经可以用了
也就是说,GPU 可以开始往这张图像上渲染了。
2. renderFinishedSemaphore
表示:
GPU 已经渲染完成了
只有渲染完成后,图像才能交给屏幕显示。
3. inFlightFence
Fence 是给 CPU 用的。
它的作用是:
防止 CPU 太快,重复使用 GPU 还没处理完的资源
例如 command buffer、uniform buffer 等。
十三、窗口大小变化怎么办?
窗口大小变化时,Swapchain 通常需要重建。
因为 Swapchain 图像大小要和窗口大小匹配。
常见触发情况:
窗口 resize
窗口最小化后恢复
屏幕旋转
vkAcquireNextImageKHR 返回 VK_ERROR_OUT_OF_DATE_KHR
vkQueuePresentKHR 返回 VK_ERROR_OUT_OF_DATE_KHR
重建流程一般是:
等待设备空闲
↓
销毁旧 Swapchain 相关资源
↓
重新创建 Swapchain
↓
重新创建 Image View
↓
重新创建 Framebuffer
↓
继续渲染
简化代码:
void recreateSwapChain() {
vkDeviceWaitIdle(device);
cleanupSwapChain();
createSwapChain();
createImageViews();
createFramebuffers();
}
十四、常见错误
1. 忘记启用 Swapchain 扩展
必须启用:
VK_KHR_SWAPCHAIN_EXTENSION_NAME
否则无法创建 Swapchain。
2. 忘记处理窗口大小变化
窗口 resize 后,旧 Swapchain 可能不能继续使用,需要重建。
3. 把 currentFrame 和 imageIndex 搞混
这是初学者常见错误。
currentFrame
表示当前是第几帧资源。
imageIndex
表示当前拿到的是第几张 Swapchain 图像。
二者不是同一个东西。
正确做法是:
recordCommandBuffer(commandBuffers[currentFrame], imageIndex);
命令缓冲区可以按 currentFrame 使用,但 framebuffer 要根据 imageIndex 选择。
4. 没有正确同步
如果没有正确使用 semaphore 和 fence,可能出现:
图像还没准备好就开始渲染
图像还没渲染完就拿去显示
CPU 重复使用 GPU 正在用的资源
所以 Swapchain 渲染一定要处理同步。
十五、总结
Swapchain 是 Vulkan 中负责“把画面显示到屏幕上”的核心机制。
可以把它理解成:
Vulkan 准备了一组图像,GPU 轮流往这些图像上画画,画完之后交给屏幕显示。
它的基本流程是:
Acquire Image
↓
Render
↓
Present
也就是:
拿图像 → 渲染 → 显示
Swapchain 需要关注几个重点:
-
它依赖
VkSurfaceKHR; -
它内部包含多张
VkImage; -
每张图像通常需要创建
VkImageView; -
使用传统 Render Pass 时,每张图像一般对应一个 Framebuffer;
-
每帧需要先 acquire,再 submit,最后 present;
-
渲染和显示之间必须正确同步;
-
窗口大小变化时需要重建 Swapchain。
如果说 Pipeline 负责“怎么画”,Shader 负责“画什么效果”,那么 Swapchain 负责的就是:
把 GPU 画好的最终结果送到屏幕上。
掌握 Swapchain 后,Vulkan 的窗口显示流程就会清晰很多。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)