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

OpenGLES2.0 DDS テクスチャの読み込みとミップマップ

昨日の続きです。ミップマップと 24bit テクスチャに対応します。
非圧縮形式はほぼ読み込めるようになりました。

DDS ファイルのミップマップの判定です。

    int	mipLevel= 1;
    if( (dp->dwCaps & DDSCAPS_MIPMAP)
            && (dp->dwFlags & DDSD_MIPMAPCOUNT)
            && dp->dwMipMapCount ){
        mipLevel= dp->dwMipMapCount;
    }

DDS テクスチャに含まれている Mipmap Level 数は dwMipMapCount に入ります。
本来最小値は 1 ですが、ツールによっては dwMipMapCount を設定していないものがあります。
dwMipMapCount が有効かどうかは dwFlags の DDSD_MIPMAPCOUNT で判別します。
DDSD_MIPMAPCOUNT が設定されていない場合、また念のため dwMipMapCount が 0 の
場合も 1 と見なした方が良いでしょう。

前回調べたように DDS と OpenGL では一部ピクセル形式に違いがあります。
MipLevel ごとに毎回変換するのは避けたいので、フォーマット判別時に全部書き換えてしまいます。
この変換処理を一度で済ませるために、まずはデータに含まれる全ピクセルの総数を求めます。

MipMap が無ければ width * height です。
何も考えずにファイルサイズ分全部変換してしまうのが一番楽かもしれません。
でも敢えて計算で求めてみました。

  unsigned int  bdif= IntLog2( IntMin( width, height ) ) << 1;
  unsigned long long  mbit= 0xaaaaaaaaLL| ((0x100000000LL >> bdif) - 1);
  mbit &= ~((0x100000000LL >> (mipLevel<<1))-1);
  pixcount= static_cast( (width * height * mbit) >> 31 );

これで任意レベル数の mipmap の合計容量がわかります。
テクスチャが正方形なら (width * height * 0xaaaaaaaaLL)>>31 だけで済みます。
縦横のサイズが異なっている場合の補正が最初の 2行の bdif です。
1pixel を半分にした場合に 0.5pixel にならないよう最小値を clamp しています。

3行目はテクスチャ画像に含まれる MipLevel を制限するためのものです。
たとえば実際のゲームでも、ミップマップでテクスチャがぼけすぎるのを避けるために
MipLevel 数を制限することがあります。1024×1024 サイズでも 5 level 分しか格納
されていないことがあり得るわけです。

IntMin() は最小値を返しているだけ。
問題は IntLog2 の部分ですが x86/x64 では下記のように 1命令で記述することができます。

inline unsigned long IntLog2( unsigned int param )
{
    unsigned long  ret;
    _BitScanReverse( &ret, static_cast( param ) );
    return  ret;
}

_BitScanReverse() は intrinsic で x86 の bsr 命令のことです。

他の CPU 対応を考えると、_BitScanReverse を置き換えるよりも最初からループ等で
MipMap の容量を求めた方が良いかもしれません。

#include  "TextureCache.h"
#include  "IntLog2.h"

#define	DDSCAPS_MIPMAP	    0x00400000
#define	DDSD_MIPMAPCOUNT    0x00020000

struct T_DDSHEADER {
    unsigned int    dwMagic;
    unsigned int    dwSize;
    unsigned int    dwFlags;
    unsigned int    dwHeight;
    unsigned int    dwWidth;
    unsigned int    dwPitchOrLinearSize;
    unsigned int    dwDepth;
    unsigned int    dwMipMapCount;
    unsigned int    dwReserved1[11];
    unsigned int    dwPfSize;
    unsigned int    dwPfFlags;
    unsigned int    dwFourCC;
    unsigned int    dwRGBBitCount;
    unsigned int    dwRBitMask;
    unsigned int    dwGBitMask;
    unsigned int    dwBBitMask;
    unsigned int    dwRGBAlphaBitMask;
    unsigned int    dwCaps;
    unsigned int    dwCaps2;
    unsigned int    dwReservedCaps[2];
    unsigned int    dwReserved2;
};
#define	__DDS__MAGIC	0x20534444	// ' SDD'

static inline unsigned int IntMin( unsigned int a, unsigned int b )
{
    return  a < b ? a : b;
}

template
static void DDStoGL16( unsigned int count, void* data )
{
    unsigned short*	ptr= reinterpret_cast( data );
    for(; count-- ;){
        unsigned int	color= *ptr;
        unsigned int	alpha= color & AMask;
        *ptr++= (color << (16-AShift)) | (alpha >> AShift);
    }
}

static void DDStoGLByte( unsigned int count, void* data, unsigned int stride )
{
    unsigned char*	ptr= reinterpret_cast( data );
    for(; count-- ;){
        unsigned char   r= ptr[0];
        unsigned char   b= ptr[2];
        ptr[0]= b;
        ptr[2]= r;
        ptr+= stride;
    }
}

GLuint _flFASTCALL
TextureCache::DDSLoader( void* mem )
{
    const T_DDSHEADER*	dp= reinterpret_cast( mem );
    int     width= dp->dwWidth;
    int     height= dp->dwHeight;
    void*   data= reinterpret_cast( reinterpret_cast( mem ) + sizeof(T_DDSHEADER) );

    GLenum  format= GL_NONE;
    GLenum  ftype= GL_UNSIGNED_BYTE;

    int	mipLevel= 1;
    unsigned int  pixcount= width * height;

    if( (dp->dwCaps & DDSCAPS_MIPMAP)
            && (dp->dwFlags & DDSD_MIPMAPCOUNT)
            && dp->dwMipMapCount ){
        mipLevel= dp->dwMipMapCount;

        unsigned int    bdif= IntLog2( IntMin( width, height ) ) << 1;
        unsigned long long  mbit= 0xaaaaaaaaLL| ((0x100000000LL >> bdif) - 1);
        mbit &= ~((0x200000000LL >> (mipLevel<<1))-1);
        pixcount= static_cast( (pixcount * mbit) >> 31 );
    }

    switch( dp->dwRGBBitCount ){
    case 32: // 8888
        switch( dp->dwRBitMask ){
        case 0x000000ff: // R G B A = GL
            format= GL_RGBA;
            ftype= GL_UNSIGNED_BYTE;
            break;
        case 0x00ff0000: // B G R A = DX
            format= GL_RGBA;
            ftype= GL_UNSIGNED_BYTE;
            DDStoGLByte( pixcount, data, 4 );
            break;
        }
        break;
    case 24: // 888
        switch( dp->dwRBitMask ){
        case 0x0000ff: // R G B = GL
            format= GL_RGB;
            ftype= GL_UNSIGNED_BYTE;
            break;
        case 0xff0000: // B G R = DX
            format= GL_RGB;
            ftype= GL_UNSIGNED_BYTE;
            DDStoGLByte( pixcount, data, 3 );
            break;
        }
        break;
    case 16:
        switch( dp->dwGBitMask ){
        case 0x00f0: // 4444
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_4_4_4_4;
            DDStoGL16<12,0xf000>( pixcount, data );
            break;
        case 0x07e0: // 565
            format= GL_RGB;
            ftype= GL_UNSIGNED_SHORT_5_6_5;
            break;
        case 0x03e0: // 1555
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_5_5_5_1;
            DDStoGL16<15,0x8000>( pixcount, data );
            break;
        case 0x0000: // L A 88
            format= GL_LUMINANCE_ALPHA;
            ftype= GL_UNSIGNED_BYTE;
            break;
        }
        break;
    case 8:
        if( dp->dwRGBAlphaBitMask ){
            format= GL_ALPHA;
            ftype= GL_UNSIGNED_BYTE;
        }else{
            format= GL_LUMINANCE;
            ftype= GL_UNSIGNED_BYTE;
        }
        break;
    }

    if( format == GL_NONE ){
        return	0;
    }

    GLuint  texid= 0;

    glGenTextures( 1, &texid );
    glBindTexture( GL_TEXTURE_2D, texid );

    for( int mi= 0 ; mi < mipLevel ; mi++ ){
        glTexImage2D(
                GL_TEXTURE_2D,
                mi,     // level
                format, // internal format
                width,
                height,
                0,      // border
                format,
                ftype,
                data
                );
        data= reinterpret_cast( reinterpret_cast( data ) + ((width * height * dp->dwRGBBitCount) >> 3) );
        width>>= 1;
        height>>= 1;
        if( width <= 0 ){
            width= 1;
        }
        if( height <= 0 ){
            height= 1;
        }
    }

    if( mipLevel > 1 ){
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
    }else{
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    }
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    return  texid;
}

TextureCache は多重にテクスチャが読み込まれないよう参照管理を行っています。
リソースマネージャーは統合されており、テクスチャやシェーダー、スクリプトファイル
等が同じ仕組みで管理下に置かれています。

関連エントリ
OpenGLES2.0 DDS テクスチャを読み込む
OpenGLES2.0 Direct3D とのフォーマット変換
OpenGLES 2.0 頂点フォーマットの管理
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理

OpenGLES2.0 DDS テクスチャを読み込む

DXT/BC 等の圧縮や Mipmap/Cubemap, HDR 対応などを考えると DDS は便利な
テクスチャフォーマットです。OpenGLES でも読めるようにしておきます。
まずは非圧縮の 2D 形式のみ考えます。

とりあえず適当に…
これ を書いた当人と思えないくらい いい加減 な判定ですが、DDS (Direct3D) と OpenGL
のフォーマットの対応はこんな感じになります。
後で説明しますがこの段階ではまだ不完全です。

GLuint _flFASTCALL
TextureCache::DDSLoader( void* mem )
{
    const T_DDSHEADER*	dp= reinterpret_cast( mem );
    int     width= dp->dwWidth;
    int     height= dp->dwHeight;
    void*   data= reinterpret_cast( reinterpret_cast( mem ) + sizeof(T_DDSHEADER) );

    GLenum  format= GL_NONE;
    GLenum  ftype= GL_UNSIGNED_BYTE;

    switch( dp->dwRGBBitCount ){
    case 32: // 8888
        switch( dp->dwRBitMask ){
        case 0x000000ff: // R G B A = GL
            format= GL_RGBA;
            ftype= GL_UNSIGNED_BYTE;
            break;
        case 0x00ff0000: // B G R A = DX
            format= GL_RGBA;
            ftype= GL_UNSIGNED_BYTE;
            break;
        }
        break;
    case 24: // 888
        switch( dp->dwRBitMask ){
        case 0x0000ff: // R G B = GL
            format= GL_RGB;
            ftype= GL_UNSIGNED_BYTE;
            break;
        case 0xff0000: // B G R = DX
            format= GL_RGB;
            ftype= GL_UNSIGNED_BYTE;
            break;
        }
        break;
    case 16:
        switch( dp->dwGBitMask ){
        case 0x00f0: // 4444
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_4_4_4_4;
            break;
        case 0x07e0: // 565
            format= GL_RGB;
            ftype= GL_UNSIGNED_SHORT_5_6_5;
            break;
        case 0x03e0: // 1555
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_5_5_5_1;
            break;
        case 0x0000: // L A 88
            format= GL_LUMINANCE_ALPHA;
            ftype= GL_UNSIGNED_BYTE;
            break;
        }
        break;
    case 8:
        if( dp->dwRGBAlphaBitMask ){
            format= GL_ALPHA;
            ftype= GL_UNSIGNED_BYTE;
        }else{
            format= GL_LUMINANCE;
            ftype= GL_UNSIGNED_BYTE;
        }
        break;
    }

    GLuint  texid= 0;
    glGenTextures( 1, &texid );
    glBindTexture( GL_TEXTURE_2D, texid );

    glTexImage2D(
            GL_TEXTURE_2D,
            0,      // level
            format, // internal format
            width,
            height,
            0,      // border
            format,
            ftype,
            data
            );

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    return  texid;
}

OpenGLES で対応できそうなのは下記の各フォーマットです。
A8R8G8B8, A8B8G8R8, R8G8B8, A4R4G4B4, R5G6B5, A1R5G5B5, A8L8, A8, L8
でも実際に表示してみると正しく出るのは一部だけ。
A8R8G8B8, R8G8B8, A4R4G4B4, A1R5G5B5 は色ずれがあり失敗します。

Direct3D と OpenGL ではピクセル内の配置が異なっているようです。
調べてみました。

● OpenGL の pixel 配列

原則として R G B A の順番となります。

各コンポーネント(RGB)が独立している場合は、メモリ上に R G B A の順で配置
されます。たとえば 32bit 8888 がそうです。Byte 単位で書き込まれています。
64bit, 128bit といった Float 形式も同様です。
この順番は x y z w の並びに一致するため、シェーダーから見ても自然なものです。

ただし 8888 を DWORD (32bit) で読み込んだ場合、リトルエンディアンでは下記の
bit 並びになります。最下位が R で最上位が Alpha です。

31 H<---------------->L 0
|  A  |  B  |  G  |  R  |    GL_RGBA + GL_UNSIGNED_BYTE  (R8 G8 B8 A8)

コンポーネントが UNSIGNED_SHORT にパックされている場合は下記の内容になります。
リトルエンディアンで読み込んだ 32bit 8888 と逆順です。

15 H<-------->L 0
| R | G | B | A |          GL_RGBA + GL_UNSIGNED_SHORT_4_4_4_4  (R4 G4 B4 A4)

15 H<-------->L 0
| R  | G  | B |A|          GL_RGBA + GL_UNSIGNED_SHORT_5_5_5_1  (R5 G5 B5 A1)

15 H<-------->L 0
| R  |  G  | B  |          GL_RGB + GL_UNSIGNED_SHORT_5_6_5  (R5 G6 B5)

OpenGL の 32bit 8888 形式は、GL_UNSIGNED_BYTE というシンボル通り Byte
アクセスを想定しています。

● Direct3D の pixel 配列 (32bit 8888 の場合)

・ Direct3D9 以前

Direct3D9 まではフォーマット名称の表記が OpenGL と逆順でした。Direct3D 上は
ARGB と表記されますが、OpenGL の呼び方にあわせると BGRA 相当です。
これは下記の bit 並びを見るとよくわかります。OpenGL と比べてみてください。

31 H<---------------->L 0
|  A  |  R  |  G  |  B  |       D3DFMT_A8R8G8B8

リトルエンディアンなので D3DFMT_A8R8G8B8 をメモリに格納すると B G R A の順番
になります。

なお Direct3D は A8B8G8R8 という逆順フォーマットも持っており、両方使うことが
出来ます。こちらは OpenGL の 8888 と全く同じ並びです。

31 H<---------------->L 0
|  A  |  B  |  G  |  R  |       D3DFMT_A8B8G8R8

ピクセルだけでなく、頂点形式でも D3DDECLTYPE_D3DCOLOR, D3DDECLTYPE_UBYTE4
と 2種類のフォーマットを選択できました。これも上記 2種類の並び順に対応します。

2種類の並び順を持っているのは基本的に 32bit 8888 だけです。
A32B32G32R32F 等の HDR/浮動小数系のフォーマットはすべて A B G R で
OpenGL と同じ順番で配置されています。

◎ Direct3D10 以降

Direct3D は上記のように 32bit 8888 の場合だけ配列が 2種類あります。
Direct3D10 以降は、HDR/浮動小数や OpenGL 同様の順番でほぼ統一されました。

まずフォーマットの表記が逆順になります。

 D3D9     D3D10/11/OpenGL
 ------------------------
 ARGB  →   BGRA
 ABGR  →   RGBA

そして 32bit 8888 のデフォルトの配列が RGBA (R8G8B8A8) になりました。
もちろんこれまで通り B8G8R8A8 も使えます。

31 H<---------------->L 0
|  A  |  B  |  G  |  R  |     DXGI_FORMAT_R8G8B8A8_UNORM  (== D3DFMT_A8B8G8R8)

31 H<---------------->L 0
|  A  |  R  |  G  |  B  |     DXGI_FORMAT_B8G8R8A8_UNORM   == D3DFMT_A8R8G8B8)

D3D9 以前と表記が逆になっているのでかなり ややこしい ことになります。
DDS テクスチャを作る場合、ほとんどのツールは Direct3D9 以前のフォーマット
表記になっているからです。

さらに Direct3D11 (DXGI1.1) では一見廃れるかと思われた B8G8R8A8 系が復活し
バリエーションが大幅に追加されています。

● Direct3D の pixel 配列 (16bit)

標準形式だった D3DFMT_A8R8G8B8 が ARGB の並びであることを思い出してください。
32bit で Alpha は最上位になります。
16bit にパックされた 565, 1555, 4444 も同様です。

31 H<---------------->L 0
|  A  |  R  |  G  |  B  |     D3DFMT_A8R8G8B8

15 H<-------->L 0
| A | R | G | B |             D3DFMT_A4R4G4B4

15 H<-------->L 0
|A| R  | G  | B |             D3DFMT_A1R5G5B5

15 H<-------->L 0
| R  |  G  | B  |             D3DFMT_R5G6B5

OpenGLES の形式と Alpha の位置が異なっています。
これで正しく表示できなかった原因が判明しました。
Alpha が存在しない 565 だけ正しく表示できたのも納得です。

独立したコンポーネントを Byte アクセスする OpenGL と違い、Direct3D は
リトルエンディアンの 32bit (DWORD) で読み込んだときに、互換のある形式に
なっているわけです。

● DDS の読み込み続き

原因が判明したのでローダーの続きです。
読み込みと同時に互換性のないピクセル並びを変換します。
まずは上で省略してしまった DDS ヘッダの定義。(詳しくはこちらをどうぞ)

#define	DDSCAPS_MIPMAP	0x00400000
struct T_DDSHEADER {
    unsigned int    dwMagic;
    unsigned int    dwSize;
    unsigned int    dwFlags;
    unsigned int    dwHeight;
    unsigned int    dwWidth;
    unsigned int    dwPitchOrLinearSize;
    unsigned int    dwDepth;
    unsigned int    dwMipMapCount;
    unsigned int    dwReserved1[11];
    unsigned int    dwPfSize;
    unsigned int    dwPfFlags;
    unsigned int    dwFourCC;
    unsigned int    dwRGBBitCount;
    unsigned int    dwRBitMask;
    unsigned int    dwGBitMask;
    unsigned int    dwBBitMask;
    unsigned int    dwRGBAlphaBitMask;
    unsigned int    dwCaps;
    unsigned int    dwCaps2;
    unsigned int    dwReservedCaps[2];
    unsigned int    dwReserved2;
};
#define	__DDS__MAGIC	0x20534444	// ' SDD'

ピクセルの入れ替えです。

template
static void _flFASTCALL
DDStoGL16( unsigned int count, void* data )
{
    unsigned short*	ptr= reinterpret_cast( data );
    for(; count-- ;){
        unsigned int	color= *ptr;
        unsigned int	alpha= color & AMask;
        *ptr++= (color << (16-AShift)) | (alpha >> AShift);
    }
}

static void _flFASTCALL
DDStoGL32( unsigned int count, void* data )
{
    unsigned int*	ptr= reinterpret_cast( data );
    for(; count-- ;){
        unsigned int	color= *ptr;
        *ptr++= (color & 0xff00ff00)
                |((color >> 16) & 0xff)
                |((color << 16) & 0xff0000);
    }
}

変換が必要な場所で呼び出します。

  case 32: // 8888
        switch( dp->dwRBitMask ){
        ~
        case 0x00ff0000: // B G R A = DX
            format= GL_RGBA;
            ftype= GL_UNSIGNED_BYTE;
            DDStoGL32( width*height, data );      // ← ここ
            break;
        }
        break;
    case 16:
        switch( dp->dwGBitMask ){
        case 0x00f0: // 4444
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_4_4_4_4;
            DDStoGL16<12,0xf000>( width*height, data );     // ← ここ
            break;
        case 0x07e0: // 565
            ~
        case 0x03e0: // 1555
            format= GL_RGBA;
            ftype= GL_UNSIGNED_SHORT_5_5_5_1;
            DDStoGL16<15,0x8000>( width*height, data );     // ← ここ
            break;
        case 0x0000: // L A 88
            ~

このコードは入力したメモリを直接書き換えている点に注意です。

DDSLoader() は、読み込んだ DDS ファイルのメモリイメージをそのまま受け取ります。
メモリイメージはテンポラリ相当なので、この仕様で問題になることはあまりないかもしれません。
もし DDSLoader() 終了後もすぐにメモリを解放せずに、同じデータで何度も
DDSLoader() を呼び出すような再利用があれば問題が生じます。
安全のためには バッファを const で受け取り、変換が必要な場合のみバッファを複製するよう
書き換えが必要かもしれません。

今回のローダーは Mipmap に対応していないので DDSLoader() 最後の 2行は重要です。

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

デフォルトで MIPMAP 有効になっていることがあったためです。
明示的に MIPMAP を切っておかないと真っ黒なテクスチャが表示されることがあります。

長くなったので Mipmap 対応は次回にします。
書き込んでから気がつきました。24bit 888 の対応も忘れていました。

関連エントリ
OpenGLES2.0 Direct3D とのフォーマット変換
OpenGLES 2.0 頂点フォーマットの管理
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
D3D関連 DDSテクスチャの取り扱い
Direct3D もうひとつのユニファイド

OpenGLES2.0 Direct3D とのフォーマット変換

Direct3D10 以降、Pixel フォーマットと頂点の各 Element のフォーマット形式は
統一されています。DXGI_FORMAT はまた Direct3D からも独立しており、
バージョンに依存せず同じものが使えるようになりました。
つまり Direct3D10 と Direct3D11 のフォーマットは同一です。

●Direct3D9

・ピクセル、インデックス用フォーマット D3DFORMAT
     D3DFMT_A8R8G8B8
     D3DFMT_R5G6B5
     D3DFMT_A16B16G16R16F
     D3DFMT_INDEX16    (IndexBuffer 用)
     等

・頂点フォーマット D3DDECLTYPE
     D3DDECLTYPE_FLOAT3
     D3DDECLTYPE_UBYTE4
     他


●Direct3D10/Direct3D11

・ピクセル、頂点、インデックス兼用  DXGI_FORMAT
     DXGI_FORMAT_R8G8B8A8_UNORM
     DXGI_FORMAT_B5G6R5_UNORM
     DXGI_FORMAT_R32G32B32_FLOAT  (従来の D3DDECLTYPE_FLOAT3 も兼ねる)
     DXGI_FORMAT_R16_UINT         (従来の D3DFMT_INDEX16 も兼ねる)
     ~

Direct3D11 ではライブラリ独自の頂点形式から ID3D11InputLayout を作るために
下記の関数を用意していました。
これをそのまま OpenGLES.20 用に置き換えたのが、先日の GLInputLayout の関数です。

// DXInputLayout.cpp
static struct _FormatType {
    DXGI_FORMAT   dxgi_format;
} formatTable[]= {

{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_UNKNOWN,			},

{	DXGI_FORMAT_R32_FLOAT,			},
{	DXGI_FORMAT_R32G32_FLOAT,		},
{	DXGI_FORMAT_R32G32B32_FLOAT,		},
{	DXGI_FORMAT_R32G32B32A32_FLOAT,		},

{	DXGI_FORMAT_R16_FLOAT,			},
{	DXGI_FORMAT_R16G16_FLOAT,		},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R16G16B16A16_FLOAT,		},

{	DXGI_FORMAT_R32_SINT,			},
{	DXGI_FORMAT_R32G32_SINT,		},
{	DXGI_FORMAT_R32G32B32_SINT,		},
{	DXGI_FORMAT_R32G32B32A32_SINT,		},

{	DXGI_FORMAT_R16_SINT,			},
{	DXGI_FORMAT_R16G16_SINT,		},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R16G16B16A16_SINT,		},

{	DXGI_FORMAT_R8_SINT,			},
{	DXGI_FORMAT_R8G8_SINT,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R8G8B8A8_SINT,		},

{	DXGI_FORMAT_R32_UINT,			},
{	DXGI_FORMAT_R32G32_UINT,		},
{	DXGI_FORMAT_R32G32B32_UINT,		},
{	DXGI_FORMAT_R32G32B32A32_UINT,		},

{	DXGI_FORMAT_R16_UINT,			},
{	DXGI_FORMAT_R16G16_UINT,		},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R16G16B16A16_UINT,		},

{	DXGI_FORMAT_R8_UINT,			},
{	DXGI_FORMAT_R8G8_UINT,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R8G8B8A8_UINT,		},

{	DXGI_FORMAT_R16_SNORM,			},
{	DXGI_FORMAT_R16G16_SNORM,		},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R16G16B16A16_SNORM,		},

{	DXGI_FORMAT_R8_SNORM,			},
{	DXGI_FORMAT_R8G8_SNORM,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R8G8B8A8_SNORM,		},

{	DXGI_FORMAT_R16_UNORM,			},
{	DXGI_FORMAT_R16G16_UNORM,		},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R16G16B16A16_UNORM,		},

{	DXGI_FORMAT_R8_UNORM,			},
{	DXGI_FORMAT_R8G8_UNORM,			},
{	DXGI_FORMAT_UNKNOWN,			},
{	DXGI_FORMAT_R8G8B8A8_UNORM,		},
};


DXGI_FORMAT _flFASTCALL
PixelToFormat( unsigned int func )
{
    assert( func < Image::FUNC_END );
    return  formatTable[ func ].dxgi_format;
}

void _flFASTCALL
SetElementDesc(
	D3D11_INPUT_ELEMENT_DESC* desc,
	unsigned int desccount,
	const a5::InputLayout& layout
	)
{
    unsigned int    ecount= layout.GetElementCount();
    assert( ecount < desccount );
    for( unsigned int ei= 0 ; ei< ecount ; ei++, desc++ ){
        const a5::InputElement*  ep= layout.GetElement( ei );
        desc->SemanticName= ep->SemanticName;
        desc->SemanticIndex= ep->SemanticIndex;
        desc->Format= PixelToFormat( ep->pixf.Func );
        desc->InputSlot= 0;
        desc->AlignedByteOffset= ep->ByteOffset;
        desc->InputSlotClass= D3D11_INPUT_PER_VERTEX_DATA;
        desc->InstanceDataStepRate= 0;
    }
}

このように、独自の頂点形式をテーブルで DXGI_FORMAT に変換しています。
DXGI そのままでないのが幸いして、OpenGLES2.0 への変換も比較的うまく
いっています。OpenGLES2.0 で用意したのが下記のコードです。
このテーブルはそのまま上の DXGI_FORMAT に対応しています。

static struct _FormatType {
    GLenum    dxgi_format;
    unsigned int   component;
    unsigned int   normalize;
} formatTable[]= {

{    0,                  0,    FALSE,    },
{    0,                  0,    FALSE,    },
{    0,                  0,    FALSE,    },
{    0,                  0,    FALSE,    },

{    GL_FLOAT,           1,    FALSE,	},
{    GL_FLOAT,           2,    FALSE,	},
{    GL_FLOAT,           3,    FALSE,	},
{    GL_FLOAT,           4,    FALSE,	},

{    0,                  0,    FALSE,	},  // half float
{    0,                  0,    FALSE,	},  // half float
{    0,                  0,    FALSE,	},  // half float
{    0,                  0,    FALSE,	},  // half float

{    0,                  0,    FALSE,	},  // signed int 32
{    0,                  0,    FALSE,	},  // signed int 32
{    0,                  0,    FALSE,	},  // signed int 32
{    0,                  0,    FALSE,	},  // signed int 32

{    GL_SHORT,	         1,    FALSE,	},  // signed int 16
{    GL_SHORT,	         2,    FALSE,	},  // signed int 16
{    GL_SHORT,	         3,    FALSE,	},  // signed int 16
{    GL_SHORT,	         4,    FALSE,	},  // signed int 16

{    GL_BYTE,	         1,    FALSE,	},  // signed byte 8
{    GL_BYTE,	         2,    FALSE,	},  // signed byte 8
{    GL_BYTE,	         3,    FALSE,	},  // signed byte 8
{    GL_BYTE,	         4,    FALSE,	},  // signed byte 8

{    0,		         0,    FALSE,	},  // unsigned int 32
{    0,		         0,    FALSE,	},  // unsigned int 32
{    0,		         0,    FALSE,	},  // unsigned int 32
{    0,		         0,    FALSE,	},  // unsigned int 32

{    GL_UNSIGNED_SHORT,  1,    FALSE,	},
{    GL_UNSIGNED_SHORT,  2,    FALSE,	},
{    GL_UNSIGNED_SHORT,  3,    FALSE,	},
{    GL_UNSIGNED_SHORT,  4,    FALSE,	},

{    GL_UNSIGNED_BYTE,   1,    FALSE,	},
{    GL_UNSIGNED_BYTE,   2,    FALSE,	},
{    GL_UNSIGNED_BYTE,   3,    FALSE,	},
{    GL_UNSIGNED_BYTE,   4,    FALSE,	},

{    GL_SHORT,           1,    TRUE,	},
{    GL_SHORT,           2,    TRUE,	},
{    GL_SHORT,           3,    TRUE,	},
{    GL_SHORT,           4,    TRUE,	},

{    GL_BYTE,            1,    TRUE,	},
{    GL_BYTE,            2,    TRUE,	},
{    GL_BYTE,            3,    TRUE,	},
{    GL_BYTE,            4,    TRUE,	},

{    GL_UNSIGNED_SHORT,  1,    TRUE,	},
{    GL_UNSIGNED_SHORT,  2,    TRUE,	},
{    GL_UNSIGNED_SHORT,  3,    TRUE,	},
{    GL_UNSIGNED_SHORT,  4,    TRUE,	},

{    GL_UNSIGNED_BYTE,   1,    TRUE,	},
{    GL_UNSIGNED_BYTE,   2,    TRUE,	},
{    GL_UNSIGNED_BYTE,   3,    TRUE,	},
{    GL_UNSIGNED_BYTE,   4,    TRUE,	},
};


GLenum _flFASTCALL
PixelToFormat( unsigned int func, unsigned int* component, unsigned int* normal )
{
    assert( func < Image::FUNC_END );
    const _FormatType*   fp= &formatTable[ func ];
    if( component ){
        *component= fp->component;
    }
    if( normal ){
        *normal= fp->normalize;
    }
    return  fp->dxgi_format;
}

前回 GLInputLayout の CreateInputLayout() の中で呼び出していた
PixelToFormat() がこれです。
まだ FLOAT しか使っていないので本当に互換性があるかどうかは検証してません。
BYTE の並びは D3D9 の D3DDECLTYPE_UBYTE4 と D3DDECLTYPE_D3DCOLOR
のように順番が異なっている可能性があります。

同じく前回「ELEMENTFLAG_NORMALIZE の定義値が 1 なのは念のため。」と書いた
のはコンパイラの最適化で三項演算子が完全に消えることを期待していたからです。

(ep->Flag & ELEMENTFLAG_NORMALIZE) ? GL_TRUE : GL_FALSE

GL_TRUE / GL_FALSE の定義は 1 と 0 なので、最終的には
「ep->Flag & ELEMENTFLAG_NORMALIZE」だけが残ります。
予想通り分岐は消えますが、シンボルが 1 以外でも shr が一つ追加されるだけでした。

; ELEMENTFLAG_NORMALIZE == 4 の場合 (x64 + VC)
000000013F4D6697  shr         ebx,2 
000000013F4D66A1  and         ebx,1 
000000013F4D66A4  mov         edx,ebx 

シフトを命令に埋め込める ARM だったら全く意識する必要ないのかもしれません。

関連エントリ
OpenGLES 2.0 頂点フォーマットの管理
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理

OpenGLES 2.0 頂点フォーマットの管理

OpenGLES2.0 上にほぼ Direct3D 互換の環境を作っています。
頂点形式も同じものです。頂点バッファを作ることも出来ました。

GLuint _flFASTCALL
CreateBuffer(
    unsigned int bytesize,
    GLenum buffer,
    GLenum btype,
    const void* initdata
    )
{
    GLuint   bid;
    glGenBuffers( 1, &bid );
    glBindBuffer( buffer, bid );

    if( initdata ){
        glBufferData( buffer, bytesize, initdata, btype );
        ErrorHandle( "CreateBuffer" );
    }
    return  bid;
}

API 的に VertexBuffer / IndexBuffer の区別が無いのは Direct3D10 以降と同じです。

// VertexBuffer
vbuf= CreateBuffer( bytesize, GL_ARRAY_BUFFER, GL_STATIC_DRAW, vdata );

// IndexBuffer
ibuf= CreateBuffer( bytesize, GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, idata );

GL_STATIC_DRAW は Direct3D の D3D10_USAGE_/D3D11_USAGE_ や AccessFlag
に相当するものです。

glBIndBuffer( GL_ARRAY_BUFFER, vbuf );   // IASetVertexBuffers()
glBIndBuffer( GL_ELEMENT_ARRAY_BUFFER, ibuf );   // IASetIndexBuffer()

与えられるバッファの区別がないので、描画に使えるバッファは 1つだけ。
頂点データを同じバッファに格納することが前提となっているようです。

シェーダーとのバインドは glVertexAttribPointer() そのままです。

GLint loc_position= glGetAttribLocation( ShaderProgram, "POSITION" );
GLint loc_normal=   glGetAttribLocation( ShaderProgram, "NORMAL" );
GLint loc_texcoord= glGetAttribLocation( ShaderProgram, "TEXCOORD" );
glVertexAttribPointer( loc_position, 3, GL_FLOAT, GL_FALSE, 32, (void*)0 );
glVertexAttribPointer( loc_normal,   3, GL_FLOAT, GL_FALSE, 32, (void*)(sizeof(float)*3) );
glVertexAttribPointer( loc_texcoord, 2, GL_FLOAT, GL_FALSE, 32, (void*)(sizeof(float)*6) );

必要なパラメータもやってることも、D3D10_INPUT_ELEMENT_DESC,
D3D11_INPUT_ELEMENT_DESC によく似ています。
違うのは、バインドシンボルが SEMANTIC ではなく変数名そのままになること。

上の例だとシェーダー側はこんな感じです。
SEMANTIC を直接変数名にすることで D3D と同じような記述になります。

// GLSL
attribute vec3 POSITION;
attribute vec3 NORMAL;
attribute vec2 TEXCOORD;

void main()
{
   gl_Position= vec4( POSITION.xyz, 1.0 ) * Matrix;
}

Direct3D で書くと下記の通りです。

// HLSL
struct VS_INPUT {
   float3  vposition : POSITION;
   float3  vnormal   : NORMAL;
   float2  vtexcoord : TEXCOORD0;
};

float4 main( VS_INPUT vin ) : SV_Position
{
   return  mul( float4( vin.vposition.xyz, 1.0f ), Matrix );
}

デモやサンプルプログラムならこのままでいいのですが、汎用的な仕組みとして
シェーダーとのバインドを考えると、D3D の InputLayout 相当の機能が必要に
なることがわかります。(ID3D10InputLayout / ID3D11InputLayout)

なので実際に作っておきます。

// GLInputLayout.h
enum {
    ELEMENTFLAG_NORMALIZE  =  (1<<0),
    ELEMENTFLAG_ACTIVE     =  (1<<1),
    ELEMENTFLAG_END        =  (1<<2),
};

struct ShaderInputElement {
    unsigned int    Location;
    unsigned int    Type;       // GLenum GL_FLOAT
    unsigned char   Stride;
    unsigned char   ByteOffset;
    unsigned char   Components;
    unsigned char   Flag;
};
// GLInputLayout.cpp
void _flFASTCALL
CreateInputLayout( 
      ShaderInputElement* edesc,
      unsigned int desccount,
      GLuint shader,
      const a5::InputLayout& layout
        )
{
    unsigned int   ecount= layout.GetElementCount();
    assert( ecount <= desccount );
    for( unsigned int ei= 0 ; ei< ecount ; ei++, edesc++ ){
        const a5::InputElement*	ep= layout.GetElement( ei );
        unsigned int	component= 0;
        unsigned int	normal= 0;
        edesc->Flag= 0;
        if( ei == ecount-1 ){
            edesc->Flag|= ELEMENTFLAG_END;
        }
        GLint  loc= glGetAttribLocation( shader, ep->SemanticName );
        ErrorHandle( "Layout" );
        if( loc < 0 ){
            continue;
        }
        edesc->Location= loc;
        edesc->Type= PixelToFormat( ep->pixf, &component, &normal );
        edesc->Stride= static_cast( layout.GetStrideSize() );
        edesc->Components= static_cast( component );
        edesc->ByteOffset= static_cast( ep->ByteOffset );
        if( normal ){
            edesc->Flag|= ELEMENTFLAG_NORMALIZE;
        }
        edesc->Flag|= ELEMENTFLAG_ACTIVE;
    }
}

void _flFASTCALL
IASetInputLayout( const ShaderInputElement* edesc )
{
    const ShaderInputElement*   ep= edesc;
    for(;; ep++ ){
        if( ep->Flag & ELEMENTFLAG_ACTIVE ){ 
            glVertexAttribPointer(
                ep->Location,
                ep->Components,
                ep->Type,
                (ep->Flag & ELEMENTFLAG_NORMALIZE) ? GL_TRUE : GL_FALSE,
                ep->Stride,
                (const void*)ep->ByteOffset
                );
            glEnableVertexAttribArray( ep->Location );
        }
        if( ep->Flag & ELEMENTFLAG_END ){
            break;
        }
    }
}

void _flFASTCALL
IAResetInputLayout( const ShaderInputElement* edesc )
{
    const ShaderInputElement*	ep= edesc;
    for(;; ep++ ){
        if( ep->Flag & ELEMENTFLAG_ACTIVE ){ 
            glDisableVertexAttribArray( ep->Location );
        }
        if( ep->Flag & ELEMENTFLAG_END ){
            break;
        }
    }
}

CreateInputLayout() はオブジェクトを生成しないで固定バッファを使用します。
ShaderInputElement がそのまま描画時に使える InputLayout 情報になります。

複雑な Flag 管理を行っているのは、エンドマークやバッファ数を別に持たなくて
済むようにです。必要 Element 分、固定で渡されたバッファで完結させるため。
メモリアロケートに抵抗がなければオブジェクトにした方がずっとシンプルでしょう。
ShaderInputElement の Type が 32bit なのは GLenum の互換性を考えたためです。
実際のシンボル値は 16bit なので、問題なければ圧縮して total 8byte に収まります。
シェーダーに対応するシンボルが無ければ頂点のセットアップを行いません。

a5::InputLayout は、個人的に Direct3D で使用している頂点情報管理 object です。
本来はこの object を元に D3D11_INPUT_ELEMENT_DESC を生成して、
ID3D11InputLayout を作っていました。

ELEMENTFLAG_NORMALIZE の定義値が 1 なのは念のため。

PixelToFormat() は頂点フォーマットに含まれている Direct3D の
DXGI_FORMAT を OpenGL 形式に変換しています。詳細は次回以降で。

// 初期化
a5::InputLayout layout( ~ );
ShaderInputElement vLayout[3];
CreateInputLayout( vLayout, 3, ShaderProgram, layout );

// 描画時
glBIndBuffer( GL_ARRAY_BUFFER, vbuf );   // IASetVertexBuffers()
IASetInputLayout( vLayout );
glBIndBuffer( GL_ELEMENT_ARRAY_BUFFER, ibuf );   // IASetIndexBuffer()

glDrawElements( GL_TRIANGLES, Indices, GL_UNSIGNED_SHORT, NULL );

頂点バッファ (頂点バッファオブジェクト)を使った描画命令も、頂点の設定同様
これまでと同じものでした。
Index の配列はすでに GL_ELEMENT_ARRAY_BUFFER で渡しているため
glDrawElements() の最後は NULL になります。
Indices は Index の個数なので GL_TRIANGLES だと常に 3 の倍数となります。

描画の仕組みを理解して API を使うだけなら難しいことは特にないです。
でも実際にゲーム開発を行っていると、サンプルだけではわからない事項がいくつも
出てきます。3D の描画エンジンを作っていて常に悩まされるのは、
汎用的に使える仕組みとその設計部分です。

例えば CG ツールからどうやって export してどんなフォーマットで保存するか、
数多く存在する頂点フォーマットやピクセルフォーマットに対応するにはどうするか、
データとシェーダーの対応付けやシェーダーの管理をどうやって行うか、など。

3D 系プログラムの開発はこのような地味で面倒なことの方が多いです。
blog の冒頭説明でも書いてますが、ゲーム開発のノウハウの差というのは目新しい
技術の実装だけでなく、データ制作やデータの管理もかなり含まれているはずです。

とりあえず export したモデルが表示できて、任意のシェーダーを適用して
アニメーション出来るところまでは作成済み。

関連エントリ
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理

WiMAX ルータ URoad-5000 (4) 充電の仕方

URoad-5000 の使い方でわからない点があったのでいくつか問い合わせてみました。
以下その結果をまとめたものです。

●バッテリー LED の意味

モードは 2種類あります。Battery モード と Charge モード

「Batteryモード」(バッテリー利用時のインジケータの色の意味)

 緑: 60%~100%
 橙: 60%以下
 赤: 残量小
 赤点滅: 残量ほぼゼロ


「Chargeモード」 (AC アダプタ接続時のインジケータの色の意味)

 緑: 90%以上充電済み
 橙: 充電中

※ モードの切り替わり時は LED が 3回点滅する

AC アダプタを接続すると LED の色の意味が変わります。
たとえば残量 70% だったら、AC アダプタをつなぐと緑から橙へ。
モードの切り替わりを示しているのが LED の 3回点滅だそうです。

●バッテリー充電時間と稼働時間

  バッテリーLED          充電時間          稼働時間の目安
  ---------------------------------------------------------
 赤点滅→緑 (0%→95%)   2時間半~3時間    約3時間
 緑→緑 (95%→100%)     +30分             +15分

Chargeモードで 緑色まで充電すると、スペック通りおよそ 3時間利用可能。
緑まで充電してからさらに 30分充電すると 100% です。

●ACアダプタをつなぐと勝手に電源が入る点について

・AC アダプタをつなぐと電源が入るのは仕様。
 このときルーターの動作が不要なら、電源を切っておいても問題無いそうです。
 電源を入れなくても充電できます。

・AC アダプタ接続中はバッテリーを消費しません。
 本体の動作は、バッテリーを経由しないで直接 AC アダプタの電源を使っている
 そうです。電源を切っても消費電力は減るけど充電時間は変わりません。

●その他 URoad-5000 利用状況など

電車内など移動中の利用はあきらめて外出時に使える回線として活用しています。
昼食時に DS を Wi-Fi につないだりとか。
ポケットに入れられるくらい小さくて薄くて軽いことと、バッテリーで動作出来るのが
良いです。ほとんど負担にならずに持ち歩けます。

つるつるな上面部は結構指紋が目立つ感じです。専用ケースも付属しているのですが
サイズが増すので今は使っていません。

本体の LED はそうでもないのですが、USB アダプタ側 UD01SS の青色 LED は非常に
まぶしく感じます。電波を探している間は点灯したままなので特に。
夜間とか外で使ってると目立つかもしれません。暗くするか消せると良いのですが。

関連エントリ
WiMAX ルータ URoad-5000 (3) 三日目
WiMAX ルータ URoad-5000 (2) 二日目
WiMAX ルータ URoad-5000
Windows7 beta で UQ WiMAX