iOS 關(guān)于離屏渲染

離屏渲染

計(jì)算機(jī)繪圖使用油畫算法,按圖層逐層繪制,由遠(yuǎn)及近,逐層覆蓋;如下圖。

painter-draw.png

對(duì)于有前后依賴的圖層(如全局剪切,陰影等),無法同時(shí)繪制多層圖像,所以會(huì)造成離屏渲染。對(duì)于有前后依賴的圖層,需要另開辟一個(gè)空間,用于臨時(shí)渲染,渲染完成后再渲染到當(dāng)前的緩沖區(qū)上,這個(gè)臨時(shí)渲染,就是離屏渲染,由于需要開辟一個(gè)新的內(nèi)存空間,并且共享同一個(gè)上下文,所以還需要做上下文切換(狀態(tài)切換),并且渲染完成后還要進(jìn)行拷貝操作

  • 開辟臨時(shí)緩存空間
  • 上下文切換,上下文對(duì)象比較大,切換操作會(huì)帶來一定的性能消耗
  • 內(nèi)存拷貝
  • 額外的渲染(沒有進(jìn)一步考證)

上面4項(xiàng)帶來的開銷會(huì)很大,并且每一幀渲染都需要執(zhí)行,如果屏幕上觸發(fā)離屏渲染的操作過多,會(huì)導(dǎo)致GPU渲染時(shí)間過長造成卡頓,應(yīng)該避免觸發(fā)離屏渲染

關(guān)于圓角問題

官方文檔關(guān)于layer.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è)置layer.cornerRadius只會(huì)作用到layer的background colorborder上面,不會(huì)作用到layer.contents上面,但是如果設(shè)置layer.masksToBounds = true,那么就會(huì)裁切contents成圓角。

離屏渲染是GPU無法按油畫算法一次性渲染完我們的視圖才會(huì)觸發(fā)。我們先來看幾個(gè)iOS的例子,選中模擬器 --> debug --> 打開Color Off-screen Rendered。

// 1、imageView
let imageV = UIImageView(frame: CGRect(x: 0, y: 120, width: 375, height: 80))
view.addSubview(imageV)
imageV.image = UIImage(named: "imageName")
        
// image + cornerRadius + masksToBounds 不會(huì)觸發(fā)離屏渲染,因?yàn)榇藭r(shí)沒有前后依賴的圖層
imageV.layer.cornerRadius = 20
imageV.layer.masksToBounds = true
        
// 設(shè)置圓角后,再設(shè)置borderWidth會(huì)導(dǎo)致離屏渲染
// imageV.layer.borderColor = UIColor.blue.cgColor
// imageV.layer.borderWidth = 3
        
// 如果只設(shè)置陰影,不設(shè)置shadowPath,會(huì)導(dǎo)致離屏渲染,因?yàn)椴恢狸幱暗降自趺蠢L制
// 如果設(shè)置了shadowPath屬性,那么知道了陰影繪制范圍,那么就不會(huì)造成離屏渲染
// 陰影不能和masksToBounds同時(shí)存在,會(huì)被裁切
imageV.layer.shadowColor = UIColor.green.cgColor
imageV.layer.shadowOffset = CGSize(width: 2, height: 6)
imageV.layer.shadowOpacity = 1
let path = CGMutablePath()
path.addRoundedRect(in: imageV.bounds, cornerWidth: 20, cornerHeight: 20)
imageV.layer.shadowPath = path
   
// 包含多圖層的View,同時(shí)賦值,會(huì)觸發(fā)離屏渲染,image + backgroundColor + cornerRadius + masksToBounds,會(huì)觸發(fā)離屏渲染
// backgroundColor會(huì)占一層
// imageV.backgroundColor = UIColor.green

// 如果添加一個(gè)frame != CGRect.zero 的view,會(huì)造成前后圖層依賴,導(dǎo)致離屏渲染
// let subView = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 30))
// subView.backgroundColor = UIColor.white
// imageV.addSubview(subView)

// 2. UIButton
let button = UIButton(frame: CGRect(x: 50, y: 250, width: 300, height: 50))
view.addSubview(button)
button.setTitle("Test", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 20)
button.setTitleColor(.blue, for: .normal)
// 設(shè)置button.imageView的圓角,不會(huì)觸發(fā)離屏渲染
// button.setImage(UIImage(named: "imageName"), for: .normal)
// button.imageView?.layer.cornerRadius = 20
// button.imageView?.layer.masksToBounds = true

// 設(shè)置圓角
button.layer.cornerRadius = 20
button.layer.masksToBounds = true

//  再設(shè)置背景顏色,會(huì)觸發(fā)離屏渲染
button.backgroundColor = .green
        
// 3、UIView
let newView = UIView(frame: CGRect(x: 50, y: 400, width: 300, height: 50))
view.addSubview(newView)
newView.backgroundColor = UIColor.green
newView.layer.cornerRadius = 20
// 開啟masksToBounds,如果有子視圖,并且子視圖被渲染(frame,alpha不為0),那么會(huì)造成離屏渲染
// newView.layer.masksToBounds = true

// 子視圖label如果被渲染,則會(huì)觸發(fā)渲染,如果text為空不會(huì)被渲染
let label = UILabel(frame: CGRect(x: 30, y: 10, width: 50, height: 20))
label.textColor = .white
newView.addSubview(label)
// 無內(nèi)容不會(huì)造成離屏渲染,當(dāng)然如果不開啟masksToBounds = true,即使有內(nèi)容也不會(huì)離屏渲染
label.text = "11" // label.text = "" 不會(huì)渲染
label.alpha = 0.8
        
// 如果視圖有子視圖,那么設(shè)置alpha<1.0,會(huì)導(dǎo)致離屏
// newView.alpha = 1
        
// 柵格化為true,會(huì)導(dǎo)致離屏
// newView.layer.shouldRasterize = true
        
// 設(shè)置shadow,并且設(shè)置shadowPath,則系統(tǒng)知道如何繪制陰影,不會(huì)觸發(fā)離屏渲染
// newView.layer.shadowColor = UIColor.red.cgColor
// newView.layer.shadowOffset = CGSize(width: 2, height: 6)
// newView.layer.shadowOpacity = 1
// let newpath = CGMutablePath()
// newpath.addRoundedRect(in: newView.bounds, cornerWidth: 20, cornerHeight: 20)
// newView.layer.shadowPath = newpath
        
// 設(shè)置遮罩 會(huì)導(dǎo)致offScreen Render (layer.mask)
// let shapLayer = CAShapeLayer()
// let bePath = UIBezierPath(roundedRect: newView.bounds, byRoundingCorners: UIRectCorner.allCorners, cornerRadii: CGSize(width: 20, height: 20))
// shapLayer.path = bePath.cgPath
// newView.layer.mask = shapLayer

總結(jié)

  • cornerRadius + masksToBounds 如果不會(huì)造成前后圖層依賴,則不會(huì)造成離屏渲染。imageView不設(shè)置backgroundColor,或者UIView設(shè)置backgroundColor后,無可渲染的子視圖,也不會(huì)造成離屏渲染。UIView如果確定子視圖不會(huì)被裁切,那么不設(shè)置masksToBounds時(shí),也不會(huì)造成離屏渲染。
  • 設(shè)置遮罩(layer.mask),肯定會(huì)導(dǎo)致離屏渲染。
  • cornerRadius + masksToBounds + 被渲染的子圖層,會(huì)導(dǎo)致離屏
  • cornerRadius + masksToBounds + layer.borderWidth > 0,會(huì)導(dǎo)致離屏
  • 柵格化layer.shouldRasterize = true,會(huì)導(dǎo)致離屏。
  • 設(shè)置shadow,且不設(shè)置shadowPath,會(huì)導(dǎo)致離屏渲染。
  • 設(shè)置組透明度為 true,并且透明度不為 1 的layer (layer.allowsGroupOpacity / layer.opacity)
  • 毛玻璃效果

轉(zhuǎn)載自 http://zhengbomo.github.io/2020-07-14/ios-offscreen-render/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容