c++directxhlsldirectx-12dxc

How to "fully bind" a constant buffer view to a descriptor range?


I am currently learning DirectX 12 and trying to get a demo application running. I am currently stuck at creating a pipeline state object using a root signature. I am using dxc to compile my vertex shader:

./dxc -T vs_6_3 -E main -Fo "basic.vert.dxi" -D DXIL "basic.vert"

My shader looks like this:

#pragma pack_matrix(row_major)

struct VertexData 
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR;
}; 

struct VertexInput
{
    float3 Position : POSITION;
    float4 Color : COLOR;
};

struct CameraData
{
    float4x4 ViewProjection;
};

ConstantBuffer<CameraData> camera : register(b0, space0);

VertexData main(in VertexInput input)
{
    VertexData vertex;
    
    vertex.Position = mul(float4(input.Position, 1.0), camera.ViewProjection);
    vertex.Color = input.Color;
 
    return vertex;
}

Now I want to define a root signature for my shader. The definition looks something like this:

CD3DX12_DESCRIPTOR_RANGE1 descriptorRange;
descriptorRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC, D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND);

CD3DX12_ROOT_PARAMETER1 rootParameter;
rootParameter.InitAsDescriptorTable(1, &descriptorRange, D3D12_SHADER_VISIBILITY_VERTEX);

ComPtr<ID3DBlob> signature, error;
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init_1_1(1, &rootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
::D3D12SerializeVersionedRootSignature(&rootSignatureDesc, &signature, &error);

ComPtr<ID3D12RootSignature> rootSignature;
device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature));

Finally, I pass the root signature along other state variables to the pipeline state object:

D3D12_GRAPHICS_PIPELINE_STATE_DESC pipelineStateDescription = {};
// ...
pipelineStateDescription.pRootSignature = rootSignature.Get();

ComPtr<ID3D12PipelineState> pipelineState;
device->CreateGraphicsPipelineState(&pipelineStateDescription, IID_PPV_ARGS(&pipelineState));

However, no matter what I do, the device keeps on complaining about the root signature not matching the vertex shader:

D3D12 ERROR: ID3D12Device::CreateGraphicsPipelineState: Root Signature doesn't match Vertex Shader: Shader CBV descriptor range (BaseShaderRegister=0, NumDescriptors=1, RegisterSpace=0) is not fully bound in root signature [ STATE_CREATION ERROR #688: CREATEGRAPHICSPIPELINESTATE_VS_ROOT_SIGNATURE_MISMATCH]

D3D12: BREAK enabled for the previous message, which was: [ ERROR STATE_CREATION #688: CREATEGRAPHICSPIPELINESTATE_VS_ROOT_SIGNATURE_MISMATCH ]

I am confused about what this error is trying to tell me, since I clearly have a constant buffer bound to register(b0, space0). Or does it mean that I have to allocate a descriptor from a heap before creating the pipeline state object?

I also tried defining a root signature within the shader itself:

#define ShaderRootSignature \
    "RootFlags( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT ), " \
              "DescriptorTable( CBV(b0, space = 0, numDescriptors = 1, flags = DATA_STATIC ) ), "

... and compiling it using [RootSignature(ShaderRootSignature)], or specifying -rootsig-define "ShaderRootSignature" for dxc. Then I tried loading the signature as suggested here, however both approaches fail, since the root signature could not be read from the shader bytecode.

Any clarification on how to interpret the error message would be much appreciated, since I really do not know what the binding in a root signature means in this context. Thanks in advance! 🙂


Solution

  • Long story short: shader visibility in DX12 is not a bit field, like in Vulkan, so setting the visibility to D3D12_SHADER_VISIBILITY_VERTEX | D3D12_SHADER_VISIBILITY_PIXEL results in the parameter only being visible to the pixel shader. Setting it to D3D12_SHADER_VISIBILITY_ALL solved my problem.