OpenGL Programming/Modern OpenGL Tutorial Text Rendering 03

Introduction

edit

You've read the other two text rendering tutorials, and are still unsatisfied. You want it all: full support for Unicode and modern text shaping, in native OpenGL without the overhead and loss of control from using a high-level library like Pango. 10 years ago, you would have been out of luck due to closed standards and fixed-function pipelines. Today, it is within reach.

Texture atlas revisited

edit

In the last tutorial we used a texture atlas with a simple row packing algorithm. Typically we have a square texture though, e.g. 2048x2048, so you should add a simple check to wrap to the next line when the row is full. We could use a more complicated packing algorithm, but it would be expensive to compute and we would have to know ahead of time what fonts and glyphs we're using. In games this is easy; we can create multiple texture packs, one for each supported language, and switch between them as necessary.

But texture atlases could become problematic if you have a large character repertoire, such as Arabic or Chinese. It's also a problem if you have a lot of fonts, like in a document processing system or a web browser. Web pages in particular tend to use many different fonts and glyphs, and can even download fonts dynamically using WebFonts. Constantly packing and re-packing our glyphs as fonts are loaded and unloaded will just lead to more overhead, or in the worst case an out-of-memory error as we fail to discard old glyphs. And, although most fonts can be treated as grayscale, you might need an extra set of buffers for colorful emoji.

It's up to you to decide what to cache. For now we return to the simple world of the first tutorial and render everything without any caching.

Text layout

edit

In our old examples we use a simple computation for layout; add the x and y advances to get the new position. But this didn't deal at all with the complexities of ligatures or non-left-to-right languages. This is what Harfbuzz does. Harfbuzz still doesn't handle script detection or Unicode control sequences though; you'll need FriBiDi or libraqm to do that. But even libraqm doesn't handle line-breaking; if you want beautiful multi-line text you'll have to disentangle minikin from the rest of Android.

Whatever method you use, at the end you will have a list of glyphs to draw and their x and y coordinates.

Glyph rendering

edit

Fonts can be thought of as a bunch of layout information plus a series of glyphs. The layout information is read by Harfbuzz and other libraries, so it's not particularly important here. But the glyphs are. Each glyph is a series of Bezier curves, line segments, and move-to instructions. But then there's hinting; at typical font sizes, most glyphs will not actually draw the outlines directly but instead use a "hinted" variation designed to look better at low dpi. With the advent of high-resolution "retina" displays, this is not as much of an issue as it once was, but old displays are still around.

Next the glyphs have to be rasterized. You could approximate the curves with lots of small line segments and draw the lines directly with OpenGL, but this is pretty expensive. A cleverer method uses OpenGL shaders to draw the curves, but of course this is also tricky as it still requires triangulation and subdividing. Fortunately it's already implemented in the Glyphy library.

The easiest method is to just rasterize on the CPU, by calling Freetype's built-in rasterizer. But this is slow, so you will need a cache, regardless of what algorithm you use. Drop shadows are particularly expensive.