1. 概述
写这篇总结的目的是想让大家了解眼睛看到屏幕上漂亮的颜色和图片背后所发生的事情。其实题目取的有点夸张,因为这里涉及到的知识非常多,所以这篇文章会我挑一些重要的内容来讲。
2. 什么是像素
简单的讲像素就是组成数字图像的基本单元。我们看到的图像其实都是由一个个像素组成的。每个像素可有各自的颜色值,像素可分成红、绿、蓝三原色像素(RGB色域),或者青、品红、黄和黑(CMYK色域,印刷行业以及打印机中常见)。我们手机、电脑、以及电视等屏幕上的像素是由红,绿,蓝三种颜色组件构成的。单位面积内的像素越多代表分辨率越高。
800万像素 3,264 × 2,448=7,990,272
1000万像素 3648 × 2,736=9,980,928
3. 软件组成
GPU(图像处理单元)一般都用来处理图像。他具有很高的并发性,这也是为什么它能同时更新所有的像素,并呈现到显示器上。
3.1 GPU 与 CPU 的比较
理解 GPU 和 CPU 之间区别的一种简单方式是比较它们如何处理任务。本质上 CPU 由专为顺序串行处理而优化的几个核心组成,而 GPU 则拥有一个由数以千计的更小、更高效的核心(专为同时处理多重任务而设计)组成的大规模并行计算架构。
GPU 具有非常出色的浮点计算性能,工作效率上比 CPU 更省电。
CPU 可以做很多不同的事情,但是合成图像上却比不过 GPU。
另外还有可能大家都不知道的是:现在热门的机器学习,深度学习都用是 GPU,各大平台也在推出 GPU 云计算服务。
3.2 那么为什么是 GPU 呢?
主要原因是:深度学习需要很高的内在并行度、大量的浮点计算能力以及矩阵预算,深度学习的工作 99% 都可以类比成将不同的矩阵进行相乘或者矩阵和向量进行相乘。刚好 GPU 可以提供这些能力,并且在相同的精度下,相对传统 CPU 的方式,拥有更快的处理速度、更少的服务器投入和更低的功耗。
http://www.nvidia.cn/object/machine-learning-cn.html
3.3 OpenGL
OpenGL(Open Graphics Library) ,它是在1992年第一次发布的(20多年前的事了)第一个和图形硬件(GPU)交流的标准化方式,简单的说就是提供图像 2D 和 3D 图形渲染的 API 的底层图形库。他虽然非常底层,但是当这是一个重大的飞跃,程序员不再需要为每个GPU重写他们的驱动应用了。
OpenGL 之上扩展出很多东西。(看上面的架构图)在 iOS 上,几乎所有的东西都是通过 Core Animation 绘制出来,Core Animation 是基于Core Graphics 和 Core Animation 的更上层的抽象。
对于一些专门的应用,尤其是游戏,可能直接和 OpenGL/OpenGL ES 交流。
这些框架都是运行在 GPU 上的,所以要记住 GPU 是一个非常强大的图形硬件,并且在显示像素方面起着核心作用。
**4. 参与的硬件,阐述需要遇到的挑战 **
第一个挑战正如上面这张简单的图片:GPU 需要将每一个 frame 的图层(位图:可以直接理解成一个画面)合成在一起(一秒60次),每一个图层会占用 VRAM(video RAM),GPU 在合成方面非常高效,但是某些合成任务却比其他更复杂,并且 GPU在 16.7ms(1/60s)内能做的工作也是有限的。
下一个挑战就是将数据传输到 GPU 上。为了让 GPU 访问数据,需要将数据从 RAM 移动到 VRAM 上。这就是提及到的上传数据到 GPU。这看起来貌似微不足道,但是一些大型的图层却会非常耗时。
最后,从一个比较实际的例子讲一下整个流程:CPU 开始运行你的程序,你可能会让 CPU 从 本地加载一张 PNG 的图片并且解压它。这所有的事情都在 CPU 上进行,然后当你需要显示解压缩后的图片时,它需要以某种方式上传到 GPU。还有一些看似平凡的,比如显示文本,对 CPU 来说却是一件非常复杂的事情,所以 CPU 会调用 GPU 上的 一些框架(Core Text 和 Core Graphics)去生成一个位图,作为一个图层上传到 GPU 并准备显示出来。当你滚动或者在屏幕上移动文本时,同样的图层能够被复用,CPU 只需简单的告诉 GPU 新的位置就行了,GPU 就可以重用存在的图层了,并不需要重新渲染,位图也不需要重新上传到 GPU。
5. 图像的合成
在图形世界中,大家都知道,一张完整的画面是有很多个图层组合在一起的,但实际的物理设备上只有一个屏幕,所以对于屏幕上的每一个像素,GPU 需要算出怎么混合这些图层来得到像素 RGB 的值。
如果我们有第二个图层放在第一个图层之上,然后GPU将会把第二个图层合成到第一个图层中。有很多种不同的合成方法,但是如果我们假定两个图层的像素对齐,并且使用正常的混合模式,我们便可以用下面这个公式来计算每一个像素:
R = S + D * ( 1 – Sa )
6. 不透明 VS 透明
通过上面的公式我们可以知道:当源图层是完全不透明的时候,目标像素就等于源图层。这可以省下 GPU 很大的工作量,这样只需简单的拷贝源图层而不需要合成所有的像素值。(不需要考虑它下方的任何东西,因为都被它遮挡住了)。
7. 像素对齐VS 不重合在一起
之前我们考虑的都是基于像素对其的图片,当所有的像素是对齐的时候我们得到上诉相对简单的计算公式。我们只需要考虑在这个像素之上的所有图层中对应的单个像素,并把这些像素合并到一起。或者,如果最顶层的图层是不透明的,这时候 GPU 就可以简单的拷贝它的像素到屏幕上。
那么为什么会照成像素不对其呢?主要有两个原因可能会造成不对齐。
第一个便是缩放;当一个图层放大缩小的时候,图层的像素便不会和屏幕的像素排列对齐。
另一个原因便是当图层的起点不在一个像素的边界上。
(Plus 上是3倍像素,如果我们在编程中设置距离为 0.5 的高度就可能出现半个像素点,照成不对其)
在这两种情况下,GPU 需要再做额外的计算。它需要将源图层上多个像素混合起来,生成一个用来合成的值。当所有的像素都是对齐的时候,GPU 只剩下很少的工作要做。
8. Masks 蒙版实现“遮罩”效果
在 iOS 中Masks有几个特点:
在 Mask 范围之外的图层将不被显示
Mask 的透明度为 0 时图层不显示或不显示,当其为 0 < alpha < 1 时图层为半透明状态
我们来看一个实际例子:sketch
ResultLayer = ContentLayer * MaskLayer_Alpha
9. 离屏渲染(Offscreen Rendering)
GPU 渲染有两种方式,一种是 On-Screen Rendering:意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。
另外一种是 Off-Screen Rendering: 意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
还有一种特殊的离屏渲染方式:CPU 渲染在 iOS 中如果我们重写了 -drawRect: 方法,并且使用任何Core Graphics的技术进行了绘制操作,整个渲染过程由 CPU 完成。
当使用离屏渲染的时候会很容易照成性能消耗,主要是屏幕外缓冲区和屏幕缓冲区上下文环境的切换很耗性能。所以经常会导致我们的 App 达不到每秒达到 60 帧的标准。
10. JPEG VS PNG
每个人都知道 JPEG。它是相机的产物。它代表着照片如何存储在电脑上。
将 JPEG 数据转换成像素数据是一个非常复杂的过程,他的解压很慢,如果你将 JPEG 文件创建一个 UIImage 并且绘制到屏幕上时,将会有一个延时,因为 CPU 这时候忙于解压这个 JPEG。特别是在列表里你,每个 Cell 都需要解压 JPEG,那么你的滚动当然不会平滑会变的非常卡。
PNG读作”ping”。和 JPEG 相反,它的压缩对格式是无损的,且解码 PNG 数据比解码 JPEG 简单的多。因为是无损的,像素还原非常好,所以对于像程序中的原图(如buttons,icons),我们要用 PNG 格式的图片。
优化总结:
保持图层的像素对齐
尽量保持图层的透明度为 1
尽量避免离屏渲染
我们还是要把尽可能多的绘制工作交给 GPU 做,而让 CPU 尽可能的来执行应用程序。通常,GPU 的渲染性能要比 CPU 高效很多,同时对系统的负载和消耗也更低一些。
选择合适的图片格式