月別アーカイブ: 2009年2月

Direct2D (5) ID2D1Geometry を使う

GPU の活用によりラスタライズはたいしたウエイトを占めておらず、CPU の仕事は
与えるデータの構築だけとなりました。それゆえ Direct2D はただの描画命令の
集まりにはなっていません。
ベクター描画の途中に介入し、ベクトルの情報のままデータを扱うことが可能です。
Direct2D が単なる GDI の置き換えではなく、可能性を秘めているのはこの Geometry
ではないかと思っています。

実際に ID2D1Geometry がどのようなものか興味あるので、機能を 1つ 1つ追いかけて
みました。

● Path の作成

Path の作成は下記の通り。

ID2D1Factory*	iFactory;
ID2D1PathGeometry*	iPath;
iFactory->CreatePathGeometry( &iPath );

ID2D1GeometrySink*	isink;
iPath->Open( &isink );
isink->SetFillMode( D2D1_FILL_MODE_WINDING );
isink->BeginFigure( startpos, D2D1_FIGURE_BEGIN_FILLED ); // = SVG M
isink->AddLine( pos1 );	    // = SVG L
isink->AddBezier( D2D1::BezierSegment( c2, c3, c4 ) );	 // = SVG C
 ...
isink->EndFigure( D2D1_FIGURE_END_CLOSED );   // = SVG Z
isink->Close();

ID2D1GeometrySink::Close() (ID2D1SimplifiedGeometrySink::Close()) は
SVG 等の Z コマンド相当かと思っていたら EndFigure( D2D1_FIGURE_END_CLOSED )
の方でした。

D2D1_FIGURE_END_OPEN     そのまま
D2D1_FIGURE_END_CLOSED   BeginFigure() の位置に直線で接続(閉じたパスにする)

ID2D1GeometrySink::SetFillMode() では塗りつぶしのルールを決定します。
SVG 等の vector graphics にある evenodd / nonzero に相当します。

・D2D1_FILL_MODE_ALTERNATE
 ”evenodd” 相当。外からのレイが交差した回数で決定します。
 交差回数が偶数なら外、奇数は中。パスの回転方向は影響しません。

・D2D1_FILL_MODE_WINDING
 ”nonzero” 相当。外からのレイが交差するとき、交わった線の方向を考慮します。
 たとえば閉じているパスが仮に右回りなら +1、左回りなら -1、結果が 0 なら外。
 つまりパスの回転方向を見ており、逆回転はくり抜き。

● Sink

Sink は値の追加、データをためることだけが出来るバッファです。
Add 系命令のみで Get はありません。
ID2D1Geometry では各オペレーションに対して受け皿である Sink を渡します。

最初にただの box を作ってみます。

ID2D1RectangleGeometry*	iRect;
iFactory->CreateRectangleGeometry(
	D2D1::RectF( 0.0f, 0.0f, 60.0f, 50.0f ), &iRect );

この box を元に、線を太らせる処理を行います。
もともとは描画時に太い幅 (strokeWidth) の線で描画するための変換ですが、
描画前に Path として受け取ることが出来るわけです。

// 変換結果を格納する ID2D1PathGeometry を作る
ID2D1PathGeometry*	iPath2;
iFactory->CreatePathGeometry( &iPath2 );

// ID2D1PathGeometry の sink に変換結果を受け取る
ID2D1GeometrySink*	isink;
iPath2->Open( &isink );
// iRect の変換結果を isink へ
iRect->Widen( 10.0f, NULL, NULL, isink ); // strokeWidth= 10.0f
isink->Close();

実際に描画したのがこちら。

Direct2D sample

↑左から順に
 ・元の Box
 ・元の Box を幅 10 の線で描画したもの
 ・幅 10 の描画を Path で受け取ったもの
 ・幅 10 で Path 化したものをさらに 幅 4 の線で描画したもの

// 描画コード
iRenderTarget->SetTransform( D2D1::Matrix3x2F::Translation( 20.5f, 20.5f ) );
iRenderTarget->DrawGeometry( iRect, iBrush0 );

iRenderTarget->SetTransform( D2D1::Matrix3x2F::Translation( 120.5f, 20.5f ) );
iRenderTarget->DrawGeometry( iRect, iBrush0, 10.0f );

iRenderTarget->SetTransform( D2D1::Matrix3x2F::Translation( 220.5f, 20.5f ) );
iRenderTarget->DrawGeometry( iPath2, iBrush0 );

iRenderTarget->SetTransform( D2D1::Matrix3x2F::Translation( 320.5f, 20.5f ) );
iRenderTarget->DrawGeometry( iPath2, iBrush0, 4.0f );

RenderTarget には直接描画可能な DrawRectangle() 命令があります。
Direct2D では内部的には全く同じように

(1) CreateRectangleGeometry() 等でプリミティブを作成
(2) Widen() など与えたオプションによって必要な変換を行う
(3) Tessellate() で ID2D1Mesh 化
(4) 最終的にすべて ID2D1RenderTarget::FillMesh()

という手順で描画しているだけかもしれません。この場合一番低レベルな描画は
FillMesh() になります。
ただし現在 FillMesh() での直接描画が成功しておらず、これらは予想の範囲を
超えておりません。

もし予想が合っているのなら、Geometry を扱うことでより柔軟なデータ管理ができます。
たとえば比較的 Mesh に近い状態のままキャッシュすることで、高速化や再利用が
可能となるでしょう。毎回 stroke を指定して Draw するよりも Geometry か Mesh
化しておいた方が高速だと思います。

●変換形状の読み出し

Geometry の中身は隠蔽されており、パスの座標値などデータを直接さわることが
できなくなっています。
ところが SDK には、テセレートされたアウトラインフォントの形状を取り出して立体化し、
3D ポリゴンとして描画するサンプルプログラム Interactive3dTextSample があります。

その仕組みはこうです。

(1) Sink 互換のインターフェースを自分で定義しておく
(2) Geometry の Tessellate() 等に渡して変換結果を受け取る

たとえば ID2D1Geometry::Tessellate() であれば、変換後の結果を格納するために
内部で ID2D1TessellationSink::AddTriangles() を呼び出しています。
互換インターフェースで AddTriangles() を定義しておけばトラップ可能となるわけです。

このように、Tessellate() など Geometry で変換した座標値をアプリケーションで
使うことができます。

関連エントリ
Direct2D (4) Direct2D の描画
Direct2D (3) 互換性の検証と Vista で Direct2D
Direct2D その(2) インターフェース
Direct2D と Direct3D10.1 の下位互換

DirectX 一覧 や GPU 年表の更新と ATI Imageon

「 Hyperでんち 」 の古くなっていたいくつかの項目を更新しました。

DirectX 一覧
GPU 年表

DirectX 一覧には Direct3D11 を追加 (リリース前なので未完)
GPU 年表は Mobile の項目を分離。

表を見てわかるとおり Mobile はちょうど今が固定機能パイプラインで DirectX7 世代。
その次は Unitifed Shader が当たり前で、一気に Direct3D10/ShaderModel4 世代と
なる可能性があります。

PowerVR SGX はすでに Unified Shader ベースであり、Intel US15W の GMA500 や
OMAP3 をはじめいくつかのデバイスで使われています。
Qualcomm の MSM に搭載されていた ATI Imageon も、新たに Xbox 360 同様の
Unified Shader Architecture を元に開発が行われていたようです。

AMD Licenses 3D Graphics Core Technology to QUALCOMM, Delivering The Ultimate Visual Experience? to Tomorrow’s Phones

GeometryShader が存在するかどうかわかりませんが Shader ベースであることは確かです。
PowerVR SGX 同様、Vertex/ Pixel の演算ユニットを共有できること、
Graphics 以外への応用が容易なこと、設計時に演算能力をスケーラブルに変更できる
ことがメリットであると考えられます。

Wikipedia Imageon

上記 wikipedia のページには Z460 という新しい Imageon core の名前が載っています。
直接 AMD の pdf 資料へのリンクもあります。登場は 2009 年以降とのこと。

これらのデータより、HTC Diamond 等に使われている現行 MSM7201A の 3D core は
ATI Imageon 2380 シリーズをベースにしたものだと考えられます。

2006/1/30 ImageonR 2380 および ImageonR 2388 で驚くべきゲームおよびマルチメディア体験を可能にし、ハンドヘルド 3D グラフィックスの水準を引き上げる ATI

AMD Imageon 2388/2380

公式ページの資料が参考になります。
Direct3DMobile では使えないものの CubeMap など一通り機能がそろっているようです。
D3D8 相当のスキニング用固定 Matrix Palette もありますが、残念ながら D3DM 側に
対応する機能がありません。
頂点演算は float で行われており、やはり NATIVEFLAOT で正解でした。
違いは VRAM のみで、2388 が内蔵 8MB、2380 が外付けで 16MB 以上に対応とのこと。

関連エントリ
Intel GMA500 のスペックについて考える。続き (2)
Intel GMA500 の機能と性能と Aero
Qualcomm と 3D
HTC Touch Diamond で Direct3DMobile その(11) 問題まとめその他

HID の読み書きと Bluetooth の違いを吸収するために API を選択する方法

せっかくなのでもう少し送受信 API 周りを解説してみます。
前回 列挙して各デバイスのパスがわかったので、あとは Open して実際の通信を行います。
問題は Bluetooth stack によって使用可能な API が異なっていることです。

// デバイスの Open
HANDLE	hdev= CreateFile(
				path,
				GENERIC_READ|GENERIC_WRITE,
				FILE_SHARE_READ|FILE_SHARE_WRITE,
				NULL,
				OPEN_EXISTING,
				FILE_FLAG_OVERLAPPED,
				NULL
			);

if( !HidD_GetPreparsedData( hdev, &Ppd ) ){
	Close();
	return	FALSE;
}

HIDP_CAPS	caps;
if( !HidP_GetCaps( Ppd, &caps ) ){
	Close();
	return	FALSE;
}

// 必要なバッファサイズを調べておく。これを元にキューを作る。
InputByteLength= caps.InputReportByteLength;
OutputByteLength= caps.OutputReportByteLength;

● API テスト

コマンド送信に使う API を選択します。

WindowsVista 標準の bluetooth Stack では HidD_SetOutputReport() を使います。
ところが Toshiba bluetooth Stack / Bluesoleil ではこの命令が使えませんでした。

このあたり、各 stack による違いは下記のエントリでまとめています。
Toshiba bluetooth stack と Bluesoleil 対応

// テストコマンドを送信、直後の ReadFile() で Timeout 判定
if( !HidD_SetOutputReport(
		hDev,
		command,
		cmdlength
			) ){
	return	FALSE;
}

まず最初に HidD_SetOutputReport() を使って何らかのリターンがあるコマンドを
送信してみます。

この API が直接エラーを返す場合は WriteFile() の選択で決定です。ただしエラー
を返してくれるのは Toshiba Stack の場合だけ。Bluesoleil v5 ではエラー無しで
送信も出来ません。

判定の手順

(1) HidD_SetOutputReport() でリターンのあるコマンドを送信してみる。

(2) HidD_SetOutputReport() がエラーを返す場合は WriteFile() を選択で決定。
  Toshiba Bluetooth stack の場合。

(3) エラーが返らない場合は直後に ReadFile() でデバイスの返答を待ちます。

(4) 結果が返ってきたら HidD_SetOutputReport() を選択。Windows stack。

(5) Timeout 判定。一定時間待っても結果が来なければ Bluesoleil。
  送信は WriteFile() を選択する。

●データ送信

HidD_SetOutputReport() には送信するデータを直接渡すことが可能です。
たとえば 4byte なら 4byte のみで構いません。
WriteFile() を使う場合は、必ず OutputReportByteLength 分のバッファを作って
から転送を行います。

char*	bufp= Queue.WriteAlloc();
assert( length <= OutputByteLength );

if( !bufp ){
	// error
	return;
}
memcpy( bufp, command, length );

memset( &WriteOverlap, 0, sizeof(OVERLAPPED) );
WriteOverlap.hEvent= handle;
if( !WriteFileEx(
			hDev,
			bufp,
			OutputByteLength,
			&WriteOverlap,
			_WriteFileCB ) ){
	int	err= GetLastError();
	// error
}

このあたりも、成功するまで何度も試して調べた部分です。
Windows7 など Bluetooth のサポートも強化されるので、おそらく今後は
Windows stack の方が標準になっていくものと考えられます。
そのうちきっと HidD_SetOutputReport() だけ済む時代が来るでしょう。

関連エントリ
複数の HID から情報を読み出すには
Toshiba bluetooth stack と Bluesoleil 対応

複数の HID から情報を読み出すには

以前公開したプログラムに関する質問のメールがほぼ同時に 2通ありました。
どちらも同じ内容でしたので、少々昔のプログラムを引っ張り出してみます。
「接続した複数の HID から情報を読み出すにはどうしたらいいのか」
制作時に参考にしたのはサンプル「\WinDDK\6000\src\hid\hclient\pnp.c」です。

HMODULE hModule= LoadLibrary( "hid.dll" );
HMODULE sModule= LoadLibrary( "setupapi.dll" );
GetProcAddress( hModule, "HidD_GetAttributes" ) )
GetProcAddress( hModule, "HidP_GetCaps" ) )
GetProcAddress( hModule, "HidD_FreePreparsedData" ) )
GetProcAddress( hModule, "HidD_GetPreparsedData" ) )
GetProcAddress( hModule, "HidD_SetOutputReport" ) )
GetProcAddress( hModule, "HidD_GetHidGuid" ) )
GetProcAddress( sModule, "SetupDiGetClassDevsA" ) )
GetProcAddress( sModule, "SetupDiEnumDeviceInterfaces" ) )
GetProcAddress( sModule, "SetupDiGetDeviceInterfaceDetailA" ) )
GetProcAddress( sModule, "SetupDiDestroyDeviceInfoList" ) )

以後列挙して目的のデバイスを探しています。

GUID	hidGuid;
HidD_GetHidGuid( &hidGuid );

HDEVINFO	hdevinfo;
hdevinfo= SetupDiGetClassDevs(
		&hidGuid,
		NULL,
		NULL,
		DIGCF_PRESENT
		|DIGCF_DEVICEINTERFACE
		|DIGCF_ALLCLASSES
	);


SP_DEVICE_INTERFACE_DETAIL_DATA*	funcClassData= NULL;

int	retCode= TRUE;
for( int i= 0 ; i< DEVICELIST_LIMIT ; i++ ){
	SP_DEVICE_INTERFACE_DATA	deviceInfoData;
	memset( &deviceInfoData, 0, sizeof(SP_DEVICE_INTERFACE_DATA) );
	deviceInfoData.cbSize= sizeof( SP_DEVICE_INTERFACE_DATA );
	if( SetupDiEnumDeviceInterfaces(
			hdevinfo,
			0,
			&hidGuid,
			i,
			&deviceInfoData
			) ){
		ULONG	reqLength= 0;
		SetupDiGetDeviceInterfaceDetail(
				hdevinfo,
				&deviceInfoData,
				NULL,
				0,
				&reqLength,
				NULL );

		funcClassData=
			reinterpret_cast(
					dAlloc( reqLength ) );
		if( sizeof(void*) == 4 ){
			funcClassData->cbSize= 5;
		}else{
			funcClassData->cbSize= 
				sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
		}
		if( !funcClassData ){
			retCode= FALSE;
			break;
		}
		if( !SetupDiGetDeviceInterfaceDetail(
				hdevinfo,
				&deviceInfoData,
				funcClassData,
				reqLength,
				&reqLength,
				NULL ) ){
			retCode= FALSE;
			break;
		}
		if( _OpenTestHidDevice( funcClassData->DevicePath,
					VENDOR_ID, PRODUCT_ID ) ){
			// 目的のデバイスを保存しておく
			CheckDeviceOpen( funcClassData->DevicePath );
		}
		if( funcClassData ){
			dFree( funcClassData );
			funcClassData= NULL;
		}
	}else{
		if( GetLastError() == ERROR_NO_MORE_ITEMS ){
			break;
		}
	}
}

SetupDiDestroyDeviceInfoList( hdevinfo );
if( funcClassData ){
	dFree( funcClassData );
	funcClassData= NULL;
}

VENDOR_ID, PRODUCT_ID はデバイスマネージャーのプロパティから
詳細→ハードウエア ID で調べることが出来ます。
このあたりのコードは力業です。たしか作成時 1つ 1つ試しながらだったので、
あまりきれいじゃないと思いつつ書いていた覚えがあります。

int _OpenTestHidDevice( const char* path, int vid, int pid )
{
	HANDLE	hdev= CreateFile(
					path,
					GENERIC_READ|GENERIC_WRITE,
					FILE_SHARE_READ|FILE_SHARE_WRITE,
					NULL,
					OPEN_EXISTING,
					0,
					NULL
				);
	int	found= FALSE;
	if( hdev == INVALID_HANDLE_VALUE ){
		return	FALSE;
	}

	HIDD_ATTRIBUTES	attr;
	if( HidD_GetAttributes( hdev, &attr ) ){
		if( attr.VendorID == vid && attr.ProductID == pid ){
			found= TRUE;
		}
	}
	CloseHandle( hdev );
	return	found;
}

検索して条件に合うデバイスをリストに保持し、それぞれ個別に通信を行っています。
CheckDeviceOpen() は内部でリストに追加しているだけです。
見つかった最初のデバイスだけ見ているか、それとも全部列挙しているかの違い
だけだと思います。

実際のデータ送受信はこのあとですが、Bluetooth Stack の違いを吸収するために
最初に API のテストや通信タイムアウトの判定などを行っています。
ここでの注意点は、複数のデバイスがあっても API の判定は最初の一度だけで
いいということ。

最初にリリースしたバージョンでは新たなデバイスが接続されるたびに API テストが
走ってしまい、すでにつながってるデバイスの通信も一時中断されてしまう問題が
ありました。このあたりの処理をきれいに組み込めずに少々苦労した覚えがあります。

また定期的に接続されているデバイスを確認して、動的な接続&切断に備えています。
すでにオープンしたデバイスかどうかの区別が必要となりますが、結局保持している
パスの文字列比較というもっとも安易な方法で済ませました。

Direct2D (4) Direct2D の描画

●描画

Direct2D の描画は下記の 4通りあります。

・線描画
・塗りつぶし
・イメージ描画
・文字

ID2D1RenderTarget に対して適用できるものです。
それぞれグループ分けと、グループごとに指定出来る共通プロパティをまとめます。

◎線描画
・DrawEllipse (楕円)
・DrawGeometry (パス)
・DrawLine (直線)
・DrawRectangle (四角)
・DrawRoundedRectangle (角が丸い四角形)

    ID2D1Brush
        色や模様

    Stroke
        線の太さ (strokeWidth)
        ID2D1StrokeStyle 線のスタイル
	 (端の形状、丸めたりとんがったり。線の形状、点線やダッシュ点線など)


◎塗りつぶし
・FillEllipse
・FillGeometry
・FillMesh (ID2D1Mesh の描画)
・FillOpacityMask (bitmap でマスク付き塗りつぶし)
・FillRectangle
・FillRoundedRectangle

    ID2D1Brush
        色や模様


◎イメージ描画
・DrawBitmap
・(FillOpacityMask)


◎文字
・DrawGlyphRun (IDWriteFontFace)
・DrawText (IDTextFormatIDWriteTextFormat) 2009/02/06 修正
・DrawTextLayout (IDWriteLeyoutIDWriteTextLayout) 2009/02/06 修正

    ID2D1Brush
        色や模様

●形状

形状の操作は Geometry で行います。
その場合も描画同様のプリミティブが使用できます。
多くの操作では同時に 3×2 Matrix (D2D1_MATRIX_3X2_F) を与えることが可能で、
形や座標判定用の命令も用意されています。

・ID2D1EllipseGeometry
・ID2D1PathGeometry
・ID2D1RectangleGeometry
・ID2D1RoundedRectangleGeometry
・ID2D1TransformedGeometry

CombineWithGeometry
 複数の Geometry 同士の合成や演算を行います。

Tessellate
 テセレートし、トライアングル化した状態にアクセスできるようにします。

Widen
 線として描画する場合に、線を太らせた形状を取り出します。

●Mesh

ID2D1Mesh は直接描画データとしてポリゴン (Triangle) を与える場合に使用します。
非常にデバイスに近い低レベルな状態でデータをもてるものと思われます。
おそらく Geometry → Tessellate も Mesh 相当への変換です。

●DirectWrite

DirectWrite はフォント扱うモジュールとして完全に独立しているようです。
描画対象として Direct2D に拘る必要はなく Direct3D など何でも良いとのこと。
Direct3D11 との組み合わせもおそらく出来るでしょう。

当初は Direct2D のデータに変換して出力しているのかと思いましたが、独自の
レンダラを持っているようです。よく考えると ClearType などフォントに依存する
部分が大きいので当然かも。

フォントを扱うためにはデータとして形状を読み込むだけでなく、同時にレイアウト
も必要となります。このあたりのインターフェースは次の通り。

IDWriteFormat → IDWriteLeyout
IDWriteTextFormat → IDWriteTextLayout

Direct2D のテキスト描画も上記 2つの Interface に対応しています。
また文字単位の形状を直接描画することも可能で、それが GlyphRun 系の命令と
なっているようです。

今回はあまりきちんと試しておらず、メモ程度の内容となっています。
間違いなどあったら訂正していきます。

関連エントリ
Direct2D (3) 互換性の検証と Vista で Direct2D
Direct2D その(2) インターフェース
Direct2D と Direct3D10.1 の下位互換