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