It has been a while since I was able to write any tutorials and continue on my last serie about OpenGL ES 1.x feels a bit out-dated so I start all over again but with OpenGL ES 2.0 but there are so many common things that I will be copy & pasting a lot. I really love all the comments I have gotten on my previous tutorials but I want to apologize in advance that I’m not able to answer all mail and comments.

I will try to keep the focus on simplicity and making it easy to understand the different parts rather then focusing on high performance and good architecture so what I am saying is that this is for learning not for production.

I don’t always remember where I found particular information so I might not always be able to give you the right reference. If you feel that I have borrowed stuff from you but have forgotten to add you as a reference, please e-mail me. I’m all about giving credit where it belongs.

Prerequisites

To be able to follow me in this tutorials all you need to know is how to setup an android project and are used to work with eclipse and java. I will try to keep the focus on simplicity and making it easy to understand. The android project needs to be at least on API level 11.

So let us start!

Setting up OpenGL ES 2.0 view

The first thing you need to do is getting the view up and running. The entry point for an android application is the Activity and this is where I start. Detecting if a device supports OpenGL ES 2.0 is quite straight forward you ask the activity manager about the device configuration information and read the OpenGL ES version from there.

private boolean hasGLES20() { ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ConfigurationInfo info = am.getDeviceConfigurationInfo(); return info.reqGlEsVersion >= 0x20000; }

You can also make sure no one install the application from Google Play on a device not supporting OpenGL ES 2.0 by adding a uses-feature in the AndroidManifest.xml.

When we are sure about the support for OpenGL ES 2.0 we can go ahead and setup the GLSurfaceView. The GLSurfaceView has a lot of functions and I will not go into them all and I will only go into a them when I need them.

The first function to look at is the setEGLContextClientVersion(int version) as it sound this is where we actually set the OpenGL ES version we want to work with, in this case 2.

The next function that is interesting at the moment is the setPreserveEGLContextOnPause(boolean preserveOnPause) this is by default set to false meaning that the OpenGL ES context is not preserved when the application is paused. Changing this to true will preserve the OpenGL ES context if the device support it. What this means is that even if we set it to true we need to handle the case then the device looses the context.

The last function for now is the setRenderer(GLSurfaceView.Renderer renderer). If we don’t set a renderer the application will crash, so we better set a renderer. The GLSurfaceView.Renderer is an interface that we need to implement.

To setup OpenGL ES 2.0 this is the code:

private void initialize() { if (hasGLES20()) { mGLView = new GLSurfaceView(this); mGLView.setEGLContextClientVersion(2); mGLView.setPreserveEGLContextOnPause(true); mGLView.setRenderer(new GLES20Renderer()); } else { // Time to get a new phone, OpenGL ES 2.0 not supported. } }

To work properly the GLSurfaceView need to get the onPause and onResume events from the Activity.

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

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

If you are doing a game you probably like to enter fullscreen mode you can go into fullscreen by adding the following lines before you set the content view.

requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(floatyView);

Putting it all together and we get this:

package com.jayway.gles20; 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; import android.view.Window; import android.view.WindowManager; public class GLES20Activity extends Activity { private GLSurfaceView mSurfaceView; private GLSurfaceView mGLView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); if (hasGLES20()) { mGLView = new GLSurfaceView(this); mGLView.setEGLContextClientVersion(2); mGLView.setPreserveEGLContextOnPause(true); mGLView.setRenderer(new GLES20Renderer()); } else { // Time to get a new phone, OpenGL ES 2.0 not // supported. } setContentView(mGLView); } private boolean hasGLES20() { ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ConfigurationInfo info = am.getDeviceConfigurationInfo(); return info.reqGlEsVersion >= 0x20000; } @Override protected void onResume() { super.onResume(); /* * The activity must call the GL surface view's * onResume() on activity onResume(). */ if (mSurfaceView != null) { mSurfaceView.onResume(); } } @Override protected void onPause() { super.onPause(); /* * The activity must call the GL surface view's * onPause() on activity onPause(). */ if (mSurfaceView != null) { mSurfaceView.onPause(); } } }

At the moment you get an error because we are missing the GLES20Renderer class, we get to that one soon.

Lifecycle

One of the most important things to get right from the beginning in all application development is the lifecycle. Android has a lifecycle for the activity and for the view but for OpenGL ES we need to create our own within the activity lifecycle. First we need to know when the OpenGL context is up and running, we also need to know if the context is lost so we can recreate the resources on the GPU more on this later.

To know if OpenGL is up and running I will use an onCreate method.

public void onCreate(int width, int height, boolean contextLost);

We also need to know when to draw stuff.

public void onDrawFrame(boolean firstDraw);

Let us start by looking at the android.opengl.GLSurfaceView.Renderer interface, if you have worked with OpenGL ES 1.x you know about this. We have three methods we need to implement here:

public void onSurfaceCreated(GL10 notUsed, EGLConfig config) public void onSurfaceChanged(GL10 notUsed, int width, int height) public void onDrawFrame(GL10 notUsed)

The reason I have named the GL10 parameter to notUsed is because it is never used when working with OpenGL ES 2.0.

The method onSurfaceCreated is called every time the surface is created which means we have gotten a new gl context. So we set a flag to true to keep track of newly created context.

mSurfaceCreated = true;

onSurfaceChanged is called direct after onSurfaceCreated or when the surface have changes properties like width and height. We also can determine here if the context have been lost by checking the mSurfaceCreated flag that we set in the onSurfaceCreated method if it is true the context is just created and we need to re-/create the resources on the gpu.

And finally onDrawFrame is called every frame, here we keep track of the frames per second counter and if it is the first drawn frame.

package com.jayway.gles20; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.opengl.GLSurfaceView.Renderer; import android.util.Log; import com.jayway.gles20.util.Util; public abstract class GLRenderer implements Renderer { private boolean mFirstDraw; private boolean mSurfaceCreated; private int mWidth; private int mHeight; private long mLastTime; private int mFPS; public GLRenderer() { mFirstDraw = true; mSurfaceCreated = false; mWidth = -1; mHeight = -1; mLastTime = System.currentTimeMillis(); mFPS = 0; } @Override public void onSurfaceCreated(GL10 notUsed, EGLConfig config) { if (Util.DEBUG) { Log.i(Util.LOG_TAG, "Surface created."); } mSurfaceCreated = true; mWidth = -1; mHeight = -1; } @Override public void onSurfaceChanged(GL10 notUsed, int width, int height) { if (!mSurfaceCreated && width == mWidth && height == mHeight) { if (Util.DEBUG) { Log.i(Util.LOG_TAG, "Surface changed but already handled."); } return; } if (Util.DEBUG) { // Android honeycomb has an option to keep the // context. String msg = "Surface changed width:" + width + " height:" + height; if (mSurfaceCreated) { msg += " context lost."; } else { msg += "."; } Log.i(Util.LOG_TAG, msg); } mWidth = width; mHeight = height; onCreate(mWidth, mHeight, mSurfaceCreated); mSurfaceCreated = false; } @Override public void onDrawFrame(GL10 notUsed) { onDrawFrame(mFirstDraw); if (Util.DEBUG) { mFPS++; long currentTime = System.currentTimeMillis(); if (currentTime - mLastTime >= 1000) { mFPS = 0; mLastTime = currentTime; } } if (mFirstDraw) { mFirstDraw = false; } } public int getFPS() { return mFPS; } public abstract void onCreate(int width, int height, boolean contextLost); public abstract void onDrawFrame(boolean firstDraw); }

I made the GLRenderer class abstract for two reasons. First reason is that it separates the lifecycle code from the rendering code making it a bit easier to follow. The second reason is that it makes our class more reusable. So to make our example work we need to create a class that inherited the GLRenderer. This is a simple implementation just making the screen black.

package com.jayway.gles20; import android.opengl.GLES20; public class GLES20Renderer extends GLRenderer { @Override public void onCreate(int width, int height, boolean contextLost) { GLES20.glClearColor(0f, 0f, 0f, 1f); } @Override public void onDrawFrame(boolean firstDraw) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); } }

This is pretty much all you need to get your view up and running. If you compile and run it you will see a nice black screen.

References

The info used in this tutorial is collected from:

Android Developers

OpenGL ES 2.0 Reference Pages

You can download the source for this tutorial here: tutorial_01.zip

You can also checkout the code from: github.com