Flutter 引擎 Flow 合成器解析:基于 Skia 的图层缓存与栅格化流水线
跨平台图形学前端【免费下载链接】engineThe Flutter engine项目地址https://gitcode.com/gh_mirrors/eng/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 与 PaintFlow 对每一帧的渲染被划分为两个阶段对应的核心数据结构是 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_spSkColorSpace(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 使用两把Stopwatchraster_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视结果决定kResubmitAndroid 上 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_entriesRasterCacheItem::PrerollFinalize在层 Preroll 结束时把条目标记为本帧已遇到。Paint 阶段RasterCache::EvictUnusedCacheEntries驱逐不再使用的缓存LayerTree::TryToPrepareRasterCache为缺失的缓存条目生成图像各层在 Paint 时若命中缓存则直接RasterCache::Draw绘制缓存图RasterCache::EndFrame汇总命中数与内存占用上报指标RasterCacheMetrics含eviction_count、in_use_count、in_use_bytes等。命中条件与访问阈值缓存并不是无条件生效的。RasterCache构造函数默认access_threshold 3raster_cache.h即一个条目需要被连续看见/命中足够次数才真正生成缓存图避免对一帧即变的动态内容做无谓缓存。此外 RasterCacheUtil 定义了kDefaultPictureAndDisplayListCacheLimitPerFrame 3每帧最多新生成 3 张 picture/DisplayList 缓存把缓存生成的开销分摊到多帧防止某一帧因生成大量缓存而 jank见GenerateNewCacheInThisFrame的实现逻辑kMinimumRendersBeforeCachingFilterLayer 3ImageFilterLayer 需要连续稳定渲染若干帧后才从缓存其子层切换为缓存过滤后的输出。缓存键与像素对齐缓存以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::TryToRasterCachelayer_tree.cc实现了父子条目的协同如果父条目成功缓存则跳过其所有子条目的缓存生成若父条目缓存失败则继续尝试子条目保证缓存层级不重叠、不浪费。图层类型与合成能力flow/layers 目录下的三十余个 Layer 类型覆盖了 Flutter 场景的绝大部分合成需求按能力可分为几类类别代表类型用途容器/变换ContainerLayer、TransformLayer、OpacityLayer组织子树、应用变换与透明度裁剪ClipRectLayer、ClipRRectLayer、ClipPathLayer矩形/圆角/路径裁剪特效ColorFilterLayer、ImageFilterLayer、BackdropFilterLayer、ShaderMaskLayer颜色/图像/背景滤镜与着色器遮罩内容DisplayListLayer、TextureLayer、PlatformViewLayer绘制命令、外部纹理、平台视图嵌入辅助PerformanceOverlayLayer、OffscreenSurfaceLayer性能浮层、离屏 surfaceLayer 还通过renderable_state_flags机制layer.h声明自己能独立处理哪些状态属性透明度、ColorFilter、ImageFilter容器层对子层做交集计算尽量让属性下放到叶子层自身渲染减少不必要的 saveLayer。Clip枚举则与 Dart 侧painting.dart保持一一对应代码注释明确要求exact copy保证了框架层与引擎层的语义一致。线程模型与外部视图嵌入Flow 的raster 线程定位在 README 中即有说明。CompositorContext的AcquireFrame接受fml::RefPtrfml::RasterThreadMergercompositor_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.ccFlow 模块测试的统一入口通过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 到像素的完整旅程。赞分享跨平台图形学前端【免费下载链接】engineThe Flutter engine项目地址https://gitcode.com/gh_mirrors/eng/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中的一行代码整个构建过程就需要重新下载依赖、编云原生容器运行时虚拟化容器编排上一篇gh_mirrors/exam/examples实战教程时序分类评估指标下一篇快速上手awesome-clothed-human10个最实用的着装人体重建项目推荐创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考