屏幕卡頓
屏幕卡頓是指圖形圖像的在顯示時出現了撕裂(即圖片錯位顯示)、掉幀(重復顯示同一幀數據)等問題,導致用戶能直觀的從屏幕上看到的一種異?,F象
為什么會出現這種情況呢?下面就來詳細解說下屏幕卡頓
【高頻面試題】屏幕卡頓的原因
主要有以下三種原因
- CPU和GPU在渲染的流水線中耗時過長,導致從緩存區獲取位圖顯示時,下一幀的數據還沒有準備好,獲取的仍是上一幀的數據,產生掉幀現象,掉幀就會導致屏幕卡頓
- 蘋果官方針對屏幕撕裂問題,目前一直使用的方案是
垂直同步+雙緩存區
,可以從根本上防止和解決屏幕撕裂,但是同時也導致了新的問題掉幀
。雖然我們采用了雙緩存區,但是我們并不能解決CPU和GPU處理圖形圖像的速度問題,導致屏幕在接收到垂直信號時,數據尚未準備好,緩存區仍是上一幀的數據,因此導致掉幀 - 在垂直同步+雙緩存區的方案上,再次進行優化,將雙緩存區,改為
三緩存區
,這樣其實也并不能從根本上解決掉幀的問題,只是比雙緩存區掉幀的概率小了很多,仍有掉幀的可能性,對于用戶而言,可能是無感知的。
接下來,詳細解析下屏幕撕裂及掉幀問題
屏幕撕裂
如圖所示,屏幕撕裂就類似于這樣的情形
- 屏幕撕裂展示
在講屏幕撕裂之前,首先說說屏幕是如何成像的,主要的流程是什么
屏幕成像過程
請看下面這張圖,詳細說明了屏幕成像的一個流程
將需要顯示的圖像,經由GPU渲染
將渲染后的結果,存儲到幀緩存區,幀緩存區中存儲的格式是位圖
由視屏控制器從幀緩存區中讀取位圖,交由顯示器,從左上角逐行掃描進行顯示
屏幕撕裂的原因
在屏幕顯示圖形圖像的過程中,是不斷從幀緩存區獲取一幀一幀數據進行顯示的,
然后在渲染的過程中,幀緩存區中仍是舊的數據,屏幕拿到舊的數據去進行顯示,
在舊的數據沒有讀取完時 ,新的一幀數據處理好了,放入了緩存區,這時就會導致屏幕另一部分的顯示是獲取的新數據,從而導致屏幕上呈現圖片不匹配,人物、景象等錯位顯示的情況。
圖示如下:
蘋果官方的解決方案
蘋果官方針對屏幕撕裂現象,目前一直采用的是 垂直同步+雙緩存
,該方案是強制要求同步,且是以掉幀為代價的。
以下是垂直同步+雙緩存的一個圖解過程,如有描述錯誤的地方,歡迎留言指出
垂直同步:是指給幀緩沖加鎖,當電子光束掃描的過程中,只有掃描完成了才會讀取下一幀的數據,而不是只讀取一部分
雙緩沖區:采用兩個幀緩沖區用途GPU處理結果的存儲,當屏幕顯示其中一個緩存區內容時,另一個緩沖區繼續等待下一個緩沖結果,兩個緩沖區依次進行交替
掉幀
采用蘋果的雙緩沖區方案后,又會出現新的問題,掉幀。
什么是掉幀?簡單來說就是 屏幕重復顯示同一幀數據的情況就是掉幀
如圖所示:當前屏幕顯示的是A,在收到垂直信號后,CPU和GPU處理的B還沒有準備好,此時,屏幕顯示的仍然是A(圖有誤,第二個為GPU)
針對掉幀情況,我們可以在蘋果方案的基礎上進行優化,即采用
三緩存區
,意味著,在屏幕顯示時,后面還準備了3個數據用于顯示。
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封裝的方法進行畫圖操作
將繪制好的位圖交由CALayer,由OpenGL ES 傳送到GPU的幀緩沖區
等屏幕接收到垂直信號后,就讀取幀緩沖區的數據,顯示到屏幕上
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通過幀緩存區、視頻控制器等相關部件,將其顯示到屏幕上
OpenGL 渲染架構分析
OpenGL中的渲染架構如圖所示
主要分為兩個模塊
- Client:是指常見的iOS代碼和OpenGL API方法,這部分是在CPU中運行
- Server:是指OpenGL底層的渲染等處理,是運行在GPU中的
架構分析
- 客戶端中通過iOS代碼調用OpenGL API中的方法,將圖形渲染的相關數據通過通道傳遞到服務器中頂點著色器和片元著色器,并交由GPU處理。
- 服務器通過與客戶端的通道接收傳遞的數據,并交由相應著色器進行渲染處理,并將最終的結果渲染到屏幕上
數據傳遞
從圖上我們可以看出,客戶端和服務器進行數據傳遞的通道有三種
- Attributes
- Uniform
- Texture Data
通道名稱 | 參數類型 | 可傳入的著色器 |
---|---|---|
Attributes | 經常發生變動的數據 :紋理坐標 、光照法線、頂點坐標 、顏色數據 | 頂點著色器 |
Uniform | 不經常發生變動的數據 | 頂點著色器、片元著色器 |
Texture Data | 紋理 | 頂點著色器、片元著色器 |
Attributes
- Attributes通道只能將數據直接傳遞到頂點著色器,不能直接傳遞到片元著色器,但是可以通過頂點著色器間接傳遞給片元著色器。
- 通過Attributes傳遞的通常是經常發生變化的數據,例如顏色、頂點等。
- Attribute主要傳遞這些參數:顏色數據、頂點坐標、紋理坐標、光照法線等。
Uniform
- Uniform通過既可以傳遞到頂點著色器,也可以傳遞到片元著色器。
- Uniform中傳遞的通常是比較統一的批次數據,不經常發生變動的數據。
Texture Data
- Texture Data同Unoform一樣,可以將數據傳遞到頂點和片元著色器。
- 由于頂點著色器主要是處理頂點數據的,我們將紋理數據傳過去并沒有多大的意義。而紋理的處理的邏輯主要是在片元著色器中進行的。