MasDennis/Rajawali — pull request #475 by AndrewJo (2013-03-13 06:43:31+00:00)
Title: Mipmapping support for compressed textures

Background
-----------------
Adding mipmapping support was only a matter of time as it greatly improves on memory bandwidth (since GPU fetches from smaller images) and improves the fillrate while providing smoother images. However, other than Mali GPUs, you do not have the ability to automatically generate mipmaps for compressed textures. Mipmapping is still supported by the GPU only that you have to generate them manually using texture compression tools.

Mipmapping does increase memory consumption by 33% but this is a non-issue on uncompressed textures. See below for comparison of memory usage for a 2048x1024 texture:

* Uncompressed textures (JPEG, PNG, GIF, BMP, etc.)
* No mipmapping: 6MB
* Mipmapping: 6MB + 33% increase = 8MB
* ETC1 compressed textures
* No mipmapping: 1MB
* Mipmapping: 1MB + 33% increase = 1.33MB

Compressed textures already benefit from lower memory bandwidth usage from their smaller size. Combined with mipmapping, this is reduced even further since in some cases, GPU only has to fetch from tiny image data ranging from few dozen bytes to several hundred kilobytes.

Notes
----------
### Breaking Changes
`addTexture` method now takes in an array of `ByteBuffer`s. Automatic mipmapping is still supported for uncompressed textures but manual mipmapping is now supported for both compressed and uncompressed textures.

Be sure to pass in the correct mipmaps at the right array index or your textures will simply not be displayed. For example, mipmap level 0 should be at index 0 of the ByteBuffer array.

This also means changes were made in `TextureInfo` to change a single `ByteBuffer` field into a `ByteBuffer` array. If you are using `getBuffer()` in your code, you'll need to make changes to accept a `ByteBuffer` array as opposed to a single `ByteBuffer` instance.

You can use tools like Mali Texture Compression Tool to manually generate mipmap levels and place them in the `res/raw` directory in your project.

There were cases before where you had to pass in `null` for `ByteBuffer` if you were going to fill the texture data later on (e.g. FBOs or through `glCopyTexImage2D`). In this case, you can pass in an array of size zero.

I added extra overloads which take in a single ByteBuffer argument and automatically generate a single sized `ByteBuffer` array so most of the previous methods will work as before.

### Mipmap Chains
Be sure to generate and load mipmaps all the way down to 1x1 texture level or OpenGL ES will deem the mipmap chain to be "incomplete" and refuse to load up the textures altogether.

![One does not simply generate incomplete mipmaps](http://cdn.memegenerator.net/instances/400x/36093916.jpg)

### Helper method for ETC1 only
Because ETC1 is the type of texture compressed supported consistently across various OpenGL ES 2.0 capable GPUs, I added the helper methods for ETC1 textures only for loading up mipmap chains. I don't have the devices or the resources at this time to test all the other texture compression methods.

Example
-------------
```java
private int[] getMipmapChainResources(int basemipmapLevel, int levels) {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int viewportWidth = display.getWidth(), viewportHeight = display.getHeight();
int[] resourceIds = new int[levels];
// Load high res textures for devices with at least 720p display
if ((viewportWidth >= 1280 || viewportHeight >= 1280) && (mQuality == GraphicQuality.HIGH || mQuality == GraphicQuality.ULTRA)) {
for (int i = 0; i < levels; i++) {
resourceIds[i] = basemipmapLevel + i;
}
} else {
resourceIds = new int[levels - 1];
for (int i = 0; i < levels - 1; i++) {
resourceIds[i] = basemipmapLevel + (i + 1);
}
}
return resourceIds;
}

private void loadTextures() {
// Load up mipmaps from level 0 all the way down to 11 (total 12 levels)
mColorMapTex = mTextureManager.addEtc1Texture(mContext,
getMipmapChainResources(R.raw.colormap_2048_mip_0, 12), TextureType.DIFFUSE);
mSpecMaskTex = mTextureManager.addEtc1Texture(mContext,
getMipmapChainResources(R.raw.specmask_mip_0, 12), TextureType.SPECULAR);
// We don't want mipmapping for this texture
mLensFlareGlareHoopTex = mTextureManager.addEtc1Texture(mContext.getResources().openRawResource(R.raw.lensflare_glare_hoop), null, TextureType.DIFFUSE);
}
```

Let me know if you have any questions or issues.