Counter Strike : Global Offensive Source Code
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.

585 lines
19 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #if !defined( _GAMECONSOLE ) && defined( _WIN32 )
  8. #define WIN32_LEAN_AND_MEAN
  9. #include <windows.h>
  10. #endif
  11. #include "vgui_surfacelib/fonttexturecache.h"
  12. #include "tier1/keyvalues.h"
  13. #include "materialsystem/itexture.h"
  14. #include "materialsystem/imaterial.h"
  15. #include "tier1/utlbuffer.h"
  16. #include "fmtstr.h"
  17. #include "vgui_surfacelib/texturedictionary.h"
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. #define TEXTURE_PAGE_WIDTH 256
  21. #define TEXTURE_PAGE_HEIGHT 256
  22. ConVar vgui_show_glyph_miss( "vgui_show_glyph_miss", "0", FCVAR_DEVELOPMENTONLY );
  23. //-----------------------------------------------------------------------------
  24. // Purpose: Constructor
  25. //-----------------------------------------------------------------------------
  26. CFontTextureCache::CFontTextureCache() : m_CharCache( 0, 256, CacheEntryLessFunc )
  27. {
  28. V_memset( m_CommonCharCache, 0, sizeof( m_CommonCharCache ) );
  29. Clear();
  30. }
  31. //-----------------------------------------------------------------------------
  32. // Purpose: Destructor
  33. //-----------------------------------------------------------------------------
  34. CFontTextureCache::~CFontTextureCache()
  35. {
  36. Clear();
  37. }
  38. //-----------------------------------------------------------------------------
  39. // Purpose:
  40. //-----------------------------------------------------------------------------
  41. void CFontTextureCache::SetPrefix( const char *pTexturePagePrefix )
  42. {
  43. m_TexturePagePrefix = pTexturePagePrefix;
  44. }
  45. //-----------------------------------------------------------------------------
  46. // Purpose: Resets the cache
  47. //-----------------------------------------------------------------------------
  48. void CFontTextureCache::Clear()
  49. {
  50. // remove all existing data
  51. m_CharCache.RemoveAll();
  52. for ( int i = 0; i < m_PageList.Count(); ++i )
  53. {
  54. if ( m_PageList[i].pPackedFontTextureCache )
  55. {
  56. delete m_PageList[i].pPackedFontTextureCache;
  57. }
  58. }
  59. m_PageList.RemoveAll();
  60. m_CurrPage = -1;
  61. m_FontPages.RemoveAll();
  62. m_FontPages.SetLessFunc( DefLessFunc( FontHandle_t ) );
  63. for ( int i = 0; i < ARRAYSIZE( m_CommonCharCache ); i++ )
  64. {
  65. delete m_CommonCharCache[i];
  66. m_CommonCharCache[i] = 0;
  67. }
  68. }
  69. //-----------------------------------------------------------------------------
  70. // Purpose: comparison function for cache entries
  71. //-----------------------------------------------------------------------------
  72. bool CFontTextureCache::CacheEntryLessFunc( CacheEntry_t const &lhs, CacheEntry_t const &rhs )
  73. {
  74. uint64 lhsLookupID = ( ((uint64)lhs.font) << 32 ) | ((uint64)lhs.wch);
  75. uint64 rhsLookupID = ( ((uint64)rhs.font) << 32 ) | ((uint64)rhs.wch);
  76. return lhsLookupID < rhsLookupID;
  77. }
  78. //-----------------------------------------------------------------------------
  79. // Purpose: returns the texture info for the given char & font
  80. //-----------------------------------------------------------------------------
  81. bool CFontTextureCache::GetTextureForChar( FontHandle_t font, FontDrawType_t type, wchar_t wch, int *textureID, float **texCoords )
  82. {
  83. // Ask for just one character
  84. return GetTextureForChars( font, type, &wch, textureID, texCoords, 1 );
  85. }
  86. //-----------------------------------------------------------------------------
  87. // Purpose: returns the texture info for the given char & font
  88. // This function copies in the texcoords out from the static into a preallocated passed in arg.
  89. //-----------------------------------------------------------------------------
  90. bool CFontTextureCache::GetTextureAndCoordsForChar( FontHandle_t font, FontDrawType_t type, wchar_t wch, int *textureID, float *texCoords )
  91. {
  92. // Ask for just one character
  93. float *textureCoords = NULL;
  94. bool bSuccess = GetTextureForChars( font, type, &wch, textureID, &textureCoords, 1 );
  95. if ( textureCoords )
  96. {
  97. texCoords[0] = textureCoords[0];
  98. texCoords[1] = textureCoords[1];
  99. texCoords[2] = textureCoords[2];
  100. texCoords[3] = textureCoords[3];
  101. }
  102. return bSuccess;
  103. }
  104. //-----------------------------------------------------------------------------
  105. // Purpose: returns the texture info for the given chars & font
  106. //-----------------------------------------------------------------------------
  107. bool CFontTextureCache::GetTextureForChars( FontHandle_t hFont, FontDrawType_t type, wchar_t *wch, int *textureID, float **texCoords, int numChars )
  108. {
  109. Assert( wch && textureID && texCoords );
  110. Assert( numChars >= 1 );
  111. if ( type == FONT_DRAW_DEFAULT )
  112. {
  113. type = FontManager().IsFontAdditive( hFont ) ? FONT_DRAW_ADDITIVE : FONT_DRAW_NONADDITIVE;
  114. }
  115. int typePage = (int)type - 1;
  116. typePage = clamp( typePage, 0, (int)FONT_DRAW_TYPE_COUNT - 1 );
  117. if ( FontManager().IsBitmapFont( hFont ) )
  118. {
  119. const int MAX_BITMAP_CHARS = 256;
  120. if ( numChars > MAX_BITMAP_CHARS )
  121. {
  122. // Increase MAX_BITMAP_CHARS
  123. Assert( 0 );
  124. return false;
  125. }
  126. for ( int i = 0; i < numChars; i++ )
  127. {
  128. static float sTexCoords[ 4*MAX_BITMAP_CHARS ];
  129. CBitmapFont *pWinFont;
  130. float left, top, right, bottom;
  131. int index;
  132. Page_t *pPage;
  133. pWinFont = reinterpret_cast< CBitmapFont* >( FontManager().GetFontForChar( hFont, wch[i] ) );
  134. if ( !pWinFont )
  135. {
  136. // bad font handle
  137. return false;
  138. }
  139. // get the texture coords
  140. pWinFont->GetCharCoords( wch[i], &left, &top, &right, &bottom );
  141. sTexCoords[i*4 + 0] = left;
  142. sTexCoords[i*4 + 1] = top;
  143. sTexCoords[i*4 + 2] = right;
  144. sTexCoords[i*4 + 3] = bottom;
  145. // find font handle in our list of ready pages
  146. index = m_FontPages.Find( hFont );
  147. if ( index == m_FontPages.InvalidIndex() )
  148. {
  149. // not found, create the texture id and its materials
  150. index = m_FontPages.Insert( hFont );
  151. pPage = &m_FontPages.Element( index );
  152. for (int type = 0; type < FONT_DRAW_TYPE_COUNT; ++type )
  153. {
  154. pPage->textureID[type] = TextureDictionary()->CreateTexture( false );
  155. }
  156. CreateFontMaterials( *pPage, pWinFont->GetTexturePage(), true );
  157. }
  158. texCoords[i] = &(sTexCoords[ i*4 ]);
  159. textureID[i] = m_FontPages.Element( index ).textureID[typePage];
  160. }
  161. }
  162. else
  163. {
  164. font_t *pWinFont = FontManager().GetFontForChar( hFont, wch[0] );
  165. if ( !pWinFont )
  166. {
  167. return false;
  168. }
  169. struct newPageEntry_t
  170. {
  171. int page; // The font page a new character will go in
  172. int drawX; // X location within the font page
  173. int drawY; // Y location within the font page
  174. };
  175. // Determine how many characters need to have their texture generated
  176. newChar_t *newChars = (newChar_t *)stackalloc( numChars*sizeof( newChar_t ) );
  177. newPageEntry_t *newEntries = (newPageEntry_t *)stackalloc( numChars*sizeof( newPageEntry_t ) );
  178. int numNewChars = 0;
  179. int maxNewCharTexels = 0;
  180. int totalNewCharTexels = 0;
  181. for ( int i = 0; i < numChars; i++ )
  182. {
  183. wchar_t wideChar = wch[i];
  184. int *pCachePage;
  185. float *pCacheCoords;
  186. // profiling dicatated that avoiding the naive font/char RB lookup was beneficial
  187. // instead waste a little memory to get all the western language chars to be direct
  188. if ( IsGameConsole() && wideChar < MAX_COMMON_CHARS && hFont < ARRAYSIZE( m_CommonCharCache ) )
  189. {
  190. // dominant amount of simple chars are instant direct lookup
  191. CommonChar_t *pCommonChars = m_CommonCharCache[hFont];
  192. if ( !pCommonChars )
  193. {
  194. // missing
  195. if ( pWinFont != FontManager().GetFontForChar( hFont, wideChar ) )
  196. {
  197. // all characters in string must come out of the same font
  198. return false;
  199. }
  200. // init and insert
  201. pCommonChars = new CommonChar_t;
  202. memset( pCommonChars, 0, sizeof( CommonChar_t ) );
  203. m_CommonCharCache[hFont] = pCommonChars;
  204. }
  205. pCachePage = &pCommonChars->details[wideChar].page;
  206. pCacheCoords = pCommonChars->details[wideChar].texCoords;
  207. }
  208. else
  209. {
  210. // for console only, either more fonts than expected (> 256 fonts!) or not a simple integer
  211. // want to keep this a direct lookup and not a search (which defeats the perf gain)
  212. AssertMsgOnce( !IsGameConsole() || hFont < ARRAYSIZE( m_CommonCharCache ), "CFontTextureCache: Unexpected hFont out-of-range\n" );
  213. // extended chars are a costlier lookup
  214. // page and char form a unique key to find in cache
  215. CacheEntry_t cacheItem;
  216. cacheItem.font = hFont;
  217. cacheItem.wch = wideChar;
  218. HCacheEntry cacheHandle = m_CharCache.Find( cacheItem );
  219. if ( !m_CharCache.IsValidIndex( cacheHandle ) )
  220. {
  221. // missing
  222. if ( pWinFont != FontManager().GetFontForChar( hFont, wideChar ) )
  223. {
  224. // all characters in string must come out of the same font
  225. return false;
  226. }
  227. // init and insert
  228. cacheItem.texCoords[0] = 0;
  229. cacheItem.texCoords[1] = 0;
  230. cacheItem.texCoords[2] = 0;
  231. cacheItem.texCoords[3] = 0;
  232. cacheHandle = m_CharCache.Insert( cacheItem );
  233. Assert( m_CharCache.IsValidIndex( cacheHandle ) );
  234. }
  235. pCachePage = &m_CharCache[cacheHandle].page;
  236. pCacheCoords = m_CharCache[cacheHandle].texCoords;
  237. }
  238. if ( pCacheCoords[2] == 0 && pCacheCoords[3] == 0 )
  239. {
  240. // invalid page, setup for page allocation
  241. // get the char details
  242. int a, b, c;
  243. pWinFont->GetCharABCWidths( wideChar, a, b, c );
  244. int fontWide = MAX( b, 1 );
  245. int fontTall = MAX( pWinFont->GetHeight(), 1 );
  246. if ( pWinFont->GetUnderlined() )
  247. {
  248. fontWide += ( a + c );
  249. }
  250. // Get a texture to render into
  251. int page, drawX, drawY, twide, ttall;
  252. if ( !AllocatePageForChar( fontWide, fontTall, page, drawX, drawY, twide, ttall ) )
  253. {
  254. return false;
  255. }
  256. // accumulate data to pass to GetCharsRGBA below
  257. newEntries[numNewChars].page = page;
  258. newEntries[numNewChars].drawX = drawX;
  259. newEntries[numNewChars].drawY = drawY;
  260. newChars[numNewChars].wch = wideChar;
  261. newChars[numNewChars].fontWide = fontWide;
  262. newChars[numNewChars].fontTall = fontTall;
  263. newChars[numNewChars].offset = 4*totalNewCharTexels;
  264. totalNewCharTexels += fontWide*fontTall;
  265. maxNewCharTexels = MAX( maxNewCharTexels, fontWide*fontTall );
  266. numNewChars++;
  267. // the 0.5 texel offset is done in CMatSystemTexture::SetMaterial()
  268. pCacheCoords[0] = (float)( (double)drawX / ((double)twide) );
  269. pCacheCoords[1] = (float)( (double)drawY / ((double)ttall) );
  270. pCacheCoords[2] = (float)( (double)(drawX + fontWide) / (double)twide );
  271. pCacheCoords[3] = (float)( (double)(drawY + fontTall) / (double)ttall );
  272. *pCachePage = page;
  273. }
  274. // give data to caller
  275. textureID[i] = m_PageList[*pCachePage].textureID[typePage];
  276. texCoords[i] = pCacheCoords;
  277. }
  278. // Generate texture data for all newly-encountered characters
  279. if ( numNewChars > 0 )
  280. {
  281. if ( vgui_show_glyph_miss.GetBool() )
  282. {
  283. char *pMissString = (char *)stackalloc( numNewChars * sizeof( char ) );
  284. char *pString = pMissString;
  285. for ( int i = 0; i < numNewChars; i++ )
  286. {
  287. // build a string representative enough for debugging puproses
  288. wchar_t wch = newChars[i].wch;
  289. if ( V_isprint( wch ) )
  290. {
  291. *pString++ = (char)wch;
  292. }
  293. else
  294. {
  295. *pString++ = '?';
  296. }
  297. }
  298. *pString = '\0';
  299. const char *pMsg = CFmtStr( "Glyph Miss: FontHandle_t:0x%8.8x (%s), %s (0x%x)\n", (int)hFont, pWinFont->GetName(), pMissString, pMissString[0] );
  300. if ( IsGameConsole() )
  301. {
  302. // valid on xbox, and really want this spew treated like console spew
  303. Warning( "%s", pMsg );
  304. }
  305. else
  306. {
  307. // debugger output only, to prevent any reentrant glyph miss as a result of spewing
  308. Plat_DebugString( pMsg );
  309. }
  310. }
  311. if ( IsGameConsole() && numNewChars > 1 )
  312. {
  313. MEM_ALLOC_CREDIT();
  314. // Use the 360 fast path that generates multiple characters at once
  315. int newCharDataSize = totalNewCharTexels*4;
  316. CUtlBuffer newCharData( 0, newCharDataSize, CUtlBuffer::READ_ONLY );
  317. unsigned char *pRGBA = (unsigned char *)newCharData.Base();
  318. #if defined( _X360 ) || defined( _PS3 )
  319. pWinFont->GetCharsRGBA( newChars, numNewChars, pRGBA );
  320. #endif
  321. // Copy the data into our font pages
  322. for ( int i = 0; i < numNewChars; i++ )
  323. {
  324. newChar_t &newChar = newChars[i];
  325. newPageEntry_t &newEntry = newEntries[i];
  326. // upload the new sub texture
  327. // NOTE: both textureIDs reference the same ITexture, so we're ok
  328. unsigned char *characterRGBA = pRGBA + newChar.offset;
  329. TextureDictionary()->SetSubTextureRGBA( m_PageList[newEntry.page].textureID[typePage], newEntry.drawX, newEntry.drawY, characterRGBA, newChar.fontWide, newChar.fontTall );
  330. }
  331. }
  332. else
  333. {
  334. // create a buffer for new characters to be rendered into
  335. int nByteCount = maxNewCharTexels * 4;
  336. unsigned char *pRGBA = (unsigned char *)stackalloc( nByteCount * sizeof( unsigned char ) );
  337. // Generate characters individually
  338. for ( int i = 0; i < numNewChars; i++ )
  339. {
  340. newChar_t &newChar = newChars[i];
  341. newPageEntry_t &newEntry = newEntries[i];
  342. // render the character into the buffer
  343. Q_memset( pRGBA, 0, nByteCount );
  344. pWinFont->GetCharRGBA( newChar.wch, newChar.fontWide, newChar.fontTall, pRGBA );
  345. // Make the char white if we are in source 2
  346. if ( !g_pMaterialSystem )
  347. {
  348. for ( int i = 0; i < nByteCount; i += 4 )
  349. {
  350. pRGBA[i+0] = pRGBA[i+1] = pRGBA[i+2] = 255;
  351. }
  352. }
  353. // upload the new sub texture
  354. // NOTE: both textureIDs reference the same ITexture, so we're ok
  355. TextureDictionary()->SetSubTextureRGBA( m_PageList[newEntry.page].textureID[typePage], newEntry.drawX, newEntry.drawY, pRGBA, newChar.fontWide, newChar.fontTall );
  356. }
  357. }
  358. }
  359. }
  360. return true;
  361. }
  362. //-----------------------------------------------------------------------------
  363. // Creates font materials
  364. //-----------------------------------------------------------------------------
  365. void CFontTextureCache::CreateFontMaterials( Page_t &page, ITexture *pFontTexture, bool bitmapFont )
  366. {
  367. // The normal material
  368. KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
  369. pVMTKeyValues->SetInt( "$vertexcolor", 1 );
  370. pVMTKeyValues->SetInt( "$vertexalpha", 1 );
  371. pVMTKeyValues->SetInt( "$ignorez", 1 );
  372. pVMTKeyValues->SetInt( "$no_fullbright", 1 );
  373. pVMTKeyValues->SetInt( "$translucent", 1 );
  374. pVMTKeyValues->SetString( "$basetexture", pFontTexture->GetName() );
  375. CUtlString materialName = m_TexturePagePrefix + "__fontpage";
  376. Assert( g_pMaterialSystem );
  377. IMaterial *pMaterial = g_pMaterialSystem->CreateMaterial( materialName, pVMTKeyValues );
  378. pMaterial->Refresh();
  379. int typePageNonAdditive = (int)FONT_DRAW_NONADDITIVE-1;
  380. TextureDictionary()->BindTextureToMaterial( page.textureID[typePageNonAdditive], pMaterial );
  381. pMaterial->DecrementReferenceCount();
  382. // The additive material
  383. pVMTKeyValues = new KeyValues( "UnlitGeneric" );
  384. pVMTKeyValues->SetInt( "$vertexcolor", 1 );
  385. pVMTKeyValues->SetInt( "$vertexalpha", 1 );
  386. pVMTKeyValues->SetInt( "$ignorez", 1 );
  387. pVMTKeyValues->SetInt( "$no_fullbright", 1 );
  388. pVMTKeyValues->SetInt( "$translucent", 1 );
  389. pVMTKeyValues->SetInt( "$additive", 1 );
  390. pVMTKeyValues->SetString( "$basetexture", pFontTexture->GetName() );
  391. CUtlString addmaterialName = m_TexturePagePrefix + "__fontpage_additive";
  392. pMaterial = g_pMaterialSystem->CreateMaterial( addmaterialName.String(), pVMTKeyValues );
  393. pMaterial->Refresh();
  394. int typePageAdditive = (int)FONT_DRAW_ADDITIVE-1;
  395. if ( bitmapFont )
  396. {
  397. TextureDictionary()->BindTextureToMaterial( page.textureID[typePageAdditive], pMaterial );
  398. }
  399. else
  400. {
  401. TextureDictionary()->BindTextureToMaterialReference( page.textureID[typePageAdditive], page.textureID[typePageNonAdditive], pMaterial);
  402. }
  403. pMaterial->DecrementReferenceCount();
  404. }
  405. //-----------------------------------------------------------------------------
  406. // Purpose: allocates a new page for a given character
  407. //-----------------------------------------------------------------------------
  408. bool CFontTextureCache::AllocatePageForChar( int charWide, int charTall, int &pageIndex, int &drawX, int &drawY, int &twide, int &ttall )
  409. {
  410. // Catch the case where the glyph is too tall for the page
  411. if ( charTall > TEXTURE_PAGE_HEIGHT )
  412. return false;
  413. // See if there is room in the last page for this character
  414. pageIndex = m_CurrPage;
  415. bool bNeedsNewPage = true;
  416. int nodeIndex = -1;
  417. Rect_t glpyhRect;
  418. glpyhRect.x = 0;
  419. glpyhRect.y = 0;
  420. glpyhRect.width = charWide;
  421. glpyhRect.height = charTall;
  422. if ( pageIndex > -1 )
  423. {
  424. // Let's use r/b tree to find a good spot.
  425. nodeIndex = m_PageList[pageIndex].pPackedFontTextureCache->InsertRect( glpyhRect );
  426. bNeedsNewPage = ( nodeIndex == -1 );
  427. }
  428. if ( bNeedsNewPage )
  429. {
  430. // allocate a new page
  431. pageIndex = m_PageList.AddToTail();
  432. Page_t &newPage = m_PageList[pageIndex];
  433. m_CurrPage = pageIndex;
  434. for (int i = 0; i < FONT_DRAW_TYPE_COUNT; ++i )
  435. {
  436. newPage.textureID[i] = TextureDictionary()->CreateTexture( true );
  437. }
  438. newPage.pPackedFontTextureCache = new CTexturePacker( TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, ( IsGameConsole() ? 0 : 1 ) );
  439. nodeIndex = newPage.pPackedFontTextureCache->InsertRect( glpyhRect );
  440. Assert( nodeIndex != -1 );
  441. static int nFontPageId = 0;
  442. char pTextureName[64];
  443. Q_snprintf( pTextureName, 64, "%s__font_page_%d", m_TexturePagePrefix.String(), nFontPageId );
  444. ++nFontPageId;
  445. MEM_ALLOC_CREDIT();
  446. if ( g_pMaterialSystem )
  447. {
  448. ITexture *pTexture = AllocateNewPage( pTextureName );
  449. CreateFontMaterials( newPage, pTexture );
  450. pTexture->DecrementReferenceCount();
  451. }
  452. if ( IsPC() || !IsDebug() )
  453. {
  454. // clear the texture from the inital checkerboard to black
  455. // allocate for 32bpp format
  456. int nByteCount = TEXTURE_PAGE_WIDTH * TEXTURE_PAGE_HEIGHT * 4;
  457. CUtlMemory<unsigned char> mRGBA;
  458. mRGBA.EnsureCapacity( nByteCount );
  459. //Q_memset( mRGBA.Base(), 0, nByteCount );
  460. // Clear to white, full alpha.
  461. for ( int i = 0; i < nByteCount; i += 4 )
  462. {
  463. mRGBA[i+0] = mRGBA[i+1] = mRGBA[i+2] = 255;
  464. mRGBA[i+3] = 0;
  465. }
  466. int typePageNonAdditive = (int)(FONT_DRAW_NONADDITIVE)-1;
  467. TextureDictionary()->SetTextureRGBAEx( newPage.textureID[typePageNonAdditive], ( const char* )mRGBA.Base(),
  468. TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, IMAGE_FORMAT_RGBA8888, k_ETextureScalingPointSample );
  469. // Note, in rendersystem2 we do not have materials, as we actually have 2 diff textures.
  470. if ( !g_pMaterialSystem )
  471. {
  472. int typePageAdditive = (int)(FONT_DRAW_ADDITIVE)-1;
  473. newPage.textureID[typePageAdditive] = newPage.textureID[typePageNonAdditive];
  474. }
  475. }
  476. }
  477. // output the position
  478. Page_t &page = m_PageList[ pageIndex ];
  479. Assert( nodeIndex != -1 );
  480. const CTexturePacker::TreeEntry_t &newEntry = page.pPackedFontTextureCache->GetEntry( nodeIndex );
  481. drawX = newEntry.rc.x;
  482. drawY = newEntry.rc.y;
  483. twide = TEXTURE_PAGE_WIDTH;
  484. ttall = TEXTURE_PAGE_HEIGHT;
  485. return true;
  486. }
  487. //-----------------------------------------------------------------------------
  488. // Purpose: allocates a new page
  489. //-----------------------------------------------------------------------------
  490. ITexture *CFontTextureCache::AllocateNewPage( char *pTextureName )
  491. {
  492. Assert( g_pMaterialSystem );
  493. ITexture *pTexture = g_pMaterialSystem->CreateProceduralTexture(
  494. pTextureName,
  495. TEXTURE_GROUP_VGUI,
  496. TEXTURE_PAGE_WIDTH,
  497. TEXTURE_PAGE_HEIGHT,
  498. IMAGE_FORMAT_RGBA8888,
  499. TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT |
  500. TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY );
  501. return pTexture;
  502. }