什么是离屏渲染?
离屏渲染就是在屏幕之外渲染图形图像,不会直接显示到屏幕上,等待合适的时机再显示。
什么情况下会触发离屏渲染?
说几种iOS开发的过程中常见的离屏渲染:
1. 使用了 mask 的 layer (layer.mask)
2. 需要进行裁剪的 layer (layer.masksToBounds / view.clipsToBounds)
3. 设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/ layer.opacity)
4. 添加了投影的 layer (layer.shadow*)
5. 采用了光栅化的 layer (layer.shouldRasterize)
6. 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
为什么会触发离屏渲染?
当app在渲染的过程中需要使用额外的渲染或者合并的时候就会触发离屏渲染。
比如我们在UIImageView上加了一个mask:
由于UIImageView本身就存在一个layer,所以这就是上面提到的第一种会触发离屏渲染的情况。在渲染的时候就需要先渲染我们原始的图片,将渲染结果放到离屏缓冲区(offscreen buffer)里面,再渲染mask图层,也将其结果存储到离屏缓冲区里,最后将这两个结果混合渲染的结果放到帧缓冲区里面,在下一次runloop到来的时候将其显示到屏幕上。
离屏渲染会带来什么问题?
1、离屏渲染需要额外的存储空间,它需要存储一些渲染过程中的一些中间的结果。而我们的离屏缓冲区是有大小限制的,在苹果关于光栅化(shouldRasterize)解释了离屏缓冲区的大小为2.5倍屏幕大小。超出其大小的结果会被丢弃。
2、会带来性能问题,最常见的就是掉帧问题(iOS中的掉帧问题)。
为什么要使用离屏渲染?
既然离屏渲染会带来各种各样的问题,我们为什么还要用离屏渲染呢?
1、一些特殊的效果,不得不使用离屏渲染。比如 使用系统的毛玻璃(UIVisualEffectView)效果,使用圆角、阴影等,它们都是由系统自动触发的离屏渲染。
2、效率优势,有些效果我们需要多次频繁的使用,就可以提前准备在离屏缓冲区(offscreen Buffer)里面,从而达到复用的目的,是渲染速度更快捷。
如何避免离屏渲染?
1、减少使用有透明度叠加的效果。
2、使用圆角的时候不是系统自带的,自己实现圆角效果(比如使用贝塞尔曲线)
注:在UIView里面使用layer.cornerRadius并不会直接触发离屏渲染,它只会对view的背景、边框进行圆角。
以上就是我对离屏渲染的理解,最后附上YYImage对圆角的处理(YYImage源码地址):
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius corners:(UIRectCorner)corners
borderWidth:(CGFloat)borderWidth
borderColor:(UIColor *)borderColor borderLineJoin:(CGLineJoin)borderLineJoin {
if (corners != UIRectCornerAllCorners) {
UIRectCorner tmp = 0;
if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight; if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight; corners = tmp;
}
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale); CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); CGContextScaleCTM(context, 1, -1); CGContextTranslateCTM(context, 0, -rect.size.height);
CGFloat minSize = MIN(self.size.width, self.size.height); if (borderWidth < minSize / 2) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
[path closePath];
CGContextSaveGState(context);
[path addClip];
CGContextDrawImage(context, rect, self.CGImage); CGContextRestoreGState(context);
}
if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius,
borderWidth)];
[path closePath];
path.lineWidth = borderWidth; path.lineJoinStyle = borderLineJoin; [borderColor setStroke];