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 QtGui 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 "qopenglgradientcache_p.h"
41 #include <private/qdrawhelper_p.h>
42 #include <private/qopenglcontext_p.h>
43 #include <private/qrgba64_p.h>
44 #include <QtCore/qmutex.h>
45 #include <QtCore/qrandom.h>
46 #include "qopenglfunctions.h"
47 #include "qopenglextensions_p.h"
48 
49 #ifndef GL_RGBA16
50 #define GL_RGBA16   0x805B
51 #endif
52 
53 QT_BEGIN_NAMESPACE
54 
55 class QOpenGL2GradientCacheWrapper
56 {
57 public:
cacheForContext(QOpenGLContext * context)58     QOpenGL2GradientCache *cacheForContext(QOpenGLContext *context) {
59         QMutexLocker lock(&m_mutex);
60         return m_resource.value<QOpenGL2GradientCache>(context);
61     }
62 
63 private:
64     QOpenGLMultiGroupSharedResource m_resource;
65     QMutex m_mutex;
66 };
67 
Q_GLOBAL_STATIC(QOpenGL2GradientCacheWrapper,qt_gradient_caches)68 Q_GLOBAL_STATIC(QOpenGL2GradientCacheWrapper, qt_gradient_caches)
69 
70 QOpenGL2GradientCache::QOpenGL2GradientCache(QOpenGLContext *ctx)
71     : QOpenGLSharedResource(ctx->shareGroup())
72 {
73 }
74 
~QOpenGL2GradientCache()75 QOpenGL2GradientCache::~QOpenGL2GradientCache()
76 {
77     cache.clear();
78 }
79 
cacheForContext(QOpenGLContext * context)80 QOpenGL2GradientCache *QOpenGL2GradientCache::cacheForContext(QOpenGLContext *context)
81 {
82     return qt_gradient_caches()->cacheForContext(context);
83 }
84 
invalidateResource()85 void QOpenGL2GradientCache::invalidateResource()
86 {
87     QMutexLocker lock(&m_mutex);
88     cache.clear();
89 }
90 
freeResource(QOpenGLContext *)91 void QOpenGL2GradientCache::freeResource(QOpenGLContext *)
92 {
93     cleanCache();
94 }
95 
cleanCache()96 void QOpenGL2GradientCache::cleanCache()
97 {
98     QMutexLocker lock(&m_mutex);
99     QOpenGLGradientColorTableHash::const_iterator it = cache.constBegin();
100     QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
101     for (; it != cache.constEnd(); ++it) {
102         const CacheInfo &cache_info = it.value();
103         funcs->glDeleteTextures(1, &cache_info.texId);
104     }
105     cache.clear();
106 }
107 
getBuffer(const QGradient & gradient,qreal opacity)108 GLuint QOpenGL2GradientCache::getBuffer(const QGradient &gradient, qreal opacity)
109 {
110     quint64 hash_val = 0;
111 
112     const QGradientStops stops = gradient.stops();
113     for (int i = 0; i < stops.size() && i <= 2; i++)
114         hash_val += stops[i].second.rgba();
115 
116     const QMutexLocker lock(&m_mutex);
117     QOpenGLGradientColorTableHash::const_iterator it = cache.constFind(hash_val);
118 
119     if (it == cache.constEnd())
120         return addCacheElement(hash_val, gradient, opacity);
121     else {
122         do {
123             const CacheInfo &cache_info = it.value();
124             if (cache_info.stops == stops && cache_info.opacity == opacity
125                 && cache_info.interpolationMode == gradient.interpolationMode())
126             {
127                 return cache_info.texId;
128             }
129             ++it;
130         } while (it != cache.constEnd() && it.key() == hash_val);
131         // an exact match for these stops and opacity was not found, create new cache
132         return addCacheElement(hash_val, gradient, opacity);
133     }
134 }
135 
136 
addCacheElement(quint64 hash_val,const QGradient & gradient,qreal opacity)137 GLuint QOpenGL2GradientCache::addCacheElement(quint64 hash_val, const QGradient &gradient, qreal opacity)
138 {
139     QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
140     if (cache.size() == maxCacheSize()) {
141         int elem_to_remove = QRandomGenerator::global()->bounded(maxCacheSize());
142         quint64 key = cache.keys()[elem_to_remove];
143 
144         // need to call glDeleteTextures on each removed cache entry:
145         QOpenGLGradientColorTableHash::const_iterator it = cache.constFind(key);
146         do {
147             funcs->glDeleteTextures(1, &it.value().texId);
148         } while (++it != cache.constEnd() && it.key() == key);
149         cache.remove(key); // may remove more than 1, but OK
150     }
151 
152     CacheInfo cache_entry(gradient.stops(), opacity, gradient.interpolationMode());
153     funcs->glGenTextures(1, &cache_entry.texId);
154     funcs->glBindTexture(GL_TEXTURE_2D, cache_entry.texId);
155     if (static_cast<QOpenGLExtensions *>(funcs)->hasOpenGLExtension(QOpenGLExtensions::Sized16Formats)) {
156         QRgba64 buffer[1024];
157         generateGradientColorTable(gradient, buffer, paletteSize(), opacity);
158         funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, paletteSize(), 1,
159                             0, GL_RGBA, GL_UNSIGNED_SHORT, buffer);
160     } else {
161         uint buffer[1024];
162         generateGradientColorTable(gradient, buffer, paletteSize(), opacity);
163         funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, paletteSize(), 1,
164                             0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
165     }
166     return cache.insert(hash_val, cache_entry).value().texId;
167 }
168 
169 
170 //TODO: Let GL generate the texture using an FBO
generateGradientColorTable(const QGradient & gradient,QRgba64 * colorTable,int size,qreal opacity) const171 void QOpenGL2GradientCache::generateGradientColorTable(const QGradient& gradient, QRgba64 *colorTable, int size, qreal opacity) const
172 {
173     int pos = 0;
174     const QGradientStops s = gradient.stops();
175 
176     bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation);
177 
178     uint alpha = qRound(opacity * 256);
179     QRgba64 current_color = combineAlpha256(s[0].second.rgba64(), alpha);
180     qreal incr = 1.0 / qreal(size);
181     qreal fpos = 1.5 * incr;
182     colorTable[pos++] = qPremultiply(current_color);
183 
184     while (fpos <= s.first().first) {
185         colorTable[pos] = colorTable[pos - 1];
186         pos++;
187         fpos += incr;
188     }
189 
190     if (colorInterpolation)
191         current_color = qPremultiply(current_color);
192 
193     const int sLast = s.size() - 1;
194     for (int i = 0; i < sLast; ++i) {
195         qreal delta = 1/(s[i+1].first - s[i].first);
196         QRgba64 next_color = combineAlpha256(s[i + 1].second.rgba64(), alpha);
197         if (colorInterpolation)
198             next_color = qPremultiply(next_color);
199 
200         while (fpos < s[i+1].first && pos < size) {
201             int dist = int(256 * ((fpos - s[i].first) * delta));
202             int idist = 256 - dist;
203             if (colorInterpolation)
204                 colorTable[pos] = interpolate256(current_color, idist, next_color, dist);
205             else
206                 colorTable[pos] = qPremultiply(interpolate256(current_color, idist, next_color, dist));
207             ++pos;
208             fpos += incr;
209         }
210         current_color = next_color;
211     }
212 
213     Q_ASSERT(s.size() > 0);
214 
215     QRgba64 last_color = qPremultiply(combineAlpha256(s[sLast].second.rgba64(), alpha));
216     for (;pos < size; ++pos)
217         colorTable[pos] = last_color;
218 
219     // Make sure the last color stop is represented at the end of the table
220     colorTable[size-1] = last_color;
221 }
222 
generateGradientColorTable(const QGradient & gradient,uint * colorTable,int size,qreal opacity) const223 void QOpenGL2GradientCache::generateGradientColorTable(const QGradient& gradient, uint *colorTable, int size, qreal opacity) const
224 {
225     int pos = 0;
226     const QGradientStops s = gradient.stops();
227 
228     bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation);
229 
230     uint alpha = qRound(opacity * 256);
231     // Qt LIES! It returns ARGB (on little-endian AND on big-endian)
232     uint current_color = ARGB_COMBINE_ALPHA(s[0].second.rgba(), alpha);
233     qreal incr = 1.0 / qreal(size);
234     qreal fpos = 1.5 * incr;
235     colorTable[pos++] = ARGB2RGBA(qPremultiply(current_color));
236 
237     while (fpos <= s.first().first) {
238         colorTable[pos] = colorTable[pos - 1];
239         pos++;
240         fpos += incr;
241     }
242 
243     if (colorInterpolation)
244         current_color = qPremultiply(current_color);
245 
246     const int sLast = s.size() - 1;
247     for (int i = 0; i < sLast; ++i) {
248         qreal delta = 1/(s[i+1].first - s[i].first);
249         uint next_color = ARGB_COMBINE_ALPHA(s[i + 1].second.rgba(), alpha);
250         if (colorInterpolation)
251             next_color = qPremultiply(next_color);
252 
253         while (fpos < s[i+1].first && pos < size) {
254             int dist = int(256 * ((fpos - s[i].first) * delta));
255             int idist = 256 - dist;
256             if (colorInterpolation)
257                 colorTable[pos] = ARGB2RGBA(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist));
258             else
259                 colorTable[pos] = ARGB2RGBA(qPremultiply(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist)));
260             ++pos;
261             fpos += incr;
262         }
263         current_color = next_color;
264     }
265 
266     Q_ASSERT(s.size() > 0);
267 
268     uint last_color = ARGB2RGBA(qPremultiply(ARGB_COMBINE_ALPHA(s[sLast].second.rgba(), alpha)));
269     for (;pos < size; ++pos)
270         colorTable[pos] = last_color;
271 
272     // Make sure the last color stop is represented at the end of the table
273     colorTable[size-1] = last_color;
274 }
275 
276 QT_END_NAMESPACE
277