一、 iOS的渲染架构
架构图.jpg
我们可以看到,在Application这一层中主要是CPU在操作,而到了Render Server这一层,CoreAnimation会将具体操作转换成发送给GPU的draw calls(以前是call OpenGL ES,现在慢慢转到了Metal),显然CPU和GPU双方同处于一个流水线中,协作完成整个渲染工作。
二、离屏渲染定义
- 如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的frame buffer(帧缓冲区),作为像素数据的存储区域,而这也是GPU存储渲染结果的的地方。如果有时候因为面临一些限制,无法把渲染结果直接写入frame buffer ,而是先暂时存放在另外的内存区域(offscreen Buffer),之后再写入frame buffer,称为离屏渲染。
离屏渲染的存在的问题
1、offScreenBuffer空间大小有限制,为屏幕像素点的2.5倍。
2、容易掉帧,产生性能问题。
3、渲染需要时间,会产生性能问题。
离屏渲染触发的几种情况:
1、使用mask(遮罩)的layer(layer.mask).
2、需要进行裁剪的layer(layer.masksToBounds/ view.clipsToBounds)
3、设置了组透明度为YES,并且透明度不为1度layer(layer.allowsGroup/layer.opacity)
4、添加了投影的layer()
5、采用了shouldRasterize光栅化
6、绘制了文字的layer
总结:导致离屏渲染的是因为多个layer叠加合并渲染导致。
CPU”离屏渲染“
大家知道,如果我们在UIView中实现了drawRect方法,就算它的函数体内部实际没有代码,系统也会为这个view申请一块内存区域,等待CoreGraphics可能的绘画操作。
对于类似这种“新开一块CGContext来画图“的操作,有很多文章和视频也称之为“离屏渲染”(因为像素数据是暂时存入了CGContext,而不是直接到了frame buffer)。进一步来说,其实所有CPU进行的光栅化操作(如文字渲染、图片解码),都无法直接绘制到由GPU掌管的frame buffer,只能暂时先放在另一块内存之中,说起来都属于“离屏渲染”。
自然我们会认为,因为CPU不擅长做这件事,所以我们需要尽量避免它,就误以为这就是需要避免离屏渲染的原因。但是根据苹果工程师的说法,CPU渲染并非真正意义上的离屏渲染。另一个证据是,如果你的view实现了drawRect,此时打开Xcode调试的“Color offscreen rendered yellow”开关,你会发现这片区域不会被标记为黄色,说明Xcode并不认为这属于离屏渲染。
其实通过CPU渲染就是俗称的“软件渲染”,而真正的离屏渲染发生在GPU。
GPU离屏渲染
在上面的渲染流水线示意图中我们可以看到,主要的渲染操作都是由CoreAnimation的Render Server模块,通过调用显卡驱动所提供的OpenGL/Metal接口来执行的。通常对于每一层layer,Render Server会遵循“画家算法”,按次序输出到frame buffer,后一层覆盖前一层,就能得到最终的显示结果(值得一提的是,与一般桌面架构不同,在iOS中,设备主存和GPU的显存共享物理内存,这样可以省去一些数据传输开销)。
画家算法.png
然而有些场景并没有那么简单。作为“画家”的GPU虽然可以一层一层往画布上进行输出,但是无法在某一层渲染完成之后,再回过头来擦除/改变其中的某个部分——因为在这一层之前的若干层layer像素数据,已经在渲染中被永久覆盖了。这就意味着,对于每一层layer,要么能找到一种通过单次遍历就能完成渲染的算法,要么就不得不另开一块内存,借助这个临时中转区域来完成一些更复杂的、多次的修改/剪裁操作。
如果要绘制一个带有圆角并剪切圆角以外内容的容器,就会触发离屏渲染。我的猜想是(如果读者中有图形学专家希望能指正):
将一个layer的内容裁剪成圆角,可能不存在一次遍历就能完成的方法
容器的子layer因为父容器有圆角,那么也会需要被裁剪,而这时它们还在渲染队列中排队,尚未被组合到一块画布上,自然也无法统一裁剪
此时我们就不得不开辟一块独立于frame buffer的空白内存,先把容器以及其所有子layer依次画好,然后把四个角“剪”成圆形,再把结果画到frame buffer中。这就是GPU的离屏渲染。
三、离屏渲染原理
在我们渲染一组layer时,若不使用离屏渲染,则会出现第一个layer渲染完后,存入帧缓冲区,然后显示在屏幕上,到第二个layer渲染完后,存入帧缓冲区,再显示再屏幕上,第一个layer就会消失,从而出现问题。
那么使用离屏渲染,系统会将每个layer渲染完的结果存放到离屏缓冲区(OffScreen Buffer),最后将这些layer存入帧缓冲区,进而显示到屏幕上,这样就能完整的显示整个图像。
总结:
1、离屏渲染只有当帧缓冲区一次性解决不了图形显示的时候,才会由系统自动触发
2、不是所有的圆角+maskToBounds都会触发离屏渲染
3、离屏渲染需要消耗性能,所以在常见可以优化的地方,不要触发离屏渲染
4、如果有性能特殊要求,可以通过打开光栅化shouldRasterize=YES;. 来进行离屏渲染的复用,如果这个layer需要以及可以被复用的话











网友评论