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