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 の機能はほぼ同一なので、
この仕様はもしかしたらハードウエア的な都合で決められたものなの
かもしれません。