图形显示是用户与软件生产者交互的一个重要媒介,也是客户端工程师需要掌握的一个重要技术点。了解图形显示相关技术,对工程师进行相关性能优化以及架构师进行工程架构设计,打磨良好的产品体验,都有巨大的帮助。
一 、图像表示
我们周围的事物都是形状和颜色的。那么计算机如何来显示真实世界上的事物呢? 让我们先来了解几个概念吧。
1、三原色
伟大的物理学家牛顿通过三棱镜把白色的光分解成七种不同颜色的光,实验发现,红,绿,蓝三种颜色的光是无法被分解的,所以我们就称红绿蓝为光的三原色。也就是说,我们可以通过不同比例的三种三原色来组合出各种各样的颜色。这也就是我们常说的RGB(A)模型。
2、如何将颜色数字化
我们知道,计算机表示数据是用二进制0,1来表示的。现在计算机,一般使用32位(色深)来表示颜色,均分给三种三原色和透明度四个分量。这样能显示的颜色组合种类就是(2^8)^3 = 16777216种颜色了。
3、纹理
纹理指GPU中的位图,存储在GPU的显存(video RAM)中。一方面它的格式是固定的,另外一方面GPU对纹理的大小有限制,比如长/宽必须是2的幂次方,最大不能超过2048或者4096等。那如果遇到纹理超过阀值的图片,比如说地图图片,又该如何处理呢?这就涉及到下面要讲的光栅化技术了。
4、光栅化(Rasterize)
计算机显示的流程可以描述为将图像转化为一系列像素点的排列然后打印在屏幕上,由图像转化为像素点的过程又称为光栅化,就是从矢量的点线面的描述,变成像素的描述。光栅化的过程由GPU负责完成。
光栅化的本质其实就是坐标变换、几何离散化,然后再填充。
因为移动端GPU性能上的劣势以及对实时光栅化的极致要求,光栅化技术经历了立即渲染(Full-screen Raterization)到分区渲染(Tile-Based Raterization),再到现在最先进的分区延迟渲染(Tile-Based Deferred Raterization)三个阶段。苹果的ARM处理器从A11开始,支持TBDR渲染。参考链接
5、像素与分辨率
从宏观到微观,一张位图图像,其实就是由一定数量的带有RGB颜色信息的点的矩阵构成的。这样的点就叫做像素(pixel)。而这个像素矩阵的大小就叫做图像的分辨率。
Retina 显示技术
所谓的“Retina”其实是一种显示标准。是把更多的像素点压缩至一块屏幕里,从而达到更高的分辨率并提高屏幕显示的细腻程度。这种技术对硬件的配置要求会更高一些。
6、手机屏幕的发展
根据材质的不同,手机屏幕可分为LCD(液晶显示器)与OLED(有机发光二极管)这两大类。其中LCD材质屏幕支持的显示技术又分为TFT,IPS和SLCD。该类屏幕具有广视角色彩还原度高的特点。OLED材质屏幕支持的显示技术分为AMOLED和Super AMOLED两种。其中AMOLED采用屏幕自发光的技术,但在阳光直射下很难看清楚,材质薄,寿命短,而后者被大量用在三星手机上,具有操作更领命,从任何角度都能看清楚屏幕的有点,但是对色彩的还原度差较差,色调艳丽不真实。参考链接
Display P3技术
值得一提的是,苹果从iPhone7 产品开始,屏幕支持一种全新的显示技术叫Display P3,它可以表示更多数量的颜色,使屏幕对真实图像的还原度更真实。
二、协同工作流程
从UI工程师代码的编写到最后图像呈现在屏幕上,都经历了哪些工程,下面我们从硬件的角度来分析一下:
1.CPU计算好图形的布局和纹理,通过总线,也就是主板交由GPU 2.GPU将资源进行绘制与渲染,渲染后的数据放置于帧缓冲区内 3.同时,显示器经过视频控制器,从帧缓冲区内获取数据,进行显示
从以上步骤,我们可总结出图像从生成到最终显示大概会经历计算–>绘制–>显示三个阶段。但是这样就会产生一个问题,帧缓冲区内数据填充和获取的整个过程会完全显示在显示器中,也就是大家看到的画面的闪烁。如何解决这个问题呢?下面就为大家介绍多重缓冲和垂直同步技术。
三、多重缓冲和垂直同步
1、双缓冲(Double Buffer)
双缓冲技术将帧缓冲区设置为两个,即前端缓冲区(Front FrameBuffer)和后备缓冲区(Back FrameBuffer)。其中后备缓冲区负责图形的绘制,前端缓冲区负责屏幕的显示。当屏幕需要重绘时,只需将后备缓冲区的内容交换到前端缓冲区来,等待的时间只是内存拷贝的时间,不需要等待图形绘制的时间。因此,采用双缓冲技术,既可以解决屏幕闪烁的问题,又可以实现快速重绘。如下图: 【双缓冲工作原理图】
但是双重缓冲有个问题,两个缓冲区随时都可能发生交换,所以就会出现这种情况:前缓冲区中的画面才刚传输了一半给显示器,两个缓冲区就发生交换了,后面传输的都是原来缓冲区中的画面,即下一帧画面。于是,显示器上的一幅画面成了前后两帧的结合,这就是画面撕裂。这种问题在高帧率情况下尤为显著,因为帧率越高,前后缓冲区的交换就约为频繁,发生这种画面没输出完就交换了的事件的概率自然也就更高,如下图:
由上图可知
1.时间从0开始,进入第一个16ms:Display显示第0帧,CPU处理完第一帧后,GPU紧接其后处理继续第一帧。三者互不干扰,一切正常。 2.时间进入第二个16ms:因为早在上一个16ms时间内,第1帧已经由CPU,GPU处理完毕。故Display可以直接显示第1帧。显示没有问题。但在本16ms期间,CPU和GPU 却并未及时去绘制第2帧数据(注意前面的空白区),而是在本周期快结束时,CPU/GPU才去处理第2帧数据。 3.时间进入第3个16ms,此时Display应该显示第2帧数据,但由于CPU和GPU还没有处理完第2帧数据,故Display只能继续显示第一帧的数据,结果使得第1 帧多画了一次(对应时间段上标注了一个Jank)。 4.通过上述分析可知,此处发生Jank的关键问题在于,为何第1个16ms段内,CPU/GPU没有及时处理第2帧数据?原因很简单,CPU可能是在忙别的事情,不知道该到处理UI绘制的时间了。可CPU一旦想起来要去处理第2帧数据,时间又错过了!
那有什么办法能解决它呢?,那就是下面要讲的垂直同步技术
2、垂直同步
在讲垂直同步技术之前,我们需要了解几个概念
屏幕刷新率
屏幕刷新率指的是设备刷新屏幕的频率,单位为赫兹/Hz. 不同的显示器支持在不同分辨率下的不同刷新率。如下图,屏幕的刷新过程是每一行从左到右,也就是行刷新(水平刷新),从上到下,也就是屏幕刷新(垂直刷新)。当整个屏幕刷新完毕,即一个垂直刷新周期完成,会有短暂的空白期,此时发出VSync信号。
帧率
帧率指的是Frame Rate,单位fps,即GPU生成帧的频率,越高越好。
VSync
垂直同步,英文简称为VSync,它其实是一种简写,完整的名字应该是“等待垂直同步信号”。
开启垂直同步,就相当于在帧缓冲区里架设了红绿灯,当显示器尚未完成一幅画面的刷新时,红灯亮起,两个缓冲区不允许交换;当显示器刷新完成一帧画面时,绿灯亮起,此时缓冲区可以进行交换了。这样以后就杜绝了“在进行数据传输的过程中交换缓冲区”的可能,自然也就解决了画面撕裂的问题。
【垂直同步原理图】
由图可知,每收到VSYNC中断,CPU就开始处理各帧数据。整个过程非常完美。
注意,垂直同步技术虽然能彻底杜绝画面撕裂,但是它的副作用同样明显–操作延迟。
在帧率比显示器刷新率高很多的情况下,为了将显示器的刷新时间和显卡向缓冲区写入画面的时间保持同步,必然就需要人为地增加延迟,来延后“过快生成的画面“向显示器的输出。因此在竞技游戏中,开启垂直同步后的手感会大打折扣。
不过,仔细琢磨图2却会发现一个新问题:图2中,CPU和GPU处理数据的速度似乎都能在16ms内完成,而且还有时间空余,也就是说,CPU/GPU的FPS(帧率,Frames Per Second)要高于Display的FPS。确实如此。由于CPU/GPU只在收到VSYNC时才开始数据处理,故它们的FPS被拉低到与Display的FPS相同。但这种处理并没有什么问题,因为移动端设备的Display FPS一般是60,其对应的显示效果非常平滑。 如果CPU/GPU的FPS小于Display的FPS,会是什么情况呢?请看下图:
由图可知: 1.在第二个16ms时间段,Display本应显示B帧,但却因为GPU还在处理B帧,导致A帧被重复显示。 2.同理,在第二个16ms时间段内,CPU无所事事,因为A Buffer被Display在使用。B Buffer被GPU在使用。注意,一旦过了VSYNC时间点, CPU就不能被触发以处理绘制工作了。
那么问题来了,不开垂直同步会画面撕裂,开了又会有延迟,那画质和性能就不能兼得吗?
其实是可以的,要想画质与操作兼得,有两个办法。
第一个办法,买个 G-SYNC 或 FreeSync 的电竞显示器
这些显示器可以动态调整自己的刷新率,使其和显卡输出画面的帧率完全同步,这样不仅可以在解决画面撕裂的同时不出现操作延迟,还可以在帧率低于 60fps 的情况下同样提供垂直同步的效果,消除画面撕裂。不过,这毕竟是个要花大钱的办法。 第二个办法就是要用到三重缓冲技术!
3、三重缓冲(Triple Buffer)
由图可知: 第二个16ms时间段,CPU使用C Buffer绘图。虽然还是会多显示A帧一次,但后续显示就比较顺畅了。 是不是Buffer越多越好呢?回答是否定的。由图4可知,在第二个时间段内,CPU绘制的第C帧数据要到第四个16ms才能显示, 这比双Buffer情况多了16ms延迟。所以,Buffer最好还是两个,三个足矣。
现在一般的客户端屏幕都支持垂直同步技术,Android从4.1版本开始支持三重缓冲技术。iPhone目前支持双重缓冲技术。
四、移动端显示
如开篇所讲,图像从生成到最终显示大概会经历一下三个阶段,
- 计算:该阶段的工作由CPU来完成,主要包括数据的创建以及样式(style)和布局(layout)的计算
- 渲染:该阶段主要包含纹理的绘制(paint)和合成(composite)两部分工作
- 显示:这一部分和硬件结合的比较紧密,在这里就不赘述了。
五、影响显示性能的因素
从上面介绍的原理,我们不难看出,界面显示的过程主要是CPU和GPU协同工作的过程。所以CPU和GPU的负载均衡是影响界面显示性能的关键指标。下面分别阐述两者资源消耗的原因和常规的解决方案:
CPU
- 现象:对象频繁的创建,调整和销毁
- 方案:
- 不涉及UI操作的对象,尽量放到后台线程来创建其生命周期
- 尽量推迟对象创建的时间,比如懒加载
- 不经常变化的对象,请尽量复用或者缓存
- 现象:布局计算过于繁重复杂
- 方案:
- 在后台线程提前计算好视图布局,比如:AsyncDisplayKit
- 尽量不要频繁的调整视图布局,如果可以,请对其缓存
- 现象:大量大尺寸图片解码和图像绘制
- 方案:
- 高质量图片解码以及图像绘制工作尽量放到非主线程
GPU
- 现象:大量大尺寸图片展示
- 方案:
- 尽量减少在短时间内大量图形的显示,尽可能将多张图形合成一张显示
- 尽量少使用过大图片
- 现象:视图层级复杂
- 方案:
- 尽量减少视图数量和层次
- 及时将视图不透明信息告知系统
今天主要讲述了计算机图形学的基本知识,希望对客户端的同学有所帮助,谢谢!