diff options
Diffstat (limited to 'src/extras/text/qdistancefieldglyphcache.cpp')
-rw-r--r-- | src/extras/text/qdistancefieldglyphcache.cpp | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/src/extras/text/qdistancefieldglyphcache.cpp b/src/extras/text/qdistancefieldglyphcache.cpp new file mode 100644 index 000000000..99085f378 --- /dev/null +++ b/src/extras/text/qdistancefieldglyphcache.cpp @@ -0,0 +1,370 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtGui/qrawfont.h> +#include <QtGui/qglyphrun.h> +#include <QtGui/private/qrawfont_p.h> + +#include "qdistancefieldglyphcache_p.h" +#include "qtextureatlas_p.h" + +#include <QtGui/qfont.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 + +using namespace Qt3DCore; + +namespace Qt3DExtras { + +// ref-count glyphs and keep track of where they are stored +class StoredGlyph { +public: + StoredGlyph() = default; + StoredGlyph(const StoredGlyph &) = default; + StoredGlyph(const QRawFont &font, quint32 glyph, bool doubleResolution); + + int refCount() const { return m_ref; } + void ref() { ++m_ref; } + int deref() { return m_ref = std::max(m_ref - 1, (quint32) 0); } + + bool addToTextureAtlas(QTextureAtlas *atlas); + void removeFromTextureAtlas(); + + QTextureAtlas *atlas() const { return m_atlas; } + QRectF glyphPathBoundingRect() const { return m_glyphPathBoundingRect; } + QRectF texCoords() const; + +private: + quint32 m_glyph = (quint32) -1; + 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; + + QVector<QTextureAtlas*> m_atlasses; +}; + +StoredGlyph::StoredGlyph(const QRawFont &font, quint32 glyph, bool doubleResolution) + : m_glyph(glyph) + , 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) +{ +} + +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(m_parentNode); + 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); + 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); + delete atlas; + } + + 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 + 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); + + if (entry.atlas()) { + ret.glyphPathBoundingRect = entry.glyphPathBoundingRect(); + ret.texCoords = entry.texCoords(); + ret.texture = entry.atlas(); + } + } + + return ret; +} +} // anonymous + +QVector<QDistanceFieldGlyphCache::Glyph> QDistanceFieldGlyphCache::refGlyphs(const QGlyphRun &run) +{ + DistanceFieldFont *dff = getOrCreateDistanceFieldFont(run.rawFont()); + QVector<QDistanceFieldGlyphCache::Glyph> ret; + + const QVector<quint32> 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 QVector<quint32> 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 |