aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2023-06-14 12:18:18 +0200
committerEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2023-06-15 17:48:28 +0200
commiteed2ee69ac291da33c48093bc2c2bc4d359f46a2 (patch)
tree416a22c9cc01e2d3b6a495a6be94c673c4c73db0 /src
parent348eaf6b2abcff6ec3c835b706fb114ea80995e3 (diff)
Fix memory leak when invalidating NativeRendering fonts
When NativeRendering is used, we keep a reference to all the font engines currently in use to make sure they stay alive, and then dereference them when the scenegraph is invalidated, typically when the window closes. There was always a controlled leak here: If you loaded new font assets continuously, then we would retain the old ones in memory. Due to the bug in QTBUG-100697, we would keep them even if they were application fonts that were removed. However, when QTBUG-100697 was fixed, a side effect was that the memory leak became more visible: It no longer only happens when loading new fonts, but just loading and unloading the same two application fonts and setting them on a text item in a loop would cause us to continuously create new font engines, give them a font cache and then add them to the m_fontEnginesToClean list. The fix is to match the registerFontEngineToClean() with an unregister function and then clean up the font engines that no longer have any references during the synchronization step, similar to how we clean up distance field caches that are no longer in use. Note that this removes a bogus qDeleteAll() from the software backend: This was added blindly by f1b188df132c42da62197055725e5f7eebcc4249. Since the set will be empty, it doesn't cause a crash, but is not the correct way to delete font engines, so to avoid future confusion or cargo-culting, we just replace it with an assert that the set is empty. [ChangeLog][Text] Fixed a controlled memory leak with Text.NativeRendering where loading and unloading the same application fonts could cause memory usage to increase indefinitely and not be regained until the window was closed. Pick-to: 6.5 6.6 Fixes: QTBUG-113714 Change-Id: I34c60e647bf63a0d203f752066f1cbfaeb049bcf Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> Reviewed-by: Andy Nichols <andy.nichols@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp3
-rw-r--r--src/quick/scenegraph/qsgcontext.cpp8
-rw-r--r--src/quick/scenegraph/qsgcontext_p.h5
-rw-r--r--src/quick/scenegraph/qsgdefaultglyphnode_p.cpp8
-rw-r--r--src/quick/scenegraph/qsgdefaultglyphnode_p_p.h1
-rw-r--r--src/quick/scenegraph/qsgdefaultrendercontext.cpp39
6 files changed, 48 insertions, 16 deletions
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
index 0c0ee5e579..4fd0003e54 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
@@ -113,8 +113,7 @@ void QSGSoftwareRenderContext::invalidate()
qDeleteAll(m_textures);
m_textures.clear();
- qDeleteAll(m_fontEnginesToClean);
- m_fontEnginesToClean.clear();
+ Q_ASSERT(m_fontEnginesToClean.isEmpty());
qDeleteAll(m_glyphCaches);
m_glyphCaches.clear();
diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp
index be7c934fd7..7668db7a69 100644
--- a/src/quick/scenegraph/qsgcontext.cpp
+++ b/src/quick/scenegraph/qsgcontext.cpp
@@ -475,7 +475,13 @@ void QSGRenderContext::invalidateGlyphCaches()
void QSGRenderContext::registerFontengineForCleanup(QFontEngine *engine)
{
engine->ref.ref();
- m_fontEnginesToClean << engine;
+ m_fontEnginesToClean[engine]++;
+}
+
+void QSGRenderContext::unregisterFontengineForCleanup(QFontEngine *engine)
+{
+ m_fontEnginesToClean[engine]--;
+ Q_ASSERT(m_fontEnginesToClean.value(engine) >= 0);
}
QRhi *QSGRenderContext::rhi() const
diff --git a/src/quick/scenegraph/qsgcontext_p.h b/src/quick/scenegraph/qsgcontext_p.h
index 299b003716..19585c6f4b 100644
--- a/src/quick/scenegraph/qsgcontext_p.h
+++ b/src/quick/scenegraph/qsgcontext_p.h
@@ -166,6 +166,7 @@ public:
virtual int maxTextureSize() const = 0;
+ void unregisterFontengineForCleanup(QFontEngine *engine);
void registerFontengineForCleanup(QFontEngine *engine);
virtual QRhi *rhi() const;
@@ -187,7 +188,9 @@ protected:
QSet<QSGTexture *> m_texturesToDelete;
QHash<QString, QSGDistanceFieldGlyphCache *> m_glyphCaches;
- QSet<QFontEngine *> m_fontEnginesToClean;
+ // References to font engines that are currently in use by native rendering glyph nodes
+ // and which must be kept alive as long as they are used in the render thread.
+ QHash<QFontEngine *, int> m_fontEnginesToClean;
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp b/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp
index e9e6c26d88..f10dbd4bc6 100644
--- a/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp
+++ b/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp
@@ -322,6 +322,8 @@ QSGTextMaskMaterial::QSGTextMaskMaterial(QSGRenderContext *rc, const QVector4D &
QSGTextMaskMaterial::~QSGTextMaskMaterial()
{
+ if (m_retainedFontEngine != nullptr)
+ m_rc->unregisterFontengineForCleanup(m_retainedFontEngine);
delete m_texture;
}
@@ -385,6 +387,12 @@ void QSGTextMaskMaterial::updateCache(QFontEngine::GlyphFormat glyphFormat)
if (!m_glyphCache || int(m_glyphCache->glyphFormat()) != glyphFormat) {
m_glyphCache = new QSGRhiTextureGlyphCache(m_rc, glyphFormat, glyphCacheTransform, color);
fontEngine->setGlyphCache(cacheKey, m_glyphCache.data());
+ if (m_retainedFontEngine != nullptr)
+ m_rc->unregisterFontengineForCleanup(m_retainedFontEngine);
+
+ // Note: This is reference counted by the render context, so it will stay alive until
+ // we release that reference
+ m_retainedFontEngine = fontEngine;
m_rc->registerFontengineForCleanup(fontEngine);
}
}
diff --git a/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h b/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h
index e77e2715cd..d3b919c275 100644
--- a/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h
+++ b/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h
@@ -69,6 +69,7 @@ private:
QSGPlainTexture *m_texture;
QExplicitlySharedDataPointer<QFontEngineGlyphCache> m_glyphCache;
QRawFont m_font;
+ QFontEngine *m_retainedFontEngine = nullptr;
QRhi *m_rhi;
QVector4D m_color;
QSize m_size;
diff --git a/src/quick/scenegraph/qsgdefaultrendercontext.cpp b/src/quick/scenegraph/qsgdefaultrendercontext.cpp
index 462196927b..73f4bec27f 100644
--- a/src/quick/scenegraph/qsgdefaultrendercontext.cpp
+++ b/src/quick/scenegraph/qsgdefaultrendercontext.cpp
@@ -59,13 +59,29 @@ void QSGDefaultRenderContext::initialize(const QSGRenderContext::InitParams *par
void QSGDefaultRenderContext::invalidateGlyphCaches()
{
- auto it = m_glyphCaches.begin();
- while (it != m_glyphCaches.end()) {
- if (!(*it)->isActive()) {
- delete *it;
- it = m_glyphCaches.erase(it);
- } else {
- ++it;
+ {
+ auto it = m_glyphCaches.begin();
+ while (it != m_glyphCaches.end()) {
+ if (!(*it)->isActive()) {
+ delete *it;
+ it = m_glyphCaches.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ {
+ auto it = m_fontEnginesToClean.begin();
+ while (it != m_fontEnginesToClean.end()) {
+ if (it.value() == 0) {
+ it.key()->clearGlyphCache(this);
+ if (!it.key()->ref.deref())
+ delete it.key();
+ it = m_fontEnginesToClean.erase(it);
+ } else {
+ ++it;
+ }
}
}
}
@@ -108,11 +124,10 @@ void QSGDefaultRenderContext::invalidate()
// code is only called from QQuickWindow's shutdown which is called
// only when the GUI is blocked, and multiple threads will call it in
// sequence. (see qsgdefaultglyphnode_p.cpp's init())
- for (QSet<QFontEngine *>::const_iterator it = m_fontEnginesToClean.constBegin(),
- end = m_fontEnginesToClean.constEnd(); it != end; ++it) {
- (*it)->clearGlyphCache(this);
- if (!(*it)->ref.deref())
- delete *it;
+ for (auto it = m_fontEnginesToClean.constBegin(); it != m_fontEnginesToClean.constEnd(); ++it) {
+ it.key()->clearGlyphCache(this);
+ if (!it.key()->ref.deref())
+ delete it.key();
}
m_fontEnginesToClean.clear();