iOS探索:UI視圖之卡頓、掉幀及繪制原理

在開始理解卡頓、掉幀及繪制原理前,首先讓我們先了解下圖像的顯示原理

圖像顯示原理

WX20181206-150708@2x.png
  • 關(guān)于CPU和GPU都是通過總線連接起來的,在CPU當(dāng)中輸出的往往是一個位圖,再經(jīng)由總線在合適的時機(jī)傳遞個GPU

  • GPU拿到這個位圖之后,會對這個位圖的圖層進(jìn)行渲染,包括紋理的合成等

  • 之后會把這個結(jié)果放到幀緩沖區(qū)中,然后視頻控制器會按照VSync信號逐行讀取幀緩沖區(qū)的數(shù)據(jù),經(jīng)過可能的數(shù)模轉(zhuǎn)換傳遞給顯示器,達(dá)到最終的顯示效果

那么接下來讓我們看一下CPU和GPU分別做了哪些事情

WX20181206-153514@2x.png
  • 首先當(dāng)我們創(chuàng)建一個UIView控件的時候,其中負(fù)責(zé)顯示的CALayer

  • CALayer中有一個contents屬性,就是我們最終要繪制到屏幕上的一個位圖,比如說我們創(chuàng)建了一個UILabel,那么在contents里面就放了一個關(guān)于Hello world的文字位圖

  • 然后系統(tǒng)會在一個合適的時機(jī)回調(diào)給我們一個drawRect:的方法,這個方法中我們可以去繪制一些自定義的內(nèi)容

  • 繪制好了之后,最終會由Core Animation這個框架提交給GPU部分的OpenGL渲染管線,進(jìn)行最終的位圖的渲染,包括紋理合成等,然后顯示在屏幕上

那么CPU和GPU具體做了哪些工作承擔(dān)呢

CPU

具體分為四個階段

  • Layout:這里主要涉及到一些UI布局,文本計(jì)算等,例如一個label的size

  • Display:繪制階段,例如drawRect方法就在這一步驟中

  • Prepare:圖片的編解碼等操作在此步驟中

  • Commit:提交位圖

GPU渲染管線

  • 頂點(diǎn)著色

  • 圖元裝配

  • 光柵化

  • 片段著色

  • 片段處理

UI卡頓、掉幀的原因

WX20181206-160621@2x.png

在顯示器中是固定的頻率,比如iOS中是每秒60幀(60FPS),即每幀16.7ms

從上圖中可以看出,每兩個VSync信號之間有時間間隔(16.7ms),在這個時間內(nèi),CPU主線程計(jì)算布局,解碼圖片,創(chuàng)建視圖,繪制文本,計(jì)算完成后將內(nèi)容交給GPU,GPU變換,合成,渲染(詳細(xì)可學(xué)習(xí) OpenGL相關(guān)課程),放入幀緩沖區(qū)

假如16.7ms內(nèi),CPU和GPU沒有來得及生產(chǎn)出一幀緩沖,那么這一幀會被丟棄,顯示器就會保持不變,繼續(xù)顯示上一幀內(nèi)容,這就將導(dǎo)致導(dǎo)致畫面卡頓

所以無論CPU,GPU,哪個消耗時間過長,都會導(dǎo)致在16.7ms內(nèi)無法生成一幀緩存

卡頓、掉幀優(yōu)化方案切入點(diǎn)

  • CPU
    CPU在準(zhǔn)備下一幀的所做的工作非常多導(dǎo)致耗時,基于減輕CPU工作時長和壓力來達(dá)到一個優(yōu)化效果
    1、部分對象的創(chuàng)建、調(diào)整和銷毀可以放到子線程去做
    2、預(yù)排版( 布局計(jì)算、文本計(jì)算),這些計(jì)算也可以放到子線程去做,這樣主線程也可以有更多的時間去響應(yīng)用戶的交互
    3、預(yù)渲染(文本等異步繪制、圖片編解碼等)

  • GPU
    1、紋理渲染:假如說我們觸發(fā)了離屏渲染,例如我們設(shè)置圓角時對maskToBounds的設(shè)置,包括一些陰影、蒙層等都會觸發(fā)GPU層面的離屏渲染,對于這種情況下,GPU對于紋理渲染的工作量就會非常的大,我們可以基于此對GPU進(jìn)行優(yōu)化,就是盡量減少離屏渲染,我們也可以通過CPU的異步繪制來減輕GPU的壓力

    2、視圖混合: 比如說我們視圖層級比較復(fù)雜,視圖之間層層疊加,那么GPU就要做每一個視圖的合成,合成每一個像素點(diǎn)的像素值,如果我們可以減少視圖的層級,也是可以減輕GPU的壓力,我們也可以通過CPU的異步繪制機(jī)制來達(dá)到一個提交的位圖本身就是一個層級比較少的位圖

UIView的繪制原理

流程圖
QQ20181206-211905@2x.png
  • 當(dāng)我們調(diào)用[UIView setNeedsDisplay]這個方法時,其實(shí)并沒有立即進(jìn)行繪制工作,系統(tǒng)會立刻調(diào)用CALayer的同名方法,并且會在當(dāng)前l(fā)ayer上打上一個標(biāo)記,然后會在當(dāng)前runloop將要結(jié)束的時候調(diào)用[CALayer display]這個方法,然后進(jìn)入我們視圖的真正繪制過程

  • 而在[CALayer display]這個方法的內(nèi)部實(shí)現(xiàn)中會判斷這個layer的delegate是否響應(yīng)displayLayer:這個方法,如果不響應(yīng)這個方法,就會進(jìn)入到系統(tǒng)繪制流程中;如果響應(yīng)這個方法,那么就會為我們提供異步繪制的入口

上面就是UIView的繪制原理,接下來我們看一下系統(tǒng)繪制流程是怎樣的

老規(guī)矩,先上流程圖
QQ20181206-213639@2x.png
  • 在CALayer內(nèi)部會先創(chuàng)建backing store,我可以理解為CGContext,我們一般在drawRect:方法中通過上下文堆棧當(dāng)中取出棧頂?shù)腸ontext,也就是上下文

  • 然后這個layer會判斷是否有代理,如果沒有代理,那么就會調(diào)用[CALayer drawInCotext:];如果有代理,會調(diào)用代理的drawLayer:inContext:方法,然后做當(dāng)前視圖的繪制工作這一步是發(fā)生在系統(tǒng)內(nèi)部的),然后在一個合適的時機(jī)給與我們這個十分熟悉的[UIView drawRect:]方法的回調(diào),[UIView drawRect:]這個方法默認(rèn)是什么都不做,,系統(tǒng)給我們開這個口子是為了讓我們可以再做一些其他的繪制工作

  • 然后無論是哪個分支,最終都會由CALayer上傳對應(yīng)的backing store(可以理解為位圖)給GPU,然后就結(jié)束了系統(tǒng)默認(rèn)的繪制流程

那么問題來了,我們?nèi)绾芜M(jìn)行異步繪制呢

實(shí)際上我們就需要借用系統(tǒng)給開的這個口子,即[layer.delegate displayLayer:]

  • 在這個異步繪制過程中就需要代理負(fù)責(zé)生成對應(yīng)的bitmap(位圖)

  • 同時設(shè)置bitmap作為layer.contents屬性的值

國際慣例,流程圖走一波(原諒我畫圖能力實(shí)在有限TT)

QQ20181206-220620@2x.png
  • 假如說我們在某一個時機(jī)調(diào)用了[view setNeedsDisplay]這個方法,系統(tǒng)會在當(dāng)前runloop將要結(jié)束的時候調(diào)用[CALyer display]方法,然后如果我們這個layer的代理實(shí)現(xiàn)了[view displayLayer]這個方法

  • 然后會通過子線程的切換,我們在子線程中去做一個位圖的繪制,主線程可以去做一些其他的操作

  • 在子線程中第一步先通過CGBitmapContextCreate()方法來創(chuàng)建一個位圖的上下文,然后我們通過CoreGraphic API可以做當(dāng)前UI控件的一些繪制工作,最后我們再通過CGBitmapContextCreateImage()這個函數(shù)來根據(jù)當(dāng)前所繪制的上下文來生成一張CGImage圖片

  • 最后回到主線程來提交這個位圖,設(shè)置layer的contents屬性,這樣就完成了一個UI控件的異步繪制過程

離屏渲染 (便于理解視圖卡頓、掉幀中對GPU的開銷)

離屏渲染指的是GPU在當(dāng)前屏幕緩沖區(qū)以外開辟了一個緩沖區(qū)進(jìn)行渲染操作

當(dāng)前屏幕渲染不需要額外創(chuàng)建新的緩存,也不需要開啟新的上下文,相對于離屏渲染性能更好。但是受當(dāng)前屏幕渲染的局限因素限制(只有自身上下文、屏幕緩存有限等),當(dāng)前屏幕渲染有些情況下的渲染解決不了的,就使用到離屏渲染

離屏渲染對性能的的代價是很高的,主要體現(xiàn)在:

  • 創(chuàng)建了新的緩沖區(qū)

  • 上下文的頻繁切換

導(dǎo)致產(chǎn)生離屏渲染的原因:

  • shouldRasterize(光柵化)

  • masks(遮罩)

  • shadows(陰影)

  • edge antialiasing(抗鋸齒)

  • group opacity(不透明)

  • 復(fù)雜形狀設(shè)置圓角等

  • 漸變

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 CALayer IOS SDK詳解之CALayer(一) http://doc.okbase.net/Hell...
    Kevin_Junbaozi閱讀 5,335評論 3 23
  • 作為一個iOS小菜鳥,當(dāng)我們需求做完之后,我們該干什么?當(dāng)然是學(xué)習(xí)!最近看了很多關(guān)于iOS性能優(yōu)化的文章,為了便于...
    小蝦啦閱讀 3,139評論 1 4
  • 繪制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一個像素是如何繪制到屏幕上去的?有很多...
    阿貍旅途T恤閱讀 1,765評論 0 7
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,629評論 1 32
  • 卷首語 歡迎來到 objc.io 的第三期! 這一期都是關(guān)于視圖層的。當(dāng)然視圖層有很多方面,我們需要把它們縮小到幾...
    評評分分閱讀 1,930評論 0 18

友情鏈接更多精彩內(nèi)容