重开一个环境(内存、资源、上下文)来完成(部分)图片的绘制
指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
红色代表GPU
需要做额外的工作来渲染View
,绿色代表GPU
无需做额外的工作来处理bitmap
。
界面渲染过程
RunLoop
有一个60fps
的回调,即每16.7ms
绘制一次屏幕,所以view
的绘制必须在这个时间内完成,view
内容的绘制是CPU
的工作,然后把绘制的内容交给GPU
渲染,包括多个View
的拼接(Compositing
)、纹理的渲染(Texture
)等等,最后显示在屏幕上。但是,如果无法是16.7ms
内完成绘制,就会出现丢帧的问题,一般情况下,如果帧率保证在30fps
以上,界面卡顿效果不明显,那么就需要在33.4ms
内完成View
的绘制,而低于这个帧率,就会产生卡顿的效果,影响体验。
渲染的过程如下:
-
UIView
的layer
层有一个content
,指向一块缓存,即backing store
-
UIView
绘制时,会调用drawRect
方法,通过context
将数据写入backing store
- 在
backing store
写完后,通过render server
交给GPU
去渲染,将backing store
中的bitmap
数据显示在屏幕上
离屏渲染
在使用圆角、阴影和遮罩等视图功能的时候,图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制,所有就需要在屏幕外的上下文中渲染,即离屏渲染。
离屏渲染卡顿原因
离屏渲染之所以会特别消耗性能,是因为要创建一个屏幕外的缓冲区,然后从当屏缓冲区切换到屏幕外的缓冲区,然后再完成渲染;其中,创建缓冲区和切换上下文最消耗性能,而绘制其实不是性能损耗的主要原因。
设置了以下属性时,就会触发离屏绘制:
- shouldRasterize(光栅化)
- masks(遮罩)
- shadows(阴影)
- edge antialiasing(抗锯齿)
- group opacity(不透明)
- 复杂形状设置圆角等
- 渐变
屏幕渲染类型
CPU
计算好显示内容提交到GPU
,GPU
渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync
信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
屏幕渲染有如下三种:
GPU
中的屏幕渲染:
1、On-Screen Rendering
意为当前屏幕渲染,指的是GPU
的渲染操作是在当前用于显示的屏幕缓冲区中进行
2、Off-Screen Rendering
意为离屏渲染,指的是GPU
在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
3、CPU
中的离屏渲染(特殊离屏渲染,即不在GPU
中的渲染)
如果我们重写了drawRect
方法,并且使用任何Core Graphics
的技术进行了绘制操作,就涉及到了CPU
渲染
CoreGraphic通常是线程安全的,所以可以进行异步绘制,显示的时候再放回主线程
https://www.cnblogs.com/fishbay/p/7576176.html
一、概念理解
OpenGL中,GPU屏幕渲染有以下两种方式:
○ On-Screen Rendering
意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。
○ Off-Screen Rendering
意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
二、离屏渲染的是是非非
相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在两个方面:
· 创建新缓冲区
要想进行离屏渲染,首先要创建一个新的缓冲区。
· 上下文切换
离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上有需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。
三、离屏渲染触发方式
设置了以下属性时,都会触发离屏绘制:
· shouldRasterize(光栅化)
· masks(遮罩)
· shadows(阴影)
· edge antialiasing(抗锯齿)
· group opacity(不透明)
需要注意的是,如果shouldRasterize被设置成YES,在触发离屏绘制的同时,会将光栅化后的内容缓存起来,如果对应的layer及其sublayers没有发生改变,在下一帧的时候可以直接复用。这将在很大程度上提升渲染性能。
而其它属性如果是开启的,就不会有缓存,离屏绘制会在每一帧都发生。
四、另一种特殊的“离屏渲染”
按照之前的说法,如果将不在GPU的当前屏幕缓冲区中进行的渲染都称为离屏渲染,那么就还有另一种特殊的“离屏渲染”方式:CPU渲染。
如果我们重写了drawRect方法,并且使用任何Core Graphics的技术进行了绘制操作,就涉及到了CPU渲染。整个渲染过程由CPU在App内同步地完成,渲染得到的bitmap最后再交由GPU用于显示。
五、Instruments
Instruments的Core Animation工具中有几个和离屏渲染相关的检查选项:
· Color Offscreen-Rendered Yellow
开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。
· Color Hits Green and Misses Red
如果shouldRasterize被设置成YES,对应的渲染结果会被缓存,如果图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。
六、如何抉择
现在摆在我们面前得有三个选择:当前屏幕渲染、离屏渲染、CPU渲染,该用哪个呢?这需要根据具体的使用场景来决定。
· 尽量使用当前屏幕渲染
鉴于离屏渲染、CPU渲染可能带来的性能问题,一般情况下,我们要尽量使用当前屏幕渲染。
· 离屏渲染 VS CPU渲染
由于GPU的浮点运算能力比CPU强,CPU渲染的效率可能不如离屏渲染;但如果仅仅是实现一个简单的效果,直接使用CPU渲染的效率又可能比离屏渲染好,毕竟离屏渲染要涉及到缓冲区创建和上下文切换等耗时操作。
总之,具体的选择应该由性能测试结果来决定。
七、写在最后
在赵岩同学的点拨下才理解了离屏渲染的概念,在此表示感谢!
如理解有误还请大家指出。
http://imgtec.eetrend.com/d6-imgtec/blog/2018-08/17019.html
在第一章的1.2节中有提到渲染的流程图,我们再更深入点,先看看最基本的渲染通道流程:
引自WWDC2014 #419 Advanced Graphics and Animations for iOS Apps
注:iOS的GPU渲染机制是Tile-Based的,而Tile-Based GPU也是现在移动设备的主流;
我们再来看看需要Offscreen Render的渲染通道流程:
引自WWDC2014 #419 Advanced Graphics and Animations for iOS Apps
一般情况下,OpenGL会将应用提交到Render Server的动画直接渲染显示(基本的Tile-Based渲染流程),但对于一些复杂的图像动画的渲染并不能直接渲染叠加显示,而是需要根据Command Buffer分通道进行渲染之后再组合,这一组合过程中,就有些渲染通道是不会直接显示的;对比基本渲染通道流程和Masking渲染通道流程图,我们可以看到到Masking渲染需要更多渲染通道和合并的步骤;而这些没有直接显示在屏幕的上的通道(如上图的 Pass 1 和 Pass 2)就是Offscreen Rendering Pass。
Offscreen Render为什么卡顿,从上图我们就可以知道,Offscreen Render需要更多的渲染通道,而且不同的渲染通道间切换需要耗费一定的时间,这个时间内GPU会闲置,当通道达到一定数量,对性能也会有较大的影响;
那哪些情况会Offscreen Render呢?
1) drawRect 2) layer.shouldRasterize = true; 3) 有mask或者是阴影(layer.masksToBounds, layer.shadow*); 4)Text(UILabel, CATextLayer, Core Text, etc) ...
注:layer.cornerRadius,layer.borderWidth,layer.borderColor并不会Offscreen Render,因为这些不需要加入Mask。
还有更多与Offscreen Render以及动画图形优化相关的知识,请认真观看WWDC。
参考:
http://oncenote.com/2015/12/08/How-to-build-UI/