我们都知道可视化程序都是由 CPU 和 GPU 协作进行的。那么我们下面先来介绍下这两个的基本概念:
CPU
- 主要是用于运算和控制。
- 有一定成都的并行运算,但是它是通过不停的切换时间片来达到我们想象中的并行运算。
GPU
- 主要是用于进行绘图相关的运算的微处理器。
- 它有很多的运算单元,可以做到真正的并行运算。
计算机将存储在内存中的形状转换绘制到屏幕上的过程叫做渲染。渲染过程中最常用的技术就是光栅化。
屏幕图像显示原理
介绍屏幕图像显示原理前,需要先了解下 CRT 显示器原理。
CRT 显示器原理
CTR 电子枪是从上到下呈 Z 字形逐行扫描,扫描完成后会就会呈现一帧的画面,然后电子枪会回到初始位置从新进行扫描。为了同步显示器的显示过程和视屏控制器,显示器会用硬件时钟产生一系列的定时信号。当电子枪进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称 HSync;当一帧画面绘制完成后,电子枪回复到原位,准备绘制下一帧前,显示器会发出一个垂直同步信号(vertical synchronization),简称 VSync。显示器通常以固定的频率进行刷新,这个刷新率就是 Vsync 信号产生的频率。
上图所示为 CPU、GPU和显示器常见的工作方式。CPU计算好显示的内容,提交至 GPU,GPU渲染完成后将数据提交到帧缓冲区中,显示器会按照 Vsync 信号逐帧读取帧缓冲区中的数据,经过数模转换最终在显示器上显示。
但是这种情况下是最简单的情况,帧缓冲区只有一个。当没有引入垂直同步信号的技术的时候,这里会造成一个常见的问题,屏幕撕裂。
屏幕撕裂
造成的原因是因为,当显示器读取帧缓冲区的内容时,当前帧缓冲区的数据还是上一帧的数据,所以,在逐行扫描显示的是老数据,当GPU在扫描的过程中,提交了新的数据到帧缓冲区后,显示器会读取到新的内容,所以导致下方绘制的是新的数据,这种情况就是屏幕撕裂产生的原因。
解决屏幕撕裂的方法,是使用垂直同步信号 (Vsync) ,相当于加锁操作,只有 GPU 渲染完一帧,放入帧缓冲区中,显示器才会去读取帧缓冲区中的内容。
在这种情况下,帧缓冲区的读取和刷新都会存在较大的效率问题,为了解决效率问题,就引入了新的技术,双缓冲区。
双缓冲区
双缓冲区的机制就是,GPU会预先渲染一帧数据放入一个缓冲区中,用于视频控制器的读取。当下一帧渲染完毕后,GPU 会直接把视屏控制器的指针指向第二个缓冲区。
但是双缓冲区仍然会产生掉帧的问题。具体掉帧的原因可以参考我写的另一篇文章【UIView的绘制原理及优化】。
在使用了双缓冲区和垂直同步信号后,由于总要等待缓冲区交换之后再进行下一帧的渲染,使得帧率无法完全得到硬件的最高水平。所以引进了三缓冲区。
三缓冲区
即在等待垂直同步信号到来之前,来回交替两个离屏缓冲区,而在垂直同步信号到来时,屏幕缓冲区会和最近渲染完成的离屏缓冲区进行交换。
渲染过程
iOS下的图形渲染框架
UIKit/AppKit
如果你开发过iOS/Mac,就会对此框架比较熟悉。iOS 下使用的是 UIKit,MacOS 下使用的是 AppKit,我们通常使用 UIKit/AppKit 组件中的 Layout 以及 backgroundColor 等属性来完成日常的界面绘画工作。
其实 UIKit/AppKit 本身并不具备屏幕成像的能力,它主要负责用户操作事件的响应。
那我们日常开发中的 UIKit 组件是如何呈现到屏幕上的呢?
Core Animation
Core Animation官方文档
Core Animation Programming Guide
Core Animation,乍一看好像是做动画的,其实动画只是 Core Animation 中的一部分。它最初是有一个叫 Layer Kit 演变而来的。
Render, compose, and animate visual elements.
Core Animation 主要用于渲染、混合和动画视觉元素,可以理解为是一个复合引擎。旨在为了可能快的组合屏幕上不同的显示内容。这些内容被分解成独立的图层,即 CALayer。从本质上来言,CALayer 是用户所能在屏幕上看到的一切的基础。
Metal
早期苹果底层也是使用 OpenGL ES 来进行开发的。在 2014 年的时候推出了 Metal,2018年的时候, OpenGL 的 API 已经被苹果废弃了。苹果推出 Metal 说是将 3D 图像的渲染性能提高了 10 倍。但是主要的原因还是想将核心技术掌握在自己手中。
Metal 类似于 OpenGL ES,也是一套第三方标准。Core Animation、Core Image、SceneKit、SpriteKit 等显然框架都是构建于 Metal 之上的。
虽然 OpenGL 已经被苹果所废弃,但是苹果已经实现了一套机制将 OpenGL 命令无缝桥接到 Metal 上,由 Metal 担任真正与硬件交互的工作。
Core Graphics
Core Graphics 是基于 Quartz 高级绘图引擎,主要用于运行时绘制图像。开发者可以用此框架处理基于路径的绘图、转换、颜色管理、离屏渲染、渐变、阴影、图像管理等。
Graphics Hardware
图形处理器,支持 Metal 和 OpenGL ES的图形硬件。也就是我们常提到的 GPU。
UIView 和 CALayer 的关系
UIKit 中的每一个 UI 视图控件内部都有一个关联的 CALayer,即 backing layer。
由于这种一一对应的关系,视图层级拥有 视图树 的树形结构,对应 CALayer 拥有 图层树 的树形结构。
其中视图的职责是创建并管理 图层,以确保子视图在层级关系中添加以及被移除时,其关联的图层在图层树中也有相应的操作,即保证视图树和图层数在结构上的一致。
UIView的职责:
- 视图的构建
- 简单的动画
- 管理子视图
- 触摸方法等的管理
Layer 的职责:
- 视图的绘制,展示的内容
- 对应 UIView 中的子视图的图层数的管理
Core Animation 渲染流水线
实际上,app本身并不负责渲染,渲染有一个独立的进程负责,即 Render Server 进程。
Core Animation 流水线的详细过程如下:
- 1、由 app 处理事件(Handle Event);如:用户的点击、触摸等,在此过程中需要更是视图树,对应的图层数也会更新
- 2、app 中通过 CPU 完成对代码的逻辑处理,计算出需要显示的内容;如:视图的创建、布局计算、图片解码、文本绘制等。在完成对显示内容的计算后,app 对图层进行打包,并在下一次 RunLoop 时将其发送至 Render Server ,即完成了一次 Commit Transaction 操作。
- 3、Render Server 主要执行 GPU、Core Graphics 相关程序,并调用 GPU
- 4、GPU 则在物理层上完成对图像的渲染。
- 5、最终,GPU 通过 Frame Buffer 、视屏控制器等相关部件,将图像显示在屏幕上
Commit Transaction
在 Core Animation 流水线中,app 调用 Render Server 的 Commit Transaction 其实可以细分为以下4个步骤:
- layout
主要是进行视图的构建;包括:LayoutSubviews 方法的重载,addSubview: 方法填充子视图等。 - display
主要进行视图的绘制,这里仅仅是设置成要成像的图元数据。重载视图的 drawReact:方法可以自定义 UIView 的显示,其原理是在 drawReact: 方法内部绘制寄宿图,该过程使用 CPU 和内存 - prepare
一般处理图像的解码和转换等操作。 - commit
将图层进行打包,并将它们发送至 Redner Server,该过程会递归执行,因为图层和视图都是以树形结构存在的。
以上整个流程所执行的时间远远超过了 16.67 ms,因此为了满足对屏幕的 60FPS 刷新率的支持,需要将这些步骤进行分解,通过流水线的方式进行并行执行,如下图所示:
参考链接
计算机那些事(8)——图形图像渲染原理
iOS 图像渲染原理