關(guān)于圖形渲染以及離屏渲染

1. CPU 與 GPU

CPU與GPU的不同

  1. 設(shè)計目標的不同簿煌,它們分別針對了兩種不同的應用場景漾唉。
  • CPU需要很強的通用性來處理各種不同的數(shù)據(jù)類型瀑志,同時又要邏輯判斷又會引入大量的分支跳轉(zhuǎn)和中斷的處理。這些都使得CPU的內(nèi)部結(jié)構(gòu)異常復雜他挎。而GPU面對的則是類型高度統(tǒng)一的烫葬、相互無依賴的大規(guī)模數(shù)據(jù)和不需要被打斷的純凈的計算環(huán)境界弧。

于是CPU和GPU就呈現(xiàn)出非常不同的架構(gòu)(示意圖):


cpu+gpu.png

圖片來自nVidia CUDA文檔凡蜻。其中綠色的是計算單元,橙紅色的是存儲單元垢箕,橙黃色的是控制單元划栓。

  • GPU采用了數(shù)量眾多的計算單元和超長的流水線,但只有非常簡單的控制邏輯并省去了Cache条获。而CPU不僅被Cache占據(jù)了大量空間忠荞,而且還有有復雜的控制邏輯和諸多優(yōu)化電路,相比之下計算能力只是CPU很小的一部分
cpu+gpu-cache.png

從上圖可以看出:

Cache, local memory: CPU > GPU

Threads(線程數(shù)): GPU > CPU

Registers: GPU > CPU 多寄存器可以支持非常多的Thread,thread需要用到register,thread數(shù)目大帅掘,register也必須得跟著很大才行委煤。

SIMD Unit(單指令多數(shù)據(jù)流,以同步方式,在同一時間內(nèi)執(zhí)行同一條指令): GPU > CPU锄开。

  • CPU 基于低延時的設(shè)cpu,下圖:


    cpu.png

CPU有強大的ALU(算術(shù)運算單元),它可以在很少的時鐘周期內(nèi)完成算術(shù)計算素标。

當今的CPU可以達到64bit 雙精度。執(zhí)行雙精度浮點源算的加法和乘法只需要1~3個時鐘周期萍悴。

CPU的時鐘周期的頻率是非常高的,達到1.532~3gigahertz(千兆HZ, 10的9次方).

大的緩存也可以降低延時寓免。保存很多的數(shù)據(jù)放在緩存里面癣诱,當需要訪問的這些數(shù)據(jù),只要在之前訪問過的袜香,如今直接在緩存里面取即可撕予。
復雜的邏輯控制單元。當程序含有多個分支的時候蜈首,它通過提供分支預測的能力來降低延時实抡。

數(shù)據(jù)轉(zhuǎn)發(fā)。 當一些指令依賴前面的指令結(jié)果時欢策,數(shù)據(jù)轉(zhuǎn)發(fā)的邏輯控制單元決定這些指令在pipeline中的位置并且盡可能快的轉(zhuǎn)發(fā)一個指令的結(jié)果給后續(xù)的指令吆寨。這些動作需要很多的對比電路單元和轉(zhuǎn)發(fā)電路單元。

  • GPU是基于大的吞吐量設(shè)計踩寇。


    gpu.png

GPU的特點是有很多的ALU和很少的cache. 緩存的目的不是保存后面需要訪問的數(shù)據(jù)的啄清,這點和CPU不同,而是為thread提高服務的俺孙。如果有很多線程需要訪問同一個相同的數(shù)據(jù)辣卒,緩存會合并這些訪問,然后再去訪問dram(因為需要訪問的數(shù)據(jù)保存在dram中而不是cache里面)睛榄,獲取數(shù)據(jù)后cache會轉(zhuǎn)發(fā)這個數(shù)據(jù)給對應的線程荣茫,這個時候是數(shù)據(jù)轉(zhuǎn)發(fā)的角色。但是由于需要訪問dram场靴,自然會帶來延時的問題啡莉。

GPU的控制單元(左邊黃色區(qū)域塊)可以把多個的訪問合并成少的訪問港准。

GPU的雖然有dram延時,卻有非常多的ALU和非常多的thread. 為了平衡內(nèi)存延時的問題票罐,我們可以充分利用多的ALU的特性達到一個非常大的吞吐量的效果叉趣。盡可能多的分配多的Threads.通常來看GPU ALU會有非常重的pipeline就是因為這樣。

所以與CPU擅長邏輯控制该押,串行的運算疗杉。和通用類型數(shù)據(jù)運算不同,GPU擅長的是大規(guī)模并發(fā)計算蚕礼,這也正是密碼破解等所需要的烟具。所以GPU除了圖像處理,也越來越多的參與到計算當中來奠蹬。

GPU的工作大部分就是這樣朝聋,計算量大,但沒什么技術(shù)含量囤躁,而且要重復很多很多次冀痕。就像你有個工作需要算幾億次一百以內(nèi)加減乘除一樣,最好的辦法就是雇上幾十個小學生一起算狸演,一人算一部分言蛇,反正這些計算也沒什么技術(shù)含量,純粹體力活而已宵距。而CPU就像老教授腊尚,積分微分都會算,就是工資高满哪,一個老教授資頂二十個小學生婿斥,你要是富士康你雇哪個?GPU就是這樣哨鸭,用很多簡單的計算單元去完成大量的計算任務民宿,純粹的人海戰(zhàn)術(shù)。這種策略基于一個前提兔跌,就是小學生A和小學生B的工作沒有什么依賴性勘高,是互相獨立的。很多涉及到大量計算的問題基本都有這種特性坟桅,比如你說的破解密碼华望,挖礦和很多圖形學的計算。這些計算可以分解為多個相同的簡單小任務仅乓,每個任務就可以分給一個小學生去做赖舟。但還有一些任務涉及到“流”的問題。比如你去相親夸楣,雙方看著順眼才能繼續(xù)發(fā)展宾抓∽愉觯總不能你這邊還沒見面呢,那邊找人把證都給領(lǐng)了石洗。這種比較復雜的問題都是CPU來做的幢泼。

總而言之,CPU和GPU因為最初用來處理的任務就不同讲衫,所以設(shè)計上有不小的區(qū)別缕棵。而某些任務和GPU最初用來解決的問題比較相似,所以用GPU來算了涉兽。GPU的運算速度取決于雇了多少小學生招驴,CPU的運算速度取決于請了多么厲害的教授。教授處理復雜任務的能力是碾壓小學生的枷畏,但是對于沒那么復雜的任務别厘,還是頂不住人多。當然現(xiàn)在的GPU也能做一些稍微復雜的工作了拥诡,相當于升級成初中生高中生的水平触趴。但還需要CPU來把數(shù)據(jù)喂到嘴邊才能開始干活,究竟還是靠CPU來管的渴肉。

貼出CPU和GPU 的官方概念:

  • CPU(Central Processing Unit):現(xiàn)代計算機的三大核心部分之一雕蔽,作為整個系統(tǒng)的運算和控制單元。CPU 內(nèi)部的流水線結(jié)構(gòu)使其擁有一定程度的并行計算能力宾娜。
  • GPU(Graphics Processing Unit):一種可進行繪圖運算工作的專用微處理器。GPU 能夠生成 2D/3D 的圖形圖像和視頻扇售,從而能夠支持基于窗口的操作系統(tǒng)前塔、圖形用戶界面、視頻游戲承冰、可視化圖像應用和視頻播放华弓。GPU 具有非常強的并行計算能力。

2.計算機的渲染原理

像素是如何繪制在屏幕上的困乒?

  • 計算機將存儲在內(nèi)存中的形狀轉(zhuǎn)換成實際繪制在屏幕上的對應的過程稱為 渲染 寂屏。渲染過程中最常用的技術(shù)就是 光柵化

關(guān)于光柵化的概念娜搂,以下圖為例迁霎,假如有一道綠光與存儲在內(nèi)存中的一堆三角形中的某一個在三維空間坐標中存在相交的關(guān)系。那么這些處于相交位置的像素都會被繪制到屏幕上百宇。當然這些三角形在三維空間中的前后關(guān)系也會以遮擋或部分遮擋的形式在屏幕上呈現(xiàn)出來考廉。一句話總結(jié):光柵化就是將數(shù)據(jù)轉(zhuǎn)化成可見像素的過程

image.png

GPU 則是執(zhí)行轉(zhuǎn)換過程的硬件部件携御。由于這個過程涉及到屏幕上的每一個像素昌粤,所以 GPU 被設(shè)計成了一個高度并行化的硬件部件既绕。

GPU 圖形渲染流水線

GPU 圖形渲染流水線的主要工作可以被劃分為兩個部分:

  • 把 3D 坐標轉(zhuǎn)換為 2D 坐標
  • 把 2D 坐標轉(zhuǎn)變?yōu)閷嶋H的有顏色的像素

GPU 圖形渲染流水線的具體實現(xiàn)可分為六個階段,如下圖所示涮坐。

  • 頂點著色器(Vertex Shader)
  • 形狀裝配(Shape Assembly)凄贩,又稱 圖元裝配
  • 幾何著色器(Geometry Shader)
  • 光柵化(Rasterization)
  • 片段著色器(Fragment Shader)
  • 測試與混合(Tests and Blending)
image.png
  • 第一階段,頂點著色器袱讹。該階段的輸入是 頂點數(shù)據(jù)(Vertex Data) 數(shù)據(jù)疲扎,比如以數(shù)組的形式傳遞 3 個 3D 坐標用來表示一個三角形。頂點數(shù)據(jù)是一系列頂點的集合廓译。頂點著色器主要的目的是把 3D 坐標轉(zhuǎn)為另一種 3D 坐標评肆,同時頂點著色器可以對頂點屬性進行一些基本處理。

  • 第二階段非区,形狀(圖元)裝配瓜挽。該階段將頂點著色器輸出的所有頂點作為輸入,并將所有的點裝配成指定圖元的形狀征绸。圖中則是一個三角形久橙。圖元(Primitive) 用于表示如何渲染頂點數(shù)據(jù),如:點管怠、線淆衷、三角形。

  • 第三階段渤弛,幾何著色器祝拯。該階段把圖元形式的一系列頂點的集合作為輸入,它可以通過產(chǎn)生新頂點構(gòu)造出新的(或是其它的)圖元來生成其他形狀她肯。例子中佳头,它生成了另一個三角形。

  • 第四階段晴氨,光柵化康嘉。該階段會把圖元映射為最終屏幕上相應的像素,生成片段籽前。片段(Fragment) 是渲染一個像素所需要的所有數(shù)據(jù)亭珍。

  • 第五階段,片段著色器枝哄。該階段首先會對輸入的片段進行 裁切(Clipping)肄梨。裁切會丟棄超出視圖以外的所有像素,用來提升執(zhí)行效率膘格。

  • 第六階段峭范,測試與混合。該階段會檢測片段的對應的深度值(z坐標)瘪贱,判斷這個像素位于其它物體的前面還是后面纱控,決定是否應該丟棄辆毡。此外,該階段還會檢查 alpha 值( alpha 值定義了一個物體的透明度)甜害,從而對物體進行混合舶掖。因此,即使在片段著色器中計算出來了一個像素輸出的顏色尔店,在渲染多個三角形的時候最后的像素顏色也可能完全不同眨攘。

關(guān)于混合,GPU 采用如下公式進行計算嚣州,并得出最后的顏色

R = S + D * (1 - Sa)

關(guān)于公式的含義鲫售,假設(shè)有兩個像素 S(source) 和 D(destination),S 在** z** 軸方向相對靠前(在上面)该肴,D 在 z 軸方向相對靠后(在下面)情竹,那么最終的顏色值就是 S(上面像素) 的顏色 + D(下面像素) 的顏色 * (1 - S(上面像素) 顏色的透明度)

上述流水線以繪制一個三角形為進行介紹匀哄,可以為每個頂點添加顏色來增加圖形的細節(jié)秦效,從而創(chuàng)建圖像。但是涎嚼,如果讓圖形看上去更加真實阱州,需要足夠多的頂點和顏色,相應也會產(chǎn)生更大的開銷法梯。為了提高生產(chǎn)效率和執(zhí)行效率苔货,開發(fā)者經(jīng)常會使用 紋理(Texture) 來表現(xiàn)細節(jié)。紋理是一個 2D 圖片(甚至也有 1D 和 3D 的紋理)立哑。紋理 一般可以直接作為圖形渲染流水線的第五階段的輸入蒲赂。

3.屏幕成像與卡頓情況

介紹屏幕圖像顯示的原理,需要先從 CRT 顯示器原理說起刁憋,如下圖所示。CRT 的電子槍從上到下逐行掃描木蹬,掃描完成后顯示器就呈現(xiàn)一幀畫面至耻。然后電子槍回到初始位置進行下一次掃描。為了同步顯示器的顯示過程和系統(tǒng)的視頻控制器镊叁,顯示器會用硬件時鐘產(chǎn)生一系列的定時信號尘颓。當電子槍換行進行掃描時,顯示器會發(fā)出一個水平同步信號(horizonal synchronization)晦譬,簡稱 HSync疤苹;而當一幀畫面繪制完成后,電子槍回復到原位敛腌,準備畫下一幀前卧土,顯示器會發(fā)出一個垂直同步信號(vertical synchronization)惫皱,簡稱 VSync。顯示器通常以固定頻率進行刷新尤莺,這個刷新率就是 VSync 信號產(chǎn)生的頻率旅敷。雖然現(xiàn)在的顯示器基本都是液晶顯示屏了,但其原理基本一致颤霎。

image.png

下圖所示為常見的 CPU媳谁、GPU、顯示器工作方式友酱。CPU 計算好顯示內(nèi)容提交至 GPU晴音,GPU 渲染完成后將渲染結(jié)果存入幀緩沖區(qū),視頻控制器會按照 VSync 信號逐幀讀取幀緩沖區(qū)的數(shù)據(jù)缔杉,經(jīng)過數(shù)據(jù)轉(zhuǎn)換后最終由顯示器進行顯示锤躁。


image.png

最簡單的情況下,幀緩沖區(qū)只有一個壮吩。此時进苍,幀緩沖區(qū)的讀取和刷新都都會有比較大的效率問題,并且有可能產(chǎn)生撕裂。

撕裂原因:

  • 在屏幕顯示圖形圖像的過程中鸭叙,是不斷從幀緩存區(qū)獲取一幀一幀數(shù)據(jù)進行顯示的觉啊,
  • 然后在渲染的過程中,幀緩存區(qū)中仍是舊的數(shù)據(jù)沈贝,屏幕拿到舊的數(shù)據(jù)去進行顯示杠人,
  • 在舊的數(shù)據(jù)沒有讀取完時 ,新的一幀數(shù)據(jù)處理好了宋下,放入了緩存區(qū)嗡善,這時就會導致屏幕另一部分的顯示是獲取的新數(shù)據(jù),從而導致屏幕上呈現(xiàn)圖片不匹配学歧,人物罩引、景象等錯位顯示的情況。

撕裂表現(xiàn)為下圖:


image.png

為了解決效率和撕裂問題枝笨,GPU 通常會引入兩個緩沖區(qū)袁铐,即雙緩沖機制。在這種情況下横浑,GPU 會預先渲染一幀放入一個緩沖區(qū)中剔桨,用于視頻控制器的讀取。當下一幀渲染完畢后徙融,GPU 會直接把視頻控制器的指針指向第二個緩沖器洒缀。

image.png

蘋果官方針對屏幕撕裂現(xiàn)象,目前一直采用的是垂直同步+雙緩存,該方案是強制要求同步树绩,且是以掉幀為代價的萨脑。

掉幀圖:


image.png

總結(jié):

  • CPU和GPU在渲染的流水線中耗時過長,導致從緩存區(qū)獲取位圖顯示時葱峡,下一幀的數(shù)據(jù)還沒有準備好砚哗,獲取的仍是上一幀的數(shù)據(jù),產(chǎn)生掉幀現(xiàn)象砰奕,掉幀就會導致屏幕卡頓
  • 蘋果官方針對屏幕撕裂問題蛛芥,目前一直使用的方案是垂直同步+雙緩存區(qū),可以從根本上防止和解決屏幕撕裂军援,但是同時也導致了新的問題掉幀仅淑。雖然我們采用了雙緩存區(qū),但是我們并不能解決CPU和GPU處理圖形圖像的速度問題胸哥,導致屏幕在接收到垂直信號時涯竟,數(shù)據(jù)尚未準備好,緩存區(qū)仍是上一幀的數(shù)據(jù)空厌,因此導致掉幀
  • 在垂直同步+雙緩存區(qū)的方案上庐船,再次進行優(yōu)化,將雙緩存區(qū)嘲更,改為三緩存區(qū)筐钟,這樣其實也并不能從根本上解決掉幀的問題,只是比雙緩存區(qū)掉幀的概率小了很多赋朦,仍有掉幀的可能性篓冲,對于用戶而言,可能是無感知的。

4.iOS渲染框架

圖形渲染流程圖:

下圖所示為 iOS App 的圖形渲染流程,App 使用 Core Graphics刹前、Core Animation、Core Image 等框架來繪制可視化內(nèi)容诽俯,這些軟件框架相互之間也有著依賴關(guān)系。這些框架都需要通過 OpenGL 來調(diào)用 GPU 進行繪制承粤,最終將內(nèi)容顯示到屏幕之上惊畏。


流程.png
  • App通過調(diào)用CoreGraphics、CoreAnimation密任、CoreImage等框架的接口觸發(fā)圖形渲染操作
  • CoreGraphics、CoreAnimation偷俭、CoreImage等框架將渲染交由OpenGL ES或者Metal浪讳,由OpenGL ES來驅(qū)動GPU做渲染,最后顯示到屏幕上
  • 由于OpenGL ES 是跨平臺的涌萤,所以在他的實現(xiàn)中淹遵,是不能有任何窗口相關(guān)的代碼口猜,而是讓各自的平臺為OpenGL ES提供載體。在ios中透揣,如果需要使用OpenGL ES济炎,就是通過CoreAnimation提供窗口,讓App可以去調(diào)用辐真。

iOS渲染框架

框架 說明 介紹
UIKit UIKit 是 iOS 開發(fā)者最常用的框架须尚,可以通過設(shè)置 UIKit 組件的布局以及相關(guān)屬性來繪制界面。 UIKit 自身并不具備在屏幕成像的能力侍咱,其主要負責對用戶操作事件的響應(UIView 繼承自 UIResponder)耐床,事件響應的傳遞大體是經(jīng)過逐層的 視圖樹 遍歷實現(xiàn)的。
Core Animation Core Animation 源自于 Layer Kit楔脯,動畫只是 Core Animation 特性的冰山一角撩轰。 Core Animation 是一個復合引擎,其職責是 盡可能快地組合屏幕上不同的可視內(nèi)容昧廷,這些可視內(nèi)容可被分解成獨立的圖層(即 CALayer)堪嫂,這些圖層會被存儲在一個叫做圖層樹的體系之中。從本質(zhì)上而言木柬,CALayer 是用戶所能在屏幕上看見的一切的基礎(chǔ)皆串。
Core Graphics Core Graphics 基于 Quartz 高級繪圖引擎,主要用于運行時繪制圖像弄诲。開發(fā)者可以使用此框架來處理基于路徑的繪圖愚战,轉(zhuǎn)換,顏色管理齐遵,離屏渲染寂玲,圖案,漸變和陰影梗摇,圖像數(shù)據(jù)管理拓哟,圖像創(chuàng)建和圖像遮罩以及 PDF 文檔創(chuàng)建,顯示和分析 當開發(fā)者需要在運行時創(chuàng)建圖像 時伶授,可以使用 Core Graphics 去繪制断序。與之相對的是 運行前創(chuàng)建圖像,例如用 Photoshop 提前做好圖片素材直接導入應用糜烹。相比之下违诗,我們更需要 Core Graphics去在運行時實時計算、繪制一系列圖像幀來實現(xiàn)動畫疮蹦。
Core Image Core Image 與 Core Graphics 恰恰相反诸迟,Core Graphics 用于在 運行時創(chuàng)建圖像,而 Core Image 是用來處理 運行前創(chuàng)建的圖像 的。Core Image 框架擁有一系列現(xiàn)成的圖像過濾器阵苇,能對已存在的圖像進行高效的處理壁公。 大部分情況下,Core Image 會在 GPU 中完成工作绅项,但如果 GPU 忙紊册,會使用 CPU 進行處理。
OpenGL ES OpenGL ES(OpenGL for Embedded Systems快耿,簡稱 GLES)囊陡,是 OpenGL 的子集。 OpenGL 是一套第三方標準润努,函數(shù)的內(nèi)部實現(xiàn)由對應的 GPU 廠商開發(fā)實現(xiàn)关斜。
Metal Metal 類似于 OpenGL ES,也是一套第三方標準铺浇,具體實現(xiàn)由蘋果實現(xiàn)。大多數(shù)開發(fā)者都沒有直接使用過 Metal倚聚,但其實所有開發(fā)者都在間接地使用 Metal惨驶。Core Animation、Core Image、SceneKit识脆、SpriteKit 等等渲染框架都是構(gòu)建于 Metal 之上的纵东。 當在真機上調(diào)試 OpenGL 程序時衰絮,控制臺會打印出啟用 Metal 的日志淌友。根據(jù)這一點可以猜測,Apple 已經(jīng)實現(xiàn)了一套機制將 OpenGL 命令無縫橋接到 Metal 上,由 Metal 擔任真正于硬件交互的工作

5.CoreAnimation渲染

在前面的Core Animation 簡介中提到 CALayer 事實上是用戶所能在屏幕上看見的一切的基礎(chǔ)千元。為什么UIKit 中的視圖能夠呈現(xiàn)可視化內(nèi)容物独?就是因為 UIKit中的每一個 UI 視圖控件其實內(nèi)部都有一個關(guān)聯(lián)的 CALayer帚称,即 backing layer楼吃。

由于這種一一對應的關(guān)系躬窜,視圖層級擁有 視圖樹 的樹形結(jié)構(gòu)厕倍,對應 CALayer 層級也擁有 圖層樹 的樹形結(jié)構(gòu)贩疙。

image.png

其中讹弯,視圖的職責是 創(chuàng)建并管理 圖層,以確保當子視圖在層級關(guān)系中 添加或被移除 時这溅,其關(guān)聯(lián)的圖層在圖層樹中也有相同的操作组民,即保證視圖樹和圖層樹在結(jié)構(gòu)上的一致性。

那么為什么 iOS 要基于 UIView 和 CALayer 提供兩個平行的層級關(guān)系呢悲靴?

其原因在于要做職責分離臭胜,這樣也能避免很多重復代碼。在 iOS 和 Mac OS X 兩個平臺上癞尚,事件和用戶交互有很多地方的不同耸三,基于多點觸控的用戶界面和基于鼠標鍵盤的交互有著本質(zhì)的區(qū)別,這就是為什么 iOS 有 UIKit 和 UIView浇揩,對應 Mac OS X 有 AppKit 和 NSView 的原因仪壮。它們在功能上很相似,但是在實現(xiàn)上有著顯著的區(qū)別胳徽。

實際上积锅,這里并不是兩個層級關(guān)系爽彤,而是四個。每一個都扮演著不同的角色缚陷。除了 視圖樹 和 圖層樹适篙,還有 呈現(xiàn)樹 和 渲染樹。

CALayer

那么為什么 CALayer 可以呈現(xiàn)可視化內(nèi)容呢箫爷?因為 CALayer 基本等同于一個 紋理匙瘪。紋理是 GPU 進行圖像渲染的重要依據(jù)。

圖形渲染原理 中提到紋理本質(zhì)上就是一張圖片蝶缀,因此 CALayer 也包含一個 contents 屬性指向一塊緩存區(qū),稱為 backing store薄货,可以存放位圖(Bitmap)翁都。iOS 中將該緩存區(qū)保存的圖片稱為 寄宿圖

image.png

圖形渲染流水線支持從頂點開始進行繪制(在流水線中谅猾,頂點會被處理生成紋理)柄慰,也支持直接使用紋理(圖片)進行渲染。相應地税娜,在實際開發(fā)中坐搔,繪制界面也有兩種方式:一種是 手動繪制;另一種是 使用圖片敬矩。

對此概行,iOS 中也有兩種相應的實現(xiàn)方式:

  • 使用圖片:contents image
  • 手動繪制:custom drawing

Contents Image

Contents Image 是指通過 CALayercontents 屬性來配置圖片。然而弧岳,contents 屬性的類型為id凳忙。在這種情況下,可以給 contents 屬性賦予任何值禽炬,app 仍可以編譯通過涧卵。但是在實踐中,如果 content 的值不是 CGImage 腹尖,得到的圖層將是空白的柳恐。

既然如此,為什么要將 contents 的屬性類型定義為 id而非 CGImage热幔。這是因為在 Mac OS 系統(tǒng)中乐设,該屬性對CGImageNSImage 類型的值都起作用,而在 iOS 系統(tǒng)中断凶,該屬性只對CGImage 起作用伤提。

本質(zhì)上,contents 屬性指向的一塊緩存區(qū)域认烁,稱為 backing store肿男,可以存放 bitmap 數(shù)據(jù)介汹。

Custom Drawing

Custom Drawing 是指使用 Core Graphics 直接繪制寄宿圖。實際開發(fā)中舶沛,一般通過繼承 UIView 并實現(xiàn) -drawRect:方法來自定義繪制嘹承。

雖然 -drawRect:是一個 UIView 方法,但事實上都是底層的 CALayer 完成了重繪工作并保存了產(chǎn)生的圖片如庭。下圖所示為 -drawRect: 繪制定義寄宿圖的基本原理叹卷。

image.png

  • UIView 有一個關(guān)聯(lián)圖層,即 CALayer坪它。
  • CALayer 有一個可選的 delegate 屬性骤竹,實現(xiàn)了 CALayerDelegate 協(xié)議。UIView 作為 CALayer 的代理實現(xiàn)了 CALayerDelegae 協(xié)議往毡。
  • 當需要重繪時蒙揣,即調(diào)用 -drawRect:,CALayer 請求其代理給予一個寄宿圖來顯示开瞭。
  • CALayer 首先會嘗試調(diào)用 -displayLayer: 方法懒震,此時代理可以直接設(shè)置 contents 屬性。
- (void)displayLayer:(CALayer *)layer;
  • 最后嗤详,由 Core Graphics 繪制生成的寄宿圖會存入 backing store个扰。

Core Animation 流水線

通過前面的介紹,我們知道了 CALayer 的本質(zhì)葱色,那么它是如何調(diào)用 GPU 并顯示可視化內(nèi)容的呢递宅?下面我們就需要介紹一下 Core Animation 流水線的工作原理。


image.png

事實上苍狰,app 本身并不負責渲染恐锣,渲染則是由一個獨立的進程負責,即 Render Server 進程舞痰。

App 通過 IPC 將渲染任務及相關(guān)數(shù)據(jù)提交給Render Server土榴。Render Server 處理完數(shù)據(jù)后,再傳遞至 GPU响牛。最后由 GPU 調(diào)用 iOS 的圖像設(shè)備進行顯示玷禽。

Core Animation 流水線的詳細過程如下:

  • 首先,由 app 處理事件(Handle Events)呀打,如:用戶的點擊操作矢赁,在此過程中 app 可能需要更新 視圖樹,相應地贬丛,圖層樹 也會被更新撩银。
  • 其次,app 通過 CPU 完成對顯示內(nèi)容的計算豺憔,如:視圖的創(chuàng)建额获、布局計算够庙、圖片解碼、文本繪制等抄邀。在完成對顯示內(nèi)容的計算之后耘眨,app 對圖層進行打包,并在下一次 RunLoop 時將其發(fā)送至 Render Server境肾,即完成了一次Commit Transaction操作剔难。
  • Render Server 主要執(zhí)行 Open GL、Core Graphics 相關(guān)程序奥喻,并調(diào)用 GPU
  • GPU 則在物理層上完成了對圖像的渲染偶宫。
  • 最終,GPU 通過 Frame Buffer环鲤、視頻控制器等相關(guān)部件读宙,將圖像顯示在屏幕上。
    對上述步驟進行串聯(lián)楔绞,它們執(zhí)行所消耗的時間遠遠超過 16.67 ms,因此為了滿足對屏幕的 60 FPS 刷新率的支持唇兑,需要將這些步驟進行分解酒朵,通過流水線的方式進行并行執(zhí)行,如下圖所示扎附。
image.png

Commit Transaction

在 Core Animation 流水線中蔫耽,app 調(diào)用 Render Server 前的最后一步 Commit Transaction 其實可以細分為 4 個步驟:

  • Layout
  • Display
  • Prepare
  • Commit

Layout

Layout階段主要進行視圖構(gòu)建,包括:LayoutSubviews 方法的重載留夜,addSubview: 方法填充子視圖等匙铡。

Display

Display 階段主要進行視圖繪制,這里僅僅是設(shè)置最要成像的圖元數(shù)據(jù)碍粥。重載視圖的 drawRect: 方法可以自定義 UIView的顯示鳖眼,其原理是在 drawRect: 方法內(nèi)部繪制寄宿圖,該過程使用 CPU 和內(nèi)存嚼摩。

Prepare

Prepare階段屬于附加步驟钦讳,一般處理圖像的解碼和轉(zhuǎn)換等操作。

Commit

Commit 階段主要將圖層進行打包枕面,并將它們發(fā)送至 Render Server愿卒。該過程會遞歸執(zhí)行,因為圖層和視圖都是以樹形結(jié)構(gòu)存在

動畫渲染原理

iOS 動畫的渲染也是基于上述 Core Animation 流水線完成的潮秘。這里我們重點關(guān)注 app 與Render Server的執(zhí)行流程琼开。

日常開發(fā)中,如果不是特別復雜的動畫枕荞,一般使用 UIView Animation 實現(xiàn)柜候,iOS 將其處理過程分為如下三部階段:

  • Step 1:調(diào)用 animationWithDuration:animations: 方法
  • Step 2:在 Animation Block 中進行 Layout搞动,Display,Prepare改橘,Commit 等步驟滋尉。
  • Step 3:Render Server 根據(jù) Animation 逐幀進行渲染。


    image.png

6.離屏渲染問題

離屏渲染與正常渲染

屏幕上最終顯示的數(shù)據(jù)有兩種加載流程

  • 正常渲染加載流程
  • 離屏渲染加載流程
image.png

從圖上看飞主,他們之間的區(qū)別就是離屏渲染比正常渲染多了一個離屏緩沖區(qū)狮惜,這個緩沖區(qū)的作用是什么呢?

正常渲染流程

APP中的數(shù)據(jù)經(jīng)過CPU計算和GPU渲染后碌识,將結(jié)果存放在幀緩沖區(qū)碾篡,利用視頻控制器從幀緩沖區(qū)中取出,并顯示到屏幕上筏餐。

  • 在GPU的渲染流程中开泽,顯示到屏幕上的圖像是遵循大畫家算法按照由遠及近的順序,依次將結(jié)果存儲到幀緩沖區(qū)
  • 視屏控制器從幀緩沖區(qū)中讀取一幀數(shù)據(jù)魁瞪,將其顯示到屏幕上后穆律,會立即丟棄這幀數(shù)據(jù),不會做任何保留导俘,這樣做的目的是可以節(jié)省空間峦耘,且在屏幕上是各自顯示各自的,互相不影響旅薄。
image.png

離屏渲染流程

當App需要進行額外的渲染和合并時辅髓,例如按鈕設(shè)置圓角,我們是需要對UIButton這個控件中的所有圖層都進行圓角+裁剪少梁,然后再將合并后的結(jié)果存入幀緩存區(qū)洛口,再從幀緩存中取出交由屏幕顯示,這時凯沪,在正常的渲染流程中第焰,我們是無法做到對所有圖層進行圓角裁剪的,因為它是用一個丟一個妨马。所以我們需要提前將處理好的結(jié)果放入離屏緩沖區(qū)樟遣,最后將幾個圖層進行疊加合并,存放到站緩沖區(qū)身笤,最后屏幕上就是我們想實現(xiàn)的效果豹悬。

image.png

所以離屏緩存區(qū)就是一個臨時的緩沖區(qū),用來存放在后續(xù)操作使用液荸,但目前并不使用的數(shù)據(jù)瞻佛。

  • 離屏渲染再給我們帶來方便的同時,也帶來了嚴重的性能問題。由于離屏渲染中的離屏緩沖區(qū)伤柄,是額外開辟的一個存儲空間绊困,當它將數(shù)據(jù)轉(zhuǎn)存到Frame Buffer時,也是需要耗費時間的适刀,所以在轉(zhuǎn)存的過程中秤朗,仍有掉幀的可能。
  • 離屏緩沖區(qū)的空間并不是無限大的笔喉, 它是又上限的取视,最大只能是屏幕的2.5倍

那為什么我們明知有性能問題時,還是要使用離屏渲染呢常挚?

  • 可以處理一些特殊的效果作谭,這種效果并不能一次就完成,需要使用離屏緩沖區(qū)來保存中間狀態(tài)奄毡,不得不使用離屏渲染折欠,這種情況下的離屏渲染是系統(tǒng)自動觸發(fā)的,例如經(jīng)常使用的圓角吼过、陰影锐秦、高斯模糊、光柵化等
  • 可以提升渲染的效率盗忱,如果一個效果是多次實現(xiàn)的酱床,可以提前渲染,保存到離屏緩沖區(qū)售淡,以達到復用的目的。這種情況是需要開發(fā)者手動觸發(fā)的慷垮。

離屏渲染的另一個原因:光柵化

When the value of this property is YES, the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content.

當我們開啟光柵化時揖闸,會將layer渲染成位圖保存在緩存中,這樣在下次使用時料身,就可以直接復用汤纸,提高效率。
針對光柵化的使用芹血,有以下幾個建議:

  • 如果layer不能被復用贮泞,則沒有必要開啟光柵化
  • 如果layer不是靜態(tài),需要被頻繁修改(例如動畫過程中)幔烛,此時開啟光柵化反而影響效率
  • 離屏渲染緩存內(nèi)容有時間限制啃擦,如果100ms內(nèi)沒有被使用,那么就會丟棄饿悬,無法進行復用
  • 離屏渲染的緩存空間有限令蛉,是屏幕的2.5倍,超過2.5倍屏幕像素大小的話也會失效,無法實現(xiàn)復用

圓角中離屏渲染的觸發(fā)時機

在講圓角之前珠叔,首先說明下CALayer的構(gòu)成蝎宇,如圖所示,它是由backgroundColor祷安、contents姥芥、borderWidth&borderColor構(gòu)成的。跟我們即將解釋的圓角觸發(fā)離屏渲染息息相關(guān)汇鞭。

image.png

圓角設(shè)置不生效問題凉唐!

在平常寫代碼時,比如UIButton設(shè)置圓角虱咧,當設(shè)置好按鈕的image熊榛、cornerRadius、borderWidth腕巡、borderColor等屬性后玄坦,運行發(fā)現(xiàn)并沒有實現(xiàn)我們想要的效果

        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: "mouse"), for: .normal)
image.png

可以發(fā)現(xiàn),我們設(shè)置的按鈕圖片還是方方正正的

下面是蘋果官方文檔針對圓角設(shè)置的一些說明:


image.png

官方文檔告訴我們绘沉,設(shè)置cornerRadius只會對CALayer中的backgroundColorboder設(shè)置圓角煎楣,不會設(shè)置contents的圓角,如果contents需要設(shè)置圓角车伞,需要同時將maskToBounds / clipsToBounds設(shè)置為true择懂。

所以我們可以理解為圓角不生效的根本原因是沒有對contents設(shè)置圓角,而按鈕設(shè)置的image是放在contents里面的另玖,所以看到的界面上的就是image沒有進行圓角裁剪困曙。

下面我們通過幾段代碼來說明 圓角設(shè)置中什么時候會離屏渲染觸發(fā)
首先,需要打開模擬器的離屏渲染顏色標記

image.png

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.red.cgColor
        self.view.addSubview(btn01)
        //設(shè)置背景顏色
        btn01.backgroundColor = UIColor.green

在這種情況下慷丽,無論是使用默認的maskToBounds / clipsToBounds(false),還是將其修改為true鳄哭,都不會觸發(fā)離屏渲染要糊,究其根本原因是contents中沒有需要圓角處理的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: "mouse"), for: .normal)
  • 使用默認的maskToBounds / clipsToBounds(false)
    這種情況就是最開始我們講到的圓角設(shè)置不生效的情況妆丘,就不再多做說明了

  • maskToBounds / clipsToBounds 修改為true

    image.png

從屏幕的顯示上可以看出锄俄,此時觸發(fā)了離屏渲染,是因為圓角的設(shè)置是需要對所有l(wèi)ayer都進行裁剪的勺拣,而maskToBounds裁剪是應用到所有l(wèi)ayer上的奶赠。如果從正常渲染的角度來說,一個個layer是用完即扔的药有。而現(xiàn)在我們的圓角設(shè)置需要3個layer疊加合并的车柠,所以將先處理好的layer保存在離屏緩沖區(qū),等到最后一個layer處理完,合并進行圓角+裁剪竹祷,所以才會觸發(fā)離屏渲染

總結(jié)

  • 當只設(shè)置backgroundColor谈跛、border,而contents中沒有子視圖時塑陵,無論maskToBounds / clipsToBounds是true還是false感憾,都不會觸發(fā)離屏渲染
  • 當contents中有子視圖時,此時設(shè)置 cornerRadius+maskToBounds / clipsToBounds,就會觸發(fā)離屏渲染
  • UIImageView中只設(shè)置圖片+maskToBounds / clipsToBounds是不會觸發(fā)離屏渲染令花,蘋果對UIImageView優(yōu)化只是將image直接畫在了contents上面這樣不設(shè)置背景色其實只需要渲染一個layer,所以不需要用到離屏緩沖區(qū)

常見觸發(fā)離屏渲染的幾種情況:

    1. 使?了 mask 的 layer (layer.mask)
    1. 需要進?裁剪的 layer (layer.masksToBounds / view.clipsToBounds)
    1. 設(shè)置了組透明度為 YES阻桅,并且透明度不為 1 的 layer (layer.allowsGroupOpacity/
      layer.opacity)
    1. 添加了投影的 layer (layer.shadow)
    1. 采?了光柵化的 layer (layer.shouldRasterize)
    1. 繪制了?字的 layer (UILabel, CATextLayer, Core Text 等)

參考

  1. 離屏渲染優(yōu)化詳解:實例示范+性能測試
  2. Mastering Offscreen Render
  3. Optimizing 2D Graphics and Animation Performance
  4. Polishing Your Interface Rotation Animations
  5. Core Animation Essentials
  6. Understanding UIKit Rendering
  7. iOS: Rendering the UI
  8. iOS 事件處理機制與圖像渲染過程
  9. iOS 動畫篇:核心動畫
  10. GPU Framebuffer Memory: Understanding Tiling
  11. iOS 保持界面流暢的技巧
  12. OpenGL ES 框架詳細解析(八) —— OpenGL ES 設(shè)計指南
  13. iOS 開發(fā)-視圖渲染與性能優(yōu)化
  14. iOS 視圖、動畫渲染機制探究
  15. iOS 事件處理機制與圖像渲染過程
  16. iOS界面渲染流程
  17. 界面渲染的整體流程
  18. iOS圖像處理之Core Graphics和OpenGL ES初見
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兼都,一起剝皮案震驚了整個濱河市嫂沉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扮碧,老刑警劉巖趟章,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異慎王,居然都是意外死亡蚓土,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門赖淤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜀漆,“玉大人,你說我怎么就攤上這事咱旱∪范” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵吐限,是天一觀的道長鲜侥。 經(jīng)常有香客問我,道長毯盈,這世上最難降的妖魔是什么剃毒? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任病袄,我火速辦了婚禮搂赋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘益缠。我一直安慰自己脑奠,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布幅慌。 她就那樣靜靜地躺著宋欺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上齿诞,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天酸休,我揣著相機與錄音,去河邊找鬼祷杈。 笑死斑司,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的但汞。 我是一名探鬼主播宿刮,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼私蕾!你這毒婦竟也來了僵缺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤踩叭,失蹤者是張志新(化名)和其女友劉穎磕潮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體懊纳,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡箫锤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年插勤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡灌危,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驯击,到底是詐尸還是另有隱情硝清,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布脚囊,位于F島的核電站龟糕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏悔耘。R本人自食惡果不足惜讲岁,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望衬以。 院中可真熱鬧缓艳,春花似錦、人聲如沸看峻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽互妓。三九已至溪窒,卻和暖如春坤塞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背澈蚌。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工摹芙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宛瞄。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓瘫辩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坛悉。 傳聞我的和親對象是個殘疾皇子伐厌,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

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