Jump to content
  • Advertisement
Sign in to follow this  
dev578

Font engine

This topic is 4817 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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; // Pixels
const unsigned int textureCharacterWidth = 16;
const float precisionCharWidth = 1/(float)textureCharacterWidth;
const float tabGap = 3; //num characters

using 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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!