2021年9月6日 星期一

GPU 架構 淺談 : IMR, TBR, TBDR

        最近工作回歸到 GPU 領域, 也再次興起寫些東西的想法, 回顧blog 的歷史紀錄,發現上次寫文章, 已經是 8 年前的事了, 心中除了些許震驚之外, 也隱隱感嘆歲月流逝是如此的迅速. 

在這段時間, 整個 GPU 或是 Rendering 的技術推陳出新. 有需多的技術出現了. 有些是新的概念, 有些則是舊有的想法逐漸演進, 隨著硬體環境以及標準的演進與成熟, 而得以實現, 因此可以說隨手拾起都是新東西,例如 Vulkan , NV(Turing), Imagination (GR6500) GPU 的 Ray Tracing.

所謂 千里之路 始於足下, 縱使有這麼多的新技術與想法可以讓我們可以去學習, 我們還是要從邁開腳下的第一步開始. 因此我想就用 GPU 基本架構分類作為我再次出發時邁出的第一步.


讓我們開始吧.

目前三種 GPU 架構, 分別是 IMR, TBR 以及 TBDR. 

IMR : Immediate Mode Rendering

這應該是最直覺且容易理解的一種 rendering path, IMR 的 block diagram 可以參考圖一


         IMR 架構的 GPU 處理 primitive 的先後順序是根據 Primitive 在 Vertex Data 中的順序以及調用 Drawcall 的先後順序決定的.例如 Drawcall #1 送出 Triangle A  然後是 Triangle B, 接著 Drawcall #2 送出 Triangle C, 則 IMR GPU 處理 Triangle 的順序依序是 A, B 然後是 C. 

         因為這樣的特性, 一般來說 IMR 架構相比其他兩種架構會有較高的帶寬(memory bandwidth)用量 以及 overdraw(同一個位置的pixel 被重複寫入多次) , 為什麼會這樣呢?
在說明原因之前, 我們先看一下 IMR 架構的 GPU 處理 primitive 的 流程:
         
以前面的 Drawcall #1 為例子, GPU 會先處理 Triangle A, Vertex Processing 階段會從 memory 提取 Vertex Data, 進行座標轉換, 在下一級進行 view plane Clipping 以及 backface  Culling.

Rasterization 會將 geometry data  轉換成 fragment 並內插出per-fragment 的 attributes(e.g. depth, color, texture coordinate,..), 如果 fragment shader 裡面不會調整新的 depth 值則 early depth testing 會先進行 depth testing 以減少進入 fragment processing 的工作量. 否則就 bypass early depth testing.

進入 fragment processing, 每個 fragment 會從 memory 提取 texture data 並進行 fragment 計算(shading), 如果 fragment shader 會調整 depth 則接著下一步到 later depth testing 進行 depth testing, 這裡可能會需要再讀寫 Depth buffer. 

最後到 ROP, 如果需要 alpha blending, 還會需要讀取memory(framebuffer), 進行 alpha blending 計算後再寫回 memory.



圖二是處理完  Triangle A後, framebuffer 的內容.
歸納以上的描述可知, Triangle A 的每個 fragment 都會占用帶寬, 以及GPU 的計算量. 

接著, GPU 使用相同的流程處理 Drawcall #1 的Triangle B. 我們假設 Triangle B 的每個 fragment 的 depth 都是小於 Triangle A 的, 也就是說, 整個 Triangle B 距離 view point 的距離小於 Triangle A.

GPU 處理完 Triangle B 後, Framebuffer 的內容如圖三.

從圖三可以看出, 原本 triangle A 所涵蓋的 pixel, 有一部分被後來的 Triangle B 覆蓋了
這些被覆蓋的 pixel 最後不會出現在 framebuffer 中, 因此在 Triangle A階段, 這些 fragment 所使用的帶寬以及計算量都被浪費了,  這種同一個 pixel 被多次複寫的狀況稱做 overdraw. 

在圖四中用橘色標出 overdraw 的部分. 在這個例子裡 Triangle A 大約 84% 的帶寬用量和計算量因為被 Triangle B  overdrawing 而浪費了.


PC 平台的 GPU 以 IMR 架構為主, 在 PC 平台,  GPU 會有自己專用的 local memory, 不會
共用系統的 system memory,  因為不會搶到 系統上其它模組的帶寬, 因此即使多消耗帶寬, 對系統的影響不大. 且 IMR GPU 架構相對簡單, 除了上述的開銷, 沒有其它架構所特有的 overhead.

雖然 IMR 的額外開銷在 PC 平台上可以被接受, 但是 mobile 平台狀況並不相同, 在 mobile 平台上, GPU 和其它 SOC 上的模組共用 system memory, 只是把 system memory 的一個區塊劃分給 GPU 使用. 帶寬的使用量和使用者體驗會有關聯性.

如果 GPU 使用的帶寬過高, 會影響到 SOC 中的其它 module 可用的帶寬量(每個 module 都有優先權高低之分), 而且 帶寬的使用量越多, 代表會有越高的功耗, 在 mobile 平台上, 如何減少功耗是一個重要的課題, 越低的功耗, 除了代表越長的使用時間, 也會減少發熱, 產生的熱越少, 則發生 thermal throttle 導致系統的效能降低的機會越少, 就會有比較良好穩定的使用者體驗.


目前在 mobile 平台上的 GPU 架構有兩種, 分別是 TBR, 以及 TBDR.

TBR : Tile Based Rendering
圖五是 TBR GPU 的架構方塊圖.


Tile Based Rendering, 顧名思義, 是將整個 screen 分割成大小相同的區塊(e.g. 16 x 16 pixels), 區塊稱為 tile.

在一個 frame 中應用程式調用 drawcall 時, 會先由 tiling 處理每個 drawcall 的 primitives, 標定有哪些 title 被 primitives 覆蓋到, 把 primitive 的 id 記錄到該 tile 所屬 primitive list 中. 下圖以前面的 drawcall #1 為例說明 tiling.



app 調用完所有 drawcall 後, 最後會調用 eglswapbuffer (以 OpenGL/OpenGLES 為例) 完成一張 frame, 此時 GPU 會開始進行 rendering 階段, GPU 依次處裡每個 tile. 將 tile 中所有 primitives 處理完後, 再處理下一個 tile 的所有 primitives. 

因為tile size (e.g. 16 x 16) 遠小於 screen size (e.g. 720P), 因此 tile 上 shading 運算時所需的 memory 可以使用 on-chip memory, 因此不管 per-tile shading 的overdraw 有多高, 所有的 memory r/w 都不需要出到SOC 外部的 system memory, 只需在 per-tile 的所有primitives 都完成 shading 後, 一次性將 tile 的color buffer 寫到 frame buffer. 
因為使用了 on-chip memory, 因此減少功耗以及system memory 帶寬使用量. 這讓 TBR 架構比 IMR 架構更適合適用在 mobile 平台. 目前 ARM 的 mali GPU 就是屬於 TBR 架構 GPU.

從前面的描述可知, TBR 減少了系統帶寬的使用量, 但是每個 tile 中的 primitive 繪製還是會引發 overdraw, 這會讓使用了 GPU 計算資源的 fragment , 可能被後來的其它 primitive 的 fragment 複寫而導致計算浪費, 所以overdraw 的問題沒有得到解決. 因此有了 TBDR (Tile Based Deferred Rendering) 架構.

TBDR

TBDR 除了有 tiling 這個 "deferred", 另外 HSR & tag buffer, 是另一個 "deferred" 的部分. HSR (Hidden Surface Remove) 的目的是消除 tile 中的 overdraw, HSR 會遍歷 tile 中所有的 primitives.找出最後真正會被畫出的 fragment. 並用 tag buffer 記錄這些 fragment 所屬的 primitive.
只有這些真正visible 的 fragment 會進行 fragment processing. TBDR 架構可以真正做到 零 overdraw 並與場景 drawcall 順序完全無關, 這可以最大限度的節省 fragment processing 的運算資源. 獲得的好處基本上會超過 "deferred" 處裡本身須付出的 overhead. 目前 Imagination 的 GPU 就是採用 TBDR 架構.