Android Tutorial - 2D Graphics : OpenGL

 Wrapper activity demonstrating the use of GLSurfaceView, a view that uses OpenGL drawing into a dedicated surface.

package app.test;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

/**
 * A vertex shaded cube.
 */
class Cube {
  public Cube() {
    int one = 0x10000;
    int vertices[] = { -one, -one, -one, one, -one, -one, one, one, -one,
        -one, one, -one, -one, -one, one, one, -one, one, one, one,
        one, -one, one, one, };

    int colors[] = { 0, 0, 0, one, one, 0, 0, one, one, one, 0, one, 0,
        one, 0, one, 0, 0, one, one, one, 0, one, one, one, one, one,
        one, 0, one, one, one, };

    byte indices[] = { 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, 7, 2, 7,
        3, 3, 7, 4, 3, 4, 0, 4, 7, 6, 4, 6, 5, 3, 0, 1, 3, 1, 2 };

    // Buffers to be passed to gl*Pointer() functions
    // must be direct, i.e., they must be placed on the
    // native heap where the garbage collector cannot
    // move them.
    //
    // Buffers with multi-byte datatypes (e.g., short, int, float)
    // must have their byte order set to native order

    ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
    vbb.order(ByteOrder.nativeOrder());
    mVertexBuffer = vbb.asIntBuffer();
    mVertexBuffer.put(vertices);
    mVertexBuffer.position(0);

    ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
    cbb.order(ByteOrder.nativeOrder());
    mColorBuffer = cbb.asIntBuffer();
    mColorBuffer.put(colors);
    mColorBuffer.position(0);

    mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
    mIndexBuffer.put(indices);
    mIndexBuffer.position(0);
  }

  public void draw(GL10 gl) {
    gl.glFrontFace(GL10.GL_CW);
    gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
    gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
    gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE,
        mIndexBuffer);
  }

  private IntBuffer mVertexBuffer;
  private IntBuffer mColorBuffer;
  private ByteBuffer mIndexBuffer;
}

/**
 * Render a pair of tumbling cubes.
 */

class CubeRenderer implements GLSurfaceView.Renderer {
  public CubeRenderer(boolean useTranslucentBackground) {
    mTranslucentBackground = useTranslucentBackground;
    mCube = new Cube();
  }

  public void onDrawFrame(GL10 gl) {
    /*
     * Usually, the first thing one might want to do is to clear the screen.
     * The most efficient way of doing this is to use glClear().
     */

    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

    /*
     * Now we're ready to draw some 3D objects
     */

    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();
    gl.glTranslatef(0, 0, -3.0f);
    gl.glRotatef(mAngle, 0, 1, 0);
    gl.glRotatef(mAngle * 0.25f, 1, 0, 0);

    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

    mCube.draw(gl);

    gl.glRotatef(mAngle * 2.0f, 0, 1, 1);
    gl.glTranslatef(0.5f, 0.5f, 0.5f);

    mCube.draw(gl);

    mAngle += 1.2f;
  }

  public void onSurfaceChanged(GL10 gl, int width, int height) {
    gl.glViewport(0, 0, width, height);

    /*
     * Set our projection matrix. This doesn't have to be done each time we
     * draw, but usually a new projection needs to be set when the viewport
     * is resized.
     */

    float ratio = (float) width / height;
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadIdentity();
    gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
  }

  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    /*
     * By default, OpenGL enables features that improve quality but reduce
     * performance. One might want to tweak that especially on software
     * renderer.
     */
    gl.glDisable(GL10.GL_DITHER);

    /*
     * Some one-time OpenGL initialization can be made here probably based
     * on features of this particular context
     */
    gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);

    if (mTranslucentBackground) {
      gl.glClearColor(0, 0, 0, 0);
    } else {
      gl.glClearColor(1, 1, 1, 1);
    }
    gl.glEnable(GL10.GL_CULL_FACE);
    gl.glShadeModel(GL10.GL_SMOOTH);
    gl.glEnable(GL10.GL_DEPTH_TEST);
  }

  private boolean mTranslucentBackground;
  private Cube mCube;
  private float mAngle;
}

/**
 * Wrapper activity demonstrating the use of {@link GLSurfaceView}, a view that
 * uses OpenGL drawing into a dedicated surface.
 */
public class Test extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Create our Preview view and set it as the content of our
    // Activity
    mGLSurfaceView = new GLSurfaceView(this);
    mGLSurfaceView.setRenderer(new CubeRenderer(false));
    setContentView(mGLSurfaceView);
  }

  @Override
  protected void onResume() {
    // Ideally a game should implement onResume() and onPause()
    // to take appropriate action when the activity looses focus
    super.onResume();
    mGLSurfaceView.onResume();
  }

  @Override
  protected void onPause() {
    // Ideally a game should implement onResume() and onPause()
    // to take appropriate action when the activity looses focus
    super.onPause();
    mGLSurfaceView.onPause();
  }

  private GLSurfaceView mGLSurfaceView;
}

OpenGL Utils

   
//package com.akjava.lib.android.opengl;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;
//import javax.swing.text.html.Option;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.opengl.GLUtils;
import android.util.Log;

public class OpenGLUtils {

  public static FloatBuffer allocateFloatBuffer(int capacity){
    ByteBuffer vbb = ByteBuffer.allocateDirect(capacity);
        vbb.order(ByteOrder.nativeOrder());
        return vbb.asFloatBuffer();
  }
  
  public static IntBuffer allocateInttBuffer(int capacity){
    ByteBuffer vbb = ByteBuffer.allocateDirect(capacity);
        vbb.order(ByteOrder.nativeOrder());
        return vbb.asIntBuffer();
  }
  
  public static ShortBuffer allocateShortBuffer(int capacity){
    ByteBuffer vbb = ByteBuffer.allocateDirect(capacity);
        vbb.order(ByteOrder.nativeOrder());
        return vbb.asShortBuffer();
  }
  
  public static void addVertex3f(FloatBuffer buffer,float x,float y,float z){
    buffer.put(x);
    buffer.put(y);
    buffer.put(z);
  }
  
  public static void addIndex(ShortBuffer buffer,int index1,int index2,int index3){
    buffer.put((short) index1);
    buffer.put((short) index2);
    buffer.put((short) index3);
  }
  
  public static void addCoord2f(FloatBuffer buffer,float x,float y){
    buffer.put(x);
    buffer.put(y);
  }
  
  public static void addColorf(FloatBuffer buffer,float r,float g,float b,float a){
    buffer.put(r);
    buffer.put(g);
    buffer.put(b);
    buffer.put(a);
  }

  public static FloatBuffer toFloatBufferPositionZero(float[] values) {
    ByteBuffer vbb = ByteBuffer.allocateDirect(values.length*4);
        vbb.order(ByteOrder.nativeOrder());
        FloatBuffer buffer=vbb.asFloatBuffer();
        buffer.put(values);
        buffer.position(0);
    return buffer;
  }
  
  public static ShortBuffer toShortBuffer(short[] values) {
    ByteBuffer vbb = ByteBuffer.allocateDirect(values.length*2);
        vbb.order(ByteOrder.nativeOrder());
        ShortBuffer buffer=vbb.asShortBuffer();
        buffer.put(values);
        buffer.position(0);
    return buffer;
  }
  
  
  public static Bitmap loadBitmap(Context mContext,int id){
    Options opt=new Options();
    Bitmap bitmap= BitmapFactory.decodeResource(mContext.getResources(), id,opt);  
    
    System.out.println(bitmap.getConfig());
    if(!sizeCheck(bitmap)){
      throw new RuntimeException("width or height not 2x size,it make invalid error on OpenGL:"+id);
    }
    return bitmap;
  }
  private static boolean sizeCheck(Bitmap bitmap){
    boolean ret=true;
    int t=2;
    int w=bitmap.getWidth();
    
    while(w!=t){
      if(w%t==1){
        Log.e("glutils w=",w+","+t);
        return false;
      }
      t*=2;
      if(t>w){
        return false;
      }
    }
    
    t=2;
    int h=bitmap.getHeight();
    while(h!=t){
      if(h%t==1){
        Log.e("glutils h=",h+","+t);
        return false;
      }
      t*=2;
      if(t>h){
        return false;
      }
    }
    
    return ret;
  }
  
  /**
   * this is for  resized
   * GLU.gluOrtho2D (gl,-1f, 1.0f, -1f, 1.0f);
   * @param x
   * @param y
   * @param screenWidth
   * @param screenHeight
   * @return
   */
  public static float[] toOpenGLCordinate(float x,float y,int screenWidth,int screenHeight){
    float sx=((float)x/screenWidth)*2-1.0f;
      float sy=((float)y/screenHeight)*2-1.0f;
      sy*=-1;
      return new float[]{sx,sy};
  }
  
  /*
   * y should *=-1;
   */
  public static float toOpenGLCordinate(float x,int screenWidth){
    Log.i("myapp","x="+x+","+screenWidth);
    float sx=((float)x/screenWidth)*2-1.0f;
      return sx;
  }
  
  
  public static float realToVirtialValue(int x,int real,float virtial){
      return virtial/real*x;
  }
  
  public static int virtualToRealvalue(float x,int real,float virtial){
    //using DecimalFormat make gc
      return (int) ((float)x/(virtial/real));
  }
  
  
  private static FloatBuffer mTextureBuffer;
  private static FloatBuffer mFVertexBuffer;
  private static ShortBuffer mIndexBuffer;
  
  public static FloatBuffer getBoxTriangleTextureBuffer(){
    if(mTextureBuffer==null){
      mTextureBuffer=OpenGLUtils.allocateFloatBuffer(4*6*2);
      OpenGLUtils.addCoord2f(mTextureBuffer,0.0f, 1.0f); 
      OpenGLUtils.addCoord2f(mTextureBuffer,0.0f, 0.0f); 
      OpenGLUtils.addCoord2f(mTextureBuffer,1.0f, 1.0f); 
      OpenGLUtils.addCoord2f(mTextureBuffer,1.0f, 1.0f); 
        OpenGLUtils.addCoord2f(mTextureBuffer,1.0f, 0.0f); 
        OpenGLUtils.addCoord2f(mTextureBuffer,0.0f, 0.0f); 
        mTextureBuffer.position(0);
    }
    return mTextureBuffer;
  }
  
  
  public static FloatBuffer getBoxTriangleFlipVerticalTextureBuffer(){
    if(mTextureBuffer==null){
      mTextureBuffer=OpenGLUtils.allocateFloatBuffer(4*6*2);
      OpenGLUtils.addCoord2f(mTextureBuffer,0.0f, 0.0f); 
      OpenGLUtils.addCoord2f(mTextureBuffer,0.0f, 1.0f); 
      OpenGLUtils.addCoord2f(mTextureBuffer,1.0f, 0.0f); 
      OpenGLUtils.addCoord2f(mTextureBuffer,1.0f, 0.0f); 
        OpenGLUtils.addCoord2f(mTextureBuffer,1.0f, 1.0f); 
        OpenGLUtils.addCoord2f(mTextureBuffer,0.0f, 1.0f); 
        mTextureBuffer.position(0);
    }
    return mTextureBuffer;
  }
  
  public static FloatBuffer getBoxTextureBuffer(){
    if(mTextureBuffer==null){
      mTextureBuffer=OpenGLUtils.allocateFloatBuffer(4*4*2);
      OpenGLUtils.addCoord2f(mTextureBuffer,0.0f, 0.0f); 
      OpenGLUtils.addCoord2f(mTextureBuffer,0.0f, 1.0f); 
      OpenGLUtils.addCoord2f(mTextureBuffer,1.0f, 0.0f); 
        OpenGLUtils.addCoord2f(mTextureBuffer,1.0f, 1.0f); 
        mTextureBuffer.position(0);
    }
    return mTextureBuffer;
  }
  public static ShortBuffer getBoxIndexBuffer(){
    if(mIndexBuffer==null){
      mIndexBuffer=OpenGLUtils.allocateShortBuffer(6*2);
        mIndexBuffer.put((short)0);
        mIndexBuffer.put((short)1);
        mIndexBuffer.put((short)2);
        mIndexBuffer.put((short)2);
        mIndexBuffer.put((short)3);
        mIndexBuffer.put((short)1);
        mIndexBuffer.position(0);
    }
    return mIndexBuffer;
  }
  public static FloatBuffer getBoxVertexBuffer(){
    if(mFVertexBuffer==null){
      mFVertexBuffer=OpenGLUtils.allocateFloatBuffer(4*4*3);
        OpenGLUtils.addVertex3f(mFVertexBuffer, -1,-1f,0);
          OpenGLUtils.addVertex3f(mFVertexBuffer, -1,1f,0);
          
          
          OpenGLUtils.addVertex3f(mFVertexBuffer, 1,-1f,0);
          OpenGLUtils.addVertex3f(mFVertexBuffer, 1,1f,0);  
          mFVertexBuffer.position(0);
          
    }
    return mFVertexBuffer;
  }
  
  
  public static FloatBuffer getBoxTriangleVertexBuffer(){
    if(mFVertexBuffer==null){
      mFVertexBuffer=OpenGLUtils.allocateFloatBuffer(4*6*3);
      OpenGLUtils.addVertex3f(mFVertexBuffer, -1,-1f,0);
          OpenGLUtils.addVertex3f(mFVertexBuffer, -1,1f,0);
          OpenGLUtils.addVertex3f(mFVertexBuffer, 1,-1f,0);
         
          OpenGLUtils.addVertex3f(mFVertexBuffer, 1,-1f,0);
          OpenGLUtils.addVertex3f(mFVertexBuffer, 1,1f,0); 
          OpenGLUtils.addVertex3f(mFVertexBuffer, -1,1f,0);
          
          mFVertexBuffer.position(0);
          
    }
    return mFVertexBuffer;
  }
  
  //TODO ??????????????
  /*
   * bitmap???????????
   */
  public static void bindTextureImage(GL10 gl,final int id,final Bitmap bitmap){
       gl.glBindTexture(GL10.GL_TEXTURE_2D,  id);
         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
       gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
       gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
       gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
      GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
      bitmap.recycle();
  }
  
}

An OpenGL ES renderer based on the GLSurfaceView rendering framework.

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Semaphore;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGL11;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * An OpenGL ES renderer based on the GLSurfaceView rendering framework. This
 * class is responsible for drawing a list of renderables to the screen every
 * frame. It also manages loading of textures and (when VBOs are used) the
 * allocation of vertex buffer objects.
 */
public class SimpleGLRenderer implements GLSurfaceView.Renderer {
  // Specifies the format our textures should be converted to upon load.
  private static BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options();
  // An array of things to draw every frame.
  private GLSprite[] mSprites;
  // Pre-allocated arrays to use at runtime so that allocation during the
  // test can be avoided.
  private int[] mTextureNameWorkspace;
  private int[] mCropWorkspace;
  // A reference to the application context.
  private Context mContext;

  // Determines the use of vertex arrays.

  // Determines the use of vertex buffer objects.

  public SimpleGLRenderer(Context context) {
    // Pre-allocate and store these objects so we can use them at runtime
    // without allocating memory mid-frame.
    mTextureNameWorkspace = new int[1];
    mCropWorkspace = new int[4];

    // Set our bitmaps to 16-bit, 565 format.
    sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;

    mContext = context;
  }

  public int[] getConfigSpec() {
    // We don't need a depth buffer, and don't care about our
    // color depth.
    int[] configSpec = { EGL10.EGL_DEPTH_SIZE, 0, EGL10.EGL_NONE };
    return configSpec;
  }

  public void setSprites(GLSprite[] sprites) {
    mSprites = sprites;
  }

  /**
   * Changes the vertex mode used for drawing.
   * 
   * @param useVerts
   *            Specifies whether to use a vertex array. If false, the
   *            DrawTexture extension is used.
   * @param useHardwareBuffers
   *            Specifies whether to store vertex arrays in main memory or on
   *            the graphics card. Ignored if useVerts is false.
   */

  /** Draws the sprites. */
  public void drawFrame(GL10 gl) {
    if (mSprites != null) {

      gl.glMatrixMode(GL10.GL_MODELVIEW);

      for (int x = 0; x < mSprites.length; x++) {
        mSprites[x].draw(gl);
      }

    }
  }

  /* Called when the size of the window changes. */
  public void sizeChanged(GL10 gl, int width, int height) {
    gl.glViewport(0, 0, width, height);

    /*
     * Set our projection matrix. This doesn't have to be done each time we
     * draw, but usually a new projection needs to be set when the viewport
     * is resized.
     */
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadIdentity();
    gl.glOrthof(0.0f, width, 0.0f, height, 0.0f, 1.0f);

    gl.glShadeModel(GL10.GL_FLAT);
    gl.glEnable(GL10.GL_BLEND);
    gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
    gl.glColor4x(0x10000, 0x10000, 0x10000, 0x10000);
    gl.glEnable(GL10.GL_TEXTURE_2D);
  }

  /**
   * Called whenever the surface is created. This happens at startup, and may
   * be called again at runtime if the device context is lost (the screen goes
   * to sleep, etc). This function must fill the contents of vram with texture
   * data and (when using VBOs) hardware vertex arrays.
   */
  public void surfaceCreated(GL10 gl) {
    /*
     * Some one-time OpenGL initialization can be made here probably based
     * on features of this particular context
     */
    gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);

    gl.glClearColor(0.5f, 0.5f, 0.5f, 1);
    gl.glShadeModel(GL10.GL_FLAT);
    gl.glDisable(GL10.GL_DEPTH_TEST);
    gl.glEnable(GL10.GL_TEXTURE_2D);
    /*
     * By default, OpenGL enables features that improve quality but reduce
     * performance. One might want to tweak that especially on software
     * renderer.
     */
    gl.glDisable(GL10.GL_DITHER);
    gl.glDisable(GL10.GL_LIGHTING);

    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

    if (mSprites != null) {

      // If we are using hardware buffers and the screen lost context
      // then the buffer indexes that we recorded previously are now
      // invalid. Forget them here and recreate them below.

      // Load our texture and set its texture name on all sprites.

      // To keep this sample simple we will assume that sprites that share
      // the same texture are grouped together in our sprite list. A real
      // app would probably have another level of texture management,
      // like a texture hash.

      int lastLoadedResource = -1;
      int lastTextureId = -1;

      for (int x = 0; x < mSprites.length; x++) {
        int resource = mSprites[x].getResourceId();
        if (resource != lastLoadedResource) {
          lastTextureId = loadBitmap(mContext, gl, resource);
          lastLoadedResource = resource;
        }
        mSprites[x].setTextureName(lastTextureId);
        // mSprites[x].getGrid().generateHardwareBuffers(gl);

      }
    }
  }

  /**
   * Called when the rendering thread shuts down. This is a good place to
   * release OpenGL ES resources.
   * 
   * @param gl
   */
  public void shutdown(GL10 gl) {
    if (mSprites != null) {

      int lastFreedResource = -1;
      int[] textureToDelete = new int[1];

      for (int x = 0; x < mSprites.length; x++) {
        int resource = mSprites[x].getResourceId();
        if (resource != lastFreedResource) {
          textureToDelete[0] = mSprites[x].getTextureName();
          gl.glDeleteTextures(1, textureToDelete, 0);
          mSprites[x].setTextureName(0);
        }
      }
    }
  }

  /**
   * Loads a bitmap into OpenGL and sets up the common parameters for 2D
   * texture maps.
   */
  protected int loadBitmap(Context context, GL10 gl, int resourceId) {
    int textureName = -1;
    if (context != null && gl != null) {
      gl.glGenTextures(1, mTextureNameWorkspace, 0);

      textureName = mTextureNameWorkspace[0];
      gl.glBindTexture(GL10.GL_TEXTURE_2D, textureName);

      gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
          GL10.GL_NEAREST);
      gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
          GL10.GL_LINEAR);

      gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
          GL10.GL_CLAMP_TO_EDGE);
      gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
          GL10.GL_CLAMP_TO_EDGE);

      gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
          GL10.GL_REPLACE);

      InputStream is = context.getResources().openRawResource(resourceId);
      Bitmap bitmap;
      try {
        bitmap = BitmapFactory.decodeStream(is, null, sBitmapOptions);
      } finally {
        try {
          is.close();
        } catch (IOException e) {
          // Ignore.
        }
      }

      GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

      mCropWorkspace[0] = 0;
      mCropWorkspace[1] = bitmap.getHeight();
      mCropWorkspace[2] = bitmap.getWidth();
      mCropWorkspace[3] = -bitmap.getHeight();

      bitmap.recycle();

      ((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D,
          GL11Ext.GL_TEXTURE_CROP_RECT_OES, mCropWorkspace, 0);

      int error = gl.glGetError();
      if (error != GL10.GL_NO_ERROR) {
        Log.e("SpriteMethodTest", "Texture Load GLError: " + error);
      }

    }

    return textureName;
  }

}

/**
 * Base class defining the core set of information necessary to render (and move
 * an object on the screen. This is an abstract type and must be derived to add
 * methods to actually draw (see CanvasSprite and GLSprite).
 */
abstract class Renderable {
  // Position.
  public float x;
  public float y;
  public float z;

  // Velocity.
  public float velocityX;
  public float velocityY;
  public float velocityZ;

  // Size.
  public float width;
  public float height;
}

/**
 * This is the OpenGL ES version of a sprite. It is more complicated than the
 * CanvasSprite class because it can be used in more than one way. This class
 * can draw using a grid of verts, a grid of verts stored in VBO objects, or
 * using the DrawTexture extension.
 */
class GLSprite extends Renderable {
  // The OpenGL ES texture handle to draw.
  private int mTextureName;
  // The id of the original resource that mTextureName is based on.
  private int mResourceId;

  // If drawing with verts or VBO verts, the grid object defining those verts.

  public GLSprite(int resourceId) {
    super();
    mResourceId = resourceId;
  }

  public void setTextureName(int name) {
    mTextureName = name;
  }

  public int getTextureName() {
    return mTextureName;
  }

  public void setResourceId(int id) {
    mResourceId = id;
  }

  public int getResourceId() {
    return mResourceId;
  }

  public void draw(GL10 gl) {
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureName);

    // Draw using the DrawTexture extension.
    ((GL11Ext) gl).glDrawTexfOES(x, y, z, width, height);

  }
}

/**
 * An implementation of SurfaceView that uses the dedicated surface for
 * displaying an OpenGL animation. This allows the animation to run in a
 * separate thread, without requiring that it be driven by the update mechanism
 * of the view hierarchy.
 * 
 * The application-specific rendering code is delegated to a GLView.Renderer
 * instance.
 */
class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
  public GLSurfaceView(Context context) {
    super(context);
    init();
  }

  public GLSurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  private void init() {
    // Install a SurfaceHolder.Callback so we get notified when the
    // underlying surface is created and destroyed
    mHolder = getHolder();
    mHolder.addCallback(this);
    mHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
  }

  public SurfaceHolder getSurfaceHolder() {
    return mHolder;
  }

  public void setGLWrapper(GLWrapper glWrapper) {
    mGLWrapper = glWrapper;
  }

  public void setRenderer(Renderer renderer) {
    mGLThread = new GLThread(renderer);
    mGLThread.start();
  }

  public void surfaceCreated(SurfaceHolder holder) {
    mGLThread.surfaceCreated();
  }

  public void surfaceDestroyed(SurfaceHolder holder) {
    // Surface will be destroyed when we return
    mGLThread.surfaceDestroyed();
  }

  public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    // Surface size or format has changed. This should not happen in this
    // example.
    mGLThread.onWindowResize(w, h);
  }

  /**
   * Inform the view that the activity is paused.
   */
  public void onPause() {
    mGLThread.onPause();
  }

  /**
   * Inform the view that the activity is resumed.
   */
  public void onResume() {
    mGLThread.onResume();
  }

  /**
   * Inform the view that the window focus has changed.
   */
  @Override
  public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    mGLThread.onWindowFocusChanged(hasFocus);
  }

  /**
   * Set an "event" to be run on the GL rendering thread.
   * 
   * @param r
   *            the runnable to be run on the GL rendering thread.
   */
  public void setEvent(Runnable r) {
    mGLThread.setEvent(r);
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    mGLThread.requestExitAndWait();
  }

  // ----------------------------------------------------------------------

  public interface GLWrapper {
    GL wrap(GL gl);
  }

  // ----------------------------------------------------------------------

  /**
   * A generic renderer interface.
   */
  public interface Renderer {
    /**
     * @return the EGL configuration specification desired by the renderer.
     */
    int[] getConfigSpec();

    /**
     * Surface created. Called when the surface is created. Called when the
     * application starts, and whenever the GPU is reinitialized. This will
     * typically happen when the device awakes after going to sleep. Set
     * your textures here.
     */
    void surfaceCreated(GL10 gl);

    /**
     * Called when the rendering thread is about to shut down. This is a
     * good place to release OpenGL ES resources (textures, buffers, etc).
     * 
     * @param gl
     */
    void shutdown(GL10 gl);

    /**
     * Surface changed size. Called after the surface is created and
     * whenever the OpenGL ES surface size changes. Set your viewport here.
     * 
     * @param gl
     * @param width
     * @param height
     */
    void sizeChanged(GL10 gl, int width, int height);

    /**
     * Draw the current frame.
     * 
     * @param gl
     */
    void drawFrame(GL10 gl);
  }

  /**
   * An EGL helper class.
   */

  private class EglHelper {
    public EglHelper() {

    }

    /**
     * Initialize EGL for a given configuration spec.
     * 
     * @param configSpec
     */
    public void start(int[] configSpec) {
      /*
       * Get an EGL instance
       */
      mEgl = (EGL10) EGLContext.getEGL();

      /*
       * Get to the default display.
       */
      mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

      /*
       * We can now initialize EGL for that display
       */
      int[] version = new int[2];
      mEgl.eglInitialize(mEglDisplay, version);

      EGLConfig[] configs = new EGLConfig[1];
      int[] num_config = new int[1];
      mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1,
          num_config);
      mEglConfig = configs[0];

      /*
       * Create an OpenGL ES context. This must be done only once, an
       * OpenGL context is a somewhat heavy object.
       */
      mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig,
          EGL10.EGL_NO_CONTEXT, null);

      mEglSurface = null;
    }

    /*
     * Create and return an OpenGL surface
     */
    public GL createSurface(SurfaceHolder holder) {
      /*
       * The window size has changed, so we need to create a new surface.
       */
      if (mEglSurface != null) {

        /*
         * Unbind and destroy the old EGL surface, if there is one.
         */
        mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
            EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
        mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
      }

      /*
       * Create an EGL surface we can render into.
       */
      mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig,
          holder, null);

      /*
       * Before we can issue GL commands, we need to make sure the context
       * is current and bound to a surface.
       */
      mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
          mEglContext);

      GL gl = mEglContext.getGL();
      if (mGLWrapper != null) {
        gl = mGLWrapper.wrap(gl);
      }
      return gl;
    }

    /**
     * Display the current render surface.
     * 
     * @return false if the context has been lost.
     */
    public boolean swap() {
      mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);

      /*
       * Always check for EGL_CONTEXT_LOST, which means the context and
       * all associated data were lost (For instance because the device
       * went to sleep). We need to sleep until we get a new surface.
       */
      return mEgl.eglGetError() != EGL11.EGL_CONTEXT_LOST;
    }

    public void finish() {
      if (mEglSurface != null) {
        mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
            EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
        mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
        mEglSurface = null;
      }
      if (mEglContext != null) {
        mEgl.eglDestroyContext(mEglDisplay, mEglContext);
        mEglContext = null;
      }
      if (mEglDisplay != null) {
        mEgl.eglTerminate(mEglDisplay);
        mEglDisplay = null;
      }
    }

    EGL10 mEgl;
    EGLDisplay mEglDisplay;
    EGLSurface mEglSurface;
    EGLConfig mEglConfig;
    EGLContext mEglContext;
  }

  /**
   * A generic GL Thread. Takes care of initializing EGL and GL. Delegates to
   * a Renderer instance to do the actual drawing.
   * 
   */

  class GLThread extends Thread {
    GLThread(Renderer renderer) {
      super();
      mDone = false;
      mWidth = 0;
      mHeight = 0;
      mRenderer = renderer;
      setName("GLThread");
    }

    @Override
    public void run() {
      /*
       * When the android framework launches a second instance of an
       * activity, the new instance's onCreate() method may be called
       * before the first instance returns from onDestroy().
       * 
       * This semaphore ensures that only one instance at a time accesses
       * EGL.
       */
      try {
        try {
          sEglSemaphore.acquire();
        } catch (InterruptedException e) {
          return;
        }
        guardedRun();
      } catch (InterruptedException e) {
        // fall thru and exit normally
      } finally {
        sEglSemaphore.release();
      }
    }

    private void guardedRun() throws InterruptedException {
      mEglHelper = new EglHelper();
      /*
       * Specify a configuration for our opengl session and grab the first
       * configuration that matches is
       */
      int[] configSpec = mRenderer.getConfigSpec();
      mEglHelper.start(configSpec);

      GL10 gl = null;
      boolean tellRendererSurfaceCreated = true;
      boolean tellRendererSurfaceChanged = true;

      /*
       * This is our main activity thread's loop, we go until asked to
       * quit.
       */
      while (!mDone) {

        /*
         * Update the asynchronous state (window size)
         */
        int w, h;
        boolean changed;
        boolean needStart = false;
        synchronized (this) {
          if (mEvent != null) {

            mEvent.run();

          }
          if (mPaused) {
            mEglHelper.finish();
            needStart = true;
          }
          if (needToWait()) {
            while (needToWait()) {
              wait();
            }
          }
          if (mDone) {
            break;
          }
          changed = mSizeChanged;
          w = mWidth;
          h = mHeight;
          mSizeChanged = false;
        }
        if (needStart) {
          mEglHelper.start(configSpec);
          tellRendererSurfaceCreated = true;
          changed = true;
        }
        if (changed) {
          gl = (GL10) mEglHelper.createSurface(mHolder);
          tellRendererSurfaceChanged = true;
        }
        if (tellRendererSurfaceCreated) {
          mRenderer.surfaceCreated(gl);
          tellRendererSurfaceCreated = false;
        }
        if (tellRendererSurfaceChanged) {
          mRenderer.sizeChanged(gl, w, h);
          tellRendererSurfaceChanged = false;
        }
        if ((w > 0) && (h > 0)) {

          /* draw a frame here */
          mRenderer.drawFrame(gl);

          /*
           * Once we're done with GL, we need to call swapBuffers() to
           * instruct the system to display the rendered frame
           */

          mEglHelper.swap();

        }

      }

      /*
       * clean-up everything...
       */
      if (gl != null) {
        mRenderer.shutdown(gl);
      }

      mEglHelper.finish();
    }

    private boolean needToWait() {
      return (mPaused || (!mHasFocus) || (!mHasSurface) || mContextLost)
          && (!mDone);
    }

    public void surfaceCreated() {
      synchronized (this) {
        mHasSurface = true;
        mContextLost = false;
        notify();
      }
    }

    public void surfaceDestroyed() {
      synchronized (this) {
        mHasSurface = false;
        notify();
      }
    }

    public void onPause() {
      synchronized (this) {
        mPaused = true;
      }
    }

    public void onResume() {
      synchronized (this) {
        mPaused = false;
        notify();
      }
    }

    public void onWindowFocusChanged(boolean hasFocus) {
      synchronized (this) {
        mHasFocus = hasFocus;
        if (mHasFocus == true) {
          notify();
        }
      }
    }

    public void onWindowResize(int w, int h) {
      synchronized (this) {
        mWidth = w;
        mHeight = h;
        mSizeChanged = true;
      }
    }

    public void requestExitAndWait() {
      // don't call this from GLThread thread or it is a guaranteed
      // deadlock!
      synchronized (this) {
        mDone = true;
        notify();
      }
      try {
        join();
      } catch (InterruptedException ex) {
        Thread.currentThread().interrupt();
      }
    }

    /**
     * Queue an "event" to be run on the GL rendering thread.
     * 
     * @param r
     *            the runnable to be run on the GL rendering thread.
     */
    public void setEvent(Runnable r) {
      synchronized (this) {
        mEvent = r;
      }
    }

    public void clearEvent() {
      synchronized (this) {
        mEvent = null;
      }
    }

    private boolean mDone;
    private boolean mPaused;
    private boolean mHasFocus;
    private boolean mHasSurface;
    private boolean mContextLost;
    private int mWidth;
    private int mHeight;
    private Renderer mRenderer;
    private Runnable mEvent;
    private EglHelper mEglHelper;
  }

  private static final Semaphore sEglSemaphore = new Semaphore(1);
  private boolean mSizeChanged = true;

  private SurfaceHolder mHolder;
  private GLThread mGLThread;
  private GLWrapper mGLWrapper;
}
OpenGL Sprite Text Activity
package com.example.android.apis.graphics.spritetext;

import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL10Ext;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;

/**
 * An OpenGL text label maker.
 * 
 * 
 * OpenGL labels are implemented by creating a Bitmap, drawing all the labels
 * into the Bitmap, converting the Bitmap into an Alpha texture, and drawing
 * portions of the texture using glDrawTexiOES.
 * 
 * The benefits of this approach are that the labels are drawn using the high
 * quality anti-aliased font rasterizer, full character set support, and all the
 * text labels are stored on a single texture, which makes it faster to use.
 * 
 * The drawbacks are that you can only have as many labels as will fit onto one
 * texture, and you have to recreate the whole texture if any label text
 * changes.
 * 
 */
class LabelMaker {
  /**
   * Create a label maker or maximum compatibility with various OpenGL ES
   * implementations, the strike width and height must be powers of two, We
   * want the strike width to be at least as wide as the widest window.
   * 
   * @param fullColor
   *            true if we want a full color backing store (4444), otherwise
   *            we generate a grey L8 backing store.
   * @param strikeWidth
   *            width of strike
   * @param strikeHeight
   *            height of strike
   */
  public LabelMaker(boolean fullColor, int strikeWidth, int strikeHeight) {
    mFullColor = fullColor;
    mStrikeWidth = strikeWidth;
    mStrikeHeight = strikeHeight;
    mTexelWidth = (float) (1.0 / mStrikeWidth);
    mTexelHeight = (float) (1.0 / mStrikeHeight);
    mClearPaint = new Paint();
    mClearPaint.setARGB(0, 0, 0, 0);
    mClearPaint.setStyle(Style.FILL);
    mState = STATE_NEW;
  }

  /**
   * Call to initialize the class. Call whenever the surface has been created.
   * 
   * @param gl
   */
  public void initialize(GL10 gl) {
    mState = STATE_INITIALIZED;
    int[] textures = new int[1];
    gl.glGenTextures(1, textures, 0);
    mTextureID = textures[0];
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);

    // Use Nearest for performance.
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
        GL10.GL_NEAREST);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
        GL10.GL_NEAREST);

    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
        GL10.GL_CLAMP_TO_EDGE);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
        GL10.GL_CLAMP_TO_EDGE);

    gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
        GL10.GL_REPLACE);
  }

  /**
   * Call when the surface has been destroyed
   */
  public void shutdown(GL10 gl) {
    if (gl != null) {
      if (mState > STATE_NEW) {
        int[] textures = new int[1];
        textures[0] = mTextureID;
        gl.glDeleteTextures(1, textures, 0);
        mState = STATE_NEW;
      }
    }
  }

  /**
   * Call before adding labels. Clears out any existing labels.
   * 
   * @param gl
   */
  public void beginAdding(GL10 gl) {
    checkState(STATE_INITIALIZED, STATE_ADDING);
    mLabels.clear();
    mU = 0;
    mV = 0;
    mLineHeight = 0;
    Bitmap.Config config = mFullColor ? Bitmap.Config.ARGB_4444
        : Bitmap.Config.ALPHA_8;
    mBitmap = Bitmap.createBitmap(mStrikeWidth, mStrikeHeight, config);
    mCanvas = new Canvas(mBitmap);
    mBitmap.eraseColor(0);
  }

  /**
   * Call to add a label
   * 
   * @param gl
   * @param text
   *            the text of the label
   * @param textPaint
   *            the paint of the label
   * @return the id of the label, used to measure and draw the label
   */
  public int add(GL10 gl, String text, Paint textPaint) {
    return add(gl, null, text, textPaint);
  }

  /**
   * Call to add a label
   * 
   * @param gl
   * @param text
   *            the text of the label
   * @param textPaint
   *            the paint of the label
   * @return the id of the label, used to measure and draw the label
   */
  public int add(GL10 gl, Drawable background, String text, Paint textPaint) {
    return add(gl, background, text, textPaint, 0, 0);
  }

  /**
   * Call to add a label
   * 
   * @return the id of the label, used to measure and draw the label
   */
  public int add(GL10 gl, Drawable drawable, int minWidth, int minHeight) {
    return add(gl, drawable, null, null, minWidth, minHeight);
  }

  /**
   * Call to add a label
   * 
   * @param gl
   * @param text
   *            the text of the label
   * @param textPaint
   *            the paint of the label
   * @return the id of the label, used to measure and draw the label
   */
  public int add(GL10 gl, Drawable background, String text, Paint textPaint,
      int minWidth, int minHeight) {
    checkState(STATE_ADDING, STATE_ADDING);
    boolean drawBackground = background != null;
    boolean drawText = (text != null) && (textPaint != null);

    Rect padding = new Rect();
    if (drawBackground) {
      background.getPadding(padding);
      minWidth = Math.max(minWidth, background.getMinimumWidth());
      minHeight = Math.max(minHeight, background.getMinimumHeight());
    }

    int ascent = 0;
    int descent = 0;
    int measuredTextWidth = 0;
    if (drawText) {
      // Paint.ascent is negative, so negate it.
      ascent = (int) Math.ceil(-textPaint.ascent());
      descent = (int) Math.ceil(textPaint.descent());
      measuredTextWidth = (int) Math.ceil(textPaint.measureText(text));
    }
    int textHeight = ascent + descent;
    int textWidth = Math.min(mStrikeWidth, measuredTextWidth);

    int padHeight = padding.top + padding.bottom;
    int padWidth = padding.left + padding.right;
    int height = Math.max(minHeight, textHeight + padHeight);
    int width = Math.max(minWidth, textWidth + padWidth);
    int effectiveTextHeight = height - padHeight;
    int effectiveTextWidth = width - padWidth;

    int centerOffsetHeight = (effectiveTextHeight - textHeight) / 2;
    int centerOffsetWidth = (effectiveTextWidth - textWidth) / 2;

    // Make changes to the local variables, only commit them
    // to the member variables after we've decided not to throw
    // any exceptions.

    int u = mU;
    int v = mV;
    int lineHeight = mLineHeight;

    if (width > mStrikeWidth) {
      width = mStrikeWidth;
    }

    // Is there room for this string on the current line?
    if (u + width > mStrikeWidth) {
      // No room, go to the next line:
      u = 0;
      v += lineHeight;
      lineHeight = 0;
    }
    lineHeight = Math.max(lineHeight, height);
    if (v + lineHeight > mStrikeHeight) {
      throw new IllegalArgumentException("Out of texture space.");
    }

    int u2 = u + width;
    int vBase = v + ascent;
    int v2 = v + height;

    if (drawBackground) {
      background.setBounds(u, v, u + width, v + height);
      background.draw(mCanvas);
    }

    if (drawText) {
      mCanvas.drawText(text, u + padding.left + centerOffsetWidth, vBase
          + padding.top + centerOffsetHeight, textPaint);
    }

    // We know there's enough space, so update the member variables
    mU = u + width;
    mV = v;
    mLineHeight = lineHeight;
    mLabels.add(new Label(width, height, ascent, u, v + height, width,
        -height));
    return mLabels.size() - 1;
  }

  /**
   * Call to end adding labels. Must be called before drawing starts.
   * 
   * @param gl
   */
  public void endAdding(GL10 gl) {
    checkState(STATE_ADDING, STATE_INITIALIZED);
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0);
    // Reclaim storage used by bitmap and canvas.
    mBitmap.recycle();
    mBitmap = null;
    mCanvas = null;
  }

  /**
   * Get the width in pixels of a given label.
   * 
   * @param labelID
   * @return the width in pixels
   */
  public float getWidth(int labelID) {
    return mLabels.get(labelID).width;
  }

  /**
   * Get the height in pixels of a given label.
   * 
   * @param labelID
   * @return the height in pixels
   */
  public float getHeight(int labelID) {
    return mLabels.get(labelID).height;
  }

  /**
   * Get the baseline of a given label. That's how many pixels from the top of
   * the label to the text baseline. (This is equivalent to the negative of
   * the label's paint's ascent.)
   * 
   * @param labelID
   * @return the baseline in pixels.
   */
  public float getBaseline(int labelID) {
    return mLabels.get(labelID).baseline;
  }

  /**
   * Begin drawing labels. Sets the OpenGL state for rapid drawing.
   * 
   * @param gl
   * @param viewWidth
   * @param viewHeight
   */
  public void beginDrawing(GL10 gl, float viewWidth, float viewHeight) {
    checkState(STATE_INITIALIZED, STATE_DRAWING);
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
    gl.glShadeModel(GL10.GL_FLAT);
    gl.glEnable(GL10.GL_BLEND);
    gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
    gl.glColor4x(0x10000, 0x10000, 0x10000, 0x10000);
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glPushMatrix();
    gl.glLoadIdentity();
    gl.glOrthof(0.0f, viewWidth, 0.0f, viewHeight, 0.0f, 1.0f);
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glPushMatrix();
    gl.glLoadIdentity();
    // Magic offsets to promote consistent rasterization.
    gl.glTranslatef(0.375f, 0.375f, 0.0f);
  }

  /**
   * Draw a given label at a given x,y position, expressed in pixels, with the
   * lower-left-hand-corner of the view being (0,0).
   * 
   * @param gl
   * @param x
   * @param y
   * @param labelID
   */
  public void draw(GL10 gl, float x, float y, int labelID) {
    checkState(STATE_DRAWING, STATE_DRAWING);
    Label label = mLabels.get(labelID);
    gl.glEnable(GL10.GL_TEXTURE_2D);
    ((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D,
        GL11Ext.GL_TEXTURE_CROP_RECT_OES, label.mCrop, 0);
    ((GL11Ext) gl).glDrawTexiOES((int) x, (int) y, 0, (int) label.width,
        (int) label.height);
  }

  /**
   * Ends the drawing and restores the OpenGL state.
   * 
   * @param gl
   */
  public void endDrawing(GL10 gl) {
    checkState(STATE_DRAWING, STATE_INITIALIZED);
    gl.glDisable(GL10.GL_BLEND);
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glPopMatrix();
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glPopMatrix();
  }

  private void checkState(int oldState, int newState) {
    if (mState != oldState) {
      throw new IllegalArgumentException("Can't call this method now.");
    }
    mState = newState;
  }

  private static class Label {
    public Label(float width, float height, float baseLine, int cropU,
        int cropV, int cropW, int cropH) {
      this.width = width;
      this.height = height;
      this.baseline = baseLine;
      int[] crop = new int[4];
      crop[0] = cropU;
      crop[1] = cropV;
      crop[2] = cropW;
      crop[3] = cropH;
      mCrop = crop;
    }

    public float width;
    public float height;
    public float baseline;
    public int[] mCrop;
  }

  private int mStrikeWidth;
  private int mStrikeHeight;
  private boolean mFullColor;
  private Bitmap mBitmap;
  private Canvas mCanvas;
  private Paint mClearPaint;

  private int mTextureID;

  private float mTexelWidth; // Convert texel to U
  private float mTexelHeight; // Convert texel to V
  private int mU;
  private int mV;
  private int mLineHeight;
  private ArrayList<Label> mLabels = new ArrayList<Label>();

  private static final int STATE_NEW = 0;
  private static final int STATE_INITIALIZED = 1;
  private static final int STATE_ADDING = 2;
  private static final int STATE_DRAWING = 3;
  private int mState;
}

class SpriteTextRenderer implements GLSurfaceView.Renderer {

  public SpriteTextRenderer(Context context) {
    mContext = context;
    mTriangle = new Triangle();
    mProjector = new Projector();
    mLabelPaint = new Paint();
    mLabelPaint.setTextSize(32);
    mLabelPaint.setAntiAlias(true);
    mLabelPaint.setARGB(0xff, 0x00, 0x00, 0x00);
  }

  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    /*
     * By default, OpenGL enables features that improve quality but reduce
     * performance. One might want to tweak that especially on software
     * renderer.
     */
    gl.glDisable(GL10.GL_DITHER);

    /*
     * Some one-time OpenGL initialization can be made here probably based
     * on features of this particular context
     */
    gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);

    gl.glClearColor(.5f, .5f, .5f, 1);
    gl.glShadeModel(GL10.GL_SMOOTH);
    gl.glEnable(GL10.GL_DEPTH_TEST);
    gl.glEnable(GL10.GL_TEXTURE_2D);

    /*
     * Create our texture. This has to be done each time the surface is
     * created.
     */

    int[] textures = new int[1];
    gl.glGenTextures(1, textures, 0);

    mTextureID = textures[0];
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);

    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
        GL10.GL_NEAREST);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
        GL10.GL_LINEAR);

    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
        GL10.GL_CLAMP_TO_EDGE);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
        GL10.GL_CLAMP_TO_EDGE);

    gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
        GL10.GL_REPLACE);

    InputStream is = mContext.getResources().openRawResource(R.raw.robot);
    Bitmap bitmap;
    try {
      bitmap = BitmapFactory.decodeStream(is);
    } finally {
      try {
        is.close();
      } catch (IOException e) {
        // Ignore.
      }
    }

    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
    bitmap.recycle();

    if (mLabels != null) {
      mLabels.shutdown(gl);
    } else {
      mLabels = new LabelMaker(true, 256, 64);
    }
    mLabels.initialize(gl);
    mLabels.beginAdding(gl);
    mLabelA = mLabels.add(gl, "A", mLabelPaint);
    mLabelB = mLabels.add(gl, "B", mLabelPaint);
    mLabelC = mLabels.add(gl, "C", mLabelPaint);
    mLabelMsPF = mLabels.add(gl, "ms/f", mLabelPaint);
    mLabels.endAdding(gl);

    if (mNumericSprite != null) {
      mNumericSprite.shutdown(gl);
    } else {
      mNumericSprite = new NumericSprite();
    }
    mNumericSprite.initialize(gl, mLabelPaint);
  }

  public void onDrawFrame(GL10 gl) {
    /*
     * By default, OpenGL enables features that improve quality but reduce
     * performance. One might want to tweak that especially on software
     * renderer.
     */
    gl.glDisable(GL10.GL_DITHER);

    gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
        GL10.GL_MODULATE);

    /*
     * Usually, the first thing one might want to do is to clear the screen.
     * The most efficient way of doing this is to use glClear().
     */

    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

    /*
     * Now we're ready to draw some 3D objects
     */

    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();

    GLU.gluLookAt(gl, 0.0f, 0.0f, -2.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

    gl.glActiveTexture(GL10.GL_TEXTURE0);
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
    gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
        GL10.GL_REPEAT);
    gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
        GL10.GL_REPEAT);

    if (false) {
      long time = SystemClock.uptimeMillis();
      if (mLastTime != 0) {
        long delta = time - mLastTime;
        Log.w("time", Long.toString(delta));
      }
      mLastTime = time;
    }

    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);

    gl.glRotatef(angle, 0, 0, 1.0f);
    gl.glScalef(2.0f, 2.0f, 2.0f);

    mTriangle.draw(gl);

    mProjector.getCurrentModelView(gl);
    mLabels.beginDrawing(gl, mWidth, mHeight);
    drawLabel(gl, 0, mLabelA);
    drawLabel(gl, 1, mLabelB);
    drawLabel(gl, 2, mLabelC);
    float msPFX = mWidth - mLabels.getWidth(mLabelMsPF) - 1;
    mLabels.draw(gl, msPFX, 0, mLabelMsPF);
    mLabels.endDrawing(gl);

    drawMsPF(gl, msPFX);
  }

  private void drawMsPF(GL10 gl, float rightMargin) {
    long time = SystemClock.uptimeMillis();
    if (mStartTime == 0) {
      mStartTime = time;
    }
    if (mFrames++ == SAMPLE_PERIOD_FRAMES) {
      mFrames = 0;
      long delta = time - mStartTime;
      mStartTime = time;
      mMsPerFrame = (int) (delta * SAMPLE_FACTOR);
    }
    if (mMsPerFrame > 0) {
      mNumericSprite.setValue(mMsPerFrame);
      float numWidth = mNumericSprite.width();
      float x = rightMargin - numWidth;
      mNumericSprite.draw(gl, x, 0, mWidth, mHeight);
    }
  }

  private void drawLabel(GL10 gl, int triangleVertex, int labelId) {
    float x = mTriangle.getX(triangleVertex);
    float y = mTriangle.getY(triangleVertex);
    mScratch[0] = x;
    mScratch[1] = y;
    mScratch[2] = 0.0f;
    mScratch[3] = 1.0f;
    mProjector.project(mScratch, 0, mScratch, 4);
    float sx = mScratch[4];
    float sy = mScratch[5];
    float height = mLabels.getHeight(labelId);
    float width = mLabels.getWidth(labelId);
    float tx = sx - width * 0.5f;
    float ty = sy - height * 0.5f;
    mLabels.draw(gl, tx, ty, labelId);
  }

  public void onSurfaceChanged(GL10 gl, int w, int h) {
    mWidth = w;
    mHeight = h;
    gl.glViewport(0, 0, w, h);
    mProjector.setCurrentView(0, 0, w, h);

    /*
     * Set our projection matrix. This doesn't have to be done each time we
     * draw, but usually a new projection needs to be set when the viewport
     * is resized.
     */

    float ratio = (float) w / h;
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadIdentity();
    gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
    mProjector.getCurrentProjection(gl);
  }

  private int mWidth;
  private int mHeight;
  private Context mContext;
  private Triangle mTriangle;
  private int mTextureID;
  private int mFrames;
  private int mMsPerFrame;
  private final static int SAMPLE_PERIOD_FRAMES = 12;
  private final static float SAMPLE_FACTOR = 1.0f / SAMPLE_PERIOD_FRAMES;
  private long mStartTime;
  private LabelMaker mLabels;
  private Paint mLabelPaint;
  private int mLabelA;
  private int mLabelB;
  private int mLabelC;
  private int mLabelMsPF;
  private Projector mProjector;
  private NumericSprite mNumericSprite;
  private float[] mScratch = new float[8];
  private long mLastTime;
}

class Triangle {
  public Triangle() {

    // Buffers to be passed to gl*Pointer() functions
    // must be direct, i.e., they must be placed on the
    // native heap where the garbage collector cannot
    // move them.
    //
    // Buffers with multi-byte datatypes (e.g., short, int, float)
    // must have their byte order set to native order

    ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
    vbb.order(ByteOrder.nativeOrder());
    mFVertexBuffer = vbb.asFloatBuffer();

    ByteBuffer tbb = ByteBuffer.allocateDirect(VERTS * 2 * 4);
    tbb.order(ByteOrder.nativeOrder());
    mTexBuffer = tbb.asFloatBuffer();

    ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
    ibb.order(ByteOrder.nativeOrder());
    mIndexBuffer = ibb.asShortBuffer();

    for (int i = 0; i < VERTS; i++) {
      for (int j = 0; j < 3; j++) {
        mFVertexBuffer.put(sCoords[i * 3 + j]);
      }
    }

    for (int i = 0; i < VERTS; i++) {
      for (int j = 0; j < 2; j++) {
        mTexBuffer.put(sCoords[i * 3 + j] * 2.0f + 0.5f);
      }
    }

    for (int i = 0; i < VERTS; i++) {
      mIndexBuffer.put((short) i);
    }

    mFVertexBuffer.position(0);
    mTexBuffer.position(0);
    mIndexBuffer.position(0);
  }

  public void draw(GL10 gl) {
    gl.glFrontFace(GL10.GL_CCW);
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
    gl.glEnable(GL10.GL_TEXTURE_2D);
    gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexBuffer);
    gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, VERTS,
        GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
  }

  public float getX(int vertex) {
    return sCoords[3 * vertex];
  }

  public float getY(int vertex) {
    return sCoords[3 * vertex + 1];
  }

  private final static int VERTS = 3;

  private FloatBuffer mFVertexBuffer;
  private FloatBuffer mTexBuffer;
  private ShortBuffer mIndexBuffer;
  // A unit-sided equalateral triangle centered on the origin.
  private final static float[] sCoords = {
      // X, Y, Z
      -0.5f, -0.25f, 0, 0.5f, -0.25f, 0, 0.0f, 0.559016994f, 0 };
}

/**
 * A utility that projects
 * 
 */
class Projector {
  public Projector() {
    mMVP = new float[16];
    mV = new float[4];
    mGrabber = new MatrixGrabber();
  }

  public void setCurrentView(int x, int y, int width, int height) {
    mX = x;
    mY = y;
    mViewWidth = width;
    mViewHeight = height;
  }

  public void project(float[] obj, int objOffset, float[] win, int winOffset) {
    if (!mMVPComputed) {
      Matrix.multiplyMM(mMVP, 0, mGrabber.mProjection, 0,
          mGrabber.mModelView, 0);
      mMVPComputed = true;
    }

    Matrix.multiplyMV(mV, 0, mMVP, 0, obj, objOffset);

    float rw = 1.0f / mV[3];

    win[winOffset] = mX + mViewWidth * (mV[0] * rw + 1.0f) * 0.5f;
    win[winOffset + 1] = mY + mViewHeight * (mV[1] * rw + 1.0f) * 0.5f;
    win[winOffset + 2] = (mV[2] * rw + 1.0f) * 0.5f;
  }

  /**
   * Get the current projection matrix. Has the side-effect of setting current
   * matrix mode to GL_PROJECTION
   * 
   * @param gl
   */
  public void getCurrentProjection(GL10 gl) {
    mGrabber.getCurrentProjection(gl);
    mMVPComputed = false;
  }

  /**
   * Get the current model view matrix. Has the side-effect of setting current
   * matrix mode to GL_MODELVIEW
   * 
   * @param gl
   */
  public void getCurrentModelView(GL10 gl) {
    mGrabber.getCurrentModelView(gl);
    mMVPComputed = false;
  }

  private MatrixGrabber mGrabber;
  private boolean mMVPComputed;
  private float[] mMVP;
  private float[] mV;
  private int mX;
  private int mY;
  private int mViewWidth;
  private int mViewHeight;
}

/**
 * Allows retrieving the current matrix even if the current OpenGL ES driver
 * does not support retrieving the current matrix.
 * 
 * Note: the actual matrix may differ from the retrieved matrix, due to
 * differences in the way the math is implemented by GLMatrixWrapper as compared
 * to the way the math is implemented by the OpenGL ES driver.
 */
class MatrixTrackingGL implements GL, GL10, GL10Ext, GL11, GL11Ext {
  private GL10 mgl;
  private GL10Ext mgl10Ext;
  private GL11 mgl11;
  private GL11Ext mgl11Ext;
  private int mMatrixMode;
  private MatrixStack mCurrent;
  private MatrixStack mModelView;
  private MatrixStack mTexture;
  private MatrixStack mProjection;

  private final static boolean _check = false;
  ByteBuffer mByteBuffer;
  FloatBuffer mFloatBuffer;
  float[] mCheckA;
  float[] mCheckB;

  public MatrixTrackingGL(GL gl) {
    mgl = (GL10) gl;
    if (gl instanceof GL10Ext) {
      mgl10Ext = (GL10Ext) gl;
    }
    if (gl instanceof GL11) {
      mgl11 = (GL11) gl;
    }
    if (gl instanceof GL11Ext) {
      mgl11Ext = (GL11Ext) gl;
    }
    mModelView = new MatrixStack();
    mProjection = new MatrixStack();
    mTexture = new MatrixStack();
    mCurrent = mModelView;
    mMatrixMode = GL10.GL_MODELVIEW;
  }

  // ---------------------------------------------------------------------
  // GL10 methods:

  public void glActiveTexture(int texture) {
    mgl.glActiveTexture(texture);
  }

  public void glAlphaFunc(int func, float ref) {
    mgl.glAlphaFunc(func, ref);
  }

  public void glAlphaFuncx(int func, int ref) {
    mgl.glAlphaFuncx(func, ref);
  }

  public void glBindTexture(int target, int texture) {
    mgl.glBindTexture(target, texture);
  }

  public void glBlendFunc(int sfactor, int dfactor) {
    mgl.glBlendFunc(sfactor, dfactor);
  }

  public void glClear(int mask) {
    mgl.glClear(mask);
  }

  public void glClearColor(float red, float green, float blue, float alpha) {
    mgl.glClearColor(red, green, blue, alpha);
  }

  public void glClearColorx(int red, int green, int blue, int alpha) {
    mgl.glClearColorx(red, green, blue, alpha);
  }

  public void glClearDepthf(float depth) {
    mgl.glClearDepthf(depth);
  }

  public void glClearDepthx(int depth) {
    mgl.glClearDepthx(depth);
  }

  public void glClearStencil(int s) {
    mgl.glClearStencil(s);
  }

  public void glClientActiveTexture(int texture) {
    mgl.glClientActiveTexture(texture);
  }

  public void glColor4f(float red, float green, float blue, float alpha) {
    mgl.glColor4f(red, green, blue, alpha);
  }

  public void glColor4x(int red, int green, int blue, int alpha) {
    mgl.glColor4x(red, green, blue, alpha);
  }

  public void glColorMask(boolean red, boolean green, boolean blue,
      boolean alpha) {
    mgl.glColorMask(red, green, blue, alpha);
  }

  public void glColorPointer(int size, int type, int stride, Buffer pointer) {
    mgl.glColorPointer(size, type, stride, pointer);
  }

  public void glCompressedTexImage2D(int target, int level,
      int internalformat, int width, int height, int border,
      int imageSize, Buffer data) {
    mgl.glCompressedTexImage2D(target, level, internalformat, width,
        height, border, imageSize, data);
  }

  public void glCompressedTexSubImage2D(int target, int level, int xoffset,
      int yoffset, int width, int height, int format, int imageSize,
      Buffer data) {
    mgl.glCompressedTexSubImage2D(target, level, xoffset, yoffset, width,
        height, format, imageSize, data);
  }

  public void glCopyTexImage2D(int target, int level, int internalformat,
      int x, int y, int width, int height, int border) {
    mgl.glCopyTexImage2D(target, level, internalformat, x, y, width,
        height, border);
  }

  public void glCopyTexSubImage2D(int target, int level, int xoffset,
      int yoffset, int x, int y, int width, int height) {
    mgl.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width,
        height);
  }

  public void glCullFace(int mode) {
    mgl.glCullFace(mode);
  }

  public void glDeleteTextures(int n, int[] textures, int offset) {
    mgl.glDeleteTextures(n, textures, offset);
  }

  public void glDeleteTextures(int n, IntBuffer textures) {
    mgl.glDeleteTextures(n, textures);
  }

  public void glDepthFunc(int func) {
    mgl.glDepthFunc(func);
  }

  public void glDepthMask(boolean flag) {
    mgl.glDepthMask(flag);
  }

  public void glDepthRangef(float near, float far) {
    mgl.glDepthRangef(near, far);
  }

  public void glDepthRangex(int near, int far) {
    mgl.glDepthRangex(near, far);
  }

  public void glDisable(int cap) {
    mgl.glDisable(cap);
  }

  public void glDisableClientState(int array) {
    mgl.glDisableClientState(array);
  }

  public void glDrawArrays(int mode, int first, int count) {
    mgl.glDrawArrays(mode, first, count);
  }

  public void glDrawElements(int mode, int count, int type, Buffer indices) {
    mgl.glDrawElements(mode, count, type, indices);
  }

  public void glEnable(int cap) {
    mgl.glEnable(cap);
  }

  public void glEnableClientState(int array) {
    mgl.glEnableClientState(array);
  }

  public void glFinish() {
    mgl.glFinish();
  }

  public void glFlush() {
    mgl.glFlush();
  }

  public void glFogf(int pname, float param) {
    mgl.glFogf(pname, param);
  }

  public void glFogfv(int pname, float[] params, int offset) {
    mgl.glFogfv(pname, params, offset);
  }

  public void glFogfv(int pname, FloatBuffer params) {
    mgl.glFogfv(pname, params);
  }

  public void glFogx(int pname, int param) {
    mgl.glFogx(pname, param);
  }

  public void glFogxv(int pname, int[] params, int offset) {
    mgl.glFogxv(pname, params, offset);
  }

  public void glFogxv(int pname, IntBuffer params) {
    mgl.glFogxv(pname, params);
  }

  public void glFrontFace(int mode) {
    mgl.glFrontFace(mode);
  }

  public void glFrustumf(float left, float right, float bottom, float top,
      float near, float far) {
    mCurrent.glFrustumf(left, right, bottom, top, near, far);
    mgl.glFrustumf(left, right, bottom, top, near, far);
    if (_check)
      check();
  }

  public void glFrustumx(int left, int right, int bottom, int top, int near,
      int far) {
    mCurrent.glFrustumx(left, right, bottom, top, near, far);
    mgl.glFrustumx(left, right, bottom, top, near, far);
    if (_check)
      check();
  }

  public void glGenTextures(int n, int[] textures, int offset) {
    mgl.glGenTextures(n, textures, offset);
  }

  public void glGenTextures(int n, IntBuffer textures) {
    mgl.glGenTextures(n, textures);
  }

  public int glGetError() {
    int result = mgl.glGetError();
    return result;
  }

  public void glGetIntegerv(int pname, int[] params, int offset) {
    mgl.glGetIntegerv(pname, params, offset);
  }

  public void glGetIntegerv(int pname, IntBuffer params) {
    mgl.glGetIntegerv(pname, params);
  }

  public String glGetString(int name) {
    String result = mgl.glGetString(name);
    return result;
  }

  public void glHint(int target, int mode) {
    mgl.glHint(target, mode);
  }

  public void glLightModelf(int pname, float param) {
    mgl.glLightModelf(pname, param);
  }

  public void glLightModelfv(int pname, float[] params, int offset) {
    mgl.glLightModelfv(pname, params, offset);
  }

  public void glLightModelfv(int pname, FloatBuffer params) {
    mgl.glLightModelfv(pname, params);
  }

  public void glLightModelx(int pname, int param) {
    mgl.glLightModelx(pname, param);
  }

  public void glLightModelxv(int pname, int[] params, int offset) {
    mgl.glLightModelxv(pname, params, offset);
  }

  public void glLightModelxv(int pname, IntBuffer params) {
    mgl.glLightModelxv(pname, params);
  }

  public void glLightf(int light, int pname, float param) {
    mgl.glLightf(light, pname, param);
  }

  public void glLightfv(int light, int pname, float[] params, int offset) {
    mgl.glLightfv(light, pname, params, offset);
  }

  public void glLightfv(int light, int pname, FloatBuffer params) {
    mgl.glLightfv(light, pname, params);
  }

  public void glLightx(int light, int pname, int param) {
    mgl.glLightx(light, pname, param);
  }

  public void glLightxv(int light, int pname, int[] params, int offset) {
    mgl.glLightxv(light, pname, params, offset);
  }

  public void glLightxv(int light, int pname, IntBuffer params) {
    mgl.glLightxv(light, pname, params);
  }

  public void glLineWidth(float width) {
    mgl.glLineWidth(width);
  }

  public void glLineWidthx(int width) {
    mgl.glLineWidthx(width);
  }

  public void glLoadIdentity() {
    mCurrent.glLoadIdentity();
    mgl.glLoadIdentity();
    if (_check)
      check();
  }

  public void glLoadMatrixf(float[] m, int offset) {
    mCurrent.glLoadMatrixf(m, offset);
    mgl.glLoadMatrixf(m, offset);
    if (_check)
      check();
  }

  public void glLoadMatrixf(FloatBuffer m) {
    int position = m.position();
    mCurrent.glLoadMatrixf(m);
    m.position(position);
    mgl.glLoadMatrixf(m);
    if (_check)
      check();
  }

  public void glLoadMatrixx(int[] m, int offset) {
    mCurrent.glLoadMatrixx(m, offset);
    mgl.glLoadMatrixx(m, offset);
    if (_check)
      check();
  }

  public void glLoadMatrixx(IntBuffer m) {
    int position = m.position();
    mCurrent.glLoadMatrixx(m);
    m.position(position);
    mgl.glLoadMatrixx(m);
    if (_check)
      check();
  }

  public void glLogicOp(int opcode) {
    mgl.glLogicOp(opcode);
  }

  public void glMaterialf(int face, int pname, float param) {
    mgl.glMaterialf(face, pname, param);
  }

  public void glMaterialfv(int face, int pname, float[] params, int offset) {
    mgl.glMaterialfv(face, pname, params, offset);
  }

  public void glMaterialfv(int face, int pname, FloatBuffer params) {
    mgl.glMaterialfv(face, pname, params);
  }

  public void glMaterialx(int face, int pname, int param) {
    mgl.glMaterialx(face, pname, param);
  }

  public void glMaterialxv(int face, int pname, int[] params, int offset) {
    mgl.glMaterialxv(face, pname, params, offset);
  }

  public void glMaterialxv(int face, int pname, IntBuffer params) {
    mgl.glMaterialxv(face, pname, params);
  }

  public void glMatrixMode(int mode) {
    switch (mode) {
    case GL10.GL_MODELVIEW:
      mCurrent = mModelView;
      break;
    case GL10.GL_TEXTURE:
      mCurrent = mTexture;
      break;
    case GL10.GL_PROJECTION:
      mCurrent = mProjection;
      break;
    default:
      throw new IllegalArgumentException("Unknown matrix mode: " + mode);
    }
    mgl.glMatrixMode(mode);
    mMatrixMode = mode;
    if (_check)
      check();
  }

  public void glMultMatrixf(float[] m, int offset) {
    mCurrent.glMultMatrixf(m, offset);
    mgl.glMultMatrixf(m, offset);
    if (_check)
      check();
  }

  public void glMultMatrixf(FloatBuffer m) {
    int position = m.position();
    mCurrent.glMultMatrixf(m);
    m.position(position);
    mgl.glMultMatrixf(m);
    if (_check)
      check();
  }

  public void glMultMatrixx(int[] m, int offset) {
    mCurrent.glMultMatrixx(m, offset);
    mgl.glMultMatrixx(m, offset);
    if (_check)
      check();
  }

  public void glMultMatrixx(IntBuffer m) {
    int position = m.position();
    mCurrent.glMultMatrixx(m);
    m.position(position);
    mgl.glMultMatrixx(m);
    if (_check)
      check();
  }

  public void glMultiTexCoord4f(int target, float s, float t, float r, float q) {
    mgl.glMultiTexCoord4f(target, s, t, r, q);
  }

  public void glMultiTexCoord4x(int target, int s, int t, int r, int q) {
    mgl.glMultiTexCoord4x(target, s, t, r, q);
  }

  public void glNormal3f(float nx, float ny, float nz) {
    mgl.glNormal3f(nx, ny, nz);
  }

  public void glNormal3x(int nx, int ny, int nz) {
    mgl.glNormal3x(nx, ny, nz);
  }

  public void glNormalPointer(int type, int stride, Buffer pointer) {
    mgl.glNormalPointer(type, stride, pointer);
  }

  public void glOrthof(float left, float right, float bottom, float top,
      float near, float far) {
    mCurrent.glOrthof(left, right, bottom, top, near, far);
    mgl.glOrthof(left, right, bottom, top, near, far);
    if (_check)
      check();
  }

  public void glOrthox(int left, int right, int bottom, int top, int near,
      int far) {
    mCurrent.glOrthox(left, right, bottom, top, near, far);
    mgl.glOrthox(left, right, bottom, top, near, far);
    if (_check)
      check();
  }

  public void glPixelStorei(int pname, int param) {
    mgl.glPixelStorei(pname, param);
  }

  public void glPointSize(float size) {
    mgl.glPointSize(size);
  }

  public void glPointSizex(int size) {
    mgl.glPointSizex(size);
  }

  public void glPolygonOffset(float factor, float units) {
    mgl.glPolygonOffset(factor, units);
  }

  public void glPolygonOffsetx(int factor, int units) {
    mgl.glPolygonOffsetx(factor, units);
  }

  public void glPopMatrix() {
    mCurrent.glPopMatrix();
    mgl.glPopMatrix();
    if (_check)
      check();
  }

  public void glPushMatrix() {
    mCurrent.glPushMatrix();
    mgl.glPushMatrix();
    if (_check)
      check();
  }

  public void glReadPixels(int x, int y, int width, int height, int format,
      int type, Buffer pixels) {
    mgl.glReadPixels(x, y, width, height, format, type, pixels);
  }

  public void glRotatef(float angle, float x, float y, float z) {
    mCurrent.glRotatef(angle, x, y, z);
    mgl.glRotatef(angle, x, y, z);
    if (_check)
      check();
  }

  public void glRotatex(int angle, int x, int y, int z) {
    mCurrent.glRotatex(angle, x, y, z);
    mgl.glRotatex(angle, x, y, z);
    if (_check)
      check();
  }

  public void glSampleCoverage(float value, boolean invert) {
    mgl.glSampleCoverage(value, invert);
  }

  public void glSampleCoveragex(int value, boolean invert) {
    mgl.glSampleCoveragex(value, invert);
  }

  public void glScalef(float x, float y, float z) {
    mCurrent.glScalef(x, y, z);
    mgl.glScalef(x, y, z);
    if (_check)
      check();
  }

  public void glScalex(int x, int y, int z) {
    mCurrent.glScalex(x, y, z);
    mgl.glScalex(x, y, z);
    if (_check)
      check();
  }

  public void glScissor(int x, int y, int width, int height) {
    mgl.glScissor(x, y, width, height);
  }

  public void glShadeModel(int mode) {
    mgl.glShadeModel(mode);
  }

  public void glStencilFunc(int func, int ref, int mask) {
    mgl.glStencilFunc(func, ref, mask);
  }

  public void glStencilMask(int mask) {
    mgl.glStencilMask(mask);
  }

  public void glStencilOp(int fail, int zfail, int zpass) {
    mgl.glStencilOp(fail, zfail, zpass);
  }

  public void glTexCoordPointer(int size, int type, int stride, Buffer pointer) {
    mgl.glTexCoordPointer(size, type, stride, pointer);
  }

  public void glTexEnvf(int target, int pname, float param) {
    mgl.glTexEnvf(target, pname, param);
  }

  public void glTexEnvfv(int target, int pname, float[] params, int offset) {
    mgl.glTexEnvfv(target, pname, params, offset);
  }

  public void glTexEnvfv(int target, int pname, FloatBuffer params) {
    mgl.glTexEnvfv(target, pname, params);
  }

  public void glTexEnvx(int target, int pname, int param) {
    mgl.glTexEnvx(target, pname, param);
  }

  public void glTexEnvxv(int target, int pname, int[] params, int offset) {
    mgl.glTexEnvxv(target, pname, params, offset);
  }

  public void glTexEnvxv(int target, int pname, IntBuffer params) {
    mgl.glTexEnvxv(target, pname, params);
  }

  public void glTexImage2D(int target, int level, int internalformat,
      int width, int height, int border, int format, int type,
      Buffer pixels) {
    mgl.glTexImage2D(target, level, internalformat, width, height, border,
        format, type, pixels);
  }

  public void glTexParameterf(int target, int pname, float param) {
    mgl.glTexParameterf(target, pname, param);
  }

  public void glTexParameterx(int target, int pname, int param) {
    mgl.glTexParameterx(target, pname, param);
  }

  public void glTexParameteriv(int target, int pname, int[] params, int offset) {
    mgl11.glTexParameteriv(target, pname, params, offset);
  }

  public void glTexParameteriv(int target, int pname, IntBuffer params) {
    mgl11.glTexParameteriv(target, pname, params);
  }

  public void glTexSubImage2D(int target, int level, int xoffset,
      int yoffset, int width, int height, int format, int type,
      Buffer pixels) {
    mgl.glTexSubImage2D(target, level, xoffset, yoffset, width, height,
        format, type, pixels);
  }

  public void glTranslatef(float x, float y, float z) {
    mCurrent.glTranslatef(x, y, z);
    mgl.glTranslatef(x, y, z);
    if (_check)
      check();
  }

  public void glTranslatex(int x, int y, int z) {
    mCurrent.glTranslatex(x, y, z);
    mgl.glTranslatex(x, y, z);
    if (_check)
      check();
  }

  public void glVertexPointer(int size, int type, int stride, Buffer pointer) {
    mgl.glVertexPointer(size, type, stride, pointer);
  }

  public void glViewport(int x, int y, int width, int height) {
    mgl.glViewport(x, y, width, height);
  }

  public void glClipPlanef(int plane, float[] equation, int offset) {
    mgl11.glClipPlanef(plane, equation, offset);
  }

  public void glClipPlanef(int plane, FloatBuffer equation) {
    mgl11.glClipPlanef(plane, equation);
  }

  public void glClipPlanex(int plane, int[] equation, int offset) {
    mgl11.glClipPlanex(plane, equation, offset);
  }

  public void glClipPlanex(int plane, IntBuffer equation) {
    mgl11.glClipPlanex(plane, equation);
  }

  // Draw Texture Extension

  public void glDrawTexfOES(float x, float y, float z, float width,
      float height) {
    mgl11Ext.glDrawTexfOES(x, y, z, width, height);
  }

  public void glDrawTexfvOES(float[] coords, int offset) {
    mgl11Ext.glDrawTexfvOES(coords, offset);
  }

  public void glDrawTexfvOES(FloatBuffer coords) {
    mgl11Ext.glDrawTexfvOES(coords);
  }

  public void glDrawTexiOES(int x, int y, int z, int width, int height) {
    mgl11Ext.glDrawTexiOES(x, y, z, width, height);
  }

  public void glDrawTexivOES(int[] coords, int offset) {
    mgl11Ext.glDrawTexivOES(coords, offset);
  }

  public void glDrawTexivOES(IntBuffer coords) {
    mgl11Ext.glDrawTexivOES(coords);
  }

  public void glDrawTexsOES(short x, short y, short z, short width,
      short height) {
    mgl11Ext.glDrawTexsOES(x, y, z, width, height);
  }

  public void glDrawTexsvOES(short[] coords, int offset) {
    mgl11Ext.glDrawTexsvOES(coords, offset);
  }

  public void glDrawTexsvOES(ShortBuffer coords) {
    mgl11Ext.glDrawTexsvOES(coords);
  }

  public void glDrawTexxOES(int x, int y, int z, int width, int height) {
    mgl11Ext.glDrawTexxOES(x, y, z, width, height);
  }

  public void glDrawTexxvOES(int[] coords, int offset) {
    mgl11Ext.glDrawTexxvOES(coords, offset);
  }

  public void glDrawTexxvOES(IntBuffer coords) {
    mgl11Ext.glDrawTexxvOES(coords);
  }

  public int glQueryMatrixxOES(int[] mantissa, int mantissaOffset,
      int[] exponent, int exponentOffset) {
    return mgl10Ext.glQueryMatrixxOES(mantissa, mantissaOffset, exponent,
        exponentOffset);
  }

  public int glQueryMatrixxOES(IntBuffer mantissa, IntBuffer exponent) {
    return mgl10Ext.glQueryMatrixxOES(mantissa, exponent);
  }

  // Unsupported GL11 methods

  public void glBindBuffer(int target, int buffer) {
    throw new UnsupportedOperationException();
  }

  public void glBufferData(int target, int size, Buffer data, int usage) {
    throw new UnsupportedOperationException();
  }

  public void glBufferSubData(int target, int offset, int size, Buffer data) {
    throw new UnsupportedOperationException();
  }

  public void glColor4ub(byte red, byte green, byte blue, byte alpha) {
    throw new UnsupportedOperationException();
  }

  public void glDeleteBuffers(int n, int[] buffers, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glDeleteBuffers(int n, IntBuffer buffers) {
    throw new UnsupportedOperationException();
  }

  public void glGenBuffers(int n, int[] buffers, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGenBuffers(int n, IntBuffer buffers) {
    throw new UnsupportedOperationException();
  }

  public void glGetBooleanv(int pname, boolean[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetBooleanv(int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetBufferParameteriv(int target, int pname, int[] params,
      int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetBufferParameteriv(int target, int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetClipPlanef(int pname, float[] eqn, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetClipPlanef(int pname, FloatBuffer eqn) {
    throw new UnsupportedOperationException();
  }

  public void glGetClipPlanex(int pname, int[] eqn, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetClipPlanex(int pname, IntBuffer eqn) {
    throw new UnsupportedOperationException();
  }

  public void glGetFixedv(int pname, int[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetFixedv(int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetFloatv(int pname, float[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetFloatv(int pname, FloatBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetLightfv(int light, int pname, float[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetLightfv(int light, int pname, FloatBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetLightxv(int light, int pname, int[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetLightxv(int light, int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetMaterialfv(int face, int pname, float[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetMaterialfv(int face, int pname, FloatBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetMaterialxv(int face, int pname, int[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetMaterialxv(int face, int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetTexEnviv(int env, int pname, int[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetTexEnviv(int env, int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetTexEnvxv(int env, int pname, int[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetTexEnvxv(int env, int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetTexParameterfv(int target, int pname, float[] params,
      int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetTexParameterfv(int target, int pname, FloatBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetTexParameteriv(int target, int pname, int[] params,
      int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetTexParameteriv(int target, int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glGetTexParameterxv(int target, int pname, int[] params,
      int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetTexParameterxv(int target, int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public boolean glIsBuffer(int buffer) {
    throw new UnsupportedOperationException();
  }

  public boolean glIsEnabled(int cap) {
    throw new UnsupportedOperationException();
  }

  public boolean glIsTexture(int texture) {
    throw new UnsupportedOperationException();
  }

  public void glPointParameterf(int pname, float param) {
    throw new UnsupportedOperationException();
  }

  public void glPointParameterfv(int pname, float[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glPointParameterfv(int pname, FloatBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glPointParameterx(int pname, int param) {
    throw new UnsupportedOperationException();
  }

  public void glPointParameterxv(int pname, int[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glPointParameterxv(int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glPointSizePointerOES(int type, int stride, Buffer pointer) {
    throw new UnsupportedOperationException();
  }

  public void glTexEnvi(int target, int pname, int param) {
    throw new UnsupportedOperationException();
  }

  public void glTexEnviv(int target, int pname, int[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glTexEnviv(int target, int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glTexParameterfv(int target, int pname, float[] params,
      int offset) {
    throw new UnsupportedOperationException();
  }

  public void glTexParameterfv(int target, int pname, FloatBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glTexParameteri(int target, int pname, int param) {
    throw new UnsupportedOperationException();
  }

  public void glTexParameterxv(int target, int pname, int[] params, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glTexParameterxv(int target, int pname, IntBuffer params) {
    throw new UnsupportedOperationException();
  }

  public void glColorPointer(int size, int type, int stride, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glDrawElements(int mode, int count, int type, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glGetPointerv(int pname, Buffer[] params) {
    throw new UnsupportedOperationException();
  }

  public void glNormalPointer(int type, int stride, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glTexCoordPointer(int size, int type, int stride, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glVertexPointer(int size, int type, int stride, int offset) {
    throw new UnsupportedOperationException();
  }

  public void glCurrentPaletteMatrixOES(int matrixpaletteindex) {
    throw new UnsupportedOperationException();
  }

  public void glLoadPaletteFromModelViewMatrixOES() {
    throw new UnsupportedOperationException();
  }

  public void glMatrixIndexPointerOES(int size, int type, int stride,
      Buffer pointer) {
    throw new UnsupportedOperationException();
  }

  public void glMatrixIndexPointerOES(int size, int type, int stride,
      int offset) {
    throw new UnsupportedOperationException();
  }

  public void glWeightPointerOES(int size, int type, int stride,
      Buffer pointer) {
    throw new UnsupportedOperationException();
  }

  public void glWeightPointerOES(int size, int type, int stride, int offset) {
    throw new UnsupportedOperationException();
  }

  /**
   * Get the current matrix
   */

  public void getMatrix(float[] m, int offset) {
    mCurrent.getMatrix(m, offset);
  }

  /**
   * Get the current matrix mode
   */

  public int getMatrixMode() {
    return mMatrixMode;
  }

  private void check() {
    int oesMode;
    switch (mMatrixMode) {
    case GL_MODELVIEW:
      oesMode = GL11.GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES;
      break;
    case GL_PROJECTION:
      oesMode = GL11.GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES;
      break;
    case GL_TEXTURE:
      oesMode = GL11.GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES;
      break;
    default:
      throw new IllegalArgumentException("Unknown matrix mode");
    }

    if (mByteBuffer == null) {
      mCheckA = new float[16];
      mCheckB = new float[16];
      mByteBuffer = ByteBuffer.allocateDirect(64);
      mByteBuffer.order(ByteOrder.nativeOrder());
      mFloatBuffer = mByteBuffer.asFloatBuffer();
    }
    mgl.glGetIntegerv(oesMode, mByteBuffer.asIntBuffer());
    for (int i = 0; i < 16; i++) {
      mCheckB[i] = mFloatBuffer.get(i);
    }
    mCurrent.getMatrix(mCheckA, 0);

    boolean fail = false;
    for (int i = 0; i < 16; i++) {
      if (mCheckA[i] != mCheckB[i]) {
        Log.d("GLMatWrap", "i:" + i + " a:" + mCheckA[i] + " a:"
            + mCheckB[i]);
        fail = true;
      }
    }
    if (fail) {
      throw new IllegalArgumentException("Matrix math difference.");
    }
  }

}

/**
 * A 2D rectangular mesh. Can be drawn textured or untextured.
 * 
 */
class Grid {

  public Grid(int w, int h) {
    if (w < 0 || w >= 65536) {
      throw new IllegalArgumentException("w");
    }
    if (h < 0 || h >= 65536) {
      throw new IllegalArgumentException("h");
    }
    if (w * h >= 65536) {
      throw new IllegalArgumentException("w * h >= 65536");
    }

    mW = w;
    mH = h;
    int size = w * h;
    final int FLOAT_SIZE = 4;
    final int CHAR_SIZE = 2;
    mVertexBuffer = ByteBuffer.allocateDirect(FLOAT_SIZE * size * 3)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
    mTexCoordBuffer = ByteBuffer.allocateDirect(FLOAT_SIZE * size * 2)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();

    int quadW = mW - 1;
    int quadH = mH - 1;
    int quadCount = quadW * quadH;
    int indexCount = quadCount * 6;
    mIndexCount = indexCount;
    mIndexBuffer = ByteBuffer.allocateDirect(CHAR_SIZE * indexCount)
        .order(ByteOrder.nativeOrder()).asCharBuffer();

    /*
     * Initialize triangle list mesh.
     * 
     * [0]-----[ 1] ... | / | | / | | / | [w]-----[w+1] ... | |
     */

    {
      int i = 0;
      for (int y = 0; y < quadH; y++) {
        for (int x = 0; x < quadW; x++) {
          char a = (char) (y * mW + x);
          char b = (char) (y * mW + x + 1);
          char c = (char) ((y + 1) * mW + x);
          char d = (char) ((y + 1) * mW + x + 1);

          mIndexBuffer.put(i++, a);
          mIndexBuffer.put(i++, b);
          mIndexBuffer.put(i++, c);

          mIndexBuffer.put(i++, b);
          mIndexBuffer.put(i++, c);
          mIndexBuffer.put(i++, d);
        }
      }
    }

  }

  void set(int i, int j, float x, float y, float z, float u, float v) {
    if (i < 0 || i >= mW) {
      throw new IllegalArgumentException("i");
    }
    if (j < 0 || j >= mH) {
      throw new IllegalArgumentException("j");
    }

    int index = mW * j + i;

    int posIndex = index * 3;
    mVertexBuffer.put(posIndex, x);
    mVertexBuffer.put(posIndex + 1, y);
    mVertexBuffer.put(posIndex + 2, z);

    int texIndex = index * 2;
    mTexCoordBuffer.put(texIndex, u);
    mTexCoordBuffer.put(texIndex + 1, v);
  }

  public void draw(GL10 gl, boolean useTexture) {
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);

    if (useTexture) {
      gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
      gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexCoordBuffer);
      gl.glEnable(GL10.GL_TEXTURE_2D);
    } else {
      gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
      gl.glDisable(GL10.GL_TEXTURE_2D);
    }

    gl.glDrawElements(GL10.GL_TRIANGLES, mIndexCount,
        GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
    gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
  }

  private FloatBuffer mVertexBuffer;
  private FloatBuffer mTexCoordBuffer;
  private CharBuffer mIndexBuffer;

  private int mW;
  private int mH;
  private int mIndexCount;
}

class MatrixGrabber {
  public MatrixGrabber() {
    mModelView = new float[16];
    mProjection = new float[16];
  }

  /**
   * Record the current modelView and projection matrix state. Has the side
   * effect of setting the current matrix state to GL_MODELVIEW
   * 
   * @param gl
   */
  public void getCurrentState(GL10 gl) {
    getCurrentProjection(gl);
    getCurrentModelView(gl);
  }

  /**
   * Record the current modelView matrix state. Has the side effect of setting
   * the current matrix state to GL_MODELVIEW
   * 
   * @param gl
   */
  public void getCurrentModelView(GL10 gl) {
    getMatrix(gl, GL10.GL_MODELVIEW, mModelView);
  }

  /**
   * Record the current projection matrix state. Has the side effect of
   * setting the current matrix state to GL_PROJECTION
   * 
   * @param gl
   */
  public void getCurrentProjection(GL10 gl) {
    getMatrix(gl, GL10.GL_PROJECTION, mProjection);
  }

  private void getMatrix(GL10 gl, int mode, float[] mat) {
    MatrixTrackingGL gl2 = (MatrixTrackingGL) gl;
    gl2.glMatrixMode(mode);
    gl2.getMatrix(mat, 0);
  }

  public float[] mModelView;
  public float[] mProjection;
}

/**
 * A matrix stack, similar to OpenGL ES's internal matrix stack.
 */
class MatrixStack {
  public MatrixStack() {
    commonInit(DEFAULT_MAX_DEPTH);
  }

  public MatrixStack(int maxDepth) {
    commonInit(maxDepth);
  }

  private void commonInit(int maxDepth) {
    mMatrix = new float[maxDepth * MATRIX_SIZE];
    mTemp = new float[MATRIX_SIZE * 2];
    glLoadIdentity();
  }

  public void glFrustumf(float left, float right, float bottom, float top,
      float near, float far) {
    Matrix.frustumM(mMatrix, mTop, left, right, bottom, top, near, far);
  }

  public void glFrustumx(int left, int right, int bottom, int top, int near,
      int far) {
    glFrustumf(fixedToFloat(left), fixedToFloat(right),
        fixedToFloat(bottom), fixedToFloat(top), fixedToFloat(near),
        fixedToFloat(far));
  }

  public void glLoadIdentity() {
    Matrix.setIdentityM(mMatrix, mTop);
  }

  public void glLoadMatrixf(float[] m, int offset) {
    System.arraycopy(m, offset, mMatrix, mTop, MATRIX_SIZE);
  }

  public void glLoadMatrixf(FloatBuffer m) {
    m.get(mMatrix, mTop, MATRIX_SIZE);
  }

  public void glLoadMatrixx(int[] m, int offset) {
    for (int i = 0; i < MATRIX_SIZE; i++) {
      mMatrix[mTop + i] = fixedToFloat(m[offset + i]);
    }
  }

  public void glLoadMatrixx(IntBuffer m) {
    for (int i = 0; i < MATRIX_SIZE; i++) {
      mMatrix[mTop + i] = fixedToFloat(m.get());
    }
  }

  public void glMultMatrixf(float[] m, int offset) {
    System.arraycopy(mMatrix, mTop, mTemp, 0, MATRIX_SIZE);
    Matrix.multiplyMM(mMatrix, mTop, mTemp, 0, m, offset);
  }

  public void glMultMatrixf(FloatBuffer m) {
    m.get(mTemp, MATRIX_SIZE, MATRIX_SIZE);
    glMultMatrixf(mTemp, MATRIX_SIZE);
  }

  public void glMultMatrixx(int[] m, int offset) {
    for (int i = 0; i < MATRIX_SIZE; i++) {
      mTemp[MATRIX_SIZE + i] = fixedToFloat(m[offset + i]);
    }
    glMultMatrixf(mTemp, MATRIX_SIZE);
  }

  public void glMultMatrixx(IntBuffer m) {
    for (int i = 0; i < MATRIX_SIZE; i++) {
      mTemp[MATRIX_SIZE + i] = fixedToFloat(m.get());
    }
    glMultMatrixf(mTemp, MATRIX_SIZE);
  }

  public void glOrthof(float left, float right, float bottom, float top,
      float near, float far) {
    Matrix.orthoM(mMatrix, mTop, left, right, bottom, top, near, far);
  }

  public void glOrthox(int left, int right, int bottom, int top, int near,
      int far) {
    glOrthof(fixedToFloat(left), fixedToFloat(right), fixedToFloat(bottom),
        fixedToFloat(top), fixedToFloat(near), fixedToFloat(far));
  }

  public void glPopMatrix() {
    preflight_adjust(-1);
    adjust(-1);
  }

  public void glPushMatrix() {
    preflight_adjust(1);
    System.arraycopy(mMatrix, mTop, mMatrix, mTop + MATRIX_SIZE,
        MATRIX_SIZE);
    adjust(1);
  }

  public void glRotatef(float angle, float x, float y, float z) {
    Matrix.setRotateM(mTemp, 0, angle, x, y, z);
    System.arraycopy(mMatrix, mTop, mTemp, MATRIX_SIZE, MATRIX_SIZE);
    Matrix.multiplyMM(mMatrix, mTop, mTemp, MATRIX_SIZE, mTemp, 0);
  }

  public void glRotatex(int angle, int x, int y, int z) {
    glRotatef(angle, fixedToFloat(x), fixedToFloat(y), fixedToFloat(z));
  }

  public void glScalef(float x, float y, float z) {
    Matrix.scaleM(mMatrix, mTop, x, y, z);
  }

  public void glScalex(int x, int y, int z) {
    glScalef(fixedToFloat(x), fixedToFloat(y), fixedToFloat(z));
  }

  public void glTranslatef(float x, float y, float z) {
    Matrix.translateM(mMatrix, mTop, x, y, z);
  }

  public void glTranslatex(int x, int y, int z) {
    glTranslatef(fixedToFloat(x), fixedToFloat(y), fixedToFloat(z));
  }

  public void getMatrix(float[] dest, int offset) {
    System.arraycopy(mMatrix, mTop, dest, offset, MATRIX_SIZE);
  }

  private float fixedToFloat(int x) {
    return x * (1.0f / 65536.0f);
  }

  private void preflight_adjust(int dir) {
    int newTop = mTop + dir * MATRIX_SIZE;
    if (newTop < 0) {
      throw new IllegalArgumentException("stack underflow");
    }
    if (newTop + MATRIX_SIZE > mMatrix.length) {
      throw new IllegalArgumentException("stack overflow");
    }
  }

  private void adjust(int dir) {
    mTop += dir * MATRIX_SIZE;
  }

  private final static int DEFAULT_MAX_DEPTH = 32;
  private final static int MATRIX_SIZE = 16;
  private float[] mMatrix;
  private int mTop;
  private float[] mTemp;
}

class NumericSprite {
  public NumericSprite() {
    mText = "";
    mLabelMaker = null;
  }

  public void initialize(GL10 gl, Paint paint) {
    int height = roundUpPower2((int) paint.getFontSpacing());
    final float interDigitGaps = 9 * 1.0f;
    int width = roundUpPower2((int) (interDigitGaps + paint
        .measureText(sStrike)));
    mLabelMaker = new LabelMaker(true, width, height);
    mLabelMaker.initialize(gl);
    mLabelMaker.beginAdding(gl);
    for (int i = 0; i < 10; i++) {
      String digit = sStrike.substring(i, i + 1);
      mLabelId[i] = mLabelMaker.add(gl, digit, paint);
      mWidth[i] = (int) Math.ceil(mLabelMaker.getWidth(i));
    }
    mLabelMaker.endAdding(gl);
  }

  public void shutdown(GL10 gl) {
    mLabelMaker.shutdown(gl);
    mLabelMaker = null;
  }

  /**
   * Find the smallest power of two >= the input value. (Doesn't work for
   * negative numbers.)
   */
  private int roundUpPower2(int x) {
    x = x - 1;
    x = x | (x >> 1);
    x = x | (x >> 2);
    x = x | (x >> 4);
    x = x | (x >> 8);
    x = x | (x >> 16);
    return x + 1;
  }

  public void setValue(int value) {
    mText = format(value);
  }

  public void draw(GL10 gl, float x, float y, float viewWidth,
      float viewHeight) {
    int length = mText.length();
    mLabelMaker.beginDrawing(gl, viewWidth, viewHeight);
    for (int i = 0; i < length; i++) {
      char c = mText.charAt(i);
      int digit = c - '0';
      mLabelMaker.draw(gl, x, y, mLabelId[digit]);
      x += mWidth[digit];
    }
    mLabelMaker.endDrawing(gl);
  }

  public float width() {
    float width = 0.0f;
    int length = mText.length();
    for (int i = 0; i < length; i++) {
      char c = mText.charAt(i);
      width += mWidth[c - '0'];
    }
    return width;
  }

  private String format(int value) {
    return Integer.toString(value);
  }

  private LabelMaker mLabelMaker;
  private String mText;
  private int[] mWidth = new int[10];
  private int[] mLabelId = new int[10];
  private final static String sStrike = "0123456789";
}

public class SpriteTextActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mGLSurfaceView = new GLSurfaceView(this);
    mGLSurfaceView.setGLWrapper(new GLSurfaceView.GLWrapper() {
      public GL wrap(GL gl) {
        return new MatrixTrackingGL(gl);
      }
    });
    mGLSurfaceView.setRenderer(new SpriteTextRenderer(this));
    setContentView(mGLSurfaceView);
  }

  @Override
  protected void onPause() {
    super.onPause();
    mGLSurfaceView.onPause();
  }

  @Override
  protected void onResume() {
    super.onResume();
    mGLSurfaceView.onResume();
  }

  private GLSurfaceView mGLSurfaceView;
}

OpenGL objects

package com.example.android.apis.graphics.kube;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;

public class Kube extends Activity implements KubeRenderer.AnimationCallback {

  private GLWorld makeGLWorld() {
    GLWorld world = new GLWorld();

    int one = 0x10000;
    int half = 0x08000;
    GLColor red = new GLColor(one, 0, 0);
    GLColor green = new GLColor(0, one, 0);
    GLColor blue = new GLColor(0, 0, one);
    GLColor yellow = new GLColor(one, one, 0);
    GLColor orange = new GLColor(one, half, 0);
    GLColor white = new GLColor(one, one, one);
    GLColor black = new GLColor(0, 0, 0);

    // coordinates for our cubes
    float c0 = -1.0f;
    float c1 = -0.38f;
    float c2 = -0.32f;
    float c3 = 0.32f;
    float c4 = 0.38f;
    float c5 = 1.0f;

    // top back, left to right
    mCubes[0] = new Cube(world, c0, c4, c0, c1, c5, c1);
    mCubes[1] = new Cube(world, c2, c4, c0, c3, c5, c1);
    mCubes[2] = new Cube(world, c4, c4, c0, c5, c5, c1);
    // top middle, left to right
    mCubes[3] = new Cube(world, c0, c4, c2, c1, c5, c3);
    mCubes[4] = new Cube(world, c2, c4, c2, c3, c5, c3);
    mCubes[5] = new Cube(world, c4, c4, c2, c5, c5, c3);
    // top front, left to right
    mCubes[6] = new Cube(world, c0, c4, c4, c1, c5, c5);
    mCubes[7] = new Cube(world, c2, c4, c4, c3, c5, c5);
    mCubes[8] = new Cube(world, c4, c4, c4, c5, c5, c5);
    // middle back, left to right
    mCubes[9] = new Cube(world, c0, c2, c0, c1, c3, c1);
    mCubes[10] = new Cube(world, c2, c2, c0, c3, c3, c1);
    mCubes[11] = new Cube(world, c4, c2, c0, c5, c3, c1);
    // middle middle, left to right
    mCubes[12] = new Cube(world, c0, c2, c2, c1, c3, c3);
    mCubes[13] = null;
    mCubes[14] = new Cube(world, c4, c2, c2, c5, c3, c3);
    // middle front, left to right
    mCubes[15] = new Cube(world, c0, c2, c4, c1, c3, c5);
    mCubes[16] = new Cube(world, c2, c2, c4, c3, c3, c5);
    mCubes[17] = new Cube(world, c4, c2, c4, c5, c3, c5);
    // bottom back, left to right
    mCubes[18] = new Cube(world, c0, c0, c0, c1, c1, c1);
    mCubes[19] = new Cube(world, c2, c0, c0, c3, c1, c1);
    mCubes[20] = new Cube(world, c4, c0, c0, c5, c1, c1);
    // bottom middle, left to right
    mCubes[21] = new Cube(world, c0, c0, c2, c1, c1, c3);
    mCubes[22] = new Cube(world, c2, c0, c2, c3, c1, c3);
    mCubes[23] = new Cube(world, c4, c0, c2, c5, c1, c3);
    // bottom front, left to right
    mCubes[24] = new Cube(world, c0, c0, c4, c1, c1, c5);
    mCubes[25] = new Cube(world, c2, c0, c4, c3, c1, c5);
    mCubes[26] = new Cube(world, c4, c0, c4, c5, c1, c5);

    // paint the sides
    int i, j;
    // set all faces black by default
    for (i = 0; i < 27; i++) {
      Cube cube = mCubes[i];
      if (cube != null) {
        for (j = 0; j < 6; j++)
          cube.setFaceColor(j, black);
      }
    }

    // paint top
    for (i = 0; i < 9; i++)
      mCubes[i].setFaceColor(Cube.kTop, orange);
    // paint bottom
    for (i = 18; i < 27; i++)
      mCubes[i].setFaceColor(Cube.kBottom, red);
    // paint left
    for (i = 0; i < 27; i += 3)
      mCubes[i].setFaceColor(Cube.kLeft, yellow);
    // paint right
    for (i = 2; i < 27; i += 3)
      mCubes[i].setFaceColor(Cube.kRight, white);
    // paint back
    for (i = 0; i < 27; i += 9)
      for (j = 0; j < 3; j++)
        mCubes[i + j].setFaceColor(Cube.kBack, blue);
    // paint front
    for (i = 6; i < 27; i += 9)
      for (j = 0; j < 3; j++)
        mCubes[i + j].setFaceColor(Cube.kFront, green);

    for (i = 0; i < 27; i++)
      if (mCubes[i] != null)
        world.addShape(mCubes[i]);

    // initialize our permutation to solved position
    mPermutation = new int[27];
    for (i = 0; i < mPermutation.length; i++)
      mPermutation[i] = i;

    createLayers();
    updateLayers();

    world.generate();

    return world;
  }

  private void createLayers() {
    mLayers[kUp] = new Layer(Layer.kAxisY);
    mLayers[kDown] = new Layer(Layer.kAxisY);
    mLayers[kLeft] = new Layer(Layer.kAxisX);
    mLayers[kRight] = new Layer(Layer.kAxisX);
    mLayers[kFront] = new Layer(Layer.kAxisZ);
    mLayers[kBack] = new Layer(Layer.kAxisZ);
    mLayers[kMiddle] = new Layer(Layer.kAxisX);
    mLayers[kEquator] = new Layer(Layer.kAxisY);
    mLayers[kSide] = new Layer(Layer.kAxisZ);
  }

  private void updateLayers() {
    Layer layer;
    GLShape[] shapes;
    int i, j, k;

    // up layer
    layer = mLayers[kUp];
    shapes = layer.mShapes;
    for (i = 0; i < 9; i++)
      shapes[i] = mCubes[mPermutation[i]];

    // down layer
    layer = mLayers[kDown];
    shapes = layer.mShapes;
    for (i = 18, k = 0; i < 27; i++)
      shapes[k++] = mCubes[mPermutation[i]];

    // left layer
    layer = mLayers[kLeft];
    shapes = layer.mShapes;
    for (i = 0, k = 0; i < 27; i += 9)
      for (j = 0; j < 9; j += 3)
        shapes[k++] = mCubes[mPermutation[i + j]];

    // right layer
    layer = mLayers[kRight];
    shapes = layer.mShapes;
    for (i = 2, k = 0; i < 27; i += 9)
      for (j = 0; j < 9; j += 3)
        shapes[k++] = mCubes[mPermutation[i + j]];

    // front layer
    layer = mLayers[kFront];
    shapes = layer.mShapes;
    for (i = 6, k = 0; i < 27; i += 9)
      for (j = 0; j < 3; j++)
        shapes[k++] = mCubes[mPermutation[i + j]];

    // back layer
    layer = mLayers[kBack];
    shapes = layer.mShapes;
    for (i = 0, k = 0; i < 27; i += 9)
      for (j = 0; j < 3; j++)
        shapes[k++] = mCubes[mPermutation[i + j]];

    // middle layer
    layer = mLayers[kMiddle];
    shapes = layer.mShapes;
    for (i = 1, k = 0; i < 27; i += 9)
      for (j = 0; j < 9; j += 3)
        shapes[k++] = mCubes[mPermutation[i + j]];

    // equator layer
    layer = mLayers[kEquator];
    shapes = layer.mShapes;
    for (i = 9, k = 0; i < 18; i++)
      shapes[k++] = mCubes[mPermutation[i]];

    // side layer
    layer = mLayers[kSide];
    shapes = layer.mShapes;
    for (i = 3, k = 0; i < 27; i += 9)
      for (j = 0; j < 3; j++)
        shapes[k++] = mCubes[mPermutation[i + j]];
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // We don't need a title either.
    requestWindowFeature(Window.FEATURE_NO_TITLE);

    mView = new GLSurfaceView(getApplication());
    mRenderer = new KubeRenderer(makeGLWorld(), this);
    mView.setRenderer(mRenderer);
    setContentView(mView);
  }

  @Override
  protected void onResume() {
    super.onResume();
    mView.onResume();
  }

  @Override
  protected void onPause() {
    super.onPause();
    mView.onPause();
  }

  public void animate() {
    // change our angle of view
    mRenderer.setAngle(mRenderer.getAngle() + 1.2f);

    if (mCurrentLayer == null) {
      int layerID = mRandom.nextInt(9);
      mCurrentLayer = mLayers[layerID];
      mCurrentLayerPermutation = mLayerPermutations[layerID];
      mCurrentLayer.startAnimation();
      boolean direction = mRandom.nextBoolean();
      int count = mRandom.nextInt(3) + 1;

      count = 1;
      direction = false;
      mCurrentAngle = 0;
      if (direction) {
        mAngleIncrement = (float) Math.PI / 50;
        mEndAngle = mCurrentAngle + ((float) Math.PI * count) / 2f;
      } else {
        mAngleIncrement = -(float) Math.PI / 50;
        mEndAngle = mCurrentAngle - ((float) Math.PI * count) / 2f;
      }
    }

    mCurrentAngle += mAngleIncrement;

    if ((mAngleIncrement > 0f && mCurrentAngle >= mEndAngle)
        || (mAngleIncrement < 0f && mCurrentAngle <= mEndAngle)) {
      mCurrentLayer.setAngle(mEndAngle);
      mCurrentLayer.endAnimation();
      mCurrentLayer = null;

      // adjust mPermutation based on the completed layer rotation
      int[] newPermutation = new int[27];
      for (int i = 0; i < 27; i++) {
        newPermutation[i] = mPermutation[mCurrentLayerPermutation[i]];
        // newPermutation[i] =
        // mCurrentLayerPermutation[mPermutation[i]];
      }
      mPermutation = newPermutation;
      updateLayers();

    } else {
      mCurrentLayer.setAngle(mCurrentAngle);
    }
  }

  GLSurfaceView mView;
  KubeRenderer mRenderer;
  Cube[] mCubes = new Cube[27];
  // a Layer for each possible move
  Layer[] mLayers = new Layer[9];
  // permutations corresponding to a pi/2 rotation of each layer about its
  // axis
  static int[][] mLayerPermutations = {
      // permutation for UP layer
      { 2, 5, 8, 1, 4, 7, 0, 3, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
          19, 20, 21, 22, 23, 24, 25, 26 },
      // permutation for DOWN layer
      { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20,
          23, 26, 19, 22, 25, 18, 21, 24 },
      // permutation for LEFT layer
      { 6, 1, 2, 15, 4, 5, 24, 7, 8, 3, 10, 11, 12, 13, 14, 21, 16, 17,
          0, 19, 20, 9, 22, 23, 18, 25, 26 },
      // permutation for RIGHT layer
      { 0, 1, 8, 3, 4, 17, 6, 7, 26, 9, 10, 5, 12, 13, 14, 15, 16, 23,
          18, 19, 2, 21, 22, 11, 24, 25, 20 },
      // permutation for FRONT layer
      { 0, 1, 2, 3, 4, 5, 24, 15, 6, 9, 10, 11, 12, 13, 14, 25, 16, 7,
          18, 19, 20, 21, 22, 23, 26, 17, 8 },
      // permutation for BACK layer
      { 18, 9, 0, 3, 4, 5, 6, 7, 8, 19, 10, 1, 12, 13, 14, 15, 16, 17,
          20, 11, 2, 21, 22, 23, 24, 25, 26 },
      // permutation for MIDDLE layer
      { 0, 7, 2, 3, 16, 5, 6, 25, 8, 9, 4, 11, 12, 13, 14, 15, 22, 17,
          18, 1, 20, 21, 10, 23, 24, 19, 26 },
      // permutation for EQUATOR layer
      { 0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 14, 17, 10, 13, 16, 9, 12, 15, 18,
          19, 20, 21, 22, 23, 24, 25, 26 },
      // permutation for SIDE layer
      { 0, 1, 2, 21, 12, 3, 6, 7, 8, 9, 10, 11, 22, 13, 4, 15, 16, 17,
          18, 19, 20, 23, 14, 5, 24, 25, 26 } };

  // current permutation of starting position
  int[] mPermutation;

  // for random cube movements
  Random mRandom = new Random(System.currentTimeMillis());
  // currently turning layer
  Layer mCurrentLayer = null;
  // current and final angle for current Layer animation
  float mCurrentAngle, mEndAngle;
  // amount to increment angle
  float mAngleIncrement;
  int[] mCurrentLayerPermutation;

  // names for our 9 layers (based on notation from
  // http://www.cubefreak.net/notation.html)
  static final int kUp = 0;
  static final int kDown = 1;
  static final int kLeft = 2;
  static final int kRight = 3;
  static final int kFront = 4;
  static final int kBack = 5;
  static final int kMiddle = 6;
  static final int kEquator = 7;
  static final int kSide = 8;

}

class Cube extends GLShape {

  public Cube(GLWorld world, float left, float bottom, float back,
      float right, float top, float front) {
    super(world);
    GLVertex leftBottomBack = addVertex(left, bottom, back);
    GLVertex rightBottomBack = addVertex(right, bottom, back);
    GLVertex leftTopBack = addVertex(left, top, back);
    GLVertex rightTopBack = addVertex(right, top, back);
    GLVertex leftBottomFront = addVertex(left, bottom, front);
    GLVertex rightBottomFront = addVertex(right, bottom, front);
    GLVertex leftTopFront = addVertex(left, top, front);
    GLVertex rightTopFront = addVertex(right, top, front);

    // vertices are added in a clockwise orientation (when viewed from the
    // outside)
    // bottom
    addFace(new GLFace(leftBottomBack, leftBottomFront, rightBottomFront,
        rightBottomBack));
    // front
    addFace(new GLFace(leftBottomFront, leftTopFront, rightTopFront,
        rightBottomFront));
    // left
    addFace(new GLFace(leftBottomBack, leftTopBack, leftTopFront,
        leftBottomFront));
    // right
    addFace(new GLFace(rightBottomBack, rightBottomFront, rightTopFront,
        rightTopBack));
    // back
    addFace(new GLFace(leftBottomBack, rightBottomBack, rightTopBack,
        leftTopBack));
    // top
    addFace(new GLFace(leftTopBack, rightTopBack, rightTopFront,
        leftTopFront));

  }

  public static final int kBottom = 0;
  public static final int kFront = 1;
  public static final int kLeft = 2;
  public static final int kRight = 3;
  public static final int kBack = 4;
  public static final int kTop = 5;

}

class GLColor {

  public final int red;
  public final int green;
  public final int blue;
  public final int alpha;

  public GLColor(int red, int green, int blue, int alpha) {
    this.red = red;
    this.green = green;
    this.blue = blue;
    this.alpha = alpha;
  }

  public GLColor(int red, int green, int blue) {
    this.red = red;
    this.green = green;
    this.blue = blue;
    this.alpha = 0x10000;
  }

  @Override
  public boolean equals(Object other) {
    if (other instanceof GLColor) {
      GLColor color = (GLColor) other;
      return (red == color.red && green == color.green
          && blue == color.blue && alpha == color.alpha);
    }
    return false;
  }
}

class GLFace {

  public GLFace() {

  }

  // for triangles
  public GLFace(GLVertex v1, GLVertex v2, GLVertex v3) {
    addVertex(v1);
    addVertex(v2);
    addVertex(v3);
  }

  // for quadrilaterals
  public GLFace(GLVertex v1, GLVertex v2, GLVertex v3, GLVertex v4) {
    addVertex(v1);
    addVertex(v2);
    addVertex(v3);
    addVertex(v4);
  }

  public void addVertex(GLVertex v) {
    mVertexList.add(v);
  }

  // must be called after all vertices are added
  public void setColor(GLColor c) {

    int last = mVertexList.size() - 1;
    if (last < 2) {
      Log.e("GLFace", "not enough vertices in setColor()");
    } else {
      GLVertex vertex = mVertexList.get(last);

      // only need to do this if the color has never been set
      if (mColor == null) {
        while (vertex.color != null) {
          mVertexList.add(0, vertex);
          mVertexList.remove(last + 1);
          vertex = mVertexList.get(last);
        }
      }

      vertex.color = c;
    }

    mColor = c;
  }

  public int getIndexCount() {
    return (mVertexList.size() - 2) * 3;
  }

  public void putIndices(ShortBuffer buffer) {
    int last = mVertexList.size() - 1;

    GLVertex v0 = mVertexList.get(0);
    GLVertex vn = mVertexList.get(last);

    // push triangles into the buffer
    for (int i = 1; i < last; i++) {
      GLVertex v1 = mVertexList.get(i);
      buffer.put(v0.index);
      buffer.put(v1.index);
      buffer.put(vn.index);
      v0 = v1;
    }
  }

  private ArrayList<GLVertex> mVertexList = new ArrayList<GLVertex>();
  private GLColor mColor;
}

class GLShape {

  public GLShape(GLWorld world) {
    mWorld = world;
  }

  public void addFace(GLFace face) {
    mFaceList.add(face);
  }

  public void setFaceColor(int face, GLColor color) {
    mFaceList.get(face).setColor(color);
  }

  public void putIndices(ShortBuffer buffer) {
    Iterator<GLFace> iter = mFaceList.iterator();
    while (iter.hasNext()) {
      GLFace face = iter.next();
      face.putIndices(buffer);
    }
  }

  public int getIndexCount() {
    int count = 0;
    Iterator<GLFace> iter = mFaceList.iterator();
    while (iter.hasNext()) {
      GLFace face = iter.next();
      count += face.getIndexCount();
    }
    return count;
  }

  public GLVertex addVertex(float x, float y, float z) {

    // look for an existing GLVertex first
    Iterator<GLVertex> iter = mVertexList.iterator();
    while (iter.hasNext()) {
      GLVertex vertex = iter.next();
      if (vertex.x == x && vertex.y == y && vertex.z == z) {
        return vertex;
      }
    }

    // doesn't exist, so create new vertex
    GLVertex vertex = mWorld.addVertex(x, y, z);
    mVertexList.add(vertex);
    return vertex;
  }

  public void animateTransform(M4 transform) {
    mAnimateTransform = transform;

    if (mTransform != null)
      transform = mTransform.multiply(transform);

    Iterator<GLVertex> iter = mVertexList.iterator();
    while (iter.hasNext()) {
      GLVertex vertex = iter.next();
      mWorld.transformVertex(vertex, transform);
    }
  }

  public void startAnimation() {
  }

  public void endAnimation() {
    if (mTransform == null) {
      mTransform = new M4(mAnimateTransform);
    } else {
      mTransform = mTransform.multiply(mAnimateTransform);
    }
  }

  public M4 mTransform;
  public M4 mAnimateTransform;
  protected ArrayList<GLFace> mFaceList = new ArrayList<GLFace>();
  protected ArrayList<GLVertex> mVertexList = new ArrayList<GLVertex>();
  protected ArrayList<Integer> mIndexList = new ArrayList<Integer>(); // make
                                    // more
                                    // efficient?
  protected GLWorld mWorld;
}

class GLVertex {

  public float x;
  public float y;
  public float z;
  final short index; // index in vertex table
  GLColor color;

  GLVertex() {
    this.x = 0;
    this.y = 0;
    this.z = 0;
    this.index = -1;
  }

  GLVertex(float x, float y, float z, int index) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.index = (short) index;
  }

  @Override
  public boolean equals(Object other) {
    if (other instanceof GLVertex) {
      GLVertex v = (GLVertex) other;
      return (x == v.x && y == v.y && z == v.z);
    }
    return false;
  }

  static public int toFixed(float x) {
    return (int) (x * 65536.0f);
  }

  public void put(IntBuffer vertexBuffer, IntBuffer colorBuffer) {
    vertexBuffer.put(toFixed(x));
    vertexBuffer.put(toFixed(y));
    vertexBuffer.put(toFixed(z));
    if (color == null) {
      colorBuffer.put(0);
      colorBuffer.put(0);
      colorBuffer.put(0);
      colorBuffer.put(0);
    } else {
      colorBuffer.put(color.red);
      colorBuffer.put(color.green);
      colorBuffer.put(color.blue);
      colorBuffer.put(color.alpha);
    }
  }

  public void update(IntBuffer vertexBuffer, M4 transform) {
    // skip to location of vertex in mVertex buffer
    vertexBuffer.position(index * 3);

    if (transform == null) {
      vertexBuffer.put(toFixed(x));
      vertexBuffer.put(toFixed(y));
      vertexBuffer.put(toFixed(z));
    } else {
      GLVertex temp = new GLVertex();
      transform.multiply(this, temp);
      vertexBuffer.put(toFixed(temp.x));
      vertexBuffer.put(toFixed(temp.y));
      vertexBuffer.put(toFixed(temp.z));
    }
  }
}

class GLWorld {

  public void addShape(GLShape shape) {
    mShapeList.add(shape);
    mIndexCount += shape.getIndexCount();
  }

  public void generate() {
    ByteBuffer bb = ByteBuffer.allocateDirect(mVertexList.size() * 4 * 4);
    bb.order(ByteOrder.nativeOrder());
    mColorBuffer = bb.asIntBuffer();

    bb = ByteBuffer.allocateDirect(mVertexList.size() * 4 * 3);
    bb.order(ByteOrder.nativeOrder());
    mVertexBuffer = bb.asIntBuffer();

    bb = ByteBuffer.allocateDirect(mIndexCount * 2);
    bb.order(ByteOrder.nativeOrder());
    mIndexBuffer = bb.asShortBuffer();

    Iterator<GLVertex> iter2 = mVertexList.iterator();
    while (iter2.hasNext()) {
      GLVertex vertex = iter2.next();
      vertex.put(mVertexBuffer, mColorBuffer);
    }

    Iterator<GLShape> iter3 = mShapeList.iterator();
    while (iter3.hasNext()) {
      GLShape shape = iter3.next();
      shape.putIndices(mIndexBuffer);
    }
  }

  public GLVertex addVertex(float x, float y, float z) {
    GLVertex vertex = new GLVertex(x, y, z, mVertexList.size());
    mVertexList.add(vertex);
    return vertex;
  }

  public void transformVertex(GLVertex vertex, M4 transform) {
    vertex.update(mVertexBuffer, transform);
  }

  int count = 0;

  public void draw(GL10 gl) {
    mColorBuffer.position(0);
    mVertexBuffer.position(0);
    mIndexBuffer.position(0);

    gl.glFrontFace(GL10.GL_CW);
    gl.glShadeModel(GL10.GL_FLAT);
    gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
    gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
    gl.glDrawElements(GL10.GL_TRIANGLES, mIndexCount,
        GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
    count++;
  }

  static public float toFloat(int x) {
    return x / 65536.0f;
  }

  private ArrayList<GLShape> mShapeList = new ArrayList<GLShape>();
  private ArrayList<GLVertex> mVertexList = new ArrayList<GLVertex>();

  private int mIndexCount = 0;

  private IntBuffer mVertexBuffer;
  private IntBuffer mColorBuffer;
  private ShortBuffer mIndexBuffer;
}

/**
 * Example of how to use OpenGL|ES in a custom view
 * 
 */
class KubeRenderer implements GLSurfaceView.Renderer {
  public interface AnimationCallback {
    void animate();
  }

  public KubeRenderer(GLWorld world, AnimationCallback callback) {
    mWorld = world;
    mCallback = callback;
  }

  public void onDrawFrame(GL10 gl) {
    if (mCallback != null) {
      mCallback.animate();
    }

    /*
     * Usually, the first thing one might want to do is to clear the screen.
     * The most efficient way of doing this is to use glClear(). However we
     * must make sure to set the scissor correctly first. The scissor is
     * always specified in window coordinates:
     */

    gl.glClearColor(0.5f, 0.5f, 0.5f, 1);
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

    /*
     * Now we're ready to draw some 3D object
     */

    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();
    gl.glTranslatef(0, 0, -3.0f);
    gl.glScalef(0.5f, 0.5f, 0.5f);
    gl.glRotatef(mAngle, 0, 1, 0);
    gl.glRotatef(mAngle * 0.25f, 1, 0, 0);

    gl.glColor4f(0.7f, 0.7f, 0.7f, 1.0f);
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
    gl.glEnable(GL10.GL_CULL_FACE);
    gl.glShadeModel(GL10.GL_SMOOTH);
    gl.glEnable(GL10.GL_DEPTH_TEST);

    mWorld.draw(gl);
  }

  public void onSurfaceChanged(GL10 gl, int width, int height) {
    gl.glViewport(0, 0, width, height);

    /*
     * Set our projection matrix. This doesn't have to be done each time we
     * draw, but usually a new projection needs to be set when the viewport
     * is resized.
     */

    float ratio = (float) width / height;
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadIdentity();
    gl.glFrustumf(-ratio, ratio, -1, 1, 2, 12);

    /*
     * By default, OpenGL enables features that improve quality but reduce
     * performance. One might want to tweak that especially on software
     * renderer.
     */
    gl.glDisable(GL10.GL_DITHER);
    gl.glActiveTexture(GL10.GL_TEXTURE0);
  }

  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // Nothing special, don't have any textures we need to recreate.
  }

  public void setAngle(float angle) {
    mAngle = angle;
  }

  public float getAngle() {
    return mAngle;
  }

  private GLWorld mWorld;
  private AnimationCallback mCallback;
  private float mAngle;
}

class Layer {

  public Layer(int axis) {
    // start with identity matrix for transformation
    mAxis = axis;
    mTransform.setIdentity();
  }

  public void startAnimation() {
    for (int i = 0; i < mShapes.length; i++) {
      GLShape shape = mShapes[i];
      if (shape != null) {
        shape.startAnimation();
      }
    }
  }

  public void endAnimation() {
    for (int i = 0; i < mShapes.length; i++) {
      GLShape shape = mShapes[i];
      if (shape != null) {
        shape.endAnimation();
      }
    }
  }

  public void setAngle(float angle) {
    // normalize the angle
    float twopi = (float) Math.PI * 2f;
    while (angle >= twopi)
      angle -= twopi;
    while (angle < 0f)
      angle += twopi;
    // mAngle = angle;

    float sin = (float) Math.sin(angle);
    float cos = (float) Math.cos(angle);

    float[][] m = mTransform.m;
    switch (mAxis) {
    case kAxisX:
      m[1][1] = cos;
      m[1][2] = sin;
      m[2][1] = -sin;
      m[2][2] = cos;
      m[0][0] = 1f;
      m[0][1] = m[0][2] = m[1][0] = m[2][0] = 0f;
      break;
    case kAxisY:
      m[0][0] = cos;
      m[0][2] = sin;
      m[2][0] = -sin;
      m[2][2] = cos;
      m[1][1] = 1f;
      m[0][1] = m[1][0] = m[1][2] = m[2][1] = 0f;
      break;
    case kAxisZ:
      m[0][0] = cos;
      m[0][1] = sin;
      m[1][0] = -sin;
      m[1][1] = cos;
      m[2][2] = 1f;
      m[2][0] = m[2][1] = m[0][2] = m[1][2] = 0f;
      break;
    }

    for (int i = 0; i < mShapes.length; i++) {
      GLShape shape = mShapes[i];
      if (shape != null) {
        shape.animateTransform(mTransform);
      }
    }
  }

  GLShape[] mShapes = new GLShape[9];
  M4 mTransform = new M4();
  // float mAngle;

  // which axis do we rotate around?
  // 0 for X, 1 for Y, 2 for Z
  int mAxis;
  static public final int kAxisX = 0;
  static public final int kAxisY = 1;
  static public final int kAxisZ = 2;
}

class M4 {
  public float[][] m = new float[4][4];

  public M4() {
  }

  public M4(M4 other) {
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        m[i][j] = other.m[i][j];
      }
    }
  }

  public void multiply(GLVertex src, GLVertex dest) {
    dest.x = src.x * m[0][0] + src.y * m[1][0] + src.z * m[2][0] + m[3][0];
    dest.y = src.x * m[0][1] + src.y * m[1][1] + src.z * m[2][1] + m[3][1];
    dest.z = src.x * m[0][2] + src.y * m[1][2] + src.z * m[2][2] + m[3][2];
  }

  public M4 multiply(M4 other) {
    M4 result = new M4();
    float[][] m1 = m;
    float[][] m2 = other.m;

    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        result.m[i][j] = m1[i][0] * m2[0][j] + m1[i][1] * m2[1][j]
            + m1[i][2] * m2[2][j] + m1[i][3] * m2[3][j];
      }
    }

    return result;
  }

  public void setIdentity() {
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        m[i][j] = (i == j ? 1f : 0f);
      }
    }
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder("[ ");
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        builder.append(m[i][j]);
        builder.append(" ");
      }
      if (i < 2)
        builder.append("\n  ");
    }
    builder.append(" ]");
    return builder.toString();
  }
}

   

Demonstrate how to use the OES_texture_cube_map extension, available on some high-end OpenGL ES 1.x GPUs.

package app.test;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;
import javax.microedition.khronos.opengles.GL11ExtensionPack;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.os.Bundle;
import android.util.Log;


/**
 * Demonstrate how to use the OES_texture_cube_map extension, available on some
 * high-end OpenGL ES 1.x GPUs.
 */
public class CubeMapActivity extends Activity {
    private GLSurfaceView mGLSurfaceView;
    private class Renderer implements GLSurfaceView.Renderer {
        private boolean mContextSupportsCubeMap;
        private Grid mGrid;
        private int mCubeMapTextureID;
        private boolean mUseTexGen = false;
        private float mAngle;

        public void onDrawFrame(GL10 gl) {
            checkGLError(gl);
            if (mContextSupportsCubeMap) {
                gl.glClearColor(0,0,1,0);
            } else {
                // Current context doesn't support cube maps.
                // Indicate this by drawing a red background.
                gl.glClearColor(1,0,0,0);
            }
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
            gl.glEnable(GL10.GL_DEPTH_TEST);
            gl.glMatrixMode(GL10.GL_MODELVIEW);
            gl.glLoadIdentity();

            GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
            gl.glRotatef(mAngle,        0, 1, 0);
            gl.glRotatef(mAngle*0.25f,  1, 0, 0);

            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

            checkGLError(gl);

            if (mContextSupportsCubeMap) {
                gl.glActiveTexture(GL10.GL_TEXTURE0);
                checkGLError(gl);
                gl.glEnable(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP);
                checkGLError(gl);
                gl.glBindTexture(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, mCubeMapTextureID);
                checkGLError(gl);
                GL11ExtensionPack gl11ep = (GL11ExtensionPack) gl;
                gl11ep.glTexGeni(GL11ExtensionPack.GL_TEXTURE_GEN_STR,
                        GL11ExtensionPack.GL_TEXTURE_GEN_MODE,
                        GL11ExtensionPack.GL_REFLECTION_MAP);
                checkGLError(gl);
                gl.glEnable(GL11ExtensionPack.GL_TEXTURE_GEN_STR);
                checkGLError(gl);
                gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_DECAL);
            }

            checkGLError(gl);
            mGrid.draw(gl);

            if (mContextSupportsCubeMap) {
                gl.glDisable(GL11ExtensionPack.GL_TEXTURE_GEN_STR);
            }
            checkGLError(gl);

            mAngle += 1.2f;
        }

        public void onSurfaceChanged(GL10 gl, int width, int height) {
            checkGLError(gl);
            gl.glViewport(0, 0, width, height);
            float ratio = (float) width / height;
            gl.glMatrixMode(GL10.GL_PROJECTION);
            gl.glLoadIdentity();
            gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
            checkGLError(gl);
        }

        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            checkGLError(gl);
            // This test needs to be done each time a context is created,
            // because different contexts may support different extensions.
            mContextSupportsCubeMap = checkIfContextSupportsCubeMap(gl);

            mGrid = generateTorusGrid(gl, 60, 60, 3.0f, 0.75f);

            if (mContextSupportsCubeMap) {
                int[] cubeMapResourceIds = new int[]{
                        R.raw.skycubemap0, R.raw.skycubemap1, R.raw.skycubemap2,
                        R.raw.skycubemap3, R.raw.skycubemap4, R.raw.skycubemap5};//jpg files
                mCubeMapTextureID = generateCubeMap(gl, cubeMapResourceIds);
            }
            checkGLError(gl);
        }

        private int generateCubeMap(GL10 gl, int[] resourceIds) {
            checkGLError(gl);
            int[] ids = new int[1];
            gl.glGenTextures(1, ids, 0);
            int cubeMapTextureId = ids[0];
            gl.glBindTexture(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, cubeMapTextureId);
            gl.glTexParameterf(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP,
                    GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
            gl.glTexParameterf(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP,
                    GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

            for (int face = 0; face < 6; face++) {
                InputStream is = getResources().openRawResource(resourceIds[face]);
                Bitmap bitmap;
                try {
                    bitmap = BitmapFactory.decodeStream(is);
                } finally {
                    try {
                        is.close();
                    } catch(IOException e) {
                        Log.e("CubeMap", "Could not decode texture for face " + Integer.toString(face));
                    }
                }
                GLUtils.texImage2D(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0,
                        bitmap, 0);
                bitmap.recycle();
            }
            checkGLError(gl);
            return cubeMapTextureId;
        }

        private Grid generateTorusGrid(GL gl, int uSteps, int vSteps, float majorRadius, float minorRadius) {
            Grid grid = new Grid(uSteps + 1, vSteps + 1);
            for (int j = 0; j <= vSteps; j++) {
                double angleV = Math.PI * 2 * j / vSteps;
                float cosV = (float) Math.cos(angleV);
                float sinV = (float) Math.sin(angleV);
                for (int i = 0; i <= uSteps; i++) {
                    double angleU = Math.PI * 2 * i / uSteps;
                    float cosU = (float) Math.cos(angleU);
                    float sinU = (float) Math.sin(angleU);
                    float d = majorRadius+minorRadius*cosU;
                    float x = d*cosV;
                    float y = d*(-sinV);
                    float z = minorRadius * sinU;

                    float nx = cosV * cosU;
                    float ny = -sinV * cosU;
                    float nz = sinU;

                    float length = (float) Math.sqrt(nx*nx + ny*ny + nz*nz);
                    nx /= length;
                    ny /= length;
                    nz /= length;

                    grid.set(i, j, x, y, z, nx, ny, nz);
                }
            }
            grid.createBufferObjects(gl);
            return grid;
        }

        private boolean checkIfContextSupportsCubeMap(GL10 gl) {
            return checkIfContextSupportsExtension(gl, "GL_OES_texture_cube_map");
        }

        /**
         * This is not the fastest way to check for an extension, but fine if
         * we are only checking for a few extensions each time a context is created.
         * @param gl
         * @param extension
         * @return true if the extension is present in the current context.
         */
        private boolean checkIfContextSupportsExtension(GL10 gl, String extension) {
            String extensions = " " + gl.glGetString(GL10.GL_EXTENSIONS) + " ";
            // The extensions string is padded with spaces between extensions, but not
            // necessarily at the beginning or end. For simplicity, add spaces at the
            // beginning and end of the extensions string and the extension string.
            // This means we can avoid special-case checks for the first or last
            // extension, as well as avoid special-case checks when an extension name
            // is the same as the first part of another extension name.
            return extensions.indexOf(" " + extension + " ") >= 0;
        }
    }

    /** A grid is a topologically rectangular array of vertices.
     *
     * This grid class is customized for the vertex data required for this
     * example.
     *
     * The vertex and index data are held in VBO objects because on most
     * GPUs VBO objects are the fastest way of rendering static vertex
     * and index data.
     *
     */

    private static class Grid {
        // Size of vertex data elements in bytes:
        final static int FLOAT_SIZE = 4;
        final static int CHAR_SIZE = 2;

        // Vertex structure:
        // float x, y, z;
        // float nx, ny, nx;

        final static int VERTEX_SIZE = 6 * FLOAT_SIZE;
        final static int VERTEX_NORMAL_BUFFER_INDEX_OFFSET = 3;

        private int mVertexBufferObjectId;
        private int mElementBufferObjectId;

        // These buffers are used to hold the vertex and index data while
        // constructing the grid. Once createBufferObjects() is called
        // the buffers are nulled out to save memory.

        private ByteBuffer mVertexByteBuffer;
        private FloatBuffer mVertexBuffer;
        private CharBuffer mIndexBuffer;

        private int mW;
        private int mH;
        private int mIndexCount;

        public Grid(int w, int h) {
            if (w < 0 || w >= 65536) {
                throw new IllegalArgumentException("w");
            }
            if (h < 0 || h >= 65536) {
                throw new IllegalArgumentException("h");
            }
            if (w * h >= 65536) {
                throw new IllegalArgumentException("w * h >= 65536");
            }

            mW = w;
            mH = h;
            int size = w * h;

            mVertexByteBuffer = ByteBuffer.allocateDirect(VERTEX_SIZE * size)
            .order(ByteOrder.nativeOrder());
            mVertexBuffer = mVertexByteBuffer.asFloatBuffer();

            int quadW = mW - 1;
            int quadH = mH - 1;
            int quadCount = quadW * quadH;
            int indexCount = quadCount * 6;
            mIndexCount = indexCount;
            mIndexBuffer = ByteBuffer.allocateDirect(CHAR_SIZE * indexCount)
            .order(ByteOrder.nativeOrder()).asCharBuffer();

            /*
             * Initialize triangle list mesh.
             *
             *     [0]-----[  1] ...
             *      |    /   |
             *      |   /    |
             *      |  /     |
             *     [w]-----[w+1] ...
             *      |       |
             *
             */

            {
                int i = 0;
                for (int y = 0; y < quadH; y++) {
                    for (int x = 0; x < quadW; x++) {
                        char a = (char) (y * mW + x);
                        char b = (char) (y * mW + x + 1);
                        char c = (char) ((y + 1) * mW + x);
                        char d = (char) ((y + 1) * mW + x + 1);

                        mIndexBuffer.put(i++, a);
                        mIndexBuffer.put(i++, c);
                        mIndexBuffer.put(i++, b);

                        mIndexBuffer.put(i++, b);
                        mIndexBuffer.put(i++, c);
                        mIndexBuffer.put(i++, d);
                    }
                }
            }
        }

        public void set(int i, int j, float x, float y, float z, float nx, float ny, float nz) {
            if (i < 0 || i >= mW) {
                throw new IllegalArgumentException("i");
            }
            if (j < 0 || j >= mH) {
                throw new IllegalArgumentException("j");
            }

            int index = mW * j + i;

            mVertexBuffer.position(index * VERTEX_SIZE / FLOAT_SIZE);
            mVertexBuffer.put(x);
            mVertexBuffer.put(y);
            mVertexBuffer.put(z);
            mVertexBuffer.put(nx);
            mVertexBuffer.put(ny);
            mVertexBuffer.put(nz);
        }

        public void createBufferObjects(GL gl) {
            checkGLError(gl);
            // Generate a the vertex and element buffer IDs
            int[] vboIds = new int[2];
            GL11 gl11 = (GL11) gl;
            gl11.glGenBuffers(2, vboIds, 0);
            mVertexBufferObjectId = vboIds[0];
            mElementBufferObjectId = vboIds[1];

            // Upload the vertex data
            gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId);
            mVertexByteBuffer.position(0);
            gl11.glBufferData(GL11.GL_ARRAY_BUFFER, mVertexByteBuffer.capacity(), mVertexByteBuffer, GL11.GL_STATIC_DRAW);

            gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId);
            mIndexBuffer.position(0);
            gl11.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer.capacity() * CHAR_SIZE, mIndexBuffer, GL11.GL_STATIC_DRAW);

            // We don't need the in-memory data any more
            mVertexBuffer = null;
            mVertexByteBuffer = null;
            mIndexBuffer = null;
            checkGLError(gl);
        }

        public void draw(GL10 gl) {
            checkGLError(gl);
            GL11 gl11 = (GL11) gl;

            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

            gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId);
            gl11.glVertexPointer(3, GL10.GL_FLOAT, VERTEX_SIZE, 0);

            gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
            gl11.glNormalPointer(GL10.GL_FLOAT, VERTEX_SIZE, VERTEX_NORMAL_BUFFER_INDEX_OFFSET * FLOAT_SIZE);

            gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId);
            gl11.glDrawElements(GL10.GL_TRIANGLES, mIndexCount, GL10.GL_UNSIGNED_SHORT, 0);
            gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
            gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
            gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
            gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
            checkGLError(gl);
        }
    }

    static void checkGLError(GL gl) {
        int error = ((GL10) gl).glGetError();
        if (error != GL10.GL_NO_ERROR) {
            throw new RuntimeException("GLError 0x" + Integer.toHexString(error));
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create our surface view and set it as the content of our
        // Activity
        mGLSurfaceView = new GLSurfaceView(this);
        mGLSurfaceView.setRenderer(new Renderer());
        setContentView(mGLSurfaceView);
    }

    @Override
    protected void onResume() {
        // Ideally a game should implement onResume() and onPause()
        // to take appropriate action when the activity looses focus
        super.onResume();
        mGLSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        // Ideally a game should implement onResume() and onPause()
        // to take appropriate action when the activity looses focus
        super.onPause();
        mGLSurfaceView.onPause();
    }
}
Wrapper activity demonstrating the use of {@link GLSurfaceView}, a view that uses OpenGL drawing into a dedicated surface.
package app.test;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.MotionEvent;

/**
 * Wrapper activity demonstrating the use of {@link GLSurfaceView}, a view that
 * uses OpenGL drawing into a dedicated surface.
 * 
 * Shows: + How to redraw in response to user input.
 */
public class Test extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Create our Preview view and set it as the content of our
    // Activity
    mGLSurfaceView = new TouchSurfaceView(this);
    setContentView(mGLSurfaceView);
    mGLSurfaceView.requestFocus();
    mGLSurfaceView.setFocusableInTouchMode(true);
  }

  @Override
  protected void onResume() {
    // Ideally a game should implement onResume() and onPause()
    // to take appropriate action when the activity looses focus
    super.onResume();
    mGLSurfaceView.onResume();
  }

  @Override
  protected void onPause() {
    // Ideally a game should implement onResume() and onPause()
    // to take appropriate action when the activity looses focus
    super.onPause();
    mGLSurfaceView.onPause();
  }

  private GLSurfaceView mGLSurfaceView;
}

/**
 * A vertex shaded cube.
 */
class Cube {
  public Cube() {
    int one = 0x10000;
    int vertices[] = { -one, -one, -one, one, -one, -one, one, one, -one,
        -one, one, -one, -one, -one, one, one, -one, one, one, one,
        one, -one, one, one, };

    int colors[] = { 0, 0, 0, one, one, 0, 0, one, one, one, 0, one, 0,
        one, 0, one, 0, 0, one, one, one, 0, one, one, one, one, one,
        one, 0, one, one, one, };

    byte indices[] = { 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, 7, 2, 7,
        3, 3, 7, 4, 3, 4, 0, 4, 7, 6, 4, 6, 5, 3, 0, 1, 3, 1, 2 };

    // Buffers to be passed to gl*Pointer() functions
    // must be direct, i.e., they must be placed on the
    // native heap where the garbage collector cannot
    // move them.
    //
    // Buffers with multi-byte datatypes (e.g., short, int, float)
    // must have their byte order set to native order

    ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
    vbb.order(ByteOrder.nativeOrder());
    mVertexBuffer = vbb.asIntBuffer();
    mVertexBuffer.put(vertices);
    mVertexBuffer.position(0);

    ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
    cbb.order(ByteOrder.nativeOrder());
    mColorBuffer = cbb.asIntBuffer();
    mColorBuffer.put(colors);
    mColorBuffer.position(0);

    mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
    mIndexBuffer.put(indices);
    mIndexBuffer.position(0);
  }

  public void draw(GL10 gl) {
    gl.glFrontFace(GL10.GL_CW);
    gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
    gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
    gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE,
        mIndexBuffer);
  }

  private IntBuffer mVertexBuffer;
  private IntBuffer mColorBuffer;
  private ByteBuffer mIndexBuffer;
}

/**
 * Implement a simple rotation control.
 * 
 */
class TouchSurfaceView extends GLSurfaceView {

  public TouchSurfaceView(Context context) {
    super(context);
    mRenderer = new CubeRenderer();
    setRenderer(mRenderer);
    setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
  }

  @Override
  public boolean onTrackballEvent(MotionEvent e) {
    mRenderer.mAngleX += e.getX() * TRACKBALL_SCALE_FACTOR;
    mRenderer.mAngleY += e.getY() * TRACKBALL_SCALE_FACTOR;
    requestRender();
    return true;
  }

  @Override
  public boolean onTouchEvent(MotionEvent e) {
    float x = e.getX();
    float y = e.getY();
    switch (e.getAction()) {
    case MotionEvent.ACTION_MOVE:
      float dx = x - mPreviousX;
      float dy = y - mPreviousY;
      mRenderer.mAngleX += dx * TOUCH_SCALE_FACTOR;
      mRenderer.mAngleY += dy * TOUCH_SCALE_FACTOR;
      requestRender();
    }
    mPreviousX = x;
    mPreviousY = y;
    return true;
  }

  /**
   * Render a cube.
   */
  private class CubeRenderer implements GLSurfaceView.Renderer {
    public CubeRenderer() {
      mCube = new Cube();
    }

    public void onDrawFrame(GL10 gl) {
      /*
       * Usually, the first thing one might want to do is to clear the
       * screen. The most efficient way of doing this is to use glClear().
       */

      gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

      /*
       * Now we're ready to draw some 3D objects
       */

      gl.glMatrixMode(GL10.GL_MODELVIEW);
      gl.glLoadIdentity();
      gl.glTranslatef(0, 0, -3.0f);
      gl.glRotatef(mAngleX, 0, 1, 0);
      gl.glRotatef(mAngleY, 1, 0, 0);

      gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
      gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

      mCube.draw(gl);
    }

    public void onSurfaceChanged(GL10 gl, int width, int height) {
      gl.glViewport(0, 0, width, height);

      /*
       * Set our projection matrix. This doesn't have to be done each time
       * we draw, but usually a new projection needs to be set when the
       * viewport is resized.
       */

      float ratio = (float) width / height;
      gl.glMatrixMode(GL10.GL_PROJECTION);
      gl.glLoadIdentity();
      gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
    }

    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
      /*
       * By default, OpenGL enables features that improve quality but
       * reduce performance. One might want to tweak that especially on
       * software renderer.
       */
      gl.glDisable(GL10.GL_DITHER);

      /*
       * Some one-time OpenGL initialization can be made here probably
       * based on features of this particular context
       */
      gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);

      gl.glClearColor(1, 1, 1, 1);
      gl.glEnable(GL10.GL_CULL_FACE);
      gl.glShadeModel(GL10.GL_SMOOTH);
      gl.glEnable(GL10.GL_DEPTH_TEST);
    }

    private Cube mCube;
    public float mAngleX;
    public float mAngleY;
  }

  private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
  private final float TRACKBALL_SCALE_FACTOR = 36.0f;
  private CubeRenderer mRenderer;
  private float mPreviousX;
  private float mPreviousY;
}
A GLSurfaceView.Renderer that uses the Android-specific android.opengl.GLESXXX static OpenGL ES APIs.
package app.test;
import static android.opengl.GLES10.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.os.Bundle;
import android.os.SystemClock;

/**
 * A GLSurfaceView.Renderer that uses the Android-specific
 * android.opengl.GLESXXX static OpenGL ES APIs. The static APIs expose more of
 * the OpenGL ES features than the javax.microedition.khronos.opengles APIs, and
 * also provide a programming model that is closer to the C OpenGL ES APIs,
 * which may make it easier to reuse code and documentation written for the C
 * OpenGL ES APIs.
 * 
 */
class StaticTriangleRenderer implements GLSurfaceView.Renderer {

  public interface TextureLoader {
    /**
     * Load a texture into the currently bound OpenGL texture.
     */
    void load(GL10 gl);
  }

  public StaticTriangleRenderer(Context context) {
    init(context, new RobotTextureLoader());
  }

  public StaticTriangleRenderer(Context context, TextureLoader loader) {
    init(context, loader);
  }

  private void init(Context context, TextureLoader loader) {
    mContext = context;
    mTriangle = new Triangle();
    mTextureLoader = loader;
  }

  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    /*
     * By default, OpenGL enables features that improve quality but reduce
     * performance. One might want to tweak that especially on software
     * renderer.
     */
    glDisable(GL_DITHER);

    /*
     * Some one-time OpenGL initialization can be made here probably based
     * on features of this particular context
     */
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);

    glClearColor(.5f, .5f, .5f, 1);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);

    /*
     * Create our texture. This has to be done each time the surface is
     * created.
     */

    int[] textures = new int[1];
    glGenTextures(1, textures, 0);

    mTextureID = textures[0];
    glBindTexture(GL_TEXTURE_2D, mTextureID);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    mTextureLoader.load(gl);
  }

  public void onDrawFrame(GL10 gl) {
    /*
     * By default, OpenGL enables features that improve quality but reduce
     * performance. One might want to tweak that especially on software
     * renderer.
     */
    glDisable(GL_DITHER);

    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    /*
     * Usually, the first thing one might want to do is to clear the screen.
     * The most efficient way of doing this is to use glClear().
     */

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /*
     * Now we're ready to draw some 3D objects
     */

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, mTextureID);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);

    glRotatef(angle, 0, 0, 1.0f);

    mTriangle.draw(gl);
  }

  public void onSurfaceChanged(GL10 gl, int w, int h) {
    glViewport(0, 0, w, h);

    /*
     * Set our projection matrix. This doesn't have to be done each time we
     * draw, but usually a new projection needs to be set when the viewport
     * is resized.
     */

    float ratio = (float) w / h;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustumf(-ratio, ratio, -1, 1, 3, 7);
  }

  private Context mContext;
  private Triangle mTriangle;
  private int mTextureID;
  private TextureLoader mTextureLoader;

  private class RobotTextureLoader implements TextureLoader {
    public void load(GL10 gl) {
      InputStream is = mContext.getResources().openRawResource(
          R.raw.robot);
      Bitmap bitmap;
      try {
        bitmap = BitmapFactory.decodeStream(is);
      } finally {
        try {
          is.close();
        } catch (IOException e) {
          // Ignore.
        }
      }

      GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
      bitmap.recycle();
    }
  }

  static class Triangle {
    public Triangle() {

      // Buffers to be passed to gl*Pointer() functions
      // must be direct, i.e., they must be placed on the
      // native heap where the garbage collector cannot
      // move them.
      //
      // Buffers with multi-byte datatypes (e.g., short, int, float)
      // must have their byte order set to native order

      ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
      vbb.order(ByteOrder.nativeOrder());
      mFVertexBuffer = vbb.asFloatBuffer();

      ByteBuffer tbb = ByteBuffer.allocateDirect(VERTS * 2 * 4);
      tbb.order(ByteOrder.nativeOrder());
      mTexBuffer = tbb.asFloatBuffer();

      ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
      ibb.order(ByteOrder.nativeOrder());
      mIndexBuffer = ibb.asShortBuffer();

      // A unit-sided equilateral triangle centered on the origin.
      float[] coords = {
          // X, Y, Z
          -0.5f, -0.25f, 0, 0.5f, -0.25f, 0, 0.0f, 0.559016994f, 0 };

      for (int i = 0; i < VERTS; i++) {
        for (int j = 0; j < 3; j++) {
          mFVertexBuffer.put(coords[i * 3 + j] * 2.0f);
        }
      }

      for (int i = 0; i < VERTS; i++) {
        for (int j = 0; j < 2; j++) {
          mTexBuffer.put(coords[i * 3 + j] * 2.0f + 0.5f);
        }
      }

      for (int i = 0; i < VERTS; i++) {
        mIndexBuffer.put((short) i);
      }

      mFVertexBuffer.position(0);
      mTexBuffer.position(0);
      mIndexBuffer.position(0);
    }

    public void draw(GL10 gl) {
      glFrontFace(GL_CCW);
      glVertexPointer(3, GL_FLOAT, 0, mFVertexBuffer);
      glEnable(GL_TEXTURE_2D);
      glTexCoordPointer(2, GL_FLOAT, 0, mTexBuffer);
      glDrawElements(GL_TRIANGLE_STRIP, VERTS, GL_UNSIGNED_SHORT,
          mIndexBuffer);
    }

    private final static int VERTS = 3;

    private FloatBuffer mFVertexBuffer;
    private FloatBuffer mTexBuffer;
    private ShortBuffer mIndexBuffer;
  }
}

public class Test extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mGLView = new GLSurfaceView(this);
    mGLView.setRenderer(new StaticTriangleRenderer(this));
    /*
     * ImageView imageView = new ImageView(this);
     * imageView.setImageResource(R.raw.robot); setContentView(imageView);
     */
    setContentView(mGLView);
  }

  @Override
  protected void onPause() {
    super.onPause();
    mGLView.onPause();
  }

  @Override
  protected void onResume() {
    super.onResume();
    mGLView.onResume();
  }

  private GLSurfaceView mGLView;
}
Check for OpenGL ES 2.0 support at runtime, and then use either OpenGL ES 1.0 or OpenGL ES 2.0, as appropriate.
package app.test;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;

class GLES20TriangleRenderer implements GLSurfaceView.Renderer {

  public GLES20TriangleRenderer(Context context) {
    mContext = context;
    mTriangleVertices = ByteBuffer
        .allocateDirect(mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
    mTriangleVertices.put(mTriangleVerticesData).position(0);
  }

  public void onDrawFrame(GL10 glUnused) {
    // Ignore the passed-in GL10 interface, and use the GLES20
    // class's static methods instead.
    GLES20.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
    GLES20.glUseProgram(mProgram);
    checkGlError("glUseProgram");

    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);

    mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
    GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT,
        false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
    checkGlError("glVertexAttribPointer maPosition");
    mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
    GLES20.glEnableVertexAttribArray(maPositionHandle);
    checkGlError("glEnableVertexAttribArray maPositionHandle");
    GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT,
        false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
    checkGlError("glVertexAttribPointer maTextureHandle");
    GLES20.glEnableVertexAttribArray(maTextureHandle);
    checkGlError("glEnableVertexAttribArray maTextureHandle");

    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);
    Matrix.setRotateM(mMMatrix, 0, angle, 0, 0, 1.0f);
    Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
    Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);

    GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
    checkGlError("glDrawArrays");
  }

  public void onSurfaceChanged(GL10 glUnused, int width, int height) {
    // Ignore the passed-in GL10 interface, and use the GLES20
    // class's static methods instead.
    GLES20.glViewport(0, 0, width, height);
    float ratio = (float) width / height;
    Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
  }

  public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
    // Ignore the passed-in GL10 interface, and use the GLES20
    // class's static methods instead.
    mProgram = createProgram(mVertexShader, mFragmentShader);
    if (mProgram == 0) {
      return;
    }
    maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
    checkGlError("glGetAttribLocation aPosition");
    if (maPositionHandle == -1) {
      throw new RuntimeException(
          "Could not get attrib location for aPosition");
    }
    maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
    checkGlError("glGetAttribLocation aTextureCoord");
    if (maTextureHandle == -1) {
      throw new RuntimeException(
          "Could not get attrib location for aTextureCoord");
    }

    muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
    checkGlError("glGetUniformLocation uMVPMatrix");
    if (muMVPMatrixHandle == -1) {
      throw new RuntimeException(
          "Could not get attrib location for uMVPMatrix");
    }

    /*
     * Create our texture. This has to be done each time the surface is
     * created.
     */

    int[] textures = new int[1];
    GLES20.glGenTextures(1, textures, 0);

    mTextureID = textures[0];
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);

    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
        GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
        GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
        GLES20.GL_REPEAT);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
        GLES20.GL_REPEAT);

    InputStream is = mContext.getResources().openRawResource(R.raw.robot);
    Bitmap bitmap;
    try {
      bitmap = BitmapFactory.decodeStream(is);
    } finally {
      try {
        is.close();
      } catch (IOException e) {
        // Ignore.
      }
    }

    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
    bitmap.recycle();

    Matrix.setLookAtM(mVMatrix, 0, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
  }

  private int loadShader(int shaderType, String source) {
    int shader = GLES20.glCreateShader(shaderType);
    if (shader != 0) {
      GLES20.glShaderSource(shader, source);
      GLES20.glCompileShader(shader);
      int[] compiled = new int[1];
      GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
      if (compiled[0] == 0) {
        Log.e(TAG, "Could not compile shader " + shaderType + ":");
        Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
        GLES20.glDeleteShader(shader);
        shader = 0;
      }
    }
    return shader;
  }

  private int createProgram(String vertexSource, String fragmentSource) {
    int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
    if (vertexShader == 0) {
      return 0;
    }

    int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
    if (pixelShader == 0) {
      return 0;
    }

    int program = GLES20.glCreateProgram();
    if (program != 0) {
      GLES20.glAttachShader(program, vertexShader);
      checkGlError("glAttachShader");
      GLES20.glAttachShader(program, pixelShader);
      checkGlError("glAttachShader");
      GLES20.glLinkProgram(program);
      int[] linkStatus = new int[1];
      GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
      if (linkStatus[0] != GLES20.GL_TRUE) {
        Log.e(TAG, "Could not link program: ");
        Log.e(TAG, GLES20.glGetProgramInfoLog(program));
        GLES20.glDeleteProgram(program);
        program = 0;
      }
    }
    return program;
  }

  private void checkGlError(String op) {
    int error;
    while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
      Log.e(TAG, op + ": glError " + error);
      throw new RuntimeException(op + ": glError " + error);
    }
  }

  private static final int FLOAT_SIZE_BYTES = 4;
  private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
  private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
  private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
  private final float[] mTriangleVerticesData = {
      // X, Y, Z, U, V
      -1.0f, -0.5f, 0, -0.5f, 0.0f, 1.0f, -0.5f, 0, 1.5f, -0.0f, 0.0f,
      1.11803399f, 0, 0.5f, 1.61803399f };

  private FloatBuffer mTriangleVertices;

  private final String mVertexShader = "uniform mat4 uMVPMatrix;\n"
      + "attribute vec4 aPosition;\n" + "attribute vec2 aTextureCoord;\n"
      + "varying vec2 vTextureCoord;\n" + "void main() {\n"
      + "  gl_Position = uMVPMatrix * aPosition;\n"
      + "  vTextureCoord = aTextureCoord;\n" + "}\n";

  private final String mFragmentShader = "precision mediump float;\n"
      + "varying vec2 vTextureCoord;\n" + "uniform sampler2D sTexture;\n"
      + "void main() {\n"
      + "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + "}\n";

  private float[] mMVPMatrix = new float[16];
  private float[] mProjMatrix = new float[16];
  private float[] mMMatrix = new float[16];
  private float[] mVMatrix = new float[16];

  private int mProgram;
  private int mTextureID;
  private int muMVPMatrixHandle;
  private int maPositionHandle;
  private int maTextureHandle;

  private Context mContext;
  private static String TAG = "GLES20TriangleRenderer";
}

class TriangleRenderer implements GLSurfaceView.Renderer {

  public TriangleRenderer(Context context) {
    mContext = context;
    mTriangle = new Triangle();
  }

  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    /*
     * By default, OpenGL enables features that improve quality but reduce
     * performance. One might want to tweak that especially on software
     * renderer.
     */
    gl.glDisable(GL10.GL_DITHER);

    /*
     * Some one-time OpenGL initialization can be made here probably based
     * on features of this particular context
     */
    gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);

    gl.glClearColor(.5f, .5f, .5f, 1);
    gl.glShadeModel(GL10.GL_SMOOTH);
    gl.glEnable(GL10.GL_DEPTH_TEST);
    gl.glEnable(GL10.GL_TEXTURE_2D);

    /*
     * Create our texture. This has to be done each time the surface is
     * created.
     */

    int[] textures = new int[1];
    gl.glGenTextures(1, textures, 0);

    mTextureID = textures[0];
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);

    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
        GL10.GL_NEAREST);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
        GL10.GL_LINEAR);

    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
        GL10.GL_CLAMP_TO_EDGE);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
        GL10.GL_CLAMP_TO_EDGE);

    gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
        GL10.GL_REPLACE);

    InputStream is = mContext.getResources().openRawResource(R.raw.robot);
    Bitmap bitmap;
    try {
      bitmap = BitmapFactory.decodeStream(is);
    } finally {
      try {
        is.close();
      } catch (IOException e) {
        // Ignore.
      }
    }

    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
    bitmap.recycle();
  }

  public void onDrawFrame(GL10 gl) {
    /*
     * By default, OpenGL enables features that improve quality but reduce
     * performance. One might want to tweak that especially on software
     * renderer.
     */
    gl.glDisable(GL10.GL_DITHER);

    gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
        GL10.GL_MODULATE);

    /*
     * Usually, the first thing one might want to do is to clear the screen.
     * The most efficient way of doing this is to use glClear().
     */

    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

    /*
     * Now we're ready to draw some 3D objects
     */

    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();

    GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

    gl.glActiveTexture(GL10.GL_TEXTURE0);
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
    gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
        GL10.GL_REPEAT);
    gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
        GL10.GL_REPEAT);

    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);

    gl.glRotatef(angle, 0, 0, 1.0f);

    mTriangle.draw(gl);
  }

  public void onSurfaceChanged(GL10 gl, int w, int h) {
    gl.glViewport(0, 0, w, h);

    /*
     * Set our projection matrix. This doesn't have to be done each time we
     * draw, but usually a new projection needs to be set when the viewport
     * is resized.
     */

    float ratio = (float) w / h;
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadIdentity();
    gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);

  }

  private Context mContext;
  private Triangle mTriangle;
  private int mTextureID;
}

class Triangle {
  public Triangle() {

    // Buffers to be passed to gl*Pointer() functions
    // must be direct, i.e., they must be placed on the
    // native heap where the garbage collector cannot
    // move them.
    //
    // Buffers with multi-byte datatypes (e.g., short, int, float)
    // must have their byte order set to native order

    ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
    vbb.order(ByteOrder.nativeOrder());
    mFVertexBuffer = vbb.asFloatBuffer();

    ByteBuffer tbb = ByteBuffer.allocateDirect(VERTS * 2 * 4);
    tbb.order(ByteOrder.nativeOrder());
    mTexBuffer = tbb.asFloatBuffer();

    ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
    ibb.order(ByteOrder.nativeOrder());
    mIndexBuffer = ibb.asShortBuffer();

    // A unit-sided equalateral triangle centered on the origin.
    float[] coords = {
        // X, Y, Z
        -0.5f, -0.25f, 0, 0.5f, -0.25f, 0, 0.0f, 0.559016994f, 0 };

    for (int i = 0; i < VERTS; i++) {
      for (int j = 0; j < 3; j++) {
        mFVertexBuffer.put(coords[i * 3 + j] * 2.0f);
      }
    }

    for (int i = 0; i < VERTS; i++) {
      for (int j = 0; j < 2; j++) {
        mTexBuffer.put(coords[i * 3 + j] * 2.0f + 0.5f);
      }
    }

    for (int i = 0; i < VERTS; i++) {
      mIndexBuffer.put((short) i);
    }

    mFVertexBuffer.position(0);
    mTexBuffer.position(0);
    mIndexBuffer.position(0);
  }

  public void draw(GL10 gl) {
    gl.glFrontFace(GL10.GL_CCW);
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
    gl.glEnable(GL10.GL_TEXTURE_2D);
    gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexBuffer);
    gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, VERTS,
        GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
  }

  private final static int VERTS = 3;

  private FloatBuffer mFVertexBuffer;
  private FloatBuffer mTexBuffer;
  private ShortBuffer mIndexBuffer;
}

/**
 * This sample shows how to check for OpenGL ES 2.0 support at runtime, and then
 * use either OpenGL ES 1.0 or OpenGL ES 2.0, as appropriate.
 */
public class Test extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mGLSurfaceView = new GLSurfaceView(this);
    if (detectOpenGLES20()) {
      // Tell the surface view we want to create an OpenGL ES
      // 2.0-compatible
      // context, and set an OpenGL ES 2.0-compatible renderer.
      mGLSurfaceView.setEGLContextClientVersion(2);
      mGLSurfaceView.setRenderer(new GLES20TriangleRenderer(this));
    } else {
      // Set an OpenGL ES 1.x-compatible renderer. In a real application
      // this renderer might approximate the same output as the 2.0
      // renderer.
      mGLSurfaceView.setRenderer(new TriangleRenderer(this));
    }
    setContentView(mGLSurfaceView);
  }

  private boolean detectOpenGLES20() {
    ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    ConfigurationInfo info = am.getDeviceConfigurationInfo();
    return (info.reqGlEsVersion >= 0x20000);
  }

  @Override
  protected void onResume() {
    // Ideally a game should implement onResume() and onPause()
    // to take appropriate action when the activity looses focus
    super.onResume();
    mGLSurfaceView.onResume();
  }

  @Override
  protected void onPause() {
    // Ideally a game should implement onResume() and onPause()
    // to take appropriate action when the activity looses focus
    super.onPause();
    mGLSurfaceView.onPause();
  }

  private GLSurfaceView mGLSurfaceView;
}