D3D Shader/OpenGL」カテゴリーアーカイブ

OpenGL / OpenGL ES ETC1 の互換性と KTX の落とし穴

昨日の記事に書いたように OpenGL / OpenGL ES の場合 KTX 形式の
テクスチャ読み込みが非常に簡単に出来ます。
ところが OpenGL ES 3.0 / OpenGL 4.3 の場合、ETC1 の読み込みに
問題がありました。

KTX のロード時に、ヘッダに GL_ETC1_RGB8_OES があれば
GL_COMPRESSED_RGB8_ETC2 に置き換える必要があります。
これは GPU が ETC2 に対応している場合だけです。

OpenGL ES 3.0 / OpenGL 4.3 で採用された ETC2 形式は ETC1 と
完全に上位互換性があります。
フォーマットも同一で、ETC1 は ETC2 の一部になりました。
そのため OpenGL ES 3.0 では事実上 ETC1 が無くなっています。

実際に Emulator (ES3 compatibility) で
glGetIntegerv( GL_COMPRESSED_TEXTURE_FORMATS, lists )
を列挙すると↓ ETC1 が含まれていないことがわかります。

tc[00]=83f0  GL_COMPRESSED_RGB_S3TC_DXT1_EXT
tc[01]=83f2  GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
tc[02]=83f3  GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
tc[03]=8b90  GL_PALETTE4_RGB8
tc[04]=8b91  GL_PALETTE4_RGBA8
tc[05]=8b92  GL_PALETTE4_R5_G6_B5
tc[06]=8b93  GL_PALETTE4_RGBA4
tc[07]=8b94  GL_PALETTE4_RGB5_A1
tc[08]=8b95  GL_PALETTE8_RGB8
tc[09]=8b96  GL_PALETTE8_RGBA8
tc[10]=8b97  GL_PALETTE8_R5_G6_B5
tc[11]=8b98  GL_PALETTE8_RGBA4
tc[12]=8b99  GL_PALETTE8_RGB5_A1
tc[13]=9274  GL_COMPRESSED_RGB8_ETC2
tc[14]=9275  GL_COMPRESSED_SRGB8_ETC2
tc[15]=9276  GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2
tc[16]=9277  GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2
tc[17]=9278  GL_COMPRESSED_RGBA8_ETC2_EAC
tc[18]=9279  GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC
tc[19]=9270  GL_COMPRESSED_R11_EAC
tc[20]=9271  GL_COMPRESSED_SIGNED_R11_EAC
tc[21]=9272  GL_COMPRESSED_RG11_EAC
tc[22]=9273  GL_COMPRESSED_SIGNED_RG11_EAC

ドキュメントによれば ETC1 画像を読み込む場合 API に
GL_COMPRESSED_RGB8_ETC2 を渡すことになっています。
そのため KTX に ETC1 が含まれている場合、OpenGL 4.3 / OpenGL ES 3.0
ではヘッダの値をそのまま使うとエラーになります。

bool HasFormat( GLenum format )
{
  GLint numformat= 0;
  glGetIntegerv( GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numformat );
  const int	MAX_TLISTBUFFER= 128;
  GLint lists[MAX_TLISTBUFFER];
  glGetIntegerv( GL_COMPRESSED_TEXTURE_FORMATS, lists );
  for( int fi= 0 ; fi< numformat ; fi++ ){
    if( lists[fi] == format ){
      return  true;
    }
  }
  return  false;
}


GLuint KTX_Loader_Compressed( const void* memory, size_t memory_size )
{
  const KTX_Header*  hp= reinterpret_cast( memory );
  const void*        image_data= address_add( memory, sizeof(KTX_Header) + hp->bytesOfKeyValueData );

  unsigned int  image_size= *reinterpret_cast( image_data );
  image_data= address_add( image_data, sizeof(int32_t) );

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

  GLenum  cformat= dp->glInternalFormat;
  if( cformat == GL_ETC1_RGB8_OES && HasFormat( GL_COMPRESSED_RGB8_ETC2 ) ){
    // **** ここで ETC2 に置き換える ****
    cformat= GL_COMPRESSED_RGB8_ETC2;
  }

  glCompressedTexImage2D( GL_TEXTURE_2D, 0,
           cformat,
           hp->pixelWidth,
           hp->pixelHeight,
	   0,
	   image_size,
	   image_data );

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

  return  texid;
}

関連エントリ
OpenGL / OpenGL ES KTX Texture の読み込み方

OpenGL / OpenGL ES KTX Texture の読み込み方

OpenGL / OpenGL ES の場合 KTX 形式のテクスチャの読み込みは簡単です。
ファイルを全部メモリに読み込んだと仮定して

const void* address_add( const void* a, uintptr_t b )
{
  return  reinterpret_cast( reinterpret_cast( a ) + b );
}


GLuint KTX_Loader( const void* memory, size_t memory_size )
{
  const KTX_Header*  hp= reinterpret_cast( memory );
  const void*        image_data= address_add( memory, sizeof(KTX_Header) + hp->bytesOfKeyValueData );

  image_data= address_add( image_data, sizeof(int32_t) );

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

  glTexImage2D( GL_TEXTURE_2D, 0,
           hp->glInternalFormat,
           hp->pixelWidth,
           hp->pixelHeight,
	   0,
           hp->glBaseInternalFormat,
           hp->glType,
	   image_data );

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

  return  texid;
}

このように、API に渡す必要な情報が全部ヘッダに入っています。
ヘッダの構造は下記を参照してください。

KTX File Format Specification

圧縮テクスチャの場合も簡単です。glType が 0 なら圧縮テクスチャです。

GLuint KTX_Loader_Compressed( const void* memory, size_t memory_size )
{
  const KTX_Header*  hp= reinterpret_cast( memory );
  const void*        image_data= address_add( memory, sizeof(KTX_Header) + hp->bytesOfKeyValueData );

  unsigned int  image_size= *reinterpret_cast( image_data );
  image_data= address_add( image_data, sizeof(int32_t) );

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

  glCompressedTexImage2D( GL_TEXTURE_2D, 0,
           hp->glInternalFormat,
           hp->pixelWidth,
           hp->pixelHeight,
	   0,
	   image_size,
	   image_data );

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

  return  texid;
}

上の例はそのまま API に渡しているので、GPU が対応していない圧縮形式の場合
エラーが発生する可能性があります。

厳密には CPU とファイルの endian が一致していないケースを考慮しなければなりません。
ヘッダの endiannness を見て必要に応じて byte swap します。
データ作成環境と実行環境が一致していればあまり問題はないと思います。

KTX の注意点は DDS とデータの並びが異なっていることです。
例えば cubemap の場合、DDS は face 毎に mipmap が並びますが、
KTX は同じ mip level の face が先に来ます。

DDS             KTX
--------------------------
[X+ 32x32]      [X+ 32x32]
[X+ 16x16]      [X- 32x32]
[X+ 8x8]        [Y+ 32x32]
[X+ 4x4]        [Y- 32x32]
[X+ 2x2]        [Z+ 32x32]
[X+ 1x1]        [Z- 32x32]
[X- 32x32]      [X+ 16x16]
[X- 16x16]      [X- 16x16]
[X- 8x8]        [Y+ 16x16]
[X- 4x4]        [Y- 16x16]
[X- 2x2]        [Z+ 16x16]
[X- 1x1]        [Z- 16x16]
[Y+ 32x32]      [X+ 8x8]
[Y+ 16x16]      [X- 8x8]
[Y+ 8x8]        [Y+ 8x8]
[Y+ 4x4]        [Y- 8x8]
[Y+ 2x2]        [Z+ 8x8]
[Y+ 1x1]        [Z- 8x8]
:               :

↑このようにデータの順番が異なっています。
wiki にも追記しましたが、データの入れ子構造の詳細は下記の通りです。

DDS :  ( ( ( ( width * height ) * volume_depth ) * mip ) * cube_face ) * array
KTX :  ( ( ( ( width * height ) * volume_depth ) * cube_face ) * array ) * mip

DDS の利点は、2D のテクスチャを 6 個並べるだけで cubemap ができること。
KTX の利点は同じサイズのサーフェースを出来るだけ一度に並べようとしていることです。

KTX の画像は下記のように 32bit のデータサイズが挿入されます。

int32 imageSize; // 32x32
[X+ 32x32]
[X- 32x32]
[Y+ 32x32]
[Y- 32x32]
[Z+ 32x32]
[Z- 32x32]
int32 imageSize; // 16x16
[X+ 16x16]
[X- 16x16]
[Y+ 16x16]
[Y- 16x16]
[Z+ 16x16]
[Z- 16x16]
int32 imageSize; // 8x8
[X+ 8x8]
[X- 8x8]
[Y+ 8x8]
[Y- 8x8]
[Z+ 8x8]
[Z- 8x8]
int32 imageSize; // 4x4
~

imageSize は 1画像分の byte 数で、同じサイズのサーフェースが
並んでいるためアドレス計算が簡単になります。
glCompressedTexImage2D() にそのまま渡すことができます。
mipmap に対応するとこんな感じです。

GLuint KTX_Loader_Mipmap( const void* memory, size_t memory_size )
{
  const KTX_Header*  hp= reinterpret_cast( memory );
  const void*        image_data= address_add( memory, sizeof(KTX_Header) + hp->bytesOfKeyValueData );

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

  unsigned int  mip_level= hp->numberOfMipmapLevels > 0 ? dp->numberOfMipmapLevels : 1;
  unsigned int  width=  hp->pixelWidth;
  unsigned int  height= hp->pixelHeight;

  for( unsigned int mip= 0 ; mip < mip_level ; mip++ ){

    unsigned int   image_size= *reinterpret_cast( image_data );
    image_data= address_add( image_data, sizeof(int32_t) );

    glTexImage2D( GL_TEXTURE_2D, mip, hp->glInternalFormat, width, height,
                0, hp->glBaseInternalFormat, hp->glType, image_data );

    width=  int_max( width>>1, 1 );
    height= int_max( height>>1, 1 );
    image_data= address_add( image_data, (image_size+ 3) & ~3 );
  }

  if( mip_level > 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;
}

これで mip + 圧縮テクスチャ や cubemap への対応も容易にできます。

KTX の一番の問題はデータの作成方法にあるようです。
ツールが絶望的に揃っていない状況で、まだあまり安定もしていません。
ARM の Texture Compression Tool は 64bit 環境でうまく動かず、
PVR の TexTool で ktx に出力すると PVRTC 以外は正しいヘッダ値が入りませんでした。

関連ページ
Texture File Format

関連エントリ
OpenGL / OpenGL ES テクスチャファイルフォーマット KTX と DDS

OpenGL / OpenGL ES テクスチャファイルフォーマット KTX と DDS

DXT テクスチャの格納や cubemap, 3D Texture, HDR 形式など、
DDS は非常に便利なファイルフォーマットでした。

ところが DirectX は仕様自体に圧縮テクスチャが含まれているため
DXT,BC 以外のフォーマットが定義されていません。
ATITC や ETC1 など、独自の FourCC で対応しているツールもあります。

同時に PVRTC/PVRTC2、ETC2/EAC や ASTC など、DDS に格納できない
データも増えています。

KTX 形式は OpenGL の値をそのまま格納するので、Extension など
GLenum 値が決まればどんな形式も格納することができます。

今後 PVRTC2, ETC2/EAC, ASTC が出てくることを考えると ktx への
対応をきちんと考えておいた方が良さそうです。

テクスチャのファイルフォーマットについて下記のページにまとめました。
取り敢えず dds, ktx, pvr, pkm の 4 種類。

Texture File Format

関連エントリ
Android OpenGL ES 2.0 の圧縮テクスチャ
Direct3D10 と DDS テクスチャフォーマット
DirectX SDK November 2007 Gather と 新DDS フォーマット

サンコー Androidスティック with DUALCORE の GPU

サンコーの Android スティック ANDHDM2S を触る機会がありました。

Androidスティック with DUALCORE

とりあえず dual core であることを確認。
CPU は NEON 使用可能で GPU は PowerVR SGX530 でした。

-------------------
CPU
-------------------
Processor	: ARMv7 Processor rev 2 (v7l)
processor	: 0
BogoMIPS	: 1061.68

processor	: 1
BogoMIPS	: 1064.96

Features	: swp half thumb fastmult vfp edsp neon vfpv3 
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x1
CPU part	: 0xc09
CPU revision	: 2

Hardware	: EMXX
Revision	: 0420
Serial		: 0000000000000000


-------------------
GPU
-------------------
GL_VERSION: OpenGL ES 2.0
GL_RENDERER: PowerVR SGX 530
GL_VENDOR: Imagination Technologies
GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL ES 1.00

pconst=64 vconst=128 vin=16 vout=8 ptex=8 vtex=8 combotex=8 maxrender=2048 maxtexsize=2048 cubetexsize=2048 viewdims=2048

Extension:
GL_OES_rgb8_rgba8
GL_OES_depth24
GL_OES_vertex_half_float
GL_OES_texture_float
GL_OES_texture_half_float
GL_OES_element_index_uint
GL_OES_mapbuffer
GL_OES_fragment_precision_high
GL_OES_compressed_ETC1_RGB8_texture
GL_OES_EGL_image
GL_OES_required_internalformat
GL_OES_depth_texture
GL_OES_get_program_binary
GL_OES_packed_depth_stencil
GL_OES_standard_derivatives
GL_OES_vertex_array_object
GL_OES_egl_sync
GL_EXT_multi_draw_arrays
GL_EXT_texture_format_BGRA8888
GL_EXT_discard_framebuffer
GL_EXT_shader_texture_lod
GL_IMG_shader_binary
GL_IMG_texture_compression_pvrtc
GL_IMG_texture_stream2
GL_IMG_texture_npot
GL_IMG_texture_format_BGRA8888
GL_IMG_read_format
GL_IMG_program_binary
GL_IMG_multisampled_render_to_texture

adb の usb ドライバは android_winusb.inf の書き換えだけで
つながりました。

%SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_0002
%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_0002&MI_01

関連ページ
日本で発売された端末全リスト

OpenGL ES 3.0 / OpenGL 4.3 ASTC 圧縮テクスチャの比較

新しい ASTC 形式で圧縮したテクスチャ画像を比べてみました。
「ETC2 の比較」 と同じ画像をエンコードして一部を切り出し 4倍に拡大しています。

↓ASTC 4×4 (8.0 bpp)
astc_4x4_ex_z0.png astc_4x4_ex_z1.png

↓ASTC 5×4 (6.5 bpp)
astc_5x4_ex_z0.png astc_5x4_ex_z1.png

↓ASTC 5×5 (5.3 bpp)
astc_5x5_ex_z0.png astc_5x5_ex_z1.png

↓ASTC 6×5 (4.4 bpp)
astc_6x5_ex_z0.png astc_6x5_ex_z1.png

↓ASTC 6×6 (3.6 bpp)
astc_6x6_ex_z0.png astc_6x6_ex_z1.png

↓ASTC 8×5 (3.3 bpp)
astc_8x5_ex_z0.png astc_8x5_ex_z1.png

↓ASTC 8×6 (2.7 bpp)
astc_8x6_ex_z0.png astc_8x6_ex_z1.png

↓ASTC 10×5 (2.6 bpp)
astc_10x5_ex_z0.png astc_10x5_ex_z1.png

↓ASTC 10×6 (2.2 bpp)
astc_10x6_ex_z0.png astc_10x6_ex_z1.png

↓ASTC 8×8 (2.0 bpp)
astc_8x8_ex_z0.png astc_8x8_ex_z1.png

↓ASTC 10×8 (1.6 bpp)
astc_10x8_ex_z0.png astc_10x8_ex_z1.png

↓ASTC 10×10 (1.3 bpp)
astc_10x10_ex_z0.png astc_10x10_ex_z1.png

↓ASTC 12×10 (1.1 bpp)
astc_12x10_ex_z0.png astc_12x10_ex_z1.png

↓ASTC 12×12 (0.9 bpp)
astc_12x12_ex_z0.png astc_12x12_ex_z1.png

ASTC 4×4 は画質がよく見えるかもしれませんが当然です。
8bpp なので DXT1/ETC1/ETC2 の半分の圧縮率です。
データサイズも 2倍になっています。

圧縮率を高めると徐々にブロックが目立ちますが、
それなりに階調は保っていることがわかります。
特に右の画像は 12×12 の 0.9bpp でも劣化が目立ちません。

下記の表を見て分かる通り、12×12 の場合わずか 7.6KB しか
ありません。

format       size     (byte)    bpp
--------------------------------------
ASTC 4x4     64.0KB   (65536)   8.0bpp
ASTC 5x4     52.0KB   (53248)   6.5bpp
ASTC 5x5     42.2KB   (43264)   5.3bpp
ASTC 6x5     34.9KB   (35776)   4.4bpp
ASTC 6x6     28.9KB   (29584)   3.6bpp
ASTC 8x5     26.0KB   (26624)   3.3bpp
ASTC 8x6     21.5KB   (22016)   2.7bpp
ASTC 10x5    21.1KB   (21632)   2.6bpp
ASTC 10x6    17.4KB   (17888)   2.2bpp
ASTC 8x8     16.0KB   (16384)   2.0bpp
ASTC 10x8    13.0KB   (13312)   1.6bpp
ASTC 10x10   10.5KB   (10816)   1.3bpp
ASTC 12x10    9.0KB   ( 9152)   1.1bpp
ASTC 12x12    7.6KB   ( 7744)   0.9bpp

ETC1/2 RGB   32.0KB   (32768)   4.0bpp
元画像      192.0KB  (196608)  24.0bpp  (256x256)

使用したコマンド

astcenc src dest 4x4 -exhaustive

関連エントリ
OpenGL 4.3/GLES 3.0 次の圧縮テクスチャ ASTC
OpenGL ES 3.0 / OpenGL 4.3 ETC2 テクスチャ圧縮の比較