深刻理解 iOS Rendering Process

前言

iOS 最先名为 iPhone OS,是 Apple 公司专门为其硬件设备开发的操做系统,最初于 2007 年随第一代 iPhone 推出,后扩展为支持 Apple 公司旗下的其余硬件设备,如 iPod、iPad 等。ios

做为一名 iOS Developer,相信大多数人都有写出过形成 iOS 设备卡顿的代码经历,相应的也有过千方百计优化卡顿代码的经验。git

本文将从 OpenGL 的角度结合 Apple 官方给出的部分资料,介绍 iOS Rendering Process 的概念及其整个底层渲染管道的各个流程。github

相信在理解了 iOS Rendering Process 的底层各个阶段以后,咱们能够在平日的开发工做之中写出性能更高的代码,在解决帧率不足的显示卡顿问题时也能够多一些思路~算法

索引

  • iOS Rendering Process 概念
  • iOS Rendering 技术框架
  • OpenGL 主要渲染步骤
  • OpenGL Render Pipeline
  • Core Animation Pipeline
  • Commit Transaction
  • Animation
  • 全文总结
  • 扩展阅读

iOS Rendering Process 概念

iOS Rendering Process 译为 iOS 渲染流程,本文特指 iOS 设备从设置将要显示的图元数据到最终在设备屏幕成像的整个过程。编程

在开始剖析 iOS Rendering Process 以前,咱们须要对 iOS 的渲染概念有一个基本的认知:缓存

基于平铺的渲染

iOS 设备的屏幕分为 N * N 像素的图块,每一个图块都适合于 SoC 缓存,几何体在图块内被大量拆分,只有在全部几何体所有提交以后才能够进行光栅化(Rasterization)。微信

Note: 这里的光栅化指将屏幕上面被大量拆分出来的几何体渲染为像素点的过程。app

iOS Rendering 技术框架

事实上 iOS 渲染相关的层级划分大概以下:框架

UIKit

嘛~ 做为一名 iOS Developer 来讲,应该对 UIKit 都不陌生,咱们平常开发中使用的用户交互组件都来自于 UIKit Framework,咱们经过设置 UIKit 组件的 Layout 以及 BackgroundColor 等属性来完成平常的界面绘画工做。ide

其实 UIKit Framework 自身并不具有在屏幕成像的能力,它主要负责对用户操做事件的响应,事件响应的传递大致是通过逐层的视图树遍历实现的。

那么咱们平常写的 UIKit 组件为何能够呈如今 iOS 设备的屏幕上呢?

Core Animation

Core Animation 实际上是一个使人误解的命名。你可能认为它只是用来作动画的,但实际上它是从一个叫作 Layer Kit 这么一个不怎么和动画有关的名字演变而来的,因此作动画仅仅是 Core Animation 特性的冰山一角。

Core Animation 本质上能够理解为是一个复合引擎,旨在尽量快的组合屏幕上不一样的显示内容。这些显示内容被分解成独立的图层,即 CALayer,CALayer 才是你所能在屏幕上看见的一切的基础。

其实不少同窗都应该知道 CALayer,UIKit 中须要在屏幕呈现的组件内部都有一个对应的 CALayer,也就是所谓的 Backing Layer。正是由于一一对应,因此 CALayer 也是树形结构的,咱们称之为图层树

视图的职责就是建立并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层一样对应在层级关系树当中有相同的操做

可是为何 iOS 要基于 UIView 和 CALayer 提供两个平行的层级关系呢?为何不用一个简单的层级关系来处理全部事情呢?

缘由在于要作职责分离,这样也能避免不少重复代码。在 iOS 和 Mac OS X 两个平台上,事件和用户交互有不少地方的不一样,基于多点触控的用户界面和基于鼠标键盘的交互有着本质的区别,这就是为何 iOS 有 UIKit 和 UIView,而 Mac OS X 有 AppKit 和 NSView 的缘由。他们功能上很类似,可是在实现上有着显著的区别。

Note: 实际上,这里并非两个层级关系,而是四个,每个都扮演不一样的角色,除了视图树图层树以外,还存在呈现树渲染树

OpenGL ES & Core Graphics

OpenGL ES

OpenGL ES 简称 GLES,即 OpenGL for Embedded Systems,是 OpenGL 的子集,一般面向**图形硬件加速处理单元(GPU)**渲染 2D 和 3D 计算机图形,例如视频游戏使用的计算机图形。

OpenGL ES 专为智能手机,平板电脑,视频游戏机和 PDA 等嵌入式系统而设计 。OpenGL ES 是“历史上应用最普遍的 3D 图形 API”。

Core Graphics

Core Graphics Framework 基于 Quartz 高级绘图引擎。它提供了具备无与伦比的输出保真度的低级别轻量级 2D 渲染。您可使用此框架来处理基于路径的绘图,转换,颜色管理,离屏渲染,图案,渐变和阴影,图像数据管理,图像建立和图像遮罩以及 PDF 文档建立,显示和分析。

Note: 在 Mac OS X 中,Core Graphics 还包括用于处理显示硬件,低级用户输入事件和窗口系统的服务。

Graphics Hardware

Graphics Hardware 译为图形硬件,iOS 设备中也有本身的图形硬件设备,也就是咱们常常说起的 GPU。

图形处理单元(GPU)是一种专用电子电路,旨在快速操做和改变存储器,以加速在用于输出到显示设备的帧缓冲器中建立图像。GPU 被用于嵌入式系统,手机,我的电脑,工做站和游戏控制台。现代 GPU 在处理计算机图形和图像方面很是高效,而且 GPU 的高度并行结构使其在大块数据并行处理的算法中比通用 CPU 更有效。

OpenGL 主要渲染步骤

OpenGL 全称 Open Graphics Library,译为开放图形库,是用于渲染 2D 和 3D 矢量图形的跨语言,跨平台应用程序编程接口(API)。OpenGL 能够直接访问 GPU,以实现硬件加速渲染。

一个用来渲染图像的 OpenGL 程序主要能够大体分为如下几个步骤:

  • 设置图元数据
  • 着色器-shader 计算图元数据(位置·颜色·其余)
  • 光栅化-rasterization 渲染为像素
  • fragment shader,决定最终成像
  • 其余操做(显示·隐藏·融合)

Note: 其实还有一些非必要的步骤,与本文主题不相关,这里点到为止。

咱们平常开发时使用 UIKit 布局视图控件,设置透明度等等都属于设置图元数据这步,这也是咱们平常开发中能够影响 OpenGL 渲染的主要步骤。

OpenGL Render Pipeline

若是有同窗看过 WWDC 的一些演讲稿或者接触过一些 OpenGL 知识,应该对 Render Pipeline 这个专业术语并不陌生。

不过 Render Pipeline 实在是一个初次见面不太容易理解的词,它译为渲染管道,也有译为渲染管线的...

其实 Render Pipeline 指的是从应用程序数据转换到最终渲染的图像之间的一系列数据处理过程

比如咱们上文中提到的 OpenGL 主要渲染步骤同样,咱们开发应用程序时在设置图元数据这步为视图控件的设定布局,背景颜色,透明度以及阴影等等数据。

下面以 OpenGL 4.5 的 Render Pipeline 为例介绍一下:

这些图元数据流入 OpenGL 中,传入顶点着色器(vetex shader),而后顶点着色器对其进行着色器内部的处理后流出。以后可能进入细分着色阶段(tessellation shading stage),其中又有可能分为细分控制着色器和细分赋值着色器两部分处理,还可能会进入几何着色阶段(geometry shading stage),数据从中传递。最后都会走片元着色阶段(fragment shading stage)

Note: 图元数据是以 copy 的形式流入 shader 的,shader 通常会以特殊的相似全局变量的形式接收数据。

OpenGL 在最终成像以前还会经历一个阶段名为计算着色阶段(compute shaing stage),这个阶段 OpenGL 会计算最重要在屏幕中成像的像素位置以及颜色,若是在以前提交代码时用到了 CALayer 会引发 blending 的显示效果(例如 Shadow)或者视图颜色或内容图片的 alpha 通道开启,都将会加大这个阶段 OpenGL 的工做量。

Core Animation Pipeline

上文说到了 iOS 设备之因此能够成像不是由于 UIKit 而是由于 LayerKit,即 Core Animation。

Core Animation 图层,即 CALayer 中包含一个属性 contents,咱们能够经过给这个属性赋值来控制 CALayer 成像的内容。这个属性的类型定义为 id,在程序编译时不论咱们给 contents 赋予任何类型的值,都是能够编译经过的。但实践中,若是 contents 赋值类型不是 CGImage,那么你将会获得一个空白图层

Note: 形成 contents 属性的奇怪表现的缘由是 Mac OS X 的历史包袱,它之因此被定义为 id 类型是由于在 Mac OS X 中这个属性对 CGImage 和 NSImage 类型的值都起做用。可是在 iOS 中,若是你赋予一个 UIImage 属性的值,仅仅会获得一个空白图层。

说完 Core Animation 的 contents 属性,下面介绍一下 iOS 中 Core Animation Pipeline:

  • 在 Application 中布局 UIKit 视图控件间接的关联 Core Animation 图层
  • Core Animation 图层相关的数据提交到 iOS Render Server,即 OpenGL ES & Core Graphics
  • Render Server 将与 GPU 通讯把数据通过处理以后传递给 GPU
  • GPU 调用 iOS 当前设备渲染相关的图形设备 Display

Note: 因为 iOS 设备目前的显示屏最大支持 60 FPS 的刷新率,因此每一个处理间隔为 16.67 ms。

能够看到从 Commit Transaction 以后咱们的图元数据就将会在下一次 RunLoop 时被 Application 发送给底层的 Render Server,底层 Render Server 直接面向 GPU 通过一些列的数据处理将处理完毕的数据传递给 GPU,而后 GPU 负责渲染工做,根据当前 iOS 设备的屏幕计算图像像素位置以及像素 alpha 通道混色计算等等最终在当前 iOS 设备的显示屏中呈现图像。

嘛~ 因为 Core Animation Pipeline 中 Render Server 包含 OpenGL ES & Core Graphics,其中 OpenGL ES 的渲染能够参考上文 OpenGL Render Pipeline 理解。

Commit Transaction

Core Animation Pipeline 的整个管线中 iOS 常规开发通常能够影响到的范围也就仅仅是在 Application 中布局 UIKit 视图控件间接的关联 Core Animation 图层这一级,即 Commit Transaction 以前的一些操做

那么在 Commit Transaction 以前咱们通常要作的事情有哪些?

  • Layout,构建视图
  • Display,绘制视图
  • Prepare,额外的 Core Animation 工做
  • Commit,打包图层并将它们发送到 Render Server

Layout

在 Layout 阶段咱们能作的是把 constraint 写的尽可能高效,iOS 的 Layout Constraint 相似于 Android 的 Relative Layout。

Note: Emmmmm... 据观察 iOS 的 Layout Constraint 在书写时应该尽可能少的依赖于视图树中同层级的兄弟视图节点,它会拖慢整个视图树的 Layout 计算过程。

这个阶段的 Layout 计算工做是在 CPU 完成的,包括 layoutSubviews 方法的重载,addSubview: 方法填充子视图等

Display

其实这里的 Display 仅仅是咱们设置 iOS 设备要最终成像的图元数据而已,重载视图 drawRect: 方法能够自定义 UIView 的显示,其原理是在 drawRect: 方法内部绘制 bitmap。

Note: 重载 drawRect: 方法绘制 bitmap 过程使用 CPU 和 内存

因此重载 drawRect: 使用不当会形成 CPU 负载太重,App 内存飙升等问题。

Prepare

这个步骤属于附加步骤,通常处理图像的解码 & 转换等操做。

Commit

Commit 步骤指打包图层并将它们发送到 Render Server。

Note: Commit 操做会递归执行,因为图层和视图同样是以树形结构存在的,当图层树过于复杂时 Commit 操做的开销也会很是大。

CATransaction

CATransaction 是 Core Animation 中用于将多个图层树操做分配到渲染树的原子更新中的机制,对图层树的每一个修改都必须是事务的一部分。

CATransaction 类没有属性或者实例方法,而且也不能用 +alloc-init 方法建立它,咱们只能用类方法 +begin+commit 分别来入栈或者出栈。

事实上任何可动画化的图层属性都会被添加到栈顶的事务,你能够经过 +setAnimationDuration: 方法设置当前事务的动画时间,或者经过 +animationDuration 方法来获取时长值(默认 0.25 秒)。

Core Animation 在每一个 RunLoop 周期中自动开始一次新的事务,即便你不显式地使用 [CATransaction begin] 开始一次事务,在一个特定 RunLoop 循环中的任何属性的变化都会被收集起来,而后作一次 0.25 秒的动画(CALayer 隐式动画)。

Note: CATransaction 支持嵌套

Animation

对于 App 用户交互体验提高最明显的工做莫过于使用动画了,那么 iOS 是如何处理动画的渲染过程的呢?

平常开发中若是不是特别复杂的动画咱们通常会使用 UIView Animation 实现,iOS 将 UIView Animation 的处理过程分为如下三个阶段:

  • 调用 animateWithDuration:animations: 方法
  • 在 Animation Block 中进行 Layout,Display,Prepare,Commit
  • Render Server 根据 Animation 逐帧渲染

Note: 原理是 animateWithDuration:animations: 内部使用了 CATransaction 来将整个 Animation Block 中的代码做为原子操做 commit 给了 RunLoop。

基于 CATransaction 实现链式动画

事实上大多数的动画交互都是有动画执行顺序的,尽管 UIView Animation 很强大,可是在写一些顺序动画时使用 UIView Animation 只能在 + (void)animateWithDuration:delay:options:animations:completion: 方法的 completion block 中层级嵌套,写成一坨一坨 block 堆砌而成的代码,实在是难以阅读更别提后期维护了。

在得知 UIView Animation 使用了 CATransaction 时,咱们不由会想到这个 completion block 是否是也是基于 CATransaction 实现的呢?

Bingo!CATransaction 中有 +completionBlock 以及 +setCompletionBlock: 方法能够对应于 UIView Animation 的 completion block 的书写。

Note: 个人一个开源库 LSAnimator - 可多链式动画库动画顺序连接时也用到了 CATransaction

全文总结

结合上下文不难梳理出一个 iOS 最基本的完整渲染通过(Rendering pass)

性能检测思路

基于整篇文章的内容概括一下咱们在平常的开发工做中遇到性能问题时检测问题代码的思路:

问题 建议 检测工具
目标帧率 60 FPS Core Animation instrument
CPU or GPU 下降使用率节约能耗 Time Profiler instrument
没必要要的 CPU 渲染 GPU 渲染更理想,但要清楚 CPU 渲染在什么时候有意义 Time Profiler instrument
过多的 offscreen passes 越少越好 Core Animation instrument
过多的 blending 越少越好 Core Animation instrument
奇怪的图片格式或大小 避免实时转换或调整大小 Core Animation instrument
开销昂贵的视图或特效 理解当前方案的开销成本 Xcode View Debugger
想象不到的层次结构 了解实际的视图层次结构 Xcode View Debugger

文章写得比较用心(是我我的的原创文章,转载请注明 lision.me/),若是发现错误会优先… 我的博客 中更新。若是有任何问题欢迎在个人微博 @Lision 联系我~

但愿个人文章能够为你带来价值~

扩展阅读


补充~ 我建了一个技术交流微信群,想在里面认识更多的朋友!若是各位同窗对文章有什么疑问或者工做之中遇到一些小问题均可以在群里找到我或者其余群友交流讨论,期待你的加入哟~

Emmmmm..因为微信群人数过百致使不能够扫码入群,因此请扫描上面的二维码关注公众号进群。

相关文章
相关标签/搜索