Putting It Together

To round this section out, let's put all this together via a nice GLGame and Screen implementation. Listing 7-5 shows the complete example.

Listing 7-5. FirstTriangleTestjava package com.badlogic.androidgames.glbasics;

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

import javax.microedition.khronos.opengles.GL10;

import com.badlogic.androidgames.framework.Game; import com.badlogic.androidgames.framework.Screen;

import com.badlogic.androidgames.framework.impl.GLGame; import com.badlogic.androidgames.framework.impl.GLGraphics;

public class FirstTriangleTest extends GLGame { @Override public Screen getStartScreen() {

return new FirstTriangleScreen(this);

The FirstTriangleTest class derives from GLGame, and thus has to implement the Game.getStartScreen() method. In that method we create a new FirstTriangleScreen, which will then be frequently called to update and present itself by the GLGame. Note that when this method is called, we are already in the main loop—or rather the GLSurfaceView rendering thread—so we can use OpenGL ES methods in the constructor of the FirstTriangleScreen class. Let's have a closer look at that Screen implementation:

class FirstTriangleScreen extends Screen { GLGraphics glGraphics; FloatBuffer vertices;

public FirstTriangleScreen(Game game) { super(game);

glGraphics = ((GLGame)game).getGLGraphics();

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(3 * 2 * 4); byteBuffer.order(ByteOrder.nativeOrder()); vertices = byteBuffer.asFloatBuffer(); vertices.put( new float[] { 0.0f, 0.0f,

vertices.flip();

The FirstTriangleScreen class holds two members: a GLGraphics instance and our trusty FloatBuffer, which stores the 2D positions of the three vertices of our triangle. In the constructor we fetch the GLGraphics instance from the GLGame and create and fill the FloatBuffer according to our previous code snippet. Since the Screen constructor gets a Game instance, we have to cast it to a GLGame instance so we can use the GLGame.getGLGraphics() method.

@Override public void present(float deltaTime) { GL10 gl = glGraphics.getGL();

gl.glViewport(0, 0, glGraphics.getWidth(), glGraphics.getHeight());

gl.glClear(GL10. GL_COLOR_BUFFER_BIT);

gl.glMatrixMode(GL10.GL_PROJECTION);

gl.glLoadIdentity();

gl.glColor4f(1, 0, 0, 1); gl.glEnableClientState(GL10. GL_VERTEX_ARRAY); gl.glVertexPointer( 2, GL10.GL_FLOAT, 0, vertices); gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

The present() method then reflects what we just discussed: we set the viewport, clear the screen, set the projection matrix so that we can work in our custom coordinate system, set the default vertex color (red in this case), specify that our vertices will have positions, tell OpenGL ES where it can find those vertex positions, and finally render our awesome little red triangle.

@Override public void update(float deltaTime) { game.getInput().getTouchEvents(); game.getInput().getKeyEvents();

@Override public void resume() { }

@Override public void dispose() { }

The rest of the class is just boilerplate code. In the update() method we make sure that our event buffers don't get filled up. The rest of the code does nothing.

NOTE: From here on we'll only focus on the Screen classes themselves, as the enclosing GLGame derivatives, such as FirstTriangleTest, will always be the same. We'll also reduce the code size a little by leaving out any empty or boilerplate methods of the Screen class. The following examples will all just differ in terms of members, constructors, and present methods.

Figure 7-7 shows the output of the preceding example.

Figure 7-7. Our first sexy triangle

So here's what we did wrong in this example in terms of OpenGL ES best practices:

■ We set the same states to the same values over and over again without any need. State changes in OpenGL ES are expensive—some a little bit more, some a little bit less. We should always try to reduce the number of state changes we make in a single frame.

■ The viewport and projection matrix will never change once we set them. We could move that code to the resume() method, which is only called once each time the OpenGL ES surface gets (re)-created, thus also handling OpenGL ES context loss.

■ We could also move setting the color used for clearing and setting the default vertex color to the resume() method. These two colors won't change either.

■ We could move the glEnableClientState() and glVertexPointer() methods to the resume() method.

■ The only things that we need to call each frame are glClear() and glDrawArrays(). Both use the current OpenGL ES states, which will stay the same as long as we don't change them and as long as we don't lose the context due to the Activity being paused and resumed.

If we had put these optimizations into practice, we would have only two OpenGL ES calls in our main loop. For the sake of clarity, we'll refrain from using these kind of minimal state change optimizations for now. When we start writing our first OpenGL ES game, though, we'll have to follow those practices as best as we can to guarantee good performance.

Let's add some more attributes to our triangle's vertices, starting with color.

NOTE: Very, very alert readers might have noticed that the triangle in Figure 7-7 is actually missing a pixel in the bottom-right corner. This may look like a typical off-by-one error, but it's actually due to the way OpenGL ES rasterizes (draws the pixels of) the triangle. There's a specific triangle rasterization rule that is responsible for that artifact. Worry not—we are mostly concerned with rendering 2D rectangles (composed of two triangles), where this effect will vanish.

0 0

Post a comment