一、什么是 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 需要关注几个重点:

  1. 它依赖 VkSurfaceKHR

  2. 它内部包含多张 VkImage

  3. 每张图像通常需要创建 VkImageView

  4. 使用传统 Render Pass 时,每张图像一般对应一个 Framebuffer;

  5. 每帧需要先 acquire,再 submit,最后 present;

  6. 渲染和显示之间必须正确同步;

  7. 窗口大小变化时需要重建 Swapchain。

如果说 Pipeline 负责“怎么画”,Shader 负责“画什么效果”,那么 Swapchain 负责的就是:

把 GPU 画好的最终结果送到屏幕上。

掌握 Swapchain 后,Vulkan 的窗口显示流程就会清晰很多。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐