工程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的各種用途:
但我想除此之外還有其他用途,至少建立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的實作比較簡單。
在建立時:
- 使用
建立BufferglGenBuffers
- 使用
配置設定空間glBufferData
- 使用
獲得在shader中的uniformbuffer的位置glGetUniformBlockIndex
在更新時:
- 使用
設定要操作的shader中的uniformbuffer的位置glUniformBlockBinding
- 使用
和glBindBufferBase
更新資料glBufferSubData
D3D11實作
D3D11的實作同樣比較簡單。
在建立時:
- 使用
建立BufferID3D11Device::CreateBuffer
在更新時:
- 使用
将buffer設定到shader中。ID3D11DeviceContext::VSSetConstantBuffers
- 使用
更新buffer資料。ID3D11DeviceContext::UpdateSubresource
D3D12實作
D3D12的實作相對步驟更多,除了必要的建立、更新Buffer之外,還需要關心的有:
- descriptor heap。在之前其實已經為“Render Target View”建立了一個,但建立那個的“Type”是
。現在為UniformBuffer所建立的descriptor heap的“Type”是D3D12_DESCRIPTOR_HEAP_TYPE_RTV
。D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV
- 需要改變管線的RootSignature,來描述目前的管線“有一個UniformBuffer”這樣一個資訊
Vulkan實作
Vulkan的實作代碼也很多,除了必要的建立、更新Buffer之外,還需要關心的有:
- 建立建立DescriptorPool
- 管線的
、VkPipelineLayout
、VkDescriptorSetLayout
等的改變。VkDescriptorSet
效果
後續問題
D3D12和Vulkan在這部分相關的代碼還比較多,牽扯到很多概念,需要更詳細的整理。