離屏渲染初探

1.離屏渲染的定義

正常情況下,在GPU渲染的過程中,會遵循‘畫家算法’按次序由遠(yuǎn)及近的一層一層將結(jié)果放置到幀緩存區(qū)中,當(dāng)需要時(shí)屏幕就會讀取顯示,然后將其從幀緩沖區(qū)中丟棄,周而復(fù)始。而某些時(shí)候,當(dāng)我們要實(shí)現(xiàn)實(shí)現(xiàn)一些特殊效果,并且這些特殊效果不是一個(gè)圖層就可以畫出來,需要幾次結(jié)果才能到,此時(shí)就需要使用一個(gè)東西來保存中間狀態(tài),這個(gè)東西就是離屏渲染緩沖區(qū),這種機(jī)制也就叫做離屏渲染。

2.離屏渲染的工作流程

不使用離屏渲染,圖像/圖形的渲染流程為:app-> FrameBuffer(幀緩沖區(qū)) -> 屏幕。


渲染流程.png

當(dāng)觸發(fā)了離屏渲染之后,圖像/圖形的渲染流程變成了:app-> offscreen Buffer(離屏緩沖區(qū)) 組合 -> FrameBuffer(幀緩沖區(qū)) -> 屏幕


1732746a82832e5d.png

舉個(gè)例子,我們現(xiàn)在繪制一個(gè)圖形,他有三個(gè)subLayer,如果正常渲染,會按順序直接從幀緩沖區(qū)中區(qū)中取出,顯示到屏幕上。若此時(shí)當(dāng)我們需要給他添加圓角,開啟離屏渲染,首先就會在offscreen Buffer中先分別緩存這三個(gè)subLayer,在其中分別繪制圓角,再取出合并,傳遞到幀緩沖區(qū),最后顯示到屏幕上。

3.離屏渲染的優(yōu)缺點(diǎn)

通過離屏渲染,可以幫助我們比較輕松的是實(shí)現(xiàn)一些特殊效果,陰影,毛玻璃等等。同時(shí)需要多次使用的效果,提前渲染存入離屏緩沖區(qū),然后復(fù)用來提高效率。
可是離屏渲染也有不少的問題:
1.離屏渲染,需要開啟額外的存儲空間,存儲空間需要消耗內(nèi)存,所以大量的離屏渲染會導(dǎo)致內(nèi)存暴漲,從而產(chǎn)生掉幀等性能問題。
2.offscreen Buffer的內(nèi)存空間也有限制,只有當(dāng)前屏幕像素的2.5倍。
3.offscreen Buffer中緩存時(shí)間也有限制,100ms,超過時(shí)間就會失效。

4.離屏渲染觸發(fā)的原理探索

首先我們看一個(gè)問題,設(shè)置圓角一定會觸發(fā)離譜渲染嗎?可能很多人都會認(rèn)為設(shè)置圓角就會觸發(fā)離屏渲染,但其實(shí)這個(gè)說法是不準(zhǔn)確的,因?yàn)閳A角觸發(fā)離屏渲染也是有條件的。我們看下面的例子。

  //1.按鈕存在背景圖片
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn1.frame = CGRectMake(100, 30, 100, 100);
    btn1.layer.cornerRadius = 50;
    [self.view addSubview:btn1];

    [btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
    btn1.clipsToBounds = YES;

    //2.按鈕不存在背景圖片
    UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn2.frame = CGRectMake(100, 180, 100, 100);
    btn2.layer.cornerRadius = 50;
    btn2.backgroundColor = [UIColor blueColor];
    [self.view addSubview:btn2];
    btn2.clipsToBounds = YES;
    
    //3.UIImageView 設(shè)置了圖片+背景色;
    UIImageView *img1 = [[UIImageView alloc]init];
    img1.frame = CGRectMake(100, 320, 100, 100);
    img1.backgroundColor = [UIColor blueColor];
    [self.view addSubview:img1];
    img1.layer.cornerRadius = 50;
    img1.layer.masksToBounds = YES;
    img1.image = [UIImage imageNamed:@"btn.png"];

    //4.UIImageView 只設(shè)置了圖片,無背景色;
    UIImageView *img2 = [[UIImageView alloc]init];
    img2.frame = CGRectMake(100, 480, 100, 100);
    [self.view addSubview:img2];
    img2.layer.cornerRadius = 50;
    img2.layer.masksToBounds = YES;
    img2.image = [UIImage imageNamed:@"btn.png"];

結(jié)果是,第1個(gè)和第三個(gè)觸發(fā)了離屏渲染,2,4沒有觸發(fā),但我們看到所有的控件都設(shè)置了圓角,但卻不是全都出發(fā)了離屏渲染,為什么呢?
我們看下CALayer的圖層結(jié)構(gòu)和cornerRadius的文檔:


CALayer.png

cornerRadius.png

可以看到,在cornerRadius的文檔中明確說明對cornerRadius的設(shè)置只對 CALayer 的backgroundColor和borderWidth&borderColor起作用,如果contents有內(nèi)容或者內(nèi)容的背景不是透明的話,只有設(shè)置masksToBounds為 true 才能對content生效,此時(shí)也就會產(chǎn)生離屏渲染。這也就說明了上面代碼為什么1和3觸發(fā)了離屏渲染,而2和4沒有觸發(fā)離屏渲染。綜上所述,我們也可以得到一個(gè)結(jié)論:離屏渲染只存在于有多個(gè)圖層需要合成的情況,如果沒有圖層合成,渲染結(jié)果就直接提交到幀緩沖區(qū)。

5.離屏渲染觸發(fā)的幾種情況

  1. 使?了 mask 的 layer (layer.mask)
  2. 需要進(jìn)?裁剪的 layer (layer.masksToBounds / view.clipsToBounds)
  3. 設(shè)置了組透明度為 YES,并且透明度不為 1 的 layer (layer.allowsGroupOpacity/
    layer.opacity)
  4. 添加了投影的 layer (layer.shadow*)
  5. 采?了光柵化的 layer (layer.shouldRasterize)
  6. 繪制了?字的 layer (UILabel, CATextLayer, Core Text 等)

6.總結(jié)

離屏渲染是系統(tǒng)觸發(fā),觸發(fā)了之后才有離屏緩沖區(qū),他在某些程度上可以有助于提高效率,但是大量的使用也會造成不少的性能問題。同時(shí)離屏渲染只有在當(dāng)要進(jìn)行的渲染操作需要對一個(gè)組合圖層進(jìn)行操作的時(shí)候,也就是說當(dāng)你無法僅僅使用FrameBuffer來畫出最終結(jié)果,那就只能另開一塊內(nèi)存空間offscreen Buffer來儲存這些中間結(jié)果,以達(dá)到我們需要的渲染效果。

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

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