离屏渲染是之在非当前屏幕缓冲区进行渲染,如果重写了drawRect并使用了Core Graphics进行了绘制操作就涉及到了cpu渲染,渲染得到的bitmap由GPU用于显示。
CoreGraphics通常是线程安全的,所以可以进行异步绘制
通常,GPU 的渲染性能要比 CPU 高效很多,同时对系统的负载和消耗也更低一些。
CPU离屏渲染:core graphics是cpu渲染
GPU离屏渲染:需要开辟新的缓冲区渲染,绘制的时候需要上下文切换。
CALayer直接添加阴影会导致离屏渲染,但是添加阴影路径不会。离屏渲染直接结果有可能导致fps较低(fps越高越会得到流畅逼真的动画)。
GPU渲染机制:
CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
添加阴影:
方式一:直接添加(导致离屏渲染)
imgView.layer.shadowColor = [UIColor blackColor].CGColor;
imgView.layer.shadowOpacity = 0.8f;
imgView.layer.shadowRadius = 4.f;
imgView.layer.shadowOffset = CGSizeMake(4,4);
方式二:路径
//路径阴影
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(-5, -5)];
//添加直线
[path addLineToPoint:CGPointMake(paintingWidth /2, -15)];
[path addLineToPoint:CGPointMake(paintingWidth +5, -5)];
[path addLineToPoint:CGPointMake(paintingWidth +15, paintingHeight /2)];
[path addLineToPoint:CGPointMake(paintingWidth +5, paintingHeight +5)];
[path addLineToPoint:CGPointMake(paintingWidth /2, paintingHeight +15)];
[path addLineToPoint:CGPointMake(-5, paintingHeight +5)];
[path addLineToPoint:CGPointMake(-15, paintingHeight /2)];
[path addLineToPoint:CGPointMake(-5, -5)];
//设置阴影路径
imgView.layer.shadowPath = path.CGPath;
设置圆角:
方式一:(直接设置,导致离屏渲染)
aView.layer.cornerRadius=8;
aView.layer.masksToBounds=YES;
方式二:(设置路径,不会导致离屏渲染)
//设置所需的圆角位置以及大小UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:aView.bounds byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight cornerRadii:CGSizeMake(10,10)];
CAShapeLayer*maskLayer =[[CAShapeLayer alloc] init];
maskLayer.frame=aView.bounds;
maskLayer.path=maskPath.CGPath;
aView.layer.mask= maskLayer;
概念:
光栅化概念:将图转为为一个个栅格组成的图像
光栅化特点:每个元素对应帧缓冲区中的一像素
当属性是YES的时候,会生成位图并且和其他内容合成,当为False的时候是直接生成。
shouldRasterize = YES
光栅化会导致离屏渲染,但是同时也会将渲染的图片缓存,提高性能。因为缓存,光栅化对层级复杂的视图或者有复杂特效效果的图层性能提升明显。
如果图层内容经常变化,缓存就会无效,这个时候离屏渲染会降低性能。
相当于光栅化是把GPU的操作转到CPU上了,生成位图缓存,直接读取复用。
尽管离屏渲染开销很大,但是当我们无法避免它的时候,可以想办法把性能影响降到最低。优化思路也很简单:既然已经花了不少精力把图片裁出了圆角,如果我能把结果缓存下来,那么下一帧渲染就可以复用这个成果,不需要再重新画一遍了。
CALayer为这个方案提供了对应的解法:shouldRasterize。一旦被设置为true,Render Server就会强制把layer的渲染结果(包括其子layer,以及圆角、阴影、group opacity等等)保存在一块内存中,这样一来在下一帧仍然可以被复用,而不会再次触发离屏渲染。有几个需要注意的点:
shouldRasterize的主旨在于降低性能损失,但总是至少会触发一次离屏渲染。如果你的layer本来并不复杂,也没有圆角阴影等等,打开这个开关反而会增加一次不必要的离屏渲染
离屏渲染缓存有空间上限,最多不超过屏幕总像素的2.5倍大小
一旦缓存超过100ms没有被使用,会自动被丢弃
layer的内容(包括子layer)必须是静态的,因为一旦发生变化(如resize,动画),之前辛苦处理得到的缓存就失效了。如果这件事频繁发生,我们就又回到了“每一帧都需要离屏渲染”的情景,而这正是开发者需要极力避免的。针对这种情况,Xcode提供了“Color Hits Green and Misses Red”的选项,帮助我们查看缓存的使用是否符合预期
其实除了解决多次离屏渲染的开销,shouldRasterize在另一个场景中也可以使用:如果layer的子结构非常复杂,渲染一次所需时间较长,同样可以打开这个开关,把layer绘制到一块缓存,然后在接下来复用这个结果,这样就不需要每次都重新绘制整个layer树了。
渲染不是CPU的强项,调用CoreGraphics会消耗其相当一部分计算时间,并且我们也不愿意因此阻塞用户操作,因此一般来说CPU渲染都在后台线程完成(这也是AsyncDisplayKit的主要思想),然后再回到主线程上,把渲染结果传回CoreAnimation。这样一来,多线程间数据同步会增加一定的复杂度
同样因为CPU渲染速度不够快,因此只适合渲染静态的元素,如文字、图片(想象一下没有硬件加速的视频解码,性能惨不忍睹)
作为渲染结果的bitmap数据量较大(形式上一般为解码后的UIImage),消耗内存较多,所以应该在使用完及时释放,并在需要的时候重新生成,否则很容易导致OOM
如果你选择使用CPU来做渲染,那么就没有理由再触发GPU的离屏渲染了,否则会同时存在两块内容相同的内存,而且CPU和GPU都会比较辛苦
一定要使用Instruments的不同工具来测试性能,而不是仅凭猜测来做决定