さっそく予定を変更して今回はShaderReflectionについて。コンパイルされたシェーダーからいろいろな情報を引き出せますよと。
ConstantBufferをやろうと思ったのだけど、その時もShaderReflectionを活用した方がいろいろ都合がよろしいので先にShaderReflectionをやることにした。
ShaderReflectionを使ってVertexShaderから入力レイアウトを生成してみる。
入力レイアウト
D3D11_INPUT_ELEMENT_DESC vbElement[] =
{
{
"POSITION" // SemanticName
, 0
, DXGI_FORMAT_R32G32B32A32_FLOAT
, 0
, 0
, D3D11_INPUT_PER_VERTEX_DATA
, 0
},
{
"COLOR" // SemanticName
, 0
, DXGI_FORMAT_R32G32B32A32_FLOAT
, 0
, D3D11_APPEND_ALIGNED_ELEMENT
, D3D11_INPUT_PER_VERTEX_DATA
, 0
}
};
上記のレイアウト情報をコンパイル済みVertexShaderから動的に構築する。
インクルードとリンク
#include <d3dcompiler.h>
として
以下のファイルへのリンクを追加する
d3dcompiler.lib
dxguid.lib
dxguidが無いと
リンクエラー _IID_ID3D11ShaderReflection
が出て悩まされることになる(悩んだ)。
追加コード
前回のコードとの差分。
Reflection
// vertex shader
ResPtr<ID3DBlob> vblob;
HRESULT hr = CompileShaderFromFile(shaderFile.c_str(), vsFunc.c_str(), "vs_4_0_level_9_1", &vblob);
if (FAILED(hr))
return false;
hr = pDevice->CreateVertexShader(vblob->GetBufferPointer(), vblob->GetBufferSize(), NULL, &m_pVsh);
if (FAILED(hr))
return false;
// vertex shader reflection
ResPtr<ID3D11ShaderReflection> pReflector;
hr = D3DReflect(vblob->GetBufferPointer(), vblob->GetBufferSize(), IID_ID3D11ShaderReflection, (void**)&pReflector);
if (FAILED(hr))
return false;
VertexShaderをコンパイルしてReflectionを作った。
InputAssemblerレイアウトを取得する
D3D11_SHADER_DESC shaderdesc;
pReflector->GetDesc(&shaderdesc);
// Create InputLayout
std::vector<D3D11_INPUT_ELEMENT_DESC> vbElement;
for (int i = 0; i < shaderdesc.InputParameters; ++i){
D3D11_SIGNATURE_PARAMETER_DESC sigdesc;
pReflector->GetInputParameterDesc(i, &sigdesc);
auto format = GetDxgiFormat(sigdesc.ComponentType, sigdesc.Mask);
D3D11_INPUT_ELEMENT_DESC eledesc = {
sigdesc.SemanticName // Semantic名
, sigdesc.SemanticIndex // POSITION0とかの数字。無ければ0
, format // DXGI_FORMAT
, 0 // 決め打ち
, D3D11_APPEND_ALIGNED_ELEMENT // 決め打ち
, D3D11_INPUT_PER_VERTEX_DATA // 決め打ち
, 0 // 決め打ち
};
vbElement.push_back(eledesc);
}
if (!vbElement.empty()){
hr = pDevice->CreateInputLayout(&vbElement[0], vbElement.size(),
vblob->GetBufferPointer(), vblob->GetBufferSize(), &m_pInputLayout);
if (FAILED(hr))
return false;
}
DXGI_FORMATをD3D11_SIGNATURE_PARAMETER_DESCから得るのがポイントで次のような関数を作った(網羅はしてない)。
GetDxgiFormat
DXGI_FORMAT GetDxgiFormat(D3D10_REGISTER_COMPONENT_TYPE type, BYTE mask)
{
if (mask & 0x0F)
{
// xyzw
switch (type)
{
case D3D10_REGISTER_COMPONENT_FLOAT32:
return DXGI_FORMAT_R32G32B32A32_FLOAT;
}
}
if (mask & 0x07)
{
// xyz
switch (type)
{
case D3D10_REGISTER_COMPONENT_FLOAT32:
return DXGI_FORMAT_R32G32B32_FLOAT;
}
}
if (mask & 0x3)
{
// xy
switch (type)
{
case D3D10_REGISTER_COMPONENT_FLOAT32:
return DXGI_FORMAT_R32G32_FLOAT;
}
}
if (mask & 0x1)
{
// x
switch (type)
{
case D3D10_REGISTER_COMPONENT_FLOAT32:
return DXGI_FORMAT_R32_FLOAT;
}
}
return DXGI_FORMAT_UNKNOWN;
}
mask変数は各ビットがxyzwを使っているか否かに対応する。
以上で、D3D11_INPUT_ELEMENT_DESC配列の生成は完了。
これでシェーダーに書いてあることを2度書く必要は無くなる。