Using DXC In Practice
11 Jul 2019DirectX Shader Compiler (DXC) is a necessary component to compile SM6 shader code. If you only want to use it to compile shaders, all DXR tutorials will include so but that’s basically it. In my recent project, I wanted to build my own shader pipelines which requires more different operations, where I found they are not sufficient to accomplish all the tasks. At the same time, since the compiler is still in relatively early stage, the documentation is not very detailed. So here I am :) and I hope everyone wants to play with the compiler can find what they need from this post.
Getting Started
To use DXC in your own project, we need three things: dxcompiler.dll
and dxil.dll
for running and header files for compiling. To get it, you can manually build the source files from the repo or install the latest Windows Dev Kit(I had 17763). For WDK, the dlls are located at C:\Program Files (x86)\Windows Kits\10\bin\<version#>\x64
, be aware the prefix path may vary for you. When you test your code, make sure that you set up the environment and double check they are avaible at runtime to your executable file.
For header files, the WDK only has dxcapi.h
, which is not enough for my cases. I recommend for now, get the header files from the official repo and make them all available to your project.
MS provided a bridge dll, but only with limited features. But it’s a great reference to start with. The source code is here. which is the reference of the most of code I will post here.
To start with, you need to include dxc/include/dxc/Support/dxcapi.use.h
, where it provides a nice wrapper to load in DLL and some core functions. This should be something global and only have one.
static dxc::DxcDllSupport support;
STATIC_BLOCK {
support.Initialize();
}
Compile Shader
To compile a shader from a buffer in the most simple way, you only need three steps:
- Provide a buffer in type of
IDxcBlob
- It’s also ok to derive from this interface class by yourself. - Create
IDxcCompiler*
, which is the actual compiler instance. - Call
IDxcCompile::Compile
and feed in your buffer to compile the shader.
IDxcCompiler* compiler;
IDxcOperationResult* operationResult;
assert_win( support.CreateInstance( CLSID_DxcCompiler, &compiler ) );
HRESULT hr = compiler->Compile(pSource, "shader_name", L"entry_point", L"ps_6_1",
arguments, argumentCount,
defines, defineCount, pInclude, // `pInclude` - IDxcIncludeHandler*
&operationResult) );
if(SUCCEEDED( hr )) {
HRESULT result = operationResult->GetResult( (IDxcBlob **)ppCode );
// handle result
} else {
ID3DBlob **ppErrorMsgs;
operationResult->GetErrorBuffer( (IDxcBlobEncoding **)ppErrorMsgs );
// print, display error message
}
The Compile
function is pretty straightforward. You can basically maps all arguments to D3DCompile
. After compilation, the byte code is available in the blob and is the equivalent to the ones from the D3DCompiler
… not exact the same actually. Remember when we setting up the environment, we need two dlls, the one is dxcompiler.dll
, which is the library for the compiler obviously. But dxil.dll
is also a important part. If you don’t have it during runtime, you will get an error like this:
D3D12 ERROR: ID3D12Device::CreateVertexShader: Vertex Shader is corrupt or in an unrecognized format. [ STATE_CREATION ERROR #322: CREATEVERTEXSHADER_INVALIDSHADERBYTECODE]
It’s because dxcompiler
will not sign the shader code if it cannot find dxil.dll
so that it’s not usable for user’s computer. here is a post with some more explanation. But it seems that warning is not a very relable test for you to determine whether missing dxil.dll
is the reason to the corrupt error because I did not get that when I was doing the experiement. Graham’s post had more discussion about signing the shader. You should check that out if you are interested.
Furthermore, if you want to compile shader from a file , you will need some extra steps to construct the buffer we mentioned before:
- Create
IDxcLibrary*
to load in files, which can also handle file encoding if you need to. - Use
IDxcLibrary::CreateBlobXXXX
functions to create the buffer, whose type is eitherIDxcBlob
orIDxcBlobEncoding
(derived fromIDxcBlob
)
IDxcLibrary* library;
IDxcBlobEncoding* source;
assert_win( support.CreateInstance( CLSID_DxcLibrary, &library ) );
assert_win( library->CreateBlobFromFile( pFileName, nullptr, &source ) );
In case if you want a standard include handler, instead of passing in D3D_COMPILE_STANDARD_FILE_INCLUDE
for compile function, you also need a IDxcLibrary
instance to create one.
IDxcIncludeHandler* includeHandler = nullptr;
assert_win( library->CreateIncludeHandler(&includeHandler) );
Shader Reflection
Creating reflection object is very similar:
IDxcContainerReflection* pReflection;
IDxcLibrary* pLibrary;
UINT32 shaderIdx;
assert_win( support.CreateInstance( CLSID_DxcContainerReflection, &pReflection ) );
assert_win( support.CreateInstance( CLSID_DxcLibrary, &pLibrary ) );
assert_win( pReflection->Load( &pBlob ) );
assert_win( pReflection->FindFirstPartKind( hlsl::DFCC_DXIL, &shaderIdx ) );
assert_win( pReflection->GetPartReflection( shaderIdx, IID_PPV_ARGS(&pShaderReflection )) );
There are two things may potentially bring confusion. If you were using dxcapi.h
from WDK only, you would not be able to compile this code. Because hlsl::DFCC_DXIL
is defined in dxc/DxilContainer/DxilContainer.h
. The second thing is more related to Visual Studio. VS has a built-in HLSL compiler, which can compile shader files of SM6 and can be avaiable as header files or binary files. But you will not be able to feed these binary files(even they are SM6) into IDxcContainerReflection
and it will complain. I am not sure the reason, but I would appreciate it if someone can clarify this :).
Create Root Signature from Shader
This was an issue I kept fighting for a while. But turned out you can just pass in the shader blob itself to ID3D12Device::CreateRootSignature
. Because the API expects a signed blob container with a root signature part. D3D12CreateRootSignatureDeserializer
works in the same way. Here is a related issue from the repo.