Introduction to D3DXFont

Introduction

Creating the D3DXFont Interface

Single Lines

Paragraphs of Text

A Few Comments About Rects

Lost Devices

Lesson Downloads

MSDN Links For Functions/Concepts Discussed Here

Introduction

In the DirectX9 Summer Update there were a lot of improvements. One of the interfaces that was in sore need of improvement was D3DXFont, and it got it. Not only is it more flexible than it was, but it's much faster. In some ways it's easier to use too.

This lesson will cover basic usage of D3DXFont. You can do more advanced things by combining it with D3DXSprite (which also was greatly improved) and by setting various flags. In this lesson we're going to cover 2 simple uses:

  • Single lines that don't need to be clipped
  • Paragraphs to be wrapped to a given rectangle

This lesson also uses dhFPSTimer which will let us gauge our application's performance. More information on dhFPSTimer can be found here.

Creating the D3DXFont Interface

The amount of options available in creating a font is dizzying. So I'll just cover some of the more common flags that you might want to use. The Further Reading section at the end of the lesson will lead to more detailed documentation on font creation.

There are 2 functions to create a D3DXFont interface: D3DXCreateFont and D3DXCreateFontIndirect. I'm going to cover D3DXCreateFont here. The main difference between them is that D3DXCreateFont takes a lot of parameters to specify the font, while D3DXCreateFontIndirect accepts a structure describing the font.

HRESULT WINAPI D3DXCreateFont(
   LPDIRECT3DDEVICE9 pDevice,
   unsigned int Height,
   unsigned int Width,
   unsigned int Weight,
   unsigned int MipLevels,
   BOOL Italic,
   DWORD CharSet,
   DWORD OutputPrecision,
   DWORD Quality,
   DWORD PitchAndFamily,
   LPCTSTR pFacename,
   LPD3DXFONT *ppFont
);
pDevice
A pointer to your Direct3D device.
Height
The height (in pixels) of the font you want to create.
Width
The width (in pixels) of the font you want to create. Set this to 0 for default width based on the height you gave above.
Weight
How thick the font is. Valid values range from 0-1,000. Common flags and their numeric values are:
  • FW_THIN(100)
  • FW_NORMAL(400)
  • FW_BOLD(700)
  • FW_HEAVY(900)
MipLevels
Number of MipMaps to create. 0 creates a full chain. Used to show the font at different sizes more efficiently. Since we won't be scaling our text, we use 1 to save memory.
Italic
True/False, do you want Italics?
CharSet
Character Set. Used to specify international character sets (Arabic, Greek, etc), just set it to DEFAULT_CHARSET.
OutputPrecision
How precisely the output must match the font. You can probably just leave this as OUT_DEFAULT_PRECIS.
Quality
Some possible values are: ANTIALIASED_QUALITY, DEFAULT_QUALITY, DRAFT_QUALITY, and PROOF_QUALITY.
PitchAndFamily
You can safely set this to DEFAULT_PITCH|FF_DONTCARE.
pFacename
The name of the font you want. You can set this to an empty string ("") and let it pick a font for you that matches the other parameters.

Here is an example of creating the font interface.

ID3DXFont *g_font=NULL;

   hr=D3DXCreateFont(g_d3d_device,     //D3D Device

                     22,               //Font height

                     0,                //Font width

                     FW_NORMAL,        //Font Weight

                     1,                //MipLevels

                     false,            //Italic

                     DEFAULT_CHARSET,  //CharSet

                     OUT_DEFAULT_PRECIS, //OutputPrecision

                     ANTIALIASED_QUALITY, //Quality

                     DEFAULT_PITCH|FF_DONTCARE,//PitchAndFamily

                     "Arial",          //pFacename,

                     &g_font);         //ppFont

When you're done with the interface, you call Release just as with all other DirectX interfaces.

   if(g_font){
      g_font->Release();
      g_font=NULL;
   }

Single Lines

In most games you rarely render large blocks of text, usually just a few short lines here and there on your screen. You usually also know that the text doesn't need to be clipped, that's all just part of laying out your display. Here we'll cover the DrawText method as well as how to call it when rendering short individual lines of text.

INT DrawText(
   LPD3DXSPRITE pSprite,
   LPCTSTR pString,
   int Count,
   LPRECT pRect,
   DWORD Format,
   D3DCOLOR Color
);
pSprite
A pointer to a ID3DXSprite interface. Used for advanced batching and optimization. Set it to NULL for now.
pString
The string you want to render.
Count
The number of characters you want to render. If you want to render the entire string, and the string is NULL terminated, you can just pass -1.
pRect
Formatting and Clipping rectangle. Used to position and format your text.
Format
Flags to specify text alignment, clipping and many other things.
Color
Standard 32-bit ARGB colour. Note: The alpha channel is respected for transparency. If you set the alpha to 0 you will not see your text.

Now let's look at how we'd use that call to render a short non-clipped string of text. I'll use the Frames Per Second readout as an example.

const char *fps_string;
RECT font_rect;

   //A pre-formatted string showing the current frames per second

   fps_string=g_timer.GetFPSString();

   SetRect(&font_rect,0,0,g_width,g_height);

   font_height=g_font->DrawText(NULL,        //pSprite

                                fps_string,  //pString

                                -1,          //Count

                                &font_rect,  //pRect

                                DT_LEFT|DT_NOCLIP,//Format,

                                0xFFFFFFFF); //Color

This renders the current Frames Per Second read out at the top left corner of the display. The first 2 numbers passed to SetRect are the Left and Top coordinates respectively. In this case, since we're not wrapping text or clipping it, the other 2 values are ignored, but they are the Right and Bottom corner.

We want the text to be left-aligned and we don't need it clipped. By setting it to not clip we get a slight performance improvement. The text is rendered in fully opaque white.

Paragraphs of Text

On the other extreme, some games need to render large blocks of text. Role Playing Games are a good example. D3DXFont supports word-wrapping, so it's easy to have text wrap and conform to the size of your text area. For this example we read in a large section of text (2 paragraphs from 1984 by George Orwell) and then render it to the screen. These 2 paragraphs are just 2 long strings, so it's up to D3DXFont to handle the wrapping.

   SetRect(&font_rect,0,30,g_width,g_height);

   font_height=g_font->DrawText(NULL,        //pSprite

                                g_text,      //pString

                                -1,          //Count

                                &font_rect,  //pRect

                                DT_LEFT|DT_WORDBREAK, //Format,

                                0xFFFFFFFF); //Color

This time our Right and Bottom coordinates in the rect are important. They'll be used as the boundaries for wrapping and clipping. Setting the DT_WORDBREAK flag makes it wrap sentences at word breaks.

A Few Comments About Rects

struct RECT{
   LONG left;
   LONG top;
   LONG right;
   LONG bottom;
};

It's important to note that rather than storing a width and height, the RECT structure stores the coordinate for the right and bottom extent. Since many other calls use width and height it's easy to make the mistake of using them here too.

Another important thing to note is that the bottom and right extents are exclusive, while the top and left extents are inclusive. If you called SetRect like this:

SetRect(&rect,
        0,  //xLeft

        0,  //yTop

        800,//xRight

        600);//yBottom)

The X coordinates would span from 0-799, an the Y coordinates from 0-599. It may seem a little odd, but in practice it works very well. Some people claim this is a bug, but it's consistent and well documented. I guess some people think anything is a bug when it doesn't work the way they think it should.

Lost Devices

So far handling lost devices has been trivial. Call TestCooperativeLevel until it gives D3DERR_DEVICENOTRESET, then call Reset. Since we create our Vertex Buffers as Managed resources there is no need to do anything to restore them. They are backed by a System memory copy so Direct3D can restore them as needed.

D3DXFont has internal resources that are not Managed, so they need to be freed and restored. It's still fairly easy to do. When we find that we've lost the device we call the OnLostDevice method, and when we've Reset the device, we call the OnResetDevice method. To do this we add a call to OnResetDevice to InitVolatileResources and OnLostDevice to FreeVolatileResources.

void InitVolatileResources(void){

   //Init internal resources

   g_font->OnResetDevice();

}
void FreeVolatileResources(void){

   //Free up some internal resources

   g_font->OnLostDevice();

}

Lesson Downloads

MSDN Links For Functions/Concepts Discussed Here

Back