何為離屏渲染?

我們看到的屏幕上的數(shù)據(jù)展示有兩種加載流程:

1末购、正常渲染加載流程
2破喻、離屏渲染加載流程
如下圖所示:


可以看出,離屏渲染比正常渲染多了一個(gè)離屏緩沖區(qū)盟榴,這個(gè)緩沖區(qū)的作用是什么呢曹质?為什么要加這個(gè)緩沖區(qū)呢?

首先擎场,說(shuō)說(shuō)正常渲染流程

正常渲染流程

APP中的數(shù)據(jù)經(jīng)過(guò)CPU計(jì)算和GPU渲染后羽德,將結(jié)果存放在幀緩沖區(qū),利用視頻控制器從幀緩沖區(qū)中取出迅办,并顯示到屏幕上宅静。

在GPU的渲染流程中,顯示到屏幕上的圖像是遵循畫(huà)家算法按照由遠(yuǎn)及近的順序站欺,依次將結(jié)果存儲(chǔ)到幀緩沖區(qū)
視頻控制器從幀緩沖區(qū)中讀取一幀數(shù)據(jù)姨夹,將其顯示到屏幕上后,會(huì)立即丟棄這幀數(shù)據(jù)镊绪,不會(huì)做任何保留匀伏,這樣做的目的是可以節(jié)省空間,且在屏幕上是各自顯示各自的蝴韭,互相不影響。


畫(huà)家算法
正常渲染數(shù)據(jù)讀取
離屏渲染流程

當(dāng)App需要進(jìn)行額外的渲染和合并時(shí)熙侍,例如按鈕設(shè)置圓角榄鉴,我們是需要對(duì)UIButton這個(gè)控件中的所有圖層都進(jìn)行圓角+裁剪,然后再將合并后的結(jié)果存入幀緩存區(qū)蛉抓,再?gòu)膸彺嬷腥〕鼋挥善聊伙@示庆尘,這時(shí),在正常的渲染流程中巷送,我們是無(wú)法做到對(duì)所有圖層進(jìn)行圓角裁剪的驶忌,因?yàn)樗怯靡粋€(gè)丟一個(gè)。所以我們需要提前將處理好的結(jié)果放入離屏緩沖區(qū)笑跛,最后將幾個(gè)圖層進(jìn)行疊加合并付魔,存放到幀緩沖區(qū),最后屏幕上就是我們想實(shí)現(xiàn)的效果飞蹂。

離屏渲染數(shù)據(jù)讀取

由此可以看出几苍,離屏緩存區(qū)就是一個(gè)臨時(shí)的緩沖區(qū),用來(lái)存放在后續(xù)操作使用陈哑,但目前并不使用的數(shù)據(jù)妻坝。

離屏渲染再給我們帶來(lái)方便的同時(shí)伸眶,也帶來(lái)了嚴(yán)重的性能問(wèn)題。由于離屏渲染中的離屏緩沖區(qū)刽宪,是額外開(kāi)辟的一個(gè)存儲(chǔ)空間厘贼,當(dāng)它將數(shù)據(jù)轉(zhuǎn)存到Frame Buffer時(shí),也是需要耗費(fèi)時(shí)間的圣拄,所以在轉(zhuǎn)存的過(guò)程中涂臣,仍有掉幀的可能。
離屏緩沖區(qū)的空間并不是無(wú)限大的售担,最大只能是屏幕的2.5倍
那為什么我們明知有性能問(wèn)題時(shí)赁遗,還是要使用離屏渲染呢?

可以處理一些特殊的效果族铆,這種效果并不能一次就完成岩四,需要使用離屏緩沖區(qū)來(lái)保存中間狀態(tài),不得不使用離屏渲染哥攘,這種情況下的離屏渲染是系統(tǒng)自動(dòng)觸發(fā)的剖煌,例如經(jīng)常使用的圓角、陰影逝淹、高斯模糊耕姊、光柵化等
可以提升渲染的效率,如果一個(gè)效果是多次實(shí)現(xiàn)的栅葡,可以提前渲染装悲,保存到離屏緩沖區(qū),以達(dá)到復(fù)用的目的签杈。這種情況是需要開(kāi)發(fā)者手動(dòng)觸發(fā)的翘狱。

注意:通常會(huì)觸發(fā)離屏渲染,除了圓角熊咽、剪裁外莫鸭,還有設(shè)置了透明度+組透明(layer.allowsGroupOpacity+layer.opacity),陰影屬性(shadowOffset 等)因?yàn)榻M透明度横殴、陰影都是和裁剪類似的被因,會(huì)作用與 layer 以及其所有 sublayer 上,這就導(dǎo)致必然會(huì)引起離屏渲染衫仑。

離屏渲染的另一個(gè)情況:光柵化(shouldRasterize )

官方文檔


由文檔中我們可以看出梨与,當(dāng)開(kāi)啟光柵化時(shí),會(huì)將layer渲染成位圖保存在緩存中惑畴,這樣在下次使用時(shí)蛋欣,就可以直接復(fù)用,提高效率如贷。

shouldRasterize使用建議:

layer不復(fù)用陷虎,沒(méi)必要使用shouldRasterize
layer不是靜態(tài)的到踏,也就是說(shuō)要頻繁的進(jìn)行修改,沒(méi)必要使用shouldRasterize
時(shí)間方面:離屏渲染緩存有100ms時(shí)間限制尚猿,超過(guò)該時(shí)間的內(nèi)容會(huì)被丟棄窝稿,進(jìn)而不能達(dá)到復(fù)用的目的
空間方面:離屏渲染空間是屏幕像素的2.5倍,如果超過(guò)也無(wú)法復(fù)用凿掂。

圓角與離屏渲染

首先說(shuō)明下CALayer的構(gòu)成伴榔,如圖所示,它是由backgroundColor庄萎、contents踪少、borderWidth&borderColor構(gòu)成的。跟我們即將研究的圓角觸發(fā)離屏渲染息息相關(guān)糠涛。


CALayer結(jié)構(gòu)(官方圖示)
圓角設(shè)置不生效問(wèn)題援奢!

在平常寫(xiě)代碼時(shí),比如UIButton設(shè)置圓角忍捡,當(dāng)設(shè)置好按鈕的image集漾、cornerRadius、borderWidth砸脊、borderColor等屬性后具篇,運(yùn)行發(fā)現(xiàn)并沒(méi)有實(shí)現(xiàn)我們想要的效果

      let btn = UIButton(type: .custom)
        btn.frame = CGRect(x: 100, y: 60, width: 100, height: 100)
        //設(shè)置圓角
        btn.layer.cornerRadius = 50
        //設(shè)置border寬度和顏色
        btn.layer.borderWidth = 2
        btn.layer.borderColor = UIColor.red.cgColor
        self.view.addSubview(btn0)
        //設(shè)置背景圖片
        btn.setImage(UIImage(named: "cat"), for: .normal)

當(dāng)我們運(yùn)行時(shí),我們發(fā)現(xiàn)沒(méi)有效果如下圖:


圓角沒(méi)效果

看到上面的情況大家都會(huì)設(shè)置masksToBounds為 true凌埂,解決的方法很簡(jiǎn)單驱显,但原理是大部人都沒(méi)有去仔細(xì)研究的。
讓我們看看蘋(píng)果官方文檔:

蘋(píng)果官方文檔

官方文檔告訴我們侨舆,設(shè)置cornerRadius只會(huì)對(duì)CALayer中的backgroundColor 和 boder設(shè)置圓角秒紧,不會(huì)設(shè)置contents的圓角,如果contents需要設(shè)置圓角挨下,需要同時(shí)將maskToBounds / clipsToBounds設(shè)置為true。

所以我們可以理解為圓角不生效的根本原因是沒(méi)有對(duì)contents設(shè)置圓角脐湾,而按鈕設(shè)置的image是放在contents里面的臭笆,所以看到的界面上的就是image沒(méi)有進(jìn)行圓角裁剪。

下面我們通過(guò)幾段代碼來(lái)說(shuō)明 圓角設(shè)置中什么時(shí)候會(huì)離屏渲染觸發(fā)
首先秤掌,需要打開(kāi)模擬器的離屏渲染顏色標(biāo)記


離屏渲染標(biāo)記開(kāi)啟方式
1愁铺、按鈕 僅設(shè)置背景顏色+border
  let btn01 = UIButton(type: .custom)
  btn01.frame = CGRect(x: 100, y: 200, width: 100, height: 100)
               //設(shè)置圓角
   btn01.layer.cornerRadius = 50
               //設(shè)置border寬度和顏色
    btn01.layer.borderWidth = 4
    btn01.layer.borderColor = UIColor.blue.cgColor
    self.view.addSubview(btn01)
               //設(shè)置背景顏色
    btn01.backgroundColor = UIColor.yellow   

在這種情況下,無(wú)論是使用默認(rèn)的maskToBounds / clipsToBounds(false)闻鉴,還是將其修改為true茵乱,都不會(huì)觸發(fā)離屏渲染,究其根本原因是 contents中沒(méi)有需要圓角處理的layer孟岛。


2瓶竭、按鈕設(shè)置背景圖片+boder
  let btn0 = UIButton(type: .custom)
        btn0.frame = CGRect(x: 100, y: 60, width: 100, height: 100)
               //設(shè)置圓角

        btn0.layer.cornerRadius = 50
               //設(shè)置border寬度和顏色
        btn0.layer.borderWidth = 2
              
        btn0.layer.borderColor = UIColor.red.cgColor
        self.view.addSubview(btn0)
         //設(shè)置背景圖片

        btn0.setImage(UIImage(named: "cat.jpg"), for: .normal)

使用默認(rèn)的maskToBounds / clipsToBounds(false)
這種情況就是最開(kāi)始我們講到的圓角設(shè)置不生效的情況督勺,就不再多做說(shuō)明了

maskToBounds / clipsToBounds 修改為true


離屏渲染示意圖
3、ImageView設(shè)置背景+圓角
 let imageView = UIImageView()
  imageView.frame = CGRect(x: 100, y: 60, width: 100, height: 100)
  imageView.layer.backgroundColor = UIColor.red.cgColor
   imageView.layer.cornerRadius = 50
       
 self.view.addSubview(imageView)

這種情況會(huì)得到圓角的且不會(huì)觸發(fā)離屏渲染斤贰。


4智哀、ImageView設(shè)置圖片+圓角
 let imageView = UIImageView()
        imageView.frame = CGRect(x: 100, y: 60, width: 100, height: 100)
//        imageView.layer.backgroundColor = UIColor.red.cgColor
        imageView.layer.cornerRadius = 50
        imageView.image = UIImage(named: "cat.jpg")
        imageView.clipsToBounds = true
        self.view.addSubview(imageView)

此時(shí)我們不設(shè)置背景色,這種情況會(huì)得到圓角的且不會(huì)觸發(fā)離屏渲染荧恍。


5瓷叫、ImageView設(shè)置圖片+圓角+背景色
 let imageView = UIImageView()
  imageView.frame = CGRect(x: 100, y: 60, width: 100, height: 100)
   imageView.layer.backgroundColor = UIColor.red.cgColor
        imageView.layer.cornerRadius = 50
        imageView.image = UIImage(named: "cat.jpg")
        imageView.clipsToBounds = true
        self.view.addSubview(imageView)

我們發(fā)現(xiàn)這時(shí)得到了圓角但是觸發(fā)了離屏渲染


總結(jié)

當(dāng)只設(shè)置backgroundColor、border送巡,而contents中沒(méi)有子視圖時(shí)摹菠,無(wú)論maskToBounds / clipsToBounds是true還是false,都不會(huì)觸發(fā)離屏渲染
當(dāng)contents中有子視圖時(shí)骗爆,此時(shí)設(shè)置 cornerRadius+maskToBounds / clipsToBounds,就會(huì)觸發(fā)離屏渲染

優(yōu)化次氨、

常見(jiàn)的圓角導(dǎo)致的離屏渲染的處理方法

方案1
_imageView.clipsToBounds=YES;
_imageView.layer.cornerRadius=4.0;
方案2
方案3
方案4
最后,大家看下YYImage的對(duì)于圓角的處理代碼淮腾。
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius 
 corners:(UIRectCorner)corners 
 borderWidth:(CGFloat)borderWidth 
 borderColor:(UIColor *)borderColor 
 borderLineJoin:(CGLineJoin)borderLineJoin { 
 if (corners != UIRectCornerAllCorners) { 
 UIRectCorner tmp = 0; 
 if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft; 
 if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight; 
 if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft; 
 if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight; 
 corners = tmp; 
 } 
 UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale); 
 CGContextRef context = UIGraphicsGetCurrentContext(); 
 CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); 
 CGContextScaleCTM(context, 1, -1); 
 CGContextTranslateCTM(context, 0, -rect.size.height); 
 CGFloat minSize = MIN(self.size.width, self.size.height); 
 if (borderWidth < minSize / 2) { 
 UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners 
cornerRadii:CGSizeMake(radius, borderWidth)]; 
 [path closePath]; 
 CGContextSaveGState(context); 
 [path addClip]; 
 CGContextDrawImage(context, rect, self.CGImage); 
 CGContextRestoreGState(context); 
 } 
 if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) { 
 CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale; 
 CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset); 
 CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0; 
 UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, 
borderWidth)]; 
 [path closePath]; 
 path.lineWidth = borderWidth; 
 path.lineJoinStyle = borderLineJoin; 
 [borderColor setStroke]; 
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末糟需,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谷朝,更是在濱河造成了極大的恐慌洲押,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件圆凰,死亡現(xiàn)場(chǎng)離奇詭異杈帐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)专钉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門挑童,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人跃须,你說(shuō)我怎么就攤上這事站叼。” “怎么了菇民?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵尽楔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我第练,道長(zhǎng)阔馋,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任娇掏,我火速辦了婚禮呕寝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婴梧。我一直安慰自己下梢,他們只是感情好客蹋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著怔球,像睡著了一般嚼酝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竟坛,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天闽巩,我揣著相機(jī)與錄音,去河邊找鬼担汤。 笑死涎跨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崭歧。 我是一名探鬼主播隅很,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼率碾!你這毒婦竟也來(lái)了叔营?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤所宰,失蹤者是張志新(化名)和其女友劉穎绒尊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體仔粥,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婴谱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躯泰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谭羔。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖麦向,靈堂內(nèi)的尸體忽然破棺而出瘟裸,到底是詐尸還是另有隱情,我是刑警寧澤诵竭,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布景描,位于F島的核電站,受9級(jí)特大地震影響秀撇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜向族,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一呵燕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧件相,春花似錦再扭、人聲如沸氧苍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)让虐。三九已至,卻和暖如春罢荡,著一層夾襖步出監(jiān)牢的瞬間赡突,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工区赵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惭缰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓笼才,卻偏偏與公主長(zhǎng)得像漱受,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骡送,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354