2022年8月7日日曜日

HLSLでRootSignature定義

前々からちょこちょこと見かけてはいたんだけど、何故かサンプルのシェーダの中にRootSignatureが定義されているものがある。
CreateRootSignatureを呼ぶ前で、すごい複雑な構造体をごちゃごちゃやって作ってるのに、また別にHLSLでも定義して何なんだと思っていた。

今までスルーしてきたけど丁度ルートパラメータ周りを見直すことにしたので、HLSLで定義しているRootSignatureについて調べてみた。

ここによると、どうやら本当にHLSL側で定義出来て、しかもコンパイルしたシェーダからD3DGetBlobPartでRootSignatureのBlobを抜き出して、CreateRootSignatureに渡せるらしい。

シェーダ定義の例

this->oMipmapShader->InitCompute( this, 
{
	{ tShaderIF::lRSDefine, {
		// RootParamater
		{	// 0 テクスチャ
			{ tShaderIF::lParamRSType	, tShaderIF::lRSTypeSRV },
			{ tShaderIF::lParamName,	L"SrcTex" },
		},
		{	// 1 書き込み用テクスチャ
			{ tShaderIF::lParamRSType	, tShaderIF::lRSTypeTexUAV },
			{ tShaderIF::lParamName,	L"DstTex" },
		},
		{	// 2 Dimension
			{ tShaderIF::lParamRSType,	tShaderIF::lRSTypeCBV32 },
			{ tShaderIF::lParamSource,	LR"---(
				uint SrcMipLev ;
				float2 TexelSize ;
			)---" },
			{ tShaderIF::lParamName,	L"Dim" },
		},
		{	// 3 サンプラ
			{ tShaderIF::lParamRSType	, tShaderIF::lRSTypeStaticSampler },
			{ tShaderIF::lParamName,	L"Sampler" },
		},
	}},
	{ tShaderIF::lCSDefine, {
		{ tShaderIF::lParamName,		L"MipmapShader" },
		{ tShaderIF::lParamHeader,		L"[numthreads( 8, 8, 1 )]" },
		{	// 0
			{ tShaderIF::lParamSemantic	, L"SV_DispatchThreadID"	},
			{ tShaderIF::lParamSource	, L"uint3 ID"		},
		},
		{ tShaderIF::lParamSource, LR"---(
			float2 uv = gDim.TexelSize * ( In.ID.xy + 0.5f ) ;
			DstTex[ In.ID.xy ] = SrcTex.SampleLevel( Sampler, uv, gDim.SrcMipLev ) ;
		)---", },
	}},
}, true ) ;

これは前回のミップマップを生成するためのComputeシェーダで、大きく分けるとRSDefineのルート署名の定義と、それ以外のシェーダの定義に分かれる。この例ではRSDefineに参照元のSRV、書き込み用UAV、パラメータ用のCBV、テクスチャを参照するためのスタティックサンプラが定義されている。
それぞれRSTypeと名称を定義、必要に応じて構造体定義のソースなども定義できる。

この定義を元にD3D12_DESCRIPTOR_RANGE1、D3D12_ROOT_PARAMETER1とシェーダソースを自動生成してコンパイルを行っていた。
これが、シェーダソースのみで良くなると言うことだ。

早速試して見ようとしたがD3DGetBlobPartはShaderModel5.1までのもので、6に切り替えたせいでもう呼べない。新しい方ではコンパイルの結果のResultからGetOutputで取り出すっぽい。ただ、今までのResultはIDxcOperationResultでGetOutputはない。
IDxcCompiler2からIDxcCompiler3に変える必要があるみたいだ。

以前DirectXShaderCompilerに切り替えた時、IDxcCompiler2でコンパイルできるようにした。他のI/Fもそうだけど単純にこの数字を上げれば使える関数が増えるので定義をIDxcCompiler2からIDxcCompiler3に変えてみたところ、色が変わらない。
ヘッダに存在しないようだ。
自分のPCに入っているdxcapi.hのバージョンが古いみたいで、VisualStudioインストーラからWindowsSDKを入れることにした。


Windows SDK インストール

既に入っているSDKは「Windows 10 SDK (10.0.19041.0)」だった。
インクルードパスが競合して新しいのが入っているのに見れないとか嫌なので、古いのはアンインストールするためチェックを外した。
新しいのは「Windows 10 SDK(10.0.20348.0)」というのもあるけど、使ってるOSがWin11と言うのもあり「Windows 11 SDK (10.0.22000.0)」にチェックを入れてインストールした。

インストール後、IDxcCompiler3に変えてみると色が変わったのでヘッダも新しいのに入れ替わったみたいだ。

新しいコンパイル関数

	tCom<IDxcUtils> oUtil ;
	HRESULT nRet = ::DxcCreateInstance( CLSID_DxcUtils, IID_PPV_ARGS( &oUtil )) ;
	if( FAILED( nRet )) { mLogWinAPI( DxcCreateInstance, nRet ) ; return ; }
	tCom<IDxcCompiler3> oCompiler ;
	nRet = ::DxcCreateInstance( CLSID_DxcCompiler, IID_PPV_ARGS( &oCompiler )) ;
	if( FAILED( nRet )) { mLogWinAPI( DxcCreateInstance, nRet ) ; return ; }

	DxcBuffer oSource = {} ;
	oSource.Ptr = sSrc.Ptr() ;
	oSource.Size = sSrc.LenB() ;
	oSource.Encoding = DXC_CP_UTF16 ;

	tStr sTarget = tStr(L"-T %s"_fs, sModel ) ;
	tStr sEntryPoint = tStr(L"-E %s"_fs, sFunc ) ;
#if defined(_DEBUG)
	cWS pArgs[] = { DXC_ARG_ENABLE_STRICTNESS, DXC_ARG_DEBUG, DXC_ARG_SKIP_OPTIMIZATIONS, L"-Qembed_debug", L"-rootsig-define DefRS", sTarget.Ptr(), sEntryPoint.Ptr(), sSrcName } ;
#else
	cWS pArgs[] = { DXC_ARG_ENABLE_STRICTNESS, DXC_ARG_OPTIMIZATION_LEVEL3, L"-rootsig-define DefRS", sTarget.Ptr(), sEntryPoint.Ptr(), sSrcName } ;
#endif
	tCom<IDxcResult> oResult ;
	nRet = this->oCompiler->Compile( &oSource, pArgs, tU4( fArray( pArgs )), nullptr, IID_PPV_ARGS( &oResult )) ;
	if( FAILED( nRet )) {
		mLogWinAPI( IDxcCompiler::Compile, nRet ) ;
		return false ;
	}

	oResult->GetStatus( &nRet ) ;
	if( FAILED( nRet )) {
		tCom<IDxcBlobEncoding> oErr ;
		oResult->GetErrorBuffer( &oErr ) ;
		tCom<IDxcBlobUtf16> oErr16 ;
		oUtil->GetBlobAsUtf16( oErr.Get(), &oErr16 ) ;
		mLogE( sModel, cWS( oErr16->GetBufferPointer())) ;
		return false ;
	}

	// Root署名
	nRet = oResult->GetOutput( DXC_OUT_ROOT_SIGNATURE, IID_PPV_ARGS( this->oRSB.ReleaseAndGetAddressOf()), nullptr ) ;
	if( FAILED( nRet )) {
		mLogWinAPI( IDxcResult::GetOutput, nRet ) ;
		return false ;
	}

IDxcCompiler3はIDxcCompiler2とは大きく変わっていて、まずCompileの第一引数がIDxcBlobEncodingを渡していたのがDxcBufferに変わった。
IDxcLibrary::CreateBlobWithEncodingFromPinnedでUTF-8文字列をBlobに変換していたのが、DxcBufferの構造体に値を入れればいいだけになる。
Ptrにポインタ、Sizeにバイト数、EncodingにDXC_CP_UTF8/16を指定する。
コードページの定義にDXC_CP_UTF16も追加されていて、ついにUTF16のままコンパイルすることが出来た。デバッガーで見る時UTF8が文字列としてクイックウォッチなどで見れないのが地味に使いづらかった。もしかするとIDxcCompiler2でもコードページ1200を指定して、CreateBlobWithEncodingFromPinnedの第2引数に文字数ではなくバイト数を指定したらうまく行ってたのかも。

あとIDxcLibraryは、IDxcUtilsに交代するみたい。

次にソース名、エントリーポイント、シェーダモデル、オプションと引数が続いていたけど廃止されてオプションのみとなった。
IDxcUtils::BuildArgumentsで以前と同じように指定が可能だけど、IDxcCompilerArgsというオブジェクトを用意しないといけないので、使わず直接オプションですべて指定することにした。

エントリーポイントは「-E 関数名」で指定する。
シェーダモデルは「-T モデル名」で指定する。
オプションは一部マクロが用意されていた。
ソース名は最初どうやって指定するかわからなかったけど、オプションの最後にハイフンなしで名称指定したらコンパイルエラー時に指定名称が表示された。

最後の引数はIDxcOperationResultからIDxcResultに変えたオブジェクトで結果を受け取る。
で、肝心のルート署名はIDxcResult::GetOutputの第一引数にDXC_OUT_ROOT_SIGNATUREを指定することで取り出せる。
コンパイル時のオプションに「-rootsig-define」を指定してどの定義がルート署名のdefineかを教えてあげる必要がある。

今までのルート署名生成

	D3D12_FEATURE_DATA_ROOT_SIGNATURE oFDRS = {} ;
	oFDRS.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1 ;
	nRet = pDev->CheckFeatureSupport( D3D12_FEATURE_ROOT_SIGNATURE, &oFDRS, sizeof( oFDRS )) ;
	if( FAILED( nRet )) oFDRS.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0 ;

	tVector<D3D12_DESCRIPTOR_RANGE1> oDRL ;
	tVector<D3D12_ROOT_PARAMETER1> oRPL ;
	tVector<D3D12_STATIC_SAMPLER_DESC> oSSL ;
	auto [ sVSSrc, sPSSrc ] = this->GenerateShaderSource( oDef, oDRL, oRPL, oSSL ) ;
	if( sVSSrc.Len() <= 0 ) return false ;
	if( sPSSrc.Len()) bPS = true ;

	D3D12_VERSIONED_ROOT_SIGNATURE_DESC oVRSD = {} ;
	oVRSD.Version = oFDRS.HighestVersion ;
	auto & oDesc = oVRSD.Desc_1_1 ;
	oDesc.NumParameters = tU4( oRPL.Count()) ;
	oDesc.pParameters = oRPL.Ptr() ;
	oDesc.NumStaticSamplers = tU4( oSSL.Count()) ;
	oDesc.pStaticSamplers = oSSL.Ptr() ;
	oDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_NONE
	|	D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
	|	D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS
	|	D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS
	|	D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS
	;

	tStr sSrcName ;
	cAny & oVSDef = oDef[ lVSDefine ] ;
	if( oVSDef.IsExist( lParamName )) sSrcName = oVSDef.Value().ToStr() ;
	if( !this->Compile( lStageVS, sVSSrc, sSrcName, lVSFunc )) break ;
	if( bPS ) {
		cAny & oPSDef = oDef[ lPSDefine ] ;
		if( oPSDef.IsExist( lParamName )) sSrcName = oPSDef.Value().ToStr() ;	// PSのNameがない場合VSのNameが受け継がれる
		if( !this->Compile( lStagePS, sPSSrc, sSrcName, lPSFunc )) break ;
	}

	tCom<ID3DBlob> oSignature ;
	tCom<ID3DBlob> oError ;
	nRet = ::D3D12SerializeVersionedRootSignature( &oVRSD, &oSignature, &oError ) ;
	if( FAILED( nRet )) { mLogWinAPI( D3D12SerializeVersionedRootSignature, nRet ) ; break ; }

	nRet = pDev->CreateRootSignature( 0, oSignature->GetBufferPointer(), oSignature->GetBufferSize(), IID_PPV_ARGS( &this->oRS )) ;
	if( FAILED( nRet )) { mLogWinAPI( ID3D12Device::CreateRootSignature, nRet ) ; break ; }

バージョンをチェックして1.1が使えない場合は1.0にする。GenerateShaderSourceで定義から各パラメータとシェーダソースを生成する。その結果を元にD3D12_VERSIONED_ROOT_SIGNATURE_DESCを作って、ソースはコンパイル。最後に署名をシリアライズ化して、CreateRootSignatureを呼び出す。

新しいルート署名生成

	auto [ sVSSrc, sPSSrc ] = this->GenerateShaderSource( oDef ) ;
	if( sVSSrc.Len() <= 0 ) return false ;
	if( sPSSrc.Len()) bPS = true ;

	tStr sSrcName ;
	cAny & oVSDef = oDef[ lVSDefine ] ;
	if( oVSDef.IsExist( lParamName )) sSrcName = oVSDef.Value().ToStr() ;
	tCom<IDxcBlob>	oRSB ;	// Root署名Blob
	if( !this->Compile( lStageVS, sVSSrc, sSrcName, lVSFunc, &oRSB )) break ;
	if( bPS ) {
		cAny & oPSDef = oDef[ lPSDefine ] ;
		if( oPSDef.IsExist( lParamName )) sSrcName = oPSDef.Value().ToStr() ;	// PSのNameがない場合VSのNameが受け継がれる
		if( !this->Compile( lStagePS, sPSSrc, sSrcName, lPSFunc )) break ;
	}

	nRet = pDev->CreateRootSignature( 0, oRSB->GetBufferPointer(), oRSB->GetBufferSize(), IID_PPV_ARGS( &this->oRS )) ;
	if( FAILED( nRet )) { mLogWinAPI( ID3D12Device::CreateRootSignature, nRet ) ; break ; }

シェーダソースを生成して、コンパイル。その際にルート署名も取り出してCreateRootSignatureを呼び出す。シェーダソース生成部分も余計な構造体の設定がなくなってスッキリした。
ただ、ルート署名バージョン1.1が使えない環境の場合どうなるのかわからない。
元のソースは構造体に1.1のデータが含まれているけど、バージョン指定に1.0と指定すれば無視してくれていた。こっちの場合はバージョン指定が無いので、1.1のデータをそもそも含めてはいけないのか?勝手に無視してくれないだろうか?
必要であればD3D12_FEATURE_ROOT_SIGNATUREをチェックして、シェーダソース生成時に1.1のデータは出力しないようにしよう。

今回の嵌まりポイント


ルート署名の定義にTabを含められない


ルート署名の文字列にTabが含まれるとエラーになる。
Tabと改行を全部空文字に置き換え後、全体をダブルクォートで囲んで1行の定義にした。

1.1のflagsなしだと動作がかわる


リソースの状態遷移について大量にエラーが出力されていた。
今まではD3D12_DESCRIPTOR_RANGE_FLAG_NONEで済ませてきたので、とりあえず無指定にして実行したらだめだった。
応急処置としてUAVはDATA_VOLATILE、それ以外にはDATA_STATIC_WHILE_SET_AT_EXECUTEを指定したけどまだだめ。
SRVの一部はテクスチャとRTVとして使うリソースもあるので、SRVもDATA_VOLATILEにしたらとりあえずエラーがでなくなった。
D3D12_DESCRIPTOR_RANGE_FLAG_NONEの動作はSRVの場合DATA_STATIC_WHILE_SET_AT_EXECUTEのはず。エラーにはリソースステータスはD3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE|D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCEでないとだめなのにD3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCEになってると言われる。
今まで通常のRenderとComputeで、テクスチャを扱う際にステータスをそれぞれ設定していたけど、同時に設定しておくものらしい。D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE部分をすべてD3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE|D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCEに書き換えたらDATA_STATIC_WHILE_SET_AT_EXECUTEでエラーが出なくなった。

 


0 件のコメント:

コメントを投稿