Pixel-exact text troubles

Screen Shot 2015-01-04 at 7.52.32 pmI was going to post about various new features I’ve been adding to MMORPG Tycoon 2, but at the very last minute I noticed something very odd in my current build;  in some of the small text strings in one of the new windows, I was getting blurry text.  The sort of blurry text that used to occur, back before I worked out how to get the VectorStorm library to draw pixel-exact text.  The string at the top of this image is one where the string appeared blurry.  Beneath it is how it should have appeared.  And did appear, in fact, in other places in the program.

Here were the clues to the problem:

  1. Not every string looked blurry.  “Level 1 Inn”, for example, looked blurry.  “Level 1 Grinding Zone”, in the same place, did not.  And this was consistent — it happens the same way every single time, no matter how big or small the window is.
  2. It seemed like only strings which were drawn with a centered justification ever appeared blurry.  But not every string drawn with a centered justification appeared blurry.

Under the hood, VectorStorm uses BMFont-style font rendering.  We build a model for each text string, with a rectangle for each glyph, and those rectangles are drawn using a texture which contains the glyphs of the font.  I make the rendering pixel-exact by ensuring that the pixels of each rectangle’s texture are aligned precisely with the pixels of the screen, so that each screen pixel is sampling from just one texture pixel.  If the pixels of the texture aren’t quite aligned with the pixels of the screen, each screen pixel blends between multiple texture pixels, and you wind up with a blurry appearance.  I line up the pixels by ensuring that the text is placed at an integral position.  That is, if I draw a letter at (1,1) or (2,2) or (203,112), it will be aligned with screen pixels.  If I draw it at (1.5,1.0) or at (2,7.1), or at any other coordinate which contains a decimal point, it won’t be aligned, and so will result in blurry text.

The magic step I do to make everything render aligned to pixels is that before rendering 2D text, I chop off any fractional part in the matrix — the structure which says where on the screen the object is to be drawn.  So if something had told me to draw the text at (1.5,1.5), I chop that off and actually draw it at (1,1), for the sake of aligning with pixels.  This means that even if a font model is located at (0,0) relative to its parent, but its window has somehow been moved to (0.5,0.5), we round off that fractional amount when it comes time to draw the font.  So it shouldn’t be possible to render text unaligned from pixels, no matter what the outside world does, right?

Except that here I had an example where text was still blurry for some reason.  Not all the time, but sometimes.

Well, I finally found the issue;  the problem was that in this particular font at this particular size, “Level 1 Inn” happens to be   61 pixels wide.  And since I centered the rectangles for those glyphs, that meant that they all had a baked-in 30.5-pixel offset;  in effect, the blur wasn’t in the screen alignment at all — it was in the model for the text itself!

Fixing that — snapping the amount the model is moved around in order to center it — resulted in the fixed, non-blurred appearance (visible beneath the blurry one).  Note the crisper vertical lines of the ‘L’ and ‘l’ in Level, and the ‘I’ in Inn, particularly.  It makes a surprisingly large difference to legibility, at normal size.

Here’s a screenshot where you can see the problem in situ (click for full-size):

InfoWindowNote how the centered “Hero” string at the top of the window is blurry, but the right-justified one on the right side of the window appears sharp.  That’s precisely this issue.

For me, it’s hugely satisfying to find this sort of problem.  Apparently, it’s satisfying enough that I’ll abort a post I was going to write about an exciting new window and game features, and will instead post a technical post-mortem on a code-fix which is so subtle it requires zooming in on pixels to make the bug visible at all to a casual glance.

I’ll get back to talking about the exciting new window, and the other things that I’ve been up to tomorrow.  :)