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 はもうしばらく気づかず見落としていたかもしれません。