This is a code sample originally written by William Guo, and extended to include the ETC2 texture compression format by Cristiano Ferreira, Graphics Software Applications Engineer at Intel Corporation. Source code for this sample is available here.
The application of an image, or texture, to a 2D or 3D model to enhance graphical detail is a very common technique in the field of computer graphics. Android* allows the usage of a variety of texture compression file formats, each of which has its own set of advantages and disadvantages. The Android Texture Compression sample allows developers to easily compare textures of five different texture compression file formats: Portable Network Graphics* (PNG), Ericsson Texture Compression* (ETC), Ericsson Texture Compression 2* (ETC2), PowerVR Texture Compression* (PVRTC), and S3 Texture Compression* (S3TC), which is also known as DirectX Texture Compression* (DXTC). This sample demonstrates how to load and use these different formats with OpenGL ES* on Android. All supported texture formats are shown side-by-side so the relative size and quality can be observed. Choosing the right compression allows the developer to balance app size, visual quality, and performance.
The sample loads each texture format, determines the mapping coordinates of the texture, and displays a portion of each of the textures. The final composition will display the full image/texture, but as four separate, format-specific textures. They are individually labeled at the top of the screen and the file sizes are provided on a small bar at the bottom of the screen.
Background on Principles/Related Terms and Texture Formats
Texture mapping is a method by which an image is applied to the surface of a shape or polygon. A helpful analogy to keep in mind is picturing the texture as wrapping paper, and the 3D model as a gift box to be wrapped. This is why this process is also called “texture wrapping.”
Figure 1 - 1 is solely the polygon/shape tank model and 2 is the texture-mapped model
Mipmaps are an optimized group of images that are generated with the primary texture. They are typically created for the purpose of improving rendering speed and reducing aliasing artifacts. Each mip (bitmap image of the collection of mipmap) is a lower resolution version of the primary texture, utilized when viewing the original texture from a distance or a downsized version of it. The creation and implementation of mipmaps comes from the basic concept that we cannot pick up as much detail in an object when it is located far away from us or when the object is miniscule. Based on this idea, different mips can be used to represent different parts of the texture/image based on the size of the objects. This increases rendering speed because the simplified mips have a much lower texel (overall number of texture pixels) count—less pixels to be processed. Additionally, since mipmaps are essentially anti-aliased, the number of noticeable artifacts is also greatly reduced. The support for mipmaps in PNG, ETC (KTX), ETC2 (KTX), PVRTC, and S3TC are included in the sample.
Portable Network Graphics (PNG)
PNG is a bitmapped image format primarily noted for its lossless data file compression. The image format is equipped with support for palette-based images (24 bit RGB or 32 bit RGBA) or grayscale images (with and without an alpha channel).
- Has a lossless compression scheme and high visual quality
- Handles both 8-bit and 16-bit transparency
- Large file size; this will increase app size and memory bandwidth requirements
- Highest GPU cost (i.e. worst performance)
Ericsson Texture Compression (ETC)
Ericsson Texture Compression is a texture compression format that operates on 4x4 blocks of pixels. Originally Khronos used Ericsson Texture Compression as the standard for OpenGL ES 2.0 (this version is also known as ETC1). Therefore, this texture compression format is available on nearly all Android devices. Recently with the release of OpenGL ES 3.0, a reworked version of ETC1, known as ETC2, was implemented as the new standard. The main differences between the two schemes is the algorithm which operates on each pixel group. The improvements in the algorithm result in higher fidelity output when it comes to finer details. The finer quality of the image comes without the cost of additional space.
ETC1 and ETC2 both support compression of 24-bit RGB data, but they do not support the compression of any images/textures containing alpha components. In addition, there are two different file formats that both fall under the category of ETC texture compression: KTX and PKM. KTX is the standard Khronos Group compression format, and it provides a container for multiple images/textures. When mipmaps are generated with KTX, only one KTX file is created. On the other hand, PKM is a much simpler file format used mainly to contain single compressed images. Generating mipmaps in this case would create multiple PKM files instead of a single file, and so as a consequence, this format is not recommended for that purpose.
- File size is considerably smaller in comparison to the PNG texture compression format
- GPU hardware acceleration supported on nearly all Android devices
- Quality is not as high as PNG texture compression (ETC is a lossy compression format)
- Does not support alpha channels/components
Example of Tools Used For Compression:
PowerVR Texture Compression (PVRTC)
PowerVR Texture Compression is a lossy, fixed-rate texture compression format utilized primarily in Imagination Technologies’ PowerVR* MBX, SGX, and Rogue technologies. It is currently being employed in all iPhone*, iPod*, and iPad* devices as the standard compression format. Unlike ETC and S3TC, PVRTC is not block-based but rather involves the bilinear upscaling and low-precision blending of two low-resolution images. In addition to the unique process of compression by the PVRTC format, it also supports RGBA (alpha channel supported) for both the 2-bpp (2 bits per pixel) and 4-bpp (4 bits per pixel) options.
- Supports alpha channels/components
- Supports RGBA data for both 2-bpp and 4-bpp modes
- File size is much smaller than one using PNG texture compression
- GPU hardware acceleration on PowerVR GPUs
- Quality is not as high as PNG texture compression (PVRTC is a lossy compression format)
- PVRTC is only supported on PowerVR hardware
- Only square (power-of-two) dimension textures are determined to work consistently, although in some cases, rectangular support is provided for the compressed texture
- Compressing textures into this format can be slow
Tool Used For Compression:
S3 Texture Compression (S3TC) or DirectX Texture Compression (DXTC)
S3 Texture Compression is a lossy, fixed-rate, texture compression format. This style of compression makes S3TC an ideal texture compression format for textures used in hardware-accelerated 3D computer graphics. Following the integration of S3TC with Microsoft’s DirectX* 6.0 and OpenGL 1.3, the compression format became much more widespread. There are at least 5 different variations of the S3TC format (including DXT1 through DXT5). The sample supports the commonly used variations (DXT1, DXT3, and DXT5).
DXT1: DXT1 is the smallest mode of S3TC compression; it converts each block of 16 pixels into 64 bits. Additionally, it is composed of two different 16-bit RGB 5:8:5 color values and a 4x4 2-bit lookup table. DXT1 does not support alpha channels.
DXT3: DXT3 converts each block of 16 pixels into 128 bits and is composed of 64 bits of alpha channel data and 64 bits of color data. DXT3 is a good format choice for images or textures with sharp alpha transitions (opaque versus translucent).
DXT5: DXT5 converts each block of 16 pixels into 128 bits and is composed of 64 bits of alpha channel data and 64 bits of color data. DXT5 is a good format choice for images or textures with gradient alpha transitions.
- File size is considerably smaller in comparison to the PNG texture compression format.
- Decent quality, low banding (artifacts not too visible)
- Good speed for compression/decompression
- GPU hardware acceleration for a wide range of video chip parts: support is almost universal on desktop, but it still needs to grow on Android devices
- Quality is not as high as PNG texture compression (S3TC is a lossy compression format)
- Not supported on all Android devices
Tool Used for Compression:
- DirectX Texture Tool from DirectX (included in the DX SDK)
Accessing the Texture Data
Most texture compression file formats have a header that is placed before the actual texture data. The header usually contains data regarding the name of the texture compression format, the width of the texture, the height of the texture, the depth of the texture, the size of the data, the internal format, and other specific properties of the file.
Our goal is to load and map texture data from each of the different texture compression files to a 2D model for comparison of quality and file size. The headers that come before the texture data is not to be included as part of the texture to be mapped, as doing so would distort the image/texture representation. The file headers all vary depending on the file compression format that is being considered, and as such, each texture compression file format needs individual support in order to load the textures and map them properly.
The PVRTC header is packed due to the presence of the 64-bit pixel format data member (mPixelFormat in the sample). ARM attempts to align the header by padding it with 4 additional bytes, making the header a total of 56 bytes instead of the raw 52 bytes. This in turn causes the image to be distorted when displayed on an ARM device. Intel devices do not pad the header, and so isn’t an issue. Packing the header solves the ARM padding issue, and the texture displays correctly on both ARM and Intel devices.
Figure 3 – Image of ARM Padding Issue with PVRTC in Previous Sample
Loading and Supporting the Texture Formats:
Mipmaps are taken care of in PNG by a simple function: glGenerateMipmap – a predefined function from Khronos OpenGL designed for this specific purpose. Sean Barrett’s public domain stb_image.c was utilized in the reading and loading of the PNG files (as well as locating and pinpointing the texture data to be processed). The following is a piece of code that initializes the texture and provides mipmap support for the PNG compression format.
// Initialize the texture
glTexImage2D( GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, pData );
// Mipmap support
glGenerateMipmap( GL_TEXTURE_2D );
Loading ETC / ETC2
As mentioned earlier, ETC is composed of two different format types—KTX and PKM. KTX is the standard compression format, used as a container for multiple images/textures, and is ideal for mipmapping. PKM, on the other hand, is designed for simple single texture compression, and so generating mipmaps gives rise to multiple PKM files, which is inefficient. For this reason, mipmap support for ETC texture compression in the sample app is restricted to KTX file compression only. Khronos provides an open source C library (libktx) in which KTX texture loading with mipmaps is supported. We took advantage of this tool and implemented the code in a texture loading function called LoadTextureETC_KTX. The function used to actually load the KTX texture compression format file is ktxLoadTextureM (loads the desired texture from data in memory). This function (ktxLoadTextureM) was provided in the library (libktx) and is documented at the Khronos site (in “Resources” below).
The following is a piece of code that initializes the texture and provides mipmap support for the ETC (KTX) compression format:
// Generate handle & Load Texture
GLuint handle = 0;
KTX_error_code result = ktxLoadTextureM( pData, fileSize, &handle, &target, NULL, &mipmapped, NULL, NULL, NULL );
if( result != KTX_SUCCESS )
LOGI( "KTXLib couldn't load texture %s. Error: %d", TextureFileName, result );
// Bind the texture
glBindTexture( target, handle );
Providing mipmap support for PVRTC textures was a bit trickier. After reading through the header, the offset is defined as the size of the header plus the metadata size (metadata follows the header and is also not part of the actual texture data). For each of the mips generated, pixels are grouped into blocks (different depending on if it is 4 bits per pixel or 2 bits per pixel—both valid PVRTC formats). Next, clamping occurs, and so height and width of the blocks are limited to certain boundaries. Then, the function glCompressedTexImage() is called to identify a two-dimensional image in the PVRTC compressed format. Following that, the pixel data size is calculated and then is added to the offset in order to group the set of pixels in the next mip. This process is repeated until there are no more mips to operate on.
// Initialize the texture
unsigned int offset = sizeof(PVRHeaderV3) + pHeader->mMetaDataSize;
unsigned int mipWidth = pHeader->mWidth;
unsigned int mipHeight = pHeader->mHeight;
unsigned int mip = 0;
// Determine size (width * height * bbp/8), min size is 32
unsigned int pixelDataSize = ( mipWidth * mipHeight * bitsPerPixel ) >> 3;
pixelDataSize = (pixelDataSize < 32) ? 32 : pixelDataSize;
// Upload texture data for this mip
glCompressedTexImage2D(GL_TEXTURE_2D, mip, format, mipWidth, mipHeight, 0, pixelDataSize, pData + offset);
// Next mips is half the size (divide by 2) with a min of 1
mipWidth = ( mipWidth >> 1 == 0 ) ? 1 : mipWidth >> 1;
mipHeight = ( mipHeight >> 1 == 0 ) ? 1 : mipHeight >> 1;
// Move to next mip
offset += pixelDataSize;
} while(mip < pHeader->mMipmapCount);
After loading an S3TC texture file, determining the format, and reading past the header, mipmap support takes place. Each mip is looped through and pixels are grouped into blocks. Then, the function glCompressedTexImage is called to identify a two-dimensional image in the S3TC compressed format. The aggregate data size of the blocks is then added to the offset in order to move to the next mip and perform the same actions. The process repeats until there are no more mips to operate on. The following is a piece of code that initializes the texture and provides mipmap support for the S3TC compression format.
// Initialize the texture
// Uploading mipmaps
unsigned int offset = 0;
unsigned int width = pHeader->mWidth;
unsigned int height = pHeader->mHeight;
unsigned int mip = 0;
// Determine size
// As defined in extension: size = ceil(<w>/4) * ceil(<h>/4) * blockSize
unsigned int Size = ((width + 3) >> 2) * ((height + 3) >> 2) * blockSize;
glCompressedTexImage2D( GL_TEXTURE_2D, mip, format, width, height, 0, Size, (pData + sizeof(DDSHeader)) + offset );
checkGlError( "glCompressedTexImage2D" );
offset += Size;
if( ( width <<= 1 ) == 0) width = 1;
if( ( height <<= 1 ) == 0) height = 1;
} while( mip < pHeader->mMipMapCount );
Depending on the situation it is used in, proper texture compression may improve visual quality, decrease the size of an app considerably, and greatly enhance performance. Optimal texture compression provides substantial advantages to developers and their applications. The Android Texture Compression sample app demonstrates how to load and access the most popular texture formats that can be found on Android. Go download the source code and incorporate the best texture compression in your next project.
About the Author:
William Guo created this sample while he was an intern with Intel’s Personal Form Factor team while working on Intel phones and tablets. He is currently attending the University of California, Berkeley as an up & rising sophomore with an expected graduation date of May 2015. He intends to major in Electrical Engineering and Computer Science with a possible minor in psychology.
Sample and article updated to include ETC2 format by Cristiano Ferreira who is currently working for Intel in developer relations. Cristiano can be contacted at Cristiano.email@example.com for questions regarding the sample.
Updated artwork for the ETC2 sample was provided by Jeffery A. Williams, Lead Digital Content Designer in the Game Development Experience group under Developer Relations at Intel Corporation.
- Texture Mapping Figure 1:
- Mipmapping Image Figure 2:
- PNG* Info:
- ETC* (KTX* and PKM*) Info:
- PVRTC* Info:
- S3TC* Info:
- Source Code: