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状态。
如果更改根签名,则会丢失所有现有绑定。 也就是说,您需要将所有资源重新绑定到新根签名所需的管道。

已标记关键词 清除标记
相关推荐
程序员的必经之路! 【限时优惠】 现在下单,还享四重好礼: 1、教学课件免费下载 2、课程案例代码免费下载 3、专属VIP学员群免费答疑 4、下单还送800元编程大礼包 【超实用课程内容】  根据《2019-2020年中国开发者调查报告》显示,超83%的开发者都在使用MySQL数据库。使用量大同时,掌握MySQL早已是运维、DBA的必备技能,甚至部分IT开发岗位也要求对数据库使用和原理有深入的了解和掌握。 学习编程,你可能会犹豫选择 C++ 还是 Java;入门数据科学,你可能会纠结于选择 Python 还是 R;但无论如何, MySQL 都是 IT 从业人员不可或缺的技能!   套餐中一共包含2门MySQL数据库必学的核心课程(共98课时)   课程1:《MySQL数据库从入门到实战应用》   课程2:《高性能MySQL实战课》   【哪些人适合学习这门课程?】  1)平时只接触了语言基础,并未学习任何数据库知识的人;  2)对MySQL掌握程度薄弱的人,课程可以让你更好发挥MySQL最佳性能; 3)想修炼更好的MySQL内功,工作中遇到高并发场景可以游刃有余; 4)被面试官打破沙锅问到底的问题问到怀疑人生的应聘者。 【课程主要讲哪些内容?】 课程一:《MySQL数据库从入门到实战应用》 主要从基础篇,SQL语言篇、MySQL进阶篇三个角度展开讲解,帮助大家更加高效的管理MySQL数据库。 课程二:《高性能MySQL实战课》主要从高可用篇、MySQL8.0新特性篇,性能优化篇,面试篇四个角度展开讲解,帮助大家发挥MySQL的最佳性能的优化方法,掌握如何处理海量业务数据和高并发请求 【你能收获到什么?】  1.基础再提高,针对MySQL核心知识点学透,用对; 2.能力再提高,日常工作中的代码换新貌,不怕问题; 3.面试再加分,巴不得面试官打破沙锅问到底,竞争力MAX。 【课程如何观看?】  1、登录CSDN学院 APP 在我的课程中进行学习; 2、移动端:CSDN 学院APP(注意不是CSDN APP哦)  本课程为录播课,课程永久有效观看时长 【资料开放】 课件、课程案例代码完全开放给你,你可以根据所学知识,自行修改、优化。  下载方式:电脑登录课程观看页面,点击右侧课件,可进行课程资料的打包下载。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页