Unity 2017 Game Optimization 讀書筆記 Dynamic Graphics(1)

The Rendering Pipeline

渲染表現差有可能取決於CPU端(CPU Bound)也有可能取決於GPU(GPU Bound).調查CPU-bound的問題相對簡單,因爲CPU端的工作就是從硬盤或者內存中加載數據並且調用圖形APU指令。想找到GPU-bound的原因會困難很多,因爲在渲染管線中很多地方都有可能是引起問題的原因。解決GPU 瓶頸的問題我們甚至可能得使用猜測法和排除法。

圖中第一行展示了CPU所做的工作,包括了通過硬件驅動調用圖形API以及把渲染指令傳輸到GPU中(通過Command Buffer) 

下邊的兩行展示了GPU所做的工作,還可以再細分爲Frond End 和 Back End兩部分。

The GPU Front End

Front End是渲染管線中處理頂點(vertex)數據的部分。它從cpu接收mesh數據和Draw Call請求。GPU隨後會收集所有vertex數據並且把它們傳入到Vertex Shaders中。在Vertex Shader中可以對頂點數據進行修改和處理,最後輸出的數據數量和輸入時保持1比1的比例(就是比如輸入100個頂點,處理後輸出的數據也是100個)。接下來頂點數據會組裝成片元(最常見的是三角形片元)並進行光柵化,根據頂點的位置和攝像機的視野會決定哪些像素會出現在最終的渲染結果中。這個過程輸出的是一個pixels的list,也就是常說的fragments,它將會用於Back End 的處理階段。

Vertex Shaders的輸出還可以用於Tessellation,它是在幾何着色器中處理(Geometry Shaders)。Geometry Shaders和Vertex Shaders 類似,但是它是1對多的關係,也因此可以產生更多的幾何細節。

總結下就是對於Front End:Vertex Shaders  -- Geometry Shaders(如果有) -- 光柵化 -- 輸出fragments給Back End處理。

The GPU Back End

Back End是渲染管線中處理Fragments的過程。每個Fragment將會傳入Fragment Shader中,這些Shader比Vertex Shader會複雜許多,比如可以包含Depth test(深度測試),alpha test(透明度測試),圖片採樣,光照計算,陰影計算等等。

經過這個過程處理後的數據就會傳入Frame Buffer中,通常渲染API中會有兩個Frame Buffer,一個用於顯示當前的,另一個會在GPU完成渲染指令後用於存儲下一幀要顯示的內容。當GPU接收到Swap Buffer指令(每一幀CPU發送的最後一個指令)後,連個frame buffer會交換,這樣新的一幀會被渲染出來,這個過程會在應用程序渲染的過程中不斷重複。

在Back End部分,會有兩個因素很有可能成爲瓶頸:Fill Rate(填充率) 和 Memory Bandwidth(內存帶寬)

Fill Rate

Fill rate和GPU渲染fragments的速度相關。一個fragment僅僅是有可能會最終成爲一個像素,如果它在fragment shader中某個test過程失敗了,就會被捨棄,這種方式能給性能帶來極大的提升,因爲渲染管線就可以略過這個fragment後邊的操作,去直接處理下一個fragment。

各種test的一個例子就是 Z-testing。它用於檢查一個fragment是否被另一個更靠近攝像機的fragment遮擋住。如果被遮擋住,就會被捨棄,如果沒有,就會被處理成一個像素,這會消耗fill rate中的一個fill。想象下,成千上萬的物體,每個物體又產生幾百幾千個fragments,很容易就會導致每幀需要處理幾百萬個fragemnt。

顯卡製造商把Fill Rate做爲顯卡性能的一個參數,通常是以Gigapixels per-second衡量,但是更準確的說法應該是 Gigafragments per-second(因爲最終的像素是屏幕固定的,但實際處理的是fragment)。不管咋說,反正這個值越大越好,說明處理能力越強。比如一個30Gigapixels每秒的顯卡,目標幀率是60hz,則 我們每幀能處理的fragment上限爲:30,000,000,000/60 = 500
million。這對於分辨率爲2560 x 1440,最理想狀態下每個像素只渲染一次的話,我們可以將整個場景渲染125次。

但是現實不是完美的,Fill Rate也會被其他渲染功能所消耗,比如陰影和後處理,他們都需要將同一個fragement數據在各自pass中處理多次。初次之外,我們通常也需要重複渲染同個像素多次,這個現象可以叫做Overdraw,它可以用於衡量我們是否有效的利用了Fill Rate。

Overdraw

通過additive alpha blending模式,我們可以很直觀的看到有多少OverDraw。越亮的地方代表着OverDraw越嚴重,因爲同一個像素被渲染了更多的次數。通過Scene window的Overdraw Shading模式即可查看:

 OverDraw越多,Fill Rate浪費越多,有一些技巧可以降低OverDraw,之後我們會討論。

值得注意的一點是渲染中有不同的隊列(queues),可以分爲兩類:Opaque(不透明)和Transparent(透明),Opaque隊列中渲染的物體可以通過之前介紹的Z-testing方法剔除fragments,但是Transparent隊列中渲染的物體則不行,這會導致很多OverDraw。Unity 的 UI總是使用Transparent隊列渲染,所以UI經常成爲OverDraw的重災區。

Memory Bandwidth

GPU 核心中有一個local texture Cache,它存儲了GPU最近使用的textures。這個cache和CPU內存的cache設計非常像,它處理速度非常快但是非常小,它使得texture的採樣開銷變小速度變快。

如果當一張textrue已經存儲在這個local texture Cache中,採樣速度會快如閃電,如果沒有,在採樣前則必須從VRAM中拉取這個texture,此時就是緩存沒有命中,這會花費時間去從VRAM中尋找並且獲取想要的那張texture。這個過程就會消耗Memory Bandwith,消耗的大小爲VRAM中存儲這個texture的大小(但是可能並不是這張圖原始大小,因爲GPU有壓縮技術)。

當Memory Bandwidth成爲瓶頸的時候,GPU不斷的尋找所需的textures,而cache會不斷的在等待所需的數據,GPU在Cache提供所需的數據之前,就沒法將渲染好的數據及時傳入到Frame Buffer中,從而導致幀率下降。

假設顯卡的Memory Bandwidth爲 96 GBs每秒,目標幀率爲60hz,則每幀可處理的texture數據量爲1.6 GBs (96/60)。雖然這麼算不精確,但是可以提供大致的估算。

要特別注意的是這個值並不是說工程中貼圖總量的最大值,也不是CPU 內存中貼圖總量的值,也不是GPU顯存中的貼圖總量的值。因爲每一幀中有可能會不斷的切換texture,有可能一個texture渲染多次,這取決於有多少Shader需要這些texture以及物體的渲染順序,有可能雖然只有幾張圖也會把Memory Bandwith消耗光。Shader中使用大量的texture會更有可能導致沒有命中緩存從而導致Memory Bandwith出現問題,當大量物體需要不同的各種高質量貼圖(法線貼圖,Emission貼圖等等)進行渲染時,就很容易觸發這種情況。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章