1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
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** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "glwindow.h"
52#include <QImage>
53#include <QOpenGLShaderProgram>
54#include <QOpenGLContext>
55#include <QOpenGLFunctions>
56#include <QOpenGLExtraFunctions>
57#include <QOpenGLVertexArrayObject>
58#include <QtGui/qopengl.h>
59#include <QDebug>
60#include <QTimer>
61#include <math.h>
62
63#ifndef GL_READ_WRITE
64#define GL_READ_WRITE 0x88BA
65#endif
66
67#ifndef GL_RGBA8
68#define GL_RGBA8 0x8058
69#endif
70
71#ifndef GL_SHADER_IMAGE_ACCESS_BARRIER_BIT
72#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020
73#endif
74
75GLWindow::GLWindow()
76{
77 const float animationStart = 0.0;
78 const float animationEnd = 10.0;
79 const float animationLength = 1000;
80
81 m_animationGroup = new QSequentialAnimationGroup(this);
82 m_animationGroup->setLoopCount(-1);
83
84 m_animationForward = new QPropertyAnimation(this, QByteArrayLiteral("blurRadius"));
85 m_animationForward->setStartValue(animationStart);
86 m_animationForward->setEndValue(animationEnd);
87 m_animationForward->setDuration(animationLength);
88 m_animationGroup->addAnimation(m_animationForward);
89
90 m_animationBackward = new QPropertyAnimation(this, QByteArrayLiteral("blurRadius"));
91 m_animationBackward->setStartValue(animationEnd);
92 m_animationBackward->setEndValue(animationStart);
93 m_animationBackward->setDuration(animationLength);
94 m_animationGroup->addAnimation(m_animationBackward);
95
96 m_animationGroup->start();
97}
98
99GLWindow::~GLWindow()
100{
101 makeCurrent();
102 delete m_texImageInput;
103 delete m_texImageProcessed;
104 delete m_texImageTmp;
105 delete m_shaderDisplay;
106 delete m_shaderComputeH;
107 delete m_shaderComputeV;
108 delete m_animationGroup;
109 delete m_animationForward;
110 delete m_animationBackward;
111 delete m_vao;
112}
113
114void GLWindow::setBlurRadius(float blurRadius)
115{
116 int radius = int(blurRadius);
117 if (radius != m_blurRadius) {
118 m_blurRadius = radius;
119 update();
120 }
121}
122
123void GLWindow::setAnimating(bool animate)
124{
125 m_animate = animate;
126 if (animate)
127 m_animationGroup->start();
128 else
129 m_animationGroup->stop();
130}
131
132void GLWindow::keyPressEvent(QKeyEvent *e)
133{
134 if (e->key() == Qt::Key_Space) { // pause
135 setAnimating(!m_animate);
136 }
137 update();
138}
139
140
141
142
143static const char *vsDisplaySource =
144 "const vec4 vertices[4] = vec4[4] (\n"
145 " vec4( -1.0, 1.0, 0.0, 1.0),\n"
146 " vec4( -1.0, -1.0, 0.0, 1.0),\n"
147 " vec4( 1.0, 1.0, 0.0, 1.0),\n"
148 " vec4( 1.0, -1.0, 0.0, 1.0)\n"
149 ");\n"
150 "const vec2 texCoords[4] = vec2[4] (\n"
151 " vec2( 0.0, 1.0),\n"
152 " vec2( 0.0, 0.0),\n"
153 " vec2( 1.0, 1.0),\n"
154 " vec2( 1.0, 0.0)\n"
155 ");\n"
156 "out vec2 texCoord;\n"
157 "uniform mat4 matProjection;\n"
158 "uniform vec2 imageRatio;\n"
159 "void main() {\n"
160 " gl_Position = matProjection * ( vertices[gl_VertexID] * vec4(imageRatio,0,1) );\n"
161 " texCoord = texCoords[gl_VertexID];\n"
162 "}\n";
163
164static const char *fsDisplaySource =
165 "in lowp vec2 texCoord; \n"
166 "uniform sampler2D samImage; \n"
167 "layout(location = 0) out lowp vec4 color;\n"
168 "void main() {\n"
169 " lowp vec4 texColor = texture(samImage,texCoord);\n"
170 " color = vec4(texColor.rgb, 1.0);\n"
171 "}\n";
172
173static const char *csComputeSourceV =
174 "#define COMPUTEPATCHSIZE 10 // Setting this to 10 to comply with MAX_COMPUTE_WORK_GROUP_INVOCATIONS for both OpenGL and OpenGLES - see QTBUG-79374 \n"
175 "#define IMGFMT rgba8 \n"
176 "layout (local_size_x = COMPUTEPATCHSIZE, local_size_y = COMPUTEPATCHSIZE) in;\n"
177 "layout(binding=0, IMGFMT) uniform readonly highp image2D inputImage; // Use a sampler to improve performance \n"
178 "layout(binding=1, IMGFMT) uniform writeonly highp image2D resultImage;\n"
179 "uniform int radius;\n"
180 "const float cutoff = 2.2;\n"
181
182 "float expFactor() { // a function, otherwise MESA produces error: initializer of global variable `expFactor' must be a constant expression\n"
183 " float sigma = clamp(float(radius) / cutoff,0.02,100.0);\n"
184 " return 1.0 / (2.0 * sigma * sigma);\n"
185 "}\n"
186
187 "float gaussian(float distance, float expfactor) {\n"
188 " return exp( -(distance * distance) * expfactor);\n"
189 "}\n"
190
191 "void main() {\n"
192 " ivec2 imgSize = imageSize(resultImage);\n"
193 " int x = int(gl_GlobalInvocationID.x);\n"
194 " int y = int(gl_GlobalInvocationID.y);\n"
195 " if ( (x >= imgSize.x) || (y >= imgSize.y) ) return;\n"
196 " vec4 sumPixels = vec4(0.0);\n"
197 " float sumWeights = 0.0;\n"
198 " int left = clamp(x - radius, 0, imgSize.x - 1);\n"
199 " int right = clamp(x + radius, 0, imgSize.x - 1);\n"
200 " int top = clamp(y - radius, 0, imgSize.y - 1);\n"
201 " int bottom = clamp(y + radius, 0, imgSize.y - 1);\n"
202 " float expfactor = expFactor();\n"
203 " for (int iY = top; iY <= bottom; iY++) {\n"
204 " float dy = float(abs(iY - y));\n"
205 " vec4 imgValue = imageLoad(inputImage, ivec2(x,iY));\n"
206 " float weight = gaussian(dy, expfactor);\n"
207 " sumWeights += weight;\n"
208 " sumPixels += (imgValue * weight);\n"
209 " }\n"
210 " sumPixels /= sumWeights;\n"
211 " imageStore(resultImage, ivec2(x,y), sumPixels);\n"
212 "}\n";
213
214static const char *csComputeSourceH =
215 "#define COMPUTEPATCHSIZE 10 \n"
216 "#define IMGFMT rgba8 \n"
217 "layout (local_size_x = COMPUTEPATCHSIZE, local_size_y = COMPUTEPATCHSIZE) in;\n"
218 "layout(binding=0, IMGFMT) uniform readonly highp image2D inputImage; // Use a sampler to improve performance \n"
219 "layout(binding=1, IMGFMT) uniform writeonly highp image2D resultImage;\n"
220 "uniform int radius;\n"
221 "const float cutoff = 2.2;\n"
222
223 "float expFactor() { // a function, otherwise MESA produces error: initializer of global variable `expFactor' must be a constant expression\n"
224 " float sigma = clamp(float(radius) / cutoff,0.02,100.0);\n"
225 " return 1.0 / (2.0 * sigma * sigma);\n"
226 "}\n"
227
228 "float gaussian(float distance, float expfactor) {\n"
229 " return exp( -(distance * distance) * expfactor);\n"
230 "}\n"
231
232 "void main() {\n"
233 " ivec2 imgSize = imageSize(resultImage);\n"
234 " int x = int(gl_GlobalInvocationID.x);\n"
235 " int y = int(gl_GlobalInvocationID.y);\n"
236 " if ( (x >= imgSize.x) || (y >= imgSize.y) ) return;\n"
237 " vec4 sumPixels = vec4(0.0);\n"
238 " float sumWeights = 0.0;\n"
239 " int left = clamp(x - radius, 0, imgSize.x - 1);\n"
240 " int right = clamp(x + radius, 0, imgSize.x - 1);\n"
241 " int top = clamp(y - radius, 0, imgSize.y - 1);\n"
242 " int bottom = clamp(y + radius, 0, imgSize.y - 1);\n"
243 " float expfactor = expFactor();\n"
244 " for (int iX = left; iX <= right; iX++) {\n"
245 " float dx = float(abs(iX - x));\n"
246 " vec4 imgValue = imageLoad(inputImage, ivec2(iX,y));\n"
247 " float weight = gaussian(dx, expfactor);\n"
248 " sumWeights += weight;\n"
249 " sumPixels += (imgValue * weight);\n"
250 " }\n"
251 " sumPixels /= sumWeights;\n"
252 " imageStore(resultImage, ivec2(x,y), sumPixels);\n"
253 "}\n";
254
255
256
257QByteArray versionedShaderCode(const char *src)
258{
259 QByteArray versionedSrc;
260
261 if (QOpenGLContext::currentContext()->isOpenGLES())
262 versionedSrc.append(QByteArrayLiteral("#version 310 es\n"));
263 else
264 versionedSrc.append(QByteArrayLiteral("#version 430 core\n"));
265
266 versionedSrc.append(src);
267 return versionedSrc;
268}
269
270void computeProjection(int winWidth, int winHeight, int imgWidth, int imgHeight, QMatrix4x4 &outProjection, QSizeF &outQuadSize)
271{
272 float ratioImg = float(imgWidth) / float(imgHeight);
273 float ratioCanvas = float(winWidth) / float(winHeight);
274
275 float correction = ratioImg / ratioCanvas;
276 float rescaleFactor = 1.0f;
277 float quadWidth = 1.0f;
278 float quadHeight = 1.0f;
279
280 if (correction < 1.0f) // canvas larger than image -- height = 1.0, vertical black bands
281 {
282 quadHeight = 1.0f;
283 quadWidth = 1.0f * ratioImg;
284 rescaleFactor = ratioCanvas;
285 correction = 1.0f / rescaleFactor;
286 }
287 else // image larger than canvas -- width = 1.0, horizontal black bands
288 {
289 quadWidth = 1.0f;
290 quadHeight = 1.0f / ratioImg;
291 correction = 1.0f / ratioCanvas;
292 }
293
294 const float frustumWidth = 1.0f * rescaleFactor;
295 const float frustumHeight = 1.0f * rescaleFactor * correction;
296
297 outProjection = QMatrix4x4();
298 outProjection.ortho(
299 -frustumWidth,
300 frustumWidth,
301 -frustumHeight,
302 frustumHeight,
303 -1.0f,
304 1.0f);
305 outQuadSize = QSizeF(quadWidth,quadHeight);
306}
307
308void GLWindow::initializeGL()
309{
310 QOpenGLContext *ctx = QOpenGLContext::currentContext();
311 qDebug() << "Got a "
312 << ctx->format().majorVersion()
313 << "."
314 << ctx->format().minorVersion()
315 << ((ctx->format().renderableType() == QSurfaceFormat::OpenGLES) ? (" GLES") : (" GL"))
316 << " context";
317
318 QImage img(":/Qt-logo-medium.png");
319 Q_ASSERT(!img.isNull());
320 delete m_texImageInput;
321 m_texImageInput = new QOpenGLTexture(img.convertToFormat(QImage::Format_RGBA8888).mirrored());
322
323 delete m_texImageTmp;
324 m_texImageTmp = new QOpenGLTexture(QOpenGLTexture::Target2D);
325 m_texImageTmp->setFormat(m_texImageInput->format());
326 m_texImageTmp->setSize(m_texImageInput->width(),m_texImageInput->height());
327 m_texImageTmp->allocateStorage(QOpenGLTexture::RGBA,QOpenGLTexture::UInt8); // WTF?
328
329 delete m_texImageProcessed;
330 m_texImageProcessed = new QOpenGLTexture(QOpenGLTexture::Target2D);
331 m_texImageProcessed->setFormat(m_texImageInput->format());
332 m_texImageProcessed->setSize(m_texImageInput->width(),m_texImageInput->height());
333 m_texImageProcessed->allocateStorage(QOpenGLTexture::RGBA,QOpenGLTexture::UInt8);
334
335 m_texImageProcessed->setMagnificationFilter(QOpenGLTexture::Linear);
336 m_texImageProcessed->setMinificationFilter(QOpenGLTexture::Linear);
337 m_texImageProcessed->setWrapMode(QOpenGLTexture::ClampToEdge);
338
339 delete m_shaderDisplay;
340 m_shaderDisplay = new QOpenGLShaderProgram;
341 // Prepend the correct version directive to the sources. The rest is the
342 // same, thanks to the common GLSL syntax.
343 m_shaderDisplay->addShaderFromSourceCode(QOpenGLShader::Vertex, versionedShaderCode(vsDisplaySource));
344 m_shaderDisplay->addShaderFromSourceCode(QOpenGLShader::Fragment, versionedShaderCode(fsDisplaySource));
345 m_shaderDisplay->link();
346
347 delete m_shaderComputeV;
348 m_shaderComputeV = new QOpenGLShaderProgram;
349 m_shaderComputeV->addShaderFromSourceCode(QOpenGLShader::Compute, versionedShaderCode(csComputeSourceV));
350 m_shaderComputeV->link();
351
352 delete m_shaderComputeH;
353 m_shaderComputeH = new QOpenGLShaderProgram;
354 m_shaderComputeH->addShaderFromSourceCode(QOpenGLShader::Compute, versionedShaderCode(csComputeSourceH));
355 m_shaderComputeH->link();
356
357 // Create a VAO. Not strictly required for ES 3, but it is for plain OpenGL core context.
358 m_vao = new QOpenGLVertexArrayObject;
359 m_vao->create();
360}
361
362void GLWindow::resizeGL(int w, int h)
363{
364 computeProjection(w,h,m_texImageInput->width(),m_texImageInput->height(),m_proj,m_quadSize);
365}
366
367QSize getWorkGroups(int workGroupSize, const QSize &imageSize)
368{
369 int x = imageSize.width();
370 x = (x % workGroupSize) ? (x / workGroupSize) + 1 : (x / workGroupSize);
371 int y = imageSize.height();
372 y = (y % workGroupSize) ? (y / workGroupSize) + 1 : (y / workGroupSize);
373 return QSize(x,y);
374}
375
376void GLWindow::paintGL()
377{
378 // Now use QOpenGLExtraFunctions instead of QOpenGLFunctions as we want to
379 // do more than what GL(ES) 2.0 offers.
380 QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
381
382
383 // Process input image
384 QSize workGroups = getWorkGroups(10, QSize(m_texImageInput->width(), m_texImageInput->height()));
385 // Pass 1
386 f->glBindImageTexture(0, m_texImageInput->textureId(), 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
387 f->glBindImageTexture(1, m_texImageTmp->textureId(), 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
388 m_shaderComputeV->bind();
389 m_shaderComputeV->setUniformValue("radius",m_blurRadius);
390 f->glDispatchCompute(workGroups.width(),workGroups.height(),1);
391 f->glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
392 m_shaderComputeV->release();
393 // Pass 2
394 f->glBindImageTexture(0, m_texImageTmp->textureId(), 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
395 f->glBindImageTexture(1, m_texImageProcessed->textureId(), 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
396 m_shaderComputeH->bind();
397 m_shaderComputeH->setUniformValue("radius",m_blurRadius);
398 f->glDispatchCompute(workGroups.width(),workGroups.height(),1);
399 f->glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
400 m_shaderComputeH->release();
401 // Compute cleanup
402 f->glBindImageTexture(0, 0, 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
403 f->glBindImageTexture(1, 0, 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
404
405 // Display processed image
406 f->glClearColor(0, 0, 0, 1);
407 f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
408 m_texImageProcessed->bind(0);
409 m_shaderDisplay->bind();
410 m_shaderDisplay->setUniformValue("matProjection",m_proj);
411 m_shaderDisplay->setUniformValue("imageRatio",m_quadSize);
412 m_shaderDisplay->setUniformValue("samImage",0);
413 m_vao->bind();
414 f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
415 m_vao->release();
416 m_shaderDisplay->release();
417 m_texImageProcessed->release(0);
418}
419
420