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

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