iOS 浅谈GPU及“App渲染流程”

前言:
最近,研究了一下GPU以及App的渲染流程与原理。
首先,感谢 QiShare团队 的指导与支持,以及 鹏哥(@snow) 对本文的审核与帮助。
接下来,让我们开始我们今天的探索之旅。


一、浅谈GPU

GPU(Graphics Processing Unit):
又名图形处理器,是显卡的 “核心”。
主要负责图像运算工作,具有高并行能力,通过计算将图像显示在屏幕像素中。

现代图形系统

GPU的工作原理,简单来说就是:
—— “3D坐标” 转换成 “2D坐标” ,再将 “2D坐标” 转换为 “实际有颜色的像素”

那么,GPU具体的工作流水线 会分为“六个阶段”,分别是:

顶点着色器 => 形状装配 => 几何着色器 => 光栅化 => 片段着色器 => 测试与混合

  • 第一阶段:顶点着色器(Vertex Shader)

该阶段输入的是顶点数据(Vertex Data),顶点数据是一系列顶点的集合。顶点着色器主要的目的是把 3D 坐标转为 “2D” 坐标,同时顶点着色器可以对顶点属性进行一些基本处理。
一句话简单说,确定形状的点。

  • 第二阶段:形状装配(Shape Assembly)

该阶段将顶点着色器输出的所有顶点作为输入,并将所有的点装配成指定图元的形状。
图元(Primitive) 用于表示如何渲染顶点数据,如:点、线、三角形。
这个阶段也叫图元装配。
一句话简单说,确定形状的线。

  • 第三阶段:几何着色器(Geometry Shader)

该阶段把图元形式的一系列定点的集合作为输入,通过生产新的顶点,构造出全新的(或者其他的)图元,来生成几何形状。
一句话简单说,确定三角形的个数,使之变成几何图形。

GPU并行处理

该阶段会把图元映射为最终屏幕上相应的像素,生成片段。片段(Fragment) 是渲染一个像素所需要的所有数据。
一句话简单说,将图转化为一个个实际屏幕像素。

  • 第五阶段:片段着色器(Fragment Shader)

该阶段首先会对输入的片段进行裁切(Clipping)。裁切会丢弃超出视图以外的所有像素,用来提升执行效率。并对片段(Fragment)进行着色。
一句话简单说,对屏幕像素点着色。

  • 第六阶段:测试与混合(Tests and Blending)

该阶段会检测片段的对应的深度值(z 坐标),来判断这个像素位于其它图层像素的前面还是后面,决定是否应该丢弃。此外,该阶段还会检查 alpha 值( alpha 值定义了一个像素的透明度),从而对图层进行混合。
一句话简单说,检查图层深度和透明度,并进行图层混合。

(PS:这个很关键,会在之后推出的“App性能优化实战”系列博客中,是我会提到优化UI性能的一个点。)

因此,即使在片段着色器中计算出来了一个像素输出的颜色,在经历测试与混合图层之后,最后的像素颜色也可能完全不同。

关于混合,GPU采用如下公式进行计算,并得出最后的实际像素颜色。

R = S + D * (1 - Sa)
含义:
R:Result,最终像素颜色。
S:Source,来源像素(上面的图层像素)。
D:Destination,目标像素(下面的图层像素)。
a:alpha,透明度。
结果 = S(上)的颜色 + D(下)的颜色 * (1 - S(上)的透明度)

GPU渲染流水线的完整过程,如下图所示:

GPU图形渲染流水线

问:CPU vs. GPU?

这里引用我们团长(@月影)之前分享的一页PPT:

CPU vs. GPU

由于屏幕每个像素点有每一帧的刷新需求,所以对GPU的并行工作效率要求更高。


简单说完了GPU渲染的流水线,我们来聊一聊App的渲染流程与原理。
iOS App的渲染主要分为以下三种:

  • 原生渲染
  • 大前端渲染(WebView、类React Native
  • Flutter渲染

二、原生渲染

说到原生渲染,首先想到的就是我们最熟悉使用的iOS渲染框架:UIKitSwiftUICore AnimationCore GraphicsCore ImageOpenGL ESMetal

  • UIKit:日常开发最常用的UI框架,可以通过设置UIKit组件的布局以及相关属性来绘制界面。其实本身UIView并不拥有屏幕成像的能力,而是View上的CALayer属性拥有展示能力。(UIView继承自UIResponder,其主要负责用户操作的事件响应,iOS事件响应传递就是经过视图树遍历实现的。)
  • SwiftUI:苹果在WWDC-2019推出的一款全新的“声明式UI”框架,使用Swift编写。一套代码,即可完成iOSiPadOSmacOSwatchOS的开发与适配。(关于SwiftUI,我去年写过一篇简单的Demo,可供参考:《用SwiftUI写一个简单页面》
  • Core Animation:核心动画,一个复合引擎。尽可能快速的组合屏幕上不同的可视内容。分解成独立的图层(CALayer),存储在图层树中。
  • Core Graphics:基于 Quartz 高级绘图引擎,主要用于运行时绘制图像。
  • Core Image:运行前图像绘制,对已存在的图像进行高效处理。
  • OpenGL ESOpenGL for Embedded Systems,简称 GLES,是 OpenGL 的子集。由GPU厂商定制实现,可通过C/C++编程操控GPU
  • Metal:由苹果公司实现,WWDC-2018已经推出Metal2,渲染性能比OpenGL ES高。为了解决OpenGL ES不能充分发挥苹果芯片优势的问题。

那么,iOS原生渲染的流程有那几部分组成呢?
主要分为以下四步:

  • 第一步:更新视图树、图层树。(分别对应View的层级结构、View上的Layer层级结构)

  • 第二步:CPU开始计算下一帧要显示的内容(包括视图创建、布局计算、视图绘制、图像解码)。当 runloopkCFRunLoopBeforeWaitingkCFRunLoopExit 状态时,会通知注册的监听,然后对图层打包,打完包后,将打包数据发送给一个独立负责渲染的进程 Render Server
    前面 CPU 所处理的这些事情统称为 Commit Transaction

commit-transaction
  • 第三步:数据到达 Render Server 后会被反序列化,得到图层树,按照图层树的图层顺序、RGBA 值、图层 frame 来过滤图层中被遮挡的部分,过滤后将图层树转成渲染树,渲染树的信息会转给 OpenGL ES/Metal
WWDC14-Session-419
  • 第四步:Render Server 会调用 GPUGPU 开始进行前面提到的顶点着色器、形状装配、几何着色器、光栅化、片段着色器、测试与混合六个阶段。完成这六个阶段的工作后,就会将 CPUGPU 计算后的数据显示在屏幕的每个像素点上。
WWDC14-实际并行时图解

那么,关于iOS原生渲染的整体流程,我也画了一张图:

iOS-原生渲染流程

三、大前端渲染

1. WebView:

对于WebView渲染,其主要工作在WebKit中完成。
WebKit本身的渲染基于macOSLay Rendering架构,iOS本身渲染也是基于这套架构。
因此,本身从渲染的实现方式来说,性能应该和原生差别不大。

但为什么我们能明显感觉到使用WebView渲染要比原生渲染的慢呢?

  • 第一,首次加载。会额外多出网络请求和脚本解析工作。
    即使是本地网页加载,WebView也要比原生多出脚本解析的工作。
    WebView要额外解析HTML+CSS+JavaScript代码。

  • 第二,语言解释执行性能来看。JS的语言解析执行性能要比原生弱。
    特别是遇到复杂的逻辑与大量的计算时,WebView 的解释执行性能要比原生慢不少。

  • 第三,WebView的渲染进程是独立的,每一帧的更新都要通过IPC调用GPU进程,会造成频繁的IPC进程通信,从而造成性能消耗。并且,两个进程无法共享纹理资源,GPU无法直接使用context光栅化,而必须要等待WebView通过IPCcontext传给GPU再光栅化。因此GPU自身的性能发挥也会受影响。

因此,WebView的渲染效率,是弱于原生渲染的。

2. 类React Native(使用JavaScriptCore引擎做为虚拟机方案)

代表:React NativeWeex、小程序等。

我们以 ReactNative 举例:
React Native的渲染层直接走的是iOS原生渲染,只不过是多了Json+JavaScript脚本解析工作。
通过JavaScriptCore引擎将“JS”与“原生控件”产生相对应的关联。
进而,达成通过JS来操控iOS原生控件的目标。
(简单来说,这个json就是一个脚本语言到本地语言的映射表,KEY是脚本语言认识的符号,VALUE是本地语言认识的符号。)

简单介绍一下,JavaScriptCore
JavaScriptCoreiOS 原生与 JS 之间的桥梁,其原本是 WebKit 中解释执行 JavaScript 代码的引擎。目前,苹果公司有 JavaScriptCore 引擎,谷歌有V8引擎。

但与 WebView 一样,RN也需要面临JS语言解释性能的问题。

因此,从渲染效率角度来说,WebView < 类ReactNative < 原生。
(因为json的复杂度比html+css低)


四、Flutter渲染

首先,推荐YouTube上的一个视频:《Flutter's Rendering Pipeline》专门讲Flutter渲染相关的知识。

1. Flutter的架构:

Flutter架构

可以看到,Flutter重写了UI框架,从UI控件到渲染全部自己重新实现了。
不依赖 iOSAndroid 平台的原生控件,
依赖Engine(C++)层的Skia图形库与系统图形绘制相关接口。
因此,在不同的平台上有了相同的体验。

2. Flutter的渲染流程:

Flutter渲染流程

简单来说,Flutter的界面由Widget组成。
所有Widget会组成Widget Tree
界面更新时,会更新Widget Tree
再更新Element Tree,最后更新RenderObjectTree

更新Widget的逻辑如下:

\ newWidget == null newWidget != null
child == null 返回null 返回新的Element
child != null 移除旧的child并返回null 如果旧child被更新就返回child,否则返回新的Element
image

接下来的渲染流程,
Flutter 渲染在 Framework 层会有 BuildWidget TreeElement TreeRenderObject TreeLayoutPaintComposited Layer 等几个阶段。
FlutterC++ 层,使用 Skia 库,将 Layer 进行组合,生成纹理,使用 OpenGL 的接口向 GPU 提交渲染内容进行光栅化与合成。
提交到 GPU 进程后,合成计算,显示屏幕的过程和 iOS 原生渲染基本是类似的,因此性能上是差不多的。

更多细节,可以查看:《Flutter 究竟是如何渲染的?》

五、总结对比

渲染方式 语言 性能 对应群体
原生 Objective-C、Swift ★★★ iOS开发者
WebView HTML、CSS、JavaScript 前端开发者
类React Native JavaScript ★★ 前端开发者
Flutter Dart ★★★ Dart开发者

但Flutter的优势在于:

  1. 跨平台,可以同时运行在 iOSAndroid 两个平台。
  2. 热重载(Hot Reload),省去了重新编译代码的时间,极大的提高了开发效率。
  3. 以及未来谷歌新系统 “Fuchsia” 的发布与加持。如果谷歌未来的新系统 Fuchsia 能应用到移动端,并且领域替代 Android 。由于Fuchsia的上层是Flutter编写的,因此Flutter开发成为了移动端领域的必选项。同时Flutter又支持跨平台开发,那么其他领域的技术栈存在的价值会越来越低。

当然,苹果的希望在于 SwiftUI
如果 Fuchisa 最终失败了,SwiftUI 也支持跨端了。同时,SwiftUI本身也支持热重载。也许也是一个未来呢。

期待,苹果今年6月的线上WWDC-2020吧,希望能给我们带来不一样的惊喜。

参考与致谢:
1.《iOS开发高手课》(戴铭老师)
2.《你不知道的GPU》(月影)
3.《Flutter从加载到显示》 (圣文前辈)
4.《UIKit性能调优实战讲解》(bestswifter)
5.《iOS - 渲染原理》
6.《iOS 图像渲染原理》
7.《计算机那些事(8)——图形图像渲染原理》
8.《WWDC14:Advanced Graphics and Animations for iOS Apps》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容