我們瞭解了iOS渲染架構,以及 GPU 的渲染流程。GPU 的渲染方式有兩種。
On-Screen Rendering:當前屏幕渲染,CPU、GPU 不停地將內容渲染完成放入frame buffer
幀緩衝區中,顯示屏幕從 frame buffer
中獲取內容顯示。
Off-Screen Rendering:離屏渲染,先創建離屏渲染幀緩衝區offscreen frame buffer
,然後逐一將內容渲染放入其中,完成後對離屏渲染緩衝區做陰影疊加、裁剪等操作,最後將結果拷貝或切換到幀緩衝區frame buffer
中,顯示屏幕從 frame buffer
中獲取內容顯示。
作爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流羣:413038000,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!
爲什麼需要
那麼爲什麼需要離屏渲染幀緩衝區offscreen frame buffer
呢?我們先來了解下“畫家算法”。
畫家算法,也叫作優先填充,它是三維計算機圖形學中處理可見性問題的一種解決方法(三維場景投影到二維平面)。如下圖,畫家算法首先將場景中的多邊形根據深度進行排序,然後按照由遠到近的順序進行描繪,這種方法通常會將不可見的部分覆蓋,這樣就可以解決可見性問題。
畫家算法“,每一層由遠到近依次輸出到畫布
對於有前後依賴的圖層(如陰影疊加、裁剪等),通過由遠到近的圖層疊加算法是無法實現的,我們需要先申請一個臨時緩衝區,所有圖層按照畫家算法,由遠到近在臨時緩衝區渲染,渲染完成後,再對這個臨時緩衝區做最後的全局操作(如陰影疊加、裁剪等),最後再把臨時緩衝區拷貝或切換到當前的緩衝區上,交給顯示器顯示。
總結一下,使用離屏渲染大概是因爲以下原因:
需要實現特殊的效果,比如說全局疊加、裁剪等等,需要用額外的幀緩衝區
offscreen frame buffer
保存中間狀態。出於效率目的,針對不會經常變更的圖層,可以緩存到
offscreen frame buffer
,供下次刷新使用。
什麼時候出現
Masking
最常見的情形就是使用了Masking
蒙版,我們看下官方提供的Masking
渲染流程:
如上圖,渲染Masking
蒙版分爲3步:
- 渲染
layer
的mask
紋理,流程同Tile Based Rendering
。 - 渲染
layer
的content
紋理,流程同Tile Based Rendering
。 - 合成操作,合併
mask
、content
紋理。
由於需要疊加兩層渲染結果,所以在疊加前,需要額外的緩衝區保存渲染結果,也就是觸發了離屏渲染。
UIVisualEffectView
iOS 8 提供的blur
模糊特效也會引起離屏渲染,我們看下官方提供的blur
渲染流程:
如上圖,渲染模糊過程分爲4步:
- 渲染
layer
的content
。 - 截獲
layer
的content
,進行縮放。 - 對縮放內容進行橫向模糊。
- 對縮放內容進行縱向模糊。
- 合成操作,合併所有模糊結果。
blur
模糊效果也會觸發離屏渲染,而且需要更多的緩衝區存儲渲染結果,更浪費性能。
Rasterization
光柵化,開啓光柵化後,會觸發離屏渲染,GPU 會強制把圖層的渲染結果保存下來,方便下次複用。我們看下官方的描述:
光珊化的本意是爲了避免靜態內容重繪、複雜view層級重繪帶來的影響。使用需要注意以下幾點:
- CALayer 的
shouldRasterize
可開啓光珊化。 - 如果 layer 不是靜態,或者頻繁修改(動畫中),開啓光珊化反而影響效率。
- 不要過度使用,光珊化離屏渲染緩存大小限制爲2.5倍屏幕大小。
- 光珊化離屏渲染緩存100ms沒有被使用,就會被丟棄。
Group Opactiy
組不透明度,某些情況也會觸發離屏渲染,可以通過CALayer
的allowsGroupOpacity
控制。我們看官方的描述:
總結一下,有以下兩點:
建議關閉
CALayer
的allowsGroupOpacity
屬性。如果開啓了
allowsGroupOpacity
,當 layer 的opacity
小於1.0,且有子 layer 或者背景圖,則會觸發離屏渲染。
而在iOS 7.0 SDK以上,allowsGroupOpacity
默認 true,所以不需要的時候,建議關閉。
Shadow
投影,和Masking
一樣,涉及到多個渲染結果合併,也會啓用離屏渲染。來看下官方的解釋:
如果單純設置shadowOffset
,會觸發離屏渲染,但是我們可以設置shadowPath
,告訴Core Animation
投影路徑,那麼系統就知道如何繪製投影了,就不會觸發離屏渲染了。
圓角
通常我們通過layer.cornerRadius
設置圓角,只會默認設置layer backgroundColor
和 border
的圓角,同時結合layer.masksToBounds
對 content
設置圓角。
單純的cornerRadius+masksToBounds
不一定會產生離屏渲染,如果這個圓角裁剪操作需要作用多個圖層,也就是layer
上有其他圖層,那麼肯定會發生離屏渲染。比如以下兩種情況:
- layer 包含 sub layer,或者有 content,會觸發離屏渲染。
- 針對
UIImageView
,同時設置了backgroundColor
和image
,會觸發離屏渲染。
測試圓角離屏渲染
比如以下代碼,運行後,打開Xcode
-> Debug
-> View Debuging
-> Rendering
-> Color Offscreen Rendered Yellow
開關,查看離屏渲染情況:
let view = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.layer.cornerRadius = 2
view.layer.masksToBounds = true
self.view.addSubview(view)
// 1.設置 layer.contents ,觸發離屏渲染
view.layer.contents = UIImage(named: "bc_delete")?.cgImage
// 2.addSubview ,觸發離屏渲染
let textLab = UILabel(frame: CGRect(x: 20, y: 20, width: 40, height: 40))
textLab.text = "test"
view.addSubview(textLab)
// 3\. UIImageView同時設置image、backgroundColor,觸發離屏渲染
let view = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.image = UIImage(named: "bc_delete")
view.backgroundColor = UIColor.gray
view.layer.cornerRadius = 20
view.layer.masksToBounds = true
self.view.addSubview(view)
複製代碼
避免圓角離屏渲染
那麼如何避免圓角裁剪產生的離屏渲染呢?我們先來看官方描述:
總結一下:
不要使用不必要的 mask,可以預處理圖片爲圓形,通過
Core Graphics
手動繪製圓角。使用中間爲圓形透明的白色背景視圖覆蓋。但這種方式不能解決背景色爲圖片或漸變色的情況。
用
UIBezierPath
貝塞爾曲線繪製閉合帶圓角的矩形,再將不帶圓角的 layer 渲染成圖片,添加到貝塞爾矩形中。這種方法效率更高,但是layer
的佈局一旦改變,貝塞爾曲線都需要手動地重新繪製,稍微麻煩。
總結
最後我們總結一下,常見觸發離屏渲染的情況有以下6種:
layer
設置了使用了mask
蒙版。layer
設置了圓角裁剪(masksToBounds
+cornerRadius
),且包含 sub layer。layer
設置了組不透明度allowsGroupOpacity
,並且不透明度opacity
小於1。layer
設置了投影shadowXX
。layer
設置了光柵化shouldRasterize
。使用了 blur 模糊效果
UIVisualEffectView
。
另外,我們可以通過打開Xcode
-> Debug
-> View Debuging
-> Rendering
-> Color Offscreen Rendered Yellow
開關,檢查離屏渲染情況。
作爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流羣:413038000,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!
作者:老青菜
鏈接:https://juejin.cn/post/6901973358657667085
來源:掘金