在Direct3D 11中,緩衝區屬於其中一種資源類型,它在內存上的佈局是一維線性的。根據HLSL支持的類型以及C++的使用狀況,緩衝區能夠分爲下面這些類型:html
所以這一章主要講述上面這些資源的建立和使用方法git
DirectX11 With Windows SDK完整目錄github
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。數組
顧名思義,頂點緩衝區存放的是一連串的頂點數據,儘管緩衝區的數據實際上仍是一堆二進制流,但在傳遞給輸入裝配階段的時候,就會根據頂點輸入佈局將其裝配成HLSL的頂點結構體數據。頂點緩衝區的數據能夠用自定義的頂點結構體數組來初始化。頂點能夠包含的成員有:頂點座標(必須有),頂點顏色,頂點法向量,紋理座標,頂點切線向量等等。每一個頂點的成員必須匹配合適的DXGI數據格式。app
固然,純粹的頂點數組只是針對單個物體而言的。若是須要繪製大量相同的物體,須要同時用到多個頂點緩衝區。這容許你將頂點數據分開成多個頂點緩衝區來存放。ide
這裏還提供了頂點緩衝區的另外一種形式:實例緩衝區。咱們能夠提供一到多個的頂點緩衝區,而後再提供一個實例緩衝區。其中實例緩衝區存放的能夠是物體的世界矩陣、世界矩陣的逆轉置、材質等。這樣作能夠減小大量重複數據的產生,以及減小大量的CPU繪製調用。函數
因爲內容重複,能夠點此跳轉進行回顧佈局
頂點緩衝區的建立須要區分下面兩種狀況:ui
若是頂點緩衝區在建立的時候提供了D3D11_SUBRESOURCE_DATA
來完成初始化,而且以後都不須要更新,則可使用D3D11_USAGE_IMMUTABLE
。
若是頂點緩衝區須要頻繁更新,則可使用D3D11_USAGE_DYNAMIC
,並容許CPU寫入(D3D11_CPU_ACCESS_WRITE
)。
若是頂點緩衝區須要綁定到流輸出,則說明頂點緩衝區須要容許GPU寫入,可使用D3D11_USAGE_DEFAULT
,而且須要提供綁定標籤D3D11_BIND_STREAM_OUTPUT
。
下圖說明了頂點緩衝區能夠綁定的位置:
頂點緩衝區不須要建立資源視圖,它能夠直接綁定到輸入裝配階段或流輸出階段。
建立頂點緩衝區和通常的建立緩衝區函數以下:
// ------------------------------ // CreateBuffer函數 // ------------------------------ // 建立緩衝區 // [In]d3dDevice D3D設備 // [In]data 初始化結構化數據 // [In]byteWidth 緩衝區字節數 // [Out]structuredBuffer 輸出的結構化緩衝區 // [In]usage 資源用途 // [In]bindFlags 資源綁定標籤 // [In]cpuAccessFlags 資源CPU訪問權限標籤 // [In]structuredByteStride 每一個結構體的字節數 // [In]miscFlags 資源雜項標籤 HRESULT CreateBuffer( ID3D11Device * d3dDevice, void * data, UINT byteWidth, ID3D11Buffer ** buffer, D3D11_USAGE usage, UINT bindFlags, UINT cpuAccessFlags, UINT structureByteStride, UINT miscFlags) { D3D11_BUFFER_DESC bufferDesc; bufferDesc.Usage = usage; bufferDesc.ByteWidth = byteWidth; bufferDesc.BindFlags = bindFlags; bufferDesc.CPUAccessFlags = cpuAccessFlags; bufferDesc.StructureByteStride = structureByteStride; bufferDesc.MiscFlags = miscFlags; D3D11_SUBRESOURCE_DATA initData; ZeroMemory(&initData, sizeof(initData)); initData.pSysMem = data; return d3dDevice->CreateBuffer(&bufferDesc, &initData, buffer); } // ------------------------------ // CreateVertexBuffer函數 // ------------------------------ // [In]d3dDevice D3D設備 // [In]data 初始化數據 // [In]byteWidth 緩衝區字節數 // [Out]vertexBuffer 輸出的頂點緩衝區 // [InOpt]dynamic 是否須要CPU常常更新 // [InOpt]streamOutput 是否還用於流輸出階段(不能與dynamic同時設爲true) HRESULT CreateVertexBuffer( ID3D11Device * d3dDevice, void * data, UINT byteWidth, ID3D11Buffer ** vertexBuffer, bool dynamic, bool streamOutput) { UINT bindFlags = D3D11_BIND_VERTEX_BUFFER; D3D11_USAGE usage; UINT cpuAccessFlags = 0; if (dynamic && streamOutput) { return E_INVALIDARG; } else if (!dynamic && !streamOutput) { usage = D3D11_USAGE_IMMUTABLE; } else if (dynamic) { usage = D3D11_USAGE_DYNAMIC; cpuAccessFlags |= D3D11_CPU_ACCESS_WRITE; } else { bindFlags |= D3D11_BIND_STREAM_OUTPUT; usage = D3D11_USAGE_DEFAULT; } return CreateBuffer(d3dDevice, data, byteWidth, vertexBuffer, usage, bindFlags, cpuAccessFlags, 0, 0); }
因爲涉及到硬件實例化,推薦直接跳到硬件實例化一章閱讀。
索引緩衝區一般須要與頂點緩衝區結合使用,它的做用就是以索引的形式來引用頂點緩衝區中的某一頂點,並按索引緩衝區的順序和圖元類型來組裝圖元。它能夠有效地減小頂點緩衝區中重複的頂點數據,從而減少網格模型佔用的數據大小。使用相同的索引值就能夠屢次引用同一個頂點。
索引緩衝區的使用不須要建立資源視圖,它僅用於輸入裝配階段,而且在裝配的時候你須要指定每一個索引所佔的字節數:
DXGI_FORMAT | 字節數 | 索引範圍 |
---|---|---|
DXGI_FORMAT_R8_UINT | 1 | 0-255 |
DXGI_FORMAT_R16_UINT | 2 | 0-65535 |
DXGI_FORMAT_R32_UINT | 4 | 0-2147483647 |
將索引緩衝區綁定到輸入裝配階段後,你就能夠用帶Indexed的Draw方法,指定起始索引偏移值和索引數目來進行繪製。
索引緩衝區的建立只考慮數據是否須要動態更新。
若是索引緩衝區在建立的時候提供了D3D11_SUBRESOURCE_DATA
來完成初始化,而且以後都不須要更新,則可使用D3D11_USAGE_IMMUTABLE
若是索引緩衝區須要頻繁更新,則可使用D3D11_USAGE_DYNAMIC
,並容許CPU寫入(D3D11_CPU_ACCESS_WRITE
)。
// ------------------------------ // CreateIndexBuffer函數 // ------------------------------ // [In]d3dDevice D3D設備 // [In]data 初始化數據 // [In]byteWidth 緩衝區字節數 // [Out]indexBuffer 輸出的索引緩衝區 // [InOpt]dynamic 是否須要CPU常常更新 HRESULT CreateIndexBuffer( ID3D11Device * d3dDevice, void * data, UINT byteWidth, ID3D11Buffer ** indexBuffer, bool dynamic) { D3D11_USAGE usage; UINT cpuAccessFlags = 0; if (dynamic) { usage = D3D11_USAGE_DYNAMIC; cpuAccessFlags |= D3D11_CPU_ACCESS_WRITE; } else { usage = D3D11_USAGE_IMMUTABLE; } return CreateBuffer(d3dDevice, data, byteWidth, indexBuffer, usage, D3D11_BIND_INDEX_BUFFER, cpuAccessFlags, 0, 0); }
常量緩衝區是咱們接觸到的第一個能夠給全部可編程着色器程序使用的緩衝區。因爲着色器函數的形參無法從C++端傳入,咱們只能經過相似全局變量的方式來讓着色器函數訪問,這些參數被打包在一個常量緩衝區中。而C++能夠經過建立對應的常量緩衝區來綁定到HLSL對應的cbuffer
,以實現從C++到HLSL的數據的傳遞。C++的常量緩衝區是以字節流來對待;而HLSL的cbuffer
內部能夠像結構體那樣包含各類類型的參數,並且還須要注意它的打包規則。
關於常量緩衝區,有太多值得須要注意的細節了:
cbuffer
須要指定register(b#)
, #
的範圍爲0到14packoffset
修飾符能夠指定起始向量和份量位置cbuffer
的全部成員都被更新。爲了減小沒必要要的更新,能夠根據這些參數的更新頻率劃分出多個常量緩衝區以節省帶寬資源*.hlsli
文件同時被多個着色器文件引用,只是說明這些着色器使用相同的常量緩衝區佈局,若是該緩衝區須要在多個着色器階段使用,你還須要在C++同時將相同的常量緩衝區綁定到各個着色器階段上下面是一個HLSL常量緩衝區的例子(註釋部分可省略,效果等價):
cbuffer CBChangesRarely : register(b2) { matrix gView /* : packoffset(c0) */; float3 gSphereCenter /* : packoffset(c4.x) */; float gSphereRadius /* : packoffset(c4.w) */; float3 gEyePosW /* : packoffset(c5.x) */; float gPad /* : packoffset(c5.w) */; }
常量緩衝區的建立須要區分下面兩種狀況:
若是常量緩衝區在建立的時候提供了D3D11_SUBRESOURCE_DATA
來完成初始化,而且以後都不須要更新,則可使用D3D11_USAGE_IMMUTABLE
。
若是常量緩衝區須要頻繁更新,則可使用D3D11_USAGE_DYNAMIC
,並容許CPU寫入(D3D11_CPU_ACCESS_WRITE
)。
若是常量緩衝區在較長的一段時間才須要更新一次,則能夠考慮使用D3D11_USAGE_DEFAULT
。
下圖說明了常量緩衝區能夠綁定的位置:
常量緩衝區的使用一樣不須要建立資源視圖。
// ------------------------------ // CreateConstantBuffer函數 // ------------------------------ // [In]d3dDevice D3D設備 // [In]data 初始化數據 // [In]byteWidth 緩衝區字節數,必須是16的倍數 // [Out]indexBuffer 輸出的索引緩衝區 // [InOpt]cpuUpdates 是否容許CPU更新 // [InOpt]gpuUpdates 是否容許GPU更新 HRESULT CreateConstantBuffer( ID3D11Device * d3dDevice, void * data, UINT byteWidth, ID3D11Buffer ** constantBuffer, bool cpuUpdates, bool gpuUpdates) { D3D11_USAGE usage; UINT cpuAccessFlags = 0; if (cpuUpdates && gpuUpdates) { return E_INVALIDARG; } else if (!cpuUpdates && !gpuUpdates) { usage = D3D11_USAGE_IMMUTABLE; } else if (cpuUpdates) { usage = D3D11_USAGE_DYNAMIC; cpuAccessFlags |= D3D11_CPU_ACCESS_WRITE; } else { usage = D3D11_USAGE_DEFAULT; } return CreateBuffer(d3dDevice, data, byteWidth, constantBuffer, usage, D3D11_BIND_CONSTANT_BUFFER, cpuAccessFlags, 0, 0); }
這是一種建立和使用起來最簡單的緩衝區,但實際使用頻率遠不如上面所講的三種緩衝區。它的數據能夠在HLSL被解釋成基本HLSL類型的數組形式。
在HLSL中,若是是隻讀的緩衝區類型,則聲明方式以下:
Buffer<float4> gBuffer : register(t0);
須要留意的是,當前緩衝區和紋理須要共用紋理寄存器,即t#,所以要注意和紋理避開使用同一個寄存器槽。
若是是可讀寫的緩衝區類型,則聲明方式以下:
RWBuffer<float4> gRWBuffer : register(u0);
有類型的緩衝區具備下面的方法:
方法 | 做用 |
---|---|
void GetDimensions(out uint) | 獲取資源各個維度下的大小 |
T Load(in int) | 按一維索引讀取緩衝區數據 |
T Operator | Buffer 僅容許讀取,RWBuffer 容許讀寫 |
有類型的緩衝區須要建立着色器資源視圖以綁定到對應的着色器階段。因爲HLSL的語法知識定義了有限的類型和元素數目,但在DXGI_FORMAT
中,有許多種成員都可以用於匹配一種HLSL類型。好比,HLSL的float4
你可使用DXGI_FORMAT_R32G32B32A32_FLOAT
, DXGI_FORMAT_R16G16B16A16_FLOAT
或DXGI_FORMAT_R8G8B8A8_UNORM
。而HLSL的int2
你可使用DXGI_FORMAT_R32G32_SINT
,DXGI_FORMAT_R16G16_SINT
或DXGI_FORMAT_R8G8_SINT
。
有類型的緩衝區一般須要綁定到着色器上做爲資源使用,所以須要將bindFlags
設爲D3D11_BIND_SHADER_RESOURCE
。
此外,有類型的緩衝區的建立須要區分下面兩種狀況:
若是緩衝區在建立的時候提供了D3D11_SUBRESOURCE_DATA
來完成初始化,而且以後都不須要更新,則可使用D3D11_USAGE_IMMUTABLE
。
若是緩衝區須要頻繁更新,則可使用D3D11_USAGE_DYNAMIC
,並容許CPU寫入(D3D11_CPU_ACCESS_WRITE
)。
若是緩衝區須要容許GPU寫入,說明後面可能須要建立UAV綁定到RWBuffer<T>
,爲此還須要給bindFlags
添加D3D11_BIND_UNORDERED_ACCESS
。
若是緩衝區的數據須要讀出到內存,則可使用D3D11_USAGE_STAGING
,並容許CPU讀取(D3D11_CPU_ACCESS_READ
)。
下圖說明了有類型的(與結構化)緩衝區能夠綁定的位置:
// ------------------------------ // CreateTypedBuffer函數 // ------------------------------ // [In]d3dDevice D3D設備 // [In]data 初始化數據 // [In]byteWidth 緩衝區字節數 // [Out]typedBuffer 輸出的有類型的緩衝區 // [InOpt]cpuUpdates 是否容許CPU更新 // [InOpt]gpuUpdates 是否容許使用RWBuffer HRESULT CreateTypedBuffer( ID3D11Device * d3dDevice, void * data, UINT byteWidth, ID3D11Buffer ** typedBuffer, bool cpuUpdates, bool gpuUpdates) { UINT bindFlags = D3D11_BIND_SHADER_RESOURCE; D3D11_USAGE usage; UINT cpuAccessFlags = 0; if (cpuUpdates && gpuUpdates) { bindFlags = 0; usage = D3D11_USAGE_STAGING; cpuAccessFlags |= D3D11_CPU_ACCESS_READ; } else if (!cpuUpdates && !gpuUpdates) { usage = D3D11_USAGE_IMMUTABLE; } else if (cpuUpdates) { usage = D3D11_USAGE_DYNAMIC; cpuAccessFlags |= D3D11_CPU_ACCESS_WRITE; } else { usage = D3D11_USAGE_DEFAULT; bindFlags |= D3D11_BIND_UNORDERED_ACCESS; } return CreateBuffer(d3dDevice, data, byteWidth, typedBuffer, usage, bindFlags, cpuAccessFlags, 0, 0); }
關於追加/消耗緩衝區,咱們後面再討論。
若是咱們但願它做爲Buffer<float4>
使用,則須要建立着色器資源視圖:
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; srvDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; srvDesc.Buffer.FirstElement = 0; // 起始元素的索引 srvDesc.Buffer.NumElements = numElements; // 元素數目 HR(m_pd3dDevice->CreateShaderResourceView(m_pBuffer.Get(), &srvDesc, m_pBufferSRV.GetAddressOf()));
而若是咱們但願它做爲RWBuffer<float4>
使用,則須要建立無序訪問視圖:
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc; uavDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; uavDesc.Buffer.FirstElement = 0; // 起始元素的索引 uavDesc.Buffer.Flags = 0; uavDesc.Buffer.NumElements = numElements; // 元素數目 m_pd3dDevice->CreateUnorderedAccessView(m_pBuffer.Get(), &uavDesc, m_pBufferUAV.GetAddressOf());
因爲這些緩衝區僅支持GPU讀取,咱們須要另外新建一個緩衝區以容許它CPU讀取和GPU寫入(STAGING),而後將保存結果的緩衝區拷貝到該緩衝區,再映射出內存便可:
HR(CreateTypedBuffer(md3dDevice.Get(), nullptr, sizeof data, mBufferOutputCopy.GetAddressOf(), true, true)); md3dImmediateContext->CopyResource(mVertexOutputCopy.Get(), mVertexOutput.Get()); D3D11_MAPPED_SUBRESOURCE mappedData; HR(md3dImmediateContext->Map(mVertexOutputCopy.Get(), 0, D3D11_MAP_READ, 0, &mappedData)); memcpy_s(data, sizeof data, mappedData.pData, sizeof data); md3dImmediateContext->Unmap(mVertexOutputCopy.Get(), 0);
結構化緩衝區能夠說是緩衝區的複合形式,它容許模板類型T
是用戶自定義的類型,即緩衝區存放的內容能夠被解釋爲結構體數組。
如今HLSL有以下結構體:
struct Data { float3 v1; float2 v2; };
若是是隻讀的結構化緩衝區,則聲明方式以下:
StructuredBuffer<Data> gStructuredBuffer : register(t0);
若是是可讀寫的結構化緩衝區類型,則聲明方式以下:
RWStructuredBuffer<Data> gRWStructuredBuffer : register(u0);
結構化緩衝區也具備下面的方法:
方法 | 做用 |
---|---|
void GetDimensions(out uint) | 獲取資源各個維度下的大小 |
T Load(in int) | 按一維索引讀取結構化緩衝區數據 |
T Operator | StructuredBuffer 僅容許讀取,RWStructuredBuffer 容許讀寫 |
結構化緩衝區的建立和有類型的緩衝區建立比較類似,區別在於:
MiscFlags
指定D3D11_RESOURCE_MISC_BUFFER_STRUCTURED
structureByteStride
說明結構體的大小// ------------------------------ // CreateStructuredBuffer函數 // ------------------------------ // 若是須要建立Append/Consume Buffer,需指定cpuUpdates爲false, gpuUpdates爲true // [In]d3dDevice D3D設備 // [In]data 初始化數據 // [In]byteWidth 緩衝區字節數 // [In]structuredByteStride 每一個結構體的字節數 // [Out]structuredBuffer 輸出的結構化緩衝區 // [InOpt]cpuUpdates 是否容許CPU更新 // [InOpt]gpuUpdates 是否容許使用RWStructuredBuffer HRESULT CreateStructuredBuffer( ID3D11Device * d3dDevice, void * data, UINT byteWidth, UINT structuredByteStride, ID3D11Buffer ** structuredBuffer, bool cpuUpdates, bool gpuUpdates) { UINT bindFlags = D3D11_BIND_SHADER_RESOURCE; D3D11_USAGE usage; UINT cpuAccessFlags = 0; if (cpuUpdates && gpuUpdates) { bindFlags = 0; usage = D3D11_USAGE_STAGING; cpuAccessFlags |= D3D11_CPU_ACCESS_READ; } else if (!cpuUpdates && !gpuUpdates) { usage = D3D11_USAGE_IMMUTABLE; } else if (cpuUpdates) { usage = D3D11_USAGE_DYNAMIC; cpuAccessFlags |= D3D11_CPU_ACCESS_WRITE; } else { usage = D3D11_USAGE_DEFAULT; bindFlags |= D3D11_BIND_UNORDERED_ACCESS; } return CreateBuffer(d3dDevice, data, byteWidth, structuredBuffer, usage, bindFlags, cpuAccessFlags, structuredByteStride, D3D11_RESOURCE_MISC_BUFFER_STRUCTURED); }
不管是SRV仍是UAV,在指定Format
時只能指定DXGI_FORMAT_UNKNOWN
。
若是咱們但願它做爲StructuredBuffer<Data>
使用,則須要建立着色器資源視圖:
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; srvDesc.Format = DXGI_FORMAT_UNKNOWN; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; srvDesc.Buffer.FirstElement = 0; // 起始元素的索引 srvDesc.Buffer.NumElements = numElements; // 元素數目 HR(m_pd3dDevice->CreateShaderResourceView(m_pBuffer.Get(), &srvDesc, m_pBufferSRV.GetAddressOf()));
而若是咱們但願它做爲RWStructuredBuffer<float4>
使用,則須要建立無序訪問視圖:
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc; uavDesc.Format = DXGI_FORMAT_UNKNOWN; uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; uavDesc.Buffer.FirstElement = 0; // 起始元素的索引 uavDesc.Buffer.Flags = 0; uavDesc.Buffer.NumElements = numElements; // 元素數目 m_pd3dDevice->CreateUnorderedAccessView(m_pBuffer.Get(), &uavDesc, m_pBufferUAV.GetAddressOf());
追加緩衝區和消耗緩衝區類型其實是結構化緩衝區的特殊變體資源。由於涉及到修改操做,它們都只能以無序訪問視圖的方式來使用。若是你只是但願這些結構體數據通過着色器變換而且不須要考慮最終的輸出順序要一致,那麼使用這兩個緩衝區是一種不錯的選擇。
ConsumeStructuredBuffer<float3> gVertexIn : register(u0); AppendStructuredBuffer<float3> gVertexOut : register(u1);
在HLSL中,AppendStructuredBuffer
僅提供了Append
方法用於尾端追加成員;而ConsumeStructuredBuffer
則僅提供了Consume
方法用於消耗尾端成員。這兩種操做實際上能夠看作是對棧的操做。此外,你也可使用GetDimensions
方法來獲取當前緩衝區還剩下多少元素。
一旦某個線程消耗了一個數據元素,就不能再被另外一個線程給消耗掉,而且一個線程將只消耗一個數據。須要注意的是,由於線程之間的執行順序是不肯定的,所以沒法根據線程ID來肯定當前消耗的是哪一個索引的資源。
此外,追加/消耗緩衝區實際上並不能動態增加,你必須在建立緩衝區的時候就要分配好足夠大的空間。
追加/消耗緩衝區能夠經由CreateStructuredBuffer
函數來建立,須要指定cpuUpdates
爲false
, gpuUpdates
爲true
.
比較關鍵的是UAV的建立,須要像結構化緩衝區同樣指定Format
爲DXGI_FORMAT_UNKNOWN
。而且不管是追加緩衝區,仍是消耗緩衝區,都須要在Buffer.Flags
中指定D3D11_BUFFER_UAV_FLAG_APPEND
:
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc; uavDesc.Format = DXGI_FORMAT_UNKNOWN; uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; uavDesc.Buffer.FirstElement = 0; // 起始元素的索引 uavDesc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_APPEND; uavDesc.Buffer.NumElements = numElements; // 元素數目 HR(m_pd3dDevice->CreateUnorderedAccessView(m_pVertexInput.Get(), &uavDesc, m_pVertexInputUAV.GetAddressOf()));
而後在將UAV綁定到着色器時,若是是追加緩衝區,一般須要指定初始元素數目爲0,而後提供給ID3D11DeviceContext::*SSetUnorderedAccessViews
方法的最後一個參數:
UINT initCounts[1] = { 0 }; m_pd3dImmediateContext->CSSetUnorderedAccessViews(0, 1, m_pVertexInputUAV.GetAddressOf(), initCounts);
而若是是消耗緩衝區,則須要指定初始元素數目:
UINT initCounts[1] = { numElements }; m_pd3dImmediateContext->CSSetUnorderedAccessViews(1, 1, m_pVertexInputUAV.GetAddressOf(), initCounts);
(未完待續)
DirectX11 With Windows SDK完整目錄
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。