一、什么是 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 的窗口显示流程就会清晰很多。