iOS離屏渲染過程示例解析
界面渲染
UIView繼承自UIResponder,可以處理系統(tǒng)傳遞過來的事件,如:UIApplication、UIViewController、UIView,以及所有從UIView派生出來的UIKit類。每個UIView內(nèi)部都有一個CALayer提供內(nèi)容的繪制和顯示,并且作為內(nèi)部RootLayer的代理視圖。
下圖為CALayer的結(jié)構(gòu)圖:

RunLoop有一個60fps的回調(diào),即每16.7ms繪制一次屏幕,所以view的繪制必須在這個時間內(nèi)完成,view內(nèi)容的繪制是CPU的工作,然后把繪制的內(nèi)容交給GPU渲染,包括多個View的拼接(Compositing)、紋理的渲染(Texture)等等,最后顯示在屏幕上。但是,如果無法是16.7ms內(nèi)完成繪制,就會出現(xiàn)丟幀的問題,一般情況下,如果幀率保證在30fps以上,界面卡頓效果不明顯,那么就需要在33.4ms內(nèi)完成View的繪制,而低于這個幀率,就會產(chǎn)生卡頓的效果,影響體驗。
渲染的過程
UIView的layer層有一個content,指向一塊緩存,即backing store
UIView繪制時,會調(diào)用drawRect方法,通過context將數(shù)據(jù)寫入backing store
在backing store寫完后,通過render server交給GPU去渲染,將backing store中的bitmap數(shù)據(jù)顯示在屏幕上

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

