Thursday, August 27, 2009
Font Stash
Rendering strings without OS support is always pain in the ass. Add different font faces, font sizes and languages into the mix and it is not funny any more. I like typography and I like having good font rendering in my apps. Sean's awesome stb_truetype was such a relief for me. No more big and ugly Freetype to be included with your project!
Now that actually generating the glyphs was not a problem anymore, I thought I'd try to create a font cache which would allow me to load few fonts faces and render strings as any font size I please. The glyphs are rendered and packed into a texture as they are needed. There are few recent opengl font rendering projects which inspired me on the way (that pango/cairo/clutter opengl thing and one example written in D).
Another thing that was important for me was to be able to render localized strings. I like UTF-8 for its' simplicty, and after googling around for a while I found Bjoern Hoehrmann's Flexible and Economical UTF-8 Decoder.
Putting all the pieces together is Font Stash. It's Xcode only, with simple SDL example. Should compile on VC without much tinkering. Performance should be ok-ish, there are spikes when new glyphs are rasterized and running out of cache behavior is not too nice. This release is not nice and its not clean, but it might evolve into something more usable later on.
Download Font Stash 1.0
I recently tried switching in STBTT in place of FreeType in the hopes that it would be faster and more friendly to dynamic allocations. Unfortunately I found that its rasterizer is about 3x slower than FreeType's. Depending on the usage scenario this may or may not matter but it certainly makes dynamic glyph caching more difficult.
ReplyDeleteA font with about 350 glyphs takes about 8ms to rasterize at size 14 in FreeType and 26ms in STBTT.... thats more than the budget for an entire frame!
On the memory front it seems it has to do a dynamic allocation for each glyph when fetching its vertices. Although this can probably be hacked around such that it uses a fixed size scratch buffer for this. I don't know about FreeType yet, never got around to hacking it so I can easily trap its allocations (wouldn't surprise me if FT is worse). I suspect STBTT wins here, or at least can easily be tweaked to win. I believe if I can fix this allocation for vertices STBTT would run without allocating any of its own memory.
Quality wise it appears FT is a bit better, specially on smaller fonts when doing a side-by-side comparison. Some strange artifacts on a few characters, some extra aliasing and on really thin characters the anti-aliasing tends to make some characters 50% transparent vs FT which still manages to keep them opaque.
Overall STBTT is certainly interesting, and I like that its easy to build into a project vs freetype. Right now I have them both built into my tools chain so I can switch back and forth so I can keep track of its progress but for now I have to stick to FT.
Thanks for you insights.
ReplyDeleteI have not profiled the memory usage nor speed of stb_tt yet. stb_tt allows you to plug in your own malloc, which might help a bit with the allocations. It also might be possible to do some simple analysis of the whole font to see what is the max vertex count.
I agree that FT does better job at rendering certain glyphs. Remind me of the Apple vs. MS font rendering argument some time ago. http://www.codinghorror.com/blog/archives/000884.html
I fell in love with the simplicity of use with stb_tt. I might take a look at the performance at some point. FontStash does somu ugly things when rendering new glyphs too, so there is definitely room for improvement there.
I made a quick test to check the performance. I made simple allocator which always return next n bytes from a scratch buffer (in the demo the max consumption per glyph was less than 6k) so stbtt does not require any per frame allocations. I reset the buffer after every call to stbtt_MakeGlyphBitmap, and the required bitmap was also allocated from the scratch buffer. It turns out the allocations do not have much impact on the performance, but of course per frame allocations are to be avoided.
ReplyDeletePer glyph render times in the demo, which also includes updating the texture, vary from 0.0010 ms to 0.0400 ms. The average update time seems to be around 0.02 ms, and the very first update to a previously empty texture takes 0.1640 ms. So a _very_ unscientific estimate would bring stb_tff pretty close to ft. ymmv.
The following trivial patch is needed to get it running on Linux.
ReplyDeletehttps://gist.github.com/973677
Then compile with:
gcc -Wall -g -o fontstash main.c fontstash.c `sdl-config --cflags --libs` -lm -lGL
It looks sensational. Thanks!