2D and 3D Fonts

  CU_Fonts.zip (34.2 KiB, 3,914 hits)

Text is useful to show scores, game menus, and any other information you may want to share. DirectX supports both 2D and 3D fonts. 2D fonts are represented by the ID3DXFont interface. Rendering text with this interface can be accomplished in as little as 2 steps: create the font, and print the font. The font creation function, D3DXCreateFont and the font rendering method, ID3DXFont::DrawText, contain some parameters that are a bit hard to memorize, so we’ll create our own CFont class that encapsulates the ID3DXFont interface

#ifndef CFONT_H 
#define CFONT_H

#include "stdafx.h"

// Font alignment
enum FONTALIGNMENT { FA_LEFT, FA_CENTER, FA_RIGHT, FA_TOPRIGHT, FA_TOPLEFT, FA_BOTTOMRIGHT, FA_BOTTOMLEFT };

class CFont
{
public:
CFont();
~CFont() { Release(); }

BOOL Initialize( LPDIRECT3DDEVICE9 pDevice, char* faceName, int size, BOOL bold = FALSE,
BOOL italic = FALSE );
void Print( char* text, int xPosition, int yPosition, DWORD color, LPD3DXSPRITE sprite = NULL,
int textBoxWidth = 0, int textBoxHeight = 0, FONTALIGNMENT alignment = FA_LEFT );
void OnLostDevice();
void OnResetDevice();
void Release();
private:
LPD3DXFONT m_pFont;
};

#endif

The CFont class will allow us to create and render 2D text easily. The FONTALIGNMENT enum will be used to align the rendered text in a bounding rectangle.

#include “..\include\stdafx.h”
#include “..\include\CFont.h”

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Default constructor.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CFont::CFont() Toggle

{
m_pFont = NULL;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Initialize the font
Parameters:
[in] pDevice – D3D Device for D3DXCreateFont call
[in] faceName – Font name
[in] size – Font size
[in] bold – Bold if TRUE
[in] italic – Italicized if TRUE
Returns: TRUE if font was created successfully, FALSE otherwise.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CFont::Initialize( LPDIRECT3DDEVICE9 pDevice, char* faceName, int size, BOOL bold, BOOL italic ) Toggle

{
SAFE_RELEASE( m_pFont );
HRESULT hr = 0;
hr = D3DXCreateFont( pDevice, -size, 0, bold ? 800 : 0, 1, italic, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, faceName, &m_pFont );
if ( FAILED( hr ) )
{
SHOWERROR( “D3DXCreateFont() failed.”, __FILE__, __LINE__ );
return FALSE;
}
return TRUE;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Print some 2D text.
Parameters:
[in] text – Text to print
[in] xPosition – X position in window coordinates
[in] yPosition – Y position in window coordinates
[in] color – Color of the text.
[in] sprite – Sprite for batch printing
[in] textBoxWidth – Width to constrain text in
[in] textBoxHeight – Height to constrain text in
[in] format – FONTALIGNMENT value.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFont::Print( char* text, int xPosition, int yPosition, DWORD color, LPD3DXSPRITE sprite, Toggle

int textBoxWidth, int textBoxHeight, FONTALIGNMENT alignment )
{
if ( !m_pFont )
{
return;
}
DWORD format = DT_EXPANDTABS;
if ( textBoxWidth == 0 )
{
format |= DT_NOCLIP;
}
else
{
format |= DT_WORDBREAK;
switch ( alignment )
{
case FA_LEFT:
format |= DT_LEFT;
break;
case FA_CENTER:
format |= DT_CENTER;
break;
case FA_RIGHT:
format |= DT_RIGHT;
break;
case FA_TOPRIGHT:
format |= DT_RIGHT | DT_TOP;
break;
case FA_BOTTOMRIGHT:
format |= DT_RIGHT | DT_BOTTOM;
break;
case FA_TOPLEFT:
format |= DT_LEFT | DT_TOP;
break;
case FA_BOTTOMLEFT:
format |= DT_LEFT | DT_BOTTOM;
break;
}
if ( textBoxHeight == 0 )
{
// A width is specified, but not a height.
// Make it seem like height is infinite
textBoxHeight = 2000;
}
}
RECT rect = { xPosition, yPosition, xPosition + textBoxWidth, yPosition + textBoxHeight };
m_pFont->DrawText( sprite, text, –1, &rect, format, color );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Releases video resources. Call whenever the device is lost or before reseting the device.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFont::OnLostDevice() Toggle

{
if ( m_pFont )
{
m_pFont->OnLostDevice();
}
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Re-acquire video resources. Call after device is reset
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFont::OnResetDevice() Toggle

{
if ( m_pFont )
{
m_pFont->OnResetDevice();
}
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Release interfaces
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFont::Release() Toggle

{
SAFE_RELEASE( m_pFont );
}

To create a font, call D3DXCreateFont. Since a lot of the parameters are hard to remember, the CFont::Initialize method simplifies the creation of a font.

The ID3DXFont::DrawText method is also wrapped up in its own Print method. The Print method simplifies the process of printing text by setting certain parameters automatically. It also allows you to align the text with the FONTALIGNMENT enum that we created.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Renders the current frame.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
[in] elapsedTime – Time elapsed since last frame
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnRenderFrame( LPDIRECT3DDEVICE9 pDevice, float elapsedTime )
{
sprintf( m_fps, “%.2f fps”, m_pFramework->GetFPS() );

pDevice->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 0, 0, 0 ), 1.0f, 0 );
pDevice->BeginScene();

m_font.Print( “Printing text without a sprite like\r\nthis slows things down a bit.”,
m_pFramework->GetWidth() – 300, m_pFramework->GetHeight() – 100, D3DCOLOR_XRGB( 0, 200, 0 ) );

m_pTextSprite->Begin( D3DXSPRITE_ALPHABLEND | D3DXSPRITE_SORT_TEXTURE );
m_font.Print( g_sometext, 50, 100, D3DCOLOR_XRGB( 255, 0, 0 ), m_pTextSprite, 200, 0, FA_CENTER );
m_font.Print( “We can even align a bunch of text along the right side. Isn’t that neat?”,
m_pFramework->GetWidth() – 110, m_pFramework->GetHeight() – (m_pFramework->GetHeight() / 2) – 30,
D3DCOLOR_XRGB( 200, 200, 255 ), m_pTextSprite, 100, 0, FA_RIGHT );

// Display framerate and instructions
m_font.Print( m_fps, 5, 5, D3DCOLOR_XRGB( 255, 0, 0 ), m_pTextSprite );
if ( m_showInstructions )
{

m_font.Print( g_instructions, 5, 20, D3DCOLOR_XRGB( 255, 255, 255 ), m_pTextSprite );

}
else
{

m_font.Print( “Hit F1 to view the instructions.”, 5, 20, D3DCOLOR_XRGB( 255, 255, 255 ), m_pTextSprite );

}

m_pTextSprite->End();

pDevice->EndScene();
pDevice->Present( 0, 0, 0, 0 );

}

The above code shows how to use the CFont wrapper class to print text in your applications. Notice after the first line of text is printed, an ID3DXSprite instance is making a call to ID3DXSprite::Begin. When you are making lots of printing calls, using an ID3DXSprite increases the performance of your application by a noticeable amount. In my tests, I received as much as a 20% increase in performance by using an ID3DXSprite. To take advantage of this ID3DXSprite optimization, we simply put all our Print calls in between ID3DXSprite::Begin and ID3DXSprite::End while passing the sprite in to each Print method. Note that an ID3DXSprite interface is created by calling D3DXCreateSprite.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary:
This callback function will be called immediately after the Direct3D device has been created. This is
the best location to create D3DPOOL_MANAGED resources. Resources created here should be released in
the OnDestroyDevice callback.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnCreateDevice( LPDIRECT3DDEVICE9 pDevice )
{
// Clip…

// Create 3D text
SAFE_RELEASE( m_pTextMesh );
HFONT hFont = CreateFont( 0, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, “Arial” );

HDC hdc = CreateCompatibleDC( NULL );

// Save the old font
HFONT hFontOld = (HFONT)SelectObject(hdc, hFont);

// Create the text mesh
if ( FAILED( D3DXCreateText( pDevice, hdc, “C-Unit”, 0.01f, 0.4f, &m_pTextMesh, NULL, NULL ) ) )
{

SHOWERROR( “D3DXCreateText() – Failed.”, __FILE__, __LINE__ );

}
// Restore the old font and clean up
SelectObject( hdc, hFontOld );
DeleteObject( hFont );
DeleteDC( hdc );

}

3D text is represented by an ID3DXMesh interface. An ID3DXMesh interface is basically an encapsulation of a vertex buffer and an index buffer. Since text in an ID3DXMesh is 3D, it is affected by lighting and the world transform matrix, therefore we have to specify a light and a material to view 3D text properly.

The process of creating 3D text is a little different that the 2D font creation code. We first create a logical font by calling CreateFont, which is part of the Windows GDI library. The function that creates the text mesh, D3DXCreateText, uses the font that is currently in use by the device context. Therefore, we first create a compatible device context to use our font by calling CreateCompatibleDC and then we tell the device context to use the font that we just made by calling SelectObject. Note that this function returns what was previously used by the device context so we can restore it back if we want. The function, D3DXCreateText, stores the mesh in the passed in ID3DXMesh pointer. Also note that with 3D text, the text to display is specified when we create the mesh. Therefore, if we want to write something else, we have to create a new mesh. Compare this to 2D text, where once we create the font, we can write anything we want without having to make a whole new object or interface.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Renders the current frame.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
[in] elapsedTime – Time elapsed since last frame
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnRenderFrame( LPDIRECT3DDEVICE9 pDevice, float elapsedTime )
{
pDevice->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 0, 0, 0 ), 1.0f, 0 );
pDevice->BeginScene();

// Clip…

// Render 3D text
if ( m_pTextMesh )
{

m_pTextMesh->DrawSubset( 0 );

}

pDevice->EndScene();
pDevice->Present( 0, 0, 0, 0 );

}

To render 3D text, all we do is call ID3DXMesh::DrawSubset. We don’t specify a position because the position and orientation is determined by the transform matrices.

The ID3DXFont and ID3DXSprite classes have methods, OnLostDevice and OnResetDevice, that need to be called whenever the device is lost or reset. Also, since the D3DXCreateText function creates the mesh in the D3DPOOL_MANAGED memory pool, the mesh needs to be released when the device is destroyed by calling. The code for these calls is so small, I didn’t bother to display them here. Check out the source code to see all the resource handling.

Text