From a3009f4bff9c29697fc86f105fa1c87cf8d3b00e Mon Sep 17 00:00:00 2001 From: rtw1x1 Date: Wed, 4 Feb 2026 10:35:46 +0000 Subject: [PATCH] FreeType: Fixed mem leak and adjustments --- src/EterLib/FontManager.cpp | 37 +++++++------ src/EterLib/FontManager.h | 10 ++-- src/EterLib/GrpFontTexture.cpp | 98 ++++++++++++++++++++++----------- src/EterLib/GrpFontTexture.h | 3 + src/EterLib/GrpText.cpp | 4 +- src/EterLib/GrpTextInstance.cpp | 8 +-- src/EterLib/TextBar.cpp | 48 ++++++++-------- src/EterLib/TextBar.h | 1 - 8 files changed, 125 insertions(+), 84 deletions(-) diff --git a/src/EterLib/FontManager.cpp b/src/EterLib/FontManager.cpp index dba64b3..8a88703 100644 --- a/src/EterLib/FontManager.cpp +++ b/src/EterLib/FontManager.cpp @@ -73,12 +73,7 @@ bool CFontManager::Initialize() void CFontManager::Destroy() { - for (auto& pair : m_faceCache) - { - if (pair.second) - FT_Done_Face(pair.second); - } - m_faceCache.clear(); + m_resolvedPathCache.clear(); m_fontPathMap.clear(); if (m_ftLibrary) @@ -142,7 +137,7 @@ std::string CFontManager::ResolveFontPath(const char* faceName) return ""; } -FT_Face CFontManager::GetFace(const char* faceName) +FT_Face CFontManager::CreateFace(const char* faceName) { if (!m_bInitialized) { @@ -150,24 +145,34 @@ FT_Face CFontManager::GetFace(const char* faceName) return nullptr; } - std::string path = ResolveFontPath(faceName); - if (path.empty()) + if (!faceName || !faceName[0]) return nullptr; - // Check cache - auto it = m_faceCache.find(path); - if (it != m_faceCache.end()) - return it->second; + std::string lowerName = ToLower(faceName); - // Load new face + // Check resolved path cache (avoids repeated disk stat calls) + std::string path; + auto cacheIt = m_resolvedPathCache.find(lowerName); + if (cacheIt != m_resolvedPathCache.end()) + { + path = cacheIt->second; + } + else + { + path = ResolveFontPath(faceName); + if (path.empty()) + return nullptr; + m_resolvedPathCache[lowerName] = path; + } + + // Create a new FT_Face — caller owns it FT_Face face = nullptr; FT_Error err = FT_New_Face(m_ftLibrary, path.c_str(), 0, &face); if (err != 0 || !face) { - TraceError("CFontManager::GetFace - FT_New_Face failed for '%s' (error %d)", path.c_str(), err); + TraceError("CFontManager::CreateFace - FT_New_Face failed for '%s' (error %d)", path.c_str(), err); return nullptr; } - m_faceCache[path] = face; return face; } diff --git a/src/EterLib/FontManager.h b/src/EterLib/FontManager.h index f4ee324..68f2038 100644 --- a/src/EterLib/FontManager.h +++ b/src/EterLib/FontManager.h @@ -14,9 +14,9 @@ public: bool Initialize(); void Destroy(); - // Get an FT_Face for a given face name. The face is owned by CFontManager. - // Callers must NOT call FT_Done_Face on it. - FT_Face GetFace(const char* faceName); + // Create a NEW FT_Face for the given font name. + // The caller OWNS the returned face and must call FT_Done_Face on it when done. + FT_Face CreateFace(const char* faceName); FT_Library GetLibrary() const { return m_ftLibrary; } @@ -34,6 +34,6 @@ private: // faceName (lowercase) -> file path std::unordered_map m_fontPathMap; - // filePath -> FT_Face (cached, shared across sizes) - std::unordered_map m_faceCache; + // faceName (lowercase) -> resolved file system path (caches disk lookups) + std::unordered_map m_resolvedPathCache; }; diff --git a/src/EterLib/GrpFontTexture.cpp b/src/EterLib/GrpFontTexture.cpp index cc0c428..ff51cbb 100644 --- a/src/EterLib/GrpFontTexture.cpp +++ b/src/EterLib/GrpFontTexture.cpp @@ -10,6 +10,20 @@ #include FT_FREETYPE_H #include FT_GLYPH_H +#include + +// Precomputed gamma LUT to sharpen FreeType's grayscale anti-aliasing. +// GDI ClearType has high-contrast edges; FreeType grayscale is softer. +// Gamma < 1.0 boosts mid-range alpha, making edges crisper. +static struct SAlphaGammaLUT { + unsigned char table[256]; + SAlphaGammaLUT() { + table[0] = 0; + for (int i = 1; i < 256; ++i) + table[i] = (unsigned char)(pow(i / 255.0, 0.80) * 255.0 + 0.5); + } +} s_alphaGammaLUT; + CGraphicFontTexture::CGraphicFontTexture() { Initialize(); @@ -53,19 +67,55 @@ void CGraphicFontTexture::Destroy() stl_wipe(m_pFontTextureVector); m_charInfoMap.clear(); - // FT_Face is owned by CFontManager, do NOT free it here - m_ftFace = nullptr; + if (m_ftFace) + { + FT_Done_Face(m_ftFace); + m_ftFace = nullptr; + } Initialize(); } bool CGraphicFontTexture::CreateDeviceObjects() { + if (!m_ftFace) + return true; + + // After device reset: wipe GPU textures, clear atlas state, and + // re-render all previously cached characters on demand. + // We keep m_charInfoMap keys but clear the entries so glyphs get re-rasterized. + std::vector cachedKeys; + cachedKeys.reserve(m_charInfoMap.size()); + for (const auto& pair : m_charInfoMap) + cachedKeys.push_back(pair.first); + + stl_wipe(m_pFontTextureVector); + m_charInfoMap.clear(); + m_x = 0; + m_y = 0; + m_step = 0; + m_isDirty = false; + + // Reset CPU atlas buffer + if (m_pAtlasBuffer) + memset(m_pAtlasBuffer, 0, m_atlasWidth * m_atlasHeight * sizeof(DWORD)); + + // Create first GPU texture page + if (!AppendTexture()) + return false; + + // Re-rasterize all previously cached glyphs + for (TCharacterKey key : cachedKeys) + UpdateCharacterInfomation(key); + + UpdateTexture(); return true; } void CGraphicFontTexture::DestroyDeviceObjects() { + m_lpd3dTexture = NULL; + stl_wipe(m_pFontTextureVector); } bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bItalic) @@ -97,19 +147,17 @@ bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bI m_pAtlasBuffer = new DWORD[width * height]; memset(m_pAtlasBuffer, 0, width * height * sizeof(DWORD)); - // Get FT_Face from FontManager - m_ftFace = CFontManager::Instance().GetFace(c_szFontName); + // Store UTF-8 name for device reset re-creation + m_fontNameUTF8 = c_szFontName ? c_szFontName : ""; + + // Create a per-instance FT_Face (this instance owns it) + m_ftFace = CFontManager::Instance().CreateFace(c_szFontName); if (!m_ftFace) { - TraceError("CGraphicFontTexture::Create - Failed to get face for '%s'", c_szFontName ? c_szFontName : "(null)"); + TraceError("CGraphicFontTexture::Create - Failed to create face for '%s'", c_szFontName ? c_szFontName : "(null)"); return false; } - Tracef(" FontTexture: loaded '%s' size=%d family='%s' style='%s'\n", - c_szFontName ? c_szFontName : "(null)", fontSize, - m_ftFace->family_name ? m_ftFace->family_name : "?", - m_ftFace->style_name ? m_ftFace->style_name : "?"); - // Set pixel size int pixelSize = (fontSize < 0) ? -fontSize : fontSize; if (pixelSize == 0) @@ -207,26 +255,6 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI if (!m_ftFace) return NULL; - // Re-apply face state (FT_Face is shared across instances via CFontManager) - int pixelSize = (m_fontSize < 0) ? -m_fontSize : m_fontSize; - if (pixelSize == 0) - pixelSize = 12; - FT_Set_Pixel_Sizes(m_ftFace, 0, pixelSize); - - if (m_bItalic) - { - FT_Matrix matrix; - matrix.xx = 0x10000L; - matrix.xy = 0x5800L; - matrix.yx = 0; - matrix.yy = 0x10000L; - FT_Set_Transform(m_ftFace, &matrix, NULL); - } - else - { - FT_Set_Transform(m_ftFace, NULL, NULL); - } - if (keyValue == 0x08) keyValue = L' '; @@ -248,8 +276,9 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI int glyphBitmapWidth = bitmap.width; int glyphBitmapHeight = bitmap.rows; + int bearingX = slot->bitmap_left; int bearingY = slot->bitmap_top; - float advance = (float)(slot->advance.x >> 6); + float advance = ceilf((float)(slot->advance.x) / 64.0f); // Normalize glyph placement to common baseline // yOffset = distance from atlas row top to where the glyph bitmap starts @@ -273,6 +302,7 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI rNewCharInfo.right = 0; rNewCharInfo.bottom = 0; rNewCharInfo.advance = advance; + rNewCharInfo.bearingX = 0.0f; return &rNewCharInfo; } @@ -319,7 +349,10 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI { unsigned char alpha = srcRow[col]; if (alpha) - dstRow[col] = ((DWORD)alpha << 24) | 0x00FFFFFF; // White + alpha + { + alpha = s_alphaGammaLUT.table[alpha]; + dstRow[col] = ((DWORD)alpha << 24) | 0x00FFFFFF; + } } } @@ -336,6 +369,7 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI rNewCharInfo.right = float(m_x + cellWidth) * rhwidth; rNewCharInfo.bottom = float(m_y + cellHeight) * rhheight; rNewCharInfo.advance = advance; + rNewCharInfo.bearingX = (float)bearingX; m_x += cellWidth; diff --git a/src/EterLib/GrpFontTexture.h b/src/EterLib/GrpFontTexture.h index 1aaadd6..d4cf282 100644 --- a/src/EterLib/GrpFontTexture.h +++ b/src/EterLib/GrpFontTexture.h @@ -6,6 +6,7 @@ #include #include FT_FREETYPE_H +#include #include #include @@ -24,6 +25,7 @@ class CGraphicFontTexture : public CGraphicTexture float right; float bottom; float advance; + float bearingX; } TCharacterInfomation; typedef std::vector TPCharacterInfomationVector; @@ -75,6 +77,7 @@ class CGraphicFontTexture : public CGraphicTexture bool m_isDirty; TCHAR m_fontName[LF_FACESIZE]; + std::string m_fontNameUTF8; // stored for device reset re-creation LONG m_fontSize; bool m_bItalic; diff --git a/src/EterLib/GrpText.cpp b/src/EterLib/GrpText.cpp index b8ae228..b4c76c4 100644 --- a/src/EterLib/GrpText.cpp +++ b/src/EterLib/GrpText.cpp @@ -33,7 +33,7 @@ CGraphicText::TType CGraphicText::Type() bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/) { - static char strName[32]; + char strName[32]; int size; bool bItalic = false; @@ -50,7 +50,7 @@ bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/) strName[nameLen] = '\0'; ++p; - static char num[8]; + char num[8]; int i = 0; while (*p && isdigit(*p)) diff --git a/src/EterLib/GrpTextInstance.cpp b/src/EterLib/GrpTextInstance.cpp index c9bf630..8211e3c 100644 --- a/src/EterLib/GrpTextInstance.cpp +++ b/src/EterLib/GrpTextInstance.cpp @@ -534,9 +534,7 @@ void CGraphicTextInstance::Render(RECT * pClipRect) } static std::unordered_map> s_vtxBatches; - for (auto& [pTexture, vtxBatch] : s_vtxBatches) { - vtxBatch.clear(); - } + s_vtxBatches.clear(); STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); @@ -612,7 +610,7 @@ void CGraphicTextInstance::Render(RECT * pClipRect) } } - fFontSx = fCurX - 0.5f; + fFontSx = fCurX + pCurCharInfo->bearingX - 0.5f; fFontSy = fCurY - 0.5f; fFontEx = fFontSx + fFontWidth; fFontEy = fFontSy + fFontHeight; @@ -717,7 +715,7 @@ void CGraphicTextInstance::Render(RECT * pClipRect) } } - fFontSx = fCurX-0.5f; + fFontSx = fCurX + pCurCharInfo->bearingX - 0.5f; fFontSy = fCurY-0.5f; fFontEx = fFontSx + fFontWidth; fFontEy = fFontSy + fFontHeight; diff --git a/src/EterLib/TextBar.cpp b/src/EterLib/TextBar.cpp index 65e3482..776183c 100644 --- a/src/EterLib/TextBar.cpp +++ b/src/EterLib/TextBar.cpp @@ -8,17 +8,22 @@ #include #include FT_BITMAP_H +#include + +// Same gamma LUT as GrpFontTexture for consistent text sharpness +static struct STextBarGammaLUT { + unsigned char table[256]; + STextBarGammaLUT() { + table[0] = 0; + for (int i = 1; i < 256; ++i) + table[i] = (unsigned char)(pow(i / 255.0, 0.80) * 255.0 + 0.5); + } +} s_textBarGammaLUT; + void CTextBar::__SetFont(int fontSize, bool isBold) { - m_ftFace = CFontManager::Instance().GetFace("Tahoma"); - if (!m_ftFace) - return; - - __ApplyFaceState(); -} - -void CTextBar::__ApplyFaceState() -{ + // Create a per-instance FT_Face (this instance owns it) + m_ftFace = CFontManager::Instance().CreateFace("Tahoma"); if (!m_ftFace) return; @@ -27,7 +32,7 @@ void CTextBar::__ApplyFaceState() pixelSize = 12; FT_Set_Pixel_Sizes(m_ftFace, 0, pixelSize); - FT_Set_Transform(m_ftFace, NULL, NULL); // TextBar never uses italic + FT_Set_Transform(m_ftFace, NULL, NULL); m_ascender = (int)(m_ftFace->size->metrics.ascender >> 6); m_lineHeight = (int)(m_ftFace->size->metrics.height >> 6); @@ -57,9 +62,6 @@ void CTextBar::GetTextExtent(const char* c_szText, SIZE* p_size) return; } - // Re-apply face state (shared FT_Face may have been changed by another user) - __ApplyFaceState(); - std::wstring wText = Utf8ToWide(c_szText); int totalAdvance = 0; @@ -70,7 +72,7 @@ void CTextBar::GetTextExtent(const char* c_szText, SIZE* p_size) glyphIndex = FT_Get_Char_Index(m_ftFace, L' '); if (FT_Load_Glyph(m_ftFace, glyphIndex, FT_LOAD_DEFAULT) == 0) - totalAdvance += (int)(m_ftFace->glyph->advance.x >> 6); + totalAdvance += (int)ceilf((float)(m_ftFace->glyph->advance.x) / 64.0f); } p_size->cx = totalAdvance; @@ -82,9 +84,6 @@ void CTextBar::TextOut(int ix, int iy, const char * c_szText) if (!c_szText || !*c_szText || !m_ftFace) return; - // Re-apply face state (shared FT_Face may have been changed by another user) - __ApplyFaceState(); - DWORD* pdwBuf = (DWORD*)m_dib.GetPointer(); if (!pdwBuf) return; @@ -111,9 +110,9 @@ void CTextBar::TextOut(int ix, int iy, const char * c_szText) FT_GlyphSlot slot = m_ftFace->glyph; - // Apply synthetic bold by embolden + // Apply synthetic bold (32 = 0.5px embolden; 64 = 1px was too aggressive) if (m_isBold && slot->bitmap.buffer) - FT_Bitmap_Embolden(CFontManager::Instance().GetLibrary(), &slot->bitmap, 64, 0); + FT_Bitmap_Embolden(CFontManager::Instance().GetLibrary(), &slot->bitmap, 32, 0); FT_Bitmap& bitmap = slot->bitmap; @@ -138,8 +137,7 @@ void CTextBar::TextOut(int ix, int iy, const char * c_szText) unsigned char alpha = srcRow[col]; if (alpha) { - // D3DFMT_A8R8G8B8 = ARGB in DWORD - // colorRGB is stored as 0x00BBGGRR, convert to ARGB + alpha = s_textBarGammaLUT.table[alpha]; DWORD r = (colorRGB >> 0) & 0xFF; DWORD g = (colorRGB >> 8) & 0xFF; DWORD b = (colorRGB >> 16) & 0xFF; @@ -148,7 +146,7 @@ void CTextBar::TextOut(int ix, int iy, const char * c_szText) } } - penX += (int)(slot->advance.x >> 6); + penX += (int)ceilf((float)(slot->advance.x) / 64.0f); } Invalidate(); @@ -171,5 +169,9 @@ CTextBar::CTextBar(int fontSize, bool isBold) CTextBar::~CTextBar() { - // FT_Face is owned by CFontManager, do NOT free it here + if (m_ftFace) + { + FT_Done_Face(m_ftFace); + m_ftFace = nullptr; + } } diff --git a/src/EterLib/TextBar.h b/src/EterLib/TextBar.h index 8b3c03b..3653b1a 100644 --- a/src/EterLib/TextBar.h +++ b/src/EterLib/TextBar.h @@ -17,7 +17,6 @@ class CTextBar : public CDibBar protected: void __SetFont(int fontSize, bool isBold); - void __ApplyFaceState(); void OnCreate();