<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • 深入GPU硬件架構及運行機制

     

    一、導言

    對于大多數圖形渲染開發者,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 ](#4.3.6 像素塊(pixel quad)))。

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

    • 開啟Alpha Test:由于Alpha Test需要在像素著色器后面的Alpha Test階段比較,所以無法在像素著色器之前就決定該像素是否被剔除。
    • 開啟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、降低功耗,提高效能比。

    4、無效像素雖然不會被存儲結果,但可輔助有效像素求導函數。詳見4.6 利用擴展例證

    這種設計雖然有其優勢,但同時,也會激化過繪制(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);
    }
    

    由上面的代碼渲染的畫面如下:

    從上面可分析出一些信息:

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

    接著修改片元著色器的顏色計算代碼以顯示Warp 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)
      • 開啟裁剪:
        • 背面裁剪
        • 遮擋裁剪
        • 視口裁剪
        • 剪切矩形(scissor rectangle)
      • 控制物體數量
        • 粒子數量多且面積小,由于像素塊機制,會加劇過繪制情況
        • 植物、沙石、毛發等也如此
    • Shader優化:

      • 避免if、switch分支語句
      • 避免for循環語句,特別是循環次數可變的
      • 減少紋理采樣次數
      • 禁用clipdiscard操作
      • 減少復雜數學函數調用

    更多優化技巧可閱讀:

    5.3 GPU的未來

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

    • 硬件升級。更多運算單元,更多存儲空間,更高并發,更高帶寬,更低延時。。。

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

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

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

    • 實時光照追蹤的普及。基于Turing架構的GPU已經加入大量RT Core、HVB、AI降噪等技術,Hybrid Rendering Pipeline就是此架構的光線追蹤渲染管線,能夠同時結合光柵化器、RT Core、Compute Core執行混合渲染:

      Hybrid Rendering Pipeline相當于光線追蹤渲染管線和光柵化渲染管線的合體:

    • 數據并發提升、深度神經網絡、GPU計算單元等普及及提升

    • AI降噪和AI抗鋸齒。AI降噪已經在部分RTX系列的光線追蹤版本得到應用,而AI抗鋸齒(Super Res)可用于超高分辨率的視頻圖像抗鋸齒:

    • 基于任務和網格著色器的渲染管線。基于任務和網格著色器的渲染管線(Graphics Pipeline with Task and Mesh Shaders)與傳統的光柵化渲染光線有著很大的差異,它以線程組(Thread Group)、任務著色器(Task shader)和網格著色器(Mesh shader)為基礎,形成一種全新的渲染管線:

      關于此技術的更多詳情可閱讀:NVIDIA Turing Architecture Whitepaper

    • 可變速率著色(Variable Rate Shading)。可變利率著色技術可判斷畫面區域的重要性(或由應用程序指定),然后根據畫面區域的重要性程度采用不同的著色分辨率精度,可以顯著降低功耗,提高著色效率。

    5.4 結語

    本文系統地講解了GPU的歷史、發展、工作流程,以及部分過程的細化說明和用到的各種技術,我們從中可以看到GPU架構的動機、機制、瓶頸,以及未來的發展。

    希望看完本文,大家能很好地回答導言提出的問題:1.3 帶著問題閱讀。如果不能全部回答,也沒關系,回頭看相關章節,總能找到答案。

    如果想更深入地了解GPU的設計細節、實現細節,可閱讀GPU廠商定期發布的白皮書和各大高校、機構發布的論文。推薦一個GPU解說視頻:A trip through the Graphics Pipeline 2011: Index,雖然是多年前的視頻,但比較系統、全面地講解了GPU的機制和技術。

     

    特別說明

    • 感謝所有參考文獻的作者們!

    • 原創文章,未經許可,禁止轉載!

     

    參考文獻

    posted @ 2019-09-06 12:40  0向往0  閱讀(83006)  評論(27編輯  收藏  舉報
    国产美女a做受大片观看