Skip to content

Commit 4710311

Browse files
authored
Fix upload glyph image data to FontAtlas out of range (#2898)
1 parent 5d086eb commit 4710311

File tree

6 files changed

+132
-74
lines changed

6 files changed

+132
-74
lines changed

axmol/2d/FontAtlas.cpp

Lines changed: 103 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
#include "simdjson/simdjson.h"
3838
#include "zlib.h"
3939
#include "axmol/base/ZipUtils.h"
40-
4140
#include "axmol/base/json.h"
4241

4342
namespace ax
@@ -121,7 +120,9 @@ void FontAtlas::loadFontAtlas(std::string_view fontatlasFile, axstd::string_map<
121120

122121
FontAtlas::FontAtlas(Font* theFont)
123122
: FontAtlas(theFont, CacheTextureWidth, CacheTextureHeight, AX_CONTENT_SCALE_FACTOR())
124-
{}
123+
{
124+
_newChars.reserve(128);
125+
}
125126

126127
FontAtlas::FontAtlas(Font* theFont, int atlasWidth, int atlasHeight, float scaleFactor)
127128
: _font(theFont), _width(atlasWidth), _height(atlasHeight), _scaleFactor(scaleFactor)
@@ -312,58 +313,57 @@ bool FontAtlas::getLetterDefinitionForChar(char32_t utf32Char, FontLetterDefinit
312313
}
313314
}
314315

315-
void FontAtlas::findNewCharacters(const std::u32string& u32Text, std::unordered_set<char32_t>& charset)
316+
bool FontAtlas::findNewCharacters(const std::u32string& u32Text)
316317
{
318+
_newChars.clear();
319+
317320
if (_letterDefinitions.empty())
318321
{
319-
std::copy(u32Text.begin(), u32Text.end(), std::inserter(charset, charset.end()));
322+
std::copy(u32Text.begin(), u32Text.end(), std::inserter(_newChars, _newChars.end()));
320323
}
321324
else
322325
{
323326
for (auto&& charCode : u32Text)
324327
if (_letterDefinitions.find(charCode) == _letterDefinitions.end())
325-
charset.insert(charCode);
328+
_newChars.insert(charCode);
326329
}
330+
331+
return !_newChars.empty();
327332
}
328333

329334
bool FontAtlas::prepareLetterDefinitions(const std::u32string& utf32Text)
330335
{
331-
if (_fontFreeType == nullptr)
332-
{
336+
if (!_fontFreeType)
333337
return false;
334-
}
335-
336338
if (!_currentPageData)
337339
reinit();
338340

339-
std::unordered_set<char32_t> charCodeSet;
340-
findNewCharacters(utf32Text, charCodeSet);
341-
if (charCodeSet.empty())
342-
{
341+
if (!findNewCharacters(utf32Text))
343342
return false;
344-
}
345343

346-
int adjustForDistanceMap = _letterPadding / 2;
347-
int adjustForExtend = _letterEdgeExtend / 2;
348-
int bitmapWidth = 0;
349-
int bitmapHeight = 0;
350-
int glyphHeight;
351-
Rect tempRect;
352-
FontLetterDefinition tempDef;
344+
const int adjustForDistanceMap = _letterPadding / 2;
345+
const int adjustForExtend = _letterEdgeExtend / 2;
353346

354-
auto pixelFormat = _pixelFormat;
347+
int bitmapWidth = 0, bitmapHeight = 0;
348+
Rect tempRect;
349+
FontLetterDefinition tempDef{};
355350

356-
int startY = (int)_currentPageOrigY;
351+
auto pixelFormat = _pixelFormat;
352+
int startY = static_cast<int>(_currentPageOrigY);
353+
int pageUploadStartY = startY; // Upload start position of the current page
354+
int pageUploadEndY = startY; // Upload end position of the current page (updated dynamically)
357355

358-
for (auto&& charCode : charCodeSet)
356+
for (auto&& charCode : _newChars)
359357
{
360358
auto missingIt = _missingGlyphFallbackFonts.find(charCode);
361359
uint8_t* bitmap = nullptr;
362360
FontFreeType* charRenderer = _fontFreeType;
361+
bool sharedBitmapData{true}; // does the bitmap data shared from FontFreeType engine
363362
if (missingIt == _missingGlyphFallbackFonts.end())
364363
{
365364
const GlyphResolution* res{nullptr};
366-
bitmap = charRenderer->getGlyphBitmap(charCode, bitmapWidth, bitmapHeight, tempRect, tempDef.xAdvance, res);
365+
bitmap = charRenderer->getGlyphBitmap(charCode, bitmapWidth, bitmapHeight, tempRect, tempDef.xAdvance, res,
366+
sharedBitmapData);
367367
if (!bitmap && res)
368368
{
369369
auto fallbackIt = _missingFallbackFonts.find(res->faceInfo.family);
@@ -382,86 +382,122 @@ bool FontAtlas::prepareLetterDefinitions(const std::u32string& utf32Text)
382382
{
383383
unsigned int glyphIndex = res->glyphIndex;
384384
bitmap = charRenderer->getGlyphBitmapByIndex(glyphIndex, bitmapWidth, bitmapHeight, tempRect,
385-
tempDef.xAdvance);
385+
tempDef.xAdvance, sharedBitmapData);
386386
_missingGlyphFallbackFonts.emplace(charCode, std::make_pair(charRenderer, glyphIndex));
387387
}
388388
}
389389
}
390390
else
391-
{ // found fallback font for missing charas, getGlyphBitmap without fallback
391+
{ // Found fallback font for missing characters, getGlyphBitmap without fallback
392392
charRenderer = missingIt->second.first;
393393
unsigned int glyphIndex = missingIt->second.second;
394-
bitmap =
395-
charRenderer->getGlyphBitmapByIndex(glyphIndex, bitmapWidth, bitmapHeight, tempRect, tempDef.xAdvance);
394+
bitmap = charRenderer->getGlyphBitmapByIndex(glyphIndex, bitmapWidth, bitmapHeight, tempRect,
395+
tempDef.xAdvance, sharedBitmapData);
396396
}
397+
397398
if (bitmap && bitmapWidth > 0 && bitmapHeight > 0)
398399
{
399-
tempDef.validDefinition = true;
400-
tempDef.width = tempRect.size.width + _letterPadding + _letterEdgeExtend;
401-
tempDef.height = tempRect.size.height + _letterPadding + _letterEdgeExtend;
402-
tempDef.offsetX = tempRect.origin.x - adjustForDistanceMap - adjustForExtend;
403-
tempDef.offsetY = _fontAscender + tempRect.origin.y - adjustForDistanceMap - adjustForExtend;
400+
// Calculate occupied area using actual bitmap size
401+
const int glyphWidth = bitmapWidth + _letterPadding + _letterEdgeExtend;
402+
const int glyphHeight = bitmapHeight + _letterPadding + _letterEdgeExtend;
404403

405-
if (_currentPageOrigX + tempDef.width > _width)
404+
// Not enough width in current line → wrap to next line
405+
if (_currentPageOrigX + glyphWidth > _width)
406406
{
407407
_currentPageOrigY += _currLineHeight;
408408
_currLineHeight = 0;
409409
_currentPageOrigX = 0;
410-
if (_currentPageOrigY + _lineHeight + _letterPadding + _letterEdgeExtend >= _height)
411-
{
412-
updateTextureContent(pixelFormat, startY);
413-
414-
startY = 0;
410+
}
415411

416-
addNewPage();
417-
}
412+
// Not enough height → upload current page and start a new page
413+
if (_currentPageOrigY + glyphHeight > _height)
414+
{
415+
// Upload the written region of the current page (startY..pageUploadEndY)
416+
updateTextureContent(pixelFormat, pageUploadStartY);
417+
// Start a new page
418+
addNewPage();
419+
_currentPageOrigX = 0;
420+
_currentPageOrigY = 0;
421+
_currLineHeight = 0;
422+
pageUploadStartY = 0;
423+
pageUploadEndY = 0;
418424
}
419-
glyphHeight = static_cast<int>(bitmapHeight) + _letterPadding + _letterEdgeExtend;
420-
_currLineHeight = std::max(glyphHeight, _currLineHeight);
421-
charRenderer->renderCharAt(_currentPageData, (int)_currentPageOrigX + adjustForExtend,
422-
(int)_currentPageOrigY + adjustForExtend, bitmap, bitmapWidth, bitmapHeight,
423-
_width, _height);
424425

426+
// Calculate tempDef offsets (based on tempRect)
427+
tempDef.validDefinition = true;
428+
tempDef.width = tempRect.size.width + _letterPadding + _letterEdgeExtend;
429+
tempDef.height = tempRect.size.height + _letterPadding + _letterEdgeExtend;
430+
tempDef.offsetX = tempRect.origin.x - adjustForDistanceMap - adjustForExtend;
431+
tempDef.offsetY = _fontAscender + tempRect.origin.y - adjustForDistanceMap - adjustForExtend;
432+
433+
// Render glyph into the current page
434+
charRenderer->renderCharAt(_currentPageData, static_cast<int>(_currentPageOrigX) + adjustForExtend,
435+
static_cast<int>(_currentPageOrigY) + adjustForExtend, bitmap, bitmapWidth,
436+
bitmapHeight, _width, _height);
437+
438+
// Record glyph position and page
425439
tempDef.U = _currentPageOrigX;
426440
tempDef.V = _currentPageOrigY;
427441
tempDef.textureID = _currentPage;
428-
_currentPageOrigX += tempDef.width + 1;
429-
// take from pixels to points
430-
tempDef.width = tempDef.width / _scaleFactor;
431-
tempDef.height = tempDef.height / _scaleFactor;
432-
tempDef.U = tempDef.U / _scaleFactor;
433-
tempDef.V = tempDef.V / _scaleFactor;
442+
443+
// Update line height and X pointer (leave 1 pixel spacing between glyphs)
444+
_currLineHeight = std::max(glyphHeight, _currLineHeight);
445+
_currentPageOrigX += glyphWidth + 1;
446+
447+
// Maintain upload range (highest Y written on this page)
448+
pageUploadEndY = std::max<int>(pageUploadEndY, _currentPageOrigY + _currLineHeight);
449+
450+
// Convert pixel dimensions to points
451+
tempDef.width /= _scaleFactor;
452+
tempDef.height /= _scaleFactor;
453+
tempDef.U /= _scaleFactor;
454+
tempDef.V /= _scaleFactor;
434455
tempDef.rotated = false;
435456
}
436457
else
437458
{
438-
delete[] bitmap;
439-
440459
tempDef.validDefinition = !!tempDef.xAdvance;
441-
tempDef.width = 0;
442-
tempDef.height = 0;
443-
tempDef.U = 0;
444-
tempDef.V = 0;
445-
tempDef.offsetX = 0;
446-
tempDef.offsetY = 0;
447-
tempDef.textureID = 0;
448-
tempDef.rotated = false;
460+
tempDef.width = tempDef.height = tempDef.U = tempDef.V = 0;
461+
tempDef.offsetX = tempDef.offsetY = 0;
462+
tempDef.textureID = 0;
463+
tempDef.rotated = false;
449464
_currentPageOrigX += 1;
450465
}
451466

467+
if (!sharedBitmapData && bitmap)
468+
delete[] bitmap;
469+
452470
_letterDefinitions[charCode] = tempDef;
453471
}
454472

455-
updateTextureContent(pixelFormat, startY);
473+
// Handle remaining upload for the current page (from startY to max written Y)
474+
if (pageUploadEndY > pageUploadStartY)
475+
{
476+
updateTextureContent(pixelFormat, pageUploadStartY);
477+
}
456478

457479
return true;
458480
}
459481

460482
void FontAtlas::updateTextureContent(rhi::PixelFormat format, int startY)
461483
{
462-
auto data = _currentPageData + (_width * (int)startY << _strideShift);
463-
_atlasTextures[_currentPage]->updateSubData(data, 0, startY, _width,
464-
(std::min)((int)_currentPageOrigY - startY + _currLineHeight, _height));
484+
// Calculate the starting data pointer
485+
auto data = _currentPageData + ((_width * (int)startY) << _strideShift);
486+
487+
// Calculate the actual upload height: from startY to _currentPageOrigY + _currLineHeight
488+
int uploadHeight = (int)_currentPageOrigY + _currLineHeight - startY;
489+
490+
// Defensive check to avoid negative values or overflow
491+
if (uploadHeight < 0)
492+
uploadHeight = 0;
493+
494+
// Clamp to remaining space to prevent out-of-bounds
495+
uploadHeight = std::min(uploadHeight, _height - startY);
496+
if (uploadHeight <= 0)
497+
return;
498+
499+
// Perform the texture update
500+
_atlasTextures[_currentPage]->updateSubData(data, 0, startY, _width, uploadHeight);
465501
}
466502

467503
void FontAtlas::addNewPage()

axmol/2d/FontAtlas.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
#include "axmol/base/Map.h"
4141
#include "axmol/2d/FontFreeType.h"
4242

43+
#include "axmol/tlx/flat_set.hpp"
44+
4345
namespace ax
4446
{
4547

@@ -132,7 +134,7 @@ class AX_DLL FontAtlas : public Object
132134

133135
void releaseTextures();
134136

135-
void findNewCharacters(const std::u32string& u32Text, std::unordered_set<char32_t>& charCodeSet);
137+
bool findNewCharacters(const std::u32string& u32Text);
136138

137139
/**
138140
* Scale each font letter by scaleFactor.
@@ -143,6 +145,8 @@ class AX_DLL FontAtlas : public Object
143145

144146
void updateTextureContent(rhi::PixelFormat format, int startY);
145147

148+
axstd::flat_set<char32_t> _newChars;
149+
146150
std::unordered_map<unsigned int, Texture2D*> _atlasTextures;
147151
std::unordered_map<char32_t, FontLetterDefinition> _letterDefinitions;
148152

axmol/2d/FontFreeType.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,8 @@ unsigned char* FontFreeType::getGlyphBitmap(char32_t charCode,
424424
int& outHeight,
425425
Rect& outRect,
426426
int& xAdvance,
427-
const GlyphResolution*& outFallbackRes)
427+
const GlyphResolution*& outFallbackRes,
428+
bool& sharedBitmapData)
428429
{
429430
unsigned char* ret = nullptr;
430431

@@ -466,19 +467,21 @@ unsigned char* FontFreeType::getGlyphBitmap(char32_t charCode,
466467
}
467468
}
468469

469-
return getGlyphBitmapByIndex(glyphIndex, outWidth, outHeight, outRect, xAdvance);
470+
return getGlyphBitmapByIndex(glyphIndex, outWidth, outHeight, outRect, xAdvance, sharedBitmapData);
470471
}
471472

472473
unsigned char* FontFreeType::getGlyphBitmapByIndex(unsigned int glyphIndex,
473474
int& outWidth,
474475
int& outHeight,
475476
Rect& outRect,
476-
int& xAdvance)
477+
int& xAdvance,
478+
bool& sharedBitmapData)
477479
{
478480
unsigned char* ret = nullptr;
479481

480482
do
481483
{
484+
sharedBitmapData = true;
482485
if (FT_Load_Glyph(_fontFace, glyphIndex, FT_LOAD_RENDER | FT_LOAD_NO_AUTOHINT))
483486
break;
484487

@@ -503,6 +506,8 @@ unsigned char* FontFreeType::getGlyphBitmapByIndex(unsigned int glyphIndex,
503506

504507
if (_outlineSize > 0 && outWidth > 0 && outHeight > 0)
505508
{
509+
sharedBitmapData = false;
510+
506511
auto copyBitmap = new unsigned char[outWidth * outHeight];
507512
memcpy(copyBitmap, ret, outWidth * outHeight * sizeof(unsigned char));
508513

@@ -653,7 +658,6 @@ void FontFreeType::renderCharAt(unsigned char* dest,
653658
memcpy(dest + (iX + (iY * atlasWidth)) * 2, bitmap + bitmap_y * 2, bitmapWidth * 2);
654659
++iY;
655660
}
656-
delete[] bitmap;
657661
}
658662
else
659663
{

axmol/2d/FontFreeType.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,15 @@ class AX_DLL FontFreeType : public Font
129129
int& outHeight,
130130
Rect& outRect,
131131
int& xAdvance,
132-
const GlyphResolution*& fallbackRes);
132+
const GlyphResolution*& fallbackRes,
133+
bool& sharedBitmapData);
133134

134135
unsigned char* getGlyphBitmapByIndex(unsigned int glyphIndex,
135136
int& outWidth,
136137
int& outHeight,
137138
Rect& outRect,
138-
int& xAdvance);
139+
int& xAdvance,
140+
bool& sharedBitmapData);
139141

140142
int getFontAscender() const;
141143
const char* getFontFamily() const;

axmol/rhi/d3d12/Texture12.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ void TextureImpl::updateSubData(int xoffset,
142142

143143
// Query footprint for the subresource
144144
const auto texDesc = _nativeTexture.resource->GetDesc();
145+
146+
assert(xoffset >= 0 && yoffset >= 0);
147+
assert(width > 0 && height > 0);
148+
assert(xoffset + width <= static_cast<int>(texDesc.Width));
149+
assert(yoffset + height <= static_cast<int>(texDesc.Height));
150+
145151
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint;
146152
UINT numRows;
147153
UINT64 rowSizeInBytes;

axmol/tlx/sorted_vector.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ class sorted_vector
165165
return {lower_bound(key), upper_bound(key)};
166166
}
167167

168+
iterator insert(iterator /*hint*/, const value_type& v)
169+
{
170+
// ignore hint, do sorted insertion
171+
return insert(v).first;
172+
}
173+
168174
std::pair<iterator, bool> insert(const value_type& v)
169175
{
170176
auto& cont = _Mypair.second();

0 commit comments

Comments
 (0)