Friday, September 04, 2009

stb_truetype: my experiments


(Blogger scaled the image down slightly so click to enlarge)

Recently I discovered that Sean Barrett has put up on his website a nice little True Type font rasterizer. The keyword here being "little", compared to FreeType its microscopic with less than 1600 lines of source code in a single file. FreeType on the other hand is about 6.5MB of just source code... I have worked on games with less source code! So the possibility of tossing out that much bloat is really appealing to me.

Anyways upon finding this I quickly tossed it into my hobby project game engine to compare performance/memory/quality to FreeType with the assumption that it is probably faster, probably thrashes memory less and quality is probably roughly the same. Here are my discoveries:

Performance
This was a big shocker to me... FreeType was a good 3 times faster than stb_truetype at rasterizing a font of about 350 characters at height=14 pixels 8ms vs 26ms... thats almost double a single frame's budget! Granted usually you rasterize fonts once during loading and if you only have 1 or 2 fonts this is probably a non-issue, but if you have lots of fonts and supporting asian localizations this could potentially add seconds to your load time and that might be a deal breaker if you are already barely squeezing into TRC load time requirements. If you are dynamically caching fonts (like because you support rich text or something) this could be make it quite difficult to keep performance up. Luckily in my situation though I am pre-baking my fonts in my tools chain not at load time so performance isn't quite a deal breaker but I would prefer not pre-bake if performance was good enough. One thing to note is that in stb_truetype I was able to set it up to rasterize directly into locked texture memory whereas FreeType has no such API so I have to rasterize and then blit into texture memory so in a way FreeType was given a small handicap already.

Memory
Because I rasterize glyphs one by one the peak memory consumption for both FreeType and stb_truetype is quite low (just a few KB). But the number of allocator calls for both is quite high. Oddly enough again stb_truetype hammers on the allocator more making ~8,000 allocations versus FreeType's ~3,000 for the same font. Neither particularly makes me happy as a runtime solution. But the good news here is that while I would not want to touch FreeType's source code with a 10 foot pole, looking around in stb_truetype's source I believe I could stick in a relatively small fixed size pool and make stb_truetype run without any allocations of its own. So there is actually quite a bit of hope here.

Quality
Quality of stb_truetype isn't terrible but its not great either. FreeType comes off much cleaner and more closely matches the way Windows rasterize the same font. stb_truetype always comes off a bit bolder than it should be, a bit blurrier and a few artifacts like some characters being 50% transparent. Overall I think quality is probably acceptable for games and tools, but I wouldn't be shocked to have a few artists complain about it looking different than it does in Windows.

Mikko Mononen brought up www.codinghorror.com/blog/archives/000884.html. I am not so sure yet if the differences are within the same threshold or not, but I suppose its possible the quality is no more different than OSX vs Windows (which, btw I think fonts look better in OSX).

Final Verdict
I am not quite ready to toss out FreeType yet, but I am awfully tempted. I think all the issues with stb_truetype can be resolved without too much effort so I fully expect to be giving FreeType the boot eventually.

1 comment:

Anonymous said...

I realize this is a little belated but I only just bumped into this post now...

There are obvious quality issues with stb_truetype because no hinting is performed. This makes it less useful for small fonts, since it leads to things like the 50% transparent stems because they're not snapped to the pixel grid.

Presumably some day I'll actually tackle that problem, but I don't know when.

The performance issue is more significant. The code hasn't really been optimized (beyond just being written mainly sanely), so I don't really know what the issue is.

One possiblity is that the way anti-aliasing is performed is by rasterizing at a higher resolution (in one dimension only); for larger fonts, the character is 5x oversampled in Y, and for smaller fonts, they are 15x oversampled. It might be worth changing both of those numbers to 3 and seeing how that affects both quality and performance (since I just pulled those numbers out of a hat).