iOS中的渲染流程解析及屏幕卡頓

iOS中的渲染

在iOS中渲染的整體流程如下所示


App通過調用CoreGraphics、CoreAnimation、CoreImage等框架的接口觸發圖形渲染操作

CoreGraphics、CoreAnimation、CoreImage等框架將渲染交由OpenGL ES,由OpenGL ES來驅動GPU做渲染,最后顯示到屏幕上

由于OpenGL ES 是跨平臺的,所以在他的實現中,是不能有任何窗口相關的代碼,而是讓各自的平臺為OpenGL ES提供載體。在ios中,如果需要使用OpenGL ES,就是通過CoreAnimation提供窗口,讓App可以去調用。

iOS中渲染框架總結

主要由以下六種框架,表格中已經說明了,就不再詳細解釋了

View 與 CALayer 的關系

首先分別簡單說下UIView和CALayer各自的作用

UIView

UIView屬于UIKIt

負責繪制圖形和動畫操作

用于界面布局和子視圖的管理

處理用戶的點擊事件

CALayer

CALayer屬于CoreAnimation

只負責顯示,且顯示的是位圖

CALayer既用于UIKit,也用于APPKit,

==> UIKit是iOS平臺的渲染框架,APPKit是Mac OSX系統下的渲染框架,

==> 由于iOS和Mac兩個系統的界面布局并不是一致的,iOS是基于多點觸控的交互方式,而Mac OSX是基于鼠標鍵盤的交互方式,且分別在對應的框架中做了布局的操作,所以并不需要layer載體去布局,且不用迎合任何布局方式。

【面試題】UIView和CALayer的關系

UIView基于UIKit框架,可以處理用戶觸摸事件,并管理子視圖

CALayer基于CoreAnimation,而CoreAnimation是基于QuartzCode的。所以CALayer只負責顯示,不能處理用戶的觸摸事件

從父類來說,CALayer繼承的是NSObject,而UIView是直接繼承自UIResponder的,所以UIVIew相比CALayer而言,只是多了事件處理功能,

從底層來說,UIView屬于UIKit的組件,而UIKit的組件到最后都會被分解成layer,存儲到圖層樹中

在應用層面來說,需要與用戶交互時,使用UIView,不需要交互時,使用兩者都可以

UIView和CALayer的渲染

下圖可以說明view 和 layer之間是如何渲染的


界面觸發的方式有兩種

==> 通過loadView中子View的drawRect方法觸發:會回調CoreAnimation中監聽Runloop的BeforeWaiting的RunloopObserver,通過RunloopObserver來進一步調用CoreAnimation內部的CA::Transaction::commit(),進而一步步走到drawRect方法

==> 用戶點擊事件觸發:喚醒Runloop,由source1處理(__IOHIDEventSystemClientQueueCallback),并且在下一個runloop里由source0轉發給UIApplication(_UIApplicationHandleEventQueue),從而能通過source0里的事件隊列來調用CoreAnimation內部的CA::Transaction::commit();方法,進而一步一步的調用drawRect。

最終都會走到CoreAnimation中的CA::Transaction::commit()方法,從而來觸發UIView和CALayer的渲染

這時,已經到了CoreAnimation的內部,即調用CA::Transaction::commit();來創建CATrasaction,然后進一步調用CALayer drawInContext:()

回調CALayer的Delegate(UIView),問UIView沒有需要畫的內容,即回調到drawRect:方法

在drawRect:方法里可以通過CoreGraphics函數或UIKit中對CoreGraphics封裝的方法進行畫圖操作

界面觸發的方式有兩種

==> 通過loadView中子View的drawRect方法觸發:會回調CoreAnimation中監聽Runloop的BeforeWaiting的RunloopObserver,通過RunloopObserver來進一步調用CoreAnimation內部的CA::Transaction::commit(),進而一步步走到drawRect方法

==> 用戶點擊事件觸發:喚醒Runloop,由source1處理(__IOHIDEventSystemClientQueueCallback),并且在下一個runloop里由source0轉發給UIApplication(_UIApplicationHandleEventQueue),從而能通過source0里的事件隊列來調用CoreAnimation內部的CA::Transaction::commit();方法,進而一步一步的調用drawRect。

最終都會走到CoreAnimation中的CA::Transaction::commit()方法,從而來觸發UIView和CALayer的渲染

這時,已經到了CoreAnimation的內部,即調用CA::Transaction::commit();來創建CATrasaction,然后進一步調用CALayer drawInContext:()

回調CALayer的Delegate(UIView),問UIView沒有需要畫的內容,即回調到drawRect:方法

在drawRect:方法里可以通過CoreGraphics函數或UIKit中對CoreGraphics封裝的方法進行畫圖操作

CoreAnimation

在蘋果官方的描述中,Render、Compose,and animate visual elements,CoreAnimationg中的動畫只是一部分,它其實是一個復合引擎,主要的職責包括 渲染、構建和動畫實現。

ios中CoreAnimation如圖所示


ios中基于CoreAnimation構建的框架有兩個:UIKit和APPKit

CoreAnimation 又是基于Metal 、CoreGraphics封裝的

蘋果為什么要基于UIView和CALayer提供兩個平行的層級關系(UIKit 和APPKit)?

職責分離,可以避免大量重復代碼

兩個系統交互規則不一致,雖然功能上類似,但實現上有顯著區別

CoreAnimation中的渲染流水線

CoreAnimation中渲染的流程如圖所示


主要分為兩部分:

CoreAnimation部分

GPU部分

CoreAnimation部分

App處理UIView、UIButton等載體的事件,然后通過CPU完成對顯示內容的計算,并將計算后的圖層進行打包,在下一次runloop時,發送到渲染服務器

Render Server中主要對收到的準備顯示的內容進行解碼,然后執行OpenGL等相關程序,并調用GPU進行渲染

==> Render Server 操作分析


GPU部分

GPU中通過頂點著色器、片元著色器完成對顯示內容的渲染,將結果存入幀緩存區

GPU通過幀緩存區、視頻控制器等相關部件,將其顯示到屏幕上

屏幕卡頓

屏幕卡頓是指圖形圖像的在顯示時出現了撕裂(即圖片錯位顯示)、掉幀(重復顯示同一幀數據)等問題,導致用戶能直觀的從屏幕上看到的一種異常現象

為什么會出現這種情況呢?下面就來詳細解說下屏幕卡頓

屏幕卡頓的原因

主要有以下三種原因

1.CPU和GPU在渲染的流水線中耗時過長,導致從緩存區獲取位圖顯示時,下一幀的數據還沒有準備好,獲取的仍是上一幀的數據,產生掉幀現象,掉幀就會導致屏幕卡頓

2.蘋果官方針對屏幕撕裂問題,目前一直使用的方案是垂直同步+雙緩存區,可以從根本上防止和解決屏幕撕裂,但是同時也導致了新的問題掉幀。雖然我們采用了雙緩存區,但是我們并不能解決CPU和GPU處理圖形圖像的速度問題,導致屏幕在接收到垂直信號時,數據尚未準備好,緩存區仍是上一幀的數據,因此導致掉幀

3.在垂直同步+雙緩存區的方案上,再次進行優化,將雙緩存區,改為三緩存區,這樣其實也并不能從根本上解決掉幀的問題,只是比雙緩存區掉幀的概率小了很多,仍有掉幀的可能性,對于用戶而言,可能是無感知的。

屏幕撕裂

如圖所示,屏幕撕裂就類似于這樣的情形


在講屏幕撕裂之前,首先說說屏幕是如何成像的,主要的流程是什么

屏幕成像過程

請看下面這張圖,詳細說明了屏幕成像的一個流程


將需要顯示的圖像,經由GPU渲染

將渲染后的結果,存儲到幀緩存區,幀緩存區中存儲的格式是位圖

由視屏控制器從幀緩存區中讀取位圖,交由顯示器,從左上角逐行掃描進行顯示

屏幕撕裂的原因

在屏幕顯示圖形圖像的過程中,是不斷從幀緩存區獲取一幀一幀數據進行顯示的,

然后在渲染的過程中,幀緩存區中仍是舊的數據,屏幕拿到舊的數據去進行顯示,

在舊的數據沒有讀取完時 ,新的一幀數據處理好了,放入了緩存區,這時就會導致屏幕另一部分的顯示是獲取的線數據,從而導致屏幕上呈現圖片不匹配,人物、景象等錯位顯示的情況。

圖示如下:


蘋果官方的解決方案

蘋果官方針對屏幕撕裂現象,目前一直采用的是?垂直同步+雙緩存,該方案是強制要求同步,且是以掉幀為代價的。

以下是垂直同步+雙緩存的一個圖解過程,


垂直同步:是指給幀緩沖加鎖,當電子光束掃描的過程中,只有掃描完成了才會讀取下一幀的數據,而不是只讀取一部分

雙緩沖區:采用兩個幀緩沖區用途GPU處理結果的存儲,當屏幕顯示其中一個緩存區內容時,另一個緩沖區繼續等待下一個緩沖結果,兩個緩沖區依次進行交替

掉幀

采用蘋果的雙緩沖區方案后,又會出現新的問題,掉幀。

什么是掉幀?簡單來說就是 屏幕重復顯示同一幀數據的情況就是掉幀

如圖所示:當前屏幕顯示的是A,在收到垂直信號后,CPU和GPU處理的B還沒有準備好,此時,屏幕顯示的仍然是A


針對掉幀情況,我們可以在蘋果方案的基礎上進行優化,即采用三緩存區,意味著,在屏幕顯示時,后面還準備了3個數據用于顯示。

文章內容和圖片引用來自:Style_月月

文章源地址

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。