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渲染框架:UIKit、SwiftUICore Animation、Core GraphicsCore ImageOpenGL ESMetal。

  • UIKit:日常開發最常用的UI框架,可以通過設置UIKit組件的布局以及相關屬性來繪制界面。其實本身UIView并不擁有屏幕成像的能力,而是View上的CALayer屬性擁有展示能力。(UIView繼承自UIResponder,其主要負責用戶操作的事件響應,iOS事件響應傳遞就是經過視圖樹遍歷實現的。)
  • SwiftUI:蘋果在WWDC-2019推出的一款全新的“聲明式UI”框架,使用Swift編寫。一套代碼,即可完成iOSiPadOS、macOS、watchOS的開發與適配。(關于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 會調用 GPU,GPU 開始進行前面提到的頂點著色器、形狀裝配、幾何著色器、光柵化、片段著色器、測試與混合六個階段。完成這六個階段的工作后,就會將 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 Native、Weex、小程序等。

我們以 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控件到渲染全部自己重新實現了。
不依賴 iOS、Android 平臺的原生控件,
依賴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 層會有 Build、Widget Tree、Element Tree、RenderObject Tree、Layout、Paint、Composited 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. 跨平臺,可以同時運行在 iOS、Android 兩個平臺。
  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閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容