OpenGL ES 2.0 Java bindings for Android

I’m a nice guy so i wrote a complete JNI bridge to OpenGL ES 2.0 for Android. You can now use OpenGL ES 2.0 from within your Java application directly. Besides the native bindings i also stole the GLJNIView from the hello-gl2 example in the NDK and modified it to become a proper GLSurfaceView implementation. Here’s an example how to decide wheter OpenGL ES 2.0 is supported and instantiation a proper GLSurfaceView then:

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

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.opengles.GL10;

import com.badlogic.gdx.backends.android.AndroidGL20;
import com.badlogic.gdx.backends.android.surfaceview.GLSurfaceView;
import com.badlogic.gdx.backends.android.surfaceview.GLSurfaceView20;
import com.badlogic.gdx.backends.android.surfaceview.GLSurfaceView.Renderer;
import com.badlogic.gdx.graphics.GL20;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;

public class GL2Test extends Activity 
{
	GLSurfaceView view;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);       
        
        if( checkGL20Support( this ) )
        	view = new GLSurfaceView20( this );
        else
        	view = new GLSurfaceView( this );
        
        view.setRenderer( new TestRenderer() );        
        setContentView(view);
    }
    
    protected void onPause( )
    {
    	super.onPause();
    	view.onPause();
    }
    
    protected void onResume( )
    {
    	super.onResume();
    	view.onResume();
    }
    
    private boolean checkGL20Support( Context context )
    {
    	EGL10 egl = (EGL10) EGLContext.getEGL();       
        EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

        int[] version = new int[2];
        egl.eglInitialize(display, version);

        int EGL_OPENGL_ES2_BIT = 4;
        int[] configAttribs =
        {
            EGL10.EGL_RED_SIZE, 4,
            EGL10.EGL_GREEN_SIZE, 4,
            EGL10.EGL_BLUE_SIZE, 4,
            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
            EGL10.EGL_NONE
        };
        
        EGLConfig[] configs = new EGLConfig[10];
        int[] num_config = new int[1];
        egl.eglChooseConfig(display, configAttribs, configs, 10, num_config);     
        egl.eglTerminate(display);
        return num_config[0] > 0;
    }  
    
    class TestRenderer implements Renderer
    {
    	AndroidGL20 gl2 = new AndroidGL20();
    	FloatBuffer vertices;
    	int program;
    	int viewportWidth, viewportHeight;

		@Override
		public void onDrawFrame(GL10 gl) 
		{		
			gl2.glClearColor( 0.7f, 0.7f, 0.7f, 1 );
			gl2.glClear( GL20.GL_COLOR_BUFFER_BIT );
	 
			gl2.glViewport ( 0, 0, viewportWidth, viewportHeight  );					
			gl2.glUseProgram ( program );
			 
			gl2.glVertexAttribPointer ( 0, 3, GL20.GL_FLOAT, false, 0, vertices );
			gl2.glEnableVertexAttribArray ( 0 );
			
			gl2.glDrawArrays ( GL20.GL_TRIANGLES, 0, 3 );
		}

		@Override
		public void onSurfaceChanged(GL10 gl, int width, int height) 
		{		
			viewportWidth = width;
			viewportHeight = height;
		}

		@Override
		public void onSurfaceCreated(GL10 gl, EGLConfig config) 
		{			
			String vertexShaderSrc =  "attribute vec4 vPosition;    \n" + 
									  "void main()                  \n" +
									  "{                            \n" +
									  "   gl_Position = vPosition;  \n" +
									  "}                            \n";
			String fragmentShaderSrc = "precision mediump float;\n" +
		      						   "void main()                                  \n" +
		      						   "{                                            \n" +
		      						   "  gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );\n" +
		      						   "}                                            \n";
			
			int vertexShader = loadShader( GL20.GL_VERTEX_SHADER, vertexShaderSrc );
			int fragmentShader = loadShader( GL20.GL_FRAGMENT_SHADER, fragmentShaderSrc );
			program = gl2.glCreateProgram();
			if( program == 0 )
				throw new RuntimeException( "creating program didn't work" );
			
			gl2.glAttachShader( program, vertexShader );
			gl2.glAttachShader( program, fragmentShader );
			
			gl2.glBindAttribLocation( program, 0, "vPosition" );
			gl2.glLinkProgram( program );
			
			ByteBuffer tmp = ByteBuffer.allocateDirect(4);
			tmp.order(ByteOrder.nativeOrder());
			IntBuffer intbuf = tmp.asIntBuffer();
			
			gl2.glGetProgramiv( program, GL20.GL_LINK_STATUS, intbuf );
			int linked = intbuf.get(0);
			if( linked == 0 )
			{
				gl2.glGetProgramiv( program, GL20.GL_INFO_LOG_LENGTH, intbuf );
				int infoLogLength = intbuf.get(0);
				if( infoLogLength > 1 )
				{
					Log.d( "GL2", "couldn't link program: " + gl2.glGetProgramInfoLog( program ) );					
				}
				
				throw new RuntimeException( "Creating program didn't work" );
			}
			
			
			float vVertices[] = {  0.0f,  0.5f, 0.0f, 
                       			   -0.5f, -0.5f, 0.0f,
                       			   0.5f, -0.5f, 0.0f };	
			
			tmp = ByteBuffer.allocateDirect( 3 * 3 * 4 );
			tmp.order(ByteOrder.nativeOrder());
			vertices = tmp.asFloatBuffer();
			vertices.put( vVertices );
			vertices.position(0);
		}
    	
		private int loadShader( int type, String source )
		{
			ByteBuffer tmp = ByteBuffer.allocateDirect(4);
			tmp.order(ByteOrder.nativeOrder());
			IntBuffer intbuf = tmp.asIntBuffer();
			
			int shader = gl2.glCreateShader( type );
			if( shader == 0 )
				throw new RuntimeException( "creating the shader didn't work" );
			gl2.glShaderSource( shader, source );
			gl2.glCompileShader( shader );
			gl2.glGetShaderiv( shader, GL20.GL_COMPILE_STATUS, intbuf );
			int compiled = intbuf.get(0);
			if( compiled == 0 )
			{					
				gl2.glGetShaderiv( shader, GL20.GL_INFO_LOG_LENGTH, intbuf );
				int infoLogLength = intbuf.get(0);
				if( infoLogLength > 1 )
				{
					String infoLog = gl2.glGetShaderInfoLog( shader );
					Log.d( "GL2", "shader info: " + infoLog );
				}
				throw new RuntimeException( "creating the shader didn't work" );
			}
			
			return shader;
		}
    }
}

This is more or less the hello triangle example from the purple book.
Here’s a couple of things that i changed:

– glGetActiveAttrib and glGetActiveUniform return a string which is
the name of the attribute/uniform.
– glGetProgramLogInfo and glGetShaderLogInfo also directly return a
string. the log is limited to 10k characters internally
– glShaderSource only takes the shader handle and a single java
string.
– glGetShaderSource is not implemented, i will add that some time soon
– glGetVertexAttribPointerv is not implemented and i don’t plan on
implementing it

Everything else is implemented 100% to the specification. I actually
took the gl2.h file from the khronos site to generate the interfaces
and stubs.

You can find the full source code at http://code.google.com/p/gl2-android/,
check it out via subversion from http://gl2-android.googlecode.com/svn/trunk/.
You’ll get an Android eclipse project which a pre-build shared
library. If you want to rebuild the native code you have to add an
Application.mk to your NDK apps/ folder somewhere. The eclipse project
is set to target 2.0, however the library works fine with 1.5 devices
too as long as you don’t try to access OpenGL ES 2.0 functionality.
Simply use the check for OpenGL ES 2.0 from above to decide which
render path to go.

And for your convenience i also packaged the shared library as well as
the jar file with the Java classes. You can find that at
http://gl2-android.googlecode.com/files/androidgl2-0.1.zip. The whole thing is apache 2 licensed so you can do with it whatever you want.

Now write some awesome shaders!

27 thoughts on “OpenGL ES 2.0 Java bindings for Android

  1. Thanks! Just what I was looking for! Hopefully they’ll include some supported ES2 bindings in a future android SDK release, but for now this works great.

  2. On the googlecode project page you say this will be obselete when Froyo is released. Unfortunately, Google have missed out the VBO compatible version of glVertexAttribPointer, so I’m still having to use your code. Thanks for it. (By the way, v0.3 on the downloads page only has java files in the jar, not class files.)

  3. I only skimmed over the Froyo bindings. That’s bad news :/

    Sorry for the fuck up with the jar. I’ll repackage it asap and upload a new version. Thanks for pointing that out.

  4. Hi, I’m trying to get the example running, but the Eclipse debugger keeps telling me this:

    “AndroidChess [Android Application]
    DalvikVM[localhost:8611]
    Thread [ main] (Suspended (exception UnsatisfiedLinkError))
    Runtime.loadLibrary(String, ClassLoader) line: 489
    System.loadLibrary(String) line: 557
    AndroidGL20.() line: 14
    Main$TestRenderer.(Main) line: 82
    Main.onCreate(Bundle) line: 39
    Instrumentation.callActivityOnCreate(Activity, Bundle) line: 1047
    ActivityThread.performLaunchActivity(ActivityThread$ActivityRecord, Intent) line: 2459
    ActivityThread.handleLaunchActivity(ActivityThread$ActivityRecord, Intent) line: 2512
    ActivityThread.access$2200(ActivityThread, ActivityThread$ActivityRecord, Intent) line: 119
    ActivityThread$H.handleMessage(Message) line: 1863
    ActivityThread$H(Handler).dispatchMessage(Message) line: 99
    Looper.loop() line: 123
    ActivityThread.main(String[]) line: 4363
    Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
    Method.invoke(Object, Object…) line: 521
    ZygoteInit$MethodAndArgsCaller.run() line: 860
    ZygoteInit.main(String[]) line: 618
    NativeStart.main(String[]) line: not available [native method]
    Thread [ Binder Thread #2] (Running)
    Thread [ Binder Thread #1] (Running)

    When looking at the “Variables” pane for the call to loadLibrary, the libName is “androidgl20″.
    I have added the .jar to the project and adjusted the native library path. Is there something I forgot?
    The error appears in the emulator as well as on the device.

    Thanks in advance!

  5. I have no idea how you can adjust the native library path on Android. Instead you normally create libs/armeabi folder and put the .so file in there (in this case androidgl20.so). See the project setup here. Also, OpenGL ES 2.0 is not supported by the emulator, so trying it there even with the shared library in place will not work. Finally, your device has to support OpenGL ES 2.0, almost all second gen devices do (N1,Droid,etc.).

  6. This is probably my fault somewhere, but im having trouble compiling these bindings.

    I have copy/pasted your app code from above, and imported the library to netbeans.

    However I get a force-close when trying to run the sample. Using DDMS it tells me the error is:

    “07-29 17:17:49.968: ERROR/AndroidRuntime(477): Caused by: java.lang.UnsatisfiedLinkError: Library androidgl20 not found

    and also

    “07-29 17:17:49.968: ERROR/AndroidRuntime(477): at com.badlogic.gdx.backends.android.AndroidGL20.(AndroidGL20.java):14)”

    If I have missed a step, apologies.

    Thanks in advance.

    Paul

  7. Hi again.

    I’m using glUniformMatrix4fv to set my projection matrix, however it is coming back with an error: AndroidRuntime(27382): java.lang.IllegalArgumentException: Must use a native order direct Buffer.

    The last argument specifies a floatbuffer and this is what I’m passing, but I get this error? Again its probably something I’m doing.

    Any help? Thanks again.

  8. Ay, i think i stated this earlier already. Passing in the FloatBuffer is good and well, however you need to allocate a direct FloatBuffer. You can do so via ByteBuffer.allocateDirect( numberOfBytes ).asFloatBuffer(). For a 4×4 matrix you’ll want to allocate 16*4=64 bytes.

  9. Hi,

    Thanks for sharing this. I’m doing something similar. I wish to write a jni bridge for another library (which I have the source code for). I was wondering how you wrote your jni bridge? Is there a tool to do that? I did some research and found SWIG but I’m a little unsure how to use it. Could you give a step-by-step guide of how you came up with the jni bridge for opengl es2? Thanks.

  10. I’m afraid there’s not simple silver bullet approach for writting a JNI bridge. Swig is great but needs some getting used too. Depending on the size of the API you want to wrap (read: it should be small) you can do it manually.

    1) Create one or more Java classes that reflect the native API as you want them.
    2) For each method in your Java API create another method with the attribute “native”, this you will need to implement in C/C++ later on.
    3) Open your favorite shell and issue a “javah -o MyShinyInclude.h -classpath bin/ your.package.and.ClassName. bin/ is where your .class files are located after compiling the Java stuff
    4) Open the .h files you just created and copy & paste the methods in there to a nice little .cpp or .c file. Include the header .h file in your C++ code.
    5) The functiosn in your cpp file correspond to the native methods of your Java file. Fill them out!
    6) Compile this crap to a shared library with your favorite C++ compiler
    7) Before using your native Methods invoke System.loadLibrary() once to load the shared library you just created.
    8)…
    9) profit

    The process is of course a bit more complex. For one, you have to know the Java Native Interface API to transfer data from Java to native code and vice versa unless it’s primitive types like int, short and so on. I’m afraid i don’t have the time at the moment to give a detailed JNI tutorial. There’s a shitload of info on the process on the web though. I suggest using your favorite search engine.

    hth,
    Mario

  11. Now that I finally got me an Android phone knowing that it can handle OpenGL ES 2.0 I wanted to mess with that again. VBOs and FBOs are just “natural”, infact, all the stuff I did so far with OpenGL ES 2.0 was based on these. Blind as I was I happily converted my heavy templated C++ Code into Java (generics are sooooo useless …), just to short stop at the weird interface of the VertexAttribute function, wondering how the calling convention for using a VBO index would be. Long story short, I ended up here, using your binding and things are finally working as they should. The only problem I had was with this Buffer stuff, it gave me a real headache and a serious feeling of “WTF?!” (Java has a couple cool things, but sometimes I just could …)

    So, thanks for providing us a fully featured OpenGL/ES 2.0 binding! Reading your blog also lowered my expectations towards the different driver implementations; it seems like things are all the same on the mobile market as they are in the desktop one (it’s “amazing” how even basic functionality breaks from one driver version to the next or gets working again, and how much code you have to add just to circumvent all these issues).

  12. Hi,
    Thanks for the lib!
    The “hello triangle” example works well… until I add a simple line:
    mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    in the GL2Test::OnCreate() event…. it then crashes.
    Anyone experienced issues with that ? I don’t have any problem when I use the standard OpenGL ES 1.x.

    Thanks

  13. Hi, I’m new to OpenGL and OpenGL ES. Is the main thing here that the NDK provides OpenGL ES 2.0 support while the android SDK only provides OpenGL 10 support. If so this could be really good as a development wrapper.

    Thanks and good job
    John

  14. Hi
    thanks for sharing. It was very handy.
    This is what I made by using your bindings… I ve ported my game from gles 1 to gles 2 and was able to add some nice effects (most noticeable would be the water and car metal in this track)
    http://www.youtube.com/watch?v=EwTi-EyTxqU

    I will credit you and your bindings in the final credits if I ever finish it :)

  15. Wow, this looks great. What did you use for the physics? Looks like you’ve rolled your own physics engine to me :)

  16. thanks
    you are right… I did very simple physics for it. I will improve it for the final version though. :)

  17. Works like a charm except glDrawElements when not passing a VBO but a buffer with indices; no matter what I try (even just 1 triangle), it bombs out the entire activity.

    gl2.glDrawElements(gl2.GL_TRIANGLES, 3, gl2.GL_UNSIGNED_SHORT, bx);

    where bx is a directbuffer, native order, holding 3 shorts as vertice indexes for the triangle. Oddly enough (once you get desperate you try everything), with GL_SHORT no crash (but no drawn output either).

    Calling glDrawArray draws the triangle ok so vertices are alright.

    Anyone who can shed light on this would be my hero.

  18. I resolved my issue by doing a rebuild of the NDK part, after that glDrawElements with index buffer works like a charm.

    Still beyond me though why it wouldnt work before…

    Excellent work, thanks for sharing these bindings, great to do GL2 with A2.1 compatbility.

  19. It’s really strange that this would not work with GL_UNSIGNED_SHORT, as per specification, that’s the constant expected there (among two afair). We use those exact bindings in libgdx and never had a problem. We use indexed vertex array geometry (so no VBOs) with our SpriteBatch in GLES 2.0, no problem to be found. Strange indeed.

  20. Once rebuild it works fine with UNSIGNED (as it should). Just in the ‘not working’ situation it would crash with unsigned, and just draw nothing (but no crash) nothing with just SHORT – again, really odd. After a rebuild of the .so by using NDK the test ran fine without any changes to either the testapp (using unsigned short) or the bindings.

    Maybe the .so was just mangled due to my winzip or something.

    And now, on to the fun part of shader programming :)

  21. It will be great help, if it works for me. The GL2Test throws ‘creating shader didn’t work’. Does this mean I don’t have support for gles2.0? But my flashed HTC magic is running on android 2.3.3. I guess my device(gpu hardware) only supports gles1.1. Is there any option to use gles2.0 with such bindings? BTW your book is very helpful for beginners like me…Thanks.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>