// NeL - MMORPG Framework // Copyright (C) 2010-2021 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" #include #include "nel/3d/texture_font.h" #include "nel/3d/font_generator.h" #include "nel/misc/common.h" #include "nel/misc/rect.h" #include "nel/misc/file.h" #include "nel/misc/path.h" using namespace std; using namespace NLMISC; #ifdef DEBUG_NEW #define new DEBUG_NEW #endif namespace NL3D { // --------------------------------------------------------------------------- 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) { setFilterMode (ITexture::Linear, ITexture::LinearMipMapOff); setWrapS (ITexture::Repeat); setWrapT (ITexture::Repeat); setUploadFormat (Alpha); setReleasable (false); resize (_TextureSizeX, _TextureSizeY, CBitmap::Alpha, true); _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY)); } CTextureFont::~CTextureFont() { } // --------------------------------------------------------------------------- void CTextureFont::dumpTextureFont(const char *filename) { CBitmap b; COFile f( filename ); b.resize (_TextureSizeX, _TextureSizeY, CBitmap::RGBA); CObjectVector&bits = b.getPixels(); CObjectVector&src = getPixels(); 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]; } 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; } // --------------------------------------------------------------------------- // out of room, clear everything and rebuild glyphs on demand // note: text will display wrong until glyphs get rendered again void CTextureFont::clearAtlas() { nlwarning("Glyph cache will be cleared."); _AtlasNodes.clear(); _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY)); // clear texture _Data[0].fill(0); // clear glyph cache for(std::map::iterator it = _Letters.begin(); it != _Letters.end(); ++it) { it->second.glyph = NULL; } _GlyphCache.clear(); _CacheVersion++; touch(); } // --------------------------------------------------------------------------- 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) { _TextureSizeX = newW; _TextureSizeY = newH; 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&src = btm.getPixels(); for(std::map::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it) { if (it->second.CacheVersion != _CacheVersion) { // TODO: must remove glyph from all letters before removing glyph from cache //continue; } SGlyphInfo &glyph = it->second; 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; } } _CacheVersion = newCacheVersion; // invalidate full texture touch(); } // --------------------------------------------------------------------------- bool CTextureFont::resizeAtlas() { uint32 newW, newH; if (!getNextTextureSize(newW, newH)) { nlwarning("Font texture at maximum (%d,%d). Resize failed.", _TextureSizeX, _TextureSizeY); return false; } // resize and redraw repackAtlas(newW, newH); return true; } // --------------------------------------------------------------------------- void CTextureFont::doGenerate(bool async) { /* nlinfo("doGenerate: Letters(%d/%d), Glyphs(%d/%d)\n", _Letters.size(), _Letters.size() * sizeof(SLetterInfo), _GlyphCache.size(), _GlyphCache.size() * sizeof(SGlyphInfo)); //std::string fname = CFile::findNewFile("/tmp/font-texture.tga"); std::string fname = toString("/tmp/font-texture-%p-%03d.tga", this, _CacheVersion); dumpTextureFont (fname.c_str()); */ } // --------------------------------------------------------------------------- uint CTextureFont::fitRegion(uint index, uint width, uint height) { if (_AtlasNodes[index].X + width > _TextureSizeX - 1) { return -1; } uint x = _AtlasNodes[index].X; uint y = _AtlasNodes[index].Y; sint widthLeft = width; while(widthLeft > 0) { if (_AtlasNodes[index].Y > y) { y = _AtlasNodes[index].Y; } // _AtlasNodes[0] for margin is not used here if (_AtlasNodes[index].Y + height > _TextureSizeY - 1) { return -1; } widthLeft -= _AtlasNodes[index].Width; index++; } return y; } bool CTextureFont::reserveAtlas(const uint32 width, const uint32 height, uint32 &x, uint32 &y) { if (_AtlasNodes.empty()) { nlwarning("No available space in texture atlas (_AtlasNodes.empty() == true)"); return false; } x = 0; y = 0; 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; } copyGlyphBitmap(bitmap, charWidth, charHeight, atlasX, atlasY); SLetterKey k = *letter; k.Size = bitmapFontSize; SGlyphInfo* glyphInfo = &_GlyphCache[k]; glyphInfo->GlyphIndex = glyphIndex; glyphInfo->Size = bitmapFontSize; glyphInfo->Embolden = letter->Embolden; glyphInfo->Oblique = letter->Oblique; glyphInfo->FontGenerator = letter->FontGenerator; glyphInfo->CacheVersion = _CacheVersion; glyphInfo->U0 = (atlasX+_PaddingL) / (float)_TextureSizeX; 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) { bitmapFontSize = (bitmapFontSize / _GlyphSizeStep) * _GlyphSizeStep; } // CacheVersion not checked, all glyphs in cache must be rendered on texture SLetterKey g = *letter; g.Size = bitmapFontSize; std::map::iterator it = _GlyphCache.find(g); if (it != _GlyphCache.end()) { return &(it->second); } if (insert) { return renderLetterGlyph(letter, bitmapFontSize); } return NULL; } // --------------------------------------------------------------------------- CTextureFont::SLetterInfo* CTextureFont::findLetter(SLetterKey &k, bool insert) { std::map::iterator it = _Letters.find(k); if (it != _Letters.end()) { return &(it->second); } if (insert) { SLetterInfo* letter = &_Letters[k]; // 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; // nothing to render, ie space char if (letter->CharWidth == 0 || letter->CharHeight == 0) 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