GlClearColor not working correctly (android opengl)

I want to change the background color of my application at runtime. Thus, when the button is clicked, 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 - doesn't change it. But when I pause the app and resume it again, the background color changes.

EDIT: I found out how to do this. Each frame I call first glClear

, but I call the call glClearColor

. So if I call glClearColor

each frame first before I call it glClear

, it works. But that still doesn't make sense to me, I wanted to avoid calling it glClearColor

in every frame, believing that it would be enough if I call it once when I want to change the color.

+3


source to share


1 answer


You can only make OpenGL calls as long as you have the current OpenGL context. When you use it GLSurfaceView

, the context handling will take care of you, so it just magically works. Until something goes wrong as in your case. Instead of just giving you a solution, let me explain what's going on under the hood to avoid future surprises.

Before you can make any OpenGL calls, you need to create an OpenGL context and be set as the current context. On Android this uses the EGL API. GLSurfaceView

handles this for you, and it all happens before it onSurfaceCreated()

gets called on your renderer. Therefore, when methods in your implementation are called Renderer

, you can always rely on the presence of the current context without worrying about it.

The most important aspect is that the current context is in the stream . GLSurfaceView

creates a render thread and all methods Renderer

are called on that thread.

The consequence of this is that you cannot create 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 trying to do. If you call the call glClearColor()

in response to a button click, you are on the UI thread and you do not have a current OpenGL context.

The workaround you've already found may actually be the most realistic solution in this case. glClearColor()

should be a cheap call, so making it in front of everyone glClear()

won't be significant. If the action you needed to do was more costly, you could also set a boolean flag when the value changed, and then do the appropriate work in onDrawFrame()

if the flag was set.

There is another subtle but very important aspect here: thread safety. Once you set values ​​on one thread (UI thread) and use them on another thread (render thread), this is what you need to worry about. Let's say if you have 3 values ​​for background color RGB components and you set them on the UI thread one by one. Perhaps the render thread is using 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 will use your example and draw a working and thread safe solution. The participating group members might look like this:



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

      

Then when you set the value on the UI thread:

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

      

And in the method onDrawFrame()

before the call 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 that access to class members shared by the two threads is inside the lock. In the last code snippet, also notice how the color values ​​are copied to local variables before being used. This might go a little too far for this simple example, but I wanted to illustrate the general goal of keeping the lock as short as possible. If you are using member variables directly, you will need to make the call glClearColor()

inside the lock. If it is an operation that can take a long time, the UI thread cannot update the values ​​and may get stuck waiting for a lock.

There is an alternative approach to using blocking. GLSurfaceView

has a method queueEvent()

that allows you to pass Runnable

, which will then be executed on the render thread. There GLSurfaceView

is an example in the documentation for this, so I won't be writing any code for it here.

+9


source







All Articles