渲染和iOS的离屏渲染

iOS下的渲染流程

1.png

知识点:

  • 整个渲染流程,可以分为OpenGL client(客户端)和OpenGL server(服务端)
  • client是在CPU里面执行,不断的驱动数据传递给server
  • server是在GPU里面执行,直接操作GPU绘制图形
  • Vertex Shader(顶点着色器),由client从三个通道:Attributes(ins)、Uniforms、Texture Data将数据传递进来
  • Fragment Shader(片元着色器),由client从两个通道:Uniforms、Texture Data传递数据进来,Attribute只能通过定点着色器传递进来
  • 顶点着色器不能直接操作片元着色器,需要由顶点着色器输出数据(开发者无法干涉)到下一个阶段(Primitive Assembly:图元装配/光栅化),将数据拷贝到着色器中
  • render 渲染图形

离屏渲染

On-Screen Rendering (当前屏幕渲染)

  • GPU的渲染操作是在当前用于显示的屏幕缓冲区进行

Off-Screen Rendering (离屏渲染)

  • GPU在当前屏幕缓冲区以外开辟的一个缓冲区进行渲染操作

Off-Screen Rendering 和 On-Screen Rendering的比较

  • 创建新的缓冲区
  • 上下文切换,即:从当前屏幕切换到离屏,离屏渲染结束后,讲渲染结果显示到屏幕上,需要将上下文环境从离屏切换到当屏(更耗性能)

触发离屏渲染的方式

主要出现在处理图层操作较多的情况下会发生离屏渲染(以下包含不仅限于)

1、layer的mask

2、layer的投影(layer.Shadow)

3、layer的光栅化(layer.ShouldRasterize)

4、layer.masksToBounds = true 可能会产生离屏渲染

  • 打开该属性,如果只设置了背景色或者图片,也不会产生离屏渲染,即:图层只有一层时,不触发离屏渲染
func demo1() {
        /// 设置了背景色+圆角:无离屏渲染
        let imgView1 = UIImageView.init(frame: CGRect(x: 30, y: 80, width: 100, height: 100))
        imgView1.backgroundColor = .gray
        imgView1.layer.cornerRadius = 10
        view.addSubview(imgView1)
        
        /// 设置了背景色+圆角+图片:无离谱渲染
        /// 打开masksToBounds属性:出现离谱渲染
        let imgView2 = UIImageView.init(frame: CGRect(x: 30, y: 200, width: 100, height: 100))
        imgView2.layer.masksToBounds = true
        imgView2.image = UIImage.init(named: "user_image")
        imgView2.layer.cornerRadius = 10
        imgView2.backgroundColor = .gray
        view.addSubview(imgView2)
        
        /// 设置了背景色+圆角+多张只设置背景色叠加:无离谱渲染
        /// 设置了背景色+图片+圆角+多张设置背景色+图片叠加:无离谱渲染
        /// 打开masksToBounds属性:出现离谱渲染
        let imgView3 = UIImageView.init(frame: CGRect(x: 30, y: 320, width: 100, height: 100))
        imgView3.layer.cornerRadius = 10
        imgView3.image = UIImage.init(named: "user_image")
        imgView3.backgroundColor = .gray
        view.addSubview(imgView3)
        
        let imgView3_1 = UIImageView.init(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
        imgView3_1.backgroundColor = .red
        imgView3_1.image = UIImage.init(named: "user_image")
        imgView3.addSubview(imgView3_1)
        
        let imgView3_2 = UIImageView.init(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
        imgView3_2.backgroundColor = .green
        imgView3_2.image = UIImage.init(named: "user_image")
        imgView3_1.addSubview(imgView3_2)
        
        let imgView3_3 = UIImageView.init(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
        imgView3_3.backgroundColor = .blue
        imgView3_3.image = UIImage.init(named: "user_image")
        imgView3_2.addSubview(imgView3_3)
    }
    
    func demo2() {
        /// 设置了图片+圆角:无离屏渲染
        let imgView1 = UIImageView.init(frame: CGRect(x: 30, y: 80, width: 100, height: 100))
        imgView1.image = UIImage.init(named: "user_image")
        imgView1.layer.cornerRadius = 10
        view.addSubview(imgView1)
        
        /// 设置了圆角+图片:无离谱渲染
        /// 只设置图片,不设置背景色,打开masksToBounds属性:无离谱渲染
        /// 设置图片,设置背景色,打开masksToBounds属性:出现离谱渲染
        let imgView2 = UIImageView.init(frame: CGRect(x: 30, y: 200, width: 100, height: 100))
        imgView2.layer.masksToBounds = true
        imgView2.backgroundColor = .blue
        imgView2.image = UIImage.init(named: "user_image")
        imgView2.layer.cornerRadius = 80
        view.addSubview(imgView2)
        
        /// 设置了圆角+多张只设置图片叠加:无离谱渲染
        /// 设置了图片+圆角+多张图片叠加:无离谱渲染
        /// 打开masksToBounds属性:出现离谱渲染
        let imgView3 = UIImageView.init(frame: CGRect(x: 30, y: 320, width: 100, height: 100))
        imgView3.layer.cornerRadius = 10
        imgView3.image = UIImage.init(named: "user_image")
        view.addSubview(imgView3)
        
        let imgView3_1 = UIImageView.init(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
        imgView3_1.image = UIImage.init(named: "user_image")
        imgView3.addSubview(imgView3_1)
        
        let imgView3_2 = UIImageView.init(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
        imgView3_2.image = UIImage.init(named: "user_image")
        imgView3_1.addSubview(imgView3_2)
        
        let imgView3_3 = UIImageView.init(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
        imgView3_3.image = UIImage.init(named: "user_image")
        imgView3_2.addSubview(imgView3_3)
    }
  • 使用了mask的layer(layer.mask)
  • masksToBounds

卡顿产生的原因和解决方案

从上述的一些知识点可知,屏幕产生卡顿的原因,主要还是在CPU和GPU上。
接下来我们看下显示过程:


26.jpg

在VSync信号到来后,系统图形服务会通过CADisplayLink等机制通知APP,APP主线程再CPU中计算显示内容,如计算布局、图片解码、文本绘制等。CPU计算好的内容再提交到GPU中,由GPU进行交换、合成、渲染。然后GPU就把结果提交到帧缓冲区,等待下一次VSync信号到来时显示到屏幕上。由于垂直同步的机制,如果再一个Vsync时间内,CPU或者GPU没有完成内容提交,则那一帧就会丢失,等待下一次机会在显示,而这个时候显示屏会保留之前的内容不变。这就是界面的卡顿原因。

从上图可以看到,无论是CPU还是GPU,只要有阻碍,都会造成掉帧现象。所以开发时,也需要分别对CPU和GPU进行评测。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。