diff options
Diffstat (limited to 'src/opengl/qopenglgradientcache.cpp')
-rw-r--r-- | src/opengl/qopenglgradientcache.cpp | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/src/opengl/qopenglgradientcache.cpp b/src/opengl/qopenglgradientcache.cpp new file mode 100644 index 0000000000..7a932a19bb --- /dev/null +++ b/src/opengl/qopenglgradientcache.cpp @@ -0,0 +1,276 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtOpenGL 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 "qopenglgradientcache_p.h" +#include <private/qdrawhelper_p.h> +#include <private/qopenglcontext_p.h> +#include <private/qrgba64_p.h> +#include <QtCore/qmutex.h> +#include <QtCore/qrandom.h> +#include "qopenglfunctions.h" +#include <private/qopenglextensions_p.h> + +#ifndef GL_RGBA16 +#define GL_RGBA16 0x805B +#endif + +QT_BEGIN_NAMESPACE + +class QOpenGL2GradientCacheWrapper +{ +public: + QOpenGL2GradientCache *cacheForContext(QOpenGLContext *context) { + QMutexLocker lock(&m_mutex); + return m_resource.value<QOpenGL2GradientCache>(context); + } + +private: + QOpenGLMultiGroupSharedResource m_resource; + QMutex m_mutex; +}; + +Q_GLOBAL_STATIC(QOpenGL2GradientCacheWrapper, qt_gradient_caches) + +QOpenGL2GradientCache::QOpenGL2GradientCache(QOpenGLContext *ctx) + : QOpenGLSharedResource(ctx->shareGroup()) +{ +} + +QOpenGL2GradientCache::~QOpenGL2GradientCache() +{ + cache.clear(); +} + +QOpenGL2GradientCache *QOpenGL2GradientCache::cacheForContext(QOpenGLContext *context) +{ + return qt_gradient_caches()->cacheForContext(context); +} + +void QOpenGL2GradientCache::invalidateResource() +{ + QMutexLocker lock(&m_mutex); + cache.clear(); +} + +void QOpenGL2GradientCache::freeResource(QOpenGLContext *) +{ + cleanCache(); +} + +void QOpenGL2GradientCache::cleanCache() +{ + QMutexLocker lock(&m_mutex); + QOpenGLGradientColorTableHash::const_iterator it = cache.constBegin(); + QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); + for (; it != cache.constEnd(); ++it) { + const CacheInfo &cache_info = it.value(); + funcs->glDeleteTextures(1, &cache_info.texId); + } + cache.clear(); +} + +GLuint QOpenGL2GradientCache::getBuffer(const QGradient &gradient, qreal opacity) +{ + quint64 hash_val = 0; + + const QGradientStops stops = gradient.stops(); + for (int i = 0; i < stops.size() && i <= 2; i++) + hash_val += stops[i].second.rgba(); + + const QMutexLocker lock(&m_mutex); + QOpenGLGradientColorTableHash::const_iterator it = cache.constFind(hash_val); + + if (it == cache.constEnd()) + return addCacheElement(hash_val, gradient, opacity); + else { + do { + const CacheInfo &cache_info = it.value(); + if (cache_info.stops == stops && cache_info.opacity == opacity + && cache_info.interpolationMode == gradient.interpolationMode()) + { + return cache_info.texId; + } + ++it; + } while (it != cache.constEnd() && it.key() == hash_val); + // an exact match for these stops and opacity was not found, create new cache + return addCacheElement(hash_val, gradient, opacity); + } +} + + +GLuint QOpenGL2GradientCache::addCacheElement(quint64 hash_val, const QGradient &gradient, qreal opacity) +{ + QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); + if (cache.size() == maxCacheSize()) { + int elem_to_remove = QRandomGenerator::global()->bounded(maxCacheSize()); + quint64 key = cache.keys()[elem_to_remove]; + + // need to call glDeleteTextures on each removed cache entry: + QOpenGLGradientColorTableHash::const_iterator it = cache.constFind(key); + do { + funcs->glDeleteTextures(1, &it.value().texId); + } while (++it != cache.constEnd() && it.key() == key); + cache.remove(key); // may remove more than 1, but OK + } + + CacheInfo cache_entry(gradient.stops(), opacity, gradient.interpolationMode()); + funcs->glGenTextures(1, &cache_entry.texId); + funcs->glBindTexture(GL_TEXTURE_2D, cache_entry.texId); + if (static_cast<QOpenGLExtensions *>(funcs)->hasOpenGLExtension(QOpenGLExtensions::Sized16Formats)) { + QRgba64 buffer[1024]; + generateGradientColorTable(gradient, buffer, paletteSize(), opacity); + funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, paletteSize(), 1, + 0, GL_RGBA, GL_UNSIGNED_SHORT, buffer); + } else { + uint buffer[1024]; + generateGradientColorTable(gradient, buffer, paletteSize(), opacity); + funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, paletteSize(), 1, + 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + } + return cache.insert(hash_val, cache_entry).value().texId; +} + + +//TODO: Let GL generate the texture using an FBO +void QOpenGL2GradientCache::generateGradientColorTable(const QGradient& gradient, QRgba64 *colorTable, int size, qreal opacity) const +{ + int pos = 0; + const QGradientStops s = gradient.stops(); + + bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation); + + uint alpha = qRound(opacity * 256); + QRgba64 current_color = combineAlpha256(s[0].second.rgba64(), alpha); + qreal incr = 1.0 / qreal(size); + qreal fpos = 1.5 * incr; + colorTable[pos++] = qPremultiply(current_color); + + while (fpos <= s.first().first) { + colorTable[pos] = colorTable[pos - 1]; + pos++; + fpos += incr; + } + + if (colorInterpolation) + current_color = qPremultiply(current_color); + + const int sLast = s.size() - 1; + for (int i = 0; i < sLast; ++i) { + qreal delta = 1/(s[i+1].first - s[i].first); + QRgba64 next_color = combineAlpha256(s[i + 1].second.rgba64(), alpha); + if (colorInterpolation) + next_color = qPremultiply(next_color); + + while (fpos < s[i+1].first && pos < size) { + int dist = int(256 * ((fpos - s[i].first) * delta)); + int idist = 256 - dist; + if (colorInterpolation) + colorTable[pos] = interpolate256(current_color, idist, next_color, dist); + else + colorTable[pos] = qPremultiply(interpolate256(current_color, idist, next_color, dist)); + ++pos; + fpos += incr; + } + current_color = next_color; + } + + Q_ASSERT(s.size() > 0); + + QRgba64 last_color = qPremultiply(combineAlpha256(s[sLast].second.rgba64(), alpha)); + for (;pos < size; ++pos) + colorTable[pos] = last_color; + + // Make sure the last color stop is represented at the end of the table + colorTable[size-1] = last_color; +} + +void QOpenGL2GradientCache::generateGradientColorTable(const QGradient& gradient, uint *colorTable, int size, qreal opacity) const +{ + int pos = 0; + const QGradientStops s = gradient.stops(); + + bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation); + + uint alpha = qRound(opacity * 256); + // Qt LIES! It returns ARGB (on little-endian AND on big-endian) + uint current_color = ARGB_COMBINE_ALPHA(s[0].second.rgba(), alpha); + qreal incr = 1.0 / qreal(size); + qreal fpos = 1.5 * incr; + colorTable[pos++] = ARGB2RGBA(qPremultiply(current_color)); + + while (fpos <= s.first().first) { + colorTable[pos] = colorTable[pos - 1]; + pos++; + fpos += incr; + } + + if (colorInterpolation) + current_color = qPremultiply(current_color); + + const int sLast = s.size() - 1; + for (int i = 0; i < sLast; ++i) { + qreal delta = 1/(s[i+1].first - s[i].first); + uint next_color = ARGB_COMBINE_ALPHA(s[i + 1].second.rgba(), alpha); + if (colorInterpolation) + next_color = qPremultiply(next_color); + + while (fpos < s[i+1].first && pos < size) { + int dist = int(256 * ((fpos - s[i].first) * delta)); + int idist = 256 - dist; + if (colorInterpolation) + colorTable[pos] = ARGB2RGBA(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist)); + else + colorTable[pos] = ARGB2RGBA(qPremultiply(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist))); + ++pos; + fpos += incr; + } + current_color = next_color; + } + + Q_ASSERT(s.size() > 0); + + uint last_color = ARGB2RGBA(qPremultiply(ARGB_COMBINE_ALPHA(s[sLast].second.rgba(), alpha))); + for (;pos < size; ++pos) + colorTable[pos] = last_color; + + // Make sure the last color stop is represented at the end of the table + colorTable[size-1] = last_color; +} + +QT_END_NAMESPACE |