离屏渲染是在iOS开发过程中脱离不了的话题,那么什么是离屏渲染以及哪些情况会导致离屏渲染呢?以及离谱渲染有哪些优势和劣势?
首先看下面一段代码,那种模式会触发离屏渲染呢?
XCode查看离屏渲染 测试效果//按钮存在背景图片,并且设置圆角
let btn1 =UIButton.init(type: .custom)
btn1.frame=CGRect.init(x:100, y:100, width:44, height:44)
btn1.layer.cornerRadius=5.0
self.view.addSubview(btn1)
btn1.setImage(UIImage.init(named:"tabbar_discover_hl"), for: .normal)
btn1.clipsToBounds=true
//按钮不存在背景图片,并且设置圆角
let btn2 =UIButton(type: .custom)
btn2.frame=CGRect.init(x:100, y:180, width:44, height:44)
btn2.layer.cornerRadius=5.0
btn2.backgroundColor = .blue
self.view.addSubview(btn2)
btn2.clipsToBounds=true
//图片设置了背景色+背景图片
let imgv1 =UIImageView(frame:CGRect.init(x:100, y:260, width:44, height:44))
imgv1.image=UIImage.init(named:"tabbar_friends_hl")
imgv1.backgroundColor = .blue
imgv1.layer.cornerRadius=5.0
imgv1.layer.masksToBounds=true
self.view.addSubview(imgv1)
//图片只设置了背景色
let imgv2 =UIImageView(frame:CGRect.init(x:100, y:340, width:44, height:44))
imgv2.image=UIImage.init(named:"tabbar_mine_hl")
imgv2.layer.cornerRadius=5.0
imgv2.layer.masksToBounds=true
self.view.addSubview(imgv2)
从上图可知,1、3中写法触发了离屏渲染,2、4两种写法未触发离屏渲染;
APP渲染流程
渲染流水线示意图从图中可以看到(图片来源:WWDC),Application以及RenderServer部分是运行在CPU上的,Application处理好数据后,提交到Render Server,然后Core Animation在会将具体操作转换成GPU的Draw calls(OpenGL/Metal);也就是CPU+GPU共同完成渲染工作
什么是离屏渲染:
如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的frame buffer,作为像素数据存储区域,而这也是GPU存储渲染结果的地方。如果有时因为面临一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染(offscreen buffer)
通过下面两幅图结合上面的概念可以清晰离屏渲染的原理:
渲染结果直接给到FrameBuffer,然后展示到屏幕上 渲染结果线给到offscreenBuffer,在到FrameBuffer,然后展示到屏幕上那么离屏渲染是如何工作的呢?通过圆角触发离屏渲染来进行分析
设置圆角触发offscreen render从上图可知,给UIButton设置背景图片并且添加圆角涉及3部分内容
1、backgroundColor
2、contents
3、borderColor/borderWidth
当每一部分layer会独立绘制,然后保存到offscreenBuffer中,当全部layer绘制后,对这3部分整体进行添加圆角,所以需要先存入到offscreenBuffer中,否则绘制完会自动销毁,当设置圆角时无法获取到之前的layer;
绘制原理图示GPU离屏渲染,“画家画法”
通过渲染流水线示意图中我们可以看到,主要的渲染操作都是由CoreAnimation的Render Server模块,通过调用显卡驱动所提供的OpenGL/Metal接口来执行的。通常对于每一层layer,Render Server会遵循“画家算法”,按次序输出到frame buffer,后一层覆盖前一层,就能得到最终的显示结果(值得一提的是,与一般桌面架构不同,在iOS中,设备主存和GPU的显存共享物理内存,这样可以省去一些数据传输开销)
然而有些场景并没有那么简单。作为“画家”的GPU虽然可以一层一层往画布上进行输出,但是无法在某一层渲染完成之后,再回过头来擦除/改变其中的某个部分——因为在这一层之前的若干层layer像素数据,已经在渲染中被永久覆盖了。这就意味着,对于每一层layer,要么能找到一种通过单次遍历就能完成渲染的算法,要么就不得不另开一块内存,借助这个临时中转区域来完成一些更复杂的、多次的修改/剪裁操作
当sublayer绘制到屏幕上之后,就会将sublayer从帧缓存区移除,从而节省空间 将要混合的内容各自渲染完,暂时存在离屏缓存区 等所有layer全部绘制完成,从offscreenBuffer中获取数据,进行组合通过上面的理解,可以发现,App需要进行额外的渲染和合并,必须使用Offscreen Buffer,然后进行组合放入到Frame Buffer中,然后现在到屏幕上
离屏渲染性能问题:
1,需要额外的存储空间,
2,将结果从Offscreen buffer转存到Frame Buffer中也是需要时间的
Offscreen Buffer的空间也是有限制的(限制大小是屏幕像素大小的2.5倍)
既然离屏渲染容易带来性能问题,为什么还要用呢?
1.非常多的特殊效果,并不能一次用一个图层就能画出来,所以需要使用额外的offscreen Buffer来保持中间状态(不得不使用),比如:圆角,阴影
2.能带来效率的优势:既然效果会多次出现在屏幕上,可提前渲染好,保存在offscreen Buffer中,从而达到复用的结果 比如:光珊化shouldRasterize
常见离屏渲染:
能引起离屏渲染所以我们在项目开发的过程中,根据实际情况进行相关的优化,突出其优势避免其劣势
特别说明:关于光珊化并不是所有的光珊化都会带来性能问题,以下是关于光珊化的使用建议
光珊化使用建议
网友评论