package org.anddev.andengine.opengl.util;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import org.anddev.andengine.engine.options.RenderOptions;
import org.anddev.andengine.opengl.texture.Texture.PixelFormat;
import org.anddev.andengine.util.Debug;
import android.graphics.Bitmap;
import android.opengl.GLException;
import android.opengl.GLUtils;
import android.os.Build;
/**
* (c) 2010 Nicolas Gramlich
* (c) 2011 Zynga Inc.
*
* @author Nicolas Gramlich
* @since 18:00:43 - 08.03.2010
*/
public class GLHelper {
// ===========================================================
// Constants
// ===========================================================
public static final int BYTES_PER_FLOAT = 4;
public static final int BYTES_PER_PIXEL_RGBA = 4;
private static final boolean IS_LITTLE_ENDIAN = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN);
private static final int[] HARDWARETEXTUREID_CONTAINER = new int[1];
private static final int[] HARDWAREBUFFERID_CONTAINER = new int[1];
// ===========================================================
// Fields
// ===========================================================
private static int sCurrentHardwareBufferID = -1;
private static int sCurrentHardwareTextureID = -1;
private static int sCurrentMatrix = -1;
private static int sCurrentSourceBlendMode = -1;
private static int sCurrentDestinationBlendMode = -1;
private static FastFloatBuffer sCurrentVertexFloatBuffer = null;
private static FastFloatBuffer sCurrentTextureFloatBuffer = null;
private static boolean sEnableDither = true;
private static boolean sEnableLightning = true;
private static boolean sEnableDepthTest = true;
private static boolean sEnableMultisample = true;
private static boolean sEnableScissorTest = false;
private static boolean sEnableBlend = false;
private static boolean sEnableCulling = false;
private static boolean sEnableTextures = false;
private static boolean sEnableTexCoordArray = false;
private static boolean sEnableVertexArray = false;
private static float sLineWidth = 1;
private static float sRed = -1;
private static float sGreen = -1;
private static float sBlue = -1;
private static float sAlpha = -1;
public static boolean EXTENSIONS_VERTEXBUFFEROBJECTS = false;
public static boolean EXTENSIONS_DRAWTEXTURE = false;
public static boolean EXTENSIONS_TEXTURE_NON_POWER_OF_TWO = false;
// ===========================================================
// Methods
// ===========================================================
public static void reset(final GL10 pGL) {
GLHelper.sCurrentHardwareBufferID = -1;
GLHelper.sCurrentHardwareTextureID = -1;
GLHelper.sCurrentMatrix = -1;
GLHelper.sCurrentSourceBlendMode = -1;
GLHelper.sCurrentDestinationBlendMode = -1;
GLHelper.sCurrentVertexFloatBuffer = null;
GLHelper.sCurrentTextureFloatBuffer = null;
GLHelper.enableDither(pGL);
GLHelper.enableLightning(pGL);
GLHelper.enableDepthTest(pGL);
GLHelper.enableMultisample(pGL);
GLHelper.disableBlend(pGL);
GLHelper.disableCulling(pGL);
GLHelper.disableTextures(pGL);
GLHelper.disableTexCoordArray(pGL);
GLHelper.disableVertexArray(pGL);
GLHelper.sLineWidth = 1;
GLHelper.sRed = -1;
GLHelper.sGreen = -1;
GLHelper.sBlue = -1;
GLHelper.sAlpha = -1;
GLHelper.EXTENSIONS_VERTEXBUFFEROBJECTS = false;
GLHelper.EXTENSIONS_DRAWTEXTURE = false;
GLHelper.EXTENSIONS_TEXTURE_NON_POWER_OF_TWO = false;
}
public static void enableExtensions(final GL10 pGL, final RenderOptions pRenderOptions) {
final String version = pGL.glGetString(GL10.GL_VERSION);
final String renderer = pGL.glGetString(GL10.GL_RENDERER);
final String extensions = pGL.glGetString(GL10.GL_EXTENSIONS);
Debug.d("RENDERER: " + renderer);
Debug.d("VERSION: " + version);
Debug.d("EXTENSIONS: " + extensions);
final boolean isOpenGL10 = version.contains("1.0");
final boolean isOpenGL2X = version.contains("2.");
final boolean isSoftwareRenderer = renderer.contains("PixelFlinger");
final boolean isVBOCapable = extensions.contains("_vertex_buffer_object");
final boolean isDrawTextureCapable = extensions.contains("draw_texture");
final boolean isTextureNonPowerOfTwoCapable = extensions.contains("texture_npot");
GLHelper.EXTENSIONS_VERTEXBUFFEROBJECTS = !pRenderOptions.isDisableExtensionVertexBufferObjects() && !isSoftwareRenderer && (isVBOCapable || !isOpenGL10);
GLHelper.EXTENSIONS_DRAWTEXTURE = !pRenderOptions.isDisableExtensionVertexBufferObjects() && (isDrawTextureCapable || !isOpenGL10);
GLHelper.EXTENSIONS_TEXTURE_NON_POWER_OF_TWO = isTextureNonPowerOfTwoCapable || isOpenGL2X;
GLHelper.hackBrokenDevices();
Debug.d("EXTENSIONS_VERXTEXBUFFEROBJECTS = " + GLHelper.EXTENSIONS_VERTEXBUFFEROBJECTS);
Debug.d("EXTENSIONS_DRAWTEXTURE = " + GLHelper.EXTENSIONS_DRAWTEXTURE);
}
private static void hackBrokenDevices() {
if (Build.PRODUCT.contains("morrison")) {
// This is the Motorola Cliq. This device LIES and says it supports
// VBOs, which it actually does not (or, more likely, the extensions string
// is correct and the GL JNI glue is broken).
GLHelper.EXTENSIONS_VERTEXBUFFEROBJECTS = false;
// TODO: if Motorola fixes this, I should switch to using the fingerprint
// (blur/morrison/morrison/morrison:1.5/CUPCAKE/091007:user/ota-rel-keys,release-keys)
// instead of the product name so that newer versions use VBOs
}
}
public static void setColor(final GL10 pGL, final float pRed, final float pGreen, final float pBlue, final float pAlpha) {
if(pAlpha != GLHelper.sAlpha || pRed != GLHelper.sRed || pGreen != GLHelper.sGreen || pBlue != GLHelper.sBlue) {
GLHelper.sAlpha = pAlpha;
GLHelper.sRed = pRed;
GLHelper.sGreen = pGreen;
GLHelper.sBlue = pBlue;
pGL.glColor4f(pRed, pGreen, pBlue, pAlpha);
}
}
public static void enableVertexArray(final GL10 pGL) {
if(!GLHelper.sEnableVertexArray) {
GLHelper.sEnableVertexArray = true;
pGL.glEnableClientState(GL10.GL_VERTEX_ARRAY);
}
}
public static void disableVertexArray(final GL10 pGL) {
if(GLHelper.sEnableVertexArray) {
GLHelper.sEnableVertexArray = false;
pGL.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
}
public static void enableTexCoordArray(final GL10 pGL) {
if(!GLHelper.sEnableTexCoordArray) {
GLHelper.sEnableTexCoordArray = true;
pGL.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
}
public static void disableTexCoordArray(final GL10 pGL) {
if(GLHelper.sEnableTexCoordArray) {
GLHelper.sEnableTexCoordArray = false;
pGL.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
}
public static void enableScissorTest(final GL10 pGL) {
if(!GLHelper.sEnableScissorTest) {
GLHelper.sEnableScissorTest = true;
pGL.glEnable(GL10.GL_SCISSOR_TEST);
}
}
public static void disableScissorTest(final GL10 pGL) {
if(GLHelper.sEnableScissorTest) {
GLHelper.sEnableScissorTest = false;
pGL.glDisable(GL10.GL_SCISSOR_TEST);
}
}
public static void enableBlend(final GL10 pGL) {
if(!GLHelper.sEnableBlend) {
GLHelper.sEnableBlend = true;
pGL.glEnable(GL10.GL_BLEND);
}
}
public static void disableBlend(final GL10 pGL) {
if(GLHelper.sEnableBlend) {
GLHelper.sEnableBlend = false;
pGL.glDisable(GL10.GL_BLEND);
}
}
public static void enableCulling(final GL10 pGL) {
if(!GLHelper.sEnableCulling) {
GLHelper.sEnableCulling = true;
pGL.glEnable(GL10.GL_CULL_FACE);
}
}
public static void disableCulling(final GL10 pGL) {
if(GLHelper.sEnableCulling) {
GLHelper.sEnableCulling = false;
pGL.glDisable(GL10.GL_CULL_FACE);
}
}
public static void enableTextures(final GL10 pGL) {
if(!GLHelper.sEnableTextures) {
GLHelper.sEnableTextures = true;
pGL.glEnable(GL10.GL_TEXTURE_2D);
}
}
public static void disableTextures(final GL10 pGL) {
if(GLHelper.sEnableTextures) {
GLHelper.sEnableTextures = false;
pGL.glDisable(GL10.GL_TEXTURE_2D);
}
}
public static void enableLightning(final GL10 pGL) {
if(!GLHelper.sEnableLightning) {
GLHelper.sEnableLightning = true;
pGL.glEnable(GL10.GL_LIGHTING);
}
}
public static void disableLightning(final GL10 pGL) {
if(GLHelper.sEnableLightning) {
GLHelper.sEnableLightning = false;
pGL.glDisable(GL10.GL_LIGHTING);
}
}
public static void enableDither(final GL10 pGL) {
if(!GLHelper.sEnableDither) {
GLHelper.sEnableDither = true;
pGL.glEnable(GL10.GL_DITHER);
}
}
public static void disableDither(final GL10 pGL) {
if(GLHelper.sEnableDither) {
GLHelper.sEnableDither = false;
pGL.glDisable(GL10.GL_DITHER);
}
}
public static void enableDepthTest(final GL10 pGL) {
if(!GLHelper.sEnableDepthTest) {
GLHelper.sEnableDepthTest = true;
pGL.glEnable(GL10.GL_DEPTH_TEST);
}
}
public static void disableDepthTest(final GL10 pGL) {
if(GLHelper.sEnableDepthTest) {
GLHelper.sEnableDepthTest = false;
pGL.glDisable(GL10.GL_DEPTH_TEST);
}
}
public static void enableMultisample(final GL10 pGL) {
if(!GLHelper.sEnableMultisample) {
GLHelper.sEnableMultisample = true;
pGL.glEnable(GL10.GL_MULTISAMPLE);
}
}
public static void disableMultisample(final GL10 pGL) {
if(GLHelper.sEnableMultisample) {
GLHelper.sEnableMultisample = false;
pGL.glDisable(GL10.GL_MULTISAMPLE);
}
}
public static void bindBuffer(final GL11 pGL11, final int pHardwareBufferID) {
/* Reduce unnecessary buffer switching calls. */
if(GLHelper.sCurrentHardwareBufferID != pHardwareBufferID) {
GLHelper.sCurrentHardwareBufferID = pHardwareBufferID;
pGL11.glBindBuffer(GL11.GL_ARRAY_BUFFER, pHardwareBufferID);
}
}
public static void deleteBuffer(final GL11 pGL11, final int pHardwareBufferID) {
GLHelper.HARDWAREBUFFERID_CONTAINER[0] = pHardwareBufferID;
pGL11.glDeleteBuffers(1, GLHelper.HARDWAREBUFFERID_CONTAINER, 0);
}
/**
* @see {@link GLHelper#forceBindTexture(GL10, int)}
* @param pGL
* @param pHardwareTextureID
*/
public static void bindTexture(final GL10 pGL, final int pHardwareTextureID) {
/* Reduce unnecessary texture switching calls. */
if(GLHelper.sCurrentHardwareTextureID != pHardwareTextureID) {
GLHelper.sCurrentHardwareTextureID = pHardwareTextureID;
pGL.glBindTexture(GL10.GL_TEXTURE_2D, pHardwareTextureID);
}
}
/**
* @see {@link GLHelper#bindTexture(GL10, int)}
* @param pGL
* @param pHardwareTextureID
*/
public static void forceBindTexture(final GL10 pGL, final int pHardwareTextureID) {
GLHelper.sCurrentHardwareTextureID = pHardwareTextureID;
pGL.glBindTexture(GL10.GL_TEXTURE_2D, pHardwareTextureID);
}
public static void deleteTexture(final GL10 pGL, final int pHardwareTextureID) {
GLHelper.HARDWARETEXTUREID_CONTAINER[0] = pHardwareTextureID;
pGL.glDeleteTextures(1, GLHelper.HARDWARETEXTUREID_CONTAINER, 0);
}
public static void texCoordPointer(final GL10 pGL, final FastFloatBuffer pTextureFloatBuffer) {
if(GLHelper.sCurrentTextureFloatBuffer != pTextureFloatBuffer) {
GLHelper.sCurrentTextureFloatBuffer = pTextureFloatBuffer;
pGL.glTexCoordPointer(2, GL10.GL_FLOAT, 0, pTextureFloatBuffer.mByteBuffer);
}
}
public static void texCoordZeroPointer(final GL11 pGL11) {
pGL11.glTexCoordPointer(2, GL10.GL_FLOAT, 0, 0);
}
public static void vertexPointer(final GL10 pGL, final FastFloatBuffer pVertexFloatBuffer) {
if(GLHelper.sCurrentVertexFloatBuffer != pVertexFloatBuffer) {
GLHelper.sCurrentVertexFloatBuffer = pVertexFloatBuffer;
pGL.glVertexPointer(2, GL10.GL_FLOAT, 0, pVertexFloatBuffer.mByteBuffer);
}
}
public static void vertexZeroPointer(final GL11 pGL11) {
pGL11.glVertexPointer(2, GL10.GL_FLOAT, 0, 0);
}
public static void blendFunction(final GL10 pGL, final int pSourceBlendMode, final int pDestinationBlendMode) {
if(GLHelper.sCurrentSourceBlendMode != pSourceBlendMode || GLHelper.sCurrentDestinationBlendMode != pDestinationBlendMode) {
GLHelper.sCurrentSourceBlendMode = pSourceBlendMode;
GLHelper.sCurrentDestinationBlendMode = pDestinationBlendMode;
pGL.glBlendFunc(pSourceBlendMode, pDestinationBlendMode);
}
}
public static void lineWidth(final GL10 pGL, final float pLineWidth) {
if(GLHelper.sLineWidth != pLineWidth) {
GLHelper.sLineWidth = pLineWidth;
pGL.glLineWidth(pLineWidth);
}
}
public static void switchToModelViewMatrix(final GL10 pGL) {
/* Reduce unnecessary matrix switching calls. */
if(GLHelper.sCurrentMatrix != GL10.GL_MODELVIEW) {
GLHelper.sCurrentMatrix = GL10.GL_MODELVIEW;
pGL.glMatrixMode(GL10.GL_MODELVIEW);
}
}
public static void switchToProjectionMatrix(final GL10 pGL) {
/* Reduce unnecessary matrix switching calls. */
if(GLHelper.sCurrentMatrix != GL10.GL_PROJECTION) {
GLHelper.sCurrentMatrix = GL10.GL_PROJECTION;
pGL.glMatrixMode(GL10.GL_PROJECTION);
}
}
public static void setProjectionIdentityMatrix(final GL10 pGL) {
GLHelper.switchToProjectionMatrix(pGL);
pGL.glLoadIdentity();
}
public static void setModelViewIdentityMatrix(final GL10 pGL) {
GLHelper.switchToModelViewMatrix(pGL);
pGL.glLoadIdentity();
}
public static void setShadeModelFlat(final GL10 pGL) {
pGL.glShadeModel(GL10.GL_FLAT);
}
public static void setPerspectiveCorrectionHintFastest(final GL10 pGL) {
pGL.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
}
public static void bufferData(final GL11 pGL11, final ByteBuffer pByteBuffer, final int pUsage) {
pGL11.glBufferData(GL11.GL_ARRAY_BUFFER, pByteBuffer.capacity(), pByteBuffer, pUsage);
}
/**
* <b>Note:</b> does not pre-multiply the alpha channel!</br>
* Except that difference, same as: {@link GLUtils#texSubImage2D(int, int, int, int, Bitmap, int, int)}</br>
* </br>
* See topic: '<a href="http://groups.google.com/group/android-developers/browse_thread/thread/baa6c33e63f82fca">PNG loading that doesn't premultiply alpha?</a>'
* @param pBorder
*/
public static void glTexImage2D(final GL10 pGL, final int pTarget, final int pLevel, final Bitmap pBitmap, final int pBorder, final PixelFormat pPixelFormat) {
final Buffer pixelBuffer = GLHelper.getPixels(pBitmap, pPixelFormat);
pGL.glTexImage2D(pTarget, pLevel, pPixelFormat.getGLFormat(), pBitmap.getWidth(), pBitmap.getHeight(), pBorder, pPixelFormat.getGLFormat(), pPixelFormat.getGLType(), pixelBuffer);
}
/**
* <b>Note:</b> does not pre-multiply the alpha channel!</br>
* Except that difference, same as: {@link GLUtils#texSubImage2D(int, int, int, int, Bitmap, int, int)}</br>
* </br>
* See topic: '<a href="http://groups.google.com/group/android-developers/browse_thread/thread/baa6c33e63f82fca">PNG loading that doesn't premultiply alpha?</a>'
*/
public static void glTexSubImage2D(final GL10 pGL, final int pTarget, final int pLevel, final int pXOffset, final int pYOffset, final Bitmap pBitmap, final PixelFormat pPixelFormat) {
final Buffer pixelBuffer = GLHelper.getPixels(pBitmap, pPixelFormat);
pGL.glTexSubImage2D(pTarget, pLevel, pXOffset, pYOffset, pBitmap.getWidth(), pBitmap.getHeight(), pPixelFormat.getGLFormat(), pPixelFormat.getGLType(), pixelBuffer);
}
private static Buffer getPixels(final Bitmap pBitmap, final PixelFormat pPixelFormat) {
final int[] pixelsARGB_8888 = GLHelper.getPixelsARGB_8888(pBitmap);
switch(pPixelFormat) {
case RGB_565:
return ByteBuffer.wrap(GLHelper.convertARGB_8888toRGB_565(pixelsARGB_8888));
case RGBA_8888:
return IntBuffer.wrap(GLHelper.convertARGB_8888toRGBA_8888(pixelsARGB_8888));
case RGBA_4444:
return ByteBuffer.wrap(GLHelper.convertARGB_8888toARGB_4444(pixelsARGB_8888));
case A_8:
return ByteBuffer.wrap(GLHelper.convertARGB_8888toA_8(pixelsARGB_8888));
default:
throw new IllegalArgumentException("Unexpected " + PixelFormat.class.getSimpleName() + ": '" + pPixelFormat + "'.");
}
}
private static int[] convertARGB_8888toRGBA_8888(final int[] pPixelsARGB_8888) {
if(GLHelper.IS_LITTLE_ENDIAN) {
for(int i = pPixelsARGB_8888.length - 1; i >= 0; i--) {
final int pixel = pPixelsARGB_8888[i];
/* ARGB to ABGR */
pPixelsARGB_8888[i] = pixel & 0xFF00FF00 | (pixel & 0x000000FF) << 16 | (pixel & 0x00FF0000) >> 16;
}
} else {
for(int i = pPixelsARGB_8888.length - 1; i >= 0; i--) {
final int pixel = pPixelsARGB_8888[i];
/* ARGB to RGBA */
pPixelsARGB_8888[i] = (pixel & 0x00FFFFFF) << 8 | (pixel & 0xFF000000) >> 24;
}
}
return pPixelsARGB_8888;
}
private static byte[] convertARGB_8888toRGB_565(final int[] pPixelsARGB_8888) {
final byte[] pixelsRGB_565 = new byte[pPixelsARGB_8888.length * 2];
if(GLHelper.IS_LITTLE_ENDIAN) {
for(int i = pPixelsARGB_8888.length - 1, j = pixelsRGB_565.length - 1; i >= 0; i--) {
final int pixel = pPixelsARGB_8888[i];
final int red = ((pixel >> 16) & 0xFF);
final int green = ((pixel >> 8) & 0xFF);
final int blue = ((pixel) & 0xFF);
/* Byte1: [R1 R2 R3 R4 R5 G1 G2 G3]
* Byte2: [G4 G5 G6 B1 B2 B3 B4 B5] */
pixelsRGB_565[j--] = (byte)((red & 0xF8) | (green >> 5));
pixelsRGB_565[j--] = (byte)(((green << 3) & 0xE0) | (blue >> 3));
}
} else {
for(int i = pPixelsARGB_8888.length - 1, j = pixelsRGB_565.length - 1; i >= 0; i--) {
final int pixel = pPixelsARGB_8888[i];
final int red = ((pixel >> 16) & 0xFF);
final int green = ((pixel >> 8) & 0xFF);
final int blue = ((pixel) & 0xFF);
/* Byte2: [G4 G5 G6 B1 B2 B3 B4 B5]
* Byte1: [R1 R2 R3 R4 R5 G1 G2 G3]*/
pixelsRGB_565[j--] = (byte)(((green << 3) & 0xE0) | (blue >> 3));
pixelsRGB_565[j--] = (byte)((red & 0xF8) | (green >> 5));
}
}
return pixelsRGB_565;
}
private static byte[] convertARGB_8888toARGB_4444(final int[] pPixelsARGB_8888) {
final byte[] pixelsARGB_4444 = new byte[pPixelsARGB_8888.length * 2];
if(GLHelper.IS_LITTLE_ENDIAN) {
for(int i = pPixelsARGB_8888.length - 1, j = pixelsARGB_4444.length - 1; i >= 0; i--) {
final int pixel = pPixelsARGB_8888[i];
final int alpha = ((pixel >> 28) & 0x0F);
final int red = ((pixel >> 16) & 0xF0);
final int green = ((pixel >> 8) & 0xF0);
final int blue = ((pixel) & 0x0F);
/* Byte1: [A1 A2 A3 A4 R1 R2 R3 R4]
* Byte2: [G1 G2 G3 G4 G2 G2 G3 G4] */
pixelsARGB_4444[j--] = (byte)(alpha | red);
pixelsARGB_4444[j--] = (byte)(green | blue);
}
} else {
for(int i = pPixelsARGB_8888.length - 1, j = pixelsARGB_4444.length - 1; i >= 0; i--) {
final int pixel = pPixelsARGB_8888[i];
final int alpha = ((pixel >> 28) & 0x0F);
final int red = ((pixel >> 16) & 0xF0);
final int green = ((pixel >> 8) & 0xF0);
final int blue = ((pixel) & 0x0F);
/* Byte2: [G1 G2 G3 G4 G2 G2 G3 G4]
* Byte1: [A1 A2 A3 A4 R1 R2 R3 R4] */
pixelsARGB_4444[j--] = (byte)(green | blue);
pixelsARGB_4444[j--] = (byte)(alpha | red);
}
}
return pixelsARGB_4444;
}
private static byte[] convertARGB_8888toA_8(final int[] pPixelsARGB_8888) {
final byte[] pixelsA_8 = new byte[pPixelsARGB_8888.length];
if(GLHelper.IS_LITTLE_ENDIAN) {
for(int i = pPixelsARGB_8888.length - 1; i >= 0; i--) {
pixelsA_8[i] = (byte) (pPixelsARGB_8888[i] >> 24);
}
} else {
for(int i = pPixelsARGB_8888.length - 1; i >= 0; i--) {
pixelsA_8[i] = (byte) (pPixelsARGB_8888[i] & 0xFF);
}
}
return pixelsA_8;
}
public static int[] getPixelsARGB_8888(final Bitmap pBitmap) {
final int w = pBitmap.getWidth();
final int h = pBitmap.getHeight();
final int[] pixelsARGB_8888 = new int[w * h];
pBitmap.getPixels(pixelsARGB_8888, 0, w, 0, 0, w, h);
return pixelsARGB_8888;
}
public static void checkGLError(final GL10 pGL) throws GLException { // TODO Use more often!
final int err = pGL.glGetError();
if (err != GL10.GL_NO_ERROR) {
throw new GLException(err);
}
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}