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

Direct3D10 GPU だけでジオメトリ計算 (1)

アニメーションから描画までの一連の演算を GPU のみで行ってみます。
少々古いネタで恐縮ですが、実際に 1年くらい前に作成した描画パスです。

特徴はまじめに描画エンジンに組み込んだこと。
Maya 等のツールから出力したデータを、画面に描画するまでの一連の作業に対応できます。
やってる内容はたいしたものではないものの、実作業ではどんなデータが来るかわかりません。
さまざまな状況への対応を考えると、やることは意外に複雑です。

例えば Animation は任意の Model と bind 可能で、各 node と対応する順番も任意、
存在しない node は無視し、Animation しないノードと共存できる必要もあります。
Bone 構造を持ったデータでも Animation を Bind せずに描画する状況も考えられます。

欠点はレンダリング回数が増えたために遅くて実用にならなかったことです。
他にもいろいろ理由があるのですが、、今までお蔵入りでした。

●考え方

・Animation なし

(1) Buffer 上の Geometry を元に各ノードの world matrix を算出します。
(2) (1) の結果をもとに実際の Primitive 描画を行います。

・Animation あり

(1) 任意 frame の Animation 値を、node 対応付けしながら取り出します。
(2) (1) の出力を入力と見なして world matrix の算出します。
(3) node 毎に bone が参照している matrix と、前計算した逆行列を乗算します。
(4) 頂点毎に必要なノードを参照し、blend しながら描画します。

●Animation なしの場合

(1) TransformShader

あらかじめ各 node 毎の変換 Matrix を Buffer に格納しておき、
描画時に world matrix に変換します。

Matrix は stream 入力し、変換した結果もそのまま Stream Output します。
ただし各ノードは親の影響を受けるため、他の Matrix を参照する必要があります。
そのため全く同じ Geometry Matrix のバッファを

・Vertex (StreamInput)
・ShaderResourceView

の両方に入力します。
現在の node は Stream (Vertex) で参照し、階層をたどる場合に
ShaderResourceView を使います。

Matrix は 4×3 で、空いた 4要素に親の index 番号を格納しています。

// 各 node の matrix。あらかじめ用意しておく。書き換えない
Buffer	_LL_geometry00
Usage	IMMUTABLE
Cpu 	0
Bind	VERTEX_BUFFER SHADER_RESOURCE
Format
	MATRIX:32x4F
	MATRIX:32x4F
	MATRIX:32x4F
	INDEX:32x4UI	// .x= 親の index 番号、 .yzw= 未使用
# node 0
f  1.0  0.0  0.0  0.0
f  0.0  1.0  0.0  0.0
f  0.0  0.0  1.0  0.0
i  99999  0  0  1	// 99999 = 親無し
# node 1
f  1.0  0.0  0.0  0.0
f  0.0  1.0  0.0  0.0
f  0.0  0.0  1.0  0.0
i  0  0  0  1
 ~

必要な node の数だけ matrix が並びます。

セットアップは下記の通り。
“GRoot” や “GTree” は Shader 内のシンボル名で、それぞれ _LL_rootWorld と
_LL_srv_geometry00 を割り当てています。

// 描画時の Shader Setup
Vertex			_LL_geometry00
CBuffer			"GRoot"= _LL_rootWorld
ShaderResourceView	"GTree"= _LL_srv_geometry00
StreamOut		_LL_geometryWorld
Draw	POINTLIST

その他のバッファ定義

// Root の Transform 値。アプリケーションから渡す描画位置
Buffer	_LL_rootWorld
Usage	DEFAULT
Cpu	0
Bind	CONSTANT_BUFFER
Format
	MATRIX:32x4F
	MATRIX:32x4F
	MATRIX:32x4F
	MATRIX:32x4F

// ResourceView
ResourceView	_LL_srv_geometry00
Link	_LL_geometry00
Format	32x4F BUFFER
Elemet	4

// 出力用
Buffer	_LL_geometryWorld
Usage	DEFAULT
Cpu	0
Bind	VERTEX_BUFFER STREAM_OUTPUT SHADER_RESOURCE
Format
	MATRIX:32x4F
	MATRIX:32x4F
	MATRIX:32x4F

問題は、各ノードが親の影響を再帰的に受けること。
相互参照が発生するため 1pass では完了しません。
ここでは index を元に毎回 root までループして計算し尽くしています。
無駄が発生しますがとりあえず一回で終わります。

// Transform Shader

struct VS_INPUT_TRANSFORM {	// _LL_geometry00
	float4	Mat0	: MATRIX0;
	float4	Mat1	: MATRIX1;
	float4	Mat2	: MATRIX2;
	uint4	Parent	: INDEX;
};

struct VS_OUTPUT_TRANSFORM {	// _LL_geometryWorld
	float4	Mat0	: MATRIX0;
	float4	Mat1	: MATRIX1;
	float4	Mat2	: MATRIX2;
};

VS_OUTPUT_TRANSFORM VS_Transform( VS_INPUT_TRANSFORM In )
{
	VS_OUTPUT_TRANSFORM	Out;

	uint	parent= In.Parent.x;
	float4	cmat0= In.Mat0;
	float4	cmat1= In.Mat1;
	float4	cmat2= In.Mat2;

	// 親まで計算する
	for(; parent < 0xffff ;){
		M34_MATRIX	mp;
		mp= mul34(
				GTree[ parent ].M[ 0 ],
				GTree[ parent ].M[ 1 ],
				GTree[ parent ].M[ 2 ],
				cmat0,
				cmat1,
				cmat2
				);
		cmat0= mp.mat[0];
		cmat1= mp.mat[1];
		cmat2= mp.mat[2];
		parent= GTree[ parent ].Parent.x;
	}

	// 最後に root を乗算
	M34_MATRIX	mp2;
	mp2= mul34(
			GRoot[ 0 ],
			GRoot[ 1 ],
			GRoot[ 2 ],
			cmat0,
			cmat1,
			cmat2
			);

	Out.Mat0= mp2.mat[0];
	Out.Mat1= mp2.mat[1];
	Out.Mat2= mp2.mat[2];

	return	Out;
}

(2) 描画

上で求めた _LL_geometryWorld を元にモデルデータの描画を行います。
頂点毎の index で _LL_geometryWorld を参照してもいいし、
オフセットを付けて空いている Stream に入れ、
1回だけのインスタンシングでパラメータを受け取ることも出来ます。

インスタンシング描画命令を使うとオフセットを渡せるので、
ジオメトリ番号を渡すためだけにバッファを書き換える必要がなくなります。

AMD Stream Computing

NVIDIA の CUDA と同じように AMD (ATI) では AMD Stream SDK が用意されています。

AMD Pioneers Stream Computing on Graphics Processor Units
AMD Stream SDK

XP のみ対応となっているようで Vista では動作しませんでした。
RADEON + Vista で Folding@Home が動作しない理由もこの辺でしょうか。 (2008-08-19 追記を参照してください)
インストール自体は可能でドキュメントは読むことが出来ました。

CUDA は C言語 の拡張で、Cg と同じように専用コンパイラを用いた高級言語が
使われています。内部の詳細は出来る限り見えないように隠されています。
対する AMD Stream SDK は R600 (RADEON HD2000/3000) の詳細な資料が付属しており
ハードウエアから GPU の ISA までより詳しく内部構造を理解できるようになっています。

資料を見ると、GPGPU よりと思われる G80 系の GeForce と違い、
従来のグラフィック向け GPU 色をそのまま強く残しているように見えます。
プログラムの実行もシェーダーパイプラインをほぼなぞる形で実行されており、
メモリアクセスも頂点やテクスチャのフェッチと変わらないようです。

ネイティブコードは VLIW なので、これをそのまま人間が手で書いてパイプライン
最適化を行うのは困難です。
そのため、よりわかりやすくハードウエアを抽象化した
CAL (Compute Abstraction Layer) が用意されています。

CAL で用いられる言語もアセンブラですが、ハード依存を無くした比較的シンプルな
命令セットになっているようです。
この命令は IL (Intermediate Language) と呼ばれています。

IL でプログラムを用意しておけば、ドライバが実行時に GPU にあわせて
ネイティブな GPU 命令にコンパイルしてくれます。

この仕組みは GPU としてはごく当たり前の動作です。
例えば Direct3D の Shader は下記の手順で実行されています。

 HLSL → Direct3D IL → Driver 内蔵 Compiler → ネイティブコード

もともと Shader も D3D で定義された汎用の中間コードを、さらにコンパイルして
実行しているわけです。
IL 自体 ShaderModel 4.0 の HLSL がはき出すコードによく似ています。
実際 Shader とほとんど同じもので、HLSL で記述することもできるようです。

グラフィック向け Shader と異なる点としては
・OpenGL/DirectX といったグラフィック用 API を使わなくて良いこと。
 描画とか画面とか複雑な初期化がいらない。
・double 演算など専用の拡張命令が使えること
・オフセットを利用してメモリ書き込み位置をある程度指定出来ること
等でしょうか。

CAL SDK の他に Brook plus SDK が付属しており、Stream プロセッサ向けの
上位言語を使うこともできるようです。

素の GPU にかなり近いこともありますが、比較的低いレベルで抽象化し
さまざまな上位言語に対応する方針であること、など
CUDA との方針の違いがよくわかります。

2008-08-19 追記:
コメントで指摘がありましたが、Folding@home は RADEON + Vista で動きます。
log file をよく見たら Nvidia と誤認識されており、ビデオカードを頻繁に
入れ替えていたのが原因だったようです。
すみません、OS は全く関係なくただの勘違いでした。

UAC 設定や管理者権限で実行したかどうかによりますが、
C:\Program Files (x86)\Folding@home\Folding@home-gpu
だけではだめで
C:\Users\<USER>\AppData\Roaming\Folding@home-gpu
のファイルもすべて消してから Folding@home の GPU2 client (6.20) を
入れ直したら動作するようになりました。

また Vista 対応 CAL runtime も入るとのことで、SDK も試せるかもしれません。
こちらはまだ未確認です。それらしき dll は Roaming 側に入っていました。

DirectX11 へ (D3D11)

MYCOM SIGGRAPH 2008 – NVIDIAが公開、DirectX 11世代の次世代GPUについて

DirectX11 への流れは思ったよりもずっと速いのかもしれません。
直前の Direct3D9 が長かったのでなおさらです。

API セットを一新した Direct3D8 はシェーダーへの流れを加速化し、
GPU の新たな可能性が明らかになりました。
D3D8 の欠点など小規模な改良修正を施し、将来を見据えて先の先の仕様まで
盛り込んだ D3D9 が出たのはわずか 2年後。
この後 D3D10 が出るまで 4年以上、長期にわたり D3D9 が使われ続けています。

汎用化の流れから再び仕様を完全に覆した D3D10 が登場しました。
それも今年の末でおよそ 2年。
足りない部分が明らかとなり、再びフィードバックの時期が来たのかもしれません。
この流れだと 11 はそれほど大幅な仕様変更ではなく、概念的には D3D10 を継承し、
比較的長期間使われるものになるのではないでしょうか。

上記 MYCOM JOURNAL の記事を見ると、パイプラインには新たに HullShader、
Tessellator、DomainShader が追加されています。

従来マルチパスレンダリングは、貧弱なピクセルパイプラインを補うのが目的でした。
その後 PixelShader に傾倒した高機能化が行われ、D3D9 ShaderModel3.0 で
この流れはほぼ一段落しています。

Direct3D10 ではむしろ StreamOutput を使ったジオメトリのためのマルチパスが
必要となりつつあります。
ジオメトリの場合は頂点数の増減など CPU へのフィードバックは難しく、
Index が展開されてしまう欠点もあります。

 (いっそ GPU 側だけで Draw 発行までできたらいいのですが、、
  Larrabee なら出来るのかもしれません。)

Direct3D 11 におけるパイプライン構成要素(HS、TS、DS)の追加は、
このようなジオメトリに対するマルチパス化要求とその問題点を
スマートに解決してくれるものと期待しています。

Direct3D10 と DDS テクスチャフォーマット

D3D10 のテクスチャは汎用的なバッファの一種でピクセルフォーマットが
拡張されています。

特に整数型の直行性が高くなり、固定少数として扱うかどうか、符号付きと
みなすかどうかといったバリエーションが増えています。
また Texture Array が追加されたため、従来のテクスチャフォーマットよりも
次元が増えました。

DirectX SDK のマニュアルでは、Direct3D 10 用の DDS フォーマット拡張を
定義しています。
残念ながらこの解説は不完全で、このドキュメントを見ても厳密な DDS のヘッダ
構造がわかりませんでした。

DirectX SDK の texconv10 コマンドを使うと D3D10 用のテクスチャを生成する
ことができます。このコマンドでヘッダの違いを調べてみました。
以下 DirectX SDK Aug2008 x64 を使用しています。

● ヘッダサイズの疑問

DDS テクスチャのヘッダは先頭の dwMagic と構造体を合わせてちょうど 128byte です。
Programming Guide for DDS によるとヘッダブロックは次のように定義されています。

In Direct3D 10:
  DWORD             dwMagic;  // 4byte
  DDS_HEADER        header;   // 124byte  = 0x7c
  DDS_HEADER_DXT10  header10; // 20byte

In Direct3D 9:
  DWORD             dwMagic;  // 4byte
  DDSURFACEDESC2    header;   // 124byte  = 0x7c

sozeof(DDS_HEADER) == sizeof(DDSURFACEDESC2) == 124byte で内容は互換性が
あります。

Direct3D10 で追加された DDS_HEADER_DXT10 は 5要素の 32bit UINT でできており
20byte と少々中途半端な大きさです。ヘッダサイズのアライメント崩れてしまう
のでこのような構造にするとは考えられにくいのが正直なところです。
例えば R32G32B32A32_FLOAT 型のテクスチャを読み書きする場合でも、ヘッダを
含めると SSE の直接アクセスに制限が生じることになります。

DDS_HEADER(DDSURFACEDESC2) の最後、DDS_PIXELFORMAT の後ろがちょうど
20byte あるので、もしここに格納されるのならつじつまが合います。

ところが実際に texconv10 で D3D10 専用形式に変換してみると、
ぴったりファイルサイズが 20byte 増えています。
128byte 目以降に追加の DDS_HEADER_DXT10 が入っていました。

よって DDS_HEADER_DXT10 の追加ブロックはマニュアルのヘッダ構造が正解です。
Direct3D 10 用 DDS はヘッダが 20byte 増えて 148byte になります。

● ヘッダ区別の方法

DDS ファイルを開いたときに、Direct3D 10 の拡張情報が含まれているかどうか
判断する必要があります。先頭 128byte 部分はほぼ完全な互換性があり、
追加情報を読むべきかどうかの手段が明確にされていないようです。

基本的に D3D9 形式で表現できるテクスチャは D3D9 ヘッダのみ格納するようです。
例えば次のように dds 変換した場合

(1) texconv   -ft dds white.bmp -m 1 -sx _dx9   -f a8r8g8b8
(2) texconv10 -ft dds white.bmp -m 1 -sx _unorm -f r8g8b8a8_unorm
(3) texconv10 -ft dds white.bmp -m 1 -sx _sint  -f r8g8b8a8_sint

出力
(1) white_dx9.dds   262272byte
(2) white_unorm.DDS 262272byte
(3) white_sint.DDS  262292byte

同じ texconv10 を使っても、D3D9 形式と互換性のある (2) と D3D10 専用の (3)
ではファイルサイズに 20byte の差が生じます。

このとき (2) の DDS_PIXELFORMAT は DDPF_RGB|DDPF_ALPHAPIXELS となり
Mask にも一通り正しい値が入りました。

(3) の DDS_PIXELFORMAT は DDPF_FOURCC で、dwFourCC に 0x30315844 = ‘DX10’
が入りそれ以外はゼロです。

よって、D3D10 拡張 DDS の場合は dwFourCC を見て、’DX10′ の場合のみ追加
の D3D10 ヘッダを参照すればいいようです。

ここで悩んだのは、(2) のテクスチャにも 128byte 目以降に追加のヘッダ情報が
格納されているように見えること。
当初 dwFourCC != ‘DX10’ の場合でも DDS_HEADER_DXT10 が挿入される場合が
あるのではないかと疑ったのですが、ファイルサイズを見るとデータ領域と
判断して良さそうです。データが壊れているのかもしれません。

その後いろいろ試したけど、やはり texconv10 ではデータが壊れてしまう問題が
生じるようです。x86 bin でもだめ。
March 2008 の texconv10 だと正しい値が取れています。要調査。

EMONSTER lite (S12HT) 対応 ctrlswapmini lite v1.02

濁点・半濁点の後付変換に対応しました。
単独で文字としての入力は出来ませんが「は」+「゛」=「ば」のように
直前の文字に追加で合成できるようになりました。
その後「゜」で「ぱ」にも置換できます。

ctrlswapmini lite v1.02

2タッチで一度動き出せば安定しますが、
起動直後やトグル入力だと、まだ入力モードが安定しないことがあるようです。

トグル入力で全角半角の境界でたまに改行が割り込んでしまうのは、
未確定バッファの確定の判断を間違えることがあるからです。

まだキーバッファにコマンドがたまっている可能性があるので、IME の状態を
参照してもうまくいきません。今現在の状態であって、送信すべきキーが処理
されるタイミングでの結果をあらわしていないからです。

以前の ctrlswapmini では、この誤差に対して、確定するけど改行しないキー操作を
使っていました。Advanced Wnn はこの操作が使えなかったのでただの改行に
置き換えているのが原因です。
送信したキーストロークから IME バッファ状態を測する必要があるかもしれません。

関連エントリ
EMONSTER lite (S12HT) 対応 ctrlswapmini lite v1.01 更新