最近在 iOS 開發(fā)中做了較多動(dòng)畫相關(guān)的編程工作更胖。因此想借此機(jī)會(huì)深入了解了一下 iOS 動(dòng)畫及渲染相關(guān)原理咱士。隨著對(duì)相關(guān)方面的深入了解关拒,發(fā)現(xiàn)這里面涉及到從硬件底層到軟件框架等一系列相關(guān)知識(shí)照激。
本文將從相對(duì)底層的角度對(duì)計(jì)算圖形渲染原理進(jìn)行簡(jiǎn)要介紹舌仍,以作為后續(xù)的知識(shí)儲(chǔ)備恭取。
引言
作為程序員泰偿,我們或多或少知道可視化應(yīng)用程序都是由 CPU 和 GPU 協(xié)作執(zhí)行的。那么我們就先來了解一下兩者的基本概念:
- CPU(Central Processing Unit):現(xiàn)代計(jì)算機(jī)的三大核心部分之一蜈垮,作為整個(gè)系統(tǒng)的運(yùn)算和控制單元耗跛。CPU 內(nèi)部的流水線結(jié)構(gòu)使其擁有一定程度的并行計(jì)算能力。
- GPU(Graphics Processing Unit):一種可進(jìn)行繪圖運(yùn)算工作的專用微處理器攒发。GPU 能夠生成 2D/3D 的圖形圖像和視頻调塌,從而能夠支持基于窗口的操作系統(tǒng)、圖形用戶界面晨继、視頻游戲烟阐、可視化圖像應(yīng)用和視頻播放。GPU 具有非常強(qiáng)的并行計(jì)算能力紊扬。
這時(shí)候可能會(huì)產(chǎn)生一個(gè)問題:CPU 難道不能代替 GPU 來進(jìn)行圖形渲染嗎蜒茄?答案當(dāng)然是肯定的,不過在看了下面這個(gè)視頻就明白為什么要用 GPU 來進(jìn)行圖形渲染了餐屎。
使用 GPU 渲染圖形的根本原因就是:速度檀葛。GPU 的并行計(jì)算能力使其能夠快速將圖形結(jié)果計(jì)算出來并在屏幕的所有像素中進(jìn)行顯示。
那么像素是如何繪制在屏幕上的腹缩?計(jì)算機(jī)將存儲(chǔ)在內(nèi)存中的形狀轉(zhuǎn)換成實(shí)際繪制在屏幕上的對(duì)應(yīng)的過程稱為 渲染屿聋。渲染過程中最常用的技術(shù)就是 光柵化。
關(guān)于光柵化的概念藏鹊,以下圖為例润讥,假如有一道綠光與存儲(chǔ)在內(nèi)存中的一堆三角形中的某一個(gè)在三維空間坐標(biāo)中存在相交的關(guān)系。那么這些處于相交位置的像素都會(huì)被繪制到屏幕上盘寡。當(dāng)然這些三角形在三維空間中的前后關(guān)系也會(huì)以遮擋或部分遮擋的形式在屏幕上呈現(xiàn)出來楚殿。一句話總結(jié):光柵化就是將數(shù)據(jù)轉(zhuǎn)化成可見像素的過程。
GPU 則是執(zhí)行轉(zhuǎn)換過程的硬件部件竿痰。由于這個(gè)過程涉及到屏幕上的每一個(gè)像素脆粥,所以 GPU 被設(shè)計(jì)成了一個(gè)高度并行化的硬件部件砌溺。
下面,我們來簡(jiǎn)單了解一下 GPU 的歷史变隔。
GPU 歷史
GPU 還未出現(xiàn)前规伐,PC 上的圖形操作是由 視頻圖形陣列(VGA,Video Graphics Array) 控制器完成匣缘。VGA 控制器由連接到一定容量的DRAM上的存儲(chǔ)控制器和顯示產(chǎn)生器構(gòu)成猖闪。
1997 年,VGA 控制器開始具備一些 3D 加速功能孵户,包括用于 三角形生成萧朝、光柵化、紋理貼圖 和 陰影夏哭。
2000 年检柬,一個(gè)單片處圖形處理器繼承了傳統(tǒng)高端工作站圖形流水線的幾乎每一個(gè)細(xì)節(jié)。因此誕生了一個(gè)新的術(shù)語(yǔ) GPU 用來表示圖形設(shè)備已經(jīng)變成了一個(gè)處理器竖配。
隨著時(shí)間的推移何址,GPU 的可編程能力愈發(fā)強(qiáng)大,其作為可編程處理器取代了固定功能的專用邏輯进胯,同時(shí)保持了基本的 3D 圖形流水線組織用爪。
近年來,GPU 增加了處理器指令和存儲(chǔ)器硬件胁镐,以支持通用編程語(yǔ)言偎血,并創(chuàng)立了一種編程環(huán)境,從而允許使用熟悉的語(yǔ)言(包括 C/C++)對(duì) GPU 進(jìn)行編程盯漂。
如今颇玷,GPU 及其相關(guān)驅(qū)動(dòng)實(shí)現(xiàn)了圖形處理中的 OpenGL
和 DirectX
模型,從而允許開發(fā)者能夠輕易地操作硬件就缆。OpenGL
嚴(yán)格來說并不是常規(guī)意義上的 API帖渠,而是一個(gè)第三方標(biāo)準(zhǔn)(由 khronos 組織制定并維護(hù)),其嚴(yán)格定義了每個(gè)函數(shù)該如何執(zhí)行竭宰,以及它們的輸出值空郊。至于每個(gè)函數(shù)內(nèi)部具體是如何實(shí)現(xiàn)的,則由 OpenGL 庫(kù)的開發(fā)者自行決定切揭。實(shí)際 OpenGL 庫(kù)的開發(fā)者通常是顯卡的生產(chǎn)商狞甚。DirectX
則是由 Microsoft 提供一套第三方標(biāo)準(zhǔn)。
GPU 圖形渲染流水線
GPU 圖形渲染流水線的主要工作可以被劃分為兩個(gè)部分:
- 把 3D 坐標(biāo)轉(zhuǎn)換為 2D 坐標(biāo)
- 把 2D 坐標(biāo)轉(zhuǎn)變?yōu)閷?shí)際的有顏色的像素
GPU 圖形渲染流水線的具體實(shí)現(xiàn)可分為六個(gè)階段廓旬,如下圖所示哼审。
- 頂點(diǎn)著色器(Vertex Shader)
- 形狀裝配(Shape Assembly),又稱 圖元裝配
- 幾何著色器(Geometry Shader)
- 光柵化(Rasterization)
- 片段著色器(Fragment Shader)
- 測(cè)試與混合(Tests and Blending)
第一階段,頂點(diǎn)著色器棺蛛。該階段的輸入是 頂點(diǎn)數(shù)據(jù)(Vertex Data) 數(shù)據(jù),比如以數(shù)組的形式傳遞 3 個(gè) 3D 坐標(biāo)用來表示一個(gè)三角形巩步。頂點(diǎn)數(shù)據(jù)是一系列頂點(diǎn)的集合旁赊。頂點(diǎn)著色器主要的目的是把 3D 坐標(biāo)轉(zhuǎn)為另一種 3D 坐標(biāo),同時(shí)頂點(diǎn)著色器可以對(duì)頂點(diǎn)屬性進(jìn)行一些基本處理椅野。
第二階段终畅,形狀(圖元)裝配。該階段將頂點(diǎn)著色器輸出的所有頂點(diǎn)作為輸入竟闪,并將所有的點(diǎn)裝配成指定圖元的形狀离福。圖中則是一個(gè)三角形。圖元(Primitive) 用于表示如何渲染頂點(diǎn)數(shù)據(jù)炼蛤,如:點(diǎn)妖爷、線、三角形理朋。
第三階段絮识,幾何著色器。該階段把圖元形式的一系列頂點(diǎn)的集合作為輸入嗽上,它可以通過產(chǎn)生新頂點(diǎn)構(gòu)造出新的(或是其它的)圖元來生成其他形狀次舌。例子中,它生成了另一個(gè)三角形兽愤。
第四階段彼念,光柵化。該階段會(huì)把圖元映射為最終屏幕上相應(yīng)的像素浅萧,生成片段逐沙。片段(Fragment) 是渲染一個(gè)像素所需要的所有數(shù)據(jù)。
第五階段惯殊,片段著色器酱吝。該階段首先會(huì)對(duì)輸入的片段進(jìn)行 裁切(Clipping)。裁切會(huì)丟棄超出視圖以外的所有像素土思,用來提升執(zhí)行效率务热。
第六階段,測(cè)試與混合己儒。該階段會(huì)檢測(cè)片段的對(duì)應(yīng)的深度值(z
坐標(biāo))崎岂,判斷這個(gè)像素位于其它物體的前面還是后面肆汹,決定是否應(yīng)該丟棄抡柿。此外,該階段還會(huì)檢查 alpha
值( alpha
值定義了一個(gè)物體的透明度)叁鉴,從而對(duì)物體進(jìn)行混合。因此江醇,即使在片段著色器中計(jì)算出來了一個(gè)像素輸出的顏色濒憋,在渲染多個(gè)三角形的時(shí)候最后的像素顏色也可能完全不同。
關(guān)于混合陶夜,GPU 采用如下公式進(jìn)行計(jì)算凛驮,并得出最后的顏色。
R = S + D * (1 - Sa)
關(guān)于公式的含義条辟,假設(shè)有兩個(gè)像素 S(source) 和 D(destination)黔夭,S 在 z
軸方向相對(duì)靠前(在上面),D 在 z
軸方向相對(duì)靠后(在下面)羽嫡,那么最終的顏色值就是 S(上面像素) 的顏色 + D(下面像素) 的顏色 * (1 - S(上面像素) 顏色的透明度)本姥。
上述流水線以繪制一個(gè)三角形為進(jìn)行介紹,可以為每個(gè)頂點(diǎn)添加顏色來增加圖形的細(xì)節(jié)杭棵,從而創(chuàng)建圖像婚惫。但是,如果讓圖形看上去更加真實(shí)魂爪,需要足夠多的頂點(diǎn)和顏色辰妙,相應(yīng)也會(huì)產(chǎn)生更大的開銷。為了提高生產(chǎn)效率和執(zhí)行效率甫窟,開發(fā)者經(jīng)常會(huì)使用 紋理(Texture) 來表現(xiàn)細(xì)節(jié)密浑。紋理是一個(gè) 2D 圖片(甚至也有 1D 和 3D 的紋理)。紋理 一般可以直接作為圖形渲染流水線的第五階段的輸入粗井。
最后尔破,我們還需要知道上述階段中的著色器事實(shí)上是一些程序,它們運(yùn)行在 GPU 中成千上萬(wàn)的小處理器核中浇衬。這些著色器允許開發(fā)者進(jìn)行配置懒构,從而可以高效地控制圖形渲染流水線中的特定部分。由于它們運(yùn)行在 GPU 中耘擂,因此可以降低 CPU 的負(fù)荷胆剧。著色器可以使用多種語(yǔ)言編寫,OpenGL 提供了 GLSL(OpenGL Shading Language) 著色器語(yǔ)言醉冤。
GPU 存儲(chǔ)系統(tǒng)
早期的 GPU秩霍,不同的著色器對(duì)應(yīng)有著不同的硬件單元。如今蚁阳,GPU 流水線則使用一個(gè)統(tǒng)一的硬件來運(yùn)行所有的著色器铃绒。此外,nVidia 還提出了 CUDA(Compute Unified Device Architecture) 編程模型螺捐,可以允許開發(fā)者通過編寫 C 代碼來訪問 GPU 中所有的處理器核颠悬,從而深度挖掘 GPU 的并行計(jì)算能力矮燎。
下圖所示為 GPU 內(nèi)部的層級(jí)結(jié)構(gòu)。最底層是計(jì)算機(jī)的系統(tǒng)內(nèi)存赔癌,其次是 GPU 的內(nèi)部存儲(chǔ)诞外,然后依次是兩級(jí) cache:L2 和 L1,每個(gè) L1 cache 連接至一個(gè) 流處理器(SM灾票,stream processor)浅乔。
- SM L1 Cache 的存儲(chǔ)容量大約為 16 至 64KB。
- GPU L2 Cache 的存儲(chǔ)容量大約為幾百 KB铝条。
- GPU 的內(nèi)存最大為 12GB。
GPU 上的各級(jí)存儲(chǔ)系統(tǒng)與對(duì)應(yīng)層級(jí)的計(jì)算機(jī)存儲(chǔ)系統(tǒng)相比要小不少席噩。
此外班缰,GPU 內(nèi)存并不具有一致性,也就意味著并不支持并發(fā)讀取和并發(fā)寫入悼枢。
GPU 流處理器
下圖所示為 GPU 中每個(gè)流處理器的內(nèi)部結(jié)構(gòu)示意圖埠忘。每個(gè)流處理器集成了一個(gè) L1 Cache。頂部是處理器核共享的寄存器堆馒索。
CPU-GPU 異構(gòu)系統(tǒng)
至此莹妒,我們大致了解了 GPU 的工作原理和內(nèi)部結(jié)構(gòu),那么實(shí)際應(yīng)用中 CPU 和 GPU 又是如何協(xié)同工作的呢绰上?
下圖所示為兩種常見的 CPU-GPU 異構(gòu)架構(gòu)旨怠。
左圖是分離式的結(jié)構(gòu),CPU 和 GPU 擁有各自的存儲(chǔ)系統(tǒng)蜈块,兩者通過 PCI-e 總線進(jìn)行連接鉴腻。這種結(jié)構(gòu)的缺點(diǎn)在于 PCI-e 相對(duì)于兩者具有低帶寬和高延遲,數(shù)據(jù)的傳輸成了其中的性能瓶頸百揭。目前使用非常廣泛爽哎,如PC、智能手機(jī)等器一。
右圖是耦合式的結(jié)構(gòu)课锌,CPU 和 GPU 共享內(nèi)存和緩存。AMD 的 APU 采用的就是這種結(jié)構(gòu)祈秕,目前主要使用在游戲主機(jī)中渺贤,如 PS4。
注意请毛,目前很多 SoC 都是集成了CPU 和 GPU癣亚,事實(shí)上這僅僅是在物理上進(jìn)行了集成,并不意味著它們使用的就是耦合式結(jié)構(gòu)获印,大多數(shù)采用的還是分離式結(jié)構(gòu)述雾。耦合式結(jié)構(gòu)是在系統(tǒng)上進(jìn)行了集成街州。
在存儲(chǔ)管理方面,分離式結(jié)構(gòu)中 CPU 和 GPU 各自擁有獨(dú)立的內(nèi)存玻孟,兩者共享一套虛擬地址空間唆缴,必要時(shí)會(huì)進(jìn)行內(nèi)存拷貝。對(duì)于耦合式結(jié)構(gòu)黍翎,GPU 沒有獨(dú)立的內(nèi)存面徽,與 GPU 共享系統(tǒng)內(nèi)存,由 MMU 進(jìn)行存儲(chǔ)管理匣掸。
圖形應(yīng)用程序調(diào)用 OpenGL
或 Direct3D
API 功能趟紊,將 GPU 作為協(xié)處理器使用。API 通過面向特殊 GPU 優(yōu)化的圖形設(shè)備驅(qū)動(dòng)向 GPU 發(fā)送命令碰酝、程序霎匈、數(shù)據(jù)。
GPU 資源管理模型
下圖所示為分離式異構(gòu)系統(tǒng)中 GPU 的資源管理模型示意圖送爸。
- MMIO(Memory-Mapped I/O)
- CPU 通過 MMIO 訪問 GPU 的寄存器狀態(tài)铛嘱。
- 通過 MMIO 傳送數(shù)據(jù)塊傳輸命令,支持 DMA 的硬件可以實(shí)現(xiàn)塊數(shù)據(jù)傳輸袭厂。
- GPU Context
- 上下文表示 GPU 的計(jì)算狀態(tài)墨吓,在 GPU 中占據(jù)部分虛擬地址空間。多個(gè)活躍態(tài)下的上下文可以在 GPU 中并存纹磺。
- CPU Channel
- 來自 CPU 操作 GPU 的命令存儲(chǔ)在內(nèi)存中帖烘,并提交至 GPU channel 硬件單元。
- 每個(gè) GPU 上下文可擁有多個(gè) GPU Channel橄杨。每個(gè) GPU 上下文都包含 GPU channel 描述符(GPU 內(nèi)存中的內(nèi)存對(duì)象)蚓让。
- 每個(gè) GPU Channel 描述符存儲(chǔ)了channel 的配置,如:其所在的頁(yè)表讥珍。
- 每個(gè) GPU Channel 都有一個(gè)專用的命令緩沖區(qū)历极,該緩沖區(qū)分配在 GPU 內(nèi)存中,通過 MMIO 對(duì) CPU 可見衷佃。
- GPU 頁(yè)表
- GPU 上下文使用 GPU 頁(yè)表進(jìn)行分配趟卸,該表將虛擬地址空間與其他地址空間隔離開來。
- GPU 頁(yè)表與 CPU 頁(yè)表分離氏义,其駐留在 GPU 內(nèi)存中锄列,物理地址位于 GPU 通道描述符中。
通過 GPU channel 提交的所有命令和程序都在對(duì)應(yīng)的 GPU 虛擬地址空間中執(zhí)行惯悠。 - GPU 頁(yè)表將 GPU 虛擬地址不僅轉(zhuǎn)換為 GPU 設(shè)備物理地址邻邮,還轉(zhuǎn)換為主機(jī)物理地址。這使得 GPU 頁(yè)面表能夠?qū)?GPU 存儲(chǔ)器和主存儲(chǔ)器統(tǒng)一到統(tǒng)一的 GPU 虛擬地址空間中克婶,從而構(gòu)成一個(gè)完成的虛擬地址空間筒严。
- PFIFO Engine
- PFIFO 是一個(gè)提交 GPU 命令的特殊引擎丹泉。
- PFIFO 維護(hù)多個(gè)獨(dú)立的命令隊(duì)列,即 channel鸭蛙。
- 命令隊(duì)列是帶有 put 和 get 指針的環(huán)形緩沖器摹恨。
- PFIFO 引擎會(huì)攔截多有對(duì)通道控制區(qū)域的訪問以供執(zhí)行。
- GPU 驅(qū)動(dòng)使用一個(gè)通道描述符來存儲(chǔ)關(guān)聯(lián)通道的設(shè)置娶视。
- BO
- 緩沖對(duì)象(Buffer Object)晒哄。一塊內(nèi)存,可以用來存儲(chǔ)紋理肪获,渲染對(duì)象寝凌,著色器代碼等等。
CPU-GPU 工作流
下圖所示為 CPU-GPU 異構(gòu)系統(tǒng)的工作流孝赫,當(dāng) CPU 遇到圖像處理的需求時(shí)较木,會(huì)調(diào)用 GPU 進(jìn)行處理,主要流程可以分為以下四步:
- 將主存的處理數(shù)據(jù)復(fù)制到顯存中
- CPU 指令驅(qū)動(dòng) GPU
- GPU 中的每個(gè)運(yùn)算單元并行處理
- GPU 將顯存結(jié)果傳回主存
屏幕圖像顯示原理
介紹屏幕圖像顯示的原理寒锚,需要先從 CRT 顯示器原理說起,如下圖所示违孝。CRT 的電子槍從上到下逐行掃描刹前,掃描完成后顯示器就呈現(xiàn)一幀畫面。然后電子槍回到初始位置進(jìn)行下一次掃描雌桑。為了同步顯示器的顯示過程和系統(tǒng)的視頻控制器喇喉,顯示器會(huì)用硬件時(shí)鐘產(chǎn)生一系列的定時(shí)信號(hào)。當(dāng)電子槍換行進(jìn)行掃描時(shí)校坑,顯示器會(huì)發(fā)出一個(gè)水平同步信號(hào)(horizonal synchronization)拣技,簡(jiǎn)稱 HSync;而當(dāng)一幀畫面繪制完成后耍目,電子槍回復(fù)到原位膏斤,準(zhǔn)備畫下一幀前,顯示器會(huì)發(fā)出一個(gè)垂直同步信號(hào)(vertical synchronization)邪驮,簡(jiǎn)稱 VSync莫辨。顯示器通常以固定頻率進(jìn)行刷新,這個(gè)刷新率就是 VSync 信號(hào)產(chǎn)生的頻率毅访。雖然現(xiàn)在的顯示器基本都是液晶顯示屏了沮榜,但其原理基本一致。
下圖所示為常見的 CPU喻粹、GPU蟆融、顯示器工作方式。CPU 計(jì)算好顯示內(nèi)容提交至 GPU守呜,GPU 渲染完成后將渲染結(jié)果存入幀緩沖區(qū)型酥,視頻控制器會(huì)按照 VSync
信號(hào)逐幀讀取幀緩沖區(qū)的數(shù)據(jù)山憨,經(jīng)過數(shù)據(jù)轉(zhuǎn)換后最終由顯示器進(jìn)行顯示。
最簡(jiǎn)單的情況下冕末,幀緩沖區(qū)只有一個(gè)萍歉。此時(shí),幀緩沖區(qū)的讀取和刷新都都會(huì)有比較大的效率問題档桃。為了解決效率問題枪孩,GPU 通常會(huì)引入兩個(gè)緩沖區(qū),即 雙緩沖機(jī)制藻肄。在這種情況下蔑舞,GPU 會(huì)預(yù)先渲染一幀放入一個(gè)緩沖區(qū)中,用于視頻控制器的讀取嘹屯。當(dāng)下一幀渲染完畢后攻询,GPU 會(huì)直接把視頻控制器的指針指向第二個(gè)緩沖器。
雙緩沖雖然能解決效率問題州弟,但會(huì)引入一個(gè)新的問題钧栖。當(dāng)視頻控制器還未讀取完成時(shí),即屏幕內(nèi)容剛顯示一半時(shí)婆翔,GPU 將新的一幀內(nèi)容提交到幀緩沖區(qū)并把兩個(gè)緩沖區(qū)進(jìn)行交換后拯杠,視頻控制器就會(huì)把新的一幀數(shù)據(jù)的下半段顯示到屏幕上,造成畫面撕裂現(xiàn)象啃奴,如下圖:
為了解決這個(gè)問題潭陪,GPU 通常有一個(gè)機(jī)制叫做垂直同步(簡(jiǎn)寫也是 V-Sync),當(dāng)開啟垂直同步后最蕾,GPU 會(huì)等待顯示器的 VSync 信號(hào)發(fā)出后依溯,才進(jìn)行新的一幀渲染和緩沖區(qū)更新。這樣能解決畫面撕裂現(xiàn)象瘟则,也增加了畫面流暢度黎炉,但需要消費(fèi)更多的計(jì)算資源,也會(huì)帶來部分延遲醋拧。
參考
- GPU Architecture and Models
- 計(jì)算機(jī)組成與設(shè)計(jì):硬件拜隧、軟件接口
- 歡迎來到OpenGL的世界
- AMD APU Series
- 一文詳解GPU結(jié)構(gòu)及工作原理
- Revisting Co-Processing for Hash Joins on the Coupled CPU-GPU Architecture
- GPU Architecture Overview
- CUDA
- iOS 保持界面流程的技巧
- iOS 開發(fā):繪制像素到屏幕
擴(kuò)展閱讀
- Rendering pipeline: The hardware side
- Graphics Processing Unit(GPU) Memory Hierarchy
- Graphics Processing Unit Architecture(GPU Arch) With a focus on NVIDIA GeForce 6800 GPU
- iOS動(dòng)畫篇:核心動(dòng)畫
- The iPhone 5s Review
- A Look Inside Apple’s Custom GPU for the iPhone
- One Apple GPU, one giant leap in graphics for iPhone 8