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