標籤:

[翻譯]A trip through the Graphics Pipeline 2011, part 1

A trip through the Graphics Pipeline 2011?

fgiesen.wordpress.com

Application

應用程序,你寫的代碼。

API runtime

資源創建/狀態設置/drawcall等。API主要記錄當前app設置的狀態,驗證參數以及其他錯誤和一致性檢查,管理用戶可見的資源,也許還會驗證shader和shader linkage(D3D一般會做,OpenGL會讓驅動去做),打包工作任務然後交給driver,確切的說是用戶態driver

User-mode driver(UMD)

CPU端大多數的「魔法」都發生在這兒。如果因為一些API調用你的App崩潰了,那麼大多都在這兒崩的。它叫做「nvd3dum.dll」(NVidia)或者」atiumd*.dll」(AMD)。正如同它的名字暗示的,它是用戶態代碼,在與你的程序一樣的上下文和地址空間中運行(API也是),而且沒有提升特權的能力。它實現了D3D需要調用的底層API(DDI),這些底層API會顯式的處理內存管理等工作。

Shader compilation也在UMD中,D3D會預先檢驗shader,在語法上和一些限制(比如使用正確的類型,沒有使用過量的textures/samplers,沒有超過cb的數量上限等)上做檢查,並把shader token stream傳給UMD。它從HLSL編譯而來,並且做了high-level的優化(循環優化,消除dead-code,常量展開,預測branch等)。它在編譯期做掉會大大減少driver的runtime負擔,但是driver還是要做一些底層的優化(比如寄存器分配,循環展開等)。簡單來說,它會轉化為中間表達(IR)然後進一步被編譯,D3D不關心硬體資源以及調度限制,所以需要UMD來處理這些繁瑣的底層細節。

而且如果你的遊戲眾所周知,NV/AMD的工程師也許會看你的shader然後給他們的硬體手寫優化版本,這些shader會被檢測出來然後在UMD中替換掉,別客氣 Orz

更好玩的是,一些API狀態會最終被編譯成shader,比如texture border有可能在sampler中根本沒有實現,而是使用額外的代碼來模擬(或者壓根就不支持)。這就表示即使相同的shader,可能因為API狀態不同而產生完全不同shader版本。

順便說一下,你會發現第一次使用一個新shader/資源會有一些延遲,是因為大量創建/編譯的工作會推遲到driver真正執行的時候才做(你都不敢相信一些程序創建了多少沒用的shit)。通常圖形程序員知道需要空畫一遍把所需的資源/shader「預熱」。

繼續UMD,它還會做一些D3D9的歷史遺留工作-固定函數管線,它會生成相應的shader來模擬固定管線。

之後就是類似內存管理這種事情了。UMD接收到類似創建貼圖的指令然後給他們提供空間。通常UMD會從KMD(kernel-mode driver)獲得更大的內存空間,mapping/unmapping pages(管理那些事UMD可以訪問的video memory空間,以及哪部分system memory可以由GPU訪問)是由KMD做的。

但是UMD能做一些事情比如交換texture的通道,調度system memory/video memory之間的傳輸等。最重要的是,當KMD分配並移交給它後,寫入command buffers(或者叫DMA buffers)。所有的狀態切換以及渲染操作會被UMD轉化為硬體能理解的commands。還有一些你不會顯式執行的操作,比如上傳紋理和shader到顯存。

總的來說,driver會儘可能多的把處理放到UMD中執行,UMD是用戶態代碼不需要使用昂貴的內核態切換開銷,它可以分配內存,多線程執行工作等,它只是一個DLL(儘管是API調用load它的,而不是你的程序)。而且這對開發driver也有好處,如果他crash了,那麼app跟著一起崩,但是操作系統不會崩,可以用普通的debugger調試等等。

但是有一個巨大的問題。UMD是一個DLL,可以被多個程序調用,因為如今的操作系統大多是多任務的。GPU是一種共享的資源,多個程序都去訪問它,而且假裝它們是唯一一個使用它的人。所以有一個圖形調度器,它會仲裁誰來訪問3D管線,根據時間片等考慮。切換上下文會帶來GPU狀態的額外切換(會產生額外的command buffer指令),而且有可能帶來顯存資源的swap in/out。一段時間內只有一個進程會提交3D指令。

你會經常聽到console程序員抱怨PC

3D API的低效,但是這是因為PC端的API和driver會遇到比console更加複雜的問題,他們需要記錄整套當前狀態,而且他們需要處理各種app並嘗試修復性能問題。

Kernel-mode drvier(KMD)

主要跟硬體打交道,同時可以有多個UMD,但總是只有一個KMD,所以如果KMD掛了,就直接藍屏,但是如今windows可以kill掉一個崩潰的driver並重新載入(重大進步!)

KMD負責分配映射物理內存,初始化GPU,設置顯示模式,管理硬體滑鼠指針,控制硬體timer,響應中斷等。另外還會負責DRM版權保護等解碼。

最重要的是KMD管理實際的command buffer,硬體真正讀取的那些。UMD產生的command buffer只是GPU可以取址的一片片隨機內存塊。UMD處理完成之後,把它們發送到scheduler,scheduler等待過程完成然後發送到KMD,KMD把調用command buffer的指令寫入main command buffer,根據GPU中的command processor能否直接讀取內存,如果不能的話需要把它們DMA到顯存中。Main command buffer通常是一種小型的ring buffer-寫入其中的內容是system/init command以及對「真正」具有內容的command buffer的調用。

但是目前他只是內存中的一段buffer,顯卡知道它的具體內容的讀取位置(read pointer),另外一個寫入位置(write pointer)由KMD來控制並寫入。Pointer是硬體的register,它們與內存有映射關係,KMD來周期性的管理更新。

匯流排(Bus)

CPU當然不能直接對顯卡進行讀寫,因為數據需要經過主板的匯流排。如今一般是PCIE,使用DMA傳輸。

Command Processor

這是GPU的前端,用來讀取KMD寫入的commands。接下去幾篇會介紹。

題外話:OpenGL

OpenGL和之前的描述差不多,區別在於API和UMD層。與D3D不同,GLSL的shader編譯並不是API處理的,它全權由驅動負責,所以副作用是每個顯卡廠商提供的驅動都不同,雖然標準一致,但實現千差萬別,各自有各自的bug;另外就是驅動需要自己來做所有的shader優化,包括那些很費的優化。D3D的bytecode格式是對這個問題的很好解決辦法,雖然顯卡廠商很多,但是編譯器統一隻有一個,而且它能做一些耗時的數據流分析等。

省略和簡化

因為這只是一個overview,有成千上萬的細節被跳過。(你是自己造過顯卡嗎我去)


鑒於我自己的翻譯水平和GPU體系結構水平都超級爛,對於語句不通順等情況大家就隨便看看,如果有技術上的謬誤問題還請大家斧正。接下來還會有十幾篇章節,以我拖延的速度估計也不會快,琢磨著又是有生之年系列了,還是推薦大家看原文。


推薦閱讀:

從零開始寫引擎(OPENGL)(四)-後處理實現
[GDC16]Global Illumination in Tom Clancys The Division
從零開始寫引擎(OPENGL)(三)-骨骼動畫系統(2)
《Exploring in UE4》Session與Onlinesubsystem[概念理解]

TAG:遊戲引擎 |