关于视图层的一些东西

绘制像素到屏幕上

软件组成

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

Display的上一层便是图形处理单元GPU, GPU是一个专门为图形高并发计算而量身定做的处理单元.
GPU Driver是直接和GPU交流的代码块. 不同的GPU是不同的性能怪物, 但是驱动使他们在下一个层级上显示的更为统一, 典型的下一层级有OpenGL/OpenGL ES.
OpenGL是一个提供了2D和3D图形渲染的API.
OpenGL之上扩展出很多东西, 在iOS上, 几乎所有的东西都是通过Core Animation绘制出来 .
要记住一件事情, GPU是一个非常强大的图形硬件, 并且在显示像素方面起着核心作用. 它连接到CPU,从硬件上讲两者之间存在某种类型的总线, 并且有像OpenGL, Core Animation和Core Graphics 这样的框架来在GPU和CPU之间精心安排数据的传输. 为了将像素显示到屏幕上, 一些处理在CPU上进行, 然后数据将会传送到GPU, 这也需要做一些相应的操作, 最终像素显示到屏幕上.

硬件参与者

正如上面这张简单的图片显示的:GPU需要将每一个frame的纹理(位图)合成在一起(一秒60次). 每一个纹理会占用VRAM(video RAM), 所以需要给GPU同时保持纹理的数量做一个限制. GPU在合成方面非常高效, 但是某些合成任务却比其他更复杂, 并且GPU在1/60s内能做的工作也是有限的.
为了让GPU访问数据, 需要将数据从RAM移动到VRAM上. 这就是体积到的上传数据到GPU了, 在一些大的纹理却会非常耗时.
最终CPU开始运行程序, 可能会让CPU从bundle加载一张PNG的图片并且解压它. 这所有的事情都在CPU上进行, 然后当你需要显示解亚索后的图片时, 它需要以某种方式上传到GPU.

合成

在图形世界中, 合成是一个描述不同位图如何放到一起来创建你最终在屏幕上看到图像的过程, 在许多方面显得显而易见, 而让人忘了背后错综复杂的计算. 假定屏幕上一切事物皆纹理, 一个纹理就是一个包含RGBA值得长方形, 比如, ,每一个像素里面都包含红. 绿. 蓝和透明度的值. 在Core Aniimation世界中这就相当于一个CALayer. 在这个建华的设置中, 每一个layer是一个纹理, 所有的纹理都以某种方式堆叠在彼此的顶部, 对于屏幕上的每一个像素, GPU需要算出怎么混合这些纹理来得到像素RGB的值.
如果我们所拥有的是一个和屏幕大小一样并且和屏幕像素对齐的单一纹理, 那么屏幕上每一个像素相当于纹理中的一个像素, 纹理的最后一个像素也就是屏幕的最后一个像素.
如果我们有第二个纹理放在第一个纹理之上, 然后GPU会把第二个纹理合成到第一个纹理中, 并且使用正常的混合模式, 便可以用下面这个公式来计算每一个像素:
R = S + D * (1 - Sa)
这个公式可以这么理解: 结果的颜色 = 顶端纹理 + 低一层的纹理 * (1 - 顶端纹理的透明度), 在这个公式中所有的颜色都假定已经预先乘以了他们的透明度.
如果两个纹理都完全不透明, alpha = 1, 结果就是
R = S
结果就是顶端纹理的颜色, 正是所期待的顶端纹理覆盖了低一层的纹理.
这只是将纹理中的一个像素合成到另一个纹理的像素上. 当两个纹理覆盖在一起的时候, GPU需要为所有像素做这种操作, 许多程序的视图都有很多层, 因此所有的纹理都需要合成到一起. 经过GPU是一块高度优化的硬件来做这件事情, 但这还是会非常的影响性能的.

不透明VS透明

当源纹理是完全不透明的时候, 目标像素就等于源纹理. 这可以省下GPU很大的工作量, 这样只需简单的拷贝源纹理而不需要合成所有的像素值. 但是没有办法能告诉GPU纹理上的像素是透明还是不透明的, 只有当你作为一名开发者知道你放什么到CALayer上了, 这也是为什么CALayer有一个叫做opaque的属性了. 如果这个属性为YES, 那么GPU将不会做任何合成, 而只是简单的从顶端纹理这个层进行拷贝, 不需要考虑它下方的任何东西(因为都被它遮挡住了). 这节省了GPU相当大的工作量, 这也是Instruments种color blended layers 选项中所涉及的. 它允许你看到哪一个layers被标注为透明的, 比如GPU正在为哪一个layers做合成. 合成不透明的layers 因为需要更少的数学计算而更廉价, 更能提高GPU的性能.
所以如果你知道你的layer是不透明的, 最好确定设置它的opeaue为YES, 如果你加载一个没有alpha通道的图片, 并且将它显示在UIImageView上, 这将会自动发生. 但要记住如果一个图片没有alpha通道和一个图片每个地方的alpha都是100%, 这将会产生很大的不同,
在后一种情况下, Core Animation需要假定是否存在像素的alpha值不为100%.

像素对齐VS不重合在一起

到现在我们都在考虑像素完美重合在一起的layers, 当所有的像素是对齐的时候我们得到相对简单的计算公式. 每当GPU需要计算出屏幕上一个像素是什么颜色的时候, 它只需要考虑在这个像素之上的所有layer中对应的单个像素, 并把这些像素合并到一起, 或者, 如果最顶层的纹理是不透明的(即图层树的最底层), 这时候GPU就可以简单的拷贝它的像素到屏幕上了.
当一个layer上所有的像素和屏幕上的像素完美的对应整齐, 那这个layer就是像素对齐的. 主要有两个原因可能会造成不对其. 第一个便是滚动: 当一个纹理上下滚动的时候, 纹理的像素便不会和屏幕的像素排列对齐. 另一个原因就是当纹理的起点不在一个像素的边界上.
在这两种情况下, GPU需要再做额外的计算, 它需要将纹理上多个像素混合起来, 生成一个用来合成的值, 当所有的像素都是对齐的时候, GPU只剩下很少的工作要做了.

Masks

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

离屏渲染(Offscreen Rendering)

离屏渲染可以被Core Animation自动触发, 或者被应用程序强制触发. 屏幕外的渲染会合并/渲染图层树的一部分到一个新的缓冲区, 然后该缓冲区被渲染到屏幕上.
离屏渲染合成计算是非常昂贵的, 有时希望强制这种操作. 一种好的方法就是缓存合成的纹理/图层, 如果你的渲染树非常复杂(所有的纹理, 以及如何组合在一起), 你可以强制离屏渲染缓存那些图层, 然后可以用缓存作为合成的结果放到屏幕上.
如果你的程序混合了很多图层, 并且想要他们一起做动画, GPU通常会为每一帧(1/60s)重复合成所有的图层. 当使用离屏渲染时, GPU第一次会混合所有图层到一个基于新的纹理的位图缓存上, 然后使用这个纹理来绘制到屏幕上, 现在, 当这些图层一起移动的时候, GPU便可以复用这个位图缓存, 并且只需要做很少的工作, 需要注意的是, 只有当那些图层不改变时, 这才可以用. 如果那些图层改变了, GPU需要重新创建位图缓存, 可以通过设置shouldRasterize为YES来触发这个行为.
然而, 这是一个权衡. 第一, 这可能会使事情变得更慢. 创建额外的屏幕外缓冲区是GPU需要多做的一部操作, 特殊情况下, 这个位图可能再也不需要被复用, 这便是一个无用功. 然而, 可以被复用的位图, GPU也有可能将它卸载了. 所以你需要计算GPU的利用率和帧的速率来判断这个位图是否有用.
离屏渲染也可能产生副作用, 如果你正在直接或者间接的将mask应用到一个图层上, Core Animation为了应用这个mask, 会强制进行屏幕外渲染. 这会对GPU产生重负. 通常情况下mask只能被直接渲染到帧的缓冲区(在屏幕内).
Instrument 的Core Animation工具有一个叫做Color Offscreen - Rendered Yellow的选项, 它会将已经被渲染到屏幕外缓冲区的区域标注为黄色(这个选项在模拟器中也可以用). 同时记得检查Color Hits Green and Misses Red 选项 绿色代表无论何时一个屏幕外缓冲区被复用, 而红色代表当缓冲区被重新创建.
一般情况下, 你需要避免离屏渲染, 因为这是很大的消耗, 直接将图层合成到帧的缓冲区中(在屏幕上)比先创建屏幕外缓冲区, 然后渲染到纹理中, 最后将结果渲染到帧的缓冲区中要廉价很多. 因为这其中涉及到两次昂贵的环境转化(转换环境到屏幕外缓冲区, 然后转换环境到帧缓冲区).
所以当你打开Color Offscreen-Rendered Yellow时看到黄色, 这便是一个警告, 但这不一定是不好的. 如果Core Animation能够复用屏幕外渲染的结果, 这便能够提升性能.
同时还要注意, rasterized layer 的空间是有限的. 苹果暗示大概有屏幕大小两倍的空间来存储rasterized layer屏幕外缓冲区.
如果你使用layer的方式会通过屏幕外渲染, 你最好摆脱这种方式. 为layer使用蒙版或者设置圆角半径会造成屏幕外渲染, 产生阴影也会如此.
至于mask, 圆角半径(特殊的mask)和clipsToBounds/masksToBounds, 你可以简单的为一个已经拥有mask的layer创建内容, 比如, 已经应用了mask的layer使用一张图片. 如果你想根据layer的内容为其应用一个长方形mask, 你可以使用contentsRect来代替蒙版.
如果你最后设置了shouldRasterize为YES, 那也要记住设置rasterizationScale为contentsScale.

Core Animation OpenGL ES

正如名字所建议的那样, Core Animation让你在屏幕上实现动画, 我们将跳过动画部分, 而集中在绘图上, 需要注意的是, Core Animation 允许你做非常高效的渲染, 这也是为什么当你使用Core Animation时可以实现每秒60帧的动画了.
Core Animation的核心是OpenGL ES 的一个抽象物, 简而言之, 它让你直接使用OpenGL ES的功能, 却不需要处理OpenGL ES 做复杂的事情.
Core Animation的layer可以有子layer, 所以最终你得到的是一个图层树. Core Animation所需要做的最繁重的任务便是判断出哪些图层需要被(重新)绘制, 而OpenGL ES 需要做的便是将图层合并, 显示到屏幕上.
举个例子, 当你设置一个layer的内容为CGImageRef时, Core Animation会创建一个OpenGL 纹理, 并确保这个图层中的位图被上传到对应的纹理中. 以及当你重写- drwaInContext 方法时, Core Animation会请求分配一个纹理, 同时确保Core Graphics会将你所做的(即你在drawInContext中绘制的东西)放入到纹理的位图数据中. 一个图层的性质和CALayer的子类会影响到OpenGL的渲染效果, 许多低等级的OpenGL ES 行为被简单易懂地封装到CALayer概念中.
Core Animation通过Core Graphics的一端和OpenGL ES 的另一端, 精心策划基于CPU的位图绘制. 因为Core Animation处在渲染过程中的重要位置上, 所以你如何使用Core Animation将会对性能产生极大的影响.

CPU限制 VS GPU限制

当你在屏幕上显示东西的时候, 有许多组件参与了其中的工作. 其中, CPU和GPU在硬件中扮演了重要的角色, 在他们命名中P和U分别代表了"处理"和"单元", 当需要在屏幕上进行绘制时, 他们都需要做处理, 同时他们都有资源限制(即CPU和GPU的硬件资源).
为了每秒达到60帧, 你需要确定CPU和GPU不能过载. 此外, 即使你当前能达到60帧, 你还是要把尽可能多的绘制工作交给GPU做, 而让CPU尽可能的来执行应用程序. 通常, GPU的渲染性能要比CPU高效很多, 同时对系统的负载和消耗也更低一些.
既然绘图性能是基于CPU和GPU的, 那么你需要找出是哪一个限制你绘图性能的. 如果你用尽了GPU所有的资源, 也就是说, 是GPU限制了你的性能, 同一样的, 如果你用尽了CPU, 那就是CPU限制了你的性能.

With - drawRect:

如果你的视图类实现了-drawRect:, 他们将像这样工作:
当你调用-setNeedsDisplay, UIKit将会在这个视图的图层上调用-setNeedsDisplay. 这位图层设置了一个标识, 标记为dirty. 但还显示原来的内容. 实际上没有做任何工作, 所以多次调用-setNeedsDisplay并不会造成性能损失.
下面, 当渲染系统准备好, 会调用视图图层的-display方法. 此时, 图层会装配它的后备存储. 然后建立一个Core Graphics上下文(CGContextRef), 将后备存储对应内存中的数据恢复出来, 绘图会进入对应的内存区域, 并使用CGContextRef绘制.
从现在开始, 图层的后备存储将会被不断的渲染到屏幕上, 知道下次再次调用视图的-setNeedsDisplay, 将会一次将图层的后备存储更新到视图上.

CALayer

到现在为止, 你需要知道在GPU内, 一个CALayer在某种方式上和一个纹理类似, 图层有一个后备存储, 这便是被用来绘制到屏幕上的位图.
通常, 当你使用CALayer时, 你会设置它的内容为一张图片. 这到底做了什么? 这样做会告诉Core Animation使用图片的位图数据作为纹理, 如果这个图片被压缩了. Core Animation将会把这个图片解压缩,然后上传像素数据到GPU.
经过还要很多其他种类的图层, 如果你是用一个简单的没有设置上下文的CALayer, 并为这个CALayer设置一个背景颜色, Core Animation并不会上传任何数据到GPU, 但却能够不用任何像素数据而在GPU上完成所有的工作, 类似的, 对于渐变的图层, GPU是能够创建渐变的, 而且不需要CPU做任何工作, 并且不需要上传任何数据到GPU.

Reference

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • 绘制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一个像素是如何绘制到屏幕上去的?有很多...
    阿狸旅途T恤阅读 1,619评论 0 7
  • 卷首语 欢迎来到 objc.io 的第三期! 这一期都是关于视图层的。当然视图层有很多方面,我们需要把它们缩小到几...
    评评分分阅读 1,755评论 0 18
  • 像素是如何显示在屏幕上的呢? 当然这里有很多种方式将某些东西显示到显示器上面,并且它们可能涉及到许多不同的fram...
    樗同学阅读 1,755评论 1 3
  • 本系列文章的重点是关注在总结iOS图形图像的原理和性能优化的常规解决方案。 事先声明,本文绝大多数概念和内容均来源...
    ac3阅读 3,769评论 10 14
  • 今日开密云新店的营销会议,制定线上线下的推广爆破方案,进军一个新兴市场更加感觉营销部署的重要性。企业竞争越来越到了...
    一世惊鸿阅读 147评论 0 0