您的位置:首页 > 其它

#翻译#将像素绘制到屏幕上

2013-08-11 18:48 344 查看
一个像素是怎么映射到屏幕上去的?有许多种方式将一些事物映射到显示屏,他们需要不同的框架和许多功能和方法的结合体。这里我们走马观花的看一下屏幕之后发生的一些事情。当你决定什么时候、怎么去查明并解决问题的时候,我希望这能帮助你理解哪一个API将能更好的工作。我们将聚焦于iOS,然而我们讨论的大多数问题也同样适用于OS X。

图形堆栈

当像素映射到屏幕上的时候后台发生了很多事情。但是一旦他们显示到屏幕上,每一个像素均由三个颜色组件构成:红,绿,蓝。三个独立的颜色单元会根据给定的颜色来显示到一个像素之上。在iPhone5上的液晶显示器上有1,136x640=727,040个像素,因此有2,181,120个颜色单元。在15寸的视网膜屏的MacBook Pro上,这一数字在15.5百万以上。所有的图形堆栈一起工作来确定每一次正确的显示。当你滚动整个屏幕的时候,数以百万计的颜色单元必须以每秒60次的速度刷新。这是一个很大的工作量。

软件组成

从简单的角度来看,软件堆栈看起来有点像这样:



显示的上一层是图形处理单元GPU,GPU是一个专门为图形高迸发计算量身定做的处理单元.这也是为什么它能同时更新所有的像素,并把结果显示到显示器上。它迸发的本性能让它高效的将不同纹理合成起来。我们将有一小块内容来更详细的讨论图形合成。关键的一点是,GPU是非常专业的,因此在某些工作上非常高效。比如,GPU非常快,并且比CPU使用更少的电来完成这个工作。正常的CPU都有一个普遍的目的,可以做很多不同的事情,但是合成图像在CPU上却会比较慢。

GPU驱动是直接和GPU交流的代码块。不同的GPU是不同野兽,但是驱动使他们在下一层显示的更统一,典型的有OpenGL/OpenGL ES.

OpenGL(Open Graphics Library)是一个提供了2D和3D图形渲染的API。

GPU是一个非常特殊的硬件,OpenGL和GPU密切的工作以促进GPU的能力,并实现硬件加速渲染。对大多数人来说,OpenGL看起来非常底层,但是当它在1992年第一次发布的时候(20多年前的事了)是第一个和图形硬件(GPU)交流的标准化方式,因此这是一个重大的飞跃,因为程序员不再需要为每个GPU重写他们的应用。

OpenGL之上扩展出很多东西。在这一点上iOS几乎所有的东西都是通过Core Animation,然而在OS X上,绕过Core Animation直接使用Core Graphics的这种情况并不少见。对于一些专门的应用,尤其是游戏,程序可能直接和OpenGL/OpenGL ES交流。事情变得使人更加困惑,因为Core Animation使用Core Graphics来做一些渲染。像AVFoundation,Core Image框架,和其他一些混合的入口。

要记住一件事情,GPU是一个非常强大的图形硬件并且在显示像素方面扮演了核心作用。它连接到CPU。从硬件上讲两者之间存在某种类型的总线,并且有像OpenGL,Core Animation和Core Graphics这样的框架来在GPU和CPU之间精心安排数据的传输。为了将像素显示到屏幕上,一些处理将在CPU上进行。数据将会传送到GPU,这也将会反过来加工(个人理解为加压、解压),最终像素显示到屏幕上。

这个过程的每一部分都有各自的挑战,并且许多时候需要做出折中的选择。

硬件参与者



正如上面一个简单的图片来显示那些挑战,GPU需要将每一个frame的纹理(位图)合成在一起(比如一秒60次)。每一个纹理会占用VRAM(video RAM),所以需要给GPU能够同时保持多少纹理一个限制。GPU在合成方面非常高效,但是某些合成任务却比其他更复杂,并且GPU在 16.7ms(1/60s)内能做的工作有限。

下一个挑战就是将数据传输到GPU上。为了让GPU访问数据,数据将需要从RAM移动到VRAM上。这就是提及到的上传数据到GPU。这看起来貌似微不足道,但是一些大型的纹理却会非常耗时。

最终,CPU开始运行你的程序。你可能会让CPU从bundle加载一张PNG的图片并且解压它。这所有的事情都在CPU上进行。然后当你需要显示解压缩后的图片时,它需要以某种方式上传到GPU。一些平凡的比如显示文本对CPU来说是一件非常复杂的事情,这会促使Core Text和Core Graphics框架更紧密的集成来根据文本生成一个位图。一旦准备好,它将会被作为一个纹理上传到GPU并准备显示出来。当你滚动或者在屏幕上移动文本,然而,同样的纹理能够被复用,CPU只需简单的告诉GPU新的位置就行了,所以GPU就可以重用存在的纹理了。CPU并不需要重新渲染文本,并且位图也不需要重新上传到GPU。

这说明一些包含错综复杂的影响,根据这个概述的方式,我们将深入一些技术影响。

合成

在图形世界中,合成是一个描述不同位图如何放到一起来创建你最终在屏幕上看到图像的过程。在许多方面显得显而易见,而让人忘了背后错综复杂的计算。

让我们忽略一些难懂的事例并且假定屏幕上一切事物皆纹理。一个纹理就是一个包含RGBA值的长方形,比如,每一个像素里面都包含红、绿、蓝和透明度的值。在Core Animation世界中这就相当于一个CALayer。

在这个简化的设置中,每一个layer是一个纹理,这所有的纹理都已某种方式堆叠在彼此的顶部。对于屏幕上的每一个像素,GPU需要算出怎么混合这些纹理来得到像素RGB的值。这就是合成大概的意思。

如果我们所拥有的是一个和屏幕大小一样并且和屏幕像素对齐的单一纹理,屏幕上每一个像素相当于纹理中的一个像素。纹理的最后一个像素也就是屏幕的最后一个像素。

如果我们有第二个纹理放在第一个纹理之上,然后GPU将会把第二个纹理合成到第一个纹理中。有很多种不同的合成方法,但是如果我们假定两个纹理的像素对齐,并且使用正常的混合模式,我们便可以用下面这个公式来计算每一个像素:

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),公式看起来便会像这样:



我们最终得到RGB值为(0.5, 0, 0.5),是一个紫色。这这是我们所期望将透明红色合成到蓝色背景上所得到的。

记住我们刚刚只是合成一个纹理单一像素到另一个纹理的像素上。当两个纹理覆盖在一起的时候,GPU需要为所有像素做这种操作。正如你所知道的一样,许多程序都有很多层,因此所有的纹理都需要合成到一起。尽管GPU是一块高度优化的硬件来做这种事情,但这还是会让它非常忙碌,

不透明 VS 透明

当源纹理是完全不透明的时候,目标像素就等于源纹理。这可以省下GPU很大的工作量,这样只需简单的拷贝源纹理而不需要合成所有的像素值。但是没有方法能告诉GPU纹理上的像素是透明还是不透明的。只有当你作为一名开发者知道你放什么到CALayer上了。这也是为什么CALayer有一个叫做opaque的属性了。如果这个属性为YES,GPU将不会做任何合成,而是简单从这个层拷贝,不需要考虑它下方的任何东西(因为都被它遮挡住了)。这节省了GPU相当大的工作量。这也正是Instruments中color blended layers选项中所涉及的。(这在模拟器中的Debug菜单中也可用).它允许你看到哪一个layers(纹理)被标注为透明的,比如GPU正在为哪一个layers做合成。合成不透明的layers因为需要更少的数学计算而更廉价。

所以如果你知道你的layer是不透明的,最好确定设置它的opaque为YES。如果你加载一个没有alpha通道的图片,并且将它显示在UIImageView上,这将会自动发生。但是要记住如果一个图片没有alpha通道和一个图片每个地方的alpha都是100%,这将会产生很大的不同。在后一种情况下,Core Animation需要假定是否存在像素的alpha值不为100%。在Finder中,你可以使用Get Info并且检查More Info部分。它将告诉你这张图片是否拥有alpha通道。

像素在一条线上 VS 不重合在一起

到现在我们都在考虑显示像素完美的重合在一起的layers。到现在为止当所有的像素是对齐的时候我们得到相对简单的计算公式。每当GPU需要计算出屏幕上一个像素是什么颜色的时候,它只需要考虑在这个像素之上的所有layer中对应的单个像素,并把这些像素合并到一起。或者,如果最顶上的纹理是不透明的,这时候GPU就可以简单的拷贝纹理中的像素到屏幕上。

当一个layer上所有的像素和屏幕上的像素完美的对应整齐,那这个layer就是像素对齐的。主要有两个原因可能会造成不对齐。第一个便是滚动;当一个纹理上下滚动的时候,纹理的像素便不会和屏幕的像素排列对齐。另一个原因便是当纹理的起点不在一个像素的边界上。

在这两种情况下,GPU需要再做额外的计算。它需要将源纹理上多个像素混合起来,生成一个用来合成的值。当所有的像素都是对齐的时候,GPU只剩下很少的工作要做。

再次,Core Animation工具和模拟器有一个叫做color misaligned images的选项,当这些在你的CALayer实例中发生的时候,这个功能便可向你展示。

蒙板

一个图层可以有一个和它相关联的蒙板,蒙板是一个拥有alpha值的位图,当像素要和它下面包含的像素合并之前都会把蒙板应用到图层的像素上去。当你要设置一个图层的圆角半径时,你可以有效的在图层上面设置一个蒙板。但是也可以指定任意一个蒙板。比如,一个字母A形状的蒙板。最终只有在蒙板中显示出来的(即图层中的部分)才会被渲染出来。

屏幕外的渲染

屏幕外的渲染可以被Core Animation自动触发,或者被应用程序强制触发。屏幕外的渲染会合并/渲染图层树的一部分到一个新的缓冲区,然后该缓冲区被渲染到屏幕上。

当屏幕外的渲染合成计算是非常昂贵的时候, 你也许希望强制这种操作。一种好的方法就是缓存合成的纹理/图层。如果你的渲染树非常复杂(所有的纹理,以及如何组合在一起),你可以强制屏幕外的渲染缓存那些图层,然后可以用缓存作为合成的结果放到屏幕上。

如果你的程序混合了很多图层,并且想要他们一起做动画,GPU通常会为每一帧(1/60s)重复合成所有的图层。当使用屏幕外渲染时,GPU第一次会混合所有图层到一个基于新的纹理的位图缓存上,然后使用这个纹理来绘制到屏幕上。现在,当这些图层一起移动的时候,GPU便可以复用这个位图缓存,并且只需要做很少的工作。需要注意的是,只有当那些图层不改变时,这才可以用。如果那些图层改变了,GPU需要重新创建位图缓存。你可以通过设置shouldRasterize为YES来触发这个行为。

然而,这是一个权衡。第一,这可能会使事情变得更慢。创建额外的屏幕外缓冲区是GPU需要多做的一步操作,特殊情况下这个位图可能再也不需要被复用,这便是一个无用功了。然而,可以被复用的位图,GPU也有可能将它卸载了。所以你需要计算GPU的利用和帧的速率来判断这个位图是否有用。

屏幕外渲染也可能产生副作用。如果你正在直接或者间接的将蒙板应用到一个图层上,Core Animation为了应用这个蒙板,会强制进行屏幕外渲染。这会对GPU产生重负。通常情况下蒙板只能被直接渲染到帧的缓冲区中(在屏幕内)。

Instrument的Core Animation工具有一个叫做Color Offscreen-Rendered Yellow的选项,它会将已经被渲染到屏幕外缓冲区的区域标注为黄色(这个选项在模拟器中也可以用)。同时记得检查Color Hits Green and Misses Red选项。绿色代表无论何时一个屏幕外缓冲区被复用,而红色代表当缓冲区被重新创建。

一般情况下,你想要避免屏幕外渲染,因为这是很大的消耗。直接将图层合成到帧的缓冲区中(在屏幕上)是比先创建屏幕外缓冲区,然后渲染到纹理中,最后将结果渲染到帧的缓冲区中要廉价很多。因为这其中涉及两次昂贵的环境转换(转换环境到屏幕外缓冲区,然后转换环境到帧缓冲区)。

所以当你打开Color Offscreen-Rendered Yellow后看到黄色,这便是一个警告,但这不一定是坏的。如果Core Animation能够复用屏幕外渲染的结果,这便能够提升性能。同时还要注意,光栅层的空间是有限的。苹果暗示大概有屏幕大小两倍的空间来存储光栅层/屏幕外缓冲区。

如果你使用图层的方式会通过屏幕外渲染,你最好摆脱这种方式。为图层使用蒙板或者设置圆角半径会造成屏幕外渲染,产生阴影也会如此。

至于蒙板,圆角半径(特殊的蒙板)和clipsToBounds /masksToBounds,你可以简单的创建一个包含蒙板里面已经存在的内容,比如,使用蒙板上已经应用的图片。如果你想根据图层的内容申请一个长方形的蒙板,你可以使用contentsRect来代替蒙板。如果你最后设置了shouldRasterize为YES,那也要记住设置rasterizationScale为contentsScale。

更多的关于合成

像往常一样,维基百科上有更多关于透明合成的基础公式。当我们谈完像素后,我们将更深入一点的谈论红,绿,蓝和alpha是怎么在内存中表现的。

翻译到这儿已经花了好长时间了,所以先把翻译好的部分发出来。原文链接http://www.objc.io/issue-3/moving-pixels-onto-the-screen.html#pixels。如果英文好的也可以直接看原文了,因为剩下的部分还不知道什么时候能翻译好,不过我也会尽快抽时间完成。文中有什么翻译的不准确的地方还希望大家提出来。(微博:IrishDiamond2009)

下文已翻译好,丢到博客园上去了:http://www.cnblogs.com/answer-Huang/p/iOS.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  像素