www.pudn.com > promptshaderey.zip > D3DFont.cpp


// -------------------------------------------------------------------------- 
// Dingus project - a collection of subsystems for game/graphics applications 
// -------------------------------------------------------------------------- 
#include "stdafx.h" 
 
#include "D3DFont.h" 
#include "DXUtil.h" 
#include "../kernel/D3DDevice.h" 
 
using namespace dingus; 
 
 
//--------------------------------------------------------------------------- 
// Custom vertex types for rendering text 
 
#define MAX_NUM_VERTICES 50*6 
 
struct SFont2DVertex { D3DXVECTOR4 p; D3DCOLOR color; 	float tu, tv; }; 
struct SFont3DVertex { D3DXVECTOR3 p; D3DXVECTOR3 n;	float tu, tv; }; 
 
#define D3DFVF_FONT2DVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1) 
#define D3DFVF_FONT3DVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1) 
 
inline SFont2DVertex gInitFont2DVertex( const D3DXVECTOR4& p, D3DCOLOR color, float tu, float tv ) 
{ 
	SFont2DVertex v; 
	v.p = p;	 v.color = color;	v.tu = tu;	 v.tv = tv; 
	return v; 
} 
 
inline SFont3DVertex gInitFont3DVertex( const D3DXVECTOR3& p, const D3DXVECTOR3& n, float tu, float tv ) 
{ 
	SFont3DVertex v; 
	v.p = p;	 v.n = n;	v.tu = tu;	 v.tv = tv; 
	return v; 
} 
 
 
 
//--------------------------------------------------------------------------- 
// CD3DFont() 
//--------------------------------------------------------------------------- 
 
 
CD3DFont::CD3DFont( const TCHAR* fontName, int height, int flags ) 
:	mFontHeight(height), mFontFlags(flags), 
	mSpacing(0), 
	/*mDevice(NULL), */mTexture(NULL), mVB(NULL), 
	mBlockSaved(NULL), mBlockDrawText(NULL) 
{ 
	_tcsncpy( mFontName, fontName, sizeof(mFontName) / sizeof(TCHAR) ); 
	mFontName[sizeof(mFontName) / sizeof(TCHAR) - 1] = _T('\0'); 
} 
 
 
CD3DFont::~CD3DFont() 
{ 
	passivateDeviceObjects(); 
	deleteDeviceObjects(); 
} 
 
 
HRESULT CD3DFont::createDeviceObjects(/* IDirect3DDevice9& device */) 
{ 
	HRESULT hr; 
	 
	CD3DDevice& device = CD3DDevice::getInstance(); 
	// Keep a local copy of the device 
	//mDevice = &device; 
	 
	// Establish the font and texture size 
	mTextScale	= 1.0f; // Draw fonts into texture without scaling 
	 
	// Large fonts need larger textures 
	if( mFontHeight > 60 ) 
		mTexWidth = mTexHeight = 2048; 
	else if( mFontHeight > 30 ) 
		mTexWidth = mTexHeight = 1024; 
	else if( mFontHeight > 15 ) 
		mTexWidth = mTexHeight = 512; 
	else 
		mTexWidth  = mTexHeight = 256; 
	 
	// If requested texture is too big, use a smaller texture and smaller font, 
	// and scale up when rendering. 
	//D3DCAPS9 caps; 
	//mDevice->GetDeviceCaps( &caps ); 
	 
	if( mTexWidth > device.getCaps().MaxTextureWidth ) { 
		mTextScale = (float)device.getCaps().MaxTextureWidth / (float)mTexWidth; 
		mTexWidth = mTexHeight = device.getCaps().MaxTextureWidth; 
	} 
	 
	// Create a new texture for the font 
	hr = device.getDevice().CreateTexture( 
		mTexWidth, mTexHeight, 1, 0, D3DFMT_A4R4G4B4, 
		D3DPOOL_MANAGED, &mTexture, NULL ); 
	if( FAILED(hr) ) 
		return hr; 
	 
	// Prepare to create a bitmap 
	DWORD*		pBitmapBits; 
	BITMAPINFO	bmi; 
	ZeroMemory( &bmi.bmiHeader,  sizeof(BITMAPINFOHEADER) ); 
	bmi.bmiHeader.biSize		= sizeof(BITMAPINFOHEADER); 
	bmi.bmiHeader.biWidth		=  (int)mTexWidth; 
	bmi.bmiHeader.biHeight		= -(int)mTexHeight; 
	bmi.bmiHeader.biPlanes		= 1; 
	bmi.bmiHeader.biCompression = BI_RGB; 
	bmi.bmiHeader.biBitCount	= 32; 
	 
	// Create a DC and a bitmap for the font 
	HDC 	hDC 	  = CreateCompatibleDC( NULL ); 
	HBITMAP hbmBitmap = CreateDIBSection( hDC, &bmi, DIB_RGB_COLORS, (void**)&pBitmapBits, NULL, 0 ); 
	SetMapMode( hDC, MM_TEXT ); 
	 
	// Create a font.  By specifying ANTIALIASED_QUALITY, we might get an 
	// antialiased font, but this is not guaranteed. 
	INT nHeight    = -MulDiv( mFontHeight, (INT)(GetDeviceCaps(hDC, LOGPIXELSY) * mTextScale), 72 ); 
	DWORD dwBold   = (mFontFlags&BOLD)	 ? FW_BOLD : FW_NORMAL; 
	DWORD dwItalic = (mFontFlags&ITALIC) ? TRUE    : FALSE; 
	HFONT hFont    = CreateFont( 
		nHeight, 0, 0, 0, dwBold, dwItalic, 
		FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 
		CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, 
		VARIABLE_PITCH, mFontName ); 
	if( NULL==hFont ) 
		return E_FAIL; 
	 
	HGDIOBJ hbmOld = SelectObject( hDC, hbmBitmap ); 
	HGDIOBJ hFontOld = SelectObject( hDC, hFont ); 
	 
	// Set text properties 
	SetTextColor( hDC, RGB(255,255,255) ); 
	SetBkColor(   hDC, 0x00000000 ); 
	SetTextAlign( hDC, TA_TOP ); 
	 
	// Loop through all printable character and output them to the bitmap.. 
	// Meanwhile, keep track of the corresponding tex coords for each character. 
	DWORD x = 0; 
	DWORD y = 0; 
	TCHAR str[2] = _T("x"); 
	SIZE size; 
	 
	// Calculate the spacing between characters based on line height 
	GetTextExtentPoint32( hDC, TEXT(" "), 1, &size ); 
	x = mSpacing = (DWORD) ceil(size.cy * 0.3f); 
	 
	for( TCHAR c=32; c<127; c++ ) { 
		str[0] = c; 
		GetTextExtentPoint32( hDC, str, 1, &size ); 
		 
		if( (DWORD)(x + size.cx + mSpacing) > mTexWidth ) { 
			x  = mSpacing; 
			y += size.cy+1; 
		} 
		 
		ExtTextOut( hDC, x+0, y+0, ETO_OPAQUE, NULL, str, 1, NULL ); 
		 
		mTexCoords[c-32][0] = ((float)(x + 0       - mSpacing))/mTexWidth; 
		mTexCoords[c-32][1] = ((float)(y + 0       + 0       ))/mTexHeight; 
		mTexCoords[c-32][2] = ((float)(x + size.cx + mSpacing))/mTexWidth; 
		mTexCoords[c-32][3] = ((float)(y + size.cy + 0       ))/mTexHeight; 
		 
		x += size.cx + (2 * mSpacing); 
	} 
	 
	// Lock the surface and write the alpha values for the set pixels 
	D3DLOCKED_RECT d3dlr; 
	mTexture->LockRect( 0, &d3dlr, 0, 0 ); 
	BYTE* pDstRow = (BYTE*)d3dlr.pBits; 
	WORD* pDst16; 
	BYTE bAlpha; // 4-bit measure of pixel intensity 
	 
	for( y=0; y < mTexHeight; y++ ) { 
		pDst16 = (WORD*)pDstRow; 
		for( x=0; x < mTexWidth; x++ ) { 
			bAlpha = (BYTE)((pBitmapBits[mTexWidth*y + x] & 0xff) >> 4); 
			if (bAlpha > 0) { 
				*pDst16++ = (WORD) ((bAlpha << 12) | 0x0fff); 
			} else { 
				*pDst16++ = 0x0000; 
			} 
		} 
		pDstRow += d3dlr.Pitch; 
	} 
	 
	// Done updating texture, so clean up used objects 
	mTexture->UnlockRect(0); 
	SelectObject( hDC, hbmOld ); 
	SelectObject( hDC, hFontOld ); 
	DeleteObject( hbmBitmap ); 
	DeleteObject( hFont ); 
	DeleteDC( hDC ); 
	 
	return S_OK; 
} 
 
 
 
HRESULT CD3DFont::activateDeviceObjects() 
{ 
	HRESULT hr; 
	 
	CD3DDevice& device = CD3DDevice::getInstance(); 
	IDirect3DDevice9& dx = device.getDevice(); 
 
	// Create vertex buffer for the letters 
	int vertexSize = sizeof(SFont2DVertex) > sizeof(SFont3DVertex ) ? sizeof(SFont2DVertex) : sizeof(SFont3DVertex); 
	if( FAILED( hr = dx.CreateVertexBuffer( 
		MAX_NUM_VERTICES * vertexSize, 
		D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0, 
		D3DPOOL_DEFAULT, &mVB, NULL ) ) ) 
	{ 
		return hr; 
	} 
	 
	// Create the state blocks for rendering text 
	for( UINT which=0; which<2; which++ ) { 
		dx.BeginStateBlock(); 
		dx.SetTexture( 0, mTexture ); 
		 
		if( ZENABLE & mFontFlags ) 
			dx.SetRenderState( D3DRS_ZENABLE, TRUE ); 
		else 
			dx.SetRenderState( D3DRS_ZENABLE, FALSE ); 
		 
		dx.SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); 
		dx.SetRenderState( D3DRS_SRCBLEND,   D3DBLEND_SRCALPHA ); 
		dx.SetRenderState( D3DRS_DESTBLEND,  D3DBLEND_INVSRCALPHA ); 
		dx.SetRenderState( D3DRS_ALPHATESTENABLE,  TRUE ); 
		dx.SetRenderState( D3DRS_ALPHAREF,		 0x08 ); 
		dx.SetRenderState( D3DRS_ALPHAFUNC,  D3DCMP_GREATEREQUAL ); 
		dx.SetRenderState( D3DRS_FILLMODE,   D3DFILL_SOLID ); 
		dx.SetRenderState( D3DRS_CULLMODE,   D3DCULL_CCW ); 
		dx.SetRenderState( D3DRS_STENCILENABLE,	 FALSE ); 
		dx.SetRenderState( D3DRS_CLIPPING,		 TRUE ); 
		dx.SetRenderState( D3DRS_CLIPPLANEENABLE,  FALSE ); 
		dx.SetRenderState( D3DRS_VERTEXBLEND, 	 D3DVBF_DISABLE ); 
		dx.SetRenderState( D3DRS_INDEXEDVERTEXBLENDENABLE, FALSE ); 
		dx.SetRenderState( D3DRS_FOGENABLE,		 FALSE ); 
		dx.SetRenderState( D3DRS_COLORWRITEENABLE, 
			D3DCOLORWRITEENABLE_RED  | D3DCOLORWRITEENABLE_GREEN | 
			D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA ); 
		dx.SetTextureStageState( 0, D3DTSS_COLOROP,	D3DTOP_MODULATE ); 
		dx.SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); 
		dx.SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); 
		dx.SetTextureStageState( 0, D3DTSS_ALPHAOP,	D3DTOP_MODULATE ); 
		dx.SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); 
		dx.SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); 
		dx.SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0 ); 
		dx.SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE ); 
		dx.SetTextureStageState( 1, D3DTSS_COLOROP,	D3DTOP_DISABLE ); 
		dx.SetTextureStageState( 1, D3DTSS_ALPHAOP,	D3DTOP_DISABLE ); 
		dx.SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_POINT ); 
		dx.SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_POINT ); 
		dx.SetSamplerState( 0, D3DSAMP_MIPFILTER, D3DTEXF_NONE ); 
		 
		if( which==0 ) 
			dx.EndStateBlock( &mBlockSaved ); 
		else 
			dx.EndStateBlock( &mBlockDrawText ); 
	} 
	 
	return S_OK; 
} 
 
 
HRESULT CD3DFont::passivateDeviceObjects() 
{ 
	safeRelease( mVB ); 
	safeRelease( mBlockSaved ); 
	safeRelease( mBlockDrawText ); 
	 
	return S_OK; 
} 
 
 
HRESULT CD3DFont::deleteDeviceObjects() 
{ 
	safeRelease( mTexture ); 
	//mDevice = NULL; 
	 
	return S_OK; 
} 
 
 
 
/* 
HRESULT CD3DFont::getTextExtent( const TCHAR* str, SIZE& resSize ) const 
{ 
	if( !str ) 
		return E_FAIL; 
	 
	float fRowWidth  = 0.0f; 
	float fRowHeight = (mTexCoords[0][3]-mTexCoords[0][1])*mTexHeight; 
	float fWidth	 = 0.0f; 
	float fHeight	 = fRowHeight; 
	 
	while( *str ) { 
		TCHAR c = *str++; 
		 
		if( c == _T('\n') ) { 
			fRowWidth = 0.0f; 
			fHeight  += fRowHeight; 
		} 
		 
		if( (c-32) < 0 || (c-32) >= 128-32 ) 
			continue; 
		 
		float tx1 = mTexCoords[c-32][0]; 
		float tx2 = mTexCoords[c-32][2]; 
		 
		fRowWidth += (tx2-tx1)*mTexWidth - 2*mSpacing; 
		 
		if( fRowWidth > fWidth ) 
			fWidth = fRowWidth; 
	} 
	 
	resSize.cx = (int)fWidth; 
	resSize.cy = (int)fHeight; 
	 
	return S_OK; 
} 
*/ 
 
 
/** 
 *  Draws scaled 2D text. Note that x and y are in viewport coordinates 
 *  (ranging from -1 to +1). xScale and yScale are the size fraction  
 *  relative to the entire viewport. For example, a xScale of 0.25 is 
 *  1/8th of the screen width. This allows you to output text at a fixed 
 *  fraction of the viewport, even if the screen or window size changes. 
 */ 
/* 
HRESULT CD3DFont::drawTextScaled( 
	float x, float y, float z, 
	float xScale, float yScale, D3DCOLOR color, 
	const TCHAR* str, int flags ) 
{ 
	if( !mDevice ) 
		return E_FAIL; 
	 
	// Set up renderstate 
	mBlockSaved->Capture(); 
	mBlockDrawText->Apply(); 
	mDevice->SetFVF( D3DFVF_FONT2DVERTEX ); 
	mDevice->SetPixelShader( NULL ); 
	mDevice->SetStreamSource( 0, mVB, 0, sizeof(SFont2DVertex) ); 
	 
	// Set filter states 
	if( flags & FILTERED ) { 
		mDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); 
		mDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); 
	} 
	 
	D3DVIEWPORT9 vp; 
	mDevice->GetViewport( &vp ); 
	float lineHeight = ( mTexCoords[0][3] - mTexCoords[0][1] ) * mTexHeight; 
	 
	// Center the text block in the viewport 
	if( flags & CENTERED_X ) { 
		const TCHAR* strTmp = str; 
		float xFinal = 0.0f; 
		 
		while( *strTmp ) { 
			TCHAR c = *strTmp++; 
			 
			if( c == _T('\n') ) 
				break;	// Isn't supported.   
			if( (c-32) < 0 || (c-32) >= 128-32 ) 
				continue; 
			 
			float tx1 = mTexCoords[c-32][0]; 
			float tx2 = mTexCoords[c-32][2]; 
			 
			float w = (tx2-tx1)*mTexWidth; 
			 
			w *= (xScale*vp.Height)/lineHeight; 
			 
			xFinal += w - (2 * mSpacing) * (xScale*vp.Height)/lineHeight; 
		} 
		 
		x = -xFinal/vp.Width; 
	} 
	if( flags & CENTERED_Y ) { 
		y = -lineHeight/vp.Height; 
	} 
	 
	float sx  = (x+1.0f)*vp.Width/2; 
	float sy  = (y+1.0f)*vp.Height/2; 
	float sz  = z; 
	float rhw = 1.0f; 
	 
	// Adjust for character spacing 
	sx -= mSpacing * (xScale*vp.Height)/lineHeight; 
	float startX = sx; 
	 
	// Fill vertex buffer 
	SFont2DVertex* pVertices; 
	int numTris = 0; 
	mVB->Lock( 0, 0, (void**)&pVertices, D3DLOCK_DISCARD ); 
	 
	while( *str ) { 
		TCHAR c = *str++; 
		 
		if( c == _T('\n') ) { 
			sx	= startX; 
			sy += yScale*vp.Height; 
		} 
		 
		if( (c-32) < 0 || (c-32) >= 128-32 ) 
			continue; 
		 
		float tx1 = mTexCoords[c-32][0]; 
		float ty1 = mTexCoords[c-32][1]; 
		float tx2 = mTexCoords[c-32][2]; 
		float ty2 = mTexCoords[c-32][3]; 
		 
		float w = (tx2-tx1)*mTexWidth; 
		float h = (ty2-ty1)*mTexHeight; 
		 
		w *= (xScale*vp.Height)/lineHeight; 
		h *= (yScale*vp.Height)/lineHeight; 
		 
		if( c != _T(' ') ) { 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+0-0.5f,sy+h-0.5f,sz,rhw), color, tx1, ty2 ); 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+0-0.5f,sy+0-0.5f,sz,rhw), color, tx1, ty1 ); 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+w-0.5f,sy+h-0.5f,sz,rhw), color, tx2, ty2 ); 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+w-0.5f,sy+0-0.5f,sz,rhw), color, tx2, ty1 ); 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+w-0.5f,sy+h-0.5f,sz,rhw), color, tx2, ty2 ); 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+0-0.5f,sy+0-0.5f,sz,rhw), color, tx1, ty1 ); 
			numTris += 2; 
			 
			if( numTris*3 > (MAX_NUM_VERTICES-6) ) { 
				// Unlock, render, and relock the vertex buffer 
				mVB->Unlock(); 
				mDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, numTris ); 
				mVB->Lock( 0, 0, (void**)&pVertices, D3DLOCK_DISCARD ); 
				numTris = 0L; 
			} 
		} 
		 
		sx += w - (2 * mSpacing) * (xScale*vp.Height)/lineHeight; 
	} 
	 
	// Unlock and render the vertex buffer 
	mVB->Unlock(); 
	if( numTris > 0 ) 
		mDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, numTris ); 
	 
	// Restore the modified renderstates 
	mBlockSaved->Apply(); 
	 
	return S_OK; 
} 
*/ 
 
/** Draws 2D text. Note that sx and sy are in pixels. */ 
HRESULT CD3DFont::drawText( float sx, float sy, D3DCOLOR color, const TCHAR* str, int flags ) 
{ 
	CD3DDevice& device = CD3DDevice::getInstance(); 
	if( !device.isDevice() ) 
		return E_FAIL; 
	 
	// Setup renderstate 
	mBlockSaved->Capture(); 
	mBlockDrawText->Apply(); 
	device.setDeclarationFVF( D3DFVF_FONT2DVERTEX ); 
	device.getDevice().SetPixelShader( NULL ); 
	device.getDevice().SetStreamSource( 0, mVB, 0, sizeof(SFont2DVertex) ); 
	 
	// Set filter states 
	if( flags & FILTERED ) { 
		device.getDevice().SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); 
		device.getDevice().SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); 
	} 
	 
	// Center the text block in the viewport 
	if( flags & CENTERED_X ) { 
		D3DVIEWPORT9 vp; 
		device.getDevice().GetViewport( &vp ); 
		const TCHAR* strTmp = str; 
		float xFinal = 0.0f; 
		 
		while( *strTmp ) { 
			TCHAR c = *strTmp++; 
			 
			if( c == _T('\n') ) 
				break;	// Isn't supported.   
			if( (c-32) < 0 || (c-32) >= 128-32 ) 
				continue; 
			 
			float tx1 = mTexCoords[c-32][0]; 
			float tx2 = mTexCoords[c-32][2]; 
			 
			float w = (tx2-tx1) *  mTexWidth / mTextScale; 
			 
			xFinal += w - (2 * mSpacing); 
		} 
		 
		sx = (vp.Width-xFinal)/2.0f; 
	} 
	if( flags & CENTERED_Y ) { 
		D3DVIEWPORT9 vp; 
		device.getDevice().GetViewport( &vp ); 
		float lineHeight = ((mTexCoords[0][3]-mTexCoords[0][1])*mTexHeight); 
		sy = (vp.Height-lineHeight)/2; 
	} 
	 
	// Adjust for character spacing 
	sx -= mSpacing; 
	float startX = sx; 
	 
	// Fill vertex buffer 
	SFont2DVertex* pVertices = NULL; 
	int numTris = 0; 
	mVB->Lock( 0, 0, (void**)&pVertices, D3DLOCK_DISCARD ); 
	 
	while( *str ) { 
		TCHAR c = *str++; 
		 
		if( c == _T('\n') ) { 
			sx = startX; 
			sy += (mTexCoords[0][3]-mTexCoords[0][1])*mTexHeight; 
		} 
		 
		if( (c-32) < 0 || (c-32) >= 128-32 ) 
			continue; 
		 
		float tx1 = mTexCoords[c-32][0]; 
		float ty1 = mTexCoords[c-32][1]; 
		float tx2 = mTexCoords[c-32][2]; 
		float ty2 = mTexCoords[c-32][3]; 
		 
		float w = (tx2-tx1) *  mTexWidth / mTextScale; 
		float h = (ty2-ty1) * mTexHeight / mTextScale; 
		 
		if( c != _T(' ') ) { 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+0-0.5f,sy+h-0.5f,0.9f,1.0f), color, tx1, ty2 ); 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+0-0.5f,sy+0-0.5f,0.9f,1.0f), color, tx1, ty1 ); 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+w-0.5f,sy+h-0.5f,0.9f,1.0f), color, tx2, ty2 ); 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+w-0.5f,sy+0-0.5f,0.9f,1.0f), color, tx2, ty1 ); 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+w-0.5f,sy+h-0.5f,0.9f,1.0f), color, tx2, ty2 ); 
			*pVertices++ = gInitFont2DVertex( D3DXVECTOR4(sx+0-0.5f,sy+0-0.5f,0.9f,1.0f), color, tx1, ty1 ); 
			numTris += 2; 
			 
			if( numTris*3 > (MAX_NUM_VERTICES-6) ) { 
				// Unlock, render, and relock the vertex buffer 
				mVB->Unlock(); 
				device.getDevice().DrawPrimitive( D3DPT_TRIANGLELIST, 0, numTris ); 
				pVertices = NULL; 
				mVB->Lock( 0, 0, (void**)&pVertices, D3DLOCK_DISCARD ); 
				numTris = 0L; 
			} 
		} 
		 
		sx += w - (2 * mSpacing); 
	} 
	 
	// Unlock and render the vertex buffer 
	mVB->Unlock(); 
	if( numTris > 0 ) 
		device.getDevice().DrawPrimitive( D3DPT_TRIANGLELIST, 0, numTris ); 
	 
	// Restore the modified renderstates 
	mBlockSaved->Apply(); 
	 
	return S_OK; 
} 
 
 
/** Renders 3D text. */ 
/* 
HRESULT CD3DFont::render3DText( const TCHAR* str, int flags ) 
{ 
	if( mDevice == NULL ) 
		return E_FAIL; 
	 
	// Setup renderstate 
	mBlockSaved->Capture(); 
	mBlockDrawText->Apply(); 
	mDevice->SetFVF( D3DFVF_FONT3DVERTEX ); 
	mDevice->SetPixelShader( NULL ); 
	mDevice->SetStreamSource( 0, mVB, 0, sizeof(SFont3DVertex) ); 
	 
	// Set filter states 
	if( flags & FILTERED ) { 
		mDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); 
		mDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); 
	} 
	 
	// Position for each text element 
	float x = 0.0f; 
	float y = 0.0f; 
	 
	// Center the text block at the origin (not the viewport) 
	if( flags & CENTERED_X ) { 
		SIZE sz; 
		getTextExtent( str, sz ); 
		x = -(((float)sz.cx)/10.0f)/2.0f; 
	} 
	if( flags & CENTERED_Y ) { 
		SIZE sz; 
		getTextExtent( str, sz ); 
		y = -(((float)sz.cy)/10.0f)/2.0f; 
	} 
	 
	// Turn off culling for two-sided text 
	if( flags & TWO_SIDED ) 
		mDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); 
	 
	// Adjust for character spacing 
	x -= mSpacing / 10.0f; 
	float startX = x; 
	TCHAR c; 
	 
	// Fill vertex buffer 
	SFont3DVertex* pVertices; 
	int numTris = 0; 
	mVB->Lock( 0, 0, (void**)&pVertices, D3DLOCK_DISCARD ); 
	 
	while( (c = *str++) != 0 ) { 
		if( c == '\n' ) { 
			x = startX; 
			y -= (mTexCoords[0][3]-mTexCoords[0][1])*mTexHeight/10.0f; 
		} 
		 
		if( (c-32) < 0 || (c-32) >= 128-32 ) 
			continue; 
		 
		float tx1 = mTexCoords[c-32][0]; 
		float ty1 = mTexCoords[c-32][1]; 
		float tx2 = mTexCoords[c-32][2]; 
		float ty2 = mTexCoords[c-32][3]; 
		 
		float w = (tx2-tx1) * mTexWidth  / ( 10.0f * mTextScale ); 
		float h = (ty2-ty1) * mTexHeight / ( 10.0f * mTextScale ); 
		 
		if( c != _T(' ') ) { 
			*pVertices++ = gInitFont3DVertex( D3DXVECTOR3(x+0,y+0,0), D3DXVECTOR3(0,0,-1), tx1, ty2 ); 
			*pVertices++ = gInitFont3DVertex( D3DXVECTOR3(x+0,y+h,0), D3DXVECTOR3(0,0,-1), tx1, ty1 ); 
			*pVertices++ = gInitFont3DVertex( D3DXVECTOR3(x+w,y+0,0), D3DXVECTOR3(0,0,-1), tx2, ty2 ); 
			*pVertices++ = gInitFont3DVertex( D3DXVECTOR3(x+w,y+h,0), D3DXVECTOR3(0,0,-1), tx2, ty1 ); 
			*pVertices++ = gInitFont3DVertex( D3DXVECTOR3(x+w,y+0,0), D3DXVECTOR3(0,0,-1), tx2, ty2 ); 
			*pVertices++ = gInitFont3DVertex( D3DXVECTOR3(x+0,y+h,0), D3DXVECTOR3(0,0,-1), tx1, ty1 ); 
			numTris += 2; 
			 
			if( numTris*3 > (MAX_NUM_VERTICES-6) ) { 
				// Unlock, render, and relock the vertex buffer 
				mVB->Unlock(); 
				mDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, numTris ); 
				mVB->Lock( 0, 0, (void**)&pVertices, D3DLOCK_DISCARD ); 
				numTris = 0; 
			} 
		} 
		 
		x += w - (2 * mSpacing) / 10.0f; 
	} 
	 
	// Unlock and render the vertex buffer 
	mVB->Unlock(); 
	if( numTris > 0 ) 
		mDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, numTris ); 
	 
	// Restore the modified renderstates 
	mBlockSaved->Apply(); 
	 
	return S_OK; 
} 
*/