Flutter Engine 绘制流程源码解析

记得刚工作那会,有幸基于Flutter Engine做二次开发,彼时的我刚出校园,没有接触过如此复杂的框架,只能如同蚕虫一点一点啃食桑叶一样,慢慢地阅读源码。如今已转业务做其他工作,做下记录,纪念当时的艰难,以及后续如果重回Flutter怀抱时能快速拾起。

Flutter 分为Framework仓库和Engine仓库,Framework仓库基于Dart语言,封装了很多基本Widget,方便快速搭建优美的界面,Engine为Framework提供了丰富和高性能的绘图能力。Engine的高性能绘图能力一方面基于Skia库提供的绘图功能,另一方面也得益于Engine layer层的设计,避免了不必要的重绘。本文会通过阅读源码来分析整个绘图流程,为了方便理解,会通过一个简单demo作为输入进行分析

通过本文,你能了解到:

  • Flutter Engine整体绘制流程
  • Engine layer层的设计
  • 如何减少不必要的重绘

下文源码来自Engine仓库,git版本:src/flutter: (HEAD detached at 992cdb6cd4)

layer简介

请注意,因为framework有很多与engine一一对应的对象,名称也一样。 如果没有额外说明,下文所指对象都为engine对象

在flutter框架的设计中,framework的所有界面绘制都会通过构造一个layer tree传递给engine,engine再通过操作layer tree调用Skia的相关api进行绘制显示,layer tree是layer的集合,layer是图层的意思,代表了绘图的基本操作单位。下面是layer的家族(不全,只列出下文会提及到的)
在这里插入图片描述

ContainerLayer

容器layer,此Layer允许有子layer,原因是它有成员变量std::vector<std::shared_ptr> layers_

TransformLayer

继承于ContainerLayer,但额外带了transform信息,即平移(x,y), 从它的构造函数可以看到需要提供SkMatrix对象

class TransformLayer : public ContainerLayer {
   
 public:
  explicit TransformLayer(const SkMatrix& transform);
 }

DisplayListLayer

稍微比较复杂,它表示了一系列绘图操作集合。举个例子:

// main.dart
Picture drawRectToPicture(Offset offset,Size size){
   
  PictureRecorder recorder = PictureRecorder();
  Canvas canvas = Canvas(recorder);

  // 画一个300 x 300 的矩形
  canvas.drawRect( offset & size, Paint()..color = Colors.green);
  
  // 再画一个300 x 300 的矩形
  canvas.drawRect( offset & size, Paint()..color = Colors.green);

  return recorder.endRecording();
}

drawRectToPicture函数返回了一个Dart对象Picture,这个Picture里面有两个三角形,阅读代码会发现Picture并不是对应一张图片,即它并没有生成bitmap,而是持有一个成员变量DisplayListLayer(engine对象),这个DisplayListLayer有一块内存保存了绘图信息,读取它会解析出如下绘制指令:

  1. op(rect) ,msg(x y width height)
  2. op(rect) ,msg(x y width height)

后续真正的渲染上屏操作会通过上述指令进行实际的绘图操作,当然,这些都是在engine里做的

Layer Demo

下面是用Dart编写的demo,通过直接操作SceneBuilder手动构造一个layer tree,并触发绘制。下面的demo生成的layer tree如下(有层级结构,低层级是上一个层级的子layer):

|---ContainerLayer(ps:根layer)
|------TransformLayer (ps:保存了平移(50,50)的操作,由sceneBuilder.pushOffset(50,50)添加)
|----------DispalyListLayer (ps:保存了矩形信息,sceneBuilder.addPicture(Offset.zero, picture)调用时添加)
 // main.dart
 void beginFrame(){
   
   PictureRecorder recorder = PictureRecorder();
   Canvas canvas = Canvas(recorder);

   // canvas绘制
   // 从300,300坐标开始绘制
   Offset offset = Offset(300, 300);
   // 绘制区域是300x300的区域
   Size size = Size(300, 300);
   canvas.drawRect(offset & size, Paint()..color = Colors.green);

   // 通过recorder.endRecording结束节点绘制并返回一个Picture
   Picture picture = recorder.endRecording();

   // 三、初始化一个SceneBuilder
   SceneBuilder sceneBuilder = SceneBuilder();
   // 通过SceneBuilder上的方法将上诉canvas生成的Picture添加到engine
   sceneBuilder.pushOffset(50, 50);
   sceneBuilder.addPicture(Offset.zero, picture);
   sceneBuilder.pop();
   // 四、通过sceneBuilder.build生成scene
   Scene scene = sceneBuilder.build();
   window.render(scene);

 }
 
 void main() {
   
  window.onDrawFrame = beginFrame;

  // 触发一个VSync信号,在下一帧触发onDrawFrame回调
  window.scheduleFrame();

}

代码解析:

  1. 生成一个Picture对象,此Picture会绘制一个(300,300,600,600) 的绿色矩形
  2. 初始化SceneBuilder 对象,初始化时会给初始化一个根layer,是ContainerLayer类型,后称root layer
  3. 调用sceneBuilder.pushOffset(50, 50),为root layer加入一个子layer:TransformLayer
  4. 调用sceneBuilder.addPicture(Offset.zero, picture),为TransformLayer加入一个子layer:DispalyListLayer
  5. 调用sceneBuilder.build() 生成一个Scene对象,此时本质上是将root layer作为参数构造一个Scene对象,Scene构造时创建一个LayerTree(看src/flutter/lib/ui/compositing/scene.cc)
// src/flutter/lib/ui/compositing/scene_builder.cc

void SceneBuilder::build(Dart_Handle scene_handle) {
   
  FML_DCHECK(layer_stack_.size() >= 1);

  // layer_stack_[0] 是 root layer
  Scene::create(
      scene_handle, std::move(layer_stack_[0]), rasterizer_tracing_threshold_,
      checkerboard_raster_cache_images_, checkerboard_offscreen_layers_);
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值