Search code examples
c++hlsldirect3ddirect3d12

Serialization of Root Signature in Direct3D12 works in debug mode, but not in release mode


I am making a game engine in Direct3D12 because I was tired of using OpenGL (please don't tell me to use directx11) and I currently have mesh loading, multisampling, transformations, texture loading, and a material system. I was happy with the progress I was making until I tried running my app in release mode. For some reason, there is an issue with serializing my shader-reflection-generated root signature. I can't find any issues with the generation code:

uint32_t numConstantBuffers = 0;
    uint32_t numTextures = 0;
    uint32_t numSamplers = 0;

    std::vector<D3D12_ROOT_PARAMETER> rootParams{};
    std::vector<D3D12_ROOT_PARAMETER> textureParams{};
    std::vector<D3D12_ROOT_PARAMETER> samplerParams{};

    D3D12_SHADER_DESC vertexShaderDesc;
    ComPtr<ID3D12ShaderReflection> vertexShaderReflection;

    for (size_t i = 0; i < 2; i++)
    {
        SHADER_TYPE type = (SHADER_TYPE)i;
        if (!shaders.contains(type)) continue;
        Shader* shader = shaders[type];

        ComPtr<ID3D12ShaderReflection> shaderReflection;
        Utils::ThrowIfFailed(D3DReflect(shader->bytecode.pShaderBytecode,
            shader->bytecode.BytecodeLength, IID_PPV_ARGS(&shaderReflection)));
        if (type == SHADER_TYPE_VERTEX) vertexShaderReflection = shaderReflection;

        D3D12_SHADER_DESC shaderDesc{};
        Utils::ThrowIfFailed(shaderReflection->GetDesc(&shaderDesc));
        if (type == SHADER_TYPE_VERTEX) vertexShaderDesc = shaderDesc;

        for (size_t res = 0; res < shaderDesc.BoundResources; res++)
        {
            D3D12_SHADER_INPUT_BIND_DESC bindDesc;
            Utils::ThrowIfFailed(shaderReflection->GetResourceBindingDesc(res, &bindDesc));

            if (bindDesc.Type == D3D_SIT_CBUFFER)
            {
                D3D12_ROOT_DESCRIPTOR rootDesc{};
                rootDesc.ShaderRegister = numConstantBuffers;
                rootDesc.RegisterSpace = 0;

                D3D12_ROOT_PARAMETER rootParam{};
                rootParam.Descriptor = rootDesc;
                rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
                rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;

                rootParams.push_back(rootParam);
                numConstantBuffers++;
            }
            if (bindDesc.Type == D3D_SIT_TEXTURE)
            {
                D3D12_DESCRIPTOR_RANGE descRange{};
                descRange.BaseShaderRegister = numTextures;
                descRange.NumDescriptors = 1;
                descRange.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
                descRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
                descRange.RegisterSpace = 0;

                D3D12_ROOT_DESCRIPTOR_TABLE descTable{};
                descTable.NumDescriptorRanges = 1;
                descTable.pDescriptorRanges = &descRange;

                D3D12_ROOT_PARAMETER rootParam{};
                rootParam.DescriptorTable = descTable;
                rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
                rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;

                textureParams.push_back(rootParam);
                numTextures++;
            }
            if (bindDesc.Type == D3D_SIT_SAMPLER)
            {
                D3D12_DESCRIPTOR_RANGE descRange{};
                descRange.BaseShaderRegister = numSamplers;
                descRange.NumDescriptors = 1;
                descRange.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
                descRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
                descRange.RegisterSpace = 0;

                D3D12_ROOT_DESCRIPTOR_TABLE descTable{};
                descTable.NumDescriptorRanges = 1;
                descTable.pDescriptorRanges = &descRange;

                D3D12_ROOT_PARAMETER rootParam{};
                rootParam.DescriptorTable = descTable;
                rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
                rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;

                samplerParams.push_back(rootParam);
                numSamplers++;
            }
        }
    }

    uint32_t numParams = rootParams.size() + textureParams.size() + samplerParams.size();
    D3D12_ROOT_PARAMETER* allParams = new D3D12_ROOT_PARAMETER[numParams];
    D3D12_ROOT_PARAMETER* ptrCpy = allParams;

    memcpy(allParams, rootParams.data(), rootParams.size() * sizeof(D3D12_ROOT_PARAMETER));
    allParams += rootParams.size();
    memcpy(allParams, textureParams.data(), textureParams.size() * sizeof(D3D12_ROOT_PARAMETER));
    allParams += textureParams.size();
    memcpy(allParams, samplerParams.data(), samplerParams.size() * sizeof(D3D12_ROOT_PARAMETER));

    CD3DX12_ROOT_SIGNATURE_DESC rsDesc{};
    rsDesc.Init(numParams, ptrCpy, 0, nullptr,
        D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS |
        D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS |
        D3D12_ROOT_SIGNATURE_FLAG_DENY_AMPLIFICATION_SHADER_ROOT_ACCESS | D3D12_ROOT_SIGNATURE_FLAG_DENY_MESH_SHADER_ROOT_ACCESS);

    ComPtr<ID3DBlob> rsBlob;
    ComPtr<ID3DBlob> errorBlob;

    HRESULT result = D3D12SerializeRootSignature(&rsDesc, D3D_ROOT_SIGNATURE_VERSION_1, &rsBlob, &errorBlob);
    if (FAILED(result) || errorBlob != nullptr)
    {
        std::string error = std::string((char*)errorBlob->GetBufferPointer());
        throw std::runtime_error(error);
    }

    Utils::ThrowIfFailed(Rendering::device->CreateRootSignature(0,
        rsBlob->GetBufferPointer(), rsBlob->GetBufferSize(), IID_PPV_ARGS(&rootSignature)));

    delete[] ptrCpy;

The full source can be found at https://github.com/LucoseGlucose/DragonEngine

When I pass in an error blob, the error contains the following: "Shader register range of type SRV (root parameter [1], visibility ALL, descriptor table slot [0]) overlaps with another shader register range (root parameter[0], visibility ALL, descriptor table slot [0])."

This doesn't make sense to me because the registers are different types, SRV and sampler

Here is the pixel shader in question (output to screen), although it also fails with my textured pixel shader:

struct PS_INPUT
{
    float4 position : SV_Position;
    float2 uv : TEXCOORD;
};

Texture2D<float4> t : register(t0);
SamplerState s : register(s0);

float3 ToneMapACESFilmic(float3 x)
{
    float a = 2.51f;
    float b = 0.03f;
    float c = 2.43f;
    float d = 0.59f;
    float e = 0.14f;
    return saturate((x * (a * x + b)) / (x * (c * x + d) + e));
}

float4 main(PS_INPUT input) : SV_TARGET
{
    float3 sceneColor = t.Sample(s, input.uv).rgb;
    float3 toneMapped = sceneColor;//ToneMapACESFilmic(sceneColor);
    float3 gammaCorrected = pow(toneMapped, 2.2);

    return float4(gammaCorrected, 1);
}

I thought that there might be a difference in the shader compilation but it didn't work even when I forced it to use the debug shaders instead. Just in case though, here is my hlsl compiler config Debug: enter image description here Release: enter image description here

I know that I can specify root signatures in hlsl, but that seems tedious and annoying compared to auto generating it. I also tried compiling the shaders at runtime but that had no effect

Is there anything I can do?


Solution

  • You have undefined behaviour in your for loop which manifests in Release build because of enabled optimizations. For example, here:

    if (bindDesc.Type == D3D_SIT_TEXTURE)
                {
                    D3D12_DESCRIPTOR_RANGE descRange{};
                    descRange.BaseShaderRegister = numTextures;
                    descRange.NumDescriptors = 1;
                    descRange.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
                    descRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
                    descRange.RegisterSpace = 0;
    
                    D3D12_ROOT_DESCRIPTOR_TABLE descTable{};
                    descTable.NumDescriptorRanges = 1;
                    descTable.pDescriptorRanges = &descRange;
    
                    D3D12_ROOT_PARAMETER rootParam{};
                    rootParam.DescriptorTable = descTable;
                    rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
                    rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
    
                    textureParams.push_back(rootParam);
                    numTextures++;
                }
    

    descRange is created on stack and is destroyed once it goes out of scope, and that is if block, which means that the DescriptorTable inside your rootParam is pointing to released memory. For practice try to fix it yourself first.