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 "qopengltextureblitter.h" |
41 | |
42 | #include <QtGui/QOpenGLBuffer> |
43 | #include <QtGui/QOpenGLShaderProgram> |
44 | #include <QtGui/QOpenGLVertexArrayObject> |
45 | #include <QtGui/QOpenGLContext> |
46 | #include <QtGui/QOpenGLFunctions> |
47 | #include <QtGui/QOpenGLExtraFunctions> |
48 | |
49 | #ifndef GL_TEXTURE_EXTERNAL_OES |
50 | #define GL_TEXTURE_EXTERNAL_OES 0x8D65 |
51 | #endif |
52 | #ifndef GL_TEXTURE_RECTANGLE |
53 | #define GL_TEXTURE_RECTANGLE 0x84F5 |
54 | #endif |
55 | #ifndef GL_TEXTURE_WIDTH |
56 | #define GL_TEXTURE_WIDTH 0x1000 |
57 | #endif |
58 | #ifndef GL_TEXTURE_HEIGHT |
59 | #define GL_TEXTURE_HEIGHT 0x1001 |
60 | #endif |
61 | |
62 | QT_BEGIN_NAMESPACE |
63 | |
64 | /*! |
65 | \class QOpenGLTextureBlitter |
66 | \brief The QOpenGLTextureBlitter class provides a convenient way to draw textured quads via OpenGL. |
67 | \since 5.8 |
68 | \ingroup painting-3D |
69 | \inmodule QtGui |
70 | |
71 | Drawing textured quads, in order to get the contents of a texture |
72 | onto the screen, is a common operation when developing 2D user |
73 | interfaces. QOpenGLTextureBlitter provides a convenience class to |
74 | avoid repeating vertex data, shader sources, buffer and program |
75 | management and matrix calculations. |
76 | |
77 | For example, a QOpenGLWidget subclass can do the following to draw |
78 | the contents rendered into a framebuffer at the pixel position \c{(x, y)}: |
79 | |
80 | \code |
81 | void OpenGLWidget::initializeGL() |
82 | { |
83 | m_blitter.create(); |
84 | m_fbo = new QOpenGLFramebufferObject(size); |
85 | } |
86 | |
87 | void OpenGLWidget::paintGL() |
88 | { |
89 | m_fbo->bind(); |
90 | // update offscreen content |
91 | m_fbo->release(); |
92 | |
93 | m_blitter.bind(); |
94 | const QRect targetRect(QPoint(x, y), m_fbo->size()); |
95 | const QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(targetRect, QRect(QPoint(0, 0), m_fbo->size())); |
96 | m_blitter.blit(m_fbo->texture(), target, QOpenGLTextureBlitter::OriginBottomLeft); |
97 | m_blitter.release(); |
98 | } |
99 | \endcode |
100 | |
101 | The blitter implements GLSL shaders both for GLSL 1.00 (suitable |
102 | for OpenGL (ES) 2.x and compatibility profiles of newer OpenGL |
103 | versions) and version 150 (suitable for core profile contexts with |
104 | OpenGL 3.2 and newer). |
105 | */ |
106 | |
107 | static const char vertex_shader150[] = |
108 | "#version 150 core\n" |
109 | "in vec3 vertexCoord;" |
110 | "in vec2 textureCoord;" |
111 | "out vec2 uv;" |
112 | "uniform mat4 vertexTransform;" |
113 | "uniform mat3 textureTransform;" |
114 | "void main() {" |
115 | " uv = (textureTransform * vec3(textureCoord,1.0)).xy;" |
116 | " gl_Position = vertexTransform * vec4(vertexCoord,1.0);" |
117 | "}" ; |
118 | |
119 | static const char fragment_shader150[] = |
120 | "#version 150 core\n" |
121 | "in vec2 uv;" |
122 | "out vec4 fragcolor;" |
123 | "uniform sampler2D textureSampler;" |
124 | "uniform bool swizzle;" |
125 | "uniform float opacity;" |
126 | "void main() {" |
127 | " vec4 tmpFragColor = texture(textureSampler, uv);" |
128 | " tmpFragColor.a *= opacity;" |
129 | " fragcolor = swizzle ? tmpFragColor.bgra : tmpFragColor;" |
130 | "}" ; |
131 | |
132 | static const char vertex_shader[] = |
133 | "attribute highp vec3 vertexCoord;" |
134 | "attribute highp vec2 textureCoord;" |
135 | "varying highp vec2 uv;" |
136 | "uniform highp mat4 vertexTransform;" |
137 | "uniform highp mat3 textureTransform;" |
138 | "void main() {" |
139 | " uv = (textureTransform * vec3(textureCoord,1.0)).xy;" |
140 | " gl_Position = vertexTransform * vec4(vertexCoord,1.0);" |
141 | "}" ; |
142 | |
143 | static const char fragment_shader[] = |
144 | "varying highp vec2 uv;" |
145 | "uniform sampler2D textureSampler;" |
146 | "uniform bool swizzle;" |
147 | "uniform highp float opacity;" |
148 | "void main() {" |
149 | " highp vec4 tmpFragColor = texture2D(textureSampler,uv);" |
150 | " tmpFragColor.a *= opacity;" |
151 | " gl_FragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;" |
152 | "}" ; |
153 | |
154 | static const char fragment_shader_external_oes[] = |
155 | "#extension GL_OES_EGL_image_external : require\n" |
156 | "varying highp vec2 uv;" |
157 | "uniform samplerExternalOES textureSampler;\n" |
158 | "uniform bool swizzle;" |
159 | "uniform highp float opacity;" |
160 | "void main() {" |
161 | " highp vec4 tmpFragColor = texture2D(textureSampler, uv);" |
162 | " tmpFragColor.a *= opacity;" |
163 | " gl_FragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;" |
164 | "}" ; |
165 | |
166 | static const char fragment_shader_rectangle[] = |
167 | "varying highp vec2 uv;" |
168 | "uniform sampler2DRect textureSampler;" |
169 | "uniform bool swizzle;" |
170 | "uniform highp float opacity;" |
171 | "void main() {" |
172 | " highp vec4 tmpFragColor = texture2DRect(textureSampler,uv);" |
173 | " tmpFragColor.a *= opacity;" |
174 | " gl_FragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;" |
175 | "}" ; |
176 | |
177 | static const char fragment_shader150_rectangle[] = |
178 | "#version 150 core\n" |
179 | "in vec2 uv;" |
180 | "out vec4 fragcolor;" |
181 | "uniform sampler2DRect textureSampler;" |
182 | "uniform bool swizzle;" |
183 | "uniform float opacity;" |
184 | "void main() {" |
185 | " vec4 tmpFragColor = texture(textureSampler, uv);" |
186 | " tmpFragColor.a *= opacity;" |
187 | " fragcolor = swizzle ? tmpFragColor.bgra : tmpFragColor;" |
188 | "}" ; |
189 | |
190 | static const GLfloat vertex_buffer_data[] = { |
191 | -1,-1, 0, |
192 | -1, 1, 0, |
193 | 1,-1, 0, |
194 | -1, 1, 0, |
195 | 1,-1, 0, |
196 | 1, 1, 0 |
197 | }; |
198 | |
199 | static const GLfloat texture_buffer_data[] = { |
200 | 0, 0, |
201 | 0, 1, |
202 | 1, 0, |
203 | 0, 1, |
204 | 1, 0, |
205 | 1, 1 |
206 | }; |
207 | |
208 | class TextureBinder |
209 | { |
210 | public: |
211 | TextureBinder(GLenum target, GLuint textureId) : m_target(target) |
212 | { |
213 | QOpenGLContext::currentContext()->functions()->glBindTexture(target: m_target, texture: textureId); |
214 | } |
215 | ~TextureBinder() |
216 | { |
217 | QOpenGLContext::currentContext()->functions()->glBindTexture(target: m_target, texture: 0); |
218 | } |
219 | |
220 | private: |
221 | GLenum m_target; |
222 | }; |
223 | |
224 | class QOpenGLTextureBlitterPrivate |
225 | { |
226 | public: |
227 | enum TextureMatrixUniform { |
228 | User, |
229 | Identity, |
230 | IdentityFlipped |
231 | }; |
232 | |
233 | enum ProgramIndex { |
234 | TEXTURE_2D, |
235 | TEXTURE_EXTERNAL_OES, |
236 | TEXTURE_RECTANGLE |
237 | }; |
238 | |
239 | QOpenGLTextureBlitterPrivate() : |
240 | swizzle(false), |
241 | opacity(1.0f), |
242 | vao(new QOpenGLVertexArrayObject), |
243 | currentTarget(TEXTURE_2D) |
244 | { } |
245 | |
246 | bool buildProgram(ProgramIndex idx, const char *vs, const char *fs); |
247 | |
248 | void blit(GLuint texture, const QMatrix4x4 &targetTransform, const QMatrix3x3 &sourceTransform); |
249 | void blit(GLuint texture, const QMatrix4x4 &targetTransform, QOpenGLTextureBlitter::Origin origin); |
250 | |
251 | QMatrix3x3 toTextureCoordinates(const QMatrix3x3 &sourceTransform) const; |
252 | |
253 | void prepareProgram(const QMatrix4x4 &); |
254 | |
255 | bool supportsRectangleTarget() const; |
256 | |
257 | QOpenGLBuffer vertexBuffer; |
258 | QOpenGLBuffer textureBuffer; |
259 | struct Program { |
260 | Program() : |
261 | vertexCoordAttribPos(0), |
262 | vertexTransformUniformPos(0), |
263 | textureCoordAttribPos(0), |
264 | textureTransformUniformPos(0), |
265 | swizzleUniformPos(0), |
266 | opacityUniformPos(0), |
267 | swizzle(false), |
268 | opacity(0.0f), |
269 | textureMatrixUniformState(User) |
270 | { } |
271 | QScopedPointer<QOpenGLShaderProgram> glProgram; |
272 | GLuint vertexCoordAttribPos; |
273 | GLuint ; |
274 | GLuint textureCoordAttribPos; |
275 | GLuint textureTransformUniformPos; |
276 | GLuint swizzleUniformPos; |
277 | GLuint opacityUniformPos; |
278 | bool swizzle; |
279 | float opacity; |
280 | TextureMatrixUniform textureMatrixUniformState; |
281 | } programs[3]; |
282 | bool swizzle; |
283 | float opacity; |
284 | QScopedPointer<QOpenGLVertexArrayObject> vao; |
285 | GLenum currentTarget; |
286 | }; |
287 | |
288 | static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GLenum target) |
289 | { |
290 | switch (target) { |
291 | case GL_TEXTURE_2D: |
292 | return QOpenGLTextureBlitterPrivate::TEXTURE_2D; |
293 | case GL_TEXTURE_EXTERNAL_OES: |
294 | return QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES; |
295 | case GL_TEXTURE_RECTANGLE: |
296 | return QOpenGLTextureBlitterPrivate::TEXTURE_RECTANGLE; |
297 | default: |
298 | qWarning(msg: "Unsupported texture target 0x%x" , target); |
299 | return QOpenGLTextureBlitterPrivate::TEXTURE_2D; |
300 | } |
301 | } |
302 | |
303 | void QOpenGLTextureBlitterPrivate::prepareProgram(const QMatrix4x4 &) |
304 | { |
305 | Program *program = &programs[targetToProgramIndex(target: currentTarget)]; |
306 | |
307 | vertexBuffer.bind(); |
308 | program->glProgram->setAttributeBuffer(location: program->vertexCoordAttribPos, GL_FLOAT, offset: 0, tupleSize: 3, stride: 0); |
309 | program->glProgram->enableAttributeArray(location: program->vertexCoordAttribPos); |
310 | vertexBuffer.release(); |
311 | |
312 | program->glProgram->setUniformValue(location: program->vertexTransformUniformPos, value: vertexTransform); |
313 | |
314 | textureBuffer.bind(); |
315 | program->glProgram->setAttributeBuffer(location: program->textureCoordAttribPos, GL_FLOAT, offset: 0, tupleSize: 2, stride: 0); |
316 | program->glProgram->enableAttributeArray(location: program->textureCoordAttribPos); |
317 | textureBuffer.release(); |
318 | |
319 | if (swizzle != program->swizzle) { |
320 | program->glProgram->setUniformValue(location: program->swizzleUniformPos, value: swizzle); |
321 | program->swizzle = swizzle; |
322 | } |
323 | |
324 | if (opacity != program->opacity) { |
325 | program->glProgram->setUniformValue(location: program->opacityUniformPos, value: opacity); |
326 | program->opacity = opacity; |
327 | } |
328 | } |
329 | |
330 | QMatrix3x3 QOpenGLTextureBlitterPrivate::toTextureCoordinates(const QMatrix3x3 &sourceTransform) const |
331 | { |
332 | if (currentTarget == GL_TEXTURE_RECTANGLE) { |
333 | // Non-normalized coordinates |
334 | QMatrix4x4 textureTransform(sourceTransform); |
335 | if (auto *glFunctions = QOpenGLContext::currentContext()->extraFunctions()) { |
336 | int width, height; |
337 | glFunctions->glGetTexLevelParameteriv(target: currentTarget, level: 0, GL_TEXTURE_WIDTH, params: &width); |
338 | glFunctions->glGetTexLevelParameteriv(target: currentTarget, level: 0, GL_TEXTURE_HEIGHT, params: &height); |
339 | textureTransform.scale(x: width, y: height); |
340 | } |
341 | return textureTransform.toGenericMatrix<3, 3>(); |
342 | } |
343 | |
344 | return sourceTransform; // Normalized coordinates |
345 | } |
346 | |
347 | void QOpenGLTextureBlitterPrivate::blit(GLuint texture, |
348 | const QMatrix4x4 &targetTransform, |
349 | const QMatrix3x3 &sourceTransform) |
350 | { |
351 | TextureBinder binder(currentTarget, texture); |
352 | prepareProgram(vertexTransform: targetTransform); |
353 | |
354 | Program *program = &programs[targetToProgramIndex(target: currentTarget)]; |
355 | |
356 | const QMatrix3x3 textureTransform = toTextureCoordinates(sourceTransform); |
357 | program->glProgram->setUniformValue(location: program->textureTransformUniformPos, value: textureTransform); |
358 | program->textureMatrixUniformState = User; |
359 | |
360 | QOpenGLContext::currentContext()->functions()->glDrawArrays(GL_TRIANGLES, first: 0, count: 6); |
361 | } |
362 | |
363 | void QOpenGLTextureBlitterPrivate::blit(GLuint texture, |
364 | const QMatrix4x4 &targetTransform, |
365 | QOpenGLTextureBlitter::Origin origin) |
366 | { |
367 | TextureBinder binder(currentTarget, texture); |
368 | prepareProgram(vertexTransform: targetTransform); |
369 | |
370 | Program *program = &programs[targetToProgramIndex(target: currentTarget)]; |
371 | |
372 | if (origin == QOpenGLTextureBlitter::OriginTopLeft) { |
373 | if (program->textureMatrixUniformState != IdentityFlipped) { |
374 | QMatrix3x3 sourceTransform; |
375 | sourceTransform(1,1) = -1; |
376 | sourceTransform(1,2) = 1; |
377 | const QMatrix3x3 textureTransform = toTextureCoordinates(sourceTransform); |
378 | program->glProgram->setUniformValue(location: program->textureTransformUniformPos, value: textureTransform); |
379 | program->textureMatrixUniformState = IdentityFlipped; |
380 | } |
381 | } else if (program->textureMatrixUniformState != Identity) { |
382 | const QMatrix3x3 textureTransform = toTextureCoordinates(sourceTransform: QMatrix3x3()); |
383 | program->glProgram->setUniformValue(location: program->textureTransformUniformPos, value: textureTransform); |
384 | program->textureMatrixUniformState = Identity; |
385 | } |
386 | |
387 | QOpenGLContext::currentContext()->functions()->glDrawArrays(GL_TRIANGLES, first: 0, count: 6); |
388 | } |
389 | |
390 | bool QOpenGLTextureBlitterPrivate::buildProgram(ProgramIndex idx, const char *vs, const char *fs) |
391 | { |
392 | Program *p = &programs[idx]; |
393 | |
394 | p->glProgram.reset(other: new QOpenGLShaderProgram); |
395 | |
396 | p->glProgram->addCacheableShaderFromSourceCode(type: QOpenGLShader::Vertex, source: vs); |
397 | p->glProgram->addCacheableShaderFromSourceCode(type: QOpenGLShader::Fragment, source: fs); |
398 | p->glProgram->link(); |
399 | if (!p->glProgram->isLinked()) { |
400 | qWarning() << "Could not link shader program:\n" << p->glProgram->log(); |
401 | return false; |
402 | } |
403 | |
404 | p->glProgram->bind(); |
405 | |
406 | p->vertexCoordAttribPos = p->glProgram->attributeLocation(name: "vertexCoord" ); |
407 | p->vertexTransformUniformPos = p->glProgram->uniformLocation(name: "vertexTransform" ); |
408 | p->textureCoordAttribPos = p->glProgram->attributeLocation(name: "textureCoord" ); |
409 | p->textureTransformUniformPos = p->glProgram->uniformLocation(name: "textureTransform" ); |
410 | p->swizzleUniformPos = p->glProgram->uniformLocation(name: "swizzle" ); |
411 | p->opacityUniformPos = p->glProgram->uniformLocation(name: "opacity" ); |
412 | |
413 | p->glProgram->setUniformValue(location: p->swizzleUniformPos, value: false); |
414 | |
415 | // minmize state left set after a create() |
416 | p->glProgram->release(); |
417 | |
418 | return true; |
419 | } |
420 | |
421 | /*! |
422 | Constructs a new QOpenGLTextureBlitter instance. |
423 | |
424 | \note no graphics resources are initialized in the |
425 | constructor. This makes it safe to place plain |
426 | QOpenGLTextureBlitter members into classes because the actual |
427 | initialization that depends on the OpenGL context happens only in |
428 | create(). |
429 | */ |
430 | QOpenGLTextureBlitter::QOpenGLTextureBlitter() |
431 | : d_ptr(new QOpenGLTextureBlitterPrivate) |
432 | { |
433 | } |
434 | |
435 | /*! |
436 | Destructs the instance. |
437 | |
438 | \note When the OpenGL context - or a context sharing resources |
439 | with it - that was current when calling create() is not current, |
440 | graphics resources will not be released. Therefore, it is |
441 | recommended to call destroy() manually instead of relying on the |
442 | destructor to perform OpenGL resource cleanup. |
443 | */ |
444 | QOpenGLTextureBlitter::~QOpenGLTextureBlitter() |
445 | { |
446 | destroy(); |
447 | } |
448 | |
449 | /*! |
450 | Initializes the graphics resources used by the blitter. |
451 | |
452 | \return \c true if successful, \c false if there was a |
453 | failure. Failures can occur when there is no OpenGL context |
454 | current on the current thread, or when shader compilation fails |
455 | for some reason. |
456 | |
457 | \sa isCreated(), destroy() |
458 | */ |
459 | bool QOpenGLTextureBlitter::create() |
460 | { |
461 | QOpenGLContext *currentContext = QOpenGLContext::currentContext(); |
462 | if (!currentContext) |
463 | return false; |
464 | |
465 | Q_D(QOpenGLTextureBlitter); |
466 | |
467 | if (d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D].glProgram) |
468 | return true; |
469 | |
470 | QSurfaceFormat format = currentContext->format(); |
471 | if (format.profile() == QSurfaceFormat::CoreProfile && format.version() >= qMakePair(x: 3,y: 2)) { |
472 | if (!d->buildProgram(idx: QOpenGLTextureBlitterPrivate::TEXTURE_2D, vs: vertex_shader150, fs: fragment_shader150)) |
473 | return false; |
474 | if (d->supportsRectangleTarget()) |
475 | if (!d->buildProgram(idx: QOpenGLTextureBlitterPrivate::TEXTURE_RECTANGLE, vs: vertex_shader150, fs: fragment_shader150_rectangle)) |
476 | return false; |
477 | } else { |
478 | if (!d->buildProgram(idx: QOpenGLTextureBlitterPrivate::TEXTURE_2D, vs: vertex_shader, fs: fragment_shader)) |
479 | return false; |
480 | if (supportsExternalOESTarget()) |
481 | if (!d->buildProgram(idx: QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES, vs: vertex_shader, fs: fragment_shader_external_oes)) |
482 | return false; |
483 | if (d->supportsRectangleTarget()) |
484 | if (!d->buildProgram(idx: QOpenGLTextureBlitterPrivate::TEXTURE_RECTANGLE, vs: vertex_shader, fs: fragment_shader_rectangle)) |
485 | return false; |
486 | } |
487 | |
488 | // Create and bind the VAO, if supported. |
489 | QOpenGLVertexArrayObject::Binder vaoBinder(d->vao.data()); |
490 | |
491 | d->vertexBuffer.create(); |
492 | d->vertexBuffer.bind(); |
493 | d->vertexBuffer.allocate(data: vertex_buffer_data, count: sizeof(vertex_buffer_data)); |
494 | d->vertexBuffer.release(); |
495 | |
496 | d->textureBuffer.create(); |
497 | d->textureBuffer.bind(); |
498 | d->textureBuffer.allocate(data: texture_buffer_data, count: sizeof(texture_buffer_data)); |
499 | d->textureBuffer.release(); |
500 | |
501 | return true; |
502 | } |
503 | |
504 | /*! |
505 | \return \c true if create() was called and succeeded. \c false otherwise. |
506 | |
507 | \sa create(), destroy() |
508 | */ |
509 | bool QOpenGLTextureBlitter::isCreated() const |
510 | { |
511 | Q_D(const QOpenGLTextureBlitter); |
512 | return d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D].glProgram; |
513 | } |
514 | |
515 | /*! |
516 | Frees all graphics resources held by the blitter. Assumes that |
517 | the OpenGL context, or another context sharing resources with it, |
518 | that was current on the thread when invoking create() is current. |
519 | |
520 | The function has no effect when the blitter is not in created state. |
521 | |
522 | \sa create() |
523 | */ |
524 | void QOpenGLTextureBlitter::destroy() |
525 | { |
526 | if (!isCreated()) |
527 | return; |
528 | Q_D(QOpenGLTextureBlitter); |
529 | d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D].glProgram.reset(); |
530 | d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES].glProgram.reset(); |
531 | d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_RECTANGLE].glProgram.reset(); |
532 | d->vertexBuffer.destroy(); |
533 | d->textureBuffer.destroy(); |
534 | d->vao.reset(); |
535 | } |
536 | |
537 | /*! |
538 | \return \c true when bind() accepts \c GL_TEXTURE_EXTERNAL_OES as |
539 | its target argument. |
540 | |
541 | \sa bind(), blit() |
542 | */ |
543 | bool QOpenGLTextureBlitter::supportsExternalOESTarget() const |
544 | { |
545 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
546 | return ctx && ctx->isOpenGLES() && ctx->hasExtension(extension: "GL_OES_EGL_image_external" ); |
547 | } |
548 | |
549 | /*! |
550 | \return \c true when bind() accepts \c GL_TEXTURE_RECTANGLE as |
551 | its target argument. |
552 | |
553 | \sa bind(), blit() |
554 | */ |
555 | bool QOpenGLTextureBlitterPrivate::supportsRectangleTarget() const |
556 | { |
557 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
558 | if (!ctx || ctx->isOpenGLES()) |
559 | return false; |
560 | |
561 | if (ctx->hasExtension(extension: "GL_ARB_texture_rectangle" )) |
562 | return true; |
563 | |
564 | if (ctx->hasExtension(extension: "GL_EXT_texture_rectangle" )) |
565 | return true; |
566 | |
567 | QSurfaceFormat f = ctx->format(); |
568 | const auto version = qMakePair(x: f.majorVersion(), y: f.minorVersion()); |
569 | if (version >= qMakePair(x: 3, y: 1)) |
570 | return true; |
571 | |
572 | return false; |
573 | } |
574 | |
575 | /*! |
576 | Binds the graphics resources used by the blitter. This must be |
577 | called before calling blit(). Code modifying the OpenGL state |
578 | should be avoided between the call to bind() and blit() because |
579 | otherwise conflicts may arise. |
580 | |
581 | \a target is the texture target for the source texture and must be |
582 | either \c GL_TEXTURE_2D, \c GL_TEXTURE_RECTANGLE, or \c GL_OES_EGL_image_external. |
583 | |
584 | \sa release(), blit() |
585 | */ |
586 | void QOpenGLTextureBlitter::bind(GLenum target) |
587 | { |
588 | Q_D(QOpenGLTextureBlitter); |
589 | |
590 | if (d->vao->isCreated()) |
591 | d->vao->bind(); |
592 | |
593 | d->currentTarget = target; |
594 | QOpenGLTextureBlitterPrivate::Program *p = &d->programs[targetToProgramIndex(target)]; |
595 | p->glProgram->bind(); |
596 | |
597 | d->vertexBuffer.bind(); |
598 | p->glProgram->setAttributeBuffer(location: p->vertexCoordAttribPos, GL_FLOAT, offset: 0, tupleSize: 3, stride: 0); |
599 | p->glProgram->enableAttributeArray(location: p->vertexCoordAttribPos); |
600 | d->vertexBuffer.release(); |
601 | |
602 | d->textureBuffer.bind(); |
603 | p->glProgram->setAttributeBuffer(location: p->textureCoordAttribPos, GL_FLOAT, offset: 0, tupleSize: 2, stride: 0); |
604 | p->glProgram->enableAttributeArray(location: p->textureCoordAttribPos); |
605 | d->textureBuffer.release(); |
606 | } |
607 | |
608 | /*! |
609 | Unbinds the graphics resources used by the blitter. |
610 | |
611 | \sa bind() |
612 | */ |
613 | void QOpenGLTextureBlitter::release() |
614 | { |
615 | Q_D(QOpenGLTextureBlitter); |
616 | d->programs[targetToProgramIndex(target: d->currentTarget)].glProgram->release(); |
617 | if (d->vao->isCreated()) |
618 | d->vao->release(); |
619 | } |
620 | |
621 | /*! |
622 | Sets whether swizzling is enabled for the red and blue color channels to |
623 | \a swizzle. An BGRA to RGBA conversion (occurring in the shader on |
624 | the GPU, instead of a slow CPU-side transformation) can be useful |
625 | when the source texture contains data from a QImage with a format |
626 | like QImage::Format_ARGB32 which maps to BGRA on little endian |
627 | systems. |
628 | |
629 | By default the red-blue swizzle is disabled since this is what a |
630 | texture attached to an framebuffer object or a texture based on a |
631 | byte ordered QImage format (like QImage::Format_RGBA8888) needs. |
632 | */ |
633 | void QOpenGLTextureBlitter::setRedBlueSwizzle(bool swizzle) |
634 | { |
635 | Q_D(QOpenGLTextureBlitter); |
636 | d->swizzle = swizzle; |
637 | } |
638 | |
639 | /*! |
640 | Changes the opacity to \a opacity. The default opacity is 1.0. |
641 | |
642 | \note the blitter does not alter the blend state. It is up to the |
643 | caller of blit() to ensure the correct blend settings are active. |
644 | |
645 | */ |
646 | void QOpenGLTextureBlitter::setOpacity(float opacity) |
647 | { |
648 | Q_D(QOpenGLTextureBlitter); |
649 | d->opacity = opacity; |
650 | } |
651 | |
652 | /*! |
653 | \enum QOpenGLTextureBlitter::Origin |
654 | |
655 | \value OriginBottomLeft Indicates that the data in the texture |
656 | follows the OpenGL convention of coordinate systems, meaning Y is |
657 | running from bottom to top. |
658 | |
659 | \value OriginTopLeft Indicates that the data in the texture has Y |
660 | running from top to bottom, which is typical with regular, |
661 | unflipped image data. |
662 | |
663 | \sa blit() |
664 | */ |
665 | |
666 | /*! |
667 | Performs the blit with the source texture \a texture. |
668 | |
669 | \a targetTransform specifies the transformation applied. This is |
670 | usually generated by the targetTransform() helper function. |
671 | |
672 | \a sourceOrigin specifies if the image data needs flipping. When |
673 | \a texture corresponds to a texture attached to an FBO pass |
674 | OriginBottomLeft. On the other hand, when \a texture is based on |
675 | unflipped image data, pass OriginTopLeft. This is more efficient |
676 | than using QImage::mirrored(). |
677 | |
678 | \sa targetTransform(), Origin, bind() |
679 | */ |
680 | void QOpenGLTextureBlitter::blit(GLuint texture, |
681 | const QMatrix4x4 &targetTransform, |
682 | Origin sourceOrigin) |
683 | { |
684 | Q_D(QOpenGLTextureBlitter); |
685 | d->blit(texture, targetTransform, origin: sourceOrigin); |
686 | } |
687 | |
688 | /*! |
689 | Performs the blit with the source texture \a texture. |
690 | |
691 | \a targetTransform specifies the transformation applied. This is |
692 | usually generated by the targetTransform() helper function. |
693 | |
694 | \a sourceTransform specifies the transformation applied to the |
695 | source. This allows using only a sub-rect of the source |
696 | texture. This is usually generated by the sourceTransform() helper |
697 | function. |
698 | |
699 | \sa sourceTransform(), targetTransform(), Origin, bind() |
700 | */ |
701 | void QOpenGLTextureBlitter::blit(GLuint texture, |
702 | const QMatrix4x4 &targetTransform, |
703 | const QMatrix3x3 &sourceTransform) |
704 | { |
705 | Q_D(QOpenGLTextureBlitter); |
706 | d->blit(texture, targetTransform, sourceTransform); |
707 | } |
708 | |
709 | /*! |
710 | Calculates a target transform suitable for blit(). |
711 | |
712 | \a target is the target rectangle in pixels. \a viewport describes |
713 | the source dimensions and will in most cases be set to (0, 0, |
714 | image width, image height). |
715 | |
716 | For unscaled output the size of \a target and \a viewport should |
717 | match. |
718 | |
719 | \sa blit() |
720 | */ |
721 | QMatrix4x4 QOpenGLTextureBlitter::targetTransform(const QRectF &target, |
722 | const QRect &viewport) |
723 | { |
724 | qreal x_scale = target.width() / viewport.width(); |
725 | qreal y_scale = target.height() / viewport.height(); |
726 | |
727 | const QPointF relative_to_viewport = target.topLeft() - viewport.topLeft(); |
728 | qreal x_translate = x_scale - 1 + ((relative_to_viewport.x() / viewport.width()) * 2); |
729 | qreal y_translate = -y_scale + 1 - ((relative_to_viewport.y() / viewport.height()) * 2); |
730 | |
731 | QMatrix4x4 matrix; |
732 | matrix(0,3) = x_translate; |
733 | matrix(1,3) = y_translate; |
734 | |
735 | matrix(0,0) = x_scale; |
736 | matrix(1,1) = y_scale; |
737 | |
738 | return matrix; |
739 | } |
740 | |
741 | /*! |
742 | Calculates a 3x3 matrix suitable as the input to blit(). This is |
743 | used when only a part of the texture is to be used in the blit. |
744 | |
745 | \a subTexture is the desired source rectangle in pixels, \a |
746 | textureSize is the full width and height of the texture data. \a |
747 | origin specifies the orientation of the image data when it comes |
748 | to the Y axis. |
749 | |
750 | \sa blit(), Origin |
751 | */ |
752 | QMatrix3x3 QOpenGLTextureBlitter::sourceTransform(const QRectF &subTexture, |
753 | const QSize &textureSize, |
754 | Origin origin) |
755 | { |
756 | qreal x_scale = subTexture.width() / textureSize.width(); |
757 | qreal y_scale = subTexture.height() / textureSize.height(); |
758 | |
759 | const QPointF topLeft = subTexture.topLeft(); |
760 | qreal x_translate = topLeft.x() / textureSize.width(); |
761 | qreal y_translate = topLeft.y() / textureSize.height(); |
762 | |
763 | if (origin == OriginTopLeft) { |
764 | y_scale = -y_scale; |
765 | y_translate = 1 - y_translate; |
766 | } |
767 | |
768 | QMatrix3x3 matrix; |
769 | matrix(0,2) = x_translate; |
770 | matrix(1,2) = y_translate; |
771 | |
772 | matrix(0,0) = x_scale; |
773 | matrix(1,1) = y_scale; |
774 | |
775 | return matrix; |
776 | } |
777 | |
778 | QT_END_NAMESPACE |
779 | |