The Quick and Dirty to OpenGL ES 2.0 on Android


This is a short article for those familiar with OpenGL's programmable pipeline and are
interested in using the OpengGL ES 2.0 API for Android development.

To use the OpenGL ES API in an Android environment you have to setup a GLSurfaceView
object, override the GLSurfaceView.Renderer, and attach the renderer to the surfaceview.
Additionally, you could extend GLSurfaceView to capture touchscreen events by overriding
onTrackballEvent() and onTouchEvent().
Step 1) Check that OpenGL ES 2.0 is supported and attach your renderer.
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
 
public class MyOpenGLApp extends Activity {
    private GLSurfaceView mGLSurfaceView;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        mGLSurfaceView = new GLSurfaceView(this);
        ActivityManager am = 
            (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        ConfigurationInfo info = am.getDeviceConfigurationInfo();
        if(info.reqGlEsVersion >= 0x20000) {
                mGLSurfaceView.setEGLContextClientVersion(2);
                mGLSurfaceView.setRenderer(new MyOpenGLAppRenderer(this));
        } else {
                //Buy a new phone
                finish();
        }
        setContententView(mGLSurfaceView);
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        mGLSurfaceView.onResume();
        //Do additional stuff to unpause your game/app
    }
 
    @Override
    protected void onPause() {
        super.onPause();
        mGLSurfaceView.onPause();
        //Do additional stuff to pause your game/app
    }
}
Step 2) Setup your OpenGL Renderer
    a. Override onSurfaceCreated
    b. Override onDrawFrame
    c. Override onSurfaceChanged
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLES20;
 
public class MyOpenGLAppRenderer implements GLSurfaceView.Renderer
{
    public MyOpenGLAppRenderer(Context context) {}
 
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
        //Initialization -> Steps 3, 4, and 5
    }
 
    public void onDrawFrame(GL10 glUnused) {
        //Update the graphics, bind resources, and render -> Steps 6 and 7
    }
 
    public void onSurfaceChanged(GL10 glUnused, int width, int height) {
        //Adjust your camera/rendering parameters or just look funny
    }
}
Step 3) Initialize your shader program
    a. Create, load, and compile the vertex and fragment shaders
        i.   glCreateShader(shader_type) // => GL_VERTEX_SHADER / GL_FRAGMENT_SHADER
        ii.  glShaderSource(shader_reference, shader_source_code);
        iii. glCompileShader(shader_reference);
    b. Create the shader program and attach the vertex and fragment shaders
        i.   glCreateProgram();
        ii.  glAttachShader(program_reference, vertex_shader_reference);
        iii. glAttachShader(program_reference, fragment_shader_reference);
    c. Link the program and check for errors
        i.   glLinkProgram(program_reference);
        ii.  glGetProgramiv(program_reference, GL_LINK_STATUS, results, 0);

Step 4) Request the locations of specific variables within the shader program
    a. Uniform Locations
        i.   glGetUniformLocation(program_reference, "uniform_name_goes_here");
    b. Attribute Locations (can also be mapped during vertex buffer binding)
        i.   glGetAttribLocation(program_reference, "attribute_name_goes_here")

Step 5) Create and Load Vertex Buffer Objects (it’s a little different if you are not using VBOs)
    a. Create Vertex and Index Buffers
        glGenBuffers(number_of_desired_buffers, returned_buffer_IDs, 0)
    b. Bind Vertex Buffer
        glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id)    
    c. Load Vertex data into buffer
        glBufferData(GL_ARRAY_BUFFER, size_in_bytes, vertex_data, GL_STATIC_DRAW);
    d. Bind Index Buffer
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_id)
    e. Load Index data into buffer
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_in_bytes, index_data, GL_STATIC_DRAW);

Step 6) Rendering Preparation
    a. Use the program shader
        i.   glUseProgram(program_reference)
    b. Map each Vertex Attribute to an index (ex: position attribute below)
        i.   glEnableVertexAttribArray(VERTEX_POS_INDEX)
        ii.  glVertexAttribPointer(VERTEX_POS_INDEX, VERTEX_POS_SIZE, GL_FLOAT, false, 
            VERTEX_SIZE_IN_BYTES, offset_in_vertex_structure)
    c. Bind each Attribute Index to its respective attribute location in the Vertex Shader
        i.   glBindAttribLocation(program_reference, VERTEX_POS_INDEX, “a_position”)
    d. Send uniform data like the model-view projection matrix to the GPU
        i.   glUniformMatrix4fv(mvp_location, 1, false, mvpMatrix.getAsFloatBuffer());

Step 7) Render
    a. glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, offset)

Step 8) The quick and dirty clean up calls for you neat freaks out there
    a. glDeleteBuffers(number_of_buffers, buffer_IDs)
    b. glDeleteShader(shader_reference)
    c. glDeleteProgram(program_reference)

Summary

Step 1 sets up your activity class, queries EGL (the ‘glue’ between OpenGL and the OS window) for OpenGL ES 2.0 compatability and attaches your renderer.
 
Step 2 sets up your renderer class allowing you to handle initialization, rendering frames, and handling surface size changes.

Step 3 gets your shaders down to the GPU as binary instructions. 

Step 4 gets the ‘addresses’ of variables in your shader program so that you can push data into them from the client side. 

Step 5 builds your vertex buffer objects on the GPU. These VBOs hold data such as vertex positions, normals, colors, and texture coordinates. 

Step 6 maps the data in your vertex structures to attribute variables in your vertex shader, and also pushes data (like the model-view matrix) down to your shader’s uniform variables. 

Step 7 renders the attribute arrays we mapped out using glVertexAttribPointer in Step 6. 

Finally, Step 8 does some clean up.

For more detailed information in audio form check out some of our podcast spotlights.  I go through the OpenGL ES 2.0 pipeline in our March 20th, 2011 podcast and take a deep dive into bump mapping, parallax mapping, and relief mapping in our July 10th, 2011 podcast.

If you are looking for a good book on this topic you can’t go wrong with the OpenGL ES 2.0 Programming Guide by Aaftab Munshi, Dan Ginsburg, and Dave Shreiner.  The book’s code examples are in C, but it’s a fairly straight forward mapping to Android’s GLES20 class.  There are also some Android examples on the book’s website. They can be pulled down via svn from here: http://opengles-book-samples.googlecode.com/svn/trunk/Android/

Feel free to post any questions or comments.