1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40/*
41 When the active program changes, we need to update it's uniforms.
42 We could track state for each program and only update stale uniforms
43 - Could lead to lots of overhead if there's a lot of programs
44 We could update all the uniforms when the program changes
45 - Could end up updating lots of uniforms which don't need updating
46
47 Updating uniforms should be cheap, so the overhead of updating up-to-date
48 uniforms should be minimal. It's also less complex.
49
50 Things which _may_ cause a different program to be used:
51 - Change in brush/pen style
52 - Change in painter opacity
53 - Change in composition mode
54
55 Whenever we set a mode on the shader manager - it needs to tell us if it had
56 to switch to a different program.
57
58 The shader manager should only switch when we tell it to. E.g. if we set a new
59 brush style and then switch to transparent painter, we only want it to compile
60 and use the correct program when we really need it.
61*/
62
63// #define QT_OPENGL_CACHE_AS_VBOS
64
65#include "qopenglgradientcache_p.h"
66#include "qopengltexturecache_p.h"
67#include "qopenglpaintengine_p.h"
68#include "qopenglpaintdevice_p.h"
69
70#include <string.h> //for memcpy
71#include <qmath.h>
72
73#include <private/qopengl_p.h>
74#include <private/qopenglcontext_p.h>
75#include <private/qopenglextensions_p.h>
76#include <private/qpaintengineex_p.h>
77#include <QPaintEngine>
78#include <private/qpainter_p.h>
79#include <private/qfontengine_p.h>
80#include <private/qdatabuffer_p.h>
81#include <private/qstatictext_p.h>
82#include <private/qtriangulator_p.h>
83
84#include "qopenglengineshadermanager_p.h"
85#include "qopengl2pexvertexarray_p.h"
86#include "qopengltextureglyphcache_p.h"
87
88#include <QDebug>
89
90#include <qtgui_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_GUI_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(n: 1, buffers: &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(a: currentBrush, b: brush))
149 return;
150
151 const Qt::BrushStyle newStyle = qbrush_style(b: 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(transformType: 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, param: wrapMode);
210 funcs.glTexParameteri(target, GL_TEXTURE_WRAP_T, param: wrapMode);
211
212 funcs.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, param: filterMode);
213 funcs.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, param: 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, texture: textureId);
235
236 return textureId;
237}
238
239template<>
240GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const QImage &image)
241{
242 return QOpenGLTextureCache::cacheForContext(context: ctx)->bindTexture(context: ctx, image);
243}
244
245template<>
246GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const QPixmap &pixmap)
247{
248 return QOpenGLTextureCache::cacheForContext(context: ctx)->bindTexture(context: 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(context: ctx)->getBuffer(gradient, opacity: 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(context: ctx)->bindTexture(context: ctx, image: imageWithOptions.image, options: 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(brushStyle: style, invert: false);
295
296 updateTexture(QT_BRUSH_TEXTURE_UNIT, texture: textureImage, GL_REPEAT, filterMode, updateMode: 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, texture: *gradient, wrapMode, filterMode, updateMode: 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(otherSize: QSize(max_texture_size, max_texture_size));
317 if (!QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(feature: QOpenGLFunctions::NPOTTextureRepeat)) {
318 if (!isPowerOfTwo(x: newSize.width()) || !isPowerOfTwo(x: newSize.height())) {
319 newSize.setHeight(qNextPowerOfTwo(v: newSize.height() - 1));
320 newSize.setWidth(qNextPowerOfTwo(v: newSize.width() - 1));
321 }
322 }
323 if (currentBrushImage.size() != newSize)
324 currentBrushImage = currentBrushImage.scaled(s: newSize, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation);
325
326 GLuint wrapMode = GL_REPEAT;
327
328 updateTexture(QT_BRUSH_TEXTURE_UNIT, texture: currentBrushImage, wrapMode, filterMode, updateMode: 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(c: currentBrush.color(), opacity: (GLfloat)q->state()->opacity);
346 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::FragmentColor), color: 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(c: currentBrush.color(), opacity: (GLfloat)q->state()->opacity);
354
355 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::PatternColor), color: col);
356
357 QVector2D halfViewportSize(width*0.5, height*0.5);
358 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::HalfViewportSize), value: 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: location(uniform: QOpenGLEngineShaderManager::LinearData), value: linearData);
376
377 QVector2D halfViewportSize(width*0.5, height*0.5);
378 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::HalfViewportSize), value: 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(degrees: g->angle());
385
386 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::Angle), value: angle);
387
388 QVector2D halfViewportSize(width*0.5, height*0.5);
389 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::HalfViewportSize), value: 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: location(uniform: QOpenGLEngineShaderManager::Fmp), point: fmp);
400
401 GLfloat fmp2_m_radius2 = -fmp.x() * fmp.x() - fmp.y() * fmp.y() + realRadius*realRadius;
402 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::Fmp2MRadius2), value: fmp2_m_radius2);
403 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::Inverse2Fmp2MRadius2),
404 value: GLfloat(1.0 / (2.0*fmp2_m_radius2)));
405 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::SqrFr),
406 value: GLfloat(g->focalRadius() * g->focalRadius()));
407 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::BRadius),
408 x: GLfloat(2 * (g->centerRadius() - g->focalRadius()) * g->focalRadius()),
409 y: g->focalRadius(),
410 z: g->centerRadius() - g->focalRadius());
411
412 QVector2D halfViewportSize(width*0.5, height*0.5);
413 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::HalfViewportSize), value: halfViewportSize);
414 }
415 else if (style == Qt::TexturePattern) {
416 const QPixmap& texPixmap = currentBrush.texture();
417
418 if (qHasPixmapTexture(brush: currentBrush) && currentBrush.texture().isQBitmap()) {
419 QColor col = qt_premultiplyColor(c: currentBrush.color(), opacity: (GLfloat)q->state()->opacity);
420 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::PatternColor), color: col);
421 }
422
423 QSizeF invertedTextureSize(1.0 / texPixmap.width(), 1.0 / texPixmap.height());
424 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::InvertedTextureSize), size: invertedTextureSize);
425
426 QVector2D halfViewportSize(width*0.5, height*0.5);
427 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::HalfViewportSize), value: halfViewportSize);
428 }
429 else
430 qWarning(msg: "QOpenGL2PaintEngineEx: Unimplemented fill style");
431
432 const QPointF &brushOrigin = q->state()->brushOrigin;
433 QTransform matrix = q->state()->matrix;
434 matrix.translate(dx: brushOrigin.x(), dy: 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: location(uniform: QOpenGLEngineShaderManager::BrushTransform), value: inv_matrix);
447 shaderManager->currentProgram()->setUniformValue(location: location(uniform: 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(x: dx - 0.5f);
493 dy = std::ceil(x: 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(a: 1 / qMax( a: qMax(a: qAbs(t: transform.m11()), b: qAbs(t: transform.m22())),
508 b: qMax(a: qAbs(t: transform.m12()), b: qAbs(t: transform.m21())) ),
509 b: 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(indx: QT_PMV_MATRIX_1_ATTR, values: pmvMatrix[0]);
517 funcs.glVertexAttrib3fv(indx: QT_PMV_MATRIX_2_ATTR, values: pmvMatrix[1]);
518 funcs.glVertexAttrib3fv(indx: QT_PMV_MATRIX_3_ATTR, values: 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(feature: 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(msg: "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(msg: "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(srcPixelsAreOpaque: opaque))
651 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
652
653 if (pattern) {
654 QColor col = qt_premultiplyColor(c: q->state()->pen.color(), opacity: (GLfloat)q->state()->opacity);
655 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::PatternColor), color: 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(coords: staticVertexCoordinateArray, rect: dest);
664 setCoords(coords: staticTextureCoordinateArray, rect: srcTextureRect);
665
666 setVertexAttribArrayEnabled(arrayIndex: QT_VERTEX_COORDS_ATTR, enabled: true);
667 setVertexAttribArrayEnabled(arrayIndex: QT_TEXTURE_COORDS_ATTR, enabled: true);
668
669 uploadData(arrayIndex: QT_VERTEX_COORDS_ATTR, data: staticVertexCoordinateArray, count: 8);
670 uploadData(arrayIndex: QT_TEXTURE_COORDS_ATTR, data: staticTextureCoordinateArray, count: 8);
671
672 funcs.glDrawArrays(GL_TRIANGLE_FAN, first: 0, count: 4);
673}
674
675void QOpenGL2PaintEngineEx::beginNativePainting()
676{
677 Q_D(QOpenGL2PaintEngineEx);
678 ensureActive();
679 d->transferMode(newMode: BrushDrawingMode);
680
681 d->nativePaintingActive = true;
682
683 d->funcs.glUseProgram(program: 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(index: i);
688
689#if !defined(QT_OPENGL_ES_2) && !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(left: 0, right: sz.width(), bottom: sz.height(), top: 0, near_val: -999999, far_val: 999999);
715
716 glMatrixMode(GL_MODELVIEW);
717 glLoadMatrixf(m: &mv_matrix[0][0]);
718 }
719#endif // QT_OPENGL_ES_2
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(flag: true);
745 funcs.glDepthFunc(GL_LESS);
746 funcs.glClearDepthf(depth: 1);
747 funcs.glStencilMask(mask: 0xff);
748 funcs.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
749 funcs.glStencilFunc(GL_ALWAYS, ref: 0, mask: 0xff);
750 setVertexAttribArrayEnabled(arrayIndex: QT_TEXTURE_COORDS_ATTR, enabled: false);
751 setVertexAttribArrayEnabled(arrayIndex: QT_VERTEX_COORDS_ATTR, enabled: false);
752 setVertexAttribArrayEnabled(arrayIndex: QT_OPACITY_ATTR, enabled: 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(indx: 3, values: color);
757 }
758 if (vao.isCreated()) {
759 vao.release();
760 funcs.glBindBuffer(GL_ARRAY_BUFFER, buffer: 0);
761 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 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(arrayIndex: QT_VERTEX_COORDS_ATTR, data: staticVertexCoordinateArray, count: 8);
796 uploadData(arrayIndex: QT_TEXTURE_COORDS_ATTR, data: staticTextureCoordinateArray, count: 8);
797 }
798
799 if (newMode == ImageArrayDrawingMode || newMode == ImageOpacityArrayDrawingMode) {
800 uploadData(arrayIndex: QT_VERTEX_COORDS_ATTR, data: (GLfloat*)vertexCoordinateArray.data(), count: vertexCoordinateArray.vertexCount() * 2);
801 uploadData(arrayIndex: QT_TEXTURE_COORDS_ATTR, data: (GLfloat*)textureCoordinateArray.data(), count: textureCoordinateArray.vertexCount() * 2);
802
803 if (newMode == ImageOpacityArrayDrawingMode)
804 uploadData(arrayIndex: QT_OPACITY_ATTR, data: (GLfloat*)opacityArray.data(), count: 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(ptr: c->vertices);
841 free(ptr: 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(newMode: 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(extension: 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(srcPixelsAreOpaque: currentBrush.isOpaque());
868 composite(boundingRect: rect);
869 } else if (path.isConvex()) {
870
871 if (path.isCacheable()) {
872 QVectorPath::CacheEntry *data = path.lookupCacheData(engine: 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(ptr: 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(engine: q, data: cache, cleanup: 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, curveInverseScale: inverseScale, outline: 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(size: floatSizeInBytes);
915 memcpy(dest: cache->vertices, src: vertexCoordinateArray.data(), n: floatSizeInBytes);
916 cache->indices = nullptr;
917#endif
918 }
919
920 prepareForDraw(srcPixelsAreOpaque: 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(arrayIndex: QT_VERTEX_COORDS_ATTR, data: cache->vertices, count: cache->vertexCount * 2);
927#endif
928 funcs.glDrawArrays(mode: cache->primitiveType, first: 0, count: 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, curveInverseScale: inverseScale, outline: false);
936 prepareForDraw(srcPixelsAreOpaque: currentBrush.isOpaque());
937 drawVertexArrays(vertexArray&: 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(engine: 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(ptr: cache->vertices);
967 free(ptr: cache->indices);
968#endif
969 updateCache = true;
970 }
971 } else {
972 cache = new QOpenGL2PEVectorPathCache;
973 data = const_cast<QVectorPath &>(path).addCacheData(engine: q, data: cache, cleanup: 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, matrix: QTransform().scale(sx: 1 / inverseScale, sy: 1 / inverseScale), lod: 1, allowUintIndices: 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(size: sizeof(float) * polys.vertices.size());
1002 if (polys.indices.type() == QVertexIndexVector::UnsignedInt) {
1003 cache->indices = (quint32 *) malloc(size: sizeof(quint32) * polys.indices.size());
1004 memcpy(dest: cache->indices, src: polys.indices.data(), n: sizeof(quint32) * polys.indices.size());
1005 } else {
1006 cache->indices = (quint16 *) malloc(size: sizeof(quint16) * polys.indices.size());
1007 memcpy(dest: cache->indices, src: polys.indices.data(), n: 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(srcPixelsAreOpaque: 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(arrayIndex: QT_VERTEX_COORDS_ATTR, data: cache->vertices, count: cache->vertexCount * 2);
1028 const GLenum indexValueType = cache->indexType == QVertexIndexVector::UnsignedInt ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
1029 const bool useIndexVbo = uploadIndexData(data: cache->indices, indexValueType, count: cache->indexCount);
1030 funcs.glDrawElements(mode: cache->primitiveType, count: cache->indexCount, type: indexValueType, indices: 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, matrix: QTransform().scale(sx: 1 / inverseScale, sy: 1 / inverseScale), lod: 1, allowUintIndices: 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(srcPixelsAreOpaque: currentBrush.isOpaque());
1055 uploadData(arrayIndex: QT_VERTEX_COORDS_ATTR, data: vertices.constData(), count: vertices.size());
1056 const GLenum indexValueType = funcs.hasOpenGLExtension(extension: QOpenGLExtensions::ElementIndexUint) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
1057 const bool useIndexVbo = uploadIndexData(data: polys.indices.data(), indexValueType, count: polys.indices.size());
1058 funcs.glDrawElements(GL_TRIANGLES, count: polys.indices.size(), type: indexValueType, indices: useIndexVbo ? nullptr : polys.indices.data());
1059 } else {
1060 // We can't handle big, concave painter paths with OpenGL without stencil buffer.
1061 qWarning(msg: "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, curveInverseScale: inverseScale, outline: false);
1069
1070 fillStencilWithVertexArray(vertexArray&: vertexCoordinateArray, useWindingFill: path.hasWindingFill());
1071
1072 funcs.glStencilMask(mask: 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, ref: 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, ref: 0, mask: 0xff);
1081 } else {
1082 // Pass when high bit is set, replace stencil value with 0
1083 funcs.glStencilFunc(GL_NOTEQUAL, ref: 0, GL_STENCIL_HIGH_BIT);
1084 }
1085 prepareForDraw(srcPixelsAreOpaque: currentBrush.isOpaque());
1086
1087 // Stencil the brush onto the dest buffer
1088 composite(boundingRect: vertexCoordinateArray.boundingRect());
1089 funcs.glStencilMask(mask: 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(mask: 0xff); // Enable stencil writes
1107
1108 if (dirtyStencilRegion.intersects(r: currentScissorBounds)) {
1109 const QRegion clearRegion = dirtyStencilRegion.intersected(r: currentScissorBounds);
1110 funcs.glClearStencil(s: 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, mask: ~GL_STENCIL_HIGH_BIT);
1134 funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
1135 composite(boundingRect: 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, ref: 0, mask: 0xff);
1141 funcs.glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
1142 composite(boundingRect: 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(mask: ~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, ref: q->state()->currentClip, mask: ~GL_STENCIL_HIGH_BIT);
1155 funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
1156 funcs.glStencilMask(GL_STENCIL_HIGH_BIT);
1157 composite(boundingRect: 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, ref: q->state()->currentClip | GL_STENCIL_HIGH_BIT,
1176 mask: ~GL_STENCIL_HIGH_BIT);
1177 } else {
1178 funcs.glStencilFunc(GL_ALWAYS, GL_STENCIL_HIGH_BIT, mask: 0xff);
1179 }
1180
1181 uploadData(arrayIndex: QT_VERTEX_COORDS_ATTR, data, count: count * 2);
1182 funcs.glDrawArrays(GL_TRIANGLE_STRIP, first: 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, ref: q->state()->currentClip, mask: 0xff);
1211 funcs.glStencilOp(GL_KEEP, GL_INVERT, GL_INVERT);
1212 funcs.glStencilMask(GL_STENCIL_HIGH_BIT);
1213 composite(boundingRect: rect);
1214
1215 // Reset clipping to 1 and everything else to zero
1216 funcs.glStencilFunc(GL_NOTEQUAL, ref: 0x01, GL_STENCIL_HIGH_BIT);
1217 funcs.glStencilOp(GL_ZERO, GL_REPLACE, GL_REPLACE);
1218 funcs.glStencilMask(mask: 0xff);
1219 composite(boundingRect: rect);
1220
1221 q->state()->currentClip = 1;
1222 q->state()->canRestoreClip = false;
1223
1224 maxClip = 1;
1225
1226 funcs.glStencilMask(mask: 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(sx: 1.0 / cache.transform().m11(), sy: 1.0 / cache.transform().m22());
1238 bool ret = prepareForDraw(srcPixelsAreOpaque: false);
1239 transform.scale(sx: cache.transform().m11(), sy: 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: location(uniform: QOpenGLEngineShaderManager::GlobalOpacity), value: (GLfloat)q->state()->opacity);
1296 opacityUniformDirty = false;
1297 }
1298
1299 if (matrixUniformDirty && shaderManager->hasComplexGeometry()) {
1300 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::Matrix),
1301 value: pmvMatrix);
1302 matrixUniformDirty = false;
1303 }
1304
1305 return changed;
1306}
1307
1308void QOpenGL2PaintEngineExPrivate::composite(const QOpenGLRect& boundingRect)
1309{
1310 setCoords(coords: staticVertexCoordinateArray, rect: boundingRect);
1311
1312 uploadData(arrayIndex: QT_VERTEX_COORDS_ATTR, data: staticVertexCoordinateArray, count: 8);
1313 funcs.glDrawArrays(GL_TRIANGLE_FAN, first: 0, count: 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(arrayIndex: QT_VERTEX_COORDS_ATTR, data, count: 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(mode: primitive, first: previousStop, count: 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(b: brush) == Qt::NoBrush)
1349 return;
1350 ensureActive();
1351 d->setBrush(brush);
1352 d->fill(path);
1353}
1354
1355Q_GUI_EXPORT 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(p: pen);
1363 if (qpen_style(p: pen) == Qt::NoPen || qbrush_style(b: penBrush) == Qt::NoBrush)
1364 return;
1365
1366 QOpenGL2PaintEngineState *s = state();
1367 if (qt_pen_is_cosmetic(pen, hints: state()->renderHints) && !qt_scaleForTransform(transform: s->transform(), scale: 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(p: pen);
1387 const QBrush &penBrush = qpen_brush(p: pen);
1388 const bool opaque = penBrush.isOpaque() && s->opacity > 0.99;
1389
1390 transferMode(newMode: 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, hints: s->renderHints);
1403
1404 } else { // Some sort of dash
1405 dasher.process(path, pen, clip, hints: s->renderHints);
1406
1407 QVectorPath dashStroke(dasher.points(),
1408 dasher.elementCount(),
1409 dasher.elementTypes());
1410 stroker.process(path: dashStroke, pen, clip, hints: s->renderHints);
1411 }
1412
1413 if (!stroker.vertexCount())
1414 return;
1415
1416 if (opaque) {
1417 prepareForDraw(srcPixelsAreOpaque: opaque);
1418
1419 uploadData(arrayIndex: QT_VERTEX_COORDS_ATTR, data: stroker.vertices(), count: stroker.vertexCount());
1420 funcs.glDrawArrays(GL_TRIANGLE_STRIP, first: 0, count: stroker.vertexCount() / 2);
1421 } else {
1422 qreal width = qpen_widthf(p: pen) / 2;
1423 if (width == 0)
1424 width = 0.5;
1425 qreal extra = pen.joinStyle() == Qt::MiterJoin
1426 ? qMax(a: pen.miterLimit() * width, b: width)
1427 : width;
1428
1429 if (qt_pen_is_cosmetic(pen, hints: q->state()->renderHints))
1430 extra = extra * inverseScale;
1431
1432 QRectF bounds = path.controlPointRect().adjusted(xp1: -extra, yp1: -extra, xp2: extra, yp2: extra);
1433
1434 fillStencilWithVertexArray(data: stroker.vertices(), count: stroker.vertexCount() / 2,
1435 stops: nullptr, stopCount: 0, bounds, mode: 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, ref: 0, GL_STENCIL_HIGH_BIT);
1441 prepareForDraw(srcPixelsAreOpaque: false);
1442
1443 // Stencil the brush onto the dest buffer
1444 composite(boundingRect: bounds);
1445
1446 funcs.glStencilMask(mask: 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#ifndef QT_OPENGL_ES_2
1480 if (!QOpenGLContext::currentContext()->isOpenGLES()) {
1481 Q_D(QOpenGL2PaintEngineEx);
1482QT_WARNING_PUSH
1483QT_WARNING_DISABLE_DEPRECATED
1484 if ((state()->renderHints & QPainter::Antialiasing)
1485#if QT_DEPRECATED_SINCE(5, 14)
1486 || (state()->renderHints & QPainter::HighQualityAntialiasing)
1487#endif
1488 )
1489 d->funcs.glEnable(GL_MULTISAMPLE);
1490 else
1491 d->funcs.glDisable(GL_MULTISAMPLE);
1492QT_WARNING_POP
1493 }
1494#endif // QT_OPENGL_ES_2
1495
1496 Q_D(QOpenGL2PaintEngineEx);
1497
1498 // This is a somewhat sneaky way of conceptually making the next call to
1499 // updateTexture() use FoceUpdate for the TextureUpdateMode. We need this
1500 // as new render hints may require updating the filter mode.
1501 d->lastTextureUsed = GLuint(-1);
1502
1503 d->brushTextureDirty = true;
1504// qDebug("QOpenGL2PaintEngineEx::renderHintsChanged() not implemented!");
1505}
1506
1507void QOpenGL2PaintEngineEx::transformChanged()
1508{
1509 Q_D(QOpenGL2PaintEngineEx);
1510 d->matrixDirty = true;
1511 state()->matrixChanged = true;
1512}
1513
1514
1515static const QRectF scaleRect(const QRectF &r, qreal sx, qreal sy)
1516{
1517 return QRectF(r.x() * sx, r.y() * sy, r.width() * sx, r.height() * sy);
1518}
1519
1520void QOpenGL2PaintEngineEx::drawPixmap(const QRectF& dest, const QPixmap & pixmap, const QRectF & src)
1521{
1522 Q_D(QOpenGL2PaintEngineEx);
1523 QOpenGLContext *ctx = d->ctx;
1524
1525 // Draw pixmaps that are really images as images since drawImage has
1526 // better handling of non-default image formats.
1527 if (pixmap.paintEngine()->type() == QPaintEngine::Raster && !pixmap.isQBitmap())
1528 return drawImage(r: dest, pm: pixmap.toImage(), sr: src);
1529
1530 int max_texture_size = ctx->d_func()->maxTextureSize();
1531 if (pixmap.width() > max_texture_size || pixmap.height() > max_texture_size) {
1532 QPixmap scaled = pixmap.scaled(w: max_texture_size, h: max_texture_size, aspectMode: Qt::KeepAspectRatio);
1533
1534 const qreal sx = scaled.width() / qreal(pixmap.width());
1535 const qreal sy = scaled.height() / qreal(pixmap.height());
1536
1537 drawPixmap(dest, pixmap: scaled, src: scaleRect(r: src, sx, sy));
1538 return;
1539 }
1540
1541 ensureActive();
1542 d->transferMode(newMode: ImageDrawingMode);
1543
1544 GLenum filterMode = state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
1545 d->updateTexture(QT_IMAGE_TEXTURE_UNIT, texture: pixmap, GL_CLAMP_TO_EDGE, filterMode);
1546
1547 bool isBitmap = pixmap.isQBitmap();
1548 bool isOpaque = !isBitmap && !pixmap.hasAlpha();
1549
1550 d->shaderManager->setSrcPixelType(isBitmap ? QOpenGLEngineShaderManager::PatternSrc : QOpenGLEngineShaderManager::ImageSrc);
1551
1552 QOpenGLRect srcRect(src.left(), src.top(), src.right(), src.bottom());
1553 d->drawTexture(dest, src: srcRect, textureSize: pixmap.size(), opaque: isOpaque, pattern: isBitmap);
1554}
1555
1556void QOpenGL2PaintEngineEx::drawImage(const QRectF& dest, const QImage& image, const QRectF& src,
1557 Qt::ImageConversionFlags)
1558{
1559 Q_D(QOpenGL2PaintEngineEx);
1560 QOpenGLContext *ctx = d->ctx;
1561
1562 int max_texture_size = ctx->d_func()->maxTextureSize();
1563 if (image.width() > max_texture_size || image.height() > max_texture_size) {
1564 QImage scaled = image.scaled(w: max_texture_size, h: max_texture_size, aspectMode: Qt::KeepAspectRatio);
1565
1566 const qreal sx = scaled.width() / qreal(image.width());
1567 const qreal sy = scaled.height() / qreal(image.height());
1568
1569 drawImage(dest, image: scaled, src: scaleRect(r: src, sx, sy));
1570 return;
1571 }
1572
1573 ensureActive();
1574 d->transferMode(newMode: ImageDrawingMode);
1575
1576 QOpenGLTextureUploader::BindOptions bindOption = QOpenGLTextureUploader::PremultipliedAlphaBindOption;
1577 // Use specialized bind for formats we have specialized shaders for.
1578 switch (image.format()) {
1579 case QImage::Format_RGBA8888:
1580 case QImage::Format_ARGB32:
1581 case QImage::Format_RGBA64:
1582 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::NonPremultipliedImageSrc);
1583 bindOption = { };
1584 break;
1585 case QImage::Format_Alpha8:
1586 if (ctx->functions()->hasOpenGLFeature(feature: QOpenGLFunctions::TextureRGFormats)) {
1587 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::AlphaImageSrc);
1588 bindOption = QOpenGLTextureUploader::UseRedForAlphaAndLuminanceBindOption;
1589 } else
1590 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
1591 break;
1592 case QImage::Format_Grayscale8:
1593 case QImage::Format_Grayscale16:
1594 if (ctx->functions()->hasOpenGLFeature(feature: QOpenGLFunctions::TextureRGFormats)) {
1595 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::GrayscaleImageSrc);
1596 bindOption = QOpenGLTextureUploader::UseRedForAlphaAndLuminanceBindOption;
1597 } else
1598 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
1599 break;
1600 default:
1601 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
1602 break;
1603 }
1604
1605 ImageWithBindOptions imageWithOptions = { .image: image, .options: bindOption };
1606 GLenum filterMode = state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
1607 d->updateTexture(QT_IMAGE_TEXTURE_UNIT, texture: imageWithOptions, GL_CLAMP_TO_EDGE, filterMode);
1608
1609 d->drawTexture(dest, src, textureSize: image.size(), opaque: !image.hasAlphaChannel());
1610}
1611
1612void QOpenGL2PaintEngineEx::drawStaticTextItem(QStaticTextItem *textItem)
1613{
1614 Q_D(QOpenGL2PaintEngineEx);
1615
1616 ensureActive();
1617
1618 QPainterState *s = state();
1619
1620 QFontEngine *fontEngine = textItem->fontEngine();
1621 if (shouldDrawCachedGlyphs(fontEngine, s->matrix)) {
1622 QFontEngine::GlyphFormat glyphFormat = fontEngine->glyphFormat != QFontEngine::Format_None
1623 ? fontEngine->glyphFormat : d->glyphCacheFormat;
1624 if (glyphFormat == QFontEngine::Format_A32) {
1625 if (d->device->context()->format().alphaBufferSize() > 0 || s->matrix.type() > QTransform::TxTranslate
1626 || (s->composition_mode != QPainter::CompositionMode_Source
1627 && s->composition_mode != QPainter::CompositionMode_SourceOver))
1628 {
1629 glyphFormat = QFontEngine::Format_A8;
1630 }
1631 }
1632
1633 d->drawCachedGlyphs(glyphFormat, staticTextItem: textItem);
1634 } else {
1635 QPaintEngineEx::drawStaticTextItem(textItem);
1636 }
1637}
1638
1639bool QOpenGL2PaintEngineEx::drawTexture(const QRectF &dest, GLuint textureId, const QSize &size, const QRectF &src)
1640{
1641 Q_D(QOpenGL2PaintEngineEx);
1642 if (!d->shaderManager)
1643 return false;
1644
1645 ensureActive();
1646 d->transferMode(newMode: ImageDrawingMode);
1647
1648 GLenum filterMode = state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
1649 d->updateTexture(QT_IMAGE_TEXTURE_UNIT, texture: textureId, GL_CLAMP_TO_EDGE, filterMode);
1650
1651 d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
1652
1653 QOpenGLRect srcRect(src.left(), src.bottom(), src.right(), src.top());
1654 d->drawTexture(dest, src: srcRect, textureSize: size, opaque: false);
1655
1656 return true;
1657}
1658
1659void QOpenGL2PaintEngineEx::drawTextItem(const QPointF &p, const QTextItem &textItem)
1660{
1661 Q_D(QOpenGL2PaintEngineEx);
1662
1663 ensureActive();
1664 QOpenGL2PaintEngineState *s = state();
1665
1666 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
1667
1668 QTransform::TransformationType txtype = s->matrix.type();
1669
1670 QFontEngine::GlyphFormat glyphFormat = ti.fontEngine->glyphFormat != QFontEngine::Format_None
1671 ? ti.fontEngine->glyphFormat : d->glyphCacheFormat;
1672
1673 if (glyphFormat == QFontEngine::Format_A32) {
1674 if (d->device->context()->format().alphaBufferSize() > 0 || txtype > QTransform::TxTranslate
1675 || (state()->composition_mode != QPainter::CompositionMode_Source
1676 && state()->composition_mode != QPainter::CompositionMode_SourceOver))
1677 {
1678 glyphFormat = QFontEngine::Format_A8;
1679 }
1680 }
1681
1682 if (shouldDrawCachedGlyphs(ti.fontEngine, s->matrix)) {
1683 QVarLengthArray<QFixedPoint> positions;
1684 QVarLengthArray<glyph_t> glyphs;
1685 QTransform matrix = QTransform::fromTranslate(dx: p.x(), dy: p.y());
1686 ti.fontEngine->getGlyphPositions(glyphs: ti.glyphs, matrix, flags: ti.flags, glyphs_out&: glyphs, positions);
1687
1688 {
1689 QStaticTextItem staticTextItem;
1690 staticTextItem.setFontEngine(ti.fontEngine);
1691 staticTextItem.glyphs = glyphs.data();
1692 staticTextItem.numGlyphs = glyphs.size();
1693 staticTextItem.glyphPositions = positions.data();
1694
1695 d->drawCachedGlyphs(glyphFormat, staticTextItem: &staticTextItem);
1696 }
1697 return;
1698 }
1699
1700 QPaintEngineEx::drawTextItem(p, textItem: ti);
1701}
1702
1703namespace {
1704
1705 class QOpenGLStaticTextUserData: public QStaticTextUserData
1706 {
1707 public:
1708 QOpenGLStaticTextUserData()
1709 : QStaticTextUserData(OpenGLUserData), cacheSize(0, 0), cacheSerialNumber(0)
1710 {
1711 }
1712
1713 ~QOpenGLStaticTextUserData()
1714 {
1715 }
1716
1717 QSize cacheSize;
1718 QOpenGL2PEXVertexArray vertexCoordinateArray;
1719 QOpenGL2PEXVertexArray textureCoordinateArray;
1720 QFontEngine::GlyphFormat glyphFormat;
1721 int cacheSerialNumber;
1722 };
1723
1724}
1725
1726
1727// #define QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO
1728
1729bool QOpenGL2PaintEngineEx::shouldDrawCachedGlyphs(QFontEngine *fontEngine, const QTransform &t) const
1730{
1731 // The paint engine does not support projected cached glyph drawing
1732 if (t.type() == QTransform::TxProject)
1733 return false;
1734
1735 // The font engine might not support filling the glyph cache
1736 // with the given transform applied, in which case we need to
1737 // fall back to the QPainterPath code-path.
1738 if (!fontEngine->supportsTransformation(transform: t)) {
1739 // Except that drawing paths is slow, so for scales between
1740 // 0.5 and 2.0 we leave the glyph cache untransformed and deal
1741 // with the transform ourselves when painting, resulting in
1742 // drawing 1x cached glyphs with a smooth-scale.
1743 float det = t.determinant();
1744 if (det >= 0.25f && det <= 4.f) {
1745 // Assuming the baseclass still agrees
1746 return QPaintEngineEx::shouldDrawCachedGlyphs(fontEngine, m: t);
1747 }
1748
1749 return false; // Fall back to path-drawing
1750 }
1751
1752 return QPaintEngineEx::shouldDrawCachedGlyphs(fontEngine, m: t);
1753}
1754
1755void QOpenGL2PaintEngineExPrivate::drawCachedGlyphs(QFontEngine::GlyphFormat glyphFormat,
1756 QStaticTextItem *staticTextItem)
1757{
1758 Q_Q(QOpenGL2PaintEngineEx);
1759
1760 QOpenGL2PaintEngineState *s = q->state();
1761
1762 void *cacheKey = ctx; // use context, not the shareGroup() -> the GL glyph cache uses FBOs which may not be shareable
1763 bool recreateVertexArrays = false;
1764
1765 QTransform glyphCacheTransform;
1766 QFontEngine *fe = staticTextItem->fontEngine();
1767 if (fe->supportsTransformation(transform: s->matrix)) {
1768 // The font-engine supports rendering glyphs with the current transform, so we
1769 // build a glyph-cache with the scale pre-applied, so that the cache contains
1770 // glyphs with the appropriate resolution in the case of retina displays.
1771 glyphCacheTransform = s->matrix.type() < QTransform::TxRotate ?
1772 QTransform::fromScale(dx: qAbs(t: s->matrix.m11()), dy: qAbs(t: s->matrix.m22())) :
1773 QTransform::fromScale(
1774 dx: QVector2D(s->matrix.m11(), s->matrix.m12()).length(),
1775 dy: QVector2D(s->matrix.m21(), s->matrix.m22()).length());
1776 }
1777
1778 QOpenGLTextureGlyphCache *cache =
1779 (QOpenGLTextureGlyphCache *) fe->glyphCache(key: cacheKey, format: glyphFormat, transform: glyphCacheTransform);
1780 if (!cache || cache->glyphFormat() != glyphFormat || cache->contextGroup() == nullptr) {
1781 cache = new QOpenGLTextureGlyphCache(glyphFormat, glyphCacheTransform);
1782 fe->setGlyphCache(key: cacheKey, data: cache);
1783 recreateVertexArrays = true;
1784 }
1785
1786 if (staticTextItem->userDataNeedsUpdate) {
1787 recreateVertexArrays = true;
1788 } else if (staticTextItem->userData() == nullptr) {
1789 recreateVertexArrays = true;
1790 } else if (staticTextItem->userData()->type != QStaticTextUserData::OpenGLUserData) {
1791 recreateVertexArrays = true;
1792 } else {
1793 QOpenGLStaticTextUserData *userData = static_cast<QOpenGLStaticTextUserData *>(staticTextItem->userData());
1794 if (userData->glyphFormat != glyphFormat) {
1795 recreateVertexArrays = true;
1796 } else if (userData->cacheSerialNumber != cache->serialNumber()) {
1797 recreateVertexArrays = true;
1798 }
1799 }
1800
1801 // We only need to update the cache with new glyphs if we are actually going to recreate the vertex arrays.
1802 // If the cache size has changed, we do need to regenerate the vertices, but we don't need to repopulate the
1803 // cache so this text is performed before we test if the cache size has changed.
1804 if (recreateVertexArrays) {
1805 cache->setPaintEnginePrivate(this);
1806 if (!cache->populate(fontEngine: fe, numGlyphs: staticTextItem->numGlyphs,
1807 glyphs: staticTextItem->glyphs, positions: staticTextItem->glyphPositions)) {
1808 // No space for glyphs in cache. We need to reset it and try again.
1809 cache->clear();
1810 cache->populate(fontEngine: fe, numGlyphs: staticTextItem->numGlyphs,
1811 glyphs: staticTextItem->glyphs, positions: staticTextItem->glyphPositions);
1812 }
1813
1814 if (cache->hasPendingGlyphs()) {
1815 // Filling in the glyphs binds and sets parameters, so we need to
1816 // ensure that the glyph cache doesn't mess with whatever unit
1817 // is currently active. Note that the glyph cache internally
1818 // uses the image texture unit for blitting to the cache, while
1819 // we switch between image and mask units when drawing.
1820 static const GLenum glypchCacheTextureUnit = QT_IMAGE_TEXTURE_UNIT;
1821 activateTextureUnit(textureUnit: glypchCacheTextureUnit);
1822
1823 cache->fillInPendingGlyphs();
1824
1825 // We assume the cache can be trusted on which texture was bound
1826 lastTextureUsed = cache->texture();
1827
1828 // But since the brush and image texture units are possibly shared
1829 // we may have to re-bind brush textures after filling in the cache.
1830 brushTextureDirty = (QT_BRUSH_TEXTURE_UNIT == glypchCacheTextureUnit);
1831 }
1832 cache->setPaintEnginePrivate(nullptr);
1833 }
1834
1835 if (cache->width() == 0 || cache->height() == 0)
1836 return;
1837
1838 if (glyphFormat == QFontEngine::Format_ARGB)
1839 transferMode(newMode: ImageArrayDrawingMode);
1840 else
1841 transferMode(newMode: TextDrawingMode);
1842
1843 int margin = fe->glyphMargin(format: glyphFormat);
1844
1845 GLfloat dx = 1.0 / cache->width();
1846 GLfloat dy = 1.0 / cache->height();
1847
1848 // Use global arrays by default
1849 QOpenGL2PEXVertexArray *vertexCoordinates = &vertexCoordinateArray;
1850 QOpenGL2PEXVertexArray *textureCoordinates = &textureCoordinateArray;
1851
1852 if (staticTextItem->useBackendOptimizations) {
1853 QOpenGLStaticTextUserData *userData = nullptr;
1854
1855 if (staticTextItem->userData() == nullptr
1856 || staticTextItem->userData()->type != QStaticTextUserData::OpenGLUserData) {
1857
1858 userData = new QOpenGLStaticTextUserData();
1859 staticTextItem->setUserData(userData);
1860
1861 } else {
1862 userData = static_cast<QOpenGLStaticTextUserData*>(staticTextItem->userData());
1863 }
1864
1865 userData->glyphFormat = glyphFormat;
1866 userData->cacheSerialNumber = cache->serialNumber();
1867
1868 // Use cache if backend optimizations is turned on
1869 vertexCoordinates = &userData->vertexCoordinateArray;
1870 textureCoordinates = &userData->textureCoordinateArray;
1871
1872 QSize size(cache->width(), cache->height());
1873 if (userData->cacheSize != size) {
1874 recreateVertexArrays = true;
1875 userData->cacheSize = size;
1876 }
1877 }
1878
1879 if (recreateVertexArrays) {
1880 vertexCoordinates->clear();
1881 textureCoordinates->clear();
1882
1883 bool supportsSubPixelPositions = fe->supportsSubPixelPositions();
1884 for (int i=0; i<staticTextItem->numGlyphs; ++i) {
1885 QFixed subPixelPosition;
1886 if (supportsSubPixelPositions)
1887 subPixelPosition = fe->subPixelPositionForX(x: staticTextItem->glyphPositions[i].x);
1888
1889 QTextureGlyphCache::GlyphAndSubPixelPosition glyph(staticTextItem->glyphs[i], subPixelPosition);
1890
1891 const QTextureGlyphCache::Coord &c = cache->coords[glyph];
1892 if (c.isNull())
1893 continue;
1894
1895 int x = qFloor(v: staticTextItem->glyphPositions[i].x.toReal() * cache->transform().m11()) + c.baseLineX - margin;
1896 int y = qRound(d: staticTextItem->glyphPositions[i].y.toReal() * cache->transform().m22()) - c.baseLineY - margin;
1897
1898 vertexCoordinates->addQuad(rect: QRectF(x, y, c.w, c.h));
1899 textureCoordinates->addQuad(rect: QRectF(c.x*dx, c.y*dy, c.w * dx, c.h * dy));
1900 }
1901
1902 staticTextItem->userDataNeedsUpdate = false;
1903 }
1904
1905 int numGlyphs = vertexCoordinates->vertexCount() / 4;
1906 if (numGlyphs == 0)
1907 return;
1908
1909 if (elementIndices.size() < numGlyphs*6) {
1910 Q_ASSERT(elementIndices.size() % 6 == 0);
1911 int j = elementIndices.size() / 6 * 4;
1912 while (j < numGlyphs*4) {
1913 elementIndices.append(t: j + 0);
1914 elementIndices.append(t: j + 0);
1915 elementIndices.append(t: j + 1);
1916 elementIndices.append(t: j + 2);
1917 elementIndices.append(t: j + 3);
1918 elementIndices.append(t: j + 3);
1919
1920 j += 4;
1921 }
1922
1923#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
1924 if (elementIndicesVBOId == 0)
1925 funcs.glGenBuffers(1, &elementIndicesVBOId);
1926
1927 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementIndicesVBOId);
1928 funcs.glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementIndices.size() * sizeof(GLushort),
1929 elementIndices.constData(), GL_STATIC_DRAW);
1930#endif
1931 } else {
1932#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
1933 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementIndicesVBOId);
1934#endif
1935 }
1936
1937 if (glyphFormat != QFontEngine::Format_ARGB || recreateVertexArrays) {
1938 uploadData(arrayIndex: QT_VERTEX_COORDS_ATTR, data: (GLfloat*)vertexCoordinates->data(), count: vertexCoordinates->vertexCount() * 2);
1939 uploadData(arrayIndex: QT_TEXTURE_COORDS_ATTR, data: (GLfloat*)textureCoordinates->data(), count: textureCoordinates->vertexCount() * 2);
1940 }
1941
1942 if (!snapToPixelGrid) {
1943 snapToPixelGrid = true;
1944 matrixDirty = true;
1945 }
1946
1947 QBrush pensBrush = q->state()->pen.brush();
1948 setBrush(pensBrush);
1949
1950 if (glyphFormat == QFontEngine::Format_A32) {
1951
1952 // Subpixel antialiasing without gamma correction
1953
1954 QPainter::CompositionMode compMode = q->state()->composition_mode;
1955 Q_ASSERT(compMode == QPainter::CompositionMode_Source
1956 || compMode == QPainter::CompositionMode_SourceOver);
1957
1958 shaderManager->setMaskType(QOpenGLEngineShaderManager::SubPixelMaskPass1);
1959
1960 if (pensBrush.style() == Qt::SolidPattern) {
1961 // Solid patterns can get away with only one pass.
1962 QColor c = pensBrush.color();
1963 qreal oldOpacity = q->state()->opacity;
1964 if (compMode == QPainter::CompositionMode_Source) {
1965 c = qt_premultiplyColor(c, opacity: q->state()->opacity);
1966 q->state()->opacity = 1;
1967 opacityUniformDirty = true;
1968 }
1969
1970 compositionModeDirty = false; // I can handle this myself, thank you very much
1971 prepareForCachedGlyphDraw(cache: *cache);
1972
1973 // prepareForCachedGlyphDraw() have set the opacity on the current shader, so the opacity state can now be reset.
1974 if (compMode == QPainter::CompositionMode_Source) {
1975 q->state()->opacity = oldOpacity;
1976 opacityUniformDirty = true;
1977 }
1978
1979 funcs.glEnable(GL_BLEND);
1980 funcs.glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR);
1981 funcs.glBlendColor(red: c.redF(), green: c.greenF(), blue: c.blueF(), alpha: c.alphaF());
1982 } else {
1983 // Other brush styles need two passes.
1984
1985 qreal oldOpacity = q->state()->opacity;
1986 if (compMode == QPainter::CompositionMode_Source) {
1987 q->state()->opacity = 1;
1988 opacityUniformDirty = true;
1989 pensBrush = Qt::white;
1990 setBrush(pensBrush);
1991 }
1992
1993 compositionModeDirty = false; // I can handle this myself, thank you very much
1994 prepareForCachedGlyphDraw(cache: *cache);
1995 funcs.glEnable(GL_BLEND);
1996 funcs.glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1997
1998 updateTexture(QT_MASK_TEXTURE_UNIT, texture: cache->texture(), GL_REPEAT, GL_NEAREST, updateMode: ForceUpdate);
1999
2000#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
2001 funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0);
2002#else
2003 const bool useIndexVbo = uploadIndexData(data: elementIndices.data(), GL_UNSIGNED_SHORT, count: 6 * numGlyphs);
2004 funcs.glDrawElements(GL_TRIANGLE_STRIP, count: 6 * numGlyphs, GL_UNSIGNED_SHORT, indices: useIndexVbo ? nullptr : elementIndices.data());
2005#endif
2006
2007 shaderManager->setMaskType(QOpenGLEngineShaderManager::SubPixelMaskPass2);
2008
2009 if (compMode == QPainter::CompositionMode_Source) {
2010 q->state()->opacity = oldOpacity;
2011 opacityUniformDirty = true;
2012 pensBrush = q->state()->pen.brush();
2013 setBrush(pensBrush);
2014 }
2015
2016 compositionModeDirty = false;
2017 prepareForCachedGlyphDraw(cache: *cache);
2018 funcs.glEnable(GL_BLEND);
2019 funcs.glBlendFunc(GL_ONE, GL_ONE);
2020 }
2021 compositionModeDirty = true;
2022 } else if (glyphFormat == QFontEngine::Format_ARGB) {
2023 currentBrush = noBrush;
2024 shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
2025 if (prepareForCachedGlyphDraw(cache: *cache))
2026 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
2027 } else {
2028 // Grayscale/mono glyphs
2029
2030 shaderManager->setMaskType(QOpenGLEngineShaderManager::PixelMask);
2031 prepareForCachedGlyphDraw(cache: *cache);
2032 }
2033
2034 GLenum textureUnit = QT_MASK_TEXTURE_UNIT;
2035 if (glyphFormat == QFontEngine::Format_ARGB)
2036 textureUnit = QT_IMAGE_TEXTURE_UNIT;
2037
2038 QOpenGLTextureGlyphCache::FilterMode filterMode = (s->matrix.type() > QTransform::TxTranslate) ?
2039 QOpenGLTextureGlyphCache::Linear : QOpenGLTextureGlyphCache::Nearest;
2040
2041 GLenum glFilterMode = filterMode == QOpenGLTextureGlyphCache::Linear ? GL_LINEAR : GL_NEAREST;
2042
2043 TextureUpdateMode updateMode = UpdateIfNeeded;
2044 if (cache->filterMode() != filterMode) {
2045 updateMode = ForceUpdate;
2046 cache->setFilterMode(filterMode);
2047 }
2048
2049 updateTexture(textureUnit, texture: cache->texture(), GL_REPEAT, filterMode: glFilterMode, updateMode);
2050
2051#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
2052 funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0);
2053 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
2054#else
2055 const bool useIndexVbo = uploadIndexData(data: elementIndices.data(), GL_UNSIGNED_SHORT, count: 6 * numGlyphs);
2056 funcs.glDrawElements(GL_TRIANGLE_STRIP, count: 6 * numGlyphs, GL_UNSIGNED_SHORT, indices: useIndexVbo ? nullptr : elementIndices.data());
2057#endif
2058}
2059
2060void QOpenGL2PaintEngineEx::drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragmentCount, const QPixmap &pixmap,
2061 QPainter::PixmapFragmentHints hints)
2062{
2063 Q_D(QOpenGL2PaintEngineEx);
2064 // Use fallback for extended composition modes.
2065 if (state()->composition_mode > QPainter::CompositionMode_Plus) {
2066 QPaintEngineEx::drawPixmapFragments(fragments, fragmentCount, pixmap, hints);
2067 return;
2068 }
2069
2070 ensureActive();
2071 int max_texture_size = d->ctx->d_func()->maxTextureSize();
2072 if (pixmap.width() > max_texture_size || pixmap.height() > max_texture_size) {
2073 QPixmap scaled = pixmap.scaled(w: max_texture_size, h: max_texture_size, aspectMode: Qt::KeepAspectRatio);
2074 d->drawPixmapFragments(fragments, fragmentCount, pixmap: scaled, hints);
2075 } else {
2076 d->drawPixmapFragments(fragments, fragmentCount, pixmap, hints);
2077 }
2078}
2079
2080
2081void QOpenGL2PaintEngineExPrivate::drawPixmapFragments(const QPainter::PixmapFragment *fragments,
2082 int fragmentCount, const QPixmap &pixmap,
2083 QPainter::PixmapFragmentHints hints)
2084{
2085 GLfloat dx = 1.0f / pixmap.size().width();
2086 GLfloat dy = 1.0f / pixmap.size().height();
2087
2088 vertexCoordinateArray.clear();
2089 textureCoordinateArray.clear();
2090 opacityArray.reset();
2091
2092 if (snapToPixelGrid) {
2093 snapToPixelGrid = false;
2094 matrixDirty = true;
2095 }
2096
2097 bool allOpaque = true;
2098
2099 for (int i = 0; i < fragmentCount; ++i) {
2100 qreal s = 0;
2101 qreal c = 1;
2102 if (fragments[i].rotation != 0) {
2103 s = qFastSin(x: qDegreesToRadians(degrees: fragments[i].rotation));
2104 c = qFastCos(x: qDegreesToRadians(degrees: fragments[i].rotation));
2105 }
2106
2107 qreal right = 0.5 * fragments[i].scaleX * fragments[i].width;
2108 qreal bottom = 0.5 * fragments[i].scaleY * fragments[i].height;
2109 QOpenGLPoint bottomRight(right * c - bottom * s, right * s + bottom * c);
2110 QOpenGLPoint bottomLeft(-right * c - bottom * s, -right * s + bottom * c);
2111
2112 vertexCoordinateArray.addVertex(x: bottomRight.x + fragments[i].x, y: bottomRight.y + fragments[i].y);
2113 vertexCoordinateArray.addVertex(x: -bottomLeft.x + fragments[i].x, y: -bottomLeft.y + fragments[i].y);
2114 vertexCoordinateArray.addVertex(x: -bottomRight.x + fragments[i].x, y: -bottomRight.y + fragments[i].y);
2115 vertexCoordinateArray.addVertex(x: -bottomRight.x + fragments[i].x, y: -bottomRight.y + fragments[i].y);
2116 vertexCoordinateArray.addVertex(x: bottomLeft.x + fragments[i].x, y: bottomLeft.y + fragments[i].y);
2117 vertexCoordinateArray.addVertex(x: bottomRight.x + fragments[i].x, y: bottomRight.y + fragments[i].y);
2118
2119 QOpenGLRect src(fragments[i].sourceLeft * dx, fragments[i].sourceTop * dy,
2120 (fragments[i].sourceLeft + fragments[i].width) * dx,
2121 (fragments[i].sourceTop + fragments[i].height) * dy);
2122
2123 textureCoordinateArray.addVertex(x: src.right, y: src.bottom);
2124 textureCoordinateArray.addVertex(x: src.right, y: src.top);
2125 textureCoordinateArray.addVertex(x: src.left, y: src.top);
2126 textureCoordinateArray.addVertex(x: src.left, y: src.top);
2127 textureCoordinateArray.addVertex(x: src.left, y: src.bottom);
2128 textureCoordinateArray.addVertex(x: src.right, y: src.bottom);
2129
2130 qreal opacity = fragments[i].opacity * q->state()->opacity;
2131 opacityArray << opacity << opacity << opacity << opacity << opacity << opacity;
2132 allOpaque &= (opacity >= 0.99f);
2133 }
2134
2135 transferMode(newMode: ImageOpacityArrayDrawingMode);
2136
2137 GLenum filterMode = q->state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
2138 updateTexture(QT_IMAGE_TEXTURE_UNIT, texture: pixmap, GL_CLAMP_TO_EDGE, filterMode);
2139
2140 bool isBitmap = pixmap.isQBitmap();
2141 bool isOpaque = !isBitmap && (!pixmap.hasAlpha() || (hints & QPainter::OpaqueHint)) && allOpaque;
2142
2143 // Setup for texture drawing
2144 currentBrush = noBrush;
2145 shaderManager->setSrcPixelType(isBitmap ? QOpenGLEngineShaderManager::PatternSrc
2146 : QOpenGLEngineShaderManager::ImageSrc);
2147 if (prepareForDraw(srcPixelsAreOpaque: isOpaque))
2148 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
2149
2150 if (isBitmap) {
2151 QColor col = qt_premultiplyColor(c: q->state()->pen.color(), opacity: (GLfloat)q->state()->opacity);
2152 shaderManager->currentProgram()->setUniformValue(location: location(uniform: QOpenGLEngineShaderManager::PatternColor), color: col);
2153 }
2154
2155 funcs.glDrawArrays(GL_TRIANGLES, first: 0, count: 6 * fragmentCount);
2156}
2157
2158bool QOpenGL2PaintEngineEx::begin(QPaintDevice *pdev)
2159{
2160 Q_D(QOpenGL2PaintEngineEx);
2161
2162 Q_ASSERT(pdev->devType() == QInternal::OpenGL);
2163 d->device = static_cast<QOpenGLPaintDevice*>(pdev);
2164
2165 if (!d->device)
2166 return false;
2167
2168 d->device->ensureActiveTarget();
2169
2170 if (d->device->context() != QOpenGLContext::currentContext() || !d->device->context()) {
2171 qWarning(msg: "QPainter::begin(): QOpenGLPaintDevice's context needs to be current");
2172 return false;
2173 }
2174
2175 if (d->ctx != QOpenGLContext::currentContext()
2176 || (d->ctx && QOpenGLContext::currentContext() && d->ctx->format() != QOpenGLContext::currentContext()->format())) {
2177 d->vertexBuffer.destroy();
2178 d->texCoordBuffer.destroy();
2179 d->opacityBuffer.destroy();
2180 d->indexBuffer.destroy();
2181 d->vao.destroy();
2182 }
2183
2184 d->ctx = QOpenGLContext::currentContext();
2185 d->ctx->d_func()->active_engine = this;
2186
2187 QOpenGLPaintDevicePrivate::get(dev: d->device)->beginPaint();
2188
2189 d->funcs.initializeOpenGLFunctions();
2190
2191 // Generate a new Vertex Array Object if we don't have one already. We can
2192 // only hit the VAO-based path when using a core profile context. This is
2193 // because while non-core contexts can support VAOs via extensions, legacy
2194 // components like the QtOpenGL module do not know about VAOs. There are
2195 // still tests for QGL-QOpenGL paint engine interoperability, so keep the
2196 // status quo for now, and avoid introducing a VAO in non-core contexts.
2197 const bool needsVAO = d->ctx->format().profile() == QSurfaceFormat::CoreProfile
2198 && d->ctx->format().version() >= qMakePair(x: 3, y: 2);
2199 if (needsVAO && !d->vao.isCreated()) {
2200 bool created = d->vao.create();
2201
2202 // If we managed to create it then we have a profile that supports VAOs
2203 if (created) {
2204 d->vao.bind();
2205
2206 // Generate a new Vertex Buffer Object if we don't have one already
2207 if (!d->vertexBuffer.isCreated()) {
2208 d->vertexBuffer.create();
2209 // Set its usage to StreamDraw, we will use this buffer only a few times before refilling it
2210 d->vertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2211 }
2212 if (!d->texCoordBuffer.isCreated()) {
2213 d->texCoordBuffer.create();
2214 d->texCoordBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2215 }
2216 if (!d->opacityBuffer.isCreated()) {
2217 d->opacityBuffer.create();
2218 d->opacityBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2219 }
2220 if (!d->indexBuffer.isCreated()) {
2221 d->indexBuffer.create();
2222 d->indexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2223 }
2224 }
2225 }
2226
2227 for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i)
2228 d->vertexAttributeArraysEnabledState[i] = false;
2229
2230 const QSize sz = d->device->size();
2231 d->width = sz.width();
2232 d->height = sz.height();
2233 d->mode = BrushDrawingMode;
2234 d->brushTextureDirty = true;
2235 d->brushUniformsDirty = true;
2236 d->matrixUniformDirty = true;
2237 d->matrixDirty = true;
2238 d->compositionModeDirty = true;
2239 d->opacityUniformDirty = true;
2240 d->needsSync = true;
2241 d->useSystemClip = !systemClip().isEmpty();
2242 d->currentBrush = QBrush();
2243
2244 d->dirtyStencilRegion = QRect(0, 0, d->width, d->height);
2245 d->stencilClean = true;
2246
2247 d->shaderManager = new QOpenGLEngineShaderManager(d->ctx);
2248
2249 d->funcs.glDisable(GL_STENCIL_TEST);
2250 d->funcs.glDisable(GL_DEPTH_TEST);
2251 d->funcs.glDisable(GL_SCISSOR_TEST);
2252
2253 d->glyphCacheFormat = QFontEngine::Format_A8;
2254
2255#ifndef QT_OPENGL_ES_2
2256 if (!QOpenGLContext::currentContext()->isOpenGLES()) {
2257 d->funcs.glDisable(GL_MULTISAMPLE);
2258 d->glyphCacheFormat = QFontEngine::Format_A32;
2259 d->multisamplingAlwaysEnabled = false;
2260 } else
2261#endif // QT_OPENGL_ES_2
2262 {
2263 // OpenGL ES can't switch MSAA off, so if the gl paint device is
2264 // multisampled, it's always multisampled.
2265 d->multisamplingAlwaysEnabled = d->device->context()->format().samples() > 1;
2266 }
2267
2268 return true;
2269}
2270
2271bool QOpenGL2PaintEngineEx::end()
2272{
2273 Q_D(QOpenGL2PaintEngineEx);
2274
2275 QOpenGLPaintDevicePrivate::get(dev: d->device)->endPaint();
2276
2277 QOpenGLContext *ctx = d->ctx;
2278 d->funcs.glUseProgram(program: 0);
2279 d->transferMode(newMode: BrushDrawingMode);
2280
2281 ctx->d_func()->active_engine = nullptr;
2282
2283 d->resetGLState();
2284
2285 delete d->shaderManager;
2286 d->shaderManager = nullptr;
2287 d->currentBrush = QBrush();
2288
2289#ifdef QT_OPENGL_CACHE_AS_VBOS
2290 if (!d->unusedVBOSToClean.isEmpty()) {
2291 glDeleteBuffers(d->unusedVBOSToClean.size(), d->unusedVBOSToClean.constData());
2292 d->unusedVBOSToClean.clear();
2293 }
2294 if (!d->unusedIBOSToClean.isEmpty()) {
2295 glDeleteBuffers(d->unusedIBOSToClean.size(), d->unusedIBOSToClean.constData());
2296 d->unusedIBOSToClean.clear();
2297 }
2298#endif
2299
2300 return false;
2301}
2302
2303void QOpenGL2PaintEngineEx::ensureActive()
2304{
2305 Q_D(QOpenGL2PaintEngineEx);
2306 QOpenGLContext *ctx = d->ctx;
2307
2308 if (d->vao.isCreated())
2309 d->vao.bind();
2310
2311 if (isActive() && ctx->d_func()->active_engine != this) {
2312 ctx->d_func()->active_engine = this;
2313 d->needsSync = true;
2314 }
2315
2316 if (d->needsSync) {
2317 d->device->ensureActiveTarget();
2318
2319 d->transferMode(newMode: BrushDrawingMode);
2320 d->funcs.glViewport(x: 0, y: 0, width: d->width, height: d->height);
2321 d->needsSync = false;
2322 d->shaderManager->setDirty();
2323 d->syncGlState();
2324 for (int i = 0; i < 3; ++i)
2325 d->vertexAttribPointers[i] = (GLfloat*)-1; // Assume the pointers are clobbered
2326 setState(state());
2327 }
2328}
2329
2330void QOpenGL2PaintEngineExPrivate::updateClipScissorTest()
2331{
2332 Q_Q(QOpenGL2PaintEngineEx);
2333 if (q->state()->clipTestEnabled) {
2334 funcs.glEnable(GL_STENCIL_TEST);
2335 funcs.glStencilFunc(GL_LEQUAL, ref: q->state()->currentClip, mask: ~GL_STENCIL_HIGH_BIT);
2336 } else {
2337 funcs.glDisable(GL_STENCIL_TEST);
2338 funcs.glStencilFunc(GL_ALWAYS, ref: 0, mask: 0xff);
2339 }
2340
2341#ifdef QT_GL_NO_SCISSOR_TEST
2342 currentScissorBounds = QRect(0, 0, width, height);
2343#else
2344 QRect bounds = q->state()->rectangleClip;
2345 if (!q->state()->clipEnabled) {
2346 if (useSystemClip)
2347 bounds = systemClip.boundingRect();
2348 else
2349 bounds = QRect(0, 0, width, height);
2350 } else {
2351 if (useSystemClip)
2352 bounds = bounds.intersected(other: systemClip.boundingRect());
2353 else
2354 bounds = bounds.intersected(other: QRect(0, 0, width, height));
2355 }
2356
2357 currentScissorBounds = bounds;
2358
2359 if (bounds == QRect(0, 0, width, height)) {
2360 funcs.glDisable(GL_SCISSOR_TEST);
2361 } else {
2362 funcs.glEnable(GL_SCISSOR_TEST);
2363 setScissor(bounds);
2364 }
2365#endif
2366}
2367
2368void QOpenGL2PaintEngineExPrivate::setScissor(const QRect &rect)
2369{
2370 const int left = rect.left();
2371 const int width = rect.width();
2372 int bottom = height - (rect.top() + rect.height());
2373 if (device->paintFlipped()) {
2374 bottom = rect.top();
2375 }
2376 const int height = rect.height();
2377
2378 funcs.glScissor(x: left, y: bottom, width, height);
2379}
2380
2381void QOpenGL2PaintEngineEx::clipEnabledChanged()
2382{
2383 Q_D(QOpenGL2PaintEngineEx);
2384
2385 state()->clipChanged = true;
2386
2387 if (painter()->hasClipping())
2388 d->regenerateClip();
2389 else
2390 d->systemStateChanged();
2391}
2392
2393void QOpenGL2PaintEngineExPrivate::clearClip(uint value)
2394{
2395 dirtyStencilRegion -= currentScissorBounds;
2396
2397 funcs.glStencilMask(mask: 0xff);
2398 funcs.glClearStencil(s: value);
2399 funcs.glClear(GL_STENCIL_BUFFER_BIT);
2400 funcs.glStencilMask(mask: 0x0);
2401
2402 q->state()->needsClipBufferClear = false;
2403}
2404
2405void QOpenGL2PaintEngineExPrivate::writeClip(const QVectorPath &path, uint value)
2406{
2407 transferMode(newMode: BrushDrawingMode);
2408
2409 if (snapToPixelGrid) {
2410 snapToPixelGrid = false;
2411 matrixDirty = true;
2412 }
2413
2414 if (matrixDirty)
2415 updateMatrix();
2416
2417 stencilClean = false;
2418
2419 const bool singlePass = !path.hasWindingFill()
2420 && (((q->state()->currentClip == maxClip - 1) && q->state()->clipTestEnabled)
2421 || q->state()->needsClipBufferClear);
2422 const uint referenceClipValue = q->state()->needsClipBufferClear ? 1 : q->state()->currentClip;
2423
2424 if (q->state()->needsClipBufferClear)
2425 clearClip(value: 1);
2426
2427 if (path.isEmpty()) {
2428 funcs.glEnable(GL_STENCIL_TEST);
2429 funcs.glStencilFunc(GL_LEQUAL, ref: value, mask: ~GL_STENCIL_HIGH_BIT);
2430 return;
2431 }
2432
2433 if (q->state()->clipTestEnabled)
2434 funcs.glStencilFunc(GL_LEQUAL, ref: q->state()->currentClip, mask: ~GL_STENCIL_HIGH_BIT);
2435 else
2436 funcs.glStencilFunc(GL_ALWAYS, ref: 0, mask: 0xff);
2437
2438 vertexCoordinateArray.clear();
2439 vertexCoordinateArray.addPath(path, curveInverseScale: inverseScale, outline: false);
2440
2441 if (!singlePass)
2442 fillStencilWithVertexArray(vertexArray&: vertexCoordinateArray, useWindingFill: path.hasWindingFill());
2443
2444 funcs.glColorMask(red: false, green: false, blue: false, alpha: false);
2445 funcs.glEnable(GL_STENCIL_TEST);
2446 useSimpleShader();
2447
2448 if (singlePass) {
2449 // Under these conditions we can set the new stencil value in a single
2450 // pass, by using the current value and the "new value" as the toggles
2451
2452 funcs.glStencilFunc(GL_LEQUAL, ref: referenceClipValue, mask: ~GL_STENCIL_HIGH_BIT);
2453 funcs.glStencilOp(GL_KEEP, GL_INVERT, GL_INVERT);
2454 funcs.glStencilMask(mask: value ^ referenceClipValue);
2455
2456 drawVertexArrays(vertexArray&: vertexCoordinateArray, GL_TRIANGLE_FAN);
2457 } else {
2458 funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
2459 funcs.glStencilMask(mask: 0xff);
2460
2461 if (!q->state()->clipTestEnabled && path.hasWindingFill()) {
2462 // Pass when any clip bit is set, set high bit
2463 funcs.glStencilFunc(GL_NOTEQUAL, GL_STENCIL_HIGH_BIT, mask: ~GL_STENCIL_HIGH_BIT);
2464 composite(boundingRect: vertexCoordinateArray.boundingRect());
2465 }
2466
2467 // Pass when high bit is set, replace stencil value with new clip value
2468 funcs.glStencilFunc(GL_NOTEQUAL, ref: value, GL_STENCIL_HIGH_BIT);
2469
2470 composite(boundingRect: vertexCoordinateArray.boundingRect());
2471 }
2472
2473 funcs.glStencilFunc(GL_LEQUAL, ref: value, mask: ~GL_STENCIL_HIGH_BIT);
2474 funcs.glStencilMask(mask: 0);
2475
2476 funcs.glColorMask(red: true, green: true, blue: true, alpha: true);
2477}
2478
2479void QOpenGL2PaintEngineEx::clip(const QVectorPath &path, Qt::ClipOperation op)
2480{
2481// qDebug("QOpenGL2PaintEngineEx::clip()");
2482 Q_D(QOpenGL2PaintEngineEx);
2483
2484 state()->clipChanged = true;
2485
2486 ensureActive();
2487
2488 if (op == Qt::ReplaceClip) {
2489 op = Qt::IntersectClip;
2490 if (d->hasClipOperations()) {
2491 d->systemStateChanged();
2492 state()->canRestoreClip = false;
2493 }
2494 }
2495
2496#ifndef QT_GL_NO_SCISSOR_TEST
2497 if (!path.isEmpty() && op == Qt::IntersectClip && (path.shape() == QVectorPath::RectangleHint)) {
2498 const QPointF* const points = reinterpret_cast<const QPointF*>(path.points());
2499 QRectF rect(points[0], points[2]);
2500
2501 if (state()->matrix.type() <= QTransform::TxScale
2502 || (state()->matrix.type() == QTransform::TxRotate
2503 && qFuzzyIsNull(d: state()->matrix.m11())
2504 && qFuzzyIsNull(d: state()->matrix.m22())))
2505 {
2506 state()->rectangleClip = state()->rectangleClip.intersected(other: state()->matrix.mapRect(rect).toAlignedRect());
2507 d->updateClipScissorTest();
2508 return;
2509 }
2510 }
2511#endif
2512
2513 const QRect pathRect = state()->matrix.mapRect(path.controlPointRect()).toAlignedRect();
2514
2515 switch (op) {
2516 case Qt::NoClip:
2517 if (d->useSystemClip) {
2518 state()->clipTestEnabled = true;
2519 state()->currentClip = 1;
2520 } else {
2521 state()->clipTestEnabled = false;
2522 }
2523 state()->rectangleClip = QRect(0, 0, d->width, d->height);
2524 state()->canRestoreClip = false;
2525 d->updateClipScissorTest();
2526 break;
2527 case Qt::IntersectClip:
2528 state()->rectangleClip = state()->rectangleClip.intersected(other: pathRect);
2529 d->updateClipScissorTest();
2530 d->resetClipIfNeeded();
2531 ++d->maxClip;
2532 d->writeClip(path, value: d->maxClip);
2533 state()->currentClip = d->maxClip;
2534 state()->clipTestEnabled = true;
2535 break;
2536 default:
2537 break;
2538 }
2539}
2540
2541void QOpenGL2PaintEngineExPrivate::regenerateClip()
2542{
2543 systemStateChanged();
2544 replayClipOperations();
2545}
2546
2547void QOpenGL2PaintEngineExPrivate::systemStateChanged()
2548{
2549 Q_Q(QOpenGL2PaintEngineEx);
2550
2551 q->state()->clipChanged = true;
2552
2553 if (systemClip.isEmpty()) {
2554 useSystemClip = false;
2555 } else {
2556 if (q->paintDevice()->devType() == QInternal::Widget && currentClipDevice) {
2557 //QWidgetPrivate *widgetPrivate = qt_widget_private(static_cast<QWidget *>(currentClipDevice)->window());
2558 //useSystemClip = widgetPrivate->extra && widgetPrivate->extra->inRenderWithPainter;
2559 useSystemClip = true;
2560 } else {
2561 useSystemClip = true;
2562 }
2563 }
2564
2565 q->state()->clipTestEnabled = false;
2566 q->state()->needsClipBufferClear = true;
2567
2568 q->state()->currentClip = 1;
2569 maxClip = 1;
2570
2571 q->state()->rectangleClip = useSystemClip ? systemClip.boundingRect() : QRect(0, 0, width, height);
2572 updateClipScissorTest();
2573
2574 if (systemClip.rectCount() == 1) {
2575 if (systemClip.boundingRect() == QRect(0, 0, width, height))
2576 useSystemClip = false;
2577#ifndef QT_GL_NO_SCISSOR_TEST
2578 // scissoring takes care of the system clip
2579 return;
2580#endif
2581 }
2582
2583 if (useSystemClip) {
2584 clearClip(value: 0);
2585
2586 QPainterPath path;
2587 path.addRegion(region: systemClip);
2588
2589 q->state()->currentClip = 0;
2590 writeClip(path: qtVectorPathForPath(path: q->state()->matrix.inverted().map(p: path)), value: 1);
2591 q->state()->currentClip = 1;
2592 q->state()->clipTestEnabled = true;
2593 }
2594}
2595
2596void QOpenGL2PaintEngineEx::setState(QPainterState *new_state)
2597{
2598 // qDebug("QOpenGL2PaintEngineEx::setState()");
2599
2600 Q_D(QOpenGL2PaintEngineEx);
2601
2602 QOpenGL2PaintEngineState *s = static_cast<QOpenGL2PaintEngineState *>(new_state);
2603 QOpenGL2PaintEngineState *old_state = state();
2604
2605 QPaintEngineEx::setState(s);
2606
2607 if (s->isNew) {
2608 // Newly created state object. The call to setState()
2609 // will either be followed by a call to begin(), or we are
2610 // setting the state as part of a save().
2611 s->isNew = false;
2612 return;
2613 }
2614
2615 // Setting the state as part of a restore().
2616
2617 if (old_state == s || old_state->renderHintsChanged)
2618 renderHintsChanged();
2619
2620 if (old_state == s || old_state->matrixChanged)
2621 d->matrixDirty = true;
2622
2623 if (old_state == s || old_state->compositionModeChanged)
2624 d->compositionModeDirty = true;
2625
2626 if (old_state == s || old_state->opacityChanged)
2627 d->opacityUniformDirty = true;
2628
2629 if (old_state == s || old_state->clipChanged) {
2630 if (old_state && old_state != s && old_state->canRestoreClip) {
2631 d->updateClipScissorTest();
2632 d->funcs.glDepthFunc(GL_LEQUAL);
2633 } else {
2634 d->regenerateClip();
2635 }
2636 }
2637}
2638
2639QPainterState *QOpenGL2PaintEngineEx::createState(QPainterState *orig) const
2640{
2641 if (orig)
2642 const_cast<QOpenGL2PaintEngineEx *>(this)->ensureActive();
2643
2644 QOpenGL2PaintEngineState *s;
2645 if (!orig)
2646 s = new QOpenGL2PaintEngineState();
2647 else
2648 s = new QOpenGL2PaintEngineState(*static_cast<QOpenGL2PaintEngineState *>(orig));
2649
2650 s->matrixChanged = false;
2651 s->compositionModeChanged = false;
2652 s->opacityChanged = false;
2653 s->renderHintsChanged = false;
2654 s->clipChanged = false;
2655
2656 return s;
2657}
2658
2659QOpenGL2PaintEngineState::QOpenGL2PaintEngineState(QOpenGL2PaintEngineState &other)
2660 : QPainterState(other)
2661{
2662 isNew = true;
2663 needsClipBufferClear = other.needsClipBufferClear;
2664 clipTestEnabled = other.clipTestEnabled;
2665 currentClip = other.currentClip;
2666 canRestoreClip = other.canRestoreClip;
2667 rectangleClip = other.rectangleClip;
2668}
2669
2670QOpenGL2PaintEngineState::QOpenGL2PaintEngineState()
2671{
2672 isNew = true;
2673 needsClipBufferClear = true;
2674 clipTestEnabled = false;
2675 canRestoreClip = true;
2676}
2677
2678QOpenGL2PaintEngineState::~QOpenGL2PaintEngineState()
2679{
2680}
2681
2682void QOpenGL2PaintEngineExPrivate::setVertexAttribArrayEnabled(int arrayIndex, bool enabled)
2683{
2684 Q_ASSERT(arrayIndex < QT_GL_VERTEX_ARRAY_TRACKED_COUNT);
2685
2686 if (vertexAttributeArraysEnabledState[arrayIndex] && !enabled)
2687 funcs.glDisableVertexAttribArray(index: arrayIndex);
2688
2689 if (!vertexAttributeArraysEnabledState[arrayIndex] && enabled)
2690 funcs.glEnableVertexAttribArray(index: arrayIndex);
2691
2692 vertexAttributeArraysEnabledState[arrayIndex] = enabled;
2693}
2694
2695void QOpenGL2PaintEngineExPrivate::syncGlState()
2696{
2697 for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i) {
2698 if (vertexAttributeArraysEnabledState[i])
2699 funcs.glEnableVertexAttribArray(index: i);
2700 else
2701 funcs.glDisableVertexAttribArray(index: i);
2702 }
2703}
2704
2705
2706QT_END_NAMESPACE
2707

source code of qtbase/src/gui/opengl/qopenglpaintengine.cpp