Day 18: Bringing It on Stage – Part 2

Today’s post is a short one, but it is one of the key days of this tutorial. After today, we are going to have enough code to build an actual working app. On day 17, we saw how to separate rendering and animation handling. Today we are going to add the rendering code.

Drawing the Scene

If you have followed the series up to here, you have already written all the rendering code in Texture and related classes. The scenes are also self-drawing. All you need to do from stage is to call the rendering method on the appropriate scene.

Yesterday we cleared the content of onDrawFrame in Stage. Now we can add what we just discussed.

public final void onDrawFrame(GL10 gl) {
    gl.glClearColor(0, 0, 0, 1.0f);
    gl.glClear(GLES10.GL_COLOR_BUFFER_BIT);
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);

    if(currentScene != null) {
        if(!currentScene.isLoaded())
            currentScene.onLoaded();

        currentScene.render(gl);
    }
}

This code is very simple! The first two lines simply clear the screen. the arguments of glClearColor specify the color to fill the screen with, which is an opaque black in this case (R:0 G:0 B:0 A:1). The third line is exactly copied from our previous onDrawFrame method, and simply loads the rectangular vertex buffer we use to draw images. What comes below this line is to check if there is a scene, and if so, ask that scene to render itself.

Handling Textures

Our code above has one problem, and that is it does not check whether required textures are loaded. In order to achieve correct rendering, all textures must be loaded. If you remember from day 10, our texture manager has a “state” property, indicating whether it has loaded all textures, currently loading them, or is completely blue. This is what we are going to use now. So, insert the following code at the beginning of onDrawFrame:

if(ts != TextureManager.STATE_LOADED) {
    if(ts == TextureManager.STATE_FRESH)
        TextureManager.loadTextures(getContext());

    return;
}

Here we call loadTextures on the centralized texture manager if it is fresh (not already in a loading process), and return from the function immediately if there are unloaded textures.

There is still something we should be careful about. The OpenGL ES context can be destroyed for a couple of reasons, including device orientation change, and a navigation away from the app. When that happens, all loaded textures are cleared and their texture id’s invalidated. This means that we should load them again next time we want to render, which again means that we should be warned when this happens.

How do we know when the OpenGL ES is lost? According to the official documentation of onSurfaceCreated, this method is called whenever the EGL context is lost. So the signal we are looking for is simply a call to this method. Our reaction is also pretty simple. We unload all textures, as we know they are not relevant at this stage. Unloading them will make the state of the texture manager “fresh”, which causes our rendering code above to call the loadTextures method. It all lines up! So, add the following code at the end of the onSurfaceCreated method:

TextureManager.unload();

Custom Background

If you remember from day 16, the scene has a background color property. Currently, we do not use this property and just clear the screen in black. To spice things up, we now use it. Look for the following line in onDrawFrame:

gl.glClearColor(0, 0, 0, 1.0f);

And change it with the following code:

final RGB clearColor = currentScene.getBackColor();
gl.glClearColor(clearColor.r, clearColor.g, clearColor.b, 1.0f);

This code works, but there is a catch. What if the scene is null? We must check for this case, as this definitely happens at least before we set the scene. In order to handle this case, we define a default background color for stage, which is set to black. Add the following private field to stage:

private RGB defClearColor = new RGB(0, 0, 0);

Then the above code can be modified to:

final RGB clearColor = currentScene == null ? defClearColor : currentScene.getBackColor();
gl.glClearColor(clearColor.r, clearColor.g, clearColor.b, 1.0f);

Why didn’t we create defClearColor locally within the method? The reason is that for performance reasons, creating objects is highly discouraged during withing any rendering procedure, as it may happen frequently. Furthermore, we didn’t define it as final for more flexibility (so we can later encapsulate this field to be able to modify the default background color).

The final onDrawFrame method will look like this:

public final void onDrawFrame(GL10 gl) {
    final int ts = TextureManager.getState();

    if(ts != TextureManager.STATE_LOADED) {
        if(ts == TextureManager.STATE_FRESH)
            TextureManager.loadTextures(getContext());

        return;
    }

    final RGB clearColor = currentScene == null ? defClearColor : currentScene.getBackColor();

    gl.glClearColor(clearColor.r, clearColor.g, clearColor.b, 1.0f);
    gl.glClear(GLES10.GL_COLOR_BUFFER_BIT);

    if(currentScene != null) {
        if(!currentScene.isLoaded())
            currentScene.onLoaded();

        currentScene.render(gl);
    }
}

Next Steps

Our framework is now ready to display some graphics. Next day we will create a simple demonstration of how the system works. You may download today’s code below: