一、OpenGL中,GPU屏幕渲染有以下两种方式
-
On-Screen Rendering 当前屏幕渲染: 是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。
-
Off-Screen Rendering 离屏渲染: GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
二、离屏渲染定义:
通常情况下,我们在屏幕上显示都是读取帧缓冲区(Frame Buffer)渲染好的的数据,然后显示在屏幕上;而离屏渲染,则是将渲染好的图像放入离屏缓冲区(OffScreen Buffer)等多个图层的数据渲染完,再组合在一起,放入帧缓冲区,然后在屏幕上显示。
如上图,要在屏幕上显示图的ImageView,通常GPU的Render Server会遵循 “画家算法” 按秩序先渲染图1的那一层,然后渲染图2的那一层,最后渲染图3,渲染好后的每一层都会存入帧缓存区,然后按照次序绘制到屏幕,当绘制完一层,就会将该层从帧缓存区中移除(以节省空间)。
当对图3显示需要进行圆角和裁剪:imageView.clipsToBounds = YES,imageView.layer.cornerRadius=4.0时,渲染完图1,图2,图3,绘制到屏幕上后,还要进行裁减,无法在某一层渲染完成之后,再回过头来擦除/改变其中的某个部分,所以不能按照正常的流程,因此苹果会先渲染好每一层,存入一个缓冲区中,即离屏缓冲区,然后经过层叠加和处理后,再存储到帧缓存去中,然后绘制到屏幕上,这种处理方式叫做离屏渲染。
三、如何检测项目中哪些图层触发了离屏渲染问题:
- 首先我们先开启离屏渲染的检测,在模拟器打开Color Off-screen Rendered,开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。
//1.按钮存在背景图片
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
btn1.frame = CGRectMake(100, 30, 100, 100);
btn1.layer.cornerRadius = 50;
[self.view addSubview:btn1];
[btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
btn1.clipsToBounds = YES;
//2.按钮不存在背景图片
UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
btn2.frame = CGRectMake(100, 180, 100, 100);
btn2.layer.cornerRadius = 50;
btn2.backgroundColor = [UIColor blueColor];
[self.view addSubview:btn2];
btn2.clipsToBounds = YES;
//3.UIImageView 设置了图片+背景色;
UIImageView *img1 = [[UIImageView alloc]init];
img1.frame = CGRectMake(100, 320, 100, 100);
img1.backgroundColor = [UIColor blueColor];
[self.view addSubview:img1];
img1.layer.cornerRadius = 50;
img1.layer.masksToBounds = YES;
img1.image = [UIImage imageNamed:@"btn.png"];
//4.UIImageView 只设置了图片,无背景色;
UIImageView *img2 = [[UIImageView alloc]init];
img2.frame = CGRectMake(100, 480, 100, 100);
[self.view addSubview:img2];
img2.layer.cornerRadius = 50;
img2.layer.masksToBounds = YES;
img2.image = [UIImage imageNamed:@"btn.png"];
这里就明显看出1和3变成了黄色,标记为触发了离屏渲染。
四、CALayer的层次结构及cornerRadius
-
CALayer由背景色backgroundColor、内容contents、边缘borderWidth&borderColor构成。
-
cornerRadius
绘制图层背景圆角时使用的半径.可设置动画.
设置layer.cornerRaclius只会设置backgroundColor的圆角.不会设置content的圆角,除非设置了layer.masksToBounds 为Ture(view中的clipsToBounds)
cornerRadius并不是对content(image)有效,根据苹果官方解释:cornerRadius 的文档中明确说明对 cornerRadius 的设置只对 CALayer 的 backgroundColor 和 borderWidth、borderColor起作用。
cornerRadius+masksToBounds 只有在设置了content且背景不是透明时,才会出现离屏渲染。
如果一定要使用cornerRadius+masksToBounds的方式裁切图片,不要设置backgroundColor。
五、离屏渲染有哪些问题
离屏渲染的代价很高,想要进行离屏渲染,首选要创建一个新的缓冲区,屏幕渲染会有一个上下文环境的一个概念,离屏渲染的整个过程需要切换上下文环境,先从当前屏幕切换到离屏,等结束后,又要将上下文环境切换回来。这也是为什么会消耗性能的原因了
- 内存开支:开辟离屏缓冲区(大小不超过2.5倍屏幕像素大小)
- 时间和性能开支:从离屏缓冲区拷贝数据到帧缓冲区,上下文切换耗性能
六、为什么要要使用离屏渲染
- 用户需要特殊的渲染效果:使用额外的离屏缓冲区(offscreen butter)保存中间状态,最后叠加、处理后绘制在屏幕上,这样就不得不使用离屏渲染
- 效率优势:需要多次使用的效果,提前渲染存入离屏缓冲区,然后复用来提高效率
七、解决标题三出现的问题
- YY_image处理圆角的方法 - (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius corners:(UIRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor borderLineJoin:(CGLineJoin)borderLineJoin
为UIImage类扩展一个实例函数,仿YYImage做法
- (UIImage *)imageWithCornerRadius:(CGFloat)radius ofSize:(CGSize)size{
/* 当前UIImage的可见绘制区域 */
CGRect rect = (CGRect){0.f,0.f,size};
/* 创建基于位图的上下文 */
UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
/* 在当前位图上下文添加圆角绘制路径 */
CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath);
/* 当前绘制路径和原绘制路径相交得到最终裁剪绘制路径 */
CGContextClip(UIGraphicsGetCurrentContext());
/* 绘制 */
[self drawInRect:rect];
/* 取得裁剪后的image */
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
/* 关闭当前位图上下文 */
UIGraphicsEndImageContext();
return image;
}
- Core Graphics方式 用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角
- CAShapeLayer 方式
八、其他情况常见的触发离屏渲染以及解决办法:
- mask(遮罩)------>使用混合图层,在layer上方叠加相应mask形状的半透明layer
2.edge antialiasing(抗锯齿)----->不设置 allowsEdgeAntialiasing 属性为YES(默认为NO)
- allowsGroupOpacity(组不透明,开启CALayer的allowsGroupOpacity属性后,子 layer 在视觉上的透明度的上限是其父 layer 的opacity(对应UIView的alpha),并且从 iOS 7 以后默认全局开启了这个功能,这样做是为了让子视图与其容器视图保持同样的透明度。)------->关闭 allowsGroupOpacity 属性,按产品需求自己控制layer透明度
4.shadows(阴影)------>设置阴影后,设置CALayer的 shadowPath,view.layer.shadowPath=[UIBezierPath pathWithCGRect:view.bounds].CGPath;
CALayer离屏渲染终极解决方案:当视图内容是静态不变时,设置 shouldRasterize(光栅化)为YES(缓存离屏渲染的数据,当下次用到的时候直接拿,不需要开辟新的离屏缓冲区),此方案最为实用方便。view.layer.shouldRasterize = true;view.layer.rasterizationScale = view.layer.contentsScale;
shouldRasterize (光栅化使用建议):
1.如果layer不需要服用,则没有必要打开
2.如果layer不是静态的,需要被频繁修改,比如出于动画之中,则开启光栅化反而影响性能
3.离屏渲染缓存有时间限制,当超过100ms,内容没有被使用就会被丢弃,无法复用
4.离屏渲染缓存有空间限制,超过屏幕像素的2.5倍则失效,并无法使用
面试小结
在UITableVIewCell触发了离屏渲染,会导致在滑动的时候高频率的开辟离屏缓冲区,这样就会造成tableView滑动卡顿,如果视图内容是静态不变时,设置 shouldRasterize(光栅化)为YES,此方案最为实用方便,但是当视图内容是动态变化(如后台下载图片完毕后切换到主线程设置)时,使用此方案反而为增加系统负荷。