開場白
本文簡單介紹了iOS中離屏渲染的相關內容呢。算法
1.什麼是離屏渲染:
要了解離屏渲染,咱們先簡單瞭解一下非離屏渲染的邏輯緩存
非離屏渲染
非離屏渲染也就是正常的渲染流程,簡要流程如圖: bash
APP將要渲染的信息提交給CPU,CPU經過必定的處理後提交給GPU。GPU不停的將內容渲染完成放到幀緩衝區中(FrameBuffer)。最後顯示到屏幕上。
離屏渲染
離屏渲染簡要流程如圖: 佈局
與普通流程不一樣的是,GPU把渲染好的的內容存放到離屏渲染緩衝區中,在離屏渲染緩衝區(OffscreenBuffer)中進一步作一些處理後,再提交到幀緩衝區(FrameBuffer)中。
2.離屏渲染對性能的影響?
- 和非離屏渲染模式相比多了進行額外的渲染合併,是對多個texture進行合併的過程。多了這麼一步,因此說對性能要求更高一些。更容易出現掉幀的狀況。
- 增長了額外的存儲空間offscreen buffer,空間大小是屏幕的2.5倍
3.模擬器打開離屏渲染顏色標註
模擬器->Debug->Color Off-screen Rendered 性能
開啓後在模擬器界面中能看到使用離屏渲染的View了 圖片中的例子是一個button,設置了一個背景色和背景圖,對layer層設置cornerRadius和masksToBounds。
masksToBounds=YES,若是不設置是看不到效果的。下面會具體說明緣由。ui
4.爲何要用離屏渲染?
使用離屏渲染大體有一下兩種狀況:spa
- 能夠顯示一些特殊效果,須要用到Offscreen buffer來保存中間狀態。
- 若是texture會屢次顯示到屏幕上,可使用offscreen buffer進行提早渲染,而且保存在其中,達到複用的效果。layer中有shouldRasterize屬性
狀況1:
通常都是系統去觸發,例如對layer層相關處理:包括圓角、陰影、mask等等。iOS系統扁平化後出現的高斯模糊也是利用離屏渲染方式。code
圓角處理
設置圓角爲何會觸發離屏渲染呢?這個要從layer的結構提及。 layer結構中包含3部分:orm
- backgroundColor
- contents
- boarder相關信息(borderWidth和borderColor)
設置圓角代碼,這個你們應該都知道:cdn
view.layer.cornerRadius = 2
複製代碼
咱們先看看官方文檔中cornerRadius相關說明:
Discussion中的說明: 設置cornerRadius超過0.0,不會影響contents,可是會影響background color和border。若是設置了masksToBounds會對content進行裁剪。 因此說出發離屏渲染的主要緣由:
masksToBounds=YES;
複製代碼
這就是上面模擬器開啓離屏渲染模式中說明的爲何要設置masksToBounds的緣由。masksToBounds須要對layer上的全部內容進行裁剪,過程當中須要對中間值進行保存。因此進行了離屏渲染操做。
注意: 若是說layer圖層比較簡單,也是不會觸發離屏渲染的。例如:UIImageView設置cornerRadius和masksToBounds是不會觸發離屏渲染的,若是再對UIImageView設置背景色,則會觸發。
self.view.backgroundColor = [UIColor grayColor];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1"]];
imageView.frame = CGRectMake(100, 100, 100, 100);
imageView.layer.cornerRadius = 30;
imageView.layer.masksToBounds = YES;
[self.view addSubview:imageView];
UIImageView *imageView2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1"]];
imageView2.backgroundColor = UIColor.redColor;
imageView2.frame = CGRectMake(100, 250, 100, 100);
imageView2.layer.cornerRadius = 30;
imageView2.layer.masksToBounds = YES;
[self.view addSubview:imageView2];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setBackgroundImage:[UIImage imageNamed:@"1"] forState:UIControlStateNormal];
btn.layer.cornerRadius = 30;
btn.layer.masksToBounds = YES;
btn.frame = CGRectMake(100, 400, 100, 100);
[self.view addSubview:btn];
複製代碼
代碼中有三個控件,前兩個是UIImageView,最後一個是UIButton。
- 第一個UIImageView設置了圖片沒有設置背景色,沒有觸發離屏渲染。
- 第二個UIImageView設置了圖片和背景色,觸發了離屏渲染。
- 最後一個UIButton,設置圖片沒有設置背景色,觸發了離屏渲染。緣由是咱們看到UIButton是由它的layer和UIImageView的layer混合起來的效果(UIButton有imageView),因此設置圓角的時候會觸發離屏渲染。
狀況2:
是一種主動行爲,是爲了提升複用的效率。一般是設置layer的shouldRasterize屬性來實現。
shouldRasterize 光柵化
shouldRasterize官方文檔
開啓後,會將layer做爲位圖保存下來,下次直接與其餘內容進行混合。這個保存的位置就是OffscreenBuffer中。這樣下次須要再次渲染的時候,就能夠直接拿來使用了。
shouldRasterize使用建議:
- layer不復用,不必使用shouldRasterize
- layer不是靜態的,也就是說要頻繁的進行修改,不必使用shouldRasterize
- 時間方面:離屏渲染緩存有100ms時間限制,超過該時間的內容會被丟棄,進而不能達到複用的目的
- 空間方面:離屏渲染空間是屏幕像素的2.5倍,若是超過也沒法複用。
5.離屏渲染邏輯
圖層的疊加繪製大體遵循「畫家算法」,也就是由遠到近的方式將圖層繪製到屏幕上,繪製近距離圖層會有覆蓋遠圖層的邏輯。
普通渲染方式,繪製完一層圖層後,直接捨棄掉。緊接着繪製稍近的圖層。以此類推:
而咱們進行離屏渲染方式(圓角、剪裁等操做)時,每層圖層繪製完不會立刻刪除,而是先保存在離屏緩存區中,等圖層繪製完成後,再依次進行特殊處理(圓角、剪裁等操做)。
注意:一般會觸發離屏渲染,除了圓角、剪裁外,還有設置了透明度+組透明(layer.allowsGroupOpacity+layer.opacity),陰影屬性(shadowOffset 等)都會產生相似的效果,由於組透明度、陰影都是和裁剪相似的,會做用與 layer 以及其全部 sublayer 上,這就致使必然會引發離屏渲染。
6.避免圓角離屏渲染
爲了不設置圓角時引發的離屏渲染操做,能夠用一下方案代替直接設置圓角的操做
- 直接更換支援,讓UI提供帶圓角的圖片。
- 使用layer.mask屬性,增長一個和背景色相同的遮罩覆蓋上層,蓋住四個角,營造出圓角的形狀。但這種方式難以解決背景色爲圖片或漸變色的狀況。
- 使用貝塞爾曲線繪製閉合圓角的矩形,在上下文中設置只有內部可見,再將不帶圓角的 layer 渲染成圖片,添加到貝塞爾矩形中。這種方法效率更高,可是 layer 的佈局一旦改變,貝塞爾曲線都須要手動地從新繪製,因此須要對 frame、color 等進行手動地監聽並重繪。
- CoreGraphics重寫 drawRect:,用 CoreGraphics 相關方法,在須要應用圓角時進行手動繪製。不過 CoreGraphics 效率也頗有限,若是須要屢次調用也會有效率問題。
7.觸發離屏渲染緣由的總結
- 設置layer.mashsToBounds/view.clipsToBounds
- 設置layer.mask
- 設置layer.shadow等相關屬性
- 設置layer.shouldRasterize光柵化
- 設置了組透明度爲 YES,而且透明度不爲 1 的 layer (layer.allowsGroupOpacity/layer.opacity)
- 繪製了文字的 layer (UILabel, CATextLayer, Core Text 等)
8.參考文章
iOS 渲染原理解析