1. 程式人生 > >深入GPU硬體架構及執行機制

深入GPU硬體架構及執行機制

目錄

  • 一、導言
    • 1.1 為何要了解GPU?
    • 1.2 內容要點
    • 1.3 帶著問題閱讀
  • 二、GPU概述
    • 2.1 GPU是什麼?
    • 2.2 GPU歷史
      • 2.2.1 NV GPU發展史
      • 2.2.2 NV GPU架構發展史
    • 2.3 GPU的功能
  • 三、GPU物理架構
    • 3.1 GPU巨集觀物理結構
    • 3.2 GPU微觀物理結構
      • 3.2.1 NVidia Tesla架構
      • 3.2.2 NVidia Fermi架構
      • 3.2.3 NVidia Maxwell架構
      • 3.2.4 NVidia Kepler架構
      • 3.2.5 NVidia Turing架構
    • 3.3 GPU架構的共性
  • 四、GPU執行機制
    • 4.1 GPU渲染總覽
    • 4.2 GPU邏輯管線
    • 4.3 GPU技術要點
      • 4.3.1 SIMD和SIMT
      • 4.3.2 co-issue
      • 4.3.3 if - else語句
      • 4.3.4 Early-Z
      • 4.3.5 統一著色器架構(Unified shader Architecture)
      • 4.3.6 畫素塊(Pixel Quad)
    • 4.4 GPU資源機制
      • 4.4.1 記憶體架構
      • 4.4.2 GPU Context和延遲
      • 4.4.3 CPU-GPU異構系統
      • 4.4.4 GPU資源管理模型
      • 4.4.5 CPU-GPU資料流
      • 4.4.6 顯像機制
    • 4.5 Shader執行機制
    • 4.6 利用擴充套件例證
  • 五、總結
    • 5.1 CPU vs GPU
    • 5.2 渲染優化建議
    • 5.3 GPU的未來
    • 5.4 結語
  • 特別說明
  • 參考文獻

 

一、導言

對於大多數圖形渲染開發者,GPU是既熟悉又陌生的部件,熟悉的是每天都需要跟它打交道,陌生的是GPU就如一個黑盒,不知道其內部硬體架構,更無從談及其執行機制。

本文以NVIDIA作為主線,將試圖全面且深入地剖析GPU的硬體架構及執行機制,主要涉及PC桌面級的GPU,不會覆蓋移動端、專業計算、圖形工作站級別的GPU。

若要通讀本文,要求讀者有一定圖形學的基礎,瞭解GPU渲染管線,最好寫過HLSL、GLSL等shader程式碼。

1.1 為何要了解GPU?

瞭解GPU硬體架構和理解執行機制,筆者認為好處多多,總結出來有:

  • 理解GPU其物理結構和執行機制,GPU由黑盒變白盒。
  • 更易找出渲染瓶頸,寫出高效率shader程式碼。
  • 緊跟時代潮流,瞭解最前沿渲染技術!
  • 技多不壓身!

1.2 內容要點

本文的內容要點提煉如下:

  • GPU簡介、歷史、特性。
  • GPU硬體架構。
  • GPU和CPU的協調排程機制。
  • GPU快取結構。
  • GPU渲染管線。
  • GPU執行機制。
  • GPU優化技巧。

1.3 帶著問題閱讀

適當帶著問題去閱讀技術文章,通常能加深理解和記憶,閱讀本文可帶著以下問題:
1、GPU是如何與CPU協調工作的?

2、GPU也有快取機制嗎?有幾層?它們的速度差異多少?

3、GPU的渲染流程有哪些階段?它們的功能分別是什麼?

4、Early-Z技術是什麼?發生在哪個階段?這個階段還會發生什麼?會產生什麼問題?如何解決?

5、SIMD和SIMT是什麼?它們的好處是什麼?co-issue呢?

6、GPU是並行處理的麼?若是,硬體層是如何設計和實現的?

7、GPC、TPC、SM是什麼?Warp又是什麼?它們和Core、Thread之間的關係如何?

8、頂點著色器(VS)和畫素著色器(PS)可以是同一處理單元嗎?為什麼?

9、畫素著色器(PS)的最小處理單位是1畫素嗎?為什麼?會帶來什麼影響?

10、Shader中的if、for等語句會降低渲染效率嗎?為什麼?

11、如下圖,渲染相同面積的圖形,三角形數量少(左)的還是數量多(右)的效率更快?為什麼?

12、GPU Context是什麼?有什麼作用?

13、造成渲染瓶頸的問題很可能有哪些?該如何避免或優化它們?

如果閱讀完本文,能夠非常清晰地回答以上所有問題,那麼,恭喜你掌握到本文的精髓了!

 

二、GPU概述

2.1 GPU是什麼?

GPU全稱是Graphics Processing Unit,圖形處理單元。它的功能最初與名字一致,是專門用於繪製圖像和處理圖元資料的特定晶片,後來漸漸加入了其它很多功能。

NVIDIA GPU晶片實物圖

我們日常討論GPU和顯示卡時,經常混為一談,嚴格來說是有所區別的。GPU是顯示卡(Video card、Display card、Graphics card)最核心的部件,但除了GPU,顯示卡還有扇熱器、通訊元件、與主機板和顯示器連線的各類插槽。

對於PC桌面,生產GPU的廠商主要有兩家:

  • NVIDIA:英偉達,是當今首屈一指的圖形渲染技術的引領者和GPU生產商佼佼者。NVIDIA的產品俗稱N卡,代表產品有GeForce系列、GTX系列、RTX系列等。

  • AMD:既是CPU生產商,也是GPU生產商,它家的顯示卡俗稱A卡。代表產品有Radeon系列。

當然,NVIDIA和AMD也都生產移動端、圖形工作站型別的GPU。此外,生產移動端顯示卡的廠商還有ARM、Imagination Technology、高通等公司。

2.2 GPU歷史

GPU自從上世紀90年代出現雛形以來,經過20多年的發展,已經發展成不僅僅是渲染圖形這麼簡單,還包含了數學計算、物理模擬、AI運算等功能。

2.2.1 NV GPU發展史

以下是GPU發展節點表:

  • 1995 – NV1

    NV1的渲染畫面及其特性。

  • 1997 – Riva 128 (NV3), DX3

  • 1998 – Riva TNT (NV4), DX5

    • 32位顏色, 24位Z快取, 8位模板快取
    • 雙紋理, 雙線性過濾
    • 每時鐘2畫素 (2 ppc)
  • 1999 - GeForce 256(NV10)

    • 固定管線,支援DirectX 7.0
    • 硬體T&L(Transform & lighting,座標變換和光照)
    • 立方體環境圖(Cubemaps)
    • DOT3 – bump mapping
    • 2倍各向異性過濾
    • 三線性過濾
    • DXT紋理壓縮
    • 4ppc
    • 引入“GPU”術語

    NV10的渲染畫面及其特性。

  • 2001 - GeForce 3

    • DirectX 8.0
    • Shader Model 1.0
    • 可程式設計渲染管線
      • 頂點著色器
      • 畫素著色器
    • 3D紋理
    • 硬體陰影圖
    • 8倍各向異性過濾
    • 多采樣抗鋸齒(MSAA)
    • 4 ppc

    NV20的渲染畫面及其特性。

  • 2003 - GeForce FX系列(NV3x)

    • DirectX 9.0
    • Shader Model 2.0
      • 256頂點操作指令
      • 32紋理 + 64算術畫素操作指令
    • Shader Model 2.0a
      • 256頂點操作指令
      • 512畫素操作指令
    • 著色語言
      • HLSL
      • CGSL
      • GLSL

NV30的渲染畫面及其特性。

  • 2004 - GeForce 6系列 (NV4x)

    • DirectX 9.0c
    • Shader Model 3.0
    • 動態流控制
      • 分支、迴圈、宣告等
    • 頂點紋理讀取
    • 高動態範圍(HDR)
      • 64位渲染紋理(Render Target)
      • FP16*4 紋理過濾和混合

NV40的渲染畫面及其特性。

  • 2006 - GeForce 8系列 (G8x)

    • DirectX 10.0
    • Shader Model 4.0
      • 幾何著色器(Geometry Shaders)
      • 沒有上限位(No caps bits)
      • 統一的著色器(Unified Shaders)
    • Vista系統全新驅動
    • 基於GPU計算的CUDA問世
    • GPU計算能力以GFLOPS計量。

    NV G80的渲染畫面及其特性。

  • 2010 - GeForce 405(GF119)

    • DirectX 11.0

      • 曲面細分(Tessellation)
        • 外殼著色器(Hull Shader)
        • 鑲嵌單元(tessellator)
        • 域著色器(Domain Shader)
      • 計算著色器(Compute Shader)
        • 支援Stream Output

      DirectX 11的渲染管線。

      • 多執行緒支援
      • 改進的紋理壓縮
    • Shader Model 5.0

      • 更多指令、儲存單元、暫存器
      • 面向物件著色語言
      • 曲面細分
      • 計算著色器
  • 2014 - GeForceGT 710(GK208)

    • DirectX 12.0
      • 輕量化驅動層
      • 硬體級多執行緒渲染支援
    • 更完善的硬體資源管理
  • 2016 - GeForceGTX 1060 6GB

    • 首次支援RTX和DXR技術,即支援光線追蹤
    • 引入RT Core(光線追蹤核心)

    支援RTX光線追蹤的顯示卡列表。

  • 2018 - TITAN RTX(TU102)

    • DirectX 12.1,OpenGL 4.5

    • 6GPC,36TPC,72SM,72RT Core,...

    • 8K解析度,1770MHz主頻,24G視訊記憶體,384位頻寬

從上面可以看出來,GPU硬體是伴隨著圖形API標準、遊戲一起發展的,並且它們形成了相互相成、相互促進的良性關係。

2.2.2 NV GPU架構發展史

眾所周知,CPU的發展符合摩爾定律:每18個月速度翻倍。

處理晶片電晶體數量符合摩爾定律,圖右是摩爾本人,Intel的創始人

而NVIDIA創始人黃仁勳在很多年前曾信誓旦旦地說,GPU的速度和功能要超越摩爾定律,每6個月就翻一倍。NV的GPU發展史證明,他確實做到了!GPU的提速幅率遠超CPU:

NVIDIA GPU架構歷經多次變革,從起初的Tesla發展到最新的Turing架構,發展史可分為以下時間節點:

  • 2008 - Tesla

    Tesla最初是給計算處理單元使用的,應用於早期的CUDA系列顯示卡晶片中,並不是真正意義上的普通圖形處理晶片。

  • 2010 - Fermi

    Fermi是第一個完整的GPU計算架構。首款可支援與共享儲存結合純cache層次的GPU架構,支援ECC的GPU架構。

  • 2012 - Kepler

    Kepler相較於Fermi更快,效率更高,效能更好。

  • 2014 - Maxwell

    其全新的立體畫素全域性光照 (VXGI) 技術首次讓遊戲 GPU 能夠提供實時的動態全域性光照效果。基於 Maxwell 架構的 GTX 980 和 970 GPU 採用了包括多幀取樣抗鋸齒 (MFAA)、動態超級解析度 (DSR)、VR Direct 以及超節能設計在內的一系列新技術。

  • 2016 - Pascal

    Pascal 架構將處理器和資料整合在同一個程式包內,以實現更高的計算效率。1080系列、1060系列基於Pascal架構

  • 2017 - Volta

    Volta 配備640 個Tensor 核心,每秒可提供超過100 兆次浮點運算(TFLOPS) 的深度學習效能,比前一代的Pascal 架構快5 倍以上。

  • 2018 - Turing

    Turing 架構配備了名為 RT Core 的專用光線追蹤處理器,能夠以高達每秒 10 Giga Rays 的速度對光線和聲音在 3D 環境中的傳播進行加速計算。Turing 架構將實時光線追蹤運算加速至上一代 NVIDIA Pascal™ 架構的 25 倍,並能以高出 CPU 30 多倍的速度進行電影效果的最終幀渲染。2060系列、2080系列顯示卡也是跳過了Volta直接選擇了Turing架構。

下圖是部分GPU架構的發展歷程:

2.3 GPU的功能

現代GPU除了繪製圖形外,還擔當了很多額外的功能,綜合起來如下幾方面:

  • 圖形繪製。

    這是GPU最傳統的拿手好戲,也是最基礎、最核心的功能。為大多數PC桌面、移動裝置、圖形工作站提供圖形處理和繪製功能。

  • 物理模擬。

    GPU硬體整合的物理引擎(PhysX、Havok),為遊戲、電影、教育、科學模擬等領域提供了成百上千倍效能的物理模擬,使得以前需要長時間計算的物理模擬得以實時呈現。

  • 海量計算。

    計算著色器及流輸出的出現,為各種可以平行計算的海量需求得以實現,CUDA就是最好的例證。

  • AI運算。

    近年來,人工智慧的崛起推動了GPU集成了AI Core運算單元,反哺AI運算能力的提升,給各行各業帶來了計算能力的提升。

  • 其它計算。

    音視訊編解碼、加解密、科學計算、離線渲染等等都離不開現代GPU的平行計算能力和海量吞吐能力。

 

三、GPU物理架構

3.1 GPU巨集觀物理結構

由於納米工藝的引入,GPU可以將數以億記的電晶體和電子器件整合在一個小小的晶片內。從巨集觀物理結構上看,現代大多數桌面級GPU的大小跟數枚硬幣同等大小,部分甚至比一枚硬幣還小(下圖)。

高通驍龍853顯示晶片比硬幣還小

當GPU結合散熱風扇、PCI插槽、HDMI介面等部件之後,就組成了顯示卡(下圖)。

顯示卡不能獨立工作,需要裝載在主機板上,結合CPU、記憶體、視訊記憶體、顯示器等硬體裝置,組成完整的PC機。

搭載了顯示卡的主機板。

3.2 GPU微觀物理結構

GPU的微觀結構因不同廠商、不同架構都會有所差異,但核心部件、概念、以及執行機制大同小異。下面將展示部分架構的GPU微觀物理結構。

3.2.1 NVidia Tesla架構

Tesla微觀架構總覽圖如上。下面將闡述它的特性和概念:

  • 擁有7組TPC(Texture/Processor Cluster,紋理處理簇)

  • 每個TPC有兩組SM(Stream Multiprocessor,流多處理器)
  • 每個SM包含:
    • 6個SP(Streaming Processor,流處理器)
    • 2個SFU(Special Function Unit,特殊函式單元)
    • L1快取、MT Issue(多執行緒指令獲取)、C-Cache(常量快取)、共享記憶體
  • 除了TPC核心單元,還有與視訊記憶體、CPU、系統記憶體互動的各種部件。

3.2.2 NVidia Fermi架構

Fermi架構如上圖,它的特性如下:

  • 擁有16個SM
  • 每個SM:
    • 2個Warp(執行緒束)
    • 兩組共32個Core
    • 16組載入儲存單元(LD/ST)
    • 4個特殊函式單元(SFU)
  • 每個Warp:
    • 16個Core
    • Warp編排器(Warp Scheduler)
    • 分發單元(Dispatch Unit)
  • 每個Core:
    • 1個FPU(浮點數單元)
    • 1個ALU(邏輯運算單元)

3.2.3 NVidia Maxwell架構

採用了Maxwell的GM204,擁有4個GPC,每個GPC有4個SM,對比Tesla架構來說,在處理單元上有了很大的提升。

3.2.4 NVidia Kepler架構

Kepler除了在硬體有了提升,有了更多處理單元之外,還將SM升級到了SMX。SMX是改進的架構,支援動態建立渲染執行緒(下圖),以降低延遲。

3.2.5 NVidia Turing架構

上圖是採納了Turing架構的TU102 GPU,它的特點如下:

  • 6 GPC(圖形處理簇)
  • 36 TPC(紋理處理簇)
  • 72 SM(流多處理器)
  • 每個GPC有6個TPC,每個TPC有2個SM

  • 4,608 CUDA核
  • 72 RT核
  • 576 Tensor核
  • 288 紋理單元
  • 12x32位 GDDR6記憶體控制器 (共384位)

單個SM的結構圖如下:

每個SM包含:

  • 64 CUDA核
  • 8 Tensor核
  • 256 KB暫存器檔案

TU102 GPU晶片實物圖:

3.3 GPU架構的共性

縱觀上一節的所有GPU架構,可以發現它們雖然有所差異,但存在著很多相同的概念和部件:

  • GPC
  • TPC
  • Thread
  • SM、SMX、SMM
  • Warp
  • SP
  • Core
  • ALU
  • FPU
  • SFU
  • ROP
  • Load/Store Unit
  • L1 Cache
  • L2 Cache
  • Memory
  • Register File

以上各個部件的用途將在下一章詳細闡述。

GPU為什麼會有這麼多層級且有這麼多雷同的部件?答案是GPU的任務是天然並行的,現代GPU的架構皆是以高度並行能力而設計的。

 

四、GPU執行機制

4.1 GPU渲染總覽

由上一章可得知,現代GPU有著相似的結構,有很多相同的部件,在執行機制上,也有很多共同點。下面是Fermi架構的執行機制總覽圖:

從Fermi開始NVIDIA使用類似的原理架構,使用一個Giga Thread Engine來管理所有正在進行的工作,GPU被劃分成多個GPCs(Graphics Processing Cluster),每個GPC擁有多個SM(SMX、SMM)和一個光柵化引擎(Raster Engine),它們其中有很多的連線,最顯著的是Crossbar,它可以連線GPCs和其它功能性模組(例如ROP或其他子系統)。

程式設計師編寫的shader是在SM上完成的。每個SM包含許多為執行緒執行數學運算的Core(核心)。例如,一個執行緒可以是頂點或畫素著色器呼叫。這些Core和其它單元由Warp Scheduler驅動,Warp Scheduler管理一組32個執行緒作為Warp(執行緒束)並將要執行的指令移交給Dispatch Units。

GPU中實際有多少這些單元(每個GPC有多少個SM,多少個GPC ......)取決於晶片配置本身。例如,GM204有4個GPC,每個GPC有4個SM,但Tegra X1有1個GPC和2個SM,它們均採用Maxwell設計。SM設計本身(核心數量,指令單位,排程程式......)也隨著時間的推移而發生變化,並幫助使晶片變得如此高效,可以從高階桌上型電腦擴充套件到膝上型電腦移動。

如上圖,對於某些GPU(如Fermi部分型號)的單個SM,包含:

  • 32個運算核心 (Core,也叫流處理器Stream Processor)
  • 16個LD/ST(load/store)模組來載入和儲存資料
  • 4個SFU(Special function units)執行特殊數學運算(sin、cos、log等)
  • 128KB暫存器(Register File)
  • 64KB L1快取
  • 全域性記憶體快取(Uniform Cache)
  • 紋理讀取單元
  • 紋理快取(Texture Cache)
  • PolyMorph Engine:多邊形引擎負責屬性裝配(attribute Setup)、頂點拉取(VertexFetch)、曲面細分、柵格化(這個模組可以理解專門處理頂點相關的東西)。
  • 2個Warp Schedulers:這個模組負責warp排程,一個warp由32個執行緒組成,warp排程器的指令通過Dispatch Units送到Core執行。

  • 指令快取(Instruction Cache)
  • 內部連結網路(Interconnect Network)

4.2 GPU邏輯管線

瞭解上一節的部件和概念之後,可以深入闡述GPU的渲染過程和步驟。下面將以Fermi家族的SM為例,進行邏輯管線的詳細說明。

  • 1、程式通過圖形API(DX、GL、WEBGL)發出drawcall指令,指令會被推送到驅動程式,驅動會檢查指令的合法性,然後會把指令放到GPU可以讀取的Pushbuffer中。

  • 2、經過一段時間或者顯式呼叫flush指令後,驅動程式把Pushbuffer的內容傳送給GPU,GPU通過主機介面(Host Interface)接受這些命令,並通過前端(Front End)處理這些命令。

  • 3、在圖元分配器(Primitive Distributor)中開始工作分配,處理indexbuffer中的頂點產生三角形分成批次(batches),然後傳送給多個PGCs。這一步的理解就是提交上來n個三角形,分配給這幾個PGC同時處理。

  • 4、在GPC中,每個SM中的Poly Morph Engine負責通過三角形索引(triangle indices)取出三角形的資料(vertex data),即圖中的Vertex Fetch模組。

  • 5、在獲取資料之後,在SM中以32個執行緒為一組的執行緒束(Warp)來排程,來開始處理頂點資料。Warp是典型的單指令多執行緒(SIMT,SIMD單指令多資料的升級)的實現,也就是32個執行緒同時執行的指令是一模一樣的,只是執行緒資料不一樣,這樣的好處就是一個warp只需要一個套邏輯對指令進行解碼和執行就可以了,晶片可以做的更小更快,之所以可以這麼做是由於GPU需要處理的任務是天然並行的。

  • 6、SM的warp排程器會按照順序分發指令給整個warp,單個warp中的執行緒會鎖步(lock-step)執行各自的指令,如果執行緒碰到不啟用執行的情況也會被遮掩(be masked out)。被遮掩的原因有很多,例如當前的指令是if(true)的分支,但是當前執行緒的資料的條件是false,或者迴圈的次數不一樣(比如for迴圈次數n不是常量,或被break提前終止了但是別的還在走),因此在shader中的分支會顯著增加時間消耗,在一個warp中的分支除非32個執行緒都走到if或者else裡面,否則相當於所有的分支都走了一遍,執行緒不能獨立執行指令而是以warp為單位,而這些warp之間才是獨立的。

  • 7、warp中的指令可以被一次完成,也可能經過多次排程,例如通常SM中的LD/ST(載入存取)單元數量明顯少於基礎數學操作單元。

  • 8、由於某些指令比其他指令需要更長的時間才能完成,特別是記憶體載入,warp排程器可能會簡單地切換到另一個沒有記憶體等待的warp,這是GPU如何克服記憶體讀取延遲的關鍵,只是簡單地切換活動執行緒組。為了使這種切換非常快,排程器管理的所有warp在暫存器檔案中都有自己的暫存器。這裡就會有個矛盾產生,shader需要越多的暫存器,就會給warp留下越少的空間,就會產生越少的warp,這時候在碰到記憶體延遲的時候就會只是等待,而沒有可以執行的warp可以切換。

  • 9、一旦warp完成了vertex-shader的所有指令,運算結果會被Viewport Transform模組處理,三角形會被裁剪然後準備柵格化,GPU會使用L1和L2快取來進行vertex-shader和pixel-shader的資料通訊。

  • 10、接下來這些三角形將被分割,再分配給多個GPC,三角形的範圍決定著它將被分配到哪個光柵引擎(raster engines),每個raster engines覆蓋了多個螢幕上的tile,這等於把三角形的渲染分配到多個tile上面。也就是畫素階段就把按三角形劃分變成了按顯示的畫素劃分了。

  • 11、SM上的Attribute Setup保證了從vertex-shader來的資料經過插值後是pixel-shade是可讀的。

  • 12、GPC上的光柵引擎(raster engines)在它接收到的三角形上工作,來負責這些這些三角形的畫素資訊的生成(同時會處理裁剪Clipping、背面剔除和Early-Z剔除)。

  • 13、32個畫素執行緒將被分成一組,或者說8個2x2的畫素塊,這是在畫素著色器上面的最小工作單元,在這個畫素執行緒內,如果沒有被三角形覆蓋就會被遮掩,SM中的warp排程器會管理畫素著色器的任務。

  • 14、接下來的階段就和vertex-shader中的邏輯步驟完全一樣,但是變成了在畫素著色器執行緒中執行。 由於不耗費任何效能可以獲取一個畫素內的值,導致鎖步執行非常便利,所有的執行緒可以保證所有的指令可以在同一點。

  • 15、最後一步,現在畫素著色器已經完成了顏色的計算還有深度值的計算,在這個點上,我們必須考慮三角形的原始api順序,然後才將資料移交給ROP(render output unit,渲染輸入單元),一個ROP內部有很多ROP單元,在ROP單元中處理深度測試,和framebuffer的混合,深度和顏色的設定必須是原子操作,否則兩個不同的三角形在同一個畫素點就會有衝突和錯誤。

4.3 GPU技術要點

由於上一節主要闡述GPU內部的工作流程和機制,為了簡潔性,省略了很多知識點和過程,本節將對它們做進一步補充說明。

4.3.1 SIMD和SIMT

SIMD(Single Instruction Multiple Data)是單指令多資料,在GPU的ALU單元內,一條指令可以處理多維向量(一般是4D)的資料。比如,有以下shader指令:

float4 c = a + b; // a, b都是float4型別

對於沒有SIMD的處理單元,需要4條指令將4個float數值相加,彙編虛擬碼如下:

ADD c.x, a.x, b.x
ADD c.y, a.y, b.y
ADD c.z, a.z, b.z
ADD c.w, a.w, b.w

但有了SIMD技術,只需一條指令即可處理完:

SIMD_ADD c, a, b

SIMT(Single Instruction Multiple Threads,單指令多執行緒)是SIMD的升級版,可對GPU中單個SM中的多個Core同時處理同一指令,並且每個Core存取的資料可以是不同的。

SIMT_ADD c, a, b

上述指令會被同時送入在單個SM中被編組的所有Core中,同時執行運算,但abc的值可以不一樣:

4.3.2 co-issue

co-issue是為了解決SIMD運算單元無法充分利用的問題。例如下圖,由於float數量的不同,ALU利用率從100%依次下降為75%、50%、25%。

為了解決著色器在低維向量的利用率低的問題,可以通過合併1D與3D或2D與2D的指令。例如下圖,DP3指令用了3D資料,ADD指令只有1D資料,co-issue會自動將它們合併,在同一個ALU只需一個指令週期即可執行完。

但是,對於向量運算單元(Vector ALU),如果其中一個變數既是運算元又是儲存數的情況,無法啟用co-issue技術:

於是標量指令著色器(Scalar Instruction Shader)應運而生,它可以有效地組合任何向量,開啟co-issue技術,充分發揮SIMD的優勢。

4.3.3 if - else語句

如上圖,SM中有8個ALU(Core),由於SIMD的特性,每個ALU的資料不一樣,導致if-else語句在某些ALU中執行的是true分支(黃色),有些ALU執行的是false分支(灰藍色),這樣導致很多ALU的執行週期被浪費掉了(即masked out),拉長了整個執行週期。最壞的情況,同一個SM中只有1/8(8是同一個SM的執行緒數,不同架構的GPU有所不同)的利用率。

同樣,for迴圈也會導致類似的情形,例如以下shader程式碼:

void func(int count, int breakNum)
{
    for(int i=0; i<count; ++i)
    {
        if (i == breakNum)
            break;
        else
            // do something
    }
}

由於每個ALU的count不一樣,加上有break分支,導致最快執行完shader的ALU可能是最慢的N分之一的時間,但由於SIMD的特性,最快的那個ALU依然要等待最慢的ALU執行完畢,才能接下一組指令的活!也就白白浪費了很多時間週期。

4.3.4 Early-Z

早期GPU的渲染管線的深度測試是在畫素著色器之後才執行(下圖),這樣會造成很多本不可見的畫素執行了耗效能的畫素著色器計算。

後來,為了減少畫素著色器的額外消耗,將深度測試提至畫素著色器之前(下圖),這就是Early-Z技術的由來。

Early-Z技術可以將很多無效的畫素提前剔除,避免它們進入耗時嚴重的畫素著色器。Early-Z剔除的最小單位不是1畫素,而是畫素塊(pixel quad,2x2個畫素,詳見4.3.6)。

但是,以下情況會導致Early-Z失效:

  • 開啟Alpha Test:由於Alpha Test需要在畫素著色器後面的Alpha Test階段比較,所以無法在畫素著色器之前就決定該畫素是否被剔除。
  • 開啟Alpha Blend:啟用了Alpha混合的畫素很多需要與frame buffer做混合,無法執行深度測試,也就無法利用Early-Z技術。
  • 開啟Tex Kill:即在shader程式碼中有畫素摒棄指令(DX的discard,OpenGL的clip)。
  • 關閉深度測試。Early-Z是建立在深度測試看開啟的條件下,如果關閉了深度測試,也就無法啟用Early-Z技術。
  • 開啟Multi-Sampling:多采樣會影響周邊畫素,而Early-Z階段無法得知周邊畫素是否被裁剪,故無法提前剔除。
  • 以及其它任何導致需要混合後面顏色的操作。

此外,Early-Z技術會導致一個問題:深度資料衝突(depth data hazard)。

例子要結合上圖,假設數值深度值5已經經過Early-Z即將寫入Frame Buffer,而深度值10剛好處於Early-Z階段,讀取並對比當前快取的深度值15,結果就是10通過了Early-Z測試,會覆蓋掉比自己小的深度值5,最終frame buffer的深度值是錯誤的結果。

避免深度資料衝突的方法之一是在寫入深度值之前,再次與frame buffer的值進行對比:

4.3.5 統一著色器架構(Unified shader Architecture)

在早期的GPU,頂點著色器和畫素著色器的硬體結構是獨立的,它們各有各的暫存器、運算單元等部件。這樣很多時候,會造成頂點著色器與畫素著色器之間任務的不平衡。對於頂點數量多的任務,畫素著色器空閒狀態多;對於畫素多的任務,頂點著色器的空閒狀態多(下圖)。

於是,為了解決VS和PS之間的不平衡,引入了統一著色器架構(Unified shader Architecture)。用了此架構的GPU,VS和PS用的都是相同的Core。也就是,同一個Core既可以是VS又可以是PS。

這樣就解決了不同型別著色器之間的不平衡問題,還可以減少GPU的硬體單元,壓縮物理尺寸和耗電量。此外,VS、PS可還可以和其它著色器(幾何、曲面、計算)統一為一體。

4.3.6 畫素塊(Pixel Quad)

上一節步驟13提到:

32個畫素執行緒將被分成一組,或者說8個2x2的畫素塊,這是在畫素著色器上面的最小工作單元,在這個畫素執行緒內,如果沒有被三角形覆蓋就會被遮掩,SM中的warp排程器會管理畫素著色器的任務。

也就是說,在畫素著色器中,會將相鄰的四個畫素作為不可分隔的一組,送入同一個SM內4個不同的Core。

為什麼畫素著色器處理的最小單元是2x2的畫素塊?

筆者推測有以下原因:

1、簡化和加速畫素分派的工作。

2、精簡SM的架構,減少硬體單元數量和尺寸。

3、降低功耗,提高效能比。

這種設計雖然有其優勢,但同時,也會激化過繪製(Over Draw)的情況,損耗額外的效能。比如下圖中,白色的三角形只佔用了3個畫素(綠色),按我們普通的思維,只需要3個Core繪製3次就可以了。

但是,由於上面的3個畫素分別佔據了不同的畫素塊(橙色分隔),實際上需要佔用12個Core繪製12次(下圖)。

這就會額外消耗300%的硬體效能,導致了更加嚴重的過繪製情況。

更多詳情可以觀看虛幻官方的視訊教學:實時渲染深入探究。

4.4 GPU資源機制

本節將闡述GPU的記憶體訪問、資源管理等機制。

4.4.1 記憶體架構

部分架構的GPU與CPU類似,也有多級快取結構:暫存器、L1快取、L2快取、GPU視訊記憶體、系統視訊記憶體。

它們的存取速度從暫存器到系統記憶體依次變慢:

儲存型別 暫存器 共享記憶體 L1快取 L2快取 紋理、常量快取 全域性記憶體
訪問週期 1 1~32 1~32 32~64 400~600 400~600

由此可見,shader直接訪問暫存器、L1、L2快取還是比較快的,但訪問紋理、常量快取和全域性記憶體非常慢,會造成很高的延遲。

上面的多級快取結構可被稱為“CPU-Style”,還存在GPU-Style的記憶體架構:

這種架構的特點是ALU多,GPU上下文(Context)多,吞吐量高,依賴高頻寬與系統記憶體交換資料。

4.4.2 GPU Context和延遲

由於SIMT技術的引入,導致很多同一個SM內的很多Core並不是獨立的,當它們當中有部分Core需要訪問到紋理、常量快取和全域性記憶體時,就會導致非常大的卡頓(Stall)。

例如下圖中,有4組上下文(Context),它們共用同一組運算單元ALU。

假設第一組Context需要訪問快取或記憶體,會導致2~3個週期的延遲,此時排程器會啟用第二組Context以利用ALU:

當第二組Context訪問快取或記憶體又卡住,會依次啟用第三、第四組Context,直到第一組Context恢復執行或所有都被啟用:

延遲的後果是每組Context的總體執行時間被拉長了:

但是,越多Context可用就越可以提升運算單元的吞吐量,比如下圖的18組Context的架構可以最大化地提升吞吐量:

4.4.3 CPU-GPU異構系統

根據CPU和GPU是否共享記憶體,可分為兩種型別的CPU-GPU架構:

上圖左是分離式架構,CPU和GPU各自有獨立的快取和記憶體,它們通過PCI-e等匯流排通訊。這種結構的缺點在於 PCI-e 相對於兩者具有低頻寬和高延遲,資料的傳輸成了其中的效能瓶頸。目前使用非常廣泛,如PC、智慧手機等。

上圖右是耦合式架構,CPU 和 GPU 共享記憶體和快取。AMD 的 APU 採用的就是這種結構,目前主要使用在遊戲主機中,如 PS4。

在儲存管理方面,分離式結構中 CPU 和 GPU 各自擁有獨立的記憶體,兩者共享一套虛擬地址空間,必要時會進行記憶體拷貝。對於耦合式結構,GPU 沒有獨立的記憶體,與 GPU 共享系統記憶體,由 MMU 進行儲存管理。

4.4.4 GPU資源管理模型

下圖是分離式架構的資源管理模型:

  • MMIO(Memory Mapped IO)

    • CPU與GPU的交流就是通過MMIO進行的。CPU 通過 MMIO 訪問 GPU 的暫存器狀態。
    • DMA傳輸大量的資料就是通過MMIO進行命令控制的。
    • I/O埠可用於間接訪問MMIO區域,像Nouveau等開源軟體從來不訪問它。
  • GPU Context

    • GPU Context代表了GPU計算的狀態。
    • 在GPU中擁有自己的虛擬地址。
    • GPU 中可以並存多個活躍態下的Context。
  • GPU Channel

    • 任何命令都是由CPU發出。
    • 命令流(command stream)被提交到硬體單元,也就是GPU Channel。
    • 每個GPU Channel關聯一個context,而一個GPU Context可以有多個GPU channel。
    • 每個GPU Context 包含相關channel的 GPU Channel Descriptors , 每個 Descriptor 都是 GPU 記憶體中的一個物件。
    • 每個 GPU Channel Descriptor 儲存了 Channel 的設定,其中就包括 Page Table 。
    • 每個 GPU Channel 在GPU記憶體中分配了唯一的命令快取,這通過MMIO對CPU可見。
    • GPU Context Switching 和命令執行都在GPU硬體內部排程。
  • GPU Page Table

    • GPU Context在虛擬基地空間由Page Table隔離其它的Context 。
    • GPU Page Table隔離CPU Page Table,位於GPU記憶體中。
    • GPU Page Table的實體地址位於 GPU Channel Descriptor中。
    • GPU Page Table不僅僅將 GPU虛擬地址轉換成GPU記憶體的實體地址,也可以轉換成CPU的實體地址。因此,GPU Page Table可以將GPU虛擬地址和CPU記憶體地址統一到GPU統一虛擬地址空間來。
  • PCI-e BAR

    • GPU 裝置通過PCI-e匯流排接入到主機上。 Base Address Registers(BARs) 是 MMIO的視窗,在GPU啟動時候配置。
    • GPU的控制暫存器和記憶體都對映到了BARs中。
    • GPU裝置記憶體通過對映的MMIO視窗去配置GPU和訪問GPU記憶體。
  • PFIFO Engine

    • PFIFO是GPU命令提交通過的一個特殊的部件。
    • PFIFO維護了一些獨立命令佇列,也就是Channel。
    • 此命令佇列是Ring Buffer,有PUT和GET的指標。
    • 所有訪問Channel控制區域的執行指令都被PFIFO 攔截下來。
    • GPU驅動使用Channel Descriptor來儲存相關的Channel設定。
    • PFIFO將讀取的命令轉交給PGRAPH Engine。
  • BO

    • Buffer Object (BO),記憶體的一塊(Block),能夠用於儲存紋理(Texture)、渲染目標(Render Target)、著色程式碼(shader code)等等。

    • Nouveau和Gdev經常使用BO。

      Nouveau是一個自由及開放原始碼顯示卡驅動程式,是為NVidia的顯示卡所編寫。

      Gdev是一套豐富的開源軟體,用於NVIDIA的GPGPU技術,包括裝置驅動程式。

更多詳細可以閱讀論文:Data Transfer Matters for GPU Computing。

4.4.5 CPU-GPU資料流

下圖是分離式架構的CPU-GPU的資料流程圖:

1、將主存的處理資料複製到視訊記憶體中。

2、CPU指令驅動GPU。

3、GPU中的每個運算單元並行處理。

4、GPU將視訊記憶體結果傳回主存。

4.4.6 顯像機制

  • 水平和垂直同步訊號

    在早期的CRT顯示器,電子槍從上到下逐行掃描,掃描完成後顯示器就呈現一幀畫面。然後電子槍回到初始位置進行下一次掃描。為了同步顯示器的顯示過程和系統的視訊控制器,顯示器會用硬體時鐘產生一系列的定時訊號。

    當電子槍換行進行掃描時,顯示器會發出一個水平同步訊號(horizonal synchronization),簡稱 HSync

    當一幀畫面繪製完成後,電子槍回覆到原位,準備畫下一幀前,顯示器會發出一個垂直同步訊號(vertical synchronization),簡稱 VSync。

    顯示器通常以固定頻率進行重新整理,這個重新整理率就是 VSync 訊號產生的頻率。雖然現在的顯示器基本都是液晶顯示屏了,但其原理基本一致。

    CPU將計算好顯示內容提交至 GPU,GPU 渲染完成後將渲染結果存入幀緩衝區,視訊控制器會按照 VSync 訊號逐幀讀取幀緩衝區的資料,經過資料轉換後最終由顯示器進行顯示。

  • 雙緩衝

    在單緩衝下,幀緩衝區的讀取和重新整理都都會有比較大的效率問題,經常會出現相互等待的情況,導致幀率下降。

    為了解決效率問題,GPU 通常會引入兩個緩衝區,即 雙緩衝機制。在這種情況下,GPU 會預先渲染一幀放入一個緩衝區中,用於視訊控制器的讀取。當下一幀渲染完畢後,GPU 會直接把視訊控制器的指標指向第二個緩衝器。

  • 垂直同步

    雙緩衝雖然能解決效率問題,但會引入一個新的問題。當視訊控制器還未讀取完成時,即螢幕內容剛顯示一半時,GPU 將新的一幀內容提交到幀緩衝區並把兩個緩衝區進行交換後,視訊控制器就會把新的一幀資料的下半段顯示到螢幕上,造成畫面撕裂現象:

    為了解決這個問題,GPU 通常有一個機制叫做垂直同步(簡寫也是V-Sync),當開啟垂直同步後,GPU 會等待顯示器的 VSync 訊號發出後,才進行新的一幀渲染和緩衝區更新。這樣能解決畫面撕裂現象,也增加了畫面流暢度,但需要消費更多的計算資源,也會帶來部分延遲。

4.5 Shader執行機制

Shader程式碼也跟傳統的C++等語言類似,需要將面向人類的高階語言(GLSL、HLSL、CGSL)通過編譯器轉成面向機器的二進位制指令,二進位制指令可轉譯成彙編程式碼,以便技術人員查閱和除錯。

由高階語言編譯成彙編指令的過程通常是在離線階段執行,以減輕執行時的消耗。

在執行階段,CPU端將shader二進位制指令經由PCI-e推送到GPU端,GPU在執行程式碼時,會用Context將指令分成若干Channel推送到各個Core的儲存空間。

對現代GPU而言,可程式設計的階段越來越多,包含但不限於:頂點著色器(Vertex Shader)、曲面細分控制著色器(Tessellation Control Shader)、幾何著色器(Geometry Shader)、畫素/片元著色器(Fragment Shader)、計算著色器(Compute Shader)、...

這些著色器形成流水線式的並行化的渲染管線。下面將配合具體的例子說明。

下段是計算漫反射的經典程式碼:

sampler mySamp;
Texture2D<float3> myTex;
float3 lightDir;

float4 diffuseShader(float3 norm, float2 uv)
{
    float3 kd;
    kd = myTex.Sample(mySamp, uv);
    kd *= clamp( dot(lightDir, norm), 0.0, 1.0);
    return float4(kd, 1.0);
}

經過編譯後成為彙編程式碼:

<diffuseShader>:
sample r0, v4, t0, s0
mul    r3, v0, cb0[0]
madd   r3, v1, cb0[1], r3
madd   r3, v2, cb0[2], r3
clmp   r3, r3, l(0.0), l(1.0)
mul    o0, r0, r3
mul    o1, r1, r3
mul    o2, r2, r3
mov    o3, l(1.0)

在執行階段,以上彙編程式碼會被GPU推送到執行上下文(Execution Context),然後ALU會逐條獲取(Detch)、解碼(Decode)彙編指令,並執行它們。

以上示例圖只是單個ALU的執行情況,實際上,GPU有幾十甚至上百個執行單元在同時執行shader指令:

對於SIMT架構的GPU,彙編指令有所不同,變成了SIMT特定指令程式碼:

<VEC8_diffuseShader>: 
VEC8_sample vec_r0, vec_v4, t0, vec_s0 
VEC8_mul    vec_r3, vec_v0, cb0[0] 
VEC8_madd   vec_r3, vec_v1, cb0[1], vec_r3 
VEC8_madd   vec_r3, vec_v2, cb0[2], vec_r3
VEC8_clmp   vec_r3, vec_r3, l(0.0), l(1.0) 
VEC8_mul    vec_o0, vec_r0, vec_r3 
VEC8_mul    vec_o1, vec_r1, vec_r3 
VEC8_mul    vec_o2, vec_r2, vec_r3 
VEC8_mov    o3, l(1.0)

並且Context以Core為單位組成共享的結構,同一個Core的多個ALU共享一組Context:

如果有多個Core,就會有更多的ALU同時參與shader計算,每個Core執行的資料是不一樣的,可能是頂點、圖元、畫素等任何資料:

4.6 利用擴充套件例證

NV shader thread group提供了OpenGL的擴充套件,可以查詢GPU執行緒、Core、SM、Warp等硬體相關的屬性。如果要開啟次此擴充套件,需要滿足以下條件:

  • OpenGL 4.3+;
  • GLSL 4.3+;
  • 支援OpenGL 4.3+的NV顯示卡;

並且此擴充套件只在NV部分5代著色器內起作用:

This extension interacts with NV_gpu_program5
This extension interacts with NV_compute_program5
This extension interacts with NV_tessellation_program5

下面是具體的欄位和代表的意義:

// 開啟擴充套件
#extension GL_NV_shader_thread_group : require     (or enable)

WARP_SIZE_NV    // 單個執行緒束的執行緒數量
WARPS_PER_SM_NV // 單個SM的執行緒束數量
SM_COUNT_NV     // SM數量

uniform uint  gl_WarpSizeNV;    // 單個執行緒束的執行緒數量
uniform uint  gl_WarpsPerSMNV;  // 單個SM的執行緒束數量
uniform uint  gl_SMCountNV;     // SM數量

in uint  gl_WarpIDNV;       // 當前執行緒束id
in uint  gl_SMIDNV;         // 當前執行緒束所在的SM id,取值[0, gl_SMCountNV-1]
in uint  gl_ThreadInWarpNV; // 當前執行緒id,取值[0, gl_WarpSizeNV-1]

in uint  gl_ThreadEqMaskNV; // 是否等於當前執行緒id的位域掩碼。
in uint  gl_ThreadGeMaskNV; // 是否大於等於當前執行緒id的位域掩碼。
in uint  gl_ThreadGtMaskNV; // 是否大於當前執行緒id的位域掩碼。
in uint  gl_ThreadLeMaskNV; // 是否小於等於當前執行緒id的位域掩碼。
in uint  gl_ThreadLtMaskNV; // 是否小於當前執行緒id的位域掩碼。

in bool  gl_HelperThreadNV; // 當前執行緒是否協助型執行緒。

上述所說的協助型執行緒gl_HelperThreadNV是指在處理2x2的畫素塊時,那些未被圖元覆蓋的畫素著色器執行緒將被標記為gl_HelperThreadNV = true,它們的結果將被忽略,也不會被儲存,但可輔助一些計算,如導數dFdxdFdy。為了防止理解有誤,貼出原文:

The variable gl_HelperThreadNV specifies if the current thread is a helper thread. In implementations supporting this extension, fragment shader invocations may be arranged in SIMD thread groups of 2x2 fragments called "quad". When a fragment shader instruction is executed on a quad, it's possible that some fragments within the quad will execute the instruction even if they are not covered by the primitive. Those threads are called helper threads. Their outputs will be discarded and they will not execute global store functions, but the intermediate values they compute can still be used by thread group sharing functions or by fragment derivative functions like dFdx and dFdy.

利用以上欄位,可以編寫特殊shader程式碼轉成顏色資訊,以便視覺化窺探GPU的工作機制和流程。

利用NV擴充套件欄位,可視化了頂點著色器、畫素著色器的SM、Warp id,方便查探GPU的工作機制和流程。

下面正式進入驗證階段,將以Geforce RTX 2060作為驗證物件,具體資訊如下:

作業系統: Windows 10 Pro, 64-bit

DirectX 版本: 12.0
GPU 處理器: GeForce RTX 2060
驅動程式版本: 417.71
Driver Type: Standard
Direct3D API 版本: 12
Direct3D 功能級別:12_1

CUDA 核心: 1920
核心時鐘: 1710 MHz
記憶體資料速率: 14.00 Gbps
記憶體介面: 192-位
記憶體頻寬: 336.05 GB/秒
全部可用的圖形記憶體:22494MB
專用視訊記憶體: 6144 MB GDDR6
系統視訊記憶體: 0MB
共享系統記憶體: 16350MB
視訊 BIOS 版本: 90.06.3F.00.73
IRQ: Not used
匯流排: PCI Express x16 Gen3

首先在應用程式建立包含兩個三角形的頂點資料:

// set up vertex data (and buffer(s)) and configure vertex attributes
const float HalfSize = 1.0f;
float vertices[] = {
    -HalfSize, -HalfSize, 0.0f, // left bottom
    HalfSize, -HalfSize, 0.0f,  // right bottom
    -HalfSize,  HalfSize, 0.0f, // top left

    -HalfSize,  HalfSize, 0.0f, // top left
    HalfSize, -HalfSize, 0.0f,  // right bottom
    HalfSize,  HalfSize, 0.0f,  // top right
}; 

渲染採用的頂點著色器非常簡單:

#version 430 core

layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos, 1.0f);
}

片元著色器也是寥寥數行:

#version 430 core

out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

繪製出來的原始畫面如下:

緊接著,修改片元著色器,加入擴充套件所需的程式碼,並修改顏色計算:

#version 430 core
#extension GL_NV_shader_thread_group : require

uniform uint  gl_WarpSizeNV;    // 單個執行緒束的執行緒數量
uniform uint  gl_WarpsPerSMNV;  // 單個SM的執行緒束數量
uniform uint  gl_SMCountNV;     // SM數量

in uint  gl_WarpIDNV;       // 當前執行緒束id
in uint  gl_SMIDNV;         // 當前執行緒所在的SM id,取值[0, gl_SMCountNV-1]
in uint  gl_ThreadInWarpNV; // 當前執行緒id,取值[0, gl_WarpSizeNV-1]

out vec4 FragColor;

void main()
{
    // SM id
    float lightness = gl_SMIDNV / gl_SMCountNV;
    FragColor = vec4(lightness);
}

由上面的程式碼渲染的以gl_SMIDNV的畫面如下:

從上面可分析出一些資訊:

  • 畫面共有32個亮度色階,也就是Geforce RTX 2060有32個SM。
  • 單個SM每次渲染16x16為單位的畫素塊,也就是每個SM有256個Core。
  • SM之間不是順序分配畫素塊,而是無序分配。
  • 不同三角形的接縫處出現斷層,說明同一個畫素塊如果分屬不同的三角形,就會分配到不同的SM進行處理。由此推斷,相同面積的區域,如果所屬的三角形越多,就會造成分配給SM的次數越多,消耗越多的渲染效能。

接著修改片元著色器的顏色計算程式碼以顯示執行緒束id:

// warp id
float lightness = gl_WarpIDNV / gl_WarpsPerSMNV;
FragColor = vec4(lightness);

得到如下畫面:

由此可得出一些資訊或推論:

  • 畫面共有32個亮度色階,也就是每個SM有32個Warp,每個Warp有8個Core。

  • 每個色塊畫素是4x8,由於每個Warp有8個Core,由此推斷每個Core單次要處理2x2的最小單元畫素塊。

  • 也是無序分配畫素塊。
  • 三角形接縫處出現斷層,同SM的推斷一致。

再修改片元著色器的顏色計算程式碼以顯示執行緒id:

// thread id
float lightness = gl_ThreadInWarpNV / gl_WarpSizeNV;
FragColor = vec4(lightness);

得到如下畫面:

為了方便分析,用Photoshop對中間區域性放大10倍,得到以下畫面:

結合上面兩幅圖,也可以得出一些結論:

  • 相較SM、執行緒束,執行緒分佈圖比較規律。說明同一個Warp的執行緒分佈是規律的。
  • 三角形接縫處出現紊亂,說明是不同的Warp造成了不同的執行緒。
  • 畫面有32個色階,說明單個Warp有32個執行緒,每個執行緒處理一個畫素。

再次說明,以上畫面和結論是基於Geforce RTX 2060,不同型號的GPU可能會不一樣,得到的結果和推論也會有所不同。

更多NV擴充套件可參見OpenGL官網:NV extensions。

 

五、總結

5.1 CPU vs GPU

CPU和GPU的差異可以描述在下面表格中:

CPU GPU
延遲容忍度
並行目標 任務(Task) 資料(Data)
核心架構 多執行緒核心 SIMT核心
執行緒數量級別 10 10000
吞吐量
快取需求量
執行緒獨立性

它們之間的差異(快取、核心數量、記憶體、執行緒數等)可用下圖展示出來:

5.2 渲染優化建議

由上章的分析,可以很容易給出渲染優化建議:

  • 減少CPU和GPU的資料交換:
    • 合批(Batch)
    • 減少頂點數、三角形數
    • 視錐裁剪
      • BVH
      • Portal
      • BSP
      • OSP
    • 避免每幀提交Buffer資料
      • CPU版的粒子、動畫會每幀修改、提交資料,可移至GPU端。
    • 減少渲染狀態設定和查詢
      • 例如:glGetUniformLocation會從GPU記憶體查詢狀態,耗費很多時間週期。
      • 避免每幀設定、查詢渲染狀態,可在初始化時快取狀態。
    • 啟用GPU Instance
    • 開啟LOD
    • 避免從視訊記憶體讀資料
  • 減少過繪製:
    • 避免Tex Kill操作
    • 避免Alpha Test
    • 避免Alpha Blend
    • 開啟深度測試
      • Early-Z
      • 層次Z緩衝(Hierarchical Z-Buffering,HZB)
    • 開啟裁剪:
      • 背面裁剪
      • 遮擋裁剪
      • 視口裁剪
    • 控制物體數量
      • 粒子數量多且面積小,由於畫素塊機制,會加劇過繪製情況
      • 植物、沙石、毛髮等也如此
  • Shader優化:
    • 避免if、switch分支語句
    • 避免for迴圈語句,特別是迴圈次數可變的
    • 減少紋理取樣次數
    • 禁用clipdiscard操作
    • 減少複雜數學函式呼叫

更多優化技巧可閱讀:

  • 移動遊戲效能優化通用技法。
  • GPU Programming Guide。

  • Real-Time Rendering Resources。

5.3 GPU的未來

從章節2.2 GPU歷史可以得出一些結論,也可以推測GPU發展的趨勢:

  • 硬體升級:更多運算單元,更多儲存空間,更高併發,更高頻寬,更低延時。。。

  • Tile-Based Rendering的整合。基於瓦片的渲染可以一定程度降低頻寬和提升光照計算效率,目前部分移動端及桌面的GPU已經引入這個技術,未來將有望成為常態。

  • 3D記憶體技術。目前大多數傳統的記憶體是2D的,3D記憶體則不同,在物理結構上是3D的,類似立方體結構,集成於晶片內。可獲得幾倍的訪問速度和效能比。

  • GPU愈加可程式設計化。GPU天生是並行且相對固定的,未來將會開放越來越多的shader可供程式設計,而CPU剛好相反,將往並行化發展。也就是說,未來的GPU越來越像CPU,而CPU越來越像GPU。難道它們應驗了古語:合久必分,分久必合麼?

  • 實時光照追蹤的普及。基於Turi