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

Direct3D 10 ShaderModel 4.0 で整数の世界

Direct3D10/DirectX10 の ShaderModel4.0 で追加された新機能に
整数演算があります。テクスチャフォーマットにも SINT, UINT など
整数形式が追加されていて、入出力も演算も一通り整数だけの処理が
できるようになりました。
実際に試してみました。

今回使用したフォーマットは DXGI_FORMAT_R8G8B8A8_UINT です。
これまで使われてきた DXGI_FORMAT_R8G8B8A8_UNORM と違うのは、
読み書き時に 0~255 を 0.0~1.0 に変換しないことです。
直接 0~255 の数値(しかも整数)として扱うことができます。

厳密な色コードの判定ができるので、特定の色を抜く、置換する
などといったカラーキー処理がしやすくなります。

また 0/1 だけでよい 2値のフォントデータなどは、各bit に畳み込んで
おくことで効率よくデータを保持することができます。DXT1 で 1pixel
あたり 4bit なので、さらに 1/4 までデータが小さくなると考えられます。

ただし整数読み込みだとフィルタはかかりませんし、D3D10/DX10 では
DXGI_FORMAT_R1_UNORM という 1bit 形式のテクスチャも使えるので、
こちらを使ったほうが良いかもしれません。

// ファイル読み込み (August2007 うまくいかない)
D3DX10_IMAGE_LOAD_INFO	info;
memset( &info, 0, sizeof(D3DX10_IMAGE_LOAD_INFO) );
info.MipLevels= 1;
info.Usage= D3D10_USAGE_DEFAULT;
info.BindFlags= D3D10_BIND_SHADER_RESOURCE|D3D10_BIND_RENDER_TARGET;
info.Format= DXGI_FORMAT_R8G8B8A8_UINT;
info.Filter= D3DX10_FILTER_NONE;
info.MipFilter= D3DX10_FILTER_NONE;
D3DX10CreateShaderResourceViewFromFile( iDevice,
		TEXT("rgba8.dds"),
		&info, NULL, &iTextureBuffer[i], NULL );

UINT でデータを読み込む場合は、D3DX10 を使うと上記のようなコードに
なるでしょう。D3DX10_IMAGE_LOAD_INFO を使ってフォーマットを指定
しています。ところがこれ、うまく動きません。(August 2007 SDK)

UINT であっても内部的に 1/255 倍されてしまうらしく、バッファには
0 か 1 の値が書き込まれてしまいます。
自前でファイルを読み込んで Texture2D を作成すると正しく動作したので、
D3DX10 側の問題かもしれません。(違っていたらごめんなさい)

自分で作成する場合は下記のようになります。ファイルロード部分は
省いています。

// Texture2D の作成, USAGE_DEFAULT なので初期データを必ず与える
ID3D10Texture2D*	riTexture2D= NULL;
D3D10_TEXTURE2D_DESC	t2ddesc;
t2ddesc.Width= *width= phead->dwWidth;
t2ddesc.Height= *height= phead->dwHeight;
t2ddesc.MipLevels= 1;
t2ddesc.ArraySize= 1;
t2ddesc.Format= DXGI_FORMAT_R8G8B8A8_UINT;
t2ddesc.SampleDesc.Count= 1;
t2ddesc.SampleDesc.Quality= 0;
t2ddesc.Usage= D3D10_USAGE_DEFAULT;
t2ddesc.BindFlags= D3D10_BIND_SHADER_RESOURCE|D3D10_BIND_RENDER_TARGET;
t2ddesc.CPUAccessFlags= 0;
t2ddesc.MiscFlags= 0;

// SysMemPitch の設定を忘れないように
D3D10_SUBRESOURCE_DATA	initdata;
initdata.pSysMem= phead->DataBody;
initdata.SysMemPitch= *width / sizeof(DWORD);
initdata.SysMemSlicePitch= 0;

iDevice->CreateTexture2D(
		&t2ddesc,
		&initdata,
		&riTexture2D
	);

// ShaderResourceView に変換する
D3D10_SHADER_RESOURCE_VIEW_DESC	srvdesc;
srvdesc.Format= DXGI_FORMAT_R8G8B8A8_UINT;
srvdesc.ViewDimension= D3D10_SRV_DIMENSION_TEXTURE2D;
srvdesc.Texture2D.MostDetailedMip= 0;
srvdesc.Texture2D.MipLevels= 1;
iDevice->CreateShaderResourceView(
	riTexture2D,
	&srvdesc,
	iResourceView
	);

riTexture2D->Release();

レンダリングも試したので、RenderTargetView も作っておきます。

ID3D10Resource*	riResource;
iResourceView->GetResource( &riResource );
D3D10_RENDER_TARGET_VIEW_DESC	rtvdesc;
memset( &rtvdesc, 0, sizeof(D3D10_RENDER_TARGET_VIEW_DESC) );
rtvdesc.Format= DXGI_FORMAT_R8G8B8A8_UINT;
rtvdesc.ViewDimension= D3D10_RTV_DIMENSION_TEXTURE2D;
iDevice->CreateRenderTargetView( riResource, &rtvdesc, &iRenderBuffer );

シェーダー側で整数値のままテクスチャから読み込むには Load() を
使います。(マニュアルはちょっとしたミスもあるようです。Return Value
が None になってますがこれは間違いでしょう。)
整数値テクスチャの読み込みでは Sample~() 系は使用できず、
サンプラーを通さないのでフィルタの類もかかりません。

uint4	color_00= InputTexture.Load( uint3(uvpos.xy,0) );

Load() の場合、読み込みアドレスはピクセル座標で指定します。
これは一般的な UV 座標の 0~1.0f ではなく、0~imagesize-1 までの
ピクセル位置になります。そのため補間された UV から変換したり、また
画像全体を読み込む場合はあらかじめイメージサイズがわかっていなければ
なりません。画像サイズを調べるには こちら で紹介した GetDimensions() を使います。

float2	pixsize;
InputTexture.GetDimensions( pixsize.x, pixsize.y );

画像全体が不要な場合、例えば任意の 128×128 pixel だけアクセスする
ような場合は、逆に 0~1.0 の UV 値と違って画像サイズを調べる必要が
ありません。
またスクリーンに対して 1対1 で転送を行う場合、PixelShader の
SV_POSITION で受け取ったスクリーン座標をそのまま渡すことができます。
Load() の引数が uint3 なのは、最後に MipLevel の指定が必要だからです。

読み込むテクスチャがどのフォーマットを返すのか、テクスチャの
宣言にも型の指定が必要です。この宣言は下記のようになります。
(なぜかマニュアルに説明がありませんでした)

Texture2D	InputTexture;

整数型でレンダリングする場合は、PixelShader の戻り値も整数で宣言します。

uint4 PS_Main( noperspective float4 Pos : SV_POSITION,
	noperspective float2 UV : TEXCOORD ) : SV_Target
{
 ~

レンダリングできました。きちんと整数のまま読み書きできています。

一般的にフレームバッファは DXGI_FORMAT_R8G8B8A8_UNORM 等の形式を
使うので、画面に描画してテストする場合は変換が必要です。
0~255 を 0.0f~1.0f にマッピングし、float4 で返します。

このへん、上記のシェーダー側の表記方法や機能、設定など、結構マニュ
アルに抜けやミスがあります。HLSL 部分のマニュアルは DirectX9 と
共有されており、D3D9 や ShaderModel 1~3 の解説もマージされています。
そのせいか、肝心の ShaderModel 4.0 の機能がわかりにくくなっています。
唯一の手がかりは実際に試すことです。1つ1つ試してエラーメッセージや
変換されたコードからから機能を類推しなければなりません。
今回、今までのシェーダー出力の調査や解析が役に立ちました。
asm 出力でテクスチャ宣言に型が埋め込んであるのを知っていなければ、
Texture2D はもうしばらく気づかず見落としていたかもしれません。

新型 PSP (PSP-2000) とゲームアーカイブス

新型 PSP (PSP-2000) を入手してしまいました。
といっても結構前です。発売日のすぐ直後に、店頭で売っているのを
見つけたのでついつい。

新型で一番嬉しかったのは、
軽いことでもなく(確かにこれはすごいのですが)、
TVに出力できることでもなく、
読み込みが早いことでもなくて、
方向キーが、最初からちゃんと斜めに入ることです。

以前持っていた PSP は初期型だったので、過去にこんな感じで
苦労していました。
PSP の十字キーが良くなった
PSPで十字キーが斜めに入らない理由

今年になってからは、ゲームアーカイブス のおかげでかなり PSP の
稼働率が上がっています。
Wikipedia ゲームアーカイブス

ゲームアーカイブスでできるのは、PS1 のソフトをダウンロードして
PSP 上で走らせられる、それだけのことです。

思いがけない過去の名作に出会うという、ソフトのよさももちろん
ありますが、むしろ良くできているのはトータルで見た時の使いやすさ
かもしれません。

早い話、今までのゲーム機と比べて非常に便利なのです。

 ・ディスク入れ替え不要で複数のソフトを入れておける
 ・手に入りにくいソフトでも気軽に買える
 ・比較的安価
 ・データダウンロードなのでパッケージやメディアで場所をとらない
 ・メモリカードでソフト本体もセーブデータも一緒に管理できる
 ・PS1 のソフトを常に持ち歩けて、いつでもサスペンドできる
 ・マニュアルもソフト内で読めるので無くさない
 ・PS3 本体でも動くしセーブデータも相互にコピーできる

などなど。
今までのゲームソフトにあった「不便」がかなり無くなっています。
据え置き CD が iPod になったように、ゲームソフトで本当のデジタル
コンテンツらしさを、ようやく実現できたのではないか、とそう感じます。
(そういえば昔、携帯電話のゲームでも同じことを思いました。)

他にも、コンテンツのダウンロード販売として柔軟な点も魅力です。

 ・一度購入すればいつでも再ダウンロードできる
 ・PS3 本体 HDD にソフトが蓄積されるので、いつでも入れ替えできる
 ・メモリカードの内容を PC で簡単にバックアップできる
 ・各ゲームを PC にコピーして保存、入れ替えもできる
  (異なるPSP本体にはコピーできない)

過去に携帯の乗り換えミスで何度もゲームを失ったことを思えば、
柔軟で融通の利くデータ管理は魅力です。

セーブデータの転送も PS3, PSP 間で相互にできるし、PS3 経由で
アダプタを使えば PS1 のメモリーカードとのやり取りもできるし、
自由度は高いです。
ただ、セーブブロックを 1つ1つコピーするのは結構手間がかかり、
どちらが本当に新しいデータなのか たまに わからなくなります。

iTunes + iPod のように、USB でつないで自動でセーブデータを
同期できたらもっと便利になりそうです。もし簡単に同期できるなら、
本当の意味で「外に持ち出して続きをプレイ」が実現しそうですね。

ゲーム本体も、メモリカードの容量が足りなくなったら入れ替える
ことができます。まるでオーディオプレイヤーのように。

これは非常に便利なので、普通のパッケージソフトや新作もぜひ
ダウンロード版を選べるようして欲しいところです。
ゲームは欲しいけれど、パッケージやメディアが場所をとって
たまっていくのはそろそろ避けたいので。CD を購入しても、一度
iTunes に読み込ませたらほとんど触らなくなるのと同じように。

以下、新型への乗換えを行った時のメモです。

●メモリカードの乗り換え

 先に 同じ PSP 本体を使って、より容量の大きいメモリカードに
 乗り換えてみました。
 PC 上でカードの内容をコピーするだけで、ゲームアーカイブスや
 セーブデータを含めて簡単に乗り換え出来ました。

 PSP 自体に USB ストレージ機能があるので、一旦 PC を経由して
 コピーしても大丈夫です。ついでに PC にバックアップも取れます。

 (1) PSP を USB モードにして PC と接続
 (2) PC 上にカードの中身を全部バックアップコピー (PC ← PSP)

 (3) PSP の接続を解除

 (4) PSP のメモリカードを新しいものに入れ替え
 (5) また USB モードで PC に接続
 (6) バックアップコピーを全部書き戻す (PSP ← PC)

●PSP 本体の乗り換え

 異なる PSP 本体では、同じメモリカードを使うことができませんでした。
 セーブデータなどのデータはそのまま持っていけますが、
 ゲームアーカイブスのゲームは起動しません。

 新しい PSP 本体を使って再認証が必要です。具体的には PS3 から
 コピーしなおすだけでした。

 PS3 で購入したゲームデータは PS3 本体の HDD にも保存されます。
 また一度購入したデータはもう一度ダウンロードすることも出来ます。
 PS3 上で展開したら、PSP をつないで △ボタンから「コピー」を
 選ぶと再認証した後 PSP にゲームを転送します。

 巨大なデータの転送しなおしになるので少々時間はかかりますが、
 再ダウンロードしなくて良い分比較的楽でした。

 機器認証は1アカウントにつき最大5台とのことなので、4回以上乗り換えた
 場合にどうなるかわかりません。
 (もしかして PS3 の分も含まれているのでしょうか?)

なお現在は PS3 が無くても、PC からゲームの購入ができるようになって
います。こちらは試していないので、PC を使った場合の再認証とか
再転送とかその辺についても把握しておりません。
PLAYSTATION Store(PC) ゲームアーカイブス