一、什么是 Swapchain在 Vulkan 中Swapchain可以理解为一组用来显示画面的图像。GPU 渲染出来的画面并不是直接显示到屏幕上的而是先画到一张图像里然后再把这张图像交给窗口系统显示。这个“管理多张显示图像”的东西就是 Swapchain。简单来说GPU 渲染画面 ↓ 写入 Swapchain 图像 ↓ 提交给窗口系统 ↓ 显示到屏幕二、为什么需要 Swapchain如果 GPU 正在画一张图而屏幕也正在显示同一张图就可能出现画面撕裂、闪烁等问题。所以 Vulkan 不会只使用一张图像而是使用多张图像轮流工作。例如三缓冲可以理解为图像 A正在屏幕上显示 图像 BGPU 正在渲染 图像 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::vectorconst 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 ModePresent 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. 图像大小 ExtentExtent 表示 Swapchain 图像的宽和高。一般应该和窗口的 framebuffer 大小一致。使用 GLFW 时推荐这样获取glfwGetFramebufferSize(window, width, height);不要简单使用窗口逻辑大小因为高 DPI 屏幕上两者可能不一样。4. 图像数量 Image CountSwapchain 里面有多少张图像。常见做法是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. inFlightFenceFence 是给 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 的窗口显示流程就会清晰很多。
Vulkan Swapchain 通俗入门:渲染结果是怎么显示到屏幕上的?
一、什么是 Swapchain在 Vulkan 中Swapchain可以理解为一组用来显示画面的图像。GPU 渲染出来的画面并不是直接显示到屏幕上的而是先画到一张图像里然后再把这张图像交给窗口系统显示。这个“管理多张显示图像”的东西就是 Swapchain。简单来说GPU 渲染画面 ↓ 写入 Swapchain 图像 ↓ 提交给窗口系统 ↓ 显示到屏幕二、为什么需要 Swapchain如果 GPU 正在画一张图而屏幕也正在显示同一张图就可能出现画面撕裂、闪烁等问题。所以 Vulkan 不会只使用一张图像而是使用多张图像轮流工作。例如三缓冲可以理解为图像 A正在屏幕上显示 图像 BGPU 正在渲染 图像 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::vectorconst 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 ModePresent 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. 图像大小 ExtentExtent 表示 Swapchain 图像的宽和高。一般应该和窗口的 framebuffer 大小一致。使用 GLFW 时推荐这样获取glfwGetFramebufferSize(window, width, height);不要简单使用窗口逻辑大小因为高 DPI 屏幕上两者可能不一样。4. 图像数量 Image CountSwapchain 里面有多少张图像。常见做法是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. inFlightFenceFence 是给 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 的窗口显示流程就会清晰很多。