Game Development Community

Font Rendering

by Robert Blanchet Jr. · in Torque Game Engine · 06/16/2003 (11:56 pm) · 3 replies

I've been stumbling through the font rendering routine for a little bit today trying to devise a way to render text on a curve defined by the spline utility classes when I ran across this bug/oversight or more likely a complete lack of understanding on my part.

The function in question is dglDrawTextN. The first two or so glances through it reveal nothing out of the ordinary until you start looking at how the text is actually being drawn. You'll notice two areas in the routine where similar code, drawing code no less, seems to be executed twice, actually more then twice since one is located within the loop and the other outside of the loop.

The two code pieces in question are:

code inside the for loop
const GFont::CharInfo &ci = font->getCharInfo(c);
TextureObject *newObj = font->getTextureHandle(ci.bitmapIndex);
if(newObj != lastTexture)
{
   if(currentPt)
   {
      glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName);
      glDrawArrays( GL_QUADS, 0, currentPt );
      currentPt = 0;
   }
   lastTexture = newObj;
}

code outside the for loop
if(currentPt)
{
   glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName);
   glDrawArrays( GL_QUADS, 0, currentPt );
}

Now I was, and to some extent still am, perplexed as to why there are two very similar pieces of code that, from what I can tell do basically the same thing. So I proceeded to comment out one piece and not the other, and visa versa and found that if the code snipit outside of the loop is commented out, then the text doesn't draw at all. However, if the code snipit inside of the loop is commented out, then the text seems to render just fine, with no glitches.

So I edited the function to this:
U32 dglDrawTextN(GFont* font, const Point2I& ptDraw, 
                 const void* in_string, U32 n, const ColorI* colorTable,
                 const U32 maxColorIndex)
{
   // return on zero length strings
   if( n < 1 )
      return ptDraw.x;
   PROFILE_START(DrawText);

   Point2I     pt;
   U8          c;
   const U8    *str     = (const U8*)in_string;
   const U8    *endStr  = str + n;
   pt.x                 = ptDraw.x;
   
   ColorI                  currentColor;
   S32                     currentPt = 0;
   U32                     storedWaterMark; 

   storedWaterMark   = FrameAllocator::getWaterMark();
   currentColor      = sg_bitmapModulation;

   TextVertex *vert = (TextVertex *) FrameAllocator::alloc(4 * n * sizeof(TextVertex));
   TextureObject *newObj = NULL;

   glDisable(GL_LIGHTING);
   
   glEnable(GL_TEXTURE_2D);
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glEnable(GL_BLEND);
   
   glEnableClientState ( GL_VERTEX_ARRAY );
   glVertexPointer     ( 3, GL_FLOAT, sizeof(TextVertex), &(vert[0].p) );

   glEnableClientState ( GL_COLOR_ARRAY );
   glColorPointer      ( 4, GL_UNSIGNED_BYTE, sizeof(TextVertex), &(vert[0].c) );

   glEnableClientState ( GL_TEXTURE_COORD_ARRAY );
   glTexCoordPointer   ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[0].t) );
   
   // first build the point, color, and coord arrays
   for (c = *str; str < endStr; c = *(++str))
   {
      // We have to do a little dance here since \t = 0x9, \n = 0xa, and \r = 0xd
      if ((c >=  1 && c <=  7) ||
          (c >= 11 && c <= 12) ||
          (c == 14)) 
      {
         // Color code
         if (colorTable) 
         {
            static U8 remap[15] = 
            { 
               0x0,
               0x0,
               0x1, 
               0x2, 
               0x3, 
               0x4, 
               0x5, 
               0x6, 
               0x0, 
               0x0,
               0x0, 
               0x7, 
               0x8,
               0x0,
               0x9 
            };

            U8 remapped = remap[c];
            // Ignore if the color is greater than the specified max index:
            if ( remapped <= maxColorIndex )
            {
               const ColorI &clr = colorTable[remapped];
               currentColor = sg_bitmapModulation = clr;
            }
         }
         continue;
      }

      // reset color?
      if ( c == 15 )
      {
         currentColor = sg_textAnchorColor;
         sg_bitmapModulation = sg_textAnchorColor;
         continue;
      }

      // push color:
      if ( c == 16 )
      {
         sg_stackColor = sg_bitmapModulation;
         continue;
      }

      // pop color:
      if ( c == 17 )
      {
         currentColor = sg_stackColor;
         sg_bitmapModulation = sg_stackColor;
         continue;
      }

      // Tab character
      if( !font->isValidChar( c ) ) 
      {
         if ( c == '\t' ) 
         {
            const GFont::CharInfo &ci = font->getCharInfo( ' ' );
            pt.x += ci.xIncrement * GFont::TabWidthInSpaces;
         }
         continue;
      }

      const GFont::CharInfo &ci = font->getCharInfo(c);
      newObj = font->getTextureHandle(ci.bitmapIndex);
      if(ci.width != 0 && ci.height != 0)
      {
         pt.y = ptDraw.y + font->getBaseline() - ci.yOrigin;
         pt.x += ci.xOrigin;
         
         F32 texLeft   = F32(ci.xOffset)             / F32(newObj->texWidth);
         F32 texRight  = F32(ci.xOffset + ci.width)  / F32(newObj->texWidth);
         F32 texTop    = F32(ci.yOffset)             / F32(newObj->texHeight);
         F32 texBottom = F32(ci.yOffset + ci.height) / F32(newObj->texHeight);

         F32 screenLeft   = pt.x;
         F32 screenRight  = pt.x + ci.width;
         F32 screenTop    = pt.y;
         F32 screenBottom = pt.y + ci.height;
         vert[currentPt++].set(screenLeft, screenBottom, texLeft, texBottom, currentColor);
         vert[currentPt++].set(screenRight, screenBottom, texRight, texBottom, currentColor);
         vert[currentPt++].set(screenRight, screenTop, texRight, texTop, currentColor);
         vert[currentPt++].set(screenLeft, screenTop, texLeft, texTop, currentColor);
         pt.x += ci.xIncrement - ci.xOrigin;
      }
      else
         pt.x += ci.xIncrement;
   }

   // why is this working even tho it's outside the loop where
   // the texture object for the current character is assigned?
   glBindTexture(GL_TEXTURE_2D, newObj->texGLName);
   glDrawArrays( GL_QUADS, 0, currentPt );
   
   glDisableClientState ( GL_VERTEX_ARRAY );
   glDisableClientState ( GL_COLOR_ARRAY );
   glDisableClientState ( GL_TEXTURE_COORD_ARRAY );
   
   glDisable(GL_BLEND);
   glDisable(GL_TEXTURE_2D);
   
   // restore the FrameAllocator
   FrameAllocator::setWaterMark(storedWaterMark);

   AssertFatal(pt.x >= ptDraw.x, "How did this happen?");
   PROFILE_END();
   return pt.x - ptDraw.x;
}

And it works fine, but I don't understand why it is working. Seems to me that it should only be drawing one character and stopping, if that. Because in the looping, all I'm doing is reassigning the TextureObject pointer over and over again. I'm beginning to think the FrameAllocator comes into play with this somehow, but I don't have much experience with how the FrameAllocator works.

So if someone could shed some light on this, and confirm that my changes are not insane I could write a patch for this function to squeeze out just a little bit more performance that the redundant drawing was sucking.

Thanks in advance.

#1
06/17/2003 (6:41 am)
*bump*
#2
06/17/2003 (7:47 pm)
Just reading through that, here's my impression:

The code looks proper as-is. If for some reason, a character in a font wants a different texture (maybe because of multibyte/unicode, or other font breakdown across multiple textures), the code handles it transparently to the caller. Aside from a per-character if-then compare, there is no overhead if the texture remains the same.

And the overhead of the rest of the loop is SOOO much larger than a single compare, that you should probably leave it as is, since there certainly IS functionality there you are ripping out. And just to be frank, if this code is even showing up on a profile, you should be happy with performance (OR, you are drawing WAAAY too much text! :) ).

d
#3
06/17/2003 (7:49 pm)
:)
Thanks for the reply. I had gotten a reply from Tim Gift about the code earlier today and forgot to update this thread. Basically just a misunderstanding, on my part, of what was actually going on in the routine.

Thanks for your help though.