Archives

August 2007 の記事

同じシェーダーで、かつ同じコンパイルオプションを指定しているのに、
生成されたシェーダーのバイナリが完全に同一になりません。
こんな症状にしばらく悩んでいました。

判明した結論は下記のとおり。

「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 を発表したそうです。

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


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

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


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


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

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

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

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

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


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

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

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

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

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

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


そういえばそろそろあれが更新されていてもおかしくないな、
と 思って見に行ったらほんとにありました。
ftp linux Sony-PS3
来てます。 CELL-Linux-CL_20070817-ADDON


まず今回の大きな更新は カーネルが 2.6.23-rc3 になった ことです。
今まで 2.6.21-rc7 だけでなく 2.6.16 も含まれていましたが、
2.6.23-rc3 一本になりました。

2.6.23-rc3 では、あの shutdown/reboot できずに止まってしまう問題も
Wireless 接続できない制限も直っているようです。


さらに、ADDON-CD で特別に用意されていた Fedora のインストール
機能が無くなりました。

これはディストリビューション側の通常のインストーラで PS3 対応に
なったので、わざわざ用意する必要がなくなったためです。

対応ディストリビューション一覧は

 doc/HowToUsePS3Linux.html

に書かれているとのこと。見てみると下記3点が載っています。

・Fedora 7
  http://fedoraproject.org/
・Gentoo LiveCD(Beta)
  http://www.gentoo.org/proj/en/base/ppc64/ps3/
・Ubuntu 7.04
  https://help.ubuntu.com/community/PlayStation_3

もちろんここには載って無いけど YDL もあるし、他にも探せば
対応ディストリビューションがいろいろでてくるでしょう。


注意点は、これまで使われてきた ps3 オリジナルコマンド群
(ps3pf-tool) の名前が変更になったことです。

ps3videomode → ps3-video-mode
boot-game-os → ps3-boot-game-os
other-os-flush-util → ps3-flash-util

特に ps3videomode と boot-game-os は使用頻度が高いので要注意です。

各種ディストリビューションに反映されるのはいつになるかわかりませんので、
今後しばらくはとりあえず新旧両方の名前で試すと良いかもしれません。


前日のエントリの続きです。
PS3 リモートプレイで Folding@Home
さらに emobile の EM・ONE 経由のリモートプレイを試してみました。

PSP も無線LANルータだと比較的簡単に通信設定できるのですが
今回は EM・ONE を使う関係上 PC 経由で接続します。

使用した無線LANアダプタは Planex の GW-US54Mini です。
このアダプタはダウンロードできるユーティリティを使うことで
アクセスポイントとして機能させることができます。

ただし対応 OS が WindowsXP/2000/Me/98SE とのこと。
残念ながら Vista ではユーティリティが起動しませんでした。
以下 XP のノートPC で実験しています。
(結局 Lets R4 に XP を入れなおしました)


●GW-US54Mini のユーティリティインストールと確認

 ダウンロードした GW-US54Mini のユーティリティ・ドライバを
 インストールします。

 ドライバインストール後に GW-US54Mini を USB ポートに差し込むと
 認識し、無線LANアダプタとして使えるようになります。

 ノート本体にも無線LANアダプタが内蔵されているため、ここで追加
 されたアダプタの接続名は「ワイヤレスネットワーク接続2」に
 なりました。
 この名前は環境依存なので必要に応じて読み替えてください。

 マイネットワーク のプロパティを開き、Explorer の表示を詳細にすると
 "ワイヤレスネットワーク接続2" のデバイス名が
   「PLANEX GW-US54Mini 54Mbps Wireless Mini USB Dongle」
 となっていることが確認できます。

 このままでは無線LANアダプタが追加されただけなので、
 ユーティリティで動作モードを変更します。


●ユーティリティの設定

 アイコントレイに PLANEX GW-US54Mini ユーティリティのアイコンが
 入っているので、ダブルクリックして設定画面を呼び出します。
 無ければスタートメニューから起動します。
 ここで使用したユーティリティのバージョンは 2.18.0.0 です。

 (1) 右上の「動作モード:」を「アクセスポイント」に切り替え

 (2)「More Setting..」ボタンで次の画面へ

 (3) General Connection Setting の [Change] を押して
   SSID に適当な名称を入れて [Apply]

 (4) Authentication を「Shared Key」にする

 (5) WEP を「Enable」にする

 (6) WEP の右側の [Setting] ボタンを押す
   [変更] ボタンを押して、
    キー長は 64bit のまま
    キーフォーマットを文字列に変更
   キーインデックス #1 に
   任意のパスワードを5文字で入力。[適用] を押す。

 一番下の Bridge Adapter は「No Bridge」にしておきます。


●EM・ONE によるダイヤルアップ

 EM・ONE をモデムとして使い、インターネット接続できるように
 設定します。

 まずは EM・ONE 用の USB ドライバをインストールします。

 設定方法は EM・ONE のマニュアルダウンロードページから
 下記の 26_S01SH_UseAsModem.pdf を参照します。
  ・取扱説明書ダウンロード
 データ通信(モデムとして使用) 26_S01SH_UseAsModem.pdf

 マニュアル通りに設定し、ここで作った接続名を仮に
 「emobile EMONE」としておきます。

 マイネットワークのプロパティを開き、ダイヤルアップの欄に
 「emobile EMONE」が入っていることを確認します。

 EM・ONE を接続するとなぜか「ワイヤレスネットワーク3」が
 増えました。これでネットワーク接続の画面には

  ワイヤレスネットワーク
  ワイヤレスネットワーク2
  ワイヤレスネットワーク3

 と3つ並びます。
 非常に紛らわしいので後の設定で名前を間違えないようにします。


●接続を共有にする

 マイネットワークのプロパティで、ダイヤルアップの
 「emobile EMONE」を選択し、マウス右ボタンから
 プロパティを開きます。

 詳細設定で「インターネット接続の共有」にチェックを入れます。

 ホームネットワーク接続の欄は、GW-US54Mini で設定した接続を
 選びます。今回の設定では「ワイヤレスネットワーク2」が相当します。
 ここを間違えるとつながりません。

 共有設定したらダイヤルアップ接続してインターネットに
 つないでおきます。


●PSP をつなぐ

 ここから先は PSP の操作です。

 (1) 「ネットワーク設定」 を選択

 (2) 「インフラストラクチャーモード」 を選択

 (3) [新しい接続の作成] を選択

 (4) 「手動で入力する」 を選びます。

 (5) SSID を入力します。これは「●ユーティリティの設定」の (3) で
   登録したもです。

 (6) ワイヤレスLANセキュリティ設定を「WEP」にします。
 
 (7) WEPキーを登録します。「●ユーティリティの設定」の (6) で
   入力した5文字の文字列です。

 (8) アドレス設定は「カスタム」を選びます。

 (9) IPアドレス設定を「自動取得」にします。

 (10) DNS設定 を「自動取得」にします。

 (11) プロキシサーバーを「使用しない」

 (12) インターネットブラウザ を「起動しない」にします。

 (13) 接続名を適当につけて完了です。

 設定内容を保存したら接続の確認をします。


これで、PSP から PC+EM・ONE 経由でインターネットに接続できる
ようになりました。うまくつながったらセキュリティ設定をもっと
強くして大丈夫かもしれません。

ノートPCと EM・ONE さえ持ち歩けば、どこでも PSP でブラウザ接続
やダウンロードできるようになるわけです。(ゲームも未確認だけど
できるっぽいです。)


●リモートプレイでつなぐ

ここまできたらあとはリモートプレイで接続するだけです。

あらかじめ PSP と PS3 を USB ケーブルでつないでペアリング
しておく必要があります。
リモートプレイをする(インターネット経由)

もし可能なら、あらかじめ家庭内 LAN 接続でリモートプレイの接続
テストを済ませておくといいでしょう。
インターネット経由でのリモートプレイ時は、さらに
PLAYSTATION Network のアカウントを PSP 側にも登録する必要が
あります。がんばって設定します。

うまくいけばどこでもいつでも Folding@Home や「まいにちいっしょ」
を楽しむことができるでしょう。


今回は USB で EM・ONE をつなぎました。
もし Bluetooth 内蔵のノートだったら Bluetooth 経由で EM・ONE を
モデムにすることができます。ということは

 HSDPA → Bluetooth → 無線LAN → PSP

と3種の無線接続を経由することになりますね。


ちなみにレスポンス速度ですが、かなり電波状況の悪いところで
テストしたので速度はいまいちでした。EM・ONE の電波状態表示で、
常に 2本~圏外を行き来するような場所です。
EM・ONE 単体でテストしても 500Kbps くらい。

リモートプレイの設定は最低の 256Kbps で、かなりタイムラグが
あって画面の更新頻度もむらがあって、操作も待たされる感じでした。
画面の確認はできるし見てるだけならいいけれど、リモートプレイ
での操作は少々厳しいようです。
もう少し電波状況が良くて、1~2Mbps 出てる場所で試してみたい
ところです。



いつものように Folding@Home のアイコンを選んでそのままおいといたら
v1.2 への更新確認画面で止まったままでした。
たぶん 1WU くらい余裕で処理できる時間です。大失敗。

二度目の更新 v1.2 で Folding@Home もさらに改良されました。
Folding@home for PLAYSTATIONR3バージョン 1.2

特にリモートプレイへの対応は、以前 こちらの記事 で「強く希望」と
書いておきつつ、後から「やっぱり無理だよなと」思っていたので驚き。
早速試してみました。

Folding@Home Remote

リモートプレイを使うと、PSP でシミュレーション進行状況をモニタ
できるようになります。

家庭内でリモートプレイを使って PSP をサブモニタにしてもいいんで
しょうが、この機能が本領を発揮するのはおそらくインターネット
経由でのモニタリングでしょう。

出先でも進行状態を確認したり、シミュレーションを止めたり、
レスポンス速度を必要としないので相性がいいかもしれません。

リモートプレイの接続方法はいくつかあります。

・インターネット経由で接続する
・家庭内で接続する
  ・PS3直接接続
  ・無線ルータ経由

今回は「家庭内で接続する」だけ実験しています。


リモートプレイでどれくらいシミュレーション速度が遅くなるか見てみます。
本体だけで描画時 0.0728sec/frame くらいのデータが、
リモートプレイで 0.0796/frame ほどです。
この数値は起動直後の値なのであまり厳密ではないですが、
リモートプレイの方が必ず数値が大きく(遅く)なるようです。

ただし、1.2 で追加されたスクリーンセーバー解除直後は
リモートプレイ時でも 0.0732 くらいとそこそこ速い速度でした。
スクリーンセーバーのロゴ画面自体も通信で PSP に送られていますが、
圧縮負荷が低くあまり負担をかけないのかもしれません。
ちなみに本体だけだと、スクリーンセーバー解除直後はさらに速いです。
(0.0708 くらい)


リモートプレイで Folding@Home を起動し、そのまま PSP を切断しても
Folding@Home は動き続けています。PS3 側はリモートプレイ待機画面の
ままとなっており、PSP で再接続するとすぐシミュレーション画面を
見ることができます。

この待機状態でも画面の生成は行われているようで、シミュレーション
速度はリモートプレイ通信中と変わりませんでした。

リモートプレイ時でも、スクリーンセーバーが起動していれば
そこそこの速度になりそうです。
速度優先なら本体だけ+スクリーンセーバー時がやっぱり速いですね。
テストはすべてノーマルモードで行っています。


WindowsMobile のキーカスタマイズソフト em1key
遠藤諭の東京カレー日記で紹介されました。

Advanced W-ZERO3 [es]で親指シフト

Advanced W-ZERO3[es] で親指シフト入力するために
oyayubiwm を使ってます。
しかもかなりカスタマイズされている!


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 です。

loop回数
  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 に戻しました。
安定したデータが取れるのはもうちょっと先になるかもしれません。
まずは電源を・・


W-ZERO3/EM・ONE 用ソフト、ctrlswapmini や em1key
 ・既知の不具合
 ・報告をいただいた不具合情報
 ・現在寄せられている要望
等を登録した BTS を公開しました。現在試験運用です。
閲覧だけなら「ゲストログイン」ボタンで入ることができます。

BTS flatlib-mantis


Shader.jp さんからの情報ですが
ゲーム開発者のための技術説明会 「Gamefest Japan 2007」 開催

CEDEC から Meltdown が消えたなと思っていたら、独自に
Gamefest Japan を行うみたいですね。
二日間にわたって開催されて結構内容も幅広いようです。
9月は忙しくなりそうです。

ゲーム開発のための技術説明会 Gamefest Japan 2007 開催 ゲーム制作技術情報を日本で初めて広く一般へ公開
こちらにも書いてあるように Gamefest はもともと開発者限定の
クローズドなイベントだったので、それが広く一般に公開された形です。
ターゲットはやはり開発者で、マルチプラットフォーム化等の推進が
狙いとのことです。

そういえばゲームは、Wii/DS 等ユーザー層の拡大がここ最近のテーマでした。
もしかしたら MS は XNA 関連を中心にして、ユーザーだけでなく
開発の一般への浸透化や、開発者の層の拡大もある程度視野に入れて
いるのかもしれません。


前々回
Direct3D 10 Shader4.0 ループと最適化
で行ったテストを、GeForce8800GTX でも試してみました。

テストしたのは、逆転して [loop] の方が高速になる 3番目の
shader です。GeForce8800GTX の傾向は GTS とまったく同じで、
loop 120 回以上で速度低下が顕著になります。

Unrollと速度

・縦軸は実行にかかった時間(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倍に近いスループットが期待できそうです。


コードを書いていて気になったので少々実験してみました。
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 も長くなるしと
他の実行サイクルの影に隠れてあまり表に出てこない可能性があります。

相殺されている分には実質的な負荷ではないので、高速化を考える場合
はこのあたりのさじ加減が難しいですね。


Direct3D 10 の Shader4.0 は、実行可能な命令スロットも多いし
整数型も整数演算も扱えるし、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回数/縦usec

 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回数/縦usec

 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回数/縦tempreg数

          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 が一番近い結果になりますが、実行すると差が
でません。内部で最適化かかってしまっていたのか、切り替えがうまく
機能していなかったのか、本当に同じ速度だったのか、
まだまだ調べる余地がありそうです。


昔のものですが必要になったのでメモ。D3D9 です。
レンダリングした結果を保存するには
・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 にコンバート。


Direct3D 10 では Window のリサイズが比較的簡単にできます。
もちろん何もしない状態でも、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 が足りなくなり、速度が極端に低下する
可能性も考えられます。場合によっては、極端な速度低下を検出したら
元のサイズに戻す処理も入れた方が良いのかもしれません。


昔に比べたら扱えるツールもかなり増えました。
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 専用フォーマットが登場したとしても、ツール側での対応はまた
しばらく時間がかかりそうですね。


サーバー移行時のメモ、前日のエントリの続きです。
blog サーバー移行メモ
チャンネル北国tv から Nucleus へ blog データの変換を行いました。
同じシステムを使っている他の地域系blogでも、おそらく変換可能だと
思います。

先日の NP_ImpExp の改造で、データ自体は問題なくインポート
できるようになりました。


●参照リンクの解決

次に少々気になるのは、自分自身の blog へのリンク、つまり過去記事を
参照している部分です。当然ですが、そのままだと移転前のページに
飛んでしまいます。

元の blog では、記事固有ページの URL は特に法則性のない名前に
なっていました。たぶん記事の管理番号か何かがそのままページ名に
なっているのだと思われます。
なので、export された MT形式のデータだけみてもどの URL がどの
データに対応していたのかわかりません。

問題はもう1つあります。Nucleus は個別の記事を itemid で区別して
おり、記事固有ページもこの item 番号が割り振られます。ところが
どの番号が割り振られるのかは、実際にデータをインポートしてみるまで
わかりません。
実験で何度も削除やインポートを繰り返しましたが、itemid は
インポートするたびに別に値になってしまいます。


問題点まとめ
・MT形式のデータでは、自分自身への参照リンクがわからない。
 参照している元の URL と、記事の対応付けが必要。

・Nucleus で個別記事を識別する番号は Import してみるまでわから
 ない。事前のツールで対応付けを静的に解決するのは難しい。
 (読み込んだ後の plug-in ならできる?)


そこで、記事個別に対する参照リンクをあきらめて、日付ごとの
アーカイブへのリンクに置き換えることにしました。
もともと1日に1記事程度のペースだったし、これなら事前に URL
も求まるし、何度も Import/Export を繰り返しても大丈夫です。

Nucleus の場合、日付によるアーカイブの URL は

http://wlog.flatlib.jp/archive/1/2007-8-12

こんな感じになります。


最初はリンク先の置き換えを手で修正してみましたが・・8月分だけで
断念。そこで、

 1. 自分の blog への参照リンク URL 抽出
 2. その URL へアクセスしてページデータダウンロード
 3. ページデータ内から日付情報の切り出し
 4. 切り出した日付から、新 blog 用のアーカイブページの URL を求めて置換

こんなスクリプトを書いて一気に置換してみました。


●画像リンクの置換

参照リンクと同じように、埋め込まれている画像の参照も変換する必要が
あります。これは参照リンクに比べたら非常に簡単なので、スクリプトで
単純にパスを置き換えました。

注意点はサーバー側で自動生成されたサムネイルを見ている可能性がある
ことです。変換時に再び生成しなおすか、または移転元から引っ張ってくる
方法もあるかと思います。

この blog ではもともと画像は少ないし容量も小さいので、今回はないも
せずに縮小前の画像へのリンクに置き換えてしまいました。


●チャンネル北国tv から Nucleus への移行手順のまとめ

・MT形式で Export
・自分への参照リンク URL の置換
・画像参照パスの置換

・Nucleus を設置して設定
・Import 前に先に NP_TrackBack Plug-in を入れておく必要あり
修正版のプラグイン NP_ImpExp を install
・NP_ImpExp で読み込み

・移行する画像データは別手段で用意しておき media/1 にアップロード


つい、いつものくせで投稿前にタイトルの文字数を数えてしまいました。
チャンネル北国tv のシステムでは、タイトル文字数が unicode 換算で
32文字までと制限があったからです。

もし逆に Nucleus から チャンネル北国tv 変換する場合は、この
タイトル文字数の対策が必要になるでしょう。


blog データの引越しを行いました。
そのときの作業メモです。


●MT形式での出力

移転前の blog で対応している入出力フォーマットは MT形式と呼ばれるもの
でした。MT形式のインポートとエクスポートはほとんどの blog でサポート
しており、事実上標準となっているようです。

中身は階層を持たないプレーンテキストで、ツールやスクリプトでも比較容易に
扱うことができます。
各記事の内容や日付などの情報、コメントやトラックバックの情報も含まれています。


●MT形式での入力

Nucleus はプラグインを使うことで MT 形式の読み込みができます。
下記の NP_ImpExp を使用しました。
NP_ImpExp

ただ移転前の blog のデータをそのまま読み込むと、かなり行が詰まった画面と
なりうまく再現されていません。サイトやもとのデータに起因する問題だとは
思いますが、空行が失われてしまうようです。

WordPress ではきれいに読み込めていたので、少々原因を調べました。
NP_ImpExp の

 plugins/sharedlibs/cles/MTFileParser.php

の最後、parseTextSection() を下記のように修正しました。(+の削除)

$lines = preg_split("/(\r?\n)+/", $comment);
	↓
$lines = preg_split("/(\r?\n)/", $comment);

これで空行もそのまま読み込めるようになりました。


●TrackBack

インポート時のログを見るといくつかエラーがでていました。トラックバック
のデータでした。Nucleus は標準で TrackBack に対応していないので、
インポート前に下記の Plugin が必要です。

NP_TrackBack

これできちんと読み込めるようになります。
何度もインポートと全データ削除を繰り返していると、TrackBack のデータ
だけが重複して何個も残ってしまうことがありました。
消すときは、item データ削除前に TrackBack のデータも全部消しておいた方が
よさそうです。


●カテゴリの違い

次の問題はカテゴリの分類です。NP_InpExp では読み込む前に、元の blog と
同じカテゴリをあらかじめ追加しておく必要があります。
それでもきちんと読み込めているものもあれば、分類情報が失われてしまう
ものもありました。

NP_ImpExp は標準で PRIMARY CATEGORY を優先し、なければ CATEGORY を
参照しています。元の blog のデータは CATEGORY に情報が入っていたので、
読み込み時に無理やり CATEGORY を参照するように書き換えました。

NP_ImpExp.php importEntry() の先頭に下記の行を追加。

$entry['PRIMARY CATEGORY'] = $entry['CATEGORY'];



●コメントとトラックバックの時刻

読み込んだデータのコメントとトラックバックだけ、日付がリセット(0=1970年)
されてしまう問題が発生しました。
いろいろ調べたところ、DB に渡す日付が '/' スラッシュ区切りになっていた
のが原因でした。これを '-' ハイフン区切りにしたところ、日付も正しく
読み込まれるようになりました。

NP_ImpExp.php importPing() と importComment() の2箇所あります。
下記のように変更しました。

$timestamp = date("Y/m/d H:i:s", $timestamp);
	↓
$timestamp = date("Y-m-d H:i:s", $timestamp);

このとき試していたのは SQLite 版だったので、もしかしたら MySQL では
スラッシュでも問題ないのかもしれません。マニュアル上はハイフンの方が
正しい形式のようです。


前回の続きで、サンプル 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
しか呼び出していません。


以前掲載した 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 );


次回は残りの処理と描画です。


ようやく Advanced W-ZERO3[es] WS011SH に触ることができました。
1~2時間ほどでしたけどキーコードの調査ができました。
協力してくれた Florian さんありがとうございました。

互換性のない問題の2キー

   es  ades
[,<] BC00 BC36
[.>] BE01 BE35

上記以外は互換性がありました。
ZERO3(WS003/004SH)や EM・ONE も es と同じです。
emobile EMONE のキーコード調査

ades の新規追加キー

[半全] F346


Mugen Power 3400mAh の大容量バッテリーを使い出してから
ちょうど一ヶ月です。
EMOBILE EM・ONE スーパー大容量バッテリー
バッテリーの持ちの良さは満足です。
自分の利用頻度だとぎりぎり一週間持たない程度で、ほぼ6日目の通信中に
空っぽになります。

とはいえバッテリーの分だけ本体は確実に大きく重くなっているので、
  かばんに入れる→取り出す頻度が減る
点も、長寿命化に貢献している可能性が大です。


現在の主な用途は次の2つです

・通信用 HSDPA
  NetFront v3.3
  メールもこれ

・テキストエディタ
  PocketWZ 3.0 + WM5 patch + USB外付けキーボード
  会議の議事録、メモなどもこれ


PC との同期や開発用にデバッガを使う場合は Bluetooth を使っています。
USB はノートPC のモデムとして使うため、モデムモード固定にしています。
ただ最近は ZERO[es] や EM・ONE +キーボード済ませているので、
あまりノートを持ち歩かなくなりました。
(でもUSBキーボードは持ち歩いてます)


もう1つ個人的に非常に重要なアプリとして TOMBO があります。
昔から PDA で書き込んできたメモやデータの閲覧に使っており、
15年以上前の SHARP の電子手帳で、ぽちぽちと打ち込んだデータまで
しっかり手元に残ってます。

PA-8500~ 電子手帳 「メモ機能」
 ↓
PI-3000~ ザウルス 「レポート&自由帳」
 ↓
MI-110M~ ザウルス 「レポート&自由帳」
 ↓
iPAQ h3630 PocketPC 「TOMBO」
 ~ ZERO3 ~ EM・ONE

こんな感じで上位機種へのデータ転送&乗換えを繰り返してきました。
PocketPC に乗り換えるときに、レポート&自由帳のコンバート先として
TOMBO がぴったりでした。蓄積したデータを保持するために今では
なくてはならないものとなっています。
このソフトには本当にお世話になってます。


Direct3D10 では、SetShaderResources や SetConstantBuffers や
OMSetRenderTargets や SOSetTargets や IASetVertexBuffers など、
レンダリングに必要なリソース登録 API のほとんどが
複数設定可能になっています。
一度に登録可能なスロット数も 16だったり 128だったり、
非常に多くなりました。

Set してあるインターフェースは Release() して解放されず、
Read 用に Set してあるリソースは Write 用に Set することができません。
使い終わったら、または別の用途で設定する場合にそれぞれ
NULL を設定して登録を解除します。

API も Slot も増えたので、シーン終了時など一度に初期化するのは
結構大変になったなと思ってました。
ところがこれ、ID3D10Device::ClearState() だけでいけるようです。
こんな便利な関数があったとは。
マニュアルはきちんと読んでおいた方がいいですね。


最近 RADEON HD2900XT での動作確認もぼちぼち始めました。
この RADEON 用に用意したマシンは決して最新スペックのものではないです。
Pentium4 の 3.2GHz と、1年半~2年くらい前の標準的な構成で、
電源だけ取り替えて RAM を 2G に増設したものです。
だけど妙にコンパイルが速く感じるので不思議に思ってました。

普段使っている PC は 2年前の Pentium4 3.4GHz なので、
気のせいかなと思いつつ計測してみました。


実験は VisualStudio 2005 Professional のコンパイラで、
ソースファイルはヘッダ込みで 280本ほど。
Release と Debug の両方を同時に生成しているため、1つのソースを
2回コンパイルしていることになります。
結果は下記のとおり。


・RADEON用、Pentium4 3.2GHz RAM 2GB WindowsVista
  約5分 (セキュリティソフトなし)

・main PC、Pentium4 3.4GHz RAM 2GB WindowsVista
  約9分 (セキュリティソフト ON)


ほんとに速かった。
試しに main PC 側も常駐のセキュリティを切ってみると


・main PC、Pentium4 3.4GHz RAM 2GB WindowsVista
  約5分 (セキュリティソフト OFF)


速い! コンソールを流れるメッセージの速度が明らかに違います。

使っているのは avast! で、変更したのは「オンアクセス保護」の停止です。
他のセキュリティ系ソフトで試してないのでわかりませんが、予想以上に
影響がありますね。

他の PC でも試してみました。

・Core2Duo E6600 2.4GHz RAM 2GB WindowsVista
  avast ON : 約8分
  avast OFF : 約3分30秒


こちらは 2倍以上も違う結果になりました。
ON だとほとんど CPU の違いが吸収されてしまっています。
増えた分の大半は HDD とか I/O 関連が占めているのかもしれません。

これらは作業中の ついで の計測で、他のソフトも起動したままの状態で、
あまり厳密なチェックではない点ご了承ください。

でもこんなに差があるなら、開発中は LAN ケーブル抜いて、
セキュリティレベル下げてのコンパイルもありですね。

DirectX9 の時は、シェーダーの全コンパイルを行うと 2時間近くも時間が
かかってました。ファイル数が 1万を優に超えてたためです。
もしこれが、PC も変えないで半分くらいの時間で済むのだったら・・大変なことです。


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


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 ではこの辺大丈夫そうですね。


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 で調べれば判定可能
なのですが、従来は無かった動作だけに新たな処理が必要になりそうです。


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でんち」 オリジナルサンプルプログラム
に掲載しましたので、興味ある方がいましたらどうぞお試しください。


2007/08/03
vi の話

まったく 3D とは関係ないし、とても個人的なことで恐縮ですが
たまには開発環境に関するメモも記しておきたいと思います。

普段開発等で使ってるテキストエディタは vi です。
当初 vi を選んだ理由は単純に

 当時使っていた非力なミニコンでは、一番軽いエディタが vi だったため

です。(フルスペックの Emacs は非常に重くて、使っていると他の人に迷惑が
かかったらしい)

他にも端末の都合 (Ctrl+n 等の、Ctrl を併用したキー操作がリピート
できないキャラクタ端末だった) もありますが、
vi の選択は比較的消極的な理由でした。(emacs は憧れでした)


そんなわけで vi を使い始め、慣れてからは結局長いこと使い続けています。
日本語で文章を書いたりマニュアル作成もずっと vi です。

vi だけでなく emacs 系の操作もそれなりに使ってます。shell の行編集は
emacs mode ですし、そして何よりこの blog の初エントリが emacs 風の
キー操作に設定することですから。

WZ Mobile で Emacs もどき

WindowsMobile 等、vi が動かない処理系などでは結構 emacs 系のキーアサイン
で使っています。(PocketWZ3.0 も)


その非常に古くからある古典的なエディタの vi なのですが、ここ最近、
むしろ若い人の方が好んで vi (vim) を使っていて驚くことがあります。


確かに vim がものすごい勢いでパワーアップしており、その変貌ぶりには
目を見張るものがあります。

昔 vi を使っていた経験のある人に向けた、オリジナルを再現するためだけの
単なる vi クローンでは無いわけです。
新しい機能を次から次へと取り込んで、新機能や使いやすさだけでも使う価値
は十分。

昔から知っているオリジナルのユーザーにも、そして便利な機能を求める新しい
ユーザーにも、どちらにも魅力的なエディタとなっているのでしょう。


vi クローン系エディタはこれまでいくつも作られてきましたが、まず最初の
難関がオリジナルの再現度でした。ちょっとしたコマンドの組み合わせによっ
て複雑な動作が可能な vi は、些細な機能の違いも大きな差に感じることが
あります。


最初比較的使ったクローン系は stevie でした。
再現度も割り切りがあって画面更新のアルゴリズムはあまり優秀じゃない
ものの、軽量なので何度も移植したり手を加えたりと、
長い間お世話になりました。


画面分割機能を持った vi として xvi もありました。
これも簡易日本語化して移植したことがありますが、それ以外に極端に目立った
特長は無く、このときは結局カスタムされた stevie を使い続けていました。


elvis は emacs 系のノウハウが活かされており完成度は高かったのですが、
最初 vi らしさの1つである "行折り返し" が無く、なかなか常用に踏み切れ
ませんでした。これも移植したことがあります。


満を持して登場した vim にはやっぱり決定打となるべき魅力がありました。
再現度も高く ex モードまであって、オリジナルの vi にも無かった
Multi Level Undo ができること。visual select ができたり、プリプロセッサ
#if~ の対応も matcing fence できたり、新機能だけでも非常に便利な
エディタになっていました。

主に使い始めたのは vim3.0 からです。これもやはり日本語化移植を
行いました。(後にこれらのパッチを土田さんがまとめてくださいました)


ちなみに当時の vim は 8bit 透過ではなくて、内部コード 7bit で設計
されていました。最上位 8bit 目が visual mode の選択フラグとなっており、
反転表示するための描画情報だったのです。

そのため、8bit 透過を売りにする他のエディタに比べると、visual mode 用
データを別メモリに展開したりなどなど日本語化は結構手間がかかりました・・。


現在の vim の最新版は
http://www.vim.org/
こちらからダウンロードすることができます。
当たり前のように多国語対応なので、そのままで日本語が通ります。
便利です。

さまざまなツールやマクロ、syntax ファイルも公開されています。
例えば Shader fx (HLSL) 用の syntax を install すれば、fx の予約語など
きれいに色分け表示されます。
専用の syntax でなくても、とりあえず .fx を cpp の設定に追加するだけでも
割ときれいにいきます。

普段 fx や doxygen 用の設定を入れていますので、機会があればこの辺りの
設定などもご紹介します。


Vista で WindowsMobile 5.0 の SDK 環境を作るメモです。
ノートにとりあえず作った最小の環境手順です。

WindowsCE の開発場合、何らかの WindowsPC 側に開発環境を構築する
必要があります。クロス開発です。


● WindowsVista

 WindowsVista を使いました。まだ特に何も設定していない状態です。


● VisualStudio2005

 まずは VisualStudio2005 を install します。
 残念ながら Express Edition は Mobile 開発に対応していないようです。
 下記の表を見ると Standard Edition からになってますね。

 ・Visual Studio 2005 機能比較

 今回は Professional Edition を使ってます。Standard では未確認なので
 ご了承ください。

 Win32API しか使わないので、インストール時に Custom を選んで
 C++ 関連残してあとはチェックをはずしました。
 .NET Framework を使うならばたぶん C# も必要でしょう。

 途中で互換性がないとか言われますが、気にせず実行を選んでとりあえず
 進めます。

 VisualStudio が終わって更新チェックもドキュメントのインストールも
 しないで完了。
 ドキュメントはオンラインだけで何とかして HDD をできるだけ
 食わないようにします。


● VisualStudio2005 SP1

 VisualStudio SP1 の install は Vista の Microsoft Update からできます。
 まず Windows Update を Microsoft Update に変更。
 更新をかけるとインストールされます。

 下記のファイルをインストールします。
 Visual Studio 2005 Service Pack 1 Update for Windows Vista


● WindowsMobile5.0 SDK

 ダウンロードは下記のページが参考になります。
 ・Windows Mobile Device - Download

 WindowsMobile5.0 の
 ・WindowsMobile5.0 SDK for PocketPC
 をダウンロードしてインストールします。

 このとき ActiveSync が必要だけど入っていない、とかいわれますが
 無視してインストールします。

 Vista の場合標準で WindowsMobile デバイスを繋いでアクセスできるように
 なっているので、SDK を使ってデバッグするだけなら
 Windows Mobile デバイスセンターを入れなくても大丈夫です。

 なので、普段同期を取ってる PC と開発に使う PC が別な場合でも、
 いちいちゲスト設定を選ばなくて良いので簡単になりました。

 また動作確認やデバッグに、実機 (Device) だけ使うなら Emulator も不要です。


● 接続&実行

 デバイスを USB で接続します。
 EM・ONE の場合は Bluetooth を内蔵していますので、Bluetooth で
 つないでも OK です。

 後は通常の VisualStudio による開発そのままなので、
 WindowsMobile5.0 向けのプロジェクトを開いて、ビルドして、
 接続先を Device に設定して実行です。



やはり VisualStudio2005 の購入が最初の難関でしょうか。

たしか一番最初は、VisualStudio だけでなくて WindowsCE 用の
開発キットも有料でした。
「WindowsCE Toolkit for Visual C++ 6.0」という箱と CD が
今でも手元に残ってます。

その後開発キットが無料でダウンロードできるようになり、
さらには

 ・eMbedded Visual Tools 3.0
 ・eMbedded Visual C++ 4.0

が無料で手に入るようになって、Mobile 向け環境は一時期全部ダウンロード
できるようになりました。
eMbedded Visual Tools/C++ は使い方もメニューも 2003 以前の
VisualStudio そのままです。

PocketPC2003SE 以前の SDK を使う場合は今でも上記ツールが使えますので、
WindowsMobile5.0 SDK にこだわらなければアプリケーションを作ることが
可能です。


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
としてアクセスする方法が考えられます。