iOS进阶--UIView的绘制

 我来答
舒适还明净的海鸥i
2022-07-29 · TA获得超过1.7万个赞
知道小有建树答主
回答量:380
采纳率:0%
帮助的人:69.4万
展开全部

如果要研究OpenGL ES相关和 GPU 相关,这篇文章很具有参考的入门价值.

首先要从 Runloop 开始说,iOS 的 MainRunloop 是一个60fps 的回调,也就是说16.7ms(毫秒)会绘制一次屏幕,这个时间段内要完成:

这些 CPU 的工作.

然后将这个缓冲区交给 GPU 渲染, 这个过程又包含:

最终现实在屏幕上.因此,如果在16.7ms 内完不成这些操作, eg: CPU做了太多的工作, 或者 view 层次过于多,图片过于大,导致 GPU 压力太大,就会导致"卡"的现象,也就是 丢帧 , 掉帧 .

苹果官方给出的最佳帧率是: 60fps (60Hz),也就是一帧不丢, 当然这是理想中的绝佳体验.

一般来说如果帧率达到 60+fps (fps >= 60帧以上,如果帧率fps > 50,人眼就基本感觉不到卡顿了,因此,如果你能让你的 iOS 程序 稳定 保持在 60fps 已经很不错了, 注释,是"稳定"在60fps,而不是, 10fps , 40fps , 20fps 这样的跳动,如果帧频不稳就会有卡的感觉, 60fps 真的很难达到, 尤其是在 iPhone 4/4s等 32bit 位机上,不过现在苹果已经全面放弃32位,支持最低64位会好很多.

总的来说, UIView从绘制到Render的过程有如下几步:

UIView 的绘制和渲染是两个过程:

上面提到的从 CPU 到 GPU 的过程可用下图表示:

下面具体来讨论下这个过程

假设我们创建一个 UILabel

这个时候不会发生任何操作, 由于 UILabel 重写了 drawRect 方法,因此,这个 View 会被 marked as "dirty" :

类似这个样子:

然后一个新的 Runloop 到来,上面说道在这个 Runloop 中需要将界面渲染上去,对于 UIKit 的渲染,Apple用的是它的 Core Animation 。 做法是在Runloop开始的时候调用:

在 Runloop 结束的时候调用

在 begin 和 commit 之间做的事情是将 view 增加到 view hierarchy 中,这个时候也不会发生任何绘制的操作。 当 [CATransaction commit] 执行完后, CPU 开始绘制这个 view :

首先 CPU 会为 layer 分配一块内存用来绘制 bitmap ,叫做 backing store
创建指向这块 bitmap 缓冲区的指针,叫做 CGContextRef
通过 Core Graphic 的 api ,也叫 Quartz2D ,绘制 bitmap
将 layer 的 content 指向生成的 bitmap
清空 dirty flag 标记
这样 CPU 的绘制基本上就完成了.
通过 time profiler 可以完整的看到个过程:

假如某个时刻修改了 label 的 text :

由于内容变了, layer 的 content 的 bitmap 的尺寸也要变化,因此这个时候当新的 Runloop 到来时, CPU 要为 layer 重新创建一个 backing store ,重新绘制 bitmap .
CPU 这一块最耗时的地方往往在 Core Graphic 的绘制上,关于 Core Graphic 的性能优化是另一个话题了,又会牵扯到很多东西,就不在这里讨论了.

GPU bound:

CPU 完成了它的任务:将 view 变成了 bitmap ,然后就是 GPU 的工作了, GPU 处理的单位是 Texture .
基本上我们控制 GPU 都是通过 OpenGL 来完成的,但是从 bitmap 到 Texture 之间需要一座桥梁, Core Animation 正好充当了这个角色:
Core Animation 对 OpenGL 的 api 有一层封装,当我们要渲染的 layer 已经有了 bitmap content 的时候,这个 content 一般来说是一个 CGImageRef , CoreAnimation 会创建一个 OpenGL 的 Texture 并将 CGImageRef(bitmap) 和这个 Texture 绑定,通过 TextureID 来标识。
这个对应关系建立起来之后,剩下的任务就是 GPU 如何将 Texture 渲染到屏幕上了。 GPU 大致的工作模式如下:

整个过程也就是一件事:

CPU 将准备好的 bitmap 放到 RAM 里, GPU 去搬这快内存到 VRAM 中处理。 而这个过程 GPU 所能承受的极限大概在16.7ms完成一帧的处理,所以最开始提到的60fps其实就是GPU能处理的最高频率.
因此, GPU 的挑战有两个:

这两个中瓶颈基本在第二点上。渲染 Texture 基本要处理这么几个问题:

Compositing 是指将多个纹理拼到一起的过程,对应 UIKit ,是指处理多个 view 合到一起的情况,如:

如果 view 之间没有叠加,那么 GPU 只需要做普通渲染即可.
如果多个 view 之间有叠加部分, GPU 需要做 blending .

加入两个 view 大小相同,一个叠加在另一个上面,那么计算公式如下:

R = S + D *( 1 - Sa )

其中 S , D 都已经 pre-multiplied 各自的 alpha 值。
Sa 代表 Texture 的 alpha 值。

假如 Top Texture (上层 view )的 alpha 值为 1 ,即不透明。那么它会遮住下层的 Texture .
即, R = S 。是合理的。

假如 Top Texture (上层 view )的 alpha 值为 0.5 ,
S 为 (1,0,0) ,乘以 alpha 后为 (0.5,0,0) 。
D 为 (0,0,1) 。
得到的 R 为 (0.5,0,0.5) 。

基本上每个像素点都需要这么计算一次。

因此, view 的层级很复杂,或者 view 都是半透明的( alpha 值不为 1 )都会带来 GPU 额外的计算工作。

这个问题,主要是处理 image 带来的,假如内存里有一张 400x400 的图片,要放到 100x100 的 imageview 里,如果不做任何处理,直接丢进去,问题就大了,这意味着, GPU 需要对大图进行缩放到小的区域显示,需要做像素点的 sampling ,这种 smapling 的代价很高,又需要兼顾 pixel alignment 。 计算量会飙升。

如果我们对 layer 做这样的操作:

会产生 offscreen rendering ,它带来的最大的问题是,当渲染这样的 layer 的时候,需要额外开辟内存,绘制好 radius,mask ,然后再将绘制好的 bitmap 重新赋值给 layer 。
因此继续性能的考虑, Quartz 提供了优化的 api :

简单的说,这是一种 cache 机制。
同样 GPU 的性能也可以通过 instrument 去衡量:

红色代表 GPU 需要做额外的工作来渲染 View ,绿色代表 GPU 无需做额外的工作来处理 bitmap 。

全文完

收录: 原文地址

已赞过 已踩过<
你对这个回答的评价是?
评论 收起
推荐律师服务: 若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询

为你推荐:

下载百度知道APP,抢鲜体验
使用百度知道APP,立即抢鲜体验。你的手机镜头里或许有别人想知道的答案。
扫描二维码下载
×

类别

我们会通过消息、邮箱等方式尽快将举报结果通知您。

说明

0/200

提交
取消

辅 助

模 式