Direct3D Compute Shader 対応の GeForce ドライバ

NVIDIA の新しいドライバ 190.62 がコンピュートシェーダに対応したらしいので試しました。
Windows7 RC/RTM どちらでも Compute Shader 4.0 動いてます。

(Direct3D11)
*HARDWARE  = 10.0 (a000)
 feature threading  DriverConcurrentCreates=1
 feature threading  DriverCommandLists=0
 feature d3d10_x  ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x=1
 feature doubles  DoublePrecisionFloatShaderOps=0

関連エントリ
Direct3D11/DirectX11 GeForce の ComputeShader とドライバ
Direct3D11/DirectX11 ComputeShader 4.0 を使う
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる

Direct3D ファントムダストと破壊の穴

ファントムダスト Phantomdust というゲームがありました。
今更ですが…忘れないうちにいくつか書いておきます。

マップの破壊表現は複数の手法の組み合わせで出来ています。
オブジェクト、パーティクル、破裂シェーダー、破壊のへこみ穴 等。
今回説明するのは「穴」の表現です。

攻撃やダメージ等によって、地形モデルのどの位置にでも削れたような穴があきます。
地面だけでなく、壁でも天井でも同様に破壊の痕跡が残ります。
このへこんだ形状は別モデルとして用意してあり、ステンシルマスクを用いて背景の
描画時に合成しています。
戦っているうちに地形の至る所が穴だらけになるわけです。

例えば下記リンク先のイレースシェルの地面がそうです。
いくつも穴がありますが、もともとは平らな地面のモデルデータです。

famitsu.com
Impress GAME Watch

●特徴

メリット

・デカールと違い立体的に見えます。
・背景モデルデータを書き換えることなく形状を変化出来ます。
・立体的なデータなのでヒット位置を厳密に割り出さなくても描画できます。
・ノーマルマップの背景に対して低コストで適用できます。

デメリット

実装時に様々な制限が生じます。問題をどのように解決したのかは後述します。
欠点というか実装できなかったのはコリジョンの追従です。コリジョンの変更はコストが
高く、ゲーム内容に関わるため通信同期も必要でした。残念ながら見送りました。

●この方法を用いた理由

破壊表現は当初からの課題でした。
プロトタイプの段階では通常のマテリアルを使っていたので、レイヤテクスチャ+
メッシュを分割+動的に頂点を書き換えていました。
問題となったのはノーマルマップです。
Xbox 1 の当時はまだ珍しかったのですが、最終的に背景もキャラも全面にノーマルマップを
使っています。これはハイポリのディテールを少ないポリゴンで再現可能な手法なので、
結果として頂点数を減らすように方針転換しました。よって

・頂点を動かすだけのポリゴンの細分化が出来ない
  頂点を減らせるのがメリットだし、TS の容量も計算も減らしたい
・変形した形状の Tangent Space の再計算はコストがかかる
・PixelShader の 8+4 stage をノーマルマップで使い切っている
  レイヤを増やすとマルチパスになる

●破壊穴モデルの形状

実際は平べったい形状ですが便宜上球と思って構いません。
穴の中のへこみを表現するモデルなので、面が裏返った内側を向いたデータです。

破壊の穴と地面の交差を判定するボリュームモデルと、実際に穴の中を描画する
描画モデルの 2つが必要となります。

・交差判定のためのボリュームモデル
・穴の中を描画するモデルデータ

描画モデルのマテリアルが単純かつ軽量なものだったので、この両者を兼用しています。
つまり描画モデルをそのまま断面の判定にも使用しています。

●断面の生成

シャドウボリュームと同じです。
テクスチャをフェッチしない専用の軽量シェーダーを割り当てて、ボリューム形状を
ステンシルバッファに 2回書きます。

(1) depth test かつ 表面、stencil +1
(2) depth test かつ 裏面、stencil -1

裏面と面面の差分が地面との交差面となります。上記の設定だと stencil が 0以外で
交差面です。
この条件を元に、同じ位置に描画モデルをレンダリングすれば合成ができます。

余談ですが、両面ステンシル機能がある GPU なら一度の描画で差分が求まります。
両面ステンシルが無くても両面マテリアルで代用可能で、例えばフレームバッファの
Alpha (DestAlpha) に加算で書き込むと一回で生成できます。
このとき描画頂点数は減りますがピクセル負荷は変わりません。

多くの GPU は Depth + Stencil の書き込みに特化したモードがあり、フィルレート
が倍になるように最適化されています。なので DestAlpha を使ってもあまりメリット
はありませんでした。
それにこのゲームは DestAlpha をすでに別の用途で利用していました。

●形状の変更

このゲームは表現上ステンシルバッファを多用しています。
8bit しかない stencil を bitmask して複数使い分けているところもあります。

・影
・オーラ
・地面や建物の破壊穴

もしゲーム画面か古い CGWORLD を見ることが出来るなら、穴の中にも形状に沿って影が
落ちていることがわかると思います。
でも描画しているモデルデータは平らなままです。

背景の影もシャドウボリュームを使っているのは、このように破壊によって動的に
地形が変わる可能性があるため。テクスチャや頂点への焼き込みだと、地形の変化に
対応できなくなります。

●負荷の相殺

破壊が進むと描画の負荷が増えて重くなりそうに見えますが実はそうでもありません。

このゲームは背景もキャラもすべてノーマルマップを使った重いシェーダーを適用
しています。最初から背景のピクセルが一番重い前提で負荷を計算しているので、
地形の描画面積を減らす処理は描画負荷の軽減につながります。

ノーマルマップを適用していない破壊の穴モデルは、数が増えれば増えるほど地形の
描画負荷を軽くすることになります。
ステンシルバッファへのレンダリングが増えますが相殺して軽くなります。
(でも頂点は軽くなりません。この判断は今思うと微妙だったかもしれません。)

同じようにキャラクタの描画負荷も相殺するのでアップになっても速度は変わりません。
半透明のエフェクトはまずいです。

●矛盾の解決

実際に組み込んでみると単純な表現でもさまざまな問題が生じます。
他の描画や表現と共存が必要だからで、1つ 1つ対策を施していきます。

・穴が開くと困る場所

破壊モデル合成時に、特殊な形状や半透明など矛盾が生じる場所があります。
あらかじめマテリアルの設定で描画しないように除外しています。
これは描画パスを分けることで実現しています。

・穴同士の重なり

Z test は別の判定に用いるので、描画の重なりの解決に Z バッファが使えません。
同じ位置にポリゴンが重なると矛盾が生じるので、描画位置を決める際に当たり判定を
行っています。必ず他の穴と重ならない位置に配置しています。

・地面の下だけ描画

破壊の穴形状は多少ずれても問題がないように球形をしています。
上半分が見えてしまわないように、地面の下に潜り込んだ部分だけ切り取るため
depth test を併用しています。Stencil がパスしてかつ、Z Fail が条件です。
地面の下にめり込んだ部分だけが描画されるようになります。

・depth 強制更新

ただ描画するだけでは不十分であることがわかります。
見た目と Z buffer に食い違いが生じるからです。
たとえば穴の上に置いた物や影が、地面の位置でまっすぐに切れてしまいます。

よって描画モデルを描き込むときに同時に depth を強制的に上書きします。
depth (Z buffer) が更新できれば、その後に描画する Shadow Volume も穴の形に
沿って落ちてくれます。

・遠くの穴が見えてしまう

地面が階層化している場所など、上の段の破壊穴と下の段の穴が重なって描画される
ことがあります。depth test の条件に Z Fail を設定しているということは、常に
遠くのオブジェクトが優先されることに等しいからです。
穴の中にさらに遠くの穴が見えてしまいます。

そこで穴モデル自体を Zソートして手前から描画し、同時にステンシルに 0 を書き
込んで消していきます。遠くの穴を描画する段階では depth test の Z Fail を
通っても、stencil テストに失敗するので描画されなくなります。

・地形との合成

上の問題を対処するためステンシルバッファを 0 で消してしまうと、今度はその
位置だけ地形を除外できなくなります。つまり穴の位置に上書きしてしまいます。
ステンシルをクリアする場合、別の条件で区別できる値を書き込まなければなりません。
元々ステンシル値の 0 または 0 以外で判定していたので思ったより簡単ではないです。
もしくは描画順番で何とかします。交差面を求めた後、地形の穴の中を描画する前に
先に地面を描画し、その後破壊穴モデルを描画すれば 0 クリアでも問題ありません。

・穴の上に重なるモデルの表示順番

地面に穴が開くはずの場所に他のモデルが置いてある場合も描画順番が問題となります。
例えばキャラが地面に立っていて、わずかに地面にめり込んでいる場合。(本当は
それが良くないのですが)
穴を開ける前の Z バッファで描画してしまうとめり込んだ部分が切れてしまいます。
だから地形に乗っているものは、穴を開けて Z バッファを更新したあとに描画する
ことになります。

・個数制限と消去

破壊の穴は同時に 30個描画しています。上限はただの定数値で、何らかの制限があった
わけではないです。これを超えた場合、視野に入っていないものから消しています。

●描画手順のまとめ

(1) 破壊可能部分の depth のみ
(2) 破壊穴モデルの交差判定
(3) 破壊穴の描画と depth 更新+識別可能な stencil で書き直す(要確認)
(4) 地形の上に乗っているモデル描画、不透明物一般
(5) 破壊不可能部分描画
(6) 破壊可能部分のカラー描画
(7) シャドウボリューム
(8) 影の合成
(9) 半透明地形の描画
(10) 半透明エフェクト

記憶だけで書いているので、おそらく異なっている部分や足りない部分があります。
これ以外にも特殊なエフェクトや表現があったので、実際はもっと描画手順が複雑です。
具体的に設定したステートの組み合わせなどは、おそらくもう一度実際に作ってみないと
わからないと思います。
それに今のハードならもっと簡単でしょう。ShaderModel 1.x の当時とは違うので、
別の表現方法も使えると思います。

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 シェーダー管理