级别:★★☆☆☆
标签:「GPU」「原生渲染」「大前端」「Flutter」
做者: 647
审校: QiShare团队php
前言:
最近,研究了一下GPU以及App的渲染流程与原理。
首先,感谢 QiShare团队 的指导与支持,以及 鹏哥(@snow) 对本文的审核与帮助。
接下来,让咱们开始咱们今天的探索之旅。css
GPU(
Graphics Processing Unit
):
又名图形处理器,是显卡的 “核心”。
主要负责图像运算工做,具备高并行能力,经过计算将图像显示在屏幕像素中。html
GPU的工做原理,简单来讲就是:
—— 将 “3D坐标” 转换成 “2D坐标” ,再将 “2D坐标” 转换为 “实际有颜色的像素” 。前端
那么,GPU具体的工做流水线 会分为“六个阶段”,分别是:git
顶点着色器
=>
形状装配=>
几何着色器=>
光栅化=>
片断着色器=>
测试与混合github
该阶段输入的是顶点数据(Vertex Data
),顶点数据是一系列顶点的集合。顶点着色器主要的目的是把 3D 坐标转为 “2D” 坐标,同时顶点着色器能够对顶点属性进行一些基本处理。 ( 一句话简单说,肯定形状的点。)编程
该阶段将顶点着色器输出的全部顶点做为输入,并将全部的点装配成指定图元的形状。 图元(Primitive
) 用于表示如何渲染顶点数据,如:点、线、三角形。 这个阶段也叫图元装配。 ( 一句话简单说,肯定形状的线。)json
该阶段把图元形式的一系列定点的集合做为输入,经过生产新的顶点,构造出全新的(或者其余的)图元,来生成几何形状。 ( 一句话简单说,肯定三角形的个数,使之变成几何图形。)小程序
该阶段会把图元映射为最终屏幕上相应的像素,生成片断。片断(Fragment
) 是渲染一个像素所须要的全部数据。 ( 一句话简单说,将图转化为一个个实际屏幕像素。)swift
该阶段首先会对输入的片断进行裁切(Clipping
)。裁切会丢弃超出视图之外的全部像素,用来提高执行效率。并对片断(Fragment
)进行着色。 ( 一句话简单说,对屏幕像素点着色。)
该阶段会检测片断的对应的深度值(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渲染流水线的完整过程,以下图所示:
问:CPU vs. GPU?
这里引用咱们团长(@月影)以前分享的一页PPT:
因为屏幕每一个像素点有每一帧的刷新需求,因此对GPU的并行工做效率要求更高。
简单说完了GPU渲染的流水线,咱们来聊一聊App的渲染流程与原理。
iOS App的渲染主要分为如下三种:
WebView
、类React Native
)说到原生渲染,首先想到的就是咱们最熟悉使用的iOS渲染框架:UIKit
、SwiftUI
、Core Animation
、Core Graphics
、Core Image
、OpenGL ES
、Metal
。
UIKit
:平常开发最经常使用的UI框架,能够经过设置UIKit
组件的布局以及相关属性来绘制界面。其实自己UIView
并不拥有屏幕成像的能力,而是View
上的CALayer
属性拥有展现能力。(UIView
继承自UIResponder
,其主要负责用户操做的事件响应,iOS事件响应传递就是通过视图树遍历实现的。)SwiftUI
:苹果在WWDC-2019
推出的一款全新的“声明式UI”框架,使用Swift
编写。一套代码,便可完成iOS
、iPadOS
、macOS
、watchOS
的开发与适配。(关于SwiftUI
,我去年写过一篇简单的Demo
,可供参考:《用SwiftUI写一个简单页面》)Core Animation
:核心动画,一个复合引擎。尽量快速的组合屏幕上不一样的可视内容。分解成独立的图层(CALayer
),存储在图层树中。Core Graphics
:基于 Quartz
高级绘图引擎,主要用于运行时绘制图像。Core Image
:运行前图像绘制,对已存在的图像进行高效处理。OpenGL ES
:OpenGL for Embedded Systems
,简称 GLES
,是 OpenGL
的子集。由GPU
厂商定制实现,可经过C/C++
编程操控GPU
。Metal
:由苹果公司实现,WWDC-2018
已经推出Metal2
,渲染性能比OpenGL ES
高。为了解决OpenGL ES
不能充分发挥苹果芯片优点的问题。那么,iOS原生渲染的流程有那几部分组成呢?
主要分为如下四步:
第一步:更新视图树、图层树。(分别对应View
的层级结构、View
上的Layer
层级结构)
第二步:CPU开始计算下一帧要显示的内容(包括视图建立、布局计算、视图绘制、图像解码)。当 runloop
在 kCFRunLoopBeforeWaiting
和 kCFRunLoopExit
状态时,会通知注册的监听,而后对图层打包,打完包后,将打包数据发送给一个独立负责渲染的进程 Render Server
。 前面 CPU 所处理的这些事情统称为 Commit Transaction
。
Render Server
后会被反序列化,获得图层树,按照图层树的图层顺序、RGBA
值、图层 frame
来过滤图层中被遮挡的部分,过滤后将图层树转成渲染树,渲染树的信息会转给 OpenGL ES
/Metal
。Render Server
会调用 GPU
,GPU
开始进行前面提到的顶点着色器、形状装配、几何着色器、光栅化、片断着色器、测试与混合六个阶段。完成这六个阶段的工做后,就会将 CPU
和 GPU
计算后的数据显示在屏幕的每一个像素点上。那么,关于iOS原生渲染的总体流程,我也画了一张图:
对于WebView
渲染,其主要工做在WebKit
中完成。
WebKit
自己的渲染基于macOS
的Lay Rendering
架构,iOS
自己渲染也是基于这套架构。
所以,自己从渲染的实现方式来讲,性能应该和原生差异不大。
但为何咱们能明显感受到使用WebView
渲染要比原生渲染的慢呢?
第一,首次加载。会额外多出网络请求和脚本解析工做。 即便是本地网页加载,WebView
也要比原生多出脚本解析的工做。 WebView
要额外解析HTML+CSS+JavaScript
代码。
第二,语言解释执行性能来看。JS的语言解析执行性能要比原生弱。 特别是遇到复杂的逻辑与大量的计算时,WebView
的解释执行性能要比原生慢很多。
第三,WebView
的渲染进程是独立的,每一帧的更新都要经过IPC
调用GPU
进程,会形成频繁的IPC
进程通讯,从而形成性能消耗。而且,两个进程没法共享纹理资源,GPU
没法直接使用context
光栅化,而必需要等待WebView
经过IPC
把context
传给GPU
再光栅化。所以GPU
自身的性能发挥也会受影响。
所以,WebView
的渲染效率,是弱于原生渲染的。
JavaScriptCore
引擎作为虚拟机方案)表明:React Native
、Weex
、小程序等。
咱们以 ReactNative
举例:
React Native
的渲染层直接走的是iOS原生渲染,只不过是多了Json
+JavaScript
脚本解析工做。
经过JavaScriptCore
引擎将“JS”与“原生控件”产生相对应的关联。
进而,达成经过JS
来操控iOS
原生控件的目标。 (简单来讲,这个json
就是一个脚本语言到本地语言的映射表,KEY
是脚本语言认识的符号,VALUE
是本地语言认识的符号。)
简单介绍一下,
JavaScriptCore
:
JavaScriptCore
是iOS
原生与JS
之间的桥梁,其本来是WebKit
中解释执行JavaScript
代码的引擎。目前,苹果公司有JavaScriptCore
引擎,谷歌有V8
引擎。
但与 WebView
同样,RN也须要面临JS语言解释性能的问题。
所以,从渲染效率角度来讲,WebView < 类ReactNative < 原生。 (由于json
的复杂度比html
+css
低)
首先,推荐YouTube上的一个视频:《Flutter's Rendering Pipeline》专门讲Flutter
渲染相关的知识。
能够看到,Flutter
重写了UI
框架,从UI
控件到渲染所有本身从新实现了。
不依赖 iOS
、Android
平台的原生控件,
依赖Engine(C++)
层的Skia
图形库与系统图形绘制相关接口。
所以,在不一样的平台上有了相同的体验。
简单来讲,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 |
接下来的渲染流程,
Flutter
渲染在 Framework
层会有 Build
、Widget Tree
、Element Tree
、RenderObject Tree
、Layout
、Paint
、Composited Layer
等几个阶段。
在 Flutter
的 C++
层,使用 Skia
库,将 Layer
进行组合,生成纹理,使用 OpenGL
的接口向 GPU
提交渲染内容进行光栅化与合成。
提交到 GPU
进程后,合成计算,显示屏幕的过程和 iOS
原生渲染基本是相似的,所以性能上是差很少的。
渲染方式 | 语言 | 性能 | 对应群体 |
---|---|---|---|
原生 | Objective-C、Swift | ★★★ | iOS开发者 |
WebView | HTML、CSS、JavaScript | ★ | 前端开发者 |
类React Native | JavaScript | ★★ | 前端开发者 |
Flutter | Dart | ★★★ | Dart开发者 |
但Flutter的优点在于:
iOS
、Android
两个平台。Hot Reload
),省去了从新编译代码的时间,极大的提升了开发效率。“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》
了解更多iOS及相关新技术,请关注咱们的公众号:
可添加以下小编微信,并备注加入QiShare技术交流群,小编会邀请你加入《QiShare技术交流群》。
关注咱们的途径有:
QiShare(简书)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公众号)
推荐文章:
iOS 查看及导出项目运行日志
Flutter Platform Channel 使用与源码分析
开发没切图怎么办?矢量图标(iconFont)上手指南
DarkMode、WKWebView、苹果登陆是否必须适配?
iOS 接入 Google、Facebook 登陆(二)
iOS 接入 Google、Facebook 登陆(一)
Nginx 入门实战 iOS中的3D变换(二)
iOS中的3D变换(一)
WebSocket 双端实践(iOS/ Golang)
今天咱们来聊一聊WebSocket(iOS/Golang)
奇舞团安卓团队——aTaller
奇舞周刊