OpenGL ES, as indicated, is a standard that is supported by a number of platforms. At the core, it's a C-like API that addresses all of the OpenGL drawing chores. However, each platform and OS is different in the way it implements displays, screen buffers, and the like. These OS-specific aspects are left to each operating system to figure out and document. Android is no different.
Starting with its 1.5 SDK, Android simplified the interaction and initialization process necessary to start drawing in OpenGL. This support is provided in the package android.opengl. The primary class that provides much of this functionality is GLSurfaceView, and it has an internal interface called GLSurfaceView.Renderer. Knowing these two entities is sufficient to make a substantial headway with OpenGL on Android.
Using GLSurfaceView and Related Classes
Starting with 1.5 of the SDK, the common usage pattern for using OpenGL is quite simplified. (Refer to the first edition of this book to see the Android 1.0–approach.) Here are the typical steps to draw using these classes:
Implement the Renderer interface.
Provide the Camera settings needed for your drawing in the implementation of the renderer.
Provide the drawing code in the onDrawFrame method of the implementation.
Construct a GLSurfaceView.
Set the renderer implemented in steps 1 to 3 in the GLSurfaceView.
Indicate whether you want animation or not to the GLSurfaceView.
Set the GLSurfaceView in an Activity as the content view. You can also use this view wherever you can use a regular view.
Let's start with how to implement the renderer interface.
The signature of the Renderer interface is shown in Listing 20–8.
Listing 20–8. The Renderer Interface
public static interface GLSurfaceView.Renderer
{
void onDrawFrame(GL10 gl);
void onSurfaceChanged(GL10 gl, int width, int height);
void onSurfaceCreated(GL10 gl, EGLConfig config);
}
The main drawing happens in the onDrawFrame() method. Whenever a new surface is created for this view, the onSurfaceCreated() method is called. We can call a number of OpenGL APIs such as dithering, depth control, or any others that can be called outside of the immediate onDrawFrame() method.
Similarly, when a surface changes, such as the width and height of the window, the onSurfaceChanged() method is called. We can set up our camera and viewing volume here.
Even in the onDrawFrame() method there are lot of things that may be common for our specific drawing context. We can take advantage of this commonality and abstract these methods in another level of abstraction called an AbstractRenderer, which will have only one method that is left unimplemented called draw().
Listing 20–9 shows the code for the AbstractRenderer.
Listing 20–9. The AbstractRenderer
//filename: AbstractRenderer.java
import android.opengl.*;
//…Use Eclipse to resolve other imports
public abstract class AbstractRenderer
implements android.opengl.GLSurfaceView.Renderer
{
public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
gl.glDisable(GL10.GL_DITHER);
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);
}
public void onSurfaceChanged(GL10 gl, int w, int h) {
gl.glViewport(0, 0, w, h);
float ratio = (float) w / h;
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
}
public void onDrawFrame(GL10 gl)
{
gl.glDisable(GL10.GL_DITHER);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
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);
draw(gl);
}
protected abstract void draw(GL10 gl);
}
Having this abstract class is very useful, as it allows us to focus on just the drawing methods. We’ll use this class to create a SimpleTriangleRenderer class; Listing 20–10 shows the source code.
Listing 20–10. SimpleTriangleRenderer
//filename: SimpleTriangleRenderer.java
public class SimpleTriangleRenderer extends AbstractRenderer
{
//Number of points or vertices we want to use
private final static int VERTS = 3;
//A raw native buffer to hold the point coordinates
private FloatBuffer mFVertexBuffer;
//A raw native buffer to hold indices
//allowing a reuse of points.
private ShortBuffer mIndexBuffer;
public SimpleTriangleRenderer(Context context)
{
ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
vbb.order(ByteOrder.nativeOrder());
mFVertexBuffer = vbb.asFloatBuffer();
ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
ibb.order(ByteOrder.nativeOrder());
mIndexBuffer = ibb.asShortBuffer();
float[] coords = {
-0.5f, -0.5f, 0, // (x1,y1,z1)
0.5f, -0.5f, 0,
0.0f, 0.5f, 0
};
for (int i = 0; i < VERTS; i++) {
for(int j = 0; j < 3; j++) {
mFVertexBuffer.put(coords[i*3+j]);
}
}
short[] myIndecesArray = {0,1,2};
for (int i=0;i<3;i++)
{
mIndexBuffer.put(myIndecesArray[i]);
}
mFVertexBuffer.position(0);
mIndexBuffer.position(0);
}
//overriden method
protected void draw(GL10 gl)
{
gl.glColor4f(1.0f, 0, 0, 0.5f);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, VERTS,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
}
}
Although there seems to be a lot of code here, most of it is used to define the vertices and then translate them to NIO buffers from Java buffers. Otherwise, the draw method is just three lines: set the color, set the vertices, and draw.
Note: Although we are allocating memory for NIO buffers, we never release them in our code. So who releases these buffers? How does this memory affect OpenGL?
According to our research, the java.nio package allocates memory space outside of the Java heap that can be directly used by such systems as OpenGL, File I/O, etc. The nio buffers are actually Java objects that eventually point to the native buffer. These nio objects are garbage collected. When they are garbage collected, they go ahead and delete the native memory. Java programs don’t have to do anything special to free the memory.
However, the gc won’t get fired unless memory is needed in the Java heap. This means you can run out of native memory and gc may not realize it. The Internet offers many examples on this subject where an out of memory exception will trigger a gc and then it’s possible to inquire if memory is now available due to gc having been invoked.
Under ordinary circumstances—and this is important for OpenGL—you can allocate the native buffers and not worry about releasing allocated memory explicitly because that is done by the gc.
Now that we have a sample renderer, let's see how we can supply this renderer to a GLSurfaceView and have it show up in an Activity.
Share with your friends: |