Cross-Compilation & Shader Reflection

Typical portable pipeline: HLSL → DXC → DXIL (D3D12) + SPIR-V (Vulkan) → SPIRV-Cross → MSL (Metal) / GLSL (GL/GLES), or author in Slang for all targets.

37.1 Cross-compilation tools

HLSL → DXIL, HLSL → SPIR-V the primary path SPIR-V → GLSL, GLSL ES, MSL (Metal), HLSL, reflection JSON used by many engines, MoltenVK Metal Shader Converter (Apple) DXIL → Metal IR (metallib) for porting D3D12 games to Apple platforms ShaderConductor (Microsoft) HLSL → DXIL/SPIR-V → GLSL/ESSL/MSL wraps DXC + SPIRV-Cross; archived, but the approach remains common

Slang (Khronos-hosted, NVIDIA-originated) Slang/HLSL → DXIL, SPIR-V, HLSL, GLSL, Metal, CUDA, WGSL, CPU largely HLSL-compatible; modules, generics, interfaces, automatic differentiation HLSL front end (partial) → SPIR-V older alternative; DXC is more complete Naga / Tint SPIR-V → WGSL (and others) HLSLcc (Unity) DXBC → GLSL/Metal legacy Unity path

spirv-opt, spirv-val, SPIRV-Reflect optimize, validate, reflect SPIR-V

Typical portable pipeline: HLSL → DXC → DXIL (D3D12) + SPIR-V (Vulkan) → SPIRV-Cross → MSL (Metal) / GLSL (GL/GLES), or author in Slang for all targets.

37.2 Shader reflection

Reflection extracts resource bindings, cbuffer layouts, input signatures, and thread-group sizes from compiled shaders — so the engine can build root signatures, descriptor layouts, and validate C++ structs automatically.

FXC/DXBC: D3DReflect(blob, size, IID_PPV_ARGS(&ID3D11ShaderReflection)).

DXR/work-graph libraries: ID3D12LibraryReflection.

SPIR-V: SPIRV-Reflect or SPIRV-Cross's reflection API.

Strip reflection from shipped shaders with -Qstrip_reflect and keep it in separate files if the engine needs it at runtime.

// DXC (DXIL) reflection
#include <d3d12shader.h>
ComPtr<IDxcBlob> reflBlob;
result->GetOutput(DXC_OUT_REFLECTION, IID_PPV_ARGS(&reflBlob), nullptr);
DxcBuffer reflBuf = { reflBlob->GetBufferPointer(), reflBlob->GetBufferSize(), 0 };
ComPtr<ID3D12ShaderReflection> refl;
utils->CreateReflection(&reflBuf, IID_PPV_ARGS(&refl));

D3D12_SHADER_DESC desc; refl->GetDesc(&desc);
for (UINT i = 0; i < desc.BoundResources; ++i)
{
    D3D12_SHADER_INPUT_BIND_DESC bind; refl->GetResourceBindingDesc(i, &bind);
    printf("%s: type %d, register %u, space %u, count %u\n",
           bind.Name, bind.Type, bind.BindPoint, bind.Space, bind.BindCount);
}
for (UINT c = 0; c < desc.ConstantBuffers; ++c)
{
    auto* cb = refl->GetConstantBufferByIndex(c);
    D3D12_SHADER_BUFFER_DESC cbDesc; cb->GetDesc(&cbDesc);
    for (UINT v = 0; v < cbDesc.Variables; ++v)
    {
        D3D12_SHADER_VARIABLE_DESC var; cb->GetVariableByIndex(v)->GetDesc(&var);
        printf("  %s.%s offset %u size %u\n", cbDesc.Name, var.Name, var.StartOffset, var.Size);
    }
}
UINT tx, ty, tz; refl->GetThreadGroupSize(&tx, &ty, &tz);   // compute shaders