/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. * Copyright (C) 2010-2012 - Hans-Kristian Arntzen * Copyright (C) 2011-2012 - Daniel De Matteis * * Some code herein may be based on code found in BSNES. * * SSNES is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with SSNES. * If not, see . */ #include #include #include "xdk360_video.h" #include "xdk360_video_debugfonts.h" #include "../general.h" #define CALCFONTFILEHEADERSIZE(x) ( sizeof(unsigned long) + (sizeof(float)* 4) + sizeof(unsigned short) + (sizeof(wchar_t)*(x)) ) #define FONTFILEVERSION 5 typedef struct FontFileHeaderImage_t { unsigned long m_dwFileVersion; // Version of the font file (Must match FONTFILEVERSION) float m_fFontHeight; // Height of the font strike in pixels float m_fFontTopPadding; // Padding above the strike zone float m_fFontBottomPadding; // Padding below the strike zone float m_fFontYAdvance; // Number of pixels to move the cursor for a line feed unsigned short m_cMaxGlyph; // Number of font characters (Should be an odd number to maintain DWORD Alignment) wchar_t m_TranslatorTable[1]; // ASCII to Glyph lookup table, NOTE: It's m_cMaxGlyph+1 in size. // Entry 0 maps to the "Unknown" glyph. } FontFileHeaderImage_t; // Font strike array. Immediately follows the FontFileHeaderImage_t // structure image typedef struct FontFileStrikesImage_t { unsigned long m_dwNumGlyphs; // Size of font strike array (First entry is the unknown glyph) GLYPH_ATTR m_Glyphs[1]; // Array of font strike uv's etc... NOTE: It's m_dwNumGlyphs in size } FontFileStrikesImage_t; //-------------------------------------------------------------------------------------- // Vertex and pixel shaders for font rendering // Please note the removal of comment or dead lines... // They are commented out because the shader compiler has no use for them. //-------------------------------------------------------------------------------------- static const char g_strFontShader[] = "struct VS_IN\n" "{\n" "float2 Pos : POSITION;\n" "float2 Tex : TEXCOORD0;\n" "float4 ChannelSelector : TEXCOORD1;\n" "};\n" // "\n" "struct VS_OUT\n" "{\n" "float4 Position : POSITION;\n" "float4 Diffuse : COLOR0_center;\n" "float2 TexCoord0 : TEXCOORD0;\n" "float4 ChannelSelector : TEXCOORD1;\n" "};\n" // "\n" "uniform float4 Color : register(c1);\n" "uniform float2 TexScale : register(c2);\n" // "\n" "sampler FontTexture : register(s0);\n" // "\n" "VS_OUT FontVertexShader( VS_IN In )\n" "{\n" "VS_OUT Out;\n" "Out.Position.x = (In.Pos.x-0.5);\n" "Out.Position.y = (In.Pos.y-0.5);\n" "Out.Position.z = ( 0.0 );\n" "Out.Position.w = ( 1.0 );\n" "Out.Diffuse = Color;\n" "Out.TexCoord0.x = In.Tex.x * TexScale.x;\n" "Out.TexCoord0.y = In.Tex.y * TexScale.y;\n" "Out.ChannelSelector = In.ChannelSelector;\n" "return Out;\n" "}\n" // "\n" "float4 FontPixelShader( VS_OUT In ) : COLOR0\n" "{\n" // "// Fetch a texel from the font texture\n" "float4 FontTexel = tex2D( FontTexture, In.TexCoord0 );\n" // "\n" "if( dot( In.ChannelSelector, float4(1,1,1,1) ) )\n" "{\n" // "// Select the color from the channel\n" "float value = dot( FontTexel, In.ChannelSelector );\n" // "\n" // "// For white pixels, the high bit is 1 and the low\n" // "// bits are luminance, so r0.a will be > 0.5. For the\n" // "// RGB channel, we want to lop off the msb and shift\n" // "// the lower bits up one bit. This is simple to do\n" // "// with the _bx2 modifier. Since these pixels are\n" // "// opaque, we emit a 1 for the alpha channel (which\n" // "// is 0.5 x2 ).\n" // "\n" // "// For black pixels, the high bit is 0 and the low\n" // "// bits are alpha, so r0.a will be < 0.5. For the RGB\n" // "// channel, we emit zero. For the alpha channel, we\n" // "// just use the x2 modifier to scale up the low bits\n" // "// of the alpha.\n" "float4 Color;\n" "Color.rgb = ( value > 0.5f ? 2*value-1 : 0.0f );\n" "Color.a = 2 * ( value > 0.5f ? 1.0f : value );\n" // "\n" // "// Return the texture color modulated with the vertex\n" // "// color\n" "return Color * In.Diffuse;\n" "}\n" "else\n" "{\n" "return FontTexel * In.Diffuse;\n" "}\n" "}\n"; typedef struct AtgFont_Locals_t { D3DVertexDeclaration* m_pFontVertexDecl; // Shared vertex buffer D3DVertexShader* m_pFontVertexShader; // Created vertex shader D3DPixelShader* m_pFontPixelShader; // Created pixel shader } AtgFont_Locals_t; // All elements are defaulted to NULL static AtgFont_Locals_t s_AtgFontLocals; // Global static instance //-------------------------------------------------------------------------------------- // Name: CreateFontShaders() // Desc: Creates the global font shaders //-------------------------------------------------------------------------------------- HRESULT XdkFont::CreateFontShaders() { // // There are only two states the globals could be in, // Initialized, in which the ref count is increased, // Uninialized, in which the vertex/pixel shaders need to be // started up and a vertex array created. /// HRESULT hr; if (!s_AtgFontLocals.m_pFontVertexDecl) { // Use the do {} while(0); trick for a fake goto // It simplies tear down on error conditions. do { // Step #1, create my vertex array with 16 bytes per entry // Floats for the position, // shorts for the uvs // 32 bit packed ARGB 8:8:8:8 for color static const D3DVERTEXELEMENT9 decl[] = { { 0, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, { 0, 8, D3DDECLTYPE_USHORT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, { 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 }, D3DDECL_END() }; // Cache this global into a register xdk360_video_t *vid = (xdk360_video_t*)g_d3d; D3DDevice *pd3dDevice = vid->xdk360_render_device; hr = pd3dDevice->CreateVertexDeclaration( decl, &s_AtgFontLocals.m_pFontVertexDecl ); if (SUCCEEDED(hr)) { // Step #2, create my vertex shader ID3DXBuffer* pShaderCode; hr = D3DXCompileShader( g_strFontShader, sizeof(g_strFontShader)-1 , NULL, NULL, "FontVertexShader", "vs.2.0", 0,&pShaderCode, NULL, NULL ); if (SUCCEEDED(hr)) { hr = pd3dDevice->CreateVertexShader( ( unsigned long * )pShaderCode->GetBufferPointer(), &s_AtgFontLocals.m_pFontVertexShader ); // Release the compiled shader pShaderCode->Release(); if(SUCCEEDED(hr)) { // Step #3, create my pixel shader hr = D3DXCompileShader( g_strFontShader, sizeof(g_strFontShader)-1 , NULL, NULL, "FontPixelShader", "ps.2.0", 0,&pShaderCode, NULL, NULL ); if ( SUCCEEDED(hr)) { hr = pd3dDevice->CreatePixelShader( ( DWORD* )pShaderCode->GetBufferPointer(), &s_AtgFontLocals.m_pFontPixelShader ); // Release the compiled shader pShaderCode->Release(); if (SUCCEEDED(hr)) { hr = S_OK; break; // Skip the teardown code } } // If the code got to here, a fatal error has occured // and a clean shutdown needs to be performed. s_AtgFontLocals.m_pFontVertexShader->Release(); } // Ensure the pointer is NULL s_AtgFontLocals.m_pFontVertexShader = NULL; } s_AtgFontLocals.m_pFontVertexDecl->Release(); } // Ensure this pointer is NULL s_AtgFontLocals.m_pFontVertexDecl = NULL; }while(0); // Exit point for the break command. return hr; } else { // // Already initialized, so just add to the ref counts // s_AtgFontLocals.m_pFontVertexDecl->AddRef(); s_AtgFontLocals.m_pFontVertexShader->AddRef(); s_AtgFontLocals.m_pFontPixelShader->AddRef(); hr = S_OK; } return hr; // Return the error code if any } //-------------------------------------------------------------------------------------- // Name: Font() // Desc: Constructor //-------------------------------------------------------------------------------------- XdkFont::XdkFont() { m_pFontTexture = NULL; m_dwNumGlyphs = 0L; m_Glyphs = NULL; m_fCursorX = 0.0f; m_fCursorY = 0.0f; m_fXScaleFactor = 2.0f; m_fYScaleFactor = 2.0f; m_cMaxGlyph = 0; m_TranslatorTable = NULL; m_dwNestedBeginCount = 0L; } //-------------------------------------------------------------------------------------- // Name: ~Font() // Desc: Destructor //-------------------------------------------------------------------------------------- XdkFont::~XdkFont() { Destroy(); } void XdkFont::SetFontSize(float x, float y) { m_fXScaleFactor = x; m_fYScaleFactor = y; } //-------------------------------------------------------------------------------------- // Name: Create() // Desc: Create the font's internal objects (texture and array of glyph info) // using the XPR packed resource file //-------------------------------------------------------------------------------------- HRESULT XdkFont::Create( const char * strFontFileName ) { // Create the font if( FAILED( m_xprResource.Create( strFontFileName ) ) ) return E_FAIL; D3DTexture * pFontTexture = m_xprResource.GetTexture( "FontTexture" ); const void * pFontData = m_xprResource.GetData( "FontData"); // Save a copy of the texture m_pFontTexture = pFontTexture; // Check version of file (to make sure it matches up with the FontMaker tool) const unsigned char * pData = static_cast(pFontData); unsigned long dwFileVersion = reinterpret_cast(pData)->m_dwFileVersion; if( dwFileVersion == FONTFILEVERSION ) { m_fFontHeight = reinterpret_cast(pData)->m_fFontHeight; m_fFontTopPadding = reinterpret_cast(pData)->m_fFontTopPadding; m_fFontBottomPadding = reinterpret_cast(pData)->m_fFontBottomPadding; m_fFontYAdvance = reinterpret_cast(pData)->m_fFontYAdvance; // Point to the translator string which immediately follows the 4 floats m_cMaxGlyph = reinterpret_cast(pData)->m_cMaxGlyph; m_TranslatorTable = const_cast(reinterpret_cast(pData))->m_TranslatorTable; pData += CALCFONTFILEHEADERSIZE( m_cMaxGlyph + 1 ); // Read the glyph attributes from the file m_dwNumGlyphs = reinterpret_cast(pData)->m_dwNumGlyphs; m_Glyphs = reinterpret_cast(pData)->m_Glyphs; // Pointer } else { SSNES_ERR( "Incorrect version number on font file.\n" ); return E_FAIL; } // Create the vertex and pixel shaders for rendering the font if( FAILED( CreateFontShaders() ) ) { SSNES_ERR( "Could not create font shaders.\n" ); return E_FAIL; } xdk360_video_t *vid = (xdk360_video_t*)g_d3d; D3DDevice *pd3dDevice = vid->xdk360_render_device; // Initialize the window D3DDISPLAYMODE DisplayMode; pd3dDevice->GetDisplayMode( 0, &DisplayMode ); m_rcWindow.x1 = 0; m_rcWindow.y1 = 0; m_rcWindow.x2 = DisplayMode.Width; m_rcWindow.y2 = DisplayMode.Height; // Determine whether we should save/restore state m_bSaveState = TRUE; return S_OK; } //-------------------------------------------------------------------------------------- // Name: Destroy() // Desc: Destroy the font object //-------------------------------------------------------------------------------------- void XdkFont::Destroy() { m_pFontTexture = NULL; m_dwNumGlyphs = 0L; m_Glyphs = NULL; m_cMaxGlyph = 0; m_TranslatorTable = NULL; m_dwNestedBeginCount = 0L; // Safely release shaders // NOTE: They are released in reverse order of creation // to make sure any interdependencies are dealt with if( ( s_AtgFontLocals.m_pFontPixelShader != NULL ) && ( s_AtgFontLocals.m_pFontPixelShader->Release() == 0 ) ) s_AtgFontLocals.m_pFontPixelShader = NULL; if( ( s_AtgFontLocals.m_pFontVertexShader != NULL ) && ( s_AtgFontLocals.m_pFontVertexShader->Release() == 0 ) ) s_AtgFontLocals.m_pFontVertexShader = NULL; if( ( s_AtgFontLocals.m_pFontVertexDecl != NULL ) && ( s_AtgFontLocals.m_pFontVertexDecl->Release() == 0 ) ) s_AtgFontLocals.m_pFontVertexDecl = NULL; if( m_xprResource.m_bInitialized) m_xprResource.Destroy(); } //-------------------------------------------------------------------------------------- // Name: SetCursorPosition() // Desc: Sets the cursor position for drawing text //-------------------------------------------------------------------------------------- void XdkFont::SetCursorPosition( float fCursorX, float fCursorY ) { m_fCursorX = floorf( fCursorX ); m_fCursorY = floorf( fCursorY ); } //-------------------------------------------------------------------------------------- // Name: GetTextExtent() // Desc: Get the dimensions of a text string //-------------------------------------------------------------------------------------- void XdkFont::GetTextExtent( const wchar_t * strText, float * pWidth, float * pHeight, int bFirstLineOnly ) const { // Set default text extent in output parameters int iWidth = 0; float fHeight = 0.0f; if( strText ) { // Initialize counters that keep track of text extent int ix = 0; float fy = m_fFontHeight; // One character high to start if( fy > fHeight ) fHeight = fy; // Loop through each character and update text extent unsigned long letter; while( (letter = *strText) != 0 ) { ++strText; // Handle newline character if( letter == L'\n' ) { if( bFirstLineOnly ) break; ix = 0; fy += m_fFontYAdvance; // since the height has changed, test against the height extent if( fy > fHeight ) fHeight = fy; } // Handle carriage return characters by ignoring them. This helps when // displaying text from a file. if( letter == L'\r' ) continue; // Translate unprintable characters const GLYPH_ATTR* pGlyph; if( letter > m_cMaxGlyph ) letter = 0; // Out of bounds? else letter = m_TranslatorTable[letter]; // Remap ASCII to glyph pGlyph = &m_Glyphs[letter]; // Get the requested glyph // Get text extent for this character's glyph ix += pGlyph->wOffset; ix += pGlyph->wAdvance; // Since the x widened, test against the x extent if( ix > iWidth ) iWidth = ix; } } // Convert the width to a float here, load/hit/store. :( float fWidth = static_cast(iWidth); // Delay the use if fWidth to reduce LHS pain // Apply the scale factor to the result fHeight *= m_fYScaleFactor; // Store the final results *pHeight = fHeight; fWidth *= m_fXScaleFactor; *pWidth = fWidth; } //-------------------------------------------------------------------------------------- // Name: GetTextWidth() // Desc: Returns the width in pixels of a text string //-------------------------------------------------------------------------------------- float XdkFont::GetTextWidth( const wchar_t * strText ) const { float fTextWidth, fTextHeight; GetTextExtent( strText, &fTextWidth, &fTextHeight ); return fTextWidth; } //-------------------------------------------------------------------------------------- // Name: Begin() // Desc: Prepares the font vertex buffers for rendering. //-------------------------------------------------------------------------------------- VOID XdkFont::Begin() { PIXBeginNamedEvent( 0, "Text Rendering" ); // Set state on the first call if( m_dwNestedBeginCount == 0 ) { // Cache the global pointer into a register xdk360_video_t *vid = (xdk360_video_t*)g_d3d; D3DDevice *pD3dDevice = vid->xdk360_render_device; // Save state if( m_bSaveState ) { // Note, we are not saving the texture, vertex, or pixel shader, // since it's not worth the performance. We're more interested // in saving state that would cause hard to find problems. pD3dDevice->GetRenderState( D3DRS_ALPHABLENDENABLE, &m_dwSavedState[ SAVEDSTATE_D3DRS_ALPHABLENDENABLE ] ); pD3dDevice->GetRenderState( D3DRS_SRCBLEND, &m_dwSavedState[ SAVEDSTATE_D3DRS_SRCBLEND ] ); pD3dDevice->GetRenderState( D3DRS_DESTBLEND, &m_dwSavedState[ SAVEDSTATE_D3DRS_DESTBLEND ] ); pD3dDevice->GetRenderState( D3DRS_BLENDOP, &m_dwSavedState[ SAVEDSTATE_D3DRS_BLENDOP ] ); pD3dDevice->GetRenderState( D3DRS_ALPHATESTENABLE, &m_dwSavedState[ SAVEDSTATE_D3DRS_ALPHATESTENABLE ] ); pD3dDevice->GetRenderState( D3DRS_ALPHAREF, &m_dwSavedState[ SAVEDSTATE_D3DRS_ALPHAREF ] ); pD3dDevice->GetRenderState( D3DRS_ALPHAFUNC, &m_dwSavedState[ SAVEDSTATE_D3DRS_ALPHAFUNC ] ); pD3dDevice->GetRenderState( D3DRS_FILLMODE, &m_dwSavedState[ SAVEDSTATE_D3DRS_FILLMODE ] ); pD3dDevice->GetRenderState( D3DRS_CULLMODE, &m_dwSavedState[ SAVEDSTATE_D3DRS_CULLMODE ] ); pD3dDevice->GetRenderState( D3DRS_ZENABLE, &m_dwSavedState[ SAVEDSTATE_D3DRS_ZENABLE ] ); pD3dDevice->GetRenderState( D3DRS_STENCILENABLE, &m_dwSavedState[ SAVEDSTATE_D3DRS_STENCILENABLE ] ); pD3dDevice->GetRenderState( D3DRS_VIEWPORTENABLE, &m_dwSavedState[ SAVEDSTATE_D3DRS_VIEWPORTENABLE ] ); pD3dDevice->GetSamplerState( 0, D3DSAMP_MINFILTER, &m_dwSavedState[ SAVEDSTATE_D3DSAMP_MINFILTER ] ); pD3dDevice->GetSamplerState( 0, D3DSAMP_MAGFILTER, &m_dwSavedState[ SAVEDSTATE_D3DSAMP_MAGFILTER ] ); pD3dDevice->GetSamplerState( 0, D3DSAMP_ADDRESSU, &m_dwSavedState[ SAVEDSTATE_D3DSAMP_ADDRESSU ] ); pD3dDevice->GetSamplerState( 0, D3DSAMP_ADDRESSV, &m_dwSavedState[ SAVEDSTATE_D3DSAMP_ADDRESSV ] ); } // Set the texture scaling factor as a vertex shader constant D3DSURFACE_DESC TextureDesc; m_pFontTexture->GetLevelDesc( 0, &TextureDesc ); // Get the description // Set render state pD3dDevice->SetTexture( 0, m_pFontTexture ); // Read the TextureDesc here to ensure no load/hit/store from GetLevelDesc() float vTexScale[4]; vTexScale[0] = 1.0f / TextureDesc.Width; // LHS due to int->float conversion vTexScale[1] = 1.0f / TextureDesc.Height; vTexScale[2] = 0.0f; vTexScale[3] = 0.0f; pD3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); pD3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); pD3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); pD3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); pD3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, TRUE ); pD3dDevice->SetRenderState( D3DRS_ALPHAREF, 0x08 ); pD3dDevice->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL ); pD3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID ); pD3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ); pD3dDevice->SetRenderState( D3DRS_ZENABLE, FALSE ); pD3dDevice->SetRenderState( D3DRS_STENCILENABLE, FALSE ); pD3dDevice->SetRenderState( D3DRS_VIEWPORTENABLE, FALSE ); pD3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); pD3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); pD3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP ); pD3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP ); pD3dDevice->SetVertexDeclaration( s_AtgFontLocals.m_pFontVertexDecl ); pD3dDevice->SetVertexShader( s_AtgFontLocals.m_pFontVertexShader ); pD3dDevice->SetPixelShader( s_AtgFontLocals.m_pFontPixelShader ); // Set the texture scaling factor as a vertex shader constant // Call here to avoid load hit store from writing to vTexScale above pD3dDevice->SetVertexShaderConstantF( 2, vTexScale, 1 ); } // Keep track of the nested begin/end calls. m_dwNestedBeginCount++; } //-------------------------------------------------------------------------------------- // Name: DrawText() // Desc: Draws text as textured polygons //-------------------------------------------------------------------------------------- VOID XdkFont::DrawText( unsigned long dwColor, const wchar_t * strText, unsigned long dwFlags, float fMaxPixelWidth ) { DrawText( m_fCursorX, m_fCursorY, dwColor, strText, dwFlags, fMaxPixelWidth ); } //-------------------------------------------------------------------------------------- // Name: DrawText() // Desc: Draws text as textured polygons // TODO: This function should use the Begin/SetVertexData/End() API when it // becomes available. //-------------------------------------------------------------------------------------- VOID XdkFont::DrawText( float fOriginX, float fOriginY, unsigned long dwColor, const wchar_t * strText, unsigned long dwFlags, float fMaxPixelWidth ) { if( strText == NULL ) return; if( L'\0' == strText[0] ) return; xdk360_video_t *vid = (xdk360_video_t*)g_d3d; D3DDevice *pd3dDevice = vid->xdk360_render_device; // Create a PIX user-defined event that encapsulates all of the text draw calls. // This makes DrawText calls easier to recognize in PIX captures, and it makes // them take up fewer entries in the event list. PIXBeginNamedEvent( dwColor, "DrawText: %S", strText ); // Set the color as a vertex shader constant float vColor[4]; vColor[0] = ( ( dwColor & 0x00ff0000 ) >> 16L ) / 255.0F; vColor[1] = ( ( dwColor & 0x0000ff00 ) >> 8L ) / 255.0F; vColor[2] = ( ( dwColor & 0x000000ff ) >> 0L ) / 255.0F; vColor[3] = ( ( dwColor & 0xff000000 ) >> 24L ) / 255.0F; // Set up stuff to prepare for drawing text Begin(); // Perform the actual storing of the color constant here to prevent // a load-hit-store by inserting work between the store and the use of // the vColor array. pd3dDevice->SetVertexShaderConstantF( 1, vColor, 1 ); // Set the starting screen position if( ( fOriginX < 0.0f ) || ( ( dwFlags & FONT_RIGHT ) && ( fOriginX <= 0.0f ) ) ) fOriginX += ( m_rcWindow.x2 - m_rcWindow.x1 ); if( fOriginY < 0.0f ) fOriginY += ( m_rcWindow.y2 - m_rcWindow.y1 ); m_fCursorX = floorf( fOriginX ); m_fCursorY = floorf( fOriginY ); // Adjust for padding fOriginY -= m_fFontTopPadding; float fEllipsesPixelWidth = m_fXScaleFactor * 3.0f * ( m_Glyphs[m_TranslatorTable[L'.']].wOffset + m_Glyphs[m_TranslatorTable[L'.']].wAdvance ); if( dwFlags & FONT_TRUNCATED ) { // Check if we will really need to truncate the string if( fMaxPixelWidth <= 0.0f ) dwFlags &= ( ~FONT_TRUNCATED ); else { float w, h; GetTextExtent( strText, &w, &h, TRUE ); // If not, then clear the flag if( w <= fMaxPixelWidth ) dwFlags &= ( ~FONT_TRUNCATED ); } } // If vertically centered, offset the starting m_fCursorY value if( dwFlags & FONT_CENTER_Y ) { float w, h; GetTextExtent( strText, &w, &h ); m_fCursorY = floorf( m_fCursorY - (h * 0.5f) ); } // Add window offsets float Winx = static_cast(m_rcWindow.x1); float Winy = static_cast(m_rcWindow.y1); fOriginX += Winx; fOriginY += Winy; m_fCursorX += Winx; m_fCursorY += Winy; // Set a flag so we can determine initial justification effects BOOL bStartingNewLine = TRUE; unsigned long dwNumEllipsesToDraw = 0; // Begin drawing the vertices // Declared as volatile to force writing in ascending // address order. It prevents out of sequence writing in write combined // memory. volatile float * pVertex; unsigned long dwNumChars = wcslen( strText ) + ( dwFlags & FONT_TRUNCATED ? 3 : 0 ); HRESULT hr = pd3dDevice->BeginVertices( D3DPT_QUADLIST, 4 * dwNumChars, sizeof( XMFLOAT4 ) , ( VOID** )&pVertex ); // The ring buffer may run out of space when tiling, doing z-prepasses, // or using BeginCommandBuffer. If so, make the buffer larger. if( FAILED( hr ) ) SSNES_ERR( "Ring buffer out of memory.\n" ); bStartingNewLine = TRUE; // Draw four vertices for each glyph while( *strText ) { wchar_t letter; if( dwNumEllipsesToDraw ) letter = L'.'; else { // If starting text on a new line, determine justification effects if( bStartingNewLine ) { if( dwFlags & ( FONT_RIGHT | FONT_CENTER_X ) ) { // Get the extent of this line float w, h; GetTextExtent( strText, &w, &h, TRUE ); // Offset this line's starting m_fCursorX value if( dwFlags & FONT_RIGHT ) m_fCursorX = floorf( fOriginX - w ); if( dwFlags & FONT_CENTER_X ) m_fCursorX = floorf( fOriginX - w * 0.5f ); } bStartingNewLine = FALSE; } // Get the current letter in the string letter = *strText++; // Handle the newline character if( letter == L'\n' ) { m_fCursorX = fOriginX; m_fCursorY += m_fFontYAdvance * m_fYScaleFactor; bStartingNewLine = TRUE; continue; } // Handle carriage return characters by ignoring them. This helps when // displaying text from a file. if( letter == L'\r' ) continue; } // Translate unprintable characters const GLYPH_ATTR * pGlyph = &m_Glyphs[ ( letter <= m_cMaxGlyph ) ? m_TranslatorTable[letter] : 0 ]; float fOffset = m_fXScaleFactor * (float)pGlyph->wOffset; float fAdvance = m_fXScaleFactor * (float)pGlyph->wAdvance; float fWidth = m_fXScaleFactor * (float)pGlyph->wWidth; float fHeight = m_fYScaleFactor * m_fFontHeight; if( 0 == dwNumEllipsesToDraw ) { if( dwFlags & FONT_TRUNCATED ) { // Check if we will be exceeded the max allowed width if( m_fCursorX + fOffset + fWidth + fEllipsesPixelWidth > fOriginX + fMaxPixelWidth ) { // Yup, draw the three ellipses dots instead dwNumEllipsesToDraw = 3; continue; } } } // Setup the screen coordinates m_fCursorX += fOffset; float X4 = m_fCursorX; float X1 = X4; float X3 = X4 + fWidth; float X2 = X1 + fWidth; float Y1 = m_fCursorY; float Y3 = Y1 + fHeight; float Y2 = Y1; float Y4 = Y3; m_fCursorX += fAdvance; // Select the RGBA channel that the compressed glyph is stored in // Takes a 4 bit per pixel ARGB value and expand it to an 8 bit per pixel ARGB value unsigned long dwChannelSelector = pGlyph->wMask; // Convert to 32 bit // Perform the conversion without branching // Splat the 4 bit per pixels from 0x1234 to 0x01020304 dwChannelSelector = ((dwChannelSelector&0xF000)<<(24-12))|((dwChannelSelector&0xF00)<<(16-8))| ((dwChannelSelector&0xF0)<<(8-4))|(dwChannelSelector&0xF); // Perform a vectorized multiply to make 0x01020304 into 0x11223344 dwChannelSelector *= 0x11; // Add the vertices to draw this glyph unsigned long tu1 = pGlyph->tu1; // Convert shorts to 32 bit longs for in register merging unsigned long tv1 = pGlyph->tv1; unsigned long tu2 = pGlyph->tu2; unsigned long tv2 = pGlyph->tv2; // NOTE: The vertexs are 2 floats for the screen coordinates, // followed by two USHORTS for the u/vs of the character, // terminated with the ARGB 32 bit color. // This makes for 16 bytes per vertex data (Easier to read) // Second NOTE: The uvs are merged and written using a DWORD due // to the write combining hardware being only able to handle 32, // 64 and 128 writes. Never store to write combined memory with // 8 or 16 bit instructions. You've been warned. pVertex[0] = X1; pVertex[1] = Y1; reinterpret_cast(pVertex)[2] = (tu1<<16)|tv1; // Merged using big endian rules reinterpret_cast(pVertex)[3] = dwChannelSelector; pVertex[4] = X2; pVertex[5] = Y2; reinterpret_cast(pVertex)[6] = (tu2<<16)|tv1; // Merged using big endian rules reinterpret_cast(pVertex)[7] = dwChannelSelector; pVertex[8] = X3; pVertex[9] = Y3; reinterpret_cast(pVertex)[10] = (tu2<<16)|tv2; // Merged using big endian rules reinterpret_cast(pVertex)[11] = dwChannelSelector; pVertex[12] = X4; pVertex[13] = Y4; reinterpret_cast(pVertex)[14] = (tu1<<16)|tv2; // Merged using big endian rules reinterpret_cast(pVertex)[15] = dwChannelSelector; pVertex+=16; // If drawing ellipses, exit when they're all drawn if( dwNumEllipsesToDraw ) { if( --dwNumEllipsesToDraw == 0 ) break; } dwNumChars--; } // Since we allocated vertex data space based on the string length, we now need to // add some dummy verts for any skipped characters (like newlines, etc.) while( dwNumChars ) { pVertex[0] = 0; pVertex[1] = 0; pVertex[2] = 0; pVertex[3] = 0; pVertex[4] = 0; pVertex[5] = 0; pVertex[6] = 0; pVertex[7] = 0; pVertex[8] = 0; pVertex[9] = 0; pVertex[10] = 0; pVertex[11] = 0; pVertex[12] = 0; pVertex[13] = 0; pVertex[14] = 0; pVertex[15] = 0; pVertex+=16; dwNumChars--; } // Stop drawing vertices pd3dDevice->EndVertices(); // Undo window offsets m_fCursorX -= Winx; m_fCursorY -= Winy; // Call End() to complete the begin/end pair for drawing text End(); // Close off the user-defined event opened with PIXBeginNamedEvent. PIXEndNamedEvent(); } //-------------------------------------------------------------------------------------- // Name: End() // Desc: Paired call that restores state set in the Begin() call. //-------------------------------------------------------------------------------------- VOID XdkFont::End() { if( --m_dwNestedBeginCount > 0 ) { PIXEndNamedEvent(); return; } // Restore state if( m_bSaveState ) { // Cache the global pointer into a register xdk360_video_t *vid = (xdk360_video_t*)g_d3d; D3DDevice *pD3dDevice = vid->xdk360_render_device; pD3dDevice->SetTexture( 0, NULL ); pD3dDevice->SetVertexDeclaration( NULL ); pD3dDevice->SetVertexShader( NULL ); pD3dDevice->SetPixelShader( NULL ); pD3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, m_dwSavedState[ SAVEDSTATE_D3DRS_ALPHABLENDENABLE ] ); pD3dDevice->SetRenderState( D3DRS_SRCBLEND, m_dwSavedState[ SAVEDSTATE_D3DRS_SRCBLEND ] ); pD3dDevice->SetRenderState( D3DRS_DESTBLEND, m_dwSavedState[ SAVEDSTATE_D3DRS_DESTBLEND ] ); pD3dDevice->SetRenderState( D3DRS_BLENDOP, m_dwSavedState[ SAVEDSTATE_D3DRS_BLENDOP ] ); pD3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, m_dwSavedState[ SAVEDSTATE_D3DRS_ALPHATESTENABLE ] ); pD3dDevice->SetRenderState( D3DRS_ALPHAREF, m_dwSavedState[ SAVEDSTATE_D3DRS_ALPHAREF ] ); pD3dDevice->SetRenderState( D3DRS_ALPHAFUNC, m_dwSavedState[ SAVEDSTATE_D3DRS_ALPHAFUNC ] ); pD3dDevice->SetRenderState( D3DRS_FILLMODE, m_dwSavedState[ SAVEDSTATE_D3DRS_FILLMODE ] ); pD3dDevice->SetRenderState( D3DRS_CULLMODE, m_dwSavedState[ SAVEDSTATE_D3DRS_CULLMODE ] ); pD3dDevice->SetRenderState( D3DRS_ZENABLE, m_dwSavedState[ SAVEDSTATE_D3DRS_ZENABLE ] ); pD3dDevice->SetRenderState( D3DRS_STENCILENABLE, m_dwSavedState[ SAVEDSTATE_D3DRS_STENCILENABLE ] ); pD3dDevice->SetRenderState( D3DRS_VIEWPORTENABLE, m_dwSavedState[ SAVEDSTATE_D3DRS_VIEWPORTENABLE ] ); pD3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, m_dwSavedState[ SAVEDSTATE_D3DSAMP_MINFILTER ] ); pD3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, m_dwSavedState[ SAVEDSTATE_D3DSAMP_MAGFILTER ] ); pD3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, m_dwSavedState[ SAVEDSTATE_D3DSAMP_ADDRESSU ] ); pD3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, m_dwSavedState[ SAVEDSTATE_D3DSAMP_ADDRESSV ] ); } PIXEndNamedEvent(); }