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