BCn Texture Compression

A widely available texture compression comes from S3, mostly due to Microsoft licensing it and including it into DirectX 7. It uses the file format .dds (DirectDraw Surface), which is basically a copy of the texture in video memory. Every graphics accelerator compatible with DirectX 7 or higher supports this texture compression.

The BCn Formats are:
BC1 = DXT1 = 8 Bytes per Block, Accuracy: R5G6B5 or R5G5B5A1 (0.5 Byte/Texel)
BC2 = DXT3 = 16 Bytes per Block, Accuracy: R5G6B5A8 (1 Byte/Texel)
BC3 = DXT5 = 16 Bytes per Block, Accuracy: R5G6B5A8 (1 Byte/Texel)
BC4 = Basically this is only the alpha channel from DXT5 without the color channel. CompressedRedRgtc1 (0.5 Byte/Texel)
BC5 = This is 2 channels of BC4, twice the size. CompressedRgRgtc1 (1 Byte/Texel)

BC1, 2 and 3 from EXT_texture_compression_s3tc (DirectX7+ Hardware)
BC4 and 5 from ARB_texture_compression_rtgc. (~DirectX10 Hardware)

The formats DXT2 and DXT4 do exist, but they include pre-multiplied Alpha which is problematic when blending with images with explicit Alpha (RGBA, DXT3/5, etc). That's why those formats have barely been used, and are partially not supported by hardware and export/import tools and they have no BCn number.

Compressed vs. Uncompressed

Texture compression encodes the whole Image into blocks of 4x4 Texel, instead of storing every single Texel of the Image. Thus the ideal compressed Texture dimension is a multiple of 4, like 640x480 or a power of 2, which can be nicely fit into these Blocks. This is the ideal and not a restriction, the specification allows any non-power-of-two dimension, but will internally use a 4x4 Block for a Texture with the size of 2x1 (the other Texels in the Block are undefined).

You probably guessed it already, there is a catch involved when reliably shrinking an image to 25% of it's uncompressed size: A lossy compression technique. This quality loss involved, which can be altered by tweaking the Filter options when compressing the image, is different to the one used in JPG compression. Although both formats - .dds and .jpg - are designed to compress an Image, the S3TC format was developed with graphics hardware in mind.

A bilinear Texture lookup usually reads 2x2 Texels from the Texture and interpolates those 4 Texels to get the final Color. Since a Block consists of 4x4 Texels, there is a good chance that all 4 Texels - which must be examined for the bilinear lookup - are in the same Block. This means that the worst case scenario involves reading 4 Blocks, but usually only 1-2 Blocks are used to achieve the bilinear lookup. When using uncompressed Textures, every bilinear lookup requires reading 4 Texels.

If you do the maths now you will notice that the compressed image actually needs 16 Bytes for 1 Block of RGBA Color, but the uncompressed 4 Texels of RGBA need 16 Bytes too. And yes, if you would only draw a single Pixel on the screen all this would not bring any noticable performance gains, actually it would be slower if multiple Blocks must be read to do the lookup.

However in OpenGL you typically draw more than a single Pixel, at least a Triangle. When the Triangle is rasterized, alot of Pixels will be very close to each other, which means their 2x2 lookup is very likely in the same 4x4 Block used by the last lookup, or a close neighbour. Graphic cards usually support this locality by using a small amount of memory in the chip for a dedicated Texture Cache. If a Cache hit is made, the cost for reading the Texels is very low, compared to reading from Video Memory.

That's why S3TC does decrease render times: the earlier mentioned 16 Bytes of a DXTn Block contain 16 Texels (1 Byte per Texel), while 16 Bytes of uncompressed Texture only contain 4 Texels (4 Bytes per Texel). Alot more data is stored in the 16 Bytes of DXTn, and alot of lookups will be able to use the fast Texture Cache. The game Quake 3 Arena's Framerate increases by ~20% when using compressed Textures, compared to using uncompressed Textures.

Although you might be convinced now that Texture Compression is something worth looking into, do handle it with care. After all, it's a lossy compression Technique which introduces compression Artifacts into the Texture. For Textures that are close to the Viewer this will be noticed, that's why 2D Elements which are drawn very close to the near Plane - like the Mouse Cursor, Fonts or User Interface Elements like the Health display - are usually done with uncompressed Textures, which do not suffer from Artifacts.
As a rule of thumb, do not use Texture Compression where 1 Texel in the Texture will map to 1 Pixel on the Screen.

Using OpenTK.Utilities .dds loader
At the time of writing, the .dds loader included with OpenTK can handle compressed 2D Textures and compressed Cube Maps. Keep in mind that the loader expects a valid OpenGL Context to be present. It will only read the file from disk and upload all MipMap levels to OpenGL. It will NOT set minification/magnification filter or wrapping mode, because it cannot guess how you intent to use it.

void LoadFromDisk( string filename, bool flip, out int texturehandle, out TextureTarget dimension)

Input Parameter: filename
A string used to locate the DDS file on the harddisk, note that escape-sequences like "\n" are NOT stripped from the string.

Input Parameter: flip
The DDS format is designed to be used with DirectX, and that defines GL.TexCoord2(0.0, 0.0) at top-left, while OpenGL uses bottom-left. If you wish to use the default OpenGL Texture Matrix, the Image must be flipped before loading it as Texture into OpenGL.

Output Parameter: texturehandle
If there occured any error while loading, the loader will return "0" in this parameter. If >0 it's a valid Texture that can be used with GL.BindTexture.

Output Parameter: dimension
This parameter is used to identify what was loaded, currently it can return "Invalid", "Texture2D" or "TextureCube".

Example Usage

TextureTarget ImageTextureTarget;
int ImageTextureHandle;
ImageDDS.LoadFromDisk( @"", true, out ImageTextureHandle, out ImageTextureTarget );
if ( ImageTextureHandle == 0 || ImageTextureTarget == TextureTarget.Invalid )
   // loading failed
// load succeeded, Texture can be used.
GL.BindTexture( ImageTextureTarget, ImageTextureHandle );
GL.TexParameter( ImageTextureTarget, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear );
int[] MipMapCount = new int[1];
GL.GetTexParameter( ImageTextureTarget, GetTextureParameter.TextureMaxLevel, out MipMapCount[0] );
if ( MipMapCount == 0 ) // if no MipMaps are present, use linear Filter
  GL.TexParameter( ImageTextureTarget, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear );
else // MipMaps are present, use trilinear Filter
  GL.TexParameter( ImageTextureTarget, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear );

Remember that you must first GL.Enable the states Texture2D or TextureCube, before using the Texture in drawing.

Useful links:

ATi Compressonator:

nVidia's Photoshop Plugin:

nVidia's GPU-accelerated Texture Tools:

Detailed comparison of uncompressed vs. compressed Images:

OpenGL Extension Specification:

Microsoft's .dds file format specification (was used to build the OpenTK .dds loader)

DXT Compression using CUDA

Real-Time YCoCg-DXT Compression

Last Update of the Links: January 2008