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

AMD Stream SDK (2)

Global Buffer は Shader には無い機能で自由に書き込めますが、同時アクセスで
衝突します。2つの入力値を比較し、相違があった場合だけ値を書き込むような
使い方だと、1回の実行で結果を一カ所に集約できます。
コリジョン判定などに使えるかもしれません。

il_ps_2_0
dcl_input_position v0.xy
dcl_resource_id(0)_type(2d,unnorm)_fmtx(float)_fmty(float)_fmtz(float)_fmtw(float)
dcl_resource_id(1)_type(2d,unnorm)_fmtx(float)_fmty(float)_fmtz(float)_fmtw(float)
dcl_output_generic o0
sample_resource(0)_sampler(0) r0, v0.xy
sample_resource(1)_sampler(0) r1, v0.xy
eq r2.x, r0, r1
cmov g[0], r2.x, v0
mov o0, r2.x000
ret_dyn
end

コンパイル時のエラーは calclGetErrorString() で返ってきますが、
エラー行もわからずあまり意味のある情報が含まれていないので慣れるまで
デバッグは少々大変。

HLSL を使うには utilities\amdhlslCompiler を使います。
HLSL コンパイラ自体はただの lib で、amdhlslCompiler 以下でそのまま
実装されているようです。

関連エントリ
AMD Stream SDK

em1key + oyayubiwm でローマ字入力を使って文字を定義する

oyayubiwm による親指シフト定義ファイルでは、デフォルトで JISかな
入力のキーストロークをシミュレートしています。

例えば “か” を入力する場合 [かな]+[T] となります。

親指入力時に、ローマ字でしか入力出来ない文字を定義したいとの質問があったので
ローマ字入力をシミュレートする方法を試しました。

「か」の入力の場合 script 内で下記のように定義されています。

### かな入力シミュレートの場合
func TABLE_FUNC _kana_KA
	SETMODIFIER	MOD_KANA
	SENDKEY	'T'	# か
	ifsw CSW_COUNT
		CALL	_subCount2
	endif
	RETURN	0
endfunc

これを次のように変更すると、JISかな入力ではなくローマ字入力のキーストロークで
文字入力を行うようになります。

### ローマ字入力シミュレートの場合
func TABLE_FUNC _kana_KA
	SENDKEY	'K'	# か
	SENDKEY	'A'	#
	ifsw CSW_COUNT
		CALL	_subCount2
	endif
	RETURN	0
endfunc

ローマ字入力で定義するメリットは、キーボード配列に依存しないことと
かなキー入力でキーボードにマップされていない文字を入力出来る可能性があることです。
デフォルトでローマ字入力になっていないのは、WindowsMobile は描画が遅いため
子音のエコーバック遅延が生じるのを避けたかったためです。

oyayubiwm v1.45
em1key v1.27
em1keypc v1.32

AMD Stream SDK

コメント欄で、Vista で走らせる方法を教えて頂きました。
WindowsVista x64 SP1 + RADEON HD 4850 を使用しています。

AMD Stream SDK

(1) AMD Stream SDK 1.01.0_beta を install
(2) Folding@home GPU2 6.20 を install
(3) C:\Program Files (x86)\AMD\AMD CAL 1.01.1_beta\lib\xp32
  内の amdcalcl.dll, amdcalrt.dll の代わりに、
  C:\Users\<USER>\AppData\Roaming\Folding@home-gpu
  に入っている dll を使う。

(2) の注意点
すでに NVIDIA GPU 上で Folding@home を走らせたことがある場合は下記の手順で
 1. Folding@home を uninstall
 2. C:\Users\<USER>\AppData\Roaming\Folding@home-gpu を削除
 3. \C:\Program Files (x86)\Folding@home\Folding@home-gpu を削除
 4. Folding@home を再び install

例えば C:\Program Files (x86)\AMD\AMD CAL 1.01.1_beta\bin\xp32 に
入っているサンプルは、上記 dll と同じ場所にあれば実行できます。
サンプルのコンパイルは VisualStudio2008 でも大丈夫でした。

CAL の使い方は
C:\Program Files (x86)\AMD\AMD CAL 1.01.1_beta\doc
の ProgrammingGuide.pdf に従います。

GPU 上で走らせるプログラム kernel はほぼシェーダー相当の機能を持っています。
入出力用のリソースを確保し、context に割り当てて、シェーダー内のレジスタに渡します。

最初少々はまったのは PixelShader 相当だということ。
入力リソースは dcl_input に渡すわけではなく、あくまで texture のように
リソース扱いで sampler 経由で読み込みます。
4096 vector 以下なら constant buffer を使うことも可能です。

dcl_input で受け取るのはラスタライザの補間パラメータ相当です。
CAL の場合はこれを自分自身の ID と見なすことが出来ます。
いわゆるシステム値の SV_Position で、出力位置を指しています。

dcl_input の詳しい説明が載ってませんが、ほぼ次のような宣言の仕方で
使われています。

dcl_input_position v0.xy
dcl_input_interp(linear) v0.xy

例えば実行時の CALdomain が 0,0 から 128,128 までなら
0.5~127.5 のように 0.5 offset された値が入ります。
上の 2つの宣言の違いは特にありませんでした。

CALdomain は出力範囲の指定に相当します。RenderTarget の書き込み範囲です。
実行するプログラムの数や dcl_input のパラメータにも影響します。

シェーダーと違うのは、自由に読み書きできる共有メモリ Global Buffer を持っていることです。
Global Buffer (g[]) は Constant Buffer (cb[]) と同じように、命令内で直接
アドレッシング出来ます。書き込みも出来ます。

mad g[0], r0, g[1], r1

o0~ を使ったストリーム出力と違い書き込み制限がありません。

マニュアルに詳細が書かれていませんが、実際に試したところ 4850/4870 では
最大 2048 vector 使用することが出来ました。

常に 128bit = 4要素単位となるため、8192 float 相当 = 32KByte 分と考えられます。
特に同期などの仕組みがないので、同じ Global Buffer への書き込みが発生した場合、
値は不定となるようです。

実際に Global Buffer から値を読んで Global Buffer に書き込むプログラムを
走らせてみるとわかります。
同じタイミングで実行されているスレッドでは同じ初期値が読み込まれます。
実行スレッド数を増やしていくと、そのうち書き込んだ値を読み出している
スレッドも含まれるようになります。

実行はほぼ ID 順ですが、リソース読みだしを行うと順番が崩れてきます。
リソースアクセス待ち間のスケジューリングのためと考えられます。

DirectX11 の Compute Shader では同期アクセスがサポートされるらしいので、
同じメモリに書き込みを行って合計値を求めるような使い方ができるみたいですが
CAL ではおそらく出来ません。

RADEON HD 4850
##calDeviceGetInfo()
target=5  RV770
maxResource1DWidth=8192
maxResource2DWidth=8192
maxResource2DHeight=8192

##calDeviceGetAttribs()
target=5
localRAM=512
uncachedRemoteRAM=2047
cachedRemoteRAM=2047
engineClock=625
memoryClock=337
wavefrontSize=64
numberOfSIMD=10
doublePrecision=1
memExport=1

関連エントリ
AMD Stream Computing

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

●まとめ

・Animation 無しの場合

 ■ Transform Shader
  + in VertexStream 0 ( _LL_geometry00 ) M3+P
  + in ShaderResourceView ( _LL_geometry00 ) M3+P
  + in ConstantBuffer ( “GRoot” = _LL_rootWorld ) M4
  + out StreamOutput ( _LL_geometryWorld ) M3

・Animation ありの場合

 ■Animation Shader ( Animation Data がある場合のみ )
  + in VertexStream 0 ( _LL_geometry00 ) M3+P
  + in ShaderResourceView ( “GAnimation” = _LL_Anim00 ) M3
  + in ConstantBuffer ( “GTime” )
  + in ConstantBuffer ( “GBind” )
  + out StreamOutput ( _LL_geometryAnimation ) M3+P

 ■Transform Shader
  + in VertexStream 0 ( _LL_geometryAnimation ) M3+P
  + in ShaderResourceView ( _LL_geometryAnimation ) M3+P
  + in ConstantBuffer ( “GRoot” = _LL_rootWorld ) M4
  + out StreamOutput ( _LL_geometryWorld ) M3

 ■Bone Shader ( Bone Matrix を持っている場合のみ)
  + in VertexStream 0 ( _LL_geometry00 ) M3+P // 後半の BoneMatrix のみ
  + in ShaderResourceView ( _LL_geometryWorld ) M3
  + out StreamOutput ( _LL_geometryWorldB ) M3

M3 = Matrix:float4x3
M4 = Matrix:float4x4
P = Index:int4

・描画

 ■Draw IndexedInstanced (single)
  + in VertexStream 0 ( VertexBuffer )
  + in VertexStream 1 ( _LL_geometryWorld )
  + in ConstantBuffer ( “GCamera” )
  + in ConstantBuffer ( “GLight” )

 ■Draw Bone Indexed
  + in VertexStream 0 ( VertexBuffer )
  + in ShaderResourceView ( _LL_geometryWorldB )
  + in ConstantBuffer ( “GCamera” )
  + in ConstantBuffer ( “GLight” )

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

●Animation なしのまとめ

 ■ Transform Shader
 + in VertexStream 0 ( _LL_geometry00 )
 + in ShaderResourceView ( _LL_geometry00 )
 + in ConstantBuffer ( _LL_rootWorld ) 描画時に CPU が与える
 + out StreamOutput ( _LL_geometryWorld )

●Animation ありの場合

準備
 1. 頂点に Geometry の node 番号と weight 値を埋め込んであります。
 2. Animation データは plot した値をバッファにつめて格納します。
 3. 描画単位のノード毎に、参照する bone の逆行列をあらかじめ求めておいて
   GeometryMatrix の後ろに格納しています。
 4. データを読み込み、モデルに Animation データを Bind します。
   ノードとの対応表を作っておきます。

(1) Aniamtion の取り出し

単純化するため plot した値をそのままバッファに格納しています。
外部から描画すべき frame を与えて、そのフレームの Animation ノードを取り出します。
フレーム補間は容易に実現できますがここでは行っていません。

単に frame 値による値の選択なので、独立した描画パスに分ける必然性はありません。
ただし、モデルのノードとアニメーションのノードをマッピングする必要があります。

・アニメーションデータの n 番が、実モデルの何番の node に対応するのか
・対応するノードがモデルに存在しているかどうか

これらのノード対応表をデータを読み込んだあと、Animation を bind するときに
作成しておきます。

アニメーションデータのバッファ定義は下記の通り。

// 実際のアニメーションデータ
Buffer	_LL_Anim00
Usage	IMMUTABLE
Cpu	0
Bind	SHADER_RESOURCE
Format
	MATRIX:32x4F
	MATRIX:32x4F
	MATRIX:32x4F
 ~

ノード毎の frame 単位の値を、単一バッファにパックしています。
ノード対応表にはただの番号ではなく、バッファ内のオフセット値を入れておきます。

例えば index0~31 が node 3 のデータ、index 32~63 が node 1 に bind され
たとします。ノード→Animation の対応表は次のようになります。

 0: 無し
 1: 32
 2: 無し
 3: 0

ここでのセットアップ情報

// Shader Setup
Vertex			_LL_geometry00
CBuffer 		"GBind"= マップデータ
ShaderResourceView 	"GAnimation"= _LL_srv_Anim00
StreamOut		_LL_geometryAnimation
Draw	POINTLIST

その他のバッファは下記の通りです。

// アニメーション参照用
ResourceView	_LL_srv_Anim00
Link	_LL_Anim00
Format	32x4F BUFFER
Element	151110

// 出力バッファ
Buffer _LL_geometryAnimation
Usage	DEFAULT
Cpu	0
Bind	STREAM_OUTPUT SHADER_RESOURCE VERTEX_BUFFER
Format
	MATRIX:32x4F
	MATRIX:32x4F
	MATRIX:32x4F
	INDEX:32x4UI

出力は、アニメーションを適用した _LL_geometry00 の置き換えとなります。

// Animation Shader
struct TBindMap {
	uint	Remap;
	uint	reserved0;
	uint	reserved1;
	uint	reserved2;
};

cbuffer cb_GBind
{
	TBindMap	GBind[MAX_TRANSFORM_BUFSIZE_];
};

struct VS_INPUT_ANIMATION { // _LL_geometry00
	float4	Mat0		: MATRIX0;
	float4	Mat1		: MATRIX1;
	float4	Mat2		: MATRIX2;
	uint4	Parent		: INDEX;
	uint	VertexID	: SV_VertexID;
};

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

VS_OUTPUT_ANIMATION VS_Animation( VS_INPUT_ANIMATION In )
{
	VS_OUTPUT_ANIMATION	Out;

	float4	cmat0= In.Mat0;
	float4	cmat1= In.Mat1;
	float4	cmat2= In.Mat2;

	uint	id= GBind[ In.VertexID ].Remap;
	if( id != 0xffffffff ){
		// animation
		uint	offset= id * 3 + cFrame.x*3;
		Out.Mat0= GAnimation[ offset + 0 ] * cBlend.x;
		Out.Mat1= GAnimation[ offset + 1 ] * cBlend.x;
		Out.Mat2= GAnimation[ offset + 2 ] * cBlend.x;
	}else{
		// through
		Out.Mat0= In.Mat0;
		Out.Mat1= In.Mat1;
		Out.Mat2= In.Mat2;
	}
	Out.Parent= In.Parent;

	return	Out;
}

(2) 階層構造の解決

(1) の出力は _LL_geometry00 と同じものなので、これを入力として
Animation なしの場合と同じ Transform Shader に与えます。

Vertex			_LL_geometryAnimation
CBuffer			"GRoot"= _LL_rootWorld
ShaderResourceView	"GTree"= _LL_srv_geometryAnimation
StreamOut		_LL_geometryWorld
Draw	POINTLIST

(3) スキニングのためのマトリクス計算

それぞれの頂点が参照するノード個数分、静止状態で前計算した Bone の逆行列を
あらかじめ格納しておきます。これは BasePose に相当します。

この Matrix には Bone Matrix の参照用 id と、描画するオブジェクト自体が
存在する node の id を持たせておきます。

バッファ数を減らすためと管理しやすくするために、非 Animation データでも
使っていた GeometryMatrix ( _LL_geometry00 ) を流用します。
この node 用 Geometry Matrix の後ろに、追加で Bone 用 Matrix 領域を
確保しておきます。

_LL_geometry00 は index 部の .x に parent の node Geometry 番号を
格納していました。

Bone 用マトリクスは、.x に Bone Matrix の番号、.y に描画するオブジェクトの
node 番号を格納しています。

Buffer	_LL_geometry00
Usage	IMMUTABLE
Cpu 	0
Bind	VERTEX_BUFFER SHADER_RESOURCE
Format
	MATRIX:32x4F
	MATRIX:32x4F
	MATRIX:32x4F
	INDEX:32x4UI	// .x= parent/bone id,  .y= world id
# node 0
 ~
# Primitive0 - Bone0
f -0.187506 -0.875848 0.444671 -7.197771
f -0.011216 0.454579 0.890635 1.248997
f -0.982199 0.162011 -0.095059 1.356605
i  6  26  0  1
 ~

ここで必要なのは Bone Matrix の部分だけです。
この値を Vertex (Stream) として入力し、アニメーション変換済みの
Bone Matrix に乗算します。

StreamOut	_LL_geometryWorldB
Vertex		_LL_geometry00	// offset 付き (Bone Matrix のみ入力)
ShaderResourceView "GOutT"= _LL_srv_geometryWorld
Draw	POINTLIST
struct VS_INPUT_BONE {
	float4	Mat0	: MATRIX0;
	float4	Mat1	: MATRIX1;
	float4	Mat2	: MATRIX2;
	uint4	Parent	: INDEX;
};

struct VS_OUTPUT_BONE {
	float4	Mat0	: MATRIX0;
	float4	Mat1	: MATRIX1;
	float4	Mat2	: MATRIX2;
};


VS_OUTPUT_BONE VS_Bone( VS_INPUT_BONE In )
{
	VS_OUTPUT_BONE	Out;

	uint	boneid= In.Parent.x;
	uint	worldid= In.Parent.y;

	float4	rmat0= In.Mat0;
	float4	rmat1= In.Mat1;
	float4	rmat2= In.Mat2;

	M34_MATRIX	mp;
	mp= mul34(
			GOutT[ boneid*3 + 0 ],
			GOutT[ boneid*3 + 1 ],
			GOutT[ boneid*3 + 2 ],
			rmat0,
			rmat1,
			rmat2
			);

	// worldid の乗算を行うと動的な変化に対応できるけどしていない
	Out.Mat0= mp.mat[0];
	Out.Mat1= mp.mat[1];
	Out.Mat2= mp.mat[2];

	return	Out;
}

今見ると、無理に Transform Shader を共有しようと思わなければ
もっと簡略化できそうですね。

関連エントリ
Direct3D10 GPU だけでジオメトリ計算 (1)