summaryrefslogtreecommitdiff
path: root/vguimatsurface/FontTextureCache.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /vguimatsurface/FontTextureCache.cpp
downloadarchived-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.cpp512
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;
+}