Saturday, August 27, 2016

[DX12] HLSLにRoot Signatureを定義する

HLSLにRoot Signatureをattributeとして書く方法は、MSDNに詳しい説明があります。

Specifying Root Signatures in HLSL

MSDNのサンプルは機能紹介のためか「全部入り」の複雑なものになっていますが、ここではもっとシンプルなHLSLにRoot Signatureを書いてみました。キューブマップを空とみなして画面全体にレンダリングしています。

cbuffer perObject : register(b0)
{
row_major matrix invVP;
}
TextureCube texCube : register(t0);
SamplerState samplerState : register(s0);
struct VsToPs
{
float4 pos : SV_POSITION;
float3 dir : DIR;
};
#define RSDEF "CBV(b0), DescriptorTable(SRV(t0)), StaticSampler(s0)"
[RootSignature(RSDEF)]
VsToPs VSMain(uint id : SV_VertexID)
{
VsToPs ret;
ret.pos = float4(id & 2 ? 1 : -1, id & 1 ? -1 : 1, 1, 1);
ret.dir = normalize(mul(ret.pos, invVP)).xyz;
return ret;
}
[RootSignature(RSDEF)]
float4 PSMain(VsToPs inp) : SV_Target
{
return texCube.Sample(samplerState, inp.dir);
}

Vertex ShaderとPixel Shaderの両方に同じ定義をするために#defineで文字列を定義します。文字列1つで短く書けて、C++側で構造体と列挙体を駆使して書くより圧倒的に読みやすくで直感的です。

文字列の全体が、D3D12_ROOT_SIGNATURE_DESCに該当します。

StaticSamplerが、D3D12_STATIC_SAMPLER_DESCに該当する記述になります。フィルタリングの方法なども書けますが、レジスタ以外は省略可能です。嬉しいのはHLSL内でサンプラーの定義から使用まで完結することです。C++側から何もする必要がありません。

今回は書いていませんが、RootFlagsがD3D12_ROOT_SIGNATURE_FLAGSに該当します。ハマりそうな点としては、ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUTを書き忘れるとおそらく頂点入力が出来なくなるので気をつけます。今回は頂点はシェーダー内で生成しているので不要です。

RootFlagsとStaticSamplerを除いたそれ以外が、D3D12_ROOT_PARAMETERの配列と同等になります。この並びがC++側から参照するインデックス、つまりSetGraphicsRootConstantBufferViewやSetGraphicsRootDescriptorTableの引数RootParameterIndexに該当します。

こうしてHLSLのattributeに書いたRoot Signatureはシェーダのバイナリに含まれます。MSDNではfxcを使っていますが、D3DCompileFromFile等でID3DBlobを生成している場合にもD3DGetBlobPartでRoot Signatureを取り出せます。Vertex ShaderとPixel Shaderのどちらから取り出しても構いません。

...
ComPtr<ID3DBlob> vertexShader = afCompileHLSL(shaderName, "VSMain", "vs_5_0");
ComPtr<ID3DBlob> pixelShader = afCompileHLSL(shaderName, "PSMain", "ps_5_0");
ComPtr<ID3DBlob> rootSignatureBlob;
ComPtr<ID3D12RootSignature> rootSignature;
if (S_OK == D3DGetBlobPart(vertexShader->GetBufferPointer(), vertexShader->GetBufferSize(), D3D_BLOB_ROOT_SIGNATURE, 0, &rootSignatureBlob))
{
device->CreateRootSignature(0, rootSignatureBlob->GetBufferPointer(), rootSignatureBlob->GetBufferSize(), IID_PPV_ARGS(&rootSignature));
}
...
ComPtr<ID3DBlob> afCompileHLSL(const char* name, const char* entryPoint, const char* target)
{
char path[MAX_PATH];
sprintf_s(path, sizeof(path), "hlsl/%s.hlsl", name);
#ifdef _DEBUG
UINT flags = D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
UINT flags = D3DCOMPILE_ENABLE_STRICTNESS;
#endif
ComPtr<ID3DBlob> blob, err;
WCHAR wname[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, path, -1, wname, dimof(wname));
HRESULT hr = D3DCompileFromFile(wname, nullptr, nullptr, entryPoint, target, flags, 0, &blob, &err);
if (err) {
MessageBoxA(nullptr, (const char*)err->GetBufferPointer(), name, MB_OK | MB_ICONERROR);
}
return blob;
}

D3DGetBlobPartにD3D_BLOB_ROOT_SIGNATUREを渡すことでシェーダーバイナリからroot signatureを取り出します。 これは、以前はD3D12SerializeRootSignatureで作っていたroot signatureのバイナリを置き換えるもので、どちらも最後はCreateRootSignatureでID3D12RootSignatureを生成します。

ところで、MSDNによるとDX11でもRoot Signature入りのシェーダを問題なく使えるようで、Root Signatureを単に無視すると書いてあります。別の視点から見ると、DX12でのみ必要だったコードをC++から追い出して、プラットフォームの差を吸収するのにも役立ってくれそうです。

No comments:

Post a Comment