2007/07/31
Direct3D 10.1 での共存とSDKバージョン
WindowsVista SP1 で対応するらしい Direct3D 10.1 ですが、
今の 10.0 対応ビデオカードでそのまま 10.1 の新機能が
使えるとは限りません。(たぶん使えなさそう)
といっても 10.0 ハードに対応しないわけにもいかないでしょう。
D3D10CreateDevice1() で Device のインターフェースを作成するときに、
・D3D10_FEATURE_LEVEL_10_1
・D3D10_FEATURE_LEVEL_10_0
と2種類のパラメータ指定ができるようです。
ということは、10.1 がリリースされた場合は今後 SDK 上は 10.1 を
使っておき、初期化で 10.0 or 10.1 を切り替えることになるのでしょうか。
ShaderModel4.1 についてはまだ何もわかりません。
実行環境が無いとコンパイルもできないので、調べようにも手が出せない
状態です。
ちなみに DirectX SDK August2007 のバージョンは 9.20 1057 でした。
久しぶりに、以前調べた SDK 一覧に追加してみると
・DirectX SDK April2007 とバージョン番号
9.19 / 1005.0000 Jun2007
9.19 / 1007.0000 Jun2007 07/10版
9.20 / 1057.0000 Aug2007 (10.1 TechPreview)
こうなります。Jun2007 も 2種類あって、今ダウンロードできるものは
7月版に入れ替わっています。
今の 10.0 対応ビデオカードでそのまま 10.1 の新機能が
使えるとは限りません。(たぶん使えなさそう)
といっても 10.0 ハードに対応しないわけにもいかないでしょう。
D3D10CreateDevice1() で Device のインターフェースを作成するときに、
・D3D10_FEATURE_LEVEL_10_1
・D3D10_FEATURE_LEVEL_10_0
と2種類のパラメータ指定ができるようです。
ということは、10.1 がリリースされた場合は今後 SDK 上は 10.1 を
使っておき、初期化で 10.0 or 10.1 を切り替えることになるのでしょうか。
ShaderModel4.1 についてはまだ何もわかりません。
実行環境が無いとコンパイルもできないので、調べようにも手が出せない
状態です。
ちなみに DirectX SDK August2007 のバージョンは 9.20 1057 でした。
久しぶりに、以前調べた SDK 一覧に追加してみると
・DirectX SDK April2007 とバージョン番号
9.19 / 1005.0000 Jun2007
9.19 / 1007.0000 Jun2007 07/10版
9.20 / 1057.0000 Aug2007 (10.1 TechPreview)
こうなります。Jun2007 も 2種類あって、今ダウンロードできるものは
7月版に入れ替わっています。
2007/07/30
Direct3D10 StreamOutput
D3D10 で StreamOutput を使う場合、ID3D10GeometryShader の作成に
ID3D10Device::CreateGeometryShader() ではなく
ID3D10Device::CreateGeometryShaderWithStreamOutput() を使います。
Effect(fx) を使用している場合も、Effect(fx) ファイルの中で
CompileShader() → ConstructGSWithSO()
の流れで、StreamOutput 対応 GeometryShader を作成します。
この辺はほとんどサンプルの記述どおりです。
出力フォーマットの記述など、扱いは Effect ファイルの方が楽なので、
・Effect(fx) ファイルに ConstructGSWithSO() で記述
・ID3D10Effect 作成
・GeometryShader のインターフェース取り出し
の手順で、WithStreamOutput の GeometryShader を簡単に取り出す
ことができます。
普段 GeometryShader を使わない場合は NULL 省略できますが、
StreamOutput では上記方法で GeometryShader を作らなければなりません。
少々不思議なのは D3D10 のサンプルでは、GeometryShader が不要な場合に
ConstructGSWithSO() にコンパイルした VertexShader を渡していること。
例えばこんな感じで使われており、実際にコンパイルしてみても
GeometryShader に VertexShader が入っていました。
VertexShader vs_transform= CompileShader( vs_4_0, VS_Transform() );
GeometryShader gs_transform= ConstructGSWithSO( vs_transform,
"POSITION.xyzw;NORMAL.xyz;TEXCOORD.xy" );
StreamOutput 先のバッファを指定するには ID3D10Device::SOSetTargets()
を使います。また同時に現在 SO Stage が有効かどうかの切り替えもこの
SOSetTargets() で行われています。
D3D10_BIND_STREAM_OUTPUT で作った ID3D10Buffer を渡すか NULL を渡すか、
これだけです。
StreamOutput を使う場合は PixelShader を通らないので、PixelShader には
NULL を設定しなければなりません。
さらに Depth/Stencil Buffer が有効になっていると、Pixel Pipeline が
有効とみなされるようです。
StreamOutput 時は Depth/Stencil test を Disable しなければなりません。
この切り替えはもちろん ID3D10DepthStencilState です。
・Shaderやバッファ作成
CreateGeometryShaderWithStreamOutput()
CreateDepthStencilState()
CreateBuffer( D3D10_BIND_STREAM_OUTPUT )
・描画時
OMSetDepthStencilState( ZENABLE= D3DZB_FALSE , ZWRITEENABLE= FALSE )
PSSetShader( NULL )
SOSetTargets( iBuffer )
Draw( ... )
SOSetTargets( NULL )
バッファ内の StreamOutput 位置は SOSetTargets() の Offset で
指定することができます。
位置が重ならないようにリソースを作ったり Stream に入れたりしても
やはり入出力に同じ Buffer を与えることはできませんでした。
ちなみに GeometryShader で複数頂点を入力して Triangle 出力すると
Index が展開されるので、DrawIndexed() 用の mesh データでも
Draw() での描画になります。
Triangle の増減も可能なのだから仕方ないですが、頂点 Cache の効率を
考えると少々もったいないですね。
GeometryShader が不要で頂点単位の事前変換だけ行う場合は、
StreamOutput 時に POINTLIST を使います。
この場合 Triangle の増減はできないものの、出力頂点数は変化ないので
事前変換しつつ DrawIndexed() を使うことができます。
ID3D10Device::CreateGeometryShader() ではなく
ID3D10Device::CreateGeometryShaderWithStreamOutput() を使います。
Effect(fx) を使用している場合も、Effect(fx) ファイルの中で
CompileShader() → ConstructGSWithSO()
の流れで、StreamOutput 対応 GeometryShader を作成します。
この辺はほとんどサンプルの記述どおりです。
出力フォーマットの記述など、扱いは Effect ファイルの方が楽なので、
・Effect(fx) ファイルに ConstructGSWithSO() で記述
・ID3D10Effect 作成
・GeometryShader のインターフェース取り出し
の手順で、WithStreamOutput の GeometryShader を簡単に取り出す
ことができます。
普段 GeometryShader を使わない場合は NULL 省略できますが、
StreamOutput では上記方法で GeometryShader を作らなければなりません。
少々不思議なのは D3D10 のサンプルでは、GeometryShader が不要な場合に
ConstructGSWithSO() にコンパイルした VertexShader を渡していること。
例えばこんな感じで使われており、実際にコンパイルしてみても
GeometryShader に VertexShader が入っていました。
VertexShader vs_transform= CompileShader( vs_4_0, VS_Transform() );
GeometryShader gs_transform= ConstructGSWithSO( vs_transform,
"POSITION.xyzw;NORMAL.xyz;TEXCOORD.xy" );
StreamOutput 先のバッファを指定するには ID3D10Device::SOSetTargets()
を使います。また同時に現在 SO Stage が有効かどうかの切り替えもこの
SOSetTargets() で行われています。
D3D10_BIND_STREAM_OUTPUT で作った ID3D10Buffer を渡すか NULL を渡すか、
これだけです。
StreamOutput を使う場合は PixelShader を通らないので、PixelShader には
NULL を設定しなければなりません。
さらに Depth/Stencil Buffer が有効になっていると、Pixel Pipeline が
有効とみなされるようです。
StreamOutput 時は Depth/Stencil test を Disable しなければなりません。
この切り替えはもちろん ID3D10DepthStencilState です。
・Shaderやバッファ作成
CreateGeometryShaderWithStreamOutput()
CreateDepthStencilState()
CreateBuffer( D3D10_BIND_STREAM_OUTPUT )
・描画時
OMSetDepthStencilState( ZENABLE= D3DZB_FALSE , ZWRITEENABLE= FALSE )
PSSetShader( NULL )
SOSetTargets( iBuffer )
Draw( ... )
SOSetTargets( NULL )
バッファ内の StreamOutput 位置は SOSetTargets() の Offset で
指定することができます。
位置が重ならないようにリソースを作ったり Stream に入れたりしても
やはり入出力に同じ Buffer を与えることはできませんでした。
ちなみに GeometryShader で複数頂点を入力して Triangle 出力すると
Index が展開されるので、DrawIndexed() 用の mesh データでも
Draw() での描画になります。
Triangle の増減も可能なのだから仕方ないですが、頂点 Cache の効率を
考えると少々もったいないですね。
GeometryShader が不要で頂点単位の事前変換だけ行う場合は、
StreamOutput 時に POINTLIST を使います。
この場合 Triangle の増減はできないものの、出力頂点数は変化ないので
事前変換しつつ DrawIndexed() を使うことができます。
2007/07/29
Direct3D 10.1 Tech Preview
DirectX SDK August 2007 がもう公開されているとのことです。早い!
・新 masafumi's Diary, 8月号
しかも Direct3D 10.1 Tech Preview が入ってるとのこと。
ざっと見てみました。
変更点など詳細はこちら
・DirectX Software Development Kit
ShaderModel 4.1 の追加と、レンダリング関連の強化が行われるようです。
まだ詳細も不明で、使えるようになるのもまだまだ先でしょうが
これは楽しみです。以下ドキュメントに書いてなさそうな点です。
追加されたインターフェース名は最後に"1"がついています。
ID3D10BlendState1
ID3D10ShaderResourceView1
ID3D10Device1
10.1 のシンボル定数は D3D10_1_~ といった感じ。
作成は D3D10CreateDevice1() / D3D10CreateDeviceAndSwapChain1()
●BlendState1
10.0 では最大8枚の出力先に対して、
・BlendEnable
・RenderTargetWriteMask
を個別に指定することができました。Blend を使うかどうか、
書き込むチャンネルのみ独立して指定でき、合成方法は全プレーン共通です。
どうやら 10.1 では、Src/Desc 等のブレンドモードも RenderTarget
毎に別パラメータを設定できるようになりそうです。
●ShaderResourceView1
ShaderResourceView1 ではドキュメントにも書かれているように
TextureCubeArray が追加されています。
10.0 では Texture1D/2D/2DMS だけが Array 対象でした。
Cube の Array というと ShadowMap などに便利かもしれません。
Texture の Load コマンドのアドレッシングが 5次元になってしまうので
引数が追加されるのでしょうか。
インターフェースを ~1 に、
やヘッダ d3d10.h → d3d10_1.h の置き換え、
D3D10CreateDeviceAndSwapChain1() で D3D10_FEATURE_LEVEL_10_1 指定、
ライブラリ d3d10_1d.lib 追加等で一応 10.1 の Build はできます。
が、d3d10_1core.dll が無いので起動できませんでした。
・新 masafumi's Diary, 8月号
しかも Direct3D 10.1 Tech Preview が入ってるとのこと。
ざっと見てみました。
変更点など詳細はこちら
・DirectX Software Development Kit
ShaderModel 4.1 の追加と、レンダリング関連の強化が行われるようです。
まだ詳細も不明で、使えるようになるのもまだまだ先でしょうが
これは楽しみです。以下ドキュメントに書いてなさそうな点です。
追加されたインターフェース名は最後に"1"がついています。
ID3D10BlendState1
ID3D10ShaderResourceView1
ID3D10Device1
10.1 のシンボル定数は D3D10_1_~ といった感じ。
作成は D3D10CreateDevice1() / D3D10CreateDeviceAndSwapChain1()
●BlendState1
10.0 では最大8枚の出力先に対して、
・BlendEnable
・RenderTargetWriteMask
を個別に指定することができました。Blend を使うかどうか、
書き込むチャンネルのみ独立して指定でき、合成方法は全プレーン共通です。
どうやら 10.1 では、Src/Desc 等のブレンドモードも RenderTarget
毎に別パラメータを設定できるようになりそうです。
●ShaderResourceView1
ShaderResourceView1 ではドキュメントにも書かれているように
TextureCubeArray が追加されています。
10.0 では Texture1D/2D/2DMS だけが Array 対象でした。
Cube の Array というと ShadowMap などに便利かもしれません。
Texture の Load コマンドのアドレッシングが 5次元になってしまうので
引数が追加されるのでしょうか。
インターフェースを ~1 に、
やヘッダ d3d10.h → d3d10_1.h の置き換え、
D3D10CreateDeviceAndSwapChain1() で D3D10_FEATURE_LEVEL_10_1 指定、
ライブラリ d3d10_1d.lib 追加等で一応 10.1 の Build はできます。
が、d3d10_1core.dll が無いので起動できませんでした。
2007/07/28
Direct3D 10 Streamと同時Resource
Vertex として Stream で読み込んでいる Buffer を、
ShaderResourceView で同時にランダムアクセスすることができました。
これはいいね、使えそう。
可能
・Stream + ShaderResourceView
不可能
・StreamOutput + ShaderResourceView
・StreamOutput + StreamInput
これらのパラメータ組み合わせは D3D10_CREATE_DEVICE_DEBUG 時は
かなり細かくチェックしてくれて詳細な説明とともにエラーが出ます。
若干気をつけないといけないのはパラメータ設定の順番です。
IASetVertex BufA
SOSetTarget BufB
Draw
IASetVertex BufB
SOSetTarget BufC
Draw
SOSetTarget NULL
一見問題ないように見えますが、2つ目の IASetVertex BufB では、
BufB がまだ StreamOutput 対象だからだめだと怒られます。
先に SOSetTarget BufC を設定しなおして BufB を切り離すか、
間に SOSetTarget NULL が必要となります。
IASetVertex BufA
SOSetTarget BufB
Draw
SOSetTarget BufC
IASetVertex BufB
Draw
SOSetTarget NULL
同様のことは ShaderResource や StreamInput 側にも言えます。
特に Stream の 2~3 番目に設定された Buffer は NULL による解放を
を忘れがちなので、あとから StreamOutput の Target にしようとすると
エラーです。
ミスを無くすためには Set 系には毎回こまめに NULL を入れた方が
いいようです。
それにしても Direct3D10 + ShaderModel4.0 は、
こうしなければいけないっていう変な制限が少なく、
Buffer やら InstanceID とか組み合わせたり、
結構自由に考えたとおりに動いてくれるのが気持ちいいですね。
(速度や効率はおいといて)
ShaderResourceView で同時にランダムアクセスすることができました。
これはいいね、使えそう。
可能
・Stream + ShaderResourceView
不可能
・StreamOutput + ShaderResourceView
・StreamOutput + StreamInput
これらのパラメータ組み合わせは D3D10_CREATE_DEVICE_DEBUG 時は
かなり細かくチェックしてくれて詳細な説明とともにエラーが出ます。
若干気をつけないといけないのはパラメータ設定の順番です。
IASetVertex BufA
SOSetTarget BufB
Draw
IASetVertex BufB
SOSetTarget BufC
Draw
SOSetTarget NULL
一見問題ないように見えますが、2つ目の IASetVertex BufB では、
BufB がまだ StreamOutput 対象だからだめだと怒られます。
先に SOSetTarget BufC を設定しなおして BufB を切り離すか、
間に SOSetTarget NULL が必要となります。
IASetVertex BufA
SOSetTarget BufB
Draw
SOSetTarget BufC
IASetVertex BufB
Draw
SOSetTarget NULL
同様のことは ShaderResource や StreamInput 側にも言えます。
特に Stream の 2~3 番目に設定された Buffer は NULL による解放を
を忘れがちなので、あとから StreamOutput の Target にしようとすると
エラーです。
ミスを無くすためには Set 系には毎回こまめに NULL を入れた方が
いいようです。
それにしても Direct3D10 + ShaderModel4.0 は、
こうしなければいけないっていう変な制限が少なく、
Buffer やら InstanceID とか組み合わせたり、
結構自由に考えたとおりに動いてくれるのが気持ちいいですね。
(速度や効率はおいといて)
2007/07/27
Direct3D 10 8bit IndexBuffer
d3d10.h (June2007) を見ると、
D3D10_16BIT_INDEX_STRIP_CUT_VALUE や
D3D10_32BIT_INDEX_STRIP_CUT_VALUE と一緒に、
D3D10_8BIT_INDEX_STRIP_CUT_VALUE なるシンボルが定義してあります。
これだけみるとなんだか 8bit IndexBuffer が使えそうです。
・ID3D10Device::IASetIndexBuffer
マニュアルにはだめ (16bit or 32bit) と明記されてますが、さすが
Unified Shader だなと思って R8_UINT を試してみました。
結果はだめでディスプレイドライバがハングアップし、ドライバリセットが
かかります。
画面にプリミティブ形状は出るので、一応中で変換するのか
またはハード上バッファは読めるが API で禁止しているのかわかりません。
追求はしておりません。
Shader が自分で Buffer を読み進めれば似たようなことはできるのですが。
STRIP_CUT_VALUE は Strip 中にプリミティブの区切りを指定する特殊な
Index 値です。D3D9 までは頂点を同じ座標に何度も重ね書きすることで
Jump してましたが D3D10 では扱いが簡単になりました。
D3D10_16BIT_INDEX_STRIP_CUT_VALUE や
D3D10_32BIT_INDEX_STRIP_CUT_VALUE と一緒に、
D3D10_8BIT_INDEX_STRIP_CUT_VALUE なるシンボルが定義してあります。
これだけみるとなんだか 8bit IndexBuffer が使えそうです。
・ID3D10Device::IASetIndexBuffer
マニュアルにはだめ (16bit or 32bit) と明記されてますが、さすが
Unified Shader だなと思って R8_UINT を試してみました。
結果はだめでディスプレイドライバがハングアップし、ドライバリセットが
かかります。
画面にプリミティブ形状は出るので、一応中で変換するのか
またはハード上バッファは読めるが API で禁止しているのかわかりません。
追求はしておりません。
Shader が自分で Buffer を読み進めれば似たようなことはできるのですが。
STRIP_CUT_VALUE は Strip 中にプリミティブの区切りを指定する特殊な
Index 値です。D3D9 までは頂点を同じ座標に何度も重ね書きすることで
Jump してましたが D3D10 では扱いが簡単になりました。
2007/07/26
Direct3D 10 HLSL(fx) の書き方とサイズ
HLSL で記述した Effect(fx) ファイルには複数の technique を記述して
おくことができます。
またそれぞれの technique は複数の pass を含めることができるので、
1つの Effect ファイルにはたくさんのシェーダーが入っている可能性があります。
その記述方法としては、例えばこんな感じになります。
---------------------------------------------------------------------
technique10 Main
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS_Main() ) );
SetGeometryShader( CompileShader( gs_4_0, GS_Main() ) );
SetPixelShader( CompileShader( ps_4_0, PS_Main() ) );
}
}
technique10 Animation
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS_Animation() ) );
SetGeometryShader( CompileShader( gs_4_0, GS_Main() ) );
SetPixelShader( CompileShader( ps_4_0, PS_Main() ) );
}
}
---------------------------------------------------------------------
ここで technique Main と Animation の違いは VertexShader だけです。
Direct3D10 の fx_4_0 では、GeometryShader と PixelShader を
あらかじめコンパイルしておいて下記のように記述することができます。
---------------------------------------------------------------------
GeometryShader gs_main= CompileShader( gs_4_0, GS_Main() );
PixelShader ps_main= CompileShader( ps_4_0, PS_Main() );
technique10 Main
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS_Main() ) );
SetGeometryShader( gs_main );
SetPixelShader( ps_main );
}
}
technique10 Animation
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS_Animation() ) );
SetGeometryShader( gs_main );
SetPixelShader( ps_main );
}
}
---------------------------------------------------------------------
この両者やってることは変わらないのに、下の方がコンパイルした後のバイナリ
サイズでちょうど重複するシェーダーの分だけ小さくなります。
おそらくコンパイル時間も短縮されているのでしょう。
technique 間のシェーダー流用は多いし、コードもだんだん複雑に長くなって
きているので、この辺しっかり注意して書いた方がよさそうですね。
おくことができます。
またそれぞれの technique は複数の pass を含めることができるので、
1つの Effect ファイルにはたくさんのシェーダーが入っている可能性があります。
その記述方法としては、例えばこんな感じになります。
---------------------------------------------------------------------
technique10 Main
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS_Main() ) );
SetGeometryShader( CompileShader( gs_4_0, GS_Main() ) );
SetPixelShader( CompileShader( ps_4_0, PS_Main() ) );
}
}
technique10 Animation
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS_Animation() ) );
SetGeometryShader( CompileShader( gs_4_0, GS_Main() ) );
SetPixelShader( CompileShader( ps_4_0, PS_Main() ) );
}
}
---------------------------------------------------------------------
ここで technique Main と Animation の違いは VertexShader だけです。
Direct3D10 の fx_4_0 では、GeometryShader と PixelShader を
あらかじめコンパイルしておいて下記のように記述することができます。
---------------------------------------------------------------------
GeometryShader gs_main= CompileShader( gs_4_0, GS_Main() );
PixelShader ps_main= CompileShader( ps_4_0, PS_Main() );
technique10 Main
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS_Main() ) );
SetGeometryShader( gs_main );
SetPixelShader( ps_main );
}
}
technique10 Animation
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS_Animation() ) );
SetGeometryShader( gs_main );
SetPixelShader( ps_main );
}
}
---------------------------------------------------------------------
この両者やってることは変わらないのに、下の方がコンパイルした後のバイナリ
サイズでちょうど重複するシェーダーの分だけ小さくなります。
おそらくコンパイル時間も短縮されているのでしょう。
technique 間のシェーダー流用は多いし、コードもだんだん複雑に長くなって
きているので、この辺しっかり注意して書いた方がよさそうですね。
2007/07/25
D3D10 DrawIndexedInstanced()後
非常に情けない話です。
・D3D10 DrawIndexedInstanced()が
で書いた DrawIndexedInstanced() のオフセットの問題は、やっぱりこちらの
プログラムが原因でした。
D3D10 Device の wrapper で StartIndexLocation を StartInstanceLocation と
間違って記述していました。
DrawInstanced() の StartInstanceLocation と同じようにしっかり意図通り動作
しております。申し訳ありません。
●VertexShader に対する入力データのまとめ
・VertexStream
・頂点単位入力
・インスタンス単位入力
・ShaderResourceView / tbuffer
・Random Access (ld,sampler)
・ConstantBuffer (cbuffer)
・Random Access (cレジスタ)
・D3D10 DrawIndexedInstanced()が
で書いた DrawIndexedInstanced() のオフセットの問題は、やっぱりこちらの
プログラムが原因でした。
D3D10 Device の wrapper で StartIndexLocation を StartInstanceLocation と
間違って記述していました。
DrawInstanced() の StartInstanceLocation と同じようにしっかり意図通り動作
しております。申し訳ありません。
●VertexShader に対する入力データのまとめ
・VertexStream
・頂点単位入力
・インスタンス単位入力
・ShaderResourceView / tbuffer
・Random Access (ld,sampler)
・ConstantBuffer (cbuffer)
・Random Access (cレジスタ)
2007/07/24
Direct3D 10 大はまり tbuffer
なぜか shader (4.0) の tbuffer がうまく動きません。
これはかなり はまり ました。
Texture など他に ShaderResource を使わない状態での
tbuffer または Buffer 宣言で、
最初のベクトルしか読み出せない症状が発生します。
例えば
tbuffer ColorData
{
float4 color0;
float4 color1;
float4 color2;
};
と宣言し、
color0 = { 1, 0, 0, 1 } (赤)
color1 = { 1, 1, 0, 1 } (黄色)
color2 = { 0, 1, 0, 1 } (緑)
のようにパラメータを設定したとします。
VS または PS で color1 を読み出してもなぜか color0 の値「赤」が
表示されてしまいます。
これは ShaderResource にして
Buffer<float4>
のように宣言して Load を使っても同じでした。
使い方の問題だと思っておかしいおかしいと散々悩むこと丸一日。
ふと Reference Driver で試すとちゃんと黄色になるではありませんか。
もしやと思って、RADEON HD 2900XT で走らせるとこちらも「黄色」!!
これは参りました。
ちなみにそれまで使っていたのは
GeForce8800GTS + ForceWare 158.24
(2007/07/22 現在の公式最新版) です。
たまたま BETA 版ドライバが 2007/07/11 に出ていたので
BETA 版 ForceWare 163.11
に入れ替えたら、、きちんと黄色です!
以前 88 の BETA 版ドライバで懲りていたので、
入れ替えを控えていたのですよ。
Reference での実験も忘れないようにします。
これはかなり はまり ました。
Texture など他に ShaderResource を使わない状態での
tbuffer または Buffer 宣言で、
最初のベクトルしか読み出せない症状が発生します。
例えば
tbuffer ColorData
{
float4 color0;
float4 color1;
float4 color2;
};
と宣言し、
color0 = { 1, 0, 0, 1 } (赤)
color1 = { 1, 1, 0, 1 } (黄色)
color2 = { 0, 1, 0, 1 } (緑)
のようにパラメータを設定したとします。
VS または PS で color1 を読み出してもなぜか color0 の値「赤」が
表示されてしまいます。
これは ShaderResource にして
Buffer<float4>
のように宣言して Load を使っても同じでした。
使い方の問題だと思っておかしいおかしいと散々悩むこと丸一日。
ふと Reference Driver で試すとちゃんと黄色になるではありませんか。
もしやと思って、RADEON HD 2900XT で走らせるとこちらも「黄色」!!
これは参りました。
ちなみにそれまで使っていたのは
GeForce8800GTS + ForceWare 158.24
(2007/07/22 現在の公式最新版) です。
たまたま BETA 版ドライバが 2007/07/11 に出ていたので
BETA 版 ForceWare 163.11
に入れ替えたら、、きちんと黄色です!
以前 88 の BETA 版ドライバで懲りていたので、
入れ替えを控えていたのですよ。
Reference での実験も忘れないようにします。
2007/07/23
Direct3D 10 ビデオカードの違いはわからない
Direct3D10 になって「caps がなくなった」のは大きな特徴のひとつです。
MS の DX10 ppt スライド等でも何度も出てきており、メリットとして強調
しています。
従来メーカー毎に、ビデオカードごとに、さらにはドライバごとに対応機能も
能力もばらばらなのが当たり前でした。
新しいビデオカードを手に入れたなら、まずは caps を調べます。
対応するサーフェースフォーマットを調べて、テクスチャサイズの制限を調べて、
使えるブレンドモードを調べて、、次に各種パフォーマンスと
やることがたくさんありました。
DirectX API のすべての機能が最初から使えるビデオカードなど無いので、
手に入れてから見たことがない機能が enable されていたりすると
妙に嬉しかったりするわけです。(←それは一部のマニアだけです)
nVIDIA と ATI の2強になってからは手間が大幅に減りましたが、このような
能力の差はかなりの開発の負担となります。結局どれでも動く当たり障りの
無い機能しか使えないし、動作確認もばかになりません。
Direct3D10 ではこの負担を減らすために、機能はどのビデオカードでも
同じように使えることが前提となりました。なので、caps が廃止された
ことの本当の意味は
caps によって機能の違いを調べる必要が無い
ということです。
実際に RADEON HD2900XT を使ってみました。
caps が違ったり機能が違ったり、列挙の値が違ったりすることも全く無くて、
普段使ってる GeForce8800GTS と一見何も変わりません。今までのように API
を使う上での違いが全く見えません。ちょっとさびしいくらいです。
これは API から見えないだけで本当はハードの特性は異なっており、
機能ではなく速度面で調査する段階になったらかなりの個性が見えてくる
はずです。
だけどそのような違いも一見ドライバレベルで隠蔽されていて、おそらく下位
モデルで試しても全く同じなのでしょう。
プログラムの開発上は機能的な差がなくなりましたが、パフォーマンスを維持
するならある程度の機能的取捨選択のノウハウは必要になると考えられます。
一見違いが無くても、下位モデルでは一部機能がソフトウエア実装だったり
使えるリソースが大幅に減っていたりする可能性が十分考えられるからです。
これらの可能性があるとしても機能的な違いが開発側から見えないということは、
開発が楽になることの裏返しとして、
実はその選択をユーザー側に押し付けているだけなのかもしれません。
アプリケーション側での選択の余地が減った代わりに、ユーザーが自分で
パフォーマンスが足りなければ機能を落として、またはより良いハードを購入
して対処しなければならないからです。
作る側としてはそうならないよう、努力を忘れないよう、
肝に銘じておきます。
MS の DX10 ppt スライド等でも何度も出てきており、メリットとして強調
しています。
従来メーカー毎に、ビデオカードごとに、さらにはドライバごとに対応機能も
能力もばらばらなのが当たり前でした。
新しいビデオカードを手に入れたなら、まずは caps を調べます。
対応するサーフェースフォーマットを調べて、テクスチャサイズの制限を調べて、
使えるブレンドモードを調べて、、次に各種パフォーマンスと
やることがたくさんありました。
DirectX API のすべての機能が最初から使えるビデオカードなど無いので、
手に入れてから見たことがない機能が enable されていたりすると
妙に嬉しかったりするわけです。(←それは一部のマニアだけです)
nVIDIA と ATI の2強になってからは手間が大幅に減りましたが、このような
能力の差はかなりの開発の負担となります。結局どれでも動く当たり障りの
無い機能しか使えないし、動作確認もばかになりません。
Direct3D10 ではこの負担を減らすために、機能はどのビデオカードでも
同じように使えることが前提となりました。なので、caps が廃止された
ことの本当の意味は
caps によって機能の違いを調べる必要が無い
ということです。
実際に RADEON HD2900XT を使ってみました。
caps が違ったり機能が違ったり、列挙の値が違ったりすることも全く無くて、
普段使ってる GeForce8800GTS と一見何も変わりません。今までのように API
を使う上での違いが全く見えません。ちょっとさびしいくらいです。
これは API から見えないだけで本当はハードの特性は異なっており、
機能ではなく速度面で調査する段階になったらかなりの個性が見えてくる
はずです。
だけどそのような違いも一見ドライバレベルで隠蔽されていて、おそらく下位
モデルで試しても全く同じなのでしょう。
プログラムの開発上は機能的な差がなくなりましたが、パフォーマンスを維持
するならある程度の機能的取捨選択のノウハウは必要になると考えられます。
一見違いが無くても、下位モデルでは一部機能がソフトウエア実装だったり
使えるリソースが大幅に減っていたりする可能性が十分考えられるからです。
これらの可能性があるとしても機能的な違いが開発側から見えないということは、
開発が楽になることの裏返しとして、
実はその選択をユーザー側に押し付けているだけなのかもしれません。
アプリケーション側での選択の余地が減った代わりに、ユーザーが自分で
パフォーマンスが足りなければ機能を落として、またはより良いハードを購入
して対処しなければならないからです。
作る側としてはそうならないよう、努力を忘れないよう、
肝に銘じておきます。
2007/07/22
D3D10 ConstantBuffer のアクセス補足など
以前こちらで ConstantBuffer を書き換える方法を調べました。
・D3D10 ConstantBuffer の更新方法
書き忘れがあったので補足します。
ID3D10Effect 自体は内部で (2) の方法、つまり
ID3D10Device::UpdateSubresource()
を使っているようです。なぜかというと、ID3D10Effect で作成した Effect から
内部の ConstantBuffer を参照して DESC を見ると D3D10_USAGE_DEFAULT で
作られているからです。
なので、ConstantBuffer の書き換えは UpdateSubresource() が標準的な
使い方なのかもしれません。
だけど、ConstantBuffer に直接アクセスしている唯一のサンプル
HLSLWithoutFX10 では Map()/Unmap() を使っているんですよね。
長かったですが、ようやく方針も固まって、周辺ライブラリとかツールとか
できてきたので実際の描画実験に手を出せそうです。
リソース管理とかシェーダーの管理、パラメータのリンク方法、スクリプト、
データフォーマットなどかなり遠回りしました。非常に時間がかかってます。
DXUT は使わず、D3DX もテクスチャローダーだけ使っています。
fx は使ってますが、ID3DEffect から ID3D10Shader を取り出して、あとは
直接管理することにしました。この方法だと内部に無駄な ID3D10Buffer が
たくさん出来ているので、最終的には直接 ID3D~Shader を生成するつもりです。
・D3D10 ConstantBuffer の更新方法
書き忘れがあったので補足します。
ID3D10Effect 自体は内部で (2) の方法、つまり
ID3D10Device::UpdateSubresource()
を使っているようです。なぜかというと、ID3D10Effect で作成した Effect から
内部の ConstantBuffer を参照して DESC を見ると D3D10_USAGE_DEFAULT で
作られているからです。
なので、ConstantBuffer の書き換えは UpdateSubresource() が標準的な
使い方なのかもしれません。
だけど、ConstantBuffer に直接アクセスしている唯一のサンプル
HLSLWithoutFX10 では Map()/Unmap() を使っているんですよね。
長かったですが、ようやく方針も固まって、周辺ライブラリとかツールとか
できてきたので実際の描画実験に手を出せそうです。
リソース管理とかシェーダーの管理、パラメータのリンク方法、スクリプト、
データフォーマットなどかなり遠回りしました。非常に時間がかかってます。
DXUT は使わず、D3DX もテクスチャローダーだけ使っています。
fx は使ってますが、ID3DEffect から ID3D10Shader を取り出して、あとは
直接管理することにしました。この方法だと内部に無駄な ID3D10Buffer が
たくさん出来ているので、最終的には直接 ID3D~Shader を生成するつもりです。
2007/07/22
Direct3D 10 June2007 SDK の非常に細
「Direct3D10 June2007 SDK の非常に細かいこと」
ID3D10ShaderReflection から取れる D3D10_SHADER_DESC のうち
マニュアルに載ってる最後の 4つ
UINT MovInstructionCount;
UINT MovcInstructionCount;
UINT ConversionInstructionCount;
UINT BitwiseInstructionCount;
は実際には定義されていないです。
動作上全然問題ないので、これはたぶん必要ない、何の特にもならない情報だと
思います。(コンパイルされた命令の詳細とか普段使わないし)
ID3D10Effect から tbuffer を取るには ConstantBuffer 扱いでした。
GetConstantBufferBy~()->GetTextureBuffer()
でいけます。
リソースなので
GetVariableBy~()->AsResource()->GetResource()
かと思ったら違いました。
この手順を使うのは Buffer 宣言した場合です。
ID3D10ShaderReflection からアクセスする場合も TBUFFER は
ConstantBuffer ではなく Texture (ShaderResourceView)扱いです。
ID3D10ShaderReflection から取れる D3D10_SHADER_DESC のうち
マニュアルに載ってる最後の 4つ
UINT MovInstructionCount;
UINT MovcInstructionCount;
UINT ConversionInstructionCount;
UINT BitwiseInstructionCount;
は実際には定義されていないです。
動作上全然問題ないので、これはたぶん必要ない、何の特にもならない情報だと
思います。(コンパイルされた命令の詳細とか普段使わないし)
ID3D10Effect から tbuffer を取るには ConstantBuffer 扱いでした。
GetConstantBufferBy~()->GetTextureBuffer()
でいけます。
リソースなので
GetVariableBy~()->AsResource()->GetResource()
かと思ったら違いました。
この手順を使うのは Buffer 宣言した場合です。
ID3D10ShaderReflection からアクセスする場合も TBUFFER は
ConstantBuffer ではなく Texture (ShaderResourceView)扱いです。
2007/07/21
Direct3D 10 ShaderReflection を
「Direct3D 10 ShaderReflection を安全に取得する方法」
以前こちらで ID3D10Effect から ID3D10ShaderReflection を取得する方法を
紹介しましたが、このままだと少々問題がありました。
・D3D10/DX10 Effect Interface内情報
というのは、以前も書いたように Effect 内で NULL が渡されたシェーダーで
あっても D3D10Effect の API はほとんど有効なまま素通りしてしまうからです。
例えば
ID3D10Effect* iEffect; // Input
ID3D10EffectTechnique* iEffectTechnique= iEffect->GetTechniqueByIndex( 0 );
if( !iEffectTechnique || !iEffectTechnique->IsValid() ){
return FALSE;
}
ID3D10EffectPass* iEffectPass= iEffectTechnique->GetPassByIndex( 0 );
if( !iEffectPass->IsValid() ){
return FALSE;
}
D3D10_PASS_SHADER_DESC pass_shader_desc;
if( FAILED( ipass->GetGeometryShaderDesc( &pass_shader_desc ) ) ){
return FALSE;
}
ID3D10EffectShaderVariable* iShaderVariable= pass_shader_desc.pShaderVariable;
if( !iShaderVariable->IsValid() ){
return FALSE;
}
D3D10_EFFECT_SHADER_DESC effect_shader_desc;
if( FAILED( shader->GetShaderDesc( pass_shader_desc.ShaderIndex, &effect_shader_desc ) ){
return FALSE;
}
ID3D10ShaderReflection* iReflection; // Output
if( FAILED( D3D10ReflectShader( esdesc.pBytecode, esdesc.BytecodeLength, &iReflection ) ){
}
と幾重にチェックを入れても引っかからず、最後の D3D10ReflectShader() で
E_INVALIDARG になります。一旦 iShaderVariable->GetGeometryShader() で実際の
シェーダーインターフェース
(ID3D10GeometryShader, ID3D10VertexShader, ID3D10PixelShader)
を取得して、設定されているシェーダーが NULL かどうか判定しても良いのですが、
この場合 Get で AddRef() されるため Release() が必要になってしまいます。
D3D10_EFFECT_SHADER_DESC effect_shader_desc が取れたところで、バイトコード
サイズによって
if( !effect_shader_desc.BytecodeLength ){
return FALSE;
}
とはじいてあげるのが良さそうです。
またすっかり忘れてましたが、NULL になる可能性があるのは GeometryShader
だけではありませんでした。StreamOutput を使ったときは PixelShader も NULL
設定される可能性があります。
以前こちらで ID3D10Effect から ID3D10ShaderReflection を取得する方法を
紹介しましたが、このままだと少々問題がありました。
・D3D10/DX10 Effect Interface内情報
というのは、以前も書いたように Effect 内で NULL が渡されたシェーダーで
あっても D3D10Effect の API はほとんど有効なまま素通りしてしまうからです。
例えば
ID3D10Effect* iEffect; // Input
ID3D10EffectTechnique* iEffectTechnique= iEffect->GetTechniqueByIndex( 0 );
if( !iEffectTechnique || !iEffectTechnique->IsValid() ){
return FALSE;
}
ID3D10EffectPass* iEffectPass= iEffectTechnique->GetPassByIndex( 0 );
if( !iEffectPass->IsValid() ){
return FALSE;
}
D3D10_PASS_SHADER_DESC pass_shader_desc;
if( FAILED( ipass->GetGeometryShaderDesc( &pass_shader_desc ) ) ){
return FALSE;
}
ID3D10EffectShaderVariable* iShaderVariable= pass_shader_desc.pShaderVariable;
if( !iShaderVariable->IsValid() ){
return FALSE;
}
D3D10_EFFECT_SHADER_DESC effect_shader_desc;
if( FAILED( shader->GetShaderDesc( pass_shader_desc.ShaderIndex, &effect_shader_desc ) ){
return FALSE;
}
ID3D10ShaderReflection* iReflection; // Output
if( FAILED( D3D10ReflectShader( esdesc.pBytecode, esdesc.BytecodeLength, &iReflection ) ){
}
と幾重にチェックを入れても引っかからず、最後の D3D10ReflectShader() で
E_INVALIDARG になります。一旦 iShaderVariable->GetGeometryShader() で実際の
シェーダーインターフェース
(ID3D10GeometryShader, ID3D10VertexShader, ID3D10PixelShader)
を取得して、設定されているシェーダーが NULL かどうか判定しても良いのですが、
この場合 Get で AddRef() されるため Release() が必要になってしまいます。
D3D10_EFFECT_SHADER_DESC effect_shader_desc が取れたところで、バイトコード
サイズによって
if( !effect_shader_desc.BytecodeLength ){
return FALSE;
}
とはじいてあげるのが良さそうです。
またすっかり忘れてましたが、NULL になる可能性があるのは GeometryShader
だけではありませんでした。StreamOutput を使ったときは PixelShader も NULL
設定される可能性があります。
2007/07/20
D3D10 DrawIndexedInstanced()が
「DrawIndexedInstanced()がおかしい?」
Direct3D10 では ID3D10Device::Draw 系命令が5つあります。
Draw()/DrawIndexed() は従来の DrawPrimitive()/DrawIndexedPrimitive()
に非常に近くほとんどの描画はこの2つを使います。
Draw(
UINT VertexCount, // 描画する頂点数
UINT StartVertexLocation // 頂点の開始位置
)
DrawIndexed(
UINT IndexCount, // 描画するインデックス数
UINT StartIndexLocation, // インデックスの開始位置
INT BaseVertexLocation // インデックス指定する頂点の開始位置
)
DrawInstanced()/DrawIndexedInstanced() は同じデータを指定回数分
繰り返し描画することができます。この Geometry Instancing については
以前こちらの方でも書きました。
・D3D10 Geometry Instancing の問題と
DrawInstanced(
UINT VertexCountPerInstance, // 描画する頂点数
UINT InstanceCount, // 繰り返し描画回数
UINT StartVertexLocation, // 頂点の開始位置
UINT StartInstanceLocation, // インスタンスの開始位置
)
DrawIndexedInstanced(
UINT IndexCountPerInstance, // 描画するインデックス数
UINT InstanceCount, // 繰り返し描画回数
UINT StartIndexLocation, // インデックスの開始位置
INT BaseVertexLocation, // インデックス指定する頂点の開始位置
UINT StartInstanceLocation // インスタンスの開始位置
)
これらの命令は Instance の描画回数を指定する引数が追加されています。
同時に Instance に対するオフセットも指定できるようになっています。
DrawIndexedInstanced() でこのオフセットである StartInstanceLocation を
使うとなぜかきちんと描画してくれません。
描画されなかったりポリゴンが飛んだり。
DrawInstanced() ではうまく行っており、こちらは意図通りに動作しています。
指定した値の分だけ Instance バッファをずらしてそこから描画してくれます。
実行は GeForce8800GTS ForceWare 158.24 ですが、Reference Driver でも
RADEON HD 2900XT でも全く同じ描画になるので、使い方を勘違いしている
だけかもしれません。もうしばらく検証してみます。
ちなみに Indexed 系の命令は BaseVertexLocation だけ INT 型で、負の値を
受け付けるようです。
考えられる用途としては、例えばもともと1つの巨大な頂点バッファを複数の
バッファに分割したとき、Index バッファの値を書き換えなくても描画可能な
ことでしょうか。
後ろにずらすだけなら IASetVertexBuffers() でもできますが、前方にずらす
ことは今まで確かにこれまでできなかったかもしれません。
Direct3D10 では ID3D10Device::Draw 系命令が5つあります。
Draw()/DrawIndexed() は従来の DrawPrimitive()/DrawIndexedPrimitive()
に非常に近くほとんどの描画はこの2つを使います。
Draw(
UINT VertexCount, // 描画する頂点数
UINT StartVertexLocation // 頂点の開始位置
)
DrawIndexed(
UINT IndexCount, // 描画するインデックス数
UINT StartIndexLocation, // インデックスの開始位置
INT BaseVertexLocation // インデックス指定する頂点の開始位置
)
DrawInstanced()/DrawIndexedInstanced() は同じデータを指定回数分
繰り返し描画することができます。この Geometry Instancing については
以前こちらの方でも書きました。
・D3D10 Geometry Instancing の問題と
DrawInstanced(
UINT VertexCountPerInstance, // 描画する頂点数
UINT InstanceCount, // 繰り返し描画回数
UINT StartVertexLocation, // 頂点の開始位置
UINT StartInstanceLocation, // インスタンスの開始位置
)
DrawIndexedInstanced(
UINT IndexCountPerInstance, // 描画するインデックス数
UINT InstanceCount, // 繰り返し描画回数
UINT StartIndexLocation, // インデックスの開始位置
INT BaseVertexLocation, // インデックス指定する頂点の開始位置
UINT StartInstanceLocation // インスタンスの開始位置
)
これらの命令は Instance の描画回数を指定する引数が追加されています。
同時に Instance に対するオフセットも指定できるようになっています。
DrawIndexedInstanced() でこのオフセットである StartInstanceLocation を
使うとなぜかきちんと描画してくれません。
描画されなかったりポリゴンが飛んだり。
DrawInstanced() ではうまく行っており、こちらは意図通りに動作しています。
指定した値の分だけ Instance バッファをずらしてそこから描画してくれます。
実行は GeForce8800GTS ForceWare 158.24 ですが、Reference Driver でも
RADEON HD 2900XT でも全く同じ描画になるので、使い方を勘違いしている
だけかもしれません。もうしばらく検証してみます。
ちなみに Indexed 系の命令は BaseVertexLocation だけ INT 型で、負の値を
受け付けるようです。
考えられる用途としては、例えばもともと1つの巨大な頂点バッファを複数の
バッファに分割したとき、Index バッファの値を書き換えなくても描画可能な
ことでしょうか。
後ろにずらすだけなら IASetVertexBuffers() でもできますが、前方にずらす
ことは今まで確かにこれまでできなかったかもしれません。
2007/07/20
Direct3D10 blog について
DirectX10/Direct3D10 というと、やっぱり性能があがって機能的にすごい!
そんなイメージがあります。GPU メーカーや Microsoft が宣伝しているように
綺麗な見た目のインパクトや、今までと違う画期的なシェーダー、派手な
エフェクトなどのデモを想像するかもしれません。
だけどこの blog では、実際に Direct3D10 を使った描画エンジンを開発しながら
その過程で調べたことやわかったこと、考えたことなどを細々と記録しています。
見た目でわかりやすい部分にはあんまり触れてなくて、非常に地味かつ狭い分野
の話が多いです。
汎用的な描画やデータの流用を考えたエンジンの設計は非常に地味で地道で、
どのようにシェーダーやリソースを管理して、ツールとどう連携するかといった
部分に結構時間を取られます。
派手なのは地道に積み上げてきた各パーツが一気につながった最後の最後
だと思います。
また Direct3D10 の利点は単に「描画性能がすごい」だけでは決してなく、
設計上の自由度が上がって柔軟な設計を許容し、開発者の負担を減らす効果も
あります。
まだ効果よりは、自由度があがったゆえの設計の苦しみに悩んでいるのが実情
なのですが。
そんなイメージがあります。GPU メーカーや Microsoft が宣伝しているように
綺麗な見た目のインパクトや、今までと違う画期的なシェーダー、派手な
エフェクトなどのデモを想像するかもしれません。
だけどこの blog では、実際に Direct3D10 を使った描画エンジンを開発しながら
その過程で調べたことやわかったこと、考えたことなどを細々と記録しています。
見た目でわかりやすい部分にはあんまり触れてなくて、非常に地味かつ狭い分野
の話が多いです。
汎用的な描画やデータの流用を考えたエンジンの設計は非常に地味で地道で、
どのようにシェーダーやリソースを管理して、ツールとどう連携するかといった
部分に結構時間を取られます。
派手なのは地道に積み上げてきた各パーツが一気につながった最後の最後
だと思います。
また Direct3D10 の利点は単に「描画性能がすごい」だけでは決してなく、
設計上の自由度が上がって柔軟な設計を許容し、開発者の負担を減らす効果も
あります。
まだ効果よりは、自由度があがったゆえの設計の苦しみに悩んでいるのが実情
なのですが。
2007/07/19
D3D10 ConstantBuffer の更新方法
Direct3D10 の Constant Buffer は、レジスタではなくリソースとしての
メモリになりました。作成方法や更新方法など、扱いは VertexBuffer,
IndexBuffer, Texture など他のバッファとほぼ同じです。
ですがシェーダー内部からは従来どおりレジスタとして見えているようで、
何らかの高速にアクセスするための専用のメモリモードやキャッシュを
備えていると考えられます。
Constant Buffer の容量は最大 4096 vecotr で、他の用途に Bind された
バッファと違い上限があります。これもハードウエアデザイン上、キャッシュ
等の特殊な高速アクセスを実現するために必要な制限だったのかもしれません。
余談ですがなぜ 4096 が上限なのか理由を考えてみました。
・エンコードされた内部 OP コードで、アドレスフィールドが 12bit だから
・128byte (32bit×4) × 4096 = 64KByte だから
・Shader で一度にアクセス可能な ConstantBuffer 数は 16個。つまり
Constant アクセスの命令フィールドは 16bit で、上位4bit がバッファ
選択に使われている。
どれも想像で根拠はありません。そうえば DirectX3 の Direct3D では
作成可能な ExecuteBuffer のサイズが 64KByte まででした。
(ビデオカードやドライバによる変動はあるかもしれない)
ExecuteBuffer というのは今の PushBuffer のようなもので、当時は
これに直接値を書き込んでコマンドを組み立てていました。
また VertexBuffer ができて、ハードウエア Transform が可能になったあたり
でも 64KByte の壁があったように覚えています。(DirectX7 頃)
その後大きなバッファが作れるようになった DirectX8 でも 32bit IndexBuffer
しか対応していなければ 64K 頂点の上限がありました。
(どれも GPU と driver 依存の可能性があります)
また余談ですが DirectX9 の ShaderModel3.0 で、PixelShader の
Constant Register 数が 224 なのは 32個分他のレジスタにマッピングされて
いるからと考えられます。
i0~i15 と b0~b15 合わせて 32個か、または r0~r31 の分かもしれません。
冒頭で述べたように、Direct3D10 の ConstantBuffer は Shader からはレジスタ
として見えていても CPU からはあくまでメモリです。書き換えるためには他の
バッファと同じように次の2種類の方法を用いる必要があります。
ID3D10Buffer::Map()/Unmap()
ID3D10Device::UpdateSubresource()
Map()/Unmap() は D3D9 のリソースに対する Lock() と同じで、メモリ上の
イメージとして直接アクセスすることができます。
リソースのロックは GPU/CPU の同期を阻害してしまう可能性があります。
D3D10 では更新をスムーズに行うために、USAGE や GPU ACCESS Flag、
CPU ACCESS Flag 等で細かな動作指定が可能です。
ただし ConstantBuffer の場合は基本的にランダムにアクセスされるので、
ストリーム系のバッファと違って
USAGE_DYNAMIC + CPU_ACCESS_WRITE + MAP_WRITE_NO_OVERWRITE
の組み合わせは使えません。USAGE_DYNAMIC + CPU_ACCESS_WRITE で Map() を
使う場合は必ず全領域書き換えの MAP_WRITE_DISCARD が必要となります。
UpdateSubresource() の方はメモリイメージの直接更新ではなく、PushBuffer
を使ったコマンドの発行に相当します。そのため従来のようなレジスタの更新
は、UpdateSubresource() を使ったほうが動作上近いかもしれません。
ConstantBuffer を書き換える手段をまとめると次の二通りです。
(1) Map()/Unmap() を使う方法
・D3D10_USAGE_DYNAMIC
・D3D10_CPU_ACCESS_WRITE
・D3D10_MAP_WRITE_DISCARD (全領域更新)
・ID3D10Buffer::Map()/Unmap()
(2) UpdateSubresource() を使う方法
・D3D10_USAGE_DEFAULT
・CPU Access Flag は 0
・ID3D10Device::UpdateSubresourece()
この辺の動作も、やっぱり ID3D10Effect を使ってシェーダーを使っている分には
全く意識する必要がありません。Constant Buffer の更新は ID3D10Effect 内部で
勝手にやってくれます。
そのせいか、Constant Buffer に絞ったリソースアクセスについては、あまり
ドキュメント化されていないようです。
もっともこの辺の処理が必要になるのは、ID3D10Effect を使わずに自前で
シェーダー用リソースの管理をする場合のみでしょう。
サンプルを流用したデモやシェーダーによる可能性の研究や実験なら使う必要は
無いと思います。
メモリになりました。作成方法や更新方法など、扱いは VertexBuffer,
IndexBuffer, Texture など他のバッファとほぼ同じです。
ですがシェーダー内部からは従来どおりレジスタとして見えているようで、
何らかの高速にアクセスするための専用のメモリモードやキャッシュを
備えていると考えられます。
Constant Buffer の容量は最大 4096 vecotr で、他の用途に Bind された
バッファと違い上限があります。これもハードウエアデザイン上、キャッシュ
等の特殊な高速アクセスを実現するために必要な制限だったのかもしれません。
余談ですがなぜ 4096 が上限なのか理由を考えてみました。
・エンコードされた内部 OP コードで、アドレスフィールドが 12bit だから
・128byte (32bit×4) × 4096 = 64KByte だから
・Shader で一度にアクセス可能な ConstantBuffer 数は 16個。つまり
Constant アクセスの命令フィールドは 16bit で、上位4bit がバッファ
選択に使われている。
どれも想像で根拠はありません。そうえば DirectX3 の Direct3D では
作成可能な ExecuteBuffer のサイズが 64KByte まででした。
(ビデオカードやドライバによる変動はあるかもしれない)
ExecuteBuffer というのは今の PushBuffer のようなもので、当時は
これに直接値を書き込んでコマンドを組み立てていました。
また VertexBuffer ができて、ハードウエア Transform が可能になったあたり
でも 64KByte の壁があったように覚えています。(DirectX7 頃)
その後大きなバッファが作れるようになった DirectX8 でも 32bit IndexBuffer
しか対応していなければ 64K 頂点の上限がありました。
(どれも GPU と driver 依存の可能性があります)
また余談ですが DirectX9 の ShaderModel3.0 で、PixelShader の
Constant Register 数が 224 なのは 32個分他のレジスタにマッピングされて
いるからと考えられます。
i0~i15 と b0~b15 合わせて 32個か、または r0~r31 の分かもしれません。
冒頭で述べたように、Direct3D10 の ConstantBuffer は Shader からはレジスタ
として見えていても CPU からはあくまでメモリです。書き換えるためには他の
バッファと同じように次の2種類の方法を用いる必要があります。
ID3D10Buffer::Map()/Unmap()
ID3D10Device::UpdateSubresource()
Map()/Unmap() は D3D9 のリソースに対する Lock() と同じで、メモリ上の
イメージとして直接アクセスすることができます。
リソースのロックは GPU/CPU の同期を阻害してしまう可能性があります。
D3D10 では更新をスムーズに行うために、USAGE や GPU ACCESS Flag、
CPU ACCESS Flag 等で細かな動作指定が可能です。
ただし ConstantBuffer の場合は基本的にランダムにアクセスされるので、
ストリーム系のバッファと違って
USAGE_DYNAMIC + CPU_ACCESS_WRITE + MAP_WRITE_NO_OVERWRITE
の組み合わせは使えません。USAGE_DYNAMIC + CPU_ACCESS_WRITE で Map() を
使う場合は必ず全領域書き換えの MAP_WRITE_DISCARD が必要となります。
UpdateSubresource() の方はメモリイメージの直接更新ではなく、PushBuffer
を使ったコマンドの発行に相当します。そのため従来のようなレジスタの更新
は、UpdateSubresource() を使ったほうが動作上近いかもしれません。
ConstantBuffer を書き換える手段をまとめると次の二通りです。
(1) Map()/Unmap() を使う方法
・D3D10_USAGE_DYNAMIC
・D3D10_CPU_ACCESS_WRITE
・D3D10_MAP_WRITE_DISCARD (全領域更新)
・ID3D10Buffer::Map()/Unmap()
(2) UpdateSubresource() を使う方法
・D3D10_USAGE_DEFAULT
・CPU Access Flag は 0
・ID3D10Device::UpdateSubresourece()
この辺の動作も、やっぱり ID3D10Effect を使ってシェーダーを使っている分には
全く意識する必要がありません。Constant Buffer の更新は ID3D10Effect 内部で
勝手にやってくれます。
そのせいか、Constant Buffer に絞ったリソースアクセスについては、あまり
ドキュメント化されていないようです。
もっともこの辺の処理が必要になるのは、ID3D10Effect を使わずに自前で
シェーダー用リソースの管理をする場合のみでしょう。
サンプルを流用したデモやシェーダーによる可能性の研究や実験なら使う必要は
無いと思います。
2007/07/18
D3D10 row_major column_major
HLSL では matrix のフォーマットとして、宣言時に row_major と column_major
を選ぶことができます。例えば float4x4 (matrix) と float4 (vector) の乗算
ではどちらで宣言しても
・水平演算 dp4 命令を使うか
・並列に積和展開するか
の違いになります。命令ステップ数どちらもは 4 で動作上は影響が出ません。
以前こちらのメモに書いた 2種類のコード展開がちょうどこの
row_major と columns_major の違いに相当します。
・並列積和か水平か
(row_major, columns_major の違いだけでなく乗算順の設計にも依存する)
なので、エンジン側の設計に合わせて row_major にするか columns_major に
するか選ぶことができます。混在も可能でその辺は HLSL コンパイラがうまい
具合に合わせてくれます。
ただし入力が float3 になると少々話が違ってきます。
例えば VertexShader の入力頂点 float3 を、LocalToWorld float4x4 に
乗算する場合を考えます。
float4 wpos= mul( LocalToWorld, float4( Input.Position.xyz, 1 ) );
Output.Pos= mul( WorldToProjection, wpos );
このとき入力頂点の w に 1を代入し、matrix の translation 成分が
そのまま加算されるようにします。
このコードは、積和に水平展開されるとちょうど最後の積和が
単純な加算に置き換わるだけなのでコードのステップ数に影響が出ません。
vs_4_0
dcl_input v0.xyz
dcl_input v1.xyz
dcl_input v2.xy
dcl_output_siv o0.xyzw , position
dcl_output o1.xyz
dcl_output o2.xy
dcl_constantbuffer cb0[21], immediateIndexed
dcl_constantbuffer cb1[4], immediateIndexed
dcl_temps 2
mul r0.xyzw, v0.yyyy, cb1[1].xyzw
mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw
mad r0.xyzw, cb1[2].xyzw, v0.zzzz, r0.xyzw
add r0.xyzw, r0.xyzw, cb1[3].xyzw // w=1 のときの加算
mul r1.xyzw, r0.yyyy, cb0[5].xyzw
mad r1.xyzw, cb0[4].xyzw, r0.xxxx, r1.xyzw
mad r1.xyzw, cb0[6].xyzw, r0.zzzz, r1.xyzw
mad o0.xyzw, cb0[7].xyzw, r0.wwww, r1.xyzw // ここは積和のまま
mov o1.xyz, v1.xyzx
mov o2.xy, v2.xyxx
ret
// Approximately 11 instruction slots used
ところが水平加算の dp4 に展開されると
vs_4_0
dcl_input v0.xyz
dcl_input v1.xyz
dcl_input v2.xy
dcl_output_siv o0.xyzw , position
dcl_output o1.xyz
dcl_output o2.xy
dcl_constantbuffer cb0[21], immediateIndexed
dcl_constantbuffer cb1[4], immediateIndexed
dcl_temps 2
mov r0.xyz, v0.xyzx // w を書き換えるためにテンポラリにコピー
mov r0.w, l(1.000000) // w=1 に代入
dp4 r1.x, cb1[0].xyzw, r0.xyzw
dp4 r1.y, cb1[1].xyzw, r0.xyzw
dp4 r1.z, cb1[2].xyzw, r0.xyzw
dp4 r1.w, cb1[3].xyzw, r0.xyzw
dp4 o0.x, cb0[4].xyzw, r1.xyzw
dp4 o0.y, cb0[5].xyzw, r1.xyzw
dp4 o0.z, cb0[6].xyzw, r1.xyzw
dp4 o0.w, cb0[7].xyzw, r1.xyzw
mov o1.xyz, v1.xyzx
mov o2.xy, v2.xyxx
ret
// Approximately 13 instruction slots used
w=1 の代入のために 2命令増えてしまいます。
消費するテンポラリ変数の個数は変わらないので、スレッド数の差は出ていない
と思われます。
ShaderModel1 の nVIDIA 拡張では、dp4 の時に vector 側の最後の成分を
強制的に 1 とみなす dph 命令がありました。もし dph 命令があったら
w=1 代入は不要なので、命令数に差は出ません。
命令の依存関係で見た場合は、mad よりも dp4 の方が直前の命令との相関性が
薄いので並列化では有利に見えます。もし依存関係によるパイプラインストール
が存在しているのなら dp4 の方が良いのかもしれません。
(とはいってもこの例ではバイパスが利くケースなのでストールは限られると
思います)
スレッドによる並列化で、そもそもパイプラインストールが存在しないなら
純粋に命令数が少ない mad の方がきっと速いのでしょう。
また実際はドライバによってさらに GPU 毎にネイティブな命令にコードに
置き換わっているので、このような単純な比較はできないのかもしれません。
を選ぶことができます。例えば float4x4 (matrix) と float4 (vector) の乗算
ではどちらで宣言しても
・水平演算 dp4 命令を使うか
・並列に積和展開するか
の違いになります。命令ステップ数どちらもは 4 で動作上は影響が出ません。
以前こちらのメモに書いた 2種類のコード展開がちょうどこの
row_major と columns_major の違いに相当します。
・並列積和か水平か
(row_major, columns_major の違いだけでなく乗算順の設計にも依存する)
なので、エンジン側の設計に合わせて row_major にするか columns_major に
するか選ぶことができます。混在も可能でその辺は HLSL コンパイラがうまい
具合に合わせてくれます。
ただし入力が float3 になると少々話が違ってきます。
例えば VertexShader の入力頂点 float3 を、LocalToWorld float4x4 に
乗算する場合を考えます。
float4 wpos= mul( LocalToWorld, float4( Input.Position.xyz, 1 ) );
Output.Pos= mul( WorldToProjection, wpos );
このとき入力頂点の w に 1を代入し、matrix の translation 成分が
そのまま加算されるようにします。
このコードは、積和に水平展開されるとちょうど最後の積和が
単純な加算に置き換わるだけなのでコードのステップ数に影響が出ません。
vs_4_0
dcl_input v0.xyz
dcl_input v1.xyz
dcl_input v2.xy
dcl_output_siv o0.xyzw , position
dcl_output o1.xyz
dcl_output o2.xy
dcl_constantbuffer cb0[21], immediateIndexed
dcl_constantbuffer cb1[4], immediateIndexed
dcl_temps 2
mul r0.xyzw, v0.yyyy, cb1[1].xyzw
mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw
mad r0.xyzw, cb1[2].xyzw, v0.zzzz, r0.xyzw
add r0.xyzw, r0.xyzw, cb1[3].xyzw // w=1 のときの加算
mul r1.xyzw, r0.yyyy, cb0[5].xyzw
mad r1.xyzw, cb0[4].xyzw, r0.xxxx, r1.xyzw
mad r1.xyzw, cb0[6].xyzw, r0.zzzz, r1.xyzw
mad o0.xyzw, cb0[7].xyzw, r0.wwww, r1.xyzw // ここは積和のまま
mov o1.xyz, v1.xyzx
mov o2.xy, v2.xyxx
ret
// Approximately 11 instruction slots used
ところが水平加算の dp4 に展開されると
vs_4_0
dcl_input v0.xyz
dcl_input v1.xyz
dcl_input v2.xy
dcl_output_siv o0.xyzw , position
dcl_output o1.xyz
dcl_output o2.xy
dcl_constantbuffer cb0[21], immediateIndexed
dcl_constantbuffer cb1[4], immediateIndexed
dcl_temps 2
mov r0.xyz, v0.xyzx // w を書き換えるためにテンポラリにコピー
mov r0.w, l(1.000000) // w=1 に代入
dp4 r1.x, cb1[0].xyzw, r0.xyzw
dp4 r1.y, cb1[1].xyzw, r0.xyzw
dp4 r1.z, cb1[2].xyzw, r0.xyzw
dp4 r1.w, cb1[3].xyzw, r0.xyzw
dp4 o0.x, cb0[4].xyzw, r1.xyzw
dp4 o0.y, cb0[5].xyzw, r1.xyzw
dp4 o0.z, cb0[6].xyzw, r1.xyzw
dp4 o0.w, cb0[7].xyzw, r1.xyzw
mov o1.xyz, v1.xyzx
mov o2.xy, v2.xyxx
ret
// Approximately 13 instruction slots used
w=1 の代入のために 2命令増えてしまいます。
消費するテンポラリ変数の個数は変わらないので、スレッド数の差は出ていない
と思われます。
ShaderModel1 の nVIDIA 拡張では、dp4 の時に vector 側の最後の成分を
強制的に 1 とみなす dph 命令がありました。もし dph 命令があったら
w=1 代入は不要なので、命令数に差は出ません。
命令の依存関係で見た場合は、mad よりも dp4 の方が直前の命令との相関性が
薄いので並列化では有利に見えます。もし依存関係によるパイプラインストール
が存在しているのなら dp4 の方が良いのかもしれません。
(とはいってもこの例ではバイパスが利くケースなのでストールは限られると
思います)
スレッドによる並列化で、そもそもパイプラインストールが存在しないなら
純粋に命令数が少ない mad の方がきっと速いのでしょう。
また実際はドライバによってさらに GPU 毎にネイティブな命令にコードに
置き換わっているので、このような単純な比較はできないのかもしれません。
2007/07/18
CEDEC 2007
2007/07/16
DirectX10 は敷居が高い?
あえて DirectX10 と書いてます。やはり始めるまでいろいろ障害があって、
今までよりも手を出しにくく敷居が高いように感じます。
その理由を考えてみました。
・WindowsVista が必要
・DirectX10 対応ビデオカードが必要
DirectX10 が動作する条件としてこの2つがあります。
そろえるだけでも結構な出費です。
PC まるごと置き換える予定がなければ、両方そろっての入れ替えは大事です。
必要なソフトの対応状況とか考えると、OS もビデオカードも入れ替えには
それぞれリスクがあります。
○今まではどうだったのか
古いバージョンの OS に対して DirectX のサポートが打ち切られることは
ありました。でも新しい DirectX の動作のために OS の入れ替えが必要になる
ことはまれでした。比較的早く動作制限が付いたのは NT4 くらいでしょうか。
OS と DirectX の更新は非同期で、それぞれお互いにリリースや対応状況に
影響しあうことがあまりなかったからです。
両方一度に入れ替えを必要とするのはたぶん今回が初めてでしょう。
それだけ Vista と DirectX は密接だということなのでしょう。
○GPU の場合
DirectX9 でも DirectX8 世代に登場した ShaderModel1.0 は使えますし、
DirectX8 でも DirectX7 世代の固定機能パイプラインは健在です。
新しい DirectX が登場しても、最新の目玉機能が使えないだけで
HAL 自体は1つ古い世代のビデオカードでも動きました。
また DirectX の機能自体も、すべてが必須の項目ではありません。
DirectX7 世代の機能しか持たないビデオカードが、DirectX8 対応と
書かれていることはよくあることです。実際 DirectX8 自体は動きます。
Shader は使えないけれど。
そのため先に DirectX SDK だけ入れ替えて、ライブラリなどを一通り
コンパイルが通るようにしておいて、あとからビデオカードを入手したら
新機能を試す、なんてこともできました。
ところが DirectX10 の場合、DirectX9 世代のビデオカードではそもそも
起動できず Reference Rasterizer しか使えません。
すべての機能への対応を要求するので、本当の DirectX10 GPU しか使えない
わけです。
直前に買ったばかりの DirectX9 ハイエンドカードが泣いています。
VRAM が 512MByte もあるのに Direct3D10 ではほとんど画面が止まったかと
思うようなソフトエミュレーションになってしまうのです。
これはこれで潔いし、そのうち時間が解決するだろうけれど、
古い環境からの置き換わりには少々時間がかかりそうな感じです。
今までよりも手を出しにくく敷居が高いように感じます。
その理由を考えてみました。
・WindowsVista が必要
・DirectX10 対応ビデオカードが必要
DirectX10 が動作する条件としてこの2つがあります。
そろえるだけでも結構な出費です。
PC まるごと置き換える予定がなければ、両方そろっての入れ替えは大事です。
必要なソフトの対応状況とか考えると、OS もビデオカードも入れ替えには
それぞれリスクがあります。
○今まではどうだったのか
古いバージョンの OS に対して DirectX のサポートが打ち切られることは
ありました。でも新しい DirectX の動作のために OS の入れ替えが必要になる
ことはまれでした。比較的早く動作制限が付いたのは NT4 くらいでしょうか。
OS と DirectX の更新は非同期で、それぞれお互いにリリースや対応状況に
影響しあうことがあまりなかったからです。
両方一度に入れ替えを必要とするのはたぶん今回が初めてでしょう。
それだけ Vista と DirectX は密接だということなのでしょう。
○GPU の場合
DirectX9 でも DirectX8 世代に登場した ShaderModel1.0 は使えますし、
DirectX8 でも DirectX7 世代の固定機能パイプラインは健在です。
新しい DirectX が登場しても、最新の目玉機能が使えないだけで
HAL 自体は1つ古い世代のビデオカードでも動きました。
また DirectX の機能自体も、すべてが必須の項目ではありません。
DirectX7 世代の機能しか持たないビデオカードが、DirectX8 対応と
書かれていることはよくあることです。実際 DirectX8 自体は動きます。
Shader は使えないけれど。
そのため先に DirectX SDK だけ入れ替えて、ライブラリなどを一通り
コンパイルが通るようにしておいて、あとからビデオカードを入手したら
新機能を試す、なんてこともできました。
ところが DirectX10 の場合、DirectX9 世代のビデオカードではそもそも
起動できず Reference Rasterizer しか使えません。
すべての機能への対応を要求するので、本当の DirectX10 GPU しか使えない
わけです。
直前に買ったばかりの DirectX9 ハイエンドカードが泣いています。
VRAM が 512MByte もあるのに Direct3D10 ではほとんど画面が止まったかと
思うようなソフトエミュレーションになってしまうのです。
これはこれで潔いし、そのうち時間が解決するだろうけれど、
古い環境からの置き換わりには少々時間がかかりそうな感じです。
2007/07/15
D3D10 Geometry Instancing の問題と
「Direct3D10 の Geometry Instancing の問題と機能の解説」
Direct3D9 の ShaderModel3.0 から Geometry Instancing に対応し、
Direct3D10 では必須機能となりました。Geometry Instancing がなぜ重要
なのか、また D3D9 での機能については前回こちらで触れました。
・Direct3D Geometry Instancing
今回は D3D10 での Geometry Instancing についてわかったことなどを
書いてみます。
D3D9 までの DrawPrimitive 命令は無くなり、D3D10 ではただの Draw 命令
となりました。これでいちいちプリミティブ数を計算して求める必要が無くなり
ました。頂点数やインデックス数だけで描画命令を発行できます。
●頂点指定の描画命令
Draw()
DrawInstanced()
●インデックス指定の描画命令
DrawIndexed()
DrawIndexedInstanced()
~Instanced() 命令は、それぞれの描画命令に繰り返し機能が付いたバージョンです。
繰り返す回数を指定することで、InputLayout の D3D10_INPUT_PER_VERTEX_DATA
指定した頂点バッファが、繰り返し数分だけ何度も読み込まれます。
例えば 24頂点の Cube の描画は
iDevice->Draw( 24, 0 );
ですが、これを繰り返し回数 (InstanceCount) 8 回 で描画すると
iDevice->DrawInstanced( 24, 8, 0, 0 );
となります。このとき同じ場所に同じモデルを 8回上書きするだけなので、
描画負荷が上がる以外に特に意味はありません。
この 8回の描画位置を何らかの方法で指定してあげる必要があります。
さまざまな方法が考えられますが、例えば Shader 内でも System Value
Semantic の SV_InstanceID を使って何個目の描画なのか取得することが
できます。Constant Buffer で Geometry を渡して Index に使えば
任意の位置に描画することが可能です。
Constant Buffer は容量的な上限があります。またアドレッシングしなくても
効率よくデータを読み込めるように、専用 Stream を使うことができます。
この Stream には区別のために D3D10_INPUT_PER_INSTANCE_DATA を指定します。
D3D10_INPUT_PER_INSTANCE_DATA 指定された Stream は、読み進めるための
ポインタを Instance 毎にしか更新しません。同一 Instance の頂点には
すべて同じ値が渡されるわけです。
これがまさに Direct3D9 の Geometry Instancing と同じもので、本来は
この使い方を想定して API の設計が行われているようです。
対して最初の Stream を使わない Direct3D10 独自の手法を
Stream Less Instancing と名付けておきます。
Per Instance Stream も単なる Buffer であって基本的には頂点と変わりません。
Draw() に DrawIndexed() があるように、Per Instance Stream でももっと
凝ったことをやろうとすると Index が欲しくなるでしょう。
Index とまでは行きませんが、D3D10 では通常の stream だけでも多少凝った
指定ができるようになっています。D3D10_INPUT_PER_INSTANCE_DATA にも同じ
ストリームを多重読み込みできるように、個別に読み込み回数を指定可能です。
ローカルなループを構成できるわけです。
このローカルな回数は InputLayout を作るときに設定します。
InstanceDataStepRate です。
この機能を使えば、繰り返し回数 (InstanceCount) = 8回のデータでも、
2個の Per Instance Data をそれぞれ 4回ずつ描画する
(InstanceDataStepRate = 4)、といった柔軟な設定が可能になります。
このとき D3D10_INPUT_PER_INSTANCE_DATA の Stream は 2個のジオメトリ
分の大きさですが、DrawInstanced()/DrawIndexedInstanced() には
InstanceCount = 8 を設定します。
ここで今の DirectX Runtime (June2007) には若干問題があって、
Debug Build だと Instance 用の VertexBuffer が描画回数に比べて
少なすぎるという WARNING が毎フレーム発生してしまいます。
[EXECUTION WARNING #356: DEVICE_DRAW_VERTEX_BUFFER_TOO_SMALL]
どうやら判定時に InstanceDataStepRate を見ていないようです。
当初自分のアプリケーションの問題かと思っていたのですが、
Sample の Instancing10 でも発生していました。
とりあえず、Direct3D10 の Geometry Instancing は、無理やり拡張したような
D3D9 と比べてかなりシェーダーで自由に扱うことができます。
情報が少ないのがネックですがかなり思い通りに使うことができるようです。
InstanceDataStepRate はいわゆる通常の Geometry Instancing に
さらに Stream Less Instancing を混在利用しているわけですし、
Texture/Buffer 読み込みを使えば Instance Data の Index 化もおそらく
可能でしょう。
Direct3D9 の ShaderModel3.0 から Geometry Instancing に対応し、
Direct3D10 では必須機能となりました。Geometry Instancing がなぜ重要
なのか、また D3D9 での機能については前回こちらで触れました。
・Direct3D Geometry Instancing
今回は D3D10 での Geometry Instancing についてわかったことなどを
書いてみます。
D3D9 までの DrawPrimitive 命令は無くなり、D3D10 ではただの Draw 命令
となりました。これでいちいちプリミティブ数を計算して求める必要が無くなり
ました。頂点数やインデックス数だけで描画命令を発行できます。
●頂点指定の描画命令
Draw()
DrawInstanced()
●インデックス指定の描画命令
DrawIndexed()
DrawIndexedInstanced()
~Instanced() 命令は、それぞれの描画命令に繰り返し機能が付いたバージョンです。
繰り返す回数を指定することで、InputLayout の D3D10_INPUT_PER_VERTEX_DATA
指定した頂点バッファが、繰り返し数分だけ何度も読み込まれます。
例えば 24頂点の Cube の描画は
iDevice->Draw( 24, 0 );
ですが、これを繰り返し回数 (InstanceCount) 8 回 で描画すると
iDevice->DrawInstanced( 24, 8, 0, 0 );
となります。このとき同じ場所に同じモデルを 8回上書きするだけなので、
描画負荷が上がる以外に特に意味はありません。
この 8回の描画位置を何らかの方法で指定してあげる必要があります。
さまざまな方法が考えられますが、例えば Shader 内でも System Value
Semantic の SV_InstanceID を使って何個目の描画なのか取得することが
できます。Constant Buffer で Geometry を渡して Index に使えば
任意の位置に描画することが可能です。
Constant Buffer は容量的な上限があります。またアドレッシングしなくても
効率よくデータを読み込めるように、専用 Stream を使うことができます。
この Stream には区別のために D3D10_INPUT_PER_INSTANCE_DATA を指定します。
D3D10_INPUT_PER_INSTANCE_DATA 指定された Stream は、読み進めるための
ポインタを Instance 毎にしか更新しません。同一 Instance の頂点には
すべて同じ値が渡されるわけです。
これがまさに Direct3D9 の Geometry Instancing と同じもので、本来は
この使い方を想定して API の設計が行われているようです。
対して最初の Stream を使わない Direct3D10 独自の手法を
Stream Less Instancing と名付けておきます。
Per Instance Stream も単なる Buffer であって基本的には頂点と変わりません。
Draw() に DrawIndexed() があるように、Per Instance Stream でももっと
凝ったことをやろうとすると Index が欲しくなるでしょう。
Index とまでは行きませんが、D3D10 では通常の stream だけでも多少凝った
指定ができるようになっています。D3D10_INPUT_PER_INSTANCE_DATA にも同じ
ストリームを多重読み込みできるように、個別に読み込み回数を指定可能です。
ローカルなループを構成できるわけです。
このローカルな回数は InputLayout を作るときに設定します。
InstanceDataStepRate です。
この機能を使えば、繰り返し回数 (InstanceCount) = 8回のデータでも、
2個の Per Instance Data をそれぞれ 4回ずつ描画する
(InstanceDataStepRate = 4)、といった柔軟な設定が可能になります。
このとき D3D10_INPUT_PER_INSTANCE_DATA の Stream は 2個のジオメトリ
分の大きさですが、DrawInstanced()/DrawIndexedInstanced() には
InstanceCount = 8 を設定します。
ここで今の DirectX Runtime (June2007) には若干問題があって、
Debug Build だと Instance 用の VertexBuffer が描画回数に比べて
少なすぎるという WARNING が毎フレーム発生してしまいます。
[EXECUTION WARNING #356: DEVICE_DRAW_VERTEX_BUFFER_TOO_SMALL]
どうやら判定時に InstanceDataStepRate を見ていないようです。
当初自分のアプリケーションの問題かと思っていたのですが、
Sample の Instancing10 でも発生していました。
とりあえず、Direct3D10 の Geometry Instancing は、無理やり拡張したような
D3D9 と比べてかなりシェーダーで自由に扱うことができます。
情報が少ないのがネックですがかなり思い通りに使うことができるようです。
InstanceDataStepRate はいわゆる通常の Geometry Instancing に
さらに Stream Less Instancing を混在利用しているわけですし、
Texture/Buffer 読み込みを使えば Instance Data の Index 化もおそらく
可能でしょう。
2007/07/14
D3D10 GetResourceBindingDesc
相変わらず Effect 管理と Reflection まわりを触っています。
ID3D10Effect から ID3D10ShaderReflection を取り出すには、
・D3D10/DX10 Effect Interface内情報
に書いた手順の通りです。
取り出した Reflection を使って、描画のために Shader の Setup を
行うためには BindingDesc を調べる必要があります。
ID3D10ShaderReflection* iReflection; // input
D3D10_SHADER_DESC shdesc;
iReflection->GetDesc( &shdesc );
for( DWORD i= 0 ; i< shdesc.BoundResources ; i++ ){
D3D10_SHADER_INPUT_BIND_DESC binddesc;
iReflection->GetResourceBindingDesc( i, &binddesc );
// :
}
これで binddesc.Name に対応する HLSL の変数名が、
bdesc.Type に Resource の種類が、
bdesc.BindPoint に Resource を割り付けるスロット番号が入ります。
bdesc.Type は D3D10_SHADER_INPUT_TYPE で定義されています。
--------------------------------------------------------
enum D3D10_SHADER_INPUT_TYPE
{
D3D10_SIT_CBUFFER,
D3D10_SIT_TBUFFER,
D3D10_SIT_TEXTURE,
D3D10_SIT_SAMPLER,
};
--------------------------------------------------------
上記の通り 4種類に分類されていることがわかります。
CBUFFER, SAMPLER は特に問題ありません。それぞれ
GSSetConstantBuffers(), GSSetSamplers() (VS~, PS~) 等の
API でシェーダーに割り当てるので、独自に BindPoint (Slot番号) が
割り当てられます。
TBUFFER と TEXTURE はどちらも Texture なので、シェーダーに
割り当てるための API は GSSetShaderResources() (VS~, PS~)
しかありません。
というのは、どちらも ID3D10ShaderResourceView で扱いは一緒なのです。
そのため Type は別でも BindPoint は両方合わせてカウントされます。
では TBUFFER と TEXTURE の違いは何かというと、
D3D10_SHADER_INPUT_BIND_DESC の ReturnType や Dimension が違います。
結局は Buffer と Texture の違いであって、Sampler を使えるかどうかの
違いといえるかもしれません。
なお ID3D10Effect 等は全部やってくれていますが、BindPoint は
GS, PS, VS それぞれ参照状況に応じて異なっている可能性があり、
さらに Effect 内に複数の Pass や Technique が存在する場合はその数だけ
区別しておく必要があります。
セットアップも必要に応じて GS, PS, VS 全部に対して実行しなければならず、
自前でやろうとすると思ったよりも手間がかかります。
やっぱり完全に ID3D10Effect 任せにした方が簡単ではあります。
ID3D10Effect から ID3D10ShaderReflection を取り出すには、
・D3D10/DX10 Effect Interface内情報
に書いた手順の通りです。
取り出した Reflection を使って、描画のために Shader の Setup を
行うためには BindingDesc を調べる必要があります。
ID3D10ShaderReflection* iReflection; // input
D3D10_SHADER_DESC shdesc;
iReflection->GetDesc( &shdesc );
for( DWORD i= 0 ; i< shdesc.BoundResources ; i++ ){
D3D10_SHADER_INPUT_BIND_DESC binddesc;
iReflection->GetResourceBindingDesc( i, &binddesc );
// :
}
これで binddesc.Name に対応する HLSL の変数名が、
bdesc.Type に Resource の種類が、
bdesc.BindPoint に Resource を割り付けるスロット番号が入ります。
bdesc.Type は D3D10_SHADER_INPUT_TYPE で定義されています。
--------------------------------------------------------
enum D3D10_SHADER_INPUT_TYPE
{
D3D10_SIT_CBUFFER,
D3D10_SIT_TBUFFER,
D3D10_SIT_TEXTURE,
D3D10_SIT_SAMPLER,
};
--------------------------------------------------------
上記の通り 4種類に分類されていることがわかります。
CBUFFER, SAMPLER は特に問題ありません。それぞれ
GSSetConstantBuffers(), GSSetSamplers() (VS~, PS~) 等の
API でシェーダーに割り当てるので、独自に BindPoint (Slot番号) が
割り当てられます。
TBUFFER と TEXTURE はどちらも Texture なので、シェーダーに
割り当てるための API は GSSetShaderResources() (VS~, PS~)
しかありません。
というのは、どちらも ID3D10ShaderResourceView で扱いは一緒なのです。
そのため Type は別でも BindPoint は両方合わせてカウントされます。
では TBUFFER と TEXTURE の違いは何かというと、
D3D10_SHADER_INPUT_BIND_DESC の ReturnType や Dimension が違います。
結局は Buffer と Texture の違いであって、Sampler を使えるかどうかの
違いといえるかもしれません。
なお ID3D10Effect 等は全部やってくれていますが、BindPoint は
GS, PS, VS それぞれ参照状況に応じて異なっている可能性があり、
さらに Effect 内に複数の Pass や Technique が存在する場合はその数だけ
区別しておく必要があります。
セットアップも必要に応じて GS, PS, VS 全部に対して実行しなければならず、
自前でやろうとすると思ったよりも手間がかかります。
やっぱり完全に ID3D10Effect 任せにした方が簡単ではあります。
2007/07/09
Direct3D Geometry Instancing
同一形状のモデルを同時に大量に描画する場合は、DirectX9 の
ShaderModel3.0 から導入された GeometryInstancing を使うことができます。
Geometry Instancing は同一の頂点バッファの内容を繰り返し描画できる
機能のことで、頂点ストリームの読み取り周期をずらしてスケジューリング
することで実現しています。
同じ形状のモデルを何個も描画する場合は、その数だけ毎回 Draw 呼び出し
を行うことになります。
WindowsPC 上の DirectX のボトルネックとして有名なのがこの Draw 呼び
出し回数の問題です。1フレームに呼び出す回数が増えるとそれだけで CPU
の負荷が高くなります。
CPU Emulation Driver (HEL) のように HAL を通さない場合や、Xbox の
ように API レイヤが薄い場合はここまで顕著な傾向は現れません。
このオーバーヘッドを軽減するアイデアとして登場したのが
Geometry Instancing です。複数のオブジェクトの描画を一度の Draw
呼び出しで済ませるための工夫といえるでしょう。
注意点は、Geometry Instancing によって恩恵を得られるのはあくまで
CPU 側だということです。必ず描画が速くなるわけではないし、pixel
負荷などで GPU がボトルネックの場合はほとんど速度が変わらない
可能性があります。
GPU の負担が軽くなる要素としては、パイプラインを止めないで済む
ということ。また Constant Register 等の更新が不要になる分効率良く
なるかもしれません。その代わり VertexShader で特殊なストリーム入力
のための負担が増えるのと、16本しかない入力パラメータを圧迫します。
まず描画回数を減らす方法として考えられるのは、必要なオブジェクト
を全部マージしてしまうことです。同じモデルであればマテリアルも共有
されるのでいっぺんに描画することができるでしょう。
Draw 回数のオーバーヘッドは激減しますが、各オブジェクトを個別に
動かしたり位置や向きを変更することができませんし、同一であるはずの
データを頂点バッファに展開するのでメモリ消費も多くなります。
Geometry Instancing ではこの考えをさらに進めて、描画すべき位置の
情報 (Geometry, Matrixなど) を頂点と同じようにストリームで渡す
ことにしました。
Geometry 情報をあらかじめ頂点バッファに書き込んでおきます。
これでマージされたオブジェクトながら、個々のオブジェクトを異なる
座標に配置できるようになります。
Geometry 情報を頂点単位に持たせるとメモリも馬鹿にならないし、
座標更新のために頂点数分書き換えなければなりません。
そのため次の2つのちょっとした追加機能が必要になりました。
・描画するモデル側は何度も繰り返し頂点バッファを読めるようにする
24頂点の Cube を 10個描画するなら、24頂点の同じバッファを
10回繰り返して読み込めるようにするわけです。
Index Buffer を必要回数コピーするようなもの。
いわゆる Maya とかの Instance。
・Geomtery 情報の頂点バッファは、1モデル単位で読み進める
つまり 24頂点の Cube があれば、24頂点には同じ Geometry
が渡されるようにします。
特殊な頂点バッファの使い方に見えますが、これが最小の追加で高い
効果を得られる方法だったのだと考えられます。
Geometry だけでなく、マテリアルカラーなども頂点ストリームで
入力できるので、個々に色や属性等も変更することが可能です。
これらが全部マージされたのと同じように一度の Draw 呼び出しで
描画できるわけです。
ShaderModel3.0 は ConstantRegister 容量の制約があるので、
異なるスキニングモーションをしているキャラクタを一度の Draw
で描画するのは少々難しいかと思います。
すべて同じポーズなら簡単です。例えば 30 frame 分のモーション
なら、各フレーム毎に描画をわけて、30回の Draw で済ませることは
できるかもしれません。
Geometry Instance 系の簡単なアニメーションデモなどはこの方法で
十分だと思います。
骨の数が少なければ、ストリームに Bone 用 Constant Register の
オフセットも入れておくことで、数個の異なるポーズのモーションを
一度に描画することはできるかもしれません。
Geometry Instance は ShaderModel3.0 から付いた機能ですが一部
例外があります。ATI(AMD) RADEON 9700~X800系の場合は、
ShaderModel2.0 であるにもかかわらずドライバのエミュレーション
で Geometry Instance を使うことができました。
あらかじめ Video Card のコントロールパネルで有効に設定して
おくことができます。
エミュレーションなのでパフォーマンス的には限界があるものの
Geometry Instance のコードがきちんと動作します。
このとき Direct3D9 の設定が Debug になっているとエラーチェックに
引っかかってしまいます。
「ShaderModel2.0 なのに Geometry Instance を使っている」
という旨のエラーが出て止まってしまうわけです。
エラーチェックの無い Retail モードだと大丈夫です。
Direct3D10 では DrawInstanced / DrawIndexedInstanced によって
最初から Geometry Instancing の描画がサポートされています。
ShaderModel3.0 から導入された GeometryInstancing を使うことができます。
Geometry Instancing は同一の頂点バッファの内容を繰り返し描画できる
機能のことで、頂点ストリームの読み取り周期をずらしてスケジューリング
することで実現しています。
同じ形状のモデルを何個も描画する場合は、その数だけ毎回 Draw 呼び出し
を行うことになります。
WindowsPC 上の DirectX のボトルネックとして有名なのがこの Draw 呼び
出し回数の問題です。1フレームに呼び出す回数が増えるとそれだけで CPU
の負荷が高くなります。
CPU Emulation Driver (HEL) のように HAL を通さない場合や、Xbox の
ように API レイヤが薄い場合はここまで顕著な傾向は現れません。
このオーバーヘッドを軽減するアイデアとして登場したのが
Geometry Instancing です。複数のオブジェクトの描画を一度の Draw
呼び出しで済ませるための工夫といえるでしょう。
注意点は、Geometry Instancing によって恩恵を得られるのはあくまで
CPU 側だということです。必ず描画が速くなるわけではないし、pixel
負荷などで GPU がボトルネックの場合はほとんど速度が変わらない
可能性があります。
GPU の負担が軽くなる要素としては、パイプラインを止めないで済む
ということ。また Constant Register 等の更新が不要になる分効率良く
なるかもしれません。その代わり VertexShader で特殊なストリーム入力
のための負担が増えるのと、16本しかない入力パラメータを圧迫します。
まず描画回数を減らす方法として考えられるのは、必要なオブジェクト
を全部マージしてしまうことです。同じモデルであればマテリアルも共有
されるのでいっぺんに描画することができるでしょう。
Draw 回数のオーバーヘッドは激減しますが、各オブジェクトを個別に
動かしたり位置や向きを変更することができませんし、同一であるはずの
データを頂点バッファに展開するのでメモリ消費も多くなります。
Geometry Instancing ではこの考えをさらに進めて、描画すべき位置の
情報 (Geometry, Matrixなど) を頂点と同じようにストリームで渡す
ことにしました。
Geometry 情報をあらかじめ頂点バッファに書き込んでおきます。
これでマージされたオブジェクトながら、個々のオブジェクトを異なる
座標に配置できるようになります。
Geometry 情報を頂点単位に持たせるとメモリも馬鹿にならないし、
座標更新のために頂点数分書き換えなければなりません。
そのため次の2つのちょっとした追加機能が必要になりました。
・描画するモデル側は何度も繰り返し頂点バッファを読めるようにする
24頂点の Cube を 10個描画するなら、24頂点の同じバッファを
10回繰り返して読み込めるようにするわけです。
Index Buffer を必要回数コピーするようなもの。
いわゆる Maya とかの Instance。
・Geomtery 情報の頂点バッファは、1モデル単位で読み進める
つまり 24頂点の Cube があれば、24頂点には同じ Geometry
が渡されるようにします。
特殊な頂点バッファの使い方に見えますが、これが最小の追加で高い
効果を得られる方法だったのだと考えられます。
Geometry だけでなく、マテリアルカラーなども頂点ストリームで
入力できるので、個々に色や属性等も変更することが可能です。
これらが全部マージされたのと同じように一度の Draw 呼び出しで
描画できるわけです。
ShaderModel3.0 は ConstantRegister 容量の制約があるので、
異なるスキニングモーションをしているキャラクタを一度の Draw
で描画するのは少々難しいかと思います。
すべて同じポーズなら簡単です。例えば 30 frame 分のモーション
なら、各フレーム毎に描画をわけて、30回の Draw で済ませることは
できるかもしれません。
Geometry Instance 系の簡単なアニメーションデモなどはこの方法で
十分だと思います。
骨の数が少なければ、ストリームに Bone 用 Constant Register の
オフセットも入れておくことで、数個の異なるポーズのモーションを
一度に描画することはできるかもしれません。
Geometry Instance は ShaderModel3.0 から付いた機能ですが一部
例外があります。ATI(AMD) RADEON 9700~X800系の場合は、
ShaderModel2.0 であるにもかかわらずドライバのエミュレーション
で Geometry Instance を使うことができました。
あらかじめ Video Card のコントロールパネルで有効に設定して
おくことができます。
エミュレーションなのでパフォーマンス的には限界があるものの
Geometry Instance のコードがきちんと動作します。
このとき Direct3D9 の設定が Debug になっているとエラーチェックに
引っかかってしまいます。
「ShaderModel2.0 なのに Geometry Instance を使っている」
という旨のエラーが出て止まってしまうわけです。
エラーチェックの無い Retail モードだと大丈夫です。
Direct3D10 では DrawInstanced / DrawIndexedInstanced によって
最初から Geometry Instancing の描画がサポートされています。
2007/07/07
Direct3D10 shaderとリソースとsampler
シェーダーはテクスチャを読み込むことができます。
1pixel の描画のために 5枚以上のテクスチャを読み込んだり、
16~64回もサンプリングしたり、といった使い方も珍しくなくなりました。
読み込むテクスチャの指定の仕方もシェーダーの進化とともに変化してきました。
Direct3D9 や D3D8 の ShaderModel1.0 は TextureStage が中心です。
これは DirectX6~7 から引き継いだ考え方で、各ステージに対して
Texture と Sampler (RenderState) を設定します。
そのため Texture Slot 番号、Sampler 番号、Texture Stage の各番号は
一致します。
同じテクスチャを何度も回読みたければ、複数の Stage にテクスチャを
設定する必要がありました。また使用できるステージ数も GeForce3/4 では
最大4ステージまででした。(RADEON 8500系は 6)
ShaderModel2.0 になると TextureStage が分離されます。
これによって演算のためにステージを消費し、読み込めるテクスチャ数が
減ることもなくなりました。
また同一テクスチャを何度も texld できるので、登録個数上限が 8個だと
してもテクスチャ読み込みの回数には上限がなくなりました。
(命令スロットの制限はあります)
Direct3D10 ではさらに分離が進んで、Texture と Sampler が完全に独立
しました。
Shader 内で任意の Sampler と ShaderResource を組み合わせることが
可能なのはもちろん、Sampler を使わずに ShaderResource から直接値を
読み込むことまでできます。
API 上一度に登録可能な Sampler の State 数も 16 slot、ShaderResource
(Texture や Buffer) も 128 slot と増加しています。
さらに Shader は統合されています。VertexShader、GeometryShader、
PixelShader どのシェーダーでも同じように Texture の読み込みが可能
なので、API もそれぞれ 3セットあります。
一度の Draw で設定可能な ShaderResource は 128×3 にもなります。
VSSetSamplers()
VSSetShaderReousrces()
GSSetSamplers()
GSSetShaderReousrces()
PSSetSamplers()
PSSetShaderReousrces()
これらの API は ID3D10Effect を使っている分には全く意識することがなく、
内部で面倒なこともやってくれるので非常に簡単です。
自前で Shader 管理を行うならば、同じ Sampler や ShaderResource で
あっても VS, GS, PS それぞれことなる slot で参照している可能性があるので
別々の設定を行わなければなりません。
D3D9 まででも Constant Register のマッピングは VS と PS で個別管理なので
一緒なのですが、、Effect が便利で頼りすぎていたのかもしれません。
といっても、今のところ PixleShader 以外では、Sampler を使ってイメージ
Texture を大量に読み込むような用途はそう頻繁には無いのかもしれません。
1pixel の描画のために 5枚以上のテクスチャを読み込んだり、
16~64回もサンプリングしたり、といった使い方も珍しくなくなりました。
読み込むテクスチャの指定の仕方もシェーダーの進化とともに変化してきました。
Direct3D9 や D3D8 の ShaderModel1.0 は TextureStage が中心です。
これは DirectX6~7 から引き継いだ考え方で、各ステージに対して
Texture と Sampler (RenderState) を設定します。
そのため Texture Slot 番号、Sampler 番号、Texture Stage の各番号は
一致します。
同じテクスチャを何度も回読みたければ、複数の Stage にテクスチャを
設定する必要がありました。また使用できるステージ数も GeForce3/4 では
最大4ステージまででした。(RADEON 8500系は 6)
ShaderModel2.0 になると TextureStage が分離されます。
これによって演算のためにステージを消費し、読み込めるテクスチャ数が
減ることもなくなりました。
また同一テクスチャを何度も texld できるので、登録個数上限が 8個だと
してもテクスチャ読み込みの回数には上限がなくなりました。
(命令スロットの制限はあります)
Direct3D10 ではさらに分離が進んで、Texture と Sampler が完全に独立
しました。
Shader 内で任意の Sampler と ShaderResource を組み合わせることが
可能なのはもちろん、Sampler を使わずに ShaderResource から直接値を
読み込むことまでできます。
API 上一度に登録可能な Sampler の State 数も 16 slot、ShaderResource
(Texture や Buffer) も 128 slot と増加しています。
さらに Shader は統合されています。VertexShader、GeometryShader、
PixelShader どのシェーダーでも同じように Texture の読み込みが可能
なので、API もそれぞれ 3セットあります。
一度の Draw で設定可能な ShaderResource は 128×3 にもなります。
VSSetSamplers()
VSSetShaderReousrces()
GSSetSamplers()
GSSetShaderReousrces()
PSSetSamplers()
PSSetShaderReousrces()
これらの API は ID3D10Effect を使っている分には全く意識することがなく、
内部で面倒なこともやってくれるので非常に簡単です。
自前で Shader 管理を行うならば、同じ Sampler や ShaderResource で
あっても VS, GS, PS それぞれことなる slot で参照している可能性があるので
別々の設定を行わなければなりません。
D3D9 まででも Constant Register のマッピングは VS と PS で個別管理なので
一緒なのですが、、Effect が便利で頼りすぎていたのかもしれません。
といっても、今のところ PixleShader 以外では、Sampler を使ってイメージ
Texture を大量に読み込むような用途はそう頻繁には無いのかもしれません。
2007/07/06
D3D10/DX10 シェーダーと64bit浮動少数
GPU も G9x (GeForce8xの次世代) では float64 にも対応するのでは
ないか、との話です。
・GPUサーバーの時代を開くNVIDIAの「Tesla」
もともと ShaderModel1.0 の PixelShader は 9~12bit 整数演算で、
D3D10 の表記でいえば SNORM に相当します。固定少数値
-1.0~+1.0 にMapされていたわけです。
VertexShader は 32bit float でした。
ShaderModel2.0 になって ATI(AMD) RADEON は 9700 シリーズで 24bit float を
導入し、これが X800 系まで続きます。
ShaderModel2.0 で出遅れた nVIDIA の方は GeForceFX で 32bit float を
先行実現しましたが、実際は 16bit half float とのハイブリッドで、
half を積極的に使わないとパフォーマンスに影響が生じるものでした。
ShaderModel3.0 になると GeForce6800~ も RADEON X1800~ も、頂点
同様の 32bit 浮動少数精度を実現します。
ここまで来てようやく PixelShader の演算精度が VertexShader に肩を
並べることができたわけで、D3D10 ShaderModel4.0 世代のユニファイド
シェーダーへ向けて準備が整ったといえます。
ちなみに ATI(AMD) は X1800 より前にも、Xbox360 向け GPU にて
先行して 32bit 浮動少数精度の PixelShader、ユニファイドシェーダー、
ストリームアウトプットなどの機能を実現していました。
さて ShaderModel4.0 で IEEE754 単精度に対応したら次は倍精度 64bit
というわけです。この流れは実は DirectX SDK の D3D10 のマニュアルを
じっくり見ているとわかります。
なにせ HLSL のリファレンスを開くと、一番最初が変数型の説明で、
その最初のページに dobule 型がしっかりと記載されています。
はじめて ShaderModel4.0 のマニュアルを読み進めていたときに、
もう倍精度も対応しているのかと驚きました。
でも試したら使えませんでした。
ID3D10EffectType や ID3D10ShaderReflectionType 等が使っている
D3D10_SHADER_VARIABLE_TYPE を見ても、32bit INT と 32bit FLOAT
しかありません。本当に 64bit float に対応するなら D3D の core
を含めてまた大幅な更新が必要となりそうです。
ちなみに cbuffer 等で double 宣言を使ったどうなるか。
実はコンパイルはきちんと通ります。でも Reflection を取ってみると
ただの 32bit の float になっていました。
ShaderModel4.0 においては half も float 扱いとなるようです。
ないか、との話です。
・GPUサーバーの時代を開くNVIDIAの「Tesla」
もともと ShaderModel1.0 の PixelShader は 9~12bit 整数演算で、
D3D10 の表記でいえば SNORM に相当します。固定少数値
-1.0~+1.0 にMapされていたわけです。
VertexShader は 32bit float でした。
ShaderModel2.0 になって ATI(AMD) RADEON は 9700 シリーズで 24bit float を
導入し、これが X800 系まで続きます。
ShaderModel2.0 で出遅れた nVIDIA の方は GeForceFX で 32bit float を
先行実現しましたが、実際は 16bit half float とのハイブリッドで、
half を積極的に使わないとパフォーマンスに影響が生じるものでした。
ShaderModel3.0 になると GeForce6800~ も RADEON X1800~ も、頂点
同様の 32bit 浮動少数精度を実現します。
ここまで来てようやく PixelShader の演算精度が VertexShader に肩を
並べることができたわけで、D3D10 ShaderModel4.0 世代のユニファイド
シェーダーへ向けて準備が整ったといえます。
ちなみに ATI(AMD) は X1800 より前にも、Xbox360 向け GPU にて
先行して 32bit 浮動少数精度の PixelShader、ユニファイドシェーダー、
ストリームアウトプットなどの機能を実現していました。
さて ShaderModel4.0 で IEEE754 単精度に対応したら次は倍精度 64bit
というわけです。この流れは実は DirectX SDK の D3D10 のマニュアルを
じっくり見ているとわかります。
なにせ HLSL のリファレンスを開くと、一番最初が変数型の説明で、
その最初のページに dobule 型がしっかりと記載されています。
はじめて ShaderModel4.0 のマニュアルを読み進めていたときに、
もう倍精度も対応しているのかと驚きました。
でも試したら使えませんでした。
ID3D10EffectType や ID3D10ShaderReflectionType 等が使っている
D3D10_SHADER_VARIABLE_TYPE を見ても、32bit INT と 32bit FLOAT
しかありません。本当に 64bit float に対応するなら D3D の core
を含めてまた大幅な更新が必要となりそうです。
ちなみに cbuffer 等で double 宣言を使ったどうなるか。
実はコンパイルはきちんと通ります。でも Reflection を取ってみると
ただの 32bit の float になっていました。
ShaderModel4.0 においては half も float 扱いとなるようです。
2007/07/05
D3D10/DX10 D3D10_FILTER その2
D3D9 までの TextureFilter は、MinFilter, MagFilter, MipFilter の
組み合わせでした。それぞれ設定可能な値は3タイプで次のようになります。
MIN : POINT, LINEAR, ANISOTROPIC
MAG : POINT, LINEAR, ANISOTROPIC
MIP : NONE, POINT, LINEAR
D3D10_FILTER では MIN, MAG, MIP の各組み合わせが個別のシンボル
(例の長いやつ)に定義されています。
だけどその定数値を良く見ると一定の法則がわかります。
・D3D10_FILTER
bit0 = MIP LINEAR
bit2 = MAG LINEAR
bit4 = MIN LINEAR
bit6 = ANISOTROPIC (MIP,MAG,MIN全部 Linear のみ)
bit7 = COMPARISON
bit31 = TEXT 1BIT
実際 ANISOTROPIC 時に point sampling を組み合わせることはないので、
必要な組み合わせだけをシンボルに指定して定義したもののようです。
また MIP NONE がありません。
テクスチャが LOD を持っているかどうか、これだけで MIP の利用を決定しても
実用上特に不便しないし、実際 MIP が付いているのに切ることは稀だし、
必要なら Shader の Sample 時に Level を決め打ちできるし、
と State 側にはいらないのかもしれません。
今気が付いたけど SDK マニュアルの SampleLevel の説明が間違っているようです。
・SampleLevel
中身が SampleCmpLevelZero のものになっていますね。
正しくは MipLevel の指定です。
組み合わせでした。それぞれ設定可能な値は3タイプで次のようになります。
MIN : POINT, LINEAR, ANISOTROPIC
MAG : POINT, LINEAR, ANISOTROPIC
MIP : NONE, POINT, LINEAR
D3D10_FILTER では MIN, MAG, MIP の各組み合わせが個別のシンボル
(例の長いやつ)に定義されています。
だけどその定数値を良く見ると一定の法則がわかります。
・D3D10_FILTER
bit0 = MIP LINEAR
bit2 = MAG LINEAR
bit4 = MIN LINEAR
bit6 = ANISOTROPIC (MIP,MAG,MIN全部 Linear のみ)
bit7 = COMPARISON
bit31 = TEXT 1BIT
実際 ANISOTROPIC 時に point sampling を組み合わせることはないので、
必要な組み合わせだけをシンボルに指定して定義したもののようです。
また MIP NONE がありません。
テクスチャが LOD を持っているかどうか、これだけで MIP の利用を決定しても
実用上特に不便しないし、実際 MIP が付いているのに切ることは稀だし、
必要なら Shader の Sample 時に Level を決め打ちできるし、
と State 側にはいらないのかもしれません。
今気が付いたけど SDK マニュアルの SampleLevel の説明が間違っているようです。
・SampleLevel
中身が SampleCmpLevelZero のものになっていますね。
正しくは MipLevel の指定です。
2007/07/04
D3D10/DX10 D3D10_FILTER の新機能
D3D10_FILTER にはものすごく長いシンボルが定義されています。
例えばこんなの
D3D10_FILTER_COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR
タイピングの困難さとかソースコードの折り返しやみやすさの問題とかは
おいといて、これらのシンボルを見ていると COMPARISON とつくものが
一通り増えていることがわかります。
・msdn D3D10_FILTER
どうやら従来 nVIDIA の GPU にだけ搭載されていた Shadow Map 用の
ハードウエア機能が一般化されて D3D の標準機能に昇格したものと思われます。
nVIDIA Shadow Map は GeForce3 から搭載されたもので、depth 値を比較して
同時にフィルタリングをしてくれました。
いわゆる PCF の 4サンプル分までがバイリニアフィルタリングつきで
低コストで得られます。
PCF によるソフトシャドウは一般的なテクスチャフィルタは使えず
膨大なサンプリングが必要なので、ある程度ハードウエアで稼げるのはお得です。
この D3D10_FILTER の比較付きサンプリングを行うためには、
HLSL の専用命令 texture.SampleCmp() や texture.SampleCmpLevelZero()
を使う必要があります。サンプリングした結果を比較し、結果の bool 値が
フィルタリング対象となるわけです。
マニュアルをよく読むと使えるフォーマットに制限があるようです。
R32_FLOAT_X8X24_TYPELESS, R32_FLOAT, R24_UNORM_X8_TYPELESS, R16_UNORM
やっぱりまだこれらの命令は depth 値専用でしょうか。
まだ D3D10 上では試していないので、影描画に着手するあたりにはいろいろ
実験してみます。
以前 RADEON HD 2900XT でも Direct3D9 で作った nVIDIA Shadow Map の
影描画プログラムが、そのまま通ってしまうことをこちらで書きました。
・D3D10/DX10 RADEON HD2900XT
RADEON X1900 世代ではバイリニア用サンプラを使った depth 値の同時
4 sample までがハードウエアサポートで、それ以降の比較やフィルタリング
はシェーダー実装する必要がありました。D3D10 の機能として採用された
おかげで nVIDIAと完全に同等の機能が使用可能になったのではないかと
考えられます。
ちなみに D3D10_FILTER の最後には D3D10_FILTER_TEXT_1BIT という特殊な
シンボルが定義されています。
フォントのための専用テクスチャフォーマット用らしいですね。
D3D10 からついた 1bit フォーマットのテクスチャで使うものと思われます。
デバッグ用フォントのフォーマットとして使えるならメモリ効率も良くて
お得ですね。
例えばこんなの
D3D10_FILTER_COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR
タイピングの困難さとかソースコードの折り返しやみやすさの問題とかは
おいといて、これらのシンボルを見ていると COMPARISON とつくものが
一通り増えていることがわかります。
・msdn D3D10_FILTER
どうやら従来 nVIDIA の GPU にだけ搭載されていた Shadow Map 用の
ハードウエア機能が一般化されて D3D の標準機能に昇格したものと思われます。
nVIDIA Shadow Map は GeForce3 から搭載されたもので、depth 値を比較して
同時にフィルタリングをしてくれました。
いわゆる PCF の 4サンプル分までがバイリニアフィルタリングつきで
低コストで得られます。
PCF によるソフトシャドウは一般的なテクスチャフィルタは使えず
膨大なサンプリングが必要なので、ある程度ハードウエアで稼げるのはお得です。
この D3D10_FILTER の比較付きサンプリングを行うためには、
HLSL の専用命令 texture.SampleCmp() や texture.SampleCmpLevelZero()
を使う必要があります。サンプリングした結果を比較し、結果の bool 値が
フィルタリング対象となるわけです。
マニュアルをよく読むと使えるフォーマットに制限があるようです。
R32_FLOAT_X8X24_TYPELESS, R32_FLOAT, R24_UNORM_X8_TYPELESS, R16_UNORM
やっぱりまだこれらの命令は depth 値専用でしょうか。
まだ D3D10 上では試していないので、影描画に着手するあたりにはいろいろ
実験してみます。
以前 RADEON HD 2900XT でも Direct3D9 で作った nVIDIA Shadow Map の
影描画プログラムが、そのまま通ってしまうことをこちらで書きました。
・D3D10/DX10 RADEON HD2900XT
RADEON X1900 世代ではバイリニア用サンプラを使った depth 値の同時
4 sample までがハードウエアサポートで、それ以降の比較やフィルタリング
はシェーダー実装する必要がありました。D3D10 の機能として採用された
おかげで nVIDIAと完全に同等の機能が使用可能になったのではないかと
考えられます。
ちなみに D3D10_FILTER の最後には D3D10_FILTER_TEXT_1BIT という特殊な
シンボルが定義されています。
フォントのための専用テクスチャフォーマット用らしいですね。
D3D10 からついた 1bit フォーマットのテクスチャで使うものと思われます。
デバッグ用フォントのフォーマットとして使えるならメモリ効率も良くて
お得ですね。
2007/07/03
D3D10/DX10 Effect と CBufferの関係
Effect Interface内情報の続きです。
ID3D10Effect は内部で必要な ConstantBuffer を生成しパラメータを保持
しています。cbuffer 宣言で明示的に ConstantBuffer を指定することが
できますが、実際に生成されているバッファは宣言した cbuffer の数よりも
1つ多いようです。
調べてみると '$Globals' という暗黙の CBuffer が作られており、おそらく
cbuffer 宣言無しに定義した global 変数が $Globals 扱いになるのでしょう。
ID3D10Effect の CBuffer は ID3D10EffectConstantBuffer 経由で取り出せます。
例えば作成済み D3D10Effect の ie1 から、cbuffer LMaterial の IBuffer
(ConstantBuffer) を参照するには次のようになります。
ID3D10Effect* ie1; // input
ID3D10Buffer* ib1= NULL; // output
ID3D10EffectConstantBuffer* cf1= ie1->GetConstantBufferByName( "LMaterial" );
cf1->GetConstantBuffer( &ib1 );
このとき ib1 は Get によって AddRef() されており参照カウンタ値は 2 です。
自前で用意した CBuffer を与えるとどうなるでしょうか。
ID3D10Effect のインスタンスを 2つ用意し、ie1 に ie2 の CBuffer を設定して
みました。
ID3D10Effect* ie1; // input
ID3D10Effect* ie2; // input
ID3D10EffectConstantBuffer* cf1= ie1->GetConstantBufferByName( "LMaterial" );
ID3D10EffectConstantBuffer* cf2= ie2->GetConstantBufferByName( "LMaterial" );
ID3D10Buffer* ib1= NULL;
ID3D10Buffer* ib2= NULL;
cf1->GetConstantBuffer( &ib1 );
cf2->GetConstantBuffer( &ib2 );
cf1->SetConstantBuffer( &1b2 ); // ie1 ← ie2
ここで ib1, ib2 それぞれの参照カウンタを見ると 1 と 3 になっています。
つまり ie1 側の CBuffer は事実上解放されており、ie2 側の CBuffer が
共有されていることが参照カウンタの増加から分かります。
ID3D10Shader を使わずとも ID3D10Effect だけでバッファの共有管理を自前で
行うことができそうです。
ID3D10Effect は内部で必要な ConstantBuffer を生成しパラメータを保持
しています。cbuffer 宣言で明示的に ConstantBuffer を指定することが
できますが、実際に生成されているバッファは宣言した cbuffer の数よりも
1つ多いようです。
調べてみると '$Globals' という暗黙の CBuffer が作られており、おそらく
cbuffer 宣言無しに定義した global 変数が $Globals 扱いになるのでしょう。
ID3D10Effect の CBuffer は ID3D10EffectConstantBuffer 経由で取り出せます。
例えば作成済み D3D10Effect の ie1 から、cbuffer LMaterial の IBuffer
(ConstantBuffer) を参照するには次のようになります。
ID3D10Effect* ie1; // input
ID3D10Buffer* ib1= NULL; // output
ID3D10EffectConstantBuffer* cf1= ie1->GetConstantBufferByName( "LMaterial" );
cf1->GetConstantBuffer( &ib1 );
このとき ib1 は Get によって AddRef() されており参照カウンタ値は 2 です。
自前で用意した CBuffer を与えるとどうなるでしょうか。
ID3D10Effect のインスタンスを 2つ用意し、ie1 に ie2 の CBuffer を設定して
みました。
ID3D10Effect* ie1; // input
ID3D10Effect* ie2; // input
ID3D10EffectConstantBuffer* cf1= ie1->GetConstantBufferByName( "LMaterial" );
ID3D10EffectConstantBuffer* cf2= ie2->GetConstantBufferByName( "LMaterial" );
ID3D10Buffer* ib1= NULL;
ID3D10Buffer* ib2= NULL;
cf1->GetConstantBuffer( &ib1 );
cf2->GetConstantBuffer( &ib2 );
cf1->SetConstantBuffer( &1b2 ); // ie1 ← ie2
ここで ib1, ib2 それぞれの参照カウンタを見ると 1 と 3 になっています。
つまり ie1 側の CBuffer は事実上解放されており、ie2 側の CBuffer が
共有されていることが参照カウンタの増加から分かります。
ID3D10Shader を使わずとも ID3D10Effect だけでバッファの共有管理を自前で
行うことができそうです。
2007/07/02
D3D10/DX10 Effect Interface内情報
D3D10 では D3D9 と違い、Effect (fx) API は D3DX ではなく D3D10 に
含まれています。インターフェースは ID3D10Effect で、シェーダーの
コンパイルや fx に含まれる各種変数やステート情報へのアクセスなどが
できるようになっています。
従来どおり個別の VertexShader / GeometryShader / PixelShader も
扱うことができ、こちらは Shader インターフェースとしてまとめられています。
・ID3D10GeometryShader
・ID3D10PixelShader
・ID3D10VertexShader
D3D10Effect から各種シェーダーを直接取り出す手順はこんな感じになりました。
// vertex shader
D3D10Effect* ieffect; // input
D3D10VertexShader* ivsh= NULL; // output
D3D10EffectTechnique* itechnique= ieffect->GetTechniqueByName( "Main" );
D3D10EffectPass* ipass= itechnique->GetPassByIndex( 0 );
if( ipass->IsValid() ){
D3D10_PASS_SHADER_DESC shdesc;
ipass->GetVertexShaderDesc( &shdesc );
ID3D10EffectShaderVariable* ishadervar= shdesc.pShaderVariable;
if( ishadervar->IsValid() ){
ishadervar->GetVertexShader( shdesc.ShaderIndex, &ivsh );
}
}
これは technique Main の Pass0 の VertexShader を参照しています。
このとき、得られた D3D10VertexShader の参照カウンタが増加するので、
不要になったら ivsh には Release() が必要です。
それに対して D3D10EffectShaderVariable 等、Reflection 系のアクセス
インターフェースは COM ではないので参照カウンタがありません。
ID3D10ShaderReflectionVariable 等も全く同じです。
D3D10 では最初はこの辺の区別がつきづらく、慣れるまで少々分かりにくく
なりました。
ここでちょっとはまった注意点として GeometryShader があります。
というのは、Effect(fx) 内部で GeometryShader に NULL を設定していても
ipass->GetGeometryShaderDesc() が成功して D3D10EffectShaderVariable* を
参照できてしまうからです。IsValid() も通過します。
どうやら内部で static の NULL object を用意しているらしく、
GeometryShader に NULL を設定した場合はどの D3D10Effect でも全く同じ
D3D10EffectShaderVariable のアドレスが返ってきました。
D3D10EffectShaderVariable::GetGeometryShader() では NULL が返るので、
このことが分かっていれば全く問題はありません。
shader からさらに D3D10ShaderReflection を取得するには次のようになります。
ID3D10EffectShaderVariable* ishadervar; // input
ID3D10ShaderReflection* iref= NULL; // output
D3D10_EFFECT_SHADER_DESC efsdesc;
ishadervar->GetShaderDesc( &efsdesc );
D3D10ReflectionShader( efsdesc.pBytecode, efsdesc.BytecodeLength, &iref );
Reflection 等から変数の要素1つ1つ調べるには ~Variable から ~Type を
取得して調べていきます。
例えば Effect なら D3D10EffectType があり、Shader なら
D3D10ShaderReflectionType を使います。
Variable が構造体なら、さらに GetMemberTypeByIndex() 等で内部の
メンバー情報を参照していく必要があるようです。
もちろん変数名等を使って Effect が管理するバッファに値を格納するだけなら
Reflection とか考えずに
GetVariableByName() → AsVector() → SetFloatVector()
とか使えば良いので簡単です。
だけど D3D9 と違って StateManager とか使わずに API だけでもきちんと
全情報にアクセスできるみたいなので、Effect 内の管理に頼らないで
パラメータ管理自前で乗っ取っても大丈夫そうです。
そういえば、変数名でなく取得したハンドルでのアクセス方法は
なくなっているみたいですね。
含まれています。インターフェースは ID3D10Effect で、シェーダーの
コンパイルや fx に含まれる各種変数やステート情報へのアクセスなどが
できるようになっています。
従来どおり個別の VertexShader / GeometryShader / PixelShader も
扱うことができ、こちらは Shader インターフェースとしてまとめられています。
・ID3D10GeometryShader
・ID3D10PixelShader
・ID3D10VertexShader
D3D10Effect から各種シェーダーを直接取り出す手順はこんな感じになりました。
// vertex shader
D3D10Effect* ieffect; // input
D3D10VertexShader* ivsh= NULL; // output
D3D10EffectTechnique* itechnique= ieffect->GetTechniqueByName( "Main" );
D3D10EffectPass* ipass= itechnique->GetPassByIndex( 0 );
if( ipass->IsValid() ){
D3D10_PASS_SHADER_DESC shdesc;
ipass->GetVertexShaderDesc( &shdesc );
ID3D10EffectShaderVariable* ishadervar= shdesc.pShaderVariable;
if( ishadervar->IsValid() ){
ishadervar->GetVertexShader( shdesc.ShaderIndex, &ivsh );
}
}
これは technique Main の Pass0 の VertexShader を参照しています。
このとき、得られた D3D10VertexShader の参照カウンタが増加するので、
不要になったら ivsh には Release() が必要です。
それに対して D3D10EffectShaderVariable 等、Reflection 系のアクセス
インターフェースは COM ではないので参照カウンタがありません。
ID3D10ShaderReflectionVariable 等も全く同じです。
D3D10 では最初はこの辺の区別がつきづらく、慣れるまで少々分かりにくく
なりました。
ここでちょっとはまった注意点として GeometryShader があります。
というのは、Effect(fx) 内部で GeometryShader に NULL を設定していても
ipass->GetGeometryShaderDesc() が成功して D3D10EffectShaderVariable* を
参照できてしまうからです。IsValid() も通過します。
どうやら内部で static の NULL object を用意しているらしく、
GeometryShader に NULL を設定した場合はどの D3D10Effect でも全く同じ
D3D10EffectShaderVariable のアドレスが返ってきました。
D3D10EffectShaderVariable::GetGeometryShader() では NULL が返るので、
このことが分かっていれば全く問題はありません。
shader からさらに D3D10ShaderReflection を取得するには次のようになります。
ID3D10EffectShaderVariable* ishadervar; // input
ID3D10ShaderReflection* iref= NULL; // output
D3D10_EFFECT_SHADER_DESC efsdesc;
ishadervar->GetShaderDesc( &efsdesc );
D3D10ReflectionShader( efsdesc.pBytecode, efsdesc.BytecodeLength, &iref );
Reflection 等から変数の要素1つ1つ調べるには ~Variable から ~Type を
取得して調べていきます。
例えば Effect なら D3D10EffectType があり、Shader なら
D3D10ShaderReflectionType を使います。
Variable が構造体なら、さらに GetMemberTypeByIndex() 等で内部の
メンバー情報を参照していく必要があるようです。
もちろん変数名等を使って Effect が管理するバッファに値を格納するだけなら
Reflection とか考えずに
GetVariableByName() → AsVector() → SetFloatVector()
とか使えば良いので簡単です。
だけど D3D9 と違って StateManager とか使わずに API だけでもきちんと
全情報にアクセスできるみたいなので、Effect 内の管理に頼らないで
パラメータ管理自前で乗っ取っても大丈夫そうです。
そういえば、変数名でなく取得したハンドルでのアクセス方法は
なくなっているみたいですね。