昨日の続きです。ミップマップと 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 シェーダー管理