DirectX11--深入理解與使用緩衝區資源

前言

在Direct3D 11中,緩衝區屬於其中一種資源類型,它在內存上的佈局是一維線性的。根據HLSL支持的類型以及C++的使用情況,緩衝區可以分爲下面這些類型:

  1. 頂點緩衝區(Vertex Buffer)
  2. 索引緩衝區(Index Buffer)
  3. 常量緩衝區(Constant Buffer)
  4. 有類型的緩衝區(Typed Buffer)
  5. 結構化緩衝區(Structured Buffer)
  6. 追加/消耗緩衝區(Append/Consume Buffer)
  7. 字節地址緩衝區(Byte Address Buffer)(未完工)
  8. 間接參數緩衝區(Indirect Argument Buffer)(未完工)

因此這一章主要講述上面這些資源的創建和使用方法

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ羣: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裏彙報。

頂點緩衝區(Vertex Buffer)

顧名思義,頂點緩衝區存放的是一連串的頂點數據,儘管緩衝區的數據實際上還是一堆二進制流,但在傳遞給輸入裝配階段的時候,就會根據頂點輸入佈局將其裝配成HLSL的頂點結構體數據。頂點緩衝區的數據可以用自定義的頂點結構體數組來初始化。頂點可以包含的成員有:頂點座標(必須有),頂點顏色,頂點法向量,紋理座標,頂點切線向量等等。每個頂點的成員必須匹配合適的DXGI數據格式。

當然,純粹的頂點數組只是針對單個物體而言的。如果需要繪製大量相同的物體,需要同時用到多個頂點緩衝區。這允許你將頂點數據分開成多個頂點緩衝區來存放。

這裏還提供了頂點緩衝區的另一種形式:實例緩衝區。我們可以提供一到多個的頂點緩衝區,然後再提供一個實例緩衝區。其中實例緩衝區存放的可以是物體的世界矩陣、世界矩陣的逆轉置、材質等。這樣做可以減少大量重複數據的產生,以及減少大量的CPU繪製調用。

CreateVertexBuffer函數--創建頂點緩衝區

頂點緩衝區的創建需要區分下面兩種情況:

  1. 頂點數據是否需要動態更新
  2. 是否需要綁定到流輸出

如果頂點緩衝區在創建的時候提供了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);
}

索引緩衝區(Index Buffer)

索引緩衝區通常需要與頂點緩衝區結合使用,它的作用就是以索引的形式來引用頂點緩衝區中的某一頂點,並按索引緩衝區的順序和圖元類型來組裝圖元。它可以有效地減少頂點緩衝區中重複的頂點數據,從而減小網格模型佔用的數據大小。使用相同的索引值就可以多次引用同一個頂點。

索引緩衝區的使用不需要創建資源視圖,它僅用於輸入裝配階段,並且在裝配的時候你需要指定每個索引所佔的字節數:

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方法,指定起始索引偏移值和索引數目來進行繪製。

CreateIndexBuffer函數--創建索引緩衝區

索引緩衝區的創建只考慮數據是否需要動態更新。

如果索引緩衝區在創建的時候提供了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);
}

常量緩衝區(Constant Buffer)

常量緩衝區是我們接觸到的第一個可以給所有可編程着色器程序使用的緩衝區。由於着色器函數的形參沒法從C++端傳入,我們只能通過類似全局變量的方式來讓着色器函數訪問,這些參數被打包在一個常量緩衝區中。而C++可以通過創建對應的常量緩衝區來綁定到HLSL對應的cbuffer,以實現從C++到HLSL的數據的傳遞。C++的常量緩衝區是以字節流來對待;而HLSL的cbuffer內部可以像結構體那樣包含各種類型的參數,而且還需要注意它的打包規則。

關於常量緩衝區,有太多值得需要注意的細節了:

  1. 每個着色器階段最多允許15個常量緩衝區,並且每個緩衝區最多可以容納4096個標量。HLSL的cbuffer需要指定register(b#), #的範圍爲0到14
  2. 在C++創建常量緩衝區時大小必須爲16字節的倍數,因爲HLSL的常量緩衝區本身以及對它的讀寫操作需要嚴格按16字節對齊
  3. 對常量緩衝區的成員使用packoffset修飾符可以指定起始向量和分量位置
  4. 在更新常量緩衝區時由於數據是提交完整的字節流數據到GPU,會導致HLSL中cbuffer的所有成員都被更新。爲了減少不必要的更新,可以根據這些參數的更新頻率劃分出多個常量緩衝區以節省帶寬資源
  5. 一個着色器在使用了多個常量緩衝區的情況下,這些常量緩衝區不能出現同名參數
  6. 單個常量緩衝區可以同時綁定到不同的可編程着色器階段,因爲這些緩衝區都是隻讀的,不會導致內存訪問衝突。一個包含常量緩衝區的*.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) */;
}

CreateConstantBuffer函數--創建常量緩衝區

常量緩衝區的創建需要區分下面兩種情況:

  1. 是否需要CPU經常更新
  2. 是否需要GPU更新

如果常量緩衝區在創建的時候提供了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);
}

有類型的緩衝區(Typed Buffer)

這是一種創建和使用起來最簡單的緩衝區,但實際使用頻率遠不如上面所講的三種緩衝區。它的數據可以在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_FLOATDXGI_FORMAT_R8G8B8A8_UNORM。而HLSL的int2你可以使用DXGI_FORMAT_R32G32_SINTDXGI_FORMAT_R16G16_SINTDXGI_FORMAT_R8G8_SINT

CreateTypedBuffer函數--創建有類型的緩衝區

有類型的緩衝區通常需要綁定到着色器上作爲資源使用,因此需要將bindFlags設爲D3D11_BIND_SHADER_RESOURCE

此外,有類型的緩衝區的創建需要區分下面兩種情況:

  1. 是否允許CPU寫入/讀取
  2. 是否允許GPU寫入

如果緩衝區在創建的時候提供了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(md3dDevice->CreateShaderResourceView(mBuffer.Get(), &srvDesc, mBufferSRV.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;   // 元素數目

md3dDevice->CreateUnorderedAccessView(mBuffer.Get(), &uavDesc, mBufferUAV.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);

結構化緩衝區(Structured Buffer)

結構化緩衝區可以說是緩衝區的複合形式,它允許模板類型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允許讀寫

CreateStructuredBuffer函數--創建結構化緩衝區

結構化緩衝區的創建和有類型的緩衝區創建比較相似,區別在於:

  1. 需要在MiscFlags指定D3D11_RESOURCE_MISC_BUFFER_STRUCTURED
  2. 需要額外提供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(md3dDevice->CreateShaderResourceView(mBuffer.Get(), &srvDesc, mBufferSRV.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;   // 元素數目

md3dDevice->CreateUnorderedAccessView(mBuffer.Get(), &uavDesc, mBufferUAV.GetAddressOf());

追加/消耗緩衝區(Append/Consume Buffer)

追加緩衝區和消耗緩衝區類型實際上是結構化緩衝區的特殊變體資源。因爲涉及到修改操作,它們都只能以無序訪問視圖的方式來使用。如果你只是希望這些結構體數據經過着色器變換並且不需要考慮最終的輸出順序要一致,那麼使用這兩個緩衝區是一種不錯的選擇。

ConsumeStructuredBuffer<float3> gVertexIn : register(u0);
AppendStructuredBuffer<float3> gVertexOut : register(u1);

在HLSL中,AppendStructuredBuffer僅提供了Append方法用於尾端追加成員;而ConsumeStructuredBuffer則僅提供了Consume方法用於消耗尾端成員。這兩種操作實際上可以看做是對棧的操作。此外,你也可以使用GetDimensions方法來獲取當前緩衝區還剩下多少元素。

一旦某個線程消耗了一個數據元素,就不能再被另一個線程給消耗掉,並且一個線程將只消耗一個數據。需要注意的是,因爲線程之間的執行順序是不確定的,因此無法根據線程ID來確定當前消耗的是哪個索引的資源。

此外,追加/消耗緩衝區實際上並不能動態增長,你必須在創建緩衝區的時候就要分配好足夠大的空間。

追加/消耗緩衝區的創建

追加/消耗緩衝區可以經由CreateStructuredBuffer函數來創建,需要指定cpuUpdatesfalse, gpuUpdatestrue.

比較關鍵的是UAV的創建,需要像結構化緩衝區一樣指定FormatDXGI_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(md3dDevice->CreateUnorderedAccessView(mVertexInput.Get(), &uavDesc, mVertexInputUAV.GetAddressOf()));

然後在將UAV綁定到着色器時,如果是追加緩衝區,通常需要指定初始元素數目爲0,然後提供給ID3D11DeviceContext::*SSetUnorderedAccessViews方法的最後一個參數:

UINT initCounts[1] = { 0 };
md3dImmediateContext->CSSetUnorderedAccessViews(0, 1, mVertexInputUAV.GetAddressOf(), initCounts);

而如果是消耗緩衝區,則需要指定初始元素數目:

UINT initCounts[1] = { numElements };
md3dImmediateContext->CSSetUnorderedAccessViews(1, 1, mVertexInputUAV.GetAddressOf(), initCounts);

(未完待續)

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ羣: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裏彙報。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章