Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

glClearColor not working correct (android opengl)

I want to change the background color of my app on runtime. So on button click I first call:

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

Then I call:

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

And it does nothing! It keeps the current background color - doesnt change it. But when I then pause my app and resume it again the background color is changed.

EDIT: I found out a way to do it. Each frame i first call glClear but I dident call glClearColor. So if I first call glClearColor each frame before I call glClear it works. But this still doesnt make sense to me, I wanted to avoid calling glClearColor at each frame, thought it would be enough if I call it once when I want to change the color.

like image 366
Alexanus Avatar asked May 07 '15 07:05

Alexanus


1 Answers

You can only make OpenGL calls while you have a current OpenGL context. When you use GLSurfaceView, context handling is taken care for you, so it all just magically seems to work. Until something goes wrong, like in your case. Instead of giving you only the solution, let me still explain what happens under the hood, to avoid future surprises.

Before you can make any OpenGL calls, an OpenGL context needs to be created, and be set as the current context. On Android, this uses the EGL API. GLSurfaceView handles that for you, and this all happens before onSurfaceCreated() is called on your renderer. So when the methods on your Renderer implementation are called, you can always count on having a current context, without ever having to worry about it.

The crucial aspect is however that the current context is per thread. GLSurfaceView creates a rendering thread, and all the Renderer methods are invoked in this thread.

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

The workaround you already found might in fact be the most realistic solution in this case. glClearColor() should be a cheap call, so making it before every glClear() will not be significant. If the action you needed to take were more expensive, you could also set a boolean flag when the value changed, and then only do the corresponding work in onDrawFrame() if the flag is set.

There's another subtle but very important aspect here: thread safety. As soon as you set values in one thread (UI thread) and use them in another thread (rendering thread), this is something you have to worry about. Say if you have 3 values for the RGB components of the background color, and you set them in the UI thread one by one. It's possible that the rendering thread uses the 3 values while the UI thread is setting them, ending up with a mix of old and new values.

To illustrate all of this, I'll use your example, and sketch out a working and thread safe solution. The class members involved could look like this:

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

Then where you set 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);
}

Note how all access to the class members shared by the two threads is inside a lock. In the last code fragment, also note how the color values are copied to local variables before being used. This may be going too far for this simple example, but I wanted to illustrate the general goal that the lock should be held as briefly as possible. If you use the member variables directly, you would have to make the glClearColor() call inside the lock. If this is an operation that could take a long time, the UI thread could not update the values, and might be stuck for a while waiting for the lock.

There is an alternate approach to using a lock. GLSurfaceView has a queueEvent() method that allows you to pass in a Runnable that will then be executed in the rendering thread. There is an example for this in the GLSurfaceView documentation, so I won't spell out code for it here.

like image 138
Reto Koradi Avatar answered Oct 16 '22 03:10

Reto Koradi