1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtOpenGL module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qglgradientcache_p.h"
41#include <private/qdrawhelper_p.h>
42#include <private/qgl_p.h>
43#include <QtCore/qmutex.h>
44#include <QtCore/qrandom.h>
45
46QT_BEGIN_NAMESPACE
47
48class QGL2GradientCacheWrapper
49{
50public:
51 QGL2GradientCache *cacheForContext(const QGLContext *context) {
52 QMutexLocker lock(&m_mutex);
53 return m_resource.value<QGL2GradientCache>(context->contextHandle());
54 }
55
56private:
57 QOpenGLMultiGroupSharedResource m_resource;
58 QMutex m_mutex;
59};
60
61Q_GLOBAL_STATIC(QGL2GradientCacheWrapper, qt_gradient_caches)
62
63QGL2GradientCache::QGL2GradientCache(QOpenGLContext *ctx)
64 : QOpenGLSharedResource(ctx->shareGroup())
65{
66}
67
68QGL2GradientCache::~QGL2GradientCache()
69{
70 cache.clear();
71}
72
73QGL2GradientCache *QGL2GradientCache::cacheForContext(const QGLContext *context)
74{
75 return qt_gradient_caches()->cacheForContext(context);
76}
77
78void QGL2GradientCache::invalidateResource()
79{
80 QMutexLocker lock(&m_mutex);
81 cache.clear();
82}
83
84void QGL2GradientCache::freeResource(QOpenGLContext *)
85{
86 cleanCache();
87}
88
89void QGL2GradientCache::cleanCache()
90{
91 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
92 QMutexLocker lock(&m_mutex);
93 QGLGradientColorTableHash::const_iterator it = cache.constBegin();
94 for (; it != cache.constEnd(); ++it) {
95 const CacheInfo &cache_info = it.value();
96 funcs->glDeleteTextures(1, &cache_info.texId);
97 }
98 cache.clear();
99}
100
101GLuint QGL2GradientCache::getBuffer(const QGradient &gradient, qreal opacity)
102{
103 QMutexLocker lock(&m_mutex);
104 quint64 hash_val = 0;
105
106 const QGradientStops stops = gradient.stops();
107 for (int i = 0; i < stops.size() && i <= 2; i++)
108 hash_val += stops[i].second.rgba();
109
110 QGLGradientColorTableHash::const_iterator it = cache.constFind(hash_val);
111
112 if (it == cache.constEnd())
113 return addCacheElement(hash_val, gradient, opacity);
114 else {
115 do {
116 const CacheInfo &cache_info = it.value();
117 if (cache_info.stops == stops && cache_info.opacity == opacity
118 && cache_info.interpolationMode == gradient.interpolationMode())
119 {
120 return cache_info.texId;
121 }
122 ++it;
123 } while (it != cache.constEnd() && it.key() == hash_val);
124 // an exact match for these stops and opacity was not found, create new cache
125 return addCacheElement(hash_val, gradient, opacity);
126 }
127}
128
129
130GLuint QGL2GradientCache::addCacheElement(quint64 hash_val, const QGradient &gradient, qreal opacity)
131{
132 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
133 if (cache.size() == maxCacheSize()) {
134 int elem_to_remove = QRandomGenerator::global()->bounded(maxCacheSize());
135 quint64 key = cache.keys()[elem_to_remove];
136
137 // need to call glDeleteTextures on each removed cache entry:
138 QGLGradientColorTableHash::const_iterator it = cache.constFind(key);
139 do {
140 funcs->glDeleteTextures(1, &it.value().texId);
141 } while (++it != cache.constEnd() && it.key() == key);
142 cache.remove(key); // may remove more than 1, but OK
143 }
144
145 CacheInfo cache_entry(gradient.stops(), opacity, gradient.interpolationMode());
146 uint buffer[1024];
147 generateGradientColorTable(gradient, buffer, paletteSize(), opacity);
148 funcs->glGenTextures(1, &cache_entry.texId);
149 funcs->glBindTexture(GL_TEXTURE_2D, cache_entry.texId);
150 funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, paletteSize(), 1,
151 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
152 return cache.insert(hash_val, cache_entry).value().texId;
153}
154
155
156// GL's expects pixels in RGBA (when using GL_RGBA), bin-endian (ABGR on x86).
157// Qt always stores in ARGB reguardless of the byte-order the mancine uses.
158static inline uint qtToGlColor(uint c)
159{
160 uint o;
161#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
162 o = (c & 0xff00ff00) // alpha & green already in the right place
163 | ((c >> 16) & 0x000000ff) // red
164 | ((c << 16) & 0x00ff0000); // blue
165#else //Q_BIG_ENDIAN
166 o = (c << 8)
167 | ((c >> 24) & 0x000000ff);
168#endif // Q_BYTE_ORDER
169 return o;
170}
171
172//TODO: Let GL generate the texture using an FBO
173void QGL2GradientCache::generateGradientColorTable(const QGradient& gradient, uint *colorTable, int size, qreal opacity) const
174{
175 int pos = 0;
176 const QGradientStops s = gradient.stops();
177 bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation);
178
179 uint alpha = qRound(opacity * 256);
180 // Qt LIES! It returns ARGB (on little-endian AND on big-endian)
181 uint current_color = ARGB_COMBINE_ALPHA(s[0].second.rgba(), alpha);
182 qreal incr = 1.0 / qreal(size);
183 qreal fpos = 1.5 * incr;
184 colorTable[pos++] = qtToGlColor(qPremultiply(current_color));
185
186 while (fpos <= s.first().first) {
187 colorTable[pos] = colorTable[pos - 1];
188 pos++;
189 fpos += incr;
190 }
191
192 if (colorInterpolation)
193 current_color = qPremultiply(current_color);
194
195 const int sLast = s.size() - 1;
196 for (int i = 0; i < sLast; ++i) {
197 qreal delta = 1/(s[i+1].first - s[i].first);
198 uint next_color = ARGB_COMBINE_ALPHA(s[i + 1].second.rgba(), alpha);
199 if (colorInterpolation)
200 next_color = qPremultiply(next_color);
201
202 while (fpos < s[i+1].first && pos < size) {
203 int dist = int(256 * ((fpos - s[i].first) * delta));
204 int idist = 256 - dist;
205 if (colorInterpolation)
206 colorTable[pos] = qtToGlColor(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist));
207 else
208 colorTable[pos] = qtToGlColor(qPremultiply(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist)));
209 ++pos;
210 fpos += incr;
211 }
212 current_color = next_color;
213 }
214
215 Q_ASSERT(s.size() > 0);
216
217 uint last_color = qtToGlColor(qPremultiply(ARGB_COMBINE_ALPHA(s[sLast].second.rgba(), alpha)));
218 for (;pos < size; ++pos)
219 colorTable[pos] = last_color;
220
221 // Make sure the last color stop is represented at the end of the table
222 colorTable[size-1] = last_color;
223}
224
225QT_END_NAMESPACE
226