17

Flutter UI 渲染浅析(一)总览

 3 years ago
source link: http://w4lle.com/2020/11/09/flutter-ui-overview/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Flutter UI 渲染浅析(一)总览

Flutter UI 渲染系列文章,基于 Flutter v1.20.4

我们知道屏幕显示的基本单位是像素,每个像素点显示不同的颜色值,按一定规则排列就形成了图像。

显示器按一定频率从 GPU 获取数据,就可以完成图像的更新。现在手机屏幕一般有60HZ、90HZ、120HZ,以60HZ为例,屏幕每秒会发出 60 个 Vsync 垂直同步信号。

Vsync 信号用于协调显示器、CPU 和 GPU 的工作。

GPU 每秒可以绘制的帧数叫做帧速率,如果帧速率大于屏幕刷新率,屏幕上显示的内容就有可能是两个图像的不完全内容,造成图像撕裂。Vsync 垂直信号可以保证刷新频率的统一。

在 Vsync 信号的调度下,UI 系统把逻辑代码变成图像显示到屏幕上,一般需要经过以下几个阶段:

  1. View,构建 View/DOM 节点
  2. Layout,计算样式(Style)、布局(Layout)
  3. ViewTree,将 View 节点按一定规则归属到不同的图层,构建或更新 ViewTree
  4. Paint,绘制,输出绘制指令到 DisplayList
  5. Rasterization,光栅化,执行 DisplayList 中的绘图指令,生成图层区域的像素数据
  6. Composite,合成,把各图层光栅化后的数据进行叠加和特性处理,输出到屏幕上

简单来说就是做了两件事情:

  1. 构建 UI 描述规则,用于构建或刷新图像,并把绘制指令保存起来用于绘制,即 1-4
  2. 光栅化和合成,对硬件绘制 API 做了统一封装,屏蔽底层绘制细节,即 5-6

Android 和 iOS 基本都是按照这个流程来构建UI的。

以 Android 为例,Android 通过 XML 来描述 UI 结构,用 DisplayList 保存绘制指令信息,通过 Skia 封装底层细节实现光栅化合成。

Flutter 作为一款跨端 UI 开发框架,它的渲染流程也是类似的。

这个系列的文章主要分析下 Flutter UI 渲染流程,涉及到的中间过程会分部解析,基于 Flutter v1.20.4 版本源码,主要分为以下几个部分:

  1. 总览
  2. Vsync 注册
  3. Animate 及动画实现原理
  4. Build
  5. Layout
  6. Paint & RepaintBoundary
  7. CompositeFrame
  8. RasterThread 光栅化及合成

Flutter 架构

ruyQF3M.png!mobile

Flutter 架构分为三层:Dart Framework、C++ Engine、 Platform Embedder。

Dart Framework 提供了响应式的开发框架,使用 Dart 开发,它对渲染逻辑做了统一封装,屏蔽了底层实现,对底层 C++ Engine 提供双向通信能力,开发者只需要组合 Widgets 用于构建 App 视图即可。

  • 最底层的 Foundation 层提供一些最基础的抽象类或定义,基于此, Animation 动画、Painting 绘制、Gestures 手势等构建出通用抽象能力
  • Rendering 层,构建出渲染树 Render Tree,也即 RenderObject Tree,用于绘制具体,RenderObject 会自动随着数据改变而动态改变
  • Widgets 层,提供了一套非常丰富的 Widget 组件库,用于构建 Widgets Tree 和 Element Tree,这是响应式编程的基础实现,每一个 RenderObject 都有一个对应的 Widget 及 Element
  • Materail 层和 Cupertino 层使用 Widgets 组件库,构建 Android Materail 或者 iOS Cupertino 风格的应用视图,开发者基于这些 Widgets 即可构建出效果一致的跨端应用

C++ Engine 是 Flutter 的核心部分,大部分使用 C++ 开发,它的主要职责是光栅化合成上屏用于显示绘制内容,同时它也提供低层次的核心能力,比如Skia图形化绘制(graphics)、TextLayout、文件系统、网络 I/O、无障碍支持、插件体系、Dart运行时(DartVM)和 GC、编译链。

Engine 层对 App 层暴露 dart:ui 包, dart:ui 包是 Flutter App 的构建基础,其中的 dart 类对 C++ Engine 层中的实现类做了包装,它提供了基础能力,诸如交互系统、图形图像处理、渲染子系统等。

其中最重要的一个类是包下的 Window ,它向上提供了最核心的一些服务,比如任务Scheduler API、绘制 API、输入事件响应等等。

Platform Embedder 是平台嵌入层,把 Flutte 代码打包嵌入到具体的实现平台,提供运行入口,并对上层提供最基础的能力,比如提供渲染画布、插件系统、无障碍、交互管理、消息循环管理等。

Flutter 分层架构使得平台相关性大大降低。

Dart Framework 对上提供统一的基于 dart 的响应式 UI 描述框架。

C++ Engine 向下对 Skia 绘制引擎对下统一封装,屏蔽了平台实现。

ReactNative / Weex 作为跨端框架 ,虽然走的也是是原生渲染,但是需要通过 js 来组织和描述 UI,中间需要做一层转换,才可以变成原生的 UI 描述结构,进而原生渲染。

而 Flutter 自建渲染引擎,不依赖平台实现,并且 dart 可以直接被编译成机器码,从架构上来说,性能相比 ReactNative / Weex 会更好。

Flutter 渲染管线

Flutter 渲染管线的设计是类似的,流程参考下图

EJN3EbE.png!mobile

首先看下用到的线程:

UIThread 是 Platform 创建的子线程,DartVM Root Isolate 所有的 dart 代码都运行在该线程。

阻塞UIThread 会直接导致 Flutter 应用卡顿掉帧、

RasterThread 原本叫做 GPUThread,也是 Platform 创建的子线程,由于很多人误认为运行在 GPU 上,但其实它是运行在 CPU 用于处理数据提交给 GPU,所以 Flutter 团队将其名字改为 Raster,表明它的作用是光栅化。

C++ Engine 中的光栅化和合成过程运行在该线程。

整个流程会经过以下几个过程:

  • C++ Engine 触发 Platform 注册 Vsync 垂直信号回调,通过 Platform -> C++ Engine -> Dart Framework 触发整个绘制流程
  • Dart Framework 构建出四棵树,Widget Tree、Element Tree、RenderObject Tree、Layer Tree,布局、记录绘制区域及绘制指令信息,并保存在 Scene 对象用以光栅化,这个过程运行在 UIThread
  • 通过 Flutter 自建引擎 Skia 进行光栅化和合成操作, 将 LayerTree 转换为 GPU 指令,并发送给 GPU 完成光栅化合成上屏显示操作,这个过程执行在 RasterThread

整个过程是生产者消费者模型。

UIThread 负责生产 Layer Tree,RasterThread 负责消费 Layer Tree。

在 Engine Pipeline 中通过深度为 2 的两个信号量 empty_available_ 控制整个流程。

  • 当 Vsync 信号到来,触发 UIThread 开始工作时, _empty -1,开始生成 LayerTree ;

  • 当 UIThread 准备好保存了 Engine LayerTree 的 Scene 对象, available_ + 1

  • 触发 Raster Thread 开始工作, available_ - 1;

  • 当 RasterThread 处理完光栅化合成上屏后, _empty + 1,使得 UIThread 继续接收 Vsync 信号,继续触发图形化流程。

  • _empty 信号量为 0 时,代表工作任务已满,就会延迟 UIThread 生成 Layer Tree 的任务,直到下一次 Vsync 信息到来。

这种调度机制可以确保 RasterThread 不至于过载(2个任务)引起图像撕裂,同时也可以避免 UIThread 不必要的资源消耗。

所以不论在 UIThread 还是在 RasterThread 耗时太久,都会导致 Flutter 应用卡顿,因为会导致延迟接受 Vsync 信号,导致掉帧。

这个调度过程会在下一篇文章中详细分析。

Flutter UI 绘制管线

其中在 UIThread 生成Layer Tree的过程,我们将其称为 Rendering Pipeline 渲染管线。

AzUBnuB.png!mobile

主要过程为:

  • Animate,触发动画更新下一帧的值
  • Build,触发构建或刷新 Widget Tree、Element Tree、RenderObject Tree
  • Layout,触发布局操作,确定布局大小和位置信息
  • CompositeBits,更新需要合成的 Layer 层标记
  • Paint,触发 RenderObject Tree 的绘制操作,构建 Layer Tree
  • Composite,触发 Layer Tree 发送到 Engine,生成 Engine LayerTree

在 UIThread 构建出四棵树,并在 Engine 生成 Scene,最后提交给 RasterThread,对 LayerTree 做光栅化合成上屏。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK