日別アーカイブ: 2007年10月3日

Direct3D 10 HLSL で再帰呼び出しの展開

HLSL では関数を再帰呼び出しすることができません。
>error X3500: ‘_Sub0’: recursive functions not yet implemented

一応 asm 命令ではシェーダープログラムのサブルーチンコールは
存在していて、call ~ ret や label 等のニモニックもあります。
今まで調べた限りでは、現在の HLSL (Shader4.0) でこれらの命令が
使われるのは、唯一 switch 文の attribute に [call] を指定した
場合だけでした。

データスタックが無いので、ローカル変数の保護など、その辺の
実装でハードルが高いのかもしれません。

とはいっても、テスト中はジオメトリシェーダー等でほんの数段で
よいから再帰的にコードを記述したくなることがあります。
シェーダー関数は基本的にすべて inline 展開されるので、指定した
数だけ勝手に inline 展開してくれれば実現可能でしょう。
将来のコンパイラで実装してほしい機能です。
([recursive(4)] _Sub0( .. ) とか、こんな感じで)

というわけで手動で再帰を展開してみます。
まず再帰関数をマクロ定義します。

#define	_DEFFUNC(V0,V1)			\
float4 _Sub##V0( uint a, float4 col )	\
{					\
    if( --a > 0 ){			\
        return _Sub##V1( a, col.yzwx );	\
    }					\
    return col;				\
}

必要な段数だけ定義します。

_DEFFUNC(4,4)
_DEFFUNC(3,4)
_DEFFUNC(2,3)
_DEFFUNC(1,2)
_DEFFUNC(0,1)

呼び出しの例

float4 PS_Main( PS_INPUT In ) : SV_Target
{
    return  _Sub0( 2, In.Color );
}

再起呼び出しの代わりに、全く同じ定義内容の別名関数を呼び出して
いるだけです。この場合 _Sub0() から呼ばれる関数は _Sub1() で
以後終了条件まで増えていきます。そのため必要な再帰の数だけ別名で
定義しておく必要が生じます。

上の例では _DEFFUNC の最後は自分自身の呼び出しをしていますが、
定数による inline 展開では、その関数が実際に呼ばれない限り HLSL
コンパイラではエラーにならないようです。
もし

    return  _Sub0( In.Level, In.Color );

のような感じで、動的なパラメータを使った呼び出しにすると最後まで
展開する必要があるためエラーになります。
このときは終端だけ専用の関数を用意します。

float4 _Sub4( uint a, float4 col )
{
    return col;
}

_DEFFUNC(3,4)
_DEFFUNC(2,3)
_DEFFUNC(1,2)
_DEFFUNC(0,1)

実際は上の例の _DEFFUNC と違って、もっと複雑な条件を持った、
もっと長い再帰関数を記述することになります。その場合いちいち行末に
「\」をつけたマクロの連続行形式で書かなければならないのが少々難点です。