3

I've managed to load textures and free rotate a sphere thanks to several tutorials, questions and answers asked here but i stumbled upon the need of texture reloading at runtime (get a bitmap image, process it and then apply it as a new texture to an object). I didnt find any working solutions for my specific problem (i've read all related questions and answers). Im quite new to OpenGL. This is my second project, my first 3D one and my first question asked here. So here it goes:

Basicaly the texture loading is done in the following function:

    public void loadGLTexture(final Context context, final int texture) {
    GLES20.glGenTextures(1, mTextures, 0);
    if (mTextures[0] != 0)
    {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled = false;
        final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), texture, options);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,   GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();
    }

    if (mTextures[0] == 0)
    {
        throw new RuntimeException("Texture load fail");
    }
}

While the draw is done in this function:

     public void draw(float[] mvpMatrix) {
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, this.mTextures[0]);
    GLES20.glFrontFace(GLES20.GL_CW);


    for (int i = 0; i < this.mTotalNumStrips; i++) {
        //vertex
        this.mVertexBuffer.get(i).position(0);
        GLES20.glVertexAttribPointer(mPositionHandle, NUM_FLOATS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, this.mVertexBuffer.get(i));
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //texture
        this.mTextureBuffer.get(i).position(0);
        GLES20.glVertexAttribPointer(mTextureCoordinateHandle, NUM_FLOATS_PER_TEXTURE,
                GLES20.GL_FLOAT, false,
                textureStride, this.mTextureBuffer.get(i));
        GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
        //draw strip
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, this.mVertices.get(i).length / NUM_FLOATS_PER_VERTEX);
    }

    GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
    // Disable the client state before leaving.
    GLES20.glDisableVertexAttribArray(mPositionHandle);
    GLES20.glDisableVertexAttribArray(mTextureCoordinateHandle);
}

In the above function i use the Strips tehnique to render a sphere. For each strip i have to load the texture and vertex data and finaly draw it.

I also have a function that should delete the textures that does nothing more than:

        public void clearGLTextures(){
    GLES20.glDeleteTextures(1, mTextures, 0);
}

What i want to achieve here is texture reloading so the plan was:

INITIALISE(works): loadGLTexture(initialtexture) -> loop the draw() function

RELOAD(does not work): clearGLTextures() -> loadGLTexture(newTexture) -> loop the draw() function

So not only that i cant reload the texture but also the call to clearGLTextures() seems not to work because the initialtexture remains on scren.

Any thoughts are welcome, Thanks!

andu
  • 146
  • 9
  • Are you making the calls to reload the textures in the rendering thread? – Reto Koradi Jun 10 '15 at 02:00
  • I am making the INITIALISE call in onSurfaceCreated of my renderer and the RELOAD calls at runtime on some click listener event. So basically every call i make at the moment is made on the same thread (UI thread) – andu Jun 10 '15 at 15:21
  • Those aren't the same threads. `GLSurfaceView` creates a rendering thread that all the `Renderer` methods are called in. You can't make OpenGL calls directly from the UI thread without doing much more work. I'll link some of my older answers to related questions below. – Reto Koradi Jun 10 '15 at 15:31
  • http://stackoverflow.com/questions/24954264/drawing-shape-on-button-click-using-opengl-in-android/24974316#24974316, http://stackoverflow.com/questions/24083972/opengl-es-2-0-android-color-picking/24084296#24084296, http://stackoverflow.com/questions/30094705/glclearcolor-not-working-correct-android-opengl/30106737#30106737 – Reto Koradi Jun 10 '15 at 15:31
  • Ill start document myself on the matters discussed in the shared links and ill search if the problem comes from there. Thanks for the search start. I will come back after i research the problem from this point of view. – andu Jun 10 '15 at 16:24
  • Ok it seems i got it and yes indeed was the thread communication problem. – andu Jun 10 '15 at 21:16
  • Im used in normal android development that such comunication on 2 threads without a propper interface would result in a error but here i did not receive no warning and i had no idea of the existance of a render thread. Your previus answers were clarifying. In short i solved it by placing my changeTexture() in a Runnable object and passed it to the method GLSurfaceView.queueEvent(). Simple and elegant after you understand the concept. Please provide an answer so i can mark as solved. – andu Jun 10 '15 at 21:23
  • Glad to hear that it's running. I'll add an answer later today. – Reto Koradi Jun 10 '15 at 22:04

2 Answers2

4

This is an example of a very common kind of problem when doing OpenGL programming on Android. Unfortunately the symptoms are very variable, so the questions are not really duplicates.

When you use GLSurfaceView, it creates a separate rendering thread. All the methods in your GLSurfaceView.Renderer implementation (onSurfaceCreated, onSurfaceChanged, onDrawFrame) are called in this rendering thread.

The second part of the puzzle is that you need a current OpenGL context to make OpenGL calls. And OpenGL contexts are current per thread. GLSurfaceView takes care of creating a context, and making it current in the rendering thread.

As a consequence, you can't make any OpenGL calls from other threads (unless you do much more work, and add complexity). The most common error is to try making OpenGL calls from the UI thread, typically when handling user input like touch events. Those calls will not work.

There are a number of options if you want to execute OpenGL calls in response to user input. For example, you can set members in the Renderer that describe the necessary state changes, and are checked in the onDraw() method. Another convenient option is using the queueEvent() method on the view, which allows you to pass in a Runnable that will later be executed in the rendering thread.

Reto Koradi
  • 53,228
  • 8
  • 93
  • 133
1

I just ran into the same problem.

Reto Koradi explained it great but I wanted to shared my solution: I used queueEvent with a Runnable in the GLSurfaceView.

public void addTexture(final int textureResId) {
    queueEvent(new Runnable() {
        @Override
        public void run() {
            mRenderer.loadTexture(textureResId);
            // or different GL thread tasks like clearing the texture
        }
    });
}
Björn Kechel
  • 7,933
  • 3
  • 54
  • 57