Function-Linking-Graph (FLG) とは
Direct3D 11.2には,シェーダーの構造を記述するFunction-Linking-Graphという機能が追加されています.
これを使うと,関数だけ事前にコンパイルしておいて,実行時に条件に合わせてリンクしてシェーダを生成する,
といったことが可能になります.
FLGによるHLSLの生成
この記事では,事前にコンパイルしたHLSLシェーダライブラリは利用せず,
FLGのみを利用して,次のような頂点シェーダを生成する方法を解説します.
void main(in float4 input_position, out float4 output_position)
{
output_position = input_position;
}
FLGによるHLSL生成は,次のような流れになります.
- ID3D11FunctionLinkingGraphの生成
- ID3D11FunctionLinkingGraphにインプットシグネチャの設定
- ID3D11FunctionLinkingGraphにアウトプットシグネチャの設定
- インプットシグネチャからアウトプットシグネチャへの値渡しの設定
- ID3D11FunctionLinkingGraphからHLSLの生成
ヘッダなど
以降のコードの前提として,以下のヘッダにより必要なインクルードや名前空間が省略されています.
#include <iostream>
#include <d3dcompiler.h>
#include <wrl/client.h>
#pragma comment(lib, "d3dcompiler")
using namespace std;
using namespace Microsoft::WRL;
// 各処理の実行結果のエラーコードを受け取る
HRESULT hr = S_OK;
ID3D11FunctionLinkingGraphの生成
FLGの基礎となるID3D11FunctionLinkingGraphは,D3DCreateFunctionLinkingGraph関数によって生成されます.
HRESULT WINAPI D3DCreateFunctionLinkingGraph(
UINT uFlags,
ID3D11FunctionLinkingGraph **ppFunctionLinkingGraph
);
仮引数名 | 概要 |
---|---|
uFlags | 予約されているだけなのと,フラグのようなので0で良いと思われます. |
ppFunctionLinkingGraph | 生成されたID3D11FunctionLinkingGraphの受け取り先 |
実際の呼び出しは,次のようになります.
ComPtr<ID3D11FunctionLinkingGraph> pVertexShaderGraph;
hr = D3DCreateFunctionLinkingGraph(0, &pVertexShaderGraph);
インプットシグネチャの設定
次に,頂点シェーダのエントリーポイントの入力となる仮引数(インプットシグネチャ)の設定を行います.
仮引数の変数1つに対応するD3D11_PARAMETER_DESCに必要な値を設定し,
ID3D11FunctionLinkingGraph::SetInputSignatureを呼び出します.
D3D11_PARAMETER_DESC vertex_shader_input_parameters[1];
vertex_shader_input_parameters[0].Name = "input_position";
vertex_shader_input_parameters[0].SemanticName = "POSITION";
vertex_shader_input_parameters[0].Type = D3D_SVT_FLOAT;
vertex_shader_input_parameters[0].Class = D3D_SVC_VECTOR;
vertex_shader_input_parameters[0].Rows = 1;
vertex_shader_input_parameters[0].Columns = 4;
vertex_shader_input_parameters[0].InterpolationMode = D3D_INTERPOLATION_UNDEFINED;
vertex_shader_input_parameters[0].Flags = D3D_PF_IN;
vertex_shader_input_parameters[0].FirstInRegister = 0;
vertex_shader_input_parameters[0].FirstInComponent = 0;
vertex_shader_input_parameters[0].FirstOutRegister = 0;
vertex_shader_input_parameters[0].FirstOutComponent = 0;
ComPtr<ID3D11LinkingNode> pVertexShaderInputNode;
hr = pVertexShaderGraph->SetInputSignature(
vertex_shader_input_parameters,
_countof(vertex_shader_input_parameters),
&pVertexShaderInputNode
);
if(FAILED(hr))
{
cerr << "頂点シェーダのインプットシグネチャ設定失敗" << endl;
return 0;
}
上記の例では,float4型のinput_positionという名前でPOSITIONというセマンティクス名を持つ変数1つだけを
インプットシグネチャとして定義しています.もちろん,配列の要素を増やせば,頂点シェーダの入力となる
変数を増やすことができます.
Typeにはfloatやintといった型を,Classにはベクトルや行列といったものを指定します.
ベクトルは,1行Columns列として扱われるので,Rowsが1でfloat4の場合はColumnsが4となります.
また,入力なのでFlagsにその旨を設定しています.
ID3D11FunctionLinkingGraph::SetInputSignatureにこの配列を渡すと,
入力シグネチャに対応したID3D11LinkingNodeというFLGを構成するノードが得られます.
アウトプットシグネチャの設定
アウトプットシグネチャの設定は,InputをOutputに変えるだけでさほど変更はありません.
D3D11_PARAMETER_DESC vertex_shader_output_parameters[1];
vertex_shader_output_parameters[0].Name = "output_position";
vertex_shader_output_parameters[0].SemanticName = "SV_POSITION";
vertex_shader_output_parameters[0].Type = D3D_SVT_FLOAT;
vertex_shader_output_parameters[0].Class = D3D_SVC_VECTOR;
vertex_shader_output_parameters[0].Rows = 1;
vertex_shader_output_parameters[0].Columns = 4;
vertex_shader_output_parameters[0].InterpolationMode = D3D_INTERPOLATION_UNDEFINED;
vertex_shader_output_parameters[0].Flags = D3D_PF_OUT;
vertex_shader_output_parameters[0].FirstInRegister = 0;
vertex_shader_output_parameters[0].FirstInComponent = 0;
vertex_shader_output_parameters[0].FirstOutRegister = 0;
vertex_shader_output_parameters[0].FirstOutComponent = 0;
ComPtr<ID3D11LinkingNode> pVertexShaderOutputNode;
hr = pVertexShaderGraph->SetOutputSignature(
vertex_shader_output_parameters,
_countof(vertex_shader_output_parameters),
&pVertexShaderOutputNode
);
if(FAILED(hr))
{
cerr << "頂点シェーダのアウトプットシグネチャ設定失敗" << endl;
return 0;
}
インプットシグネチャからアウトプットシグネチャへの値渡しの設定
現在のFLGは,インプットシグネチャノードとアウトプットシグネチャノードが
浮いているだけのグラフになっています.
ID3D11FunctionLinkingGraph::PassValueを呼び出し,データの流れる辺を
追加します.
HRESULT PassValue(
ID3D11LinkingNode *pSrcNode,
INT SrcParameterIndex,
ID3D11LinkingNode *pDstNode,
INT DstParameterIndex
);
仮引数名 | 概要 |
---|---|
pSrcNode | データソースとなるノード |
SrcParameterIndex | 何番目の仮引数か |
pDstNode | データの送り先のノード |
DstParameterIndex | 何番目の仮引数か |
ここでは,仮引数はどちらにも1つずつしかないので,それぞれ0でつなぐことになります.
実際の処理は,以下のようになります.
hr = pVertexShaderGraph->PassValue(pVertexShaderInputNode.Get(), 0, pVertexShaderOutputNode.Get(), 0);
if(FAILED(hr))
{
cerr << "インプットからアウトプットへの接続失敗" << endl;
return 0;
}
HLSLの生成
グラフが出来たので,ID3D11FunctionLinkingGraph::GenerateHlslを呼び出して,
HLSLを生成します.
HRESULT GenerateHlsl(
UINT uFlags,
ID3DBlob **ppBuffer
);
仮引数名 | 概要 |
---|---|
uFlags | 予約されているだけなので,0で良いと思われる. |
ppBuffer | 生成されたHLSLが格納されるブロブへのポインタ |
実際の処理は,次のようになります.
ComPtr<ID3DBlob> pBlob;
hr = pVertexShaderGraph->GenerateHlsl(0, &pBlob);
if(FAILED(hr))
{
cerr << "" << endl;
return 0;
}
cout.write(
static_cast<const char *>(pBlob->GetBufferPointer()),
pBlob->GetBufferSize()
);
まとめ
高々数行のHLSLのコードを生成するためにどれだけのコードを書かないといけないのか,
という感じですが,本来はコンパイル済みのHLSLのコードをリンクして,実行時に
シェーダを効率良く生成するためのものです.
実際,HLSL shader compiler sampleというサンプルでは,コンパイル済みのものをリンクするのと,
コンパイルし直すのとどちらが速いか,といったベンチマークが行われています.
今回解説した手順は,デバッグ時などに思った通りのHLSLになっているかどうか,
確認するためには使えると思います.
FLGは,Unreal Engine 4のブループリントのようにビジュアルスクリプトで
シェーダを生成するような場合に相性が良い気がします.
ソースコード全文
#include <iostream>
#include <d3dcompiler.h>
#include <wrl/client.h>
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d3dcompiler")
using namespace std;
using namespace Microsoft::WRL;
int main()
{
ComPtr<ID3D11FunctionLinkingGraph> pVertexShaderGraph;
HRESULT hr = D3DCreateFunctionLinkingGraph(0, &pVertexShaderGraph);
if(FAILED(hr))
{
cerr << "Failed to create vertex shader function linking graph" << endl;
return 0;
}
D3D11_PARAMETER_DESC vertex_shader_input_parameters[1];
vertex_shader_input_parameters[0].Name = "input_position";
vertex_shader_input_parameters[0].SemanticName = "POSITION";
vertex_shader_input_parameters[0].Type = D3D_SVT_FLOAT;
vertex_shader_input_parameters[0].Class = D3D_SVC_VECTOR;
vertex_shader_input_parameters[0].Rows = 1;
vertex_shader_input_parameters[0].Columns = 4;
vertex_shader_input_parameters[0].InterpolationMode = D3D_INTERPOLATION_UNDEFINED;
vertex_shader_input_parameters[0].Flags = D3D_PF_IN;
vertex_shader_input_parameters[0].FirstInRegister = 0;
vertex_shader_input_parameters[0].FirstInComponent = 0;
vertex_shader_input_parameters[0].FirstOutRegister = 0;
vertex_shader_input_parameters[0].FirstOutComponent = 0;
ComPtr<ID3D11LinkingNode> pVertexShaderInputNode;
hr = pVertexShaderGraph->SetInputSignature(
vertex_shader_input_parameters,
_countof(vertex_shader_input_parameters),
&pVertexShaderInputNode
);
if(FAILED(hr))
{
cerr << "Failed to create vertex shader input signature node!" << endl;
return 0;
}
D3D11_PARAMETER_DESC vertex_shader_output_parameters[1];
vertex_shader_output_parameters[0].Name = "output_position";
vertex_shader_output_parameters[0].SemanticName = "SV_POSITION";
vertex_shader_output_parameters[0].Type = D3D_SVT_FLOAT;
vertex_shader_output_parameters[0].Class = D3D_SVC_VECTOR;
vertex_shader_output_parameters[0].Rows = 1;
vertex_shader_output_parameters[0].Columns = 4;
vertex_shader_output_parameters[0].InterpolationMode = D3D_INTERPOLATION_UNDEFINED;
vertex_shader_output_parameters[0].Flags = D3D_PF_OUT;
vertex_shader_output_parameters[0].FirstInRegister = 0;
vertex_shader_output_parameters[0].FirstInComponent = 0;
vertex_shader_output_parameters[0].FirstOutRegister = 0;
vertex_shader_output_parameters[0].FirstOutComponent = 0;
ComPtr<ID3D11LinkingNode> pVertexShaderOutputNode;
hr = pVertexShaderGraph->SetOutputSignature(
vertex_shader_output_parameters,
_countof(vertex_shader_output_parameters),
&pVertexShaderOutputNode
);
if(FAILED(hr))
{
cerr << "Failed to create vertex shader output signature node!" << endl;
return 0;
}
hr = pVertexShaderGraph->PassValue(pVertexShaderInputNode.Get(), 0, pVertexShaderOutputNode.Get(), 0);
if(FAILED(hr))
{
cerr << "Failed to link input to output!" << endl;
return 0;
}
ComPtr<ID3DBlob> pBlob;
hr = pVertexShaderGraph->GenerateHlsl(0, &pBlob);
if(FAILED(hr))
{
cerr << "Failed to generate HLSL!" << endl;
return 0;
}
cout.write(
static_cast<const char *>(pBlob->GetBufferPointer()),
pBlob->GetBufferSize()
);
return 0;
}