1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qopenglgradientcache_p.h"
5#include <private/qdrawhelper_p.h>
6#include <private/qopenglcontext_p.h>
7#include <private/qrgba64_p.h>
8#include <QtCore/qmutex.h>
9#include <QtCore/qrandom.h>
10#include "qopenglfunctions.h"
11#include <private/qopenglextensions_p.h>
12
13#ifndef GL_RGBA16
14#define GL_RGBA16 0x805B
15#endif
16
17QT_BEGIN_NAMESPACE
18
19class QOpenGL2GradientCacheWrapper
20{
21public:
22 QOpenGL2GradientCache *cacheForContext(QOpenGLContext *context) {
23 QMutexLocker lock(&m_mutex);
24 return m_resource.value<QOpenGL2GradientCache>(context);
25 }
26
27private:
28 QOpenGLMultiGroupSharedResource m_resource;
29 QMutex m_mutex;
30};
31
32Q_GLOBAL_STATIC(QOpenGL2GradientCacheWrapper, qt_gradient_caches)
33
34QOpenGL2GradientCache::QOpenGL2GradientCache(QOpenGLContext *ctx)
35 : QOpenGLSharedResource(ctx->shareGroup())
36{
37}
38
39QOpenGL2GradientCache::~QOpenGL2GradientCache()
40{
41 cache.clear();
42}
43
44QOpenGL2GradientCache *QOpenGL2GradientCache::cacheForContext(QOpenGLContext *context)
45{
46 return qt_gradient_caches()->cacheForContext(context);
47}
48
49void QOpenGL2GradientCache::invalidateResource()
50{
51 QMutexLocker lock(&m_mutex);
52 cache.clear();
53}
54
55void QOpenGL2GradientCache::freeResource(QOpenGLContext *)
56{
57 cleanCache();
58}
59
60void QOpenGL2GradientCache::cleanCache()
61{
62 QMutexLocker lock(&m_mutex);
63 QOpenGLGradientColorTableHash::const_iterator it = cache.constBegin();
64 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
65 for (; it != cache.constEnd(); ++it) {
66 const CacheInfo &cache_info = it.value();
67 funcs->glDeleteTextures(n: 1, textures: &cache_info.texId);
68 }
69 cache.clear();
70}
71
72GLuint QOpenGL2GradientCache::getBuffer(const QGradient &gradient, qreal opacity)
73{
74 quint64 hash_val = 0;
75
76 const QGradientStops stops = gradient.stops();
77 for (int i = 0; i < stops.size() && i <= 2; i++)
78 hash_val += stops[i].second.rgba();
79
80 const QMutexLocker lock(&m_mutex);
81 QOpenGLGradientColorTableHash::const_iterator it = cache.constFind(key: hash_val);
82
83 if (it == cache.constEnd())
84 return addCacheElement(hash_val, gradient, opacity);
85 else {
86 do {
87 const CacheInfo &cache_info = it.value();
88 if (cache_info.stops == stops && cache_info.opacity == opacity
89 && cache_info.interpolationMode == gradient.interpolationMode())
90 {
91 return cache_info.texId;
92 }
93 ++it;
94 } while (it != cache.constEnd() && it.key() == hash_val);
95 // an exact match for these stops and opacity was not found, create new cache
96 return addCacheElement(hash_val, gradient, opacity);
97 }
98}
99
100
101GLuint QOpenGL2GradientCache::addCacheElement(quint64 hash_val, const QGradient &gradient, qreal opacity)
102{
103 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
104 if (cache.size() == maxCacheSize()) {
105 int elem_to_remove = QRandomGenerator::global()->bounded(highest: maxCacheSize());
106 quint64 key = cache.keys()[elem_to_remove];
107
108 // need to call glDeleteTextures on each removed cache entry:
109 QOpenGLGradientColorTableHash::const_iterator it = cache.constFind(key);
110 do {
111 funcs->glDeleteTextures(n: 1, textures: &it.value().texId);
112 } while (++it != cache.constEnd() && it.key() == key);
113 cache.remove(key); // may remove more than 1, but OK
114 }
115
116 CacheInfo cache_entry(gradient.stops(), opacity, gradient.interpolationMode());
117 funcs->glGenTextures(n: 1, textures: &cache_entry.texId);
118 funcs->glBindTexture(GL_TEXTURE_2D, texture: cache_entry.texId);
119 if (static_cast<QOpenGLExtensions *>(funcs)->hasOpenGLExtension(extension: QOpenGLExtensions::Sized16Formats)) {
120 QRgba64 buffer[1024];
121 generateGradientColorTable(gradient, colorTable: buffer, size: paletteSize(), opacity);
122 funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA16, width: paletteSize(), height: 1,
123 border: 0, GL_RGBA, GL_UNSIGNED_SHORT, pixels: buffer);
124 } else {
125 uint buffer[1024];
126 generateGradientColorTable(gradient, colorTable: buffer, size: paletteSize(), opacity);
127 funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width: paletteSize(), height: 1,
128 border: 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels: buffer);
129 }
130 return cache.insert(key: hash_val, value: cache_entry).value().texId;
131}
132
133
134//TODO: Let GL generate the texture using an FBO
135void QOpenGL2GradientCache::generateGradientColorTable(const QGradient& gradient, QRgba64 *colorTable, int size, qreal opacity) const
136{
137 int pos = 0;
138 const QGradientStops s = gradient.stops();
139
140 bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation);
141
142 uint alpha = qRound(d: opacity * 256);
143 QRgba64 current_color = combineAlpha256(rgba64: s[0].second.rgba64(), alpha256: alpha);
144 qreal incr = 1.0 / qreal(size);
145 qreal fpos = 1.5 * incr;
146 colorTable[pos++] = qPremultiply(c: current_color);
147
148 while (fpos <= s.first().first) {
149 colorTable[pos] = colorTable[pos - 1];
150 pos++;
151 fpos += incr;
152 }
153
154 if (colorInterpolation)
155 current_color = qPremultiply(c: current_color);
156
157 const int sLast = s.size() - 1;
158 for (int i = 0; i < sLast; ++i) {
159 qreal delta = 1/(s[i+1].first - s[i].first);
160 QRgba64 next_color = combineAlpha256(rgba64: s[i + 1].second.rgba64(), alpha256: alpha);
161 if (colorInterpolation)
162 next_color = qPremultiply(c: next_color);
163
164 while (fpos < s[i+1].first && pos < size) {
165 int dist = int(256 * ((fpos - s[i].first) * delta));
166 int idist = 256 - dist;
167 if (colorInterpolation)
168 colorTable[pos] = interpolate256(x: current_color, alpha1: idist, y: next_color, alpha2: dist);
169 else
170 colorTable[pos] = qPremultiply(c: interpolate256(x: current_color, alpha1: idist, y: next_color, alpha2: dist));
171 ++pos;
172 fpos += incr;
173 }
174 current_color = next_color;
175 }
176
177 Q_ASSERT(s.size() > 0);
178
179 QRgba64 last_color = qPremultiply(c: combineAlpha256(rgba64: s[sLast].second.rgba64(), alpha256: alpha));
180 for (;pos < size; ++pos)
181 colorTable[pos] = last_color;
182
183 // Make sure the last color stop is represented at the end of the table
184 colorTable[size-1] = last_color;
185}
186
187void QOpenGL2GradientCache::generateGradientColorTable(const QGradient& gradient, uint *colorTable, int size, qreal opacity) const
188{
189 int pos = 0;
190 const QGradientStops s = gradient.stops();
191
192 bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation);
193
194 uint alpha = qRound(d: opacity * 256);
195 // Qt LIES! It returns ARGB (on little-endian AND on big-endian)
196 uint current_color = ARGB_COMBINE_ALPHA(s[0].second.rgba(), alpha);
197 qreal incr = 1.0 / qreal(size);
198 qreal fpos = 1.5 * incr;
199 colorTable[pos++] = ARGB2RGBA(x: qPremultiply(x: current_color));
200
201 while (fpos <= s.first().first) {
202 colorTable[pos] = colorTable[pos - 1];
203 pos++;
204 fpos += incr;
205 }
206
207 if (colorInterpolation)
208 current_color = qPremultiply(x: current_color);
209
210 const int sLast = s.size() - 1;
211 for (int i = 0; i < sLast; ++i) {
212 qreal delta = 1/(s[i+1].first - s[i].first);
213 uint next_color = ARGB_COMBINE_ALPHA(s[i + 1].second.rgba(), alpha);
214 if (colorInterpolation)
215 next_color = qPremultiply(x: next_color);
216
217 while (fpos < s[i+1].first && pos < size) {
218 int dist = int(256 * ((fpos - s[i].first) * delta));
219 int idist = 256 - dist;
220 if (colorInterpolation)
221 colorTable[pos] = ARGB2RGBA(x: INTERPOLATE_PIXEL_256(x: current_color, a: idist, y: next_color, b: dist));
222 else
223 colorTable[pos] = ARGB2RGBA(x: qPremultiply(x: INTERPOLATE_PIXEL_256(x: current_color, a: idist, y: next_color, b: dist)));
224 ++pos;
225 fpos += incr;
226 }
227 current_color = next_color;
228 }
229
230 Q_ASSERT(s.size() > 0);
231
232 uint last_color = ARGB2RGBA(x: qPremultiply(ARGB_COMBINE_ALPHA(s[sLast].second.rgba(), alpha)));
233 for (;pos < size; ++pos)
234 colorTable[pos] = last_color;
235
236 // Make sure the last color stop is represented at the end of the table
237 colorTable[size-1] = last_color;
238}
239
240QT_END_NAMESPACE
241

source code of qtbase/src/opengl/qopenglgradientcache.cpp