1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010-2021 Winch Gate Property Limited
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.
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/>.
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"
29 using namespace NLMISC
;
38 // ---------------------------------------------------------------------------
39 CTextureFont::CTextureFont()
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
)
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
];
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;
94 newH
= _TextureSizeY
* 2;
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.");
109 _AtlasNodes
.push_back(CRect(0, 0, _TextureSizeX
, _TextureSizeY
));
115 for(std::map
<SLetterKey
, SLetterInfo
>::iterator it
= _Letters
.begin(); it
!= _Letters
.end(); ++it
)
117 it
->second
.glyph
= NULL
;
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;
142 oldW
= _TextureSizeX
;
143 oldH
= _TextureSizeY
;
144 btm
.resize(oldW
, oldH
, CBitmap::Alpha
, true);
145 btm
.blit(this, 0, 0);
148 if (_TextureSizeX
!= newW
|| _TextureSizeY
!= newH
)
150 _TextureSizeX
= newW
;
151 _TextureSizeY
= newH
;
152 resize (_TextureSizeX
, _TextureSizeY
, CBitmap::Alpha
, true);
159 // release atlas and rebuild
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
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
];
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
;
200 _CacheVersion
= newCacheVersion
;
202 // invalidate full texture
206 // ---------------------------------------------------------------------------
207 bool CTextureFont::resizeAtlas()
210 if (!getNextTextureSize(newW
, newH
))
212 nlwarning("Font texture at maximum (%d,%d). Resize failed.", _TextureSizeX
, _TextureSizeY
);
217 repackAtlas(newW
, newH
);
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)
241 uint x
= _AtlasNodes
[index
].X
;
242 uint y
= _AtlasNodes
[index
].Y
;
243 sint widthLeft
= width
;
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)
258 widthLeft
-= _AtlasNodes
[index
].Width
;
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)");
277 sint bestWidth
= _TextureSizeX
;
278 sint bestHeight
= _TextureSizeY
;
282 for (uint i
= 0; i
< _AtlasNodes
.size(); ++i
)
284 selY
= fitRegion(i
, width
, height
);
287 if (((selY
+ height
) < bestHeight
) || ((selY
+ height
) == bestHeight
&& _AtlasNodes
[i
].Width
> 0 && _AtlasNodes
[i
].Width
< bestWidth
))
289 bestHeight
= selY
+ height
;
291 bestWidth
= _AtlasNodes
[i
].Width
;
292 x
= _AtlasNodes
[i
].X
;
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
;
320 _AtlasNodes
.erase(_AtlasNodes
.begin() + i
);
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);
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
];
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
);
378 // ---------------------------------------------------------------------------
379 CTextureFont::SGlyphInfo
* CTextureFont::renderLetterGlyph(SLetterInfo
*letter
, uint bitmapFontSize
)
389 uint8
*bitmap
= letter
->FontGenerator
->getBitmap (letter
->Char
, bitmapFontSize
, letter
->Embolden
, letter
->Oblique
,
390 charWidth
, charHeight
,
394 uint32 atlasX
, atlasY
;
396 rectW
= charWidth
+ _PaddingL
+ _PaddingR
;
397 rectH
= charHeight
+ _PaddingT
+ _PaddingB
;
399 if (!reserveAtlas(rectW
, rectH
, atlasX
, atlasY
))
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
;
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
);
456 return renderLetterGlyph(letter
, bitmapFontSize
);
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
);
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
;
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
);
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
)
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
519 if (_MaxGlyphSize
> _MinGlyphSize
)
521 _MaxGlyphSize
= max(_MinGlyphSize
, _MaxGlyphSize
- 10);
524 letter
->glyph
= findLetterGlyph(letter
, true);