From 866cc118b9cc15c24da9659240ffc5c5c07a0755 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Wed, 27 Nov 2024 18:09:58 +0000 Subject: [PATCH 1/7] backup backup before a rip my hair out. SDF Generation works but is generating incorrect output. --- Engine/source/gfx/gFont.cpp | 426 +++++++++++---------- Engine/source/gfx/gFont.h | 23 +- Engine/source/gfx/gfxFontRenderBatcher.cpp | 4 +- Engine/source/platform/platformFont.h | 2 + 4 files changed, 238 insertions(+), 217 deletions(-) diff --git a/Engine/source/gfx/gFont.cpp b/Engine/source/gfx/gFont.cpp index 05a3f55ac9..ce7d07257d 100644 --- a/Engine/source/gfx/gFont.cpp +++ b/Engine/source/gfx/gFont.cpp @@ -195,13 +195,11 @@ Resource GFont::create(const String &faceName, U32 size, const char *cach GFont::GFont() { - VECTOR_SET_ASSOCIATION(mCharInfoList); VECTOR_SET_ASSOCIATION(mTextureSheets); - std::fill_n(mRemapTable, Font_Table_MAX,-1); - - mCurX = mCurY = mCurSheet = -1; - + mCurX = mCurY = 0; + mCurSheet = -1; + mMaxRowHeight = 0; mPlatformFont = NULL; mSize = 0; mCharSet = 0; @@ -212,6 +210,7 @@ GFont::GFont() mNeedSave = false; mMutex = Mutex::createMutex(); + mCharMap.clear(); } GFont::~GFont() @@ -229,14 +228,10 @@ GFont::~GFont() stream.close(); } - S32 i; - - for(i = 0;i < mCharInfoList.size();i++) - { - SAFE_DELETE_ARRAY(mCharInfoList[i].bitmapData); - } + mCharMap.clear(); - for(i=0; imapEnd) mapEnd = i; - } + U32 charCode = entry.key; // The key (character code) + + mapCount++; // Count this character + + // Update the minimum and maximum character codes + if (charCode < mapBegin) mapBegin = charCode; + if (charCode > mapEnd) mapEnd = charCode; } + // Let's write out all the info we can on this font. Con::printf(" '%s' %dpt", mFaceName.c_str(), mSize); Con::printf(" - %d texture sheets, %d mapped characters.", mTextureSheets.size(), mapCount); @@ -276,8 +273,9 @@ bool GFont::loadCharInfo(const UTF16 ch) { PROFILE_SCOPE(GFont_loadCharInfo); - if(mRemapTable[ch] != -1) - return true; // Not really an error + auto it = mCharMap.find(ch); + if (it != mCharMap.end()) + return true; if(mPlatformFont && mPlatformFont->isValidChar(ch)) { @@ -286,8 +284,7 @@ bool GFont::loadCharInfo(const UTF16 ch) if(ci.bitmapData) addBitmap(ci); - mCharInfoList.push_back(ci); - mRemapTable[ch] = mCharInfoList.size() - 1; + mCharMap[ch] = ci; mNeedSave = true; @@ -298,45 +295,117 @@ bool GFont::loadCharInfo(const UTF16 ch) return false; } -void GFont::addBitmap(PlatformFont::CharInfo &charInfo) +void GFont::generateSDF(const U8* bitmap, S32 width, S32 height, U8* sdfBitmap, S32 sdfWidth, S32 sdfHeight, const F32 spreadFactor) { - U32 nextCurX = U32(mCurX + charInfo.width ); /*7) & ~0x3;*/ - U32 nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + for (S32 y = 0; y < sdfHeight; ++y) + { + for (S32 x = 0; x < sdfWidth; ++x) + { + F32 minDist = F32_MAX; - // These are here for postmortem debugging. - bool routeA = false, routeB = false; + // Map SDF coordinates to original bitmap space + F32 scaledX = x * (F32)width / sdfWidth; + F32 scaledY = y * (F32)height / sdfHeight; - if(mCurSheet == -1 || nextCurY >= TextureSheetSize) - { - routeA = true; - addSheet(); + for (S32 by = 0; by < height; ++by) + { + for (S32 bx = 0; bx < width; ++bx) + { + bool isInside = bitmap[by * width + bx] > 0; + F32 dist = mSqrt((scaledX - bx) * (scaledX - bx) + (scaledY - by) * (scaledY - by)); + if (isInside) + minDist = mMin(minDist, dist); + } + } + // Normalize distance for SDF + F32 sdfValue = (1.0f - mMin(minDist, spreadFactor) / spreadFactor); - // Recalc our nexts. - nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3; - nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + // Store the smoothed SDF value back to the bitmap + sdfBitmap[y * sdfWidth + x] = (U8)(255 * sdfValue); + } } +} - if( nextCurX >= TextureSheetSize) +void GFont::padGlyphBitmap(const U8* original, S32 origWidth, S32 origHeight, U8* padded, S32 padWidth, S32 padHeight, S32 padding) +{ + // Initialize padded bitmap with zeros + dMemset(padded, 0, padWidth * padHeight); + + // Copy the original bitmap into the center of the padded bitmap + for (S32 y = 0; y < origHeight; ++y) { - routeB = true; - mCurX = 0; - mCurY = nextCurY; + for (S32 x = 0; x < origWidth; ++x) + { + S32 srcIndex = y * origWidth + x; + S32 dstIndex = (y + padding) * padWidth + (x + padding); + padded[dstIndex] = original[srcIndex]; + } + } +} + +void GFont::addBitmap(PlatformFont::CharInfo &charInfo) +{ + const S32 padding = 8; + + // Dimensions for the padded bitmap and SDF + S32 paddedWidth = charInfo.width + (2 * padding); + S32 paddedHeight = charInfo.height + (2 * padding); + + S32 sdfWidth = paddedWidth; + S32 sdfHeight = paddedHeight; + + // Allocate buffers + FrameTemp paddedBitmap(paddedWidth * paddedHeight); + + // Allocate buffer for SDF bitmap + FrameTemp sdfBitmap(sdfWidth * sdfHeight); - // Recalc our nexts. - nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3; - nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + // Pad the original bitmap + padGlyphBitmap(charInfo.bitmapData, charInfo.width, charInfo.height, paddedBitmap, paddedWidth, paddedHeight, padding); + + // Generate the SDF + F32 sdfSpread = 1.0f / (4.0f + (charInfo.width / charInfo.height)); + sdfSpread = mMax(charInfo.width, charInfo.height) * sdfSpread; + generateSDF(paddedBitmap, paddedWidth, paddedHeight, sdfBitmap, sdfWidth, sdfHeight, sdfSpread); + + U32 nextCurX = U32(mCurX + sdfWidth); /*7) & ~0x3;*/ + U32 nextCurY = U32(mCurY + sdfHeight); // + 7) & ~0x3; + + if (mCurSheet == -1) + { + addSheet(); } - // Check the Y once more - sometimes we advance to a new row and run off - // the end. - if(nextCurY >= TextureSheetSize) + // Check if the current texture sheet is full and if we need to add a new one + if (nextCurY >= TextureSheetSize) { - routeA = true; + // When nextCurY exceeds sheet height, add a new sheet addSheet(); + nextCurX = mCurX + sdfWidth; + nextCurY = mCurY + sdfHeight; + } + + // If the X position exceeds the sheet width, move to the next row + if (nextCurX >= TextureSheetSize) + { + // Move to the next row, reset mCurX and adjust mCurY + mCurX = 0; // Reset to the start of the row + + // Move down by the height of the tallest character in the current row + mCurY += mMaxRowHeight + padding; // Add the row height and padding + + // Ensure the Y position doesn't exceed the texture sheet height + if (mCurY >= TextureSheetSize) + { + // If we've exceeded the texture sheet height, add a new sheet + addSheet(); + mCurX = 0; + mCurY = 0; // Reset to the top of the new sheet + } - // Recalc our nexts. - nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3; - nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + // Recalculate the position for the current character + nextCurX = mCurX + sdfWidth; + nextCurY = mCurY + sdfHeight; } charInfo.bitmapIndex = mCurSheet; @@ -350,9 +419,17 @@ void GFont::addBitmap(PlatformFont::CharInfo &charInfo) AssertFatal(bmp->getFormat() == GFXFormatA8, "GFont::addBitmap - cannot added characters to non-greyscale textures!"); - for(y = 0;y < charInfo.height;y++) - for(x = 0;x < charInfo.width;x++) - *bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = charInfo.bitmapData[y * charInfo.width + x]; + for(y = 0;y < sdfHeight;y++) + for(x = 0;x < sdfWidth;x++) + *bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = sdfBitmap[y * sdfWidth + x]; + + // update our width and height. + //charInfo.width = sdfWidth; + //charInfo.height = sdfHeight; + charInfo.texWidth = sdfWidth; + charInfo.texHeight = sdfHeight; + + mMaxRowHeight = mMax(mMaxRowHeight, sdfHeight); mTextureSheets[mCurSheet].refresh(); } @@ -369,8 +446,6 @@ void GFont::addSheet() mTextureSheets.increment(); mTextureSheets.last() = handle; - mCurX = 0; - mCurY = 0; mCurSheet = mTextureSheets.size() - 1; } @@ -382,12 +457,11 @@ const PlatformFont::CharInfo &GFont::getCharInfo(const UTF16 in_charIndex) AssertFatal(in_charIndex, "GFont::getCharInfo - can't get info for char 0!"); - if(mRemapTable[in_charIndex] == -1) + auto it = mCharMap.find(in_charIndex); + if (it == mCharMap.end()) loadCharInfo(in_charIndex); - - AssertFatal(mRemapTable[in_charIndex] != -1, "No remap info for this character"); - return mCharInfoList[mRemapTable[in_charIndex]]; + return mCharMap[in_charIndex]; } const PlatformFont::CharInfo &GFont::getDefaultCharInfo() @@ -657,54 +731,54 @@ void GFont::wrapString(const UTF8 *txt, U32 lineWidth, Vector &startLineOff bool GFont::read(Stream& io_rStream) { - // Handle versioning - U32 version; - io_rStream.read(&version); - if(version != csm_fileVersion) - return false; - - char buf[256]; - io_rStream.readString(buf); - mFaceName = buf; - - io_rStream.read(&mSize); - io_rStream.read(&mCharSet); - - io_rStream.read(&mHeight); - io_rStream.read(&mBaseline); - io_rStream.read(&mAscent); - io_rStream.read(&mDescent); - - U32 size = 0; - io_rStream.read(&size); - mCharInfoList.setSize(size); - U32 i; - for(i = 0; i < size; i++) - { - PlatformFont::CharInfo *ci = &mCharInfoList[i]; - io_rStream.read(&ci->bitmapIndex); - io_rStream.read(&ci->xOffset); - io_rStream.read(&ci->yOffset); - io_rStream.read(&ci->width); - io_rStream.read(&ci->height); - io_rStream.read(&ci->xOrigin); - io_rStream.read(&ci->yOrigin); - io_rStream.read(&ci->xIncrement); - ci->bitmapData = NULL; + // Handle versioning + U32 version; + io_rStream.read(&version); + if(version != csm_fileVersion) + return false; + + char buf[256]; + io_rStream.readString(buf); + mFaceName = buf; + + io_rStream.read(&mSize); + io_rStream.read(&mCharSet); + + io_rStream.read(&mHeight); + io_rStream.read(&mBaseline); + io_rStream.read(&mAscent); + io_rStream.read(&mDescent); + + U32 size = 0; + io_rStream.read(&size); + for(U32 i = 0; i < size; i++) + { + U32 charCode; + io_rStream.read(&charCode); + + PlatformFont::CharInfo ci; + io_rStream.read(&ci.bitmapIndex); + io_rStream.read(&ci.xOffset); + io_rStream.read(&ci.yOffset); + io_rStream.read(&ci.texWidth); + io_rStream.read(&ci.texHeight); + io_rStream.read(&ci.width); + io_rStream.read(&ci.height); + io_rStream.read(&ci.xOrigin); + io_rStream.read(&ci.yOrigin); + io_rStream.read(&ci.xIncrement); + ci.bitmapData = NULL; + + // Insert the character information into the map + mCharMap[charCode] = ci; // Insert the key-value pair into the map } U32 numSheets = 0; io_rStream.read(&numSheets); - for(i = 0; i < numSheets; i++) + for(U32 i = 0; i < numSheets; i++) { GBitmap *bmp = new GBitmap; - /*String path = String::ToString("%s/%s %d %d (%s).png", Con::getVariable("$GUI::fontCacheDirectory"), mFaceName.c_str(), mSize, i, getCharSetName(mCharSet)); - if(!bmp->readBitmap("png", path)) - { - delete bmp; - return false; - }*/ U32 len; io_rStream.read(&len); @@ -724,32 +798,6 @@ bool GFont::read(Stream& io_rStream) io_rStream.read(&mCurY); io_rStream.read(&mCurSheet); - // Read the remap table. - U32 minGlyph, maxGlyph; - io_rStream.read(&minGlyph); - io_rStream.read(&maxGlyph); - - if(maxGlyph >= minGlyph) - { - // Length of buffer.. - U32 buffLen; - io_rStream.read(&buffLen); - - // Read the buffer. - FrameTemp inBuff(buffLen); - io_rStream.read(buffLen, inBuff); - - // Decompress. - uLongf destLen = (static_cast(maxGlyph) - minGlyph + 1) * sizeof(S32); - uncompress((Bytef*)&mRemapTable[minGlyph], &destLen, (Bytef*)(S32*)inBuff, buffLen); - - AssertISV(destLen == (maxGlyph-minGlyph+1)*sizeof(S32), "GFont::read - invalid remap table data!"); - - // Make sure we've got the right endianness. - for(i = minGlyph; i <= maxGlyph; i++) - mRemapTable[i] = convertBEndianToHost(mRemapTable[i]); - } - return (io_rStream.getStatus() == Stream::Ok); } @@ -768,27 +816,36 @@ bool GFont::write(Stream& stream) stream.write(mAscent); stream.write(mDescent); - // Write char info list - stream.write(U32(mCharInfoList.size())); - U32 i; - for(i = 0; i < mCharInfoList.size(); i++) + // Write char info count + stream.write(U32(mCharMap.size())); + + // Write each character's info (iterate through the unordered_map) + for (const auto& entry : mCharMap) { - const PlatformFont::CharInfo *ci = &mCharInfoList[i]; - stream.write(ci->bitmapIndex); - stream.write(ci->xOffset); - stream.write(ci->yOffset); - stream.write(ci->width); - stream.write(ci->height); - stream.write(ci->xOrigin); - stream.write(ci->yOrigin); - stream.write(ci->xIncrement); - } + U32 charCode = entry.key; // Character code (U32) + stream.write(charCode); + const PlatformFont::CharInfo& ci = entry.value; // CharInfo associated with the character + + // Write each part of the CharInfo structure to the stream + stream.write(ci.bitmapIndex); + stream.write(ci.xOffset); + stream.write(ci.yOffset); + stream.write(ci.texWidth); + stream.write(ci.texHeight); + stream.write(ci.width); + stream.write(ci.height); + stream.write(ci.xOrigin); + stream.write(ci.yOrigin); + stream.write(ci.xIncrement); + } stream.write(mTextureSheets.size()); - for (i = 0; i < mTextureSheets.size(); i++) + for (U32 i = 0; i < mTextureSheets.size(); i++) { - /*String path = String::ToString("%s/%s %d %d (%s).png", Con::getVariable("$GUI::fontCacheDirectory"), mFaceName.c_str(), mSize, i, getCharSetName(mCharSet)); - mTextureSheets[i].getBitmap()->writeBitmap("png", path);*/ + // Debugging write out to images. + String path = String::ToString("%s/%s %d %d (%s).png", Con::getVariable("$GUI::fontCacheDirectory"), mFaceName.c_str(), mSize, i, getCharSetName(mCharSet)); + mTextureSheets[i].getBitmap()->writeBitmap("png", path); + mTextureSheets[i].getBitmap()->writeBitmapStream("png", stream); } @@ -797,45 +854,6 @@ bool GFont::write(Stream& stream) stream.write(mCurY); stream.write(mCurSheet); - // Get the min/max we have values for, and only write that range out. - S32 minGlyph = S32_MAX, maxGlyph = 0; - - for(i = 0; i < 65536; i++) - { - if(mRemapTable[i] != -1) - { - if(i>maxGlyph) maxGlyph = i; - if(i= minGlyph) - { - // Put everything big endian, to be consistent. Do this inplace. - for(i = minGlyph; i <= maxGlyph; i++) - mRemapTable[i] = convertHostToBEndian(mRemapTable[i]); - - { - // Compress. - const U32 buffSize = 128 * 1024; - FrameTemp outBuff(buffSize); - uLongf destLen = buffSize * sizeof(S32); - compress2((Bytef*)(S32*)outBuff, &destLen, (Bytef*)(S32*)&mRemapTable[minGlyph], (static_cast(maxGlyph) - minGlyph + 1) * sizeof(S32), 9); - - // Write out. - stream.write((U32)destLen); - stream.write(destLen, outBuff); - } - - // Put us back to normal. - for(i = minGlyph; i <= maxGlyph; i++) - mRemapTable[i] = convertBEndianToHost(mRemapTable[i]); - } - return (stream.getStatus() == Stream::Ok); } @@ -847,11 +865,11 @@ void GFont::exportStrip(const char *fileName, U32 padding, U32 kerning) S32 heightMin=0, heightMax=0; - for(S32 i=0; i glyphList; - glyphList.reserve(mCharInfoList.size()); + glyphList.reserve(mCharMap.size()); U32 curWidth = 0; - for(S32 i=0; igetFormat()); - lastGlyphMap.charId = i; + lastGlyphMap.bitmap = new GBitmap(entry.value.width + kerning + 2 * padding, entry.value.height + 2 * padding, false, strip->getFormat()); + lastGlyphMap.charId = entry.key; // Copy the rect. - RectI ri(curWidth, getBaseline() - mCharInfoList[i].yOrigin, lastGlyphMap.bitmap->getWidth(), lastGlyphMap.bitmap->getHeight()); + RectI ri(curWidth, getBaseline() - entry.value.yOrigin, lastGlyphMap.bitmap->getWidth(), lastGlyphMap.bitmap->getHeight()); Point2I outRi(0,0); lastGlyphMap.bitmap->copyRect(strip, ri, outRi); // Update glyph attributes. - mCharInfoList[i].width = lastGlyphMap.bitmap->getWidth(); - mCharInfoList[i].height = lastGlyphMap.bitmap->getHeight(); - mCharInfoList[i].xOffset -= kerning + padding; - mCharInfoList[i].xIncrement += kerning; - mCharInfoList[i].yOffset -= padding; + entry.value.width = lastGlyphMap.bitmap->getWidth(); + entry.value.height = lastGlyphMap.bitmap->getHeight(); + entry.value.xOffset -= kerning + padding; + entry.value.xIncrement += kerning; + entry.value.yOffset -= padding; // Advance. curWidth += ri.extent.x; @@ -961,7 +979,7 @@ void GFont::importStrip(const char *fileName, U32 padding, U32 kerning) S32 maxHeight = 0; for(U32 i = 0; i < glyphList.size(); i++) { - PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId]; + PlatformFont::CharInfo *ci = &mCharMap[glyphList[i].charId]; if(ci->height > maxHeight) maxHeight = ci->height; @@ -1021,7 +1039,7 @@ void GFont::importStrip(const char *fileName, U32 padding, U32 kerning) for(S32 i=0; ibitmapIndex; mTextureSheets[bi]->getBitmap()->copyRect(glyphList[i].bitmap, RectI(0,0, glyphList[i].bitmap->getWidth(),glyphList[i].bitmap->getHeight()), Point2I(ci->xOffset, ci->yOffset)); } diff --git a/Engine/source/gfx/gFont.h b/Engine/source/gfx/gFont.h index 2f5274af24..8e8cf0cef6 100644 --- a/Engine/source/gfx/gFont.h +++ b/Engine/source/gfx/gFont.h @@ -39,18 +39,21 @@ #ifndef _GFXTEXTUREHANDLE_H_ #include "gfx/gfxTextureHandle.h" #endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif GFX_DeclareTextureProfile(GFXFontTextureProfile); -#define Font_Table_MAX 65536 class GFont { public: + typedef Map typeCharMap; enum Constants { TabWidthInSpaces = 3, - TextureSheetSize = 256, + TextureSheetSize = 512, }; public: @@ -128,10 +131,10 @@ class GFont protected: bool loadCharInfo(const UTF16 ch); + void generateSDF(const U8* bitmap, S32 width, S32 height, U8* sdfBitmap, S32 sdfWidth, S32 sdfHeight, const F32 spreadFactor); + void padGlyphBitmap(const U8* original, S32 origWidth, S32 origHeight, U8* padded, S32 padWidth, S32 padHeight, S32 padding); void addBitmap(PlatformFont::CharInfo &charInfo); void addSheet(void); - void assignSheet(S32 sheetNum, GBitmap *bmp); - void *mMutex; private: @@ -143,6 +146,7 @@ class GFont S32 mCurX; S32 mCurY; S32 mCurSheet; + S32 mMaxRowHeight; bool mNeedSave; Torque::Path mGFTFile; @@ -155,12 +159,8 @@ class GFont U32 mAscent; U32 mDescent; - /// List of character info structures, must be accessed through the - /// getCharInfo(U32) function to account for remapping. - Vector mCharInfoList; - - /// Index remapping - S32 mRemapTable[Font_Table_MAX]; + // Cache charinfo into a map. + typeCharMap mCharMap; }; inline U32 GFont::getCharXIncrement(const UTF16 in_charIndex) @@ -183,7 +183,8 @@ inline U32 GFont::getCharHeight(const UTF16 in_charIndex) inline bool GFont::isValidChar(const UTF16 in_charIndex) const { - if(mRemapTable[in_charIndex] != -1) + auto it = mCharMap.find(in_charIndex); + if (it != mCharMap.end()) return true; if(mPlatformFont) diff --git a/Engine/source/gfx/gfxFontRenderBatcher.cpp b/Engine/source/gfx/gfxFontRenderBatcher.cpp index 4fce36b4e1..978a57392a 100644 --- a/Engine/source/gfx/gfxFontRenderBatcher.cpp +++ b/Engine/source/gfx/gfxFontRenderBatcher.cpp @@ -96,9 +96,9 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) const F32 texWidth = (F32)tex->getWidth(); const F32 texHeight = (F32)tex->getHeight(); const F32 texLeft = (F32)(ci.xOffset) / texWidth; - const F32 texRight = (F32)(ci.xOffset + ci.width) / texWidth; + const F32 texRight = (F32)(ci.xOffset + ci.texWidth) / texWidth; const F32 texTop = (F32)(ci.yOffset) / texHeight; - const F32 texBottom = (F32)(ci.yOffset + ci.height) / texHeight; + const F32 texBottom = (F32)(ci.yOffset + ci.texHeight) / texHeight; const F32 fillConventionOffset = GFX->getFillConventionOffset(); const F32 screenLeft = drawX - fillConventionOffset; diff --git a/Engine/source/platform/platformFont.h b/Engine/source/platform/platformFont.h index 63a47d95a5..5f35e05043 100644 --- a/Engine/source/platform/platformFont.h +++ b/Engine/source/platform/platformFont.h @@ -65,6 +65,8 @@ class PlatformFont /// rendered, i.e., \n, \r, etc. U32 xOffset; ///< x offset into bitmap sheet U32 yOffset; ///< y offset into bitmap sheet + U32 texWidth; + U32 texHeight; U32 width; ///< width of character (pixels) U32 height; ///< height of character (pixels) S32 xOrigin; From ae0e7cd663a7d499f720d48d7a43fb2903d5508f Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 28 Nov 2024 13:51:02 +0000 Subject: [PATCH 2/7] fix sdf sdf fixed had to use an upscaled original glyph.... added shader in hlsl to render the font on screen statics for now but will become uniforms for customisation. --- Engine/source/gfx/gFont.cpp | 217 +++++++++++++++--- Engine/source/gfx/gfxFontRenderBatcher.cpp | 27 ++- Engine/source/gfx/gfxFontRenderBatcher.h | 6 +- .../rendering/scripts/gfxData/shaders.tscript | 11 + .../shaders/fixedFunction/sdfFontRenderP.hlsl | 83 +++++++ 5 files changed, 311 insertions(+), 33 deletions(-) create mode 100644 Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl diff --git a/Engine/source/gfx/gFont.cpp b/Engine/source/gfx/gFont.cpp index ce7d07257d..e94a712207 100644 --- a/Engine/source/gfx/gFont.cpp +++ b/Engine/source/gfx/gFont.cpp @@ -137,7 +137,7 @@ Resource GFont::create(const String &faceName, U32 size, const char *cach } // Otherwise attempt to have the platform generate a new font - PlatformFont *platFont = createPlatformFont(faceName, size, charset); + PlatformFont *platFont = createPlatformFont(faceName, size*4, charset); if (platFont == NULL) { @@ -176,11 +176,10 @@ Resource GFont::create(const String &faceName, U32 size, const char *cach font->mFaceName = faceName; font->mSize = size; font->mCharSet = charset; - - font->mHeight = platFont->getFontHeight(); + font->mHeight = platFont->getFontHeight() * 0.25; font->mBaseline = platFont->getFontBaseLine(); font->mAscent = platFont->getFontBaseLine(); - font->mDescent = platFont->getFontHeight() - platFont->getFontBaseLine(); + font->mDescent = (platFont->getFontHeight() - platFont->getFontBaseLine()); // Flag it to save when we exit font->mNeedSave = true; @@ -301,29 +300,197 @@ void GFont::generateSDF(const U8* bitmap, S32 width, S32 height, U8* sdfBitmap, { for (S32 x = 0; x < sdfWidth; ++x) { - F32 minDist = F32_MAX; - // Map SDF coordinates to original bitmap space F32 scaledX = x * (F32)width / sdfWidth; F32 scaledY = y * (F32)height / sdfHeight; + F32 minDistInsideSquared = F32_MAX; // Closest distance to an "inside" pixel + F32 minDistOutsideSquared = F32_MAX; // Closest distance to an "outside" pixel + + // Iterate over the entire bitmap (can optimize to a smaller region) for (S32 by = 0; by < height; ++by) { for (S32 bx = 0; bx < width; ++bx) { bool isInside = bitmap[by * width + bx] > 0; - F32 dist = mSqrt((scaledX - bx) * (scaledX - bx) + (scaledY - by) * (scaledY - by)); + F32 dx = scaledX - bx; + F32 dy = scaledY - by; + F32 distSquared = dx * dx + dy * dy; + + // Update the minimum distances for inside and outside if (isInside) - minDist = mMin(minDist, dist); + minDistInsideSquared = mMin(minDistInsideSquared, distSquared); + else + minDistOutsideSquared = mMin(minDistOutsideSquared, distSquared); } } - // Normalize distance for SDF - F32 sdfValue = (1.0f - mMin(minDist, spreadFactor) / spreadFactor); - // Store the smoothed SDF value back to the bitmap - sdfBitmap[y * sdfWidth + x] = (U8)(255 * sdfValue); + // Compute the signed distance + F32 minDistInside = mSqrt(minDistInsideSquared); + F32 minDistOutside = mSqrt(minDistOutsideSquared); + F32 signedDist = minDistOutside - minDistInside; + + // Normalize the signed distance + F32 normalizedDist = 0.5f + (signedDist / spreadFactor) * 0.5f; + + // Clamp and scale to [0, 255] + normalizedDist = mClampF(normalizedDist, 0.0f, 1.0f); + sdfBitmap[y * sdfWidth + x] = (U8)(255 * normalizedDist); } } + + //// FMM SDF method + //// Initialize distance field + //Vector distanceField; + //distanceField.setSize(sdfWidth * sdfHeight); + //distanceField.fill(F32_MAX); + + //// Helper to check if a pixel is inside the glyph (foreground) + //auto isInside = [&](S32 x, S32 y) -> bool { + // return bitmap[y * width + x] > 0; + //}; + + //// Priority queue vectors (x, y, distance) + //Vector pqX, pqY; + //Vector pqDist; + + //// Initialize priority queue with foreground pixels (distance = 0) + //for (S32 y = 0; y < sdfHeight; ++y) + //{ + // for (S32 x = 0; x < sdfWidth; ++x) + // { + // // Map the SDF coordinates to the bitmap space + // F32 scaledX = x * (F32)width / sdfWidth; + // F32 scaledY = y * (F32)height / sdfHeight; + + // // Check if the pixel in the bitmap is inside (foreground) + // bool inside = false; + // for (S32 by = 0; by < height; ++by) + // { + // for (S32 bx = 0; bx < width; ++bx) + // { + // if (isInside(bx, by)) + // { + // F32 dist = mSqrt((scaledX - bx) * (scaledX - bx) + (scaledY - by) * (scaledY - by)); + // if (dist < 1.0f) // Only consider close points as part of the glyph + // { + // inside = true; + // break; + // } + // } + // } + // if (inside) break; + // } + + // if (inside) + // { + // // Mark the foreground pixels with distance 0 + // distanceField[y * sdfWidth + x] = 0.0f; + // pqX.push_back(x); + // pqY.push_back(y); + // pqDist.push_back(0.0f); + // } + // else + // { + // // Set the background pixels to a large distance initially + // distanceField[y * sdfWidth + x] = F32_MAX; + // } + // } + //} + + //// Neighbors for 8-way connectivity (up, down, left, right, and diagonals) + //const S32 dx[8] = { -1, 1, 0, 0, -1, 1, -1, 1 }; + //const S32 dy[8] = { 0, 0, -1, 1, -1, -1, 1, 1 }; + + //// Fast Marching Method: propagate distances + //while (!pqX.empty()) + //{ + // // Get the pixel with the smallest distance (priority queue) + // S32 x = pqX.front(); + // S32 y = pqY.front(); + // F32 currentDist = pqDist.front(); + + // pqX.erase(pqX.begin()); + // pqY.erase(pqY.begin()); + // pqDist.erase(pqDist.begin()); + + // // Explore neighbors + // for (S32 i = 0; i < 8; ++i) + // { + // S32 nx = x + dx[i]; + // S32 ny = y + dy[i]; + + // if (nx >= 0 && ny >= 0 && nx < sdfWidth && ny < sdfHeight) + // { + // // Compute the new distance as the grid distance (simplified) + // F32 newDist = currentDist + 1.0f; + + // if (newDist < distanceField[ny * sdfWidth + nx]) + // { + // distanceField[ny * sdfWidth + nx] = newDist; + + // // Push the new distances to the "priority queue" (sorted vector) + // pqX.push_back(nx); + // pqY.push_back(ny); + // pqDist.push_back(newDist); + + // // Sort the queue (to mimic priority) + // for (S32 j = pqDist.size() - 1; j > 0; --j) + // { + // if (pqDist[j] < pqDist[j - 1]) + // { + // std::swap(pqX[j], pqX[j - 1]); + // std::swap(pqY[j], pqY[j - 1]); + // std::swap(pqDist[j], pqDist[j - 1]); + // } + // } + // } + // } + // } + //} + + //// Find the maximum distance in the field for normalization + //F32 maxDistance = 0.0f; + //for (S32 i = 0; i < sdfWidth * sdfHeight; ++i) + //{ + // if (distanceField[i] > maxDistance) + // { + // maxDistance = distanceField[i]; + // } + //} + + //// Smoothing step: Apply Gaussian blur (or simple kernel filter) + //Vector smoothedField; + //smoothedField.setSize(sdfWidth* sdfHeight); + //smoothedField.fill(0.0f); + + //for (S32 y = 1; y < sdfHeight - 1; ++y) + //{ + // for (S32 x = 1; x < sdfWidth - 1; ++x) + // { + // // Simple 3x3 box blur (you can replace it with Gaussian blur for better results) + // F32 sum = 0.0f; + // for (S32 dy = -1; dy <= 1; ++dy) + // { + // for (S32 dx = -1; dx <= 1; ++dx) + // { + // sum += distanceField[(y + dy) * sdfWidth + (x + dx)]; + // } + // } + // smoothedField[y * sdfWidth + x] = sum / 9.0f; // Average of 3x3 neighborhood + // } + //} + + //// Invert the distance field so that foreground pixels are black (0), and background pixels are white (255) + //for (S32 y = 0; y < sdfHeight; ++y) + //{ + // for (S32 x = 0; x < sdfWidth; ++x) + // { + // // Normalize the value to the range [0, 255], inverted + // F32 sdfValue = 1.0f - (smoothedField[y * sdfWidth + x] / maxDistance); + // sdfBitmap[y * sdfWidth + x] = (U8)(255.0f * sdfValue); + // } + //} } void GFont::padGlyphBitmap(const U8* original, S32 origWidth, S32 origHeight, U8* padded, S32 padWidth, S32 padHeight, S32 padding) @@ -348,24 +515,22 @@ void GFont::addBitmap(PlatformFont::CharInfo &charInfo) const S32 padding = 8; // Dimensions for the padded bitmap and SDF - S32 paddedWidth = charInfo.width + (2 * padding); - S32 paddedHeight = charInfo.height + (2 * padding); - - S32 sdfWidth = paddedWidth; - S32 sdfHeight = paddedHeight; + S32 paddedWidth = charInfo.width + (2 * (padding * 4)); + S32 paddedHeight = charInfo.height + (2 * (padding * 4)); // Allocate buffers FrameTemp paddedBitmap(paddedWidth * paddedHeight); + padGlyphBitmap(charInfo.bitmapData, charInfo.width, charInfo.height, paddedBitmap, paddedWidth, paddedHeight, padding*4); + + S32 sdfWidth = paddedWidth * 0.25; + S32 sdfHeight = paddedHeight * 0.25; // Allocate buffer for SDF bitmap FrameTemp sdfBitmap(sdfWidth * sdfHeight); - // Pad the original bitmap - padGlyphBitmap(charInfo.bitmapData, charInfo.width, charInfo.height, paddedBitmap, paddedWidth, paddedHeight, padding); - // Generate the SDF - F32 sdfSpread = 1.0f / (4.0f + (charInfo.width / charInfo.height)); - sdfSpread = mMax(charInfo.width, charInfo.height) * sdfSpread; + F32 sdfSpread = 16.0f;// / (4.0f + (charInfo.width / charInfo.height)); + //sdfSpread = mMax(charInfo.width, charInfo.height) * sdfSpread; generateSDF(paddedBitmap, paddedWidth, paddedHeight, sdfBitmap, sdfWidth, sdfHeight, sdfSpread); U32 nextCurX = U32(mCurX + sdfWidth); /*7) & ~0x3;*/ @@ -424,11 +589,9 @@ void GFont::addBitmap(PlatformFont::CharInfo &charInfo) *bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = sdfBitmap[y * sdfWidth + x]; // update our width and height. - //charInfo.width = sdfWidth; - //charInfo.height = sdfHeight; - charInfo.texWidth = sdfWidth; - charInfo.texHeight = sdfHeight; - + charInfo.width = sdfWidth; + charInfo.height = sdfHeight; + charInfo.xIncrement = charInfo.xIncrement * 0.25; mMaxRowHeight = mMax(mMaxRowHeight, sdfHeight); mTextureSheets[mCurSheet].refresh(); diff --git a/Engine/source/gfx/gfxFontRenderBatcher.cpp b/Engine/source/gfx/gfxFontRenderBatcher.cpp index 978a57392a..08fcdbdfcc 100644 --- a/Engine/source/gfx/gfxFontRenderBatcher.cpp +++ b/Engine/source/gfx/gfxFontRenderBatcher.cpp @@ -22,6 +22,7 @@ #include "gfx/gfxFontRenderBatcher.h" #include "gfx/gFont.h" +#include "materials/shaderData.h" FontRenderBatcher::FontRenderBatcher() : mStorage(8096) { @@ -48,6 +49,17 @@ FontRenderBatcher::FontRenderBatcher() : mStorage(8096) f.setColorWrites(true, true, true, false); // NOTE: comment this out if alpha write is needed mFontSB = GFX->createStateBlock(f); } + + // Find ShaderData + ShaderData* shaderData; + mFontShader = Sim::findObject("SDFFontRenderingGUI", shaderData) ? shaderData->getShader() : NULL; + if (!mFontShader) + { + Con::errorf("FontRenderBatcher - could not find Font shader"); + } + // Create ShaderConstBuffer and Handles + mFontShaderConsts = mFontShader->allocConstBuffer(); + } void FontRenderBatcher::render( F32 rot, const Point2F &offset ) @@ -89,16 +101,16 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) const PlatformFont::CharInfo &ci = mFont->getCharInfo( m.c ); // Where are we drawing it? - F32 drawY = offset.y + mFont->getBaseline() - ci.yOrigin * TEXT_MAG; + F32 drawY = offset.y + (mFont->getBaseline() * 0.25) - (ci.yOrigin * 0.25) - (mFont->getDescent() * 0.25); F32 drawX = offset.x + m.x + ci.xOrigin; // Figure some values. const F32 texWidth = (F32)tex->getWidth(); const F32 texHeight = (F32)tex->getHeight(); const F32 texLeft = (F32)(ci.xOffset) / texWidth; - const F32 texRight = (F32)(ci.xOffset + ci.texWidth) / texWidth; + const F32 texRight = (F32)(ci.xOffset + ci.width) / texWidth; const F32 texTop = (F32)(ci.yOffset) / texHeight; - const F32 texBottom = (F32)(ci.yOffset + ci.texHeight) / texHeight; + const F32 texBottom = (F32)(ci.yOffset + ci.height) / texHeight; const F32 fillConventionOffset = GFX->getFillConventionOffset(); const F32 screenLeft = drawX - fillConventionOffset; @@ -172,8 +184,11 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) AssertFatal(currentPt <= mLength * 6, "FontRenderBatcher::render - too many verts for length of string!"); GFX->setVertexBuffer(verts); - GFX->setupGenericShaders( GFXDevice::GSAddColorTexture ); - + GFX->setShader(mFontShader); + GFX->setShaderConstBuffer(mFontShaderConsts); + MatrixF tempMatrix = GFX->getProjectionMatrix() * GFX->getViewMatrix() * GFX->getWorldMatrix(); + mFontShaderConsts->set(mFontShader->getShaderConstHandle("$modelView"), tempMatrix, GFXSCT_Float4x4); + // Now do an optimal render! for( S32 i = 0; i < mSheets.size(); i++ ) { @@ -182,6 +197,8 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) if(!mSheets[i]->numChars ) continue; + Point2I texDim = mFont->getTextureHandle(i).getWidthHeight(); + mFontShaderConsts->setSafe(mFontShader->getShaderConstHandle("$texDim"), Point2F(texDim.x, texDim.y)); GFX->setTexture( 0, mFont->getTextureHandle(i) ); GFX->drawPrimitive(GFXTriangleList, mSheets[i]->startVertex, mSheets[i]->numChars * 2); diff --git a/Engine/source/gfx/gfxFontRenderBatcher.h b/Engine/source/gfx/gfxFontRenderBatcher.h index 25f707db49..85c3f8a0d1 100644 --- a/Engine/source/gfx/gfxFontRenderBatcher.h +++ b/Engine/source/gfx/gfxFontRenderBatcher.h @@ -54,6 +54,10 @@ class FontRenderBatcher SheetMarker &getSheetMarker(U32 sheetID); + // font shader. + GFXShaderRef mFontShader; + GFXShaderConstBufferRef mFontShaderConsts; + public: FontRenderBatcher(); @@ -64,4 +68,4 @@ class FontRenderBatcher void render(F32 rot, const Point2F &offset ); }; -#endif \ No newline at end of file +#endif diff --git a/Templates/BaseGame/game/core/rendering/scripts/gfxData/shaders.tscript b/Templates/BaseGame/game/core/rendering/scripts/gfxData/shaders.tscript index 239c76a80c..bb08bb9c4e 100644 --- a/Templates/BaseGame/game/core/rendering/scripts/gfxData/shaders.tscript +++ b/Templates/BaseGame/game/core/rendering/scripts/gfxData/shaders.tscript @@ -154,6 +154,17 @@ singleton ShaderData( CubemapSaveShader ) //----------------------------------------------------------------------------- // GUI shaders //----------------------------------------------------------------------------- +singleton ShaderData( SDFFontRenderingGUI ) +{ + DXVertexShaderFile = $Core::CommonShaderPath @ "/fixedFunction/addColorTextureV.hlsl"; + DXPixelShaderFile = $Core::CommonShaderPath @ "/fixedFunction/sdfFontRenderP.hlsl"; + + OGLVertexShaderFile = $Core::CommonShaderPath @ "/fixedFunction/gl/colorV.glsl"; + OGLPixelShaderFile = $Core::CommonShaderPath @ "/fixedFunction/gl/roundedRectangleP.glsl"; + + pixVersion = 3.0; +}; + singleton ShaderData( RoundedRectangleGUI ) { DXVertexShaderFile = $Core::CommonShaderPath @ "/fixedFunction/colorV.hlsl"; diff --git a/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl b/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl new file mode 100644 index 0000000000..51ca537396 --- /dev/null +++ b/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../shaderModel.hlsl" + +struct Conn +{ + float4 HPOS : TORQUE_POSITION; + float4 color : COLOR; + float2 texCoord : TEXCOORD0; +}; + +TORQUE_UNIFORM_SAMPLER2D(diffuseMap, 0); +uniform float2 texDim; + +// Customizable parameters +static const float sdfThreshold = 0.6; // Center threshold for the edge of the glyph +static const float slopeMultiplier = 1.0; // Multiplies the slope of the transition +static const float inBias = -0.05; // Shifts the threshold inward +static const float outBias = 0.05; // Shifts the threshold outward +static const float smoothness = 0.05; // Controls the falloff region +static const float alphaThreshold = 0.1f; // Sets the alpha threshold +static const int supersample = 4; // Supersampling factor + +float4 main( Conn IN ) : TORQUE_TARGET0 +{ + float distance = TORQUE_TEX2D(diffuseMap, IN.texCoord).a; + + float adjustedThreshold = sdfThreshold + inBias - outBias; + + float edge = (distance - adjustedThreshold) * slopeMultiplier; + + // Smooth edges using the distance field + float alpha = smoothstep(-smoothness, smoothness, edge); + + if(supersample > 1) + { + float2 texelSize = float2(1.0f / texDim.x, 1.0f / texDim.y); + float2 offsets[4] = { + float2(-0.5, -0.5) * texelSize, + float2(0.5, -0.5) * texelSize, + float2(-0.5, 0.5) * texelSize, + float2(0.5, 0.5) * texelSize + }; + + float totalAlpha = 0.0; + for(uint i = 0; i < (uint)supersample; ++i) + { + float2 offsetCoord = IN.texCoord + offsets[i % 4]; + float sampleDist = TORQUE_TEX2D(diffuseMap, offsetCoord).a; + float sampleEdge = (sampleDist - adjustedThreshold) * slopeMultiplier; + totalAlpha += smoothstep(-smoothness, smoothness, sampleEdge); + } + alpha = totalAlpha / float(supersample); + } + + // Ensure alpha does not go below a minimum threshold for transparency + // alpha = max(alpha, alphaThreshold); + + // Debug: Uncomment to visualize alpha field + //return float4(alpha, alpha, alpha, 1.0); + + return float4(IN.color.rgb, alpha); +} \ No newline at end of file From b79b6733fc80e929ed21f7854d743c836916b52c Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 28 Nov 2024 14:03:48 +0000 Subject: [PATCH 3/7] Update sdfFontRenderP.hlsl some tweaks and docs added to the shader also removed previous debug alpha check and added a discard --- .../shaders/fixedFunction/sdfFontRenderP.hlsl | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl b/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl index 51ca537396..8559b97ce3 100644 --- a/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl +++ b/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl @@ -33,14 +33,20 @@ TORQUE_UNIFORM_SAMPLER2D(diffuseMap, 0); uniform float2 texDim; // Customizable parameters -static const float sdfThreshold = 0.6; // Center threshold for the edge of the glyph -static const float slopeMultiplier = 1.0; // Multiplies the slope of the transition -static const float inBias = -0.05; // Shifts the threshold inward -static const float outBias = 0.05; // Shifts the threshold outward +static const float sdfThreshold = 0.5; // Center threshold for the edge of the glyph + // Min: 0.4 , Max: 0.6 +static const float slopeMultiplier = 0.5; // Multiplies the slope of the transition + // Min: 0.5 (softer edges), Max: 4.0 (sharper edges) +static const float inBias = 0.0; // Shifts the threshold inward + // Min: -0.05 (slightly thinner), Max: 0.0 (default, no inward shift) +static const float outBias = 0.0; // Shifts the threshold outward + // Min: 0.0 (default, no outward shift), Max: 0.05 (slightly thicker) static const float smoothness = 0.05; // Controls the falloff region -static const float alphaThreshold = 0.1f; // Sets the alpha threshold -static const int supersample = 4; // Supersampling factor - + // Min: 0.005 (very sharp edges), Max: 0.05 (soft, anti-aliased edges) +static const int supersample = 1; // Supersampling factor + // Min: 1 (no supersampling), Max: 8 (high-quality, computationally expensive) +static const float alphaThreshold = 0.0f; // Sets the alpha threshold + float4 main( Conn IN ) : TORQUE_TARGET0 { float distance = TORQUE_TEX2D(diffuseMap, IN.texCoord).a; @@ -73,8 +79,9 @@ float4 main( Conn IN ) : TORQUE_TARGET0 alpha = totalAlpha / float(supersample); } - // Ensure alpha does not go below a minimum threshold for transparency - // alpha = max(alpha, alphaThreshold); + // Alpha lower than min discard. + if(alpha < alphaThreshold) + discard; // Debug: Uncomment to visualize alpha field //return float4(alpha, alpha, alpha, 1.0); From 70090a91382740eb046bf339f53dd50eae9d0d50 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 28 Nov 2024 16:31:20 +0000 Subject: [PATCH 4/7] fixes fix spacing size. use spread function instead of fixed value move all the * 0.25 to gfont so its centralized for later --- Engine/source/gfx/gFont.cpp | 28 +++++++++++++++------- Engine/source/gfx/gfxFontRenderBatcher.cpp | 2 +- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Engine/source/gfx/gFont.cpp b/Engine/source/gfx/gFont.cpp index e94a712207..4155b3492e 100644 --- a/Engine/source/gfx/gFont.cpp +++ b/Engine/source/gfx/gFont.cpp @@ -177,9 +177,9 @@ Resource GFont::create(const String &faceName, U32 size, const char *cach font->mSize = size; font->mCharSet = charset; font->mHeight = platFont->getFontHeight() * 0.25; - font->mBaseline = platFont->getFontBaseLine(); - font->mAscent = platFont->getFontBaseLine(); - font->mDescent = (platFont->getFontHeight() - platFont->getFontBaseLine()); + font->mBaseline = platFont->getFontBaseLine() * 0.25; + font->mAscent = platFont->getFontBaseLine() * 0.25; + font->mDescent = (platFont->getFontHeight() - platFont->getFontBaseLine()) * 0.25; // Flag it to save when we exit font->mNeedSave = true; @@ -276,10 +276,17 @@ bool GFont::loadCharInfo(const UTF16 ch) if (it != mCharMap.end()) return true; - if(mPlatformFont && mPlatformFont->isValidChar(ch)) + if (mPlatformFont && mPlatformFont->isValidChar(ch)) { - Mutex::lockMutex(mMutex); // the CharInfo returned by mPlatformFont is static data, must protect from changes. - PlatformFont::CharInfo &ci = mPlatformFont->getCharInfo(ch); + Mutex::lockMutex(mMutex); // the CharInfo returned by mPlatformFont is static data, must protect from changes. + PlatformFont::CharInfo& ci = mPlatformFont->getCharInfo(ch); + if (ch == dT(' ')) + { + ci.yOrigin *= 0.25; + ci.xOrigin *= 0.25; + ci.xIncrement = (ci.xIncrement * 0.25); + } + if(ci.bitmapData) addBitmap(ci); @@ -529,8 +536,8 @@ void GFont::addBitmap(PlatformFont::CharInfo &charInfo) FrameTemp sdfBitmap(sdfWidth * sdfHeight); // Generate the SDF - F32 sdfSpread = 16.0f;// / (4.0f + (charInfo.width / charInfo.height)); - //sdfSpread = mMax(charInfo.width, charInfo.height) * sdfSpread; + F32 sdfSpread = 1.0 / (4.0f + (charInfo.width / charInfo.height)); + sdfSpread = mMax(charInfo.width, charInfo.height) * sdfSpread; generateSDF(paddedBitmap, paddedWidth, paddedHeight, sdfBitmap, sdfWidth, sdfHeight, sdfSpread); U32 nextCurX = U32(mCurX + sdfWidth); /*7) & ~0x3;*/ @@ -591,7 +598,10 @@ void GFont::addBitmap(PlatformFont::CharInfo &charInfo) // update our width and height. charInfo.width = sdfWidth; charInfo.height = sdfHeight; - charInfo.xIncrement = charInfo.xIncrement * 0.25; + charInfo.yOrigin *= 0.25; + charInfo.xOrigin *= 0.25; + charInfo.xIncrement = (charInfo.xIncrement *0.25); + mMaxRowHeight = mMax(mMaxRowHeight, sdfHeight); mTextureSheets[mCurSheet].refresh(); diff --git a/Engine/source/gfx/gfxFontRenderBatcher.cpp b/Engine/source/gfx/gfxFontRenderBatcher.cpp index 08fcdbdfcc..69523f6b70 100644 --- a/Engine/source/gfx/gfxFontRenderBatcher.cpp +++ b/Engine/source/gfx/gfxFontRenderBatcher.cpp @@ -101,7 +101,7 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) const PlatformFont::CharInfo &ci = mFont->getCharInfo( m.c ); // Where are we drawing it? - F32 drawY = offset.y + (mFont->getBaseline() * 0.25) - (ci.yOrigin * 0.25) - (mFont->getDescent() * 0.25); + F32 drawY = offset.y + (mFont->getBaseline()) - (ci.yOrigin) - (mFont->getDescent()); F32 drawX = offset.x + m.x + ci.xOrigin; // Figure some values. From b320041cec65dee0f718a70fc43c897bb6d2207c Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 28 Nov 2024 16:45:27 +0000 Subject: [PATCH 5/7] remove unnecessary --- Engine/source/gfx/gFont.cpp | 4 ---- Engine/source/gfx/gfxFontRenderBatcher.cpp | 2 +- Engine/source/platform/platformFont.h | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Engine/source/gfx/gFont.cpp b/Engine/source/gfx/gFont.cpp index 4155b3492e..53851ba52c 100644 --- a/Engine/source/gfx/gFont.cpp +++ b/Engine/source/gfx/gFont.cpp @@ -933,8 +933,6 @@ bool GFont::read(Stream& io_rStream) io_rStream.read(&ci.bitmapIndex); io_rStream.read(&ci.xOffset); io_rStream.read(&ci.yOffset); - io_rStream.read(&ci.texWidth); - io_rStream.read(&ci.texHeight); io_rStream.read(&ci.width); io_rStream.read(&ci.height); io_rStream.read(&ci.xOrigin); @@ -1003,8 +1001,6 @@ bool GFont::write(Stream& stream) stream.write(ci.bitmapIndex); stream.write(ci.xOffset); stream.write(ci.yOffset); - stream.write(ci.texWidth); - stream.write(ci.texHeight); stream.write(ci.width); stream.write(ci.height); stream.write(ci.xOrigin); diff --git a/Engine/source/gfx/gfxFontRenderBatcher.cpp b/Engine/source/gfx/gfxFontRenderBatcher.cpp index 69523f6b70..cad8326aa7 100644 --- a/Engine/source/gfx/gfxFontRenderBatcher.cpp +++ b/Engine/source/gfx/gfxFontRenderBatcher.cpp @@ -101,7 +101,7 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) const PlatformFont::CharInfo &ci = mFont->getCharInfo( m.c ); // Where are we drawing it? - F32 drawY = offset.y + (mFont->getBaseline()) - (ci.yOrigin) - (mFont->getDescent()); + F32 drawY = offset.y + mFont->getBaseline() - ci.yOrigin * TEXT_MAG; F32 drawX = offset.x + m.x + ci.xOrigin; // Figure some values. diff --git a/Engine/source/platform/platformFont.h b/Engine/source/platform/platformFont.h index 5f35e05043..63a47d95a5 100644 --- a/Engine/source/platform/platformFont.h +++ b/Engine/source/platform/platformFont.h @@ -65,8 +65,6 @@ class PlatformFont /// rendered, i.e., \n, \r, etc. U32 xOffset; ///< x offset into bitmap sheet U32 yOffset; ///< y offset into bitmap sheet - U32 texWidth; - U32 texHeight; U32 width; ///< width of character (pixels) U32 height; ///< height of character (pixels) S32 xOrigin; From 24bfcd45b4997047b7be6e4db9aaf1df5771fc9b Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 29 Nov 2024 15:46:29 +0000 Subject: [PATCH 6/7] some more fixes added a debug view to gfxFontRenderBatcher updated font glyph metrics for padding --- Engine/source/gfx/gFont.cpp | 176 ++---------------- Engine/source/gfx/gfxFontRenderBatcher.cpp | 29 ++- Engine/source/platform/platformFont.h | 1 + Engine/source/platformWin32/winFont.h | 5 + .../shaders/fixedFunction/sdfFontRenderP.hlsl | 12 +- 5 files changed, 51 insertions(+), 172 deletions(-) diff --git a/Engine/source/gfx/gFont.cpp b/Engine/source/gfx/gFont.cpp index 53851ba52c..d623c9b4b4 100644 --- a/Engine/source/gfx/gFont.cpp +++ b/Engine/source/gfx/gFont.cpp @@ -176,10 +176,10 @@ Resource GFont::create(const String &faceName, U32 size, const char *cach font->mFaceName = faceName; font->mSize = size; font->mCharSet = charset; - font->mHeight = platFont->getFontHeight() * 0.25; - font->mBaseline = platFont->getFontBaseLine() * 0.25; - font->mAscent = platFont->getFontBaseLine() * 0.25; - font->mDescent = (platFont->getFontHeight() - platFont->getFontBaseLine()) * 0.25; + font->mHeight = (platFont->getFontHeight() * 0.25); + font->mAscent = (platFont->getFontBaseLine() * 0.25); + font->mBaseline = (platFont->getFontBaseLine() * 0.25); + font->mDescent = (platFont->getFontDescent() * 0.25); // Flag it to save when we exit font->mNeedSave = true; @@ -345,159 +345,6 @@ void GFont::generateSDF(const U8* bitmap, S32 width, S32 height, U8* sdfBitmap, sdfBitmap[y * sdfWidth + x] = (U8)(255 * normalizedDist); } } - - //// FMM SDF method - //// Initialize distance field - //Vector distanceField; - //distanceField.setSize(sdfWidth * sdfHeight); - //distanceField.fill(F32_MAX); - - //// Helper to check if a pixel is inside the glyph (foreground) - //auto isInside = [&](S32 x, S32 y) -> bool { - // return bitmap[y * width + x] > 0; - //}; - - //// Priority queue vectors (x, y, distance) - //Vector pqX, pqY; - //Vector pqDist; - - //// Initialize priority queue with foreground pixels (distance = 0) - //for (S32 y = 0; y < sdfHeight; ++y) - //{ - // for (S32 x = 0; x < sdfWidth; ++x) - // { - // // Map the SDF coordinates to the bitmap space - // F32 scaledX = x * (F32)width / sdfWidth; - // F32 scaledY = y * (F32)height / sdfHeight; - - // // Check if the pixel in the bitmap is inside (foreground) - // bool inside = false; - // for (S32 by = 0; by < height; ++by) - // { - // for (S32 bx = 0; bx < width; ++bx) - // { - // if (isInside(bx, by)) - // { - // F32 dist = mSqrt((scaledX - bx) * (scaledX - bx) + (scaledY - by) * (scaledY - by)); - // if (dist < 1.0f) // Only consider close points as part of the glyph - // { - // inside = true; - // break; - // } - // } - // } - // if (inside) break; - // } - - // if (inside) - // { - // // Mark the foreground pixels with distance 0 - // distanceField[y * sdfWidth + x] = 0.0f; - // pqX.push_back(x); - // pqY.push_back(y); - // pqDist.push_back(0.0f); - // } - // else - // { - // // Set the background pixels to a large distance initially - // distanceField[y * sdfWidth + x] = F32_MAX; - // } - // } - //} - - //// Neighbors for 8-way connectivity (up, down, left, right, and diagonals) - //const S32 dx[8] = { -1, 1, 0, 0, -1, 1, -1, 1 }; - //const S32 dy[8] = { 0, 0, -1, 1, -1, -1, 1, 1 }; - - //// Fast Marching Method: propagate distances - //while (!pqX.empty()) - //{ - // // Get the pixel with the smallest distance (priority queue) - // S32 x = pqX.front(); - // S32 y = pqY.front(); - // F32 currentDist = pqDist.front(); - - // pqX.erase(pqX.begin()); - // pqY.erase(pqY.begin()); - // pqDist.erase(pqDist.begin()); - - // // Explore neighbors - // for (S32 i = 0; i < 8; ++i) - // { - // S32 nx = x + dx[i]; - // S32 ny = y + dy[i]; - - // if (nx >= 0 && ny >= 0 && nx < sdfWidth && ny < sdfHeight) - // { - // // Compute the new distance as the grid distance (simplified) - // F32 newDist = currentDist + 1.0f; - - // if (newDist < distanceField[ny * sdfWidth + nx]) - // { - // distanceField[ny * sdfWidth + nx] = newDist; - - // // Push the new distances to the "priority queue" (sorted vector) - // pqX.push_back(nx); - // pqY.push_back(ny); - // pqDist.push_back(newDist); - - // // Sort the queue (to mimic priority) - // for (S32 j = pqDist.size() - 1; j > 0; --j) - // { - // if (pqDist[j] < pqDist[j - 1]) - // { - // std::swap(pqX[j], pqX[j - 1]); - // std::swap(pqY[j], pqY[j - 1]); - // std::swap(pqDist[j], pqDist[j - 1]); - // } - // } - // } - // } - // } - //} - - //// Find the maximum distance in the field for normalization - //F32 maxDistance = 0.0f; - //for (S32 i = 0; i < sdfWidth * sdfHeight; ++i) - //{ - // if (distanceField[i] > maxDistance) - // { - // maxDistance = distanceField[i]; - // } - //} - - //// Smoothing step: Apply Gaussian blur (or simple kernel filter) - //Vector smoothedField; - //smoothedField.setSize(sdfWidth* sdfHeight); - //smoothedField.fill(0.0f); - - //for (S32 y = 1; y < sdfHeight - 1; ++y) - //{ - // for (S32 x = 1; x < sdfWidth - 1; ++x) - // { - // // Simple 3x3 box blur (you can replace it with Gaussian blur for better results) - // F32 sum = 0.0f; - // for (S32 dy = -1; dy <= 1; ++dy) - // { - // for (S32 dx = -1; dx <= 1; ++dx) - // { - // sum += distanceField[(y + dy) * sdfWidth + (x + dx)]; - // } - // } - // smoothedField[y * sdfWidth + x] = sum / 9.0f; // Average of 3x3 neighborhood - // } - //} - - //// Invert the distance field so that foreground pixels are black (0), and background pixels are white (255) - //for (S32 y = 0; y < sdfHeight; ++y) - //{ - // for (S32 x = 0; x < sdfWidth; ++x) - // { - // // Normalize the value to the range [0, 255], inverted - // F32 sdfValue = 1.0f - (smoothedField[y * sdfWidth + x] / maxDistance); - // sdfBitmap[y * sdfWidth + x] = (U8)(255.0f * sdfValue); - // } - //} } void GFont::padGlyphBitmap(const U8* original, S32 origWidth, S32 origHeight, U8* padded, S32 padWidth, S32 padHeight, S32 padding) @@ -533,11 +380,11 @@ void GFont::addBitmap(PlatformFont::CharInfo &charInfo) S32 sdfHeight = paddedHeight * 0.25; // Allocate buffer for SDF bitmap - FrameTemp sdfBitmap(sdfWidth * sdfHeight); + FrameTemp sdfBitmap((sdfWidth * sdfHeight)); // Generate the SDF - F32 sdfSpread = 1.0 / (4.0f + (charInfo.width / charInfo.height)); - sdfSpread = mMax(charInfo.width, charInfo.height) * sdfSpread; + F32 sdfSpread = 1.0f / (4.0f + (static_cast(paddedWidth / paddedHeight))); + sdfSpread = mMax(paddedWidth, paddedHeight) * sdfSpread; generateSDF(paddedBitmap, paddedWidth, paddedHeight, sdfBitmap, sdfWidth, sdfHeight, sdfSpread); U32 nextCurX = U32(mCurX + sdfWidth); /*7) & ~0x3;*/ @@ -586,11 +433,10 @@ void GFont::addBitmap(PlatformFont::CharInfo &charInfo) mCurX = nextCurX; - S32 x, y; GBitmap *bmp = mTextureSheets[mCurSheet].getBitmap(); AssertFatal(bmp->getFormat() == GFXFormatA8, "GFont::addBitmap - cannot added characters to non-greyscale textures!"); - + S32 x, y; for(y = 0;y < sdfHeight;y++) for(x = 0;x < sdfWidth;x++) *bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = sdfBitmap[y * sdfWidth + x]; @@ -598,9 +444,9 @@ void GFont::addBitmap(PlatformFont::CharInfo &charInfo) // update our width and height. charInfo.width = sdfWidth; charInfo.height = sdfHeight; - charInfo.yOrigin *= 0.25; - charInfo.xOrigin *= 0.25; - charInfo.xIncrement = (charInfo.xIncrement *0.25); + charInfo.yOrigin = ((F32)(charInfo.yOrigin) * 0.25) + padding; + charInfo.xOrigin = ((F32)(charInfo.xOrigin) * 0.25) - padding; + charInfo.xIncrement = ((F32)(charInfo.xIncrement) * 0.25) + 2; mMaxRowHeight = mMax(mMaxRowHeight, sdfHeight); diff --git a/Engine/source/gfx/gfxFontRenderBatcher.cpp b/Engine/source/gfx/gfxFontRenderBatcher.cpp index cad8326aa7..1143bf6b6b 100644 --- a/Engine/source/gfx/gfxFontRenderBatcher.cpp +++ b/Engine/source/gfx/gfxFontRenderBatcher.cpp @@ -23,6 +23,7 @@ #include "gfx/gfxFontRenderBatcher.h" #include "gfx/gFont.h" #include "materials/shaderData.h" +#include "gfxDrawUtil.h" FontRenderBatcher::FontRenderBatcher() : mStorage(8096) { @@ -93,7 +94,6 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) mSheets[i]->startVertex = currentPt; const GFXTextureObject *tex = mFont->getTextureHandle(i); - for( S32 j = 0; j < mSheets[i]->numChars; j++ ) { // Get some general info to proceed with... @@ -203,6 +203,33 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) GFX->setTexture( 0, mFont->getTextureHandle(i) ); GFX->drawPrimitive(GFXTriangleList, mSheets[i]->startVertex, mSheets[i]->numChars * 2); } + +#if 0 + for (S32 i = 0; i < mSheets.size(); i++) + { + // Do some early outs... + if (!mSheets[i]) + continue; + + if (!mSheets[i]->numChars) + continue; + for (S32 j = 0; j < mSheets[i]->numChars; j++) + { + // Get some general info to proceed with... + const CharMarker& m = mSheets[i]->charIndex[j]; + const PlatformFont::CharInfo& ci = mFont->getCharInfo(m.c); + F32 yStart = offset.y + mFont->getBaseline() - ci.yOrigin * TEXT_MAG; + F32 xStart = offset.x + m.x + ci.xOrigin; + + // draw baseline + GFX->getDrawUtil()->drawLine(xStart, yStart + ci.yOrigin, xStart + ci.width, yStart + ci.yOrigin, ColorI::GREEN); + // draw origin line + GFX->getDrawUtil()->drawLine(xStart, yStart, xStart + ci.width, yStart, ColorI::RED); + // draw bounds + GFX->getDrawUtil()->drawRect(Point2F(xStart, yStart), Point2F(xStart + ci.width, yStart + ci.height), ColorI::BLUE); + } + } +#endif } void FontRenderBatcher::queueChar( UTF16 c, S32 ¤tX, GFXVertexColor ¤tColor ) diff --git a/Engine/source/platform/platformFont.h b/Engine/source/platform/platformFont.h index 63a47d95a5..9f7cb8f3cb 100644 --- a/Engine/source/platform/platformFont.h +++ b/Engine/source/platform/platformFont.h @@ -81,6 +81,7 @@ class PlatformFont virtual U32 getFontHeight() const = 0; virtual U32 getFontBaseLine() const = 0; + virtual U32 getFontDescent() const = 0; virtual PlatformFont::CharInfo &getCharInfo(const UTF16 ch) const = 0; virtual PlatformFont::CharInfo &getCharInfo(const UTF8 *str) const = 0; diff --git a/Engine/source/platformWin32/winFont.h b/Engine/source/platformWin32/winFont.h index b5a9807058..fabc75c4e2 100644 --- a/Engine/source/platformWin32/winFont.h +++ b/Engine/source/platformWin32/winFont.h @@ -51,6 +51,11 @@ class WinFont : public PlatformFont return mTextMetric.tmAscent; } + inline U32 getFontDescent() const override + { + return mTextMetric.tmDescent; + } + PlatformFont::CharInfo &getCharInfo(const UTF16 ch) const override; PlatformFont::CharInfo &getCharInfo(const UTF8 *str) const override; diff --git a/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl b/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl index 8559b97ce3..7fa26cc4e4 100644 --- a/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl +++ b/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl @@ -32,20 +32,20 @@ struct Conn TORQUE_UNIFORM_SAMPLER2D(diffuseMap, 0); uniform float2 texDim; -// Customizable parameters -static const float sdfThreshold = 0.5; // Center threshold for the edge of the glyph +// Customizable parameters +static const float sdfThreshold = 0.55; // Center threshold for the edge of the glyph // Min: 0.4 , Max: 0.6 -static const float slopeMultiplier = 0.5; // Multiplies the slope of the transition +static const float slopeMultiplier = 1.0; // Multiplies the slope of the transition // Min: 0.5 (softer edges), Max: 4.0 (sharper edges) -static const float inBias = 0.0; // Shifts the threshold inward +static const float inBias = -0.05; // Shifts the threshold inward // Min: -0.05 (slightly thinner), Max: 0.0 (default, no inward shift) -static const float outBias = 0.0; // Shifts the threshold outward +static const float outBias = 0.01; // Shifts the threshold outward // Min: 0.0 (default, no outward shift), Max: 0.05 (slightly thicker) static const float smoothness = 0.05; // Controls the falloff region // Min: 0.005 (very sharp edges), Max: 0.05 (soft, anti-aliased edges) static const int supersample = 1; // Supersampling factor // Min: 1 (no supersampling), Max: 8 (high-quality, computationally expensive) -static const float alphaThreshold = 0.0f; // Sets the alpha threshold +static const float alphaThreshold = 0.2f; // Sets the alpha threshold float4 main( Conn IN ) : TORQUE_TARGET0 { From af9b9f9029fb9295827a5274c074eba1793fc343 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 29 Nov 2024 16:44:46 +0000 Subject: [PATCH 7/7] revert changes to descent --- Engine/source/gfx/gFont.cpp | 2 +- Engine/source/platform/platformFont.h | 1 - Engine/source/platformWin32/winFont.h | 5 ----- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Engine/source/gfx/gFont.cpp b/Engine/source/gfx/gFont.cpp index d623c9b4b4..d2428eae9a 100644 --- a/Engine/source/gfx/gFont.cpp +++ b/Engine/source/gfx/gFont.cpp @@ -179,7 +179,7 @@ Resource GFont::create(const String &faceName, U32 size, const char *cach font->mHeight = (platFont->getFontHeight() * 0.25); font->mAscent = (platFont->getFontBaseLine() * 0.25); font->mBaseline = (platFont->getFontBaseLine() * 0.25); - font->mDescent = (platFont->getFontDescent() * 0.25); + font->mDescent = (font->mHeight - font->mBaseline); // Flag it to save when we exit font->mNeedSave = true; diff --git a/Engine/source/platform/platformFont.h b/Engine/source/platform/platformFont.h index 9f7cb8f3cb..63a47d95a5 100644 --- a/Engine/source/platform/platformFont.h +++ b/Engine/source/platform/platformFont.h @@ -81,7 +81,6 @@ class PlatformFont virtual U32 getFontHeight() const = 0; virtual U32 getFontBaseLine() const = 0; - virtual U32 getFontDescent() const = 0; virtual PlatformFont::CharInfo &getCharInfo(const UTF16 ch) const = 0; virtual PlatformFont::CharInfo &getCharInfo(const UTF8 *str) const = 0; diff --git a/Engine/source/platformWin32/winFont.h b/Engine/source/platformWin32/winFont.h index fabc75c4e2..b5a9807058 100644 --- a/Engine/source/platformWin32/winFont.h +++ b/Engine/source/platformWin32/winFont.h @@ -51,11 +51,6 @@ class WinFont : public PlatformFont return mTextMetric.tmAscent; } - inline U32 getFontDescent() const override - { - return mTextMetric.tmDescent; - } - PlatformFont::CharInfo &getCharInfo(const UTF16 ch) const override; PlatformFont::CharInfo &getCharInfo(const UTF8 *str) const override;