Merge branch 'ryzom/ark-features' into main/gingo-test
[ryzomcore.git] / nel / src / 3d / texture_font.cpp
blob58be439c34d21835e004074ad436959d7738208e
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010-2021 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "std3d.h"
18 #include <map>
20 #include "nel/3d/texture_font.h"
21 #include "nel/3d/font_generator.h"
23 #include "nel/misc/common.h"
24 #include "nel/misc/rect.h"
25 #include "nel/misc/file.h"
26 #include "nel/misc/path.h"
28 using namespace std;
29 using namespace NLMISC;
31 #ifdef DEBUG_NEW
32 #define new DEBUG_NEW
33 #endif
35 namespace NL3D
38 // ---------------------------------------------------------------------------
39 CTextureFont::CTextureFont()
40 : _CacheVersion(1),
41 _TextureSizeX(512), _TextureSizeY(512), _TextureMaxW(4096), _TextureMaxH(4096),
42 _PaddingL(0), _PaddingT(0), _PaddingR(1), _PaddingB(1),
43 _MinGlyphSize(5), _MaxGlyphSize(200),
44 _GlyphSizeStepMin(50), _GlyphSizeStep(5)
46 setFilterMode (ITexture::Linear, ITexture::LinearMipMapOff);
48 setWrapS (ITexture::Repeat);
49 setWrapT (ITexture::Repeat);
51 setUploadFormat (Alpha);
53 setReleasable (false);
55 resize (_TextureSizeX, _TextureSizeY, CBitmap::Alpha, true);
57 _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY));
61 CTextureFont::~CTextureFont()
65 // ---------------------------------------------------------------------------
66 void CTextureFont::dumpTextureFont(const char *filename)
68 CBitmap b;
69 COFile f( filename );
70 b.resize (_TextureSizeX, _TextureSizeY, CBitmap::RGBA);
71 CObjectVector<uint8>&bits = b.getPixels();
72 CObjectVector<uint8>&src = getPixels();
74 for (uint i = 0; i < (_TextureSizeX*_TextureSizeY); ++i)
76 bits[i*4+0] = bits[i*4+1] = bits[i*4+2] = bits[i*4+3] = src[i];
79 b.writeTGA (f, 32);
82 // ---------------------------------------------------------------------------
83 bool CTextureFont::getNextTextureSize(uint32 &newW, uint32 &newH) const
85 // width will be resized first (256x256 -> 512x256)
86 if (_TextureSizeX <= _TextureSizeY)
88 newW = _TextureSizeX * 2;
89 newH = _TextureSizeY;
91 else
93 newW = _TextureSizeX;
94 newH = _TextureSizeY * 2;
97 // no more room
98 return newW <= _TextureMaxW && newH <= _TextureMaxH;
101 // ---------------------------------------------------------------------------
102 // out of room, clear everything and rebuild glyphs on demand
103 // note: text will display wrong until glyphs get rendered again
104 void CTextureFont::clearAtlas()
106 nlwarning("Glyph cache will be cleared.");
108 _AtlasNodes.clear();
109 _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY));
111 // clear texture
112 _Data[0].fill(0);
114 // clear glyph cache
115 for(std::map<SLetterKey, SLetterInfo>::iterator it = _Letters.begin(); it != _Letters.end(); ++it)
117 it->second.glyph = NULL;
119 _GlyphCache.clear();
121 _CacheVersion++;
123 touch();
126 // ---------------------------------------------------------------------------
127 void CTextureFont::repackAtlas()
129 repackAtlas(_TextureSizeX, _TextureSizeY);
132 // ---------------------------------------------------------------------------
133 // backup old glyphs and move them to newly resized texture
134 // new atlas will be sorted if _GlyphCache is
135 void CTextureFont::repackAtlas(uint32 newW, uint32 newH)
137 uint32 newCacheVersion = _CacheVersion+1;
139 CBitmap btm;
140 uint32 oldW, oldH;
142 oldW = _TextureSizeX;
143 oldH = _TextureSizeY;
144 btm.resize(oldW, oldH, CBitmap::Alpha, true);
145 btm.blit(this, 0, 0);
147 // resize texture
148 if (_TextureSizeX != newW || _TextureSizeY != newH)
150 _TextureSizeX = newW;
151 _TextureSizeY = newH;
152 resize (_TextureSizeX, _TextureSizeY, CBitmap::Alpha, true);
154 else
156 _Data[0].fill(0);
159 // release atlas and rebuild
160 _AtlasNodes.clear();
161 _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY));
163 CObjectVector<uint8>&src = btm.getPixels();
164 for(std::map<SLetterKey, SGlyphInfo>::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it)
166 if (it->second.CacheVersion != _CacheVersion)
168 // TODO: must remove glyph from all letters before removing glyph from cache
169 //continue;
172 SGlyphInfo &glyph = it->second;
174 glyph.CacheVersion = newCacheVersion;
176 uint32 atlasX, atlasY;
177 if (reserveAtlas(glyph.W, glyph.H, atlasX, atlasY))
179 for (uint y = 0; y < glyph.H; ++y)
181 uint8 *pDst = &_Data[0][(atlasY + y) * _TextureSizeX + atlasX];
182 for (uint x = 0; x < glyph.W; ++x)
184 *pDst = src[(glyph.Y + y) * oldW + glyph.X + x];
185 ++pDst;
189 // TODO: dup code with renderGlyph
190 glyph.U0 = (atlasX+_PaddingL) / (float)_TextureSizeX;
191 glyph.V0 = (atlasY+_PaddingT) / (float)_TextureSizeY;
192 glyph.U1 = (atlasX+_PaddingL+glyph.CharWidth) / (float)_TextureSizeX;
193 glyph.V1 = (atlasY+_PaddingT+glyph.CharHeight) / (float)_TextureSizeY;
195 glyph.X = atlasX;
196 glyph.Y = atlasY;
200 _CacheVersion = newCacheVersion;
202 // invalidate full texture
203 touch();
206 // ---------------------------------------------------------------------------
207 bool CTextureFont::resizeAtlas()
209 uint32 newW, newH;
210 if (!getNextTextureSize(newW, newH))
212 nlwarning("Font texture at maximum (%d,%d). Resize failed.", _TextureSizeX, _TextureSizeY);
213 return false;
216 // resize and redraw
217 repackAtlas(newW, newH);
218 return true;
221 // ---------------------------------------------------------------------------
222 void CTextureFont::doGenerate(bool async)
225 nlinfo("doGenerate: Letters(%d/%d), Glyphs(%d/%d)\n", _Letters.size(), _Letters.size() * sizeof(SLetterInfo),
226 _GlyphCache.size(), _GlyphCache.size() * sizeof(SGlyphInfo));
227 //std::string fname = CFile::findNewFile("/tmp/font-texture.tga");
228 std::string fname = toString("/tmp/font-texture-%p-%03d.tga", this, _CacheVersion);
229 dumpTextureFont (fname.c_str());
233 // ---------------------------------------------------------------------------
234 uint CTextureFont::fitRegion(uint index, uint width, uint height)
236 if (_AtlasNodes[index].X + width > _TextureSizeX - 1)
238 return -1;
241 uint x = _AtlasNodes[index].X;
242 uint y = _AtlasNodes[index].Y;
243 sint widthLeft = width;
245 while(widthLeft > 0)
247 if (_AtlasNodes[index].Y > y)
249 y = _AtlasNodes[index].Y;
252 // _AtlasNodes[0] for margin is not used here
253 if (_AtlasNodes[index].Y + height > _TextureSizeY - 1)
255 return -1;
258 widthLeft -= _AtlasNodes[index].Width;
259 index++;
262 return y;
265 bool CTextureFont::reserveAtlas(const uint32 width, const uint32 height, uint32 &x, uint32 &y)
267 if (_AtlasNodes.empty())
269 nlwarning("No available space in texture atlas (_AtlasNodes.empty() == true)");
270 return false;
273 x = 0;
274 y = 0;
276 sint bestIndex = -1;
277 sint bestWidth = _TextureSizeX;
278 sint bestHeight = _TextureSizeY;
280 sint selY=0;
282 for (uint i = 0; i < _AtlasNodes.size(); ++i)
284 selY = fitRegion(i, width, height);
285 if (selY >=0)
287 if (((selY + height) < bestHeight) || ((selY + height) == bestHeight && _AtlasNodes[i].Width > 0 && _AtlasNodes[i].Width < bestWidth))
289 bestHeight = selY + height;
290 bestIndex = i;
291 bestWidth = _AtlasNodes[i].Width;
292 x = _AtlasNodes[i].X;
293 y = selY;
298 if (bestIndex == -1)
300 x = 0;
301 y = 0;
302 return false;
305 CRect r(x, y + height, width, 0);
306 _AtlasNodes.insert(_AtlasNodes.begin() + bestIndex, r);
308 // shrink or remove nodes overlaping with newly inserted node
309 for(uint i = bestIndex+1; i< _AtlasNodes.size(); i++)
311 if (_AtlasNodes[i].X < (_AtlasNodes[i-1].X + _AtlasNodes[i-1].Width))
313 sint shrink = _AtlasNodes[i-1].X + _AtlasNodes[i-1].Width - _AtlasNodes[i].X;
314 _AtlasNodes[i].X += shrink;
315 if (_AtlasNodes[i].Width > shrink)
317 _AtlasNodes[i].Width -= shrink;
318 break;
320 _AtlasNodes.erase(_AtlasNodes.begin() + i);
321 i--;
323 else break;
326 // merge nearby nodes from same row
327 for(uint i = 0; i < _AtlasNodes.size() - 1; i++)
329 if (_AtlasNodes[i].Y == _AtlasNodes[i+1].Y)
331 _AtlasNodes[i].Width += _AtlasNodes[i+1].Width;
332 _AtlasNodes.erase(_AtlasNodes.begin() + i + 1);
333 i--;
337 return true;
340 // ---------------------------------------------------------------------------
341 // bitmap : texture data
342 // bitmapW : bitmap width
343 // bitmapH : bitmap height
344 // atlasX : pos x in font texture
345 // atlasY : pos y in font texture
346 void CTextureFont::copyGlyphBitmap(uint8* bitmap, uint32 bitmapW, uint32 bitmapH, uint32 atlasX, uint32 atlasY)
348 for (uint bY = 0; bY < bitmapH; ++bY)
350 uint8 *pDst = &_Data[0][(atlasY+_PaddingT+bY) * _TextureSizeX+atlasX+_PaddingL];
351 for (uint bX = 0; bX < bitmapW; ++bX)
353 *pDst = bitmap[bY * bitmapW+bX];
354 ++pDst;
358 if (_PaddingR > 0 || _PaddingB > 0 || _PaddingL > 0 || _PaddingT > 0)
360 for(uint i = 0; i<(bitmapH+_PaddingT+_PaddingB); ++i)
362 if (_PaddingT > 0) _Data[0][(atlasY + i) * _TextureSizeX + atlasX ] = 0;
363 if (_PaddingB > 0) _Data[0][(atlasY + i) * _TextureSizeX + atlasX + _PaddingL + bitmapW] = 0;
366 for (uint i = 0; i<(bitmapW+_PaddingL+_PaddingR); ++i)
368 if (_PaddingL > 0) _Data[0][atlasY * _TextureSizeX + atlasX + i] = 0;
369 if (_PaddingB > 0) _Data[0][(atlasY + _PaddingT + bitmapH) * _TextureSizeX + atlasX + i] = 0;
373 CRect r(atlasX, atlasY, bitmapW + _PaddingL + _PaddingR, bitmapH + _PaddingT + _PaddingB);
374 touchRect(r);
378 // ---------------------------------------------------------------------------
379 CTextureFont::SGlyphInfo* CTextureFont::renderLetterGlyph(SLetterInfo *letter, uint bitmapFontSize)
381 uint32 nPitch;
382 sint32 left;
383 sint32 top;
384 sint32 advx;
385 uint32 charWidth;
386 uint32 charHeight;
387 uint32 glyphIndex;
389 uint8 *bitmap = letter->FontGenerator->getBitmap (letter->Char, bitmapFontSize, letter->Embolden, letter->Oblique,
390 charWidth, charHeight,
391 nPitch, left, top,
392 advx, glyphIndex );
394 uint32 atlasX, atlasY;
395 uint32 rectW, rectH;
396 rectW = charWidth + _PaddingL + _PaddingR;
397 rectH = charHeight + _PaddingT + _PaddingB;
399 if (!reserveAtlas(rectW, rectH, atlasX, atlasY))
401 // no room
402 return NULL;
404 copyGlyphBitmap(bitmap, charWidth, charHeight, atlasX, atlasY);
406 SLetterKey k = *letter;
407 k.Size = bitmapFontSize;
409 SGlyphInfo* glyphInfo = &_GlyphCache[k];
411 glyphInfo->GlyphIndex = glyphIndex;
412 glyphInfo->Size = bitmapFontSize;
413 glyphInfo->Embolden = letter->Embolden;
414 glyphInfo->Oblique = letter->Oblique;
415 glyphInfo->FontGenerator = letter->FontGenerator;
416 glyphInfo->CacheVersion = _CacheVersion;
418 glyphInfo->U0 = (atlasX+_PaddingL) / (float)_TextureSizeX;
419 glyphInfo->V0 = (atlasY+_PaddingT) / (float)_TextureSizeY;
420 glyphInfo->U1 = (atlasX+_PaddingL+charWidth) / (float)_TextureSizeX;
421 glyphInfo->V1 = (atlasY+_PaddingT+charHeight) / (float)_TextureSizeY;
423 glyphInfo->CharWidth = charWidth;
424 glyphInfo->CharHeight = charHeight;
426 glyphInfo->X = atlasX;
427 glyphInfo->Y = atlasY;
428 glyphInfo->W = rectW;
429 glyphInfo->H = rectH;
431 return glyphInfo;
435 // ---------------------------------------------------------------------------
436 CTextureFont::SGlyphInfo* CTextureFont::findLetterGlyph(SLetterInfo *letter, bool insert)
438 uint bitmapFontSize = max((sint)_MinGlyphSize, min((sint)_MaxGlyphSize, letter->Size));
439 if (_GlyphSizeStep > 1 && bitmapFontSize > _GlyphSizeStepMin)
441 bitmapFontSize = (bitmapFontSize / _GlyphSizeStep) * _GlyphSizeStep;
444 // CacheVersion not checked, all glyphs in cache must be rendered on texture
445 SLetterKey g = *letter;
446 g.Size = bitmapFontSize;
448 std::map<SLetterKey, SGlyphInfo>::iterator it = _GlyphCache.find(g);
449 if (it != _GlyphCache.end())
451 return &(it->second);
454 if (insert)
456 return renderLetterGlyph(letter, bitmapFontSize);
459 return NULL;
462 // ---------------------------------------------------------------------------
463 CTextureFont::SLetterInfo* CTextureFont::findLetter(SLetterKey &k, bool insert)
465 std::map<SLetterKey, SLetterInfo>::iterator it = _Letters.find(k);
466 if (it != _Letters.end())
468 return &(it->second);
471 if (insert)
473 SLetterInfo* letter = &_Letters[k];
475 // get metrics for requested size
476 letter->Char = k.Char;
477 letter->Size = k.Size;
478 letter->Embolden = k.Embolden;
479 letter->Oblique = k.Oblique;
480 letter->FontGenerator = k.FontGenerator;
482 uint32 nPitch;
483 letter->FontGenerator->getBitmap(letter->Char, letter->Size, letter->Embolden, letter->Oblique,
484 letter->CharWidth, letter->CharHeight,
485 nPitch, letter->Left, letter->Top,
486 letter->AdvX, letter->GlyphIndex );
488 return letter;
491 return NULL;
494 // ---------------------------------------------------------------------------
495 CTextureFont::SLetterInfo* CTextureFont::getLetterInfo (SLetterKey& k, bool render)
497 // find already cached letter or create new one
498 SLetterInfo* letter = findLetter(k, true);
499 // letter not found (=NULL) or render not requested
500 if (!letter || !render) return letter;
502 // nothing to render, ie space char
503 if (letter->CharWidth == 0 || letter->CharHeight == 0) return letter;
505 if (!letter->glyph || letter->glyph->CacheVersion != _CacheVersion)
507 // render glyph
508 letter->glyph = findLetterGlyph(letter, true);
509 if (letter->glyph == NULL)
511 // resize/repack and try again
512 if (!resizeAtlas()) repackAtlas();
514 letter->glyph = findLetterGlyph(letter, true);
515 if (letter->glyph == NULL)
517 // make room by clearing all glyphs and reduce max size for glyphs
518 clearAtlas();
519 if (_MaxGlyphSize > _MinGlyphSize)
521 _MaxGlyphSize = max(_MinGlyphSize, _MaxGlyphSize - 10);
524 letter->glyph = findLetterGlyph(letter, true);
529 return letter;
532 } // NL3D