ios的离屏渲染

在ios中,离屏渲染(Offscreen Rendering)指的是GPU需要创建一个新的缓冲区,在屏幕外进行图像处理,然后再将结果合成到屏幕上。

UIView和CALayer的区别

UIView继承自UIResponder,可以处理系统传递过来的事件,如:UIApplication、UIViewController、UIview,以及所有从UIview派生出来的UIKit类。每个UiView内部都有个CALayer提供内容的绘制和显示,并且作为内部RootLayer的代理视图。

CALayer继承NSObject类,负责显示UIview提供的内部contents,CALayer有三个视觉元素:背景色、内容和边框,其中,内容的本质是一个CGImage

界面渲染的过程

RunLoop有一个60fps的回调,即每16.7ms绘制一次屏幕,所以view的绘制必须在这个时间内完成,view内容的绘制是CPU的工作,然后把绘制的内容交给GPU渲染,包括多个View的拼接(Compositing)、纹理的渲染(Texture)等等,最后显示在屏幕上。

屏幕渲染的两种方式

·on-screen rendering:当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。当前屏幕渲染显示都是直接从帧缓冲区中读取数据然后直接显示。

·off-screen rendering :离屏渲染,指的是GPU在当前屏幕缓冲区以外开辟一个新的缓冲区(离屏缓冲区)进行渲染操作。等所有数据都在离屏渲染区完成渲染后才会提交到帧缓存区,然后被显示出来。

为什么要使用离屏渲染?

帧缓存区只是暂存显示内容,内容显示到屏幕上后,就直接被丢弃了。但是当使用圆角,阴影,遮罩的时候,图层属性的混合体被指定为在未预合成之前是不能直接在屏幕中绘制,也就是会所没办法一次渲染完成直接显示,所以需要建立一个新的屏幕外的渲染,使用离屏缓存区将内容分批保存,等待所有的内容渲染完成后再传到帧缓存区,以便显示。屏幕外渲染不意味着软件绘制,但是它意味着图层必须在被显示前在一个屏幕外上下文中被渲染(无论GPU还是CPU)。相比当前屏幕渲染,离屏渲染的代价是很高的,主要体现两个方面:

·创建新的缓存区:想要进行离屏渲染,首先要创建一个新的缓冲区。

·上下文切换:离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(on-screen)到到离屏(off-screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上又需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大的代价的。

离屏渲染的实例

我们来举一个例子

首先我要知道有些时候我们设置cornerRadius的时候,为什么设置不成功

官方文档告诉我们,设置cornerRadius只会对CALayer中的backgroundColor 和 boder设置圆角,不会设置contents的圆角,如果contents需要设置圆角,需要同时将maskToBounds / clipsToBounds设置为true。

所以我们可以理解为圆角不生效的根本原因是没有对contents设置圆角,而按钮设置的image是放在contents里面的,所以看到的界面上的就是image没有进行圆角裁剪。

1:只设置背景颜色

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let uiview = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        uiview.backgroundColor = .black
        self.view.addSubview(uiview)
        uiview.layer.cornerRadius = 50
        uiview.clipsToBounds = true
    }
}

然后运行虚拟机,打开离屏渲染debug

尽管我们把clipsToBounds设置为true,我们并没有看到发生离屏渲染的过程,究极原因是因为contents中没有需要圆角处理的layer。所以不会发生离屏渲染。

2:设置图片

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let uiview = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        uiview.backgroundColor = .black
        self.view.addSubview(uiview)
        uiview.layer.cornerRadius = 50
        uiview.setImage(UIImage(named: "图片"), for: .normal)
        uiview.clipsToBounds = true
    }
}

在上面的代码中,我们设置了button的图片属性,并且使用了clipsToBounds(当为false时也不会发生,圆角设置不生效) 。

从屏幕的显示可以看出,发生了离屏渲染,因为圆角的设置需要对所有的layer进行裁剪,从正常的角度来说,一个layer渲染完成后就要被扔掉。而现在我们的圆角设置需要3个layer叠加合并的,所以要先将处理好的layer放在缓冲区中,等最后一个layer处理完,在合并。

注意:ios中UIImageView使用maskTobounds是不会触发离屏渲染的。因为 UIImageView 的内部实现进行了优化,它直接在 纹理级别(Texture Level)裁剪,而不需要 GPU 额外创建一个离屏缓冲区(Offscreen Buffer)。

发生离屏渲染的场景

1:使用阴影(shadow)相关的属性。解决办法:设置阴影后,设置CALayer的 shadowPath。shadowPath 明确告知渲染引擎阴影的形状,使其无需额外计算,从而直接在屏幕缓冲区中绘制阴影,提升渲染性能。

2:透明度(opacity)相关属性(UIView 的 alpha 属性设置底层就是通过 Layer 的 opacity 实现的,没啥差别。设置 UIView alpha 属性同样能触发离屏渲染的。)

3:cornerRadius+maskToBounds。

4:模糊视图(UIVisualEffectView)。

5:视图光栅化:光栅化(Rasterization) 是矢量图形(Vector Graphics)转换为位图(Bitmap)的过程,以加速绘制和提高渲染性能。在 iOS 开发中,CALayer 提供shouldRasterize 属性,允许 GPU 将视图内容缓存成位图,避免每一帧都重新计算绘制。注意:光栅化只适用于静态属性。

myView.layer.shouldRasterize = true
myView.layer.rasterizationScale = UIScreen.main.scale  // 确保清晰度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值