Font engine

Started by
5 comments, last by matthughson 18 years, 10 months ago
What I am trying to do is make a font engine that can load characters from some kind of bitmap, and somehow display it to the screen. I would like to do this in a semi professional/optimized way. By using DirectX 9.0c, what is the fastest way to get the characters from the bitmap, and render it to the screen? I am doing this so that I can use customized fonts, much like in a professional game. To rephrase the question, what is the fastest way to render a portion of a bitmap file to the screen using DirectX 9.0c? Any help is appreciated, -Dev578 [Edited by - dev578 on June 6, 2005 7:22:41 PM]
Advertisement
My guess is that you'd just put all the characters on a single texture, create a file with data about where each of them is located, and get a set of texture coordinates that contain it. You could then put them onto screen-aligned quads.
--------------Trans2D - 2D library for C# / Managed DirectX
You need a single texture to put on a quad correct? Let me see if I understand this correctly, here is what I would do step by step:

1. Load the entire bitmap onto an IDirect3DSurface9 by using D3DXLoadSurfaceFromFile
2. Use D3DXLoadSurfaceFromSurface with the appropriate source and destination rectangles to copy each character from the font surface to another surface that will store only the needed text

Now, to get from the surface to a texture, I'm guessing:

3. Lock the surface with the needed text on it, lock the texture, and copy bit by bit the data of the surface onto the texture; then render the texture to the screen

Locking and copying doesn't seem like it would be efficient at all, but after a bit of research, I don't see another way. How can I do this better, because it really doesn't look too efficient?

Any help is appreciated,



-Dev578
That is an easy way of getting up to 100 characters onto the screen.

However, when the amount starts becoming much (integrated text chat; name tags on 100 characters; ...) you probably want to write entire lines of text to an offscreen bitmap using GDI, FreeType or something similar, and upload that as part of a texture, and map the entire drawn text to a single pair of triangles.

You only re-generate the texture image when you change some of the data, of course, and you probably only update the actual dirty area, to save on bandwidth when text really does change.
enum Bool { True, False, FileNotFound };
What i have done in the past is create a texture 256x256 with 16x16 characters for the entire ascii table. Search "Bitmap Font Builder" with Google to find the software to create the textures. Next create quads for the letters and alter the UV coordinates to refllect the character from the ascii bitmap. Oh and load the ascii bitmap as a normal texture, make sure it has a alpha channel sorted so the BG can be transparent.

Here is a font engine class i wrote lately:

#ifndef FONT_ENGINE_H#define FONT_ENGINE_H#include <d3dx9.h>#include <vector>#include <map>#include <string>#include <memory>#include "font_objectmanager.h"#include "font_object.h"#include "font_def.h"#include "DynamicVertexBuffer.h"const unsigned int		fontTextureWidth			=	256;		// Pixelsconst unsigned int		textureCharacterWidth		=	16;const float				precisionCharWidth			=	1/(float)textureCharacterWidth;const float				tabGap						=	3; //num charactersusing std::string;typedef	std::map< string, LPDIRECT3DTEXTURE9 >	textureMap;struct TextUnit{	std::vector< Coord_2D >*	pVertices;	font_object*				pFontObject;};class FontEngine{public:	FontEngine( LPDIRECT3DDEVICE9& renderDevice );	~FontEngine();	void Init();	void Frame();	void AddFont( string& name, LPDIRECT3DTEXTURE9 texture, string& widths );	void AddFont( string& name, string &path, string& widths );	void Write( string& text, string& fontName, float x, float y, float width, float height );	private:	// This will be the font object manager	std::auto_ptr< fontobjectmanager >				pFontManager;	/* Vertex Buffer for storing all of the vertices */	DynamicVertexBuffer< Coord_2D >*				pVertexBuffer;	LPDIRECT3DDEVICE9& renderDevice;	void BuildVerticesForText( string& text, string& fontName, float x, float y, float width, float height, std::vector< Coord_2D >& vertVec );	void GetUVFromFontTexture( char	character, float& u, float& v );			void Cleanup();};#endif///CPP#include "font.h"FontEngine::FontEngine( LPDIRECT3DDEVICE9& renderDevice ):renderDevice( renderDevice ){	pFontManager.reset( new fontobjectmanager( renderDevice ) );	pVertexBuffer = NULL;	Init();}FontEngine::~FontEngine(){	Cleanup();}void FontEngine::Init(){	DYNAMIC_VERTEXBUFFER_PROPS vbProps = { renderDevice, D3DFVF_TRANSTEXT, D3DPOOL_DEFAULT, D3DUSAGE_DYNAMIC };	pVertexBuffer = new DynamicVertexBuffer< Coord_2D >(25, 10000, &vbProps );}void FontEngine::Frame(){}void FontEngine::Cleanup(){	if ( pVertexBuffer )	{		delete pVertexBuffer;	}}void FontEngine::AddFont( string& name, string& path, string& widths ){	pFontManager->NewFontObject( path, name, widths );}void FontEngine::AddFont( string& name, LPDIRECT3DTEXTURE9 texture, string& widths ){	pFontManager->NewFontObject( texture, name, widths );}void FontEngine::Write( string& text, string& fontName, float x, float y, float width, float height ){	std::vector< Coord_2D >	vertices;	font_object* pFo;	BuildVerticesForText( text, fontName, x, y, width, height, vertices );	/* Add the vertices to the vertex buffer */	(*pVertexBuffer) += vertices;	/* Render the font */	pFo = pFontManager->FetchFontObject( fontName );	// Set some alpha blending render states	renderDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true );	renderDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );	renderDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );	renderDevice->SetTexture( 0, pFo->GetTexture() );	renderDevice->SetStreamSource( 0, &(*pVertexBuffer), 0, sizeof( Coord_2D ) );	renderDevice->SetFVF( D3DFVF_TRANSTEXT );	renderDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, pVertexBuffer->Size() / 3 );}/*------------------------------------------------------------------------------------------------------------Calculates and defines a quad representing the incoming characters. Left alignment is used.*/void FontEngine::BuildVerticesForText( string& text, string& fontName, float x, float y, float width, float height, std::vector< Coord_2D >& vertVec ){	unsigned int	lenText				=	( unsigned int ) text.length();	// Length of the text	unsigned int	letterIterator		=	0;				// Iterator to index through the text	float			currentXOffset		=	x;	float			currentYOffset		=	y;	float			charTopLeftU		=	0.0f;	float			charTopLeftV		=	0.0f;	float			currCharWidth;	float			precisionCharWidth = 0;	float			precisionCharHeight = 0.0625;	font_object*	pFo;									// pointer to a font object    Coord_2D		tempVertex;					for (	letterIterator = 0;			letterIterator < lenText;			letterIterator++ )	{		// If the '\n' character is detected then drop down and back a line		if ( text[ letterIterator ] == '\n' )		{			// Now drop down and return back to the satart of the line			currentYOffset += height;			currentXOffset = x;			// increment the index past the '\n' so the '\n' isnt rendered			letterIterator++;		}		else if (text[ letterIterator ] == '\t')		{			currentXOffset += ( tabGap * width );			letterIterator++;		}		// Fetch the UV coordinates for the top left of the letter in the font texture		GetUVFromFontTexture( text[letterIterator], charTopLeftU, charTopLeftV );		// Calculate the x,y,u and v allowing for the different character widths		// First retrieve the font object		pFo = pFontManager->FetchFontObject( fontName );		// Fetch the width of this character		currCharWidth = (float) pFo->GetWidthByAsciiIndex( (unsigned int)text[ letterIterator ] );		// Since everything in the font texture is right aligned, we can just calculate the indentation that is the		// right hand side of the character		float tuRightIndent = currCharWidth/fontTextureWidth;		char  coRightIndent = currCharWidth;		// Create the vertices one by one and add them to the vector		tempVertex = Coord_2D( currentXOffset, currentYOffset, 0.0f, 1.0f, charTopLeftU, charTopLeftV );		vertVec.push_back( tempVertex );		tempVertex = Coord_2D( currentXOffset+coRightIndent, currentYOffset+height, 0.0f, 1.0f, charTopLeftU+tuRightIndent, charTopLeftV+precisionCharHeight );		vertVec.push_back( tempVertex );		tempVertex = Coord_2D( currentXOffset, currentYOffset+height, 0.0f, 1.0f, charTopLeftU, charTopLeftV+precisionCharHeight );		vertVec.push_back( tempVertex );		tempVertex = Coord_2D( currentXOffset, currentYOffset, 0.0f, 1.0f, charTopLeftU, charTopLeftV );		vertVec.push_back( tempVertex );		tempVertex = Coord_2D( currentXOffset+coRightIndent, currentYOffset, 0.0f, 1.0f, charTopLeftU+tuRightIndent, charTopLeftV );		vertVec.push_back( tempVertex );		tempVertex = Coord_2D( currentXOffset+coRightIndent, currentYOffset+height, 0.0f, 1.0f, charTopLeftU+tuRightIndent, charTopLeftV+precisionCharHeight );		vertVec.push_back( tempVertex );		// Increment the xOffset so by the width of the character		currentXOffset += currCharWidth;	}   	unsigned int test = 0;}void FontEngine::GetUVFromFontTexture( char	character, float& u, float& v ){	unsigned int asciiVal;	unsigned int xOffset, yOffset;	unsigned int charsInTextureWidth = fontTextureWidth/textureCharacterWidth;			asciiVal = (unsigned int) character;	// there has to be a better way of doing this^^	unsigned int remainderY = ( asciiVal % (fontTextureWidth/textureCharacterWidth) );	unsigned int wholeRows = (asciiVal - remainderY) / (fontTextureWidth/textureCharacterWidth);	yOffset =	wholeRows;	xOffset =	remainderY;	u = ((float)xOffset / (float)(fontTextureWidth/textureCharacterWidth));	v = ((float)yOffset / (float)(fontTextureWidth/textureCharacterWidth));}


There are pieces missing, but posting the whole thing wouldn't keep you thinking :P. It should get you thinking and help with some of the algorithms.

Umm, hope this helps and PM with any questions.

ace
ace
I was thinking more like this:

-Load the image as a texture
-For a given piece of text you want to draw, create a vertex buffer large enough to hold 6*max characters
-Whenever you draw text, fill the vertex buffer with screen-aligned quads with texture coordinates such that each one contains one character. You can find this coordinates by taking the x,y,width,height of the character on the image in pixels and dividing by its dimension to get the coordinates in texels.
-SetTexture(your font texture) and draw the vertex buffer.

It's worth it to load new data into the vertex buffer each time for the fact that you only have to make a single draw primitives call. hplus is right, though, this is only suitable for small amounts of text.
--------------Trans2D - 2D library for C# / Managed DirectX
You may also want to look into the ID3DXFont class. It allows you to use TrueType fonts, write out of memory. It looks very clean, and allows a lot of customization.

The main downfall, however, is the speed. You can get some pretty drastic drops in frame rate when a lot of text is on the screen, but depending on what you are doing, that may not be a problem.

Update:

NeXe seems to say that it's actually quite fast now! I was using it through DX8, so maybe things have gotten a lot better!

And here's a whole bunch of tutorial stuff on it: Tutorials.

Matt Hughson
__________________________________[ Website ] [ Résumé ] [ [email=contact[at]matthughson[dot]com]Contact[/email] ][ Have I been Helpful? Hook me up! ]

This topic is closed to new replies.

Advertisement