Search code examples
libgdxspritebatch

Need help to optimize texture drawing on LibGDX, SpriteBatch


I'm converting my game Hold & Drag from Swift (SpriteKit) to Android using LibGDX, I created my 'SpriteKit' api a little clone of the official on Xcode.

I'm looking for optimizing the render of textures especially because I got FPS drop (45-60 /60 fps) When I don't draw textures I got 60 (the maximum).

I hope you will help me as efficiently as possible!

@Override
public void draw() {
    if (texture == null)
        return;

    Gdx.gl.glEnable(GL20.GL_BLEND);
    Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);

    spriteBatch.setTransformMatrix(getTransformMatrix());
    spriteBatch.begin();
    Color color = spriteBatch.getColor();
    color.a = getAlpha();
    spriteBatch.setColor(color);
    spriteBatch.draw(texture, -size.width / 2, -size.height / 2, size.width, size.height);
    spriteBatch.end();

    Gdx.gl.glDisable(GL20.GL_BLEND);
}
public List<SKNode> getParents() {
    List<SKNode> parents = new ArrayList<>();
    if (parent != null) {
        parents.add(parent);
        parents.addAll(parent.getParents());
    }
    return parents;
}

public Matrix4 getTransformMatrix() {
    List<SKNode> nodes = getParents();
    Collections.reverse(nodes);
    nodes.add(this);

    Matrix4 transformMatrix = new Matrix4();
    transformMatrix.idt();
    for (SKNode node : nodes) {
        transformMatrix.translate(node.position.x + node.origin.x, node.position.y + node.origin.y, 0);
        transformMatrix.rotate(0, 0, 1, node.zRotation);
        transformMatrix.scale(node.xScale, node.yScale, 0);
        transformMatrix.translate(-node.origin.x, -node.origin.y, 0);
    }

    return transformMatrix;
}

Solution

  • It is slow to do things that cause the sprite batch to "flush", which means it has to issue a number of OpenGL commands and transfer vertex data, etc. A flush occurs when you call spriteBatch.end() but also occurs if you:

    • Draw something with a different texture instance than the last thing drawn
    • Change the projection or transform matrices
    • Enable/disable blending

    So you want to organize so you are not triggering a flush on every object you draw. What is typically done is to begin the batch, and then each sprite is drawn at its particular location with one of the spriteBatch.draw() methods that includes all the parameters you want. Like this:

    batch.setProjectionMatrix(camera.combined);
    batch.begin();
    for (GameObject obj : myGameObjects)
        obj.draw();
    batch.end();
    
    //And in your game object / sprite class
    public void draw() {
        if (texture == null)
            return;
        spriteBatch.setColor(1f, 1f, 1f, getAlpha());
        spriteBatch.draw(texture, -size.width / 2, -size.height / 2, size.width, size.height);
    }
    

    Note that the above assumes you are referring to the same sprite batch instance in every object.

    Now, to make it behave more like SpriteKit (I'm assuming since I haven't used it), your objects each need a transform. But you don't want to be calling setTransformMatrix or you will trigger a flush. Instead you can use the Affine2 class instead of Matrix4. It functions just as well for holding transform data for a 2D object. Then you can use spriteBatch.draw(textureRegion, width, height, affine2Transform) to draw it without triggering a flush.

    To avoid triggering flushes from using different texture instances, you should use a TextureAtlas and texture regions. You can read up on that in the LibGDX documentation on their wiki.

    As an aside, when using SpriteBatch, you do not need to make OpenGL calls to enable and disable blending. That is handled internally by SpriteBatch. Call spriteBatch.enableBlending() instead.