Java – glClearColor not working (android opengl)

glClearColor not working (android opengl)… here is a solution to the problem.

glClearColor not working (android opengl)

I want to change the background color of my application at runtime.
So when clicking on the button I call first:

GLES20.glClearColor(color[0], color[1], color[2], color[3]);

Then I called:

GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

It does nothing! It keeps the current background color – does not change it. But when I pause my app and resume it again, the background color changes.

Edit:
I found a way to do this. Every frame I call glClear first, but I don’t call glClearColor. So if I call glClearColor before calling glClear every frame, it works. But that still doesn’t make sense to me, and I want to avoid calling glClearColor every frame, thinking that calling it once if I want to change the color is enough.

Solution

You can only make OpenGL calls if you have the current OpenGL context. When you use GLSurfaceView, context processing takes care of it for you, so it seems to work magically. Until something goes wrong, as in your case. Let me explain what’s going on behind the scenes and not just give you solutions to avoid surprises in the future.

Before you can make any OpenGL calls, you need to create an OpenGL context and set it as the current context. On Android, this uses the EGL API. GLSurfaceView takes care of it for you, and it all happens before onSurfaceCreated() is called on your renderer. Therefore, when a method on your Renderer implementation is called, you can always count on having the current context without worrying about it.

However, the key aspect is that the current context is per-thread. GLSurfaceView creates a rendering thread where all Renderer methods are called.

The result of this is that you cannot make OpenGL calls from other threads because they do not have the current OpenGL context. This includes the UI thread. That’s exactly what you want to do. If you call glClearColor() in response to a button click, you are in the UI thread and do not have the current OpenGL context.

In this case, the workaround you have found may actually be the most realistic one. glClearColor() should be a cheap call, so it doesn’t matter if you do it before each glClear(). If the action you need to take is more expensive, you can also set a boolean flag when the value changes, and then do the corresponding work in onDrawFrame() only if the flag is set.

There is another subtle but very important aspect here: thread safety. Once you set values in one thread (UI thread) and use them in another thread (render thread), this is something you have to worry about. Suppose you have 3 RGB component values for background colors, and you set them one by one in the UI thread. The render thread may use these 3 values when the UI thread sets them, eventually mixing the old and new values.

To illustrate all of this, I’ll use your example and sketch out an effective and thread-safe solution. The class members involved may look like this:

float mBackRed, mBackGreen, mBackBlue;
boolean mBackChanged;
Object mBackLock = new Object();

Then set the location of the value in the UI thread:

synchronized(mBackLock) {
    mBackRed = ...;
    mBackGreen = ...;
    mBackBlue = ...;
    mBackChanged = true;
}

And in the onDrawFrame() method before calling glClear().

Boolean changed = false;
float backR = 0.0f, backG = 0.0f, backB = 0.0f;
synchronized(mBackLock) {
    if (mBackChanged) {
        changed = true;
        backR = mBackRed;
        backG = mBackGreen;
        backB = mBackBlue;
        mBackChanged = false;
    }
}

if (changed) {
    glClearColor(backR, backG, backB, 0.0f);
}

Notice how all access to class members shared by the two threads occurs in the lock. In the last code fragment, also note how the color values are copied to local variables before use. This may be overdone for this simple example, but I want to illustrate the overall goal of holding locks as briefly as possible. If you use member variables directly, you must call glClearColor() inside the lock. If this is an operation that can take a long time, the UI thread cannot update the value and may be stuck for a while waiting for a lock.

There is also an alternative to using locks. GLSurfaceView has a queueEvent() method that allows you to pass in a Runnable and execute it in the render thread. There is an example in the GLSurfaceView documentation, so I won’t go into detail about the code here.

Related Problems and Solutions