美文网首页iOS 收藏篇
OpenGL-05-屏幕卡顿原因及iOS下的渲染

OpenGL-05-屏幕卡顿原因及iOS下的渲染

作者: 宇宙那么大丶 | 来源:发表于2020-07-13 23:51 被阅读0次

    今天我们来看一下:
    图片撕裂、掉帧、屏幕卡顿的原因、iOS下的渲染框架、CoreAnimation的渲染流水线、UIView与CALayer的区别及部分OpenGL相关知识补充。

    一、图片撕裂、掉帧、屏幕卡顿

    1、图片撕裂

    image.png

    撕裂:(图像显示过程是不断从帧缓冲区获取一帧一帧数据进行显示的)在渲染过程中,帧缓冲区中有旧数据在进行显示,在继续扫描读取的时候新的数据被处理好放入了缓冲区,这时候造成了上部分显示旧数据,下部分显示新数据。导致显示的图片出现错位、不匹配的情况

    什么时候会出现撕裂?
    当CPU和GPU的计算能力跟不上所需要的帧率(60FPS),此时会可能发生撕裂。一般是在低端设备上,加载一个高FPS的视频或者游戏场景。iOS设备不太常见,大多在安卓设备上出现。

    • 补充一下【屏幕成像的过程】:
      需要显示的图片 ===> GPU进行图像渲染 (最后得到位图)===> 存入帧缓冲区 <===> 由视频控制器进行读取 ===> (经过数模转换,从左上角逐行扫描)显示在屏幕上

    处理方法:垂直同步Vsync + 双缓存区 DeubleBuffering。这种方案苹果推出的,是强制要求同步,且是以掉帧为代价的。

    • 垂直同步: 在帧缓存区加锁,等这张图片扫描完才进行更新,防止出现撕裂
    • 双缓存区:因为CPU和GPU计算时间不同,存在时间差。从而在旧数据未读取完,新数据已经处理好加入了缓冲区,造成了撕裂。于是要想从根本上解决撕裂,就得用到双缓存区

    也就是说,垂直同步:防止出现撕裂。双缓存区:从根本上解决撕裂。
    这种方法治标不治本,标是做出了人眼看不出撕裂的操作,本是CPU和GPU比较老

    这里在网上找到一张很形象的流程图:


    image.png

    2、掉帧

    当我们启用了垂直同步+ 双缓存区的方案,解决了屏幕撕裂的问题的同时,也是会产生新的问题。

    掉帧:简单来说就是,重复渲染同一帧数据。
    (在我们接收到垂直同步的时候,由于CPU和GPU的速度问题,导致数据还没有准备好,这时时视频控制器拿不到frameBuffer)

    如下图,当前屏幕显示的是A,在收到垂直信号后,CPUHE GPU还没有处理好B,这时候该显示B,但是显示的是A。重复显示了A就是掉帧。


    image.png

    处理方法:引入三缓冲区。(注意:这里并不是彻底解决了掉帧,只是比双缓冲方案比较,减少了掉帧情况)它主要是为了充分利用CPU和GPU的空闲时间,开辟ABC三个帧缓冲区,A显示屏幕,B也渲染好了,C再从GPU中拿取渲染数据,当屏幕缓冲区和帧缓冲区都弄好了,视频控制器再指向帧缓冲区的另外一个再显示,这样进行交替,就减少了掉帧的情况

    image.png

    3、屏幕卡顿

    屏幕卡顿:也就是掉帧问题导致的。【这里也是一个高频面试题】

    屏幕刷新频率必须要足够高才能流畅。对于 iPhone 手机来说,屏幕最大的刷新频率是 60 FPS,一般只要保证 50 FPS 就已经是较好的体验了。但是如果掉帧过多,导致刷新频率过低,就会造成不流畅的使用体验。

    主要3个原因:

    • CPU和GPU在 渲染流水线(下文中介绍)中耗时过长,导致下一帧数据没准备好,获取的还是上一帧数据,产生掉帧现象
    • 在解决屏幕撕裂问题上,我们使用垂直同步+ 双缓存区方案,但这是以掉帧为代价进行的
    • 虽然我们用了三缓冲区,也只是减少了掉帧的情况。并不是不会出现掉帧

    二、iOS下的渲染

    1、渲染框架

    image.png

    2、渲染流程

    image.png

    如图:
    1、我们的App通过调用CoreGraphics、CoreAnimation、CoreImage等框架的接口触发图形渲染操作
    2、CoreGraphics、CoreAnimation、CoreImage等框架将渲染交由OpenGL ES/Metal来驱动GPU进行渲染,最终显示在屏幕上。(上文中也说了OpenGL ES 是跨平台的,在iOS中,APP调用CoreAnimation提供窗口 来使用OpenGL ES)

    3、CoreAnimation

    苹果的官方描述:Render, compose, and animate visual elements.
    其实,CoreAnimation本质上可以理解为一个复合引擎。渲染、构建和实现动画。

    image.png
    • 基于CoreAnimation构建的框架有两个:UIKit(iOS)和APPKit(Mac OSX)
    • CoreAnimation 是基于Metal 、CoreGraphics封装的

    拓展:【UIKit是iOS平台的渲染框架,APPKit是Mac OSX系统下的渲染框架。由于iOS和Mac两个系统的界面布局并不是一致的,iOS是基于多点触控的交互方式,而Mac OSX是基于鼠标键盘的交互方式,且分别在对应的框架中做了布局的操作,所以并不需要layer载体去布局,且不用迎合任何布局方式。】

    苹果基于UIView和CALayer提供两个平行的层级关系(UIKit 和APPKit):

    • 职责分离,可以避免大量重复代码
    • 两个系统交互规则不一样(手机是通过触发点击事件,电脑是通过鼠标键盘等输入),它们虽然功能上相似,但实现上有显著的区别

    4、CoreAnimation中的渲染流水线

    image.png
    如图,整个流程分了两部分:CoreAnimation部分、GPU部分

    在CoreAnimation部分下的3步操作:

    • HandleEvents 事件处理
    • Commit Transaction 提交图片
    • Render Server 交给CPU解码

    CoreAnimation把解码好的东西 ===> 提交给OpenGL ===> 调度GPU ===> 进行渲染流程 [下文第三部分中会给出详解:顶点数据--->顶点着色器--->片元着色器]===> 等待下一个runloop去显示

    Commit Transaction中间发生了什么
    主要进行的是:Layout、Display、Prepare、Commit 等四个具体的操作。

    • Layout(构建视图):这个阶段是在 CPU 中进行,调用重载的 layoutSubviews 方法、创建视图并通过 addSubview 方法添加子视图、计算视图布局(即所有的 Layout Constraint)
    • Display(绘制视图):这个阶段主要是交给 Core Graphics 进行视图的绘制,根据上一阶段 Layout 的结果得到图元信息。【如果重写了 drawRect: 方法,这个阶段会直接调用 Core Graphics 绘制方法得到 bitmap 数据,同时系统会额外申请一块内存,用于暂存绘制好的 bitmap。会有额外的开销,使用CPU和内存】
    • Prepare(准备工作):图片解码和转换
    • Commit(提交):图层打包并发送到 Render Server

    Render Server操作分析:

    image.png

    在GPU部分下的操作:

    • GPU中通过顶点着色器、片元着色器完成对显示内容的渲染,将结果存入帧缓存区
    • GPU通过帧缓存区、视频控制器等相关部件,将其显示到屏幕上

    如上整个流水线是连贯的两部分。

    5、UIView与CALayer

    • UIView基于UIKit框架,可以处理用户触摸事件,并管理子视图
    • CALayer基于CoreAnimation,而CoreAnimation是基于QuartzCode的。所以CALayer只负责显示,不能处理用户的触摸事件
    • 从父类来说,CALayer继承的是NSObject,而UIView是直接继承自UIResponder的,所以UIView相比CALayer而言,只是多了事件处理功能
    • 从底层来说,UIView属于UIKit的组件,而UIKit的组件到最后都会被分解成layer,存储到图层树中
    • 在应用层面来说,需要与用户交互时,使用UIView,不需要交互时,使用两者都可以
    • 总结一下区别:

    UIView:
    1、负责绘制图形和动画操作
    2、布局及子view的管理
    3、处理点击事件
    4、属于UIKit,继承自 UIResponder

    CALayer:
    1、只做渲染和动画功能
    2、显示的是位图(bitmap)
    3、属于CoreAnimation(不仅仅用于UIKit,也用于APPKit),继承自 NSObject

    • 总结一下核心关系:

    1、CALayer 是 UIView 的属性之一,负责渲染和动画,提供可视内容的呈现。
    2、UIView 提供了对 CALayer 部分功能的封装,同时也另外负责了交互事件的处理。

    iOS下界面触发渲染的流程:

    • 有两种触发方式:
      1、通过loadView中子View的drawRect方法触发:会回调CoreAnimation中监听Runloop的BeforeWaiting的【RunloopObserver】,通过RunloopObserver来进一步调用CoreAnimation内部的【CA::Transaction::commit()】,进而一步步走到【drawRect】方法
      2、用户点击事件触发:唤醒Runloop',由【source1】处理(__IOHIDEventSystemClientQueueCallback),并且在下一个runloop里由【source0】转发给UIApplication(_UIApplicationHandleEventQueue),从而能通过source0里的事件队列来调用CoreAnimation内部的【CA::Transaction::commit()】方法,进而一步一步的调用【drawRect】。

    • 已经到了CoreAnimation的内部,即调用CA::Transaction::commit();来创建CATrasaction,然后进一步调用 CALayer drawInContext:()

    • 在drawRect:方法里可以通过CoreGraphics函数或UIKit中对CoreGraphics封装的方法进行画图操作

    • 将绘制好的位图交由CALayer,由OpenGL ES 传送到GPU的帧缓冲区

    • 等屏幕接收到垂直信号后,就读取帧缓冲区的数据,显示到屏幕上

    三、OpenGL知识补充

    1、着色器渲染流程

    image.png
    • 渲染流程中,必须存储2种着色器,分别是:顶点着色器、片元着色器
    • 顶点着色器拿到顶点数据进行几何操作 ==> 通过图元装配连接 ==> 剪切、光栅化 ==> 片元着色器进行每一个像素点的着色等操作 ==>显示

    2、CPU与GPU

    CPU:计算机的运算核心、控制核心

    • 处理复杂的逻辑、数据
    • 依赖性高。利用时间片的切换达到并发效果
    • CPU上拥有控制单元、计算单元、缓存单元等
    • CPU做图片解码

    GPU:负责绘图运算的微处理器

    • GLSL语言及其简单,处理单一的运算, 利用并行处理能力解决运算任务
    • 依赖性低。拥有很多计算单元,很好的达到高并发
    • GPU上有大量的计算单元
    • GPU做视频解码

    图片是怎么显示的?
    1、CPU做图片解码转换成位图
    2、GPU纹理混合,经过着色器渲染流程,把数据放到帧缓冲区
    3、等待时钟信号(垂直/水平 同步信号)
    4、渲染上屏

    图片的强行解压,就是对图片进行重新绘制,得到新的位图。(iOS需要使用的CGBitmapContextCreate,可以看下YYImage和SDWebImage看看是怎么写的)

    3、计算机显示方式

    最初形态:随机扫描显示,如图


    image.png

    后来演变成了:光栅扫描显示,如图


    image.png

    光栅扫描需要注意的是:因为图像是由像素阵列组成的,显示一张图像的时间与图像本身的复杂度无关。(显示过程中,是在不断的刷新,人眼1秒16帧以上是看不出来的。)

    光栅扫描显示系统的组成:

    • 显示器:显示的内容来自帧缓冲区
    • 视频控制器:负责控制刷新的部件进行刷新。读帧缓冲区 —进行显示绘制 —显示在显示器上
    • 帧缓冲区:每一个像素点,存储了颜色值、帧缓存(显存:存储显卡处理过或者即将提取的渲染数据)

    一个60*60的位图所占的位置有多大?
    大小就是3600 * 4=14400,那么就需要这么大的空间存储这张位图

    相关文章

      网友评论

        本文标题:OpenGL-05-屏幕卡顿原因及iOS下的渲染

        本文链接:https://www.haomeiwen.com/subject/dvtvcktx.html