1/****************************************************************************
2**
3** Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
4** Contact: http://www.qt-project.org/legal
5**
6** This file is part of the Qt Graphical Effects 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 "qgfxshaderbuilder_p.h"
41
42#include <QtCore/QDebug>
43#include <QtGui/QOffscreenSurface>
44#include <QtGui/QOpenGLContext>
45#include <QtGui/QOpenGLFunctions>
46
47#include <qmath.h>
48#include <qnumeric.h>
49
50#ifndef GL_MAX_VARYING_COMPONENTS
51#define GL_MAX_VARYING_COMPONENTS 0x8B4B
52#endif
53
54#ifndef GL_MAX_VARYING_FLOATS
55#define GL_MAX_VARYING_FLOATS 0x8B4B
56#endif
57
58#ifndef GL_MAX_VARYING_VECTORS
59#define GL_MAX_VARYING_VECTORS 0x8DFC
60#endif
61
62QGfxShaderBuilder::QGfxShaderBuilder()
63 : m_coreProfile(false)
64{
65 // The following code makes the assumption that an OpenGL context the GUI
66 // thread will get the same capabilities as the render thread's OpenGL
67 // context. Not 100% accurate, but it works...
68 QOpenGLContext context;
69 if (!context.create()) {
70 qDebug() << "failed to acquire GL context to resolve capabilities, using defaults..";
71 m_maxBlurSamples = 8; // minimum number of varyings in the ES 2.0 spec.
72 return;
73 }
74
75 QOffscreenSurface surface;
76 // In very odd cases, we can get incompatible configs here unless we pass the
77 // GL context's format on to the offscreen format.
78 surface.setFormat(context.format());
79 surface.create();
80
81 QOpenGLContext *oldContext = QOpenGLContext::currentContext();
82 QSurface *oldSurface = oldContext ? oldContext->surface() : 0;
83 if (context.makeCurrent(surface: &surface)) {
84 QOpenGLFunctions *gl = context.functions();
85 if (context.isOpenGLES()) {
86 gl->glGetIntegerv(GL_MAX_VARYING_VECTORS, params: &m_maxBlurSamples);
87 } else if (context.format().majorVersion() >= 3) {
88 int components;
89 gl->glGetIntegerv(GL_MAX_VARYING_COMPONENTS, params: &components);
90 m_maxBlurSamples = components / 2.0;
91 m_coreProfile = context.format().profile() == QSurfaceFormat::CoreProfile;
92 } else {
93 int floats;
94 gl->glGetIntegerv(GL_MAX_VARYING_FLOATS, params: &floats);
95 m_maxBlurSamples = floats / 2.0;
96 }
97 if (oldContext && oldSurface)
98 oldContext->makeCurrent(surface: oldSurface);
99 else
100 context.doneCurrent();
101 } else {
102 qDebug() << "failed to acquire GL context to resolve capabilities, using defaults..";
103 m_maxBlurSamples = 8; // minimum number of varyings in the ES 2.0 spec.
104 }
105}
106
107/*
108
109 The algorithm works like this..
110
111 For every two pixels we want to sample we take one sample between those
112 two pixels and rely on linear interpoliation to get both values at the
113 cost of one texture sample. The sample point is calculated based on the
114 gaussian weights at the two texels.
115
116 I've included the table here for future reference:
117
118 Requested Effective Actual Actual
119 Samples Radius/Kernel Samples Radius(*)
120 -------------------------------------------------
121 0 0 / 1x1 1 0
122 1 0 / 1x1 1 0
123 2 1 / 3x3 2 0
124 3 1 / 3x3 2 0
125 4 2 / 5x5 3 1
126 5 2 / 5x5 3 1
127 6 3 / 7x7 4 1
128 7 3 / 7x7 4 1
129 8 4 / 9x9 5 2
130 9 4 / 9x9 5 2
131 10 5 / 11x11 6 2
132 11 5 / 11x11 6 2
133 12 6 / 13x13 7 3
134 13 6 / 13x13 7 3
135 ... ... ... ...
136
137 When ActualSamples is an 'odd' nunber, sample center pixel separately:
138 EffectiveRadius: 4
139 EffectiveKernel: 9x9
140 ActualSamples: 5
141 -4 -3 -2 -1 0 +1 +2 +3 +4
142 | | | | | | | | | |
143 \ / \ / | \ / \ /
144 tL2 tL1 tC tR1 tR2
145
146 When ActualSamples is an 'even' number, sample 3 center pixels with two
147 samples:
148 EffectiveRadius: 3
149 EffectiveKernel: 7x7
150 ActualSamples: 4
151 -3 -2 -1 0 +1 +2 +3
152 | | | | | | | |
153 \ / \ / | \ /
154 tL1 tL0 tR0 tR2
155
156 From this table we have the following formulas:
157 EffectiveRadius = RequestedSamples / 2;
158 EffectiveKernel = EffectiveRadius * 2 + 1
159 ActualSamples = 1 + RequstedSamples / 2;
160 ActualRadius = RequestedSamples / 4;
161
162 (*) ActualRadius excludes the pixel pair sampled in the center
163 for even 'actual sample' counts
164*/
165
166static qreal qgfx_gaussian(qreal x, qreal d)
167{
168 return qExp(v: - x * x / (2 * d * d));
169}
170
171struct QGfxGaussSample
172{
173 QByteArray name;
174 qreal pos;
175 qreal weight;
176 inline void set(const QByteArray &n, qreal p, qreal w) {
177 name = n;
178 pos = p;
179 weight = w;
180 }
181};
182
183static void qgfx_declareBlurVaryings(QByteArray &shader, QGfxGaussSample *s, int samples)
184{
185 for (int i=0; i<samples; ++i) {
186 shader += "varying highp vec2 ";
187 shader += s[i].name;
188 shader += ";\n";
189 }
190}
191
192static void qgfx_declareCoreBlur(QByteArray &shader, const QByteArray& direction, QGfxGaussSample *s, int samples)
193{
194 for (int i=0; i<samples; ++i) {
195 shader += direction + " vec2 ";
196 shader += s[i].name;
197 shader += ";\n";
198 }
199}
200
201static void qgfx_buildGaussSamplePoints(QGfxGaussSample *p, int samples, int radius, qreal deviation)
202{
203
204 if ((samples % 2) == 1) {
205 p[radius].set(n: "tC", p: 0, w: 1);
206 for (int i=0; i<radius; ++i) {
207 qreal p0 = (i + 1) * 2 - 1;
208 qreal p1 = (i + 1) * 2;
209 qreal w0 = qgfx_gaussian(x: p0, d: deviation);
210 qreal w1 = qgfx_gaussian(x: p1, d: deviation);
211 qreal w = w0 + w1;
212 qreal samplePos = (p0 * w0 + p1 * w1) / w;
213 if (qIsNaN(d: samplePos)) {
214 samplePos = 0;
215 w = 0;
216 }
217 p[radius - i - 1].set(n: "tL" + QByteArray::number(i), p: samplePos, w);
218 p[radius + i + 1].set(n: "tR" + QByteArray::number(i), p: -samplePos, w);
219 }
220 } else {
221 { // tL0
222 qreal wl = qgfx_gaussian(x: -1.0, d: deviation);
223 qreal wc = qgfx_gaussian(x: 0.0, d: deviation);
224 qreal w = wl + wc;
225 p[radius].set(n: "tL0", p: -1.0 * wl / w, w);
226 p[radius+1].set(n: "tR0", p: 1.0, w: wl); // reuse wl as gauss(-1)==gauss(1);
227 }
228 for (int i=0; i<radius; ++i) {
229 qreal p0 = (i + 1) * 2;
230 qreal p1 = (i + 1) * 2 + 1;
231 qreal w0 = qgfx_gaussian(x: p0, d: deviation);
232 qreal w1 = qgfx_gaussian(x: p1, d: deviation);
233 qreal w = w0 + w1;
234 qreal samplePos = (p0 * w0 + p1 * w1) / w;
235 if (qIsNaN(d: samplePos)) {
236 samplePos = 0;
237 w = 0;
238 }
239 p[radius - i - 1].set(n: "tL" + QByteArray::number(i+1), p: samplePos, w);
240 p[radius + i + 2].set(n: "tR" + QByteArray::number(i+1), p: -samplePos, w);
241
242 }
243 }
244}
245
246QByteArray qgfx_gaussianVertexShader(QGfxGaussSample *p, int samples)
247{
248 QByteArray shader;
249 shader.reserve(asize: 1024);
250 shader += "attribute highp vec4 qt_Vertex;\n"
251 "attribute highp vec2 qt_MultiTexCoord0;\n\n"
252 "uniform highp mat4 qt_Matrix;\n"
253 "uniform highp float spread;\n"
254 "uniform highp vec2 dirstep;\n\n";
255
256 qgfx_declareBlurVaryings(shader, s: p, samples);
257
258 shader += "\nvoid main() {\n"
259 " gl_Position = qt_Matrix * qt_Vertex;\n\n";
260
261 for (int i=0; i<samples; ++i) {
262 shader += " ";
263 shader += p[i].name;
264 shader += " = qt_MultiTexCoord0";
265 if (p[i].pos != 0.0) {
266 shader += " + spread * dirstep * float(";
267 shader += QByteArray::number(p[i].pos);
268 shader += ')';
269 }
270 shader += ";\n";
271 }
272
273 shader += "}\n";
274
275 return shader;
276}
277
278QByteArray qgfx_gaussianVertexCoreShader(QGfxGaussSample *p, int samples)
279{
280 QByteArray shader;
281 shader.reserve(asize: 1024);
282 shader += "#version 150 core\n"
283 "in vec4 qt_Vertex;\n"
284 "in vec2 qt_MultiTexCoord0;\n\n"
285 "uniform mat4 qt_Matrix;\n"
286 "uniform float spread;\n"
287 "uniform vec2 dirstep;\n\n";
288
289 qgfx_declareCoreBlur(shader, direction: "out", s: p, samples);
290
291 shader += "\nvoid main() {\n"
292 " gl_Position = qt_Matrix * qt_Vertex;\n\n";
293
294 for (int i=0; i<samples; ++i) {
295 shader += " ";
296 shader += p[i].name;
297 shader += " = qt_MultiTexCoord0";
298 if (p[i].pos != 0.0) {
299 shader += " + spread * dirstep * float(";
300 shader += QByteArray::number(p[i].pos);
301 shader += ')';
302 }
303 shader += ";\n";
304 }
305
306 shader += "}\n";
307
308 return shader;
309}
310
311QByteArray qgfx_gaussianFragmentShader(QGfxGaussSample *p, int samples, bool alphaOnly)
312{
313 QByteArray shader;
314 shader.reserve(asize: 1024);
315 shader += "uniform lowp sampler2D source;\n"
316 "uniform lowp float qt_Opacity;\n";
317
318 if (alphaOnly) {
319 shader += "uniform lowp vec4 color;\n"
320 "uniform lowp float thickness;\n";
321 }
322
323 shader += "\n";
324
325
326
327 qgfx_declareBlurVaryings(shader, s: p, samples);
328
329 shader += "\nvoid main() {\n"
330 " gl_FragColor = ";
331 if (alphaOnly)
332 shader += "mix(vec4(0), color, clamp((";
333 else
334 shader += "(";
335
336 qreal sum = 0;
337 for (int i=0; i<samples; ++i)
338 sum += p[i].weight;
339
340 for (int i=0; i<samples; ++i) {
341 shader += "\n + float(";
342 shader += QByteArray::number(p[i].weight / sum);
343 shader += ") * texture2D(source, ";
344 shader += p[i].name;
345 shader += ")";
346 if (alphaOnly)
347 shader += ".a";
348 }
349
350 shader += "\n )";
351 if (alphaOnly)
352 shader += "/thickness, 0.0, 1.0))";
353 shader += "* qt_Opacity;\n}";
354
355 return shader;
356}
357
358QByteArray qgfx_gaussianFragmentCoreShader(QGfxGaussSample *p, int samples, bool alphaOnly)
359{
360 QByteArray shader;
361 shader.reserve(asize: 1024);
362 shader += "#version 150 core\n"
363 "uniform sampler2D source;\n"
364 "uniform float qt_Opacity;\n";
365
366 if (alphaOnly) {
367 shader += "uniform vec4 color;\n"
368 "uniform float thickness;\n";
369 }
370
371 shader += "out vec4 fragColor;\n";
372
373 qgfx_declareCoreBlur(shader, direction: "in", s: p, samples);
374
375 shader += "\nvoid main() {\n"
376 " fragColor = ";
377 if (alphaOnly)
378 shader += "mix(vec4(0), color, clamp((";
379 else
380 shader += "(";
381
382 qreal sum = 0;
383 for (int i=0; i<samples; ++i)
384 sum += p[i].weight;
385
386 for (int i=0; i<samples; ++i) {
387 shader += "\n + float(";
388 shader += QByteArray::number(p[i].weight / sum);
389 shader += ") * texture(source, ";
390 shader += p[i].name;
391 shader += ")";
392 if (alphaOnly)
393 shader += ".a";
394 }
395
396 shader += "\n )";
397 if (alphaOnly)
398 shader += "/thickness, 0.0, 1.0))";
399 shader += "* qt_Opacity;\n}";
400
401 return shader;
402}
403
404static QByteArray qgfx_fallbackVertexShader()
405{
406 return "attribute highp vec4 qt_Vertex;\n"
407 "attribute highp vec2 qt_MultiTexCoord0;\n"
408 "uniform highp mat4 qt_Matrix;\n"
409 "varying highp vec2 qt_TexCoord0;\n"
410 "void main() {\n"
411 " gl_Position = qt_Matrix * qt_Vertex;\n"
412 " qt_TexCoord0 = qt_MultiTexCoord0;\n"
413 "}\n";
414}
415
416static QByteArray qgfx_fallbackCoreVertexShader()
417{
418 return "#version 150 core\n"
419 "in vec4 qt_Vertex;\n"
420 "in vec2 qt_MultiTexCoord0;\n"
421 "uniform mat4 qt_Matrix;\n"
422 "out vec2 qt_TexCoord0;\n"
423 "void main() {\n"
424 " gl_Position = qt_Matrix * qt_Vertex;\n"
425 " qt_TexCoord0 = qt_MultiTexCoord0;\n"
426 "}\n";
427}
428
429static QByteArray qgfx_fallbackFragmentShader(int requestedRadius, qreal deviation, bool masked, bool alphaOnly)
430{
431 QByteArray fragShader;
432 if (masked)
433 fragShader += "uniform mediump sampler2D mask;\n";
434 fragShader +=
435 "uniform highp sampler2D source;\n"
436 "uniform lowp float qt_Opacity;\n"
437 "uniform mediump float spread;\n"
438 "uniform highp vec2 dirstep;\n";
439 if (alphaOnly) {
440 fragShader += "uniform lowp vec4 color;\n"
441 "uniform lowp float thickness;\n";
442 }
443 fragShader +=
444 "\n"
445 "varying highp vec2 qt_TexCoord0;\n"
446 "\n"
447 "void main() {\n";
448 if (alphaOnly)
449 fragShader += " mediump float result = 0.0;\n";
450 else
451 fragShader += " mediump vec4 result = vec4(0);\n";
452 fragShader += " highp vec2 pixelStep = dirstep * spread;\n";
453 if (masked)
454 fragShader += " pixelStep *= texture2D(mask, qt_TexCoord0).a;\n";
455
456 float wSum = 0;
457 for (int r=-requestedRadius; r<=requestedRadius; ++r) {
458 float w = qgfx_gaussian(x: r, d: deviation);
459 wSum += w;
460 fragShader += " result += float(";
461 fragShader += QByteArray::number(w);
462 fragShader += ") * texture2D(source, qt_TexCoord0 + pixelStep * float(";
463 fragShader += QByteArray::number(r);
464 fragShader += "))";
465 if (alphaOnly)
466 fragShader += ".a";
467 fragShader += ";\n";
468 }
469 fragShader += " const mediump float wSum = float(";
470 fragShader += QByteArray::number(wSum);
471 fragShader += ");\n"
472 " gl_FragColor = ";
473 if (alphaOnly)
474 fragShader += "mix(vec4(0), color, clamp((result / wSum) / thickness, 0.0, 1.0)) * qt_Opacity;\n";
475 else
476 fragShader += "(qt_Opacity / wSum) * result;\n";
477 fragShader += "}\n";
478
479 return fragShader;
480}
481
482static QByteArray qgfx_fallbackCoreFragmentShader(int requestedRadius, qreal deviation, bool masked, bool alphaOnly)
483{
484 QByteArray fragShader = "#version 150 core\n";
485 if (masked)
486 fragShader += "uniform sampler2D mask;\n";
487 fragShader +=
488 "uniform sampler2D source;\n"
489 "uniform float qt_Opacity;\n"
490 "uniform float spread;\n"
491 "uniform vec2 dirstep;\n";
492 if (alphaOnly) {
493 fragShader += "uniform vec4 color;\n"
494 "uniform float thickness;\n";
495 }
496 fragShader +=
497 "out vec4 fragColor;\n"
498 "in vec2 qt_TexCoord0;\n"
499 "\n"
500 "void main() {\n";
501 if (alphaOnly)
502 fragShader += " float result = 0.0;\n";
503 else
504 fragShader += " vec4 result = vec4(0);\n";
505 fragShader += " vec2 pixelStep = dirstep * spread;\n";
506 if (masked)
507 fragShader += " pixelStep *= texture(mask, qt_TexCoord0).a;\n";
508
509 float wSum = 0;
510 for (int r=-requestedRadius; r<=requestedRadius; ++r) {
511 float w = qgfx_gaussian(x: r, d: deviation);
512 wSum += w;
513 fragShader += " result += float(";
514 fragShader += QByteArray::number(w);
515 fragShader += ") * texture(source, qt_TexCoord0 + pixelStep * float(";
516 fragShader += QByteArray::number(r);
517 fragShader += "))";
518 if (alphaOnly)
519 fragShader += ".a";
520 fragShader += ";\n";
521 }
522 fragShader += " const float wSum = float(";
523 fragShader += QByteArray::number(wSum);
524 fragShader += ");\n"
525 " fragColor = ";
526 if (alphaOnly)
527 fragShader += "mix(vec4(0), color, clamp((result / wSum) / thickness, 0.0, 1.0)) * qt_Opacity;\n";
528 else
529 fragShader += "(qt_Opacity / wSum) * result;\n";
530 fragShader += "}\n";
531
532 return fragShader;
533}
534
535QVariantMap QGfxShaderBuilder::gaussianBlur(const QJSValue &parameters)
536{
537 int requestedRadius = qMax(a: 0.0, b: parameters.property(QStringLiteral("radius")).toNumber());
538 qreal deviation = parameters.property(QStringLiteral("deviation")).toNumber();
539 bool masked = parameters.property(QStringLiteral("masked")).toBool();
540 bool alphaOnly = parameters.property(QStringLiteral("alphaOnly")).toBool();
541
542 int requestedSamples = requestedRadius * 2 + 1;
543 int samples = 1 + requestedSamples / 2;
544 int radius = requestedSamples / 4;
545 bool fallback = parameters.property(QStringLiteral("fallback")).toBool();
546
547 QVariantMap result;
548
549 if (samples > m_maxBlurSamples || masked || fallback) {
550
551 if (m_coreProfile) {
552 result[QStringLiteral("fragmentShader")] = qgfx_fallbackCoreFragmentShader(requestedRadius, deviation, masked, alphaOnly);
553 result[QStringLiteral("vertexShader")] = qgfx_fallbackCoreVertexShader();
554 } else {
555 result[QStringLiteral("fragmentShader")] = qgfx_fallbackFragmentShader(requestedRadius, deviation, masked, alphaOnly);
556 result[QStringLiteral("vertexShader")] = qgfx_fallbackVertexShader();
557 }
558 return result;
559 }
560
561 QVarLengthArray<QGfxGaussSample, 64> p(samples);
562 qgfx_buildGaussSamplePoints(p: p.data(), samples, radius, deviation);
563
564 if (m_coreProfile) {
565 result[QStringLiteral("fragmentShader")] = qgfx_gaussianFragmentCoreShader(p: p.data(), samples, alphaOnly);
566 result[QStringLiteral("vertexShader")] = qgfx_gaussianVertexCoreShader(p: p.data(), samples);
567 } else {
568 result[QStringLiteral("fragmentShader")] = qgfx_gaussianFragmentShader(p: p.data(), samples, alphaOnly);
569 result[QStringLiteral("vertexShader")] = qgfx_gaussianVertexShader(p: p.data(), samples);
570 }
571 return result;
572}
573
574

source code of qtgraphicaleffects/src/effects/private/qgfxshaderbuilder.cpp