FreeType: Fixed mem leak and adjustments

This commit is contained in:
rtw1x1
2026-02-04 10:35:46 +00:00
parent 8f0b956fa9
commit a3009f4bff
8 changed files with 125 additions and 84 deletions

View File

@@ -73,12 +73,7 @@ bool CFontManager::Initialize()
void CFontManager::Destroy() void CFontManager::Destroy()
{ {
for (auto& pair : m_faceCache) m_resolvedPathCache.clear();
{
if (pair.second)
FT_Done_Face(pair.second);
}
m_faceCache.clear();
m_fontPathMap.clear(); m_fontPathMap.clear();
if (m_ftLibrary) if (m_ftLibrary)
@@ -142,7 +137,7 @@ std::string CFontManager::ResolveFontPath(const char* faceName)
return ""; return "";
} }
FT_Face CFontManager::GetFace(const char* faceName) FT_Face CFontManager::CreateFace(const char* faceName)
{ {
if (!m_bInitialized) if (!m_bInitialized)
{ {
@@ -150,24 +145,34 @@ FT_Face CFontManager::GetFace(const char* faceName)
return nullptr; return nullptr;
} }
std::string path = ResolveFontPath(faceName); if (!faceName || !faceName[0])
if (path.empty())
return nullptr; return nullptr;
// Check cache std::string lowerName = ToLower(faceName);
auto it = m_faceCache.find(path);
if (it != m_faceCache.end())
return it->second;
// 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_Face face = nullptr;
FT_Error err = FT_New_Face(m_ftLibrary, path.c_str(), 0, &face); FT_Error err = FT_New_Face(m_ftLibrary, path.c_str(), 0, &face);
if (err != 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; return nullptr;
} }
m_faceCache[path] = face;
return face; return face;
} }

View File

@@ -14,9 +14,9 @@ public:
bool Initialize(); bool Initialize();
void Destroy(); void Destroy();
// Get an FT_Face for a given face name. The face is owned by CFontManager. // Create a NEW FT_Face for the given font name.
// Callers must NOT call FT_Done_Face on it. // The caller OWNS the returned face and must call FT_Done_Face on it when done.
FT_Face GetFace(const char* faceName); FT_Face CreateFace(const char* faceName);
FT_Library GetLibrary() const { return m_ftLibrary; } FT_Library GetLibrary() const { return m_ftLibrary; }
@@ -34,6 +34,6 @@ private:
// faceName (lowercase) -> file path // faceName (lowercase) -> file path
std::unordered_map<std::string, std::string> m_fontPathMap; std::unordered_map<std::string, std::string> m_fontPathMap;
// filePath -> FT_Face (cached, shared across sizes) // faceName (lowercase) -> resolved file system path (caches disk lookups)
std::unordered_map<std::string, FT_Face> m_faceCache; std::unordered_map<std::string, std::string> m_resolvedPathCache;
}; };

View File

@@ -10,6 +10,20 @@
#include FT_FREETYPE_H #include FT_FREETYPE_H
#include FT_GLYPH_H #include FT_GLYPH_H
#include <cmath>
// 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() CGraphicFontTexture::CGraphicFontTexture()
{ {
Initialize(); Initialize();
@@ -53,19 +67,55 @@ void CGraphicFontTexture::Destroy()
stl_wipe(m_pFontTextureVector); stl_wipe(m_pFontTextureVector);
m_charInfoMap.clear(); m_charInfoMap.clear();
// FT_Face is owned by CFontManager, do NOT free it here if (m_ftFace)
m_ftFace = nullptr; {
FT_Done_Face(m_ftFace);
m_ftFace = nullptr;
}
Initialize(); Initialize();
} }
bool CGraphicFontTexture::CreateDeviceObjects() 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<TCharacterKey> 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; return true;
} }
void CGraphicFontTexture::DestroyDeviceObjects() void CGraphicFontTexture::DestroyDeviceObjects()
{ {
m_lpd3dTexture = NULL;
stl_wipe(m_pFontTextureVector);
} }
bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bItalic) 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]; m_pAtlasBuffer = new DWORD[width * height];
memset(m_pAtlasBuffer, 0, width * height * sizeof(DWORD)); memset(m_pAtlasBuffer, 0, width * height * sizeof(DWORD));
// Get FT_Face from FontManager // Store UTF-8 name for device reset re-creation
m_ftFace = CFontManager::Instance().GetFace(c_szFontName); 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) 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; 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 // Set pixel size
int pixelSize = (fontSize < 0) ? -fontSize : fontSize; int pixelSize = (fontSize < 0) ? -fontSize : fontSize;
if (pixelSize == 0) if (pixelSize == 0)
@@ -207,26 +255,6 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
if (!m_ftFace) if (!m_ftFace)
return NULL; 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) if (keyValue == 0x08)
keyValue = L' '; keyValue = L' ';
@@ -248,8 +276,9 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
int glyphBitmapWidth = bitmap.width; int glyphBitmapWidth = bitmap.width;
int glyphBitmapHeight = bitmap.rows; int glyphBitmapHeight = bitmap.rows;
int bearingX = slot->bitmap_left;
int bearingY = slot->bitmap_top; 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 // Normalize glyph placement to common baseline
// yOffset = distance from atlas row top to where the glyph bitmap starts // yOffset = distance from atlas row top to where the glyph bitmap starts
@@ -273,6 +302,7 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
rNewCharInfo.right = 0; rNewCharInfo.right = 0;
rNewCharInfo.bottom = 0; rNewCharInfo.bottom = 0;
rNewCharInfo.advance = advance; rNewCharInfo.advance = advance;
rNewCharInfo.bearingX = 0.0f;
return &rNewCharInfo; return &rNewCharInfo;
} }
@@ -319,7 +349,10 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
{ {
unsigned char alpha = srcRow[col]; unsigned char alpha = srcRow[col];
if (alpha) 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.right = float(m_x + cellWidth) * rhwidth;
rNewCharInfo.bottom = float(m_y + cellHeight) * rhheight; rNewCharInfo.bottom = float(m_y + cellHeight) * rhheight;
rNewCharInfo.advance = advance; rNewCharInfo.advance = advance;
rNewCharInfo.bearingX = (float)bearingX;
m_x += cellWidth; m_x += cellWidth;

View File

@@ -6,6 +6,7 @@
#include <ft2build.h> #include <ft2build.h>
#include FT_FREETYPE_H #include FT_FREETYPE_H
#include <string>
#include <vector> #include <vector>
#include <map> #include <map>
@@ -24,6 +25,7 @@ class CGraphicFontTexture : public CGraphicTexture
float right; float right;
float bottom; float bottom;
float advance; float advance;
float bearingX;
} TCharacterInfomation; } TCharacterInfomation;
typedef std::vector<TCharacterInfomation*> TPCharacterInfomationVector; typedef std::vector<TCharacterInfomation*> TPCharacterInfomationVector;
@@ -75,6 +77,7 @@ class CGraphicFontTexture : public CGraphicTexture
bool m_isDirty; bool m_isDirty;
TCHAR m_fontName[LF_FACESIZE]; TCHAR m_fontName[LF_FACESIZE];
std::string m_fontNameUTF8; // stored for device reset re-creation
LONG m_fontSize; LONG m_fontSize;
bool m_bItalic; bool m_bItalic;

View File

@@ -33,7 +33,7 @@ CGraphicText::TType CGraphicText::Type()
bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/) bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/)
{ {
static char strName[32]; char strName[32];
int size; int size;
bool bItalic = false; bool bItalic = false;
@@ -50,7 +50,7 @@ bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/)
strName[nameLen] = '\0'; strName[nameLen] = '\0';
++p; ++p;
static char num[8]; char num[8];
int i = 0; int i = 0;
while (*p && isdigit(*p)) while (*p && isdigit(*p))

View File

@@ -534,9 +534,7 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
} }
static std::unordered_map<LPDIRECT3DTEXTURE9, std::vector<SVertex>> s_vtxBatches; static std::unordered_map<LPDIRECT3DTEXTURE9, std::vector<SVertex>> s_vtxBatches;
for (auto& [pTexture, vtxBatch] : s_vtxBatches) { s_vtxBatches.clear();
vtxBatch.clear();
}
STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); 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; fFontSy = fCurY - 0.5f;
fFontEx = fFontSx + fFontWidth; fFontEx = fFontSx + fFontWidth;
fFontEy = fFontSy + fFontHeight; 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; fFontSy = fCurY-0.5f;
fFontEx = fFontSx + fFontWidth; fFontEx = fFontSx + fFontWidth;
fFontEy = fFontSy + fFontHeight; fFontEy = fFontSy + fFontHeight;

View File

@@ -8,17 +8,22 @@
#include <ft2build.h> #include <ft2build.h>
#include FT_BITMAP_H #include FT_BITMAP_H
#include <cmath>
// 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) void CTextBar::__SetFont(int fontSize, bool isBold)
{ {
m_ftFace = CFontManager::Instance().GetFace("Tahoma"); // Create a per-instance FT_Face (this instance owns it)
if (!m_ftFace) m_ftFace = CFontManager::Instance().CreateFace("Tahoma");
return;
__ApplyFaceState();
}
void CTextBar::__ApplyFaceState()
{
if (!m_ftFace) if (!m_ftFace)
return; return;
@@ -27,7 +32,7 @@ void CTextBar::__ApplyFaceState()
pixelSize = 12; pixelSize = 12;
FT_Set_Pixel_Sizes(m_ftFace, 0, pixelSize); 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_ascender = (int)(m_ftFace->size->metrics.ascender >> 6);
m_lineHeight = (int)(m_ftFace->size->metrics.height >> 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; return;
} }
// Re-apply face state (shared FT_Face may have been changed by another user)
__ApplyFaceState();
std::wstring wText = Utf8ToWide(c_szText); std::wstring wText = Utf8ToWide(c_szText);
int totalAdvance = 0; 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' '); glyphIndex = FT_Get_Char_Index(m_ftFace, L' ');
if (FT_Load_Glyph(m_ftFace, glyphIndex, FT_LOAD_DEFAULT) == 0) 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; 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) if (!c_szText || !*c_szText || !m_ftFace)
return; return;
// Re-apply face state (shared FT_Face may have been changed by another user)
__ApplyFaceState();
DWORD* pdwBuf = (DWORD*)m_dib.GetPointer(); DWORD* pdwBuf = (DWORD*)m_dib.GetPointer();
if (!pdwBuf) if (!pdwBuf)
return; return;
@@ -111,9 +110,9 @@ void CTextBar::TextOut(int ix, int iy, const char * c_szText)
FT_GlyphSlot slot = m_ftFace->glyph; 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) 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; 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]; unsigned char alpha = srcRow[col];
if (alpha) if (alpha)
{ {
// D3DFMT_A8R8G8B8 = ARGB in DWORD alpha = s_textBarGammaLUT.table[alpha];
// colorRGB is stored as 0x00BBGGRR, convert to ARGB
DWORD r = (colorRGB >> 0) & 0xFF; DWORD r = (colorRGB >> 0) & 0xFF;
DWORD g = (colorRGB >> 8) & 0xFF; DWORD g = (colorRGB >> 8) & 0xFF;
DWORD b = (colorRGB >> 16) & 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(); Invalidate();
@@ -171,5 +169,9 @@ CTextBar::CTextBar(int fontSize, bool isBold)
CTextBar::~CTextBar() CTextBar::~CTextBar()
{ {
// FT_Face is owned by CFontManager, do NOT free it here if (m_ftFace)
{
FT_Done_Face(m_ftFace);
m_ftFace = nullptr;
}
} }

View File

@@ -17,7 +17,6 @@ class CTextBar : public CDibBar
protected: protected:
void __SetFont(int fontSize, bool isBold); void __SetFont(int fontSize, bool isBold);
void __ApplyFaceState();
void OnCreate(); void OnCreate();