天天看點

圖形API學習工程(6):建立并使用UniformBuffer目标Buffer的用途目前的抽象OpenGL實作D3D11實作D3D12實作Vulkan實作效果後續問題

工程GIT位址:https://gitee.com/yaksue/yaksue-graphics

目标

所謂的 “UniformBuffer” (在DirectX中被稱為 “ConstantBuffer” )是指一塊能在shader中通路的資料,其值可以在CPU中每幀更新。

這個術語中的“Uniform”(統一的)、“Constant”(不變的)描述,我覺得是相對于那些在每個“頂點”或“像素”中不一樣的資料。在一次繪制調用(Draw Call)裡,“UniformBuffer”相當于一個不會變化的全局資料,每個頂點和像素都可以通路到。而那些 逐頂點/像素 的資料,則會在 頂點着色器/像素着色器 中各自并行計算。

在這一篇裡,我的目标是建立一個UniformBuffer,其中存儲一個表示偏移的向量。頂點着色器中将讀取這個向量并将其加算到最終輸出的頂點位置中。(另外為了調試,還在每一幀中通過鍵盤控制這個值的變化)

我先參考了一些範例代碼,力求将效果先跑出來。然後再研究這部分代碼的邏輯。

在代碼的參考上,主要是:

  • OpenGL:《【OpenGL程式設計】Uniform緩沖對象(Uniform Buffer Object)_憨豆酒的部落格-CSDN部落格》
  • D3D11:官方SDK中的範例:【Tutorial 4: 3D Spaces】
  • D3D12:DX12官方範例中的【D3D12HelloConstBuffers】
  • Vulkan:官方教程的【Uniform Buffers】

而概念的學習主要參考:

  • OpenGL:《OpenGL Programming Guide, 8th Edition》
  • D3D11:官方SDK中的範例:【Tutorial 4: 3D Spaces】
  • D3D12:《DX12龍書》
  • Vulkan:官方教程

Buffer的用途

Buffer,其實說白了就是一塊資料,這裡特指能被GPU所通路的資料,在D3D12中也稱為Resource。各個圖形API都有對應的接口來處理Buffer。

Buffer擁有廣泛的用途,例如 VertexBuffer,UniformBuffer等等。

對于OpenGL,建立Buffer的接口是

glGenBuffers

(還有

glCreateBuffers

?二者差別待查)。

而另一個和Buffer相關的接口是

glBindBuffer

,其第一個參數 “Buffer Binding Targets” 将代表Buffer的用途。其值是下列其中之一:

GL_ARRAY_BUFFER
GL_COPY_READ_BUFFER
GL_COPY_WRITE_BUFFER
GL_ELEMENT_ARRAY_BUFFER
GL_PIXEL_PACK_BUFFER
GL_PIXEL_UNPACK_BUFFER
GL_TEXTURE_BUFFER
GL_TRANSFORM_FEEDBACK_BUFFER
GL_UNIFORM_BUFFER
           

在《OpenGL Programming Guide, 8th Edition》的【Data in OpenGL Buffers】章節中有對其詳細的介紹。

對于D3D11,建立Buffer的接口是

ID3D11Device::CreateBuffer

。其參數

D3D11_BUFFER_DESC

包含一個

UINT BindFlags;

,它是

D3D11_BIND_FLAG

的組合。

enum D3D11_BIND_FLAG
{
    D3D11_BIND_VERTEX_BUFFER	= 0x1L,
    D3D11_BIND_INDEX_BUFFER	= 0x2L,
    D3D11_BIND_CONSTANT_BUFFER	= 0x4L,
    D3D11_BIND_SHADER_RESOURCE	= 0x8L,
    D3D11_BIND_STREAM_OUTPUT	= 0x10L,
    D3D11_BIND_RENDER_TARGET	= 0x20L,
    D3D11_BIND_DEPTH_STENCIL	= 0x40L,
    D3D11_BIND_UNORDERED_ACCESS	= 0x80L,
    D3D11_BIND_DECODER	= 0x200L,
    D3D11_BIND_VIDEO_ENCODER	= 0x400L
}
           

對于Vulkan,建立Buffer的接口是

vkCreateBuffer

,其參數

VkBufferCreateInfo

包含一個

VkBufferUsageFlags usage;

來表示用途,它是

VkBufferUsageFlagBits

的組合。

enum VkBufferUsageFlagBits {
    VK_BUFFER_USAGE_TRANSFER_SRC_BIT = 0x00000001,
    VK_BUFFER_USAGE_TRANSFER_DST_BIT = 0x00000002,
    VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000004,
    VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT = 0x00000008,
    VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT = 0x00000010,
    VK_BUFFER_USAGE_STORAGE_BUFFER_BIT = 0x00000020,
    VK_BUFFER_USAGE_INDEX_BUFFER_BIT = 0x00000040,
    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT = 0x00000080,
    VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT = 0x00000100,
    VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT = 0x00020000,
    VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT = 0x00000800,
    VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT = 0x00001000,
    VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT = 0x00000200,
    VK_BUFFER_USAGE_RAY_TRACING_BIT_KHR = 0x00000400,
    VK_BUFFER_USAGE_RAY_TRACING_BIT_NV = VK_BUFFER_USAGE_RAY_TRACING_BIT_KHR,
    VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_EXT = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
    VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
    VK_BUFFER_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
}
           

對于D3D12,建立Buffer的接口是

ID3D12Device::CreateCommittedResource

和上面幾個不同的是,我沒有找到“類似一個用來表示用途的enum”。但我看到有一系列類似

Create...View

樣式的函數,我想他們展現了Buffer的各種用途:

圖形API學習工程(6):建立并使用UniformBuffer目标Buffer的用途目前的抽象OpenGL實作D3D11實作D3D12實作Vulkan實作效果後續問題

但我想除此之外還有其他用途,至少建立VertexBuffer的時候也使用了

ID3D12Device::CreateCommittedResource

。而随後有一個

D3D12_VERTEX_BUFFER_VIEW

展現其用途。(除了頂點緩沖外還有索引緩沖

D3D12_INDEX_BUFFER_VIEW

也就是說,目前看來有以下幾種:

ConstantBuffer
ShaderResource
UnorderedAccess
RenderTarget
DepthStencil
Sampler
VertextBuffer
IndexBuffer
           

看起來各個圖形API裡有關Buffer的用途有所差别,不過又有很大相似之處。

目前的抽象

我定義的

UniformBufferCreateInfo

将作為建立UniformBuffer時的參數

struct UniformBufferCreateInfo
{
	void* BufferAddress;	//Buffer的CPU位址
	uint32_t BufferSize;	//Buffer的尺寸
	std::string NameInShader;	//shader中的名字
	AbstractGraphicsPipeline* Pipeline;//管線
	bool InVertexShader;	//在頂點着色器中
	bool InPixelShader;		//在像素着色器中
};
           

AbstractUniformBuffer

代表了一個UniformBuffer,它基礎的成員是必須指明這塊資料在CPU中的位址(C++代碼中變量的位址)和尺寸。随後各個圖形API會繼承這個成員,添加各自定義的Buffer資料。

class AbstractUniformBuffer
{
public:
	void* BufferAddress;	//Buffer的CPU位址
	uint32_t BufferSize;	//Buffer的尺寸
};
           

OpenGL實作

OpenGL的實作比較簡單。

在建立時:

  • 使用

    glGenBuffers

    建立Buffer
  • 使用

    glBufferData

    配置設定空間
  • 使用

    glGetUniformBlockIndex

    獲得在shader中的uniformbuffer的位置

在更新時:

  • 使用

    glUniformBlockBinding

    設定要操作的shader中的uniformbuffer的位置
  • 使用

    glBindBufferBase

    glBufferSubData

    更新資料

D3D11實作

D3D11的實作同樣比較簡單。

在建立時:

  • 使用

    ID3D11Device::CreateBuffer

    建立Buffer

在更新時:

  • 使用

    ID3D11DeviceContext::VSSetConstantBuffers

    将buffer設定到shader中。
  • 使用

    ID3D11DeviceContext::UpdateSubresource

    更新buffer資料。

D3D12實作

D3D12的實作相對步驟更多,除了必要的建立、更新Buffer之外,還需要關心的有:

  • descriptor heap。在之前其實已經為“Render Target View”建立了一個,但建立那個的“Type”是

    D3D12_DESCRIPTOR_HEAP_TYPE_RTV

    。現在為UniformBuffer所建立的descriptor heap的“Type”是

    D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV

  • 需要改變管線的RootSignature,來描述目前的管線“有一個UniformBuffer”這樣一個資訊

Vulkan實作

Vulkan的實作代碼也很多,除了必要的建立、更新Buffer之外,還需要關心的有:

  • 建立建立DescriptorPool
  • 管線的

    VkPipelineLayout

    VkDescriptorSetLayout

    VkDescriptorSet

    等的改變。

效果

圖形API學習工程(6):建立并使用UniformBuffer目标Buffer的用途目前的抽象OpenGL實作D3D11實作D3D12實作Vulkan實作效果後續問題
圖形API學習工程(6):建立并使用UniformBuffer目标Buffer的用途目前的抽象OpenGL實作D3D11實作D3D12實作Vulkan實作效果後續問題

後續問題

D3D12和Vulkan在這部分相關的代碼還比較多,牽扯到很多概念,需要更詳細的整理。

繼續閱讀