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 
46 QT_BEGIN_NAMESPACE
47 
48 class QGL2GradientCacheWrapper
49 {
50 public:
cacheForContext(const QGLContext * context)51     QGL2GradientCache *cacheForContext(const QGLContext *context) {
52         QMutexLocker lock(&m_mutex);
53         return m_resource.value<QGL2GradientCache>(context->contextHandle());
54     }
55 
56 private:
57     QOpenGLMultiGroupSharedResource m_resource;
58     QMutex m_mutex;
59 };
60 
Q_GLOBAL_STATIC(QGL2GradientCacheWrapper,qt_gradient_caches)61 Q_GLOBAL_STATIC(QGL2GradientCacheWrapper, qt_gradient_caches)
62 
63 QGL2GradientCache::QGL2GradientCache(QOpenGLContext *ctx)
64     : QOpenGLSharedResource(ctx->shareGroup())
65 {
66 }
67 
~QGL2GradientCache()68 QGL2GradientCache::~QGL2GradientCache()
69 {
70     cache.clear();
71 }
72 
cacheForContext(const QGLContext * context)73 QGL2GradientCache *QGL2GradientCache::cacheForContext(const QGLContext *context)
74 {
75     return qt_gradient_caches()->cacheForContext(context);
76 }
77 
invalidateResource()78 void QGL2GradientCache::invalidateResource()
79 {
80     QMutexLocker lock(&m_mutex);
81     cache.clear();
82 }
83 
freeResource(QOpenGLContext *)84 void QGL2GradientCache::freeResource(QOpenGLContext *)
85 {
86     cleanCache();
87 }
88 
cleanCache()89 void 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 
getBuffer(const QGradient & gradient,qreal opacity)101 GLuint 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 
addCacheElement(quint64 hash_val,const QGradient & gradient,qreal opacity)130 GLuint 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.
qtToGlColor(uint c)158 static 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
generateGradientColorTable(const QGradient & gradient,uint * colorTable,int size,qreal opacity) const173 void 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 
225 QT_END_NAMESPACE
226