Changed: New font texture implementation

--HG--
branch : develop
This commit is contained in:
Nimetu 2018-10-24 14:22:35 +03:00
parent ab6467f64c
commit e5b7064a4d
10 changed files with 637 additions and 329 deletions

View file

@ -178,6 +178,10 @@ public:
CVertexBuffer Vertices; CVertexBuffer Vertices;
CMaterial *Material; CMaterial *Material;
CRGBA Color; CRGBA Color;
ucstring Text;
uint32 CacheVersion;
/// The width of the string, in pixels (eg: 30) /// The width of the string, in pixels (eg: 30)
float StringWidth; float StringWidth;
/// The height of the string, in pixels (eg: 10) /// The height of the string, in pixels (eg: 10)
@ -223,6 +227,7 @@ public:
*/ */
CComputedString (bool bSetupVB=true) CComputedString (bool bSetupVB=true)
{ {
CacheVersion = 0;
StringWidth = 0; StringWidth = 0;
StringHeight = 0; StringHeight = 0;
if (bSetupVB) if (bSetupVB)

View file

@ -74,6 +74,8 @@ public:
uint32 getUID() { return _UID; } uint32 getUID() { return _UID; }
std::string getFontFileName() const;
private: private:
static uint32 _FontGeneratorCounterUID; static uint32 _FontGeneratorCounterUID;

View file

@ -59,6 +59,9 @@ class CFontManager
CSmartPtr<CMaterial> _MatFont; CSmartPtr<CMaterial> _MatFont;
CSmartPtr<CTextureFont> _TexFont; CSmartPtr<CTextureFont> _TexFont;
// Keep track number of textures created to properly report cache version
uint32 _TexCacheNr;
public: public:
/** /**
@ -71,6 +74,7 @@ public:
_NbChar = 0; _NbChar = 0;
_MatFont = NULL; _MatFont = NULL;
_TexFont = NULL; _TexFont = NULL;
_TexCacheNr = 0;
} }
@ -94,7 +98,6 @@ public:
*/ */
CMaterial* getFontMaterial(); CMaterial* getFontMaterial();
/** /**
* Compute primitive blocks and materials of each character of * Compute primitive blocks and materials of each character of
* the string. * the string.
@ -152,7 +155,8 @@ public:
void dumpCache (const char *filename) void dumpCache (const char *filename)
{ {
_TexFont->dumpTextureFont (filename); if (_TexFont)
_TexFont->dumpTextureFont (filename);
} }
/** /**
@ -160,6 +164,15 @@ public:
*/ */
void invalidate(); void invalidate();
// get font atlas rebuild count
uint32 getCacheVersion() const
{
if (_TexFont)
return (_TexFont->getCacheVersion() << 16) + _TexCacheNr;
return 0;
}
}; };

View file

@ -150,6 +150,10 @@ public:
{ {
nlassert (index < _CacheStrings.size()); nlassert (index < _CacheStrings.size());
CComputedString &rCS = _CacheStrings[index]; CComputedString &rCS = _CacheStrings[index];
if (rCS.CacheVersion != _FontManager->getCacheVersion())
{
computeString(rCS.Text, rCS);
}
if (_Shaded) if (_Shaded)
{ {
CRGBA bkup = rCS.Color; CRGBA bkup = rCS.Color;
@ -184,6 +188,10 @@ public:
{ {
nlassert (index < _CacheStrings.size()); nlassert (index < _CacheStrings.size());
CComputedString &rCS = _CacheStrings[index]; CComputedString &rCS = _CacheStrings[index];
if (rCS.CacheVersion != _FontManager->getCacheVersion())
{
computeString(rCS.Text, rCS);
}
if(_Shaded) if(_Shaded)
{ {
CRGBA bkup = rCS.Color; CRGBA bkup = rCS.Color;
@ -218,6 +226,11 @@ public:
{ {
nlassert (index < _CacheStrings.size()); nlassert (index < _CacheStrings.size());
CComputedString &rCS = _CacheStrings[index]; CComputedString &rCS = _CacheStrings[index];
if (rCS.CacheVersion != _FontManager->getCacheVersion())
{
computeString(rCS.Text, rCS);
}
if (_Shaded) if (_Shaded)
{ {
CRGBA bkup = rCS.Color; CRGBA bkup = rCS.Color;

View file

@ -18,6 +18,7 @@
#define NL_TEXTURE_FONT_H #define NL_TEXTURE_FONT_H
#include "nel/misc/types_nl.h" #include "nel/misc/types_nl.h"
#include "nel/misc/rect.h"
#include "nel/3d/texture.h" #include "nel/3d/texture.h"
namespace NL3D namespace NL3D
@ -25,9 +26,6 @@ namespace NL3D
class CFontGenerator; class CFontGenerator;
#define TEXTUREFONT_NBCATEGORY 5 // Config 1
//#define TEXTUREFONT_NBCATEGORY 4
// **************************************************************************** // ****************************************************************************
/** /**
* CTextureFont * CTextureFont
@ -37,32 +35,59 @@ class CTextureFont : public ITexture
public: public:
struct SLetterInfo // Holds info for glyphs rendered on atlas
struct SGlyphInfo
{ {
// To generate the letter // font atlas info
ucchar Char; uint32 CacheVersion;
CFontGenerator *FontGenerator;
// atlas region with padding
uint32 X, Y, W, H;
// rendered glyph size without padding
uint32 CharWidth;
uint32 CharHeight;
// UV coords for rendered glyph without padding
float U0, V0, U1, V1;
uint32 GlyphIndex;
sint Size; sint Size;
bool Embolden; bool Embolden;
bool Oblique; bool Oblique;
CFontGenerator *FontGenerator;
SGlyphInfo()
: CacheVersion(0),
U0(0.f), V0(0.f), U1(0.f), V1(0.f),
X(0), Y(0), W(0), H(0), CharWidth(0), CharHeight(0),
GlyphIndex(0), Size(0), Embolden(false), Oblique(false), FontGenerator(NULL)
{
}
};
// The less recently used infos // Holds info for glyphs displayed on screen
SLetterInfo *Next, *Prev; struct SLetterInfo
{
ucchar Char;
sint Size;
bool Embolden;
bool Oblique;
CFontGenerator *FontGenerator;
uint Cat; // 8x8, 16x16, 24x24, 32x32 uint32 GlyphIndex;
uint32 CharWidth; // Displayed glyph height
////////////////////////////////////////////////////////////////////// uint32 CharHeight; // Displayed glyph height
float U ,V;
uint32 CharWidth;
uint32 CharHeight;
uint32 GlyphIndex; // number of the character in the this font
sint32 Top; // Distance between origin and top of the texture sint32 Top; // Distance between origin and top of the texture
sint32 Left; // Distance between origin and left of the texture sint32 Left; // Distance between origin and left of the texture
sint32 AdvX; // Advance to the next caracter sint32 AdvX; // Advance to the next caracter
SLetterInfo():Char(0), FontGenerator(NULL), Size(0), Embolden(false), Oblique(false), Next(NULL), Prev(NULL), Cat(0), CharWidth(0), CharHeight(0), GlyphIndex(0), Top(0), Left(0), AdvX(0) SGlyphInfo* glyph;
SLetterInfo()
: Char(0), Size(0), Embolden(false), Oblique(false), FontGenerator(NULL),
GlyphIndex(0), CharWidth(0), CharHeight(0), Top(0), Left(0), AdvX(0),
glyph(NULL)
{ {
} }
}; };
@ -70,14 +95,13 @@ public:
struct SLetterKey struct SLetterKey
{ {
ucchar Char; ucchar Char;
CFontGenerator *FontGenerator;
sint Size; sint Size;
bool Embolden; bool Embolden;
bool Oblique; bool Oblique;
CFontGenerator *FontGenerator;
// Does not use FontGenerator in return value
uint32 getVal(); uint32 getVal();
//bool operator < (const SLetterKey&k) const;
//bool operator == (const SLetterKey&k) const;
SLetterKey():Char(0), FontGenerator(NULL), Size(0), Embolden(false), Oblique(false) SLetterKey():Char(0), FontGenerator(NULL), Size(0), Embolden(false), Oblique(false)
{ {
@ -96,19 +120,76 @@ public:
void doGenerate (bool async = false); void doGenerate (bool async = false);
// This function manage the cache if the letter wanted does not exist // This function manage the cache if the letter wanted does not exist
SLetterInfo* getLetterInfo (SLetterKey& k); // \param render Set to true if letter is currently visible on screen
SLetterInfo* getLetterInfo (SLetterKey& k, bool render);
void dumpTextureFont (const char *filename); void dumpTextureFont (const char *filename);
// Version is increased with each rebuild of font atlas
uint32 getCacheVersion() const { return _CacheVersion; }
private: private:
uint32 _CacheVersion;
// current texture size
uint32 _TextureSizeX;
uint32 _TextureSizeY;
// maximum texture size allowed
uint32 _TextureMaxW;
uint32 _TextureMaxH;
// padding around glyphs
uint8 _PaddingL, _PaddingT;
uint8 _PaddingR, _PaddingB;
// To find a letter in the texture // To find a letter in the texture
std::map<uint32, SLetterInfo*> Accel;
std::vector<SLetterInfo> Letters[TEXTUREFONT_NBCATEGORY]; // Keep track of available space in main texture
SLetterInfo *Front[TEXTUREFONT_NBCATEGORY], *Back[TEXTUREFONT_NBCATEGORY]; std::vector<NLMISC::CRect> _AtlasNodes;
void rebuildLetter (sint cat, sint x, sint y); std::vector <SLetterInfo> _Letters;
// lookup letter from letter cache or create new
SLetterInfo* findLetter(SLetterKey& k, bool insert);
// lower/upper bound of glyphs to render, sizes outside are scaled bitmaps
uint _MinGlyphSize;
uint _MaxGlyphSize;
// start using size stem from this font size
uint _GlyphSizeStepMin;
// every n'th font size is rendered, intermediates are using bitmap scaling
uint _GlyphSizeStep;
// rendered glyph cache
std::list<SGlyphInfo> _GlyphCache;
SGlyphInfo* findLetterGlyph(SLetterInfo *letter, bool insert);
// render letter glyph into glyph cache
SGlyphInfo* renderLetterGlyph(SLetterInfo *letter, uint32 bitmapFontSize);
// copy glyph bitmap into texture and invalidate that region
void copyGlyphBitmap(uint8* bitmap, uint32 bitmapW, uint32 bitmapH, uint32 atlasX, uint32 atlasY);
// Find best fit for WxH rect in atlas
uint fitRegion(uint index, uint width, uint height);
// Return top/left from font texture or false if there is no more room
bool reserveAtlas(const uint32 width, const uint32 height, uint32 &x, uint32 &y);
// repack glyphs, resize texture, and invalidate unused glyphs.
void repackAtlas();
void repackAtlas(uint32 width, uint32 height);
// resize texture,
bool resizeAtlas();
// remove all glyphs from atlas, clear glyph cache, letter info is kept
void clearAtlas();
// if return true: newW, newH contains next size font atlas should be resized
// if return false: _TextureMaxW and _TextureMaxH is reached
bool getNextTextureSize(uint32 &newW, uint32 &newH) const;
/// Todo: serialize a font texture. /// Todo: serialize a font texture.
public: public:

View file

@ -81,6 +81,11 @@ const char *CFontGenerator::getFT2Error(FT_Error fte)
return ukn; return ukn;
} }
std::string CFontGenerator::getFontFileName() const
{
return _FontFileName;
}
CFontGenerator *newCFontGenerator(const std::string &fontFileName) CFontGenerator *newCFontGenerator(const std::string &fontFileName)
{ {
return new CFontGenerator(fontFileName); return new CFontGenerator(fontFileName);

View file

@ -46,6 +46,7 @@ CMaterial* CFontManager::getFontMaterial()
if (_TexFont == NULL) if (_TexFont == NULL)
{ {
_TexFont = new CTextureFont; _TexFont = new CTextureFont;
_TexCacheNr++;
} }
if (_MatFont == NULL) if (_MatFont == NULL)
@ -142,11 +143,17 @@ void CFontManager::computeString (const ucstring &s,
sint32 nMaxZ = -1000000, nMinZ = 1000000; sint32 nMaxZ = -1000000, nMinZ = 1000000;
output.StringHeight = 0; output.StringHeight = 0;
// save string info for later rebuild as needed
output.Text = s;
output.CacheVersion = getCacheVersion();
uint j = 0; uint j = 0;
{ {
CVertexBufferReadWrite vba; CVertexBufferReadWrite vba;
output.Vertices.lock (vba); output.Vertices.lock (vba);
hlfPixScrW = 0.f;
hlfPixScrH = 0.f;
// For all chars // For all chars
for (uint i = 0; i < s.size(); i++) for (uint i = 0; i < s.size(); i++)
@ -157,38 +164,43 @@ void CFontManager::computeString (const ucstring &s,
k.Size = fontSize; k.Size = fontSize;
k.Embolden = embolden; k.Embolden = embolden;
k.Oblique = oblique; k.Oblique = oblique;
CTextureFont::SLetterInfo *pLI = pTexFont->getLetterInfo (k); // render letter
CTextureFont::SLetterInfo *pLI = pTexFont->getLetterInfo (k, true);
if(pLI != NULL) if(pLI != NULL)
{ {
if ((pLI->CharWidth > 0) && (pLI->CharHeight > 0)) if (pLI->glyph)
{ {
// If letter is heavily upscaled, then there is noticeable clipping on edges
// fixing UV will make it bit better
if ((pLI->Size >> 1) > pLI->glyph->Size)
{
hlfPixTexW = 0.5f * TexRatioW;
hlfPixTexH = 0.5f * TexRatioH;
}
// Creating vertices // Creating vertices
dx = pLI->Left; dx = pLI->Left;
dz = -((sint32)pLI->CharHeight-(sint32)(pLI->Top)); dz = -((sint32)pLI->CharHeight - (sint32)(pLI->Top));
u1 = pLI->U - hlfPixTexW;
v1 = pLI->V - hlfPixTexH;
u2 = pLI->U + ((float)pLI->CharWidth) * TexRatioW + hlfPixTexW;
v2 = pLI->V + ((float)pLI->CharHeight) * TexRatioH + hlfPixTexH;
x1 = (penx + dx) - hlfPixScrW; x1 = (penx + dx) - hlfPixScrW;
z1 = (penz + dz) - hlfPixScrH; z1 = (penz + dz) - hlfPixScrH;
x2 = (penx + dx + (sint32)pLI->CharWidth) + hlfPixScrW; x2 = (penx + dx + (sint32)pLI->CharWidth) + hlfPixScrW;
z2 = (penz + dz + (sint32)pLI->CharHeight) + hlfPixScrH; z2 = (penz + dz + (sint32)pLI->CharHeight) + hlfPixScrH;
vba.setVertexCoord (j, x1, 0, z1); vba.setVertexCoord (j, x1, 0, z1);
vba.setTexCoord (j, 0, u1, v2); vba.setTexCoord (j, 0, pLI->glyph->U0-hlfPixTexW, pLI->glyph->V1+hlfPixTexH);
++j; ++j;
vba.setVertexCoord (j, x2, 0, z1); vba.setVertexCoord (j, x2, 0, z1);
vba.setTexCoord (j, 0, u2, v2); vba.setTexCoord (j, 0, pLI->glyph->U1+hlfPixTexW, pLI->glyph->V1+hlfPixTexH);
++j; ++j;
vba.setVertexCoord (j, x2, 0, z2); vba.setVertexCoord (j, x2, 0, z2);
vba.setTexCoord (j, 0, u2, v1); vba.setTexCoord (j, 0, pLI->glyph->U1+hlfPixTexW, pLI->glyph->V0-hlfPixTexH);
++j; ++j;
vba.setVertexCoord (j, x1, 0, z2); vba.setVertexCoord (j, x1, 0, z2);
vba.setTexCoord (j, 0, u1, v1); vba.setTexCoord (j, 0, pLI->glyph->U0-hlfPixTexW, pLI->glyph->V0-hlfPixTexH);
++j; ++j;
// String Bound // String Bound
@ -245,6 +257,19 @@ void CFontManager::computeStringInfo ( const ucstring &s,
{ {
output.Color = color; output.Color = color;
// save string info for later rebuild as needed
output.Text = s;
output.CacheVersion = 0;
if (s.empty())
{
output.StringWidth = 0.f;
output.StringHeight = 0;
output.StringLine = 0;
return;
}
// resize fontSize if window not of 800x600. // resize fontSize if window not of 800x600.
if (keep800x600Ratio) if (keep800x600Ratio)
{ {
@ -273,7 +298,7 @@ void CFontManager::computeStringInfo ( const ucstring &s,
k.Size = fontSize; k.Size = fontSize;
k.Embolden = embolden; k.Embolden = embolden;
k.Oblique = oblique; k.Oblique = oblique;
pLI = pTexFont->getLetterInfo (k); pLI = pTexFont->getLetterInfo (k, false);
if(pLI != NULL) if(pLI != NULL)
{ {
if ((pLI->CharWidth > 0) && (pLI->CharHeight > 0)) if ((pLI->CharWidth > 0) && (pLI->CharHeight > 0))
@ -318,7 +343,11 @@ void CFontManager::invalidate()
{ {
if (_TexFont) if (_TexFont)
_TexFont = NULL; _TexFont = NULL;
_TexFont = new CTextureFont; _TexFont = new CTextureFont;
_TexCacheNr++;
getFontMaterial()->setTexture(0, _TexFont);
} }

View file

@ -74,25 +74,9 @@ uint32 CTextContext::textPush (const char *format, ...)
char *str; char *str;
NLMISC_CONVERT_VARGS (str, format, NLMISC::MaxCStringSize); NLMISC_CONVERT_VARGS (str, format, NLMISC::MaxCStringSize);
if (_CacheNbFreePlaces == 0) ucstring uc;
{ uc.fromUtf8((const char *)str);
CComputedString csTmp; return textPush(uc);
_CacheStrings.push_back (csTmp);
if (_CacheFreePlaces.empty())
_CacheFreePlaces.resize (1);
_CacheFreePlaces[0] = (uint32)_CacheStrings.size()-1;
_CacheNbFreePlaces = 1;
}
// compute the string.
uint32 index = _CacheFreePlaces[_CacheNbFreePlaces-1];
CComputedString &strToFill = _CacheStrings[index];
_FontManager->computeString (str, _FontGen, _Color, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio);
_CacheNbFreePlaces--;
return index;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -115,8 +99,10 @@ uint32 CTextContext::textPush (const ucstring &str)
uint32 index = _CacheFreePlaces[_CacheNbFreePlaces-1]; uint32 index = _CacheFreePlaces[_CacheNbFreePlaces-1];
nlassert (index < _CacheStrings.size()); nlassert (index < _CacheStrings.size());
CComputedString &strToFill = _CacheStrings[index]; CComputedString &strToFill = _CacheStrings[index];
_FontManager->computeString (str, _FontGen, _Color
, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio); _FontManager->computeString (str, _FontGen, _Color, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio);
// just compute letters, glyphs are rendered on demand before first draw
//_FontManager->computeStringInfo(str, _FontGen, _Color, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio);
_CacheNbFreePlaces--; _CacheNbFreePlaces--;

View file

@ -23,7 +23,7 @@
#include "nel/misc/common.h" #include "nel/misc/common.h"
#include "nel/misc/rect.h" #include "nel/misc/rect.h"
#include "nel/misc/file.h" #include "nel/misc/file.h"
#include "nel/misc/path.h"
using namespace std; using namespace std;
using namespace NLMISC; using namespace NLMISC;
@ -35,37 +35,14 @@ using namespace NLMISC;
namespace NL3D namespace NL3D
{ {
// Config 1
const int TextureSizeX = 1024;
const int TextureSizeY = 1024; // If change this value -> change NbLine too
const int Categories[TEXTUREFONT_NBCATEGORY] = { 8, 16, 24, 32, 64 };
const int NbLine[TEXTUREFONT_NBCATEGORY] = { 8, 24, 16, 4, 1 }; // Based on textsize
/*
const int TextureSizeX = 256;
const int TextureSizeY = 256;
const int Categories[TEXTUREFONT_NBCATEGORY] = { 8, 16, 24, 32 };
const int NbLine[TEXTUREFONT_NBCATEGORY] = { 4, 6, 4, 1 }; // Based on textsize
*/
// ---------------------------------------------------------------------------
inline uint32 CTextureFont::SLetterKey::getVal()
{
// this limits Size to 6bits
// Large sizes already render wrong when many
// different glyphs are used due to limited texture atlas
uint8 eb = ((uint)Embolden) + ((uint)Oblique << 1);
if (FontGenerator == NULL)
return Char + ((Size&255)<<16) + (eb << 22);
else
return Char + ((Size&255)<<16) + (eb << 22) + ((FontGenerator->getUID()&0xFF)<<24);
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
CTextureFont::CTextureFont() CTextureFont::CTextureFont()
: _CacheVersion(1),
_TextureSizeX(512), _TextureSizeY(512), _TextureMaxW(4096), _TextureMaxH(4096),
_PaddingL(0), _PaddingT(0), _PaddingR(1), _PaddingB(1),
_MinGlyphSize(5), _MaxGlyphSize(200),
_GlyphSizeStepMin(50), _GlyphSizeStep(5)
{ {
uint i;
setFilterMode (ITexture::Linear, ITexture::LinearMipMapOff); setFilterMode (ITexture::Linear, ITexture::LinearMipMapOff);
setWrapS (ITexture::Repeat); setWrapS (ITexture::Repeat);
@ -75,53 +52,9 @@ CTextureFont::CTextureFont()
setReleasable (false); setReleasable (false);
resize (TextureSizeX, TextureSizeY, CBitmap::Alpha); resize (_TextureSizeX, _TextureSizeY, CBitmap::Alpha, true);
for(i = 0; i < TextureSizeX*TextureSizeY; ++i)
getPixels()[i] = 0;
// convertToType (CBitmap::Alpha);
sint posY = 0; _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY));
for(i = 0; i < TEXTUREFONT_NBCATEGORY; ++i)
{
// Number of chars per cache
Letters[i].resize ((TextureSizeX/Categories[i])*NbLine[i]);
for(uint32 j = 0; j < Letters[i].size(); ++j)
{
SLetterInfo &rLetter = Letters[i][j];
rLetter.Char = 0xffff;
rLetter.FontGenerator = NULL;
rLetter.Size= 0;
rLetter.Embolden = false;
rLetter.Oblique = false;
// The less recently used infos
if (j < Letters[i].size()-1)
rLetter.Next = &Letters[i][j+1];
else
rLetter.Next = NULL;
if (j > 0)
rLetter.Prev = &Letters[i][j-1];
else
rLetter.Prev = NULL;
rLetter.Cat = i;
sint sizeX = TextureSizeX/Categories[i];
rLetter.U = (Categories[i]*(j%sizeX)) / ((float)TextureSizeX);
rLetter.V = (posY + Categories[i]*((sint)(j/sizeX))) / ((float)TextureSizeY);
/////////////////////////////////////////////////
rLetter.CharWidth = rLetter.CharHeight = 0;
rLetter.GlyphIndex = rLetter.Top = rLetter.Left = rLetter.AdvX = 0;
}
Front[i] = &Letters[i][0];
Back[i] = &Letters[i][Letters[i].size()-1];
posY += NbLine[i] * Categories[i];
}
} }
@ -129,17 +62,16 @@ CTextureFont::~CTextureFont()
{ {
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
void CTextureFont::dumpTextureFont(const char *filename) void CTextureFont::dumpTextureFont(const char *filename)
{ {
CBitmap b; CBitmap b;
COFile f( filename ); COFile f( filename );
b.resize (TextureSizeX, TextureSizeY, CBitmap::RGBA); b.resize (_TextureSizeX, _TextureSizeY, CBitmap::RGBA);
CObjectVector<uint8>&bits = b.getPixels(); CObjectVector<uint8>&bits = b.getPixels();
CObjectVector<uint8>&src = getPixels(); CObjectVector<uint8>&src = getPixels();
for (uint i = 0; i < (TextureSizeX*TextureSizeY); ++i) for (uint i = 0; i < (_TextureSizeX*_TextureSizeY); ++i)
{ {
bits[i*4+0] = bits[i*4+1] = bits[i*4+2] = bits[i*4+3] = src[i]; bits[i*4+0] = bits[i*4+1] = bits[i*4+2] = bits[i*4+3] = src[i];
} }
@ -147,242 +79,468 @@ void CTextureFont::dumpTextureFont(const char *filename)
b.writeTGA (f, 32); b.writeTGA (f, 32);
} }
// ---------------------------------------------------------------------------
bool CTextureFont::getNextTextureSize(uint32 &newW, uint32 &newH) const
{
// width will be resized first (256x256 -> 512x256)
if (_TextureSizeX <= _TextureSizeY)
{
newW = _TextureSizeX * 2;
newH = _TextureSizeY;
}
else
{
newW = _TextureSizeX;
newH = _TextureSizeY * 2;
}
// no more room
return newW <= _TextureMaxW && newH <= _TextureMaxH;
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// cat : categories where the letter is // out of room, clear everything and rebuild glyphs on demand
// x : pos x of the letter // note: text will display wrong until glyphs get rendered again
// y : pos y of the letter void CTextureFont::clearAtlas()
void CTextureFont::rebuildLetter (sint cat, sint x, sint y)
{ {
sint sizex = TextureSizeX / Categories[cat]; nlwarning("Glyph cache will be cleared.");
sint index = x + y*sizex;
SLetterInfo &rLetter = Letters[cat][index];
if (rLetter.FontGenerator == NULL) _AtlasNodes.clear();
return; _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY));
sint catTopY = 0; // clear texture
sint c = 0; _Data[0].fill(0);
while (c < cat)
// clear glyph cache
for(uint i = 0; i< _Letters.size(); ++i)
{ {
catTopY += NbLine[c] * Categories[c]; _Letters[i].glyph = NULL;
++c;
} }
// Destination position in pixel of the letter _GlyphCache.clear();
sint posx = x * Categories[cat];
sint posy = catTopY + y * Categories[cat];
uint32 pitch = 0; _CacheVersion++;
uint8 *bitmap = rLetter.FontGenerator->getBitmap ( rLetter.Char, rLetter.Size, rLetter.Embolden, rLetter.Oblique,
rLetter.CharWidth, rLetter.CharHeight,
pitch, rLetter.Left, rLetter.Top,
rLetter.AdvX, rLetter.GlyphIndex );
// Copy FreeType buffer touch();
uint i; }
for (i = 0; i < rLetter.CharHeight; ++i)
// ---------------------------------------------------------------------------
void CTextureFont::repackAtlas()
{
repackAtlas(_TextureSizeX, _TextureSizeY);
}
// ---------------------------------------------------------------------------
// backup old glyphs and move them to newly resized texture
// new atlas will be sorted if _GlyphCache is
void CTextureFont::repackAtlas(uint32 newW, uint32 newH)
{
uint32 newCacheVersion = _CacheVersion+1;
CBitmap btm;
uint32 oldW, oldH;
oldW = _TextureSizeX;
oldH = _TextureSizeY;
btm.resize(oldW, oldH, CBitmap::Alpha, true);
btm.blit(this, 0, 0);
// resize texture
if (_TextureSizeX != newW || _TextureSizeY != newH)
{ {
uint8 *pDst = &_Data[0][posx + (posy+i)*TextureSizeY]; _TextureSizeX = newW;
uint8 *pSrc = &bitmap[i*pitch]; _TextureSizeY = newH;
for (uint j = 0; j < rLetter.CharWidth; ++j) resize (_TextureSizeX, _TextureSizeY, CBitmap::Alpha, true);
}
else
{
_Data[0].fill(0);
}
// release atlas and rebuild
_AtlasNodes.clear();
_AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY));
CObjectVector<uint8>&src = btm.getPixels();
for(std::list<SGlyphInfo>::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it)
{
if (it->CacheVersion != _CacheVersion)
{ {
*pDst = *pSrc; // TODO: must remove glyph from all letters before removing glyph from cache
++pDst; //continue;
++pSrc; }
SGlyphInfo &glyph = *it;
glyph.CacheVersion = newCacheVersion;
uint32 atlasX, atlasY;
if (reserveAtlas(glyph.W, glyph.H, atlasX, atlasY))
{
for (uint y = 0; y < glyph.H; ++y)
{
uint8 *pDst = &_Data[0][(atlasY + y) * _TextureSizeX + atlasX];
for (uint x = 0; x < glyph.W; ++x)
{
*pDst = src[(glyph.Y + y) * oldW + glyph.X + x];
++pDst;
}
}
// TODO: dup code with renderGlyph
glyph.U0 = (atlasX+_PaddingL) / (float)_TextureSizeX;
glyph.V0 = (atlasY+_PaddingT) / (float)_TextureSizeY;
glyph.U1 = (atlasX+_PaddingL+glyph.CharWidth) / (float)_TextureSizeX;
glyph.V1 = (atlasY+_PaddingT+glyph.CharHeight) / (float)_TextureSizeY;
glyph.X = atlasX;
glyph.Y = atlasY;
} }
} }
// Black border bottom and right _CacheVersion = newCacheVersion;
for (i = 0; i < rLetter.CharHeight+1; ++i)
// invalidate full texture
touch();
}
// ---------------------------------------------------------------------------
bool CTextureFont::resizeAtlas()
{
uint32 newW, newH;
if (!getNextTextureSize(newW, newH))
{ {
_Data[0][posx + rLetter.CharWidth + (posy+i)*TextureSizeY] = 0; nlwarning("Font texture at maximum (%d,%d). Resize failed.", _TextureSizeX, _TextureSizeY);
return false;
} }
for (i = 0; i < rLetter.CharWidth+1; ++i) // resize and redraw
{ repackAtlas(newW, newH);
_Data[0][posx + i + (posy+rLetter.CharHeight)*TextureSizeY] = 0; return true;
}
/*
dumpTextureFont (this);
int a = 5;
a++;
*/
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
void CTextureFont::doGenerate(bool async) void CTextureFont::doGenerate(bool async)
{ {
// Rectangle invalidate ? /*
if (_ListInvalidRect.begin()!=_ListInvalidRect.end()) nlinfo("doGenerate: Letters(%d/%d), Glyphs(%d/%d)\n", _Letters.size(), _Letters.size() * sizeof(SLetterInfo),
{ _GlyphCache.size(), _GlyphCache.size() * sizeof(SGlyphInfo));
// Yes, rebuild only those rectangles. //std::string fname = CFile::findNewFile("/tmp/font-texture.tga");
std::string fname = toString("/tmp/font-texture-%p-%03d.tga", this, _CacheVersion);
// For each rectangle to compute dumpTextureFont (fname.c_str());
std::list<NLMISC::CRect>::iterator ite=_ListInvalidRect.begin(); */
while (ite!=_ListInvalidRect.end())
{
// Compute rectangle coordinates
sint x = ite->left();
sint y = ite->bottom();
// Look in which category is the rectangle
sint cat = 0;
sint catTopY = 0;
sint catBotY = NbLine[cat] * Categories[cat];
while (y > catBotY)
{
if (y < catBotY)
break;
++cat;
nlassert (cat < TEXTUREFONT_NBCATEGORY);
catTopY = catBotY;
catBotY += NbLine[cat] * Categories[cat];
}
x = x / Categories[cat];
y = ite->top();
y = y - catTopY;
y = y / Categories[cat];
rebuildLetter (cat, x, y);
// Next rectangle
ite++;
}
}
else
{
for(int cat = 0; cat < TEXTUREFONT_NBCATEGORY; ++cat)
{
sint sizex = TextureSizeX / Categories[cat];
sint sizey = NbLine[cat];
for (sint y = 0; y < sizey; y++)
for (sint x = 0; x < sizex; x++)
{
rebuildLetter (cat, x, y);
}
}
}
/*
dumpTextureFont (this);
int a = 5;
*/
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
CTextureFont::SLetterInfo* CTextureFont::getLetterInfo (SLetterKey& k) uint CTextureFont::fitRegion(uint index, uint width, uint height)
{ {
sint cat; if (_AtlasNodes[index].X + width > _TextureSizeX - 1)
uint32 nTmp = k.getVal();
map<uint32, SLetterInfo*>::iterator itAccel = Accel.find (nTmp);
if (itAccel != Accel.end())
{ {
// Put it in the first place return -1;
SLetterInfo *pLetterToMove = itAccel->second; }
cat = pLetterToMove->Cat;
if (pLetterToMove != Front[cat]) uint x = _AtlasNodes[index].X;
uint y = _AtlasNodes[index].Y;
sint widthLeft = width;
while(widthLeft > 0)
{
if (_AtlasNodes[index].Y > y)
{ {
// unlink y = _AtlasNodes[index].Y;
nlassert(pLetterToMove->Prev);
pLetterToMove->Prev->Next = pLetterToMove->Next;
if (pLetterToMove == Back[cat])
{
Back[cat] = pLetterToMove->Prev;
}
else
{
pLetterToMove->Next->Prev = pLetterToMove->Prev;
}
// link to front
pLetterToMove->Prev = NULL;
pLetterToMove->Next = Front[cat];
Front[cat]->Prev = pLetterToMove;
Front[cat] = pLetterToMove;
} }
return pLetterToMove;
// _AtlasNodes[0] for margin is not used here
if (_AtlasNodes[index].Y + height > _TextureSizeY - 1)
{
return -1;
}
widthLeft -= _AtlasNodes[index].Width;
index++;
} }
// The letter is not already present return y;
// Found the category of the new letter }
uint32 width, height;
//k.FontGenerator->getSizes (k.Char, k.Size, width, height); bool CTextureFont::reserveAtlas(const uint32 width, const uint32 height, uint32 &x, uint32 &y)
// \todo mat : Temp !!! Try to use freetype cache {
uint32 nPitch, nGlyphIndex; if (_AtlasNodes.empty())
sint32 nLeft, nTop, nAdvX; {
k.FontGenerator->getBitmap (k.Char, k.Size, k.Embolden, k.Oblique, width, height, nPitch, nLeft, nTop, nlwarning("No available space in texture atlas (_AtlasNodes.empty() == true)");
nAdvX, nGlyphIndex ); return false;
}
// Add 1 pixel space for black border to get correct category x = 0;
cat = 0; y = 0;
if (((sint)width+1 > Categories[TEXTUREFONT_NBCATEGORY-1]) ||
((sint)height+1 > Categories[TEXTUREFONT_NBCATEGORY-1])) sint bestIndex = -1;
sint bestWidth = _TextureSizeX;
sint bestHeight = _TextureSizeY;
sint selY=0;
for (uint i = 0; i < _AtlasNodes.size(); ++i)
{
selY = fitRegion(i, width, height);
if (selY >=0)
{
if (((selY + height) < bestHeight) || ((selY + height) == bestHeight && _AtlasNodes[i].Width > 0 && _AtlasNodes[i].Width < bestWidth))
{
bestHeight = selY + height;
bestIndex = i;
bestWidth = _AtlasNodes[i].Width;
x = _AtlasNodes[i].X;
y = selY;
}
}
}
if (bestIndex == -1)
{
x = 0;
y = 0;
return false;
}
CRect r(x, y + height, width, 0);
_AtlasNodes.insert(_AtlasNodes.begin() + bestIndex, r);
// shrink or remove nodes overlaping with newly inserted node
for(uint i = bestIndex+1; i< _AtlasNodes.size(); i++)
{
if (_AtlasNodes[i].X < (_AtlasNodes[i-1].X + _AtlasNodes[i-1].Width))
{
sint shrink = _AtlasNodes[i-1].X + _AtlasNodes[i-1].Width - _AtlasNodes[i].X;
_AtlasNodes[i].X += shrink;
if (_AtlasNodes[i].Width > shrink)
{
_AtlasNodes[i].Width -= shrink;
break;
}
_AtlasNodes.erase(_AtlasNodes.begin() + i);
i--;
}
else break;
}
// merge nearby nodes from same row
for(uint i = 0; i < _AtlasNodes.size() - 1; i++)
{
if (_AtlasNodes[i].Y == _AtlasNodes[i+1].Y)
{
_AtlasNodes[i].Width += _AtlasNodes[i+1].Width;
_AtlasNodes.erase(_AtlasNodes.begin() + i + 1);
i--;
}
}
return true;
}
// ---------------------------------------------------------------------------
// bitmap : texture data
// bitmapW : bitmap width
// bitmapH : bitmap height
// atlasX : pos x in font texture
// atlasY : pos y in font texture
void CTextureFont::copyGlyphBitmap(uint8* bitmap, uint32 bitmapW, uint32 bitmapH, uint32 atlasX, uint32 atlasY)
{
for (uint bY = 0; bY < bitmapH; ++bY)
{
uint8 *pDst = &_Data[0][(atlasY+_PaddingT+bY) * _TextureSizeX+atlasX+_PaddingL];
for (uint bX = 0; bX < bitmapW; ++bX)
{
*pDst = bitmap[bY * bitmapW+bX];
++pDst;
}
}
if (_PaddingR > 0 || _PaddingB > 0 || _PaddingL > 0 || _PaddingT > 0)
{
for(uint i = 0; i<(bitmapH+_PaddingT+_PaddingB); ++i)
{
if (_PaddingT > 0) _Data[0][(atlasY + i) * _TextureSizeX + atlasX ] = 0;
if (_PaddingB > 0) _Data[0][(atlasY + i) * _TextureSizeX + atlasX + _PaddingL + bitmapW] = 0;
}
for (uint i = 0; i<(bitmapW+_PaddingL+_PaddingR); ++i)
{
if (_PaddingL > 0) _Data[0][atlasY * _TextureSizeX + atlasX + i] = 0;
if (_PaddingB > 0) _Data[0][(atlasY + _PaddingT + bitmapH) * _TextureSizeX + atlasX + i] = 0;
}
}
CRect r(atlasX, atlasY, bitmapW + _PaddingL + _PaddingR, bitmapH + _PaddingT + _PaddingB);
touchRect(r);
}
// ---------------------------------------------------------------------------
CTextureFont::SGlyphInfo* CTextureFont::renderLetterGlyph(SLetterInfo *letter, uint bitmapFontSize)
{
uint32 nPitch;
sint32 left;
sint32 top;
sint32 advx;
uint32 charWidth;
uint32 charHeight;
uint32 glyphIndex;
uint8 *bitmap = letter->FontGenerator->getBitmap (letter->Char, bitmapFontSize, letter->Embolden, letter->Oblique,
charWidth, charHeight,
nPitch, left, top,
advx, glyphIndex );
uint32 atlasX, atlasY;
uint32 rectW, rectH;
rectW = charWidth + _PaddingL + _PaddingR;
rectH = charHeight + _PaddingT + _PaddingB;
if (!reserveAtlas(rectW, rectH, atlasX, atlasY))
{
// no room
return NULL; return NULL;
}
copyGlyphBitmap(bitmap, charWidth, charHeight, atlasX, atlasY);
while (((sint)width+1 > Categories[cat]) || ((sint)height+1 > Categories[cat])) SGlyphInfo* glyphInfo = NULL;
{ {
++cat; // keep cache sorted by height (smaller first)
nlassert (cat != TEXTUREFONT_NBCATEGORY); std::list<SGlyphInfo>::iterator it = _GlyphCache.begin();
while(it != _GlyphCache.end() && it->CharHeight < charHeight)
{
++it;
}
it = _GlyphCache.insert(it, SGlyphInfo());
glyphInfo = &(*it);
} }
// And replace the less recently used letter glyphInfo->GlyphIndex = glyphIndex;
SLetterKey k2; glyphInfo->Size = bitmapFontSize;
k2.Char = Back[cat]->Char; glyphInfo->Embolden = letter->Embolden;
k2.FontGenerator = Back[cat]->FontGenerator; glyphInfo->Oblique = letter->Oblique;
k2.Size = Back[cat]->Size; glyphInfo->FontGenerator = letter->FontGenerator;
k2.Embolden = Back[cat]->Embolden; glyphInfo->CacheVersion = _CacheVersion;
k2.Oblique = Back[cat]->Oblique;
itAccel = Accel.find (k2.getVal()); glyphInfo->U0 = (atlasX+_PaddingL) / (float)_TextureSizeX;
if (itAccel != Accel.end()) glyphInfo->V0 = (atlasY+_PaddingT) / (float)_TextureSizeY;
glyphInfo->U1 = (atlasX+_PaddingL+charWidth) / (float)_TextureSizeX;
glyphInfo->V1 = (atlasY+_PaddingT+charHeight) / (float)_TextureSizeY;
glyphInfo->CharWidth = charWidth;
glyphInfo->CharHeight = charHeight;
glyphInfo->X = atlasX;
glyphInfo->Y = atlasY;
glyphInfo->W = rectW;
glyphInfo->H = rectH;
return glyphInfo;
}
// ---------------------------------------------------------------------------
CTextureFont::SGlyphInfo* CTextureFont::findLetterGlyph(SLetterInfo *letter, bool insert)
{
uint bitmapFontSize = max((sint)_MinGlyphSize, min((sint)_MaxGlyphSize, letter->Size));
if (_GlyphSizeStep > 1 && bitmapFontSize > _GlyphSizeStepMin)
{ {
Accel.erase (itAccel); uint size = (bitmapFontSize / _GlyphSizeStep) * _GlyphSizeStep;
} }
SLetterInfo *NewBack = Back[cat]->Prev; // CacheVersion not checked, all glyphs in cache must be rendered on texture
NewBack->Next = NULL; for(std::list<SGlyphInfo>::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it)
Back[cat]->Cat = cat;
Back[cat]->Char = k.Char;
Back[cat]->FontGenerator = k.FontGenerator;
Back[cat]->Size = k.Size;
Back[cat]->Embolden = k.Embolden;
Back[cat]->Oblique = k.Oblique;
Back[cat]->CharWidth = width;
Back[cat]->CharHeight = height;
Back[cat]->Top = nTop;
Back[cat]->Left = nLeft;
Back[cat]->AdvX = nAdvX;
Back[cat]->Prev = NULL;
Back[cat]->Next = Front[cat];
Front[cat]->Prev = Back[cat];
Front[cat] = Back[cat];
Back[cat] = NewBack;
Accel.insert (map<uint32, SLetterInfo*>::value_type(k.getVal(),Front[cat]));
// Invalidate the zone
sint index = (sint)(Front[cat] - &Letters[cat][0]);// / sizeof (SLetterInfo);
sint sizex = TextureSizeX / Categories[cat];
sint x = index % sizex;
sint y = index / sizex;
x = x * Categories[cat];
y = y * Categories[cat];
sint c = 0;
while (c < cat)
{ {
y = y + NbLine[c] * Categories[c]; if (it->GlyphIndex == letter->GlyphIndex &&
++c; it->Size == bitmapFontSize &&
it->Embolden == letter->Embolden &&
it->Oblique == letter->Oblique &&
it->FontGenerator == letter->FontGenerator)
{
return &(*it);
}
} }
// must update the char, WITH the black borders if (insert)
CRect r (x, y, width+1, height+1); {
return renderLetterGlyph(letter, bitmapFontSize);
}
touchRect (r); return NULL;
}
return Front[cat]; // ---------------------------------------------------------------------------
CTextureFont::SLetterInfo* CTextureFont::findLetter(SLetterKey &k, bool insert)
{
// TODO: use std::map<uint64>
for(uint i = 0; i < _Letters.size(); ++i)
{
if (_Letters[i].Char == k.Char && _Letters[i].Size == k.Size &&
_Letters[i].Embolden == k.Embolden && _Letters[i].Oblique == k.Oblique &&
_Letters[i].FontGenerator == k.FontGenerator)
{
return &_Letters[i];
}
}
if (insert)
{
_Letters.push_back(SLetterInfo());
SLetterInfo* letter = &_Letters.back();
// get metrics for requested size
letter->Char = k.Char;
letter->Size = k.Size;
letter->Embolden = k.Embolden;
letter->Oblique = k.Oblique;
letter->FontGenerator = k.FontGenerator;
uint32 nPitch;
letter->FontGenerator->getBitmap(letter->Char, letter->Size, letter->Embolden, letter->Oblique,
letter->CharWidth, letter->CharHeight,
nPitch, letter->Left, letter->Top,
letter->AdvX, letter->GlyphIndex );
return letter;
}
return NULL;
}
// ---------------------------------------------------------------------------
CTextureFont::SLetterInfo* CTextureFont::getLetterInfo (SLetterKey& k, bool render)
{
// find already cached letter or create new one
SLetterInfo* letter = findLetter(k, true);
// letter not found (=NULL) or render not requested
if (!letter || !render) return letter;
if (!letter->glyph || letter->glyph->CacheVersion != _CacheVersion)
{
// render glyph
letter->glyph = findLetterGlyph(letter, true);
if (letter->glyph == NULL)
{
// resize/repack and try again
if (!resizeAtlas()) repackAtlas();
letter->glyph = findLetterGlyph(letter, true);
if (letter->glyph == NULL)
{
// make room by clearing all glyphs and reduce max size for glyphs
clearAtlas();
if (_MaxGlyphSize > _MinGlyphSize)
{
_MaxGlyphSize = max(_MinGlyphSize, _MaxGlyphSize - 10);
}
letter->glyph = findLetterGlyph(letter, true);
}
}
}
return letter;
} }
} // NL3D } // NL3D

View file

@ -3419,6 +3419,22 @@ void displayDebugClusters()
} }
NLMISC_COMMAND(dumpFontTexture, "Write font texture to file", "")
{
CInterfaceManager *im = CInterfaceManager::getInstance();
if (TextContext)
{
std::string fname = CFile::findNewFile("font-texture.tga");
TextContext->dumpCacheTexture(fname.c_str());
im->displaySystemInfo(ucstring(fname + " created"), "SYS");
}
else
{
im->displaySystemInfo(ucstring("Error: TextContext == NULL"), "SYS");
}
return true;
}
// *************************************************************************** // ***************************************************************************
void inGamePatchUncompleteWarning() void inGamePatchUncompleteWarning()