切換
舊版
前往
大廳
主題

SIGGRAPH 2015: DX12、Vulkan與Metal的比較

Lumi | 2015-09-09 20:51:22 | 巴幣 4 | 人氣 1489

此篇乃NVIDIA在SIGGRAPH2015所發表Next-Generation Graphics APIs的整理,希望有更多人對寫繪圖程式有興趣。


為什麼我們需要新的圖形API?

這些新的API目標都是提升CPU效能,以下說明如何達成目的:

減少CPU負擔,減低CPU成為瓶頸的機會
傳統只有一個thread會用來交付GPU工作,一旦遇到複雜情境,CPU就會成為瓶頸。一般作法是使用render thread來專門和GPU打交道,減輕main thread的負擔。即使driver內部改用多個thread同時處理,改善幅度仍舊有限。新API解決問題的方式很直接,即所有thread都可以交付GPU工作。

Driver的效能更穩定、更容易預測
以往呼叫某個API時,driver可能會開始編譯shader、或是插入fence、清除快取、配置記憶體等。這意味著每次呼叫同一個API,所花費的時間可能都不一樣,也因此每個frame的時間很難一致,此問題因下述功能得以解決。

提供明確、更接近遊戲主機的控制能力,
應用程式現在得自行負責CPU與GPU之間所共享的資源的同步問題、防止資源同時被讀寫,還有記憶體管理。


各家API的相似與相異處

Command Buffers
當CPU thread要交付GPU工作時,並不是一次只送一項指令(command),而是將指令收集起來。傳統上只有一個thread會收集指令,且收集動作是藏在driver內部。等到應用程式下令送出(flush),driver才會將整個command buffer送給GPU。在GPU依順序執行指令的期間,CPU又回到收集指令的作業。

三個新API都提供了command buffer與command queue物件,我們可在每個thread分別使用不同buffer,將指令填充進去。

D3D12 Metal Vulkan
ID3D12CommandList MTLCommandBuffer VkCmdBuffer
ID3D12CommandQueue MTLCommandQueue VkCmdQueue

三者相同處:
  • 任何thread都可儲存與交付指令
  • Command buffer內部不透明,無法像遊戲主機一樣製作pre-built buffer
  • State不會繼承,不同Buffer的State設定不會互相影響

三者相異處:
  • Metal的command buffer被提交後就無法再使用
  • Vulkan與D3D12中允許重複使用
    • 可在不同frame提交同一個command buffer
    • command buffer內可包含另一個commander buffer

Pipeline State Objects(PSO)

與以往不同,現在大部分的state設定值都儲存在PSO裡面。應用程式無法在繪圖中切換單一個state,必須整組一起切換。這種設計可以讓driver提早編譯和檢查state的錯誤,避免driver在繪圖中切換state時還得停下來檢查。

PSO內包含所有階段的shader、大部分fixed-function的state,還有vertex與render target在記憶體中的格式(format)。vertex buffer與texture不會直接存進PSO內,而是用綁定(binding)的方式,其他沒在PSO內的fixed-function state的處理方式則是各家API稍有不同。

D3D10是直接對command buffer設定這些state,Metal中的一部分state也是如此:
d3dCommandBuffer->OMSetStencilRef(0xFFFFFFFF);
mtlCommandBuffer.setTriangleFillMode(.Lines);

Vulkan的所有state與Metal一部分的state使用小型的state物件來儲存:
mtlCommandBuffer.setDepthStencilState(mtlDepthStencilState);
vkCreateDynamicViewportState(device, &vpInfo, &vpState);

Tiled architectures和Pass的設計只出現在Metal與Vulkan,詳細看這裡


Memory and Resources
此小節只與DX12與Vulkan有關。

以下是專有名詞解釋:
  • Allocation, 代表一塊實體或虛擬的address space
    • 配置記憶體時可選擇cache行為、CPU與GPU是否可存取
  • Resource, 特定(記憶體)布局的記憶體
    • 可以是一個線性buffer,或是2D/3D/multisample texture
  • View,特定"格式/用途"的resource
    • color render target或depth stencil target

兩者用語比較:
D3D12 Vulkan
Allocation ID3D12Heap VkDeviceMemory
Resource ID3D12Resource VkImage
VkBuffer
View ID3D12DepthStencilView
ID3D12RenderTargetView
...
VkImageView
VkBufferView

下圖是新API的resource binding模型。

每個綁定點稱作descriptor,descriptor會儲存指向資源的指標和其它相關資訊,例如texture的長寬和格式等。不同GPU可能會需要不同的資訊,所以descriptor設計成對應用程式不透明。
Descriptor table是一個API物件,相當於一塊用來儲存descriptor的buffer。在D3D中,指向一組descriptor table的物件被稱為root table。
Pipeline layout(或root layout)也是個API物件,它用來規定每一個descriptor應該指向什麼資源。不同的PSO可使用相同的pipeline layout,如此一來繪圖時便可使用同一組table。

D3D12與Vulkan皆提供pool(heap)用於配置descriptor table,以下是兩者用語比較:
D3D12 Vulkan
ID3D12DescriptorHeap VkDescriptorPool
- VkDescriptorSet
D3D12_ROOT_DESCRIPTOR_TABLE VkDescriptorSetLayout
ID3D12RootLayout VkPipelineLayout


Data Hazards and Object Lifetimes
舊API driver會在背後自動處理資料存取的問題,例如:
  • 對一塊正在被GPU使用中的buffer做記憶體映射(map),driver會等待該buffer使用完,或是乾脆配置一塊新的buffer,然後找時機取代掉舊的。
  • 繪圖到一張image上,然後馬上將它當作texture。driver會防止資料寫入前就作讀取動作,並處理cache一致性問題。
  • 配置比GPU記憶體還大的texture,drvier會幫忙將資料page in/out來製造空間。

新API全都得自己來:
  • 明確同步CPU/GPU
    • 沒有map discard或類似的功能了,此功能會使效能無法被預測
    • 不要寫入GPU正在讀取的資料
    • 用ID3D12Fence/VkEvent來同步
  • 明確管理物件生命週期
    • 不要刪除GPU正在用的物件
  • 明確管理資源常駐(D3D12)
  • 明確指示資源的transition(見這裡state transition小節的說明)
    • 每個資源都處於某個狀態(state),例如"我現在是texture",或"我現在是color target"
    • driver不會自動變更資源的狀態,須明確下指令來切換
    • driver會在狀態切換時自動插入barrier、清空cache、壓縮或解壓縮資料
    • 使用錯誤狀態的資源會得到未定義的結果

總結
新的API做了一些妥協,我們得到更多的控制權和可預測性,但我們也承擔了一部分driver的責任,更像是在遊戲主機上寫程式。SIGGRAPH 2015還有一些演講者分享使用新API的心得,還有使新API容易使用的策略,各位有興趣可以接著到這裡


創作回應

更多創作