gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Coding > Text system not working!

#139552 - Ruben - Fri Sep 07, 2007 12:23 pm

I was recently programming a text system into my demo but I just can't seem to get it to work! It's driving me CRAZY! I'm using variable widths which explains why it's harder. All my font data is stored in a 'const u8' array which is acessed when needed. The text is drawn onto the screen by copying the required tiles into the VRAM so I can get the variable width thing. But whenever I try it, it fails! Here's the code I'm currently using:
Code:

//This Draws Text onto the Screen
void DrawText(char* Text) {
 //Return if Text isn't Initialized
 if(!TextInitialized) return;
 u16 i, j, k;
 u16 CurrentChar;
 u16 CurrentOffset = 0;
 u16 CurrentWidth;
 u16* charBaseBlock = (u16*)0x6004000;
 
 for(i=0;Text[i]!=0;i++) {
  CurrentChar = Text[i]-32;
  CurrentWidth = TextWidths[CurrentChar];
  for(j=0;j<8;j++) { //Height
   for(k=0;k<CurrentWidth;k++) { //Width
    charBaseBlock[CurrentOffset+(j*CurrentWidth)+k] = FontData[(CurrentChar*64)+(j*CurrentWidth)+k];
   }
  }
  CurrentOffset += 8*CurrentWidth;
 }
}

'TextWidths[]' is an array containing all the character widths. 'FontData[]' is the u8 array containing the image. Can someone please tell me what I'm doing wrong as it is REALLY annoying when you can't get it to work! Thanks guys!

#139570 - Cearn - Fri Sep 07, 2007 4:44 pm

A lot depends on the video mode, background mode and formatting of VRAM here, which you're not explicitly mentioning here. I'm assuming you're using a tiled background with 8bit tiles and that FontData contains 8x8 bitmaps for the glyph pixels. If so, there are several problems, mostly to do with this line:
Ruben wrote:
Code:
    charBaseBlock[CurrentOffset+(j*CurrentWidth)+k] = FontData[(CurrentChar*64)+(j*CurrentWidth)+k];
  • charBaseBlock is a u16 pointer, FontData is u8*. This means you're going to skip every other pixel in VRAM.
  • In the tiled modes, VRAM, isn't linear, it's composed of 8x8 tiles. going from coordinates to memory offsets is a little trickier in that case and I don't see any code that does that here.
  • Even though it depends on your specific formatting of FontData and VRAM, I highly doubt that (j*CurrentWidth) gives the correct pixel offset for either the source and destination pixels. If the source glyphs are all on a 8x8 canvas, glyph-scanlines will be 8 bytes apart, not CurrentWidth. And I'm very sure that the scanlines in VRAM won't be CurrentWidth apart.
  • I'm also wondering about 'CurrentOffset += 8*CurrentWidth'. This should probably just be 'CurrentOffset += CurrentWidth' because you'll probably want to step to the next right of the glyph, not the bottom-right.
As a side note, some of these errors would have been more visible if the inner-loop wasn't so long -- it's hard to see the forest from the trees here. By using pointers and shorter names, it becomes much clearer:

Code:
int i, ch, charW;      // DON'T use u16 for locals. int or u32 are better.
int ix, iy;         // For 2D things, use x and y so you can see what's what.
int x0= 0;
const u8 *src;
u16 *dst= (u16*)0x06000400;

for(i=0; Text[i] != '\0'; i++)
{
    ch = Text[i]-' ';
    src= &FontData[ch*64];      // Pointer to the current char's pixels
    charW = TextWidths[ch];

    for(iy=0; iy<8; iy++)
        for(ix=0; ix<charW; ix++)
            dst[iy*charW + ix+x0] = src[iy*charW + ix];

    x0 += 8*charW;
}

Of course it still doesn't work, but at least you can see why more easily.

IMHO, 8bit tiled rendering happen to be pretty much the most annoying mode to work with due to the tiling and the fact that you can't write in byte-size chunks to VRAM. Still, if you must ...

Code:
//! Draw onto an 8bit tile canvas. dstPitch is the distance
/*! in bytes between successive tile rows.
*/
static inline void chr8_plot(int x, int y, u8 color, void *dstBase, u32 dstPitch)
{
   u32 ofs= (y>>3)*dstPitch + (y&7)*8 + (x>>3)*64;
   u16 *dst= (u16*)(dstBase + ofs + (x&6));

   if(x&1)
      *dst = (*dst & 0x00FF) | color<<8;
   else
      *dst = (*dst &~0x00FF) | color;
}

//! Draws a string at position x, y.
/*! \note   Assumptions:
      - VRAM tiles are 8bpp and formatted as a 32x32 grid of tiles, row major.
      - input is ascii text.
*/
void DrawText(int x, int y, const char *str)
{
   int ix, iy;
   int ch, charW;
   const u8 *src;
   u16 *dst= (u16*)0x06004000;

   while((ch= *str++) != '\0')
   {
      ch -= ' ';
      src= &FontData[ch*64];
      charW = FontWidths[ch];
      
      for(ix=0; ix<charW; ix++)
         for(iy=0; iy<8; iy++)
            if(src[iy*8+ix])   // Only plot if there's something to do
               chr8_plot(x+ix, y+iy, src[iy*8+ix], dst, TILE_PITCH);
            
      x += charW;
   }
}

I've tested it a little and it seems to work. It's going to be very, very slow though because of all the coordinate corrections in chr8_plot(). There are ways around it, but they're not pretty. If the font permits it and you need more speed, I'd recommend going to 4bpp tiles which allow for much faster rendering because you can cover 8 pixels at once (but it requires some fairly fancy bitslinging to get it done efficiently). Cydrax' post here is an example of that, which should work for any-sized glyphs as well.

#139612 - Ruben - Sat Sep 08, 2007 4:04 am

Oh! Sorry. I had actually forgot to mention that as I was a bit sleepy last night so yeah. Umm... thanks for the link to that example code as I actually DO use 4bpp tiles. Also, that code seems to be very good. I'll see what I can do with it :P. And yeah. I probably should've described what my glyph image is. It starts at ASCII character 32 (space) and each character is written as an 8x8 tile... and coming to think of it, it really wouldn't give the right offset as well... hmm... probably should've triple checked the code before posting :P! Hehehe... thanks Cearn!