pingpoli.de



Indie Game Dev Log 55: New Font and Text Rendering System

Creating a more efficient custom font file format and removing FreeType dependencies

13.09.2021 - 21:17
I know, it's been a while since my last game development blog post. I didn't have a lot of time to work on game development recently and I have been focussing on writing and 3D modeling/art instead. I'm also at a point in the game I am working on where I would need a few weeks or months of mostly uninterrupted time to focus on adding content and balancing. 

Anyway, I had a bit of time recently and I really wanted to do some C++ game-development-related programming. One of the systems that I wanted to improve for a while was the font and text rendering. I wasn't happy with the current implementation and I wanted to remove most FreeType dependencies from my code in my ever-lasting quest of limiting the number of third-party libraries. 

Font file formats

Instead of using FreeType to access standard font files directly, I wanted to create a custom format for my engine that's optimized for my needs. However, to create a custom font file format, we need to have a look at existing file formats first.

A file format like TrueType (.ttf) is a collection of small vector graphics, also called glyphs, which define the shapes and outlines of all characters. In addition to that, the font file contains a bunch of character metrics, like how wide it is, how far it goes below the baseline, etc. Finally, there are some characters like A and V that can be moved closer together when they appear behind each other. This is called kerning and many fonts define kerning values for every character pair. 

A custom font file format

When using FreeType to draw text, we need to use the FT_Render_Glyph function. It converts the glyph from the internal pixel-independent outline format to a bitmap. However, this process isn’t particularly fast, so I wanted to eliminate this step and store the bitmaps directly. While this is a lot faster, it comes with a couple of drawbacks: A normal font file supports every font size, but storing the bitmaps directly only includes one size. However, it’s a drawback that I can live with because realistically, you only need a handful of different sizes for a game and different font styles like bold or italic are separate files for normal fonts as well.

In addition to the bitmaps, the file needs to include some metrics. FreeType provides a lot of different metrics for every character. These metrics are very confusing and to this day, I have never fully understood how all of them work, but I think I have found the ones that are relevant for the font rendering I want to do.

First of all, we store the width of every character, so we know how wide each bitmap is. Secondly, we need the advance value. The advance value tells us how many pixels we need to move the cursor forward after every character. Then there is a very confusing character metric: The left bearing, or as I call it in my format the offset. Some characters start on the left side of the cursor, which is a big problem when you want to draw text on an image at x=0. If the character starts -2 pixels left of the cursor, we would be trying to draw outside of the bounds of our image and that’s a big memory out-of-bounds exception waiting to happen. Therefore, we also need to store this value and shift everything forward if it's negative. 

Finally, there are the kerning values. I was contemplating not to include them in my files, because not every font has kerning values anyway and it’s also a lot of extra data that has to be stored (it’s the number of characters squared). However, I decided to bite the apple and include the kerning values, so that rendered text looks as good as possible. 

Another feature that is missing in my custom format so far is Unicode support. I didn’t include it yet, because my engine doesn’t have proper Unicode support at the moment either. Adding Unicode support to my engine is going to be complicated and I'm dreading the task, but I will have to do it at some point. However, adding Unicode support to my font file format should be possible without too many problems. 

Text Rendering

Without going into too much detail, I kept the two main text rendering types I have in my engine. The first one is image texts and the second one is vertex texts. For image texts, the text is drawn to an image that’s then turned into a texture and displayed on a single quad, while vertex texts use a texture atlas and a quad is created for every character with the correct uvs. For both of these systems, the font class does most of the work, because it provides a function that renders text to an image as well as a function that fills the correct vertex positions and uvs. This way, the OpenGL code doesn’t have to know anything about the details of text drawing and the code is cleaner and more encapsulated. 

Performance

As I mentioned above, the FreeType text drawing isn’t very fast, because the outline to bitmap conversion is slow and the library has a lot of overhead. My new format removes the conversion step and most of the overhead, which makes it a lot faster. Drawing the same text (a 5 paragraph Lorem Ipsum) to an image takes almost 600ms with FreeType, while it only takes 22.9ms with my system. Filling the vertex positions and uvs is even faster with only 4.19ms. However, drawing a vertex text to the screen with OpenGL is slower than an image text, because an image text only has a single quad, while a vertex text has potentially hundreds, even thousands of quads depending on the length of the text. 



Auto Line Breaks

My old text rendering system already had an auto line break feature, but it had a few issues and never worked great. So I completely rewrote the auto line breaking system and it’s working a lot better now. It basically goes through the text and always remembers the position of the last space character while counting the total length of the line. Once the length of the line goes over the page width, the last space character is replaced with a line break. If a string of characters without spaces is wider than the page, the function just enforces a hard break in the middle of the string. Because the vertex text creation is so fast, this also makes it possible to dynamically resize a page and recreate the text with auto line breaks on the fly:



Filesize

Comparing the file size of my font format to FreeType directly doesn’t make a lot of sense, because it doesn’t contain any Unicode information and it only includes a single size, which means that it’s obviously going to be a lot smaller. In any case, the storage space required for fonts is very small compared to textures and other game assets.

However, I made a bunch of improvements compared to the first version of the custom format. For one, I only store a single channel of the bitmap instead of all 4 and I also only store the values for the 96 printable ASCII characters instead of all 128 characters. This makes the new version roughly 5 times smaller than the first version, while still containing the same amount of information. 





I’m very happy with my new font library and text rendering system. It’s faster than the old one and especially when compared to FreeType, while also requiring less storage space. Additionally, it allows me to remove all FreeType dependencies from my code, except for the program that converts from normal font files to my format. 

The next step is actually removing the old text rendering code and replacing everything with the new system. However, knowingly breaking working code is always terrifying - even when you know that the new system is going to be better - so I’m procrastinating on this step at the moment. 




by Christian - 13.09.2021 - 21:17


Comments

Social Media:  © pingpoli.de 2020 All rights reservedCookiesImpressum generated in 8 ms