iOS 離屏渲染分析 爲什麼需要 什麼時候出現 總結

我們瞭解了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呢?我們先來了解下“畫家算法”。

畫家算法,也叫作優先填充,它是三維計算機圖形學中處理可見性問題的一種解決方法(三維場景投影到二維平面)。如下圖,畫家算法首先將場景中的多邊形根據深度進行排序,然後按照由遠到近的順序進行描繪,這種方法通常會將不可見的部分覆蓋,這樣就可以解決可見性問題。

畫家算法“,每一層由遠到近依次輸出到畫布

對於有前後依賴的圖層(如陰影疊加、裁剪等),通過由遠到近的圖層疊加算法是無法實現的,我們需要先申請一個臨時緩衝區,所有圖層按照畫家算法,由遠到近在臨時緩衝區渲染,渲染完成後,再對這個臨時緩衝區做最後的全局操作(如陰影疊加、裁剪等),最後再把臨時緩衝區拷貝或切換到當前的緩衝區上,交給顯示器顯示。

總結一下,使用離屏渲染大概是因爲以下原因:

  1. 需要實現特殊的效果,比如說全局疊加、裁剪等等,需要用額外的幀緩衝區offscreen frame buffer保存中間狀態。

  2. 出於效率目的,針對不會經常變更的圖層,可以緩存到offscreen frame buffer,供下次刷新使用。

什麼時候出現

Masking

最常見的情形就是使用了Masking蒙版,我們看下官方提供的Masking渲染流程:

如上圖,渲染Masking蒙版分爲3步:

  1. 渲染 layermask 紋理,流程同 Tile Based Rendering
  2. 渲染 layercontent 紋理,流程同 Tile Based Rendering
  3. 合成操作,合併 maskcontent 紋理。

由於需要疊加兩層渲染結果,所以在疊加前,需要額外的緩衝區保存渲染結果,也就是觸發了離屏渲染。

UIVisualEffectView

iOS 8 提供的blur模糊特效也會引起離屏渲染,我們看下官方提供的blur渲染流程:

如上圖,渲染模糊過程分爲4步:

  1. 渲染 layercontent
  2. 截獲 layercontent,進行縮放。
  3. 對縮放內容進行橫向模糊。
  4. 對縮放內容進行縱向模糊。
  5. 合成操作,合併所有模糊結果。

blur模糊效果也會觸發離屏渲染,而且需要更多的緩衝區存儲渲染結果,更浪費性能。

Rasterization

光柵化,開啓光柵化後,會觸發離屏渲染,GPU 會強制把圖層的渲染結果保存下來,方便下次複用。我們看下官方的描述:

光珊化的本意是爲了避免靜態內容重繪、複雜view層級重繪帶來的影響。使用需要注意以下幾點:

  1. CALayer 的 shouldRasterize 可開啓光珊化。
  2. 如果 layer 不是靜態,或者頻繁修改(動畫中),開啓光珊化反而影響效率。
  3. 不要過度使用,光珊化離屏渲染緩存大小限制爲2.5倍屏幕大小。
  4. 光珊化離屏渲染緩存100ms沒有被使用,就會被丟棄。

Group Opactiy

組不透明度,某些情況也會觸發離屏渲染,可以通過CALayerallowsGroupOpacity控制。我們看官方的描述:

總結一下,有以下兩點:

  1. 建議關閉CALayerallowsGroupOpacity 屬性。

  2. 如果開啓了 allowsGroupOpacity,當 layer 的 opacity 小於1.0,且有子 layer 或者背景圖,則會觸發離屏渲染。

而在iOS 7.0 SDK以上,allowsGroupOpacity 默認 true,所以不需要的時候,建議關閉。

Shadow

投影,和Masking一樣,涉及到多個渲染結果合併,也會啓用離屏渲染。來看下官方的解釋:

如果單純設置shadowOffset,會觸發離屏渲染,但是我們可以設置shadowPath,告訴Core Animation投影路徑,那麼系統就知道如何繪製投影了,就不會觸發離屏渲染了。

圓角

通常我們通過layer.cornerRadius設置圓角,只會默認設置layer backgroundColorborder的圓角,同時結合layer.masksToBoundscontent設置圓角。

單純的cornerRadius+masksToBounds不一定會產生離屏渲染,如果這個圓角裁剪操作需要作用多個圖層,也就是layer上有其他圖層,那麼肯定會發生離屏渲染。比如以下兩種情況:

  1. layer 包含 sub layer,或者有 content,會觸發離屏渲染。
  2. 針對 UIImageView,同時設置了 backgroundColorimage,會觸發離屏渲染。

測試圓角離屏渲染

比如以下代碼,運行後,打開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)
複製代碼

避免圓角離屏渲染

那麼如何避免圓角裁剪產生的離屏渲染呢?我們先來看官方描述:

總結一下:

  1. 不要使用不必要的 mask,可以預處理圖片爲圓形,通過 Core Graphics手動繪製圓角。

  2. 使用中間爲圓形透明的白色背景視圖覆蓋。但這種方式不能解決背景色爲圖片或漸變色的情況。

  3. UIBezierPath 貝塞爾曲線繪製閉合帶圓角的矩形,再將不帶圓角的 layer 渲染成圖片,添加到貝塞爾矩形中。這種方法效率更高,但是 layer 的佈局一旦改變,貝塞爾曲線都需要手動地重新繪製,稍微麻煩。

總結

最後我們總結一下,常見觸發離屏渲染的情況有以下6種:

  1. layer 設置了使用了 mask 蒙版。

  2. layer 設置了圓角裁剪(masksToBounds+cornerRadius),且包含 sub layer。

  3. layer 設置了組不透明度allowsGroupOpacity,並且不透明度opacity小於1。

  4. layer 設置了投影shadowXX

  5. layer 設置了光柵化shouldRasterize

  6. 使用了 blur 模糊效果UIVisualEffectView

另外,我們可以通過打開Xcode -> Debug -> View Debuging -> Rendering -> Color Offscreen Rendered Yellow開關,檢查離屏渲染情況。

作爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流羣:413038000,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!

作者:老青菜
鏈接:https://juejin.cn/post/6901973358657667085
來源:掘金

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章