Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

512 lines
16 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #if defined ( WIN32 ) && !defined( _X360 )
  8. #define WIN32_LEAN_AND_MEAN
  9. #include <windows.h>
  10. #elif defined( OSX )
  11. #include <Carbon/Carbon.h>
  12. #elif defined( LINUX )
  13. //#error
  14. #elif defined( _X360 )
  15. #else
  16. #error
  17. #endif
  18. #include "FontTextureCache.h"
  19. #include "MatSystemSurface.h"
  20. #include <vgui_surfacelib/BitmapFont.h>
  21. #include <vgui/IVGui.h>
  22. #include <vgui_controls/Controls.h>
  23. #include "bitmap/imageformat.h"
  24. #include "vtf/vtf.h"
  25. #include "materialsystem/imaterialvar.h"
  26. #include "materialsystem/itexture.h"
  27. #include "tier1/KeyValues.h"
  28. #include "tier1/utlbuffer.h"
  29. #include "pixelwriter.h"
  30. #include "tier0/icommandline.h"
  31. // memdbgon must be the last include file in a .cpp file!!!
  32. #include "tier0/memdbgon.h"
  33. extern CMatSystemSurface g_MatSystemSurface;
  34. static int g_FontRenderBoundingBoxes = -1;
  35. #define TEXTURE_PAGE_WIDTH 256
  36. #define TEXTURE_PAGE_HEIGHT 256
  37. // row size
  38. int CFontTextureCache::s_pFontPageSize[FONT_PAGE_SIZE_COUNT] =
  39. {
  40. 16,
  41. 32,
  42. 64,
  43. 128,
  44. 256,
  45. };
  46. static bool g_mat_texture_outline_fonts = false;
  47. CON_COMMAND( mat_texture_outline_fonts, "Outline fonts textures." )
  48. {
  49. g_mat_texture_outline_fonts = !g_mat_texture_outline_fonts;
  50. Msg( "mat_texture_outline_fonts: %d\n", g_mat_texture_outline_fonts );
  51. g_MatSystemSurface.ResetFontCaches();
  52. }
  53. //-----------------------------------------------------------------------------
  54. // Purpose: Constructor
  55. //-----------------------------------------------------------------------------
  56. CFontTextureCache::CFontTextureCache()
  57. : m_CharCache(0, 256, CacheEntryLessFunc)
  58. {
  59. Clear();
  60. }
  61. //-----------------------------------------------------------------------------
  62. // Purpose: Destructor
  63. //-----------------------------------------------------------------------------
  64. CFontTextureCache::~CFontTextureCache()
  65. {
  66. }
  67. //-----------------------------------------------------------------------------
  68. // Purpose: Resets the cache
  69. //-----------------------------------------------------------------------------
  70. void CFontTextureCache::Clear()
  71. {
  72. // remove all existing data
  73. m_CharCache.RemoveAll();
  74. m_PageList.RemoveAll();
  75. // reinitialize
  76. CacheEntry_t listHead = { 0, 0 };
  77. m_LRUListHeadIndex = m_CharCache.Insert(listHead);
  78. m_CharCache[m_LRUListHeadIndex].nextEntry = m_LRUListHeadIndex;
  79. m_CharCache[m_LRUListHeadIndex].prevEntry = m_LRUListHeadIndex;
  80. for (int i = 0; i < FONT_PAGE_SIZE_COUNT; ++i)
  81. {
  82. m_pCurrPage[i] = -1;
  83. }
  84. m_FontPages.SetLessFunc( DefLessFunc( vgui::HFont ) );
  85. m_FontPages.RemoveAll();
  86. }
  87. //-----------------------------------------------------------------------------
  88. // Purpose: comparison function for cache entries
  89. //-----------------------------------------------------------------------------
  90. bool CFontTextureCache::CacheEntryLessFunc(CacheEntry_t const &lhs, CacheEntry_t const &rhs)
  91. {
  92. if (lhs.font < rhs.font)
  93. return true;
  94. else if (lhs.font > rhs.font)
  95. return false;
  96. return (lhs.wch < rhs.wch);
  97. }
  98. //-----------------------------------------------------------------------------
  99. // Purpose: returns the texture info for the given char & font
  100. //-----------------------------------------------------------------------------
  101. bool CFontTextureCache::GetTextureForChar( vgui::HFont font, vgui::FontDrawType_t type, wchar_t wch, int *textureID, float **texCoords )
  102. {
  103. // Ask for just one character
  104. return GetTextureForChars( font, type, &wch, textureID, texCoords, 1 );
  105. }
  106. //-----------------------------------------------------------------------------
  107. // Purpose: returns the texture info for the given chars & font
  108. //-----------------------------------------------------------------------------
  109. bool CFontTextureCache::GetTextureForChars( vgui::HFont font, vgui::FontDrawType_t type, const wchar_t *wch, int *textureID, float **texCoords, int numChars )
  110. {
  111. Assert( wch && textureID && texCoords );
  112. Assert( numChars >= 1 );
  113. if ( type == vgui::FONT_DRAW_DEFAULT )
  114. {
  115. type = g_MatSystemSurface.IsFontAdditive( font ) ? vgui::FONT_DRAW_ADDITIVE : vgui::FONT_DRAW_NONADDITIVE;
  116. }
  117. int typePage = (int)type - 1;
  118. typePage = clamp( typePage, 0, (int)vgui::FONT_DRAW_TYPE_COUNT - 1 );
  119. if ( FontManager().IsBitmapFont( font ) )
  120. {
  121. const int MAX_BITMAP_CHARS = 256;
  122. if ( numChars > MAX_BITMAP_CHARS )
  123. {
  124. // Increase MAX_BITMAP_CHARS
  125. Assert( 0 );
  126. return false;
  127. }
  128. for ( int i = 0; i < numChars; i++ )
  129. {
  130. static float sTexCoords[ 4*MAX_BITMAP_CHARS ];
  131. CBitmapFont *pWinFont;
  132. float left, top, right, bottom;
  133. int index;
  134. Page_t *pPage;
  135. pWinFont = reinterpret_cast< CBitmapFont* >( FontManager().GetFontForChar( font, wch[i] ) );
  136. if ( !pWinFont )
  137. {
  138. // bad font handle
  139. return false;
  140. }
  141. // get the texture coords
  142. pWinFont->GetCharCoords( wch[i], &left, &top, &right, &bottom );
  143. sTexCoords[i*4 + 0] = left;
  144. sTexCoords[i*4 + 1] = top;
  145. sTexCoords[i*4 + 2] = right;
  146. sTexCoords[i*4 + 3] = bottom;
  147. // find font handle in our list of ready pages
  148. index = m_FontPages.Find( font );
  149. if ( index == m_FontPages.InvalidIndex() )
  150. {
  151. // not found, create the texture id and its materials
  152. index = m_FontPages.Insert( font );
  153. pPage = &m_FontPages.Element( index );
  154. for (int type = 0; type < FONT_DRAW_TYPE_COUNT; ++type )
  155. {
  156. pPage->textureID[type] = g_MatSystemSurface.CreateNewTextureID( false );
  157. }
  158. CreateFontMaterials( *pPage, pWinFont->GetTexturePage(), true );
  159. }
  160. texCoords[i] = &(sTexCoords[ i*4 ]);
  161. textureID[i] = m_FontPages.Element( index ).textureID[typePage];
  162. }
  163. }
  164. else
  165. {
  166. struct newPageEntry_t
  167. {
  168. int page; // The font page a new character will go in
  169. int drawX; // X location within the font page
  170. int drawY; // Y location within the font page
  171. };
  172. // Determine how many characters need to have their texture generated
  173. int numNewChars = 0;
  174. int maxNewCharTexels = 0;
  175. int totalNewCharTexels = 0;
  176. newChar_t *newChars = (newChar_t *)_alloca( numChars*sizeof( newChar_t ) );
  177. newPageEntry_t *newEntries = (newPageEntry_t *)_alloca( numChars*sizeof( newPageEntry_t ) );
  178. font_t *winFont = FontManager().GetFontForChar( font, wch[0] );
  179. if ( !winFont )
  180. return false;
  181. for ( int i = 0; i < numChars; i++ )
  182. {
  183. CacheEntry_t cacheItem;
  184. cacheItem.font = font;
  185. cacheItem.wch = wch[i];
  186. HCacheEntry cacheHandle = m_CharCache.Find( cacheItem );
  187. if ( ! m_CharCache.IsValidIndex( cacheHandle ) )
  188. {
  189. // All characters must come out of the same font
  190. if ( winFont != FontManager().GetFontForChar( font, wch[i] ) )
  191. return false;
  192. // get the char details
  193. int a, b, c;
  194. winFont->GetCharABCWidths( wch[i], a, b, c );
  195. int fontWide = max( b, 1 );
  196. int fontTall = max( winFont->GetHeight(), 1 );
  197. if ( winFont->GetUnderlined() )
  198. {
  199. fontWide += ( a + c );
  200. }
  201. // Get a texture to render into
  202. int page, drawX, drawY, twide, ttall;
  203. if ( !AllocatePageForChar( fontWide, fontTall, page, drawX, drawY, twide, ttall ) )
  204. return false;
  205. // accumulate data to pass to GetCharsRGBA below
  206. newEntries[ numNewChars ].page = page;
  207. newEntries[ numNewChars ].drawX = drawX;
  208. newEntries[ numNewChars ].drawY = drawY;
  209. newChars[ numNewChars ].wch = wch[i];
  210. newChars[ numNewChars ].fontWide = fontWide;
  211. newChars[ numNewChars ].fontTall = fontTall;
  212. newChars[ numNewChars ].offset = 4*totalNewCharTexels;
  213. totalNewCharTexels += fontWide*fontTall;
  214. maxNewCharTexels = max( maxNewCharTexels, fontWide*fontTall );
  215. numNewChars++;
  216. // set the cache info
  217. cacheItem.page = page;
  218. // the 0.5 texel offset is done in CMatSystemTexture::SetMaterial() / CMatSystemSurface::StartDrawing()
  219. double adjust = 0.0f;
  220. cacheItem.texCoords[0] = (float)( (double)drawX / ((double)twide + adjust) );
  221. cacheItem.texCoords[1] = (float)( (double)drawY / ((double)ttall + adjust) );
  222. cacheItem.texCoords[2] = (float)( (double)(drawX + fontWide) / (double)twide );
  223. cacheItem.texCoords[3] = (float)( (double)(drawY + fontTall) / (double)ttall );
  224. m_CharCache.Insert(cacheItem);
  225. cacheHandle = m_CharCache.Find( cacheItem );
  226. Assert( m_CharCache.IsValidIndex( cacheHandle ) );
  227. }
  228. int page = m_CharCache[cacheHandle].page;
  229. textureID[i] = m_PageList[page].textureID[typePage];
  230. texCoords[i] = m_CharCache[cacheHandle].texCoords;
  231. }
  232. // Generate texture data for all newly-encountered characters
  233. if ( numNewChars > 0 )
  234. {
  235. #ifdef _X360
  236. if ( numNewChars > 1 )
  237. {
  238. MEM_ALLOC_CREDIT();
  239. // Use the 360 fast path that generates multiple characters at once
  240. int newCharDataSize = totalNewCharTexels*4;
  241. CUtlBuffer newCharData( newCharDataSize, newCharDataSize, 0 );
  242. unsigned char *pRGBA = (unsigned char *)newCharData.Base();
  243. winFont->GetCharsRGBA( newChars, numNewChars, pRGBA );
  244. // Copy the data into our font pages
  245. for ( int i = 0; i < numNewChars; i++ )
  246. {
  247. newChar_t & newChar = newChars[i];
  248. newPageEntry_t & newEntry = newEntries[i];
  249. // upload the new sub texture
  250. // NOTE: both textureIDs reference the same ITexture, so we're ok
  251. g_MatSystemSurface.DrawSetTexture( m_PageList[newEntry.page].textureID[typePage] );
  252. unsigned char *characterRGBA = pRGBA + newChar.offset;
  253. g_MatSystemSurface.DrawSetSubTextureRGBA( m_PageList[newEntry.page].textureID[typePage], newEntry.drawX, newEntry.drawY, characterRGBA, newChar.fontWide, newChar.fontTall );
  254. }
  255. }
  256. else
  257. #endif
  258. {
  259. // create a buffer for new characters to be rendered into
  260. int nByteCount = maxNewCharTexels * 4;
  261. unsigned char *pRGBA = (unsigned char *)_alloca( nByteCount );
  262. // Generate characters individually
  263. for ( int i = 0; i < numNewChars; i++ )
  264. {
  265. newChar_t & newChar = newChars[i];
  266. newPageEntry_t & newEntry = newEntries[i];
  267. // render the character into the buffer
  268. Q_memset( pRGBA, 0, nByteCount );
  269. winFont->GetCharRGBA( newChar.wch, newChar.fontWide, newChar.fontTall, pRGBA );
  270. if ( g_mat_texture_outline_fonts )
  271. {
  272. int width = newChar.fontWide;
  273. int height = newChar.fontTall;
  274. CPixelWriter pixelWriter;
  275. pixelWriter.SetPixelMemory( IMAGE_FORMAT_RGBA8888, pRGBA, width * sizeof( BGRA8888_t ) );
  276. for( int x = 0; x < width; x++ )
  277. {
  278. pixelWriter.Seek( x, 0 );
  279. pixelWriter.WritePixel( 255, 0, 255, 255 );
  280. pixelWriter.Seek( x, height - 1 );
  281. pixelWriter.WritePixel( 255, 0, 255, 255 );
  282. }
  283. for( int y = 0; y < height; y++ )
  284. {
  285. if ( y < 4 || y > height - 4 )
  286. {
  287. pixelWriter.Seek( 0, y );
  288. pixelWriter.WritePixel( 255, 0, 255, 255 );
  289. pixelWriter.Seek( width - 1, y );
  290. pixelWriter.WritePixel( 255, 0, 255, 255 );
  291. }
  292. }
  293. }
  294. // upload the new sub texture
  295. // NOTE: both textureIDs reference the same ITexture, so we're ok)
  296. g_MatSystemSurface.DrawSetTexture( m_PageList[newEntry.page].textureID[typePage] );
  297. g_MatSystemSurface.DrawSetSubTextureRGBA( m_PageList[newEntry.page].textureID[typePage], newEntry.drawX, newEntry.drawY, pRGBA, newChar.fontWide, newChar.fontTall );
  298. }
  299. }
  300. }
  301. }
  302. return true;
  303. }
  304. //-----------------------------------------------------------------------------
  305. // Creates font materials
  306. //-----------------------------------------------------------------------------
  307. void CFontTextureCache::CreateFontMaterials( Page_t &page, ITexture *pFontTexture, bool bitmapFont )
  308. {
  309. // The normal material
  310. KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
  311. pVMTKeyValues->SetInt( "$vertexcolor", 1 );
  312. pVMTKeyValues->SetInt( "$vertexalpha", 1 );
  313. pVMTKeyValues->SetInt( "$ignorez", 1 );
  314. pVMTKeyValues->SetInt( "$no_fullbright", 1 );
  315. pVMTKeyValues->SetInt( "$translucent", 1 );
  316. pVMTKeyValues->SetString( "$basetexture", pFontTexture->GetName() );
  317. IMaterial *pMaterial = g_pMaterialSystem->CreateMaterial( "__fontpage", pVMTKeyValues );
  318. pMaterial->Refresh();
  319. int typePageNonAdditive = (int)vgui::FONT_DRAW_NONADDITIVE-1;
  320. g_MatSystemSurface.DrawSetTextureMaterial( page.textureID[typePageNonAdditive], pMaterial );
  321. pMaterial->DecrementReferenceCount();
  322. // The additive material
  323. pVMTKeyValues = new KeyValues( "UnlitGeneric" );
  324. pVMTKeyValues->SetInt( "$vertexcolor", 1 );
  325. pVMTKeyValues->SetInt( "$vertexalpha", 1 );
  326. pVMTKeyValues->SetInt( "$ignorez", 1 );
  327. pVMTKeyValues->SetInt( "$no_fullbright", 1 );
  328. pVMTKeyValues->SetInt( "$translucent", 1 );
  329. pVMTKeyValues->SetInt( "$additive", 1 );
  330. pVMTKeyValues->SetString( "$basetexture", pFontTexture->GetName() );
  331. pMaterial = g_pMaterialSystem->CreateMaterial( "__fontpage_additive", pVMTKeyValues );
  332. pMaterial->Refresh();
  333. int typePageAdditive = (int)vgui::FONT_DRAW_ADDITIVE-1;
  334. if ( bitmapFont )
  335. {
  336. g_MatSystemSurface.DrawSetTextureMaterial( page.textureID[typePageAdditive], pMaterial );
  337. }
  338. else
  339. {
  340. g_MatSystemSurface.ReferenceProceduralMaterial( page.textureID[typePageAdditive], page.textureID[typePageNonAdditive], pMaterial );
  341. }
  342. pMaterial->DecrementReferenceCount();
  343. }
  344. //-----------------------------------------------------------------------------
  345. // Computes the page size given a character height
  346. //-----------------------------------------------------------------------------
  347. int CFontTextureCache::ComputePageType( int charTall ) const
  348. {
  349. for (int i = 0; i < FONT_PAGE_SIZE_COUNT; ++i)
  350. {
  351. if ( charTall < s_pFontPageSize[i] )
  352. return i;
  353. }
  354. return -1;
  355. }
  356. //-----------------------------------------------------------------------------
  357. // Purpose: allocates a new page for a given character
  358. //-----------------------------------------------------------------------------
  359. bool CFontTextureCache::AllocatePageForChar(int charWide, int charTall, int &pageIndex, int &drawX, int &drawY, int &twide, int &ttall)
  360. {
  361. // see if there is room in the last page for this character
  362. int nPageType = ComputePageType( charTall );
  363. if ( nPageType < 0 )
  364. {
  365. Assert( !"Font is too tall for texture cache of glyphs\n" );
  366. return false;
  367. }
  368. pageIndex = m_pCurrPage[nPageType];
  369. int nNextX = 0;
  370. bool bNeedsNewPage = true;
  371. if ( pageIndex > -1 )
  372. {
  373. Page_t &page = m_PageList[ pageIndex ];
  374. nNextX = page.nextX + charWide;
  375. // make sure we have room on the current line of the texture page
  376. if ( nNextX > page.wide )
  377. {
  378. // move down a line
  379. page.nextX = 0;
  380. nNextX = charWide;
  381. page.nextY += page.tallestCharOnLine;
  382. page.tallestCharOnLine = charTall;
  383. }
  384. page.tallestCharOnLine = max( page.tallestCharOnLine, (short)charTall );
  385. bNeedsNewPage = ((page.nextY + page.tallestCharOnLine) > page.tall);
  386. }
  387. if ( bNeedsNewPage )
  388. {
  389. // allocate a new page
  390. pageIndex = m_PageList.AddToTail();
  391. Page_t &newPage = m_PageList[pageIndex];
  392. m_pCurrPage[nPageType] = pageIndex;
  393. for (int i = 0; i < FONT_DRAW_TYPE_COUNT; ++i )
  394. {
  395. newPage.textureID[i] = g_MatSystemSurface.CreateNewTextureID( true );
  396. }
  397. newPage.maxFontHeight = s_pFontPageSize[nPageType];
  398. newPage.wide = TEXTURE_PAGE_WIDTH;
  399. newPage.tall = TEXTURE_PAGE_HEIGHT;
  400. newPage.nextX = 0;
  401. newPage.nextY = 0;
  402. newPage.tallestCharOnLine = charTall;
  403. nNextX = charWide;
  404. static int nFontPageId = 0;
  405. char pTextureName[64];
  406. Q_snprintf( pTextureName, 64, "__font_page_%d", nFontPageId );
  407. ++nFontPageId;
  408. MEM_ALLOC_CREDIT();
  409. ITexture *pTexture = g_pMaterialSystem->CreateProceduralTexture(
  410. pTextureName,
  411. TEXTURE_GROUP_VGUI,
  412. newPage.wide,
  413. newPage.tall,
  414. IMAGE_FORMAT_RGBA8888,
  415. TEXTUREFLAGS_POINTSAMPLE | TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT |
  416. TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY );
  417. CreateFontMaterials( newPage, pTexture );
  418. pTexture->DecrementReferenceCount();
  419. if ( IsPC() || !IsDebug() )
  420. {
  421. // clear the texture from the inital checkerboard to black
  422. // allocate for 32bpp format
  423. int nByteCount = TEXTURE_PAGE_WIDTH * TEXTURE_PAGE_HEIGHT * 4;
  424. unsigned char *pRGBA = (unsigned char *)_alloca( nByteCount );
  425. Q_memset( pRGBA, 0, nByteCount );
  426. int typePageNonAdditive = (int)(vgui::FONT_DRAW_NONADDITIVE)-1;
  427. g_MatSystemSurface.DrawSetTextureRGBA( newPage.textureID[typePageNonAdditive], pRGBA, newPage.wide, newPage.tall, false, false );
  428. }
  429. }
  430. // output the position
  431. Page_t &page = m_PageList[ pageIndex ];
  432. drawX = page.nextX;
  433. drawY = page.nextY;
  434. twide = page.wide;
  435. ttall = page.tall;
  436. // Update the next position to draw in
  437. page.nextX = nNextX + 1;
  438. return true;
  439. }