iOS 收藏篇

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

2020-07-13  本文已影响0人  宇宙那么大丶

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

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

1、图片撕裂

image.png

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

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

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

也就是说,垂直同步:防止出现撕裂。双缓存区:从根本上解决撕裂。
这种方法治标不治本,标是做出了人眼看不出撕裂的操作,本是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个原因:

二、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

拓展:【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 等四个具体的操作。

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、CPU与GPU

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

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

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

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

3、计算机显示方式

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


image.png

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


image.png

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

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

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

上一篇下一篇

猜你喜欢

热点阅读