Search code examples
iosopengl-eslibgdxopengl-es-2.0framebuffer

LibGDX FrameBuffer quirk on iOS


I've got dynamic textures for a word game that I create and keep on a FrameBuffer. I then use TextureRegions backed by that FrameBuffer to draw to the screen. This code works fine on Android, but I'm having problems on iOS.

You can see below a screenshot with the FrameBuffer contents drawn in the bottom left - all is well.

enter image description here

However, if I return to the iOS home screen, I dispose of the FrameBuffer and then recreate and redraw everything to the FrameBuffer as required. The contents of the FrameBuffer then appear to be partial flipped mirror image of the screen.

enter image description here

I tried disposing and recreating the FrameBuffer on every frame to see what happens. Again, this works perfectly unless I return to the iOS home screen, after which the contents of the FrameBuffer are incorrect.

I then tried reducing the size of the FrameBuffer and drawing across mutliple FrameBuffers. If I do this it again works fine unless I return to the iOS home screen. However, in this instance only the first FrameBuffer created becomes corrupted, all subsequent buffers are fine.

enter image description here

I then tried always creating a 1x1px FrameBuffer first that I never draw anything to and this fixes my problem.... but why!? It seems the first FrameBuffer I create after returning from the iOS home screen is not working as expected, but every FrameBuffer after that is fine. It looks like it's always trying to draw the screen over the first FrameBuffer I create.

FrameBuffer creation and drawing code below:

private static FrameBuffer getFrameBuffer(boolean newRequired) {
    if (newRequired || frameBuffers.size == 0) {
        FrameBuffer frameBuffer;

        try {
            frameBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, getFrameBufferSize(), getFrameBufferSize(), false);
        } catch (Exception e) {
            frameBuffer = new FrameBuffer(Format.RGBA4444, getFrameBufferSize(), getFrameBufferSize(), false);
        }

        frameBuffer.begin();
        Gdx.gl20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
        frameBuffer.end();

        frameBuffers.add(frameBuffer);

        // Set up the camera correctly for the frame buffer
        OrthographicCamera camera = new OrthographicCamera(frameBuffer.getWidth(), frameBuffer.getHeight());
        camera.position.set(frameBuffer.getWidth() * 0.5f, frameBuffer.getHeight() * 0.5f, 0);
        camera.update();
        spriteBatch.setProjectionMatrix(camera.combined);
    }

    return frameBuffers.get(frameBuffers.size - 1);
}

private static TextureRegion getTextureUsingGpu(String letter, Bubble.BubbleType bubbleType) {
    if (!enabled)
        return null;

    TextureRegion tx = getBlockImage(letter, bubbleType);

    int width = (int) (tx.getRegionWidth() * LayoutManager.getShortEdge() / 1080 * LayoutManager.getScaling());
    int height = (int) (tx.getRegionHeight() * LayoutManager.getShortEdge() / 1080 * LayoutManager.getScaling());

    if (nextTextureX + width + PADDING > getFrameBufferSize()) {
        nextTextureX = PADDING;
        nextTextureY = nextRowY + PADDING;
        if (nextTextureY + height + PADDING > getFrameBufferSize()) {
            getFrameBuffer(true);
            nextRowY = PADDING;
            nextTextureY = PADDING;
        }
    }

    FrameBuffer fb = getFrameBuffer(false);

    fb.begin();
    spriteBatch.begin();

    tx.flip(false, !tx.isFlipY());

    spriteBatch.disableBlending();
    spriteBatch.draw(tx, nextTextureX, nextTextureY, width, height);
    spriteBatch.enableBlending();

    // Drawing Code Removed

    spriteBatch.end();
    fb.end();

    TextureRegion textureRegion = new TextureRegion(fb.getColorBufferTexture(), nextTextureX, nextTextureY, width, height);
    textureRegion.getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
    cacheTexture(letter, textureRegion);

    nextTextureX += width;
    nextRowY = Math.max(nextRowY, nextTextureY + height);

    return textureRegion;
}

Solution

  • If you are calling getFrameBuffer or getTextureUsingGpu from your resize or resume method, that is the problem. On iOS, resize and resume are not necessarily called before OpenGL is ready to resume, so it is not safe to do anything directly related to OpenGL there, like creating ShaderPrograms, loading Textures, or creating FrameBuffers. I fixed this in my game by using resume to set a member boolean, and use that to defer the creation of the new frame buffer to the render method.