Ray 的记录站

日常开发实践记录

0%

移动设备图形渲染原理

大部分手机应用程序都会拥有用户界面(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 帧渲染驱动的, 这个之后会结合代码说明.