一、Flutter 之图像绘制原理

2,932 阅读6分钟

二、Widget、Element、RenderObject

三、Flutter UI 更新流程

四、build 流程分析

五、layout 流程分析

六、Paint 绘制(1)

七、Paint 绘制(2)

八、composite 流程分析

九、Flutter 小实践

在界面上显示一个宽为 300、高为 200 矩形, 如下图所示

前端实现方式很多 (1)dom

<html>
<style type='text/css'>
    .demo {
        width: 300px;
        height: 200px;
        background: #f00;
    }
</style>
<body>
    <div class='demo'></div>
</body>
</html>

html文本描述了页面应该有哪些功能,css告诉浏览器该长什么样 浏览器引擎通过解析html和css,翻译成一些列的预定义UI控件 然后UI控件去调用操作系统绘图指令去绘制图像展现给用户

(2) canvas

<html>
<body>
    <script>
        var c= document.createElement("canvas");
        var ctx=c.getContext("2d");
        ctx.fillStyle="#FF0000";
        ctx.fillRect(0,0,300,200);
        document.body.append(c);
    </script>
</body>
</html>

可以通过画布绘制,如以上代码所示, 通过创建 canvas 节点,获取 canvas 绘制上下文,调用相关 API, 便可绘制一个矩形,并 append 到 body 节点中。

1、图形绘制原理

显示器(屏幕)是由一个个物理显示单元(像素点)组成,而每一个像素点可以发出多种颜色,显示器成相的原理就是在不同的物理像素点上显示不同的颜色,最终构成完整的图像。图形计算和绘制都是由相应的硬件来完成,操作系统一般屏蔽了这些底层硬件操作指令,提供一些封装后的API供操作系统之上的应用调用,但是对于应用开发者来说,直接调用这些操作系统提供的API是比较复杂和低效的,因此几乎所有用于开发GUI程序的编程语言都会在操作系统之上再封装一层,将操作系统原生API封装在一个编程框架和模型中,然后定义一种简单的开发规则来开发GUI应用程序,而这一层抽象,就是所谓的“UI”系统,如Android SDK正是封装了Android操作系统API,提供了一个“UI描述文件XML+Java操作DOM”的UI系统,而iOS的UIKit 对View的抽象也是一样的,他们都将操作系统API抽象成一个基础对象(如用于2D图形绘制的Canvas),然后再定义一套规则来描述UI,如UI树结构,UI操作的单线程原则等。

2、跨端应用

Weex

上图是weex 工作流程图,WEEX框架中核心的部分是JavaScript Runtime。当JSBundle从服务器端下载完成之后,WEEX在Android、iOS和Web端会运行一个JavaScript引擎来执行JSBundle,同时向各终端的渲染层发送渲染指令,并调度客户端的渲染引擎实现视图渲染、事件绑定和处理用户交互等操作。

ReactNative

上图为 reactnative 流程架构图,和 weex 类似, reactNative 所有的标签也不是真实控件,JS 代码中所生成存的 dom,最后都是由 Native 端解析,再得到对应的Native控件渲染。

不管是 Weex 还是 ReactJS,自身是不直接绘制UI的,而是调用原生组件执行页面渲染操作。Bridges则是用来绘制指令给原生组件进行绘制

Flutter

与 React Native 和 WEEX 使用原生组件渲染界面不同,Flutter并不需要使用原生组件来渲染界面,而是使用自带的渲染引擎(Engine层)来绘制页面组件。

如下图所示,Flutter框架主要由 Framework 层和 Engine 层组成,其中 Engine 层 其以Skia作为其2D渲染引擎,有自己的UI绘制机制。

在 Flutter 中,也有 Canvas 的概念,Engine 层向 Dart 层的暴露了 Canvas, PictureRecorder 等接口,利用它们可以绘制任何自己想显示的东西~如以下代码,类似 js 中调用 canvas 类似

 void main() {
// 获取绘制图层
OffsetLayer rootLayer = new OffsetLayer();
PictureLayer pictureLayer = PictureLayer(Rect.zero);
rootLayer.append(pictureLayer);
// 绘制图形
pictureLayer.picture =
   createSolidRectanglePicture(Color(0xFFFF0000), 300, 200);
rootLayer.updateSubtreeNeedsAddToScene();
SceneBuilder sceneBuilder = SceneBuilder();
rootLayer.addToScene(sceneBuilder);
Scene scene = sceneBuilder.build();
window.onDrawFrame = () {
 // 将绘制的图层输送到GPU
 window.render(scene);
};
window.scheduleFrame();
}

简单地讲,flutter 提供一张画布、我们可以在画布上天马星空,绘制任何自己想绘制的东西

3、Vsync 更新机制

Vsync 相关概念

在计算机系统中,CPU、GPU和显示器以一种特定的方式协作:CPU将计算好的显示内容提交给 GPU,GPU渲染后放入帧缓冲区,它们是图像生产者,往帧缓冲区(BufferQueue) 不断填充数据, 显示器可以理解为消费者,从帧缓冲区取帧数据(BufferQueue)中获取数据, 负责把渲染后的内容呈现到屏幕上。

为了更新显示画面,显示器是以固定的频率刷新(从GPU取数据),比如有一部手机屏幕的刷新频率是 60Hz。当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(如VSync), 60Hz的屏幕就会一秒内发出 60次这样的信号, 这个信号是用来同步 CPU、GPU 和显示器的工作的,即提示 CPU 和 GPU 进行下一帧工作的信号, Android系统每隔16.6ms发出VSYNC信号,来通知界面进行输入、动画、绘制等动作

为什么需要 Vsync

Refresh Rate:代表了屏幕在一秒内刷新屏幕的次数,这取决于硬件的固定参数,例如60Hz Frame Rate:代表了GPU在一秒内绘制操作的帧数,例如30fps,60fps

如果没有 Vsync 机制,容易产生以下两种情况

(1)供过于求 现在的显卡通常可以将CS的帧率渲染到120以上,即120FPS。可通常我们使用的显示器只能达到60HZ的刷新率。显然,即使显卡在1秒内将画面变化了120次,但显示器只有展示其中60幅的能力。这种时候,我们感知的流畅度其实是60FPS,显卡在显示器两次刷新的一个周期内,输出了两帧画面,前一帧就会被当作无效帧丢弃、多余的帧是无效帧,对画面效果提升没有任何作用。

(2)供不应求

如果图像生成的速率低于图像消费速率,即供不应求的情况下,容易产生卡顿的现象,如上图中:

第一个 16ms:Display显示第0帧,CPU处理完第一帧后,GPU紧接其后处理继续第一帧

第二个16ms:因为早在上一个16ms时间内,第1帧已经由CPU,GPU处理完毕。故Display可以直接显示第1帧。显示没有问题。但在本16ms期间,CPU和GPU却并未及时去绘制第2帧数据,而是在本周期快结束时,CPU/GPU才去处理第2帧数据

第三个16ms: 此时Display应该显示第2帧数据,但由于CPU和GPU还没有处理完第2帧数据,故Display只能继续显示第一帧的数据,结果使得第1帧多画了一次

Vsync 机制是起到一个协调作用,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象

Flutter Vsync 流程

更多可参考 www.jianshu.com/p/75139692b…