Cubemaps & ARB_texture_compression

Hello,
I’m implementing standard texture compression into my renderer.
All works fine, until the compressor reaches my cube maps.
At this point, it fails to compress each face.
Here’s the code:-

destcomponents = GL_COMPRESSED_RGB_ARB;
sourcecomponents = GL_RGB;
dtype = GL_UNSIGNED_BYTE;

glTexImage2D( GLcubemapfacenames[i],
0,
destcomponents,
m_Image->GetImageXSize(),
m_Image->GetImageYSize(),
0,
sourcecomponents,
dtype,
m_Image->GetImageData(i));
if (compressing)
{
GLint comp=0;
glGetTexLevelParameteriv(GLcubemapfacenames[i], 0, GL_TEXTURE_COMPRESSED_ARB, &comp);
Print("Result:%i
", comp);
}

Result is always 0 for cubemaps.

Any ideas?

[This message has been edited by inept (edited 11-29-2002).]

Another question to do with compressed textures…
Is it possible to subimage compressed texture data into a non-compressed texture? Because I can’t get it to work…

I take it nobody knows anything about texture compression?

inept: no, you cannot subimage compressed data into an uncompressed image, AFAIK.

Also, driver compression has traditionally been an area where drivers have been less well tested, although the specification is sufficiently clear that the friendly driver people can usually fix it when you run into trouble.

When a driver compresses an image, it doesn’t spend as much time doing as good a job as the Photoshop plug-in would, though, so I recommend you save out pre-compressed images to .dds files, and then just upload them ready-compressed to the card. That’s not only much faster and well tested in the drivers, but it also results in nicer compression (less artifacts).

Here’s some code to upload a .dds, if you need it (taken from my experimentation code, not from production code):

// little-endian, of course
#define DDS_MAGIC 0x20534444

// DDS_header.dwFlags
#define DDSD_CAPS 0x00000001
#define DDSD_HEIGHT 0x00000002
#define DDSD_WIDTH 0x00000004
#define DDSD_PITCH 0x00000008
#define DDSD_PIXELFORMAT 0x00001000
#define DDSD_MIPMAPCOUNT 0x00020000
#define DDSD_LINEARSIZE 0x00080000
#define DDSD_DEPTH 0x00800000

// DDS_header.sPixelFormat.dwFlags
#define DDPF_ALPHAPIXELS 0x00000001
#define DDPF_FOURCC 0x00000004
#define DDPF_INDEXED 0x00000020
#define DDPF_RGB 0x00000040

// DDS_header.sCaps.dwCaps1
#define DDSCAPS_COMPLEX 0x00000008
#define DDSCAPS_TEXTURE 0x00001000
#define DDSCAPS_MIPMAP 0x00400000

// DDS_header.sCaps.dwCaps2
#define DDSCAPS2_CUBEMAP 0x00000200
#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400
#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800
#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000
#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000
#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000
#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000
#define DDSCAPS2_VOLUME 0x00200000

#define D3DFMT_DXT1 ‘1TXD’ // DXT1 compression texture format
#define D3DFMT_DXT2 ‘2TXD’ // DXT2 compression texture format
#define D3DFMT_DXT3 ‘3TXD’ // DXT3 compression texture format
#define D3DFMT_DXT4 ‘4TXD’ // DXT4 compression texture format
#define D3DFMT_DXT5 ‘5TXD’ // DXT5 compression texture format

#define PF_IS_DXT1(pf)
((pf.dwFlags & DDPF_FOURCC) &&
(pf.dwFourCC == D3DFMT_DXT1))

#define PF_IS_DXT3(pf)
((pf.dwFlags & DDPF_FOURCC) &&
(pf.dwFourCC == D3DFMT_DXT3))

#define PF_IS_DXT5(pf)
((pf.dwFlags & DDPF_FOURCC) &&
(pf.dwFourCC == D3DFMT_DXT5))

#define PF_IS_BGRA8(pf)
((pf.dwFlags & DDPF_RGB) &&
(pf.dwFlags & DDPF_ALPHAPIXELS) &&
(pf.dwRGBBitCount == 32) &&
(pf.dwRBitMask == 0xff0000) &&
(pf.dwGBitMask == 0xff00) &&
(pf.dwBBitMask == 0xff) &&
(pf.dwAlphaBitMask == 0xff000000U))

#define PF_IS_BGR8(pf)
((pf.dwFlags & DDPF_ALPHAPIXELS) &&
!(pf.dwFlags & DDPF_ALPHAPIXELS) &&
(pf.dwRGBBitCount == 24) &&
(pf.dwRBitMask == 0xff0000) &&
(pf.dwGBitMask == 0xff00) &&
(pf.dwBBitMask == 0xff))

#define PF_IS_BGR5A1(pf)
((pf.dwFlags & DDPF_RGB) &&
(pf.dwFlags & DDPF_ALPHAPIXELS) &&
(pf.dwRGBBitCount == 16) &&
(pf.dwRBitMask == 0x00007c00) &&
(pf.dwGBitMask == 0x000003e0) &&
(pf.dwBBitMask == 0x0000001f) &&
(pf.dwAlphaBitMask == 0x00008000))

#define PF_IS_BGR565(pf)
((pf.dwFlags & DDPF_RGB) &&
!(pf.dwFlags & DDPF_ALPHAPIXELS) &&
(pf.dwRGBBitCount == 16) &&
(pf.dwRBitMask == 0x0000f800) &&
(pf.dwGBitMask == 0x000007e0) &&
(pf.dwBBitMask == 0x0000001f))

#define PF_IS_INDEX8(pf)
((pf.dwFlags & DDPF_INDEXED) &&
(pf.dwRGBBitCount == 8))

union DDS_header {
struct {
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 ];

//  DDPIXELFORMAT
struct {
  unsigned int    dwSize;
  unsigned int    dwFlags;
  unsigned int    dwFourCC;
  unsigned int    dwRGBBitCount;
  unsigned int    dwRBitMask;
  unsigned int    dwGBitMask;
  unsigned int    dwBBitMask;
  unsigned int    dwAlphaBitMask;
}               sPixelFormat;

//  DDCAPS2
struct {
  unsigned int    dwCaps1;
  unsigned int    dwCaps2;
  unsigned int    dwDDSX;
  unsigned int    dwReserved;
}               sCaps;
unsigned int    dwReserved2;

};
char data[ 128 ];
};

struct DdsLoadInfo {
bool compressed;
bool swap;
bool palette;
unsigned int divSize;
unsigned int blockBytes;
GLenum internalFormat;
GLenum externalFormat;
GLenum type;
};

DdsLoadInfo loadInfoDXT1 = {
true, false, false, 4, 8, GL_COMPRESSED_RGBA_S3TC_DXT1
};
DdsLoadInfo loadInfoDXT3 = {
true, false, false, 4, 16, GL_COMPRESSED_RGBA_S3TC_DXT3
};
DdsLoadInfo loadInfoDXT5 = {
true, false, false, 4, 16, GL_COMPRESSED_RGBA_S3TC_DXT5
};
DdsLoadInfo loadInfoBGRA8 = {
false, false, false, 1, 4, GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE
};
DdsLoadInfo loadInfoBGR8 = {
false, false, false, 1, 3, GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE
};
DdsLoadInfo loadInfoBGR5A1 = {
false, true, false, 1, 2, GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV
};
DdsLoadInfo loadInfoBGR565 = {
false, true, false, 1, 2, GL_RGB5, GL_RGB, GL_UNSIGNED_SHORT_5_6_5
};
DdsLoadInfo loadInfoIndex8 = {
false, false, true, 1, 1, GL_RGB8, GL_BGRA, GL_UNSIGNED_BYTE
};

bool
MyTexture::loadDds( FILE * f, bool nearest )
{
DDS_header hdr;
size_t s = 0;
unsigned int x = 0;
unsigned int y = 0;
unsigned int mipMapCount = 0;
// DDS is so simple to read, too
fread( &hdr, sizeof( hdr ), 1, f );
assert( hdr.dwMagic == DDS_MAGIC );
assert( hdr.dwSize == 124 );

if( hdr.dwMagic != DDS_MAGIC | | hdr.dwSize != 124 | |
!(hdr.dwFlags & DDSD_PIXELFORMAT) | | !(hdr.dwFlags & DDSD_CAPS) )
{
goto failure;
}

xSize = hdr.dwWidth;
ySize = hdr.dwHeight;
assert( !(xSize & (xSize-1)) );
assert( !(ySize & (ySize-1)) );

DdsLoadInfo * li;

if( PF_IS_DXT1( hdr.sPixelFormat ) ) {
li = &loadInfoDXT1;
}
else if( PF_IS_DXT3( hdr.sPixelFormat ) ) {
li = &loadInfoDXT3;
}
else if( PF_IS_DXT5( hdr.sPixelFormat ) ) {
li = &loadInfoDXT5;
}
else if( PF_IS_BGRA8( hdr.sPixelFormat ) ) {
li = &loadInfoBGRA8;
}
else if( PF_IS_BGR8( hdr.sPixelFormat ) ) {
li = &loadInfoBGR8;
}
else if( PF_IS_BGR5A1( hdr.sPixelFormat ) ) {
li = &loadInfoBGR5A1;
}
else if( PF_IS_BGR565( hdr.sPixelFormat ) ) {
li = &loadInfoBGR565;
}
else if( PF_IS_INDEX8( hdr.sPixelFormat ) ) {
li = &loadInfoIndex8;
}
else {
goto failure;
}

//fixme: do cube maps later
//fixme: do 3d later
x = xSize;
y = ySize;
glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE );
mipMapCount = (hdr.dwFlags & DDSD_MIPMAPCOUNT) ? hdr.dwMipMapCount : 1;
if( li->compressed ) {
size_t size = max( li->divSize, x )/li->divSize * max( li->divSize, y )/li->divSize * li->blockBytes;
assert( size == hdr.dwPitchOrLinearSize );
assert( hdr.dwFlags & DDSD_LINEARSIZE );
unsigned char * data = (unsigned char *)malloc( size );
if( !data ) {
goto failure;
}
format = cFormat = li->internalFormat;
for( unsigned int ix = 0; ix < mipMapCount; ++ix ) {
fread( data, 1, size, f );
glCompressedTexImage2D( GL_TEXTURE_2D, ix, li->internalFormat, x, y, 0, size, data );
gl->updateError();
x = (x+1)>>1;
y = (y+1)>>1;
size = max( li->divSize, x )/li->divSize * max( li->divSize, y )/li->divSize * li->blockBytes;
}
free( data );
}
else if( li->palette ) {
// currently, we unpack palette into BGRA
assert( hdr.dwFlags & DDSD_PITCH );
assert( hdr.sPixelFormat.dwRGBBitCount == 8 );
size_t size = hdr.dwPitchOrLinearSize * ySize;
assert( size == x * y * li->blockBytes );
format = li->externalFormat;
cFormat = li->internalFormat;
unsigned char * data = (unsigned char *)malloc( size );
unsigned int palette[ 256 ];
unsigned int * unpacked = (unsigned int )malloc( sizesizeof( unsigned int ) );
fread( palette, 4, 256, f );
for( unsigned int ix = 0; ix < mipMapCount; ++ix ) {
fread( data, 1, size, f );
for( unsigned int zz = 0; zz < size; ++zz ) {
unpacked[ zz ] = palette[ data[ zz ] ];
}
glPixelStorei( GL_UNPACK_ROW_LENGTH, y );
glTexImage2D( GL_TEXTURE_2D, ix, li->internalFormat, x, y, 0, li->externalFormat, li->type, unpacked );
gl->updateError();
x = (x+1)>>1;
y = (y+1)>>1;
size = x * y * li->blockBytes;
}
free( data );
free( unpacked );
}
else {
if( li->swap ) {
glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_TRUE );
}
size = x * y * li->blockBytes;
format = li->externalFormat;
cFormat = li->internalFormat;
unsigned char * data = (unsigned char )malloc( size );
//fixme: how are MIP maps stored for 24-bit if pitch != ySize
3 ?
for( unsigned int ix = 0; ix < mipMapCount; ++ix ) {
fread( data, 1, size, f );
glPixelStorei( GL_UNPACK_ROW_LENGTH, y );
glTexImage2D( GL_TEXTURE_2D, ix, li->internalFormat, x, y, 0, li->externalFormat, li->type, data );
gl->updateError();
x = (x+1)>>1;
y = (y+1)>>1;
size = x * y * li->blockBytes;
}
free( data );
glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE );
gl->updateError();
}
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipMapCount-1 );
gl->updateError();

return true;

failure:
return false;
}

Oh ****!
But it is possible to subimage compressed texture data into a compressed texture?
The spec is not very clear on this.
Have you any idea why my cube maps won’t compress successfully?
(thanks for the dds loading code, but I’m banking on making a self contained app that compresses and saves the textures myself (for later fast loading/rendering) - can’t use external apps like photoshop - I’ve already got my own binary file format for my texture data, similar to dds but expandable by me).
If I can’t subimage compressed data into my textures, then I’ll have to recreate the textures using glCompressedTexImage2D which will be a lot slower than subimaging…

There is a CompressedTexSubImage() to sub-image pre-compressed data, and you can also use TexSubImage() to compress a sub-image into a compressed texture. Because compressing is so slow and will stutter frame rate if done to large textures at run time, we sliced up the compression work in several sub-pieces, and issued one of the pieces per frame – this threw many of the drivers for a loop back then, but it seems to be well supported by major vendors now.

However, I really question the wisdom of letting the driver compress the image. It really doesn’t do as good a job as standalone compression code, that can take more time to do a better search.

However, if you’re talking of CopyTexSubImage(), there’s no helping you there, as you can’t render to a compressed image :slight_smile:

Originally posted by inept:
[b]Hello,
I’m implementing standard texture compression into my renderer.
All works fine, until the compressor reaches my cube maps.
At this point, it fails to compress each face.
Here’s the code:-

destcomponents = GL_COMPRESSED_RGB_ARB;
sourcecomponents = GL_RGB;
dtype = GL_UNSIGNED_BYTE;

[quote]

glTexImage2D( GLcubemapfacenames[i],
0,
destcomponents,
m_Image->GetImageXSize(),
m_Image->GetImageYSize(),
0,
sourcecomponents,
dtype,
m_Image->GetImageData(i));
if (compressing)
{
GLint comp=0;
glGetTexLevelParameteriv(GLcubemapfacenames[i], 0, GL_TEXTURE_COMPRESSED_ARB, &comp);
Print("Result:%i
", comp);
}

Result is always 0 for cubemaps.

Any ideas?

[This message has been edited by inept (edited 11-29-2002).][/b][/QUOTE]

I wrote the ARB_texture_compression and EXT_texture_compression_s3tc specs, but I haven’t been around to answer questions the last few days.

Technically, there’s no requirement that an implementation supporting ARB_texture_compression actually do compression at all! In practice, implementations will pick and choose what formats they do compress. For example, if you pass in COMPRESSED_LUMINANCE or COMPRESSED_ALPHA, a driver supporting only S3TC (for example) may choose not to do any compression. S3TC is an RGB(A)-based format, and you would end up with little or no compression relative to uncompressed LUMINANCE or ALPHA, but would still all the compression artifacts.

It appears that the platform you’re running on (you didn’t say) doesn’t support compressed cube maps, and falls back to using uncompressed cube maps.

Originally posted by inept:
Another question to do with compressed textures…
Is it possible to subimage compressed texture data into a non-compressed texture? Because I can’t get it to work…

No. It’s not allowed. You can decompress the data and then load as a subimage.

Originally posted by inept:
Oh ****!
But it is possible to subimage compressed texture data into a compressed texture?
The spec is not very clear on this.
Have you any idea why my cube maps won’t compress successfully?
(thanks for the dds loading code, but I’m banking on making a self contained app that compresses and saves the textures myself (for later fast loading/rendering) - can’t use external apps like photoshop - I’ve already got my own binary file format for my texture data, similar to dds but expandable by me).
If I can’t subimage compressed data into my textures, then I’ll have to recreate the textures using glCompressedTexImage2D which will be a lot slower than subimaging…

In general, ARB_texture_compression does NOT allow you to subimage compressed data into a compressed texture. The only exception to this is that you are always allowed to subimage compressed data with an offset of (0,0). In this case, the rest of your texture is then undefined. This is provided to support the use of images with non-power-of-2 sizes in texture maps – you can’t load them with any TexImage entry points.

However, ARB_texture_compression was written to be very generic. For example, you could in theory use a JPEG compressor if your hardware supported decompression appropriately. Doing a subimage merge would be a pain in the rear in that case, and could cause additional artifacts. See issue (5) in the spec.

But you are using S3TC textures under EXT_texture_compresssion_s3tc. Since S3TC textures are made up of 4x4 blocks, the spec relaxes ARB_texture_compression’s restrictions. For S3TC textures, subimage updates using compressed data are OK, as long as the update region is 4x4 block-aligned.

Thank god someones replied!

pbrown:
It appears that the platform you’re running on (you didn’t say) doesn’t support compressed cube maps, and falls back to using uncompressed cube maps

NVidia Geforce4, with latest detonator drivers.

As for the gl(compressed)texsubimage, I don’t want to replace part of the texture, I want to replace all of the texture (offset=0,0 width=texturewidth,height=textureheight) as it’s proven to be faster than glteximage with non-compressed textures for me in the past.

Could you tell me what the algorithm is for compressing? Is there a library I could use? As I’ve said before, it would be impossible for me to rely on a 3rd party application to compress the textures, as I’m producing a stand-alone application (that’s why I’m having to stick with the driver compression method).

If you’re prepared to specify the S3TC format specifically (rather than just generic compressed formats) then it’s legal, and it works, to subimage from uncompressed data into a compressed texture, as long as it’s done on a 4x4 boundar (just as Pat said).

If you want to do the compression instead of the driver, nVIDIA has several texture compression tools and libraries on their developer web site. Again, these require that you specify S3TC format explicitly, which requires EXT_texture_compression_s3tc.

The format of S3TC texture compressed blocks (also known as DXT1/3/5) is well documented on MSDN. The algorithm for finding the optimal interpolands can easily degrade to exhaustive search, though, which gets very CPU intensive.

Originally posted by inept:
[b]Thank god someones replied!

NVidia Geforce4, with latest detonator drivers.

As for the gl(compressed)texsubimage, I don’t want to replace part of the texture, I want to replace all of the texture (offset=0,0 width=texturewidth,height=textureheight) as it’s proven to be faster than glteximage with non-compressed textures for me in the past.

Could you tell me what the algorithm is for compressing? Is there a library I could use? As I’ve said before, it would be impossible for me to rely on a 3rd party application to compress the textures, as I’m producing a stand-alone application (that’s why I’m having to stick with the driver compression method).[/b]

inept,

It may be that our driver isn’t automatically turning the COMPRESSED_RGB token into a compressed format for cube maps. One way to force the issue is to instead specify an internal format of COMPRESSED_RGB_S3TC_DXT1_EXT (requiring the EXT_texture_compresssion_s3tc extension). It wouldn’t surprise me if that worked for you.

I am not in a position to describe our driver’s S3TC compression algorithm in any detail. From jwatte’s comments, it sounds like our developer relations site has some code samples that might be of use. I personally haven’t looked at any of that code, so I’m not sure how helpful it would be for you.

You may be able to find S3TC format information in DirectX/MSDN documentation, but it’s kind of spotty. S3 used to have good developer information available, but that was a LONG time ago. Fortunately, the S3TC texture formats are described in considerable detail in the EXT_texture_compression_s3tc spec.

[This message has been edited by pbrown (edited 12-02-2002).]