Revision control
++ // Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include <QtGui/qrawfont.h>
++ #include <QtGui/qglyphrun.h>
++ #include <QtGui/private/qrawfont_p.h>
++
++ #include "qdistancefieldglyphcache_p.h"
++ #include "qtextureatlas_p.h"
++
++ #include <QtGui/qpainterpath.h>
++ #include <QtGui/qfont.h>
++ #include <QtGui/qpainterpath.h>
++ #include <QtGui/private/qdistancefield_p.h>
++ #include <Qt3DCore/private/qnode_p.h>
++ #include <Qt3DExtras/private/qtextureatlas_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ #define DEFAULT_IMAGE_PADDING 1
++
++
++ namespace Qt3DExtras {
++
++ using namespace Qt3DCore;
++
++ // ref-count glyphs and keep track of where they are stored
++ class StoredGlyph {
++ public:
++ StoredGlyph() = default;
++ StoredGlyph(const QRawFont &font, quint32 glyph, bool doubleResolution);
++
++ int refCount() const { return int(m_ref); }
++ void ref() { ++m_ref; }
++ int deref() { m_ref = std::max(m_ref - 1, quint32(0)); return int(m_ref); }
++
++ bool addToTextureAtlas(QTextureAtlas *atlas);
++ void removeFromTextureAtlas();
++
++ QTextureAtlas *atlas() const { return m_atlas; }
++ QRectF glyphPathBoundingRect() const { return m_glyphPathBoundingRect; }
++ QRectF texCoords() const;
++
++ private:
++ quint32 m_ref = 0;
++ QTextureAtlas *m_atlas = nullptr;
++ QTextureAtlas::TextureId m_atlasEntry = QTextureAtlas::InvalidTexture;
++ QRectF m_glyphPathBoundingRect;
++ QImage m_distanceFieldImage; // only used until added to texture atlas
++ };
++
++ // A DistanceFieldFont stores all glyphs for a given QRawFont.
++ // it will use multiple QTextureAtlasess to store the distance
++ // fields and uses ref-counting for each glyph to ensure that
++ // unused glyphs are removed from the texture atlasses.
++ class DistanceFieldFont
++ {
++ public:
++ DistanceFieldFont(const QRawFont &font, bool doubleRes, Qt3DCore::QNode *parent);
++ ~DistanceFieldFont();
++
++ StoredGlyph findGlyph(quint32 glyph) const;
++ StoredGlyph refGlyph(quint32 glyph);
++ void derefGlyph(quint32 glyph);
++
++ bool doubleGlyphResolution() const { return m_doubleGlyphResolution; }
++
++ private:
++ QRawFont m_font;
++ bool m_doubleGlyphResolution;
++ Qt3DCore::QNode *m_parentNode; // parent node for the QTextureAtlasses
++
++ QHash<quint32, StoredGlyph> m_glyphs;
++
++ QList<QTextureAtlas*> m_atlasses;
++ };
++
++ StoredGlyph::StoredGlyph(const QRawFont &font, quint32 glyph, bool doubleResolution)
++ : m_ref(1)
++ , m_atlas(nullptr)
++ , m_atlasEntry(QTextureAtlas::InvalidTexture)
++ {
++ // create new single-channel distance field image for given glyph
++ const QPainterPath path = font.pathForGlyph(glyph);
++ const QDistanceField dfield(font, glyph, doubleResolution);
++ m_distanceFieldImage = dfield.toImage(QImage::Format_Alpha8);
++
++ // scale bounding rect down (as in QSGDistanceFieldGlyphCache::glyphData())
++ const QRectF pathBound = path.boundingRect();
++ float f = 1.0f / QT_DISTANCEFIELD_SCALE(doubleResolution);
++ m_glyphPathBoundingRect = QRectF(pathBound.left() * f, -pathBound.top() * f, pathBound.width() * f, pathBound.height() * f);
++ }
++
++ bool StoredGlyph::addToTextureAtlas(QTextureAtlas *atlas)
++ {
++ if (m_atlas || m_distanceFieldImage.isNull())
++ return false;
++
++ const auto texId = atlas->addImage(m_distanceFieldImage, DEFAULT_IMAGE_PADDING);
++ if (texId != QTextureAtlas::InvalidTexture) {
++ m_atlas = atlas;
++ m_atlasEntry = texId;
++ m_distanceFieldImage = QImage(); // free glyph image data
++ return true;
++ }
++
++ return false;
++ }
++
++ void StoredGlyph::removeFromTextureAtlas()
++ {
++ if (m_atlas) {
++ m_atlas->removeImage(m_atlasEntry);
++ m_atlas = nullptr;
++ m_atlasEntry = QTextureAtlas::InvalidTexture;
++ }
++ }
++
++ QRectF StoredGlyph::texCoords() const
++ {
++ return m_atlas ? m_atlas->imageTexCoords(m_atlasEntry) : QRectF();
++ }
++
++ DistanceFieldFont::DistanceFieldFont(const QRawFont &font, bool doubleRes, Qt3DCore::QNode *parent)
++ : m_font(font)
++ , m_doubleGlyphResolution(doubleRes)
++ , m_parentNode(parent)
++ {
++ Q_ASSERT(m_parentNode);
++ }
++
++ DistanceFieldFont::~DistanceFieldFont()
++ {
++ qDeleteAll(m_atlasses);
++ }
++
++ StoredGlyph DistanceFieldFont::findGlyph(quint32 glyph) const
++ {
++ const auto it = m_glyphs.find(glyph);
++ return (it != m_glyphs.cend()) ? it.value() : StoredGlyph();
++ }
++
++ StoredGlyph DistanceFieldFont::refGlyph(quint32 glyph)
++ {
++ // if glyph already exists, just increase ref-count
++ auto it = m_glyphs.find(glyph);
++ if (it != m_glyphs.end()) {
++ it.value().ref();
++ return it.value();
++ }
++
++ // need to create new glyph
++ StoredGlyph storedGlyph(m_font, glyph, m_doubleGlyphResolution);
++
++ // see if one of the existing atlasses can hold the distance field image
++ for (int i = 0; i < m_atlasses.size(); i++)
++ if (storedGlyph.addToTextureAtlas(m_atlasses[i]))
++ break;
++
++ // if no texture atlas is big enough (or no exists yet), allocate a new one
++ if (!storedGlyph.atlas()) {
++ // this should be enough to store 40-60 glyphs, which should be sufficient for most
++ // scenarios
++ const int size = m_doubleGlyphResolution ? 512 : 256;
++
++ QTextureAtlas *atlas = new QTextureAtlas();
++ atlas->setWidth(size);
++ atlas->setHeight(size);
++ atlas->setFormat(Qt3DRender::QAbstractTexture::R8_UNorm);
++ atlas->setPixelFormat(QOpenGLTexture::Red);
++ atlas->setMinificationFilter(Qt3DRender::QAbstractTexture::Linear);
++ atlas->setMagnificationFilter(Qt3DRender::QAbstractTexture::Linear);
++ atlas->setParent(m_parentNode);
++ m_atlasses << atlas;
++
++ if (!storedGlyph.addToTextureAtlas(atlas))
++ qWarning() << Q_FUNC_INFO << "Couldn't add glyph to newly allocated atlas. Glyph could be huge?";
++ }
++
++ m_glyphs.insert(glyph, storedGlyph);
++ return storedGlyph;
++ }
++
++ void DistanceFieldFont::derefGlyph(quint32 glyph)
++ {
++ auto it = m_glyphs.find(glyph);
++ if (it == m_glyphs.end())
++ return;
++
++ // TODO
++ // possible optimization: keep unreferenced glyphs as the texture atlas
++ // still has space. only if a new glyph needs to be allocated, and there
++ // is no more space within the atlas, then we can actually remove the glyphs
++ // from the atlasses.
++
++ // remove glyph if no refs anymore
++ if (it.value().deref() <= 0) {
++ QTextureAtlas *atlas = it.value().atlas();
++ it.value().removeFromTextureAtlas();
++
++ // remove atlas, if it contains no glyphs anymore
++ if (atlas && atlas->imageCount() == 0) {
++ Q_ASSERT(m_atlasses.contains(atlas));
++
++ m_atlasses.removeAll(atlas);
++
++ // This function might have been called as a result of destroying
++ // the scene root which traverses the entire scene tree. Calling
++ // delete on the atlas here could lead to dangling pointers in the
++ // least of children being traversed for destruction.
++ atlas->deleteLater();
++ }
++
++ m_glyphs.erase(it);
++ }
++ }
++
++ // copied from QSGDistanceFieldGlyphCacheManager::fontKey
++ // we use this function to compare QRawFonts, as QRawFont doesn't
++ // implement a stable comparison function
++ QString QDistanceFieldGlyphCache::fontKey(const QRawFont &font)
++ {
++ QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine;
++ if (!fe->faceId().filename.isEmpty()) {
++ QByteArray keyName = fe->faceId().filename;
++ if (font.style() != QFont::StyleNormal)
++ keyName += QByteArray(" I");
++ if (font.weight() != QFont::Normal)
++ keyName += ' ' + QByteArray::number(font.weight());
++ keyName += QByteArray(" DF");
++ return QString::fromUtf8(keyName);
++ } else {
++ return QString::fromLatin1("%1_%2_%3_%4")
++ .arg(font.familyName())
++ .arg(font.styleName())
++ .arg(font.weight())
++ .arg(font.style());
++ }
++ }
++
++ DistanceFieldFont* QDistanceFieldGlyphCache::getOrCreateDistanceFieldFont(const QRawFont &font)
++ {
++ // return, if font already exists (make sure to only create one DistanceFieldFont for
++ // each unique QRawFont, by building a hash on the QRawFont that ignores the font size)
++ const QString key = fontKey(font);
++ const auto it = m_fonts.constFind(key);
++ if (it != m_fonts.cend())
++ return it.value();
++
++ // logic taken from QSGDistanceFieldGlyphCache::QSGDistanceFieldGlyphCache
++ QRawFontPrivate *fontD = QRawFontPrivate::get(font);
++ const int glyphCount = fontD->fontEngine->glyphCount();
++ const bool useDoubleRes = qt_fontHasNarrowOutlines(font) && glyphCount < QT_DISTANCEFIELD_HIGHGLYPHCOUNT();
++
++ // only keep one FontCache with a fixed pixel size for each distinct font type
++ QRawFont actualFont = font;
++ actualFont.setPixelSize(QT_DISTANCEFIELD_BASEFONTSIZE(useDoubleRes) * QT_DISTANCEFIELD_SCALE(useDoubleRes));
++
++ // create new font cache
++ // we set the parent node to nullptr, since the parent node of QTextureAtlasses
++ // will be set when we pass them to QText2DMaterial later
++ Q_ASSERT(m_rootNode);
++ DistanceFieldFont *dff = new DistanceFieldFont(actualFont, useDoubleRes, m_rootNode);
++ m_fonts.insert(key, dff);
++ return dff;
++ }
++
++ QDistanceFieldGlyphCache::QDistanceFieldGlyphCache()
++ : m_rootNode(nullptr)
++ {
++ }
++
++ QDistanceFieldGlyphCache::~QDistanceFieldGlyphCache()
++ {
++ }
++
++ void QDistanceFieldGlyphCache::setRootNode(QNode *rootNode)
++ {
++ m_rootNode = rootNode;
++ }
++
++ QNode *QDistanceFieldGlyphCache::rootNode() const
++ {
++ return m_rootNode;
++ }
++
++ bool QDistanceFieldGlyphCache::doubleGlyphResolution(const QRawFont &font)
++ {
++ return getOrCreateDistanceFieldFont(font)->doubleGlyphResolution();
++ }
++
++ namespace {
++ QDistanceFieldGlyphCache::Glyph refAndGetGlyph(DistanceFieldFont *dff, quint32 glyph)
++ {
++ QDistanceFieldGlyphCache::Glyph ret;
++
++ if (dff) {
++ const auto entry = dff->refGlyph(glyph);
++
++ Q_ASSERT(entry.atlas());
++ ret.glyphPathBoundingRect = entry.glyphPathBoundingRect();
++ ret.texCoords = entry.texCoords();
++ ret.texture = entry.atlas();
++ }
++
++ return ret;
++ }
++ } // anonymous
++
++ QList<QDistanceFieldGlyphCache::Glyph> QDistanceFieldGlyphCache::refGlyphs(const QGlyphRun &run)
++ {
++ DistanceFieldFont *dff = getOrCreateDistanceFieldFont(run.rawFont());
++ QList<QDistanceFieldGlyphCache::Glyph> ret;
++
++ const auto glyphs = run.glyphIndexes();
++ for (quint32 glyph : glyphs)
++ ret << refAndGetGlyph(dff, glyph);
++
++ return ret;
++ }
++
++ QDistanceFieldGlyphCache::Glyph QDistanceFieldGlyphCache::refGlyph(const QRawFont &font, quint32 glyph)
++ {
++ return refAndGetGlyph(getOrCreateDistanceFieldFont(font), glyph);
++ }
++
++ void QDistanceFieldGlyphCache::derefGlyphs(const QGlyphRun &run)
++ {
++ DistanceFieldFont *dff = getOrCreateDistanceFieldFont(run.rawFont());
++
++ const auto glyphs = run.glyphIndexes();
++ for (quint32 glyph : glyphs)
++ dff->derefGlyph(glyph);
++ }
++
++ void QDistanceFieldGlyphCache::derefGlyph(const QRawFont &font, quint32 glyph)
++ {
++ getOrCreateDistanceFieldFont(font)->derefGlyph(glyph);
++ }
++
++ } // namespace Qt3DExtras
++
++ QT_END_NAMESPACE