1

I am building a C++ application that uses DirectX 12 as graphics api.
I want to define macros(for eg #define A_MACRO) when compiling shader programs.
I looked on the internet but I didn't found where to put it. In terms of code I have this:

Root signature creation:

void Effect::createRootSignature(const D3D12_ROOT_PARAMETER* rootParams,
    UINT nbParams,
    const D3D12_STATIC_SAMPLER_DESC* samplers,
    UINT numSamplers)
{
        CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
        rootSignatureDesc.Init(nbParams,
            rootParams,
            numSamplers,
            samplers,
            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);

        ID3DBlob* errorBuff;
        ID3DBlob* signature;
        HRESULT hr = D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &errorBuff);
        if (FAILED(hr))
        {
            TRACE((char*)errorBuff->GetBufferPointer());
            assert(false);
            return;
        }

        hr = D3D12Device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&m_rootSignature));
        assert(SUCCEEDED(hr));
}

Shader deserialization:

ID3DBlob* Effect::readShader(const std::string& shaderName)
{
    std::wstring shaderPath(shaderName.begin(), shaderName.end());
    shaderPath = SHADER_PATH(shaderPath + L".cso");

    ID3DBlob* shader = nullptr;
    HRESULT hr = D3DReadFileToBlob(shaderPath.c_str(), &shader);
    if (FAILED(hr))
    {
        return nullptr;
    }

    return shader;
}  

PSO complilation:

void Effect::compilePipeline(PipelineStatePtr& pipelineStateDst,
    ID3DBlob* vertexShader,
    ID3DBlob* pixelShader,
    const D3D12_INPUT_ELEMENT_DESC* inputLayoutElementDesc,
    const UINT inputLayoutNumElements,
    const D3D12_BLEND_DESC& blendDesc,
    const D3D12_DEPTH_STENCIL_DESC& depthStencilDesc,
    const D3D12_RASTERIZER_DESC& rasterizerDesc,
    const std::vector<DXGI_FORMAT>& renderTargetFormats,
    const D3D12_PRIMITIVE_TOPOLOGY_TYPE topologyType)
{
    // Fill out a shader bytecode structure, which is basically just a pointer
    // to the shader bytecode and the size of the shader bytecode.
    D3D12_SHADER_BYTECODE vertexShaderBytecode = {};
    vertexShaderBytecode.BytecodeLength = vertexShader->GetBufferSize();
    vertexShaderBytecode.pShaderBytecode = vertexShader->GetBufferPointer();

    // Fill out a pixel shader bytecode structure.
    D3D12_SHADER_BYTECODE pixelShaderBytecode = {};
    pixelShaderBytecode.BytecodeLength = pixelShader->GetBufferSize();
    pixelShaderBytecode.pShaderBytecode = pixelShader->GetBufferPointer();

    // Fill out an input layout description structure
    D3D12_INPUT_LAYOUT_DESC inputLayoutDesc = {};
    inputLayoutDesc.NumElements = inputLayoutNumElements;
    inputLayoutDesc.pInputElementDescs = inputLayoutElementDesc;

    // Fill out a PSO description
    D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
    psoDesc.InputLayout = inputLayoutDesc;
    psoDesc.pRootSignature = m_rootSignature;
    psoDesc.VS = vertexShaderBytecode;
    psoDesc.PS = pixelShaderBytecode;
    psoDesc.PrimitiveTopologyType = topologyType;

    assert(renderTargetFormats.size() <= 8);
    const UINT numRenderTargets = std::min((UINT)renderTargetFormats.size(), 8u);

    for (nbUint32 i = 0; i < numRenderTargets; ++i)
        psoDesc.RTVFormats[i] = renderTargetFormats[i];

    psoDesc.SampleDesc = m_sampleDesc;
    psoDesc.SampleMask = 0xffffffff;
    psoDesc.RasterizerState = rasterizerDesc;
    psoDesc.BlendState = blendDesc;
    psoDesc.NumRenderTargets = numRenderTargets;
    psoDesc.DepthStencilState = depthStencilDesc;
    psoDesc.DSVFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT7;

    // Now create the PSO
    HRESULT hr = D3D12Device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineStateDst));
    assert(SUCCEEDED(hr));
}
Chuck Walbourn
  • 38,259
  • 2
  • 58
  • 81
Cloyz
  • 93
  • 2
  • 11
  • 1
    The macros would only apply while COMPILING the shader. Once you have a shader blob, you *cannot change it*. If you want multiple variants, then you need to build the shader multiple times and produced distinct shader blobs. Then you create one or more PSOs for each such blob. For an example, see [DirectX Tool Kit](https://github.com/microsoft/DirectXTK12/blob/main/Src/Shaders/CompileShaders.cmd). – Chuck Walbourn Apr 25 '23 at 03:53
  • Ah yes shader is already compiled. That is my answer. Thanks for helping! – Cloyz Apr 25 '23 at 04:01

2 Answers2

3

Macros can only be used during shader compilation. Once built, the shader blob is immutable and you cannot change it. To build variants of the same shader controlled by macros, you must invoke the shader compiler multiple times and create PSOs from each distinct shader blob.

For an example, see DirectX Tool Kit for DX12 and the CompileShaders.cmd script.

Chuck Walbourn
  • 38,259
  • 2
  • 58
  • 81
0

As mentioned by Chuck Walbourn, I currently read shaders CSO so the shaders are already compiled. To add macros to shaders i have two options.

  1. Precompile all the variants of all the shaders.

  2. Compile shaders at runtime, macros can be put there:

     ID3DBlob* Effect::compileShader(const std::wstring& shaderName,  
         bool isVertexShader,
         const D3D_SHADER_MACRO* macros)
     {
     #if defined(_DEBUG)
         UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
     #else
         UINT compileFlags = 0u;
     #endif
    
     ID3DBlob* errorBuff;
     ID3DBlob* shader;
     HRESULT hr = D3DCompileFromFile(SHADER_PATH(shaderName),
             macros,
             nullptr,
             "main",
             isVertexShader ? "vs_5_0" : "ps_5_0",
             compileFlags,
             0,
             &shader,
             &errorBuff);
    
     if (FAILED(hr))
     {
         TRACE((char*)errorBuff->GetBufferPointer());
         return nullptr;
     }
    
     return shader;
     }
    
Cloyz
  • 93
  • 2
  • 11
  • 3
    Compiling at runtime is convenient for experiments, but is not a good way to ship production code especially since your macros make it easy to build all used combinations at build time. Also, for DirectX 12 you should consider using **DXC** with Shader Model 6 instead of legacy **FXC** Shader Model 5.0 or 5.1. See [GitHub](https://github.com/microsoft/DirectXShaderCompiler). – Chuck Walbourn Apr 25 '23 at 23:37