Tutorial 12: Font Engine

Writing text onto the screen is a pretty important function of any application. Rendering text in DirectX 10 requires that you first know how to render 2D images. As we covered that topic in the previous tutorial this tutorial will just build on that knowledge.

The first thing you are going to need is your own font image. I built a really simple one with the characters I wanted and put it on a 1024x16 DDS texture:

As you can see it has the basic characters needed and they are all on a single texture file. We can now build a simple font engine which uses an index into that texture to draw individual characters to the screen as needed. How we do that in DirectX 10 is we build a square out of two triangles and then render the single character from the texture onto that square. So if we have a sentence we figure out the characters we need, create a square for each of them, and then render the characters to those squares. After doing that we render all the squares onto the screen to the location that forms the sentence. This is the same method we used in the previous tutorial to render a 2D image to the screen.

Now to index the texture we will need a text file that gives the location and size of each character in the texture. This text file will allow the font engine to quickly grab the pixels it needs out of the texture to form a character that it can render. Below is the index file for this font texture.

This format of the file is: [Ascii value of character] [The character] [Left Texture U coordinate] [Right Texture U Coordinate] [Pixel Width of Character]

32   0.0        0.0         0
33 ! 0.0        0.000976563 1
34 " 0.00195313 0.00488281  3
35 # 0.00585938 0.0136719   8
36 $ 0.0146484  0.0195313   5
37 % 0.0205078  0.0302734   10
38 & 0.03125    0.0390625   8
39 ' 0.0400391  0.0410156   1
40 ( 0.0419922  0.0449219   3
41 ) 0.0458984  0.0488281   3
42 * 0.0498047  0.0546875   5
43 + 0.0556641  0.0625      7
44 , 0.0634766  0.0644531   1
45 - 0.0654297  0.0683594   3
46 . 0.0693359  0.0703125   1
47 / 0.0712891  0.0751953   4
48 0 0.0761719  0.0820313   6
49 1 0.0830078  0.0859375   3
50 2 0.0869141  0.0927734   6
51 3 0.09375    0.0996094   6
52 4 0.100586   0.106445    6
53 5 0.107422   0.113281    6
54 6 0.114258   0.120117    6
55 7 0.121094   0.126953    6
56 8 0.12793    0.133789    6
57 9 0.134766   0.140625    6
58 : 0.141602   0.142578    1
59 ; 0.143555   0.144531    1
60 < 0.145508   0.151367    6
61 = 0.152344   0.15918     7
62 > 0.160156   0.166016    6
63 ? 0.166992   0.171875    5
64 @ 0.172852   0.18457     12
65 A 0.185547   0.194336    9
66 B 0.195313   0.202148    7
67 C 0.203125   0.209961    7
68 D 0.210938   0.217773    7
69 E 0.21875    0.225586    7
70 F 0.226563   0.232422    6
71 G 0.233398   0.241211    8
72 H 0.242188   0.249023    7
73 I 0.25       0.250977    1
74 J 0.251953   0.256836    5
75 K 0.257813   0.265625    8
76 L 0.266602   0.272461    6
77 M 0.273438   0.282227    9
78 N 0.283203   0.290039    7
79 O 0.291016   0.298828    8
80 P 0.299805   0.306641    7
81 Q 0.307617   0.31543     8
82 R 0.316406   0.323242    7
83 S 0.324219   0.331055    7
84 T 0.332031   0.338867    7
85 U 0.339844   0.34668     7
86 V 0.347656   0.356445    9
87 W 0.357422   0.370117    13
88 X 0.371094   0.37793     7
89 Y 0.378906   0.385742    7
90 Z 0.386719   0.393555    7
91 [ 0.394531   0.396484    2
92 \ 0.397461   0.401367    4
93 ] 0.402344   0.404297    2
94 ^ 0.405273   0.410156    5
95 _ 0.411133   0.417969    7
96 ` 0.418945   0.420898    2
97 a 0.421875   0.426758    5
98 b 0.427734   0.432617    5
99 c 0.433594   0.438477    5
100 d 0.439453  0.444336    5
101 e 0.445313  0.450195    5
102 f 0.451172  0.455078    4
103 g 0.456055  0.460938    5
104 h 0.461914  0.466797    5
105 i 0.467773  0.46875     1
106 j 0.469727  0.472656    3
107 k 0.473633  0.478516    5
108 l 0.479492  0.480469    1
109 m 0.481445  0.490234    9
110 n 0.491211  0.496094    5
111 o 0.49707   0.501953    5
112 p 0.50293   0.507813    5
113 q 0.508789  0.513672    5
114 r 0.514648  0.517578    3
115 s 0.518555  0.523438    5
116 t 0.524414  0.527344    3
117 u 0.52832   0.533203    5
118 v 0.53418   0.539063    5
119 w 0.540039  0.548828    9
120 x 0.549805  0.554688    5
121 y 0.555664  0.560547    5
122 z 0.561523  0.566406    5
123 { 0.567383  0.570313    3
124 | 0.571289  0.572266    1
125 } 0.573242  0.576172    3
126 ~ 0.577148  0.583984    7

With both the index file and texture file we have what we need to build the font engine. If you need to build your own index file just make sure that each character is only separated by spaces and you can write a bitmap parser yourself to create TU and TV coordinates based on where there are no blank spaces.

Remember that different users will run your application in different resolutions. One size font is not going to be clearly readable on all resolutions. So you may want to make 3-4 different font sizes and use certain ones for certain resolutions to fix this problem.


Framework

As we will want to encapsulate the font functionality in its own set of classes we will add some new classes to our frame work. The updated frame work looks like the following:

In this tutorial we have three new classes called TextClass, FontClass, and FontShader class. FontShaderClass is the shader for rendering fonts similar to how TextureShaderClass was used for rendering bitmap images in the previous tutorial. FontClass holds the font data and constructs vertex buffers needed for rendering strings. TextClass contains the vertex and index buffers for each set of text strings that need to be rendered to the screen, it uses FontClass to create the vertex buffer for the strings and then uses FontShaderClass to render those buffers.


Fontclass.h

We will look at the new FontClass first. This class will handle the texture for the font, the font data from the text file, and the function used to build vertex buffers with the font data. The vertex buffers that hold the font data for individual sentences will be in the TextClass and not inside this class.

////////////////////////////////////////////////////////////////////////////////
// Filename: fontclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FONTCLASS_H_
#define _FONTCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <fstream>
using namespace std;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: FontClass
////////////////////////////////////////////////////////////////////////////////
class FontClass
{
private:

The FontType structure is used to hold the indexing data read in from the font index file. The left and right are the TU texture coordinates. The size is the width of the character in pixels.

	struct FontType
	{
		float left, right;
		int size;
	};

The VertexType structure is for the actual vertex data used to build the square to render the text character on. The individual character will require two triangles to make a square. Those triangles will have position and texture data only.

	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

public:
	FontClass();
	FontClass(const FontClass&);
	~FontClass();

	bool Initialize(ID3D10Device*, char*, WCHAR*);
	void Shutdown();

	ID3D10ShaderResourceView* GetTexture();

BuildVertexArray will handle building and returning a vertex array of triangles that will render the character sentence which was given as input to this function. This function will be called by the new TextClass to build vertex arrays of all the sentences it needs to render.

	void BuildVertexArray(void*, char*, float, float);

private:
	bool LoadFontData(char*);
	void ReleaseFontData();
	bool LoadTexture(ID3D10Device*, WCHAR*);
	void ReleaseTexture();

private:
	FontType* m_Font;
	TextureClass* m_Texture;
};

#endif

Fontclass.cpp

///////////////////////////////////////////////////////////////////////////////
// Filename: fontclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "fontclass.h"

The class constructor initializes all the private member variables for the FontClass to null.

FontClass::FontClass()
{
	m_Font = 0;
	m_Texture = 0;
}


FontClass::FontClass(const FontClass& other)
{
}


FontClass::~FontClass()
{
}

Initialize will load the font data and the font texture.

bool FontClass::Initialize(ID3D10Device* device, char* fontFilename, WCHAR* textureFilename)
{
	bool result;


	// Load in the text file containing the font data.
	result = LoadFontData(fontFilename);
	if(!result)
	{
		return false;
	}

	// Load the texture that has the font characters on it.
	result = LoadTexture(device, textureFilename);
	if(!result)
	{
		return false;
	}

	return true;
}

Shutdown will release the font data and the font texture.

void FontClass::Shutdown()
{
	// Release the font texture.
	ReleaseTexture();

	// Release the font data.
	ReleaseFontData();

	return;
}

The LoadFontData function is where we load the fontdata.txt file which contains the indexing information for the texture.

bool FontClass::LoadFontData(char* filename)
{
	ifstream fin;
	int i;
	char temp;

First we create an array of the FontType structure. The size of the array is set to 95 as that is the number of characters in the texture and hence the number of indexes in the fontdata.txt file.

	// Create the font spacing buffer.
	m_Font = new FontType[95];
	if(!m_Font)
	{
		return false;
	}

Now we open the file and read each line into the array m_Font. We only need to read in the texture TU left and right coordinates as well as the pixel size of the character.

	// Read in the font size and spacing between chars.
	fin.open(filename);
	if(fin.fail())
	{
		return false;
	}

	// Read in the 95 used ascii characters for text.
	for(i=0; i<95; i++)
	{
		fin.get(temp);
		while(temp != ' ')
		{
			fin.get(temp);
		}
		fin.get(temp);
		while(temp != ' ')
		{
			fin.get(temp);
		}

		fin >> m_Font[i].left;
		fin >> m_Font[i].right;
		fin >> m_Font[i].size;
	}

	// Close the file.
	fin.close();

	return true;
}

The ReleaseFontData function releases the array that holds the texture indexing data.

void FontClass::ReleaseFontData()
{
	// Release the font data array.
	if(m_Font)
	{
		delete [] m_Font;
		m_Font = 0;
	}

	return;
}

The LoadTexture function reads in the font.dds file into the texture shader resource. This will be the texture we take the characters from and write them to their own square polygons for rendering.

bool FontClass::LoadTexture(ID3D10Device* device, WCHAR* filename)
{
	bool result;


	// Create the texture object.
	m_Texture = new TextureClass;
	if(!m_Texture)
	{
		return false;
	}

	// Initialize the texture object.
	result = m_Texture->Initialize(device, filename);
	if(!result)
	{
		return false;
	}

	return true;
}

ReleaseTexture function releases the texture that was used for the font.

void FontClass::ReleaseTexture()
{
	// Release the texture object.
	if(m_Texture)
	{
		m_Texture->Shutdown();
		delete m_Texture;
		m_Texture = 0;
	}

	return;
}

GetTexture returns the font texture interface so the font graphics can be rendered.

ID3D10ShaderResourceView* FontClass::GetTexture()
{
	return m_Texture->GetTexture();
}

BuildVertexArray will be called by the TextClass to build vertex buffers out of the text sentences it sends to this function as input. This way each sentence in the TextClass that needs to be drawn has its own vertex buffer that can be rendered easily after being created. The vertices input is the pointer to the vertex array that will be returned to the TextClass once it is built. The sentence input is the text sentence that will be used to create the vertex array. The drawX and drawY input variables are the screen coordinates of where to draw the sentence.

void FontClass::BuildVertexArray(void* vertices, char* sentence, float drawX, float drawY)
{
	VertexType* vertexPtr;
	int numLetters, index, i, letter;


	// Coerce the input vertices into a VertexType structure.
	vertexPtr = (VertexType*)vertices;

	// Get the number of letters in the sentence.
	numLetters = (int)strlen(sentence);

	// Initialize the index to the vertex array.
	index = 0;

The following loop will now build the vertex and index arrays. It takes each character from the sentence and creates two triangles for it. It then maps the character from the font texture onto those two triangles using the m_Font array which has the TU texture coordinates and pixel size. Once the polygon for that character has been created it then updates the X coordinate on the screen of where to draw the next character.

	// Draw each letter onto a quad.
	for(i=0; i<numLetters; i++)
	{
		letter = ((int)sentence[i]) - 32;

		// If the letter is a space then just move over three pixels.
		if(letter == 0)
		{
			drawX = drawX + 3.0f;
		}
		else
		{
			// First triangle in quad.
			vertexPtr[index].position = D3DXVECTOR3(drawX, drawY, 0.0f);  // Top left.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].left, 0.0f);
			index++;

			vertexPtr[index].position = D3DXVECTOR3((drawX + m_Font[letter].size), (drawY - 16), 0.0f);  // Bottom right.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].right, 1.0f);
			index++;

			vertexPtr[index].position = D3DXVECTOR3(drawX, (drawY - 16), 0.0f);  // Bottom left.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].left, 1.0f);
			index++;

			// Second triangle in quad.
			vertexPtr[index].position = D3DXVECTOR3(drawX, drawY, 0.0f);  // Top left.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].left, 0.0f);
			index++;

			vertexPtr[index].position = D3DXVECTOR3(drawX + m_Font[letter].size, drawY, 0.0f);  // Top right.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].right, 0.0f);
			index++;

			vertexPtr[index].position = D3DXVECTOR3((drawX + m_Font[letter].size), (drawY - 16), 0.0f);  // Bottom right.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].right, 1.0f);
			index++;

			// Update the x location for drawing by the size of the letter and one pixel.
			drawX = drawX + m_Font[letter].size + 1.0f;
		}
	}

	return;
}

Font.fx

Font.fx is the shader for rendering 2D text on top of our 3D scenes. This shader is just a modified version of texture.fx that was used to render 2D images.

////////////////////////////////////////////////////////////////////////////////
// Filename: font.fx
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
Texture2D shaderTexture;

One of the new global variables in the shader is pixelColor. We use this to control the color of the pixel that will be used to draw the font.

float4 pixelColor;


///////////////////
// SAMPLE STATES //
///////////////////
SamplerState SampleType
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};

The second change to the shader is the new blend state we create called AlphaBlendingState. Blending allows the font to blend with the 3D objects in the background. If we don't turn on blending we see the black triangles behind the text. But with blending turned on only the pixels for the text show up on the screen and the rest of the triangle is completely see-through. The structure for the blend state is called D3D10_BLEND_DESC. In the blend state here I use the default settings except for the destination blend which I change to invert source alpha. I won't get into great detail about blending here but a simple blend was needed for this tutorial to work correctly.

/////////////////////
// BLENDING STATES //
/////////////////////
BlendState AlphaBlendingState
{
    BlendEnable[0] = TRUE;
    DestBlend = INV_SRC_ALPHA;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType FontVertexShader(VertexInputType input)
{
    PixelInputType output;
    
    
    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;
    
    return output;
}

The FontPixelShader first samples the font texture to get the pixel. If it samples a pixel that is black then it is just part of the background triangle and not a text pixel. In this case we set the alpha of this pixel to zero so when the blending is calculated it will determine that this pixel should be see-through. If the color of the input pixel is not black then it is a text pixel. In this case we multiply it by the pixelColor to get the pixel colored the way we want and then draw that to the screen.

////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 FontPixelShader(PixelInputType input) : SV_Target
{
    float4 color;
	
	
    // Sample the texture pixel at this location.
    color = shaderTexture.Sample(SampleType, input.tex);
	
    // If the color is black on the texture then treat this pixel as transparent.
    if(color.r == 0.0f)
    {
        color.a = 0.0f;
    }
	
    // If the color is other than black on the texture then this is a pixel in the font so draw it using the font pixel color.
    else
    {
        color.a = 1.0f;
        color = color * pixelColor;
    }

    return color;
}

The FontTechnique is different in that we turn on the alpha blending state using SetBlendState. The first parameter is the blend state we created in this shader. The second two parameters are just defaults. Note that if you do turn on blending here in this shader then it is on permanently until another shader turns it off. Changing the blending state is fairly expensive also so it shouldn't be done too often, the code should be organized such that scenes are drawn with minimal blending state changes.

////////////////////////////////////////////////////////////////////////////////
// Technique
////////////////////////////////////////////////////////////////////////////////
technique10 FontTechnique
{
    pass pass0
    {
        SetBlendState(AlphaBlendingState, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xFFFFFFFF);
        SetVertexShader(CompileShader(vs_4_0, FontVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, FontPixelShader()));
        SetGeometryShader(NULL);
    }
}

Fontshaderclass.h

The FontShaderClass is just the TextureShaderClass from the previous tutorial renamed with a couple code changes for rendering fonts.

////////////////////////////////////////////////////////////////////////////////
// Filename: fontshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FONTSHADERCLASS_H_
#define _FONTSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d10.h>
#include <d3dx10.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: FontShaderClass
////////////////////////////////////////////////////////////////////////////////
class FontShaderClass
{
public:
	FontShaderClass();
	FontShaderClass(const FontShaderClass&);
	~FontShaderClass();

	bool Initialize(ID3D10Device*, HWND);
	void Shutdown();
	void Render(ID3D10Device*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView*, D3DXVECTOR4);

private:
	bool InitializeShader(ID3D10Device*, HWND, WCHAR*);
	void ShutdownShader();
	void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

	void SetShaderParameters(D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView*, D3DXVECTOR4);
	void RenderShader(ID3D10Device*, int);

private:
	ID3D10Effect* m_effect;
	ID3D10EffectTechnique* m_technique;
	ID3D10InputLayout* m_layout;

	ID3D10EffectMatrixVariable* m_worldMatrixPtr;
	ID3D10EffectMatrixVariable* m_viewMatrixPtr;
	ID3D10EffectMatrixVariable* m_projectionMatrixPtr;
	ID3D10EffectShaderResourceVariable* m_texturePtr;

The FontShaderClass has a new shader pointer for the pixel color that will be used to render the fonts with color.

	ID3D10EffectVectorVariable* m_pixelColorPtr;
};

#endif

Fontshaderclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: fontshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "fontshaderclass.h"


FontShaderClass::FontShaderClass()
{
	m_effect = 0;
	m_technique = 0;
	m_layout = 0;

	m_worldMatrixPtr = 0;
	m_viewMatrixPtr = 0;
	m_projectionMatrixPtr = 0;
	m_texturePtr = 0;

We initialize the pixel color pointer to null in the class constructor.

	m_pixelColorPtr = 0;
}


FontShaderClass::FontShaderClass(const FontShaderClass& other)
{
}


FontShaderClass::~FontShaderClass()
{
}

Initialize loads and compiles the HLSL font shader file.

bool FontShaderClass::Initialize(ID3D10Device* device, HWND hwnd)
{
	bool result;


	// Initialize the shader that will be used to draw the triangle.
	result = InitializeShader(device, hwnd, L"../Engine/font.fx");
	if(!result)
	{
		return false;
	}

	return true;
}

Shutdown releases the font shader related pointers and data.

void FontShaderClass::Shutdown()
{
	// Shutdown the shader effect.
	ShutdownShader();

	return;
}

Render will set the shader parameters and then draw the buffers using the font shader. Notice the is the same as TextureShaderClass except that it takes in the new pixelColor parameter.

void FontShaderClass::Render(ID3D10Device* device, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
			     D3DXMATRIX projectionMatrix, ID3D10ShaderResourceView* texture, D3DXVECTOR4 pixelColor)
{
	// Set the shader parameters that it will use for rendering.
	SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, texture, pixelColor);

	// Now render the prepared buffers with the shader.
	RenderShader(device, indexCount);

	return;
}

The InitializeShader function loads the HLSL font shader into an effect and prepares the pointer variables that interface with the font shader.

bool FontShaderClass::InitializeShader(ID3D10Device* device, HWND hwnd, WCHAR* filename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	D3D10_INPUT_ELEMENT_DESC polygonLayout[2];
	unsigned int numElements;
	D3D10_PASS_DESC passDesc;


	// Initialize the error message.
	errorMessage = 0;

	// Load the shader in from the file.
	result = D3DX10CreateEffectFromFile(filename, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, 
					    device, NULL, NULL, &m_effect, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, filename);
		}
		// If there was  nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, filename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

The name of the technique has been changed to FontTechnique.

	// Get a pointer to the technique inside the shader.
	m_technique = m_effect->GetTechniqueByName("FontTechnique");
	if(!m_technique)
	{
		return false;
	}

	// Now setup the layout of the data that goes into the shader.
	// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
	polygonLayout[0].SemanticName = "POSITION";
	polygonLayout[0].SemanticIndex = 0;
	polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[0].InputSlot = 0;
	polygonLayout[0].AlignedByteOffset = 0;
	polygonLayout[0].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[0].InstanceDataStepRate = 0;

	polygonLayout[1].SemanticName = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	// Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	// Get the description of the first pass described in the shader technique.
	m_technique->GetPassByIndex(0)->GetDesc(&passDesc);

	// Create the input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, 
					   &m_layout);
	if(FAILED(result))
	{
		return false;
	}

	// Get pointers to the three matrices inside the shader so we can update them from this class.
	m_worldMatrixPtr = m_effect->GetVariableByName("worldMatrix")->AsMatrix();
	m_viewMatrixPtr = m_effect->GetVariableByName("viewMatrix")->AsMatrix();
	m_projectionMatrixPtr = m_effect->GetVariableByName("projectionMatrix")->AsMatrix();

	// Get pointer to the texture resource inside the shader.
	m_texturePtr = m_effect->GetVariableByName("shaderTexture")->AsShaderResource();

The second change is the pointer to the pixel color inside the shader is setup.

	// Get pointer to the pixel color variable inside the shader.
	m_pixelColorPtr = m_effect->GetVariableByName("pixelColor")->AsVector();

	return true;
}

The ShutdownShader function releases all the shader related data.

void FontShaderClass::ShutdownShader()
{

The new pixel color pointer is released here.

	// Release the pointer to the pixel color of the font.
	m_pixelColorPtr = 0;

	// Release the pointer to the texture in the shader file.
	m_texturePtr = 0;

	// Release the pointers to the matrices inside the shader.
	m_worldMatrixPtr = 0;
	m_viewMatrixPtr = 0;
	m_projectionMatrixPtr = 0;

	// Release the pointer to the shader layout.
	if(m_layout)
	{
		m_layout->Release();
		m_layout = 0;
	}

	// Release the pointer to the shader technique.
	m_technique = 0;

	// Release the pointer to the shader.
	if(m_effect)
	{
		m_effect->Release();
		m_effect = 0;
	}

	return;
}

OutputShaderErrorMessage writes any shader compilation errors to a text file that can be reviewed in the event of a failure in compilation.

void FontShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
	char* compileErrors;
	unsigned long bufferSize, i;
	ofstream fout;


	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for(i=0; i<bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}

The SetShaderParameters function sets all the shader variables before rendering.

void FontShaderClass::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix,
					  ID3D10ShaderResourceView* texture, D3DXVECTOR4 pixelColor)
{
	// Set the world matrix variable inside the shader.
	m_worldMatrixPtr->SetMatrix((float*)&worldMatrix);

	// Set the view matrix variable inside the shader.
	m_viewMatrixPtr->SetMatrix((float*)&viewMatrix);

	// Set the projection matrix variable inside the shader.
	m_projectionMatrixPtr->SetMatrix((float*)&projectionMatrix);

	// Bind the texture.
	m_texturePtr->SetResource(texture);

The new pixelColor shader variable is set here.

	// Set the pixel color variable inside the shader with the pixelColor vector.
	m_pixelColorPtr->SetFloatVector((float*)&pixelColor);

	return;
}

RenderShader draws the prepared font vertex/index buffers using the font shader.

void FontShaderClass::RenderShader(ID3D10Device* device, int indexCount)
{
	D3D10_TECHNIQUE_DESC techniqueDesc;
	unsigned int i;
	

	// Set the input layout.
	device->IASetInputLayout(m_layout);

	// Get the description structure of the technique from inside the shader so it can be used for rendering.
	m_technique->GetDesc(&techniqueDesc);

	// Go through each pass in the technique (should be just one currently) and renders the triangles.
	for(i=0; i<techniqueDesc.Passes; ++i)
	{
		m_technique->GetPassByIndex(i)->Apply(0);
		device->DrawIndexed(indexCount, 0, 0);
	}

	return;
}

Textclass.h

The TextClass handles all the 2D text drawing that the application will need. It renders 2D text to the screen and uses the FontClass and FontShaderClass to assist it in doing so.

////////////////////////////////////////////////////////////////////////////////
// Filename: textclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTCLASS_H_
#define _TEXTCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "fontclass.h"
#include "fontshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: TextClass
////////////////////////////////////////////////////////////////////////////////
class TextClass
{
private:

SentenceType is the structure that holds the rendering information for each text sentence.

	struct SentenceType
	{
		ID3D10Buffer *vertexBuffer, *indexBuffer;
		int vertexCount, indexCount, maxLength;
		float red, green, blue;
	};

The VertexType must match the one in the FontClass.

	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

public:
	TextClass();
	TextClass(const TextClass&);
	~TextClass();

	bool Initialize(ID3D10Device*, HWND, int, int, D3DXMATRIX);
	void Shutdown();
	void Render(ID3D10Device*, D3DXMATRIX, D3DXMATRIX);

private:
	bool InitializeSentence(SentenceType**, int, ID3D10Device*);
	bool UpdateSentence(SentenceType*, char*, int, int, float, float, float);
	void ReleaseSentence(SentenceType**);
	void RenderSentence(ID3D10Device*, SentenceType*, D3DXMATRIX, D3DXMATRIX);

private:
	FontClass* m_Font;
	FontShaderClass* m_FontShader;
	int m_screenWidth, m_screenHeight;
	D3DXMATRIX m_baseViewMatrix;

We will use two sentences in this tutorial.

	SentenceType* m_sentence1;
	SentenceType* m_sentence2;
};

#endif

Textclass.cpp

///////////////////////////////////////////////////////////////////////////////
// Filename: textclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "textclass.h"

The class constructor initializes the private member variables to null.

TextClass::TextClass()
{
	m_Font = 0;
	m_FontShader = 0;

	m_sentence1 = 0;
	m_sentence2 = 0;
}


TextClass::TextClass(const TextClass& other)
{
}


TextClass::~TextClass()
{
}


bool TextClass::Initialize(ID3D10Device* device, HWND hwnd, int screenWidth, int screenHeight, D3DXMATRIX baseViewMatrix)
{
	bool result;

Store the screen size and the base view matrix, these will be used for rendering 2D text.

	// Store the screen width and height.
	m_screenWidth = screenWidth;
	m_screenHeight = screenHeight;

	// Store the base view matrix.
	m_baseViewMatrix = baseViewMatrix;

Create and initialize the font object.

	// Create the font object.
	m_Font = new FontClass;
	if(!m_Font)
	{
		return false;
	}

	// Initialize the font object.
	result = m_Font->Initialize(device, "../Engine/data/fontdata.txt", L"../Engine/data/font.dds");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the font object.", L"Error", MB_OK);
		return false;
	}

Create and initialize the font shader object.

	// Create the font shader object.
	m_FontShader = new FontShaderClass;
	if(!m_FontShader)
	{
		return false;
	}

	// Initialize the font shader object.
	result = m_FontShader->Initialize(device, hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the font shader object.", L"Error", MB_OK);
		return false;
	}

Create and initialize the two strings that will be used for this tutorial. One string says Hello in white at 100, 100 and the other says Goodbye in yellow at 100, 200. The UpdateSentence function can be called to change the contents, location, and color of the strings at any time.

	// Initialize the first sentence.
	result = InitializeSentence(&m_sentence1, 16, device);
	if(!result)
	{
		return false;
	}

	// Now update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence1, "Hello", 100, 100, 1.0f, 1.0f, 1.0f);
	if(!result)
	{
		return false;
	}

	// Initialize the first sentence.
	result = InitializeSentence(&m_sentence2, 16, device);
	if(!result)
	{
		return false;
	}

	// Now update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence2, "Goodbye", 100, 200, 1.0f, 1.0f, 0.0f);
	if(!result)
	{
		return false;
	}

	return true;
}

The Shutdown function will release the two sentences, the font object, and the font shader object.

void TextClass::Shutdown()
{
	// Release the first sentence.
	ReleaseSentence(&m_sentence1);

	// Release the second sentence.
	ReleaseSentence(&m_sentence2);

	// Release the font shader object.
	if(m_FontShader)
	{
		m_FontShader->Shutdown();
		delete m_FontShader;
		m_FontShader = 0;
	}

	// Release the font object.
	if(m_Font)
	{
		m_Font->Shutdown();
		delete m_Font;
		m_Font = 0;
	}

	return;
}

Render will draw the two sentences to the screen.

void TextClass::Render(ID3D10Device* device, D3DXMATRIX worldMatrix, D3DXMATRIX orthoMatrix)
{
	// Draw the first sentence.
	RenderSentence(device, m_sentence1, worldMatrix, orthoMatrix);

	// Draw the second sentence.
	RenderSentence(device, m_sentence2, worldMatrix, orthoMatrix);

	return;
}

The InitializeSentence function creates a SentenceType with an empty vertex buffer which will be used to store and render sentences. The maxLength input parameter determines how large the vertex buffer will be. All sentences have a vertex and index buffer associated with them which is initialized first in this function.

bool TextClass::InitializeSentence(SentenceType** sentence, int maxLength, ID3D10Device* device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D10_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D10_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;
	int i;


	// Create a new sentence object.
	*sentence = new SentenceType;
	if(!*sentence)
	{
		return false;
	}

	// Initialize the sentence buffers to null.
	(*sentence)->vertexBuffer = 0;
	(*sentence)->indexBuffer = 0;

	// Set the maximum length of the sentence.
	(*sentence)->maxLength = maxLength;

	// Set the number of vertices in the vertex array.
	(*sentence)->vertexCount = 6 * maxLength;

	// Set the number of indexes in the index array.
	(*sentence)->indexCount = (*sentence)->vertexCount;

	// Create the vertex array.
	vertices = new VertexType[(*sentence)->vertexCount];
	if(!vertices)
	{
		return false;
	}

	// Create the index array.
	indices = new unsigned long[(*sentence)->indexCount];
	if(!indices)
	{
		return false;
	}

	// Initialize vertex array to zeros at first.
	memset(vertices, 0, (sizeof(VertexType) * (*sentence)->vertexCount));

	// Initialize the index array.
	for(i=0; i<(*sentence)->indexCount; i++)
	{
		indices[i] = i;
	}

During the creation of the vertex buffer description for the sentence we set the Usage type to dynamic as we may want to change the contents of the sentence at any time.

	// Set up the description of the dynamic vertex buffer.
	vertexBufferDesc.Usage = D3D10_USAGE_DYNAMIC;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * (*sentence)->vertexCount;
	vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
	vertexBufferDesc.MiscFlags = 0;

	// Give the subresource structure a pointer to the vertex data.
	vertexData.pSysMem = vertices;

	// Create the vertex buffer.
	result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &(*sentence)->vertexBuffer);
	if(FAILED(result))
	{
		return false;
	}

The index buffer is setup as a normal static buffer since the contents will never need to change.

	// Set up the description of the static index buffer.
	indexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
	indexBufferDesc.ByteWidth = sizeof(unsigned long) * (*sentence)->indexCount;
	indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
	indexBufferDesc.CPUAccessFlags = 0;
	indexBufferDesc.MiscFlags = 0;

	// Give the subresource structure a pointer to the index data.
	indexData.pSysMem = indices;

	// Create the index buffer.
	result = device->CreateBuffer(&indexBufferDesc, &indexData, &(*sentence)->indexBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Release the vertex array as it is no longer needed.
	delete [] vertices;
	vertices = 0;

	// Release the index array as it is no longer needed.
	delete [] indices;
	indices = 0;

	return true;
}

UpdateSentence changes the contents of the vertex buffer for the input sentence. It uses the Map and Unmap functions along with memcpy to update the contents of the vertex buffer.

bool TextClass::UpdateSentence(SentenceType* sentence, char* text, int positionX, int positionY, float red, float green, float blue)
{
	int numLetters;
	VertexType* vertices;
	float drawX, drawY;
	void* verticesPtr;
	HRESULT result;

Set the color and size of the sentence.

	// Store the color of the sentence.
	sentence->red = red;
	sentence->green = green;
	sentence->blue = blue;

	// Get the number of letters in the sentence.
	numLetters = (int)strlen(text);

	// Check for possible buffer overflow.
	if(numLetters > sentence->maxLength)
	{
		return false;
	}

	// Create the vertex array.
	vertices = new VertexType[sentence->vertexCount];
	if(!vertices)
	{
		return false;
	}

	// Initialize vertex array to zeros at first.
	memset(vertices, 0, (sizeof(VertexType) * sentence->vertexCount));

Calculate the starting location to draw the sentence on the screen at.

	// Calculate the X and Y pixel position on the screen to start drawing to.
	drawX = (float)(((m_screenWidth / 2) * -1) + positionX);
	drawY = (float)((m_screenHeight / 2) - positionY);

Build the vertex array using the FontClass and the sentence information.

	// Use the font class to build the vertex array from the sentence text and sentence draw location.
	m_Font->BuildVertexArray((void*)vertices, text, drawX, drawY);

Copy the vertex array information into the sentence vertex buffer.

	// Initialize the vertex buffer pointer to null first.
	verticesPtr = 0;

	// Lock the vertex buffer.
	result = sentence->vertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&verticesPtr);
	if(FAILED(result))
	{
		return false;
	}

	// Copy the vertex array into the vertex buffer.
	memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * sentence->vertexCount));

	// Unlock the vertex buffer.
	sentence->vertexBuffer->Unmap();

	// Release the vertex array as it is no longer needed.
	delete [] vertices;
	vertices = 0;

	return true;
}

ReleaseSentence is used to release the sentence vertex and index buffer as well as the sentence itself.

void TextClass::ReleaseSentence(SentenceType** sentence)
{
	if(*sentence)
	{
		// Release the sentence vertex buffer.
		if((*sentence)->vertexBuffer)
		{
			(*sentence)->vertexBuffer->Release();
			(*sentence)->vertexBuffer = 0;
		}

		// Release the sentence index buffer.
		if((*sentence)->indexBuffer)
		{
			(*sentence)->indexBuffer->Release();
			(*sentence)->indexBuffer = 0;
		}

		// Release the sentence.
		delete *sentence;
		*sentence = 0;
	}

	return;
}

The RenderSentence function puts the sentence vertex and index buffer on the input assembler and then calls the FontShaderClass object to draw the sentence that was given as input to this function. Notice that we use the m_baseViewMatrix instead of the current view matrix. This allows us to draw text to the same location on the screen each frame regardless of where the current view may be. Likewise we use the orthoMatrix instead of the regular projection matrix since this should be drawn using 2D coordinates.

void TextClass::RenderSentence(ID3D10Device* device, SentenceType* sentence, D3DXMATRIX worldMatrix, D3DXMATRIX orthoMatrix)
{
	unsigned int stride, offset;
	D3DXVECTOR4 pixelColor;


	// Set vertex buffer stride and offset.
	stride = sizeof(VertexType); 
	offset = 0;

	// Set the vertex buffer to active in the input assembler so it can be rendered.
	device->IASetVertexBuffers(0, 1, &sentence->vertexBuffer, &stride, &offset);

	// Set the index buffer to active in the input assembler so it can be rendered.
	device->IASetIndexBuffer(sentence->indexBuffer, DXGI_FORMAT_R32_UINT, 0);

	// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
	device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

Get the color this string should be rendered from the sentence structure.

	// Create a pixel color vector with the input sentence color.
	pixelColor = D3DXVECTOR4(sentence->red, sentence->green, sentence->blue, 1.0f);

	// Render the text using the font shader.
	m_FontShader->Render(device, sentence->indexCount, worldMatrix, m_baseViewMatrix, orthoMatrix, m_Font->GetTexture(), pixelColor);

	return;
}

Graphicsclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"

We now include the new TextClass header file.

#include "textclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	void Frame();
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;

There is a new private variable for the TextClass object.

	TextClass* m_Text;
};

#endif

Graphicsclass.cpp

We will just look at the functions that have changed since the previous tutorial.

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;

Initialize the new TextClass object to null in the class constructor.

	m_Text = 0;
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;
	D3DXMATRIX baseViewMatrix;

		
	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

We create a new view matrix from the camera object for the TextClass to use. It will always use this view matrix so that the text is always drawn in the same location on the screen.

	// Initialize a base view matrix with the camera for 2D user interface rendering.
	m_Camera->SetPosition(0.0f, 0.0f, -1.0f);
	m_Camera->Render();
	m_Camera->GetViewMatrix(baseViewMatrix);

Here we create and initialize the new TextClass object.

	// Create the text object.
	m_Text = new TextClass;
	if(!m_Text)
	{
		return false;
	}

	// Initialize the text object.
	result = m_Text->Initialize(m_D3D->GetDevice(), hwnd, screenWidth, screenHeight, baseViewMatrix);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the text object.", L"Error", MB_OK);
		return false;
	}

	return true;
}


void GraphicsClass::Shutdown()
{

Here we release the TextClass object.

	// Release the text object.
	if(m_Text)
	{
		m_Text->Shutdown();
		delete m_Text;
		m_Text = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the Direct3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}


bool GraphicsClass::Render()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;


	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, projection, and ortho matrices from the camera and d3d objects.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);
	m_D3D->GetOrthoMatrix(orthoMatrix);

	// Turn off the Z buffer to begin all 2D rendering.
	m_D3D->TurnZBufferOff();

We call the text object to render all its sentences to the screen here. And just like with 2D images we disable the Z buffer before drawing and then enable it again after all the 2D has been drawn.

	// Render the text strings.
	m_Text->Render(m_D3D->GetDevice(), worldMatrix, orthoMatrix);

	// Turn the Z buffer back on now that all 2D rendering has completed.
	m_D3D->TurnZBufferOn();

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}

Summary

Now we are able to render colored text to any location of the screen.


To Do Exercises

1. Recompile the code and ensure you get a white "Hello" written to 100x100 on your screen as well as a yellow "Goodbye" beneath it.

2. Change the pixel color, location, and content of the sentences.

3. Create a third sentence structure and have it render also.

4. Comment out the SetBlendState(AlphaBlendingState, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xFFFFFFFF) in the font.fx file and set m_D3D->BeginScene(0.0f, 0.0f, 1.0f, 1.0f); in the GraphicsClass::Render function. This will show why blending is needed.

5. Add SetBlendState(AlphaBlendingState, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xFFFFFFFF) back into the font.fx file and keep m_D3D->BeginScene(0.0f, 0.0f, 1.0f, 1.0f); in the GraphicsClass::Render function to see the difference.


Source Code

Visual Studio 2008 Project: dx10tut12.zip

Source Only: dx10src12.zip

Executable Only: dx10exe12.zip

Back to Tutorial Index