離屏渲染產(chǎn)生的原因
在討論離屏渲染之前我們先要搞清楚正常的渲染流程是怎樣的
非離屏渲染流程:
我們可以看到在非離屏渲染的場(chǎng)景下医窿,需要渲染的數(shù)據(jù)是直接提交到GPU的幀緩沖區(qū)游岳,等待屏幕的垂直同步信號(hào)來(lái)完成頁(yè)面的顯示返干,但是在離屏渲染的場(chǎng)景下又是怎樣的呢?請(qǐng)看下圖
我們可以看到在離屏渲染的場(chǎng)景下浦妄,要完成頁(yè)面的展示曾雕,中間多了一個(gè)過(guò)程,需要渲染的數(shù)據(jù)首先提交到了離屏緩沖區(qū)体谒,然后再提交到幀緩沖區(qū)的杯聚,為什么要增加一個(gè)OffsceenBuffer呢?這個(gè)OffsceenBuffer又有什么作用呢抒痒?
為什么要增加OffsceenBuffer幌绍?
如果觸發(fā)了離屏渲染,那么在顯示這個(gè)頁(yè)面的每一幀都會(huì)去做離屏渲染的處理故响,比如切圓角傀广,顯示陰影等,如果每一幀都要重新去處理彩届,那么對(duì)于CPU以及GPU都帶來(lái)很大的負(fù)擔(dān)伪冰。所以引入了離屏緩沖區(qū),將處理好的圖層直接丟到離屏緩沖區(qū)樟蠕,下次渲染的時(shí)候直接拿出來(lái)顯示到屏幕上
離屏緩存區(qū)也是有限制的
- 時(shí)間限制:緩存的內(nèi)容超過(guò)100ms沒(méi)有被使用贮聂,內(nèi)容將會(huì)被丟棄
- 空間限制:超過(guò)屏幕像素大小的2.5倍,將不能再存儲(chǔ)新的數(shù)據(jù)寨辩,離屏渲染會(huì)失效
這樣做雖然解決了多次處理數(shù)據(jù)的問(wèn)題吓懈,但是還是會(huì)帶來(lái)另外的性能問(wèn)題,由于離屏緩沖區(qū)和幀緩沖區(qū)屬于兩個(gè)不同的緩沖區(qū)靡狞,如果一個(gè)頁(yè)面觸發(fā)了多個(gè)離屏渲染耻警,那么GPU在顯示每一幀的時(shí)候都要在離屏緩沖區(qū)和幀緩沖區(qū)之間切換,在快速滾動(dòng)的場(chǎng)景下就會(huì)出現(xiàn)嚴(yán)重的掉幀甸怕。那么怎么解決這個(gè)問(wèn)題呢甘穿?其實(shí)答案就是要減少離屏渲染,那些場(chǎng)景會(huì)觸發(fā)離屏渲染呢梢杭?
觸發(fā)離屏渲染的場(chǎng)景
離屏渲染的檢測(cè)
在模擬器運(yùn)行時(shí)我們?cè)谶@里開(kāi)啟離屏渲染的檢測(cè):
開(kāi)啟后温兼,如果有離屏渲染,則會(huì)顯示成黃色武契,如下圖:
1. cornerRadius + masksToBounds
這兩個(gè)屬性我們都不陌生妨托,這是我們經(jīng)常使用的切圓角的方式,將cornerRadius > 0同時(shí)masksToBounds = YES時(shí)吝羞,就可能會(huì)觸發(fā)離屏渲染兰伤,注意,是可能钧排,我們來(lái)看下圖中的例子
先不要著急敦腔,我們?cè)賮?lái)看另外一個(gè)例子:
為什么同樣都是設(shè)置cornerRadius + masksToBounds,例1和例2的結(jié)果卻不相同呢恨溜?相信細(xì)心的同學(xué)已經(jīng)發(fā)現(xiàn)了符衔,例2多了一個(gè)綠色的view找前,例2中的圖層層級(jí)是這樣的:
所以我們能夠得出:
如果只有一個(gè)圖層,就算同時(shí)設(shè)置 cornerRadius + masksToBounds 也并不會(huì)觸發(fā)離屏渲染判族,只有多個(gè)圖層發(fā)生疊加的時(shí)候才會(huì)觸發(fā)離屏渲染
為什么多個(gè)圖層疊加會(huì)觸發(fā)離屏渲染呢 躺盛?
畫家算法: 當(dāng)GPU在渲染圖層的時(shí)候會(huì)根據(jù)圖層的遠(yuǎn)近去由遠(yuǎn)及近完成渲染,這里說(shuō)的遠(yuǎn)近就是疊加圖層時(shí)由內(nèi)到外形帮,當(dāng)一幀數(shù)據(jù)處理好后將被提交到幀緩沖區(qū)槽惫,但是如果當(dāng)前圖層是多個(gè)圖層疊加后的結(jié)果,那么將不能被直接提交到幀緩沖區(qū)(如果提交了辩撑,下一個(gè)垂直同步信號(hào)到來(lái)的時(shí)候會(huì)被直接顯示在界面上)界斜,需要存儲(chǔ)起來(lái),等待這個(gè)圖層的所有子圖層都被處理完通過(guò)疊加后合冀,再提交到幀緩沖區(qū)用于顯示
如果我們只設(shè)置cornerRadius各薇,會(huì)不會(huì)觸發(fā)離屏渲染呢?
cornerRadius
這個(gè)就要從CALayer的結(jié)構(gòu)說(shuō)起了
CALayer大致分為三層
- backgroundColor : 背景顏色層
- content :內(nèi)容顯示層君躺,這一層主要用于顯示我們?cè)O(shè)置的內(nèi)容
- border :當(dāng)我們?cè)O(shè)置邊框的時(shí)候峭判,就是這一層在工作
cornerRadius只會(huì)設(shè)置backgroundColor和border的圓角,并不會(huì)對(duì)content設(shè)置圓角棕叫。除非同時(shí)設(shè)置了masksToBounds為YES(對(duì)應(yīng)view中的clipsToBounds),所以如果只設(shè)置cornerRadius朝抖,在圖層疊加時(shí)并不會(huì)觸發(fā)離屏渲染
2.mask
Mask 效果與混合圖層的效果非常相似,只是使用同一個(gè)遮罩圖像時(shí)谍珊,mask 與混合圖層的效果是相反的。Mask無(wú)法取消離屏渲染
3. GroupOpacity
GroupOpacity 是指 CALayer 的allowsGroupOpacity屬性急侥,UIView 的alpha屬性等同于 CALayer opacity屬性砌滞。開(kāi)啟 GroupOpacity 后,子 layer 在視覺(jué)上的透明度的上限是其父 layer 的opacity坏怪。
從 iOS 7 以后默認(rèn)全局開(kāi)啟了這個(gè)功能贝润,這樣做是為了讓子視圖與其容器視圖保持同樣的透明度。
GroupOpacity 開(kāi)啟離屏渲染的條件是:layer.opacity != 1.0并且有子 layer 或者背景圖铝宵。
4. shadow
陰影直接合成在視圖的下面打掘,視圖結(jié)構(gòu)里并沒(méi)有多出一個(gè)視圖。在沒(méi)有指定陰影路徑時(shí)鹏秋,陰影是沿著視圖的非透明部分?jǐn)U展的尊蚁,而且 CALayer 的三個(gè)視覺(jué)元素至少有一個(gè)存在時(shí)才會(huì)有陰影。
使用陰影必須保證 layer 的masksToBounds = false侣夷,因此陰影與系統(tǒng)圓角不兼容横朋。但是注意,只是在視覺(jué)上看不到百拓,對(duì)性能的影響依然琴锭。
如果我們?cè)谠O(shè)置陰影時(shí)晰甚,給陰影指定了路徑,就不會(huì)觸發(fā)離屏渲染了
例如决帖,這樣設(shè)置陰影:
self.view1.layer.shadowColor = [UIColor greenColor].CGColor;
self.view1.layer.shadowOffset = CGSizeMake(5, 5);
self.view1.layer.shadowOpacity = 0.5;
self.view1.layer.shadowRadius = 3;
UIBezierPath * path = [UIBezierPath bezierPathWithRect:self.view1.bounds];
self.view1.layer.shadowPath = path.CGPath;