Search code examples
opengl-eslibgdxscene2djava-threads

Using TimerTask to change shader causes error 50% of the time - LIBGDX


I'm changing my shader using a TimerTask:

    public java.util.Timer ExplodeTimerTask = new Timer();
    ExplodeTimerTask.schedule(new TimerTask() {
        @Override
        public void run(){
            sprite_explosion.setRegion(textureAtlasExplosion.findRegion("spacesepxl0047"));
            exploded = true;
            game_upper_screen.TurnToSepiaForContrast();
        }
    }, 0);

    public void TurnToSepiaForContrast(){
        this.batch.setShader(shader_finish);
    }

50% of the time the shader is activated correctly, otherwise it fails with this error:

exploded one: oneException in thread "Timer-0" 
java.lang.RuntimeException: No OpenGL context found in the current thread.
    at org.lwjgl.opengl.GLContext.getCapabilities(GLContext.java:124)
    at org.lwjgl.opengl.GL11.glBindTexture(GL11.java:651)
    at com.badlogic.gdx.backends.lwjgl.LwjglGL20.glBindTexture(LwjglGL20.java:67)
    at com.badlogic.gdx.graphics.GLTexture.bind(GLTexture.java:77)
    at com.badlogic.gdx.graphics.g2d.SpriteBatch.flush(SpriteBatch.java:955)
    at com.badlogic.gdx.graphics.g2d.SpriteBatch.setShader(SpriteBatch.java:1056)
    at com.cryotrax.game._ex01MyGame.TurnToSepiaForContrast(_ex01MyGame.java:474)
    at com.cryotrax.game.ex01MyGameMain$2.run(ex011MyGameMain.java:1292)
    at java.util.TimerThread.mainLoop(Unknown Source)
    at java.util.TimerThread.run(Unknown Source)

I see this only on my desktop, not on mobile. What could be the issue? Is there another way I can ASYNC these calls so I don't get the error?

If I don't use the async method the shader sets up correctly all the time but I get some FPS drop and I don't want that. (I'm using framerate-independent movement.)


Solution

  • You're doing operations that require OpenGL state (the setShader call), so those can only be done from the Libgdx UI Thread. See https://github.com/libgdx/libgdx/wiki/Threading. This probably fails intermittently because sometimes the UI thread might handle the timer.

    You can fix this by using Application.postRunnable:

       Gdx.app.postRunnable(new Runnable() {
            @Override
            public void run(){
                sprite_explosion.setRegion(textureAtlasExplosion.findRegion("spacesepxl0047"));
                exploded = true;
                game_upper_screen.TurnToSepiaForContrast();
            }
        });
    

    There is no delay, it will run on the next iteration of the UI loop.

    However, this is identical to just invoking the shader directly in the UI thread, so you might as well invoke it directly, which is going to bring back your performance problems.

    Are you perhaps invoking this more than once per frame? Each calls is triggering a flush of the batch, and I suspect that is the expensive part. If you can set things up so this call happens outside your batch being/end it should be cheaper. (And the "postRunnable" will effectively do that, so maybe that is the best thing to do.)