深刻理解 iOS Rendering Process

理解的核心是理清:application、render server、opengl、core animation、gpu、顯示器的關係。ios

 

本文將從 OpenGL 的角度結合 Apple 官方給出的部分資料,介紹 iOS Rendering Process 的概念及其整個底層渲染管道的各個流程。git

相信在理解了 iOS Rendering Process 的底層各個階段以後,咱們能夠在平日的開發工做之中寫出性能更高的代碼,在解決幀率不足的顯示卡頓問題時也能夠多一些思路~github

索引算法

  • iOS Rendering Process 概念編程

  • iOS Rendering 技術框架緩存

  • OpenGL 主要渲染步驟微信

  • OpenGL Render Pipelineapp

  • Core Animation Pipeline框架

  • Commit Transactionide

  • Animation

  • 全文總結

  • 擴展閱讀

iOS Rendering Process 概念

iOS Rendering Process 譯爲 iOS 渲染流程,本文特指 iOS 設備從設置將要顯示的圖元數據到最終在設備屏幕成像的整個過程。

在開始剖析 iOS Rendering Process 以前,咱們須要對 iOS 的渲染概念有一個基本的認知:

基於平鋪的渲染

iOS 設備的屏幕分爲 N * N 像素的圖塊,每一個圖塊都適合於 SoC 緩存,幾何體在圖塊內被大量拆分,只有在全部幾何體所有提交以後才能夠進行光柵化(Rasterization)。

微信圖片_20180404155447.jpg

Note: 這裏的光柵化指將屏幕上面被大量拆分出來的幾何體渲染爲像素點的過程。

rasterization.jpg

iOS Rendering 技術框架

事實上 iOS 渲染相關的層級劃分大概以下:

ios_rendering_framework.png

UIKit

嘛~ 做爲一名 iOS Developer 來講,應該對 UIKit 都不陌生,咱們平常開發中使用的用戶交互組件都來自於 UIKit Framework,咱們經過設置 UIKit 組件的 Layout 以及 BackgroundColor 等屬性來完成平常的界面繪畫工做。

其實 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_rendering_pipeline.png

這些圖元數據流入 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

core_animation_pipeline.png

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 逐幀渲染

animation.png

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)

rendering_pass.png

性能檢測思路

基於整篇文章的內容概括一下咱們在平常的開發工做中遇到性能問題時檢測問題代碼的思路:

TIM截圖20180417113100.png

文章寫得比較用心(是我我的的原創文章,轉載請註明 https://lision.me/),若是發現錯誤會優先在個人 我的博客 中更新。若是有任何問題歡迎在個人微博 @Lision 聯繫我~

但願個人文章能夠爲你帶來價值~

擴展閱讀

https://lision.me/ios_rendering_process/

相關文章
相關標籤/搜索