在工作中離屏真的不重要嗎,代碼優化就真的不考慮0.1%的離屏問題嗎,懂得離屏渲染原理,讓你程式員之後走的更長

来源:https://www.cnblogs.com/mysweetAngleBaby/archive/2022/06/04/16341632.html
-Advertisement-
Play Games

GPU 渲染機制:CPU計算好顯示內容提交到GPU,GPU渲染完成後將渲染結果放入幀緩衝區frame buffer,隨後視頻控制器會按照VSync信號逐行讀取幀緩衝區的數據,經過可能的數模轉換傳遞給顯示器顯示。 GPU 屏幕渲染有以下兩種方式: ● 1)On-Screen Rendering,意為當 ...


GPU 渲染機制:CPU計算好顯示內容提交到GPU,GPU渲染完成後將渲染結果放入幀緩衝區frame buffer,隨後視頻控制器會按照VSync信號逐行讀取幀緩衝區的數據,經過可能的數模轉換傳遞給顯示器顯示。

GPU 屏幕渲染有以下兩種方式:
● 1)On-Screen Rendering,意為當前屏幕渲染,指的是GPU的渲染操作是在當前用於顯示的屏幕緩衝區中進行。
● 2)Off-Screen Rendering,意為離屏渲染,指的是GPU在當前屏幕緩衝區以外新開闢一個緩衝區進行渲染操作。
特殊的離屏渲染:如果將不在GPU的當前屏幕緩衝區中進行的渲染都稱為離屏渲染,那麼就還有另一種特殊的“離屏渲染”方式:CPU渲染。如果我們重寫了drawRect方法,並且使用任何Core Graphics的技術進行了繪製操作,就涉及到了CPU渲染。整個渲染過程由CPU在App內同步地完成,渲染得到的bitmap最後再交由GPU用於顯示。備註:Core Graphics通常是線程安全的,所以可以進行非同步繪製,顯示的時候再放回主線程,一個簡單的非同步繪製過程大致如下:

(void)display {
		dispatch_async(backgroundQueue, ^{
			CGContextRef ctx = CGBitmapContextCreate(...);
			// draw in context...
			CGImageRef img = CGBitmapContextCreateImage(ctx);
			CFRelease(ctx);
			dispatch_async(mainQueue, ^{
				layer.contents = img;
			});
		});
}

離屏渲染的觸發方式:
1)shouldRasterize(光柵化),光柵化是比較特別的一種。光柵化概念:將圖轉化為一個個柵格組成的圖象。光柵化特點:每個元素對應幀緩衝區中的一像素。shouldRasterize = YES 在其他屬性觸發離屏渲染的同時,會將光柵化後的內容緩存起來,如果對應的 layer 及其 sublayers 沒有發生改變,在下一幀的時候可以直接復用。shouldRasterize = YES 這將隱式的創建一個點陣圖,各種陰影遮罩等效果也會保存到點陣圖中並緩存起來,從而減少渲染的頻度。相當於光柵化是把 GPU 的操作轉到 CPU 上了,生成點陣圖緩存,直接讀取復用。當你使用光柵化時,你可以開啟 Color Hits Green and Misses Red 來檢查該場景下光柵化操作是否是一個好的選擇。綠色表示緩存被覆用,紅色表示緩存在被重覆創建。如果光柵化的層變紅得太頻繁那麼光柵化對優化可能沒有多少用處。點陣圖緩存從記憶體中刪除又重新創建得太過頻繁,紅色表明緩存重建得太遲。可以針對性的選擇某個較小而較深的層結構進行光柵化,來嘗試減少渲染時間。對於經常變動的內容,這個時候不要開啟,否則會造成性能的浪費。例如經常打交道的 TableViewCell,因為 TableViewCell 的重繪是很頻繁的(因為 Cell 的復用),如果Cell的內容不斷變化,則Cell需要不斷重繪,如果此時設置了 cell.layer 可光柵化,則會造成大量的離屏渲染,降低圖形性能。

2)masks(遮罩)
3)shadows(陰影)
4)edge antialiasing(抗鋸齒)
5)group opacity(不透明)
6)複雜形狀設置圓角等
7)漸變

為什麼會使用離屏渲染:當使用圓角,陰影,遮罩的時候,圖層屬性的混合體被指定為在未預合成之前(下一個 VSync 信號開始前)不能直接在屏幕中繪製,所以就需要屏幕外渲染被喚起。屏幕外渲染並不意味著軟體繪製,但是它意味著圖層必須在被顯示之前在一個屏幕外上下文中被渲染(不論 CPU 還是 GPU)。所以當使用離屏渲染的時候會很容易造成性能消耗,因為離屏渲染會單獨在記憶體中創建一個屏幕外緩衝區併進行渲染,而屏幕外緩衝區跟當前屏幕緩衝區上下文切換是很耗性能的。由於垂直同步的機制,如果在一個 VSync 時間內,CPU或者GPU沒有完成內容提交,則那一幀就會被丟棄,等待下一次機會再顯示,而這時顯示屏會保留之前的內容不變。這就是界面卡頓的原因。
Instruments 監測離屏渲染:
1)Color Offscreen-Rendered Yellow,開啟後會把那些需要離屏渲染的圖層高亮成黃色,這就意味著黃色圖層可能存在性能問題。
2)Color Hits Green and Misses Red,如果 shouldRasterize 被設置成YES,對應的渲染結果會被緩存,如果圖層是綠色,就表示這些緩存被覆用;如果是紅色就表示緩存會被重覆創建,這就表示該處存在性能問題了。

GPU離屏渲染的性能影響
GPU的操作是高度流水線化的。本來所有計算工作都在有條不紊地正在向frame buffer輸出,此時突然收到指令,需要輸出到另一塊記憶體,那麼流水線中正在進行的一切都不得不被丟棄,切換到只能服務於我們當前的“切圓角”操作。等到完成以後再次清空,再回到向frame buffer輸出的正常流程。
在tableView或者collectionView中,滾動的每一幀變化都會觸發每個cell的重新繪製,因此一旦存在離屏渲染,上面提到的上下文切換就會每秒發生60次,並且很可能每一幀有幾十張的圖片要求這麼做,對於GPU的性能衝擊可想而知(GPU非常擅長大規模並行計算,但是我想頻繁的上下文切換顯然不在其設計考量之中)

相比於當前屏幕渲染,離屏渲染的代價是很高的,主要體現在兩個方面:
(1)創建新緩衝區
要想進行離屏渲染,首先要創建一個新的緩衝區。
(2)上下文切換
離屏渲染的整個過程,需要多次切換上下文環境:先是從當前屏幕(On-Screen)切換到離屏(Off-Screen),等到離屏渲染結束以後,將離屏緩衝區的渲染結果顯示到屏幕上有需要將上下文環境從離屏切換到當前屏幕。而上下文環境的切換是要付出很大代價的。

善用離屏渲染
儘管離屏渲染開銷很大,但是當我們無法避免它的時候,可以想辦法把性能影響降到最低。優化思路也很簡單:既然已經花了不少精力把圖片裁出了圓角,如果我能把結果緩存下來,那麼下一幀渲染就可以復用這個成果,不需要再重新畫一遍了。
CALayer為這個方案提供了對應的解法:shouldRasterize。一旦被設置為true,Render Server就會強制把layer的渲染結果(包括其子layer,以及圓角、陰影、group opacity等等)保存在一塊記憶體中,這樣一來在下一幀仍然可以被覆用,而不會再次觸發離屏渲染。有幾個需要註意的點:

● shouldRasterize的主旨在於降低性能損失,但總是至少會觸發一次離屏渲染。如果你的layer本來並不複雜,也沒有圓角陰影等等,打開這個開關反而會增加一次不必要的離屏渲染
● 離屏渲染緩存有空間上限,最多不超過屏幕總像素的2.5倍大小
● 一旦緩存超過100ms沒有被使用,會自動被丟棄
● layer的內容(包括子layer)必須是靜態的,因為一旦發生變化(如resize,動畫),之前辛苦處理得到的緩存就失效了。如果這件事頻繁發生,我們就又回到了“每一幀都需要離屏渲染”的情景,而這正是開發者需要極力避免的。針對這種情況,Xcode提供了“Color Hits Green and Misses Red”的選項,幫助我們查看緩存的使用是否符合預期
● 其實除瞭解決多次離屏渲染的開銷,shouldRasterize在另一個場景中也可以使用:如果layer的子結構非常複雜,渲染一次所需時間較長,同樣可以打開這個開關,把layer繪製到一塊緩存,然後在接下來複用這個結果,這樣就不需要每次都重新繪製整個layer樹了

什麼時候需要CPU渲染
渲染性能的調優,其實始終是在做一件事:平衡CPU和GPU的負載,讓他們儘量做各自最擅長的工作。

平衡CPU和GPU的負載
絕大多數情況下,得益於GPU針對圖形處理的優化,我們都會傾向於讓GPU來完成渲染任務,而給CPU留出足夠時間處理各種各樣複雜的App邏輯。為此Core Animation做了大量的工作,儘量把渲染工作轉換成適合GPU處理的形式(也就是所謂的硬體加速,如layer composition,設置backgroundColor等等)。
但是對於一些情況,如文字(CoreText使用CoreGraphics渲染)和圖片(ImageIO)渲染,由於GPU並不擅長做這些工作,不得不先由CPU來處理好以後,再把結果作為texture傳給GPU。除此以外,有時候也會遇到GPU實在忙不過來的情況,而CPU相對空閑(GPU瓶頸),這時可以讓CPU分擔一部分工作,提高整體效率。

來自WWDC18 session 221,可以看到Core Text基於Core Graphics
一個典型的例子是,我們經常會使用CoreGraphics給圖片加上圓角(將圖片中圓角以外的部分渲染成透明)。整個過程全部是由CPU完成的。這樣一來既然我們已經得到了想要的效果,就不需要再另外給圖片容器設置cornerRadius。另一個好處是,我們可以靈活地控製裁剪和緩存的時機,巧妙避開CPU和GPU最繁忙的時段,達到平滑性能波動的目的。
這裡有幾個需要註意的點:

● 渲染不是CPU的強項,調用CoreGraphics會消耗其相當一部分計算時間,並且我們也不願意因此阻塞用戶操作,因此一般來說CPU渲染都在後臺線程完成(這也是AsyncDisplayKit的主要思想),然後再回到主線程上,把渲染結果傳回CoreAnimation。這樣一來,多線程間數據同步會增加一定的複雜度
● 同樣因為CPU渲染速度不夠快,因此只適合渲染靜態的元素,如文字、圖片(想象一下沒有硬體加速的視頻解碼,性能慘不忍睹)
● 作為渲染結果的bitmap數據量較大(形式上一般為解碼後的UIImage),消耗記憶體較多,所以應該在使用完及時釋放,併在需要的時候重新生成,否則很容易導致OOM
● 如果你選擇使用CPU來做渲染,那麼就沒有理由再觸發GPU的離屏渲染了,否則會同時存在兩塊內容相同的記憶體,而且CPU和GPU都會比較辛苦
● 一定要使用Instruments的不同工具來測試性能,而不是僅憑猜測來做決定.

優化方案
官方對離屏渲染產生性能問題也進行了優化:
iOS 9.0 之前UIimageView跟UIButton設置圓角都會觸發離屏渲染。
iOS 9.0 之後UIButton設置圓角會觸發離屏渲染,而UIImageView里png圖片設置圓角不會觸發離屏渲染了,如果設置其他陰影效果之類的還是會觸發離屏渲染的。
1、圓角優化
在APP開發中,圓角圖片還是經常出現的。如果一個界面中只有少量圓角圖片或許對性能沒有非常大的影響,但是當圓角圖片比較多的時候就會APP性能產生明顯的影響。
我們設置圓角一般通過如下方式:

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

這樣處理的渲染機制是GPU在當前屏幕緩衝區外新開闢一個渲染緩衝區進行工作,也就是離屏渲染,這會給我們帶來額外的性能損耗,如果這樣的圓角操作達到一定數量,會觸發緩衝區的頻繁合併和上下文的的頻繁切換,性能的代價會巨集觀地表現在用戶體驗上——掉幀。
優化方案1:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角

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

優化方案2:使用CAShapeLayer和UIBezierPath設置圓角

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]; 
//設置大小 
maskLayer.frame = imageView.bounds; 
//設置圖形樣子 
maskLayer.path = maskPath.CGPath;
imageView.layer.mask = maskLayer; 

對於方案2需要解釋的是:
● CAShapeLayer繼承於CALayer,可以使用CALayer的所有屬性值;
● CAShapeLayer需要貝塞爾曲線配合使用才有意義(也就是說才有效果)
● 使用CAShapeLayer(屬於CoreAnimation)與貝塞爾曲線可以實現不在view的drawRect(繼承於CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形
● CAShapeLayer動畫渲染直接提交到手機的GPU當中,相較於view的drawRect方法使用CPU渲染而言,其效率極高,能大大優化記憶體使用情況。
總的來說就是用CAShapeLayer的記憶體消耗少,渲染速度快,建議使用優化方案2。
2、shadow優化
對於shadow,如果圖層是個簡單的幾何圖形或者圓角圖形,我們可以通過設置shadowPath來優化性能,能大幅提高性能。示例如下:

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;

我們還可以通過設置shouldRasterize屬性值為YES來強制開啟離屏渲染。其實就是光柵化(Rasterization)。既然離屏渲染這麼不好,為什麼我們還要強制開啟呢?當一個圖像混合了多個圖層,每次移動時,每一幀都要重新合成這些圖層,十分消耗性能。當我們開啟光柵化後,會在首次產生一個點陣圖緩存,當再次使用時候就會復用這個緩存。但是如果圖層發生改變的時候就會重新產生點陣圖緩存。所以這個功能一般不能用於UITableViewCell中,cell的復用反而降低了性能。最好用於圖層較多的靜態內容的圖形。而且產生的點陣圖緩存的大小是有限制的,一般是2.5個屏幕尺寸。在100ms之內不使用這個緩存,緩存也會被刪除。所以我們要根據使用場景而定。
3、其他的一些優化建議
● 當我們需要圓角效果時,可以使用一張中間透明圖片蒙上去
● 使用ShadowPath指定layer陰影效果路徑
● 使用非同步進行layer渲染(Facebook開源的非同步繪製框架AsyncDisplayKit)
● 設置layer的opaque值為YES,減少複雜圖層合成
● 儘量使用不包含透明(alpha)通道的圖片資源
● 儘量設置layer的大小值為整形值
● 直接讓美工把圖片切成圓角進行顯示,這是效率最高的一種方案
● 很多情況下用戶上傳圖片進行顯示,可以讓服務端處理圓角
● 使用代碼手動生成圓角Image設置到要顯示的View上,利用UIBezierPath(CoreGraphics框架)畫出來圓角圖片

青山不改,綠水長流!謝謝大家


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 來源:my.oschina.net/xiaolyuh/blog/1615639 在日常開發中有很多地方都有類似扣減庫存的操作,比如電商系統中的商品庫存,抽獎系統中的獎品庫存等。 解決方案 使用mysql資料庫,使用一個欄位來存儲庫存,每次扣減庫存去更新這個欄位。 還是使用資料庫,但是將庫存分層多份存 ...
  • 初始Linux Linux可劃分為以下四部分: Linux內核 GNU工具 圖形化桌面環境 應用軟體 每一部分在Linux系統中各司其職,下圖是各部分對應關係: 1、Linux內核 Linux系統的核心是內核,內核控制著電腦系統上的所有硬體和軟體,在必要時分配硬體,並根據需要執行軟體。內核主要負責 ...
  • DS18B20 是一個單線通信的數字溫度計, 允許在一根匯流排上掛接多個 DS18B20 並分別通信, 在普通溫度下, 可以直接從數據口取電, 這時候只需要兩根連線. 供電電壓相容3.3V和5V, 溫度檢測範圍[-55°C, +125°C]攝氏度, 在 [-10°C, +85°C] 精確率可以達到 ±... ...
  • 閑來無事,嘗試一下HTTPS。 正好華為雲有活動,功能變數名稱10塊錢一年,證書免費。 參考:https://www.bbsmax.com/A/B0zqr3wnJv/ 功能變數名稱綁定&申請證書 照著說明做就行。 安裝證書 這就是這篇博客的重點了。 安裝nginx的ssl模塊 如果還沒有安裝nginx的話,可以參考 ...
  • top top命令是Linux下常用的性能分析工具,能夠實時顯示系統中各個進程的資源占用狀況,類似於Windows的任務管理器。top顯示系統當前的進程和其他狀況,是一個動態顯示過程,可以自動或者通過用戶按鍵來不斷刷新當前狀態。如果在前臺執行該命令,它將獨占前臺,直到用戶終止該程式為止.。比較準確的 ...
  • 寫在前面 某音作為風靡中外的一款音樂創意短視頻社交軟體,其成功性不言而喻,一直聽說其強大的“威力”,但卻從沒深入研究過,作為人民的先鋒隊,這怎麼行,毅然決然的我,在上周五註冊了一個賬號,但沒想到的是等待我的確是一條不歸路~(以下內容純屬個人經歷與個人看法,沒有任何代表性,圖一樂呵兒) 一個視頻在發出 ...
  • 一、游標概念 • 在 PL/SQL 塊執行 SELECT/INSERT/UPDATE/DELETE 語句時,Oracle 會在記憶體中為其分配上下文區,而游標是指向該區域的指針。 • 游標為應用程式提供了一種對具有多行數據查詢結果集中的每一行單獨處理的方案,是設計互動式應用程式的編程介面。 二、游標優 ...
  • 一、引言 • PL/SQL 程式可通過條件或迴圈結構來控制命令執行的流程。 • PL/SQL 提供了豐富的流程式控制制語句,與 Java 一樣也有三種控制結構: • 順序結構 • 選擇結構 • 迴圈結構 二、選擇結構 2.1、IF-THEN 1)IF-THEN 該結構先判斷一個條件是否為 TRUE,條件 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...