From 47a5c708bf4e555cb8febef583f32c99f7d8ea1e Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Thu, 24 Nov 2011 13:48:19 +0100 Subject: Add support for shared glyph cache Use a shared graphics cache to back the distance fields if it is available. Change-Id: Id5e6e7a28e38e349d787e66016b2d0faebc791d7 Reviewed-by: Jiang Jiang --- .../qsgshareddistancefieldglyphcache.cpp | 621 +++++++++++++++++++++ 1 file changed, 621 insertions(+) create mode 100644 src/quick/scenegraph/qsgshareddistancefieldglyphcache.cpp (limited to 'src/quick/scenegraph/qsgshareddistancefieldglyphcache.cpp') diff --git a/src/quick/scenegraph/qsgshareddistancefieldglyphcache.cpp b/src/quick/scenegraph/qsgshareddistancefieldglyphcache.cpp new file mode 100644 index 0000000000..841322e58b --- /dev/null +++ b/src/quick/scenegraph/qsgshareddistancefieldglyphcache.cpp @@ -0,0 +1,621 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $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 + +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(QVector) + +QT_BEGIN_NAMESPACE + +QSGSharedDistanceFieldGlyphCache::QSGSharedDistanceFieldGlyphCache(const QByteArray &cacheId, + QPlatformSharedGraphicsCache *sharedGraphicsCache, + QSGDistanceFieldGlyphCacheManager *man, + QOpenGLContext *c, + const QRawFont &font) + : QSGDistanceFieldGlyphCache(man, c, font) + , m_cacheId(cacheId) + , m_sharedGraphicsCache(sharedGraphicsCache) +{ +#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); + + qRegisterMetaType >(); + qRegisterMetaType >(); + + connect(sharedGraphicsCache, SIGNAL(itemsMissing(QByteArray,QVector)), + this, SLOT(reportItemsMissing(QByteArray,QVector)), + Qt::DirectConnection); + connect(sharedGraphicsCache, SIGNAL(itemsAvailable(QByteArray,void*,QSize,QVector,QVector)), + this, SLOT(reportItemsAvailable(QByteArray,void*,QSize,QVector,QVector)), + Qt::DirectConnection); + connect(sharedGraphicsCache, SIGNAL(itemsUpdated(QByteArray,void*,QSize,QVector,QVector)), + this, SLOT(reportItemsAvailable(QByteArray,void*,QSize,QVector,QVector)), + Qt::DirectConnection); + connect(sharedGraphicsCache, SIGNAL(itemsInvalidated(QByteArray,QVector)), + this, SLOT(reportItemsInvalidated(QByteArray,QVector)), + 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) +{ + 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); + + QVector glyphsVector; + glyphsVector.reserve(glyphs.size()); + + QSet::const_iterator it; + for (it = glyphs.constBegin(); it != glyphs.constEnd(); ++it) { + Q_ASSERT(!m_bufferForGlyph.contains(*it)); + glyphsVector.append(*it); + } + + // Invoke method on queued connection to make sure it's called asynchronously on the + // correct thread (requestGlyphs() is called from the rendering thread.) + QMetaObject::invokeMethod(m_sharedGraphicsCache, "requestItems", Qt::QueuedConnection, + Q_ARG(QByteArray, m_cacheId), + Q_ARG(QVector, glyphsVector)); +} + +void QSGSharedDistanceFieldGlyphCache::waitForGlyphs() +{ + { + QMutexLocker locker(&m_pendingGlyphsMutex); + while (!m_requestedGlyphsThatHaveNotBeenReturned.isEmpty()) + m_pendingGlyphsCondition.wait(&m_pendingGlyphsMutex); + } +} + +void QSGSharedDistanceFieldGlyphCache::storeGlyphs(const QHash &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); + QHash::const_iterator it = glyphs.constBegin(); + int i=0; + while (it != glyphs.constEnd()) { + m_requestedGlyphsThatHaveNotBeenReturned.insert(it.key()); + glyphIds[i] = it.key(); + images[i] = it.value(); + + ++it; ++i; + } + + QMetaObject::invokeMethod(m_sharedGraphicsCache, "insertItems", Qt::QueuedConnection, + Q_ARG(QByteArray, m_cacheId), + Q_ARG(QVector, glyphIds), + Q_ARG(QVector, images)); + } + + 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) +{ +#if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG) + qDebug("QSGSharedDistanceFieldGlyphCache::releaseGlyphs() called for %s (%d glyphs)", + m_cacheId.constData(), glyphs.size()); +#endif + + QVector glyphsVector; + glyphsVector.reserve(glyphs.size()); + + QSet::const_iterator glyphsIt; + for (glyphsIt = glyphs.constBegin(); glyphsIt != glyphs.constEnd(); ++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); + } + + QMetaObject::invokeMethod(m_sharedGraphicsCache, "releaseItems", Qt::QueuedConnection, + Q_ARG(QByteArray, m_cacheId), + Q_ARG(QVector, glyphsVector)); +} + +void QSGSharedDistanceFieldGlyphCache::registerOwnerElement(QQuickItem *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); +} + +void QSGSharedDistanceFieldGlyphCache::unregisterOwnerElement(QQuickItem *ownerElement) +{ + disconnect(this, SIGNAL(glyphsPending()), ownerElement, SLOT(triggerPreprocess())); +} + +#if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG_) +# include + +void QSGSharedDistanceFieldGlyphCache::saveTexture(GLuint textureId, int width, int height) +{ + GLuint fboId; + glGenFramebuffers(1, &fboId); + + GLuint tmpTexture = 0; + glGenTextures(1, &tmpTexture); + glBindTexture(GL_TEXTURE_2D, tmpTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + + glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboId); + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, + tmpTexture, 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, textureId); + + glDisable(GL_STENCIL_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_BLEND); + + GLfloat textureCoordinateArray[8]; + textureCoordinateArray[0] = 0.0f; + textureCoordinateArray[1] = 0.0f; + textureCoordinateArray[2] = 1.0f; + textureCoordinateArray[3] = 0.0f; + textureCoordinateArray[4] = 1.0f; + textureCoordinateArray[5] = 1.0f; + textureCoordinateArray[6] = 0.0f; + textureCoordinateArray[7] = 1.0f; + + GLfloat vertexCoordinateArray[8]; + vertexCoordinateArray[0] = -1.0f; + vertexCoordinateArray[1] = -1.0f; + vertexCoordinateArray[2] = 1.0f; + vertexCoordinateArray[3] = -1.0f; + vertexCoordinateArray[4] = 1.0f; + vertexCoordinateArray[5] = 1.0f; + vertexCoordinateArray[6] = -1.0f; + vertexCoordinateArray[7] = 1.0f; + + glViewport(0, 0, width, height); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertexCoordinateArray); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, textureCoordinateArray); + + { + static const char *vertexShaderSource = + "attribute highp vec4 vertexCoordsArray; \n" + "attribute highp vec2 textureCoordArray; \n" + "varying highp vec2 textureCoords; \n" + "void main(void) \n" + "{ \n" + " gl_Position = vertexCoordsArray; \n" + " textureCoords = textureCoordArray; \n" + "} \n"; + + static const char *fragmentShaderSource = + "varying highp vec2 textureCoords; \n" + "uniform sampler2D texture; \n" + "void main() \n" + "{ \n" + " gl_FragColor = texture2D(texture, textureCoords); \n" + "} \n"; + + GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); + GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + if (vertexShader == 0 || fragmentShader == 0) { + GLenum error = glGetError(); + qWarning("SharedGraphicsCacheServer::setupShaderPrograms: Failed to create shaders. (GL error: %x)", + error); + return; + } + + glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); + glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); + glCompileShader(vertexShader); + + GLint len = 1; + glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &len); + + char infoLog[2048]; + glGetShaderInfoLog(vertexShader, 2048, NULL, infoLog); + if (qstrlen(infoLog) > 0) { + qWarning("SharedGraphicsCacheServer::setupShaderPrograms, problems compiling vertex shader:\n %s", + infoLog); + //return; + } + + glCompileShader(fragmentShader); + glGetShaderInfoLog(fragmentShader, 2048, NULL, infoLog); + if (qstrlen(infoLog) > 0) { + qWarning("SharedGraphicsCacheServer::setupShaderPrograms, problems compiling fragent shader:\n %s", + infoLog); + //return; + } + + GLuint shaderProgram = glCreateProgram(); + glAttachShader(shaderProgram, vertexShader); + glAttachShader(shaderProgram, fragmentShader); + + glBindAttribLocation(shaderProgram, 0, "vertexCoordsArray"); + glBindAttribLocation(shaderProgram, 1, "textureCoordArray"); + + glLinkProgram(shaderProgram); + glGetProgramInfoLog(shaderProgram, 2048, NULL, infoLog); + if (qstrlen(infoLog) > 0) { + qWarning("SharedGraphicsCacheServer::setupShaderPrograms, problems linking shaders:\n %s", + infoLog); + //return; + } + + glUseProgram(shaderProgram); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + int textureUniformLocation = glGetUniformLocation(shaderProgram, "texture"); + glUniform1i(textureUniformLocation, 0); + } + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + { + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + qWarning("SharedGraphicsCacheServer::readBackBuffer: glDrawArrays reported error 0x%x", + error); + } + } + + uchar *data = new uchar[width * height * 4]; + + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); + + QImage image(width, height, QImage::Format_ARGB32); + quint32 *dest = reinterpret_cast(image.bits()); + for (int i=0; i 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 = it.value().size; + +#if defined(QSGSHAREDDISTANCEFIELDGLYPHCACHE_DEBUG_) + saveTexture(texture.textureId, texture.size.width(), texture.size.height()); +#endif + setGlyphsTexture(it.value().glyphs, texture); + + ++it; + } + } + } + + m_pendingMissingGlyphs.clear(); + m_pendingInvalidatedGlyphs.clear(); + m_pendingReadyGlyphs.clear(); + } +} + +void QSGSharedDistanceFieldGlyphCache::reportItemsAvailable(const QByteArray &cacheId, + void *bufferId, const QSize &bufferSize, + const QVector &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::reportItemsAvailable() called for %s (%d glyphs, bufferSize: %dx%d)", + cacheId.constData(), itemIds.size(), bufferSize.width(), bufferSize.height()); +#endif + + for (int i=0; i= pendingGlyph.bufferSize.height()); + + pendingGlyph.buffer = bufferId; + pendingGlyph.position = positions.at(i); + pendingGlyph.bufferSize = bufferSize; + + m_sharedGraphicsCache->referenceBuffer(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