/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** 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 http://www.qt.io/terms-conditions. For further ** information use the contact form at http://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 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #define EGL_EGLEXT_PROTOTYPES #define GL_GLEXT_PROTOTYPES #if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG) #include #include #endif #include "qsgshareddistancefieldglyphcache_p.h" #include #include #include #include #include // #define QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG QT_BEGIN_NAMESPACE namespace { class QSGInvokeEvent: public QEvent { public: QSGInvokeEvent(QPlatformSharedGraphicsCache *cache, const QByteArray &cacheId = QByteArray(), const QVector &glyphIds = QVector(), bool inSceneGraphUpdate = false) : QEvent(User) , m_cache(cache) , m_cacheId(cacheId) , m_glyphIds(glyphIds) , m_inSceneGraphUpdate(inSceneGraphUpdate) {} bool inSceneGraphUpdate() const { return m_inSceneGraphUpdate; } QPlatformSharedGraphicsCache *cache() const { return m_cache; } virtual void invoke() = 0; protected: QPlatformSharedGraphicsCache *m_cache; QByteArray m_cacheId; QVector m_glyphIds; bool m_inSceneGraphUpdate; }; class QSGReleaseItemsEvent: public QSGInvokeEvent { public: QSGReleaseItemsEvent(QPlatformSharedGraphicsCache *cache, const QByteArray &cacheId, const QVector &glyphIds, bool inSceneGraphUpdate) : QSGInvokeEvent(cache, cacheId, glyphIds, inSceneGraphUpdate) { } void invoke() { m_cache->releaseItems(m_cacheId, m_glyphIds); } }; class QSGRequestItemsEvent: public QSGInvokeEvent { public: QSGRequestItemsEvent(QPlatformSharedGraphicsCache *cache, const QByteArray &cacheId, const QVector &glyphIds, bool inSceneGraphUpdate) : QSGInvokeEvent(cache, cacheId, glyphIds, inSceneGraphUpdate) { } void invoke() { m_cache->requestItems(m_cacheId, m_glyphIds); } }; class QSGInsertItemsEvent: public QSGInvokeEvent { public: QSGInsertItemsEvent(QPlatformSharedGraphicsCache *cache, const QByteArray &cacheId, const QVector &glyphIds, const QVector &images, bool inSceneGraphUpdate) : QSGInvokeEvent(cache, cacheId, glyphIds, inSceneGraphUpdate) , m_images(images) { } void invoke() { m_cache->insertItems(m_cacheId, m_glyphIds, m_images); } private: QVector m_images; }; class QSGEndRequestBatchEvent: public QSGInvokeEvent { public: QSGEndRequestBatchEvent(QPlatformSharedGraphicsCache *cache) : QSGInvokeEvent(cache) { } void invoke() { if (m_cache->requestBatchStarted()) m_cache->endRequestBatch(); } }; class QSGMainThreadInvoker: public QObject { public: bool event(QEvent *e) { if (e->type() == QEvent::User) { Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); QSGInvokeEvent *invokeEvent = static_cast(e); if (invokeEvent->inSceneGraphUpdate()) { QPlatformSharedGraphicsCache *cache = invokeEvent->cache(); if (!cache->requestBatchStarted()) cache->beginRequestBatch(); } static_cast(e)->invoke(); return true; } return QObject::event(e); } static QSGMainThreadInvoker *instance() { if (m_invoker == 0) { m_invoker = new QSGMainThreadInvoker; m_invoker->moveToThread(QCoreApplication::instance()->thread()); } return m_invoker; } private: static QSGMainThreadInvoker *m_invoker; }; QSGMainThreadInvoker* QSGMainThreadInvoker::m_invoker = 0; } QSGSharedDistanceFieldGlyphCache::QSGSharedDistanceFieldGlyphCache(const QByteArray &cacheId, QPlatformSharedGraphicsCache *sharedGraphicsCache, QSGDistanceFieldGlyphCacheManager *man, QOpenGLContext *c, const QRawFont &font) : QSGDistanceFieldGlyphCache(man, c, font) , m_cacheId(cacheId) , m_sharedGraphicsCache(sharedGraphicsCache) , m_isInSceneGraphUpdate(false) , m_hasPostedEvents(false) { #if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG) qDebug("QSGSharedDistanceFieldGlyphCache with id %s created in thread %p", cacheId.constData(), QThread::currentThreadId()); #endif Q_ASSERT(sizeof(glyph_t) == sizeof(quint32)); Q_ASSERT(sharedGraphicsCache != 0); connect(sharedGraphicsCache, SIGNAL(itemsMissing(QByteArray,QVector)), this, SLOT(reportItemsMissing(QByteArray,QVector)), Qt::DirectConnection); connect(sharedGraphicsCache, SIGNAL(itemsAvailable(QByteArray,void*,QVector,QVector)), this, SLOT(reportItemsAvailable(QByteArray,void*,QVector,QVector)), Qt::DirectConnection); connect(sharedGraphicsCache, SIGNAL(itemsUpdated(QByteArray,void*,QVector,QVector)), this, SLOT(reportItemsUpdated(QByteArray,void*,QVector,QVector)), Qt::DirectConnection); connect(sharedGraphicsCache, SIGNAL(itemsInvalidated(QByteArray,QVector)), this, SLOT(reportItemsInvalidated(QByteArray,QVector)), Qt::DirectConnection); Q_ASSERT(c); QQuickWindow *window = static_cast(c->surface()); Q_ASSERT(window != 0); connect(window, SIGNAL(beforeSynchronizing()), this, SLOT(sceneGraphUpdateStarted()), Qt::DirectConnection); connect(window, SIGNAL(beforeRendering()), this, SLOT(sceneGraphUpdateDone()), Qt::DirectConnection); } QSGSharedDistanceFieldGlyphCache::~QSGSharedDistanceFieldGlyphCache() { { QHash::const_iterator it = m_bufferForGlyph.constBegin(); while (it != m_bufferForGlyph.constEnd()) { m_sharedGraphicsCache->dereferenceBuffer(it.value()); ++it; } } { QHash::const_iterator it = m_pendingReadyGlyphs.constBegin(); while (it != m_pendingReadyGlyphs.constEnd()) { m_sharedGraphicsCache->dereferenceBuffer(it.value().buffer); ++it; } } } void QSGSharedDistanceFieldGlyphCache::requestGlyphs(const QSet &glyphs) { typedef QSet::const_iterator GlyphSetConstIt; QMutexLocker locker(&m_pendingGlyphsMutex); #if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG) qDebug("QSGSharedDistanceFieldGlyphCache::requestGlyphs() called for %s (%d glyphs)", m_cacheId.constData(), glyphs.size()); #endif m_requestedGlyphsThatHaveNotBeenReturned.unite(glyphs); m_requestedGlyphs.unite(glyphs); QVector glyphsVector; glyphsVector.reserve(glyphs.size()); for (GlyphSetConstIt it = glyphs.constBegin(), cend = glyphs.constEnd(); it != cend; ++it) { Q_ASSERT(!m_bufferForGlyph.contains(*it)); glyphsVector.append(*it); } m_hasPostedEvents = true; QSGMainThreadInvoker *invoker = QSGMainThreadInvoker::instance(); QCoreApplication::postEvent(invoker, new QSGRequestItemsEvent(m_sharedGraphicsCache, m_cacheId, glyphsVector, m_isInSceneGraphUpdate)); } void QSGSharedDistanceFieldGlyphCache::waitForGlyphs() { Q_ASSERT(!m_isInSceneGraphUpdate); if (m_isInSceneGraphUpdate) { qWarning("QSGSharedDistanceFieldGlyphCache::waitForGlyphs: Called from inside " "scenegraph update. Will freeze."); } { QMutexLocker locker(&m_pendingGlyphsMutex); while (!m_requestedGlyphsThatHaveNotBeenReturned.isEmpty()) m_pendingGlyphsCondition.wait(&m_pendingGlyphsMutex); } } void QSGSharedDistanceFieldGlyphCache::storeGlyphs(const QList &glyphs) { { QMutexLocker locker(&m_pendingGlyphsMutex); #if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG) qDebug("QSGSharedDistanceFieldGlyphCache::storeGlyphs() called for %s (%d glyphs)", m_cacheId.constData(), glyphs.size()); #endif int glyphCount = glyphs.size(); QVector glyphIds(glyphCount); QVector images(glyphCount); for (int i = 0; i < glyphs.size(); ++i) { const QDistanceField &df = glyphs.at(i); m_requestedGlyphsThatHaveNotBeenReturned.insert(df.glyph()); glyphIds[i] = df.glyph(); // ### TODO: Handle QDistanceField in QPlatformSharedGraphicsCache images[i] = df.toImage(QImage::Format_Indexed8); } m_hasPostedEvents = true; QSGMainThreadInvoker *invoker = QSGMainThreadInvoker::instance(); QCoreApplication::postEvent(invoker, new QSGInsertItemsEvent(m_sharedGraphicsCache, m_cacheId, glyphIds, images, m_isInSceneGraphUpdate)); } processPendingGlyphs(); } void QSGSharedDistanceFieldGlyphCache::referenceGlyphs(const QSet &glyphs) { Q_UNUSED(glyphs); // Intentionally empty. Not required in this implementation, since the glyphs are reference // counted outside and releaseGlyphs() will only be called when there are no more references. } void QSGSharedDistanceFieldGlyphCache::releaseGlyphs(const QSet &glyphs) { typedef QSet::const_iterator GlyphSetConstIt; #if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG) qDebug("QSGSharedDistanceFieldGlyphCache::releaseGlyphs() called for %s (%d glyphs)", m_cacheId.constData(), glyphs.size()); #endif m_requestedGlyphs.subtract(glyphs); QVector glyphsVector; glyphsVector.reserve(glyphs.size()); for (GlyphSetConstIt glyphsIt = glyphs.constBegin(), cend = glyphs.constEnd(); glyphsIt != cend; ++glyphsIt) { QHash::iterator bufferIt = m_bufferForGlyph.find(*glyphsIt); if (bufferIt != m_bufferForGlyph.end()) { void *buffer = bufferIt.value(); removeGlyph(*glyphsIt); m_bufferForGlyph.erase(bufferIt); Q_ASSERT(!m_bufferForGlyph.contains(*glyphsIt)); if (!m_sharedGraphicsCache->dereferenceBuffer(buffer)) { #if !defined(QT_NO_DEBUG) bufferIt = m_bufferForGlyph.begin(); while (bufferIt != m_bufferForGlyph.end()) { Q_ASSERT(bufferIt.value() != buffer); ++bufferIt; } #endif } } glyphsVector.append(*glyphsIt); } m_hasPostedEvents = true; QSGMainThreadInvoker *mainThreadInvoker = QSGMainThreadInvoker::instance(); QCoreApplication::postEvent(mainThreadInvoker, new QSGReleaseItemsEvent(m_sharedGraphicsCache, m_cacheId, glyphsVector, m_isInSceneGraphUpdate)); } void QSGSharedDistanceFieldGlyphCache::registerOwnerElement(QQuickItem *ownerElement) { Owner &owner = m_registeredOwners[ownerElement]; if (owner.ref == 0) { owner.item = ownerElement; bool ok = connect(this, SIGNAL(glyphsPending()), ownerElement, SLOT(triggerPreprocess())); Q_ASSERT_X(ok, Q_FUNC_INFO, "QML element that owns a glyph node must have triggerPreprocess() slot"); Q_UNUSED(ok); } ++owner.ref; } void QSGSharedDistanceFieldGlyphCache::unregisterOwnerElement(QQuickItem *ownerElement) { QHash::iterator it = m_registeredOwners.find(ownerElement); if (it != m_registeredOwners.end() && --it->ref <= 0) { if (it->item) disconnect(this, SIGNAL(glyphsPending()), ownerElement, SLOT(triggerPreprocess())); m_registeredOwners.erase(it); } } namespace { struct TextureContent { QSize size; QVector glyphs; }; } void QSGSharedDistanceFieldGlyphCache::processPendingGlyphs() { Q_ASSERT(QThread::currentThread() == thread()); waitForGlyphs(); { QMutexLocker locker(&m_pendingGlyphsMutex); if (m_pendingMissingGlyphs.isEmpty() && m_pendingReadyGlyphs.isEmpty() && m_pendingInvalidatedGlyphs.isEmpty()) { return; } { QVector pendingMissingGlyphs; pendingMissingGlyphs.reserve(m_pendingMissingGlyphs.size()); QSet::const_iterator it = m_pendingMissingGlyphs.constBegin(); while (it != m_pendingMissingGlyphs.constEnd()) { pendingMissingGlyphs.append(*it); ++it; } markGlyphsToRender(pendingMissingGlyphs); } { QVector filteredPendingInvalidatedGlyphs; filteredPendingInvalidatedGlyphs.reserve(m_pendingInvalidatedGlyphs.size()); QSet::const_iterator it = m_pendingInvalidatedGlyphs.constBegin(); while (it != m_pendingInvalidatedGlyphs.constEnd()) { bool rerequestGlyph = false; // The glyph was invalidated right after being posted as ready, we throw away // the ready glyph and rerequest it to be certain QHash::iterator pendingGlyphIt = m_pendingReadyGlyphs.find(*it); if (pendingGlyphIt != m_pendingReadyGlyphs.end()) { m_sharedGraphicsCache->dereferenceBuffer(pendingGlyphIt.value().buffer); pendingGlyphIt = m_pendingReadyGlyphs.erase(pendingGlyphIt); rerequestGlyph = true; } void *bufferId = m_bufferForGlyph.value(*it, 0); if (bufferId != 0) { m_sharedGraphicsCache->dereferenceBuffer(bufferId); m_bufferForGlyph.remove(*it); rerequestGlyph = true; } if (rerequestGlyph) filteredPendingInvalidatedGlyphs.append(*it); ++it; } // If this cache is still using the glyphs, reset the texture held by them, and mark them // to be rendered again since they are still needed. if (!filteredPendingInvalidatedGlyphs.isEmpty()) { setGlyphsTexture(filteredPendingInvalidatedGlyphs, Texture()); markGlyphsToRender(filteredPendingInvalidatedGlyphs); } } { QList glyphPositions; QHash textureContentForBuffer; { QHash::iterator it = m_pendingReadyGlyphs.begin(); while (it != m_pendingReadyGlyphs.end()) { void *currentGlyphBuffer = m_bufferForGlyph.value(it.key(), 0); if (currentGlyphBuffer != 0) { if (!m_sharedGraphicsCache->dereferenceBuffer(currentGlyphBuffer)) { Q_ASSERT(!textureContentForBuffer.contains(currentGlyphBuffer)); } } PendingGlyph &pendingGlyph = it.value(); // We don't ref or deref the buffer here, since it was already referenced when // added to the pending ready glyphs m_bufferForGlyph[it.key()] = pendingGlyph.buffer; textureContentForBuffer[pendingGlyph.buffer].size = pendingGlyph.bufferSize; textureContentForBuffer[pendingGlyph.buffer].glyphs.append(it.key()); GlyphPosition glyphPosition; glyphPosition.glyph = it.key(); glyphPosition.position = pendingGlyph.position; glyphPositions.append(glyphPosition); ++it; } } setGlyphsPosition(glyphPositions); { QHash::const_iterator it = textureContentForBuffer.constBegin(); while (it != textureContentForBuffer.constEnd()) { Texture texture; texture.textureId = m_sharedGraphicsCache->textureIdForBuffer(it.key()); texture.size = m_sharedGraphicsCache->sizeOfBuffer(it.key()); setGlyphsTexture(it.value().glyphs, texture); ++it; } } } m_pendingMissingGlyphs.clear(); m_pendingInvalidatedGlyphs.clear(); m_pendingReadyGlyphs.clear(); } } void QSGSharedDistanceFieldGlyphCache::reportItemsAvailable(const QByteArray &cacheId, void *bufferId, const QVector &itemIds, const QVector &positions) { bool requestedItemsInList = false; { QMutexLocker locker(&m_pendingGlyphsMutex); if (m_cacheId != cacheId) return; #if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG) qDebug("QSGSharedDistanceFieldGlyphCache::reportItemsAvailable() called for %s (%d glyphs)", cacheId.constData(), itemIds.size()); #endif for (int i=0; i &itemIds, const QVector &positions) { { QMutexLocker locker(&m_pendingGlyphsMutex); if (m_cacheId != cacheId) return; Q_ASSERT(itemIds.size() == positions.size()); #if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG) qDebug("QSGSharedDistanceFieldGlyphCache::reportItemsUpdated() called for %s (%d glyphs)", cacheId.constData(), itemIds.size()); #endif for (int i=0; ireferenceBuffer(bufferId); if (oldBuffer != 0) m_sharedGraphicsCache->dereferenceBuffer(oldBuffer); m_requestedGlyphsThatHaveNotBeenReturned.remove(itemIds.at(i)); } } } m_pendingGlyphsCondition.wakeAll(); emit glyphsPending(); } void QSGSharedDistanceFieldGlyphCache::reportItemsInvalidated(const QByteArray &cacheId, const QVector &itemIds) { { QMutexLocker locker(&m_pendingGlyphsMutex); if (m_cacheId != cacheId) return; for (int i=0; i &itemIds) { { QMutexLocker locker(&m_pendingGlyphsMutex); if (m_cacheId != cacheId) return; #if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG) qDebug("QSGSharedDistanceFieldGlyphCache::reportItemsMissing() called for %s (%d glyphs)", cacheId.constData(), itemIds.size()); #endif for (int i=0; i