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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
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!
Wow this looks great. Thanks for making this available, it looks perfect for what I wanted to do.
-Kevin
Cool, hope it works for you. Remember to drop by again and showing us what you made with it!
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.
Glad to help.
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.)
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.
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!
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.).
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
Oops, seems like my problem is related to the poster above!
Many thanks.
Paul
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.
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.
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.
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
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).
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
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
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 🙂
Wow, this looks great. What did you use for the physics? Looks like you’ve rolled your own physics engine to me 🙂
thanks
you are right… I did very simple physics for it. I will improve it for the final version though. 🙂
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.
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.
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.
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 🙂
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.