OpenGL入门 -- 屏幕卡顿/撕裂 及 渲染流程解读

1,867 阅读6分钟

屏幕卡顿/撕裂

屏幕卡顿/撕裂 是指屏幕在显示图形图像时出现的一些显示异常的现象。

  • 撕裂:是图形图片显示错位
  • 掉帧:是重复显示同一帧图片数据

那么为什么会出现这样的显示问题呢?接下来详细解说一下。

在分析之前,先了解一下 CPUGPU的一些知识,以及 屏幕成像 的演变过程

CPU 和 GPU

  • CPU:是整个计算机的运算核心和控制核心,处理复杂的逻辑和数据,依赖非常高,靠时间片切换实现并发
  • GPU:绘图运算工作准用的微处理器,单元计算,高并发,依赖非常低

屏幕成像

计算机的显示方式是经过最开始的 随机扫描显示 演变到现在的光栅扫描显示

图形图像是有像素阵列组成的,图像的显示和图像的复杂度无关,光栅扫描都是从左上角开始扫描显示,是显示整个光栅的时间。

简单扫描光栅系统结构如下:

比如:要显示一个60 * 60 的位图,需要开辟的内存为60 * 60 * 4(RGBA) = 14400,在早期没有显卡时期,图片要先通过CPU解码到内存中,然后显示控制器通过系统总线从系统主存中读去显示的图片,这样读取慢,就演变出了常用扫描光栅系统结构,结构如下:

常用扫描光栅系统结构是在系统主存中开辟一块作内存为帧缓存,来存储显示的数据,但是依然通过系统总线中读取,依然读取受限,然后演变出了下面的高级扫描光栅系统结构

图片解码到内存中,copy帧缓存区GPU直接操作显存中的帧缓存区的数据

了解完这些之后,开始分析一下屏幕撕裂的问题。

屏幕撕裂

光栅扫描系统主要分三个部分:

  • 显示器(来源于帧缓存区),
  • 视频控制器(负责控制刷新的部件,帧缓存区和显示器的对应关系),
  • 帧缓存区(显示的内容, 帧缓存/显存主要存储图像刷新数据)

显示器显示的是帧缓存区的数据,在光栅扫描的过程中,由于人眼的视觉暂留,当刷新的频率超过16帧时,人眼就觉得是连贯的。简单的屏幕成像过程如下:

屏幕撕裂的原因

  • 在屏幕显示图像过程中,是不断从帧缓存区获取一帧一帧是数据进行显示

  • 然后在渲染的过程中,帧缓存区中仍是旧的数据,屏幕拿到旧的数据去进行显示

  • 在旧的数据没有读取完时,新的一帧数据处理好了,放入了缓存区,这时就会导致屏幕另一部分的显示是获取的新数据,从而导致屏幕上呈现图片不匹配,人物、景象等错位显示的情况。如下图:

    撕裂的原因:

解决撕裂

苹果官方,采用垂直同步+双缓存区来解决撕裂问题,下面是简单图解:

  • 垂直同步:是指给帧缓冲加锁,当电子光束扫描的过程中,只有扫描完成了才会读取下一帧的数据,而不是只读取一部分
  • 双缓冲区:采用两个帧缓冲区来存储GPU处理结果,当屏幕显示其中一个缓存区内容时,另一个缓冲区继续等待下一个缓冲结果,两个缓冲区依次交替

下面是苹果官方示意图:

使用垂直同步+双缓存区,从根本上解决了屏幕撕裂的问题,但是随之有产生了另一个问题——— 屏幕卡顿掉帧

撕裂一般出现在低端设备加载一个高FPS的视频或者游戏场景,在iOS设备上并不常见,或者是低端的iOS设备,加载一个非常高频的动效以及图层复杂度高并覆盖了动效效果。

屏幕卡顿

掉帧:简单说就是屏幕重复显示同一帧数据,当接收到Vsync时,由于CPU和GPU的图片数据还没处理完,拿不到frameBuffer的数据,而重复渲染同一帧的数据。

为了减少掉帧(减少而不是解决掉帧),引入了三缓冲区,充分合理利用CPU/GPU 减少掉帧次数。

小结:

  • CPU/GPU 渲染流⽔线耗时过⻓->掉帧
  • 垂直同步Vsync + 双缓存区 DoubleBuffering 以掉帧作为代价解决屏幕撕裂问题
  • 三缓存区: 合理使⽤CPU/GPU减少掉帧次数

iOS 渲染流程

iOS中整体渲染流程如下:

  • App通过调用CoreGraphicsCoreAnimationCoreImage等框架的接口触发图形渲染
  • 将渲染交由OpenGL ES或者Metal,驱动GPU做渲染,最后显示到屏幕上
  • 由于OpenGL ES 是跨平台的,所以在实现中,需让各自的平台为OpenGL ES提供载体。在iOS中,就是通过CoreAnimation提供窗口,让App可以去调用。

View 和 CALayer 的关系

  • View

    View属于UIKit,负责绘制和动画,负责布局和子View的管理,负责处理点击事件

  • CALayer

    属于CoreAnimation框架,负责渲染显示,既用于UIKit,也用于APPKit

  • 二者的关系

    二者基于的框架不同,UIVIew基于UIKit框架,可以处理触摸事件,可以管理子视图,CALayer基于CoreAnimation,而CoreAnimation是基于QuartzCode,只负责渲染,不能处理用户事件。

    从继承关系来说,CALayer继承NSObject,而UIView是直接继承自UIResponder的,UIView属于UIKit的组件,而UIKit的组件到最后都会被分解成layer,存储到图层树中,需要与用户交互时,用UIView,不需要交互时,都可以

CoreAnimation

CoreAnimation官方解释:Render、Compose,and animate visual elements,本质是一个复合引擎,主要的职责包括 渲染、构建和动画实现。

iOS中基于CoreAnimation构建的框架有两个,分别是UIKitAPPKit,而CoreAnimation底层是基于MetalCoreGraphics

CoreAnimation的渲染流程

流程如如下:

主要可以分为两部分:

  • Coreanimation部分

    • APP处理UIView等的事件,通过CPU完成对显示内容的计算,将计算后的图层打包,在下一次runloop时提交到渲染服务器
    • Ren der Server中主要对收到内容解码,然后执行OPenGL相关程序,调用GPU渲染,Render Server操作分析如下:
  • GPU渲染部分

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