2007/08/31
Direct3D 10 Shader4.0 APIによってコンパイラが違う
同じシェーダーで、かつ同じコンパイルオプションを指定しているのに、
生成されたシェーダーのバイナリが完全に同一になりません。
こんな症状にしばらく悩んでいました。
判明した結論は下記のとおり。
「Direct3D10 は使用する API によって HLSL コンパイラが異なっている」
この違いは April2007 から生じており、当時のリリースノートを
良く読むとそれらしいことが書いてありました。
(マニュアルの「What's New in the April 2007 DirectX SDK」)
以下、HLSL Compile 系 API の違いなどを説明しながら調べた結果を
書いてみます。
DirectX10 では Effect(fx) や HLSL 関連の API も core に含まれる
ようになりました。そのため必ずしも D3DX を使う必要は無く、
エフェクトのコンパイルから生成まで core API だけで実現する
ことができます。
それでも D3DX には Shader や Effect のコンパイル関連の API が
数多く用意されています。core API とほとんど同じ名前で似たものが多く、
あまり違いが無いように見えます。
では D3DX の Compile 系関数を使う利点はどこにあるのでしょうか。
(1) Compile → Create の手順が一度に出来る。
(2) ファイルやリソースから直接読み込める。
(3) 非同期実行できる。(ID3D10ThreadPump)
(4) #include に対応している。
core 側の API は Compile と Create の API が分かれています。
例えば ID3D10Effect の場合
1. D3D10CompileEffectFromMemory( .. )
2. D3D10CreateEffectFromMemory( .. )
と2ステップ必要になります。API が分かれることで、
コンパイル済みバイナリをファイル保存できるし、
コンパイルされたバイナリを読み込めば Compile 無しに Create
だけ通すことも出来ます。
Compile 後のバイナリをそのままファイルに書き出せば、
fxc.exe で作った fxo ファイルと全く同じものができました。
ちなみに API は FromMemory しかなく、FromFile も FromResource
もありません。以前は D3DX に含まれていたことから、わかり
やすいように関数名だけ継承したものと思われます。
D3DX の場合は洗練された汎用性よりも便利さ優先で、下記の命令で
いきなり ID3D10Effect のインスタンスを生成することが出来ます。
D3DX10CreateEffectFromFile( ... )
D3DX10CreateEffectFromMemory( ... )
D3DX10CreateEffectFromResource( ... )
メモリだけでなく直接ファイルやリソースから作ることも出来ます。
ちなみにマニュアルにはミスがあって、最後の引数
が正しく載っていないものがあります。これは ID3DX10ThreadPump で
非同期実行したときに、実行結果を受け取るためのバッファです。
非同期実行しない場合は関数の戻り値で結果がわかるので、この
パラメータは NULL で構いません。
また core API は内部で勝手にファイルアクセスされると困るので、
HLSL 内の #include ディレクティブに対応していません。
#include を意図したとおりに機能させるには、自前でインプリメント
した ID3D10Include が必要です。
#include 以外のプリプロセッサコマンドはそのまま通ります。
D3DX の関数では何もしなくても、内部で勝手に ID3D10Include を
用意してくれます。
もちろん自分で定義した ID3D10Include を渡すことも出来ます。
マニュアルを見ると FromFile の場合のみ自動で #include を処理
してくれるようなことが書いてあります。FromMemory/Resource
ではユーザー定義の ID3D10Include に頼らなければいけないように
見えます。ところが実際に試してみると、
D3DX10CreateEffectFromMemory()
D3DX10CompileFromMemory()
D3DX10PreprocessShaderFromMemory()
どれも ID3D10Include 無しに #include 可能でした。実際、下記の
コードの代わりに
FromMemory を使った次の書き方をしても動きました。
FromResource() もそのままで #include 処理出来るかもしれません。
このように、一見重複に見えるけど D3DX 側には便利な関数が
それなりの理由をもって用意されているわけです。
と、最初はこのくらいに考えていました。
ところが実際は、大きく分けて下記の2種類の HLSL コンパイラが
共存していることになります。
・core API が呼び出す HLSL コンパイラ
・D3DX が呼び出す HLSL コンパイラ
ID3D10Shader の D3D10_SHADER_DESC Creator を見るとこの両者の
違いがわかります。
D3DX が呼び出すコンパイラは C:\Windows\System32 以下にある
dll です。それぞれ内部のバージョン番号も記してみました。
D3DX の Compile 系 API を呼び出したときだけこれらの dll が
読み込まれていることが確認できます。
core API が呼び出しているコンパイラは上記よりもさらに古い
バージョンとなるわけです。
April2007 のリリースノートを見ると bug fix や最適化など
いろいろ修正が入ったようです。
すべての API で新しいコンパイラに置き換わらないのは、
おそらく core 側に入ったことで、安易に変更出来なくなった
ためでしょう。
ちなみに fxc.exe のコマンドラインには
と表示されますが、生成されたバイナリは 9.19.949.1104 となり
D3DX 側のコンパイラが呼ばれていることがわかります。
実際にコンパイルされた結果を見ると、わかる範囲でですが
若干テンポラリレジスタの割り当てが変更されており、
ConstantBuffer もシェーダーで参照している分しか宣言しない
ようになっていました。
●結論
D3DX 側関数を使う理由にもう1つ
(5) 最新の HLSL コンパイラを使うことが出来る
がありました。HLSL のコンパイルは fxc.exe か D3DX の関数を
使いましょう。core API 側の関数を使うと古いバージョンの
コンパイラを呼び出してしまいます。
(そしてSDKのリリースノートはちゃんと読んでおきましょう・・)
生成されたシェーダーのバイナリが完全に同一になりません。
こんな症状にしばらく悩んでいました。
判明した結論は下記のとおり。
「Direct3D10 は使用する API によって HLSL コンパイラが異なっている」
この違いは April2007 から生じており、当時のリリースノートを
良く読むとそれらしいことが書いてありました。
(マニュアルの「What's New in the April 2007 DirectX SDK」)
以下、HLSL Compile 系 API の違いなどを説明しながら調べた結果を
書いてみます。
DirectX10 では Effect(fx) や HLSL 関連の API も core に含まれる
ようになりました。そのため必ずしも D3DX を使う必要は無く、
エフェクトのコンパイルから生成まで core API だけで実現する
ことができます。
それでも D3DX には Shader や Effect のコンパイル関連の API が
数多く用意されています。core API とほとんど同じ名前で似たものが多く、
あまり違いが無いように見えます。
では D3DX の Compile 系関数を使う利点はどこにあるのでしょうか。
(1) Compile → Create の手順が一度に出来る。
(2) ファイルやリソースから直接読み込める。
(3) 非同期実行できる。(ID3D10ThreadPump)
(4) #include に対応している。
core 側の API は Compile と Create の API が分かれています。
例えば ID3D10Effect の場合
1. D3D10CompileEffectFromMemory( .. )
2. D3D10CreateEffectFromMemory( .. )
と2ステップ必要になります。API が分かれることで、
コンパイル済みバイナリをファイル保存できるし、
コンパイルされたバイナリを読み込めば Compile 無しに Create
だけ通すことも出来ます。
Compile 後のバイナリをそのままファイルに書き出せば、
fxc.exe で作った fxo ファイルと全く同じものができました。
ちなみに API は FromMemory しかなく、FromFile も FromResource
もありません。以前は D3DX に含まれていたことから、わかり
やすいように関数名だけ継承したものと思われます。
D3DX の場合は洗練された汎用性よりも便利さ優先で、下記の命令で
いきなり ID3D10Effect のインスタンスを生成することが出来ます。
D3DX10CreateEffectFromFile( ... )
D3DX10CreateEffectFromMemory( ... )
D3DX10CreateEffectFromResource( ... )
メモリだけでなく直接ファイルやリソースから作ることも出来ます。
ちなみにマニュアルにはミスがあって、最後の引数
HRESULT *pHResult
が正しく載っていないものがあります。これは ID3DX10ThreadPump で
非同期実行したときに、実行結果を受け取るためのバッファです。
非同期実行しない場合は関数の戻り値で結果がわかるので、この
パラメータは NULL で構いません。
また core API は内部で勝手にファイルアクセスされると困るので、
HLSL 内の #include ディレクティブに対応していません。
#include を意図したとおりに機能させるには、自前でインプリメント
した ID3D10Include が必要です。
#include 以外のプリプロセッサコマンドはそのまま通ります。
D3DX の関数では何もしなくても、内部で勝手に ID3D10Include を
用意してくれます。
もちろん自分で定義した ID3D10Include を渡すことも出来ます。
マニュアルを見ると FromFile の場合のみ自動で #include を処理
してくれるようなことが書いてあります。FromMemory/Resource
ではユーザー定義の ID3D10Include に頼らなければいけないように
見えます。ところが実際に試してみると、
D3DX10CreateEffectFromMemory()
D3DX10CompileFromMemory()
D3DX10PreprocessShaderFromMemory()
どれも ID3D10Include 無しに #include 可能でした。実際、下記の
コードの代わりに
D3DX10CreateEffectFromFile( "sysdef.fx", NULL, // macro NULL, // include "fx_4_0", 0, // HLSL flags 0, // FX flags g_iDevice, NULL, // EffectPool NULL, // ThreadPump &iEffect, &blobError, NULL // HResult )
FromMemory を使った次の書き方をしても動きました。
const char* memory= "#include \"sysdef.fx\"\n"; D3DX10CreateEffectFromMemory( memory, strlen( memory ), fxFileName, NULL, // macro NULL, // include "fx_4_0", 0, // HLSL flags 0, // FX flags g_iDevice, NULL, // EffectPool NULL, // ThreadPump &iEffect, &blobError, NULL // HResult )
FromResource() もそのままで #include 処理出来るかもしれません。
このように、一見重複に見えるけど D3DX 側には便利な関数が
それなりの理由をもって用意されているわけです。
と、最初はこのくらいに考えていました。
ところが実際は、大きく分けて下記の2種類の HLSL コンパイラが
共存していることになります。
・core API が呼び出す HLSL コンパイラ
・D3DX が呼び出す HLSL コンパイラ
ID3D10Shader の D3D10_SHADER_DESC Creator を見るとこの両者の
違いがわかります。
・D3D10CompileEffectFromMemory() の場合 Microsoft (R) HLSL Shader Compiler ・D3DX10CompileFromMemoryD3DX() (August2007) の場合 Microsoft (R) HLSL Shader Compiler 9.19.949.1104
D3DX が呼び出すコンパイラは C:\Windows\System32 以下にある
dll です。それぞれ内部のバージョン番号も記してみました。
D3DCompiler_33.dll 9.18.949.0015 (April2007) D3DCompiler_34.dll 9.19.949.0046 (June2007) D3DCompiler_35.dll 9.19.949.1104 (August2007)
D3DX の Compile 系 API を呼び出したときだけこれらの dll が
読み込まれていることが確認できます。
core API が呼び出しているコンパイラは上記よりもさらに古い
バージョンとなるわけです。
April2007 のリリースノートを見ると bug fix や最適化など
いろいろ修正が入ったようです。
すべての API で新しいコンパイラに置き換わらないのは、
おそらく core 側に入ったことで、安易に変更出来なくなった
ためでしょう。
ちなみに fxc.exe のコマンドラインには
Microsoft (R) D3D10 Shader Compiler 9.19.949.1075
と表示されますが、生成されたバイナリは 9.19.949.1104 となり
D3DX 側のコンパイラが呼ばれていることがわかります。
実際にコンパイルされた結果を見ると、わかる範囲でですが
若干テンポラリレジスタの割り当てが変更されており、
ConstantBuffer もシェーダーで参照している分しか宣言しない
ようになっていました。
●結論
D3DX 側関数を使う理由にもう1つ
(5) 最新の HLSL コンパイラを使うことが出来る
がありました。HLSL のコンパイルは fxc.exe か D3DX の関数を
使いましょう。core API 側の関数を使うと古いバージョンの
コンパイラを呼び出してしまいます。
(そしてSDKのリリースノートはちゃんと読んでおきましょう・・)
2007/08/30
AMD SSE5 Shader のような新しい命令
AMD が新しい命令セット SSE5 を発表したそうです。
・AMD、新たなx86拡張命令セット「SSE5」~「Bulldozer」コアに搭載予定
こちら のページから資料を見ることができます。
3オペランド命令は扱いやすいので素直にうれしいですね。
命令セットをざっと眺めてみると、16bit fp のサポートも
あるみたいです。これはいい!
符号1、指数5、仮数10 の s10e5 で、Shader の half 型と一緒です。
相互変換命令によるサポートですが、GPU との相性もいいだろうし
HDR テクスチャの生成や変換も速くなるでしょう。
他にも shader 等ではおなじみの積和命令があります。例えば
これは dest= src1*src2 + src3 の演算を行うもので、shader だと
に相当します。でもこれ、3オペランドどころか 4オペランドです。
どうやら各フィールドは完全に独立しておらず、どこかの src
レジスタを dest と共有しなければいけないようにみえます。
よく読んでみると確かに、レジスタフィールドは
DREX.dest、ModRM.reg、ModRM.r/m の3箇所で、
残る1つのソースは dest と同じレジスタを使うと書いてありました。
あまり素直に喜べないかもしれません。
演算時の符号バリエーションとして次の4種類、それぞれ個別の
命令があるようです。
また整数演算用の4オペランド積和命令もあります。
これら以外にも、比較などいろいろ追加命令があります。
例えば PHADDBQ を使うと、8bit の値×8 の合計がいっぺんに求まります。
128bit レジスタは 16byte 相当なので、上位 8個と下位 8個の byte
値の合計2個になります。
8bit + 8bit → 16bit
16bit + 16bit → 32bit
32bit + 32bit → 64bit
と、加算3段階分です。
・AMD、新たなx86拡張命令セット「SSE5」~「Bulldozer」コアに搭載予定
こちら のページから資料を見ることができます。
3オペランド命令は扱いやすいので素直にうれしいですね。
命令セットをざっと眺めてみると、16bit fp のサポートも
あるみたいです。これはいい!
CVTPH2PS fp16×4 → fp32×4 CVTPS2PH fp32×4 → fp16×4
符号1、指数5、仮数10 の s10e5 で、Shader の half 型と一緒です。
相互変換命令によるサポートですが、GPU との相性もいいだろうし
HDR テクスチャの生成や変換も速くなるでしょう。
他にも shader 等ではおなじみの積和命令があります。例えば
FMADDPS dest, src1, src2, src3
これは dest= src1*src2 + src3 の演算を行うもので、shader だと
mad r0, r1, r2, r3
に相当します。でもこれ、3オペランドどころか 4オペランドです。
どうやら各フィールドは完全に独立しておらず、どこかの src
レジスタを dest と共有しなければいけないようにみえます。
FMADDPS xmm1, xmm1, xmm2, xmm3/mem32 FMADDPS xmm1, xmm1, xmm3/mem32, xmm2 FMADDPS xmm1, xmm2, xmm3/mem32, xmm1 FMADDPS xmm1, xmm3/mem32, xmm2, xmm1
よく読んでみると確かに、レジスタフィールドは
DREX.dest、ModRM.reg、ModRM.r/m の3箇所で、
残る1つのソースは dest と同じレジスタを使うと書いてありました。
あまり素直に喜べないかもしれません。
演算時の符号バリエーションとして次の4種類、それぞれ個別の
命令があるようです。
dest= src1*src2 + src3 dest= src1*src2 - src3 dest= -src1*src2 + src3 dest= -src1*src2 - src3
また整数演算用の4オペランド積和命令もあります。
これら以外にも、比較などいろいろ追加命令があります。
例えば PHADDBQ を使うと、8bit の値×8 の合計がいっぺんに求まります。
128bit レジスタは 16byte 相当なので、上位 8個と下位 8個の byte
値の合計2個になります。
8bit + 8bit → 16bit
16bit + 16bit → 32bit
32bit + 32bit → 64bit
と、加算3段階分です。
2007/08/29
Direct3D 10 Shader4.0 ピクセル補間モード
DirectX10 の PixelShader は、入力パラメータの補間方法を
選択することができます。例えば
こんな感じで In.Tex0 を受け取るとパースペクティブ補正がかかりません。
同次除算をいちいち Shader で打ち消す必要も無いので、補正無しの
リニアな値が欲しい場合は Shader4.0 に移植するだけで動作が速くなる
可能性があります。
他にも centroid, nointerpolation, linear といった宣言ができます。
使用可能な組み合わせをまとめてみました。
・nointerpolation (== constant)
・linear
・linear centroid
・linear noperspective
・linear noperspective centroid
無指定時は linear 相当なので、linear は書かなくてもかまいません。
nointerpolation は linear を打ち消すことができ、この場合補間
しない constant 相当となります。
整数値を渡す場合は補間できないので nointerpolation を使います。
linear 時は centroid と noperspective の組み合わせが可能です。
centroid は表記方法が違いますが D3D9 にもありました。
これら組み合わせを変えて試したところ、GeForce8800GTX では
noperspective の有り無しで若干速度が変化しました。
それ以外の組み合わせでは特に速度変化が表面上わかりませんでした。
実行時間(usec)の変化
パースペクティブ補正は演算量が多いためか、ピクセル面積が多いと
上記のように若干速度に違いがでるようです。
なお、この数値は機能の違いを調べるために差が出る状況を作り出した
ものです。負荷があがるかどうか増減を見るだけにしてください。
補間指定によってどれくらい遅くなるかなど、比率での比較は
できませんのでご注意ください。
RADEON HD2900XT ではまだ変化がでる状況が見られないので、
組み合わせによって負荷に違いがあるかどうかわかりませんでした。
選択することができます。例えば
struct PS_INPUT { float4 Pos : SV_POSITION; float3 Normal : NORMAL; noperspective float2 Tex0 : TEXCOORD0; float2 Tex1 : TEXCOORD1; }; float4 PS_Main( PS_INPUT In ) : SV_Target { ... }
こんな感じで In.Tex0 を受け取るとパースペクティブ補正がかかりません。
同次除算をいちいち Shader で打ち消す必要も無いので、補正無しの
リニアな値が欲しい場合は Shader4.0 に移植するだけで動作が速くなる
可能性があります。
他にも centroid, nointerpolation, linear といった宣言ができます。
使用可能な組み合わせをまとめてみました。
・nointerpolation (== constant)
・linear
・linear centroid
・linear noperspective
・linear noperspective centroid
無指定時は linear 相当なので、linear は書かなくてもかまいません。
nointerpolation は linear を打ち消すことができ、この場合補間
しない constant 相当となります。
整数値を渡す場合は補間できないので nointerpolation を使います。
linear 時は centroid と noperspective の組み合わせが可能です。
centroid は表記方法が違いますが D3D9 にもありました。
これら組み合わせを変えて試したところ、GeForce8800GTX では
noperspective の有り無しで若干速度が変化しました。
それ以外の組み合わせでは特に速度変化が表面上わかりませんでした。
1600 nointerpolation 1643 linear perspective 1643 linear perspective centroid 1600 linear noperspective 1600 linear noperspective centroid
実行時間(usec)の変化
パースペクティブ補正は演算量が多いためか、ピクセル面積が多いと
上記のように若干速度に違いがでるようです。
なお、この数値は機能の違いを調べるために差が出る状況を作り出した
ものです。負荷があがるかどうか増減を見るだけにしてください。
補間指定によってどれくらい遅くなるかなど、比率での比較は
できませんのでご注意ください。
RADEON HD2900XT ではまだ変化がでる状況が見られないので、
組み合わせによって負荷に違いがあるかどうかわかりませんでした。
2007/08/24
Direct3D 10 Shader4.0 RADEON HD2900XT の速度傾向
DirectX10 対応 GPU は表面上機能的な差が無いので、ハードの違いを
意識することがあまりなくなりました。
ATI(AMD) の RADEON HD2900XT を最初に使った印象がまさにそうで、
対応機能をチェックしたり、それぞれ個別に専用のシェーダーを用意
したりする必要も無さそうです。
これまで GeForce8800GTS/GTX 周りの Shader 実行速度を調べて
きました。やっぱり RADEON の結果も気になるでしょう。
実は RADEON のデータも取ってあります。
でも実時間よりもどうも遅い数値が出てるようで、いまいち信頼に
欠けるのです。
テストでプログラムは Query の TIMESTAMP を使っているのですが、
2倍の値が返ってきているように見えます。または Frequency が
半分なのかもしれません。プログラムの問題だとは思うのですが
まだ原因がよくわかっていません。
Catalyst 7.8 に上げても同じでした。
もう1点、使っているテスト PC の電源が足りないようです。
RADEON HD2900XT は本来、PCI-E 用の 8pin + 6pin の電源を使います。
マニュアルには一応 6pin + 6pin でも動作すると書かれていたので、
GeForce8800GTS/GTX と同じように 6pin + 6pin で動かしていました。
ただこれだと HD2900XT 本来の性能を発揮できず、動作クロックが
抑えられてしまうようです。
・測定された実行時間はおそらく 2倍の値(半分で実時間)になっている。
・電源が足りずに動作クロックが抑えられた状態になっている。
よって上記2点の理由により、測定値は絶対的な指標として他の
GPU との比較ができず、相対的に遅くなる条件や傾向の判断用の
データとなる点ご了承ください。
まずは先日の GTX と同じ、[loop] の方が高速になる unroll/loop
展開テストです。temporary register 数の増加がどれくらい実行
速度に影響を与えているか、その判断にもなるかと思います。
使用したドライバは Catalyst 7.8 です。

・縦 時間 (縦軸は半分の値と思ってください)
・横 ループ回数
テンポラリレジスタが増えると遅くなる傾向は GeForce8800 と
同じです。80(12)~100回(15) に大きな隔たりができています。
このあたりでおそらくレジスタ数と動作スレッド数のバランスが
崩壊しています。18固定後の負荷上昇率が意外に小さいので、
ループ処理自体の動作効率は良いようです。
100回以上は一定の上昇率なので、このケースではテンポラリ
レジスタ数 15以上はほぼ同じ負荷となっているようです。
ネイティブコード変換でぜんぜん違うプログラムに置き換わっている
のかもしれません。
テンポラリレジスタ数増加に対する耐性が弱く、比較的低い位置
で負荷が上昇するものの、シェーダー自体の実行効率は良いので
ループ回数が極端に増えれば良い結果がでそうです。
本来比較できない今のデータのままでも、場合によっては
GeForce の結果を追い越すかもしれません。
本来高速な条件では速度が伸びないけど、遅い条件でも割と耐える
感じです。
次に出力レジスタ(ラスタライザの補間パラメータ数)数と動作速度の
関係は下記のとおりです。内部の最適化 (VLIW化?) の影響なのか、
別のところに要因があるせいなのかわかりません。

・縦 時間 (縦軸は半分の値と思ってください)
・横 出力レジスタ数
9~10まで一気に上昇するものの11でなぜか下がります。
11~15の数値はかなり速くなっており、出力レジスタ数の影響が
もともと無いのか、それとも意味が無いと思って最適化されたのか
もしれません。
Catalyst 7.8 にしたら今まで動いていたプログラムで動かなく
なったものがあるので 7.7 に戻しました。
安定したデータが取れるのはもうちょっと先になるかもしれません。
まずは電源を・・
意識することがあまりなくなりました。
ATI(AMD) の RADEON HD2900XT を最初に使った印象がまさにそうで、
対応機能をチェックしたり、それぞれ個別に専用のシェーダーを用意
したりする必要も無さそうです。
これまで GeForce8800GTS/GTX 周りの Shader 実行速度を調べて
きました。やっぱり RADEON の結果も気になるでしょう。
実は RADEON のデータも取ってあります。
でも実時間よりもどうも遅い数値が出てるようで、いまいち信頼に
欠けるのです。
テストでプログラムは Query の TIMESTAMP を使っているのですが、
2倍の値が返ってきているように見えます。または Frequency が
半分なのかもしれません。プログラムの問題だとは思うのですが
まだ原因がよくわかっていません。
Catalyst 7.8 に上げても同じでした。
もう1点、使っているテスト PC の電源が足りないようです。
RADEON HD2900XT は本来、PCI-E 用の 8pin + 6pin の電源を使います。
マニュアルには一応 6pin + 6pin でも動作すると書かれていたので、
GeForce8800GTS/GTX と同じように 6pin + 6pin で動かしていました。
ただこれだと HD2900XT 本来の性能を発揮できず、動作クロックが
抑えられてしまうようです。
・測定された実行時間はおそらく 2倍の値(半分で実時間)になっている。
・電源が足りずに動作クロックが抑えられた状態になっている。
よって上記2点の理由により、測定値は絶対的な指標として他の
GPU との比較ができず、相対的に遅くなる条件や傾向の判断用の
データとなる点ご了承ください。
まずは先日の GTX と同じ、[loop] の方が高速になる unroll/loop
展開テストです。temporary register 数の増加がどれくらい実行
速度に影響を与えているか、その判断にもなるかと思います。
使用したドライバは Catalyst 7.8 です。

loop回数 [unroll] [loop] temp reg 40 1225 1014 5 60 2550 1400 7 80 3340 1780 12 100 8100 2160 15 120 9600 2550 17 140 11000 2950 18 160 12500 3340 18 180 13960 3710 18 200 15436 4092 18
・縦 時間 (縦軸は半分の値と思ってください)
・横 ループ回数
テンポラリレジスタが増えると遅くなる傾向は GeForce8800 と
同じです。80(12)~100回(15) に大きな隔たりができています。
このあたりでおそらくレジスタ数と動作スレッド数のバランスが
崩壊しています。18固定後の負荷上昇率が意外に小さいので、
ループ処理自体の動作効率は良いようです。
100回以上は一定の上昇率なので、このケースではテンポラリ
レジスタ数 15以上はほぼ同じ負荷となっているようです。
ネイティブコード変換でぜんぜん違うプログラムに置き換わっている
のかもしれません。
テンポラリレジスタ数増加に対する耐性が弱く、比較的低い位置
で負荷が上昇するものの、シェーダー自体の実行効率は良いので
ループ回数が極端に増えれば良い結果がでそうです。
本来比較できない今のデータのままでも、場合によっては
GeForce の結果を追い越すかもしれません。
本来高速な条件では速度が伸びないけど、遅い条件でも割と耐える
感じです。
次に出力レジスタ(ラスタライザの補間パラメータ数)数と動作速度の
関係は下記のとおりです。内部の最適化 (VLIW化?) の影響なのか、
別のところに要因があるせいなのかわかりません。

・縦 時間 (縦軸は半分の値と思ってください)
・横 出力レジスタ数
9~10まで一気に上昇するものの11でなぜか下がります。
11~15の数値はかなり速くなっており、出力レジスタ数の影響が
もともと無いのか、それとも意味が無いと思って最適化されたのか
もしれません。
Catalyst 7.8 にしたら今まで動いていたプログラムで動かなく
なったものがあるので 7.7 に戻しました。
安定したデータが取れるのはもうちょっと先になるかもしれません。
まずは電源を・・
2007/08/22
Microsoft Gamefest Japan 2007
Shader.jp さんからの情報ですが
・ゲーム開発者のための技術説明会 「Gamefest Japan 2007」 開催
CEDEC から Meltdown が消えたなと思っていたら、独自に
Gamefest Japan を行うみたいですね。
二日間にわたって開催されて結構内容も幅広いようです。
9月は忙しくなりそうです。
・ゲーム開発のための技術説明会 Gamefest Japan 2007 開催 ゲーム制作技術情報を日本で初めて広く一般へ公開
こちらにも書いてあるように Gamefest はもともと開発者限定の
クローズドなイベントだったので、それが広く一般に公開された形です。
ターゲットはやはり開発者で、マルチプラットフォーム化等の推進が
狙いとのことです。
そういえばゲームは、Wii/DS 等ユーザー層の拡大がここ最近のテーマでした。
もしかしたら MS は XNA 関連を中心にして、ユーザーだけでなく
開発の一般への浸透化や、開発者の層の拡大もある程度視野に入れて
いるのかもしれません。
・ゲーム開発者のための技術説明会 「Gamefest Japan 2007」 開催
CEDEC から Meltdown が消えたなと思っていたら、独自に
Gamefest Japan を行うみたいですね。
二日間にわたって開催されて結構内容も幅広いようです。
9月は忙しくなりそうです。
・ゲーム開発のための技術説明会 Gamefest Japan 2007 開催 ゲーム制作技術情報を日本で初めて広く一般へ公開
こちらにも書いてあるように Gamefest はもともと開発者限定の
クローズドなイベントだったので、それが広く一般に公開された形です。
ターゲットはやはり開発者で、マルチプラットフォーム化等の推進が
狙いとのことです。
そういえばゲームは、Wii/DS 等ユーザー層の拡大がここ最近のテーマでした。
もしかしたら MS は XNA 関連を中心にして、ユーザーだけでなく
開発の一般への浸透化や、開発者の層の拡大もある程度視野に入れて
いるのかもしれません。
2007/08/22
Direct3D 10 GeForce8800GTX は GTS の 1.5倍速い
前々回
・Direct3D 10 Shader4.0 ループと最適化
で行ったテストを、GeForce8800GTX でも試してみました。
テストしたのは、逆転して [loop] の方が高速になる 3番目の
shader です。GeForce8800GTX の傾向は GTS とまったく同じで、
loop 120 回以上で速度低下が顕著になります。

・縦軸は実行にかかった時間(usec)。位置が低い方が高速。
・横軸はループ回数
このデータを見る限り、temporary register の割り当てに関しては
特に GTS と GTX で差がないように見えます。つまりシェーダー
ユニットに対する、割り当て可能な register pool の割合はおそらく
同率になっているのでしょう。
GTS はシェーダーユニット数が少ないけれど、その分潤沢にレジスタを
使えるわけではありませんでした。
数値を取っていて気になったのは、思った以上に GTX が速いという
ことです。上のグラフには前々回の GTS の結果も重ねており、また
表には GTS 比でどれくらい速いのかも書き込んでみました。
だいたい 1.5倍程度 GTX の方が高速に動作している計算です。
こんなに差があったかな・・と思ってスペックを確認してみました。
・DirectX10 GPU メモ
Stream Processor の数で 約1.33 倍
Shader Clock の差で 1.125 倍
1.33 × 1.125 ≒ 1.50
ぴったり計算どおりでした。
シェーダーの実行速度に対して、メモリ速度は clock で 1.125倍、
bus 幅で 1.2倍、あわせて 1.35 倍です。
シェーダーの演算ではなくメモリが足を引っ張る状況では、GTS と
GTX の速度差はもっと小さくなります。
普段体感している速度差は、おそらくこちらの方が近いのではない
でしょうか。
例えば前回(昨日)
・Direct3D 10 Shader4.0 補間レジスタ数と速度の関係
のグラフをもう一度じっくり見てみます。

出力数が 11以上の右側では、GTS と GTX の差を計算してみると
ちょうど 1.5倍前後になっていることがわかります。
ここは上と同じで計算どおりなので、純粋にシェーダーの演算能力
で頭打ちになっているといえるでしょう。
それに比べて左側、11未満の結果では、GTS と GTX の差がほとんど
ありません。GTX の速度は GTS 比でわずか 1.06~1.1 倍程度です。
ほぼこれは Core や Shader、Memory 等の Clock 比 1.125倍に相当
すると考えられます。
つまりここでのボトルネックは、core か Shader Unit 内部に存在
する固定ユニットの実行速度なのでしょう。GTS でも GTX でも GPU
内にたぶん同一個数実装されていると推測できます。
もし仮にこれが 1.35倍に開いていたら、それはおそらくメモリが
足を引っ張っている部分です。
これらの結果から GTX は GTS と比較して、状況によって
1.125~1.5 倍高速に実行できます。メモリ速度を考えると、本当に
1.5倍の速度差がでるようなケースはそれほど多くないと思います。
また同じように GeForce8800Ultra で計算すると、GTS 比で次のよう
になります。
Shader 1.66倍
Memory 1.62倍
Clock 1.224~1.35
これは速いですね。メモリ速度も Shader 並みの比率を保っているので、
比較的1.6倍に近いスループットが期待できそうです。
・Direct3D 10 Shader4.0 ループと最適化
で行ったテストを、GeForce8800GTX でも試してみました。
テストしたのは、逆転して [loop] の方が高速になる 3番目の
shader です。GeForce8800GTX の傾向は GTS とまったく同じで、
loop 120 回以上で速度低下が顕著になります。

・縦軸は実行にかかった時間(usec)。位置が低い方が高速。
・横軸はループ回数
GeForce8800GTX loop回数 time GTS比 40 371 1.52 60 570 1.52 80 962 1.55 100 1752 1.51 120 2120 1.57 140 3870 1.50 160 5620 1.50 180 7200 1.49 200 8780 1.45
このデータを見る限り、temporary register の割り当てに関しては
特に GTS と GTX で差がないように見えます。つまりシェーダー
ユニットに対する、割り当て可能な register pool の割合はおそらく
同率になっているのでしょう。
GTS はシェーダーユニット数が少ないけれど、その分潤沢にレジスタを
使えるわけではありませんでした。
数値を取っていて気になったのは、思った以上に GTX が速いという
ことです。上のグラフには前々回の GTS の結果も重ねており、また
表には GTS 比でどれくらい速いのかも書き込んでみました。
だいたい 1.5倍程度 GTX の方が高速に動作している計算です。
こんなに差があったかな・・と思ってスペックを確認してみました。
・DirectX10 GPU メモ
stream processor shader clock memory clock mem-bus GeForce8800Ultra 128sp 1500MHz 1080MHz 384bit GeForce8800GTX 128sp 1350MHz 900MHz 384bit GeForce8800GTS 96sp 1200MHz 800MHz 320bit
Stream Processor の数で 約1.33 倍
Shader Clock の差で 1.125 倍
1.33 × 1.125 ≒ 1.50
ぴったり計算どおりでした。
シェーダーの実行速度に対して、メモリ速度は clock で 1.125倍、
bus 幅で 1.2倍、あわせて 1.35 倍です。
シェーダーの演算ではなくメモリが足を引っ張る状況では、GTS と
GTX の速度差はもっと小さくなります。
普段体感している速度差は、おそらくこちらの方が近いのではない
でしょうか。
例えば前回(昨日)
・Direct3D 10 Shader4.0 補間レジスタ数と速度の関係
のグラフをもう一度じっくり見てみます。

出力数が 11以上の右側では、GTS と GTX の差を計算してみると
ちょうど 1.5倍前後になっていることがわかります。
ここは上と同じで計算どおりなので、純粋にシェーダーの演算能力
で頭打ちになっているといえるでしょう。
それに比べて左側、11未満の結果では、GTS と GTX の差がほとんど
ありません。GTX の速度は GTS 比でわずか 1.06~1.1 倍程度です。
ほぼこれは Core や Shader、Memory 等の Clock 比 1.125倍に相当
すると考えられます。
つまりここでのボトルネックは、core か Shader Unit 内部に存在
する固定ユニットの実行速度なのでしょう。GTS でも GTX でも GPU
内にたぶん同一個数実装されていると推測できます。
もし仮にこれが 1.35倍に開いていたら、それはおそらくメモリが
足を引っ張っている部分です。
これらの結果から GTX は GTS と比較して、状況によって
1.125~1.5 倍高速に実行できます。メモリ速度を考えると、本当に
1.5倍の速度差がでるようなケースはそれほど多くないと思います。
また同じように GeForce8800Ultra で計算すると、GTS 比で次のよう
になります。
Shader 1.66倍
Memory 1.62倍
Clock 1.224~1.35
これは速いですね。メモリ速度も Shader 並みの比率を保っているので、
比較的1.6倍に近いスループットが期待できそうです。
2007/08/21
Direct3D 10 Shader4.0 補間レジスタ数と速度の関係
コードを書いていて気になったので少々実験してみました。
VertexShader または GeometryShader が出力する値はラスタライザ
(RasterizerStage) に渡ります。
PixelShader は各ピクセルごとに補間された値を受け取ります。
この VertexShader の出力数値の数と描画速度の関係を調べてみました。
ここでは GeometryShader は NULL にしています。
横軸がシェーダーで使用した出力レジスタの総数、
縦がかかった時間(usec)です。

・88GTS = GeForce8800GTS 640 の結果
・88GTX = GeForce8800GTX の結果
・GTS固定 = GeForce8800GTS で、出力レジスタ数を2個固定にし、
命令数だけ増やしていったもの。
88GTS で出力レジスタ数が2~7個の間特に変化がないのは、動作時間へ
与える影響が他の実行ボトルネックに隠れてしまったためと考えられます。
7~10 までの緩やかな上昇がそれ以前も継続していた可能性が高いです。
その緩やかな上昇の要因として、出力レジスタ数の増加だけではなく
実行する命令数そのものが増えたことも考慮しなければなりません。
そのため出力レジスタ数を2個固定にして、プログラムの長さ(slot数)
を 12~27 まで変化させて同じように速度を量ってみました。
それがグラフ中の「GTS固定」です。
これを見るとほぼ一定なので、やはり10個以前でも出力レジスタ数が
動作に何らかの影響を与えている可能性があります。
注目すべきところはやっぱり出力数 10を超えたところで生じる急激な
変化です。ハード的なバッファの限界、同時に実行可能な補間ユニット
の数、内部バス転送能力の限界、等が考えられます。
VertexShader そのものの命令数が増加して実行時間が長くなれば、
それだけ Rasterizer にも余裕ができます。当初は、その拮抗点が
ちょうど10個目の位置にあったのではないかと仮定しました。
だけど「GTS固定」の結果を見ると、この程度の命令数ではほとんど
実行に影響を与えていないことがわかります。
よって 10 はハード的なマジックナンバーである可能性が高いです。
なお、あとから GeForce8800GTX でも同等のテストを行ってみましたが、
10個を越えた時点で発生する大きな変化は一緒でした。
(グラフに加えました)
ShaderModel 毎に対応している補間レジスタ数は次のとおりです。
ShaderModel2.0
・clmap された COLOR0~1 (2個)
・TEXCOORD0~7 (8個)
・POSITION、FOG
ShaderModel3.0
・自由に割り振れる 12個 (o0~11)
ShaderModel4.0
・自由に割り振れる 16個 (o0~o15)
ShaderModel 2.0~3.0 では、たぶんほとんどのケースで出力レジスタ
数は 10個以内に収まってしまうでしょう。
GeForce68/78 など 3.0 当時の設計でも、仕様上の上限よりは若干低い
位置にハードウエア的な制限があったと考えられます。
GeForce8800 で従来のシェーダーも非常に高速なのは、もしかしたら
こんなところにも原因があるのかもしれません。
今回のテストの結論は、もし速度を追求するなら出力レジスタ数は
10個以内に収めましょう、ということです。
ただ、これはあくまでボトルネックになる得る条件の1つに過ぎないので、
通常はピクセル面積ももっと大きいし、VertexShader も長くなるしと
他の実行サイクルの影に隠れてあまり表に出てこない可能性があります。
相殺されている分には実質的な負荷ではないので、高速化を考える場合
はこのあたりのさじ加減が難しいですね。
VertexShader または GeometryShader が出力する値はラスタライザ
(RasterizerStage) に渡ります。
PixelShader は各ピクセルごとに補間された値を受け取ります。
この VertexShader の出力数値の数と描画速度の関係を調べてみました。
ここでは GeometryShader は NULL にしています。
横軸がシェーダーで使用した出力レジスタの総数、
縦がかかった時間(usec)です。

出力数 命令数 88GTS 88GTX 2 12 145 136 usec 3 13 150 130 4 15 147 138 5 16 148 140 6 17 148 134 7 18 148 140 8 19 157 135 9 20 164 135 10 21 173 135 11 22 335 221 12 23 347 229 13 24 360 237 14 25 376 248 15 26 398 263 16 27 410 271
・88GTS = GeForce8800GTS 640 の結果
・88GTX = GeForce8800GTX の結果
・GTS固定 = GeForce8800GTS で、出力レジスタ数を2個固定にし、
命令数だけ増やしていったもの。
88GTS で出力レジスタ数が2~7個の間特に変化がないのは、動作時間へ
与える影響が他の実行ボトルネックに隠れてしまったためと考えられます。
7~10 までの緩やかな上昇がそれ以前も継続していた可能性が高いです。
その緩やかな上昇の要因として、出力レジスタ数の増加だけではなく
実行する命令数そのものが増えたことも考慮しなければなりません。
そのため出力レジスタ数を2個固定にして、プログラムの長さ(slot数)
を 12~27 まで変化させて同じように速度を量ってみました。
それがグラフ中の「GTS固定」です。
これを見るとほぼ一定なので、やはり10個以前でも出力レジスタ数が
動作に何らかの影響を与えている可能性があります。
注目すべきところはやっぱり出力数 10を超えたところで生じる急激な
変化です。ハード的なバッファの限界、同時に実行可能な補間ユニット
の数、内部バス転送能力の限界、等が考えられます。
VertexShader そのものの命令数が増加して実行時間が長くなれば、
それだけ Rasterizer にも余裕ができます。当初は、その拮抗点が
ちょうど10個目の位置にあったのではないかと仮定しました。
だけど「GTS固定」の結果を見ると、この程度の命令数ではほとんど
実行に影響を与えていないことがわかります。
よって 10 はハード的なマジックナンバーである可能性が高いです。
なお、あとから GeForce8800GTX でも同等のテストを行ってみましたが、
10個を越えた時点で発生する大きな変化は一緒でした。
(グラフに加えました)
ShaderModel 毎に対応している補間レジスタ数は次のとおりです。
ShaderModel2.0
・clmap された COLOR0~1 (2個)
・TEXCOORD0~7 (8個)
・POSITION、FOG
ShaderModel3.0
・自由に割り振れる 12個 (o0~11)
ShaderModel4.0
・自由に割り振れる 16個 (o0~o15)
ShaderModel 2.0~3.0 では、たぶんほとんどのケースで出力レジスタ
数は 10個以内に収まってしまうでしょう。
GeForce68/78 など 3.0 当時の設計でも、仕様上の上限よりは若干低い
位置にハードウエア的な制限があったと考えられます。
GeForce8800 で従来のシェーダーも非常に高速なのは、もしかしたら
こんなところにも原因があるのかもしれません。
今回のテストの結論は、もし速度を追求するなら出力レジスタ数は
10個以内に収めましょう、ということです。
ただ、これはあくまでボトルネックになる得る条件の1つに過ぎないので、
通常はピクセル面積ももっと大きいし、VertexShader も長くなるしと
他の実行サイクルの影に隠れてあまり表に出てこない可能性があります。
相殺されている分には実質的な負荷ではないので、高速化を考える場合
はこのあたりのさじ加減が難しいですね。
2007/08/19
Direct3D 10 Shader4.0 ループと最適化
Direct3D 10 の Shader4.0 は、実行可能な命令スロットも多いし
整数型も整数演算も扱えるし、HLSL を使うと Shader であることを
忘れそうになります。
条件分岐やループ命令等もそのまま記述し、当たり前のように実行
できるようになりました。
例えばまったく意味のない内容ですが、VertexShader の最後に
わざとこんなコードを書いてみます。
描画するデータは次のとおり。
・Teapot (Maxのプリミティブ)
・ポリゴン数 57600
・頂点数 29646
Pixel の影響をできるだけ受けないように、PixelShader は固定の
定数を return するだけとし、描画面積も点に近い状態でテストします。
実行するとさすがに時間がかかって、GPU 時間で 1410 usec ほど
消費しました。(GeForce8800GTS 640)
コンパイルされたコードはこんな感じです。命令コードに loop 命令が
あって、本当に Shader 内でループ実行されていることがわかります。
元のソースコードに loop 展開のアトリビュートを次のように追加すると
145 usec と実行時間が一気に 1/10 になりました。
出力コードを見てみると・・
当たり前です。これは元の例題が悪かったです。
ただの積和なので畳み込まれてしまいました。
ここまできちんとオプティマイズかかるんですね。
逆に attribute が [loop] (または無指定) だと意味のない演算でも
そのままループに展開されてしまうわけです。
ほんのわずかに複雑なコードにしてみます。
これでもまだ法則性があるので最適化の余地があります。
[loop] で 20slot、[unroll] で 409slot の命令になります。
速度は2倍差ほど。
[unroll] だとこんな感じに展開されています。
さらに法則性を取り除きます。
これだとおそらく [unroll] でも極端な差が出ないと予想できます。
逆転しました。
[unroll] 側の時間増加が極端なので何か他に原因がありそうです。
使用する命令スロット数の増加もペナルティがあるのかもしれません。
ループ回数を変えて計測してみました。

80 と 120 前後で大きな変化があるようです。slot 数でいえば
234~399のあたりです。
この数値を目安にして別の演算でも同じ傾向が出るか確認してみます。
これも unroll でリニアなコードに展開されます。

きれいなリニアです。命令は 1000slot 超えても問題ないし、
しかも unroll の方が速いし、命令スロット数はまったく影響を
与えていないように見えます。
[unroll] で遅くなる先ほどの shader が、unroll すると意外に
temporary register を消費していることがわかりました。

80~120 前後での急激な負荷上昇と一致します。どうやら速度低下の
原因は、セオリー通り temporary register 数だったようです。
単なるループ展開だと思って見落としていました。
結論は、[unroll] の方が高速。だけど最適化によって temp register
が増えてしまうくらいなら素直に [loop] した方がまし。
unroll するけど積極的な最適化をしない attribute もあると
もう少し違ってくる可能性があります。
最適化レベルの /O0~/O3 はとくに変化が見られませんでした。
[unroll] かつ /Od が一番近い結果になりますが、実行すると差が
でません。内部で最適化かかってしまっていたのか、切り替えがうまく
機能していなかったのか、本当に同じ速度だったのか、
まだまだ調べる余地がありそうです。
整数型も整数演算も扱えるし、HLSL を使うと Shader であることを
忘れそうになります。
条件分岐やループ命令等もそのまま記述し、当たり前のように実行
できるようになりました。
例えばまったく意味のない内容ですが、VertexShader の最後に
わざとこんなコードを書いてみます。
Out.Normal= 0; for( int i= 0 ; i< 200 ; i++ ){ Out.Normal+= In.Normal; Out.Normal+= wpos; }
描画するデータは次のとおり。
・Teapot (Maxのプリミティブ)
・ポリゴン数 57600
・頂点数 29646
Pixel の影響をできるだけ受けないように、PixelShader は固定の
定数を return するだけとし、描画面積も点に近い状態でテストします。
実行するとさすがに時間がかかって、GPU 時間で 1410 usec ほど
消費しました。(GeForce8800GTS 640)
コンパイルされたコードはこんな感じです。命令コードに loop 命令が
あって、本当に Shader 内でループ実行されていることがわかります。
vs_4_0 dcl_input v0.xyz dcl_input v1.xyz dcl_output_siv o0.xyzw , position dcl_output o1.xyz dcl_constantbuffer cb0[8], immediateIndexed dcl_constantbuffer cb1[3], immediateIndexed dcl_temps 3 mov r0.xyz, v0.xyzx mov r0.w, l(1.000000) dp4 r1.x, cb1[0].xyzw, r0.xyzw dp4 r1.y, cb1[1].xyzw, r0.xyzw dp4 r1.z, cb1[2].xyzw, r0.xyzw mul r0.xyzw, r1.yyyy, cb0[5].xyzw mad r0.xyzw, cb0[4].xyzw, r1.xxxx, r0.xyzw mad r0.xyzw, cb0[6].xyzw, r1.zzzz, r0.xyzw add o0.xyzw, r0.xyzw, cb0[7].xyzw mov r0.xyzw, l(0,0,0,0) loop ige r1.w, r0.w, l(200) breakc_nz r1.w add r2.xyz, r0.xyzx, v1.xyzx add r0.xyz, r1.xyzx, r2.xyzx iadd r0.w, r0.w, l(1) endloop mov o1.xyz, r0.xyzx ret // Approximately 19 instruction slots used
元のソースコードに loop 展開のアトリビュートを次のように追加すると
Out.Normal= 0; [unroll] // attribute for( int i= 0 ; i< 200 ; i++ ){ Out.Normal+= In.Normal; Out.Normal+= wpos; }
145 usec と実行時間が一気に 1/10 になりました。
出力コードを見てみると・・
vs_4_0 dcl_input v0.xyz dcl_input v1.xyz dcl_output_siv o0.xyzw , position dcl_output o1.xyz dcl_constantbuffer cb0[8], immediateIndexed dcl_constantbuffer cb1[3], immediateIndexed dcl_temps 3 mov r0.xyz, v0.xyzx mov r0.w, l(1.000000) dp4 r1.y, cb1[1].xyzw, r0.xyzw mul r2.xyzw, r1.yyyy, cb0[5].xyzw dp4 r1.x, cb1[0].xyzw, r0.xyzw dp4 r1.z, cb1[2].xyzw, r0.xyzw mad r0.xyzw, cb0[4].xyzw, r1.xxxx, r2.xyzw mad r0.xyzw, cb0[6].xyzw, r1.zzzz, r0.xyzw add o0.xyzw, r0.xyzw, cb0[7].xyzw mul r0.xyz, v1.xyzx, l(200.000000, 200.000000, 200.000000, 0.000000) mad o1.xyz, r1.xyzx, l(200.000000, 200.000000, 200.000000, 0.000000), r0.xyzx ret // Approximately 12 instruction slots used
当たり前です。これは元の例題が悪かったです。
ただの積和なので畳み込まれてしまいました。
ここまできちんとオプティマイズかかるんですね。
逆に attribute が [loop] (または無指定) だと意味のない演算でも
そのままループに展開されてしまうわけです。
ほんのわずかに複雑なコードにしてみます。
for( int i= 0 ; i< 200 ; i++ ){ Out.Normal+= Temp[i&3]; Out.Normal+= wpos; }
これでもまだ法則性があるので最適化の余地があります。
[loop] で 20slot、[unroll] で 409slot の命令になります。
速度は2倍差ほど。
[loop] 20 slot 1652 usec [unroll] 409 slot 806 usec
[unroll] だとこんな感じに展開されています。
add r0.xyz, r0.xyzx, cb1[0].xyzx add r0.xyz, r1.xyzx, r0.xyzx add r0.xyz, r0.xyzx, cb1[1].xyzx add r0.xyz, r1.xyzx, r0.xyzx add r0.xyz, r0.xyzx, cb1[2].xyzx add r0.xyz, r1.xyzx, r0.xyzx add r0.xyz, r0.xyzx, cb1[3].xyzx :
さらに法則性を取り除きます。
for( int i= 0 ; i< 200 ; i++ ){ Out.Normal+= Temp[(int)(Pos.x*i)&3]; Out.Normal+= wpos; }
これだとおそらく [unroll] でも極端な差が出ないと予想できます。
逆転しました。
[loop] 23 slot 3296 usec [unroll] 564 slot 12786 usec
[unroll] 側の時間増加が極端なので何か他に原因がありそうです。
使用する命令スロット数の増加もペナルティがあるのかもしれません。
ループ回数を変えて計測してみました。

loop u-slot [unroll] l-slot [loop] 40 124 565 23 651 usec 60 179 866 23 967 80 234 1488 23 1284 100 289 2653 23 1600 120 344 3320 23 1915 140 399 5810 23 2232 160 454 8430 23 2558 180 509 10730 23 2882 200 564 12786 23 3296
80 と 120 前後で大きな変化があるようです。slot 数でいえば
234~399のあたりです。
この数値を目安にして別の演算でも同じ傾向が出るか確認してみます。
これも unroll でリニアなコードに展開されます。
Out.Normal= 0; for( int i= 0 ; i< 100 ; i++ ){ Out.Normal+= pow( i, In.Pos.x ); }

loop u-slot [unroll] [loop] 100 159 252 843 usec 200 309 532 1656 300 458 824 2482 400 609 1130 3323 500 759 1400 4263 600 909 1686 5120 700 1059 1988 5970 800 1209 2290 6678
きれいなリニアです。命令は 1000slot 超えても問題ないし、
しかも unroll の方が速いし、命令スロット数はまったく影響を
与えていないように見えます。
[unroll] で遅くなる先ほどの shader が、unroll すると意外に
temporary register を消費していることがわかりました。

loop slot time temp [unroll] 40 124 565 5 [unroll] 60 179 866 7 [unroll] 80 234 1488 12 [unroll] 100 289 2653 15 [unroll] 120 344 3320 17 [unroll] 140 399 5810 18 [unroll] 160 454 8430 18 [unroll] 180 509 10730 18 [unroll] 200 564 12786 18
80~120 前後での急激な負荷上昇と一致します。どうやら速度低下の
原因は、セオリー通り temporary register 数だったようです。
単なるループ展開だと思って見落としていました。
結論は、[unroll] の方が高速。だけど最適化によって temp register
が増えてしまうくらいなら素直に [loop] した方がまし。
unroll するけど積極的な最適化をしない attribute もあると
もう少し違ってくる可能性があります。
最適化レベルの /O0~/O3 はとくに変化が見られませんでした。
[unroll] かつ /Od が一番近い結果になりますが、実行すると差が
でません。内部で最適化かかってしまっていたのか、切り替えがうまく
機能していなかったのか、本当に同じ速度だったのか、
まだまだ調べる余地がありそうです。
2007/08/18
Direct3D9 フレームバッファの保存
昔のものですが必要になったのでメモ。D3D9 です。
レンダリングした結果を保存するには
・BackBuffer を Lock して読み込んで保存
ですが、そのままでは RenderTarget は Lock できないので、
同じフォーマットかつ Lock 可能なサーフェースを作ってコピー。
16F は扱いづらいので、変換が必要なときは 32F にコンバート。
レンダリングした結果を保存するには
・BackBuffer を Lock して読み込んで保存
ですが、そのままでは RenderTarget は Lock できないので、
同じフォーマットかつ Lock 可能なサーフェースを作ってコピー。
IDirect3DSurface9* iBackBuffer= NULL; iDevice->GetBackBuffer( 0, 0, &iBackBuffer ); // copy先作成 D3DFORMAT sformat= D3DFMT_A8R8G8B8; //D3DFORMAT sformat= D3DFMT_A16B16G16R16F; IDirect3DSurface9* iSurface= NULL; iDevice->CreateRenderTarget( width, height, sformat, D3DMULTISAMPLE_NONE, 0, TRUE, // lockable &iSurface ); // copy iDevice->StretchRect( iBackBuffer, NULL, iSurface, NULL, D3DTEXF_NONE ); // 読み込み D3DLOCKED_RECT rect; iSurface->LockRect( &rect, NULL, D3DLOCK_READONLY ); void* ptr= rect.pBits; // ... 読み込み iSurface->UnlockRect(); iSurface->Release(); iBackBuffer->Release();
16F は扱いづらいので、変換が必要なときは 32F にコンバート。
2007/08/17
Direct3D 10 ウィンドウのリサイズ (ResizeBuffers)
Direct3D 10 では Window のリサイズが比較的簡単にできます。
もちろん何もしない状態でも、DirectX9 と同じようにサイズの異なる
FrontBuffer に対して BackBuffer を拡縮 Copy します。
よってそのままでもウィンドウのリサイズが可能です。
Direct3D 10 の場合、さらに BackBuffer のサイズも容易に追従
させることができます。
サイズの変更は IDXGISwapChain の ResizeBuffer() です。
SwapChain が作成した Buffer のリサイズは行いますが、Buffer を
参照しているオブジェクトがあると同時には解決できません。
それらのオブジェクトはあらかじめ Release() しておく必要があります。
通常は BackBuffer から ID3D10RenderTargetView を作りますので、
RenderTargetView は先に開放しておきます。
また OMSetRenderTarget() で同時に DepthStencilView を登録する
場合、RenderTargetView とサイズが異なると受け付けてくれません。
結局 DepthStencilView も作り直す必要があります。
まずは Release()
次にリサイズ。サイズに 0, 0 を渡すと、Window の ClientRect
にあわせてくれます。(ここでは WindowMode のみ対象にしています)
必要なバッファを作り直します。この手順は最初の初期化時と全く
同じなので、共通化しておいたほうが良いです。
BackBuffer と DepthStencil の作成
忘れてはならないのが Viewport の設定です。
またここでは書いていませんが、ProjectionMatrix も再計算が必要です。
WM_SIZE でサイズが変わったことを検知したら、これらの処理を
呼び出します。アイコン化最小化などもきちんと区別しておかないと、
サイズ (0, 0) に対してリサイズが発生してしまうので注意。
またリサイズによって VRAM が足りなくなり、速度が極端に低下する
可能性も考えられます。場合によっては、極端な速度低下を検出したら
元のサイズに戻す処理も入れた方が良いのかもしれません。
もちろん何もしない状態でも、DirectX9 と同じようにサイズの異なる
FrontBuffer に対して BackBuffer を拡縮 Copy します。
よってそのままでもウィンドウのリサイズが可能です。
Direct3D 10 の場合、さらに BackBuffer のサイズも容易に追従
させることができます。
サイズの変更は IDXGISwapChain の ResizeBuffer() です。
SwapChain が作成した Buffer のリサイズは行いますが、Buffer を
参照しているオブジェクトがあると同時には解決できません。
それらのオブジェクトはあらかじめ Release() しておく必要があります。
通常は BackBuffer から ID3D10RenderTargetView を作りますので、
RenderTargetView は先に開放しておきます。
また OMSetRenderTarget() で同時に DepthStencilView を登録する
場合、RenderTargetView とサイズが異なると受け付けてくれません。
結局 DepthStencilView も作り直す必要があります。
まずは Release()
ID3D10RenderTargetView* iRTV= NULL; iDevice->OMSetRenderTarget( 1, &iRTV, NULL ); // NULL, NULL の設定 iRenderTargetView->Release(); iDepthStencilView->Release();
次にリサイズ。サイズに 0, 0 を渡すと、Window の ClientRect
にあわせてくれます。(ここでは WindowMode のみ対象にしています)
iSwapChain->ResizeBuffers( 2, 0, 0, // ClientRect を参照する SwapChainDesc.BufferDesc.Format, // Format は DESC から 0 );
必要なバッファを作り直します。この手順は最初の初期化時と全く
同じなので、共通化しておいたほうが良いです。
BackBuffer と DepthStencil の作成
// back buffer ID3D10Texture2D* iBackBuffer= NULL; iSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), reinterpret_cast<void**>( &iBackBuffer ) ); iDevice->CreateRenderTargetView( iBackBuffer, NULL, &iRenderTargetView ); iBackBuffer->Release(); // depth stencil buffer ID3D10Texture2D* iTexture= NULL; D3D10_TEXTURE2D_DESC tex2ddesc= { width, height, 1, 1, // width, height, mip, array DXGI_FORMAT_D24_UNORM_S8_UINT, // format { 1, 0, }, // sample D3D10_USAGE_DEFAULT, D3D10_BIND_DEPTH_STENCIL, 0, // cpu flags 0 // misc flags }; iDevice->CreateTexture2D( &tex2ddesc, NULL, &iTexture ); D3D10_DEPTH_STENCIL_VIEW_DESC vdesc= { DXGI_FORMAT_D24_UNORM_S8_UINT, D3D10_DSV_DIMENSION_TEXTURE2D }; iDevice->CreateDepthStencilView( iTexture, &vdesc, &iDepthStencilView ); iTexture->Release(); g_iDevice->OMSetRenderTargets( 1, &g_iRenderTargetView, g_iDepthStencilView );
忘れてはならないのが Viewport の設定です。
またここでは書いていませんが、ProjectionMatrix も再計算が必要です。
// viewport D3D10_VIEWPORT viewport= { 0, 0, width, height, 0.0f, 1.0f }; iDevice->RSSetViewports( 1, &viewport );
WM_SIZE でサイズが変わったことを検知したら、これらの処理を
呼び出します。
サイズ (0, 0) に対してリサイズが発生してしまうので注意。
またリサイズによって VRAM が足りなくなり、速度が極端に低下する
可能性も考えられます。場合によっては、極端な速度低下を検出したら
元のサイズに戻す処理も入れた方が良いのかもしれません。
2007/08/16
D3D関連 DDSテクスチャの取り扱い
昔に比べたら扱えるツールもかなり増えました。
DDS 形式のデータの取り扱いも、もうさほど困ることがないとは
思いますが、一応手に入りやすい DDS 関連ツールのまとめを
作ってみました。
・DDSテクスチャの表示&作成ツールの解説
ただ、Maya とか CGツール系も DDS 自体には対応したものの、扱える
フォーマットがそれぞれ微妙に食い違っていたり制限があったりします。
この辺はなかなか厄介です。
さらに今後 Direct3D10 向けの画像データをどう扱うのかが、
問題になってきそうです。
例えばこの blog でも過去に書いてますが、テクスチャ画素の RGB の
並びが Direct3D 10 では逆順になりました。
ベクトル系データの x y z w にあわせて統一するためです。
従来 A R G B だった色の並びが A B G R になります。
・Direct3D もうひとつのユニファイド
ちなみにメモリ上ではリトルエンディアンなので、従来 B G R A と
並んでいたものが R G B A になります。
ある意味自然な配列になりました。
DXGI_FORMAT での表記も R G B A 順となっています。
D3D10 専用フォーマットが登場したとしても、ツール側での対応はまた
しばらく時間がかかりそうですね。
DDS 形式のデータの取り扱いも、もうさほど困ることがないとは
思いますが、一応手に入りやすい DDS 関連ツールのまとめを
作ってみました。
・DDSテクスチャの表示&作成ツールの解説
ただ、Maya とか CGツール系も DDS 自体には対応したものの、扱える
フォーマットがそれぞれ微妙に食い違っていたり制限があったりします。
この辺はなかなか厄介です。
さらに今後 Direct3D10 向けの画像データをどう扱うのかが、
問題になってきそうです。
例えばこの blog でも過去に書いてますが、テクスチャ画素の RGB の
並びが Direct3D 10 では逆順になりました。
ベクトル系データの x y z w にあわせて統一するためです。
従来 A R G B だった色の並びが A B G R になります。
・Direct3D もうひとつのユニファイド
ちなみにメモリ上ではリトルエンディアンなので、従来 B G R A と
並んでいたものが R G B A になります。
ある意味自然な配列になりました。
DXGI_FORMAT での表記も R G B A 順となっています。
D3D10 専用フォーマットが登場したとしても、ツール側での対応はまた
しばらく時間がかかりそうですね。
2007/08/13
Direct3D 10 ss00 サンプルの解説 (2)
前回の続きで、サンプル ss00 の簡単なソース解説を続けます。
・Direct3D 10 ss00 サンプルの解説 (1)
ソースはこちらからどうぞ
・HYPERでんち
●ウィンドウ作成 WinMain()
Windows のアプリケーションは Window を作成する必要があります。
簡単にするために、WinMain() 内部でウィンドウの作成も
メッセージループも記述してしまっています。
フレームレートの調整やウエイト等も一切せずにループしているので
ご注意ください。(一応 Sleep が入っています)
●描画 Render()
最初にフレームバッファをクリアします。
float color[4]= {
0.0f, 0.0f, 1.0f, 0.0f
};
g_iDevice->ClearRenderTargetView( g_iRenderTargetView, color );
g_iDevice->ClearDepthStencilView( g_iDepthStencilView,
D3D10_CLEAR_DEPTH|D3D10_CLEAR_STENCIL, 1.0f, 0 );
Direct3D9 以前までは Clear() 命令ひとつで行われていた部分です。
D3D10 では、RenderTarget と Depth Buffer それぞれ個別にクリアします。
またカラー値が R8G8B8A8_UINT ではなく R32G32B32A32_FLOAT による
指定が可能となりました。
Direct3D9 では HDR 値の初期化には Shader を使う等の別の手段が必要でした。
サンプルではデモのためにカメラを回転しているのでその計算が若干入ります。
固定ならば Effect に対して Matrix を書き込んで終わりです。
g_iEffect->GetVariableByName( "WorldToView" )->AsMatrix()->SetMatrix(
(float*)&view );
ID3D10Effect のパラメータ類を反映させるために
g_iEffect->GetTechniqueByIndex( 0 )->GetPassByIndex( 0 )->Apply( 0 );
を実行します。Effect 内に Technique が1つしかないこと、Pass も 1つ
しかないことがわかっているなら、これだけでも十分動きます。
Direct3D10 では初期値が未登録で、描画前に必ず設定しなければならないのが
この IASetPrimitiveTopology() でした。
オーソドックスな TRIANGLELIST にします。
g_iDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
このシェーダーは頂点バッファもインデックスバッファも不要なので、
いきなり描画です。
g_iDevice->DrawInstanced( 36, 12, 0, 0 );
Cube なので Triangle ×2× 6面分=36頂点、さらに 12個のインスタンスを
描画しています。
最後は g_iSwapChain->Present( 0, 0 ); です。
Direct3D9 以前の BeginScene()~EndScene() 系が必要ないので、
D3D10 では非常にすっきりしています。
Matrix 計算、ID3DEffect への変数アクセス以外には、描画のために 5つの API
しか呼び出していません。
・Direct3D 10 ss00 サンプルの解説 (1)
ソースはこちらからどうぞ
・HYPERでんち
●ウィンドウ作成 WinMain()
Windows のアプリケーションは Window を作成する必要があります。
簡単にするために、WinMain() 内部でウィンドウの作成も
メッセージループも記述してしまっています。
フレームレートの調整やウエイト等も一切せずにループしているので
ご注意ください。(一応 Sleep が入っています)
●描画 Render()
最初にフレームバッファをクリアします。
float color[4]= {
0.0f, 0.0f, 1.0f, 0.0f
};
g_iDevice->ClearRenderTargetView( g_iRenderTargetView, color );
g_iDevice->ClearDepthStencilView( g_iDepthStencilView,
D3D10_CLEAR_DEPTH|D3D10_CLEAR_STENCIL, 1.0f, 0 );
Direct3D9 以前までは Clear() 命令ひとつで行われていた部分です。
D3D10 では、RenderTarget と Depth Buffer それぞれ個別にクリアします。
またカラー値が R8G8B8A8_UINT ではなく R32G32B32A32_FLOAT による
指定が可能となりました。
Direct3D9 では HDR 値の初期化には Shader を使う等の別の手段が必要でした。
サンプルではデモのためにカメラを回転しているのでその計算が若干入ります。
固定ならば Effect に対して Matrix を書き込んで終わりです。
g_iEffect->GetVariableByName( "WorldToView" )->AsMatrix()->SetMatrix(
(float*)&view );
ID3D10Effect のパラメータ類を反映させるために
g_iEffect->GetTechniqueByIndex( 0 )->GetPassByIndex( 0 )->Apply( 0 );
を実行します。Effect 内に Technique が1つしかないこと、Pass も 1つ
しかないことがわかっているなら、これだけでも十分動きます。
Direct3D10 では初期値が未登録で、描画前に必ず設定しなければならないのが
この IASetPrimitiveTopology() でした。
オーソドックスな TRIANGLELIST にします。
g_iDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
このシェーダーは頂点バッファもインデックスバッファも不要なので、
いきなり描画です。
g_iDevice->DrawInstanced( 36, 12, 0, 0 );
Cube なので Triangle ×2× 6面分=36頂点、さらに 12個のインスタンスを
描画しています。
最後は g_iSwapChain->Present( 0, 0 ); です。
Direct3D9 以前の BeginScene()~EndScene() 系が必要ないので、
D3D10 では非常にすっきりしています。
Matrix 計算、ID3DEffect への変数アクセス以外には、描画のために 5つの API
しか呼び出していません。
2007/08/12
Direct3D 10 ss00 サンプルの解説 (1)
以前掲載した D3D10 のサンプルプログラム ss00 を少々解説します。
ss00.cpp 以外にライブラリやら DXUT も使わず、できるだけ
Direct3D 10 の API だけで済むようコードを減らしていったものです。
それでも初期化は思ったより長い関数になりました。
・Direct3D 10 シェーダー4.0サンプルプログラム
あらためてゼロから書いてみると結構手間がかかりますね。
それでも DirectX3~6 あたりまでと比べると、シェーダーが必須になった
だけで、初期化自体は簡単になっているはずです。
ソースはこちらからどうぞ
・HYPERでんち
動作には Vista + DirectX10 対応 GPU が必要です。
ss00.cpp
●初期化 InitDevice()
InitDevice() は初期化を行います。
Direct3D10 で最初に必要なインターフェースは次の2つです。
・ID3D10Device
・IDXGISwapChain
この2つは名称が異なるものの、D3D10CreateDeviceAndSwapChain()
関数一発で簡単に作ることができます。
・D3D10CreateDeviceAndSwapChain()
DirectX9 以前のように、Device の前に IDirect3D を作る必要がなくなりました。
IDirect3D が行っていた Display 周りの選択や制御が DXGI に移行した形と
なっています。
D3DX10CreateDeviceAndSwapChain() 作成時に与えるパラメータは、
Direct3D9 の D3DPRESENT_PARAMETERS とほとんど同じです。
・D3DPRESENT_PARAMETERS
ここは Direct3D9 のコードを基にしていても比較的容易に対応できる
部分でしょう。
フレームバッファのサイズ、リフレッシュレート、フレームバッファのフォーマット、
フロントバッファへ反映させるときのエフェクトやタイミング、フルスクリーン
かどうか、などを与えています。
DXGI_SWAP_CHAIN_DESC swapdesc= {
{
width, height, // フレームバッファサイズ
{ 60, 1, }, // リフレッシュレート
DXGI_FORMAT_R8G8B8A8_UNORM, // フォーマット
DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
DXGI_MODE_SCALING_UNSPECIFIED,
},
{ 1, 0, }, // sample
DXGI_USAGE_RENDER_TARGET_OUTPUT,
1,
hwnd, // Window ハンドル
winmode, // WindowMode か FullScreen か
DXGI_SWAP_EFFECT_DISCARD,
0
};
D3D10CreateDeviceAndSwapChain(
NULL,
D3D10_DRIVER_TYPE_HARDWARE,
NULL,
D3D10_CREATE_DEVICE_DEBUG,
D3D10_SDK_VERSION,
&swapdesc,
&g_iSwapChain, // IDXGISwapChain
&g_iDevice // ID3D10Device
);
ID3D10Device と IDXGISwapChain ができたら View を作ります。
この View は Direct3D10 になって登場した新しい概念です。
メモリ上のテクスチャ空間に対して、実際にどのような手段で Shader が
アクセスするのか決定します。
リソースの自由度が上がった分、具体的にどのような使い方をするのか
ひと手間必要になったわけですね。
IDXGISwapChain から BackBuffer を取り出し、Direct3D からアクセスするための
ID3D10RenderTargetView を作成します。
ID3D10Texture2D* iBackBuffer= NULL;
g_iSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ),
reinterpret_cast<void**>( &iBackBuffer ) );
g_iDevice->CreateRenderTargetView( iBackBuffer, NULL, &g_iRenderTargetView );
ZRELEASE( iBackBuffer );
次に Depth Buffer を作成しています。
ここでは何も考えずに Stencil のない D3D10 新フォーマットと思われる
D32_FLOAT を使っています。
ID3D10Texture2D* iTexture= NULL;
D3D10_TEXTURE2D_DESC tex2ddesc= {
width, height, 1, 1, // width, height, mip, array
DXGI_FORMAT_D32_FLOAT, // format
{ 1, 0, }, // sample
D3D10_USAGE_DEFAULT,
D3D10_BIND_DEPTH_STENCIL,
0, // cpu flags
0 // misc flags
};
g_iDevice->CreateTexture2D( &tex2ddesc, NULL, &iTexture );
D3D10_DEPTH_STENCIL_VIEW_DESC vdesc= {
DXGI_FORMAT_D32_FLOAT,
D3D10_DSV_DIMENSION_TEXTURE2D
};
g_iDevice->CreateDepthStencilView( iTexture, &vdesc, &g_iDepthStencilView );
ZRELEASE( iTexture );
初期化はこれでおしまいです。
ここからは実際にレンダリングに必要な設定を行います。
今回は非常に単純な描画なので、特に毎フレーム実行しなくても良い処理を
初期化の部分に書いてしまっています。
シェーダーの生成以外の部分、パラメータ設定関連は、必要であれば1フレーム
の描画中に何回か書き換えることになるでしょう。
// レンダーターゲットの設定
g_iDevice->OMSetRenderTargets( 1,
&g_iRenderTargetView, g_iDepthStencilView );
// ビューポートの設定
D3D10_VIEWPORT viewport= {
0, 0, width, height, 0.0f, 1.0f
};
g_iDevice->RSSetViewports( 1, &viewport );
これら関数はどちらも一度に複数個登録できます。今回は1つだけなので
定数 1 を最初に渡しています。
// shader の作成
ID3D10Blob* iblob= NULL;
if( FAILED( D3DX10CreateEffectFromFile(
TEXT("cube.fx"),
NULL, NULL, "fx_4_0", 0, 0, g_iDevice,
NULL, NULL, &g_iEffect, &iblob, NULL ) ) ){
OutputDebugStringA( reinterpret_cast<char*>(
iblob->GetBufferPointer() ) ); // エラーが発生したら表示
ZRELEASE( iblob );
}
ここでは fx ファイルから Shader を作成しています。
今回のサンプルでは ID3D10Effect をそのまま使っています。
HLSL のコンパイル時にはエラーが発生することがあるので、
コンパイラのエラーメッセージをそのまま表示出力しています。
最後に Effect の変数を初期化しています。
// projection の設定
D3DXMATRIX projection;
D3DXMatrixPerspectiveFovLH( &projection, 3.141592f/3.0f,
WINDOW_SIZE_WIDTH/(float)WINDOW_SIZE_HEIGHT, 1.0f, 10000.0f );
g_iEffect->GetVariableByName( "Projection" )->AsMatrix()->SetMatrix( (float*)&projection );
次回は残りの処理と描画です。
ss00.cpp 以外にライブラリやら DXUT も使わず、できるだけ
Direct3D 10 の API だけで済むようコードを減らしていったものです。
それでも初期化は思ったより長い関数になりました。
・Direct3D 10 シェーダー4.0サンプルプログラム
あらためてゼロから書いてみると結構手間がかかりますね。
それでも DirectX3~6 あたりまでと比べると、シェーダーが必須になった
だけで、初期化自体は簡単になっているはずです。
ソースはこちらからどうぞ
・HYPERでんち
動作には Vista + DirectX10 対応 GPU が必要です。
ss00.cpp
●初期化 InitDevice()
InitDevice() は初期化を行います。
Direct3D10 で最初に必要なインターフェースは次の2つです。
・ID3D10Device
・IDXGISwapChain
この2つは名称が異なるものの、D3D10CreateDeviceAndSwapChain()
関数一発で簡単に作ることができます。
・D3D10CreateDeviceAndSwapChain()
DirectX9 以前のように、Device の前に IDirect3D を作る必要がなくなりました。
IDirect3D が行っていた Display 周りの選択や制御が DXGI に移行した形と
なっています。
D3DX10CreateDeviceAndSwapChain() 作成時に与えるパラメータは、
Direct3D9 の D3DPRESENT_PARAMETERS とほとんど同じです。
・D3DPRESENT_PARAMETERS
ここは Direct3D9 のコードを基にしていても比較的容易に対応できる
部分でしょう。
フレームバッファのサイズ、リフレッシュレート、フレームバッファのフォーマット、
フロントバッファへ反映させるときのエフェクトやタイミング、フルスクリーン
かどうか、などを与えています。
DXGI_SWAP_CHAIN_DESC swapdesc= {
{
width, height, // フレームバッファサイズ
{ 60, 1, }, // リフレッシュレート
DXGI_FORMAT_R8G8B8A8_UNORM, // フォーマット
DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
DXGI_MODE_SCALING_UNSPECIFIED,
},
{ 1, 0, }, // sample
DXGI_USAGE_RENDER_TARGET_OUTPUT,
1,
hwnd, // Window ハンドル
winmode, // WindowMode か FullScreen か
DXGI_SWAP_EFFECT_DISCARD,
0
};
D3D10CreateDeviceAndSwapChain(
NULL,
D3D10_DRIVER_TYPE_HARDWARE,
NULL,
D3D10_CREATE_DEVICE_DEBUG,
D3D10_SDK_VERSION,
&swapdesc,
&g_iSwapChain, // IDXGISwapChain
&g_iDevice // ID3D10Device
);
ID3D10Device と IDXGISwapChain ができたら View を作ります。
この View は Direct3D10 になって登場した新しい概念です。
メモリ上のテクスチャ空間に対して、実際にどのような手段で Shader が
アクセスするのか決定します。
リソースの自由度が上がった分、具体的にどのような使い方をするのか
ひと手間必要になったわけですね。
IDXGISwapChain から BackBuffer を取り出し、Direct3D からアクセスするための
ID3D10RenderTargetView を作成します。
ID3D10Texture2D* iBackBuffer= NULL;
g_iSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ),
reinterpret_cast<void**>( &iBackBuffer ) );
g_iDevice->CreateRenderTargetView( iBackBuffer, NULL, &g_iRenderTargetView );
ZRELEASE( iBackBuffer );
次に Depth Buffer を作成しています。
ここでは何も考えずに Stencil のない D3D10 新フォーマットと思われる
D32_FLOAT を使っています。
ID3D10Texture2D* iTexture= NULL;
D3D10_TEXTURE2D_DESC tex2ddesc= {
width, height, 1, 1, // width, height, mip, array
DXGI_FORMAT_D32_FLOAT, // format
{ 1, 0, }, // sample
D3D10_USAGE_DEFAULT,
D3D10_BIND_DEPTH_STENCIL,
0, // cpu flags
0 // misc flags
};
g_iDevice->CreateTexture2D( &tex2ddesc, NULL, &iTexture );
D3D10_DEPTH_STENCIL_VIEW_DESC vdesc= {
DXGI_FORMAT_D32_FLOAT,
D3D10_DSV_DIMENSION_TEXTURE2D
};
g_iDevice->CreateDepthStencilView( iTexture, &vdesc, &g_iDepthStencilView );
ZRELEASE( iTexture );
初期化はこれでおしまいです。
ここからは実際にレンダリングに必要な設定を行います。
今回は非常に単純な描画なので、特に毎フレーム実行しなくても良い処理を
初期化の部分に書いてしまっています。
シェーダーの生成以外の部分、パラメータ設定関連は、必要であれば1フレーム
の描画中に何回か書き換えることになるでしょう。
// レンダーターゲットの設定
g_iDevice->OMSetRenderTargets( 1,
&g_iRenderTargetView, g_iDepthStencilView );
// ビューポートの設定
D3D10_VIEWPORT viewport= {
0, 0, width, height, 0.0f, 1.0f
};
g_iDevice->RSSetViewports( 1, &viewport );
これら関数はどちらも一度に複数個登録できます。今回は1つだけなので
定数 1 を最初に渡しています。
// shader の作成
ID3D10Blob* iblob= NULL;
if( FAILED( D3DX10CreateEffectFromFile(
TEXT("cube.fx"),
NULL, NULL, "fx_4_0", 0, 0, g_iDevice,
NULL, NULL, &g_iEffect, &iblob, NULL ) ) ){
OutputDebugStringA( reinterpret_cast<char*>(
iblob->GetBufferPointer() ) ); // エラーが発生したら表示
ZRELEASE( iblob );
}
ここでは fx ファイルから Shader を作成しています。
今回のサンプルでは ID3D10Effect をそのまま使っています。
HLSL のコンパイル時にはエラーが発生することがあるので、
コンパイラのエラーメッセージをそのまま表示出力しています。
最後に Effect の変数を初期化しています。
// projection の設定
D3DXMATRIX projection;
D3DXMatrixPerspectiveFovLH( &projection, 3.141592f/3.0f,
WINDOW_SIZE_WIDTH/(float)WINDOW_SIZE_HEIGHT, 1.0f, 10000.0f );
g_iEffect->GetVariableByName( "Projection" )->AsMatrix()->SetMatrix( (float*)&projection );
次回は残りの処理と描画です。
2007/08/09
Direct3D 10 ClearState
Direct3D10 では、SetShaderResources や SetConstantBuffers や
OMSetRenderTargets や SOSetTargets や IASetVertexBuffers など、
レンダリングに必要なリソース登録 API のほとんどが
複数設定可能になっています。
一度に登録可能なスロット数も 16だったり 128だったり、
非常に多くなりました。
Set してあるインターフェースは Release() して解放されず、
Read 用に Set してあるリソースは Write 用に Set することができません。
使い終わったら、または別の用途で設定する場合にそれぞれ
NULL を設定して登録を解除します。
API も Slot も増えたので、シーン終了時など一度に初期化するのは
結構大変になったなと思ってました。
ところがこれ、ID3D10Device::ClearState() だけでいけるようです。
こんな便利な関数があったとは。
マニュアルはきちんと読んでおいた方がいいですね。
OMSetRenderTargets や SOSetTargets や IASetVertexBuffers など、
レンダリングに必要なリソース登録 API のほとんどが
複数設定可能になっています。
一度に登録可能なスロット数も 16だったり 128だったり、
非常に多くなりました。
Set してあるインターフェースは Release() して解放されず、
Read 用に Set してあるリソースは Write 用に Set することができません。
使い終わったら、または別の用途で設定する場合にそれぞれ
NULL を設定して登録を解除します。
API も Slot も増えたので、シーン終了時など一度に初期化するのは
結構大変になったなと思ってました。
ところがこれ、ID3D10Device::ClearState() だけでいけるようです。
こんな便利な関数があったとは。
マニュアルはきちんと読んでおいた方がいいですね。
2007/08/07
Direct3D 10 GPU の Query 値の違い
Query 系を少しだけ試してみました。やはり RADEON HD2900XT でも
同じように使えます。TIMESTAMP 取れました。よし。
ただドライバによって結果が異なるかもしれませんが、
GeForce8800GTS (163.11) では ID3D10Query の PIPELINE_STATISTICS の
CInvocations, CPrimitives が 0 のままです。
RADEON HD2900XT だとそれっぽい値が入っています。
IA 系の基礎的なパラメータは一緒ですが、CI~CP~以外にも値の
違うものがあります。例えば VSinvocations など。
おそらく描画に StreamOutput を使っているせいだと思いますが、
RADEON は IAVertices と同じ値で、GeForce はなぜか半分以下の値です。
SO_STATISTICS のPrimitivesStorageNeeded は、GeForce の場合
NumPrimitivesWritten と同じですが、RADEON の場合やたらと大きな値です。
基本的に動的な増減も無く、3 in 3 out しか使ってないのでこれは
計算方法の違いでしょうか。
まったく同じデータを表示してみた結果の例
・キャラクタの表示なので描画面積はかなり小さい
・ボーンアニメーションしているので、Pixel 数が常に変動しており誤差がある
・結果を表示するフォント描画の分も計算に含まれている
●GeForce8800GTS 163.11
IAVertices 4092
IAPrimitives 1504
VSInvocations 1893
GSInvocations 115
GSPrimitives 230
CInvocations 0
CPrimitives 0
PSInvocations 9034
NumPrimitivesWritten 95
PrimitivesStorageNeeded 95
●RADEON HD2900XT 07.7
IAVertices 4100
IAPrimitives 1512
VSInvocations 4100
GSInvocations 123
GSPrimitives 246
CInvocations 1635
CPrimitives 1635
PSInvocations 9656
NumPrimitivesWritten 95
PrimitivesStorageNeeded 1389
Primitive 数が 8 個だけ RADEON の方が多いのは、上記の計測値もポリゴン
でフォント描画しているためです。つまり、CInvocations, CPrimitives,
PrimitivesStorageNeeded の値でちょうど 8桁分、数字が多いわけです。
データもプログラムも同一です。
もう少しライブラリを整備したらきちんと調べていきます。
ちなみに Counter はどちらも全滅でした。
D3D10_COUNTER_INFO は両者とも
LastDeviceDependentCounter= 0
NumSimultaneousCounters= 0
NumDetectableParallelUnits= 1
同じように使えます。TIMESTAMP 取れました。よし。
ただドライバによって結果が異なるかもしれませんが、
GeForce8800GTS (163.11) では ID3D10Query の PIPELINE_STATISTICS の
CInvocations, CPrimitives が 0 のままです。
RADEON HD2900XT だとそれっぽい値が入っています。
IA 系の基礎的なパラメータは一緒ですが、CI~CP~以外にも値の
違うものがあります。例えば VSinvocations など。
おそらく描画に StreamOutput を使っているせいだと思いますが、
RADEON は IAVertices と同じ値で、GeForce はなぜか半分以下の値です。
SO_STATISTICS のPrimitivesStorageNeeded は、GeForce の場合
NumPrimitivesWritten と同じですが、RADEON の場合やたらと大きな値です。
基本的に動的な増減も無く、3 in 3 out しか使ってないのでこれは
計算方法の違いでしょうか。
まったく同じデータを表示してみた結果の例
・キャラクタの表示なので描画面積はかなり小さい
・ボーンアニメーションしているので、Pixel 数が常に変動しており誤差がある
・結果を表示するフォント描画の分も計算に含まれている
●GeForce8800GTS 163.11
IAVertices 4092
IAPrimitives 1504
VSInvocations 1893
GSInvocations 115
GSPrimitives 230
CInvocations 0
CPrimitives 0
PSInvocations 9034
NumPrimitivesWritten 95
PrimitivesStorageNeeded 95
●RADEON HD2900XT 07.7
IAVertices 4100
IAPrimitives 1512
VSInvocations 4100
GSInvocations 123
GSPrimitives 246
CInvocations 1635
CPrimitives 1635
PSInvocations 9656
NumPrimitivesWritten 95
PrimitivesStorageNeeded 1389
Primitive 数が 8 個だけ RADEON の方が多いのは、上記の計測値もポリゴン
でフォント描画しているためです。つまり、CInvocations, CPrimitives,
PrimitivesStorageNeeded の値でちょうど 8桁分、数字が多いわけです。
データもプログラムも同一です。
もう少しライブラリを整備したらきちんと調べていきます。
ちなみに Counter はどちらも全滅でした。
D3D10_COUNTER_INFO は両者とも
LastDeviceDependentCounter= 0
NumSimultaneousCounters= 0
NumDetectableParallelUnits= 1
2007/08/06
Direct3D 10 Query
CPU と GPU は基本的に非同期に動作しますが、CPU 側でも GPU から値を
受け取ったり、動作状況を見てタイミングを計ったりすることがあります。
特に CPU と GPU の「動作と描画のタイミング取り」は重要で、入力から
どれくらいの遅延を許容するのか、CPU と GPU がどれくらい並列に
動くのか、設計時に把握しておく必要があるでしょう。
たとえば CPU 側で何らかのデータを受け取る場合、GPU 処理のタイミング
を何も考えないと、お互いに同期のための Block が発生してしまいます。
やはりきちんとフレームを制御しながら Double Buffering にするなど
いろいろ工夫が必要です。
GPU 側の動作状況を調べるには、Direct3D9 なら IDirect3DQuery9 を使います。
GPU のコマンドにいわゆる Fence の挿入が可能で、普段見えない GPU の
動きを調べることができます。
Direct3D10 の場合は ID3D10Query です。API が異なりますが主要な機能は
D3D9 とほとんど変わらず同じように使えるようです。
一部の同期用 (EVENT等)コマンドは Begin が無いのも一緒。
でも API が違います。
IDirect3DQuery9
・Issue( D3DISSUE_BEGIN )
・Issue( D3DISSUE_END )
・GetData()
ID3D10Query
・Begin()
・End()
・GetData()
D3D9 の場合 RADEON で TIMESTAMP が取れなかったりと GPU によって
機能の違いがありましたが、D3D10 ではこの辺大丈夫そうですね。
受け取ったり、動作状況を見てタイミングを計ったりすることがあります。
特に CPU と GPU の「動作と描画のタイミング取り」は重要で、入力から
どれくらいの遅延を許容するのか、CPU と GPU がどれくらい並列に
動くのか、設計時に把握しておく必要があるでしょう。
たとえば CPU 側で何らかのデータを受け取る場合、GPU 処理のタイミング
を何も考えないと、お互いに同期のための Block が発生してしまいます。
やはりきちんとフレームを制御しながら Double Buffering にするなど
いろいろ工夫が必要です。
GPU 側の動作状況を調べるには、Direct3D9 なら IDirect3DQuery9 を使います。
GPU のコマンドにいわゆる Fence の挿入が可能で、普段見えない GPU の
動きを調べることができます。
Direct3D10 の場合は ID3D10Query です。API が異なりますが主要な機能は
D3D9 とほとんど変わらず同じように使えるようです。
一部の同期用 (EVENT等)コマンドは Begin が無いのも一緒。
でも API が違います。
IDirect3DQuery9
・Issue( D3DISSUE_BEGIN )
・Issue( D3DISSUE_END )
・GetData()
ID3D10Query
・Begin()
・End()
・GetData()
D3D9 の場合 RADEON で TIMESTAMP が取れなかったりと GPU によって
機能の違いがありましたが、D3D10 ではこの辺大丈夫そうですね。
2007/08/05
Direct3D 10 の便利な点 DEVICELOST
Vista + Direct3D 10 になって API 回りも一新、便利な機能が増えています。
特に OS との親和性が高まったおかげか、管理周りの負担が減りました。
これは非常に歓迎すべきことです。
例えば D3D9 までは常に頭を悩ませていた DEVICELOST 対策が、D3D10 では
ほぼいらなくなりました。
D3D10 の新機能も別に使わないし、能力的にも DirectX9 までで満足しているし、
といった場合でも、これはきっと気になるポイントでしょう。
Windows 上では 1つのアプリケーションが GPU や VRAM を占有するわけでは
ないので、常にリソースの競合が発生します。
ほぼ占有可能なフルスクリーンモードでも、いわゆる ALT+TAB で
Task を切り替えるとリソースを明け渡さなければなりません。
このとき DirectX9 以前の API は D3DERR_DEVICELOST を返し、リソースが
他の Task に取られたため実行できないことを訴えます。
こうなったら描画動作の続行をあきらめるしかありません。
TestCooperativeLevel() を呼び出して再び利用可能になるのを待ち続けます。
利用可能な状態に戻ったことを確認したら、VRAM 内容や各種 GPU ステートを
戻してプログラムが継続できるよう復帰させる必要があるわけです。
もちろん DEVICELOST はフルスクリーンモードに限らず Window Mode でも
起こります。例えばスクリーンセーバーなど、突然他のアプリが全画面を
占拠して描画を始めることも十分ありえるからです。
D3D9 の場合でも、D3DPOOL_MANAGED を使えば VRAM 内容の復帰までは自動で
行ってくれるようになっています。これはこれでかなり手間が減りました。
それ以外のリソースは、明示的にリセットしたり作り直しです。
例えば RenderTaget 用の Surface とか、ID3DXEffect とか、
IDirect3DQuery9 など。
Direct3D 10 になると Vista がより深く Direct3D を活用するおかげか
この辺の GPU リソースもしっかり管理してくれます。
DEVICELOST 時の後始末をアプリケーションが行わなくても、ALT-TAB の
切り替えからでも、スクリーンセーバーからでも、きちんと復帰して動作を
続けてくれます。(ちょっと感動)
ただ、アプリケーションが完全にバックグラウンドに切り替わってしまったのに、
何もしないで CPU を消費し続けるのはあまり好ましいとはいえません。
画面描画可能な状態かどうかは、やっぱりアプリケーション側でも
責任を持ってきちんと判別しておく必要があります。
Direct3D 10 では SwapChain の Present() が D3DERR_DEVICELOST
の代わりに DXGI_STATUS_OCCLUDED を返します。
フルスクリーンモードからの切り替わり、スクリーンセーバー、ユーザーの
切り替えなど、バックグラウンドに回る可能性はいくらでもあるわけです。
GPU の状態を調べる ID3D10Query も、DEVIELOST 等によってインターフェース
を作成しなおす必要がなくなりました。
ただその代わり、途中で DEVICELOST 相当の状態が発生すると計測値の正当性が
失われてしまいます。この状況は TIMESTAMP_DISJOINT で調べれば判定可能
なのですが、従来は無かった動作だけに新たな処理が必要になりそうです。
特に OS との親和性が高まったおかげか、管理周りの負担が減りました。
これは非常に歓迎すべきことです。
例えば D3D9 までは常に頭を悩ませていた DEVICELOST 対策が、D3D10 では
ほぼいらなくなりました。
D3D10 の新機能も別に使わないし、能力的にも DirectX9 までで満足しているし、
といった場合でも、これはきっと気になるポイントでしょう。
Windows 上では 1つのアプリケーションが GPU や VRAM を占有するわけでは
ないので、常にリソースの競合が発生します。
ほぼ占有可能なフルスクリーンモードでも、いわゆる ALT+TAB で
Task を切り替えるとリソースを明け渡さなければなりません。
このとき DirectX9 以前の API は D3DERR_DEVICELOST を返し、リソースが
他の Task に取られたため実行できないことを訴えます。
こうなったら描画動作の続行をあきらめるしかありません。
TestCooperativeLevel() を呼び出して再び利用可能になるのを待ち続けます。
利用可能な状態に戻ったことを確認したら、VRAM 内容や各種 GPU ステートを
戻してプログラムが継続できるよう復帰させる必要があるわけです。
もちろん DEVICELOST はフルスクリーンモードに限らず Window Mode でも
起こります。例えばスクリーンセーバーなど、突然他のアプリが全画面を
占拠して描画を始めることも十分ありえるからです。
D3D9 の場合でも、D3DPOOL_MANAGED を使えば VRAM 内容の復帰までは自動で
行ってくれるようになっています。これはこれでかなり手間が減りました。
それ以外のリソースは、明示的にリセットしたり作り直しです。
例えば RenderTaget 用の Surface とか、ID3DXEffect とか、
IDirect3DQuery9 など。
Direct3D 10 になると Vista がより深く Direct3D を活用するおかげか
この辺の GPU リソースもしっかり管理してくれます。
DEVICELOST 時の後始末をアプリケーションが行わなくても、ALT-TAB の
切り替えからでも、スクリーンセーバーからでも、きちんと復帰して動作を
続けてくれます。(ちょっと感動)
ただ、アプリケーションが完全にバックグラウンドに切り替わってしまったのに、
何もしないで CPU を消費し続けるのはあまり好ましいとはいえません。
画面描画可能な状態かどうかは、やっぱりアプリケーション側でも
責任を持ってきちんと判別しておく必要があります。
Direct3D 10 では SwapChain の Present() が D3DERR_DEVICELOST
の代わりに DXGI_STATUS_OCCLUDED を返します。
フルスクリーンモードからの切り替わり、スクリーンセーバー、ユーザーの
切り替えなど、バックグラウンドに回る可能性はいくらでもあるわけです。
GPU の状態を調べる ID3D10Query も、DEVIELOST 等によってインターフェース
を作成しなおす必要がなくなりました。
ただその代わり、途中で DEVICELOST 相当の状態が発生すると計測値の正当性が
失われてしまいます。この状況は TIMESTAMP_DISJOINT で調べれば判定可能
なのですが、従来は無かった動作だけに新たな処理が必要になりそうです。
2007/08/04
Direct3D 10 シェーダー4.0サンプルプログラム
Direct3D 10 の ShaderModel4.0 は非常に自由度が高く、かなりいろいろな
ことができます。3.0 以前の制約の中で結構がんばってコードを書いた経験が
あるなら、この柔軟性にはちょっとした感動を覚えるかもしれません。
特にマニュアルを読んでいて衝撃を受けたのがこれ
・Using the Input-Assembler Stage without Buffers (Direct3D 10)
マニュアルの下記の場所にあります。
+ DirectX Graphics
+ Direct3D 10
+ Programming Guide
+ Pipeline Stages
+ Input-Assembler Stage
+ Getting Started with the Input-Assembler Stage
+ Using the Input-Assembler Stage without Buffers (Direct3D 10)
SV_VertexID を元に VertexShader の中で頂点を選択して描画しています。
つまり、VertexBuffer も IndexBuffer も無く、InputLayout も設定せずに
ポリゴンが描画できてしまうのです。
このシンプルさに惚れました。
自分でも同じように、頂点バッファ無しにポリゴン描画できるシェーダーを
作ってみました。fx を読み込んで描画するだけで、一切リソースを作らなくても
こんな感じの Cube を表示することができます。

形状を生成しているのは下記の部分です。
ついにシェーダーでこんなトリッキーなコードが走るようになりました。
float3 VS_Main( uint id : SV_VertexID, uint sid : SV_InstanceID ) : POSITION
{
uint _map[]= {
101733320,
104889305,
56280874,
125360034,
};
id= _map[id&3]>>(((id&60)>>2)*3);
float3 pos;
pos.x= id & 1 ? 1 : -1;
pos.y= id & 2 ? -1 : 1;
pos.z= id & 4 ? 1 : -1;
float2 ss;
sincos( sid * 0.5236f, ss.x, ss.y );
pos.xy+= ss * 5;
return mul( float4( pos, 1 ), WorldToView ).xyz;
}
シェーダー全部でもこれだけです。
・cube.fx
描画している C++ 側はこんな感じです。
g_iEffect->GetTechniqueByIndex( 0 )->GetPassByIndex( 0 )->Apply( 0 );
g_iDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
g_iDevice->DrawInstanced( 36, 12, 0, 0 );
g_iSwapChain->Present( 0, 0 );
ソースリストと実行ファイルは
・「HYPERでんち」 オリジナルサンプルプログラム
に掲載しましたので、興味ある方がいましたらどうぞお試しください。
ことができます。3.0 以前の制約の中で結構がんばってコードを書いた経験が
あるなら、この柔軟性にはちょっとした感動を覚えるかもしれません。
特にマニュアルを読んでいて衝撃を受けたのがこれ
・Using the Input-Assembler Stage without Buffers (Direct3D 10)
マニュアルの下記の場所にあります。
+ DirectX Graphics
+ Direct3D 10
+ Programming Guide
+ Pipeline Stages
+ Input-Assembler Stage
+ Getting Started with the Input-Assembler Stage
+ Using the Input-Assembler Stage without Buffers (Direct3D 10)
SV_VertexID を元に VertexShader の中で頂点を選択して描画しています。
つまり、VertexBuffer も IndexBuffer も無く、InputLayout も設定せずに
ポリゴンが描画できてしまうのです。
このシンプルさに惚れました。
自分でも同じように、頂点バッファ無しにポリゴン描画できるシェーダーを
作ってみました。fx を読み込んで描画するだけで、一切リソースを作らなくても
こんな感じの Cube を表示することができます。

形状を生成しているのは下記の部分です。
ついにシェーダーでこんなトリッキーなコードが走るようになりました。
float3 VS_Main( uint id : SV_VertexID, uint sid : SV_InstanceID ) : POSITION
{
uint _map[]= {
101733320,
104889305,
56280874,
125360034,
};
id= _map[id&3]>>(((id&60)>>2)*3);
float3 pos;
pos.x= id & 1 ? 1 : -1;
pos.y= id & 2 ? -1 : 1;
pos.z= id & 4 ? 1 : -1;
float2 ss;
sincos( sid * 0.5236f, ss.x, ss.y );
pos.xy+= ss * 5;
return mul( float4( pos, 1 ), WorldToView ).xyz;
}
シェーダー全部でもこれだけです。
・cube.fx
描画している C++ 側はこんな感じです。
g_iEffect->GetTechniqueByIndex( 0 )->GetPassByIndex( 0 )->Apply( 0 );
g_iDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
g_iDevice->DrawInstanced( 36, 12, 0, 0 );
g_iSwapChain->Present( 0, 0 );
ソースリストと実行ファイルは
・「HYPERでんち」 オリジナルサンプルプログラム
に掲載しましたので、興味ある方がいましたらどうぞお試しください。
2007/08/01
Direct3D 10 ConstantBuffer と配列
ConstantBuffer は D3D8~9 同様 32bit×4 が Element の基本サイズに
なります。ただし D3D10 は float, int の混在が可能です。
マニュアルには Packing Rule として、変数がどのように Constant Buffer
へ格納されるか説明が載っています。
配列変数の場合は、インデックス参照を行うとアドレッシングの影響を強く
受けてしまうので大胆なパッキングができません。
例えば次のように宣言して、A を変数インデックスで動的に参照する場合、
cbuffer _Array {
uint A[256];
};
cb[] の参照は必ず Element 単位となるため 128bit のアライメント整合
が発生します。よって上記の cbuffer は
256 × 4 × 4 = 4096byte になります。(厳密には 4096-12=4084byte)
さらに次のように宣言すると
cbuffer _Array {
uint A[256];
float B[256];
};
本当は A を .x に、B を .y にでもパックしてしまえばデータ構造的には
要素をまとめることができるはずですが、
この場合もリニアに 512 × 4 × 4 = 8192byte とられてしまいます。
(厳密には 8192-12 = 8180byte)
struct _pack {
uint a;
float b;
};
cbuffer _Array {
_pack A[256];
};
上のように自分で構造体にまとめると半分の 4096 (4096-8=4088) byte です。
ld 命令が 4要素単位となる tbuffer (Buffer) でも全く同じでした。
この「32bit×4」の制限を回避するには、Sampler を使って 1D Texture
としてアクセスする方法が考えられます。
なります。ただし D3D10 は float, int の混在が可能です。
マニュアルには Packing Rule として、変数がどのように Constant Buffer
へ格納されるか説明が載っています。
配列変数の場合は、インデックス参照を行うとアドレッシングの影響を強く
受けてしまうので大胆なパッキングができません。
例えば次のように宣言して、A を変数インデックスで動的に参照する場合、
cbuffer _Array {
uint A[256];
};
cb[] の参照は必ず Element 単位となるため 128bit のアライメント整合
が発生します。よって上記の cbuffer は
256 × 4 × 4 = 4096byte になります。(厳密には 4096-12=4084byte)
さらに次のように宣言すると
cbuffer _Array {
uint A[256];
float B[256];
};
本当は A を .x に、B を .y にでもパックしてしまえばデータ構造的には
要素をまとめることができるはずですが、
この場合もリニアに 512 × 4 × 4 = 8192byte とられてしまいます。
(厳密には 8192-12 = 8180byte)
struct _pack {
uint a;
float b;
};
cbuffer _Array {
_pack A[256];
};
上のように自分で構造体にまとめると半分の 4096 (4096-8=4088) byte です。
ld 命令が 4要素単位となる tbuffer (Buffer) でも全く同じでした。
この「32bit×4」の制限を回避するには、Sampler を使って 1D Texture
としてアクセスする方法が考えられます。