iOS图形显示基本知识
从一个像素点到真正显示在屏幕上,iOS到底在里面做了哪些工作,涉及到哪些Frameworks与Libraries?这是这一章想搞明白的问题。
1.1 图形显示原理
屏幕渲染相关 CRT(Cathode ray tube) 显示器 和 LCD(Liquid-crystal display) 显示器
可以参考文章
图像想显示到屏幕上使人肉眼可见都需借助像素的力量。简单地说,每个像素由红,绿,蓝三种颜色组成,它们密集的排布在手机屏幕上,将任何图形通过不同的色值表现出来。
计算机显示的流程大致可以描述为将图像转化为一系列像素点的排列然后打印在屏幕上,由图像转化为像素点的过程又可以称之为光栅化,就是从矢量的点线面的描述,变成像素的描述。
回溯历史,可以从过去的 CRT 显示器原理说起。CRT 的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称 HSync;而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(vertical synchronization),简称 VSync。显示器通常以固定频率进行刷新,这个刷新率就是 VSync 信号产生的频率。尽管现在的设备大都是液晶显示屏了,但原理仍然没有变。1
如在 iPhone5 的上就有1,136×640=727,040个像素,而在15寸Retain的MBP上,这一数字达到15.5百万以上,当你滚动整个屏幕的时候,数以百万计的颜色单元必须以每秒60次的速度刷新,计算量可想而知。
1.2 iOS的显示架构
从软件层面上,iOS借助Core Graohics
,Core Animation
,Core Image
完成图形的处理,它们又都是借助OpenGL ES
来完成底层的工作,其结构如下图所示:
Display 的上一层便是图形处理单元 GPU,GPU 是一个专门为图形高并发计算而量身定做的处理单元。这也是为什么它能同时更新所有的像素,并呈现到显示器上。它并发的本性让它能高效的将不同纹理合成起来。因为涉及到各种图形矩阵的计算,它跟CPU最直观的区别在于浮点计算能力要超出CPU很多。所以在开发中,我们应该尽量让CPU负责主线程的UI调动,把图形显示相关的工作交给GPU来处理,因为涉及到光栅化等一些工作时,CPU也会参与进来,这点在后面再详细描述。
GPU Driver
是直接和 GPU 交流的代码块。不同的GPU是不同的性能怪兽,但是驱动使他们在下一个层级上显示的更为统一,典型的下一层级有 OpenGL/OpenGL ES.
OpenGL
(Open Graphics Library) 是一个提供了 2D 和 3D 图形渲染的 API。GPU 是一块非常特殊的硬件,OpenGL 和 GPU 密切的工作以提高GPU的能力,并实现硬件加速渲染。
OpenGL 之上扩展出很多东西。在 iOS 上,几乎所有的东西都是通过 Core Animation 绘制出来,然而在 OS X 上,绕过 Core Animation 直接使用 Core Graphics 绘制的情况并不少见。2
在硬件层面的调度我们可以看下图所示:
计算机系统中 CPU、GPU、显示器是以上面这种方式协同工作的。CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
在最简单的情况下,帧缓冲区只有一个,这时帧缓冲区的读取和刷新都都会有比较大的效率问题。为了解决效率问题,显示系统通常会引入两个缓冲区,即双缓冲机制。在这种情况下,GPU 会预先渲染好一帧放入一个缓冲区内,让视频控制器读取,当下一帧渲染好后,GPU 会直接把视频控制器的指针指向第二个缓冲器。如此一来效率会有很大的提升。
1.3 iOS图形显示流程
我们可以再从上层看一下iOS中不同的Frameworks和Libraries之间的一些联系:
在最顶层的就是UIKit,一个在iOS中用来管理用户图形交互的Objc高级的框架,它由一系列的集合类构成,例如UIButton、UILabel,每一个都负责他们指定的UI Control角色。UIKit本身构建在一个叫Core Animation的框架之上。最后一部分是Core Graphics,曾经在Quartz(一个基于CPU的绘制引擎,在OS X系统上初次露脸)中被引入。这两个较为底层的框架都是用C语言编写的。
我们经常说到的硬件加速其实是指OpenGL,Core Animation/UIKit基于GPU之上对计算机图形合成以及绘制的实现,直到目前为止,iOS上的硬件加速能力还是大大领先与android,后者由于依赖CPU的绘制,绝大多数的动画实现都会让人感觉明显的卡顿3
CoreAnimation的渲染流程可以用下图来概括:
在GPU的渲染过程中,我们能看到顶点着色器与像素着色器参与到图像的处理。
在objc.io中有一篇文章进一步地阐明了顶点着色器与像素着色器 (GPU 加速下的图像处理)4
1.4 补充知识
1.4.1 图像多层次的合成—为何设置透明会增加GPU工作量5
合成 | Blended
在图形世界中,合成是一个描述不同位图如何放到一起来创建你最终在屏幕上看到图像的过程。
一个不透明的红色盖在蓝色上那我们看到的就是一个蓝色,但一个半透明的红色盖在蓝色让我们得到的却是一个紫色,这便是合成所要做的工作。
我们可以用下面这个公式来计算每一个像素:
R = S + D * ( 1 – Sa )
结果的颜色是源色彩(顶端纹理)+目标颜色(低一层的纹理)*(1-源颜色的透明度)。在这个公式中所有的颜色都假定已经预先乘以了他们的透明度。
假定两个纹理都完全不透明,比如 alpha=1.如果目标纹理(低一层的纹理)是蓝色(RGB=0,0,1),并且源纹理(顶层的纹理)颜色是红色(RGB=1,0,0),因为 Sa 为1,所以结果为:
R = S
如果源颜色层为50%的透明,比如 alpha=0.5,既然 alpha 组成部分需要预先乘进 RGB 的值中,那么 S 的 RGB 值为(0.5, 0, 0),公式看起来便会像这样:
所以当源纹理是完全不透明的时候,目标像素就等于源纹理。这可以省下 GPU 很大的工作量
这也是为什么 CALayer 有一个叫做 opaque 的属性了。如果这个属性为 NO,GPU 将不会做任何合成,而是简单从这个层拷贝,不需要考虑它下方的任何东西(因为都被它遮挡住了)。
1.4.2 图层对齐—为何图片缩放会增加GPU工作量
当所有的像素是对齐的时候我们得到相对简单的计算公式。每当 GPU 需要计算出屏幕上一个像素是什么颜色的时候,它只需要考虑在这个像素之上的所有 layer 中对应的单个像素,并把这些像素合并到一起。或者,如果最顶层的纹理是不透明的(即图层树的最底层),这时候 GPU 就可以简单的拷贝它的像素到屏幕上。
当一个 layer 上所有的像素和屏幕上的像素完美的对应整齐,那这个 layer 就是像素对齐的。主要有两个原因可能会造成不对齐。第一个便是滚动;当一个纹理上下滚动的时候,纹理的像素便不会和屏幕的像素排列对齐。另一个原因便是当纹理的起点不在一个像素的边界上。
在这两种情况下,GPU 需要再做额外的计算。它需要将源纹理上多个像素混合起来,生成一个用来合成的值。当所有的像素都是对齐的时候,GPU 只剩下很少的工作要做。
Core Animation 工具和模拟器有一个叫做 color misaligned images 的选项,当这些在你的 CALayer 实例中发生的时候,这个功能便可向你展示。
关于iOS设备的一些尺寸限制可以看这里:iOSRes