iOS學(xué)習(xí)日記-離屏渲染

本文參考:https://imlifengfeng.github.io/article/593/

一、概述

OpenGL ES是一套多功能開放標(biāo)準(zhǔn)的用于嵌入系統(tǒng)的C-based的圖形庫,用于2D和3D數(shù)據(jù)的可視化。OpenGL被設(shè)計(jì)用來轉(zhuǎn)換一組圖形調(diào)用功能到底層圖形硬件(GPU),由GPU執(zhí)行圖形命令,用來實(shí)現(xiàn)復(fù)雜的圖形操作和運(yùn)算,從而能夠高性能、高幀率利用GPU提供的2D和3D繪制能力。iOS系統(tǒng)默認(rèn)支持OpenGl ES1.0、ES2.0以及ES3.0 3個(gè)版本,三者之間并不是簡單的版本升級,設(shè)計(jì)理念甚至完全不同。GPU屏幕渲染方式中有一種方式為離屏渲染,處理不好離屏渲染往往會對APP的性能產(chǎn)生較大的影響。

二、當(dāng)前屏幕渲染與離屏渲染

OpenGL中,GPU屏幕渲染有兩種方式:

(1)On-Screen Rendering (當(dāng)前屏幕渲染)

指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行。

(2)Off-Screen Rendering (離屏渲染)

指的是在GPU在當(dāng)前屏幕緩沖區(qū)以外開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作。

當(dāng)前屏幕渲染不需要額外創(chuàng)建新的緩存,也不需要開啟新的上下文,相對于離屏渲染性能更好。但是受當(dāng)前屏幕渲染的局限因素限制(只有自身上下文、屏幕緩存有限等),當(dāng)前屏幕渲染有些情況下的渲染解決不了的,就使用到離屏渲染。

相比于當(dāng)前屏幕渲染,離屏渲染的代價(jià)是很高的,主要體現(xiàn)在兩個(gè)方面:

(1)創(chuàng)建新緩沖區(qū)

要想進(jìn)行離屏渲染,首先要創(chuàng)建一個(gè)新的緩沖區(qū)。

(2)上下文切換

離屏渲染的整個(gè)過程,需要多次切換上下文環(huán)境:先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen),等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上有需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕。而上下文環(huán)境的切換是要付出很大代價(jià)的。

特殊的“離屏渲染”:CPU渲染

如果我們重寫了drawRect方法,并且使用任何Core Graphics的技術(shù)進(jìn)行了繪制操作,就涉及到了CPU渲染。整個(gè)渲染過程由CPU在App內(nèi)同步地完成,渲染得到的bitmap(位圖)最后再交由GPU用于顯示。

Designing for iOS: Graphics & Performance 這篇文章也提到了使用 Core Graphics API 會觸發(fā)離屏渲染。 蘋果 iOS 4.1-8 時(shí)期的 UIKit 組成員Andy Matuschak也曾對這個(gè)說法進(jìn)行解釋:「Core Graphics 的繪制 API 的確會觸發(fā)離屏渲染,但不是那種 GPU 的離屏渲染。使用 Core Graphics 繪制 API 是在 CPU 上執(zhí)行,觸發(fā)的是 CPU 版本的離屏渲染。」

三、為什么要有離屏渲染

大家高中物理應(yīng)該學(xué)過顯示器是如何顯示圖像的:需要顯示的圖像經(jīng)過CRT電子槍以極快的速度一行一行的掃描,掃描出來就呈現(xiàn)了一幀畫面,隨后電子槍又會回到初始位置循環(huán)掃描,形成了我們看到的圖片或視頻。

為了讓顯示器的顯示跟視頻控制器同步,當(dāng)電子槍新掃描一行的時(shí)候,準(zhǔn)備掃描的時(shí)發(fā)送一個(gè)水平同步信號(HSync信號),顯示器的刷新頻率就是HSync信號產(chǎn)生的頻率。然后CPU計(jì)算好frame等屬性,將計(jì)算好的內(nèi)容交給GPU去渲染,GPU渲染好之后就會放入幀緩沖區(qū)。然后視頻控制器會按照HSync信號逐行讀取幀緩沖區(qū)的數(shù)據(jù),經(jīng)過可能的數(shù)模轉(zhuǎn)換傳遞給顯示器,就顯示出來了。具體的大家自行查找資料或詢問相關(guān)專業(yè)人士,這里只參考網(wǎng)上資料做一個(gè)簡單的描述。

離屏渲染的代價(jià)很高,想要進(jìn)行離屏渲染,首選要創(chuàng)建一個(gè)新的緩沖區(qū),屏幕渲染會有一個(gè)上下文環(huán)境的一個(gè)概念,離屏渲染的整個(gè)過程需要切換上下文環(huán)境,先從當(dāng)前屏幕切換到離屏,等結(jié)束后,又要將上下文環(huán)境切換回來。這也是為什么會消耗性能的原因了。

由于垂直同步的機(jī)制,如果在一個(gè) HSync 時(shí)間內(nèi),CPU 或者 GPU 沒有完成內(nèi)容提交,則那一幀就會被丟棄,等待下一次機(jī)會再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變。這就是界面卡頓的原因。

既然離屏渲染這么耗性能,為什么有這套機(jī)制呢?

有些效果被認(rèn)為不能直接呈現(xiàn)于屏幕,而需要在別的地方做額外的處理預(yù)合成。圖層屬性的混合體沒有預(yù)合成之前不能直接在屏幕中繪制,所以就需要屏幕外渲染。屏幕外渲染并不意味著軟件繪制,但是它意味著圖層必須在被顯示之前在一個(gè)屏幕外上下文中被渲染(不論CPU還是GPU)。

下面的情況或操作會引發(fā)離屏渲染:

為圖層設(shè)置遮罩(layer.mask)

將圖層的layer.masksToBounds / view.clipsToBounds屬性設(shè)置為true

將圖層layer.allowsGroupOpacity屬性設(shè)置為YES和layer.opacity小于1.0

為圖層設(shè)置陰影(layer.shadow *)。

為圖層設(shè)置layer.shouldRasterize=true

具有l(wèi)ayer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的圖層

文本(任何種類,包括UILabel,CATextLayer,Core Text等)。

使用CGContext在drawRect :方法中繪制大部分情況下會導(dǎo)致離屏渲染,甚至僅僅是一個(gè)空的實(shí)現(xiàn)。

四、優(yōu)化方案

官方對離屏渲染產(chǎn)生性能問題也進(jìn)行了優(yōu)化:

iOS 9.0 之前UIimageView跟UIButton設(shè)置圓角都會觸發(fā)離屏渲染。

iOS 9.0 之后UIButton設(shè)置圓角會觸發(fā)離屏渲染,而UIImageView里png圖片設(shè)置圓角不會觸發(fā)離屏渲染了,如果設(shè)置其他陰影效果之類的還是會觸發(fā)離屏渲染的。

1、圓角優(yōu)化

在APP開發(fā)中,圓角圖片還是經(jīng)常出現(xiàn)的。如果一個(gè)界面中只有少量圓角圖片或許對性能沒有非常大的影響,但是當(dāng)圓角圖片比較多的時(shí)候就會APP性能產(chǎn)生明顯的影響。

我們設(shè)置圓角一般通過如下方式:


imageView.layer.cornerRadius = CGFloat(10);
imageView.layer.masksToBounds = YES;

這樣處理的渲染機(jī)制是GPU在當(dāng)前屏幕緩沖區(qū)外新開辟一個(gè)渲染緩沖區(qū)進(jìn)行工作,也就是離屏渲染,這會給我們帶來額外的性能損耗,如果這樣的圓角操作達(dá)到一定數(shù)量,會觸發(fā)緩沖區(qū)的頻繁合并和上下文的的頻繁切換,性能的代價(jià)會宏觀地表現(xiàn)在用戶體驗(yàn)上——掉幀。
優(yōu)化方案1:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個(gè)圓角

UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)]; 
imageView.image = [UIImage imageNamed:@"myImg"]; 
//開始對imageView進(jìn)行畫圖 
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0); 
//使用貝塞爾曲線畫出一個(gè)圓形圖 
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
[imageView drawRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext(); 
//結(jié)束畫圖 
UIGraphicsEndImageContext();

優(yōu)化方案2:使用CAShapeLayer和UIBezierPath設(shè)置圓角

UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)]; 
imageView.image = [UIImage imageNamed:@"myImg"]; 
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init]; 
//設(shè)置大小 
maskLayer.frame = imageView.bounds; 
//設(shè)置圖形樣子 
maskLayer.path = maskPath.CGPath;
imageView.layer.mask = maskLayer; 

對于方案2需要解釋的是:

  • CAShapeLayer繼承于CALayer,可以使用CALayer的所有屬性值;
  • CAShapeLayer需要貝塞爾曲線配合使用才有意義(也就是說才有效果)
  • 使用CAShapeLayer(屬于CoreAnimation)與貝塞爾曲線可以實(shí)現(xiàn)不在view的drawRect(繼承于CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形
  • CAShapeLayer動畫渲染直接提交到手機(jī)的GPU當(dāng)中,相較于view的drawRect方法使用CPU渲染而言,其效率極高,能大大優(yōu)化內(nèi)存使用情況。

總的來說就是用CAShapeLayer的內(nèi)存消耗少,渲染速度快,建議使用優(yōu)化方案2。

2、shadow優(yōu)化
對于shadow,如果圖層是個(gè)簡單的幾何圖形或者圓角圖形,我們可以通過設(shè)置shadowPath來優(yōu)化性能,能大幅提高性能。示例如下:

imageView.layer.shadowColor = [UIColor grayColor].CGColor;
imageView.layer.shadowOpacity = 1.0;
imageView.layer.shadowRadius = 2.0;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:imageView.frame];
imageView.layer.shadowPath = path.CGPath;

我們還可以通過設(shè)置shouldRasterize屬性值為YES來強(qiáng)制開啟離屏渲染。其實(shí)就是光柵化(Rasterization)。既然離屏渲染這么不好,為什么我們還要強(qiáng)制開啟呢?當(dāng)一個(gè)圖像混合了多個(gè)圖層,每次移動時(shí),每一幀都要重新合成這些圖層,十分消耗性能。當(dāng)我們開啟光柵化后,會在首次產(chǎn)生一個(gè)位圖緩存,當(dāng)再次使用時(shí)候就會復(fù)用這個(gè)緩存。但是如果圖層發(fā)生改變的時(shí)候就會重新產(chǎn)生位圖緩存。所以這個(gè)功能一般不能用于UITableViewCell中,cell的復(fù)用反而降低了性能。最好用于圖層較多的靜態(tài)內(nèi)容的圖形。而且產(chǎn)生的位圖緩存的大小是有限制的,一般是2.5個(gè)屏幕尺寸。在100ms之內(nèi)不使用這個(gè)緩存,緩存也會被刪除。所以我們要根據(jù)使用場景而定。

3、其他的一些優(yōu)化建議

  • 當(dāng)我們需要圓角效果時(shí),可以使用一張中間透明圖片蒙上去
  • 使用ShadowPath指定layer陰影效果路徑
  • 使用異步進(jìn)行l(wèi)ayer渲染(Facebook開源的異步繪制框架AsyncDisplayKit)
  • 設(shè)置layer的opaque值為YES,減少復(fù)雜圖層合成
  • 盡量使用不包含透明(alpha)通道的圖片資源
  • 盡量設(shè)置layer的大小值為整形值
  • 直接讓美工把圖片切成圓角進(jìn)行顯示,這是效率最高的一種方案
  • 很多情況下用戶上傳圖片進(jìn)行顯示,可以讓服務(wù)端處理圓角
  • 使用代碼手動生成圓角Image設(shè)置到要顯示的View上,利用UIBezierPath(CoreGraphics框架)畫出來圓角圖片

五、Core Animation工具檢測離屏渲染

Core Animation工具檢測

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內(nèi)容