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