1. 程式人生 > >D3D12渲染技術之常量緩衝區

D3D12渲染技術之常量緩衝區

常量緩衝區是GPU資源(ID3D12Resource),其資料內容可以在著色器程式中引用,正如我們將在部落格中學到的,紋理和其他型別的緩衝區資源也可以在著色器程式中引用。頂點著色器具有以下程式碼:

cbuffer cbPerObject : register(b0)
{
  float4x4 gWorldViewProj; 
};

此程式碼引用名為cbPerObject的cbuffer物件(常量緩衝區),在此案例中,常量緩衝區儲存一個名為gWorldViewProj的4×4矩陣,表示用於將點從區域性空間轉換為齊次裁剪空間的組合世界:檢視和投影矩陣。 在HLSL中,內建的float4x4型別聲明瞭一個4×4矩陣; 例如,要宣告一個3×4矩陣和2×4矩陣,我們將分別使用float3x4和float2x2型別。 與頂點和索引緩衝區不同,常量緩衝區通常由CPU每幀更新一次, 例如,如果攝像機每幀移動,則需要使用每幀的新檢視矩陣更新常量緩衝區。 因此,我們在上傳堆而不是預設堆中建立常量緩衝區,以便我們可以從CPU更新內容。 常量緩衝區還具有特殊的硬體要求,即它們的大小必須是最小硬體分配大小(256位元組)的倍數。 通常我們需要多個相同型別的常量緩衝區, 例如,上面的常量緩衝區cbPerObject儲存每個物件不同的常量,因此如果我們有n個物件,那麼我們將需要n個這種型別的常量緩衝區。 以下程式碼顯示了我們如何建立一個緩衝區來儲存NumElements許多常量緩衝區:

struct ObjectConstants
{
  DirectX::XMFLOAT4X4 WorldViewProj = MathHelper::Identity4x4();
};
UINT elementByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

ComPtr<ID3D12Resource> mUploadCBuffer;
device->CreateCommittedResource(
  &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
  D3D12_HEAP_FLAG_NONE,
  &
CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize * NumElements), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&mUploadCBuffer));

我們可以將mUploadCBuffer視為儲存ObjectConstants型別的常量緩衝區陣列(使用填充來生成256位元組的倍數)。 當繪製物件時,我們只需將常量緩衝區檢視(CBV)繫結到緩衝區的子區域,該區域儲存該物件的常量。 請注意,我們經常將緩衝區mUploadCBuffer稱為常量緩衝區,因為它儲存了一個常量緩衝區陣列。 程式函式d3dUtil :: CalcConstantBufferByteSize執行算術以將緩衝區的位元組大小舍入為最小硬體分配大小的倍數(256位元組):

UINT d3dUtil::CalcConstantBufferByteSize(UINT byteSize)
{
  // Constant buffers must be a multiple of the minimum hardware
  // allocation size (usually 256 bytes). So round up to nearest
  // multiple of 256. We do this by adding 255 and then masking off
  // the lower 2 bytes which store all bits < 256.
  // Example: Suppose byteSize = 300.
  // (300 + 255) & ˜255
  // 555 & ˜255
  // 0x022B & ˜0x00ff
  // 0x022B & 0xff00
  // 0x0200
  // 512
  return (byteSize + 255) & ˜255;
}

注意,即使我們以256的倍數分配常量資料,也沒有必要在HLSL結構中顯式填充相應的常量資料,因為它是隱式完成的:

// Implicitly padded to 256 bytes.
cbuffer cbPerObject : register(b0)
{
  float4x4 gWorldViewProj; 
};

// Explicitly padded to 256 bytes.
cbuffer cbPerObject : register(b0)
{
  float4x4 gWorldViewProj; 
  float4x4 Pad0;
  float4x4 Pad1;
  float4x4 Pad1;
};

為了避免將常量緩衝區元素舍入為256位元組的倍數,可以顯式填充所有常量緩衝區結構,使其始終為256位元組的倍數。 Direct3D12引入了著色器模型5.1。 Shader模型5.1引入了另一種HLSL語法,用於定義如下所示的常量緩衝區:

struct ObjectConstants
{
  float4x4 gWorldViewProj;
  uint matIndex; 
};
ConstantBuffer<ObjectConstants> gObjConstants : register(b0);

這裡,常量緩衝區的資料元素只是在一個單獨的結構中定義,然後從該結構建立一個常量緩衝區。 然後使用資料成員語法在著色器中訪問常量緩衝區的欄位:

uint index = gObjConstants.matIndex;

因為使用堆型別D3D12_HEAP_TYPE_UPLOAD建立了常量緩衝區,所以我們可以將資料從CPU上傳到常量緩衝區資源。 要做到這一點,我們首先必須獲得一個指向資源資料的指標,這可以使用Map方法完成:

ComPtr<ID3D12Resource> mUploadBuffer;
BYTE* mMappedData = nullptr;
mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData));

第一個引數是標識要對映的子資源的子資源索引, 對於緩衝區,唯一的子資源是緩衝區本身,因此我們將其設定為0,第二個引數是指向D3D12_RANGE結構的可選指標,該結構描述要對映的記憶體範圍;指定null對映整個資源。 第二個引數返回指向對映資料的指標,要將資料從系統記憶體複製到常量緩衝區,我們可以執行memcpy:

memcpy(mMappedData, &data, dataSizeInBytes);

當我們完成一個常量緩衝區時,我們應該在釋放記憶體之前取消對映它:

if(mUploadBuffer != nullptr)
  mUploadBuffer->Unmap(0, nullptr);

mMappedData = nullptr;

Unmap的第一個引數是一個子資源索引,用於標識要對映的子資源,緩衝區為0。 Unmap的第二個引數是指向D3D12_RANGE結構的可選指標,該結構描述了要取消對映的記憶體範圍; 指定null取消對映整個資源。

我們在UploadBuffer.h中定義了以下類,以便更輕鬆地使用上傳緩衝區, 它為我們處理上傳緩衝區資源的構造和銷燬,處理對映和取消對映資源,並提供CopyData方法來更新緩衝區中的特定元素。 當我們需要從CPU更改上傳緩衝區的內容時(例如,當檢視矩陣改變時),我們使用CopyData方法。 請注意,此類可用於任何上傳緩衝區,不一定是常量緩衝區。 但是,如果我們將它用於常量緩衝區,我們需要通過isConstantBuffer建構函式引數來指示。 如果它儲存一個常量緩衝區,那麼它將自動填充記憶體,使每個常量緩衝區成為256位元組的倍數。

template<typename T>
class UploadBuffer
{
public:
  UploadBuffer(ID3D12Device* device, UINT elementCount, bool isConstantBuffer) : 
    mIsConstantBuffer(isConstantBuffer)
  {
    mElementByteSize = sizeof(T);

    // Constant buffer elements need to be multiples of 256 bytes.
    // This is because the hardware can only view constant data 
    // at m*256 byte offsets and of n*256 byte lengths. 
    // typedef struct D3D12_CONSTANT_BUFFER_VIEW_DESC {
    // UINT64 OffsetInBytes; // multiple of 256
    // UINT  SizeInBytes;  // multiple of 256
    // } D3D12_CONSTANT_BUFFER_VIEW_DESC;
    if(isConstantBuffer)
    mElementByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(T));

    ThrowIfFailed(device->CreateCommittedResource(
      &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
      D3D12_HEAP_FLAG_NONE,
      &CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize*elementCount),
         D3D12_RESOURCE_STATE_GENERIC_READ,
      nullptr,
      IID_PPV_ARGS(&mUploadBuffer)));

    ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));

    // We do not need to unmap until we are done with the resource.
    // However, we must not write to the resource while it is in use by
    // the GPU (so we must use synchronization techniques).
  }
   UploadBuffer(const UploadBuffer& rhs) = delete;
  UploadBuffer& operator=(const UploadBuffer& rhs) = delete;
  ˜UploadBuffer()
  {
    if(mUploadBuffer != nullptr)
      mUploadBuffer->Unmap(0, nullptr);

    mMappedData = nullptr;
  }

  ID3D12Resource* Resource()const
  {
    return mUploadBuffer.Get();
  }

  void CopyData(int elementIndex, const T& data)
  {
    memcpy(&mMappedData[elementIndex*mElementByteSize], &data, sizeof(T));
  }

private:
  Microsoft::WRL::ComPtr<ID3D12Resource> mUploadBuffer;
  BYTE* mMappedData = nullptr;

  UINT mElementByteSize = 0;
  bool mIsConstantBuffer = false;
};

通常,物件的世界矩陣在移動/旋轉/縮放時將改變,當攝像機移動/旋轉時檢視矩陣改變,並且當視窗調整大小時投影矩陣改變。 在本篇的案例中,我們允許使用者使用滑鼠旋轉和移動攝像機,並且我們在Update功能的每一幀中使用新檢視矩陣更新組合的世界檢視投影矩陣:

void BoxApp::OnMouseMove(WPARAM btnState, int x, int y)
{
  if((btnState & MK_LBUTTON) != 0)
  {
    // Make each pixel correspond to a quarter of a degree.
    float dx = XMConvertToRadians(0.25f*static_cast<float> (x - mLastMousePos.x));
    float dy = XMConvertToRadians(0.25f*static_cast<float> (y - mLastMousePos.y));

    // Update angles based on input to orbit camera around box.
    mTheta += dx;
    mPhi += dy;
     // Restrict the angle mPhi.
    mPhi = MathHelper::Clamp(mPhi, 0.1f, MathHelper::Pi - 0.1f);
  }
  else if((btnState & MK_RBUTTON) != 0)
  {
    // Make each pixel correspond to 0.005 unit in the scene.
    float dx = 0.005f*static_cast<float>(x - mLastMousePos.x);
    float dy = 0.005f*static_cast<float>(y - mLastMousePos.y);

    // Update the camera radius based on input.
    mRadius += dx - dy;

    // Restrict the radius.
    mRadius = MathHelper::Clamp(mRadius, 3.0f, 15.0f);
  }

  mLastMousePos.x = x;
  mLastMousePos.y = y;
  }

void BoxApp::Update(const GameTimer& gt)
{
  // Convert Spherical to Cartesian coordinates.
  float x = mRadius*sinf(mPhi)*cosf(mTheta);
  float z = mRadius*sinf(mPhi)*sinf(mTheta);
  float y = mRadius*cosf(mPhi);

  // Build the view matrix.
  XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
  XMVECTOR target = XMVectorZero();
  XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

  XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
  XMStoreFloat4x4(&mView, view);

  XMMATRIX world = XMLoadFloat4x4(&mWorld);
  XMMATRIX proj = XMLoadFloat4x4(&mProj);
   XMMATRIX worldViewProj = world*view*proj;

  // Update the constant buffer with the latest worldViewProj matrix.
  ObjectConstants objConstants;
  XMStoreFloat4x4(&objConstants.WorldViewProj,  XMMatrixTranspose(worldViewProj));
  mObjectCB->CopyData(0, objConstants);
}

回想一下,我們通過描述符物件將資源繫結到渲染管道, 到目前為止,我們已經使用描述符/檢視來渲染目標,深度/模板緩衝區以及頂點和索引緩衝區。 我們還需要描述符來將常量緩衝區繫結到管道, 常量緩衝區描述符位於D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV型別的描述符堆中。 這樣的堆可以儲存常量緩衝區,著色器資源和無序訪問描述符的混合。 要儲存這些新型別的描述符,我們需要建立一個這種型別的新描述符堆:

D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.NumDescriptors = 1;
cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbvHeapDesc.NodeMask = 0;

ComPtr<ID3D12DescriptorHeap> mCbvHeap
md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,
  IID_PPV_ARGS(&mCbvHeap));

此程式碼類似於我們建立渲染目標和深度/模板緩衝區描述符堆的方式, 但是,一個重要的區別是我們指定D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE標誌以指示著色器程式將訪問這些描述符。 在演示中,我們沒有SRV或UAV描述符,我們只會繪製一個物件; 因此,我們在這個堆中只需要1個描述符來儲存1個CBV。 通過填寫D3D12_CONSTANT_BUFFER_VIEW_DESC例項並呼叫來建立常量緩衝區檢視

ID3D12Device::CreateConstantBufferView:
// Constant data per-object.
struct ObjectConstants
{
  XMFLOAT4X4 WorldViewProj = MathHelper::Identity4x4();
};
// Constant buffer to store the constants of n object.
std::unique_ptr<UploadBuffer<ObjectConstants>> mObjectCB = nullptr;
mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(
  md3dDevice.Get(), n, true);

UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

// Address to start of the buffer (0th constant buffer).
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB->Resource()->GetGPUVirtualAddress();

// Offset to the ith object constant buffer in the buffer.
int boxCBufIndex = i;
cbAddress += boxCBufIndex*objCBByteSize;

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

md3dDevice->CreateConstantBufferView(
  &cbvDesc,
  mCbvHeap->GetCPUDescriptorHandleForHeapStart());
// Texture resource bound to texture register slot 0.
Texture2D  gDiffuseMap : register(t0);

// Sampler resources bound to sampler register slots 0-5.
SamplerState gsamPointWrap        : register(s0);
SamplerState gsamPointClamp       : register(s1);
SamplerState gsamLinearWrap       : register(s2);
SamplerState gsamLinearClamp      : register(s3);
SamplerState gsamAnisotropicWrap  : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);

// cbuffer resource bound to cbuffer register slots 0-2
cbuffer cbPerObject : register(b0)
{
  float4x4 gWorld;
  float4x4 gTexTransform;
};

// Constant data that varies per material.
cbuffer cbPass : register(b1)
{
  float4x4 gView;
  float4x4 gProj;
  […] // Other fields omitted for brevity.
};

cbuffer cbMaterial : register(b2)
{
  float4  gDiffuseAlbedo;
  float3  gFresnelR0;
  float  gRoughness;
  float4x4 gMatTransform;
};

// Root parameter can be a table, root descriptor or root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[1];

// Create a single descriptor table of CBVs.
CD3DX12_DESCRIPTOR_RANGE cbvTable;
cbvTable.Init(
  D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 
  1, // Number of descriptors in table
  0);// base shader register arguments are bound to for this root parameter

slotRootParameter[0].InitAsDescriptorTable(
  1,     // Number of ranges
  &cbvTable); // Pointer to array of ranges

// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter, 0, nullptr, 
  D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

// create a root signature with a single slot which points to a 
// descriptor range consisting of a single constant buffer.
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, 
  D3D_ROOT_SIGNATURE_VERSION_1,
  serializedRootSig.GetAddressOf(), 
  errorBlob.GetAddressOf());

ThrowIfFailed(md3dDevice->CreateRootSignature(
  0,
  serializedRootSig->GetBufferPointer(),
  serializedRootSig->GetBufferSize(),
  IID_PPV_ARGS(&mRootSignature)));
CD3DX12_ROOT_PARAMETER slotRootParameter[1];

CD3DX12_DESCRIPTOR_RANGE cbvTable;
cbvTable.Init(
  D3D12_DESCRIPTOR_RANGE_TYPE_CBV, // table type
  1, // Number of descriptors in table
  0);// base shader register arguments are bound to for this root parameter
  cbvDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

md3dDevice->CreateConstantBufferView(
  &cbvDesc,
  mCbvHeap->GetCPUDescriptorHandleForHeapStart());

D3D12_CONSTANT_BUFFER_VIEW_DESC結構描述了要繫結到HLSL常量緩衝區結構的常量緩衝區資源的子集。 如上所述,通常一個常量緩衝區儲存n個物件的每個物件常量陣列,但是我們可以通過使用BufferLocation和SizeInBytes來獲取第i個物件常量資料的檢視。 由於硬體要求,D3D12_CONSTANT_BUFFER_VIEW_DESC :: SizeInBytes和D3D12_CONSTANT_BUFFER_VIEW_DESC :: OffsetInBytes成員必須是256位元組的倍數。 例如,如果指定了64,那麼將收到以下除錯錯誤: D3D12 ERROR: ID3D12Device::CreateConstantBufferView: SizeInBytes of 64 is invalid. Device requires SizeInBytes be a multiple of 256.

D3D12 ERROR: ID3D12Device:: CreateConstantBufferView: OffsetInBytes of 64 is invalid. 裝置要求OffsetInBytes是256的倍數。 通常,不同的著色器程式期望在執行繪製呼叫之前將不同的資源繫結到呈現管道, 資源繫結到特定的暫存器槽,可以通過著色器程式訪問它們。 例如,前一個頂點和畫素著色器只需要一個常量緩衝區繫結到暫存器b0。 我們會在後面使用的一組更高階的頂點和畫素著色器需要將幾個常量緩衝區,紋理和取樣器繫結到各種暫存器槽:

// Texture resource bound to texture register slot 0.
Texture2D  gDiffuseMap : register(t0);

// Sampler resources bound to sampler register slots 0-5.
SamplerState gsamPointWrap        : register(s0);
SamplerState gsamPointClamp       : register(s1);
SamplerState gsamLinearWrap       : register(s2);
SamplerState gsamLinearClamp      : register(s3);
SamplerState gsamAnisotropicWrap  : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);

// cbuffer resource bound to cbuffer register slots 0-2
cbuffer cbPerObject : register(b0)
{
  float4x4 gWorld;
  float4x4 gTexTransform;
};

// Constant data that varies per material.
cbuffer cbPass : register(b1)
{
  float4x4 gView;
  float4x4 gProj;
  […] // Other fields omitted for brevity.
};

cbuffer cbMaterial : register(b2)
{
  float4  gDiffuseAlbedo;
  float3  gFresnelR0;
  float  gRoughness;
  float4x4 gMatTransform;
};

根簽名定義了在執行繪製呼叫之前應用程式將繫結到呈現管道的資源以及這些資源被對映到著色器輸入暫存器的位置。 根簽名必須與它將使用的著色器相容(即,根簽名必須提供著色器期望在執行繪製呼叫之前繫結到渲染管道的所有資源); 這將在建立管道狀態物件時驗證後面介紹。 不同的繪製呼叫可以使用不同的著色器程式集,這將需要不同的根簽名。 注意,如果我們將著色器程式視為一個函式,並將著色器期望的輸入資源視為函式引數,那麼根簽名可以被認為是定義函式簽名(因此名稱為root簽名)。 通過將不同的資源繫結為引數,著色器輸出將是不同的。 因此,例如,頂點著色器將取決於輸入到著色器的實際頂點,以及繫結的資源。 ID3D12RootSignature介面在Direct3D中表示根簽名,它由一組根引數定義,這些引數描述著色器對繪製呼叫所期望的資源。 根引數可以是根常量,根描述符或描述符表。 我們將在下一篇部落格討論根常量和根描述符; 在本篇中,我們將只使用描述符表, 描述符表指定描述符堆中的連續描述符範圍。 下面的程式碼建立了一個根簽名,它有一個根引數,它是一個足以儲存一個CBV(常量緩衝區檢視)的描述符表:

// Root parameter can be a table, root descriptor or root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[1];

// Create a single descriptor table of CBVs.
CD3DX12_DESCRIPTOR_RANGE cbvTable;
cbvTable.Init(
  D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 
  1, // Number of descriptors in table
  0);// base shader register arguments are bound to for this root parameter

slotRootParameter[0].InitAsDescriptorTable(
  1,     // Number of ranges
  &cbvTable); // Pointer to array of ranges

// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter, 0, nullptr, 
  D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

// create a root signature with a single slot which points to a 
// descriptor range consisting of a single constant buffer.
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, 
  D3D_ROOT_SIGNATURE_VERSION_1,
  serializedRootSig.GetAddressOf(), 
  errorBlob.GetAddressOf());

ThrowIfFailed(md3dDevice->CreateRootSignature(
  0,
  serializedRootSig->GetBufferPointer(),
  serializedRootSig->GetBufferSize(),
  IID_PPV_ARGS(&mRootSignature)));

我們將描述 CD3DX12_ROOT_PARAMETER和CD3DX12_DESCRIPTOR_RANGE在下一篇中有更多內容,但現在只需瞭解程式碼:

CD3DX12_ROOT_PARAMETER slotRootParameter[1];

CD3DX12_DESCRIPTOR_RANGE cbvTable;
cbvTable.Init(
  D3D12_DESCRIPTOR_RANGE_TYPE_CBV, // table type
  1, // Number of descriptors in table
  0);// base shader register arguments are bound to for this root parameter

slotRootParameter[0].InitAsDescriptorTable(
  1,     // Number of ranges
  &cbvTable); // Pointer to array of ranges

建立一個根引數,該引數期望1 CBV的描述符表被繫結到常量緩衝暫存器0(即HLSL程式碼中的暫存器(b0)) 我們在本篇部落格中的根簽名示例非常簡單, 我們將在本系列部落格中看到許多根簽名的例子,並且它們將根據需要增加複雜性。 根簽名僅定義應用程式將繫結到呈現管道的資源; 它實際上沒有做任何資源繫結。 使用命令列表設定根簽名後,我們使用ID3D12GraphicsCommandList :: SetGraphicsRootDescriptorTable將描述符表繫結到管道:

void ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable( 
  UINT RootParameterIndex,
  D3D12_GPU_DESCRIPTOR_HANDLE BaseDescriptor);

RootParameterIndex:我們正在設定的根引數的索引。 BaseDescriptor:處理堆中的描述符,該描述符指定正在設定的表中的第一個描述符。 例如,如果根簽名指定此表有五個描述符,則堆中的BaseDescriptor和接下來的四個描述符將被設定為此根表。 以下程式碼將根簽名和CBV堆設定為命令列表,並設定描述符表以標識要繫結到管道的資源:

mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps); 

// Offset the CBV we want to use for this draw call.
CD3DX12_GPU_DESCRIPTOR_HANDLE cbv(mCbvHeap ->GetGPUDescriptorHandleForHeapStart());
cbv.Offset(cbvIndex, mCbvSrvUavDescriptorSize);

mCommandList->SetGraphicsRootDescriptorTable(0, cbv);

注意,為了提高效能,請使根簽名儘可能小,並嘗試最小化每個渲染幀更改根簽名的次數。 當內容的任何部分在繪製/排程呼叫之間發生變化時,應用程式繫結的根簽名(描述符表,根常量和根描述符)的內容會自動獲得D3D12驅動程式的版本控制。 因此,每個繪製/分派都會獲得一組唯一的Root Signature狀態。 如果更改根簽名,則會丟失所有現有繫結。 也就是說,您需要將所有資源重新繫結到新根簽名所需的管道。