溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

iOS渲染原理是什么

發(fā)布時(shí)間:2021-11-12 16:38:10 來(lái)源:億速云 閱讀:160 作者:iii 欄目:移動(dòng)開(kāi)發(fā)

這篇文章主要講解了“iOS渲染原理是什么”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“iOS渲染原理是什么”吧!

iOS渲染原理是什么cdn.xitu.io/2020/6/2/1727430740ce99d7?w=515&h=405&f=png&s=73957">

1. 計(jì)算機(jī)渲染原理

CPU 與 GPU 的架構(gòu)

對(duì)于現(xiàn)代計(jì)算機(jī)系統(tǒng),簡(jiǎn)單來(lái)說(shuō)可以大概視作三層架構(gòu):硬件、操作系統(tǒng)與進(jìn)程。對(duì)于移動(dòng)端來(lái)說(shuō),進(jìn)程就是 app,而 CPU 與 GPU 是硬件層面的重要組成部分。CPU 與 GPU 提供了計(jì)算能力,通過(guò)操作系統(tǒng)被 app 調(diào)用。

iOS渲染原理是什么

  • CPU(Central Processing Unit):現(xiàn)代計(jì)算機(jī)整個(gè)系統(tǒng)的運(yùn)算核心、控制核心。

  • GPU(Graphics Processing Unit):可進(jìn)行繪圖運(yùn)算工作的專(zhuān)用微處理器,是連接計(jì)算機(jī)和顯示終端的紐帶。

CPU 和 GPU 其設(shè)計(jì)目標(biāo)就是不同的,它們分別針對(duì)了兩種不同的應(yīng)用場(chǎng)景。CPU 是運(yùn)算核心與控制核心,需要有很強(qiáng)的運(yùn)算通用性,兼容各種數(shù)據(jù)類(lèi)型,同時(shí)也需要能處理大量不同的跳轉(zhuǎn)、中斷等指令,因此 CPU 的內(nèi)部結(jié)構(gòu)更為復(fù)雜。而 GPU 則面對(duì)的是類(lèi)型統(tǒng)一、更加單純的運(yùn)算,也不需要處理復(fù)雜的指令,但也肩負(fù)著更大的運(yùn)算任務(wù)。

iOS渲染原理是什么

因此,CPU 與 GPU 的架構(gòu)也不同。因?yàn)?CPU 面臨的情況更加復(fù)雜,因此從上圖中也可以看出,CPU 擁有更多的緩存空間 Cache 以及復(fù)雜的控制單元,計(jì)算能力并不是 CPU 的主要訴求。CPU 是設(shè)計(jì)目標(biāo)是低時(shí)延,更多的高速緩存也意味著可以更快地訪問(wèn)數(shù)據(jù);同時(shí)復(fù)雜的控制單元也能更快速地處理邏輯分支,更適合串行計(jì)算。

而 GPU 擁有更多的計(jì)算單元 Arithmetic Logic Unit,具有更強(qiáng)的計(jì)算能力,同時(shí)也具有更多的控制單元。GPU 基于大吞吐量而設(shè)計(jì),每一部分緩存都連接著一個(gè)流處理器(stream processor),更加適合大規(guī)模的并行計(jì)算。

圖像渲染流水線

圖像渲染流程粗粒度地大概分為下面這些步驟:

iOS渲染原理是什么

上述圖像渲染流水線中,除了第一部分 Application 階段,后續(xù)主要都由 GPU 負(fù)責(zé),為了方便后文講解,先將 GPU 的渲染流程圖展示出來(lái):

iOS渲染原理是什么

上圖就是一個(gè)三角形被渲染的過(guò)程中,GPU 所負(fù)責(zé)的渲染流水線??梢钥吹胶?jiǎn)單的三角形繪制就需要大量的計(jì)算,如果再有更多更復(fù)雜的頂點(diǎn)、顏色、紋理信息(包括 3D 紋理),那么計(jì)算量是難以想象的。這也是為什么 GPU 更適合于渲染流程。

接下來(lái),具體講解渲染流水線中各個(gè)部分的具體任務(wù):

Application 應(yīng)用處理階段:得到圖元

這個(gè)階段具體指的就是圖像在應(yīng)用中被處理的階段,此時(shí)還處于 CPU 負(fù)責(zé)的時(shí)期。在這個(gè)階段應(yīng)用可能會(huì)對(duì)圖像進(jìn)行一系列的操作或者改變,最終將新的圖像信息傳給下一階段。這部分信息被叫做圖元(primitives),通常是三角形、線段、頂點(diǎn)等。

Geometry 幾何處理階段:處理圖元

進(jìn)入這個(gè)階段之后,以及之后的階段,就都主要由 GPU 負(fù)責(zé)了。此時(shí) GPU 可以拿到上一個(gè)階段傳遞下來(lái)的圖元信息,GPU 會(huì)對(duì)這部分圖元進(jìn)行處理,之后輸出新的圖元。這一系列階段包括:

  • 頂點(diǎn)著色器(Vertex Shader):這個(gè)階段中會(huì)將圖元中的頂點(diǎn)信息進(jìn)行視角轉(zhuǎn)換、添加光照信息、增加紋理等操作。

  • 形狀裝配(Shape Assembly):圖元中的三角形、線段、點(diǎn)分別對(duì)應(yīng)三個(gè) Vertex、兩個(gè) Vertex、一個(gè) Vertex。這個(gè)階段會(huì)將 Vertex 連接成相對(duì)應(yīng)的形狀。

  • 幾何著色器(Geometry Shader):額外添加額外的Vertex,將原始圖元轉(zhuǎn)換成新圖元,以構(gòu)建一個(gè)不一樣的模型。簡(jiǎn)單來(lái)說(shuō)就是基于通過(guò)三角形、線段和點(diǎn)構(gòu)建更復(fù)雜的幾何圖形。

Rasterization 光柵化階段:圖元轉(zhuǎn)換為像素

光柵化的主要目的是將幾何渲染之后的圖元信息,轉(zhuǎn)換為一系列的像素,以便后續(xù)顯示在屏幕上。這個(gè)階段中會(huì)根據(jù)圖元信息,計(jì)算出每個(gè)圖元所覆蓋的像素信息等,從而將像素劃分成不同的部分。

iOS渲染原理是什么

一種簡(jiǎn)單的劃分就是根據(jù)中心點(diǎn),如果像素的中心點(diǎn)在圖元內(nèi)部,那么這個(gè)像素就屬于這個(gè)圖元。如上圖所示,深藍(lán)色的線就是圖元信息所構(gòu)建出的三角形;而通過(guò)是否覆蓋中心點(diǎn),可以遍歷出所有屬于該圖元的所有像素,即淺藍(lán)色部分。

Pixel 像素處理階段:處理像素,得到位圖

經(jīng)過(guò)上述光柵化階段,我們得到了圖元所對(duì)應(yīng)的像素,此時(shí),我們需要給這些像素填充顏色和效果。所以最后這個(gè)階段就是給像素填充正確的內(nèi)容,最終顯示在屏幕上。這些經(jīng)過(guò)處理、蘊(yùn)含大量信息的像素點(diǎn)集合,被稱(chēng)作位圖(bitmap)。也就是說(shuō),Pixel 階段最終輸出的結(jié)果就是位圖,過(guò)程具體包含:

這些點(diǎn)可以進(jìn)行不同的排列和染色以構(gòu)成圖樣。當(dāng)放大位圖時(shí),可以看見(jiàn)賴(lài)以構(gòu)成整個(gè)圖像的無(wú)數(shù)單個(gè)方塊。只要有足夠多的不同色彩的像素,就可以制作出色彩豐富的圖象,逼真地表現(xiàn)自然界的景象??s放和旋轉(zhuǎn)容易失真,同時(shí)文件容量較大。

  • 片段著色器(Fragment Shader):也叫做 Pixel Shader,這個(gè)階段的目的是給每一個(gè)像素 Pixel 賦予正確的顏色。顏色的來(lái)源就是之前得到的頂點(diǎn)、紋理、光照等信息。由于需要處理紋理、光照等復(fù)雜信息,所以這通常是整個(gè)系統(tǒng)的性能瓶頸。

  • 測(cè)試與混合(Tests and Blending):也叫做 Merging 階段,這個(gè)階段主要處理片段的前后位置以及透明度。這個(gè)階段會(huì)檢測(cè)各個(gè)著色片段的深度值 z 坐標(biāo),從而判斷片段的前后位置,以及是否應(yīng)該被舍棄。同時(shí)也會(huì)計(jì)算相應(yīng)的透明度 alpha 值,從而進(jìn)行片段的混合,得到最終的顏色。

2. 屏幕成像與卡頓

在圖像渲染流程結(jié)束之后,接下來(lái)就需要將得到的像素信息顯示在物理屏幕上了。GPU 最后一步渲染結(jié)束之后像素信息,被存在幀緩沖器(Framebuffer)中,之后視頻控制器(Video Controller)會(huì)讀取幀緩沖器中的信息,經(jīng)過(guò)數(shù)模轉(zhuǎn)換傳遞給顯示器(Monitor),進(jìn)行顯示。完整的流程如下圖所示:

iOS渲染原理是什么

經(jīng)過(guò) GPU 處理之后的像素集合,也就是位圖,會(huì)被幀緩沖器緩存起來(lái),供之后的顯示使用。顯示器的電子束會(huì)從屏幕的左上角開(kāi)始逐行掃描,屏幕上的每個(gè)點(diǎn)的圖像信息都從幀緩沖器中的位圖進(jìn)行讀取,在屏幕上對(duì)應(yīng)地顯示。掃描的流程如下圖所示:

iOS渲染原理是什么

電子束掃描的過(guò)程中,屏幕就能呈現(xiàn)出對(duì)應(yīng)的結(jié)果,每次整個(gè)屏幕被掃描完一次后,就相當(dāng)于呈現(xiàn)了一幀完整的圖像。屏幕不斷地刷新,不停呈現(xiàn)新的幀,就能呈現(xiàn)出連續(xù)的影像。而這個(gè)屏幕刷新的頻率,就是幀率(Frame per Second,F(xiàn)PS)。由于人眼的視覺(jué)暫留效應(yīng),當(dāng)屏幕刷新頻率足夠高時(shí)(FPS 通常是 50 到 60 左右),就能讓畫(huà)面看起來(lái)是連續(xù)而流暢的。對(duì)于 iOS 而言,app 應(yīng)該盡量保證 60 FPS 才是最好的體驗(yàn)。

屏幕撕裂 Screen Tearing

在這種單一緩存的模式下,最理想的情況就是一個(gè)流暢的流水線:每次電子束從頭開(kāi)始新的一幀的掃描時(shí),CPU+GPU 對(duì)于該幀的渲染流程已經(jīng)結(jié)束,渲染好的位圖已經(jīng)放入幀緩沖器中。但這種完美的情況是非常脆弱的,很容易產(chǎn)生屏幕撕裂:

iOS渲染原理是什么

CPU+GPU 的渲染流程是一個(gè)非常耗時(shí)的過(guò)程。如果在電子束開(kāi)始掃描新的一幀時(shí),位圖還沒(méi)有渲染好,而是在掃描到屏幕中間時(shí)才渲染完成,被放入幀緩沖器中 —— 那么已掃描的部分就是上一幀的畫(huà)面,而未掃描的部分則會(huì)顯示新的一幀圖像,這就造成屏幕撕裂。

垂直同步 Vsync + 雙緩沖機(jī)制 Double Buffering

解決屏幕撕裂、提高顯示效率的一個(gè)策略就是使用垂直同步信號(hào) Vsync 與雙緩沖機(jī)制 Double Buffering。根據(jù)蘋(píng)果的官方文檔描述,iOS 設(shè)備會(huì)始終使用 Vsync + Double Buffering 的策略。

垂直同步信號(hào)(vertical synchronisation,Vsync)相當(dāng)于給幀緩沖器加鎖:當(dāng)電子束完成一幀的掃描,將要從頭開(kāi)始掃描時(shí),就會(huì)發(fā)出一個(gè)垂直同步信號(hào)。只有當(dāng)視頻控制器接收到 Vsync 之后,才會(huì)將幀緩沖器中的位圖更新為下一幀,這樣就能保證每次顯示的都是同一幀的畫(huà)面,因而避免了屏幕撕裂。

但是這種情況下,視頻控制器在接受到 Vsync 之后,就要將下一幀的位圖傳入,這意味著整個(gè) CPU+GPU 的渲染流程都要在一瞬間完成,這是明顯不現(xiàn)實(shí)的。所以雙緩沖機(jī)制會(huì)增加一個(gè)新的備用緩沖器(back buffer)。渲染結(jié)果會(huì)預(yù)先保存在 back buffer 中,在接收到 Vsync 信號(hào)的時(shí)候,視頻控制器會(huì)將 back buffer 中的內(nèi)容置換到 frame buffer 中,此時(shí)就能保證置換操作幾乎在一瞬間完成(實(shí)際上是交換了內(nèi)存地址)。

iOS渲染原理是什么

掉幀 Jank

啟用 Vsync 信號(hào)以及雙緩沖機(jī)制之后,能夠解決屏幕撕裂的問(wèn)題,但是會(huì)引入新的問(wèn)題:掉幀。如果在接收到 Vsync 之時(shí) CPU 和 GPU 還沒(méi)有渲染好新的位圖,視頻控制器就不會(huì)去替換 frame buffer 中的位圖。這時(shí)屏幕就會(huì)重新掃描呈現(xiàn)出上一幀一模一樣的畫(huà)面。相當(dāng)于兩個(gè)周期顯示了同樣的畫(huà)面,這就是所謂掉幀的情況。

iOS渲染原理是什么
如圖所示,A、B 代表兩個(gè)幀緩沖器,當(dāng) B 沒(méi)有渲染完畢時(shí)就接收到了 Vsync 信號(hào),所以屏幕只能再顯示相同幀 A,這就發(fā)生了第一次的掉幀。

三緩沖 Triple Buffering

事實(shí)上上述策略還有優(yōu)化空間。我們注意到在發(fā)生掉幀的時(shí)候,CPU 和 GPU 有一段時(shí)間處于閑置狀態(tài):當(dāng) A 的內(nèi)容正在被掃描顯示在屏幕上,而 B 的內(nèi)容已經(jīng)被渲染好,此時(shí) CPU 和 GPU 就處于閑置狀態(tài)。那么如果我們?cè)黾右粋€(gè)幀緩沖器,就可以利用這段時(shí)間進(jìn)行下一步的渲染,并將渲染結(jié)果暫存于新增的幀緩沖器中。

iOS渲染原理是什么

如圖所示,由于增加了新的幀緩沖器,可以一定程度上地利用掉幀的空檔期,合理利用 CPU 和 GPU 性能,從而減少掉幀的次數(shù)。

屏幕卡頓的本質(zhì)

手機(jī)使用卡頓的直接原因,就是掉幀。前文也說(shuō)過(guò),屏幕刷新頻率必須要足夠高才能流暢。對(duì)于 iPhone 手機(jī)來(lái)說(shuō),屏幕最大的刷新頻率是 60 FPS,一般只要保證 50 FPS 就已經(jīng)是較好的體驗(yàn)了。但是如果掉幀過(guò)多,導(dǎo)致刷新頻率過(guò)低,就會(huì)造成不流暢的使用體驗(yàn)。

這樣看來(lái),可以大概總結(jié)一下

  • 屏幕卡頓的根本原因:CPU 和 GPU 渲染流水線耗時(shí)過(guò)長(zhǎng),導(dǎo)致掉幀。

  • Vsync 與雙緩沖的意義:強(qiáng)制同步屏幕刷新,以掉幀為代價(jià)解決屏幕撕裂問(wèn)題。

  • 三緩沖的意義:合理使用 CPU、GPU 渲染性能,減少掉幀次數(shù)。

3. iOS 中的渲染框架

iOS渲染原理是什么

iOS 的渲染框架依然符合渲染流水線的基本架構(gòu),具體的技術(shù)棧如上圖所示。在硬件基礎(chǔ)之上,iOS 中有 Core Graphics、Core Animation、Core Image、OpenGL 等多種軟件框架來(lái)繪制內(nèi)容,在 CPU 與 GPU 之間進(jìn)行了更高層地封裝。

GPU Driver:上述軟件框架相互之間也有著依賴(lài)關(guān)系,不過(guò)所有框架最終都會(huì)通過(guò) OpenGL 連接到 GPU Driver,GPU Driver 是直接和 GPU 交流的代碼塊,直接與 GPU 連接。

OpenGL:是一個(gè)提供了 2D 和 3D 圖形渲染的 API,它能和 GPU 密切的配合,最高效地利用 GPU 的能力,實(shí)現(xiàn)硬件加速渲染。OpenGL的高效實(shí)現(xiàn)(利用了圖形加速硬件)一般由顯示設(shè)備廠商提供,而且非常依賴(lài)于該廠商提供的硬件。OpenGL 之上擴(kuò)展出很多東西,如 Core Graphics 等最終都依賴(lài)于 OpenGL,有些情況下為了更高的效率,比如游戲程序,甚至?xí)苯诱{(diào)用 OpenGL 的接口。

Core Graphics:Core Graphics 是一個(gè)強(qiáng)大的二維圖像繪制引擎,是 iOS 的核心圖形庫(kù),常用的比如 CGRect 就定義在這個(gè)框架下。

Core Animation:在 iOS 上,幾乎所有的東西都是通過(guò) Core Animation 繪制出來(lái),它的自由度更高,使用范圍也更廣。

Core Image:Core Image 是一個(gè)高性能的圖像處理分析的框架,它擁有一系列現(xiàn)成的圖像濾鏡,能對(duì)已存在的圖像進(jìn)行高效的處理。

Metal:Metal 類(lèi)似于 OpenGL ES,也是一套第三方標(biāo)準(zhǔn),具體實(shí)現(xiàn)由蘋(píng)果實(shí)現(xiàn)。Core Animation、Core Image、SceneKit、SpriteKit 等等渲染框架都是構(gòu)建于 Metal 之上的。

Core Animation 是什么

Render, compose, and animate visual elements. —— Apple

Core Animation,它本質(zhì)上可以理解為一個(gè)復(fù)合引擎,主要職責(zé)包含:渲染、構(gòu)建和實(shí)現(xiàn)動(dòng)畫(huà)。

通常我們會(huì)使用 Core Animation 來(lái)高效、方便地實(shí)現(xiàn)動(dòng)畫(huà),但是實(shí)際上它的前身叫做 Layer Kit,關(guān)于動(dòng)畫(huà)實(shí)現(xiàn)只是它功能中的一部分。對(duì)于 iOS app,不論是否直接使用了 Core Animation,它都在底層深度參與了 app 的構(gòu)建。而對(duì)于 OS X app,也可以通過(guò)使用 Core Animation 方便地實(shí)現(xiàn)部分功能。

iOS渲染原理是什么

Core Animation 是 AppKit 和 UIKit 完美的底層支持,同時(shí)也被整合進(jìn)入 Cocoa 和 Cocoa Touch 的工作流之中,它是 app 界面渲染和構(gòu)建的最基礎(chǔ)架構(gòu)。Core Animation 的職責(zé)就是盡可能快地組合屏幕上不同的可視內(nèi)容,這個(gè)內(nèi)容是被分解成獨(dú)立的 layer(iOS 中具體而言就是 CALayer),并且被存儲(chǔ)為樹(shù)狀層級(jí)結(jié)構(gòu)。這個(gè)樹(shù)也形成了 UIKit 以及在 iOS 應(yīng)用程序當(dāng)中你所能在屏幕上看見(jiàn)的一切的基礎(chǔ)。

簡(jiǎn)單來(lái)說(shuō)就是用戶(hù)能看到的屏幕上的內(nèi)容都由 CALayer 進(jìn)行管理。那么 CALayer 究竟是如何進(jìn)行管理的呢?另外在 iOS 開(kāi)發(fā)過(guò)程中,最大量使用的視圖控件實(shí)際上是 UIView 而不是 CALayer,那么他們兩者的關(guān)系到底如何呢?

CALayer 是顯示的基礎(chǔ):存儲(chǔ) bitmap

簡(jiǎn)單理解,CALayer 就是屏幕顯示的基礎(chǔ)。那 CALayer 是如何完成的呢?讓我們來(lái)從源碼向下探索一下,在 CALayer.h 中,CALayer 有這樣一個(gè)屬性 contents:

/** Layer content properties and methods. **//* An object providing the contents of the layer, typically a CGImageRef, * but may be something else. (For example, NSImage objects are * supported on Mac OS X 10.6 and later.) Default value is nil. * Animatable. */@property(nullable, strong) id contents;

An object providing the contents of the layer, typically a CGImageRef.

contents 提供了 layer 的內(nèi)容,是一個(gè)指針類(lèi)型,在 iOS 中的類(lèi)型就是 CGImageRef(在 OS X 中還可以是 NSImage)。而我們進(jìn)一步查到,Apple 對(duì) CGImageRef 的定義是:

A bitmap image or image mask.

看到 bitmap,這下我們就可以和之前講的的渲染流水線聯(lián)系起來(lái)了:實(shí)際上,CALayer 中的 contents 屬性保存了由設(shè)備渲染流水線渲染好的位圖 bitmap(通常也被稱(chēng)為 backing store),而當(dāng)設(shè)備屏幕進(jìn)行刷新時(shí),會(huì)從 CALayer 中讀取生成好的 bitmap,進(jìn)而呈現(xiàn)到屏幕上。

所以,如果我們?cè)诖a中對(duì) CALayer 的 contents 屬性進(jìn)行了設(shè)置,比如這樣:

// 注意 CGImage 和 CGImageRef 的關(guān)系:// typedef struct CGImage CGImageRef;layer.contents = (__bridge id)image.CGImage;**

那么在運(yùn)行時(shí),操作系統(tǒng)會(huì)調(diào)用底層的接口,將 image 通過(guò) CPU+GPU 的渲染流水線渲染得到對(duì)應(yīng)的 bitmap,存儲(chǔ)于 CALayer.contents 中,在設(shè)備屏幕進(jìn)行刷新的時(shí)候就會(huì)讀取 bitmap 在屏幕上呈現(xiàn)。

也正因?yàn)槊看我讳秩镜膬?nèi)容是被靜態(tài)的存儲(chǔ)起來(lái)的,所以每次渲染時(shí),Core Animation 會(huì)觸發(fā)調(diào)用 drawRect: 方法,使用存儲(chǔ)好的 bitmap 進(jìn)行新一輪的展示。

CALayer 與 UIView 的關(guān)系

UIView 作為最常用的視圖控件,和 CALayer 也有著千絲萬(wàn)縷的聯(lián)系,那么兩者之間到底是個(gè)什么關(guān)系,他們有什么差異?

當(dāng)然,兩者有很多顯性的區(qū)別,比如是否能夠響應(yīng)點(diǎn)擊事件。但為了從根本上徹底搞懂這些問(wèn)題,我們必須要先搞清楚兩者的職責(zé)。

UIView - Apple

Views are the fundamental building blocks of your app’s user interface, and the UIView class defines the behaviors that are common to all views. A view object renders content within its bounds rectangle and handles any interactions with that content.

根據(jù) Apple 的官方文檔,UIView 是 app 中的基本組成結(jié)構(gòu),定義了一些統(tǒng)一的規(guī)范。它會(huì)負(fù)責(zé)內(nèi)容的渲染以及,處理交互事件。具體而言,它負(fù)責(zé)的事情可以歸為下面三類(lèi)

  • Drawing and animation:繪制與動(dòng)畫(huà)

  • Layout and subview management:布局與子 view 的管理

  • Event handling:點(diǎn)擊事件處理

CALayer - Apple

Layers are often used to provide the backing store for views but can also be used without a view to display content. A layer’s main job is to manage the visual content that you provide…

If the layer object was created by a view, the view typically assigns itself as the layer’s delegate automatically, and you should not change that relationship.

而從 CALayer 的官方文檔中我們可以看出,CALayer 的主要職責(zé)是管理內(nèi)部的可視內(nèi)容,這也和我們前文所講的內(nèi)容吻合。當(dāng)我們創(chuàng)建一個(gè) UIView 的時(shí)候,UIView 會(huì)自動(dòng)創(chuàng)建一個(gè) CALayer,為自身提供存儲(chǔ) bitmap 的地方(也就是前文說(shuō)的 backing store),并將自身固定設(shè)置為 CALayer 的代理。

iOS渲染原理是什么

從這兒我們大概總結(jié)出下面兩個(gè)核心關(guān)系

  1. CALayer 是 UIView 的屬性之一,負(fù)責(zé)渲染和動(dòng)畫(huà),提供可視內(nèi)容的呈現(xiàn)。

  2. UIView 提供了對(duì) CALayer 部分功能的封裝,同時(shí)也另外負(fù)責(zé)了交互事件的處理。

有了這兩個(gè)最關(guān)鍵的根本關(guān)系,那么下面這些經(jīng)常出現(xiàn)在面試答案里的顯性的異同就很好解釋了。舉幾個(gè)例子:

  • 相同的層級(jí)結(jié)構(gòu):我們對(duì) UIView 的層級(jí)結(jié)構(gòu)非常熟悉,由于每個(gè) UIView 都對(duì)應(yīng) CALayer 負(fù)責(zé)頁(yè)面的繪制,所以 CALayer 也具有相應(yīng)的層級(jí)結(jié)構(gòu)。

  • 部分效果的設(shè)置:因?yàn)?UIView 只對(duì) CALayer 的部分功能進(jìn)行了封裝,而另一部分如圓角、陰影、邊框等特效都需要通過(guò)調(diào)用 layer 屬性來(lái)設(shè)置。

  • 是否響應(yīng)點(diǎn)擊事件:CALayer 不負(fù)責(zé)點(diǎn)擊事件,所以不響應(yīng)點(diǎn)擊事件,而 UIView 會(huì)響應(yīng)。

  • 不同繼承關(guān)系:CALayer 繼承自 NSObject,UIView 由于要負(fù)責(zé)交互事件,所以繼承自 UIResponder。

當(dāng)然還剩最后一個(gè)問(wèn)題,為什么要將 CALayer 獨(dú)立出來(lái),直接使用 UIView 統(tǒng)一管理不行嗎?為什么不用一個(gè)統(tǒng)一的對(duì)象來(lái)處理所有事情呢?

這樣設(shè)計(jì)的主要原因就是為了職責(zé)分離,拆分功能,方便代碼的復(fù)用。通過(guò) Core Animation 框架來(lái)負(fù)責(zé)可視內(nèi)容的呈現(xiàn),這樣在 iOS 和 OS X 上都可以使用 Core Animation 進(jìn)行渲染。與此同時(shí),兩個(gè)系統(tǒng)還可以根據(jù)交互規(guī)則的不同來(lái)進(jìn)一步封裝統(tǒng)一的控件,比如 iOS 有 UIKit 和 UIView,OS X 則是AppKit 和 NSView。

4. Core Animation 渲染全內(nèi)容

Core Animation Pipeline 渲染流水線

當(dāng)我們了解了 Core Animation 以及 CALayer 的基本知識(shí)后,接下來(lái)我們來(lái)看下 Core Animation 的渲染流水線。

iOS渲染原理是什么

整個(gè)流水線一共有下面幾個(gè)步驟:

Handle Events:這個(gè)過(guò)程中會(huì)先處理點(diǎn)擊事件,這個(gè)過(guò)程中有可能會(huì)需要改變頁(yè)面的布局和界面層次。

Commit Transaction:此時(shí) app 會(huì)通過(guò) CPU 處理顯示內(nèi)容的前置計(jì)算,比如布局計(jì)算、圖片解碼等任務(wù),接下來(lái)會(huì)進(jìn)行詳細(xì)的講解。之后將計(jì)算好的圖層進(jìn)行打包發(fā)給 Render Server。

Decode:打包好的圖層被傳輸?shù)? Render Server 之后,首先會(huì)進(jìn)行解碼。注意完成解碼之后需要等待下一個(gè) RunLoop 才會(huì)執(zhí)行下一步 Draw Calls。

Draw Calls:解碼完成后,Core Animation 會(huì)調(diào)用下層渲染框架(比如 OpenGL 或者 Metal)的方法進(jìn)行繪制,進(jìn)而調(diào)用到 GPU。

Render:這一階段主要由 GPU 進(jìn)行渲染。

Display:顯示階段,需要等 render 結(jié)束的下一個(gè) RunLoop 觸發(fā)顯示。

Commit Transaction 發(fā)生了什么

一般開(kāi)發(fā)當(dāng)中能影響到的就是 Handle Events 和 Commit Transaction 這兩個(gè)階段,這也是開(kāi)發(fā)者接觸最多的部分。Handle Events 就是處理觸摸事件,而 Commit Transaction 這部分中主要進(jìn)行的是:Layout、Display、Prepare、Commit 等四個(gè)具體的操作。

Layout:構(gòu)建視圖

這個(gè)階段主要處理視圖的構(gòu)建和布局,具體步驟包括:

  1. 調(diào)用重載的 layoutSubviews 方法

  2. 創(chuàng)建視圖,并通過(guò) addSubview 方法添加子視圖

  3. 計(jì)算視圖布局,即所有的 Layout Constraint

由于這個(gè)階段是在 CPU 中進(jìn)行,通常是 CPU 限制或者 IO 限制,所以我們應(yīng)該盡量高效輕量地操作,減少這部分的時(shí)間,比如減少非必要的視圖創(chuàng)建、簡(jiǎn)化布局計(jì)算、減少視圖層級(jí)等。

Display:繪制視圖

這個(gè)階段主要是交給 Core Graphics 進(jìn)行視圖的繪制,注意不是真正的顯示,而是得到前文所說(shuō)的圖元 primitives 數(shù)據(jù):

  1. 根據(jù)上一階段 Layout 的結(jié)果創(chuàng)建得到圖元信息。

  2. 如果重寫(xiě)了 drawRect: 方法,那么會(huì)調(diào)用重載的 drawRect: 方法,在 drawRect: 方法中手動(dòng)繪制得到 bitmap 數(shù)據(jù),從而自定義視圖的繪制。

注意正常情況下 Display 階段只會(huì)得到圖元 primitives 信息,而位圖 bitmap 是在 GPU 中根據(jù)圖元信息繪制得到的。但是如果重寫(xiě)了 drawRect: 方法,這個(gè)方法會(huì)直接調(diào)用 Core Graphics 繪制方法得到 bitmap 數(shù)據(jù),同時(shí)系統(tǒng)會(huì)額外申請(qǐng)一塊內(nèi)存,用于暫存繪制好的 bitmap。

由于重寫(xiě)了  drawRect: 方法,導(dǎo)致繪制過(guò)程從 GPU 轉(zhuǎn)移到了 CPU,這就導(dǎo)致了一定的效率損失。與此同時(shí),這個(gè)過(guò)程會(huì)額外使用 CPU 和內(nèi)存,因此需要高效繪制,否則容易造成 CPU 卡頓或者內(nèi)存爆炸。

Prepare:Core Animation 額外的工作

這一步主要是:圖片解碼和轉(zhuǎn)換

Commit:打包并發(fā)送

這一步主要是:圖層打包并發(fā)送到 Render Server。

注意 commit 操作是依賴(lài)圖層樹(shù)遞歸執(zhí)行的,所以如果圖層樹(shù)過(guò)于復(fù)雜,commit 的開(kāi)銷(xiāo)就會(huì)很大。這也是我們希望減少視圖層級(jí),從而降低圖層樹(shù)復(fù)雜度的原因。

Rendering Pass:Render Server 的具體操作

iOS渲染原理是什么

Render Server 通常是 OpenGL 或者是 Metal。以 OpenGL 為例,那么上圖主要是 GPU 中執(zhí)行的操作,具體主要包括:

  1. GPU 收到 Command Buffer,包含圖元 primitives 信息

  2. Tiler 開(kāi)始工作:先通過(guò)頂點(diǎn)著色器 Vertex Shader 對(duì)頂點(diǎn)進(jìn)行處理,更新圖元信息

  3. 平鋪過(guò)程:平鋪生成 tile bucket 的幾何圖形,這一步會(huì)將圖元信息轉(zhuǎn)化為像素,之后將結(jié)果寫(xiě)入 Parameter Buffer 中

  4. Tiler 更新完所有的圖元信息,或者 Parameter Buffer 已滿(mǎn),則會(huì)開(kāi)始下一步

  5. Renderer 工作:將像素信息進(jìn)行處理得到 bitmap,之后存入 Render Buffer

  6. Render Buffer 中存儲(chǔ)有渲染好的 bitmap,供之后的 Display 操作使用

使用 Instrument 的 OpenGL ES,可以對(duì)過(guò)程進(jìn)行監(jiān)控。OpenGL ES tiler utilization 和 OpenGL ES renderer utilization 可以分別監(jiān)控 Tiler 和 Renderer 的工作情況

5. Offscreen Rendering 離屏渲染

離屏渲染作為一個(gè)面試高頻問(wèn)題,時(shí)常被提及,下面來(lái)從頭到尾講一下離屏渲染。

離屏渲染具體過(guò)程

根據(jù)前文,簡(jiǎn)化來(lái)看,通常的渲染流程是這樣的:

iOS渲染原理是什么

App 通過(guò) CPU 和 GPU 的合作,不停地將內(nèi)容渲染完成放入 Framebuffer 幀緩沖器中,而顯示屏幕不斷地從 Framebuffer 中獲取內(nèi)容,顯示實(shí)時(shí)的內(nèi)容。

而離屏渲染的流程是這樣的:

iOS渲染原理是什么

與普通情況下 GPU 直接將渲染好的內(nèi)容放入 Framebuffer 中不同,需要先額外創(chuàng)建離屏渲染緩沖區(qū) Offscreen Buffer,將提前渲染好的內(nèi)容放入其中,等到合適的時(shí)機(jī)再將 Offscreen Buffer 中的內(nèi)容進(jìn)一步疊加、渲染,完成后將結(jié)果切換到 Framebuffer 中。

離屏渲染的效率問(wèn)題

從上面的流程來(lái)看,離屏渲染時(shí)由于 App 需要提前對(duì)部分內(nèi)容進(jìn)行額外的渲染并保存到 Offscreen Buffer,以及需要在必要時(shí)刻對(duì) Offscreen Buffer 和 Framebuffer 進(jìn)行內(nèi)容切換,所以會(huì)需要更長(zhǎng)的處理時(shí)間(實(shí)際上這兩步關(guān)于 buffer 的切換代價(jià)都非常大)。

并且 Offscreen Buffer 本身就需要額外的空間,大量的離屏渲染可能早能內(nèi)存的過(guò)大壓力。與此同時(shí),Offscreen Buffer 的總大小也有限,不能超過(guò)屏幕總像素的 2.5 倍。

可見(jiàn)離屏渲染的開(kāi)銷(xiāo)非常大,一旦需要離屏渲染的內(nèi)容過(guò)多,很容易造成掉幀的問(wèn)題。所以大部分情況下,我們都應(yīng)該盡量避免離屏渲染。

為什么使用離屏渲染

那么為什么要使用離屏渲染呢?主要是因?yàn)橄旅孢@兩種原因:

  1. 一些特殊效果需要使用額外的 Offscreen Buffer 來(lái)保存渲染的中間狀態(tài),所以不得不使用離屏渲染。

  2. 處于效率目的,可以將內(nèi)容提前渲染保存在 Offscreen Buffer 中,達(dá)到復(fù)用的目的。

對(duì)于第一種情況,也就是不得不使用離屏渲染的情況,一般都是系統(tǒng)自動(dòng)觸發(fā)的,比如陰影、圓角等等。

最常見(jiàn)的情形之一就是:使用了 mask 蒙版。

iOS渲染原理是什么

如圖所示,由于最終的內(nèi)容是由兩層渲染結(jié)果疊加,所以必須要利用額外的內(nèi)存空間對(duì)中間的渲染結(jié)果進(jìn)行保存,因此系統(tǒng)會(huì)默認(rèn)觸發(fā)離屏渲染。

又比如下面這個(gè)例子,iOS 8 開(kāi)始提供的模糊特效 UIBlurEffectView:

iOS渲染原理是什么

整個(gè)模糊過(guò)程分為多步:Pass 1 先渲染需要模糊的內(nèi)容本身,Pass 2 對(duì)內(nèi)容進(jìn)行縮放,Pass 3 4 分別對(duì)上一步內(nèi)容進(jìn)行橫縱方向的模糊操作,最后一步用模糊后的結(jié)果疊加合成,最終實(shí)現(xiàn)完整的模糊特效。

而第二種情況,為了復(fù)用提高效率而使用離屏渲染一般是主動(dòng)的行為,是通過(guò) CALayer 的 shouldRasterize 光柵化操作實(shí)現(xiàn)的。

shouldRasterize 光柵化

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.

開(kāi)啟光柵化后,會(huì)觸發(fā)離屏渲染,Render Server 會(huì)強(qiáng)制將 CALayer 的渲染位圖結(jié)果 bitmap 保存下來(lái),這樣下次再需要渲染時(shí)就可以直接復(fù)用,從而提高效率。

而保存的 bitmap 包含 layer 的 subLayer、圓角、陰影、組透明度 group opacity 等,所以如果 layer 的構(gòu)成包含上述幾種元素,結(jié)構(gòu)復(fù)雜且需要反復(fù)利用,那么就可以考慮打開(kāi)光柵化。

圓角、陰影、組透明度等會(huì)由系統(tǒng)自動(dòng)觸發(fā)離屏渲染,那么打開(kāi)光柵化可以節(jié)約第二次及以后的渲染時(shí)間。而多層 subLayer 的情況由于不會(huì)自動(dòng)觸發(fā)離屏渲染,所以相比之下會(huì)多花費(fèi)第一次離屏渲染的時(shí)間,但是可以節(jié)約后續(xù)的重復(fù)渲染的開(kāi)銷(xiāo)。

不過(guò)使用光柵化的時(shí)候需要注意以下幾點(diǎn):

  1. 如果 layer 不能被復(fù)用,則沒(méi)有必要打開(kāi)光柵化

  2. 如果 layer 不是靜態(tài),需要被頻繁修改,比如處于動(dòng)畫(huà)之中,那么開(kāi)啟離屏渲染反而影響效率

  3. 離屏渲染緩存內(nèi)容有時(shí)間限制,緩存內(nèi)容 100ms 內(nèi)如果沒(méi)有被使用,那么就會(huì)被丟棄,無(wú)法進(jìn)行復(fù)用

  4. 離屏渲染緩存空間有限,超過(guò) 2.5 倍屏幕像素大小的話(huà)也會(huì)失效,無(wú)法復(fù)用

圓角的離屏渲染

通常來(lái)講,設(shè)置了 layer 的圓角效果之后,會(huì)自動(dòng)觸發(fā)離屏渲染。但是究竟什么情況下設(shè)置圓角才會(huì)觸發(fā)離屏渲染呢?

iOS渲染原理是什么

如上圖所示,layer 由三層組成,我們?cè)O(shè)置圓角通常會(huì)首先像下面這行代碼一樣進(jìn)行設(shè)置:

view.layer.cornerRadius = 2

根據(jù) cornerRadius - Apple 的描述,上述代碼只會(huì)默認(rèn)設(shè)置 backgroundColor 和 border 的圓角,而不會(huì)設(shè)置 content 的圓角,除非同時(shí)設(shè)置了 layer.masksToBounds 為 true(對(duì)應(yīng) UIView 的 clipsToBounds 屬性):

Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.

如果只是設(shè)置了 cornerRadius 而沒(méi)有設(shè)置 masksToBounds,由于不需要疊加裁剪,此時(shí)是并不會(huì)觸發(fā)離屏渲染的。而當(dāng)設(shè)置了裁剪屬性的時(shí)候,由于 masksToBounds 會(huì)對(duì) layer 以及所有 subLayer 的 content 都進(jìn)行裁剪,所以不得不觸發(fā)離屏渲染。

view.layer.masksToBounds = true // 觸發(fā)離屏渲染的原因

所以,Texture 也提出在沒(méi)有必要使用圓角裁剪的時(shí)候,盡量不去觸發(fā)離屏渲染而影響效率:

iOS渲染原理是什么

離屏渲染的具體邏輯

剛才說(shuō)了圓角加上 masksToBounds 的時(shí)候,因?yàn)?masksToBounds 會(huì)對(duì) layer 上的所有內(nèi)容進(jìn)行裁剪,從而誘發(fā)了離屏渲染,那么這個(gè)過(guò)程具體是怎么回事呢,下面我們來(lái)仔細(xì)講一下。

圖層的疊加繪制大概遵循“畫(huà)家算法”,在這種算法下會(huì)按層繪制,首先繪制距離較遠(yuǎn)的場(chǎng)景,然后用繪制距離較近的場(chǎng)景覆蓋較遠(yuǎn)的部分。

iOS渲染原理是什么

在普通的 layer 繪制中,上層的 sublayer 會(huì)覆蓋下層的 sublayer,下層 sublayer 繪制完之后就可以?huà)仐壛?,從而?jié)約空間提高效率。所有 sublayer 依次繪制完畢之后,整個(gè)繪制過(guò)程完成,就可以進(jìn)行后續(xù)的呈現(xiàn)了。假設(shè)我們需要繪制一個(gè)三層的 sublayer,不設(shè)置裁剪和圓角,那么整個(gè)繪制過(guò)程就如下圖所示:

iOS渲染原理是什么

而當(dāng)我們?cè)O(shè)置了 cornerRadius 以及 masksToBounds 進(jìn)行圓角 + 裁剪時(shí),如前文所述,masksToBounds 裁剪屬性會(huì)應(yīng)用到所有的 sublayer 上。這也就意味著所有的 sublayer 必須要重新被應(yīng)用一次圓角+裁剪,這也就意味著所有的 sublayer 在第一次被繪制完之后,并不能立刻被丟棄,而必須要被保存在 Offscreen buffer 中等待下一輪圓角+裁剪,這也就誘發(fā)了離屏渲染,具體過(guò)程如下:

iOS渲染原理是什么

實(shí)際上不只是圓角+裁剪,如果設(shè)置了透明度+組透明(layer.allowsGroupOpacity+layer.opacity),陰影屬性(shadowOffset 等)都會(huì)產(chǎn)生類(lèi)似的效果,因?yàn)榻M透明度、陰影都是和裁剪類(lèi)似的,會(huì)作用與 layer 以及其所有 sublayer 上,這就導(dǎo)致必然會(huì)引起離屏渲染。

避免圓角離屏渲染

除了盡量減少圓角裁剪的使用,還有什么別的辦法可以避免圓角+裁剪引起的離屏渲染嗎?

由于剛才我們提到,圓角引起離屏渲染的本質(zhì)是裁剪的疊加,導(dǎo)致 masksToBounds 對(duì) layer 以及所有 sublayer 進(jìn)行二次處理。那么我們只要避免使用 masksToBounds 進(jìn)行二次處理,而是對(duì)所有的 sublayer 進(jìn)行預(yù)處理,就可以只進(jìn)行“畫(huà)家算法”,用一次疊加就完成繪制。

那么可行的實(shí)現(xiàn)方法大概有下面幾種:

  1. 【換資源】直接使用帶圓角的圖片,或者替換背景色為帶圓角的純色背景圖,從而避免使用圓角裁剪。不過(guò)這種方法需要依賴(lài)具體情況,并不通用。

  2. 【mask】再增加一個(gè)和背景色相同的遮罩 mask 覆蓋在最上層,蓋住四個(gè)角,營(yíng)造出圓角的形狀。但這種方式難以解決背景色為圖片或漸變色的情況。

  3. 【UIBezierPath】用貝塞爾曲線繪制閉合帶圓角的矩形,在上下文中設(shè)置只有內(nèi)部可見(jiàn),再將不帶圓角的 layer 渲染成圖片,添加到貝塞爾矩形中。這種方法效率更高,但是 layer 的布局一旦改變,貝塞爾曲線都需要手動(dòng)地重新繪制,所以需要對(duì) frame、color 等進(jìn)行手動(dòng)地監(jiān)聽(tīng)并重繪。

  4. 【CoreGraphics】重寫(xiě) drawRect:,用 CoreGraphics 相關(guān)方法,在需要應(yīng)用圓角時(shí)進(jìn)行手動(dòng)繪制。不過(guò) CoreGraphics 效率也很有限,如果需要多次調(diào)用也會(huì)有效率問(wèn)題。

感謝各位的閱讀,以上就是“iOS渲染原理是什么”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)iOS渲染原理是什么這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

ios
AI