月別アーカイブ: 2007年8月

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( … )

メモリだけでなく直接ファイルやリソースから作ることも出来ます。

ちなみにマニュアルにはミスがあって、最後の引数

  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のリリースノートはちゃんと読んでおきましょう・・)

AMD SSE5 Shader のような新しい命令

AMD が新しい命令セット SSE5 を発表したそうです。

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段階分です。

WindowsMobile ドコモのスマートフォン 1100

Windows Mobile OS搭載FOMA「F1100」「HT1100」の2機種を開発
どちらも似たような形状でテンキーが付いているけれど、
良く見ると HT1100 の方はタッチパネル搭載で
Windows Mobile 6 professional ですね。
この両者 結構中身は違っているようです。

メモリや CPU などのスペックはまだ出てないみたいですね。
気になります。
情報が集まったら PocketPC 一覧 を更新します。

Direct3D 10 Shader4.0 ピクセル補間モード

DirectX10 の PixelShader は、入力パラメータの補間方法を
選択することができます。例えば

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 ではまだ変化がでる状況が見られないので、
組み合わせによって負荷に違いがあるかどうかわかりませんでした。

PS3 が停電に耐えた

突然の瞬電
室内の蛍光灯が一瞬消え、
PCの電源も落ちて再起動がかかり、
ルータのランプもちかちしている。

何の前触れも無くあわてる暇もなく、
電気を使いすぎたか、と思ったけどブレーカーは大丈夫みたい。

それより何より、
何事も無かったように たんぱく質を畳み続ける PS3。
ええっ?なんでお前は無事なの?

ルータがリセットされたせいで「ログアウトしました」という
メッセージは表示されたけど、再起動もせず平然と動き続ける
そのたくましさに驚いた。

その後はひどい雷雨になって、停電の原因は判明しました。

昨日 FIXSTARS さんのサイトを見ていて
下記のニュースをみつけました。

リアルタイム音響測定用ソフトウェア RT-IR01 販売開始

これは PS3 を使った実用ソフトで、PS3 をただの DSP ボックス
として活用しています。アクセラレータとして演算能力しか
使っていないようです。

PC とは LAN でつなぎ、操作も出力も PC で行うので完全に
周辺機器状態です。

こんな活用の仕方もあるんですね。
ゲーム機の活用といえば、過去にもファミコンを通信端末にしたり、
めがねの試着ソフトを走らせたり、もあった気がします。

Folding@Home もそうですが、端末用途でもなく、PC 代わりでもなく、
純粋に CPU 能力が活用されているのは今までに無く新鮮です。