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
53QT_BEGIN_NAMESPACE
54
55class QOpenGL2GradientCacheWrapper
56{
57public:
58 QOpenGL2GradientCache *cacheForContext(QOpenGLContext *context) {
59 QMutexLocker lock(&m_mutex);
60 return m_resource.value<QOpenGL2GradientCache>(context);
61 }
62
63private:
64 QOpenGLMultiGroupSharedResource m_resource;
65 QMutex m_mutex;
66};
67
68Q_GLOBAL_STATIC(QOpenGL2GradientCacheWrapper, qt_gradient_caches)
69
70QOpenGL2GradientCache::QOpenGL2GradientCache(QOpenGLContext *ctx)
71 : QOpenGLSharedResource(ctx->shareGroup())
72{
73}
74
75QOpenGL2GradientCache::~QOpenGL2GradientCache()
76{
77 cache.clear();
78}
79
80QOpenGL2GradientCache *QOpenGL2GradientCache::cacheForContext(QOpenGLContext *context)
81{
82 return qt_gradient_caches()->cacheForContext(context);
83}
84
85void QOpenGL2GradientCache::invalidateResource()
86{
87 QMutexLocker lock(&m_mutex);
88 cache.clear();
89}
90
91void QOpenGL2GradientCache::freeResource(QOpenGLContext *)
92{
93 cleanCache();
94}
95
96void 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
108GLuint 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
137GLuint 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
171void 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
223void 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
276QT_END_NAMESPACE
277