Font engine
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]
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.
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
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.
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.
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:
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
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.
-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.
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
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
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement