移动设备图形渲染原理
本文最后更新于 2021年5月12日 晚上
大部分手机应用程序都会拥有用户界面(UI), 用户看到的实际是显示在屏幕上的”图片”, 这些”图片”由若干像素组成, 图片在高速变化时, 就形成动画. 当用户触摸某个按钮时, 实际是由设备发送手指在屏幕上触摸的坐标.
显示在屏幕上的”图片”要进行更新, 需要外部事件驱动, 这些事件包括:
- 用户手势(触摸)
- 各类传感器(光线/方向/位置等)
- 网络(network)通信
- 时间(定时器, 比如动画)
图片被更新后, 显示器如何知道它更新了呢? 显示设备以固定速率(60Hz 或更精确的 59.97Hz)从 GPU 帧缓存中获取帧, 帧即显示在屏幕上的”图片”描述, 从而完成显示.
图像显示原理
在 iOS 设备上, 使用双缓冲+vSync 的方式进行显示, 在安卓 4.1 之后, 使用三缓冲+vSync 方式.
什么是 vSync ? 双缓冲是如何工作的呢? 我们接着往下看.
vSync 指的是垂直同步. 当显示器准备取下一帧时(不严谨的说法), 它会发送一个 vSync 信号到设备, 这个信号最终会被 CPU 拿到, 从而触发视图布局/渲染流程计算, 计算结果被发送给 GPU, GPU 完成光栅化等操作, 最后将计算好的像素数据送到显示缓存, 从而实现图像显示.
在双缓冲环境下, 这样的同步又是另外一种情况了.
vSync 同步信号工作机制简述
当前在 iOS 或安卓系统下, 均使用 vSync 作为显示同步信号, 对 CPU/GPU/显示器三者的工作进行同步.
就 Flutter 框架而言, 当显示器需要获取下一帧时, 它会发送一个 vSync 信号, 这个信号会被交给引擎, 通过引擎驱动 Framework 层执行相关的布局/渲染/合成工作(CPU 侧), 当这些工作完成后, 将数据打包为”场景”并发送给 GPU 侧(_window.render(scene)
, 引擎端负责后续处理), 由 GPU 完成这一帧的后续计算, 并将结果发送到帧缓存. 如下引用一张经典的图例:
帧缓存和显示器
显示器从帧缓存取得要显示的”像素数据”. 对拥有两个帧缓存的系统而言, 工作流程如下所示:
可以看到, 前后缓冲区交替变换, 这样显示器从两个帧缓存交替取显示数据, 而 GPU 每次都向下一个需要读取的缓存发送数据(这里的后缓冲区), 这样就可以保证帧数据的连续.
(Flutter 平台 CPU 侧的帧数据处理代码在 RendererBinding
中的 drawFrame
方法中, 即 rendering pipeline)
掉帧的根本原因
通过开启 vSync, 显示设备的取帧速率是固定的, 那为什么会有掉帧的情况呢?
答案就是: 当开启 vSync 垂直同步后, GPU 的帧更新(生成)速率(Frame rate)不足以达到显示设备的取帧速率, 造成若干次取帧时取到的是之前的帧, 从而就造成画面”卡”在那里(帧的连续变化不协调), 从而感受到的就是掉帧.
因此, 要解决掉帧, 就要从每一帧减少 CPU 的处理时间和减少 GPU 的处理时间入手.
Flutter 框架(或者说任何 UI 框架)的基本任务就是合成显示在屏幕上的帧的数据信息并交给 GPU, 同时处理外部事件交互.
并且 Flutter Framework 中的代码几乎全部是由Flutter Engine 帧渲染驱动的, 这个之后会结合代码说明.