iOS界面渲染机制

Core Animation Pipeline

Core Animation是iOS和Mac OS界面渲染的核心组件,其实动画仅仅算是本身的部分功能,所以这个这个组件的名字不太贴切。桌面和移动端的GPU性能差别很大,所以两端的组件底层细节估计会不一样,这个主要是讲iOS系统上的Core Animation的工作流程。

当你把一个View提交到视图树上并写好约束以后,系统会先进行layot确定其在屏幕上的位置,然后根据是否重载draw方法来调用draw函数(如果重写了就调用),并把绘制结果转换成位图保存起来,再把视图中的图片等数据解压成bitmap或者做一些变换,最后再按照视图树递归式把树形结构打包成数据模型发送给渲染进程,整个过程称之为Commit Transaction。

渲染进程收到IPC调用以后把收到的数据还原成树形结构,经过一些处理逻辑后,调用硬件的绘制方法完成绘制。整个流程如下图所示:

Commit Transaction 主要有4个过程:

  1. Layout

    在这个阶段会按照视图树调用view的layoutSubviews方法,以及数据库查询、一些业务逻辑计算等。这个阶段一般是CPU或IO密集型操作。

  2. Draw

    调用drawRect: 方法来绘图,以及绘制文本,一般是CPU或者内存密集型操作。

  3. Prepare

    做一些提交前的准备工作,如图片解码(如果是压缩图片的话,会解压成bitmap),或者图片格式转换。

  4. Commit

    把layer tree打包成数据模型发送渲染进程,这个过程是递归进行的,所以如果layer tree比较复杂的话,这个过程会很花时间。

Animation

iOS动画的执行需要先了解iOS中的视图树和呈现树。视图树存储的就是视图的树形结构,当的执行下面类似的代码时

1
2
3
[UIView animateWithDuration:1 animations:^{
view.frame = CGRectMake(new, new, new, new);
}];

对视图树的修改会立即生效,设置了新的frame后马上再读发现值已经是最新的了,视图树始终存储着是最终的值。反之呈现树就是当前view的数值,当在做一段动画效果时,可以通过self.view.layer.presentationLayer来获取呈现树的值。它的值会随着动画的执行而变化。

当给一个View添加动画时,可以分为3个阶段:

  1. 创建动画效果,一般就是用UIView的block方法来创建隐式动画
  2. 向渲染进程提交动画请求,这个过程和前面的Core Animation Pipeline一样
  3. 渲染进程解析出动画对象,根据持续时间和效果插值绘制对应的动画效果

渲染流程

iOS的GPU以前是用的Imagination的PowerVR架构,后来和Imagination闹掰,但是GPU架构基本上还是沿袭了PowerVR那一套的设计。PowerVR的看家本领就是Tile Based Deferred Rendering(TBDR),字面意义就是分片式延迟渲染。

这种渲染方式是先把屏幕分割成一定大小的独立区域(例如32px * 32px),如图3-1所示,这样GPU每次仅仅处理一个小块,因为移动GPU的总线带宽有限,所以分解成多块能减轻带宽压力。

在TBDR的渲染流水线里,几何阶段生成(已经经过裁剪,屏幕外的部分已经被丢弃)的多边形或者说三角形参数信息都存放在一个名为Parameter Buffer缓存里(这个缓存在soc芯片上,类似于桌面cpu的L1、L2缓存),理论上里面保存的应该是同一帧画面里的所有三角形信息,为了提高能效,GPU会对所有三角形运行HSR算法(Hidden Surface Removal,隐面消除,这里的隐面是指被其他实体多边形遮盖的多边形)来消除被挡住的部分,最终只渲染屏幕上能看到的三角形所覆盖的片元。

这一步是TBDR独有的,相较之下,传统的GPU在几何阶段扔出到三角形后纹理单元/着色单元就马上渲染,因此人们将传统的GPU渲染方式称作立即渲染器。把上面的加入了HSR逻辑的叫做延迟渲染。

​ 图3-1 图3-2 图3-3

得到屏幕上能看到的三角形以后,会把这些数据发送到后续的光栅化流程中去,在经过后续的处理后GPU就绘制出了对应的像素,最后输出到frame buffer中。

复杂效果的渲染

在上一节说的是简单效果的渲染,如果效果中有遮罩,毛玻璃等较复杂的效果,则需要经过多次简单渲染后才能达到最终效果。

  • 遮罩效果

    遮罩效果是经过3次简单渲染流程合成的,第一步先渲染出遮罩层;接着再渲染出类容,然后最后一步是把前两步的结果再合并起来,得到最终结果。这里绘制的图像并不是直接绘制到frame buffer中,所以也叫做离屏渲染。在实际开发中,经常会用到圆角,一般是设置cornerRadiusmasksToBounds两个属性,其实这两个属性都设置时就是在layer上添加了一个遮罩效果,所以也会触发离屏渲染。

    不过离屏渲染并不是总是低效的,这里是GPU用硬件来离屏渲染,所以速度仍然很快。如果重载draw方法来绘图,这种离屏渲染是CPU来执行,而且渲染后还要再发送到GPU中处理,效率就比前者低很多。

  • 毛玻璃效果

    毛玻璃效果的主要流程和遮罩差不多,就是流程会多很多步,最终效果需要由5个中间步骤合成。所以毛玻璃效果对硬件的要求比较高。

参考

移动图形芯片的故事(上)GPU是什么鬼?

移动图形芯片的故事(下)IMR与TBR/TBDR两大流派的爱恨情仇

A look at the PowerVR graphics architecture: Tile-based rendering

A look at the PowerVR graphics architecture: Deferred rendering

Dialling it up on PowerVR GPUs: how to optimise automotive dashboards for efficient rendering