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

Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験(2)

D3D10/DX10 の新しいテクスチャフォーマットの続きです。
Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験

DXGI_FORMAT_R11G11B10_FLOAT の 11bit / 10bit FLOAT は、
指数部が 16F と同じだったので 16F との相互変換は比較的容易です。
符号を落として下位bit を切り詰めるだけで十分かもしれません。
実際に相互変換してみました。

まず DXGI_FORMAT_R11G11B10_FLOAT への変換です。16F に変換した後
下位 bit の切り落としを行っています。符号が無いので入力が負数の
場合は 0 にクランプしています。

DWORD F32toF11( const float* f32 )
{
    WORD  _f16[3];
    D3DXFloat32To16Array(
            reinterpret_cast( _f16 ), f32, 3 );

    for( int i= 0 ; i< 3 ; i++ ){
        if( _f16[i] & 0x8000 ){
            _f16[i]= 0;
        }
    }

    return  (((DWORD)_f16[0] >>  4) & 0x000007ff)
           |(((DWORD)_f16[1] <<  7) & 0x003ff800)
           |(((DWORD)_f16[2] << 17) & 0xffc00000);
}

次に DXGI_FORMAT_R11G11B10_FLOAT から 32F に変換してみます。
同じように一旦 16F を経由しています。

void F11toF32( float* f32, DWORD f11 )
{
    WORD   _f16[3];
    _f16[0]= (WORD)((f11 << 4) & 0x7ff0);
    _f16[1]= (WORD)((f11 >> 7) & 0x7ff0);
    _f16[2]= (WORD)((f11 >>17) & 0x7fe0);

    D3DXFloat16To32Array( f32,
            reinterpret_cast( _f16 ), 3 );
}

D3DXFLOAT16 のメンバはマニュアルでは WORD Value となってますが、
実際には小文字の value でした。しかも protected だったので
上の例では cast でごまかしています。

DXGI_FORMAT_R11G11B10_FLOAT と同様、Direct3D10/DirectX10 でもう
1つ追加された新フォーマットがあります。

DXGI_FORMAT_R9G9B9E5_SHAREDEXP

こちらも調べてみました。5bit の exponent を持つことから、
R11G11B10_FLOAT 同様により広い範囲をカバーできます。ただし共有
されているため極端にスケールの異なる component を持つことは
できないでしょう。

FLOAT のグループではないことと、フォーマット名に E の成分も記載
されていることから、exponent の演算はシェーダーを併用するのでは
ないかと予想していました。

ところが実際に試すと E5 の成分がきちんと反映された値が返ってきます。
またシェーダーでは E5 の値を直接読み取ることができず、w は存在
しませんでした。(w を読み取ろうとすると 1.0 固定となる)
また RenderTarget にはできません。

R9G9B9E5_SHAREDEXP の各 bit の意味は下記の通りです。

E5    B9        G9        R9
01111 111111111 111111111 111111111

指数部は 5bit なので、16F や R11G11B10_FLOAT と同じように 15
が 0 の offset 15 と仮定します。このとき R9G9B9 を 0 にすると
真っ黒になりました。

上のように R G B すべての bit を 1 にするとほぼ (1.0, 1.0, 1.0)
相当になっています。ただ厳密に一致せず、それぞれわずかに 1.0
より小さな値になります。

この結果からわかるのは、指数部は 16F や R11G11B10_FLOAT ほぼ同じ
内容で合っているのではないかということ。
また R9G9B9 は IEEE754 等の浮動少数の仮数に近いものではなく、
9bit の値がそのまま格納されていることです。511 が厳密に 1.0 に
ならないため、R8G8B8_UNORM のような UNORM 変換 1/511 倍ではなく
1/512 倍している可能性があります。

次に 0xc0040201 を入れてみたところ完全に 1.0 に一致しました。

// 0xc0040201
E5    B9        G9        R9
11000 000000001 000000001 000000001

これは上記のように各 component が 1 で指数は 24 になっています。
offset は 15 なので 24-15 = 9、つまり 1 * (2^9) = 512 相当に
なります。簡単に書けば 1<<9 です。 同様に 0x84020100 でも (1.0, 1.0, 1.0) になります。こちらは
最上位に合わせており次のようになります。

// 0x84020100
E5    B9        G9        R9
10000 100000000 100000000 100000000

16-15 = 1、256 * (2^1) = 512 です。
よって offset 15 の exponent とみなすよりも、offset 16 で正規化
し、最上位 bit を隠していない浮動少数とみなした方がしっくりくる
かもしれません。

16F も R11G11B10_FLOAT も R9G9B9E5_SHAREDEXP も指数部は全く同じ
5bit となっていました。それぞれ exponent の機能はほぼ同一なので、
この仕様はもしかしたらハードウエア的な都合で決められたものなの
かもしれません。

ZERO3[es] に 小型 Bluetooth アダプタ PTM-UBT3S をつないでみた

EM・ONE を WindowsMobile6 アップグレードのため発送しました。
EMOBILE EM・ONE WindowsMobile6 アップグレード申し込み
2007/10/05 の受付開始の朝にすぐ申し込んで、用紙が届いたのが
2007/10/10。2007/10/11 発送でいつ届くか楽しみです。

しばらく EM・ONE が使えなくなったので、久しぶりに W-ZERO3[es] (WS007SH)
を使っています。久しぶりに使うと ZERO3[es] は妙に使いやすく感じます。
カーソルキーが押しやすいのと、長いこと使って慣れていたせい
かもしれません。

最近は Bluetooth キーボード を持ち歩いているので Bluetooth 接続だけは
何とか実現したいところ。

試しに、以前購入した PTM-UBT3S をつないでみました。

変換コネクタが巨大に見えてしまいます。

PTM-UBT3S

↑PTM-UBT3S 本体の角は引っかかりそうだったので やすりで削っています。

つないだところ。

PTM-UBT3SとZERO3[es]

Bluetooth の設定機能を呼び出すために、こちらの星羽さんの
WakeBT Ver1.01 を使わせていただきました。
[自作] Bluetooth機能を呼び起こす WakeBT Ver1.01
インストールして実行するだけなのでとても簡単でした。便利です。

あとは EM・ONE と同じ手順で、設定画面からペアリングすることが可能です。
とりあえず確認したのは下記の 2つです。

・PC との ActiveSync 接続
・Bluetooth キーボード RBK-2000BT2 の接続

便利になりました。
音声系のデバイスは持っていないので、ヘッドセットとかは試してないです。

ちょっと気になったのは、このアダプタをつないでいると ZERO3 が
オートパワーオフしなくなること。ここだけ注意です。
どこかに設定があるのかもしれません。待ち受けだといいのかも。

関連エントリ
小型 Bluetooth アダプタ Princeton PTM-UBT3S
Bluetooth キーボード Rboard for Keitai RBK-2000BT2
em1key Bluetooth keyboard RBK-2000BTII の設定 その3

Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験

Direct3D10/DirectX10 ではいくつかの新しいフォーマットが追加されて
います。その 1つに DXGI_FORMAT_R11G11B10_FLOAT があります。
32bit で 3チャンネル持っていて、かつ FLOAT なのでどんな構造を
しているか興味あります。
調べてみました。

見つけた資料はこちらです。
NVIDIA next-gen-dx10-games-develop06.pdf

各 component 毎に 5bit の exponent があると書かれているので、
RGB がそれぞれ 6e5, 6e5, 5e5 であると考えられます。
符号はありません。
実際にバッファを作って描画してみました。

その結果、0x781e03c0 がちょうど ( 1.0, 1.0, 1.0 ) であることが
わかりました。この値を 2進数に変換すると

7    8    1    e    0    3    c    0
0111 1000 0001 1110 0000 0011 1100 0000

さらに 11bit 11bit 10bit に分解すると

B          G           R
0111100000 01111000000 01111000000

となります。B が上位で 10bit、残りが 11bit です。
exponent が各 5bit で、そのうち 4bit が立っています。

指数部は 5bit の 0~31 で、上記のように center の 15 が 0 であると
考えられます。この構造は fp16 と同じです。fp16 については以前
下記のページに書きました。
DDS Texture format memo
仮数部は最上位の 1 が省略されているので全部 0 です。

DXGI_FORMAT_R11G11B10_FLOAT はテクスチャだけでなく RenderTarget
として実際にレンダリングできることも確認できました。
これはかなり使えそうなフォーマットです。

他に指数を持たない似たような型として次のフォーマットもあります。

DXGI_FORMAT_R10G10B10A2_UNORM
DXGI_FORMAT_R10G10B10A2_UINT

この 10bit 型は DirectX9 にもありました。
だけど D3D10/DX10 になって新しいのは、UNORM だけでなく整数型 UINT が
使えることと、NVIDIA のビデオカードでもちゃんと使えることです。
こちらも RenderTarget としてレンダリングできました。

Direct3D 10 ShaderModel 4.0 で整数アクセス2、異なる View を活用

前回書いた整数テクスチャの扱いに関して少々追加です。
レンダリングで更新する時は整数処理を行いますが、デバッグなどで
画面に描画する時は RenderTarget の 0.0~1.0 に変換する必要があると
下記エントリで書きました。
Direct3D 10 ShaderModel 4.0 で整数の世界
これを DirectX10(D3D10) の機能を使って自動化できます。

まずリソースの作成を DXGI_FORMAT_R8G8B8A8_UINT ではなく、
DXGI_FORMAT_R8G8B8A8_TYPELESS で宣言しておきます。
初期データの渡し方などは同じです。

D3D10_TEXTURE2D_DESC  t2ddesc;
t2ddesc.Format= DXGI_FORMAT_R8G8B8A8_TYPELESS;
  ~
ID3D10Texture2D*  riTexture2D= NULL;
iDevice->CreateTexture2D( &t2ddesc, &initdata, &riTexture2D );

その後、同じリソースから ShaderResourceView を 2個作成します。
こちらは型を明確にして UINT と UNORM にします。

// 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,
	iResourceViewUINT );

srvdesc.Format= DXGI_FORMAT_R8G8B8A8_UNORM; // 固定少数化
srvdesc.ViewDimension= D3D10_SRV_DIMENSION_TEXTURE2D;
iDevice->CreateShaderResourceView( riTexture2D, &srvdesc,
	iResourceViewUNORM );

これでシェーダーからは、iResourceViewUINT 経由でアクセスすると
0~255 の整数値として読み込むことができ、iResourceViewUNORM で
アクセスすると、従来どおり 0~1.0 の少数値で受け取ることが
できるようになります。

例えばシェーダー側では次のように宣言しておきます。

Texture2D	InputTextureUI;
Texture2D	InputTextureF;

エフェクトの変数設定はこんな感じで。

iEffect->GetVariableByName( "InputTextureUI" )->AsShaderResource()->
	SetResource( iTextureBufferUINT );
iEffect->GetVariableByName( "InputTextureF" )->AsShaderResource()->
	SetResource( iTextureBufferUNORM );

受け取るシェーダー側です。

// PixelShader で整数としてアクセスする場合 (0~255)
float4 PS_Update( noperspective float4 Pos : SV_POSITION,
        noperspective float2 UV : TEXCOORD ) : SV_Target
{
    float2  pixsize;
    InputTexture.GetDimensions( pixsize.x, pixsize.y );
    uint2   uvpos= (uint2)( UV.xy * pixsize.xy );
    return  InputTextureUI.Load( uint3(uvpos.xy,0) )* (1.0f/255.0f);
}

// 浮動少数で受け取れるので乗算が不要 (0~1.0)
float4 PS_View( noperspective float4 Pos : SV_POSITION,
        noperspective float2 UV : TEXCOORD ) : SV_Target
{
    float2  pixsize;
    InputTexture.GetDimensions( pixsize.x, pixsize.y );
    uint2   uvpos= (uint2)( UV.xy * pixsize.xy );
    return  InputTextureF.Load( uint3(uvpos.xy,0) );
}

便利です。さすがに良く考えられています。

注意点は、以前のエントリ で書いたように同一のリソースを複数の View
として設定するため、リソースが握られたままになって衝突が
おきやすいことです。
Direct3D 10 HLSL Effect/FX リソース設定のはまり

上記の PixelShader では、UV を 0.0~1.0 に補間した値で受け取って
いるためテクスチャのサイズを乗算する処理が入っています。

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