GLSurface View Making Things Easy Since 2008

The first thing we need is some type of View that will allow us to draw via OpenGL ES. Luckily there's such a View in the Android API. It's called GLSurfaceView, and it's a descendent of the SurfaceView class, which we already used for drawing the world of Mr. Nom.

We also need a separate main loop thread again so that we don't bog down the UI thread. Surprise: GLSurfaceView already sets up such a thread for us! All we need to do is implement a listener interface called GLSurfaceView.Renderer and register it with the GLSurfaceView. The interface has three methods:

interface Renderer {

public void onSurfaceCreated(GL10 gl, EGLConfig config);

public void onSurfaceChanged(GL10 gl, int width, int height);

public void onDrawFrame(GL10 gl);

The onSurfaceCreated() method is called each time the GLSurfaceView surface is created. This happens the first time we fire up the Activity and each time we come back to the Activity from a paused state. The method takes two parameters, a GL10 instance and an EGLConfig. The GL10 instance allows us to issue commands to OpenGL ES. The EGLConfig just tells us about the attributes of the surface, such as the color depth and so on. We usually ignore it. We will set up our geometries and textures in the onSurfaceCreated() method.

The onSurfaceChanged() method is called each time the surface is resized. We get the new width and height of the surface in pixels as parameters, along with a GL10 instance if we want to issue OpenGL ES commands.

The onDrawFrame() method is where the fun happens. It is similar in spirit to our Screen.render() method, which gets called as often as possible by the rendering thread that the GLSurfaceView sets up for us. In this method we perform all our rendering.

Besides registering a Renderer listener, we also have to call

GLSurfaceView.onPause()/onResume() in our Activity's onPause()/onResume() methods. The reason for this is simple. The GLSurfaceView will start up the rendering thread in its onResume() method and tear it down in its onPause() method. This means that our listener will not be called while our Activity is paused, since the rendering thread which calls our listener will also be paused.

And here comes the only bummer: each time our Activity is paused, the surface of the GLSurfaceView will be destroyed. When the Activity is resumed again (and GLSurfaceView.onResume() is called by us), the GLSurfaceView instantiates a new OpenGL ES rendering surface for us, and informs us of this by calling our listener's onSurfaceCreated() method. This would all be well if not for a single problem: all the OpenGL ES states we set so far will be lost. This also includes things such as textures and so on, which we'll have to reload in that case. This problem is known as a context loss. The word context stems from the fact that OpenGL ES associates a so-called context with each surface we create, which holds the current states. When we destroy that surface, the context is lost as well. It's not all that bad, though, given that we design our games properly to handle this context loss.

NOTE: Actually, it's EGL that is responsible for the context and surface creation and destruction. EGL is another Khronos Group standard; it defines how an operating system's UI works together with OpenGL ES, and how the operating system grants OpenGL ES access to the underlying graphics hardware. This includes surface creation as well as context management. Since GLSurfaceView handles all the EGL stuff for us, we can safely ignore it in almost all cases.

Following tradition, let's write a small example that will clear the screen with a random color each frame. Listing 7-1 shows the code.

Listing 7-1. GLSurfaceViewTestjava; Screen-Clearing Madness package com.badlogic.androidgames.glbasics;

import java.util.Random;

import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;

import android.opengl.GLSurfaceView;

import android.opengl.GLSurfaceView.Renderer;

import android.os.Bundle;

import android.util.Log;

import android.view.Window;

import android.view.WindowManager;

public class GLSurfaceViewTest extends Activity {

GLSurfaceView glView;

public void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(windowManager.LayoutParams.FLAG_FULLSCREEN,

WindowManager.LayoutParams.FLAG_FULLSCREEN); glView = new GLSurfaceView(this); glView.setRenderer(new SimpleRenderer()); setContentView(glView);

We keep a reference to a GLSurfaceView instance as a member of the class. In the onCreate() method, we make our application go full-screen, create the GLSurfaceView, set our Renderer implementation, and make the GLSurfaceView the content view of our Activity.

@Override public void onResume() { super.onPause(); glView.onResume();

@Override public void onPause() { super.onPause(); glView.onPause();

In the onResume() and onPause() methods, we call the supermethods as well as the respective GLSurfaceView methods. These will start up and tear down the rendering thread of the GLSurfaceView, which in turn will trigger the callback methods of our Renderer implementation at appropriate times.

static class SimpleRenderer implements Renderer { Random rand = new Random();

@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { Log.d("GLSurfaceViewTest", "surface created");

@Override public void onSurfaceChanged(GL10 gl, int width, int height) { Log.d("GLSurfaceViewTest", "surface changed: " + width + "x" + height);

@Override public void onDrawFrame(GL10 gl) {

gl.glClearColor(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), 1); gl.glClear(GL10. GL_COLOR_BUFFER_BIT);

The final piece of the code is our Renderer implementation. It's just logs some information in the onSurfaceCreated() and onSurfaceChanged() methods. The really interesting part is the onDrawFrame() method.

As said earlier, the GL10 instance gives us access to the OpenGL ES API. The 10 in GL10 indicates that it offers us all the functions defined in the OpenGL ES 1.0 standard. For now we can be happy with that. All the methods of that class map to a corresponding C function, as defined in the standard. Each method begins with the prefix gl, an old tradition of OpenGL ES.

The first OpenGL ES method we call is glClearColor(). You probably already know what that will do. It sets the color to be used when we issue a command to clear the screen. Colors in OpenGL ES are almost always RGBA colors where each component has a range between 0 and 1. There are ways to define a color in, say, RGB565, but for now let's stick to the floating-point representation. We could set the color used for clearing only once and OpenGL ES would remember it. The color we set with glClearColor() is one of OpenGL ES's states.

The next call actually clears the screen with the clear color we just specified. The method glClear() takes a single argument that specifies which buffer to clear. OpenGL ES does not only have the notation of a framebuffer that holds pixels, but also other types of buffers. We'll get to know them in Chapter 10, but for now all we care about is the framebuffer that holds our pixels. OpenGL ES calls that the color buffer. To tell OpenGL ES that we want to clear that exact buffer, we specify the constant GL10.GL_COLOR_BUFFER_BIT.

OpenGL ES has a lot of constants, which are all defined as static public members of the GL10 interface. Like the methods, each constant has the prefix GL_.

And that was our first OpenGL ES application. I'll spare you the impressive screenshot, since you probably know what it looks like.

NOTE: Thou shall never call OpenGL ES from another thread! First and last commandment! The reason is that OpenGL ES is designed to be used in single threaded environments only and is not thread-safe. It can be made to somewhat work on multiple threads, but many drivers have problems with this and there's no real benefit to doing so.

0 0

Post a comment