1、離屏渲染
1.1什么是離屏渲染
在正常情況下,經(jīng)過CPU的計算以及GPU的渲染之后,會將結(jié)果存放到幀緩存區(qū),隨后視頻控制器會讀取幀緩存區(qū)的數(shù)據(jù),經(jīng)過數(shù)模轉(zhuǎn)換,再逐行顯示到屏幕上。在GPU渲染的過程中,一般情況下,會遵循‘畫家算法’按次序由遠(yuǎn)及近的一層一層將結(jié)果放置到幀緩存區(qū)中,當(dāng)當(dāng)前幀緩存區(qū)的數(shù)據(jù)顯示到屏幕上之后,就會將該幀丟棄,周而復(fù)始。(如下圖)
畫家算法--由遠(yuǎn)及近
但是某些特殊情況下(例:一個多圖層的view設(shè)置圓角),當(dāng)我們一層一層的渲染完圖層后,要應(yīng)用一些操作(例:裁剪),但是之前放置在幀緩存區(qū)的已渲染完的數(shù)據(jù)早已經(jīng)被丟棄,這時是不可能對已經(jīng)丟棄的圖層進行操作了。
因此,我們需要開辟些離屏緩存區(qū)來存放一些中間狀態(tài)的數(shù)據(jù),等待全部的圖層都渲染到離屏緩存區(qū)之后,再分別從各離屏緩存區(qū)取出數(shù)據(jù),分別做相應(yīng)的操作(裁剪)后,組合存入幀緩存區(qū),再等待屏幕控制器的讀取和屏幕刷新。就是離屏渲染。
1.2離屏渲染觸發(fā)原理
app進行額外的渲染和合并-->需額外開辟offsecrren buffer空間 --> frame buffer --> 屏幕(如下圖)
離屏渲染流程.png
例1、2離屏渲染圖/正常渲染圖
正常渲染
當(dāng)sublayer繪制到屏幕上之后,就會將sublayer從幀緩存區(qū)移除,從而節(jié)省空間
離屏渲染圖
1.3離屏渲染的優(yōu)劣
-
優(yōu)勢
多次出現(xiàn)在屏幕上的數(shù)據(jù),可提前渲染,達到復(fù)用的目的,CPU/GPU避免一些重復(fù)的計算。
在很多iOS開發(fā)的需求背景之下,一些特殊動畫效果的開發(fā),需要多圖層以及離屏緩存區(qū)保存中間狀態(tài),這種情況下就不得不使用離屏渲染。 -
劣勢
性能問題:離屏渲染需開辟額外空間其實是加大了系統(tǒng)的負(fù)擔(dān),會造成性能上的損耗。
離屏渲染需要額外的存儲空間,存儲空間大小的上限是2.5倍的屏幕像素大小,超過則無法使用離屏渲染。
容易掉幀:因為離屏渲染導(dǎo)致最終存入幀緩存區(qū)的時候,已經(jīng)超過了16.67ms,則會出現(xiàn)掉幀的情況
1.4 常見的幾種觸發(fā)離屏渲染的情況
- 使用了 mask 的 layer (layer.mask)
- 需要進行裁剪的 layer (layer.masksToBounds/view.clipsToBounds)
- 設(shè)置了組透明度為 YES,并且透明度不為 1 的layer(layer.allowsGroupOpacity/ layer.opacity)
- 添加了投影的 layer (layer.shadow*)
- 繪制了文字的 layer (UILabel, CATextLayer, Core Text 等)
- 高斯模糊
- 采用了光柵化的 layer (layer.shouldRasterize)
光柵化--離屏渲染
光柵化是將一個layer預(yù)先渲染成位圖(bitmap),然后加入緩存中。對于陰影效果比消耗資源對靜態(tài)內(nèi)容進行緩存,可提升一定幅度的性能。
使用時注意事項:
- 離屏渲染的空間有限,超過屏幕像素點2.5倍,離屏渲染也會失效,無法復(fù)用
- 離屏渲染的緩存有時間限制,100ms內(nèi)如果緩存的內(nèi)容沒有被復(fù)用,則會被丟棄,而無法復(fù)用
- layer不能被重用,沒必要使用光柵化;
- layer非靜態(tài)的,需要被頻繁修改(例:如處于動畫之中),開啟離屏渲染反而影響效率
1.4 iOS常見的圓角導(dǎo)致的離屏渲染的處理方法
方案1
_imageView.clipsToBounds=YES;
_imageView.layer.cornerRadius=4.0;
方案2

方案3

方案4

2、圓角離屏渲染實例
圓角cornerRadius解釋
Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners
設(shè)置cornerRadius>0,只為layer的backgroundColor和border設(shè)置圓角;而不會對layer的contents設(shè)置圓角,除非同時設(shè)置了layer.masksToBounds為true(對應(yīng)UIView的clipsToBounds屬性)。
驗證官方解釋
2.1 只設(shè)置cornerRadius>0,不設(shè)置ClipsBounds和layer.masksToBounds 結(jié)果:如圖1
UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
view1.backgroundColor = [UIColor cyanColor];
view1.layer.cornerRadius = 20;
view1.layer.borderColor = [UIColor blackColor].CGColor;
view1.layer.borderWidth = 1.0;
[self.view addSubview:view1];

結(jié)果圖顯示backgroundcolor和border都顯示圓角,并且無觸發(fā)離屏渲染。
設(shè)置clipsToBounds或layer.masksToBounds 為true。同樣無觸發(fā)離屏渲染。 結(jié)果圖:圖1。
2.2 為view1添加子view2結(jié)果(如圖2)或者view1的contents添加內(nèi)容結(jié)果(如圖3)。只設(shè)置cornerRadius。根據(jù)結(jié)果圖顯示子view2和contents內(nèi)容都未顯示成圓角。未觸發(fā)離屏渲染。
//**********view1的contents添加內(nèi)容
UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
view1.backgroundColor = [UIColor cyanColor];
view1.layer.cornerRadius = 20;
view1.layer.borderColor = [UIColor blackColor].CGColor;
view1.layer.borderWidth = 1.0;
view1.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"123"].CGImage);
//view1.clipsToBounds = YES;
[self.view addSubview:view1];
//**********view1添加子view2
UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
view1.backgroundColor = [UIColor cyanColor];
view1.layer.cornerRadius = 20;
view1.layer.borderColor = [UIColor blackColor].CGColor;
view1.layer.borderWidth = 1.0;
[self.view addSubview:view1];
UIView *view2 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
view2.backgroundColor = [UIColor purpleColor];
[view1 addSubview:view2];

設(shè)置clipsToBounds或layer.masksToBounds為true。添加子view結(jié)果(結(jié)果圖4),contents結(jié)果(圖5)。結(jié)果圖顯示設(shè)置裁剪后無論添加的子view2或者contents內(nèi)容都顯示成圓角了。并且觸發(fā)離屏渲染。

從上面例子可以看出設(shè)置圓角、裁剪時觸發(fā)離屏渲染的例子。單獨設(shè)置背景或一個圖層的圓角時。子view或著contents是沒有圓角的,也沒有觸發(fā)離屏渲染。所以獲取每一圖層單獨渲染即可。
同時設(shè)置裁剪時,只有單一圖層或著背景時,同樣也沒有觸發(fā)離屏渲染。獲取相應(yīng)的圖層直接渲染即可。當(dāng)存在其他子view或著contents時。需要其中的子view或contents都顯示成圓角(子view/contents 因為背景view的裁剪屬性需組合)。觸發(fā)了離屏渲染。
因此圓角觸發(fā)離屏渲染只需多個圖層都需要顯示成圓角并且組合之后顯示才行。
第二個例子中在view1中添加子view2。但是不設(shè)置裁剪。分別設(shè)置view1和view2的cornerRadius值相同。也可達到我們想要的圓角效果。但是結(jié)果卻是未觸發(fā)離屏渲染。這是因為view1和view2的兩個圖層不需要進行組合。
2.3圓角離屏渲染觸發(fā)
圖層的疊加繪制大概遵循“畫家算法/油畫算法”(由遠(yuǎn)及近,即先繪制場景中距離觀察者較遠(yuǎn)的物體,再繪制較近的物體)。
例:圖7, 先繪制紅色圖形,在繪制黃色圖形,在繪制灰色圖形。

當(dāng)我們設(shè)置了cornerRadius以及masksToBounds進行圓角+裁剪時,masksToBounds裁剪屬性會應(yīng)用到所有的圖層上。

需各圖層在offsecreen(離屏緩存區(qū))保存,等待圓角、裁剪處理完成后在片元著色器組合 ->進入幀緩存區(qū)->渲染顯示(1.2中離屏渲染流程圖)。



