iOS離屏渲染過(guò)程示例解析
界面渲染
UIView繼承自UIResponder,可以處理系統(tǒng)傳遞過(guò)來(lái)的事件,如:UIApplication、UIViewController、UIView,以及所有從UIView派生出來(lái)的UIKit類(lèi)。每個(gè)UIView內(nèi)部都有一個(gè)CALayer提供內(nèi)容的繪制和顯示,并且作為內(nèi)部RootLayer的代理視圖。
下圖為CALayer的結(jié)構(gòu)圖:
RunLoop有一個(gè)60fps的回調(diào),即每16.7ms繪制一次屏幕,所以view的繪制必須在這個(gè)時(shí)間內(nèi)完成,view內(nèi)容的繪制是CPU的工作,然后把繪制的內(nèi)容交給GPU渲染,包括多個(gè)View的拼接(Compositing)、紋理的渲染(Texture)等等,最后顯示在屏幕上。但是,如果無(wú)法是16.7ms內(nèi)完成繪制,就會(huì)出現(xiàn)丟幀的問(wèn)題,一般情況下,如果幀率保證在30fps以上,界面卡頓效果不明顯,那么就需要在33.4ms內(nèi)完成View的繪制,而低于這個(gè)幀率,就會(huì)產(chǎn)生卡頓的效果,影響體驗(yàn)。
渲染的過(guò)程
UIView的layer層有一個(gè)content,指向一塊緩存,即backing store
UIView繪制時(shí),會(huì)調(diào)用drawRect方法,通過(guò)context將數(shù)據(jù)寫(xiě)入backing store
在backing store寫(xiě)完后,通過(guò)render server交給GPU去渲染,將backing store中的bitmap數(shù)據(jù)顯示在屏幕上
渲染的過(guò)程
ios離屏渲染
On-Screen Rendering:當(dāng)前屏幕渲染,指的是 GPU 的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)中進(jìn)行
Off-Screen Rendering:離屏渲染,分為 CPU 離屏渲染和 GPU 離屏渲染兩種形式。GPU 離屏渲染指的是 GPU 在當(dāng)前屏幕緩沖區(qū)外新開(kāi)辟一個(gè)緩沖區(qū)進(jìn)行渲染操作
為什么會(huì)使用離屏渲染
當(dāng)使用圓角,陰影,遮罩的時(shí)候,圖層屬性的混合體被指定為在未預(yù)合成之前不能直接在屏幕中繪制,所以就需要屏幕外渲染被喚起。
GPU 離屏渲染的代價(jià)是很大的
離屏渲染之所以會(huì)特別消耗性能,是因?yàn)橐獎(jiǎng)?chuàng)建一個(gè)屏幕外的緩沖區(qū),然后從當(dāng)屏緩沖區(qū)切換到屏幕外的緩沖區(qū),然后再完成渲染;其中,創(chuàng)建緩沖區(qū)和切換上下文最消耗性能,而繪制其實(shí)不是性能損耗的主要原因。
上下文之間的切換這個(gè)過(guò)程的消耗會(huì)比較昂貴,涉及到 OpenGL的 pipeline 跟 barrier,而且 offscreen-render 在每一幀都會(huì)涉及到,因此處理不當(dāng)肯定會(huì)對(duì)性能產(chǎn)生一定的影響。另外由于離屏渲染會(huì)增加 GPU 的工作量,可能會(huì)導(dǎo)致 CPU+GPU 的處理時(shí)間超出 16.7ms,導(dǎo)致掉幀卡頓。
離屏渲染的場(chǎng)景和優(yōu)化
圓角優(yōu)化
方法一:
一般情況下我們會(huì)用這個(gè)方法去設(shè)置圓角:
iv.layer.cornerRadius = 30; iv.layer.masksToBounds = YES;
使用cornerRadius進(jìn)行切圓角,在iOS9之前會(huì)產(chǎn)生離屏渲染,比較消耗性能,而之后系統(tǒng)做了優(yōu)化,則不會(huì)產(chǎn)生離屏渲染,但是操作最簡(jiǎn)單
方法二:
利用mask設(shè)置圓角,利用的是UIBezierPath和CAShapeLayer來(lái)完成
CAShapeLayer *mask = [[CAShapeLayer alloc] init]; mask1.opacity = 0.3; mask1.path = [UIBezierPath bezierPathWithOvalInRect:iv.bounds].CGPath; iv.layer.mask = mask;
方法三:
利用CoreGraphics畫(huà)一個(gè)圓形上下文,然后把圖片繪制上去,得到一個(gè)圓形的圖片
- (UIImage *)drawCircleImage:(UIImage*)image { CGFloat side = MIN(image.size.width, image.size.height); UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), false, [UIScreen mainScreen].scale); CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, side, side)].CGPath); CGContextClip(UIGraphicsGetCurrentContext()); CGFloat marginX = -(image.size.width - side) * 0.5; CGFloat marginY = -(image.size.height - side) * 0.5; [image drawInRect:CGRectMake(marginX, marginY, image.size.width, image.size.height)]; CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke); UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage; }
三種方法里面,方法三是性能最好的。
當(dāng)然了,直接讓美工畫(huà)一個(gè)圓角的圖效率是最高的。
shadow優(yōu)化
我們可以通過(guò)設(shè)置shadowPath來(lái)優(yōu)化性能,能大幅提高性能
imageView.layer.shadowColor=[UIColorgrayColor].CGColor; imageView.layer.shadowOpacity=1.0; imageView.layer.shadowRadius=2.0; UIBezierPath *path=[UIBezierPathbezierPathWithRect:imageView.frame]; imageView.layer.shadowPath=path.CGPath;
組不透明
開(kāi)啟CALayer的 allowsGroupOpacity 屬性后,子 layer 在視覺(jué)上的透明度的上限是其父 layer 的 opacity (對(duì)應(yīng)UIView的 alpha ),并且從 iOS 7 以后默認(rèn)全局開(kāi)啟了這個(gè)功能,這樣做是為了讓子視圖與其容器視圖保持同樣的透明度。
所以,可以關(guān)閉 allowsGroupOpacity 屬性,按產(chǎn)品需求自己控制layer透明度。
關(guān)閉抗鋸齒
allowsEdgeAntialiasing屬性為YES(默認(rèn)為NO)
離屏渲染的檢測(cè)
Instruments的Core Animation工具中有幾個(gè)和離屏渲染相關(guān)的檢查選項(xiàng):
Color Offscreen-Rendered Yellow
開(kāi)啟后會(huì)把那些需要離屏渲染的圖層高亮成黃色,這就意味著黃色圖層可能存在性能問(wèn)題。
Color Hits Green and Misses Red
如果shouldRasterize被設(shè)置成YES,對(duì)應(yīng)的渲染結(jié)果會(huì)被緩存,如果圖層是綠色,就表示這些緩存被復(fù)用;如果是紅色就表示緩存會(huì)被重復(fù)創(chuàng)建,這就表示該處存在性能問(wèn)題了。
iOS版本上的優(yōu)化
iOS 9.0 之前UIimageView跟UIButton設(shè)置圓角都會(huì)觸發(fā)離屏渲染
iOS 9.0 之后UIButton設(shè)置圓角會(huì)觸發(fā)離屏渲染,而UIImageView里png圖片設(shè)置圓角不會(huì)觸發(fā)離屏渲染了,如果設(shè)置其他陰影效果之類(lèi)的還是會(huì)觸發(fā)離屏渲染的。
善用離屏渲染
盡管離屏渲染開(kāi)銷(xiāo)很大,但是當(dāng)我們無(wú)法避免它的時(shí)候,可以想辦法把性能影響降到最低。優(yōu)化思路也很簡(jiǎn)單:既然已經(jīng)花了不少精力把圖片裁出了圓角,如果我能把結(jié)果緩存下來(lái),那么下一幀渲染就可以復(fù)用這個(gè)成果,不需要再重新畫(huà)一遍了。
CALayer為這個(gè)方案提供了對(duì)應(yīng)的解法:shouldRasterize。一旦被設(shè)置為true,Render Server就會(huì)強(qiáng)制把layer的渲染結(jié)果(包括其子layer,以及圓角、陰影、group opacity等等)保存在一塊內(nèi)存中,這樣一來(lái)在下一幀仍然可以被復(fù)用,而不會(huì)再次觸發(fā)離屏渲染。有幾個(gè)需要注意的點(diǎn):
shouldRasterize的主旨在于降低性能損失,但總是至少會(huì)觸發(fā)一次離屏渲染。如果你的layer本來(lái)并不復(fù)雜,也沒(méi)有圓角陰影等等,打開(kāi)這個(gè)開(kāi)關(guān)反而會(huì)增加一次不必要的離屏渲染
離屏渲染緩存有空間上限,最多不超過(guò)屏幕總像素的2.5倍大小
一旦緩存超過(guò)100ms沒(méi)有被使用,會(huì)自動(dòng)被丟棄
layer的內(nèi)容(包括子layer)必須是靜態(tài)的,因?yàn)橐坏┌l(fā)生變化(如resize,動(dòng)畫(huà)),之前辛苦處理得到的緩存就失效了。如果這件事頻繁發(fā)生,我們就又回到了“每一幀都需要離屏渲染”的情景,而這正是開(kāi)發(fā)者需要極力避免的。針對(duì)這種情況,Xcode提供了“Color Hits Green and Misses Red”的選項(xiàng),幫助我們查看緩存的使用是否符合預(yù)期
其實(shí)除了解決多次離屏渲染的開(kāi)銷(xiāo),shouldRasterize在另一個(gè)場(chǎng)景中也可以使用:如果layer的子結(jié)構(gòu)非常復(fù)雜,渲染一次所需時(shí)間較長(zhǎng),同樣可以打開(kāi)這個(gè)開(kāi)關(guān),把layer繪制到一塊緩存,然后在接下來(lái)復(fù)用這個(gè)結(jié)果,這樣就不需要每次都重新繪制整個(gè)layer樹(shù)了
什么時(shí)候需要CPU渲染
絕大多數(shù)情況下,得益于GPU針對(duì)圖形處理的優(yōu)化,我們都會(huì)傾向于讓GPU來(lái)完成渲染任務(wù),而給CPU留出足夠時(shí)間處理各種各樣復(fù)雜的App邏輯。為此Core Animation做了大量的工作,盡量把渲染工作轉(zhuǎn)換成適合GPU處理的形式(也就是所謂的硬件加速,如layer composition,設(shè)置backgroundColor等等)。
但是對(duì)于一些情況,如文字(CoreText使用CoreGraphics渲染)和圖片(ImageIO)渲染,由于GPU并不擅長(zhǎng)做這些工作,不得不先由CPU來(lái)處理好以后,再把結(jié)果作為texture傳給GPU。除此以外,有時(shí)候也會(huì)遇到GPU實(shí)在忙不過(guò)來(lái)的情況,而CPU相對(duì)空閑(GPU瓶頸),這時(shí)可以讓CPU分擔(dān)一部分工作,提高整體效率。
一個(gè)典型的例子是,我們經(jīng)常會(huì)使用CoreGraphics給圖片加上圓角(將圖片中圓角以外的部分渲染成透明)。整個(gè)過(guò)程全部是由CPU完成的。這樣一來(lái)既然我們已經(jīng)得到了想要的效果,就不需要再另外給圖片容器設(shè)置cornerRadius。另一個(gè)好處是,我們可以靈活地控制裁剪和緩存的時(shí)機(jī),巧妙避開(kāi)CPU和GPU最繁忙的時(shí)段,達(dá)到平滑性能波動(dòng)的目的。
但要注意的是:
渲染不是CPU的強(qiáng)項(xiàng),調(diào)用CoreGraphics會(huì)消耗其相當(dāng)一部分計(jì)算時(shí)間,并且我們也不愿意因此阻塞用戶(hù)操作,因此一般來(lái)說(shuō)CPU渲染都在后臺(tái)線(xiàn)程完成(這也是AsyncDisplayKit的主要思想),然后再回到主線(xiàn)程上,把渲染結(jié)果傳回CoreAnimation。這樣一來(lái),多線(xiàn)程間數(shù)據(jù)同步會(huì)增加一定的復(fù)雜度
同樣因?yàn)镃PU渲染速度不夠快,因此只適合渲染靜態(tài)的元素,如文字、圖片(想象一下沒(méi)有硬件加速的視頻解碼,性能慘不忍睹)
作為渲染結(jié)果的bitmap數(shù)據(jù)量較大(形式上一般為解碼后的UIImage),消耗內(nèi)存較多,所以應(yīng)該在使用完及時(shí)釋放,并在需要的時(shí)候重新生成,否則很容易導(dǎo)致OOM
如果你選擇使用CPU來(lái)做渲染,那么就沒(méi)有理由再觸發(fā)GPU的離屏渲染了,否則會(huì)同時(shí)存在兩塊內(nèi)容相同的內(nèi)存,而且CPU和GPU都會(huì)比較辛苦。
以上就是iOS離屏渲染過(guò)程示例解析的詳細(xì)內(nèi)容,更多關(guān)于iOS離屏渲染的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS三級(jí)聯(lián)動(dòng)選擇器的實(shí)現(xiàn)代碼示例
本篇文章主要介紹了iOS三級(jí)聯(lián)動(dòng)選擇器的實(shí)現(xiàn)代碼示例,這里整理了詳細(xì)的代碼,有需要的小伙伴可以參考下2017-09-09使用SDLocalize實(shí)現(xiàn)高效完成iOS多語(yǔ)言工作
這篇文章主要介紹了使用SDLocalize實(shí)現(xiàn)高效完成iOS多語(yǔ)言工作的相關(guān)資料,需要的朋友可以參考下2022-10-10

iOS動(dòng)態(tài)驗(yàn)證碼實(shí)現(xiàn)代碼

iOS 獲取當(dāng)前時(shí)間及時(shí)間戳的互換實(shí)例

淺析Objective-C中分類(lèi)Category的使用

IOS HTTP請(qǐng)求的常見(jiàn)狀態(tài)碼總結(jié)

IOS 開(kāi)發(fā)之?dāng)?shù)據(jù)存儲(chǔ)writeToFile的應(yīng)用實(shí)例