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/*
41 When the active program changes, we need to update it's uniforms.
42 We could track state for each program and only update stale uniforms
43 - Could lead to lots of overhead if there's a lot of programs
44 We could update all the uniforms when the program changes
45 - Could end up updating lots of uniforms which don't need updating
46
47 Updating uniforms should be cheap, so the overhead of updating up-to-date
48 uniforms should be minimal. It's also less complex.
49
50 Things which _may_ cause a different program to be used:
51 - Change in brush/pen style
52 - Change in painter opacity
53 - Change in composition mode
54
55 Whenever we set a mode on the shader manager - it needs to tell us if it had
56 to switch to a different program.
57
58 The shader manager should only switch when we tell it to. E.g. if we set a new
59 brush style and then switch to transparent painter, we only want it to compile
60 and use the correct program when we really need it.
61*/
62
63// #define QT_OPENGL_CACHE_AS_VBOS
64
65#include "qopenglgradientcache_p.h"
66#include "qopengltexturecache_p.h"
67#include "qopenglpaintengine_p.h"
68#include "qopenglpaintdevice_p.h"
69
70#include <string.h> //for memcpy
71#include <qmath.h>
72
73#include <private/qopengl_p.h>
74#include <private/qopenglcontext_p.h>
75#include <private/qopenglextensions_p.h>
76#include <private/qpaintengineex_p.h>
77#include <QPaintEngine>
78#include <private/qpainter_p.h>
79#include <private/qfontengine_p.h>
80#include <private/qdatabuffer_p.h>
81#include <private/qstatictext_p.h>
82#include <private/qtriangulator_p.h>
83
84#include "qopenglengineshadermanager_p.h"
85#include "qopengl2pexvertexarray_p.h"
86#include "qopengltextureglyphcache_p.h"
87
88#include <QDebug>
89
90#ifndef GL_KHR_blend_equation_advanced
91#define GL_KHR_blend_equation_advanced 1
92#define GL_MULTIPLY_KHR 0x9294
93#define GL_SCREEN_KHR 0x9295
94#define GL_OVERLAY_KHR 0x9296
95#define GL_DARKEN_KHR 0x9297
96#define GL_LIGHTEN_KHR 0x9298
97#define GL_COLORDODGE_KHR 0x9299
98#define GL_COLORBURN_KHR 0x929A
99#define GL_HARDLIGHT_KHR 0x929B
100#define GL_SOFTLIGHT_KHR 0x929C
101#define GL_DIFFERENCE_KHR 0x929E
102#define GL_EXCLUSION_KHR 0x92A0
103#endif /* GL_KHR_blend_equation_advanced */
104
105#ifndef GL_KHR_blend_equation_advanced_coherent
106#define GL_KHR_blend_equation_advanced_coherent 1
107#define GL_BLEND_ADVANCED_COHERENT_KHR 0x9285
108#endif /* GL_KHR_blend_equation_advanced_coherent */
109
110QT_BEGIN_NAMESPACE
111
112
113Q_GUI_EXPORT QImage qt_imageForBrush(int brushStyle, bool invert);
114
115////////////////////////////////// Private Methods //////////////////////////////////////////
116
117QOpenGL2PaintEngineExPrivate::~QOpenGL2PaintEngineExPrivate()
118{
119 delete shaderManager;
120
121 vertexBuffer.destroy();
122 texCoordBuffer.destroy();
123 opacityBuffer.destroy();
124 indexBuffer.destroy();
125 vao.destroy();
126
127 if (elementIndicesVBOId != 0) {
128 funcs.glDeleteBuffers(1, &elementIndicesVBOId);
129 elementIndicesVBOId = 0;
130 }
131}
132
133inline QColor qt_premultiplyColor(QColor c, GLfloat opacity)
134{
135 qreal alpha = c.alphaF() * opacity;
136 c.setAlphaF(alpha);
137 c.setRedF(c.redF() * alpha);
138 c.setGreenF(c.greenF() * alpha);
139 c.setBlueF(c.blueF() * alpha);
140 return c;
141}
142
143
144void QOpenGL2PaintEngineExPrivate::setBrush(const QBrush& brush)
145{
146 if (qbrush_fast_equals(currentBrush, brush))
147 return;
148
149 const Qt::BrushStyle newStyle = qbrush_style(brush);
150 Q_ASSERT(newStyle != Qt::NoBrush);
151
152 currentBrush = brush;
153 if (!currentBrushImage.isNull())
154 currentBrushImage = QImage();
155 brushUniformsDirty = true; // All brushes have at least one uniform
156
157 if (newStyle > Qt::SolidPattern)
158 brushTextureDirty = true;
159
160 if (currentBrush.style() == Qt::TexturePattern
161 && qHasPixmapTexture(brush) && brush.texture().isQBitmap())
162 {
163 shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::TextureSrcWithPattern);
164 } else {
165 shaderManager->setSrcPixelType(newStyle);
166 }
167 shaderManager->optimiseForBrushTransform(currentBrush.transform().type());
168}
169
170
171void QOpenGL2PaintEngineExPrivate::useSimpleShader()
172{
173 shaderManager->useSimpleProgram();
174
175 if (matrixDirty)
176 updateMatrix();
177}
178
179/*
180 Single entry-point for activating, binding, and setting properties.
181
182 Allows keeping track of (caching) the latest texture unit and bound
183 texture in a central place, so that we can skip re-binding unless
184 needed.
185
186 \note Any code or Qt API that internally activates or binds will
187 not affect the cache used by this function, which means they will
188 lead to inconsisent state. QPainter::beginNativePainting() takes
189 care of resetting the cache, so for user–code this is fine, but
190 internally in the paint engine care must be taken to not call
191 functions that may activate or bind under our feet.
192*/
193template<typename T>
194void QOpenGL2PaintEngineExPrivate::updateTexture(GLenum textureUnit, const T &texture, GLenum wrapMode, GLenum filterMode, TextureUpdateMode updateMode)
195{
196 static const GLenum target = GL_TEXTURE_2D;
197
198 activateTextureUnit(textureUnit);
199
200 GLuint textureId = bindTexture(texture);
201
202 if (updateMode == UpdateIfNeeded && textureId == lastTextureUsed)
203 return;
204
205 lastTextureUsed = textureId;
206
207 funcs.glTexParameteri(target, GL_TEXTURE_WRAP_S, wrapMode);
208 funcs.glTexParameteri(target, GL_TEXTURE_WRAP_T, wrapMode);
209
210 funcs.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filterMode);
211 funcs.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filterMode);
212}
213
214void QOpenGL2PaintEngineExPrivate::activateTextureUnit(GLenum textureUnit)
215{
216 if (textureUnit != lastTextureUnitUsed) {
217 funcs.glActiveTexture(GL_TEXTURE0 + textureUnit);
218 lastTextureUnitUsed = textureUnit;
219
220 // We simplify things by keeping a single cached value of the last
221 // texture that was bound, instead of one per texture unit. This
222 // means that switching texture units could potentially mean we
223 // need a re-bind and corresponding parameter updates.
224 lastTextureUsed = GLuint(-1);
225 }
226}
227
228template<>
229GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const GLuint &textureId)
230{
231 if (textureId != lastTextureUsed)
232 funcs.glBindTexture(GL_TEXTURE_2D, textureId);
233
234 return textureId;
235}
236
237template<>
238GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const QImage &image)
239{
240 return QOpenGLTextureCache::cacheForContext(ctx)->bindTexture(ctx, image);
241}
242
243template<>
244GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const QPixmap &pixmap)
245{
246 return QOpenGLTextureCache::cacheForContext(ctx)->bindTexture(ctx, pixmap);
247}
248
249template<>
250GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const QGradient &gradient)
251{
252 // We apply global opacity in the fragment shaders, so we always pass 1.0
253 // for opacity to the cache.
254 GLuint textureId = QOpenGL2GradientCache::cacheForContext(ctx)->getBuffer(gradient, 1.0);
255
256 // QOpenGL2GradientCache::getBuffer() may bind and generate a new texture if it
257 // hasn't been cached yet, but will otherwise return an unbound texture id. To
258 // be sure that the texture is bound, we unfortunately have to bind again,
259 // which results in the initial generation of the texture doing two binds.
260 return bindTexture(textureId);
261}
262
263struct ImageWithBindOptions
264{
265 const QImage &image;
266 QOpenGLTextureUploader::BindOptions options;
267};
268
269template<>
270GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const ImageWithBindOptions &imageWithOptions)
271{
272 return QOpenGLTextureCache::cacheForContext(ctx)->bindTexture(ctx, imageWithOptions.image, imageWithOptions.options);
273}
274
275inline static bool isPowerOfTwo(int x)
276{
277 // Assumption: x >= 1
278 return x == (x & -x);
279}
280
281void QOpenGL2PaintEngineExPrivate::updateBrushTexture()
282{
283 Q_Q(QOpenGL2PaintEngineEx);
284// qDebug("QOpenGL2PaintEngineExPrivate::updateBrushTexture()");
285 Qt::BrushStyle style = currentBrush.style();
286
287 bool smoothPixmapTransform = q->state()->renderHints & QPainter::SmoothPixmapTransform;
288 GLenum filterMode = smoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
289
290 if ( (style >= Qt::Dense1Pattern) && (style <= Qt::DiagCrossPattern) ) {
291 // Get the image data for the pattern
292 QImage textureImage = qt_imageForBrush(style, false);
293
294 updateTexture(QT_BRUSH_TEXTURE_UNIT, textureImage, GL_REPEAT, filterMode, ForceUpdate);
295 }
296 else if (style >= Qt::LinearGradientPattern && style <= Qt::ConicalGradientPattern) {
297 // Gradiant brush: All the gradiants use the same texture
298
299 const QGradient *gradient = currentBrush.gradient();
300
301 GLenum wrapMode = GL_CLAMP_TO_EDGE;
302 if (gradient->spread() == QGradient::RepeatSpread || gradient->type() == QGradient::ConicalGradient)
303 wrapMode = GL_REPEAT;
304 else if (gradient->spread() == QGradient::ReflectSpread)
305 wrapMode = GL_MIRRORED_REPEAT;
306
307 updateTexture(QT_BRUSH_TEXTURE_UNIT, *gradient, wrapMode, filterMode, ForceUpdate);
308 }
309 else if (style == Qt::TexturePattern) {
310 currentBrushImage = currentBrush.textureImage();
311
312 int max_texture_size = ctx->d_func()->maxTextureSize();
313 QSize newSize = currentBrushImage.size();
314 newSize = newSize.boundedTo(QSize(max_texture_size, max_texture_size));
315 if (!QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(QOpenGLFunctions::NPOTTextureRepeat)) {
316 if (!isPowerOfTwo(newSize.width()) || !isPowerOfTwo(newSize.height())) {
317 newSize.setHeight(qNextPowerOfTwo(newSize.height() - 1));
318 newSize.setWidth(qNextPowerOfTwo(newSize.width() - 1));
319 }
320 }
321 if (currentBrushImage.size() != newSize)
322 currentBrushImage = currentBrushImage.scaled(newSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
323
324 GLuint wrapMode = GL_REPEAT;
325
326 updateTexture(QT_BRUSH_TEXTURE_UNIT, currentBrushImage, wrapMode, filterMode, ForceUpdate);
327 }
328 brushTextureDirty = false;
329}
330
331
332void QOpenGL2PaintEngineExPrivate::updateBrushUniforms()
333{
334// qDebug("QOpenGL2PaintEngineExPrivate::updateBrushUniforms()");
335 Qt::BrushStyle style = currentBrush.style();
336
337 if (style == Qt::NoBrush)
338 return;
339
340 QTransform brushQTransform = currentBrush.transform();
341
342 if (style == Qt::SolidPattern) {
343 QColor col = qt_premultiplyColor(currentBrush.color(), (GLfloat)q->state()->opacity);
344 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::FragmentColor), col);
345 }
346 else {
347 // All other brushes have a transform and thus need the translation point:
348 QPointF translationPoint;
349
350 if (style <= Qt::DiagCrossPattern) {
351 QColor col = qt_premultiplyColor(currentBrush.color(), (GLfloat)q->state()->opacity);
352
353 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::PatternColor), col);
354
355 QVector2D halfViewportSize(width*0.5, height*0.5);
356 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::HalfViewportSize), halfViewportSize);
357 }
358 else if (style == Qt::LinearGradientPattern) {
359 const QLinearGradient *g = static_cast<const QLinearGradient *>(currentBrush.gradient());
360
361 QPointF realStart = g->start();
362 QPointF realFinal = g->finalStop();
363 translationPoint = realStart;
364
365 QPointF l = realFinal - realStart;
366
367 QVector3D linearData(
368 l.x(),
369 l.y(),
370 1.0f / (l.x() * l.x() + l.y() * l.y())
371 );
372
373 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::LinearData), linearData);
374
375 QVector2D halfViewportSize(width*0.5, height*0.5);
376 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::HalfViewportSize), halfViewportSize);
377 }
378 else if (style == Qt::ConicalGradientPattern) {
379 const QConicalGradient *g = static_cast<const QConicalGradient *>(currentBrush.gradient());
380 translationPoint = g->center();
381
382 GLfloat angle = -qDegreesToRadians(g->angle());
383
384 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::Angle), angle);
385
386 QVector2D halfViewportSize(width*0.5, height*0.5);
387 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::HalfViewportSize), halfViewportSize);
388 }
389 else if (style == Qt::RadialGradientPattern) {
390 const QRadialGradient *g = static_cast<const QRadialGradient *>(currentBrush.gradient());
391 QPointF realCenter = g->center();
392 QPointF realFocal = g->focalPoint();
393 qreal realRadius = g->centerRadius() - g->focalRadius();
394 translationPoint = realFocal;
395
396 QPointF fmp = realCenter - realFocal;
397 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::Fmp), fmp);
398
399 GLfloat fmp2_m_radius2 = -fmp.x() * fmp.x() - fmp.y() * fmp.y() + realRadius*realRadius;
400 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::Fmp2MRadius2), fmp2_m_radius2);
401 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::Inverse2Fmp2MRadius2),
402 GLfloat(1.0 / (2.0*fmp2_m_radius2)));
403 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::SqrFr),
404 GLfloat(g->focalRadius() * g->focalRadius()));
405 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::BRadius),
406 GLfloat(2 * (g->centerRadius() - g->focalRadius()) * g->focalRadius()),
407 g->focalRadius(),
408 g->centerRadius() - g->focalRadius());
409
410 QVector2D halfViewportSize(width*0.5, height*0.5);
411 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::HalfViewportSize), halfViewportSize);
412 }
413 else if (style == Qt::TexturePattern) {
414 const QPixmap& texPixmap = currentBrush.texture();
415
416 if (qHasPixmapTexture(currentBrush) && currentBrush.texture().isQBitmap()) {
417 QColor col = qt_premultiplyColor(currentBrush.color(), (GLfloat)q->state()->opacity);
418 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::PatternColor), col);
419 }
420
421 QSizeF invertedTextureSize(1.0 / texPixmap.width(), 1.0 / texPixmap.height());
422 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::InvertedTextureSize), invertedTextureSize);
423
424 QVector2D halfViewportSize(width*0.5, height*0.5);
425 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::HalfViewportSize), halfViewportSize);
426 }
427 else
428 qWarning("QOpenGL2PaintEngineEx: Unimplemented fill style");
429
430 const QPointF &brushOrigin = q->state()->brushOrigin;
431 QTransform matrix = q->state()->matrix;
432 matrix.translate(brushOrigin.x(), brushOrigin.y());
433
434 QTransform translate(1, 0, 0, 1, -translationPoint.x(), -translationPoint.y());
435 qreal m22 = -1;
436 qreal dy = height;
437 if (device->paintFlipped()) {
438 m22 = 1;
439 dy = 0;
440 }
441 QTransform gl_to_qt(1, 0, 0, m22, 0, dy);
442 QTransform inv_matrix = gl_to_qt * (brushQTransform * matrix).inverted() * translate;
443
444 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::BrushTransform), inv_matrix);
445 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::BrushTexture), QT_BRUSH_TEXTURE_UNIT);
446 }
447 brushUniformsDirty = false;
448}
449
450
451// This assumes the shader manager has already setup the correct shader program
452void QOpenGL2PaintEngineExPrivate::updateMatrix()
453{
454// qDebug("QOpenGL2PaintEngineExPrivate::updateMatrix()");
455
456 const QTransform& transform = q->state()->matrix;
457
458 // The projection matrix converts from Qt's coordinate system to GL's coordinate system
459 // * GL's viewport is 2x2, Qt's is width x height
460 // * GL has +y -> -y going from bottom -> top, Qt is the other way round
461 // * GL has [0,0] in the center, Qt has it in the top-left
462 //
463 // This results in the Projection matrix below, which is multiplied by the painter's
464 // transformation matrix, as shown below:
465 //
466 // Projection Matrix Painter Transform
467 // ------------------------------------------------ ------------------------
468 // | 2.0 / width | 0.0 | -1.0 | | m11 | m21 | dx |
469 // | 0.0 | -2.0 / height | 1.0 | * | m12 | m22 | dy |
470 // | 0.0 | 0.0 | 1.0 | | m13 | m23 | m33 |
471 // ------------------------------------------------ ------------------------
472 //
473 // NOTE: The resultant matrix is also transposed, as GL expects column-major matracies
474
475 const GLfloat wfactor = 2.0f / width;
476 GLfloat hfactor = -2.0f / height;
477
478 GLfloat dx = transform.dx();
479 GLfloat dy = transform.dy();
480
481 if (device->paintFlipped()) {
482 hfactor *= -1;
483 dy -= height;
484 }
485
486 // Non-integer translates can have strange effects for some rendering operations such as
487 // anti-aliased text rendering. In such cases, we snap the translate to the pixel grid.
488 if (snapToPixelGrid && transform.type() == QTransform::TxTranslate) {
489 // 0.50 needs to rounded down to 0.0 for consistency with raster engine:
490 dx = std::ceil(dx - 0.5f);
491 dy = std::ceil(dy - 0.5f);
492 }
493 pmvMatrix[0][0] = (wfactor * transform.m11()) - transform.m13();
494 pmvMatrix[1][0] = (wfactor * transform.m21()) - transform.m23();
495 pmvMatrix[2][0] = (wfactor * dx) - transform.m33();
496 pmvMatrix[0][1] = (hfactor * transform.m12()) + transform.m13();
497 pmvMatrix[1][1] = (hfactor * transform.m22()) + transform.m23();
498 pmvMatrix[2][1] = (hfactor * dy) + transform.m33();
499 pmvMatrix[0][2] = transform.m13();
500 pmvMatrix[1][2] = transform.m23();
501 pmvMatrix[2][2] = transform.m33();
502
503 // 1/10000 == 0.0001, so we have good enough res to cover curves
504 // that span the entire widget...
505 inverseScale = qMax(1 / qMax( qMax(qAbs(transform.m11()), qAbs(transform.m22())),
506 qMax(qAbs(transform.m12()), qAbs(transform.m21())) ),
507 qreal(0.0001));
508
509 matrixDirty = false;
510 matrixUniformDirty = true;
511
512 // Set the PMV matrix attribute. As we use an attributes rather than uniforms, we only
513 // need to do this once for every matrix change and persists across all shader programs.
514 funcs.glVertexAttrib3fv(QT_PMV_MATRIX_1_ATTR, pmvMatrix[0]);
515 funcs.glVertexAttrib3fv(QT_PMV_MATRIX_2_ATTR, pmvMatrix[1]);
516 funcs.glVertexAttrib3fv(QT_PMV_MATRIX_3_ATTR, pmvMatrix[2]);
517
518 dasher.setInvScale(inverseScale);
519 stroker.setInvScale(inverseScale);
520}
521
522
523void QOpenGL2PaintEngineExPrivate::updateCompositionMode()
524{
525 // NOTE: The entire paint engine works on pre-multiplied data - which is why some of these
526 // composition modes look odd.
527// qDebug() << "QOpenGL2PaintEngineExPrivate::updateCompositionMode() - Setting GL composition mode for " << q->state()->composition_mode;
528 if (ctx->functions()->hasOpenGLFeature(QOpenGLFunctions::BlendEquationAdvanced)) {
529 if (q->state()->composition_mode <= QPainter::CompositionMode_Plus) {
530 funcs.glDisable(GL_BLEND_ADVANCED_COHERENT_KHR);
531 funcs.glBlendEquation(GL_FUNC_ADD);
532 } else {
533 funcs.glEnable(GL_BLEND_ADVANCED_COHERENT_KHR);
534 }
535 shaderManager->setCompositionMode(q->state()->composition_mode);
536 } else {
537 if (q->state()->composition_mode > QPainter::CompositionMode_Plus) {
538 qWarning("Unsupported composition mode");
539 compositionModeDirty = false;
540 return;
541 }
542 }
543 switch(q->state()->composition_mode) {
544 case QPainter::CompositionMode_SourceOver:
545 funcs.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
546 break;
547 case QPainter::CompositionMode_DestinationOver:
548 funcs.glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
549 break;
550 case QPainter::CompositionMode_Clear:
551 funcs.glBlendFunc(GL_ZERO, GL_ZERO);
552 break;
553 case QPainter::CompositionMode_Source:
554 funcs.glBlendFunc(GL_ONE, GL_ZERO);
555 break;
556 case QPainter::CompositionMode_Destination:
557 funcs.glBlendFunc(GL_ZERO, GL_ONE);
558 break;
559 case QPainter::CompositionMode_SourceIn:
560 funcs.glBlendFunc(GL_DST_ALPHA, GL_ZERO);
561 break;
562 case QPainter::CompositionMode_DestinationIn:
563 funcs.glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
564 break;
565 case QPainter::CompositionMode_SourceOut:
566 funcs.glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ZERO);
567 break;
568 case QPainter::CompositionMode_DestinationOut:
569 funcs.glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
570 break;
571 case QPainter::CompositionMode_SourceAtop:
572 funcs.glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
573 break;
574 case QPainter::CompositionMode_DestinationAtop:
575 funcs.glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA);
576 break;
577 case QPainter::CompositionMode_Xor:
578 funcs.glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
579 break;
580 case QPainter::CompositionMode_Plus:
581 funcs.glBlendFunc(GL_ONE, GL_ONE);
582 break;
583 case QPainter::CompositionMode_Multiply:
584 funcs.glBlendEquation(GL_MULTIPLY_KHR);
585 break;
586 case QPainter::CompositionMode_Screen:
587 funcs.glBlendEquation(GL_SCREEN_KHR);
588 break;
589 case QPainter::CompositionMode_Overlay:
590 funcs.glBlendEquation(GL_OVERLAY_KHR);
591 break;
592 case QPainter::CompositionMode_Darken:
593 funcs.glBlendEquation(GL_DARKEN_KHR);
594 break;
595 case QPainter::CompositionMode_Lighten:
596 funcs.glBlendEquation(GL_LIGHTEN_KHR);
597 break;
598 case QPainter::CompositionMode_ColorDodge:
599 funcs.glBlendEquation(GL_COLORDODGE_KHR);
600 break;
601 case QPainter::CompositionMode_ColorBurn:
602 funcs.glBlendEquation(GL_COLORBURN_KHR);
603 break;
604 case QPainter::CompositionMode_HardLight:
605 funcs.glBlendEquation(GL_HARDLIGHT_KHR);
606 break;
607 case QPainter::CompositionMode_SoftLight:
608 funcs.glBlendEquation(GL_SOFTLIGHT_KHR);
609 break;
610 case QPainter::CompositionMode_Difference:
611 funcs.glBlendEquation(GL_DIFFERENCE_KHR);
612 break;
613 case QPainter::CompositionMode_Exclusion:
614 funcs.glBlendEquation(GL_EXCLUSION_KHR);
615 break;
616 default:
617 qWarning("Unsupported composition mode");
618 break;
619 }
620
621 compositionModeDirty = false;
622}
623
624static inline void setCoords(GLfloat *coords, const QOpenGLRect &rect)
625{
626 coords[0] = rect.left;
627 coords[1] = rect.top;
628 coords[2] = rect.right;
629 coords[3] = rect.top;
630 coords[4] = rect.right;
631 coords[5] = rect.bottom;
632 coords[6] = rect.left;
633 coords[7] = rect.bottom;
634}
635
636void QOpenGL2PaintEngineExPrivate::drawTexture(const QOpenGLRect& dest, const QOpenGLRect& src, const QSize &textureSize, bool opaque, bool pattern)
637{
638 // Setup for texture drawing
639 currentBrush = noBrush;
640
641 if (snapToPixelGrid) {
642 snapToPixelGrid = false;
643 matrixDirty = true;
644 }
645
646 if (prepareForDraw(opaque))
647 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
648
649 if (pattern) {
650 QColor col = qt_premultiplyColor(q->state()->pen.color(), (GLfloat)q->state()->opacity);
651 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::PatternColor), col);
652 }
653
654 GLfloat dx = 1.0 / textureSize.width();
655 GLfloat dy = 1.0 / textureSize.height();
656
657 QOpenGLRect srcTextureRect(src.left*dx, src.top*dy, src.right*dx, src.bottom*dy);
658
659 setCoords(staticVertexCoordinateArray, dest);
660 setCoords(staticTextureCoordinateArray, srcTextureRect);
661
662 setVertexAttribArrayEnabled(QT_VERTEX_COORDS_ATTR, true);
663 setVertexAttribArrayEnabled(QT_TEXTURE_COORDS_ATTR, true);
664
665 uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8);
666 uploadData(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray, 8);
667
668 funcs.glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
669}
670
671void QOpenGL2PaintEngineEx::beginNativePainting()
672{
673 Q_D(QOpenGL2PaintEngineEx);
674 ensureActive();
675 d->transferMode(BrushDrawingMode);
676
677 d->nativePaintingActive = true;
678
679 d->funcs.glUseProgram(0);
680
681 // Disable all the vertex attribute arrays:
682 for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i)
683 d->funcs.glDisableVertexAttribArray(i);
684
685#if !defined(QT_OPENGL_ES_2) && !defined(QT_OPENGL_DYNAMIC)
686 Q_ASSERT(QOpenGLContext::currentContext());
687 const QOpenGLContext *ctx = d->ctx;
688 const QSurfaceFormat &fmt = d->device->context()->format();
689 if (fmt.majorVersion() < 3 || (fmt.majorVersion() == 3 && fmt.minorVersion() < 1)
690 || (fmt.majorVersion() == 3 && fmt.minorVersion() == 1 && ctx->hasExtension(QByteArrayLiteral("GL_ARB_compatibility")))
691 || fmt.profile() == QSurfaceFormat::CompatibilityProfile)
692 {
693 // be nice to people who mix OpenGL 1.x code with QPainter commands
694 // by setting modelview and projection matrices to mirror the GL 1
695 // paint engine
696 const QTransform& mtx = state()->matrix;
697
698 float mv_matrix[4][4] =
699 {
700 { float(mtx.m11()), float(mtx.m12()), 0, float(mtx.m13()) },
701 { float(mtx.m21()), float(mtx.m22()), 0, float(mtx.m23()) },
702 { 0, 0, 1, 0 },
703 { float(mtx.dx()), float(mtx.dy()), 0, float(mtx.m33()) }
704 };
705
706 const QSize sz = d->device->size();
707
708 glMatrixMode(GL_PROJECTION);
709 glLoadIdentity();
710 glOrtho(0, sz.width(), sz.height(), 0, -999999, 999999);
711
712 glMatrixMode(GL_MODELVIEW);
713 glLoadMatrixf(&mv_matrix[0][0]);
714 }
715#endif // QT_OPENGL_ES_2
716
717 d->resetGLState();
718
719 // We don't know what texture units and textures the native painting
720 // will activate and bind, so we can't assume anything when we return
721 // from the native painting.
722 d->lastTextureUnitUsed = QT_UNKNOWN_TEXTURE_UNIT;
723 d->lastTextureUsed = GLuint(-1);
724
725 d->dirtyStencilRegion = QRect(0, 0, d->width, d->height);
726
727 d->shaderManager->setDirty();
728
729 d->needsSync = true;
730}
731
732void QOpenGL2PaintEngineExPrivate::resetGLState()
733{
734 activateTextureUnit(QT_DEFAULT_TEXTURE_UNIT);
735
736 funcs.glDisable(GL_BLEND);
737 funcs.glDisable(GL_STENCIL_TEST);
738 funcs.glDisable(GL_DEPTH_TEST);
739 funcs.glDisable(GL_SCISSOR_TEST);
740 funcs.glDepthMask(true);
741 funcs.glDepthFunc(GL_LESS);
742 funcs.glClearDepthf(1);
743 funcs.glStencilMask(0xff);
744 funcs.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
745 funcs.glStencilFunc(GL_ALWAYS, 0, 0xff);
746 setVertexAttribArrayEnabled(QT_TEXTURE_COORDS_ATTR, false);
747 setVertexAttribArrayEnabled(QT_VERTEX_COORDS_ATTR, false);
748 setVertexAttribArrayEnabled(QT_OPACITY_ATTR, false);
749 if (!QOpenGLContext::currentContext()->isOpenGLES()) {
750 // gl_Color, corresponding to vertex attribute 3, may have been changed
751 float color[] = { 1.0f, 1.0f, 1.0f, 1.0f };
752 funcs.glVertexAttrib4fv(3, color);
753 }
754 if (vao.isCreated()) {
755 vao.release();
756 funcs.glBindBuffer(GL_ARRAY_BUFFER, 0);
757 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
758 }
759}
760
761void QOpenGL2PaintEngineEx::endNativePainting()
762{
763 Q_D(QOpenGL2PaintEngineEx);
764 d->needsSync = true;
765 d->nativePaintingActive = false;
766}
767
768void QOpenGL2PaintEngineEx::invalidateState()
769{
770 Q_D(QOpenGL2PaintEngineEx);
771 d->needsSync = true;
772}
773
774bool QOpenGL2PaintEngineEx::isNativePaintingActive() const {
775 Q_D(const QOpenGL2PaintEngineEx);
776 return d->nativePaintingActive;
777}
778
779void QOpenGL2PaintEngineExPrivate::transferMode(EngineMode newMode)
780{
781 if (newMode == mode)
782 return;
783
784 if (newMode == TextDrawingMode) {
785 shaderManager->setHasComplexGeometry(true);
786 } else {
787 shaderManager->setHasComplexGeometry(false);
788 }
789
790 if (newMode == ImageDrawingMode) {
791 uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8);
792 uploadData(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray, 8);
793 }
794
795 if (newMode == ImageArrayDrawingMode || newMode == ImageOpacityArrayDrawingMode) {
796 uploadData(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinateArray.data(), vertexCoordinateArray.vertexCount() * 2);
797 uploadData(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinateArray.data(), textureCoordinateArray.vertexCount() * 2);
798
799 if (newMode == ImageOpacityArrayDrawingMode)
800 uploadData(QT_OPACITY_ATTR, (GLfloat*)opacityArray.data(), opacityArray.size());
801 }
802
803 // This needs to change when we implement high-quality anti-aliasing...
804 if (newMode != TextDrawingMode)
805 shaderManager->setMaskType(QOpenGLEngineShaderManager::NoMask);
806
807 mode = newMode;
808}
809
810struct QOpenGL2PEVectorPathCache
811{
812#ifdef QT_OPENGL_CACHE_AS_VBOS
813 GLuint vbo;
814 GLuint ibo;
815#else
816 float *vertices;
817 void *indices;
818#endif
819 int vertexCount;
820 int indexCount;
821 GLenum primitiveType;
822 qreal iscale;
823 QVertexIndexVector::Type indexType;
824};
825
826void QOpenGL2PaintEngineExPrivate::cleanupVectorPath(QPaintEngineEx *engine, void *data)
827{
828 QOpenGL2PEVectorPathCache *c = (QOpenGL2PEVectorPathCache *) data;
829#ifdef QT_OPENGL_CACHE_AS_VBOS
830 Q_ASSERT(engine->type() == QPaintEngine::OpenGL2);
831 static_cast<QOpenGL2PaintEngineEx *>(engine)->d_func()->unusedVBOSToClean << c->vbo;
832 if (c->ibo)
833 d->unusedIBOSToClean << c->ibo;
834#else
835 Q_UNUSED(engine);
836 free(c->vertices);
837 free(c->indices);
838#endif
839 delete c;
840}
841
842// Assumes everything is configured for the brush you want to use
843void QOpenGL2PaintEngineExPrivate::fill(const QVectorPath& path)
844{
845 transferMode(BrushDrawingMode);
846
847 if (snapToPixelGrid) {
848 snapToPixelGrid = false;
849 matrixDirty = true;
850 }
851
852 // Might need to call updateMatrix to re-calculate inverseScale
853 if (matrixDirty)
854 updateMatrix();
855
856 const bool supportsElementIndexUint = funcs.hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint);
857
858 const QPointF* const points = reinterpret_cast<const QPointF*>(path.points());
859
860 // Check to see if there's any hints
861 if (path.shape() == QVectorPath::RectangleHint) {
862 QOpenGLRect rect(points[0].x(), points[0].y(), points[2].x(), points[2].y());
863 prepareForDraw(currentBrush.isOpaque());
864 composite(rect);
865 } else if (path.isConvex()) {
866
867 if (path.isCacheable()) {
868 QVectorPath::CacheEntry *data = path.lookupCacheData(q);
869 QOpenGL2PEVectorPathCache *cache;
870
871 bool updateCache = false;
872
873 if (data) {
874 cache = (QOpenGL2PEVectorPathCache *) data->data;
875 // Check if scale factor is exceeded and regenerate if so...
876 qreal scaleFactor = cache->iscale / inverseScale;
877 if (scaleFactor < 0.5 || scaleFactor > 2.0) {
878#ifdef QT_OPENGL_CACHE_AS_VBOS
879 glDeleteBuffers(1, &cache->vbo);
880 cache->vbo = 0;
881 Q_ASSERT(cache->ibo == 0);
882#else
883 free(cache->vertices);
884 Q_ASSERT(cache->indices == 0);
885#endif
886 updateCache = true;
887 }
888 } else {
889 cache = new QOpenGL2PEVectorPathCache;
890 data = const_cast<QVectorPath &>(path).addCacheData(q, cache, cleanupVectorPath);
891 updateCache = true;
892 }
893
894 // Flatten the path at the current scale factor and fill it into the cache struct.
895 if (updateCache) {
896 vertexCoordinateArray.clear();
897 vertexCoordinateArray.addPath(path, inverseScale, false);
898 int vertexCount = vertexCoordinateArray.vertexCount();
899 int floatSizeInBytes = vertexCount * 2 * sizeof(float);
900 cache->vertexCount = vertexCount;
901 cache->indexCount = 0;
902 cache->primitiveType = GL_TRIANGLE_FAN;
903 cache->iscale = inverseScale;
904#ifdef QT_OPENGL_CACHE_AS_VBOS
905 funcs.glGenBuffers(1, &cache->vbo);
906 funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
907 funcs.glBufferData(GL_ARRAY_BUFFER, floatSizeInBytes, vertexCoordinateArray.data(), GL_STATIC_DRAW);
908 cache->ibo = 0;
909#else
910 cache->vertices = (float *) malloc(floatSizeInBytes);
911 memcpy(cache->vertices, vertexCoordinateArray.data(), floatSizeInBytes);
912 cache->indices = 0;
913#endif
914 }
915
916 prepareForDraw(currentBrush.isOpaque());
917#ifdef QT_OPENGL_CACHE_AS_VBOS
918 funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
919 uploadData(QT_VERTEX_COORD_ATTR, 0, cache->vertexCount);
920 setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, 0);
921#else
922 uploadData(QT_VERTEX_COORDS_ATTR, cache->vertices, cache->vertexCount * 2);
923#endif
924 funcs.glDrawArrays(cache->primitiveType, 0, cache->vertexCount);
925
926 } else {
927 // printf(" - Marking path as cachable...\n");
928 // Tag it for later so that if the same path is drawn twice, it is assumed to be static and thus cachable
929 path.makeCacheable();
930 vertexCoordinateArray.clear();
931 vertexCoordinateArray.addPath(path, inverseScale, false);
932 prepareForDraw(currentBrush.isOpaque());
933 drawVertexArrays(vertexCoordinateArray, GL_TRIANGLE_FAN);
934 }
935
936 } else {
937 bool useCache = path.isCacheable();
938 if (useCache) {
939 QRectF bbox = path.controlPointRect();
940 // If the path doesn't fit within these limits, it is possible that the triangulation will fail.
941 useCache &= (bbox.left() > -0x8000 * inverseScale)
942 && (bbox.right() < 0x8000 * inverseScale)
943 && (bbox.top() > -0x8000 * inverseScale)
944 && (bbox.bottom() < 0x8000 * inverseScale);
945 }
946
947 if (useCache) {
948 QVectorPath::CacheEntry *data = path.lookupCacheData(q);
949 QOpenGL2PEVectorPathCache *cache;
950
951 bool updateCache = false;
952
953 if (data) {
954 cache = (QOpenGL2PEVectorPathCache *) data->data;
955 // Check if scale factor is exceeded and regenerate if so...
956 qreal scaleFactor = cache->iscale / inverseScale;
957 if (scaleFactor < 0.5 || scaleFactor > 2.0) {
958#ifdef QT_OPENGL_CACHE_AS_VBOS
959 glDeleteBuffers(1, &cache->vbo);
960 glDeleteBuffers(1, &cache->ibo);
961#else
962 free(cache->vertices);
963 free(cache->indices);
964#endif
965 updateCache = true;
966 }
967 } else {
968 cache = new QOpenGL2PEVectorPathCache;
969 data = const_cast<QVectorPath &>(path).addCacheData(q, cache, cleanupVectorPath);
970 updateCache = true;
971 }
972
973 // Flatten the path at the current scale factor and fill it into the cache struct.
974 if (updateCache) {
975 QTriangleSet polys = qTriangulate(path, QTransform().scale(1 / inverseScale, 1 / inverseScale), 1, supportsElementIndexUint);
976 cache->vertexCount = polys.vertices.size() / 2;
977 cache->indexCount = polys.indices.size();
978 cache->primitiveType = GL_TRIANGLES;
979 cache->iscale = inverseScale;
980 cache->indexType = polys.indices.type();
981#ifdef QT_OPENGL_CACHE_AS_VBOS
982 funcs.glGenBuffers(1, &cache->vbo);
983 funcs.glGenBuffers(1, &cache->ibo);
984 funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
985 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache->ibo);
986
987 if (polys.indices.type() == QVertexIndexVector::UnsignedInt)
988 funcs.glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quint32) * polys.indices.size(), polys.indices.data(), GL_STATIC_DRAW);
989 else
990 funcs.glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quint16) * polys.indices.size(), polys.indices.data(), GL_STATIC_DRAW);
991
992 QVarLengthArray<float> vertices(polys.vertices.size());
993 for (int i = 0; i < polys.vertices.size(); ++i)
994 vertices[i] = float(inverseScale * polys.vertices.at(i));
995 funcs.glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
996#else
997 cache->vertices = (float *) malloc(sizeof(float) * polys.vertices.size());
998 if (polys.indices.type() == QVertexIndexVector::UnsignedInt) {
999 cache->indices = (quint32 *) malloc(sizeof(quint32) * polys.indices.size());
1000 memcpy(cache->indices, polys.indices.data(), sizeof(quint32) * polys.indices.size());
1001 } else {
1002 cache->indices = (quint16 *) malloc(sizeof(quint16) * polys.indices.size());
1003 memcpy(cache->indices, polys.indices.data(), sizeof(quint16) * polys.indices.size());
1004 }
1005 for (int i = 0; i < polys.vertices.size(); ++i)
1006 cache->vertices[i] = float(inverseScale * polys.vertices.at(i));
1007#endif
1008 }
1009
1010 prepareForDraw(currentBrush.isOpaque());
1011#ifdef QT_OPENGL_CACHE_AS_VBOS
1012 funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
1013 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache->ibo);
1014 uploadData(QT_VERTEX_COORDS_ATTR, 0, cache->vertexCount);
1015 setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, 0);
1016 if (cache->indexType == QVertexIndexVector::UnsignedInt)
1017 funcs.glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_INT, 0);
1018 else
1019 funcs.glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_SHORT, 0);
1020 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
1021 funcs.glBindBuffer(GL_ARRAY_BUFFER, 0);
1022#else
1023 uploadData(QT_VERTEX_COORDS_ATTR, cache->vertices, cache->vertexCount * 2);
1024 const GLenum indexValueType = cache->indexType == QVertexIndexVector::UnsignedInt ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
1025 const bool useIndexVbo = uploadIndexData(cache->indices, indexValueType, cache->indexCount);
1026 funcs.glDrawElements(cache->primitiveType, cache->indexCount, indexValueType, useIndexVbo ? nullptr : cache->indices);
1027#endif
1028
1029 } else {
1030 // printf(" - Marking path as cachable...\n");
1031 // Tag it for later so that if the same path is drawn twice, it is assumed to be static and thus cachable
1032 path.makeCacheable();
1033
1034 if (device->context()->format().stencilBufferSize() <= 0) {
1035 // If there is no stencil buffer, triangulate the path instead.
1036
1037 QRectF bbox = path.controlPointRect();
1038 // If the path doesn't fit within these limits, it is possible that the triangulation will fail.
1039 bool withinLimits = (bbox.left() > -0x8000 * inverseScale)
1040 && (bbox.right() < 0x8000 * inverseScale)
1041 && (bbox.top() > -0x8000 * inverseScale)
1042 && (bbox.bottom() < 0x8000 * inverseScale);
1043 if (withinLimits) {
1044 QTriangleSet polys = qTriangulate(path, QTransform().scale(1 / inverseScale, 1 / inverseScale), 1, supportsElementIndexUint);
1045
1046 QVarLengthArray<float> vertices(polys.vertices.size());
1047 for (int i = 0; i < polys.vertices.size(); ++i)
1048 vertices[i] = float(inverseScale * polys.vertices.at(i));
1049
1050 prepareForDraw(currentBrush.isOpaque());
1051 uploadData(QT_VERTEX_COORDS_ATTR, vertices.constData(), vertices.size());
1052 const GLenum indexValueType = funcs.hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
1053 const bool useIndexVbo = uploadIndexData(polys.indices.data(), indexValueType, polys.indices.size());
1054 funcs.glDrawElements(GL_TRIANGLES, polys.indices.size(), indexValueType, useIndexVbo ? nullptr : polys.indices.data());
1055 } else {
1056 // We can't handle big, concave painter paths with OpenGL without stencil buffer.
1057 qWarning("Painter path exceeds +/-32767 pixels.");
1058 }
1059 return;
1060 }
1061
1062 // The path is too complicated & needs the stencil technique
1063 vertexCoordinateArray.clear();
1064 vertexCoordinateArray.addPath(path, inverseScale, false);
1065
1066 fillStencilWithVertexArray(vertexCoordinateArray, path.hasWindingFill());
1067
1068 funcs.glStencilMask(0xff);
1069 funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
1070
1071 if (q->state()->clipTestEnabled) {
1072 // Pass when high bit is set, replace stencil value with current clip
1073 funcs.glStencilFunc(GL_NOTEQUAL, q->state()->currentClip, GL_STENCIL_HIGH_BIT);
1074 } else if (path.hasWindingFill()) {
1075 // Pass when any bit is set, replace stencil value with 0
1076 funcs.glStencilFunc(GL_NOTEQUAL, 0, 0xff);
1077 } else {
1078 // Pass when high bit is set, replace stencil value with 0
1079 funcs.glStencilFunc(GL_NOTEQUAL, 0, GL_STENCIL_HIGH_BIT);
1080 }
1081 prepareForDraw(currentBrush.isOpaque());
1082
1083 // Stencil the brush onto the dest buffer
1084 composite(vertexCoordinateArray.boundingRect());
1085 funcs.glStencilMask(0);
1086 updateClipScissorTest();
1087 }
1088 }
1089}
1090
1091
1092void QOpenGL2PaintEngineExPrivate::fillStencilWithVertexArray(const float *data,
1093 int count,
1094 int *stops,
1095 int stopCount,
1096 const QOpenGLRect &bounds,
1097 StencilFillMode mode)
1098{
1099 Q_ASSERT(count || stops);
1100
1101// qDebug("QOpenGL2PaintEngineExPrivate::fillStencilWithVertexArray()");
1102 funcs.glStencilMask(0xff); // Enable stencil writes
1103
1104 if (dirtyStencilRegion.intersects(currentScissorBounds)) {
1105 const QRegion clearRegion = dirtyStencilRegion.intersected(currentScissorBounds);
1106 funcs.glClearStencil(0); // Clear to zero
1107 for (const QRect &rect : clearRegion) {
1108#ifndef QT_GL_NO_SCISSOR_TEST
1109 setScissor(rect);
1110#endif
1111 funcs.glClear(GL_STENCIL_BUFFER_BIT);
1112 }
1113
1114 dirtyStencilRegion -= currentScissorBounds;
1115
1116#ifndef QT_GL_NO_SCISSOR_TEST
1117 updateClipScissorTest();
1118#endif
1119 }
1120
1121 funcs.glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // Disable color writes
1122 useSimpleShader();
1123 funcs.glEnable(GL_STENCIL_TEST); // For some reason, this has to happen _after_ the simple shader is use()'d
1124
1125 if (mode == WindingFillMode) {
1126 Q_ASSERT(stops && !count);
1127 if (q->state()->clipTestEnabled) {
1128 // Flatten clip values higher than current clip, and set high bit to match current clip
1129 funcs.glStencilFunc(GL_LEQUAL, GL_STENCIL_HIGH_BIT | q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
1130 funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
1131 composite(bounds);
1132
1133 funcs.glStencilFunc(GL_EQUAL, GL_STENCIL_HIGH_BIT, GL_STENCIL_HIGH_BIT);
1134 } else if (!stencilClean) {
1135 // Clear stencil buffer within bounding rect
1136 funcs.glStencilFunc(GL_ALWAYS, 0, 0xff);
1137 funcs.glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
1138 composite(bounds);
1139 }
1140
1141 // Inc. for front-facing triangle
1142 funcs.glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_INCR_WRAP, GL_INCR_WRAP);
1143 // Dec. for back-facing "holes"
1144 funcs.glStencilOpSeparate(GL_BACK, GL_KEEP, GL_DECR_WRAP, GL_DECR_WRAP);
1145 funcs.glStencilMask(~GL_STENCIL_HIGH_BIT);
1146 drawVertexArrays(data, stops, stopCount, GL_TRIANGLE_FAN);
1147
1148 if (q->state()->clipTestEnabled) {
1149 // Clear high bit of stencil outside of path
1150 funcs.glStencilFunc(GL_EQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
1151 funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
1152 funcs.glStencilMask(GL_STENCIL_HIGH_BIT);
1153 composite(bounds);
1154 }
1155 } else if (mode == OddEvenFillMode) {
1156 funcs.glStencilMask(GL_STENCIL_HIGH_BIT);
1157 funcs.glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); // Simply invert the stencil bit
1158 drawVertexArrays(data, stops, stopCount, GL_TRIANGLE_FAN);
1159
1160 } else { // TriStripStrokeFillMode
1161 Q_ASSERT(count && !stops); // tristrips generated directly, so no vertexArray or stops
1162 funcs.glStencilMask(GL_STENCIL_HIGH_BIT);
1163#if 0
1164 funcs.glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); // Simply invert the stencil bit
1165 setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, data);
1166 funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
1167#else
1168
1169 funcs.glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
1170 if (q->state()->clipTestEnabled) {
1171 funcs.glStencilFunc(GL_LEQUAL, q->state()->currentClip | GL_STENCIL_HIGH_BIT,
1172 ~GL_STENCIL_HIGH_BIT);
1173 } else {
1174 funcs.glStencilFunc(GL_ALWAYS, GL_STENCIL_HIGH_BIT, 0xff);
1175 }
1176
1177 uploadData(QT_VERTEX_COORDS_ATTR, data, count * 2);
1178 funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
1179#endif
1180 }
1181
1182 // Enable color writes & disable stencil writes
1183 funcs.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1184}
1185
1186/*
1187 If the maximum value in the stencil buffer is GL_STENCIL_HIGH_BIT - 1,
1188 restore the stencil buffer to a pristine state. The current clip region
1189 is set to 1, and the rest to 0.
1190*/
1191void QOpenGL2PaintEngineExPrivate::resetClipIfNeeded()
1192{
1193 if (maxClip != (GL_STENCIL_HIGH_BIT - 1))
1194 return;
1195
1196 Q_Q(QOpenGL2PaintEngineEx);
1197
1198 useSimpleShader();
1199 funcs.glEnable(GL_STENCIL_TEST);
1200 funcs.glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1201
1202 QRectF bounds = q->state()->matrix.inverted().mapRect(QRectF(0, 0, width, height));
1203 QOpenGLRect rect(bounds.left(), bounds.top(), bounds.right(), bounds.bottom());
1204
1205 // Set high bit on clip region
1206 funcs.glStencilFunc(GL_LEQUAL, q->state()->currentClip, 0xff);
1207 funcs.glStencilOp(GL_KEEP, GL_INVERT, GL_INVERT);
1208 funcs.glStencilMask(GL_STENCIL_HIGH_BIT);
1209 composite(rect);
1210
1211 // Reset clipping to 1 and everything else to zero
1212 funcs.glStencilFunc(GL_NOTEQUAL, 0x01, GL_STENCIL_HIGH_BIT);
1213 funcs.glStencilOp(GL_ZERO, GL_REPLACE, GL_REPLACE);
1214 funcs.glStencilMask(0xff);
1215 composite(rect);
1216
1217 q->state()->currentClip = 1;
1218 q->state()->canRestoreClip = false;
1219
1220 maxClip = 1;
1221
1222 funcs.glStencilMask(0x0);
1223 funcs.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1224}
1225
1226bool QOpenGL2PaintEngineExPrivate::prepareForCachedGlyphDraw(const QFontEngineGlyphCache &cache)
1227{
1228 Q_Q(QOpenGL2PaintEngineEx);
1229
1230 Q_ASSERT(cache.transform().type() <= QTransform::TxScale);
1231
1232 QTransform &transform = q->state()->matrix;
1233 transform.scale(1.0 / cache.transform().m11(), 1.0 / cache.transform().m22());
1234 bool ret = prepareForDraw(false);
1235 transform.scale(cache.transform().m11(), cache.transform().m22());
1236
1237 return ret;
1238}
1239
1240bool QOpenGL2PaintEngineExPrivate::prepareForDraw(bool srcPixelsAreOpaque)
1241{
1242 if (brushTextureDirty && (mode == TextDrawingMode || mode == BrushDrawingMode))
1243 updateBrushTexture();
1244
1245 if (compositionModeDirty)
1246 updateCompositionMode();
1247
1248 if (matrixDirty)
1249 updateMatrix();
1250
1251 const bool stateHasOpacity = q->state()->opacity < 0.99f;
1252 if (q->state()->composition_mode == QPainter::CompositionMode_Source
1253 || (q->state()->composition_mode == QPainter::CompositionMode_SourceOver
1254 && srcPixelsAreOpaque && !stateHasOpacity))
1255 {
1256 funcs.glDisable(GL_BLEND);
1257 } else {
1258 funcs.glEnable(GL_BLEND);
1259 }
1260
1261 QOpenGLEngineShaderManager::OpacityMode opacityMode;
1262 if (mode == ImageOpacityArrayDrawingMode) {
1263 opacityMode = QOpenGLEngineShaderManager::AttributeOpacity;
1264 } else {
1265 opacityMode = stateHasOpacity ? QOpenGLEngineShaderManager::UniformOpacity
1266 : QOpenGLEngineShaderManager::NoOpacity;
1267 if (stateHasOpacity && (mode != ImageDrawingMode && mode != ImageArrayDrawingMode)) {
1268 // Using a brush
1269 bool brushIsPattern = (currentBrush.style() >= Qt::Dense1Pattern) &&
1270 (currentBrush.style() <= Qt::DiagCrossPattern);
1271
1272 if ((currentBrush.style() == Qt::SolidPattern) || brushIsPattern)
1273 opacityMode = QOpenGLEngineShaderManager::NoOpacity; // Global opacity handled by srcPixel shader
1274 }
1275 }
1276 shaderManager->setOpacityMode(opacityMode);
1277
1278 bool changed = shaderManager->useCorrectShaderProg();
1279 // If the shader program needs changing, we change it and mark all uniforms as dirty
1280 if (changed) {
1281 // The shader program has changed so mark all uniforms as dirty:
1282 brushUniformsDirty = true;
1283 opacityUniformDirty = true;
1284 matrixUniformDirty = true;
1285 }
1286
1287 if (brushUniformsDirty && (mode == TextDrawingMode || mode == BrushDrawingMode))
1288 updateBrushUniforms();
1289
1290 if (opacityMode == QOpenGLEngineShaderManager::UniformOpacity && opacityUniformDirty) {
1291 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::GlobalOpacity), (GLfloat)q->state()->opacity);
1292 opacityUniformDirty = false;
1293 }
1294
1295 if (matrixUniformDirty && shaderManager->hasComplexGeometry()) {
1296 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::Matrix),
1297 pmvMatrix);
1298 matrixUniformDirty = false;
1299 }
1300
1301 return changed;
1302}
1303
1304void QOpenGL2PaintEngineExPrivate::composite(const QOpenGLRect& boundingRect)
1305{
1306 setCoords(staticVertexCoordinateArray, boundingRect);
1307
1308 uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8);
1309 funcs.glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
1310}
1311
1312// Draws the vertex array as a set of <vertexArrayStops.size()> triangle fans.
1313void QOpenGL2PaintEngineExPrivate::drawVertexArrays(const float *data, int *stops, int stopCount,
1314 GLenum primitive)
1315{
1316 // Now setup the pointer to the vertex array:
1317 uploadData(QT_VERTEX_COORDS_ATTR, data, stops[stopCount-1] * 2);
1318
1319 int previousStop = 0;
1320 for (int i=0; i<stopCount; ++i) {
1321 int stop = stops[i];
1322
1323 funcs.glDrawArrays(primitive, previousStop, stop - previousStop);
1324 previousStop = stop;
1325 }
1326}
1327
1328/////////////////////////////////// Public Methods //////////////////////////////////////////
1329
1330QOpenGL2PaintEngineEx::QOpenGL2PaintEngineEx()
1331 : QPaintEngineEx(*(new QOpenGL2PaintEngineExPrivate(this)))
1332{
1333}
1334
1335QOpenGL2PaintEngineEx::~QOpenGL2PaintEngineEx()
1336{
1337}
1338
1339void QOpenGL2PaintEngineEx::fill(const QVectorPath &path, const QBrush &brush)
1340{
1341 Q_D(QOpenGL2PaintEngineEx);
1342
1343 if (qbrush_style(brush) == Qt::NoBrush)
1344 return;
1345 ensureActive();
1346 d->setBrush(brush);
1347 d->fill(path);
1348}
1349
1350Q_GUI_EXPORT bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp
1351
1352
1353void QOpenGL2PaintEngineEx::stroke(const QVectorPath &path, const QPen &pen)
1354{
1355 Q_D(QOpenGL2PaintEngineEx);
1356
1357 const QBrush &penBrush = qpen_brush(pen);
1358 if (qpen_style(pen) == Qt::NoPen || qbrush_style(penBrush) == Qt::NoBrush)
1359 return;
1360
1361 QOpenGL2PaintEngineState *s = state();
1362 if (qt_pen_is_cosmetic(pen, state()->renderHints) && !qt_scaleForTransform(s->transform(), 0)) {
1363 // QTriangulatingStroker class is not meant to support cosmetically sheared strokes.
1364 QPaintEngineEx::stroke(path, pen);
1365 return;
1366 }
1367
1368 ensureActive();
1369 d->setBrush(penBrush);
1370 d->stroke(path, pen);
1371}
1372
1373void QOpenGL2PaintEngineExPrivate::stroke(const QVectorPath &path, const QPen &pen)
1374{
1375 const QOpenGL2PaintEngineState *s = q->state();
1376 if (snapToPixelGrid) {
1377 snapToPixelGrid = false;
1378 matrixDirty = true;
1379 }
1380
1381 const Qt::PenStyle penStyle = qpen_style(pen);
1382 const QBrush &penBrush = qpen_brush(pen);
1383 const bool opaque = penBrush.isOpaque() && s->opacity > 0.99;
1384
1385 transferMode(BrushDrawingMode);
1386
1387 // updateMatrix() is responsible for setting the inverse scale on
1388 // the strokers, so we need to call it here and not wait for
1389 // prepareForDraw() down below.
1390 updateMatrix();
1391
1392 QRectF clip = q->state()->matrix.inverted().mapRect(q->state()->clipEnabled
1393 ? q->state()->rectangleClip
1394 : QRectF(0, 0, width, height));
1395
1396 if (penStyle == Qt::SolidLine) {
1397 stroker.process(path, pen, clip, s->renderHints);
1398
1399 } else { // Some sort of dash
1400 dasher.process(path, pen, clip, s->renderHints);
1401
1402 QVectorPath dashStroke(dasher.points(),
1403 dasher.elementCount(),
1404 dasher.elementTypes());
1405 stroker.process(dashStroke, pen, clip, s->renderHints);
1406 }
1407
1408 if (!stroker.vertexCount())
1409 return;
1410
1411 if (opaque) {
1412 prepareForDraw(opaque);
1413
1414 uploadData(QT_VERTEX_COORDS_ATTR, stroker.vertices(), stroker.vertexCount());
1415 funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, stroker.vertexCount() / 2);
1416 } else {
1417 qreal width = qpen_widthf(pen) / 2;
1418 if (width == 0)
1419 width = 0.5;
1420 qreal extra = pen.joinStyle() == Qt::MiterJoin
1421 ? qMax(pen.miterLimit() * width, width)
1422 : width;
1423
1424 if (qt_pen_is_cosmetic(pen, q->state()->renderHints))
1425 extra = extra * inverseScale;
1426
1427 QRectF bounds = path.controlPointRect().adjusted(-extra, -extra, extra, extra);
1428
1429 fillStencilWithVertexArray(stroker.vertices(), stroker.vertexCount() / 2,
1430 0, 0, bounds, QOpenGL2PaintEngineExPrivate::TriStripStrokeFillMode);
1431
1432 funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
1433
1434 // Pass when any bit is set, replace stencil value with 0
1435 funcs.glStencilFunc(GL_NOTEQUAL, 0, GL_STENCIL_HIGH_BIT);
1436 prepareForDraw(false);
1437
1438 // Stencil the brush onto the dest buffer
1439 composite(bounds);
1440
1441 funcs.glStencilMask(0);
1442
1443 updateClipScissorTest();
1444 }
1445}
1446
1447void QOpenGL2PaintEngineEx::penChanged() { }
1448void QOpenGL2PaintEngineEx::brushChanged() { }
1449void QOpenGL2PaintEngineEx::brushOriginChanged() { }
1450
1451void QOpenGL2PaintEngineEx::opacityChanged()
1452{
1453// qDebug("QOpenGL2PaintEngineEx::opacityChanged()");
1454 Q_D(QOpenGL2PaintEngineEx);
1455 state()->opacityChanged = true;
1456
1457 Q_ASSERT(d->shaderManager);
1458 d->brushUniformsDirty = true;
1459 d->opacityUniformDirty = true;
1460}
1461
1462void QOpenGL2PaintEngineEx::compositionModeChanged()
1463{
1464// qDebug("QOpenGL2PaintEngineEx::compositionModeChanged()");
1465 Q_D(QOpenGL2PaintEngineEx);
1466 state()->compositionModeChanged = true;
1467 d->compositionModeDirty = true;
1468}
1469
1470void QOpenGL2PaintEngineEx::renderHintsChanged()
1471{
1472 state()->renderHintsChanged = true;
1473
1474#ifndef QT_OPENGL_ES_2
1475 if (!QOpenGLContext::currentContext()->isOpenGLES()) {
1476 Q_D(QOpenGL2PaintEngineEx);
1477 if ((state()->renderHints & QPainter::Antialiasing)
1478#if QT_DEPRECATED_SINCE(5, 14)
1479 || (state()->renderHints & QPainter::HighQualityAntialiasing)
1480#endif
1481 )
1482 d->funcs.glEnable(GL_MULTISAMPLE);
1483 else
1484 d->funcs.glDisable(GL_MULTISAMPLE);
1485 }
1486#endif // QT_OPENGL_ES_2
1487
1488 Q_D(QOpenGL2PaintEngineEx);
1489
1490 // This is a somewhat sneaky way of conceptually making the next call to
1491 // updateTexture() use FoceUpdate for the TextureUpdateMode. We need this
1492 // as new render hints may require updating the filter mode.
1493 d->lastTextureUsed = GLuint(-1);
1494
1495 d->brushTextureDirty = true;
1496// qDebug("QOpenGL2PaintEngineEx::renderHintsChanged() not implemented!");
1497}
1498
1499void QOpenGL2PaintEngineEx::transformChanged()
1500{
1501 Q_D(QOpenGL2PaintEngineEx);
1502 d->matrixDirty = true;
1503 state()->matrixChanged = true;
1504}
1505
1506
1507static const QRectF scaleRect(const QRectF &r, qreal sx, qreal sy)
1508{
1509 return QRectF(r.x() * sx, r.y() * sy, r.width() * sx, r.height() * sy);
1510}
1511
1512void QOpenGL2PaintEngineEx::drawPixmap(const QRectF& dest, const QPixmap & pixmap, const QRectF & src)
1513{
1514 Q_D(QOpenGL2PaintEngineEx);
1515 QOpenGLContext *ctx = d->ctx;
1516
1517 // Draw pixmaps that are really images as images since drawImage has
1518 // better handling of non-default image formats.
1519 if (pixmap.paintEngine()->type() == QPaintEngine::Raster && !pixmap.isQBitmap())
1520 return drawImage(dest, pixmap.toImage(), src);
1521
1522 int max_texture_size = ctx->d_func()->maxTextureSize();
1523 if (pixmap.width() > max_texture_size || pixmap.height() > max_texture_size) {
1524 QPixmap scaled = pixmap.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
1525
1526 const qreal sx = scaled.width() / qreal(pixmap.width());
1527 const qreal sy = scaled.height() / qreal(pixmap.height());
1528
1529 drawPixmap(dest, scaled, scaleRect(src, sx, sy));
1530 return;
1531 }
1532
1533 ensureActive();
1534 d->transferMode(ImageDrawingMode);
1535
1536 GLenum filterMode = state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
1537 d->updateTexture(QT_IMAGE_TEXTURE_UNIT, pixmap, GL_CLAMP_TO_EDGE, filterMode);
1538
1539 bool isBitmap = pixmap.isQBitmap();
1540 bool isOpaque = !isBitmap && !pixmap.hasAlpha();
1541
1542 d->shaderManager->setSrcPixelType(isBitmap ? QOpenGLEngineShaderManager::PatternSrc : QOpenGLEngineShaderManager::ImageSrc);
1543
1544 QOpenGLRect srcRect(src.left(), src.top(), src.right(), src.bottom());
1545 d->drawTexture(dest, srcRect, pixmap.size(), isOpaque, isBitmap);
1546}
1547
1548void QOpenGL2PaintEngineEx::drawImage(const QRectF& dest, const QImage& image, const QRectF& src,
1549 Qt::ImageConversionFlags)
1550{
1551 Q_D(QOpenGL2PaintEngineEx);
1552 QOpenGLContext *ctx = d->ctx;
1553
1554 int max_texture_size = ctx->d_func()->maxTextureSize();
1555 if (image.width() > max_texture_size || image.height() > max_texture_size) {
1556 QImage scaled = image.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
1557
1558 const qreal sx = scaled.width() / qreal(image.width());
1559 const qreal sy = scaled.height() / qreal(image.height());
1560
1561 drawImage(dest, scaled, scaleRect(src, sx, sy));
1562 return;
1563 }
1564
1565 ensureActive();
1566 d->transferMode(ImageDrawingMode);
1567
1568 QOpenGLTextureUploader::BindOptions bindOption = QOpenGLTextureUploader::PremultipliedAlphaBindOption;
1569 // Use specialized bind for formats we have specialized shaders for.
1570 switch (image.format()) {
1571 case QImage::Format_RGBA8888:
1572 case QImage::Format_ARGB32:
1573 case QImage::Format_RGBA64:
1574 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::NonPremultipliedImageSrc);
1575 bindOption = 0;
1576 break;
1577 case QImage::Format_Alpha8:
1578 if (ctx->functions()->hasOpenGLFeature(QOpenGLFunctions::TextureRGFormats)) {
1579 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::AlphaImageSrc);
1580 bindOption = QOpenGLTextureUploader::UseRedForAlphaAndLuminanceBindOption;
1581 } else
1582 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
1583 break;
1584 case QImage::Format_Grayscale8:
1585 case QImage::Format_Grayscale16:
1586 if (ctx->functions()->hasOpenGLFeature(QOpenGLFunctions::TextureRGFormats)) {
1587 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::GrayscaleImageSrc);
1588 bindOption = QOpenGLTextureUploader::UseRedForAlphaAndLuminanceBindOption;
1589 } else
1590 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
1591 break;
1592 default:
1593 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
1594 break;
1595 }
1596
1597 ImageWithBindOptions imageWithOptions = { image, bindOption };
1598 GLenum filterMode = state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
1599 d->updateTexture(QT_IMAGE_TEXTURE_UNIT, imageWithOptions, GL_CLAMP_TO_EDGE, filterMode);
1600
1601 d->drawTexture(dest, src, image.size(), !image.hasAlphaChannel());
1602}
1603
1604void QOpenGL2PaintEngineEx::drawStaticTextItem(QStaticTextItem *textItem)
1605{
1606 Q_D(QOpenGL2PaintEngineEx);
1607
1608 ensureActive();
1609
1610 QPainterState *s = state();
1611
1612 QFontEngine *fontEngine = textItem->fontEngine();
1613 if (shouldDrawCachedGlyphs(fontEngine, s->matrix)) {
1614 QFontEngine::GlyphFormat glyphFormat = fontEngine->glyphFormat != QFontEngine::Format_None
1615 ? fontEngine->glyphFormat : d->glyphCacheFormat;
1616 if (glyphFormat == QFontEngine::Format_A32) {
1617 if (d->device->context()->format().alphaBufferSize() > 0 || s->matrix.type() > QTransform::TxTranslate
1618 || (s->composition_mode != QPainter::CompositionMode_Source
1619 && s->composition_mode != QPainter::CompositionMode_SourceOver))
1620 {
1621 glyphFormat = QFontEngine::Format_A8;
1622 }
1623 }
1624
1625 d->drawCachedGlyphs(glyphFormat, textItem);
1626 } else {
1627 QPaintEngineEx::drawStaticTextItem(textItem);
1628 }
1629}
1630
1631bool QOpenGL2PaintEngineEx::drawTexture(const QRectF &dest, GLuint textureId, const QSize &size, const QRectF &src)
1632{
1633 Q_D(QOpenGL2PaintEngineEx);
1634 if (!d->shaderManager)
1635 return false;
1636
1637 ensureActive();
1638 d->transferMode(ImageDrawingMode);
1639
1640 GLenum filterMode = state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
1641 d->updateTexture(QT_IMAGE_TEXTURE_UNIT, textureId, GL_CLAMP_TO_EDGE, filterMode);
1642
1643 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
1644
1645 QOpenGLRect srcRect(src.left(), src.bottom(), src.right(), src.top());
1646 d->drawTexture(dest, srcRect, size, false);
1647
1648 return true;
1649}
1650
1651void QOpenGL2PaintEngineEx::drawTextItem(const QPointF &p, const QTextItem &textItem)
1652{
1653 Q_D(QOpenGL2PaintEngineEx);
1654
1655 ensureActive();
1656 QOpenGL2PaintEngineState *s = state();
1657
1658 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
1659
1660 QTransform::TransformationType txtype = s->matrix.type();
1661
1662 QFontEngine::GlyphFormat glyphFormat = ti.fontEngine->glyphFormat != QFontEngine::Format_None
1663 ? ti.fontEngine->glyphFormat : d->glyphCacheFormat;
1664
1665 if (glyphFormat == QFontEngine::Format_A32) {
1666 if (d->device->context()->format().alphaBufferSize() > 0 || txtype > QTransform::TxTranslate
1667 || (state()->composition_mode != QPainter::CompositionMode_Source
1668 && state()->composition_mode != QPainter::CompositionMode_SourceOver))
1669 {
1670 glyphFormat = QFontEngine::Format_A8;
1671 }
1672 }
1673
1674 if (shouldDrawCachedGlyphs(ti.fontEngine, s->matrix)) {
1675 QVarLengthArray<QFixedPoint> positions;
1676 QVarLengthArray<glyph_t> glyphs;
1677 QTransform matrix = QTransform::fromTranslate(p.x(), p.y());
1678 ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
1679
1680 {
1681 QStaticTextItem staticTextItem;
1682 staticTextItem.setFontEngine(ti.fontEngine);
1683 staticTextItem.glyphs = glyphs.data();
1684 staticTextItem.numGlyphs = glyphs.size();
1685 staticTextItem.glyphPositions = positions.data();
1686
1687 d->drawCachedGlyphs(glyphFormat, &staticTextItem);
1688 }
1689 return;
1690 }
1691
1692 QPaintEngineEx::drawTextItem(p, ti);
1693}
1694
1695namespace {
1696
1697 class QOpenGLStaticTextUserData: public QStaticTextUserData
1698 {
1699 public:
1700 QOpenGLStaticTextUserData()
1701 : QStaticTextUserData(OpenGLUserData), cacheSize(0, 0), cacheSerialNumber(0)
1702 {
1703 }
1704
1705 ~QOpenGLStaticTextUserData()
1706 {
1707 }
1708
1709 QSize cacheSize;
1710 QOpenGL2PEXVertexArray vertexCoordinateArray;
1711 QOpenGL2PEXVertexArray textureCoordinateArray;
1712 QFontEngine::GlyphFormat glyphFormat;
1713 int cacheSerialNumber;
1714 };
1715
1716}
1717
1718
1719// #define QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO
1720
1721bool QOpenGL2PaintEngineEx::shouldDrawCachedGlyphs(QFontEngine *fontEngine, const QTransform &t) const
1722{
1723 // The paint engine does not support projected cached glyph drawing
1724 if (t.type() == QTransform::TxProject)
1725 return false;
1726
1727 // The font engine might not support filling the glyph cache
1728 // with the given transform applied, in which case we need to
1729 // fall back to the QPainterPath code-path.
1730 if (!fontEngine->supportsTransformation(t)) {
1731 // Except that drawing paths is slow, so for scales between
1732 // 0.5 and 2.0 we leave the glyph cache untransformed and deal
1733 // with the transform ourselves when painting, resulting in
1734 // drawing 1x cached glyphs with a smooth-scale.
1735 float det = t.determinant();
1736 if (det >= 0.25f && det <= 4.f) {
1737 // Assuming the baseclass still agrees
1738 return QPaintEngineEx::shouldDrawCachedGlyphs(fontEngine, t);
1739 }
1740
1741 return false; // Fall back to path-drawing
1742 }
1743
1744 return QPaintEngineEx::shouldDrawCachedGlyphs(fontEngine, t);
1745}
1746
1747void QOpenGL2PaintEngineExPrivate::drawCachedGlyphs(QFontEngine::GlyphFormat glyphFormat,
1748 QStaticTextItem *staticTextItem)
1749{
1750 Q_Q(QOpenGL2PaintEngineEx);
1751
1752 QOpenGL2PaintEngineState *s = q->state();
1753
1754 void *cacheKey = ctx; // use context, not the shareGroup() -> the GL glyph cache uses FBOs which may not be shareable
1755 bool recreateVertexArrays = false;
1756
1757 QTransform glyphCacheTransform;
1758 QFontEngine *fe = staticTextItem->fontEngine();
1759 if (fe->supportsTransformation(s->matrix)) {
1760 // The font-engine supports rendering glyphs with the current transform, so we
1761 // build a glyph-cache with the scale pre-applied, so that the cache contains
1762 // glyphs with the appropriate resolution in the case of retina displays.
1763 glyphCacheTransform = s->matrix.type() < QTransform::TxRotate ?
1764 QTransform::fromScale(qAbs(s->matrix.m11()), qAbs(s->matrix.m22())) :
1765 QTransform::fromScale(
1766 QVector2D(s->matrix.m11(), s->matrix.m12()).length(),
1767 QVector2D(s->matrix.m21(), s->matrix.m22()).length());
1768 }
1769
1770 QOpenGLTextureGlyphCache *cache =
1771 (QOpenGLTextureGlyphCache *) fe->glyphCache(cacheKey, glyphFormat, glyphCacheTransform);
1772 if (!cache || cache->glyphFormat() != glyphFormat || cache->contextGroup() == 0) {
1773 cache = new QOpenGLTextureGlyphCache(glyphFormat, glyphCacheTransform);
1774 fe->setGlyphCache(cacheKey, cache);
1775 recreateVertexArrays = true;
1776 }
1777
1778 if (staticTextItem->userDataNeedsUpdate) {
1779 recreateVertexArrays = true;
1780 } else if (staticTextItem->userData() == 0) {
1781 recreateVertexArrays = true;
1782 } else if (staticTextItem->userData()->type != QStaticTextUserData::OpenGLUserData) {
1783 recreateVertexArrays = true;
1784 } else {
1785 QOpenGLStaticTextUserData *userData = static_cast<QOpenGLStaticTextUserData *>(staticTextItem->userData());
1786 if (userData->glyphFormat != glyphFormat) {
1787 recreateVertexArrays = true;
1788 } else if (userData->cacheSerialNumber != cache->serialNumber()) {
1789 recreateVertexArrays = true;
1790 }
1791 }
1792
1793 // We only need to update the cache with new glyphs if we are actually going to recreate the vertex arrays.
1794 // If the cache size has changed, we do need to regenerate the vertices, but we don't need to repopulate the
1795 // cache so this text is performed before we test if the cache size has changed.
1796 if (recreateVertexArrays) {
1797 cache->setPaintEnginePrivate(this);
1798 if (!cache->populate(fe, staticTextItem->numGlyphs,
1799 staticTextItem->glyphs, staticTextItem->glyphPositions)) {
1800 // No space for glyphs in cache. We need to reset it and try again.
1801 cache->clear();
1802 cache->populate(fe, staticTextItem->numGlyphs,
1803 staticTextItem->glyphs, staticTextItem->glyphPositions);
1804 }
1805
1806 if (cache->hasPendingGlyphs()) {
1807 // Filling in the glyphs binds and sets parameters, so we need to
1808 // ensure that the glyph cache doesn't mess with whatever unit
1809 // is currently active. Note that the glyph cache internally
1810 // uses the image texture unit for blitting to the cache, while
1811 // we switch between image and mask units when drawing.
1812 static const GLenum glypchCacheTextureUnit = QT_IMAGE_TEXTURE_UNIT;
1813 activateTextureUnit(glypchCacheTextureUnit);
1814
1815 cache->fillInPendingGlyphs();
1816
1817 // We assume the cache can be trusted on which texture was bound
1818 lastTextureUsed = cache->texture();
1819
1820 // But since the brush and image texture units are possibly shared
1821 // we may have to re-bind brush textures after filling in the cache.
1822 brushTextureDirty = (QT_BRUSH_TEXTURE_UNIT == glypchCacheTextureUnit);
1823 }
1824 cache->setPaintEnginePrivate(nullptr);
1825 }
1826
1827 if (cache->width() == 0 || cache->height() == 0)
1828 return;
1829
1830 if (glyphFormat == QFontEngine::Format_ARGB)
1831 transferMode(ImageArrayDrawingMode);
1832 else
1833 transferMode(TextDrawingMode);
1834
1835 int margin = fe->glyphMargin(glyphFormat);
1836
1837 GLfloat dx = 1.0 / cache->width();
1838 GLfloat dy = 1.0 / cache->height();
1839
1840 // Use global arrays by default
1841 QOpenGL2PEXVertexArray *vertexCoordinates = &vertexCoordinateArray;
1842 QOpenGL2PEXVertexArray *textureCoordinates = &textureCoordinateArray;
1843
1844 if (staticTextItem->useBackendOptimizations) {
1845 QOpenGLStaticTextUserData *userData = 0;
1846
1847 if (staticTextItem->userData() == 0
1848 || staticTextItem->userData()->type != QStaticTextUserData::OpenGLUserData) {
1849
1850 userData = new QOpenGLStaticTextUserData();
1851 staticTextItem->setUserData(userData);
1852
1853 } else {
1854 userData = static_cast<QOpenGLStaticTextUserData*>(staticTextItem->userData());
1855 }
1856
1857 userData->glyphFormat = glyphFormat;
1858 userData->cacheSerialNumber = cache->serialNumber();
1859
1860 // Use cache if backend optimizations is turned on
1861 vertexCoordinates = &userData->vertexCoordinateArray;
1862 textureCoordinates = &userData->textureCoordinateArray;
1863
1864 QSize size(cache->width(), cache->height());
1865 if (userData->cacheSize != size) {
1866 recreateVertexArrays = true;
1867 userData->cacheSize = size;
1868 }
1869 }
1870
1871 if (recreateVertexArrays) {
1872 vertexCoordinates->clear();
1873 textureCoordinates->clear();
1874
1875 bool supportsSubPixelPositions = fe->supportsSubPixelPositions();
1876 for (int i=0; i<staticTextItem->numGlyphs; ++i) {
1877 QFixed subPixelPosition;
1878 if (supportsSubPixelPositions)
1879 subPixelPosition = fe->subPixelPositionForX(staticTextItem->glyphPositions[i].x);
1880
1881 QTextureGlyphCache::GlyphAndSubPixelPosition glyph(staticTextItem->glyphs[i], subPixelPosition);
1882
1883 const QTextureGlyphCache::Coord &c = cache->coords[glyph];
1884 if (c.isNull())
1885 continue;
1886
1887 int x = qFloor(staticTextItem->glyphPositions[i].x.toReal() * cache->transform().m11()) + c.baseLineX - margin;
1888 int y = qRound(staticTextItem->glyphPositions[i].y.toReal() * cache->transform().m22()) - c.baseLineY - margin;
1889
1890 vertexCoordinates->addQuad(QRectF(x, y, c.w, c.h));
1891 textureCoordinates->addQuad(QRectF(c.x*dx, c.y*dy, c.w * dx, c.h * dy));
1892 }
1893
1894 staticTextItem->userDataNeedsUpdate = false;
1895 }
1896
1897 int numGlyphs = vertexCoordinates->vertexCount() / 4;
1898 if (numGlyphs == 0)
1899 return;
1900
1901 if (elementIndices.size() < numGlyphs*6) {
1902 Q_ASSERT(elementIndices.size() % 6 == 0);
1903 int j = elementIndices.size() / 6 * 4;
1904 while (j < numGlyphs*4) {
1905 elementIndices.append(j + 0);
1906 elementIndices.append(j + 0);
1907 elementIndices.append(j + 1);
1908 elementIndices.append(j + 2);
1909 elementIndices.append(j + 3);
1910 elementIndices.append(j + 3);
1911
1912 j += 4;
1913 }
1914
1915#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
1916 if (elementIndicesVBOId == 0)
1917 funcs.glGenBuffers(1, &elementIndicesVBOId);
1918
1919 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementIndicesVBOId);
1920 funcs.glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementIndices.size() * sizeof(GLushort),
1921 elementIndices.constData(), GL_STATIC_DRAW);
1922#endif
1923 } else {
1924#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
1925 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementIndicesVBOId);
1926#endif
1927 }
1928
1929 if (glyphFormat != QFontEngine::Format_ARGB || recreateVertexArrays) {
1930 uploadData(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinates->data(), vertexCoordinates->vertexCount() * 2);
1931 uploadData(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinates->data(), textureCoordinates->vertexCount() * 2);
1932 }
1933
1934 if (!snapToPixelGrid) {
1935 snapToPixelGrid = true;
1936 matrixDirty = true;
1937 }
1938
1939 QBrush pensBrush = q->state()->pen.brush();
1940 setBrush(pensBrush);
1941
1942 if (glyphFormat == QFontEngine::Format_A32) {
1943
1944 // Subpixel antialiasing without gamma correction
1945
1946 QPainter::CompositionMode compMode = q->state()->composition_mode;
1947 Q_ASSERT(compMode == QPainter::CompositionMode_Source
1948 || compMode == QPainter::CompositionMode_SourceOver);
1949
1950 shaderManager->setMaskType(QOpenGLEngineShaderManager::SubPixelMaskPass1);
1951
1952 if (pensBrush.style() == Qt::SolidPattern) {
1953 // Solid patterns can get away with only one pass.
1954 QColor c = pensBrush.color();
1955 qreal oldOpacity = q->state()->opacity;
1956 if (compMode == QPainter::CompositionMode_Source) {
1957 c = qt_premultiplyColor(c, q->state()->opacity);
1958 q->state()->opacity = 1;
1959 opacityUniformDirty = true;
1960 }
1961
1962 compositionModeDirty = false; // I can handle this myself, thank you very much
1963 prepareForCachedGlyphDraw(*cache);
1964
1965 // prepareForCachedGlyphDraw() have set the opacity on the current shader, so the opacity state can now be reset.
1966 if (compMode == QPainter::CompositionMode_Source) {
1967 q->state()->opacity = oldOpacity;
1968 opacityUniformDirty = true;
1969 }
1970
1971 funcs.glEnable(GL_BLEND);
1972 funcs.glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR);
1973 funcs.glBlendColor(c.redF(), c.greenF(), c.blueF(), c.alphaF());
1974 } else {
1975 // Other brush styles need two passes.
1976
1977 qreal oldOpacity = q->state()->opacity;
1978 if (compMode == QPainter::CompositionMode_Source) {
1979 q->state()->opacity = 1;
1980 opacityUniformDirty = true;
1981 pensBrush = Qt::white;
1982 setBrush(pensBrush);
1983 }
1984
1985 compositionModeDirty = false; // I can handle this myself, thank you very much
1986 prepareForCachedGlyphDraw(*cache);
1987 funcs.glEnable(GL_BLEND);
1988 funcs.glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1989
1990 updateTexture(QT_MASK_TEXTURE_UNIT, cache->texture(), GL_REPEAT, GL_NEAREST, ForceUpdate);
1991
1992#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
1993 funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0);
1994#else
1995 const bool useIndexVbo = uploadIndexData(elementIndices.data(), GL_UNSIGNED_SHORT, 6 * numGlyphs);
1996 funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, useIndexVbo ? nullptr : elementIndices.data());
1997#endif
1998
1999 shaderManager->setMaskType(QOpenGLEngineShaderManager::SubPixelMaskPass2);
2000
2001 if (compMode == QPainter::CompositionMode_Source) {
2002 q->state()->opacity = oldOpacity;
2003 opacityUniformDirty = true;
2004 pensBrush = q->state()->pen.brush();
2005 setBrush(pensBrush);
2006 }
2007
2008 compositionModeDirty = false;
2009 prepareForCachedGlyphDraw(*cache);
2010 funcs.glEnable(GL_BLEND);
2011 funcs.glBlendFunc(GL_ONE, GL_ONE);
2012 }
2013 compositionModeDirty = true;
2014 } else if (glyphFormat == QFontEngine::Format_ARGB) {
2015 currentBrush = noBrush;
2016 shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
2017 if (prepareForCachedGlyphDraw(*cache))
2018 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
2019 } else {
2020 // Grayscale/mono glyphs
2021
2022 shaderManager->setMaskType(QOpenGLEngineShaderManager::PixelMask);
2023 prepareForCachedGlyphDraw(*cache);
2024 }
2025
2026 GLenum textureUnit = QT_MASK_TEXTURE_UNIT;
2027 if (glyphFormat == QFontEngine::Format_ARGB)
2028 textureUnit = QT_IMAGE_TEXTURE_UNIT;
2029
2030 QOpenGLTextureGlyphCache::FilterMode filterMode = (s->matrix.type() > QTransform::TxTranslate) ?
2031 QOpenGLTextureGlyphCache::Linear : QOpenGLTextureGlyphCache::Nearest;
2032
2033 GLenum glFilterMode = filterMode == QOpenGLTextureGlyphCache::Linear ? GL_LINEAR : GL_NEAREST;
2034
2035 TextureUpdateMode updateMode = UpdateIfNeeded;
2036 if (cache->filterMode() != filterMode) {
2037 updateMode = ForceUpdate;
2038 cache->setFilterMode(filterMode);
2039 }
2040
2041 updateTexture(textureUnit, cache->texture(), GL_REPEAT, glFilterMode, updateMode);
2042
2043#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
2044 funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0);
2045 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
2046#else
2047 const bool useIndexVbo = uploadIndexData(elementIndices.data(), GL_UNSIGNED_SHORT, 6 * numGlyphs);
2048 funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, useIndexVbo ? nullptr : elementIndices.data());
2049#endif
2050}
2051
2052void QOpenGL2PaintEngineEx::drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragmentCount, const QPixmap &pixmap,
2053 QPainter::PixmapFragmentHints hints)
2054{
2055 Q_D(QOpenGL2PaintEngineEx);
2056 // Use fallback for extended composition modes.
2057 if (state()->composition_mode > QPainter::CompositionMode_Plus) {
2058 QPaintEngineEx::drawPixmapFragments(fragments, fragmentCount, pixmap, hints);
2059 return;
2060 }
2061
2062 ensureActive();
2063 int max_texture_size = d->ctx->d_func()->maxTextureSize();
2064 if (pixmap.width() > max_texture_size || pixmap.height() > max_texture_size) {
2065 QPixmap scaled = pixmap.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
2066 d->drawPixmapFragments(fragments, fragmentCount, scaled, hints);
2067 } else {
2068 d->drawPixmapFragments(fragments, fragmentCount, pixmap, hints);
2069 }
2070}
2071
2072
2073void QOpenGL2PaintEngineExPrivate::drawPixmapFragments(const QPainter::PixmapFragment *fragments,
2074 int fragmentCount, const QPixmap &pixmap,
2075 QPainter::PixmapFragmentHints hints)
2076{
2077 GLfloat dx = 1.0f / pixmap.size().width();
2078 GLfloat dy = 1.0f / pixmap.size().height();
2079
2080 vertexCoordinateArray.clear();
2081 textureCoordinateArray.clear();
2082 opacityArray.reset();
2083
2084 if (snapToPixelGrid) {
2085 snapToPixelGrid = false;
2086 matrixDirty = true;
2087 }
2088
2089 bool allOpaque = true;
2090
2091 for (int i = 0; i < fragmentCount; ++i) {
2092 qreal s = 0;
2093 qreal c = 1;
2094 if (fragments[i].rotation != 0) {
2095 s = qFastSin(qDegreesToRadians(fragments[i].rotation));
2096 c = qFastCos(qDegreesToRadians(fragments[i].rotation));
2097 }
2098
2099 qreal right = 0.5 * fragments[i].scaleX * fragments[i].width;
2100 qreal bottom = 0.5 * fragments[i].scaleY * fragments[i].height;
2101 QOpenGLPoint bottomRight(right * c - bottom * s, right * s + bottom * c);
2102 QOpenGLPoint bottomLeft(-right * c - bottom * s, -right * s + bottom * c);
2103
2104 vertexCoordinateArray.addVertex(bottomRight.x + fragments[i].x, bottomRight.y + fragments[i].y);
2105 vertexCoordinateArray.addVertex(-bottomLeft.x + fragments[i].x, -bottomLeft.y + fragments[i].y);
2106 vertexCoordinateArray.addVertex(-bottomRight.x + fragments[i].x, -bottomRight.y + fragments[i].y);
2107 vertexCoordinateArray.addVertex(-bottomRight.x + fragments[i].x, -bottomRight.y + fragments[i].y);
2108 vertexCoordinateArray.addVertex(bottomLeft.x + fragments[i].x, bottomLeft.y + fragments[i].y);
2109 vertexCoordinateArray.addVertex(bottomRight.x + fragments[i].x, bottomRight.y + fragments[i].y);
2110
2111 QOpenGLRect src(fragments[i].sourceLeft * dx, fragments[i].sourceTop * dy,
2112 (fragments[i].sourceLeft + fragments[i].width) * dx,
2113 (fragments[i].sourceTop + fragments[i].height) * dy);
2114
2115 textureCoordinateArray.addVertex(src.right, src.bottom);
2116 textureCoordinateArray.addVertex(src.right, src.top);
2117 textureCoordinateArray.addVertex(src.left, src.top);
2118 textureCoordinateArray.addVertex(src.left, src.top);
2119 textureCoordinateArray.addVertex(src.left, src.bottom);
2120 textureCoordinateArray.addVertex(src.right, src.bottom);
2121
2122 qreal opacity = fragments[i].opacity * q->state()->opacity;
2123 opacityArray << opacity << opacity << opacity << opacity << opacity << opacity;
2124 allOpaque &= (opacity >= 0.99f);
2125 }
2126
2127 transferMode(ImageOpacityArrayDrawingMode);
2128
2129 GLenum filterMode = q->state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
2130 updateTexture(QT_IMAGE_TEXTURE_UNIT, pixmap, GL_CLAMP_TO_EDGE, filterMode);
2131
2132 bool isBitmap = pixmap.isQBitmap();
2133 bool isOpaque = !isBitmap && (!pixmap.hasAlpha() || (hints & QPainter::OpaqueHint)) && allOpaque;
2134
2135 // Setup for texture drawing
2136 currentBrush = noBrush;
2137 shaderManager->setSrcPixelType(isBitmap ? QOpenGLEngineShaderManager::PatternSrc
2138 : QOpenGLEngineShaderManager::ImageSrc);
2139 if (prepareForDraw(isOpaque))
2140 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
2141
2142 if (isBitmap) {
2143 QColor col = qt_premultiplyColor(q->state()->pen.color(), (GLfloat)q->state()->opacity);
2144 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::PatternColor), col);
2145 }
2146
2147 funcs.glDrawArrays(GL_TRIANGLES, 0, 6 * fragmentCount);
2148}
2149
2150bool QOpenGL2PaintEngineEx::begin(QPaintDevice *pdev)
2151{
2152 Q_D(QOpenGL2PaintEngineEx);
2153
2154 Q_ASSERT(pdev->devType() == QInternal::OpenGL);
2155 d->device = static_cast<QOpenGLPaintDevice*>(pdev);
2156
2157 if (!d->device)
2158 return false;
2159
2160 d->device->ensureActiveTarget();
2161
2162 if (d->device->context() != QOpenGLContext::currentContext() || !d->device->context()) {
2163 qWarning("QPainter::begin(): QOpenGLPaintDevice's context needs to be current");
2164 return false;
2165 }
2166
2167 if (d->ctx != QOpenGLContext::currentContext()
2168 || (d->ctx && QOpenGLContext::currentContext() && d->ctx->format() != QOpenGLContext::currentContext()->format())) {
2169 d->vertexBuffer.destroy();
2170 d->texCoordBuffer.destroy();
2171 d->opacityBuffer.destroy();
2172 d->indexBuffer.destroy();
2173 d->vao.destroy();
2174 }
2175
2176 d->ctx = QOpenGLContext::currentContext();
2177 d->ctx->d_func()->active_engine = this;
2178
2179 QOpenGLPaintDevicePrivate::get(d->device)->beginPaint();
2180
2181 d->funcs.initializeOpenGLFunctions();
2182
2183 // Generate a new Vertex Array Object if we don't have one already. We can
2184 // only hit the VAO-based path when using a core profile context. This is
2185 // because while non-core contexts can support VAOs via extensions, legacy
2186 // components like the QtOpenGL module do not know about VAOs. There are
2187 // still tests for QGL-QOpenGL paint engine interoperability, so keep the
2188 // status quo for now, and avoid introducing a VAO in non-core contexts.
2189 const bool needsVAO = d->ctx->format().profile() == QSurfaceFormat::CoreProfile
2190 && d->ctx->format().version() >= qMakePair(3, 2);
2191 if (needsVAO && !d->vao.isCreated()) {
2192 bool created = d->vao.create();
2193
2194 // If we managed to create it then we have a profile that supports VAOs
2195 if (created) {
2196 d->vao.bind();
2197
2198 // Generate a new Vertex Buffer Object if we don't have one already
2199 if (!d->vertexBuffer.isCreated()) {
2200 d->vertexBuffer.create();
2201 // Set its usage to StreamDraw, we will use this buffer only a few times before refilling it
2202 d->vertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2203 }
2204 if (!d->texCoordBuffer.isCreated()) {
2205 d->texCoordBuffer.create();
2206 d->texCoordBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2207 }
2208 if (!d->opacityBuffer.isCreated()) {
2209 d->opacityBuffer.create();
2210 d->opacityBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2211 }
2212 if (!d->indexBuffer.isCreated()) {
2213 d->indexBuffer.create();
2214 d->indexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2215 }
2216 }
2217 }
2218
2219 for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i)
2220 d->vertexAttributeArraysEnabledState[i] = false;
2221
2222 const QSize sz = d->device->size();
2223 d->width = sz.width();
2224 d->height = sz.height();
2225 d->mode = BrushDrawingMode;
2226 d->brushTextureDirty = true;
2227 d->brushUniformsDirty = true;
2228 d->matrixUniformDirty = true;
2229 d->matrixDirty = true;
2230 d->compositionModeDirty = true;
2231 d->opacityUniformDirty = true;
2232 d->needsSync = true;
2233 d->useSystemClip = !systemClip().isEmpty();
2234 d->currentBrush = QBrush();
2235
2236 d->dirtyStencilRegion = QRect(0, 0, d->width, d->height);
2237 d->stencilClean = true;
2238
2239 d->shaderManager = new QOpenGLEngineShaderManager(d->ctx);
2240
2241 d->funcs.glDisable(GL_STENCIL_TEST);
2242 d->funcs.glDisable(GL_DEPTH_TEST);
2243 d->funcs.glDisable(GL_SCISSOR_TEST);
2244
2245 d->glyphCacheFormat = QFontEngine::Format_A8;
2246
2247#ifndef QT_OPENGL_ES_2
2248 if (!QOpenGLContext::currentContext()->isOpenGLES()) {
2249 d->funcs.glDisable(GL_MULTISAMPLE);
2250 d->glyphCacheFormat = QFontEngine::Format_A32;
2251 d->multisamplingAlwaysEnabled = false;
2252 } else
2253#endif // QT_OPENGL_ES_2
2254 {
2255 // OpenGL ES can't switch MSAA off, so if the gl paint device is
2256 // multisampled, it's always multisampled.
2257 d->multisamplingAlwaysEnabled = d->device->context()->format().samples() > 1;
2258 }
2259
2260 return true;
2261}
2262
2263bool QOpenGL2PaintEngineEx::end()
2264{
2265 Q_D(QOpenGL2PaintEngineEx);
2266
2267 QOpenGLPaintDevicePrivate::get(d->device)->endPaint();
2268
2269 QOpenGLContext *ctx = d->ctx;
2270 d->funcs.glUseProgram(0);
2271 d->transferMode(BrushDrawingMode);
2272
2273 ctx->d_func()->active_engine = 0;
2274
2275 d->resetGLState();
2276
2277 delete d->shaderManager;
2278 d->shaderManager = 0;
2279 d->currentBrush = QBrush();
2280
2281#ifdef QT_OPENGL_CACHE_AS_VBOS
2282 if (!d->unusedVBOSToClean.isEmpty()) {
2283 glDeleteBuffers(d->unusedVBOSToClean.size(), d->unusedVBOSToClean.constData());
2284 d->unusedVBOSToClean.clear();
2285 }
2286 if (!d->unusedIBOSToClean.isEmpty()) {
2287 glDeleteBuffers(d->unusedIBOSToClean.size(), d->unusedIBOSToClean.constData());
2288 d->unusedIBOSToClean.clear();
2289 }
2290#endif
2291
2292 return false;
2293}
2294
2295void QOpenGL2PaintEngineEx::ensureActive()
2296{
2297 Q_D(QOpenGL2PaintEngineEx);
2298 QOpenGLContext *ctx = d->ctx;