This week I finally tackled font rendering for Rival Fortress’s game engine.
After a few iterations I decided to use pre-cooked bitmap fonts for the debug overlays and True-Type runtime rasterization for the game proper. For both paths I’m using Sean Barret’s excellent stb_truetype library, along with stb_rect_pack to pack multiple font sizes into a texture atlas. I also did some testing using distance field fonts[PDF] but I couldn’t find a way to render them with reasonable performance without sacrificing too much detail. For now I’ll just stick with bitmap fonts.
Offline Pre-cooked Bitmap Fonts
The font used for debug overlays is Roboto Mono. To reduce the size of the font I extracted just glyphs ranging from ASCII 0x20 to 0x98, which is essentially all alpha-numeric characters and the most common punctuation marks. I used FontForge for glyph extraction.
The preprocessing is done offline, by a tool that is run as part of the build pipeline when the font changes. The tool rasterizes the glyphs and packs them into a texture atlas. The texture atlas is then compressed using a simple compression algorithm and encoded using Base85. The Base85 encoded string is embedded in a generated C++ header file as a
char* and included by the game engine. This meas that in case something goes wrong during asset initialization and I want to gracefully warn the user.
The reason I decided to pre-cook the debug font is that since I will only ever use a very small glyph range and the font size will never change, there is no need to pay the rasterization cost at runtime (albeit minimal).
On an optimization note: Roboto Mono is certainly overkill for just the debugging overlay, as the glyphs contain quite a bit of detail that increases the compressed font size (the compressed and Base85 encoded string is a little over 10k bytes). In a future iteration I will most likely switch to a simpler font.
Runtime Rasterized Bitmap Fonts
The main game will use True-Type fonts that are rasterized at runtime. This allows greater flexibility as you can render the correct size fonts based on the user’s resolution as well as only rasterize the glyphs needed by the user’s locale, thus reducing the texture atlas size.
The metadata stored in the PAK is minimal (max glyph size, glyph count, unicode codepoint ranges). Kerning tables are calculated at runtime based on the glyph ranges needs, but I’ll have to test if pre-calculating them and storing them in the PAK is better.
At runtime the fonts are lazily loaded when any one of the game modules needs to render text. Currently I’m allocating a fixed size texture atlas that I progressively fill with rasterized glyphs and upload to the graphics card whenever it is updated. Only the subset of the texture atlas that contains bitmap information is sent to the card.