四、深入剖析【離屏渲染】原理

OpenGL + OpenGL ES +Metal 系列文章匯總

離屏渲染與正常渲染

屏幕上最終顯示的數(shù)據(jù)有兩種加載流程

  • 正常渲染加載流程
  • 離屏渲染加載流程


    離屏渲染與正常渲染

從圖上看,他們之間的區(qū)別就是離屏渲染比正常渲染多了一個離屏緩沖區(qū),這個緩沖區(qū)的作用是什么呢?下面來仔細說說

首先,說說正常渲染流程

正常渲染流程

APP中的數(shù)據(jù)經(jīng)過CPU計算和GPU渲染后,將結果存放在幀緩沖區(qū),利用視頻控制器從幀緩沖區(qū)中取出,并顯示到屏幕上。

  • 在GPU的渲染流程中,顯示到屏幕上的圖像是遵循大畫家算法按照由遠及近的順序,依次將結果存儲到幀緩沖區(qū)
  • 視屏控制器從幀緩沖區(qū)中讀取一幀數(shù)據(jù),將其顯示到屏幕上后,會立即丟棄這幀數(shù)據(jù),不會做任何保留,這樣做的目的是可以節(jié)省空間,且在屏幕上是各自顯示各自的,互相不影響。


    正常渲染數(shù)據(jù)讀取

離屏渲染流程

當App需要進行額外的渲染和合并時,例如按鈕設置圓角,我們是需要對UIButton這個控件中的所有圖層都進行圓角+裁剪,然后再將合并后的結果存入幀緩存區(qū),再從幀緩存中取出交由屏幕顯示,這時,在正常的渲染流程中,我們是無法做到對所有圖層進行圓角裁剪的,因為它是用一個丟一個。所以我們需要提前將處理好的結果放入離屏緩沖區(qū),最后將幾個圖層進行疊加合并,存放到站緩沖區(qū),最后屏幕上就是我們想實現(xiàn)的效果。


離屏渲染數(shù)據(jù)讀取

說白了,離屏緩存區(qū)就是一個臨時的緩沖區(qū),用來存放在后續(xù)操作使用,但目前并不使用的數(shù)據(jù)。

  • 離屏渲染再給我們帶來方便的同時,也帶來了嚴重的性能問題。由于離屏渲染中的離屏緩沖區(qū),是額外開辟的一個存儲空間,當它將數(shù)據(jù)轉存到Frame Buffer時,也是需要耗費時間的,所以在轉存的過程中,仍有掉幀的可能。
  • 離屏緩沖區(qū)的空間并不是無限大的, 它是又上限的,最大只能是屏幕的2.5倍

那為什么我們明知有性能問題時,還是要使用離屏渲染呢?

  • 可以處理一些特殊的效果,這種效果并不能一次就完成,需要使用離屏緩沖區(qū)來保存中間狀態(tài),不得不使用離屏渲染,這種情況下的離屏渲染是系統(tǒng)自動觸發(fā)的,例如經(jīng)常使用的圓角、陰影、高斯模糊、光柵化等
  • 可以提升渲染的效率,如果一個效果是多次實現(xiàn)的,可以提前渲染,保存到離屏緩沖區(qū),以達到復用的目的。這種情況是需要開發(fā)者手動觸發(fā)的。

離屏渲染的另一個原因:光柵化

When the value of this property is YES, the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content.

當我們開啟光柵化時,會將layer渲染成位圖保存在緩存中,這樣在下次使用時,就可以直接復用,提高效率。
針對光柵化的使用,有以下幾個建議:

  • 如果layer不能被復用,則沒有必要開啟光柵化
  • 如果layer不是靜態(tài),需要被頻繁修改(例如動畫過程中),此時開啟光柵化反而影響效率
  • 離屏渲染緩存內(nèi)容有時間限制,如果100ms內(nèi)沒有被使用,那么就會丟棄,無法進行復用
  • 離屏渲染的緩存空間有限,是屏幕的2.5倍,超過2.5倍屏幕像素大小的話也會失效,無法實現(xiàn)復用

圓角中離屏渲染的觸發(fā)時機

在講圓角之前,首先說明下CALayer的構成,如圖所示,它是由backgroundColor、contents、borderWidth&borderColor構成的。跟我們即將解釋的圓角觸發(fā)離屏渲染息息相關。

CALayer結構

圓角設置不生效問題!

在平常寫代碼時,比如UIButton設置圓角,當設置好按鈕的image、cornerRadius、borderWidth、borderColor等屬性后,運行發(fā)現(xiàn)并沒有實現(xiàn)我們想要的效果

        let btn0 = UIButton(type: .custom)
        btn0.frame = CGRect(x: 100, y: 60, width: 100, height: 100)
        //設置圓角
        btn0.layer.cornerRadius = 50
        //設置border寬度和顏色
        btn0.layer.borderWidth = 2
        btn0.layer.borderColor = UIColor.red.cgColor
        self.view.addSubview(btn0)
        //設置背景圖片
        btn0.setImage(UIImage(named: "mouse"), for: .normal)

此時的效果就是這樣的,可以發(fā)現(xiàn),我們設置的按鈕圖片還是方方正正的


圓角不生效效果圖

針對上面的這個問題,我相信99%的人都能信手拈來,知道必須要設置masksToBounds為 true,才會得到我們想要的效果。解決的方法很簡單,但原理是大部人都沒有去仔細研究的。

下面是蘋果官方文檔針對圓角設置的一些說明:


蘋果官方對圓角的說明

官方文檔告訴我們,設置cornerRadius只會對CALayer中的backgroundColor 和 boder設置圓角,不會設置contents的圓角,如果contents需要設置圓角,需要同時將maskToBounds / clipsToBounds設置為true。

所以我們可以理解為圓角不生效的根本原因是沒有對contents設置圓角,而按鈕設置的image是放在contents里面的,所以看到的界面上的就是image沒有進行圓角裁剪。

下面我們通過幾段代碼來說明 圓角設置中什么時候會離屏渲染觸發(fā)
首先,需要打開模擬器的離屏渲染顏色標記


離屏渲染標記開啟方式

1、按鈕 僅設置背景顏色+border

        let btn01 = UIButton(type: .custom)
        btn01.frame = CGRect(x: 100, y: 200, width: 100, height: 100)
        //設置圓角
        btn01.layer.cornerRadius = 50
        //設置border寬度和顏色
        btn01.layer.borderWidth = 4
        btn01.layer.borderColor = UIColor.red.cgColor
        self.view.addSubview(btn01)
        //設置背景顏色
        btn01.backgroundColor = UIColor.green

在這種情況下,無論是使用默認的maskToBounds / clipsToBounds(false),還是將其修改為true,都不會觸發(fā)離屏渲染,究其根本原因是 contents中沒有需要圓角處理的layer

結果

情況2:按鈕設置背景圖片+boder

        let btn0 = UIButton(type: .custom)
        btn0.frame = CGRect(x: 100, y: 60, width: 100, height: 100)
        //設置圓角
        btn0.layer.cornerRadius = 50
        //設置border寬度和顏色
        btn0.layer.borderWidth = 2
        btn0.layer.borderColor = UIColor.red.cgColor
        self.view.addSubview(btn0)
        //設置背景圖片
        btn0.setImage(UIImage(named: "mouse"), for: .normal)
  • 使用默認的maskToBounds / clipsToBounds(false)
    這種情況就是最開始我們講到的圓角設置不生效的情況,就不再多做說明了

  • maskToBounds / clipsToBounds 修改為true

    離屏渲染觸發(fā)效果

從屏幕的顯示上可以看出,此時觸發(fā)了離屏渲染,是因為圓角的設置是需要對所有l(wèi)ayer都進行裁剪的,而maskToBounds裁剪是應用到所有l(wèi)ayer上的。如果從正常渲染的角度來說,一個個layer是用完即扔的。而現(xiàn)在我們的圓角設置需要3個layer疊加合并的,所以將先處理好的layer保存在離屏緩沖區(qū),等到最后一個layer處理完,合并進行圓角+裁剪,所以才會觸發(fā)離屏渲染

總結

  • 當只設置backgroundColor、border,而contents中沒有子視圖時,無論maskToBounds / clipsToBoundstrue還是false,都不會觸發(fā)離屏渲染
  • 當contents中有子視圖時,此時設置 cornerRadius+maskToBounds / clipsToBounds,就會觸發(fā)離屏渲染,但是這種情況在UIImageView中并不適用,當UIImageView中只設置圖片+maskToBounds / clipsToBounds是不會觸發(fā)離屏渲染,蘋果對UIImageView優(yōu)化我想也只是將image直接畫在了contents上面這樣不設置背景色其實只需要渲染一個layer,所以不需要用到離屏緩沖區(qū),,(在這里感謝下das112
    童鞋對這段描述更清晰的解釋,這里確實寫的有點虎頭蛇尾的),所以不會產(chǎn)生離屏渲染,如果此時再加上背景色,就會觸發(fā)離屏渲染。

所以,綜合來說,離屏渲染是否觸發(fā),在于我們是否需要使用離屏緩沖區(qū)。

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