3
\$\begingroup\$

I'm currently working on a 2d sandbox tile game in LibGDX and I noticed I had 700+ draw calls. So checked my code and I could not find anything that would cause a new draw call. All the tiles are drawn between a single batch.begin() and a batch.end(). I commented out the code for rendering the tiles and I had around 2-3 draw calls. I can't figure out whats wrong so I am posting this here.

Here is the call chain of the render method:

In my world class:

public void render(SpriteBatch batch, ShapeRenderer shapeBatch) {

    batch.begin();
    chunkManager.render(batch);
    if (selectedBlock != null) batch.draw(gameScreen.app.assetManager.get("misc/block_selection_texture.png",Texture.class), selectedPosition.x * 32, selectedPosition.y * 32);
    batch.end();

}

World is calling chunkManager.render(..):

public void render(SpriteBatch batch) {
    //Render all the chunks
    for (Chunk c : activeChunks) {
        if (c != currentChunk) {
            c.render(batch);
        }
    }

    //Render Current Chunk the player is in in front of every other chunk
    currentChunk.render(batch);
}

ChunkManager is calling c.render(..):

public void render(SpriteBatch batch){
    //Render all Blocks inside the Chunk
    for(Block b:blocks.values()){
        b.render(batch); // <- this is the line I commented out
    }

    //Render all Entities inside the Chunk
    for(Entity e:entities){
        e.render(batch);
    }
}

Chunk is calling b.render(..):

public void render(SpriteBatch batch){

    float hp = hardness/maxHardness;

    //tint the blocks based on their health
    batch.setColor(1*hp,1*hp,1*hp,1);
    batch.draw(texture, x*32, y*32);
    batch.setColor(Color.WHITE);

}

and e.render(..):

public void render(SpriteBatch batch){
   if(isAnimated) {
       renderAnimation(batch); // -> just batch.draw(..) for animations
   } else {
       batch.draw(texture, x, y);
   }
}
\$\endgroup\$
3
\$\begingroup\$

At the GPU API level (that is, the D3D or OpenGL level) drawing something with a different texture is generally going to involve issuing a new draw call. Draw calls only use the current state as-of the point where the call is issued; changing textures is a state change.

"Sprite batch" implementations generally use some kind of sorting to minimize draw calls; for example, if you call "spritebatch.draw" with a set of parameters (including texture) that match an existing set of parameters (including texture) within that batch's begin/end block, the batch will make sure to draw both sets of geometry with that set of GPU state before switching to another. But that still means if you have 100 textures, you'll get a minimum of 100 draw calls; more if the sprite batch doesn't implement this technique well or if there is other state you're changing with each draw.

A quick examination of LibGDX's sprite batch suggests that it does not perform the above optimization. Rather, every time you call draw it checks to see if the texture you've given it is the same as the last time. If it isn't, it calls switchTexture. switchTexture calls flush, which renders the actual geometry queued up.

This means you are potentially getting one GPU draw call for every sprite batch draw call (in the worst case scenario where the texture changes every time).

You can solve this by sorting or batching your own calls to the sprite batch so that keep the texture parameter the same for as many consecutive calls to draw as possible. This way the batch will only flush when it hits its vertex buffer threshold (maximum number of quads to draw at once, basically). This should give you much fewer draw calls, to the theoretical best case of one call per unique texture.

Alternatively, you can pack multiple sprites into one larger texture and reduce the need to switch textures that way as well.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.