diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /vguimatsurface/FontTextureCache.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'vguimatsurface/FontTextureCache.cpp')
| -rw-r--r-- | vguimatsurface/FontTextureCache.cpp | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/vguimatsurface/FontTextureCache.cpp b/vguimatsurface/FontTextureCache.cpp new file mode 100644 index 0000000..d94e9af --- /dev/null +++ b/vguimatsurface/FontTextureCache.cpp @@ -0,0 +1,512 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#if defined ( WIN32 ) && !defined( _X360 ) +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#elif defined( OSX ) +#include <Carbon/Carbon.h> +#elif defined( LINUX ) +//#error +#elif defined( _X360 ) +#else +#error +#endif +#include "FontTextureCache.h" +#include "MatSystemSurface.h" +#include <vgui_surfacelib/BitmapFont.h> +#include <vgui/IVGui.h> +#include <vgui_controls/Controls.h> +#include "bitmap/imageformat.h" +#include "vtf/vtf.h" +#include "materialsystem/imaterialvar.h" +#include "materialsystem/itexture.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "pixelwriter.h" +#include "tier0/icommandline.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern CMatSystemSurface g_MatSystemSurface; +static int g_FontRenderBoundingBoxes = -1; + +#define TEXTURE_PAGE_WIDTH 256 +#define TEXTURE_PAGE_HEIGHT 256 + +// row size +int CFontTextureCache::s_pFontPageSize[FONT_PAGE_SIZE_COUNT] = +{ + 16, + 32, + 64, + 128, + 256, +}; + +static bool g_mat_texture_outline_fonts = false; +CON_COMMAND( mat_texture_outline_fonts, "Outline fonts textures." ) +{ + g_mat_texture_outline_fonts = !g_mat_texture_outline_fonts; + Msg( "mat_texture_outline_fonts: %d\n", g_mat_texture_outline_fonts ); + g_MatSystemSurface.ResetFontCaches(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CFontTextureCache::CFontTextureCache() + : m_CharCache(0, 256, CacheEntryLessFunc) +{ + Clear(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CFontTextureCache::~CFontTextureCache() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Resets the cache +//----------------------------------------------------------------------------- +void CFontTextureCache::Clear() +{ + // remove all existing data + m_CharCache.RemoveAll(); + m_PageList.RemoveAll(); + + // reinitialize + CacheEntry_t listHead = { 0, 0 }; + m_LRUListHeadIndex = m_CharCache.Insert(listHead); + + m_CharCache[m_LRUListHeadIndex].nextEntry = m_LRUListHeadIndex; + m_CharCache[m_LRUListHeadIndex].prevEntry = m_LRUListHeadIndex; + + for (int i = 0; i < FONT_PAGE_SIZE_COUNT; ++i) + { + m_pCurrPage[i] = -1; + } + m_FontPages.SetLessFunc( DefLessFunc( vgui::HFont ) ); + m_FontPages.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: comparison function for cache entries +//----------------------------------------------------------------------------- +bool CFontTextureCache::CacheEntryLessFunc(CacheEntry_t const &lhs, CacheEntry_t const &rhs) +{ + if (lhs.font < rhs.font) + return true; + else if (lhs.font > rhs.font) + return false; + + return (lhs.wch < rhs.wch); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the texture info for the given char & font +//----------------------------------------------------------------------------- +bool CFontTextureCache::GetTextureForChar( vgui::HFont font, vgui::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 chars & font +//----------------------------------------------------------------------------- +bool CFontTextureCache::GetTextureForChars( vgui::HFont font, vgui::FontDrawType_t type, const wchar_t *wch, int *textureID, float **texCoords, int numChars ) +{ + Assert( wch && textureID && texCoords ); + Assert( numChars >= 1 ); + + if ( type == vgui::FONT_DRAW_DEFAULT ) + { + type = g_MatSystemSurface.IsFontAdditive( font ) ? vgui::FONT_DRAW_ADDITIVE : vgui::FONT_DRAW_NONADDITIVE; + } + + int typePage = (int)type - 1; + typePage = clamp( typePage, 0, (int)vgui::FONT_DRAW_TYPE_COUNT - 1 ); + + if ( FontManager().IsBitmapFont( font ) ) + { + 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( font, 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( font ); + if ( index == m_FontPages.InvalidIndex() ) + { + // not found, create the texture id and its materials + index = m_FontPages.Insert( font ); + pPage = &m_FontPages.Element( index ); + + for (int type = 0; type < FONT_DRAW_TYPE_COUNT; ++type ) + { + pPage->textureID[type] = g_MatSystemSurface.CreateNewTextureID( false ); + } + CreateFontMaterials( *pPage, pWinFont->GetTexturePage(), true ); + } + + texCoords[i] = &(sTexCoords[ i*4 ]); + textureID[i] = m_FontPages.Element( index ).textureID[typePage]; + } + } + else + { + 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 + int numNewChars = 0; + int maxNewCharTexels = 0; + int totalNewCharTexels = 0; + newChar_t *newChars = (newChar_t *)_alloca( numChars*sizeof( newChar_t ) ); + newPageEntry_t *newEntries = (newPageEntry_t *)_alloca( numChars*sizeof( newPageEntry_t ) ); + + font_t *winFont = FontManager().GetFontForChar( font, wch[0] ); + if ( !winFont ) + return false; + + for ( int i = 0; i < numChars; i++ ) + { + CacheEntry_t cacheItem; + cacheItem.font = font; + cacheItem.wch = wch[i]; + HCacheEntry cacheHandle = m_CharCache.Find( cacheItem ); + if ( ! m_CharCache.IsValidIndex( cacheHandle ) ) + { + // All characters must come out of the same font + if ( winFont != FontManager().GetFontForChar( font, wch[i] ) ) + return false; + + // get the char details + int a, b, c; + winFont->GetCharABCWidths( wch[i], a, b, c ); + int fontWide = max( b, 1 ); + int fontTall = max( winFont->GetHeight(), 1 ); + if ( winFont->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 = wch[i]; + newChars[ numNewChars ].fontWide = fontWide; + newChars[ numNewChars ].fontTall = fontTall; + newChars[ numNewChars ].offset = 4*totalNewCharTexels; + totalNewCharTexels += fontWide*fontTall; + maxNewCharTexels = max( maxNewCharTexels, fontWide*fontTall ); + numNewChars++; + + // set the cache info + cacheItem.page = page; + + // the 0.5 texel offset is done in CMatSystemTexture::SetMaterial() / CMatSystemSurface::StartDrawing() + double adjust = 0.0f; + + cacheItem.texCoords[0] = (float)( (double)drawX / ((double)twide + adjust) ); + cacheItem.texCoords[1] = (float)( (double)drawY / ((double)ttall + adjust) ); + cacheItem.texCoords[2] = (float)( (double)(drawX + fontWide) / (double)twide ); + cacheItem.texCoords[3] = (float)( (double)(drawY + fontTall) / (double)ttall ); + + m_CharCache.Insert(cacheItem); + cacheHandle = m_CharCache.Find( cacheItem ); + Assert( m_CharCache.IsValidIndex( cacheHandle ) ); + } + + int page = m_CharCache[cacheHandle].page; + textureID[i] = m_PageList[page].textureID[typePage]; + texCoords[i] = m_CharCache[cacheHandle].texCoords; + } + + // Generate texture data for all newly-encountered characters + if ( numNewChars > 0 ) + { + +#ifdef _X360 + if ( numNewChars > 1 ) + { + MEM_ALLOC_CREDIT(); + + // Use the 360 fast path that generates multiple characters at once + int newCharDataSize = totalNewCharTexels*4; + CUtlBuffer newCharData( newCharDataSize, newCharDataSize, 0 ); + unsigned char *pRGBA = (unsigned char *)newCharData.Base(); + winFont->GetCharsRGBA( newChars, numNewChars, pRGBA ); + + // 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 + g_MatSystemSurface.DrawSetTexture( m_PageList[newEntry.page].textureID[typePage] ); + unsigned char *characterRGBA = pRGBA + newChar.offset; + g_MatSystemSurface.DrawSetSubTextureRGBA( m_PageList[newEntry.page].textureID[typePage], newEntry.drawX, newEntry.drawY, characterRGBA, newChar.fontWide, newChar.fontTall ); + } + } + else +#endif + { + // create a buffer for new characters to be rendered into + int nByteCount = maxNewCharTexels * 4; + unsigned char *pRGBA = (unsigned char *)_alloca( nByteCount ); + + // 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 ); + + winFont->GetCharRGBA( newChar.wch, newChar.fontWide, newChar.fontTall, pRGBA ); + + if ( g_mat_texture_outline_fonts ) + { + int width = newChar.fontWide; + int height = newChar.fontTall; + + CPixelWriter pixelWriter; + pixelWriter.SetPixelMemory( IMAGE_FORMAT_RGBA8888, pRGBA, width * sizeof( BGRA8888_t ) ); + for( int x = 0; x < width; x++ ) + { + pixelWriter.Seek( x, 0 ); + pixelWriter.WritePixel( 255, 0, 255, 255 ); + pixelWriter.Seek( x, height - 1 ); + pixelWriter.WritePixel( 255, 0, 255, 255 ); + } + for( int y = 0; y < height; y++ ) + { + if ( y < 4 || y > height - 4 ) + { + pixelWriter.Seek( 0, y ); + pixelWriter.WritePixel( 255, 0, 255, 255 ); + pixelWriter.Seek( width - 1, y ); + pixelWriter.WritePixel( 255, 0, 255, 255 ); + } + } + } + + // upload the new sub texture + // NOTE: both textureIDs reference the same ITexture, so we're ok) + g_MatSystemSurface.DrawSetTexture( m_PageList[newEntry.page].textureID[typePage] ); + g_MatSystemSurface.DrawSetSubTextureRGBA( 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() ); + IMaterial *pMaterial = g_pMaterialSystem->CreateMaterial( "__fontpage", pVMTKeyValues ); + pMaterial->Refresh(); + + int typePageNonAdditive = (int)vgui::FONT_DRAW_NONADDITIVE-1; + g_MatSystemSurface.DrawSetTextureMaterial( 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() ); + pMaterial = g_pMaterialSystem->CreateMaterial( "__fontpage_additive", pVMTKeyValues ); + pMaterial->Refresh(); + + int typePageAdditive = (int)vgui::FONT_DRAW_ADDITIVE-1; + if ( bitmapFont ) + { + g_MatSystemSurface.DrawSetTextureMaterial( page.textureID[typePageAdditive], pMaterial ); + } + else + { + g_MatSystemSurface.ReferenceProceduralMaterial( page.textureID[typePageAdditive], page.textureID[typePageNonAdditive], pMaterial ); + } + pMaterial->DecrementReferenceCount(); +} + +//----------------------------------------------------------------------------- +// Computes the page size given a character height +//----------------------------------------------------------------------------- +int CFontTextureCache::ComputePageType( int charTall ) const +{ + for (int i = 0; i < FONT_PAGE_SIZE_COUNT; ++i) + { + if ( charTall < s_pFontPageSize[i] ) + return i; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// 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) +{ + // see if there is room in the last page for this character + int nPageType = ComputePageType( charTall ); + if ( nPageType < 0 ) + { + Assert( !"Font is too tall for texture cache of glyphs\n" ); + return false; + } + + pageIndex = m_pCurrPage[nPageType]; + + int nNextX = 0; + bool bNeedsNewPage = true; + if ( pageIndex > -1 ) + { + Page_t &page = m_PageList[ pageIndex ]; + + nNextX = page.nextX + charWide; + + // make sure we have room on the current line of the texture page + if ( nNextX > page.wide ) + { + // move down a line + page.nextX = 0; + nNextX = charWide; + page.nextY += page.tallestCharOnLine; + page.tallestCharOnLine = charTall; + } + page.tallestCharOnLine = max( page.tallestCharOnLine, (short)charTall ); + + bNeedsNewPage = ((page.nextY + page.tallestCharOnLine) > page.tall); + } + + if ( bNeedsNewPage ) + { + // allocate a new page + pageIndex = m_PageList.AddToTail(); + Page_t &newPage = m_PageList[pageIndex]; + m_pCurrPage[nPageType] = pageIndex; + + for (int i = 0; i < FONT_DRAW_TYPE_COUNT; ++i ) + { + newPage.textureID[i] = g_MatSystemSurface.CreateNewTextureID( true ); + } + + newPage.maxFontHeight = s_pFontPageSize[nPageType]; + newPage.wide = TEXTURE_PAGE_WIDTH; + newPage.tall = TEXTURE_PAGE_HEIGHT; + newPage.nextX = 0; + newPage.nextY = 0; + newPage.tallestCharOnLine = charTall; + + nNextX = charWide; + + static int nFontPageId = 0; + char pTextureName[64]; + Q_snprintf( pTextureName, 64, "__font_page_%d", nFontPageId ); + ++nFontPageId; + + MEM_ALLOC_CREDIT(); + ITexture *pTexture = g_pMaterialSystem->CreateProceduralTexture( + pTextureName, + TEXTURE_GROUP_VGUI, + newPage.wide, + newPage.tall, + IMAGE_FORMAT_RGBA8888, + TEXTUREFLAGS_POINTSAMPLE | TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | + TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY ); + + 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; + unsigned char *pRGBA = (unsigned char *)_alloca( nByteCount ); + Q_memset( pRGBA, 0, nByteCount ); + + int typePageNonAdditive = (int)(vgui::FONT_DRAW_NONADDITIVE)-1; + g_MatSystemSurface.DrawSetTextureRGBA( newPage.textureID[typePageNonAdditive], pRGBA, newPage.wide, newPage.tall, false, false ); + } + } + + // output the position + Page_t &page = m_PageList[ pageIndex ]; + drawX = page.nextX; + drawY = page.nextY; + twide = page.wide; + ttall = page.tall; + + // Update the next position to draw in + page.nextX = nNextX + 1; + return true; +} |