DEV Community

Mike
Mike

Posted on • Originally published at skaterdad.dev

Perf Tip: Put Your Bitmap Fonts in an Atlas!

Did you know that Bitmap Fonts can be added to the same Texture Atlas as the rest of your UI textures? I sure didn't. Read on to see how I measured and optimized my game's UI performance.

Outline

Background

In my previous post, we learned about the importance of minimizing texture bindings in OpenGL. Using Texture Atlases is one of the easiest and most effective ways of optimizing your game rendering.

The LibGDX Wiki and many tutorials do a great job at recommending and explaining the use of atlases for your game world, but it was not obvious to me that you should also pack your Bitmap Fonts with your UI textures.

While making Santa Skate, I used the Hiero tool to generate the .fnt and .png files for my fonts. I generated four fonts, each of which had a 512x512 texture with mostly empty space.

The generated font textures

What's the problem?

In the screenshot below, there is a typical scene from Santa Skate. There is a mixture of game world sprites and Scene2D UI elements. I had already gone through efforts to minimize draw calls/texture bindings on the game world, but I hadn't put much thought into the UI layer.

In-game screenshot. So pretty.

Measure before optimizing

After setting up the GLProfiler, I was surprised to see 13+ texture bindings in the UI layer per frame!

Why so many texture bindings?

The reason this happens is that the Scene2D Stage draws each Actor in the order they were added to the stage. Some of those actors need to draw both an image and text, which required multiple texture files in my project. The progress bar and the lower power-up icons (which render one at a time) are clear examples of this.

Packing your fonts?

While searching online for tips on optimizing Scene2D performance, I found some forum posts that mentioned packing your fonts with the rest of the UI graphics. This post by 'davedes' on java-gaming.org was especially useful. In hindsight, that tip made perfect sense and I'm a little annoyed that I didn't think of it!

Following that advice, I packed up all of my UI layer graphics, fonts, and particles into a single texture atlas.

The result?

Only 5 texture bindings/draw calls in the UI layer per frame!

Why not 1 draw call? My UI uses multiple Group actors, which can trigger SpriteBatch flush() calls that force a texture binding.

Packing fonts in your UI Texture Atlas

  1. Open your favorite texture packer tool. I really like GDX Texture Packer GUI.
  2. If you have a UI textures project already, open it. Otherwise, add all of your UI graphics to the list of files to pack.
  3. Add your bitmap font PNG files to the list!
  4. Turn on the option "Strip whitespace Y" to get rid of all the empty vertical space in your fonts.
  5. Configure the other options to your needs.
  6. Pack it!

Packed and efficient.

Tell your Bitmap Font to use the new atlas

When you create Bitmap fonts with tools like Hiero, the .fnt file references the PNG that was generated with it. We won't be including those PNG files in our assets anymore, so we need to tell the BitmapFont classes where to find the atlas.

If you're using an AssetManager to load your resources, it only takes a couple lines of code to update your fonts to use the Texture Atlas. Create a BitmapFontParameter object, and set the atlasName property to the internal filepath of your texture atlas definition file (normally a .atlas or .pack extension). Pass that parameter as the third argument to the AssetManager load() function.

For my game, this is how I updated all 4 fonts. No other code changes were required!

  // 1. Create a BitmapFontParameter, pointing to your atlas
  BitmapFontLoader.BitmapFontParameter fontParameter = new BitmapFontLoader.BitmapFontParameter();
  fontParameter.atlasName = "ui/gameui.pack";

  // 2. Pass that parameter as the third argument to "load"
  assetManager.load("fonts/red32.fnt", BitmapFont.class, fontParameter);
  assetManager.load("fonts/red64.fnt", BitmapFont.class, fontParameter);
  assetManager.load("fonts/yellow64.fnt",BitmapFont.class, fontParameter);
  assetManager.load("fonts/yellow32.fnt",BitmapFont.class, fontParameter);

If you are not using AssetManager and are creating your BitmapFonts manually, you will need to pass in the TextureRegion to the constructor.

  // This code has not been tested!
  // Consider it inspiration.

  TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("textures/gui.pack"));

  BitmapFont font = new BitmapFont(
    Gdx.files.internal("fonts/yourFont.fnt"),
    atlas.findRegion("yourFont")
  );

Top comments (0)