【免费下载链接】engine
The Flutter engine
导读
本文以 Flutter 引擎中 Impeller 渲染器的 Vulkan 后端为对象,深入讲解其交换链(Swapchain)实现——这是 Impeller 在 Vulkan 上完成窗口系统集成(WSI,Window System Integration)的核心模块。文章围绕 swapchain 目录 中的 SwapchainVK 抽象、基于 VK_KHR_swapchain 扩展的通用实现(KHR Swapchain)以及面向 Android 的硬件缓冲交换链(AHB Swapchain)展开,结合源码剖析交换链创建、帧同步、图像获取与呈现的完整生命周期。读完本文,你将掌握 Impeller Vulkan 交换链的架构设计、KHR 与 AHB 两条实现路径的适用场景与取舍,以及底层 vkAcquireNextImageKHR / vkQueuePresentKHR 调用链中同步与错误处理的工程细节。
一、为什么 Impeller 需要自己的 Swapchain 抽象
在 Vulkan 中,向屏幕呈现渲染结果并不是直接写入某个固定缓冲,而是通过“交换链(swapchain)”向窗口系统申请可呈现的图像(image),渲染完成后再通过呈现队列交还给窗口系统。交换链与具体的窗口系统集成方式(WSI)强相关:桌面平台、移动平台与嵌入式平台的窗口抽象、缓冲格式、同步模型各不相同。
Flutter 引擎的 Impeller 渲染器在其 Vulkan 后端 中为此建立了一层独立的交换链模块,即 impeller/renderer/backend/vulkan/swapchain。该目录正如其 README 所述,“包含面向不同平台窗口系统集成(WSI)的交换链实现”,其核心目标是把“窗口系统差异”封装在交换链内部,让上层渲染管线只关心“获取下一帧可绘制表面”这一个统一接口。
这一抽象的直接体现是基类 swapchain_vk.h 中的 SwapchainVK:
class SwapchainVK {
public:
static std::shared_ptr<SwapchainVK> Create(
const std::shared_ptr<Context>& context,
vk::UniqueSurfaceKHR surface,
const ISize& size,
bool enable_msaa = true);
virtual bool IsValid() const = 0;
virtual std::unique_ptr<Surface> AcquireNextDrawable() = 0;
virtual vk::Format GetSurfaceFormat() const = 0;
virtual void UpdateSurfaceSize(const ISize& size) = 0;
...
};
从源码结构看,该抽象刻意只暴露四个关键操作:合法性查询(IsValid)、获取下一帧可绘制表面(AcquireNextDrawable)、查询表面格式(GetSurfaceFormat)、更新表面尺寸(UpdateSurfaceSize)。其中注释明确指出:交换链会自适应底层表面失效(out of date)的情况;如果调用方无法获取下一个 drawable,说明发生了不可恢复的错误,交换链必须连同新的表面一起重建。
UpdateSurfaceSize 的设计也反映了移动端高频场景:窗口尺寸变化(旋转、键盘弹出)时并不销毁整个交换链对象,而是将其标记为“配置脏”,在下一帧强制按新尺寸重建,见 khr_swapchain_impl_vk.cc 中重建逻辑与 oldSwapchain 的传递。
二、两条实现路径:KHR Swapchain 与 AHB Swapchain
SwapchainVK::Create 是两条路径的汇合点(swapchain_vk.cc):
- 桌面 / 通用路径:接收一个已经创建好的
vk::UniqueSurfaceKHR,直接构造KHRSwapchainVK; - Android 路径:接收一个
ANativeWindow*,先通过createAndroidSurfaceKHRUnique创建 Android 平台 surface,再进入通用创建流程。
Android 路径的代码注释写得很直白:“如果 AHB 交换链不可用,则回退到 KHR 交换链”(Fallback to KHR swapchains if AHB swapchains aren't available)。也就是说,KHR 路径既是通用方案,也是兜底方案。
2.1 KHR Swapchain:通用且可靠的兜底实现
KHR Swapchain 的 README 给出了明确的定位:一个依赖 VK_KHR_swapchain 扩展的 Vulkan 交换链实现,在大多数平台上可用,但在所有平台上未必是最优选择——不过它是一个良好的回退(good fallback)。
其实现由三个类协作完成:
| 类 | 职责 | 源码位置 |
|---|---|---|
KHRSwapchainVK | 对外门面,持有 impl、尺寸与 MSAA 开关 | khr_swapchain_vk.h |
KHRSwapchainImplVK | 实际执行创建、获取、呈现的核心逻辑 | khr_swapchain_impl_vk.h |
KHRSwapchainImageVK | 包装单个 swapchain 图像(image / image view) | khr_swapchain_image_vk.h |
表面格式与合成模式的选择
创建阶段的关键决策集中在 khr_swapchain_impl_vk.cc:
ChooseSurfaceFormat:先查询物理设备支持的vk::SurfaceFormatKHR列表,优先匹配 Impeller 默认颜色格式(ContextVK的GetDefaultColorFormat),否则在B8G8R8A8Unorm与R8G8B8A8Unorm两个 sRGB 选项中回退选择;若全部不支持则创建失败。ChooseAlphaCompositionMode:按eInherit → ePreMultiplied → ePostMultiplied → eOpaque的优先级,从 surface capabilities 中挑选合成透明度模式。
SwapchainCreateInfo 的关键参数
创建信息(khr_swapchain_impl_vk.cc)中的几个参数值得关注:
presentMode = eFifo:使用 Vulkan 默认的 FIFO 垂直同步呈现模式,这是兼容性最好的选择;imageExtent:将请求尺寸 clamp 到minImageExtent与maxImageExtent之间,避免越界;minImageCount:以minImageCount + 1为期望值,并在maxImageCount == 0(表示无上限)时做保护性处理;imageUsage:包含eColorAttachment(颜色附件,经 resolve 使用)、eTransferDst(blit 目标)与eInputAttachment(输入附件);clipped = true:告知 Vulkan 引擎永远不会回读该缓冲,注释中以 Metal 的[CAMetalLayer framebufferOnly]作类比;preTransform = eIdentity:不做 Vulkan 预旋转,旋转场景通过轮询尺寸变化处理;oldSwapchain:重建时传入旧交换链,实现图像的无缝交接。
帧同步机制:三个同步原语 + 三帧在飞
KHRSwapchainImplVK 为每帧在飞(frame in flight)维护一组同步对象(khr_swapchain_impl_vk.cc):
acquire(fence):以已信号状态创建,用于 CPU 端等待上一轮该帧槽的渲染彻底完成;render_ready(semaphore):acquireNextImageKHR在获取到图像时对其发信号,通知 GPU 该图像已可用;present_ready(semaphore):渲染完成后在最终命令缓冲提交时发信号,供呈现队列等待。
帧在飞数量由 kMaxFramesInFlight = 3u 限定(khr_swapchain_impl_vk.cc),即同时允许三帧处于渲染管线中,以平衡延迟与吞吐。
获取下一帧(AcquireNextDrawable,khr_swapchain_impl_vk.cc)的流程为:
- 轮转
current_frame_,对当前帧槽的 fence 执行waitForFences+resetFences(无限超时); - 调用
acquireNextImageKHR,使用无限超时并携带render_ready信号量——代码注释特别提醒:某些旧 Android 设备不支持非无限超时,且只会产生令人困惑的日志刷屏,因此直接使用无限超时; - 对返回结果分类处理:
eSuccess继续;eSuboptimalKHR/eErrorOutOfDateKHR属于可恢复错误,标记为 out of date;其余为不可恢复错误,记录日志并返回失败; - 将索引对应的
KHRSwapchainImageVK包装成SurfaceVK,并注册一个 swap 回调,回调最终会调用Present(image, index)。
呈现(Present,khr_swapchain_impl_vk.cc)则分为三个阶段:
- 创建最终命令缓冲,将图像布局通过 barrier 从颜色附件输出过渡到
ePresentSrcKHR; - 提交该命令缓冲,等待
render_ready、信号present_ready,并用acquirefence 作为提交完成信号; - 调用
vkQueuePresentKHR呈现图像,对eErrorOutOfDateKHR、eErrorSurfaceLostKHR、eSuboptimalKHR三类结果统一按“可恢复/保证队列操作仍会完成”处理,交给下一次获取时重建。
2.2 AHB Swapchain:Android 上的硬件缓冲直通方案
面向 Android 的 AHB(Android Hardware Buffer)交换链位于 ahb 子目录。其头文件注释给出了明确的适用边界(ahb_swapchain_vk.h):
该交换链并非在 Flutter 支持的所有 Android 版本上都可用。必须执行
IsAvailableOnPlatform检查,若无法创建则回退到 KHR 交换链。此类交换链所需的基础构件仅存在于 Android API level >= 29。
IsAvailableOnPlatform() 的实现(ahb_swapchain_vk.cc)同时要求 SurfaceControl::IsAvailableOnPlatform() 与 HardwareBuffer::IsAvailableOnPlatform() 为真。
与 KHR 路径的核心差异在于图像的来源与呈现通道:
- 图像不是由
VkSwapchainKHR分配,而是来自AHBTexturePoolVK硬件缓冲纹理池(ahb_texture_pool_vk.h); - 呈现目标不是呈现队列,而是
android::SurfaceControl——Impeller 通过 Android Surface 事务(ASurfaceTransaction)把硬件缓冲交给系统合成器(ahb_swapchain_impl_vk.cc 起的Present实现); - 缓冲数量由 surface capabilities 计算得出(
minImageCount + 1并 clamp),并维护一个pending_presents_信号量,其上限kMaxPendingPresents = 3u(ahb_swapchain_impl_vk.cc):2 张图像交给系统合成器、1 张留给栅格线程; - 同步通过 外部 fence(
ExternalFenceVK)与 render-ready fence 完成,GPU 需要等待 render-ready fence 被信号后才开始渲染(SubmitWaitForRenderReady),呈现完成后在ASurfaceTransactionStats回调(OnTextureUpdatedOnSurfaceControl)中确认纹理已被消费。
AHBSwapchainImplVK 按尺寸管理生命周期:每个尺寸对应一个 impl 实例,尺寸变化会导致该尺寸的 impl 连同其全部缓存与 transients 一起被丢弃重建(见 ahb_swapchain_impl_vk.h 的注释与 ahb_swapchain_vk.cc 的 UpdateSurfaceSize 实现——尺寸不变则直接复用,变化才重建)。这也是它在 Android 上比 KHR 更优的主要原因:图像直接来自硬件缓冲,可绕开一次额外的格式转换与拷贝。
三、Surface 包装与 transients 缓存:把交换链图像变成渲染目标
无论哪种交换链,最终获得的“可绘制表面”都被统一包装成 SurfaceVK(surface_vk.h),其注释说明这是为了“提供 Impeller 将交换链图像用作屏幕渲染目标所需的额外配置”。WrapSwapchainImage 同时负责创建配套的 MSAA 与深度模板纹理,并通过 SwapCallback 将 Present() 与交换链的呈现逻辑绑定。
这里有一个值得注意的性能设计:SwapchainTransientsVK(swapchain_transients_vk.h)。注释指出,被它缓存的是“直觉上创建很便宜、但在某些 Vulkan 驱动上实测非常耗时”的资源——设备本地的 MSAA 纹理与深度模板纹理。由于同一交换链的所有图像共享同一组 transients,这些纹理只需创建一次,在整条交换链生命周期内被所有 swapchain 图像复用(MSAA 与深度模板天然不依赖具体图像内容,只依赖尺寸与格式,因此可安全共享)。enable_msaa 参数控制是否启用 MSAA 与对应的缓存。
由此可以梳理出 Impeller 一帧的完整数据流:
SwapchainVK::AcquireNextDrawable()
│
▼
获取可呈现图像(KHR: acquireNextImageKHR / AHB: 纹理池弹出)
│
▼
SurfaceVK::WrapSwapchainImage(绑定 transients:MSAA + 深度模板)
│
▼
上层渲染(以交换链图像为最终 resolve 目标)
│
▼
SurfaceVK::Present() ──> swap 回调
│
├─ KHR: 布局过渡 → 提交 → vkQueuePresentKHR
└─ AHB: 外部 fence 同步 → SurfaceControl 事务呈现
四、如何阅读与验证:源码索引
若希望进一步深入,可按以下路径在仓库中对照阅读:
- 交换链目录总览:impeller/renderer/backend/vulkan/swapchain
- KHR 实现说明:khr/README.md
- 抽象基类与工厂:swapchain_vk.h、swapchain_vk.cc
- KHR 创建 / 获取 / 呈现核心:khr_swapchain_impl_vk.cc
- AHB 创建 / 呈现核心:ahb_swapchain_impl_vk.cc
- Surface 包装与 transients:surface_vk.h、swapchain_transients_vk.h
五、小结
Impeller 的 Vulkan 交换链模块回答了“在不同 WSI 平台上如何高效呈现渲染结果”这一问题,其设计要点可以概括为:
- 统一抽象:
SwapchainVK将 surface 失效、尺寸变化、格式协商等平台差异全部收敛到四个接口方法之内,上层渲染无需感知平台差异; - 双实现 + 回退策略:KHR Swapchain 基于
VK_KHR_swapchain,覆盖绝大多数平台且作为兜底;AHB Swapchain 在 Android API 29+ 上通过硬件缓冲 + SurfaceControl 直达系统合成器,是 Android 平台的高性能选项,不可用时自动回退; - 工程细节到位:三帧在飞的同步结构、可恢复错误与不可恢复错误的严格区分、
oldSwapchain无缝重建、transients 跨图像复用等,都是移动端 Vulkan 渲染中直接影响流畅度与兼容性的关键处理。
对于需要阅读或扩展 Flutter Impeller Vulkan 后端的开发者而言,swapchain 目录是理解“渲染到屏幕”这一环节的最佳入口,也是观察 Vulkan WSI 最佳实践的一个高质量参考实现。
【免费下载链接】engine
The Flutter engine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



