- 跨平台
- 图形学
- 前端
【免费下载链接】engine
The Flutter engine
Flow 是 Flutter 引擎中负责"合成(compositing)"的核心模块。它的职责非常聚焦:基于 Skia 缓存已经记录的绘制命令(recorded paint commands)以及由这些记录生成的像素(pixels),并运行在独立的 raster 线程上,把合成结果上传给 Skia 完成最终渲染。本文以仓库中 flow/README.md 的定义为骨架,深入 flow 目录的源码实现,讲解 Flow 的整体架构、两阶段渲染流水线(Preroll / Paint)、RasterCache 缓存机制与帧级脏区域(frame damage)优化,帮助你从引擎源码层面理解 Flutter 每一帧"从 layer tree 到屏幕像素"的完整路径。
Flow 在 Flutter 引擎中的定位
flow/README.md 对 Flow 给出了精确而简洁的定义:
Flow is a simple compositor based on Skia that the Flutter engine uses to cache recorded paint commands and pixels generated from those recordings. Flow runs on the raster thread and uploads information to Skia.
翻译过来就是三层含义:
- 它是一个基于 Skia 的合成器(compositor):所有绘制最终都落到 Skia(或 Impeller 后端)的 canvas 上,Flow 本身不负责底层图形 API 调用。
- 它的核心能力是缓存:既缓存"记录的绘制命令"(即 DisplayList / picture),也缓存"由这些记录生成的像素"(即离屏栅格化后的位图),从而避免每一帧都重复执行昂贵的绘制指令。
- 它运行在 raster 线程上:与 UI 线程解耦,UI 线程只负责构建 layer tree,真正的栅格化与合成在 raster 线程完成。
从工程目录看,flow/BUILD.gn 中定义了一个名为flow的 source_set,包含 compositor_context.cc、diff_context.cc、embedded_views.cc、frame_timings.cc、raster_cache.cc、stopwatch.cc 以及layers/子目录下三十余个 Layer 实现文件,这就是 Flow 模块的全部构成。
两阶段渲染模型:Preroll 与 Paint
Flow 对每一帧的渲染被划分为两个阶段,对应的核心数据结构是 LayerTree(持有根 Layer 与物理像素尺寸frame_size)和 Layer(所有图层类型的抽象基类)。
Preroll:布局与缓存决策阶段
LayerTree::Preroll 负责在整棵树上做一次预遍历:
bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame, bool ignore_raster_cache, DlRect cull_rect) { ... PrerollContext context = { .raster_cache = ignore_raster_cache ? nullptr : &frame.context().raster_cache(), .gr_context = frame.gr_context(), .view_embedder = frame.view_embedder(), .state_stack = state_stack, .dst_color_space = sk_ref_sp<SkColorSpace>(color_space), .surface_needs_readback = false, .raster_time = frame.context().raster_time(), .ui_time = frame.context().ui_time(), .texture_registry = frame.context().texture_registry(), .raster_cached_entries = &raster_cache_items_, }; root_layer_->Preroll(&context); return context.surface_needs_readback; }从 PrerollContext 的定义可以看出,Preroll 阶段完成以下工作:
- 把
RasterCache注入到上下文,供各 Layer 决定是否、以及如何参与缓存(记录到raster_cached_entries); - 通过
LayerStateStack维护变换、裁剪等状态,并为每个 Layer 计算paint_bounds(绘制边界); - 汇总整棵树是否"需要 readback"(如某些滤镜/平台视图需要从根 surface 读回数据),返回给调用方决定是否需要 saveLayer;
- 统计
has_platform_view、has_texture_layer等标记,供后续合成策略使用。
Paint:真正的栅格化阶段
LayerTree::Paint 在 Preroll 之后执行,它先做两件与缓存相关的事,再真正绘制:
if (cache) { cache->EvictUnusedCacheEntries(); TryToRasterCache(raster_cache_items_, &context, ignore_raster_cache); } if (root_layer_->needs_painting(context)) { root_layer_->Paint(context); }即:先驱逐本帧不再使用的缓存条目,再尝试为需要缓存的条目生成缓存图,最后递归调用各 Layer 的 Paint。needs_painting结合paint_bounds与裁剪栈做绘制剔除,见 layer.h 中Layer::needs_painting。
LayerTree还提供 Flatten 方法,可将整棵树在不使用 raster cache 的情况下铺平成单个DisplayList,用于需要把场景导出为记录的场景(例如测试或部分嵌入场景)。
CompositorContext 与帧生命周期
CompositorContext 是 Flow 的"合成上下文",它持有一帧合成所需的所有共享状态:
RasterCache raster_cache_(非 SLIMPELLER 构建下启用)TextureRegistry(外部纹理注册表,供 TextureLayer 使用)- 两把
Stopwatch:raster_time_与ui_time_,用于计时与性能统计
ScopedFrame:一帧的 RAII 生命周期
每一帧的合成通过CompositorContext::AcquireFrame取得一个 ScopedFrame,构造时调用BeginFrame启动raster_time_计时,析构时调用EndFrame停止计时,天然保证帧生命周期的成对性。
ScopedFrame::Raster是帧栅格化的总入口,compositor_context.cc 中它的执行顺序是:
- 若有
FrameDamage,先计算本帧的裁剪矩形clip_rect(部分重绘); - 调用
layer_tree.Preroll(...),传入裁剪矩形或kGiantRect; - 询问
ExternalViewEmbedder的PostPrerollAction,视结果决定kResubmit(Android 上 FlutterImageView 场景需重提帧)还是kSkipAndRetry(等待 raster/platform 线程合并,见 compositor_context.h 中RasterStatus枚举的注释); - 根据后端选择
PaintLayerTreeSkia或PaintLayerTreeImpeller; - 返回
RasterStatus::kSuccess。
值得注意的细节:在Raster中,如果 Impeller 后端启用(aiks_context_非空),还会调用ShouldPerformPartialRepaint判断是否值得做部分重绘——由于 Impeller 的部分重绘需要额外的 resolve 纹理与最终 blit,当脏区域宽度或高度占比超过阈值kImpellerRepaintRatio = 0.7f时放弃部分重绘,见 compositor_context.cc。
帧损伤(FrameDamage)与脏区域计算
FrameDamage 是 Flutter 实现"只重绘变化区域"的关键。它持有上一帧的 LayerTree,通过ComputeClipRect调用 DiffContext 对比新旧两棵树(Layer::Diff/Layer::IsReplacing/PreservePaintRegion),计算出frame_damage与buffer_damage,再叠加外部提供的additional_damage(多缓冲累积损伤),最终得到本帧实际需要重绘的矩形。Reset()则用于通知客户端本帧不要做部分重绘(例如 Impeller 认为损伤过大时)。
RasterCache:命令缓存与像素缓存
缓存是 Flow 的灵魂。RasterCache 用「既缓存绘制命令、又缓存栅格化像素」的双重策略避免重复绘制,其生命周期在 raster_cache.h 的类注释中有完整描述:
- Preroll 阶段:
RasterCacheItem::PrerollSetup把候选缓存项加入PrerollContext::raster_cached_entries;RasterCacheItem::PrerollFinalize在层 Preroll 结束时把条目标记为"本帧已遇到"。 - Paint 阶段:
RasterCache::EvictUnusedCacheEntries驱逐不再使用的缓存;LayerTree::TryToPrepareRasterCache为缺失的缓存条目生成图像;各层在 Paint 时若命中缓存则直接RasterCache::Draw绘制缓存图;RasterCache::EndFrame汇总命中数与内存占用,上报指标(RasterCacheMetrics,含eviction_count、in_use_count、in_use_bytes等)。
命中条件与访问阈值
缓存并不是无条件生效的。RasterCache构造函数默认access_threshold = 3(raster_cache.h),即一个条目需要被连续"看见/命中"足够次数才真正生成缓存图,避免对一帧即变的动态内容做无谓缓存。此外 RasterCacheUtil 定义了:
kDefaultPictureAndDisplayListCacheLimitPerFrame = 3:每帧最多新生成 3 张 picture/DisplayList 缓存,把缓存生成的开销分摊到多帧,防止某一帧因生成大量缓存而 jank(见GenerateNewCacheInThisFrame的实现逻辑);kMinimumRendersBeforeCachingFilterLayer = 3:ImageFilterLayer 需要连续稳定渲染若干帧后,才从"缓存其子层"切换为"缓存过滤后的输出"。
缓存键与像素对齐
缓存以RasterCacheKey为键(raster_cache_key.h),由RasterCacheKeyID(唯一 ID + 类型kLayer/kDisplayList/kLayerChildren)+ 变换矩阵组成。矩阵中只保留小数部分的平移量(matrix_[kMTransX] = 0后再取SkScalarFraction),配合RasterCacheUtil::GetIntegralTransCTM将平移分量吸附到整数像素,确保缓存纹理与物理像素精确对齐,避免缓存启用前后出现半像素偏移导致视觉抖动。
图层级与命令级缓存
- DisplayList(命令)级:
DisplayListLayer以其 DisplayList 的unique_id为键(display_list_layer.h),通过DisplayListRasterCacheItem把绘制命令缓存成位图; - Layer(图层)级:普通图层以
unique_id为键(layer.h 中Layer::caching_key_id),通过LayerRasterCacheItem实现;kLayerChildren类型则用于 ImageFilterLayer 等场景——当过滤层自身不稳定时,缓存其子层而不是过滤后的输出。
LayerTree::TryToRasterCache(layer_tree.cc)实现了父子条目的协同:如果父条目成功缓存,则跳过其所有子条目的缓存生成;若父条目缓存失败,则继续尝试子条目,保证缓存层级不重叠、不浪费。
图层类型与合成能力
flow/layers 目录下的三十余个 Layer 类型覆盖了 Flutter 场景的绝大部分合成需求,按能力可分为几类:
| 类别 | 代表类型 | 用途 |
|---|---|---|
| 容器/变换 | ContainerLayer、TransformLayer、OpacityLayer | 组织子树、应用变换与透明度 |
| 裁剪 | ClipRectLayer、ClipRRectLayer、ClipPathLayer | 矩形/圆角/路径裁剪 |
| 特效 | ColorFilterLayer、ImageFilterLayer、BackdropFilterLayer、ShaderMaskLayer | 颜色/图像/背景滤镜与着色器遮罩 |
| 内容 | DisplayListLayer、TextureLayer、PlatformViewLayer | 绘制命令、外部纹理、平台视图嵌入 |
| 辅助 | PerformanceOverlayLayer、OffscreenSurfaceLayer | 性能浮层、离屏 surface |
Layer 还通过renderable_state_flags机制(layer.h)声明自己能独立处理哪些状态属性(透明度、ColorFilter、ImageFilter),容器层对子层做交集计算,尽量让属性下放到叶子层自身渲染,减少不必要的 saveLayer。Clip枚举则与 Dart 侧painting.dart保持一一对应(代码注释明确要求"exact copy"),保证了框架层与引擎层的语义一致。
线程模型与外部视图嵌入
Flow 的"raster 线程"定位在 README 中即有说明。CompositorContext的AcquireFrame接受fml::RefPtr<fml::RasterThreadMerger>(compositor_context.h),用于 raster 线程与 platform 线程的合并/分离管理;而 EmbeddedViews 中的ExternalViewEmbedder接口则让平台视图(Android 的 FlutterView、iOS 的 UIView)可以插入到合成流程中。在 iOS 等场景下,遇到平台视图时 Flow 会切换 canvas,并把LayerStateStack累积的所有状态变更重放到新 canvas 上(见 layer.h 中PaintContext的注释),保证平台视图两侧的绘制状态完全一致。
性能观测与测试验证
Flow 内置了完整的性能观测设施:CompositorContext的raster_time_/ui_time_两把 Stopwatch 分别统计栅格化与 UI 阶段的耗时,PerformanceOverlayLayer负责把它们可视化到屏幕上。此外 frame_timings.cc 提供帧级时间戳记录,供 DevTools 等工具分析帧节奏。
测试方面,Flow 有完备的单元测试覆盖,分散在各文件对应的*_unittests.cc中,例如:
- raster_cache_unittests.cc:验证缓存的命中、驱逐、内存统计与
access_threshold行为; - diff_context_unittests.cc 与 layer_tree_unittests.cc:验证脏区域计算与整树 Preroll/Paint 流程;
- flow_run_all_unittests.cc:Flow 模块测试的统一入口,通过
flutter/testing的测试框架运行(构建配置见 flow/BUILD.gn 中的flow_unittests目标)。
这些测试连同stopwatch_unittests.cc、embedded_view_params_unittests.cc等,共同保证了 Flow 合成流水线的正确性与稳定性。
小结
Flow 作为 Flutter 引擎中基于 Skia 的轻量合成器,用两阶段(Preroll / Paint)遍历 + RasterCache 双重缓存 + 帧损伤计算,把"记录绘制命令"与"复用栅格化像素"这两件事做到了极致:命令缓存让静态场景免于重复构建绘制指令,像素缓存让复杂图层免于重复栅格化,脏区域机制让每次提交只重绘变化的部分。理解 flow 目录的代码组织与数据流,也就理解了 Flutter 每一帧在 raster 线程上"从 layer tree 到像素"的完整旅程。
- 跨平台
- 图形学
- 前端
【免费下载链接】engine
The Flutter engine
相关推荐
deck.gl CARTO RasterTileLayer 实战:基于 Quadbin 的栅格瓦片可视化图层解析
deck.gl CARTO RasterTileLayer 实战:基于 Quadbin 的栅格瓦片可视化图层解析 RasterTileLayer 是 deck.
前端数据可视化3D渲染图形学OpenLayers 10.2.0 版本解读:Flow 粒子流图层、WMS 1.1.1 解析与瓦片缓存优化
OpenLayers 10.2.0 版本解读:Flow 粒子流图层、WMS 1.1.1 解析与瓦片缓存优化 10.2.0 是 OpenLayers 在 WebG
前端GIS数据可视化Docker构建缓存优化:利用分层缓存加速CI/CD流水线
Docker构建缓存优化:利用分层缓存加速CI/CD流水线 你是否遇到过这样的情况:每次修改Dockerfile中的一行代码,整个构建过程就需要重新下载依赖、编
云原生容器运行时虚拟化容器编排
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考