diff options
Diffstat (limited to 'src/declarative/scenegraph/qsgdistancefieldglyphcache.cpp')
-rw-r--r-- | src/declarative/scenegraph/qsgdistancefieldglyphcache.cpp | 952 |
1 files changed, 952 insertions, 0 deletions
diff --git a/src/declarative/scenegraph/qsgdistancefieldglyphcache.cpp b/src/declarative/scenegraph/qsgdistancefieldglyphcache.cpp new file mode 100644 index 0000000000..50c946a849 --- /dev/null +++ b/src/declarative/scenegraph/qsgdistancefieldglyphcache.cpp @@ -0,0 +1,952 @@ +/**************************************************************************** +** +** 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$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgdistancefieldglyphcache_p.h" + +#include <qmath.h> +#include <private/qtriangulator_p.h> +#include <private/qdeclarativeglobal_p.h> +#include <qglshaderprogram.h> +#include <private/qglengineshadersource_p.h> +#include <private/qsgcontext_p.h> +#include <private/qrawfont_p.h> +#include <qglfunctions.h> +#include <qglyphrun.h> +#include <qrawfont.h> + +QT_BEGIN_NAMESPACE + +#define QT_DISTANCEFIELD_DEFAULT_BASEFONTSIZE 54 +#define QT_DISTANCEFIELD_DEFAULT_TILESIZE 64 +#define QT_DISTANCEFIELD_DEFAULT_SCALE 16 +#define QT_DISTANCEFIELD_DEFAULT_RADIUS 80 +#define QT_DISTANCEFIELD_HIGHGLYPHCOUNT 2000 + +#define QT_DISTANCEFIELD_BASEFONTSIZE \ + (m_textureData->doubleGlyphResolution ? QT_DISTANCEFIELD_DEFAULT_BASEFONTSIZE * 2 : \ + QT_DISTANCEFIELD_DEFAULT_BASEFONTSIZE) +#define QT_DISTANCEFIELD_TILESIZE \ + (m_textureData->doubleGlyphResolution ? QT_DISTANCEFIELD_DEFAULT_TILESIZE * 2 : \ + QT_DISTANCEFIELD_DEFAULT_TILESIZE) +#define QT_DISTANCEFIELD_SCALE \ + (m_textureData->doubleGlyphResolution ? QT_DISTANCEFIELD_DEFAULT_SCALE / 2 : \ + QT_DISTANCEFIELD_DEFAULT_SCALE) +#define QT_DISTANCEFIELD_RADIUS \ + (m_textureData->doubleGlyphResolution ? QT_DISTANCEFIELD_DEFAULT_RADIUS / 2 : \ + QT_DISTANCEFIELD_DEFAULT_RADIUS) + +static inline int qt_next_power_of_two(int v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + ++v; + return v; +} + +struct DFPoint +{ + float x, y; +}; + +struct DFVertex +{ + DFPoint p; + float d; +}; + +static void drawRectangle(float *bits, int width, int height, const DFVertex *v1, const DFVertex *v2, const DFVertex *v3, const DFVertex *v4) +{ + float minY = qMin(qMin(v1->p.y, v2->p.y), qMin(v3->p.y, v4->p.y)); + if (v2->p.y == minY) { + const DFVertex *tmp = v1; + v1 = v2; + v2 = v3; + v3 = v4; + v4 = tmp; + } else if (v3->p.y == minY) { + const DFVertex *tmp1 = v1; + const DFVertex *tmp2 = v2; + v1 = v3; + v2 = v4; + v3 = tmp1; + v4 = tmp2; + } else if (v4->p.y == minY) { + const DFVertex *tmp = v4; + v4 = v3; + v3 = v2; + v2 = v1; + v1 = tmp; + } + + /* + v1 + / \ + v4 v2 + \ / + v3 + */ + + int fromY = qMax(0, qCeil(v1->p.y)); + int midY1 = qMin(height, qCeil(qMin(v2->p.y, v4->p.y))); + int midY2 = qMin(height, qCeil(qMax(v2->p.y, v4->p.y))); + int toY = qMin(height, qCeil(v3->p.y)); + + if (toY <= fromY) + return; + + bits += width * fromY; + int y = fromY; + + float leftDx = (v4->p.x - v1->p.x) / (v4->p.y - v1->p.y); + float leftDd = (v4->d - v1->d) / (v4->p.y - v1->p.y); + float leftX = v1->p.x + (fromY - v1->p.y) * leftDx; + float leftD = v1->d + (fromY - v1->p.y) * leftDd; + + float rightDx = (v2->p.x - v1->p.x) / (v2->p.y - v1->p.y); + float rightDd = (v2->d - v1->d) / (v2->p.y - v1->p.y); + float rightX = v1->p.x + (fromY - v1->p.y) * rightDx; + float rightD = v1->d + (fromY - v1->p.y) * rightDd; + + float dd = ((v2->d - v1->d) * (v3->p.y - v1->p.y) - (v2->p.y - v1->p.y) * (v3->d - v1->d)) + / ((v2->p.x - v1->p.x) * (v3->p.y - v1->p.y) - (v2->p.y - v1->p.y) * (v3->p.x - v1->p.x)); + + for (; y < midY1; ++y, leftX += leftDx, leftD += leftDd, rightX += rightDx, rightD += rightDd, bits += width) { + int fromX = qMax(0, qCeil(leftX)); + int toX = qMin(width, qCeil(rightX)); + if (toX <= fromX) + continue; + float d = leftD + (fromX - leftX) * dd; + for (int x = fromX; x < toX; ++x, d += dd) { + if (abs(d) < abs(bits[x])) + bits[x] = d; + } + } + + if (midY1 == toY) + return; + + if (v2->p.y > v4->p.y) { + // Long right edge. + leftDx = (v3->p.x - v4->p.x) / (v3->p.y - v4->p.y); + leftDd = (v3->d - v4->d) / (v3->p.y - v4->p.y); + leftX = v4->p.x + (midY1 - v4->p.y) * leftDx; + leftD = v4->d + (midY1 - v4->p.y) * leftDd; + } else { + // Long left edge. + rightDx = (v3->p.x - v2->p.x) / (v3->p.y - v2->p.y); + rightDd = (v3->d - v2->d) / (v3->p.y - v2->p.y); + rightX = v2->p.x + (midY1 - v2->p.y) * rightDx; + rightD = v2->d + (midY1 - v2->p.y) * rightDd; + } + + for (; y < midY2; ++y, leftX += leftDx, leftD += leftDd, rightX += rightDx, rightD += rightDd, bits += width) { + int fromX = qMax(0, qCeil(leftX)); + int toX = qMin(width, qCeil(rightX)); + if (toX <= fromX) + continue; + float d = leftD + (fromX - leftX) * dd; + for (int x = fromX; x < toX; ++x, d += dd) { + if (abs(d) < abs(bits[x])) + bits[x] = d; + } + } + + if (midY2 == toY) + return; + + if (v2->p.y > v4->p.y) { + // Long left edge. + rightDx = (v3->p.x - v2->p.x) / (v3->p.y - v2->p.y); + rightDd = (v3->d - v2->d) / (v3->p.y - v2->p.y); + rightX = v2->p.x + (midY2 - v2->p.y) * rightDx; + rightD = v2->d + (midY2 - v2->p.y) * rightDd; + } else { + // Long right edge. + leftDx = (v3->p.x - v4->p.x) / (v3->p.y - v4->p.y); + leftDd = (v3->d - v4->d) / (v3->p.y - v4->p.y); + leftX = v4->p.x + (midY2 - v4->p.y) * leftDx; + leftD = v4->d + (midY2 - v4->p.y) * leftDd; + } + + for (; y < toY; ++y, leftX += leftDx, leftD += leftDd, rightX += rightDx, rightD += rightDd, bits += width) { + int fromX = qMax(0, qCeil(leftX)); + int toX = qMin(width, qCeil(rightX)); + if (toX <= fromX) + continue; + float d = leftD + (fromX - leftX) * dd; + for (int x = fromX; x < toX; ++x, d += dd) { + if (abs(d) < abs(bits[x])) + bits[x] = d; + } + } +} + +static void drawTriangle(float *bits, int width, int height, const DFVertex *v1, const DFVertex *v2, const DFVertex *v3) +{ + float minY = qMin(qMin(v1->p.y, v2->p.y), v3->p.y); + if (v2->p.y == minY) { + const DFVertex *tmp = v1; + v1 = v2; + v2 = v3; + v3 = tmp; + } else if (v3->p.y == minY) { + const DFVertex *tmp = v3; + v3 = v2; + v2 = v1; + v1 = tmp; + } + + /* + v1 + / \ + v3--v2 + */ + + int fromY = qMax(0, qCeil(v1->p.y)); + int midY = qMin(height, qCeil(qMin(v2->p.y, v3->p.y))); + int toY = qMin(height, qCeil(qMax(v2->p.y, v3->p.y))); + + if (toY <= fromY) + return; + + float leftDx = (v3->p.x - v1->p.x) / (v3->p.y - v1->p.y); + float leftDd = (v3->d - v1->d) / (v3->p.y - v1->p.y); + float leftX = v1->p.x + (fromY - v1->p.y) * leftDx; + float leftD = v1->d + (fromY - v1->p.y) * leftDd; + + float rightDx = (v2->p.x - v1->p.x) / (v2->p.y - v1->p.y); + float rightDd = (v2->d - v1->d) / (v2->p.y - v1->p.y); + float rightX = v1->p.x + (fromY - v1->p.y) * rightDx; + float rightD = v1->d + (fromY - v1->p.y) * rightDd; + + float dd = ((v2->d - v1->d) * (v3->p.y - v1->p.y) - (v2->p.y - v1->p.y) * (v3->d - v1->d)) + / ((v2->p.x - v1->p.x) * (v3->p.y - v1->p.y) - (v2->p.y - v1->p.y) * (v3->p.x - v1->p.x)); + + bits += width * fromY; + int y = fromY; + for (; y < midY; ++y, leftX += leftDx, leftD += leftDd, rightX += rightDx, rightD += rightDd, bits += width) { + int fromX = qMax(0, qCeil(leftX)); + int toX = qMin(width, qCeil(rightX)); + if (toX <= fromX) + continue; + float d = leftD + (fromX - leftX) * dd; + for (int x = fromX; x < toX; ++x, d += dd) { + if (abs(d) < abs(bits[x])) + bits[x] = d; + } + } + + if (midY == toY) + return; + + if (v2->p.y > v3->p.y) { + // Long right edge. + leftDx = (v2->p.x - v3->p.x) / (v2->p.y - v3->p.y); + leftDd = (v2->d - v3->d) / (v2->p.y - v3->p.y); + leftX = v3->p.x + (midY - v3->p.y) * leftDx; + leftD = v3->d + (midY - v3->p.y) * leftDd; + } else { + // Long left edge. + rightDx = (v3->p.x - v2->p.x) / (v3->p.y - v2->p.y); + rightDd = (v3->d - v2->d) / (v3->p.y - v2->p.y); + rightX = v2->p.x + (midY - v2->p.y) * rightDx; + rightD = v2->d + (midY - v2->p.y) * rightDd; + } + + for (; y < toY; ++y, leftX += leftDx, leftD += leftDd, rightX += rightDx, rightD += rightDd, bits += width) { + int fromX = qMax(0, qCeil(leftX)); + int toX = qMin(width, qCeil(rightX)); + if (toX <= fromX) + continue; + float d = leftD + (fromX - leftX) * dd; + for (int x = fromX; x < toX; ++x, d += dd) { + if (abs(d) < abs(bits[x])) + bits[x] = d; + } + } +} + +static QImage makeDistanceField(int imgSize, const QPainterPath &path, int dfScale, float offs) +{ + QImage image(imgSize, imgSize, QImage::Format_ARGB32_Premultiplied); + + if (path.isEmpty()) { + image.fill(0); + return image; + } + + QPolylineSet polys = qPolyline(path); + + union Pacific { + float value; + QRgb color; + }; + Pacific interior; + Pacific exterior; + interior.value = 127; + exterior.value = -127; + + image.fill(exterior.color); + + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.translate(offs, offs); + p.scale(1 / qreal(dfScale), 1 / qreal(dfScale)); + p.fillPath(path, QColor::fromRgba(interior.color)); + p.end(); + + float *bits = (float *)image.bits(); + const float angleStep = 15 * 3.141592653589793238f / 180; + const DFPoint rotation = { cos(angleStep), sin(angleStep) }; + + bool isShortData = polys.indices.type() == QVertexIndexVector::UnsignedShort; + const void *indices = polys.indices.data(); + int index = 0; + QVector<DFPoint> normals; + QVector<DFVertex> vertices; + normals.reserve(polys.vertices.count()); + vertices.reserve(polys.vertices.count()); + + while (index < polys.indices.size()) { + normals.clear(); + vertices.clear(); + + // Find end of polygon. + int end = index; + if (isShortData) { + while (((quint16 *)indices)[end] != quint16(-1)) + ++end; + } else { + while (((quint32 *)indices)[end] != quint32(-1)) + ++end; + } + + // Calculate vertex normals. + for (int next = index, prev = end - 1; next < end; prev = next++) { + quint32 fromVertexIndex = isShortData ? (quint32)((quint16 *)indices)[prev] : ((quint32 *)indices)[prev]; + quint32 toVertexIndex = isShortData ? (quint32)((quint16 *)indices)[next] : ((quint32 *)indices)[next]; + const qreal *from = &polys.vertices.at(fromVertexIndex * 2); + const qreal *to = &polys.vertices.at(toVertexIndex * 2); + DFPoint n; + n.x = float(to[1] - from[1]); + n.y = float(from[0] - to[0]); + if (n.x == 0 && n.y == 0) + continue; + float scale = offs / sqrt(n.x * n.x + n.y * n.y); + n.x *= scale; + n.y *= scale; + normals.append(n); + + DFVertex v; + v.p.x = float(to[0] / dfScale) + offs - 0.5f; + v.p.y = float(to[1] / dfScale) + offs - 0.5f; + v.d = 0.0f; + vertices.append(v); + } + + QVector<bool> isConvex(normals.count()); + for (int next = 0, prev = normals.count() - 1; next < normals.count(); prev = next++) + isConvex[prev] = (normals.at(prev).x * normals.at(next).y - normals.at(prev).y * normals.at(next).x > 0); + + // Draw quads. + for (int next = 0, prev = normals.count() - 1; next < normals.count(); prev = next++) { + DFPoint n = normals.at(next); + DFVertex intPrev = vertices.at(prev); + DFVertex extPrev = vertices.at(prev); + DFVertex intNext = vertices.at(next); + DFVertex extNext = vertices.at(next); + + extPrev.p.x += n.x; + extPrev.p.y += n.y; + intPrev.p.x -= n.x; + intPrev.p.y -= n.y; + extNext.p.x += n.x; + extNext.p.y += n.y; + intNext.p.x -= n.x; + intNext.p.y -= n.y; + extPrev.d = 127; + extNext.d = 127; + intPrev.d = -127; + intNext.d = -127; + + drawRectangle(bits, image.width(), image.height(), + &vertices.at(prev), &extPrev, &extNext, &vertices.at(next)); + + drawRectangle(bits, image.width(), image.height(), + &intPrev, &vertices.at(prev), &vertices.at(next), &intNext); + + if (isConvex.at(prev)) { + DFVertex v = extPrev; + for (;;) { + DFPoint rn = { n.x * rotation.x + n.y * rotation.y, + n.y * rotation.x - n.x * rotation.y }; + n = rn; + if (n.x * normals.at(prev).y - n.y * normals.at(prev).x >= -0.001) { + v.p.x = vertices.at(prev).p.x + normals.at(prev).x; + v.p.y = vertices.at(prev).p.y + normals.at(prev).y; + drawTriangle(bits, image.width(), image.height(), &vertices.at(prev), &v, &extPrev); + break; + } + + v.p.x = vertices.at(prev).p.x + n.x; + v.p.y = vertices.at(prev).p.y + n.y; + drawTriangle(bits, image.width(), image.height(), &vertices.at(prev), &v, &extPrev); + extPrev = v; + } + } else { + DFVertex v = intPrev; + for (;;) { + DFPoint rn = { n.x * rotation.x - n.y * rotation.y, + n.y * rotation.x + n.x * rotation.y }; + n = rn; + if (n.x * normals.at(prev).y - n.y * normals.at(prev).x <= 0.001) { + v.p.x = vertices.at(prev).p.x - normals.at(prev).x; + v.p.y = vertices.at(prev).p.y - normals.at(prev).y; + drawTriangle(bits, image.width(), image.height(), &vertices.at(prev), &intPrev, &v); + break; + } + + v.p.x = vertices.at(prev).p.x - n.x; + v.p.y = vertices.at(prev).p.y - n.y; + drawTriangle(bits, image.width(), image.height(), &vertices.at(prev), &intPrev, &v); + intPrev = v; + } + } + } + index = end + 1; + } + + for (int y = 0; y < image.height(); ++y) { + QRgb *iLine = (QRgb *)image.scanLine(y); + float *fLine = (float *)iLine; + for (int x = 0; x < image.width(); ++x) + iLine[x] = QRgb(fLine[x] + 127.5) << 24; + } + + return image; +} + +static void convert_to_Format_Alpha(QImage *image) +{ + const int width = image->width(); + const int height = image->height(); + uchar *data = image->bits(); + + for (int i = 0; i < height; ++i) { + uchar *o = data + i * width; + for (int x = 0; x < width; ++x) + o[x] = (uchar)qAlpha(image->pixel(x, i)); + } +} + +static bool fontHasNarrowOutlines(const QRawFont &f) +{ + QRawFont font = f; + font.setPixelSize(QT_DISTANCEFIELD_DEFAULT_BASEFONTSIZE); + Q_ASSERT(font.isValid()); + + QVector<quint32> glyphIndices = font.glyphIndexesForString(QLatin1String("O")); + if (glyphIndices.size() < 1) + return false; + + QImage im = font.alphaMapForGlyph(glyphIndices.at(0), QRawFont::PixelAntialiasing); + if (im.isNull()) + return false; + + int minHThick = 999; + int minVThick = 999; + + int thick = 0; + bool in = false; + int y = (im.height() + 1) / 2; + for (int x = 0; x < im.width(); ++x) { + int a = qAlpha(im.pixel(x, y)); + if (a > 127) { + in = true; + ++thick; + } else if (in) { + in = false; + minHThick = qMin(minHThick, thick); + thick = 0; + } + } + + thick = 0; + in = false; + int x = (im.width() + 1) / 2; + for (int y = 0; y < im.height(); ++y) { + int a = qAlpha(im.pixel(x, y)); + if (a > 127) { + in = true; + ++thick; + } else if (in) { + in = false; + minVThick = qMin(minVThick, thick); + thick = 0; + } + } + + return minHThick == 1 || minVThick == 1; +} + +DEFINE_BOOL_CONFIG_OPTION(disableDistanceField, QML_DISABLE_DISTANCEFIELD) + +QHash<QPair<const QGLContext *, QFontEngine *>, QSGDistanceFieldGlyphCache *> QSGDistanceFieldGlyphCache::m_caches; +QHash<QFontEngine *, QGLContextGroupResource<QSGDistanceFieldGlyphCache::DistanceFieldTextureData> > QSGDistanceFieldGlyphCache::m_textures_data; + +QSGDistanceFieldGlyphCache *QSGDistanceFieldGlyphCache::get(const QGLContext *ctx, const QRawFont &font) +{ + QRawFontPrivate *fontD = QRawFontPrivate::get(font); + QPair<const QGLContext *, QFontEngine *> key(ctx, fontD->fontEngine); + QHash<QPair<const QGLContext *, QFontEngine *>, QSGDistanceFieldGlyphCache *>::iterator atlas = m_caches.find(key); + if (atlas == m_caches.end()) + atlas = m_caches.insert(key, new QSGDistanceFieldGlyphCache(ctx, font)); + + return atlas.value(); +} + +QSGDistanceFieldGlyphCache::DistanceFieldTextureData *QSGDistanceFieldGlyphCache::textureData() +{ + return m_textures_data[QRawFontPrivate::get(m_font)->fontEngine].value(ctx); +} + +QSGDistanceFieldGlyphCache::QSGDistanceFieldGlyphCache(const QGLContext *c, const QRawFont &font) + : QObject() + , m_maxTextureSize(0) + , ctx(c) + , m_blitProgram(0) +{ + Q_ASSERT(font.isValid()); + m_font = font; + + m_textureData = textureData(); + + QRawFontPrivate *fontD = QRawFontPrivate::get(m_font); + m_glyphCount = fontD->fontEngine->glyphCount(); + + m_textureData->doubleGlyphResolution = fontHasNarrowOutlines(font) && m_glyphCount < QT_DISTANCEFIELD_HIGHGLYPHCOUNT; + + m_referenceFont = m_font; + m_referenceFont.setPixelSize(QT_DISTANCEFIELD_BASEFONTSIZE); + Q_ASSERT(m_referenceFont.isValid()); + + m_vertexCoordinateArray[0] = -1.0f; + m_vertexCoordinateArray[1] = -1.0f; + m_vertexCoordinateArray[2] = 1.0f; + m_vertexCoordinateArray[3] = -1.0f; + m_vertexCoordinateArray[4] = 1.0f; + m_vertexCoordinateArray[5] = 1.0f; + m_vertexCoordinateArray[6] = -1.0f; + m_vertexCoordinateArray[7] = 1.0f; + + m_textureCoordinateArray[0] = 0.0f; + m_textureCoordinateArray[1] = 0.0f; + m_textureCoordinateArray[2] = 1.0f; + m_textureCoordinateArray[3] = 0.0f; + m_textureCoordinateArray[4] = 1.0f; + m_textureCoordinateArray[5] = 1.0f; + m_textureCoordinateArray[6] = 0.0f; + m_textureCoordinateArray[7] = 1.0f; + + connect(QGLSignalProxy::instance(), SIGNAL(aboutToDestroyContext(const QGLContext*)), + this, SLOT(onContextDestroyed(const QGLContext*))); +} + +QSGDistanceFieldGlyphCache::~QSGDistanceFieldGlyphCache() +{ + delete m_blitProgram; +} + +void QSGDistanceFieldGlyphCache::onContextDestroyed(const QGLContext *context) +{ + if (context != ctx) + return; + + QRawFontPrivate *fontD = QRawFontPrivate::get(m_font); + QPair<const QGLContext *, QFontEngine *> key(context, fontD->fontEngine); + m_caches.remove(key); + deleteLater(); +} + +GLuint QSGDistanceFieldGlyphCache::texture() +{ + return m_textureData->texture; +} + +QSize QSGDistanceFieldGlyphCache::textureSize() const +{ + return m_textureData->size; +} + +int QSGDistanceFieldGlyphCache::maxTextureSize() const +{ + if (!m_maxTextureSize) + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize); + return m_maxTextureSize; +} + +QSGDistanceFieldGlyphCache::Metrics QSGDistanceFieldGlyphCache::glyphMetrics(glyph_t glyph) +{ + QHash<glyph_t, Metrics>::iterator metric = m_metrics.find(glyph); + if (metric == m_metrics.end()) { + QPainterPath path = m_font.pathForGlyph(glyph); + QRectF br = path.boundingRect(); + + Metrics m; + m.width = br.width(); + m.height = br.height(); + m.baselineX = br.x(); + m.baselineY = -br.y(); + + metric = m_metrics.insert(glyph, m); + } + + return metric.value(); +} + +QSGDistanceFieldGlyphCache::TexCoord QSGDistanceFieldGlyphCache::glyphTexCoord(glyph_t glyph) +{ + return m_textureData->texCoords.value(glyph); +} + +QImage QSGDistanceFieldGlyphCache::renderDistanceFieldGlyph(glyph_t glyph) const +{ + QRawFont renderFont = m_font; + renderFont.setPixelSize(QT_DISTANCEFIELD_BASEFONTSIZE * QT_DISTANCEFIELD_SCALE); + + QPainterPath path = renderFont.pathForGlyph(glyph); + path.translate(-path.boundingRect().topLeft()); + path.setFillRule(Qt::WindingFill); + + QImage im = makeDistanceField(QT_DISTANCEFIELD_TILESIZE, + path, + QT_DISTANCEFIELD_SCALE, + QT_DISTANCEFIELD_RADIUS / qreal(QT_DISTANCEFIELD_SCALE)); + return im; +} + +qreal QSGDistanceFieldGlyphCache::fontScale() const +{ + return qreal(m_font.pixelSize()) / QT_DISTANCEFIELD_BASEFONTSIZE; +} + +int QSGDistanceFieldGlyphCache::distanceFieldRadius() const +{ + return QT_DISTANCEFIELD_DEFAULT_RADIUS / QT_DISTANCEFIELD_SCALE; +} + +void QSGDistanceFieldGlyphCache::populate(int count, const glyph_t *glyphs) +{ + // Avoid useless and costly glyph re-generation + if (cacheIsFull() && !m_textureData->unusedGlyphs.isEmpty()) { + for (int i = 0; i < count; ++i) { + glyph_t glyphIndex = glyphs[i]; + if (m_textureData->texCoords.contains(glyphIndex) && m_textureData->unusedGlyphs.contains(glyphIndex)) + m_textureData->unusedGlyphs.remove(glyphIndex); + } + } + + for (int i = 0; i < count; ++i) { + glyph_t glyphIndex = glyphs[i]; + if ((int) glyphIndex >= glyphCount()) { + qWarning("Warning: distance-field glyph is not available with index %d", glyphIndex); + continue; + } + + if (++m_textureData->glyphRefCount[glyphIndex] == 1) + m_textureData->unusedGlyphs.remove(glyphIndex); + + if (m_textureData->texCoords.contains(glyphIndex) + || (cacheIsFull() && m_textureData->unusedGlyphs.isEmpty())) + continue; + + QPainterPath path = m_referenceFont.pathForGlyph(glyphIndex); + if (path.isEmpty()) { + m_textureData->texCoords.insert(glyphIndex, TexCoord()); + continue; + } + QRectF br = path.boundingRect(); + + TexCoord c; + c.xMargin = QT_DISTANCEFIELD_RADIUS / qreal(QT_DISTANCEFIELD_SCALE); + c.yMargin = QT_DISTANCEFIELD_RADIUS / qreal(QT_DISTANCEFIELD_SCALE); + c.x = m_textureData->currX; + c.y = m_textureData->currY; + c.width = br.width(); + c.height = br.height(); + + if (!cacheIsFull()) { + m_textureData->currX += QT_DISTANCEFIELD_TILESIZE; + if (m_textureData->currX >= maxTextureSize()) { + m_textureData->currX = 0; + m_textureData->currY += QT_DISTANCEFIELD_TILESIZE; + } + } else { + // Recycle glyphs + if (!m_textureData->unusedGlyphs.isEmpty()) { + glyph_t unusedGlyph = *m_textureData->unusedGlyphs.constBegin(); + TexCoord unusedCoord = glyphTexCoord(unusedGlyph); + c.x = unusedCoord.x; + c.y = unusedCoord.y; + m_textureData->unusedGlyphs.remove(unusedGlyph); + m_textureData->texCoords.remove(unusedGlyph); + } + } + + if (c.y < maxTextureSize()) { + m_textureData->texCoords.insert(glyphIndex, c); + m_textureData->pendingGlyphs.add(glyphIndex); + } + } +} + +void QSGDistanceFieldGlyphCache::derefGlyphs(int count, const glyph_t *glyphs) +{ + for (int i = 0; i < count; ++i) + if (--m_textureData->glyphRefCount[glyphs[i]] == 0 && !glyphTexCoord(glyphs[i]).isNull()) + m_textureData->unusedGlyphs.insert(glyphs[i]); +} + +void QSGDistanceFieldGlyphCache::createTexture(int width, int height) +{ + if (ctx->d_ptr->workaround_brokenFBOReadBack && m_textureData->image.isNull()) + m_textureData->image = QImage(width, height, QImage::Format_ARGB32_Premultiplied); + + while (glGetError() != GL_NO_ERROR) { } + + glGenTextures(1, &m_textureData->texture); + glBindTexture(GL_TEXTURE_2D, m_textureData->texture); + + QVarLengthArray<uchar> data(width * height); + for (int i = 0; i < data.size(); ++i) + data[i] = 0; + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, &data[0]); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_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); + + m_textureData->size = QSize(width, height); + + GLuint error = glGetError(); + if (error != GL_NO_ERROR) { + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &m_textureData->texture); + m_textureData->texture = 0; + } + +} + +void QSGDistanceFieldGlyphCache::resizeTexture(int width, int height) +{ + int oldWidth = m_textureData->size.width(); + int oldHeight = m_textureData->size.height(); + if (width == oldWidth && height == oldHeight) + return; + + GLuint oldTexture = m_textureData->texture; + createTexture(width, height); + + if (!oldTexture) + return; + + if (ctx->d_ptr->workaround_brokenFBOReadBack) { + m_textureData->image = m_textureData->image.copy(0, 0, width, height); + QImage copy = m_textureData->image.copy(0, 0, oldWidth, oldHeight); + convert_to_Format_Alpha(©); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, oldWidth, oldHeight, GL_ALPHA, GL_UNSIGNED_BYTE, copy.constBits()); + glDeleteTextures(1, &oldTexture); + return; + } + + if (!m_textureData->fbo) + ctx->functions()->glGenFramebuffers(1, &m_textureData->fbo); + ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER_EXT, m_textureData->fbo); + + GLuint tmp_texture; + glGenTextures(1, &tmp_texture); + glBindTexture(GL_TEXTURE_2D, tmp_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, oldWidth, oldHeight, 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); + ctx->functions()->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, tmp_texture, 0); + + ctx->functions()->glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, oldTexture); + + // save current render states + GLboolean stencilTestEnabled; + GLboolean depthTestEnabled; + GLboolean scissorTestEnabled; + GLboolean blendEnabled; + GLint viewport[4]; + glGetBooleanv(GL_STENCIL_TEST, &stencilTestEnabled); + glGetBooleanv(GL_DEPTH_TEST, &depthTestEnabled); + glGetBooleanv(GL_SCISSOR_TEST, &scissorTestEnabled); + glGetBooleanv(GL_BLEND, &blendEnabled); + glGetIntegerv(GL_VIEWPORT, &viewport[0]); + + glDisable(GL_STENCIL_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_BLEND); + + glViewport(0, 0, oldWidth, oldHeight); + + if (m_blitProgram == 0) { + m_blitProgram = new QGLShaderProgram; + + { + QString source; + source.append(QLatin1String(qglslMainWithTexCoordsVertexShader)); + source.append(QLatin1String(qglslUntransformedPositionVertexShader)); + + QGLShader *vertexShader = new QGLShader(QGLShader::Vertex, m_blitProgram); + vertexShader->compileSourceCode(source); + + m_blitProgram->addShader(vertexShader); + } + + { + QString source; + source.append(QLatin1String(qglslMainFragmentShader)); + source.append(QLatin1String(qglslImageSrcFragmentShader)); + + QGLShader *fragmentShader = new QGLShader(QGLShader::Fragment, m_blitProgram); + fragmentShader->compileSourceCode(source); + + m_blitProgram->addShader(fragmentShader); + } + + m_blitProgram->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR); + m_blitProgram->bindAttributeLocation("textureCoordArray", QT_TEXTURE_COORDS_ATTR); + + m_blitProgram->link(); + } + + ctx->functions()->glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, m_vertexCoordinateArray); + ctx->functions()->glVertexAttribPointer(QT_TEXTURE_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, m_textureCoordinateArray); + + m_blitProgram->bind(); + m_blitProgram->enableAttributeArray(int(QT_VERTEX_COORDS_ATTR)); + m_blitProgram->enableAttributeArray(int(QT_TEXTURE_COORDS_ATTR)); + m_blitProgram->disableAttributeArray(int(QT_OPACITY_ATTR)); + m_blitProgram->setUniformValue("imageTexture", GLuint(0)); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + glBindTexture(GL_TEXTURE_2D, m_textureData->texture); + + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, oldWidth, oldHeight); + + ctx->functions()->glFramebufferRenderbuffer(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_RENDERBUFFER_EXT, 0); + glDeleteTextures(1, &tmp_texture); + glDeleteTextures(1, &oldTexture); + + ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); + + // restore render states + if (stencilTestEnabled) + glEnable(GL_STENCIL_TEST); + if (depthTestEnabled) + glEnable(GL_DEPTH_TEST); + if (scissorTestEnabled) + glEnable(GL_SCISSOR_TEST); + if (blendEnabled) + glEnable(GL_BLEND); + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); +} + +void QSGDistanceFieldGlyphCache::updateCache() +{ + if (m_textureData->pendingGlyphs.isEmpty()) + return; + + int requiredWidth = m_textureData->currY == 0 ? m_textureData->currX : maxTextureSize(); + int requiredHeight = qMin(maxTextureSize(), m_textureData->currY + QT_DISTANCEFIELD_TILESIZE); + + resizeTexture((requiredWidth), (requiredHeight)); + glBindTexture(GL_TEXTURE_2D, m_textureData->texture); + + for (int i = 0; i < m_textureData->pendingGlyphs.size(); ++i) { + glyph_t glyphIndex = m_textureData->pendingGlyphs.at(i); + QImage glyph = renderDistanceFieldGlyph(glyphIndex); + TexCoord c = m_textureData->texCoords.value(glyphIndex); + + if (ctx->d_ptr->workaround_brokenFBOReadBack) { + QPainter p(&m_textureData->image); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.drawImage(c.x, c.y, glyph); + p.end(); + } + + convert_to_Format_Alpha(&glyph); + glTexSubImage2D(GL_TEXTURE_2D, 0, c.x, c.y, glyph.width(), glyph.height(), GL_ALPHA, GL_UNSIGNED_BYTE, glyph.constBits()); + } + m_textureData->pendingGlyphs.reset(); +} + +bool QSGDistanceFieldGlyphCache::useWorkaroundBrokenFBOReadback() const +{ + return ctx->d_ptr->workaround_brokenFBOReadBack; +} + +bool QSGDistanceFieldGlyphCache::distanceFieldEnabled() +{ + return !disableDistanceField(); +} + +int QSGDistanceFieldGlyphCache::glyphCount() const +{ + return m_glyphCount; +} + +QT_END_NAMESPACE |