基本概念

在OpenGL中,GPU屏幕渲染有以下两种方式:

Onscreen-Rendered:当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。
Offscreen-Rendered:即离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。

Offscreen-rendered缺点

与当前屏幕渲染相比,离屏渲染的代价是很高的,主要体现在两个方面:

创建新缓冲区:要想进行离屏渲染,首先要创建一个新的缓冲区。
上下文切换:离屏渲染的整个过程,需要多次切换上下文环境。先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上又需要将上下文环境从离屏切换到当前屏幕。但是,上下文环境的切换是要付出很大代价的。

哪些可引起Offscreen-rendered

有以下方式可以引起离屏渲染:

shadows(阴影)
cornerRadius+clipToBounds/maskToBounds设置圆角
shouldRasterize设置为YES
masks(遮罩)
edge antialiasing(抗锯齿)
group opacity(不透明)
重写drawRect交由CPU渲染

这里只作圆角和阴影测试。

离屏渲染的检测方法

模拟器的 Debug -> 选取 Color Off-screen Rendered.

Color Offscreen-Rendered Yellow
开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。

Color Hits Green and Misses Red
如果shouldRasterize被设置成YES,对应的渲染结果会被缓存,如果图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。

测试开始

圆角测试

    CGFloat imageW = 180.0;
    UIImage *image = [UIImage imageNamed:@"example.jpg"];
    //方法1:直接调用系统方法
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    imageView.contentMode = UIViewContentModeScaleAspectFill;
    imageView.frame = CGRectMake(0, 30, imageW, imageW);
    imageView.centerX = self.view.centerX;
    imageView.layer.cornerRadius = 30;
    imageView.layer.masksToBounds = YES;
    [self.view addSubview:imageView];
    //方法2:图片绘制圆角
    CGFloat cornerRadius = 30.0 / imageW * MIN(image.size.width, image.size.height);
    UIImageView *imageView2 = [[UIImageView alloc] initWithImage:[image roundedWithRadius:cornerRadius]];
    imageView2.contentMode = UIViewContentModeScaleAspectFill;
    imageView2.frame = CGRectMake(0, 0, imageW, imageW);
    imageView2.centerX = self.view.centerX;
    imageView2.centerY = self.view.centerY + 10;
    [self.view addSubview:imageView2];
    //方法3:内部也是调用了方法2
    UIImageView *imageView3 = [UIImageView imageViewWithImage:image imageViewSize:CGSizeMake(imageW, imageW) cornerRadius:30];
    imageView3.contentMode = UIViewContentModeScaleAspectFill;
    imageView3.frame = CGRectMake(0, 0, imageW, imageW);
    imageView3.centerX = self.view.centerX;
    imageView3.y = self.view.height - imageW - 10;
    [self.view addSubview:imageView3];

方法1:使用cornerRadius+masksToBounds方法绘制圆角,从图中可以看到圆角处变成了黄色,说明触发了离屏渲染。
方法2与方法3:内部都采取对图片绘制圆角,没有变成黄色。

圆角+阴影测试

    CGFloat imageW = 180.0;
    UIImage *image = [UIImage imageNamed:@"example.jpg"];
    //方法1:直接调用系统方法
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    imageView.contentMode = UIViewContentModeScaleAspectFill;
    imageView.frame = CGRectMake(0, 30, imageW, imageW);
    imageView.centerX = self.view.centerX;
    imageView.layer.cornerRadius = 30;
    imageView.layer.shadowOffset = CGSizeMake(4, 4);
    imageView.layer.shadowColor = [UIColor redColor].CGColor;
    imageView.layer.shadowRadius = 3;
    imageView.layer.shadowOpacity = 0.5;
    //只是通过设置layer属性,圆角跟阴影是无法共存的,masksToBounds设置为YES会造成阴影无法显示,设置为NO则圆角无法显示。
    imageView.layer.masksToBounds = NO;
    [self.view addSubview:imageView];
    //方法2:图片绘制圆角,通过设置阴影达到圆角与阴影共存
    CGFloat cornerRadius = 30.0 / imageW * MIN(image.size.width, image.size.height);
    UIImageView *imageView2 = [[UIImageView alloc] initWithImage:[image roundedWithRadius:cornerRadius]];
    imageView2.contentMode = UIViewContentModeScaleAspectFill;
    imageView2.frame = CGRectMake(0, 0, imageW, imageW);
    imageView2.layer.shadowOffset = CGSizeMake(4, 4);
    imageView2.layer.shadowColor = [UIColor redColor].CGColor;
    imageView2.layer.shadowRadius = 3;
    imageView2.layer.shadowOpacity = 0.5;
    imageView2.centerX = self.view.centerX;
    imageView2.centerY = self.view.centerY + 10;
    [self.view addSubview:imageView2];
    //方法3:内部也是调用了方法2,只不过多设置了一个shadowPath
    UIImageView *imageView3 = [UIImageView imageViewWithImage:image imageViewSize:CGSizeMake(imageW, imageW) cornerRadius:30 horizontal:4 vertical:4 shadowColor:[UIColor redColor] shadowOpacity:0.5 shadowRadius:3];
    imageView3.contentMode = UIViewContentModeScaleAspectFill;
    imageView3.frame = CGRectMake(0, 0, imageW, imageW);
    imageView3.centerX = self.view.centerX;
    imageView3.y = self.view.height - imageW - 10;
    [self.view addSubview:imageView3];

方法1:通过layer属性设置圆角与阴影。通过此方法,圆角跟阴影是无法共存的,masksToBounds设置为YES会造成阴影无法显示,设置为NO则圆角无法显示。而且从图中可以看到,整个UIImageView都变成了黄色,说明阴影的设置触发了离屏渲染。
方法2:通过上面圆角测试中的方法2对圆角绘制圆角,然后通过layer添加阴影。从图中可以看到,圆角与阴影共存了,整个UIImageView都变成了黄色,说明阴影的设置触发了离屏渲染。
方法3:也是通过layer方法添加阴影,但是多添加了一个shadowPath。

imageView.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds
                                                       byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)].CGPath;

主要绘制方法

UIImage+TM.h

//
//  UIImage+TM.h
//  TMDemo
//
//  Created by TIM on 2018/7/23.
//  Copyright © 2018年 tim. All rights reserved.
//

#import <UIKit/UIKit.h>

// 圆角
typedef NS_ENUM(NSInteger, YKImageRoundedCornerCorner) {
    YKImageRoundedCornerCornerTopLeft = 1,
    YKImageRoundedCornerCornerTopRight = 1 << 1,
    YKImageRoundedCornerCornerBottomRight = 1 << 2,
    YKImageRoundedCornerCornerBottomLeft = 1 << 3
};

@interface UIImage (TM)

/**
 高性能绘圆形图片
 
 @return 圆形图片
 */
- (UIImage *)rounded;

/**
 高性能绘制圆角图片(默认绘制4个圆角)
 
 @param radius 圆角
 @return 圆角图片
 */
- (UIImage *)roundedWithRadius:(CGFloat)radius;

/**
 高性能绘制圆角图片
 
 @param radius 圆角
 @param cornerMask 要绘制的圆角
 @return 圆角图片
 */
- (UIImage *)roundedWithRadius:(CGFloat)radius cornerMask:(YKImageRoundedCornerCorner)cornerMask;

@end

UIImage+TM.m

//
//  UIImage+TM.m
//  TMDemo
//
//  Created by TIM on 2018/7/23.
//  Copyright © 2018年 tim. All rights reserved.
//

#import "UIImage+TM.h"

@implementation UIImage (TM)

// UIKit坐标系统原点在左上角,y方向向下的(坐标系A),但在Quartz中坐标系原点在左下角,y方向向上的(坐标系B)。图片绘制也是颠倒的。
void addRoundedRectToPath(CGContextRef context, CGRect rect, float radius, YKImageRoundedCornerCorner cornerMask)
{
    //原点在左下方,y方向向上。移动到线条2的起点。
    CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
    //画出线条2, 目前画线的起始点已经移动到线条2的结束地方了。
    CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
    //如果左上角需要画圆角,画出一个弧线出来。
    if (cornerMask & YKImageRoundedCornerCornerTopLeft)
    {
        //已左上的正方形的右下脚为圆心,半径为radius, 180度到90度画一个弧线,
        CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius,
                        radius, M_PI, M_PI / 2, 1);
    }
    else
    {
        //如果不需要画左上角的弧度。从线2终点,画到线3的终点,
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height);
        //线3终点,画到线4的起点
        CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y + rect.size.height);
    }
    //画线4的起始,到线4的终点
    CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius,
                            rect.origin.y + rect.size.height);
    //画右上角
    if (cornerMask & YKImageRoundedCornerCornerTopRight)
    {
        CGContextAddArc(context, rect.origin.x + rect.size.width - radius,
                        rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
    }
    else
    {
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height - radius);
    }
    CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + radius);
    //画右下角弧线
    if (cornerMask & YKImageRoundedCornerCornerBottomRight)
    {
        CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + radius,
                        radius, 0.0f, -M_PI / 2, 1);
    }
    else
    {
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius, rect.origin.y);
    }
    CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
    //画左下角弧线
    if (cornerMask & YKImageRoundedCornerCornerBottomLeft)
    {
        CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius,
                        -M_PI / 2, M_PI, 1);
    }
    else
    {
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + radius);
    }
    CGContextClosePath(context);
}

- (UIImage *)rounded
{
    if (!self) return nil;
    CGFloat radius = MIN(self.size.width, self.size.height) / 2.0;
    return [self roundedWithRadius:radius];
}

- (UIImage *)roundedWithRadius:(CGFloat)radius
{
    return [self roundedWithRadius:radius cornerMask:YKImageRoundedCornerCornerBottomLeft | YKImageRoundedCornerCornerBottomRight | YKImageRoundedCornerCornerTopLeft | YKImageRoundedCornerCornerTopRight];
}

- (UIImage *)roundedWithRadius:(CGFloat)radius cornerMask:(YKImageRoundedCornerCorner)cornerMask
{
    if (!self) return nil;
    if (radius <= 0) return self;
    //UIImage绘制为圆角
    int w = self.size.width;
    int h = self.size.height;
    UIImage *newImage = self;
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
    CGRect rect = CGRectMake(0, 0, w, h);
    CGContextBeginPath(context);
    addRoundedRectToPath(context, rect, radius, cornerMask);
    CGContextClosePath(context);
    CGContextClip(context);
    
    CGContextDrawImage(context, CGRectMake(0, 0, w, h), newImage.CGImage);
    CGImageRef imageMasked = CGBitmapContextCreateImage(context);
    newImage = [UIImage imageWithCGImage:imageMasked];
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    CGImageRelease(imageMasked);
    
    return newImage;
}

@end

UIImageView+TM.h

//
//  UIImageView+TM.h
//  TMDemo
//
//  Created by TIM on 2018/7/23.
//  Copyright © 2018年 tim. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIImageView (TM)

/**
 高性能绘制带圆角图片
 
 @param image 原始图片
 @param imageViewSize UIImageView的size,绘制必须先确定size
 @param cornerRadius 圆角
 @return UIImageView
 */
+ (UIImageView *)imageViewWithImage:(UIImage *)image
                      imageViewSize:(CGSize)imageViewSize
                       cornerRadius:(CGFloat)cornerRadius;

/**
 高性能绘制带圆角+阴影图片
 
 @param image 原始图片
 @param imageViewSize UIImageView的size,绘制必须先确定size
 @param cornerRadius 圆角
 @param horizontal 水平阴影
 @param vertical 垂直阴影
 @param shadowColor 阴影颜色
 @param shadowOpacity shadowOpacity
 @param shadowRadius shadowRadius
 @return UIImageView
 */
+ (UIImageView *)imageViewWithImage:(UIImage *)image
                      imageViewSize:(CGSize)imageViewSize
                       cornerRadius:(CGFloat)cornerRadius
                         horizontal:(CGFloat)horizontal
                           vertical:(CGFloat)vertical
                        shadowColor:(UIColor *)shadowColor
                      shadowOpacity:(float)shadowOpacity
                       shadowRadius:(CGFloat)shadowRadius;

@end

UIImageView+TM.m

//
//  UIImageView+TM.m
//  TMDemo
//
//  Created by TIM on 2018/7/23.
//  Copyright © 2018年 tim. All rights reserved.
//

#import "UIImageView+TM.h"
#import "UIImage+TM.h"

@implementation UIImageView (TM)

+ (UIImageView *)imageViewWithImage:(UIImage *)image
                      imageViewSize:(CGSize)imageViewSize
                       cornerRadius:(CGFloat)cornerRadius
{
    if (image && cornerRadius > 0)
    {
        //cornerRadius是相对imageViewSize的圆角,需要转换成image的实际圆角
        CGFloat radis = cornerRadius / MIN(imageViewSize.width, imageViewSize.height) * MIN(image.size.width, image.size.height);
        image = [image roundedWithRadius:radis cornerMask:YKImageRoundedCornerCornerBottomLeft | YKImageRoundedCornerCornerBottomRight | YKImageRoundedCornerCornerTopLeft | YKImageRoundedCornerCornerTopRight];
    }
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    CGRect frame = imageView.frame;
    frame.size = imageViewSize;
    imageView.frame = frame;
    return imageView;
}

+ (UIImageView *)imageViewWithImage:(UIImage *)image
                      imageViewSize:(CGSize)imageViewSize
                       cornerRadius:(CGFloat)cornerRadius
                         horizontal:(CGFloat)horizontal
                           vertical:(CGFloat)vertical
                        shadowColor:(UIColor *)shadowColor
                      shadowOpacity:(float)shadowOpacity
                       shadowRadius:(CGFloat)shadowRadius
{
    UIImageView *imageView = [self imageViewWithImage:image imageViewSize:imageViewSize cornerRadius:cornerRadius];
    imageView.layer.shadowOffset = CGSizeMake(horizontal, vertical);
    imageView.layer.shadowColor = [shadowColor CGColor];
    imageView.layer.shadowOpacity = shadowOpacity;
    imageView.layer.shadowRadius = shadowRadius;
    imageView.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds
                                                       byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)].CGPath;
    return imageView;
}

@end

提呐个莫
32 声望9 粉丝