屏幕卡頓
屏幕卡頓是指圖形圖像的在顯示時(shí)出現(xiàn)了撕裂(即圖片錯(cuò)位顯示)、掉幀(重復(fù)顯示同一幀數(shù)據(jù))等問(wèn)題空闲,導(dǎo)致用戶能直觀的從屏幕上看到的一種異沉铊荆現(xiàn)象
為什么會(huì)出現(xiàn)這種情況呢?下面就來(lái)詳細(xì)解說(shuō)下屏幕卡頓
屏幕卡頓原因
主要以下三種原因
- CPU和GPU在渲染的流水線中耗時(shí)過(guò)長(zhǎng)碴倾,導(dǎo)致從緩存區(qū)獲取位圖顯示時(shí)逗噩,下一幀的數(shù)據(jù)還沒(méi)有準(zhǔn)備好,獲取的仍是上一幀的數(shù)據(jù)跌榔,產(chǎn)生掉幀現(xiàn)象异雁,掉幀就會(huì)導(dǎo)致屏幕卡頓
- 蘋(píng)果官方針對(duì)屏幕撕裂問(wèn)題,目前一直使用的方案是垂直同步+雙緩存區(qū)僧须,可以從根本上防止和解決屏幕撕裂纲刀,但是同時(shí)也導(dǎo)致了新的問(wèn)題掉幀。雖然我們采用了雙緩存區(qū)担平,但是我們并不能解決CPU和GPU處理圖形圖像的速度問(wèn)題示绊,導(dǎo)致屏幕在接收到垂直信號(hào)時(shí),數(shù)據(jù)尚未準(zhǔn)備好暂论,緩存區(qū)仍是上一幀的數(shù)據(jù)面褐,因此導(dǎo)致掉幀
- 在垂直同步+雙緩存區(qū)的方案上,再次進(jìn)行優(yōu)化取胎,將雙緩存區(qū)展哭,改為三緩存區(qū),這樣其實(shí)也并不能從根本上解決掉幀的問(wèn)題闻蛀,只是比雙緩存區(qū)掉幀的概率小了很多匪傍,仍有掉幀的可能性,對(duì)于用戶而言觉痛,可能是無(wú)感知的役衡。
詳解解釋一下屏幕撕裂問(wèn)題
我們看一下下面這張圖是典型的撕裂問(wèn)題造成的
在講屏幕撕裂之前,首先說(shuō)說(shuō)屏幕是如何成像的秧饮,主要的流程是什么
屏幕成像過(guò)程
請(qǐng)看下面這張圖映挂,詳細(xì)說(shuō)明了屏幕成像的一個(gè)流程
- 將需要顯示的圖像泽篮,經(jīng)由GPU渲染
- 將渲染后的結(jié)果,存儲(chǔ)到幀緩存區(qū)柑船,幀緩存區(qū)中存儲(chǔ)的格式是位圖
- 由視頻控制器從幀緩存區(qū)中讀取位圖帽撑,交由顯示器,從左上角逐行掃描進(jìn)行顯示
屏幕撕裂原因 - 在屏幕顯示圖形圖像的過(guò)程中鞍时,是不斷從幀緩存區(qū)獲取一幀一幀數(shù)據(jù)進(jìn)行顯示的亏拉,然后在渲染的過(guò)程中,幀緩存區(qū)中仍是舊的數(shù)據(jù)逆巍,屏幕拿到舊的數(shù)據(jù)去進(jìn)行顯示及塘,在舊的數(shù)據(jù)沒(méi)有讀取完時(shí) ,新的一幀數(shù)據(jù)處理好了锐极,放入了緩存區(qū)笙僚,這時(shí)就會(huì)導(dǎo)致屏幕另一部分的顯示是獲取的新數(shù)據(jù),從而導(dǎo)致屏幕上呈現(xiàn)圖片不匹配灵再,人物肋层、景象等錯(cuò)位顯示的情況。
本人一開(kāi)始對(duì)幀的概念不是很理解翎迁,下面對(duì)幀和幀緩存做一下解釋在放映電影的過(guò)程中栋猖,畫(huà)面被一幅幅地放映在銀幕上。畫(huà)幅移開(kāi)時(shí)汪榔,光線就被遮住蒲拉,幕上便出現(xiàn)短暫的黑暗;每放映一個(gè)畫(huà)幅后痴腌,幕上就黑暗一次雌团。但這一次次極短暫的黑暗,被人的視覺(jué)生理現(xiàn)象“視覺(jué)暫留”所彌補(bǔ)衷掷。人眼在觀察景物時(shí)辱姨,光信號(hào)傳入大腦神經(jīng)需經(jīng)過(guò)一段短暫時(shí)間,光的作用結(jié)束時(shí)戚嗅,視覺(jué)也不立即消失。視覺(jué)的這一現(xiàn)象稱為“視覺(jué)暫留”枢舶。當(dāng)電影畫(huà)面換幅頻率達(dá)到每秒15幅~30幅時(shí)懦胞,觀眾便見(jiàn)不到黑暗的間隔了,這時(shí)人“看到”的就是運(yùn)動(dòng)的事物凉泄,這就是電影的基本原理躏尉。這里的一幅畫(huà)面就是電影的一幀,實(shí)際上就是電影膠片中的一格后众。
幀——就是影像動(dòng)畫(huà)中最小單位的單幅影像畫(huà)面胀糜。 一幀就是一副靜止的畫(huà)面颅拦,連續(xù)的幀就形成動(dòng)畫(huà),如電視圖象等教藻。 通常說(shuō)幀數(shù)距帅,簡(jiǎn)單地說(shuō),就是在1秒鐘時(shí)間里傳輸?shù)膱D片的幀數(shù)括堤,也可以理解為圖形處理器每秒鐘能夠刷新幾次碌秸,通常用FPS(Frames Per Second)表示。每一幀都是靜止的圖象悄窃,快速連續(xù)地顯示幀便形成了運(yùn)動(dòng)的假象讥电。高的幀率可以得到更流暢、更逼真的動(dòng)畫(huà)轧抗。每秒鐘幀數(shù) (fps) 越多恩敌,所顯示的動(dòng)作就會(huì)越流暢。
-
幀緩沖存儲(chǔ)器(Frame Buffer):簡(jiǎn)稱幀緩存或顯存横媚,它是屏幕所顯示畫(huà)面的一個(gè)直接映象潮剪,又稱為位映射圖(Bit Map)或光柵。幀緩存的每一存儲(chǔ)單元對(duì)應(yīng)屏幕上的一個(gè)像素分唾,整個(gè)幀緩存對(duì)應(yīng)一幀圖像抗碰。
蘋(píng)果官方解決撕裂問(wèn)題方案
蘋(píng)果官方針對(duì)屏幕撕裂現(xiàn)象,目前一直采用的是垂直同步+雙緩存绽乔,該方案是強(qiáng)制要求同步弧蝇,且是以掉幀為代價(jià)的。
以下是垂直同步+雙緩存的一個(gè)圖解過(guò)程
- 垂直同步:是指給幀緩沖加鎖折砸,當(dāng)電子光束掃描的過(guò)程中看疗,只有掃描完成了才會(huì)讀取下一幀的數(shù)據(jù),而不是只讀取一部分
- 雙緩沖區(qū):采用兩個(gè)幀緩沖區(qū)用途GPU處理結(jié)果的存儲(chǔ)睦授,當(dāng)屏幕顯示其中一個(gè)緩存區(qū)內(nèi)容時(shí)两芳,另一個(gè)緩沖區(qū)繼續(xù)等待下一個(gè)緩沖結(jié)果,兩個(gè)緩沖區(qū)依次進(jìn)行交替
掉幀
采用蘋(píng)果的雙緩沖區(qū)方案后去枷,又會(huì)出現(xiàn)新的問(wèn)題怖辆,掉幀。
什么是掉幀删顶?簡(jiǎn)單來(lái)說(shuō)就是 屏幕重復(fù)顯示同一幀數(shù)據(jù)的情況就是掉幀
如圖所示:當(dāng)前屏幕顯示的是A竖螃,在收到垂直信號(hào)后,CPU和GPU處理的B還沒(méi)有準(zhǔn)備好逗余,此時(shí)特咆,屏幕顯示的仍然是A
- 針對(duì)掉幀情況,我們可以在蘋(píng)果方案的基礎(chǔ)上進(jìn)行優(yōu)化录粱,即采用三緩存區(qū)腻格,意味著画拾,在屏幕顯示時(shí),后面還準(zhǔn)備了3個(gè)數(shù)據(jù)用于顯示菜职,但是這只能減少掉幀情況仍然無(wú)法從根本上解決掉幀問(wèn)題青抛。
iOS 中渲染流程
在iOS中渲染的整體流程如下所示
- App通過(guò)調(diào)用CoreGraphics、CoreAnimation些楣、CoreImage等框架的接口觸發(fā)圖形渲染操作脂凶,CoreGraphics、CoreAnimation愁茁、CoreImage等框架將渲染交由OpenGL ES蚕钦,OpenGL ES來(lái)驅(qū)動(dòng)GPU做渲染,最后顯示到屏幕上鹅很,由于OpenGL ES 是跨平臺(tái)的嘶居,所以在他的實(shí)現(xiàn)中,是不能有任何窗口相關(guān)的代碼促煮,而是讓各自的平臺(tái)為OpenGL ES提供載體邮屁。在ios中,如果需要使用OpenGL ES菠齿,就是通過(guò)CoreAnimation提供窗口佑吝,讓App可以去調(diào)用。
iOS中渲染框架總結(jié)
主要由以下六種框架
View 與 CALayer 的關(guān)系
首先分別簡(jiǎn)單說(shuō)下UIView和CALayer各自的作用
-
UIView
- UIView屬于UIKIt
- 負(fù)責(zé)繪制圖形和動(dòng)畫(huà)操作
- 用于界面布局和子視圖的管理
- 處理用戶的點(diǎn)擊事件
-
CALayer
- CALayer屬于CoreAnimation
- 只負(fù)責(zé)顯示绳匀,且顯示的是位圖
- CALayer既用于UIKit芋忿,也用于APPKit,
==> UIKit是iOS平臺(tái)的渲染框架疾棵,APPKit是Mac OSX系統(tǒng)下的渲染框架戈钢,
==> 由于iOS和Mac兩個(gè)系統(tǒng)的界面布局并不是一致的,iOS是基于多點(diǎn)觸控的交互方式是尔,而Mac OSX是基于鼠標(biāo)鍵盤(pán)的交互方式殉了,且分別在對(duì)應(yīng)的框架中做了布局的操作,所以并不需要layer載體去布局拟枚,且不用迎合任何布局方式薪铜。
UIView和CALayer的關(guān)系
- UIView基于UIKit框架,可以處理用戶觸摸事件梨州,并管理子視圖
- CALayer基于CoreAnimation痕囱,而CoreAnimation是基于QuartzCode的。所以CALayer只負(fù)責(zé)顯示暴匠,不能處理用戶的觸摸事件
- 從父類來(lái)說(shuō),CALayer繼承的是NSObject傻粘,而UIView是直接繼承自UIResponder的每窖,所以UIVIew相比CALayer而言帮掉,只是多了事件處理功能,
- 從底層來(lái)說(shuō)窒典,UIView屬于UIKit的組件蟆炊,而UIKit的組件到最后都會(huì)被分解成layer,存儲(chǔ)到圖層樹(shù)中
- 在應(yīng)用層面來(lái)說(shuō)瀑志,需要與用戶交互時(shí)涩搓,使用UIView,不需要交互時(shí)劈猪,使用兩者都可以
UIView和CALayer的渲染
下圖可以說(shuō)明view 和 layer之間是如何渲染的
界面觸發(fā)的方式有兩種
- ==> 通過(guò)loadView中子View的drawRect方法觸發(fā):會(huì)回調(diào)CoreAnimation中監(jiān)聽(tīng)Runloop的BeforeWaiting的RunloopObserver昧甘,通過(guò)RunloopObserver來(lái)進(jìn)一步調(diào)用CoreAnimation內(nèi)部的CA::Transaction::commit(),進(jìn)而一步步走到drawRect方法
==> 用戶點(diǎn)擊事件觸發(fā):?jiǎn)拘裄unloop战得,由source1處理(__IOHIDEventSystemClientQueueCallback)充边,并且在下一個(gè)runloop里由source0轉(zhuǎn)發(fā)給UIApplication(_UIApplicationHandleEventQueue),從而能通過(guò)source0里的事件隊(duì)列來(lái)調(diào)用CoreAnimation內(nèi)部的CA::Transaction::commit();方法常侦,進(jìn)而一步一步的調(diào)用drawRect浇冰。
最終都會(huì)走到CoreAnimation中的CA::Transaction::commit()方法,從而來(lái)觸發(fā)UIView和CALayer的渲染,這時(shí)聋亡,已經(jīng)到了CoreAnimation的內(nèi)部肘习,即調(diào)用CA::Transaction::commit();來(lái)創(chuàng)建CATrasaction,然后進(jìn)一步調(diào)用 CALayer drawInContext:()回調(diào)CALayer的Delegate(UIView)坡倔,問(wèn)UIView沒(méi)有需要畫(huà)的內(nèi)容漂佩,即回調(diào)到drawRect:方法在drawRect:方法里可以通過(guò)CoreGraphics函數(shù)或UIKit中對(duì)CoreGraphics封裝的方法進(jìn)行畫(huà)圖操作將繪制好的位圖交由CALayer,由OpenGL ES 傳送到GPU的幀緩沖區(qū),等屏幕接收到垂直信號(hào)后致讥,就讀取幀緩沖區(qū)的數(shù)據(jù)仅仆,顯示到屏幕上
CoreAnimation
在蘋(píng)果官方的描述中,Render垢袱、Compose墓拜,and animate visual elements,CoreAnimationg中的動(dòng)畫(huà)只是一部分请契,它其實(shí)是一個(gè)復(fù)合引擎咳榜,主要的職責(zé)包括 渲染、構(gòu)建和動(dòng)畫(huà)實(shí)現(xiàn)爽锥。
ios中CoreAnimation如圖所示
- ios中基于CoreAnimation構(gòu)建的框架有兩個(gè):UIKit和APPKit
- CoreAnimation 又是基于Metal 涌韩、CoreGraphics封裝的
蘋(píng)果為什么要基于UIView和CALayer提供兩個(gè)平行的層級(jí)關(guān)系(UIKit 和APPKit)?- 職責(zé)分離氯夷,可以避免大量重復(fù)代碼
- 兩個(gè)系統(tǒng)交互規(guī)則不一致臣樱,雖然功能上類似,但實(shí)現(xiàn)上有顯著區(qū)別
CoreAnimation中的渲染流水線
CoreAnimation中渲染的流程如圖所示
主要分為兩部分:
- CoreAnimation部分
- GPU部分
CoreAnimation部分 - App處理UIView、UIButton等載體的事件雇毫,然后通過(guò)CPU完成對(duì)顯示內(nèi)容的計(jì)算玄捕,并將計(jì)算后的圖層進(jìn)行打包,在下一次runloop時(shí)棚放,發(fā)送到渲染服務(wù)器
- Render Server中主要對(duì)收到的準(zhǔn)備顯示的內(nèi)容進(jìn)行解碼枚粘,然后執(zhí)行OpenGL等相關(guān)程序,并調(diào)用GPU進(jìn)行渲染
==> Render Server 操作分析
GPU部分 - GPU中通過(guò)頂點(diǎn)著色器飘蚯、片元著色器完成對(duì)顯示內(nèi)容的渲染馍迄,將結(jié)果存入幀緩存區(qū)
- GPU通過(guò)幀緩存區(qū)、視頻控制器等相關(guān)部件局骤,將其顯示到屏幕上
總結(jié)
屏幕撕裂問(wèn)題和掉幀問(wèn)題根本的原因是CPU和GPU的計(jì)算能力跟不上幀的刷新率(60FPS)才會(huì)有可能發(fā)生撕裂或者掉幀攀圈,在iOS的設(shè)備上經(jīng)過(guò)蘋(píng)果的垂直同步信號(hào)+雙緩沖區(qū)的解決方案后很少看到撕裂,除非是在舊的設(shè)備(例如iphone4s)或運(yùn)行對(duì)畫(huà)質(zhì)要求特別高的游戲或復(fù)雜動(dòng)畫(huà)有可能會(huì)出現(xiàn)撕裂庄涡,掉幀也是同樣的道理量承。iOS 繪制普通的UIView、UIImageView等是不會(huì)出現(xiàn)撕裂或掉幀的穴店。雖然蘋(píng)果的解決方案沒(méi)有從根本上解決存在的問(wèn)題撕捍,但是我們的實(shí)際體驗(yàn)中撕裂和掉幀問(wèn)題并不常見(jiàn)。