//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #if !defined( _GAMECONSOLE ) && defined( _WIN32 ) #define WIN32_LEAN_AND_MEAN #include #endif #include "vgui_surfacelib/fonttexturecache.h" #include "tier1/keyvalues.h" #include "materialsystem/itexture.h" #include "materialsystem/imaterial.h" #include "tier1/utlbuffer.h" #include "fmtstr.h" #include "vgui_surfacelib/texturedictionary.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define TEXTURE_PAGE_WIDTH 256 #define TEXTURE_PAGE_HEIGHT 256 ConVar vgui_show_glyph_miss( "vgui_show_glyph_miss", "0", FCVAR_DEVELOPMENTONLY ); //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CFontTextureCache::CFontTextureCache() : m_CharCache( 0, 256, CacheEntryLessFunc ) { V_memset( m_CommonCharCache, 0, sizeof( m_CommonCharCache ) ); Clear(); } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CFontTextureCache::~CFontTextureCache() { Clear(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFontTextureCache::SetPrefix( const char *pTexturePagePrefix ) { m_TexturePagePrefix = pTexturePagePrefix; } //----------------------------------------------------------------------------- // Purpose: Resets the cache //----------------------------------------------------------------------------- void CFontTextureCache::Clear() { // remove all existing data m_CharCache.RemoveAll(); for ( int i = 0; i < m_PageList.Count(); ++i ) { if ( m_PageList[i].pPackedFontTextureCache ) { delete m_PageList[i].pPackedFontTextureCache; } } m_PageList.RemoveAll(); m_CurrPage = -1; m_FontPages.RemoveAll(); m_FontPages.SetLessFunc( DefLessFunc( FontHandle_t ) ); for ( int i = 0; i < ARRAYSIZE( m_CommonCharCache ); i++ ) { delete m_CommonCharCache[i]; m_CommonCharCache[i] = 0; } } //----------------------------------------------------------------------------- // Purpose: comparison function for cache entries //----------------------------------------------------------------------------- bool CFontTextureCache::CacheEntryLessFunc( CacheEntry_t const &lhs, CacheEntry_t const &rhs ) { uint64 lhsLookupID = ( ((uint64)lhs.font) << 32 ) | ((uint64)lhs.wch); uint64 rhsLookupID = ( ((uint64)rhs.font) << 32 ) | ((uint64)rhs.wch); return lhsLookupID < rhsLookupID; } //----------------------------------------------------------------------------- // Purpose: returns the texture info for the given char & font //----------------------------------------------------------------------------- bool CFontTextureCache::GetTextureForChar( FontHandle_t font, FontDrawType_t type, wchar_t wch, int *textureID, float **texCoords ) { // Ask for just one character return GetTextureForChars( font, type, &wch, textureID, texCoords, 1 ); } //----------------------------------------------------------------------------- // Purpose: returns the texture info for the given char & font // This function copies in the texcoords out from the static into a preallocated passed in arg. //----------------------------------------------------------------------------- bool CFontTextureCache::GetTextureAndCoordsForChar( FontHandle_t font, FontDrawType_t type, wchar_t wch, int *textureID, float *texCoords ) { // Ask for just one character float *textureCoords = NULL; bool bSuccess = GetTextureForChars( font, type, &wch, textureID, &textureCoords, 1 ); if ( textureCoords ) { texCoords[0] = textureCoords[0]; texCoords[1] = textureCoords[1]; texCoords[2] = textureCoords[2]; texCoords[3] = textureCoords[3]; } return bSuccess; } //----------------------------------------------------------------------------- // Purpose: returns the texture info for the given chars & font //----------------------------------------------------------------------------- bool CFontTextureCache::GetTextureForChars( FontHandle_t hFont, FontDrawType_t type, wchar_t *wch, int *textureID, float **texCoords, int numChars ) { Assert( wch && textureID && texCoords ); Assert( numChars >= 1 ); if ( type == FONT_DRAW_DEFAULT ) { type = FontManager().IsFontAdditive( hFont ) ? FONT_DRAW_ADDITIVE : FONT_DRAW_NONADDITIVE; } int typePage = (int)type - 1; typePage = clamp( typePage, 0, (int)FONT_DRAW_TYPE_COUNT - 1 ); if ( FontManager().IsBitmapFont( hFont ) ) { const int MAX_BITMAP_CHARS = 256; if ( numChars > MAX_BITMAP_CHARS ) { // Increase MAX_BITMAP_CHARS Assert( 0 ); return false; } for ( int i = 0; i < numChars; i++ ) { static float sTexCoords[ 4*MAX_BITMAP_CHARS ]; CBitmapFont *pWinFont; float left, top, right, bottom; int index; Page_t *pPage; pWinFont = reinterpret_cast< CBitmapFont* >( FontManager().GetFontForChar( hFont, wch[i] ) ); if ( !pWinFont ) { // bad font handle return false; } // get the texture coords pWinFont->GetCharCoords( wch[i], &left, &top, &right, &bottom ); sTexCoords[i*4 + 0] = left; sTexCoords[i*4 + 1] = top; sTexCoords[i*4 + 2] = right; sTexCoords[i*4 + 3] = bottom; // find font handle in our list of ready pages index = m_FontPages.Find( hFont ); if ( index == m_FontPages.InvalidIndex() ) { // not found, create the texture id and its materials index = m_FontPages.Insert( hFont ); pPage = &m_FontPages.Element( index ); for (int type = 0; type < FONT_DRAW_TYPE_COUNT; ++type ) { pPage->textureID[type] = TextureDictionary()->CreateTexture( false ); } CreateFontMaterials( *pPage, pWinFont->GetTexturePage(), true ); } texCoords[i] = &(sTexCoords[ i*4 ]); textureID[i] = m_FontPages.Element( index ).textureID[typePage]; } } else { font_t *pWinFont = FontManager().GetFontForChar( hFont, wch[0] ); if ( !pWinFont ) { return false; } struct newPageEntry_t { int page; // The font page a new character will go in int drawX; // X location within the font page int drawY; // Y location within the font page }; // Determine how many characters need to have their texture generated newChar_t *newChars = (newChar_t *)stackalloc( numChars*sizeof( newChar_t ) ); newPageEntry_t *newEntries = (newPageEntry_t *)stackalloc( numChars*sizeof( newPageEntry_t ) ); int numNewChars = 0; int maxNewCharTexels = 0; int totalNewCharTexels = 0; for ( int i = 0; i < numChars; i++ ) { wchar_t wideChar = wch[i]; int *pCachePage; float *pCacheCoords; // profiling dicatated that avoiding the naive font/char RB lookup was beneficial // instead waste a little memory to get all the western language chars to be direct if ( IsGameConsole() && wideChar < MAX_COMMON_CHARS && hFont < ARRAYSIZE( m_CommonCharCache ) ) { // dominant amount of simple chars are instant direct lookup CommonChar_t *pCommonChars = m_CommonCharCache[hFont]; if ( !pCommonChars ) { // missing if ( pWinFont != FontManager().GetFontForChar( hFont, wideChar ) ) { // all characters in string must come out of the same font return false; } // init and insert pCommonChars = new CommonChar_t; memset( pCommonChars, 0, sizeof( CommonChar_t ) ); m_CommonCharCache[hFont] = pCommonChars; } pCachePage = &pCommonChars->details[wideChar].page; pCacheCoords = pCommonChars->details[wideChar].texCoords; } else { // for console only, either more fonts than expected (> 256 fonts!) or not a simple integer // want to keep this a direct lookup and not a search (which defeats the perf gain) AssertMsgOnce( !IsGameConsole() || hFont < ARRAYSIZE( m_CommonCharCache ), "CFontTextureCache: Unexpected hFont out-of-range\n" ); // extended chars are a costlier lookup // page and char form a unique key to find in cache CacheEntry_t cacheItem; cacheItem.font = hFont; cacheItem.wch = wideChar; HCacheEntry cacheHandle = m_CharCache.Find( cacheItem ); if ( !m_CharCache.IsValidIndex( cacheHandle ) ) { // missing if ( pWinFont != FontManager().GetFontForChar( hFont, wideChar ) ) { // all characters in string must come out of the same font return false; } // init and insert cacheItem.texCoords[0] = 0; cacheItem.texCoords[1] = 0; cacheItem.texCoords[2] = 0; cacheItem.texCoords[3] = 0; cacheHandle = m_CharCache.Insert( cacheItem ); Assert( m_CharCache.IsValidIndex( cacheHandle ) ); } pCachePage = &m_CharCache[cacheHandle].page; pCacheCoords = m_CharCache[cacheHandle].texCoords; } if ( pCacheCoords[2] == 0 && pCacheCoords[3] == 0 ) { // invalid page, setup for page allocation // get the char details int a, b, c; pWinFont->GetCharABCWidths( wideChar, a, b, c ); int fontWide = MAX( b, 1 ); int fontTall = MAX( pWinFont->GetHeight(), 1 ); if ( pWinFont->GetUnderlined() ) { fontWide += ( a + c ); } // Get a texture to render into int page, drawX, drawY, twide, ttall; if ( !AllocatePageForChar( fontWide, fontTall, page, drawX, drawY, twide, ttall ) ) { return false; } // accumulate data to pass to GetCharsRGBA below newEntries[numNewChars].page = page; newEntries[numNewChars].drawX = drawX; newEntries[numNewChars].drawY = drawY; newChars[numNewChars].wch = wideChar; newChars[numNewChars].fontWide = fontWide; newChars[numNewChars].fontTall = fontTall; newChars[numNewChars].offset = 4*totalNewCharTexels; totalNewCharTexels += fontWide*fontTall; maxNewCharTexels = MAX( maxNewCharTexels, fontWide*fontTall ); numNewChars++; // the 0.5 texel offset is done in CMatSystemTexture::SetMaterial() pCacheCoords[0] = (float)( (double)drawX / ((double)twide) ); pCacheCoords[1] = (float)( (double)drawY / ((double)ttall) ); pCacheCoords[2] = (float)( (double)(drawX + fontWide) / (double)twide ); pCacheCoords[3] = (float)( (double)(drawY + fontTall) / (double)ttall ); *pCachePage = page; } // give data to caller textureID[i] = m_PageList[*pCachePage].textureID[typePage]; texCoords[i] = pCacheCoords; } // Generate texture data for all newly-encountered characters if ( numNewChars > 0 ) { if ( vgui_show_glyph_miss.GetBool() ) { char *pMissString = (char *)stackalloc( numNewChars * sizeof( char ) ); char *pString = pMissString; for ( int i = 0; i < numNewChars; i++ ) { // build a string representative enough for debugging puproses wchar_t wch = newChars[i].wch; if ( V_isprint( wch ) ) { *pString++ = (char)wch; } else { *pString++ = '?'; } } *pString = '\0'; const char *pMsg = CFmtStr( "Glyph Miss: FontHandle_t:0x%8.8x (%s), %s (0x%x)\n", (int)hFont, pWinFont->GetName(), pMissString, pMissString[0] ); if ( IsGameConsole() ) { // valid on xbox, and really want this spew treated like console spew Warning( "%s", pMsg ); } else { // debugger output only, to prevent any reentrant glyph miss as a result of spewing Plat_DebugString( pMsg ); } } if ( IsGameConsole() && numNewChars > 1 ) { MEM_ALLOC_CREDIT(); // Use the 360 fast path that generates multiple characters at once int newCharDataSize = totalNewCharTexels*4; CUtlBuffer newCharData( 0, newCharDataSize, CUtlBuffer::READ_ONLY ); unsigned char *pRGBA = (unsigned char *)newCharData.Base(); #if defined( _X360 ) || defined( _PS3 ) pWinFont->GetCharsRGBA( newChars, numNewChars, pRGBA ); #endif // Copy the data into our font pages for ( int i = 0; i < numNewChars; i++ ) { newChar_t &newChar = newChars[i]; newPageEntry_t &newEntry = newEntries[i]; // upload the new sub texture // NOTE: both textureIDs reference the same ITexture, so we're ok unsigned char *characterRGBA = pRGBA + newChar.offset; TextureDictionary()->SetSubTextureRGBA( m_PageList[newEntry.page].textureID[typePage], newEntry.drawX, newEntry.drawY, characterRGBA, newChar.fontWide, newChar.fontTall ); } } else { // create a buffer for new characters to be rendered into int nByteCount = maxNewCharTexels * 4; unsigned char *pRGBA = (unsigned char *)stackalloc( nByteCount * sizeof( unsigned char ) ); // Generate characters individually for ( int i = 0; i < numNewChars; i++ ) { newChar_t &newChar = newChars[i]; newPageEntry_t &newEntry = newEntries[i]; // render the character into the buffer Q_memset( pRGBA, 0, nByteCount ); pWinFont->GetCharRGBA( newChar.wch, newChar.fontWide, newChar.fontTall, pRGBA ); // Make the char white if we are in source 2 if ( !g_pMaterialSystem ) { for ( int i = 0; i < nByteCount; i += 4 ) { pRGBA[i+0] = pRGBA[i+1] = pRGBA[i+2] = 255; } } // upload the new sub texture // NOTE: both textureIDs reference the same ITexture, so we're ok TextureDictionary()->SetSubTextureRGBA( m_PageList[newEntry.page].textureID[typePage], newEntry.drawX, newEntry.drawY, pRGBA, newChar.fontWide, newChar.fontTall ); } } } } return true; } //----------------------------------------------------------------------------- // Creates font materials //----------------------------------------------------------------------------- void CFontTextureCache::CreateFontMaterials( Page_t &page, ITexture *pFontTexture, bool bitmapFont ) { // The normal material KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" ); pVMTKeyValues->SetInt( "$vertexcolor", 1 ); pVMTKeyValues->SetInt( "$vertexalpha", 1 ); pVMTKeyValues->SetInt( "$ignorez", 1 ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$translucent", 1 ); pVMTKeyValues->SetString( "$basetexture", pFontTexture->GetName() ); CUtlString materialName = m_TexturePagePrefix + "__fontpage"; Assert( g_pMaterialSystem ); IMaterial *pMaterial = g_pMaterialSystem->CreateMaterial( materialName, pVMTKeyValues ); pMaterial->Refresh(); int typePageNonAdditive = (int)FONT_DRAW_NONADDITIVE-1; TextureDictionary()->BindTextureToMaterial( page.textureID[typePageNonAdditive], pMaterial ); pMaterial->DecrementReferenceCount(); // The additive material pVMTKeyValues = new KeyValues( "UnlitGeneric" ); pVMTKeyValues->SetInt( "$vertexcolor", 1 ); pVMTKeyValues->SetInt( "$vertexalpha", 1 ); pVMTKeyValues->SetInt( "$ignorez", 1 ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$translucent", 1 ); pVMTKeyValues->SetInt( "$additive", 1 ); pVMTKeyValues->SetString( "$basetexture", pFontTexture->GetName() ); CUtlString addmaterialName = m_TexturePagePrefix + "__fontpage_additive"; pMaterial = g_pMaterialSystem->CreateMaterial( addmaterialName.String(), pVMTKeyValues ); pMaterial->Refresh(); int typePageAdditive = (int)FONT_DRAW_ADDITIVE-1; if ( bitmapFont ) { TextureDictionary()->BindTextureToMaterial( page.textureID[typePageAdditive], pMaterial ); } else { TextureDictionary()->BindTextureToMaterialReference( page.textureID[typePageAdditive], page.textureID[typePageNonAdditive], pMaterial); } pMaterial->DecrementReferenceCount(); } //----------------------------------------------------------------------------- // Purpose: allocates a new page for a given character //----------------------------------------------------------------------------- bool CFontTextureCache::AllocatePageForChar( int charWide, int charTall, int &pageIndex, int &drawX, int &drawY, int &twide, int &ttall ) { // Catch the case where the glyph is too tall for the page if ( charTall > TEXTURE_PAGE_HEIGHT ) return false; // See if there is room in the last page for this character pageIndex = m_CurrPage; bool bNeedsNewPage = true; int nodeIndex = -1; Rect_t glpyhRect; glpyhRect.x = 0; glpyhRect.y = 0; glpyhRect.width = charWide; glpyhRect.height = charTall; if ( pageIndex > -1 ) { // Let's use r/b tree to find a good spot. nodeIndex = m_PageList[pageIndex].pPackedFontTextureCache->InsertRect( glpyhRect ); bNeedsNewPage = ( nodeIndex == -1 ); } if ( bNeedsNewPage ) { // allocate a new page pageIndex = m_PageList.AddToTail(); Page_t &newPage = m_PageList[pageIndex]; m_CurrPage = pageIndex; for (int i = 0; i < FONT_DRAW_TYPE_COUNT; ++i ) { newPage.textureID[i] = TextureDictionary()->CreateTexture( true ); } newPage.pPackedFontTextureCache = new CTexturePacker( TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, ( IsGameConsole() ? 0 : 1 ) ); nodeIndex = newPage.pPackedFontTextureCache->InsertRect( glpyhRect ); Assert( nodeIndex != -1 ); static int nFontPageId = 0; char pTextureName[64]; Q_snprintf( pTextureName, 64, "%s__font_page_%d", m_TexturePagePrefix.String(), nFontPageId ); ++nFontPageId; MEM_ALLOC_CREDIT(); if ( g_pMaterialSystem ) { ITexture *pTexture = AllocateNewPage( pTextureName ); CreateFontMaterials( newPage, pTexture ); pTexture->DecrementReferenceCount(); } if ( IsPC() || !IsDebug() ) { // clear the texture from the inital checkerboard to black // allocate for 32bpp format int nByteCount = TEXTURE_PAGE_WIDTH * TEXTURE_PAGE_HEIGHT * 4; CUtlMemory mRGBA; mRGBA.EnsureCapacity( nByteCount ); //Q_memset( mRGBA.Base(), 0, nByteCount ); // Clear to white, full alpha. for ( int i = 0; i < nByteCount; i += 4 ) { mRGBA[i+0] = mRGBA[i+1] = mRGBA[i+2] = 255; mRGBA[i+3] = 0; } int typePageNonAdditive = (int)(FONT_DRAW_NONADDITIVE)-1; TextureDictionary()->SetTextureRGBAEx( newPage.textureID[typePageNonAdditive], ( const char* )mRGBA.Base(), TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, IMAGE_FORMAT_RGBA8888, k_ETextureScalingPointSample ); // Note, in rendersystem2 we do not have materials, as we actually have 2 diff textures. if ( !g_pMaterialSystem ) { int typePageAdditive = (int)(FONT_DRAW_ADDITIVE)-1; newPage.textureID[typePageAdditive] = newPage.textureID[typePageNonAdditive]; } } } // output the position Page_t &page = m_PageList[ pageIndex ]; Assert( nodeIndex != -1 ); const CTexturePacker::TreeEntry_t &newEntry = page.pPackedFontTextureCache->GetEntry( nodeIndex ); drawX = newEntry.rc.x; drawY = newEntry.rc.y; twide = TEXTURE_PAGE_WIDTH; ttall = TEXTURE_PAGE_HEIGHT; return true; } //----------------------------------------------------------------------------- // Purpose: allocates a new page //----------------------------------------------------------------------------- ITexture *CFontTextureCache::AllocateNewPage( char *pTextureName ) { Assert( g_pMaterialSystem ); ITexture *pTexture = g_pMaterialSystem->CreateProceduralTexture( pTextureName, TEXTURE_GROUP_VGUI, TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, IMAGE_FORMAT_RGBA8888, TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY ); return pTexture; }