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 qsizetype floatSizeInBytes = qsizetype(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 // Using uint16 indices. Also we do not want 0xFFFF to be used as an index
1926 // value to not accidentally conflict with primitive restart.
1927 constexpr int maxGlyphsPerRun = 65536 / 4 - 1; // 16383 -> largest index value is 65531
1928 if (numGlyphs > maxGlyphsPerRun) {
1929 qWarning("drawCachedGlyphs: glyph run of %d glyphs exceeds the %d-glyph limit; clamping",
1930 numGlyphs, maxGlyphsPerRun);
1931 numGlyphs = maxGlyphsPerRun;
1932 }
1933
1934 if (elementIndices.size() < numGlyphs*6) {
1935 Q_ASSERT(elementIndices.size() % 6 == 0);
1936 int j = elementIndices.size() / 6 * 4;
1937 while (j < numGlyphs*4) {
1938 elementIndices.append(j + 0);
1939 elementIndices.append(j + 0);
1940 elementIndices.append(j + 1);
1941 elementIndices.append(j + 2);
1942 elementIndices.append(j + 3);
1943 elementIndices.append(j + 3);
1944
1945 j += 4;
1946 }
1947
1948#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
1949 if (elementIndicesVBOId == 0)
1950 funcs.glGenBuffers(1, &elementIndicesVBOId);
1951
1952 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementIndicesVBOId);
1953 funcs.glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementIndices.size() * sizeof(GLushort),
1954 elementIndices.constData(), GL_STATIC_DRAW);
1955#endif
1956 } else {
1957#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
1958 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementIndicesVBOId);
1959#endif
1960 }
1961
1962 if (glyphFormat != QFontEngine::Format_ARGB || recreateVertexArrays) {
1963 uploadData(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinates->data(), vertexCoordinates->vertexCount() * 2);
1964 uploadData(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinates->data(), textureCoordinates->vertexCount() * 2);
1965 }
1966
1967 if (!snapToPixelGrid) {
1968 snapToPixelGrid = true;
1969 matrixDirty = true;
1970 }
1971
1972 QBrush pensBrush = q->state()->pen.brush();
1973 setBrush(pensBrush);
1974
1975 if (glyphFormat == QFontEngine::Format_A32) {
1976
1977 // Subpixel antialiasing without gamma correction
1978
1979 QPainter::CompositionMode compMode = q->state()->composition_mode;
1980 Q_ASSERT(compMode == QPainter::CompositionMode_Source
1981 || compMode == QPainter::CompositionMode_SourceOver);
1982
1983 shaderManager->setMaskType(QOpenGLEngineShaderManager::SubPixelMaskPass1);
1984
1985 if (pensBrush.style() == Qt::SolidPattern) {
1986 // Solid patterns can get away with only one pass.
1987 QColor c = pensBrush.color();
1988 qreal oldOpacity = q->state()->opacity;
1989 if (compMode == QPainter::CompositionMode_Source) {
1990 c = qt_premultiplyColor(c, q->state()->opacity);
1991 q->state()->opacity = 1;
1992 opacityUniformDirty = true;
1993 }
1994
1995 compositionModeDirty = false; // I can handle this myself, thank you very much
1996 prepareForCachedGlyphDraw(*cache);
1997
1998 // prepareForCachedGlyphDraw() have set the opacity on the current shader, so the opacity state can now be reset.
1999 if (compMode == QPainter::CompositionMode_Source) {
2000 q->state()->opacity = oldOpacity;
2001 opacityUniformDirty = true;
2002 }
2003
2004 funcs.glEnable(GL_BLEND);
2005 funcs.glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR);
2006 funcs.glBlendColor(c.redF(), c.greenF(), c.blueF(), c.alphaF());
2007 } else {
2008 // Other brush styles need two passes.
2009
2010 qreal oldOpacity = q->state()->opacity;
2011 if (compMode == QPainter::CompositionMode_Source) {
2012 q->state()->opacity = 1;
2013 opacityUniformDirty = true;
2014 pensBrush = Qt::white;
2015 setBrush(pensBrush);
2016 }
2017
2018 compositionModeDirty = false; // I can handle this myself, thank you very much
2019 prepareForCachedGlyphDraw(*cache);
2020 funcs.glEnable(GL_BLEND);
2021 funcs.glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2022
2023 updateTexture(QT_MASK_TEXTURE_UNIT, cache->texture(), GL_REPEAT, GL_NEAREST, ForceUpdate);
2024
2025#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
2026 funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0);
2027#else
2028 const bool useIndexVbo = uploadIndexData(elementIndices.data(), GL_UNSIGNED_SHORT, 6 * numGlyphs);
2029 funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, useIndexVbo ? nullptr : elementIndices.data());
2030#endif
2031
2032 shaderManager->setMaskType(QOpenGLEngineShaderManager::SubPixelMaskPass2);
2033
2034 if (compMode == QPainter::CompositionMode_Source) {
2035 q->state()->opacity = oldOpacity;
2036 opacityUniformDirty = true;
2037 pensBrush = q->state()->pen.brush();
2038 setBrush(pensBrush);
2039 }
2040
2041 compositionModeDirty = false;
2042 prepareForCachedGlyphDraw(*cache);
2043 funcs.glEnable(GL_BLEND);
2044 funcs.glBlendFunc(GL_ONE, GL_ONE);
2045 }
2046 compositionModeDirty = true;
2047 } else if (glyphFormat == QFontEngine::Format_ARGB) {
2048 currentBrush = noBrush;
2049 shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
2050 if (prepareForCachedGlyphDraw(*cache))
2051 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
2052 } else {
2053 // Grayscale/mono glyphs
2054
2055 shaderManager->setMaskType(QOpenGLEngineShaderManager::PixelMask);
2056 prepareForCachedGlyphDraw(*cache);
2057 }
2058
2059 GLenum textureUnit = QT_MASK_TEXTURE_UNIT;
2060 if (glyphFormat == QFontEngine::Format_ARGB)
2061 textureUnit = QT_IMAGE_TEXTURE_UNIT;
2062
2063 QOpenGLTextureGlyphCache::FilterMode filterMode = (s->matrix.type() > QTransform::TxTranslate) ?
2064 QOpenGLTextureGlyphCache::Linear : QOpenGLTextureGlyphCache::Nearest;
2065
2066 GLenum glFilterMode = filterMode == QOpenGLTextureGlyphCache::Linear ? GL_LINEAR : GL_NEAREST;
2067
2068 TextureUpdateMode updateMode = UpdateIfNeeded;
2069 if (cache->filterMode() != filterMode) {
2070 updateMode = ForceUpdate;
2071 cache->setFilterMode(filterMode);
2072 }
2073
2074 updateTexture(textureUnit, cache->texture(), GL_REPEAT, glFilterMode, updateMode);
2075
2076#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
2077 funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0);
2078 funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
2079#else
2080 const bool useIndexVbo = uploadIndexData(elementIndices.data(), GL_UNSIGNED_SHORT, 6 * numGlyphs);
2081 funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, useIndexVbo ? nullptr : elementIndices.data());
2082#endif
2083}
2084
2085QT_WARNING_POP
2086
2087void QOpenGL2PaintEngineEx::drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragmentCount, const QPixmap &pixmap,
2088 QPainter::PixmapFragmentHints hints)
2089{
2090 Q_D(QOpenGL2PaintEngineEx);
2091 // Use fallback for extended composition modes.
2092 if (state()->composition_mode > QPainter::CompositionMode_Plus) {
2093 QPaintEngineEx::drawPixmapFragments(fragments, fragmentCount, pixmap, hints);
2094 return;
2095 }
2096
2097 ensureActive();
2098 int max_texture_size = d->ctx->d_func()->maxTextureSize();
2099 if (pixmap.width() > max_texture_size || pixmap.height() > max_texture_size) {
2100 QPixmap scaled = pixmap.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
2101 d->drawPixmapFragments(fragments, fragmentCount, scaled, hints);
2102 } else {
2103 d->drawPixmapFragments(fragments, fragmentCount, pixmap, hints);
2104 }
2105}
2106
2107
2108void QOpenGL2PaintEngineExPrivate::drawPixmapFragments(const QPainter::PixmapFragment *fragments,
2109 int fragmentCount, const QPixmap &pixmap,
2110 QPainter::PixmapFragmentHints hints)
2111{
2112 GLfloat dx = 1.0f / pixmap.size().width();
2113 GLfloat dy = 1.0f / pixmap.size().height();
2114
2115 vertexCoordinateArray.clear();
2116 textureCoordinateArray.clear();
2117 opacityArray.reset();
2118
2119 if (snapToPixelGrid) {
2120 snapToPixelGrid = false;
2121 matrixDirty = true;
2122 }
2123
2124 bool allOpaque = true;
2125
2126 for (int i = 0; i < fragmentCount; ++i) {
2127 qreal s = 0;
2128 qreal c = 1;
2129 if (fragments[i].rotation != 0) {
2130 s = qFastSin(qDegreesToRadians(fragments[i].rotation));
2131 c = qFastCos(qDegreesToRadians(fragments[i].rotation));
2132 }
2133
2134 qreal right = 0.5 * fragments[i].scaleX * fragments[i].width;
2135 qreal bottom = 0.5 * fragments[i].scaleY * fragments[i].height;
2136 QOpenGLPoint bottomRight(right * c - bottom * s, right * s + bottom * c);
2137 QOpenGLPoint bottomLeft(-right * c - bottom * s, -right * s + bottom * c);
2138
2139 vertexCoordinateArray.addVertex(bottomRight.x + fragments[i].x, bottomRight.y + fragments[i].y);
2140 vertexCoordinateArray.addVertex(-bottomLeft.x + fragments[i].x, -bottomLeft.y + fragments[i].y);
2141 vertexCoordinateArray.addVertex(-bottomRight.x + fragments[i].x, -bottomRight.y + fragments[i].y);
2142 vertexCoordinateArray.addVertex(-bottomRight.x + fragments[i].x, -bottomRight.y + fragments[i].y);
2143 vertexCoordinateArray.addVertex(bottomLeft.x + fragments[i].x, bottomLeft.y + fragments[i].y);
2144 vertexCoordinateArray.addVertex(bottomRight.x + fragments[i].x, bottomRight.y + fragments[i].y);
2145
2146 QOpenGLRect src(fragments[i].sourceLeft * dx, fragments[i].sourceTop * dy,
2147 (fragments[i].sourceLeft + fragments[i].width) * dx,
2148 (fragments[i].sourceTop + fragments[i].height) * dy);
2149
2150 textureCoordinateArray.addVertex(src.right, src.bottom);
2151 textureCoordinateArray.addVertex(src.right, src.top);
2152 textureCoordinateArray.addVertex(src.left, src.top);
2153 textureCoordinateArray.addVertex(src.left, src.top);
2154 textureCoordinateArray.addVertex(src.left, src.bottom);
2155 textureCoordinateArray.addVertex(src.right, src.bottom);
2156
2157 qreal opacity = fragments[i].opacity * q->state()->opacity;
2158 opacityArray << opacity << opacity << opacity << opacity << opacity << opacity;
2159 allOpaque &= (opacity >= 0.99f);
2160 }
2161
2162 transferMode(ImageOpacityArrayDrawingMode);
2163
2164 uploadData(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinateArray.data(), vertexCoordinateArray.vertexCount() * 2);
2165 uploadData(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinateArray.data(), textureCoordinateArray.vertexCount() * 2);
2166 uploadData(QT_OPACITY_ATTR, (GLfloat*)opacityArray.data(), opacityArray.size());
2167
2168 GLenum filterMode = q->state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
2169 updateTexture(QT_IMAGE_TEXTURE_UNIT, pixmap, GL_CLAMP_TO_EDGE, filterMode);
2170
2171 bool isBitmap = pixmap.isQBitmap();
2172 bool isOpaque = !isBitmap && (!pixmap.hasAlpha() || (hints & QPainter::OpaqueHint)) && allOpaque;
2173
2174 // Setup for texture drawing
2175 currentBrush = noBrush;
2176 shaderManager->setSrcPixelType(isBitmap ? QOpenGLEngineShaderManager::PatternSrc
2177 : QOpenGLEngineShaderManager::ImageSrc);
2178 if (prepareForDraw(isOpaque))
2179 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
2180
2181 if (isBitmap) {
2182 QColor col = qt_premultiplyColor(q->state()->pen.color(), (GLfloat)q->state()->opacity);
2183 shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::PatternColor), col);
2184 }
2185
2186 funcs.glDrawArrays(GL_TRIANGLES, 0, 6 * fragmentCount);
2187}
2188
2189bool QOpenGL2PaintEngineEx::begin(QPaintDevice *pdev)
2190{
2191 Q_D(QOpenGL2PaintEngineEx);
2192
2193 Q_ASSERT(pdev->devType() == QInternal::OpenGL);
2194 d->device = static_cast<QOpenGLPaintDevice*>(pdev);
2195
2196 if (!d->device)
2197 return false;
2198
2199 d->device->ensureActiveTarget();
2200
2201 if (d->device->context() != QOpenGLContext::currentContext() || !d->device->context()) {
2202 qWarning("QPainter::begin(): QOpenGLPaintDevice's context needs to be current");
2203 return false;
2204 }
2205
2206 if (d->ctx != QOpenGLContext::currentContext()
2207 || (d->ctx && QOpenGLContext::currentContext() && d->ctx->format() != QOpenGLContext::currentContext()->format())) {
2208 d->vertexBuffer.destroy();
2209 d->texCoordBuffer.destroy();
2210 d->opacityBuffer.destroy();
2211 d->indexBuffer.destroy();
2212 d->vao.destroy();
2213 }
2214
2215 d->ctx = QOpenGLContext::currentContext();
2216 d->ctx->d_func()->active_engine = this;
2217
2218 QOpenGLPaintDevicePrivate::get(d->device)->beginPaint();
2219
2220 d->funcs.initializeOpenGLFunctions();
2221
2222 // Generate a new Vertex Array Object if we don't have one already. We can
2223 // only hit the VAO-based path when using a core profile context. This is
2224 // because while non-core contexts can support VAOs via extensions, legacy
2225 // components like the QtOpenGL module do not know about VAOs. There are
2226 // still tests for QGL-QOpenGL paint engine interoperability, so keep the
2227 // status quo for now, and avoid introducing a VAO in non-core contexts.
2228 const bool needsVAO = d->ctx->format().profile() == QSurfaceFormat::CoreProfile
2229 && d->ctx->format().version() >= std::pair(3, 2);
2230 if (needsVAO && !d->vao.isCreated()) {
2231 bool created = d->vao.create();
2232
2233 // If we managed to create it then we have a profile that supports VAOs
2234 if (created)
2235 d->vao.bind();
2236 }
2237
2238 // Generate a new Vertex Buffer Object if we don't have one already
2239 if (!d->vertexBuffer.isCreated()) {
2240 d->vertexBuffer.create();
2241 // Set its usage to StreamDraw, we will use this buffer only a few times before refilling it
2242 d->vertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2243 }
2244 if (!d->texCoordBuffer.isCreated()) {
2245 d->texCoordBuffer.create();
2246 d->texCoordBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2247 }
2248 if (!d->opacityBuffer.isCreated()) {
2249 d->opacityBuffer.create();
2250 d->opacityBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2251 }
2252 if (!d->indexBuffer.isCreated()) {
2253 d->indexBuffer.create();
2254 d->indexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
2255 }
2256
2257 for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i)
2258 d->vertexAttributeArraysEnabledState[i] = false;
2259
2260 const QSize sz = d->device->size();
2261 d->width = sz.width();
2262 d->height = sz.height();
2263 d->mode = BrushDrawingMode;
2264 d->brushTextureDirty = true;
2265 d->brushUniformsDirty = true;
2266 d->matrixUniformDirty = true;
2267 d->matrixDirty = true;
2268 d->compositionModeDirty = true;
2269 d->opacityUniformDirty = true;
2270 d->needsSync = true;
2271 d->useSystemClip = !systemClip().isEmpty();
2272 d->currentBrush = QBrush();
2273
2274 d->dirtyStencilRegion = QRect(0, 0, d->width, d->height);
2275 d->stencilClean = true;
2276
2277 d->shaderManager = new QOpenGLEngineShaderManager(d->ctx);
2278
2279 d->funcs.glDisable(GL_STENCIL_TEST);
2280 d->funcs.glDisable(GL_DEPTH_TEST);
2281 d->funcs.glDisable(GL_SCISSOR_TEST);
2282
2283 d->glyphCacheFormat = QFontEngine::Format_A8;
2284
2285#if !QT_CONFIG(opengles2)
2286 if (!QOpenGLContext::currentContext()->isOpenGLES()) {
2287 d->funcs.glDisable(GL_MULTISAMPLE);
2288 d->glyphCacheFormat = QFontEngine::Format_A32;
2289 d->multisamplingAlwaysEnabled = false;
2290 } else
2291#endif // !QT_CONFIG(opengles2)
2292 {
2293 // OpenGL ES can't switch MSAA off, so if the gl paint device is
2294 // multisampled, it's always multisampled.
2295 d->multisamplingAlwaysEnabled = d->device->context()->format().samples() > 1;
2296 }
2297
2298 return true;
2299}
2300
2301bool QOpenGL2PaintEngineEx::end()
2302{
2303 Q_D(QOpenGL2PaintEngineEx);
2304
2305 QOpenGLPaintDevicePrivate::get(d->device)->endPaint();
2306
2307 QOpenGLContext *ctx = d->ctx;
2308 d->funcs.glUseProgram(0);
2309 d->transferMode(BrushDrawingMode);
2310
2311 ctx->d_func()->active_engine = nullptr;
2312
2313 d->resetGLState();
2314
2315 delete d->shaderManager;
2316 d->shaderManager = nullptr;
2317 d->currentBrush = QBrush();
2318
2319#ifdef QT_OPENGL_CACHE_AS_VBOS
2320 if (!d->unusedVBOSToClean.isEmpty()) {
2321 glDeleteBuffers(d->unusedVBOSToClean.size(), d->unusedVBOSToClean.constData());
2322 d->unusedVBOSToClean.clear();
2323 }
2324 if (!d->unusedIBOSToClean.isEmpty()) {
2325 glDeleteBuffers(d->unusedIBOSToClean.size(), d->unusedIBOSToClean.constData());
2326 d->unusedIBOSToClean.clear();
2327 }
2328#endif
2329
2330 return false;
2331}
2332
2333void QOpenGL2PaintEngineEx::ensureActive()
2334{
2335 Q_D(QOpenGL2PaintEngineEx);
2336 QOpenGLContext *ctx = d->ctx;
2337
2338 if (d->vao.isCreated())
2339 d->vao.bind();
2340
2341 if (isActive() && ctx->d_func()->active_engine != this) {
2342 ctx->d_func()->active_engine = this;
2343 d->needsSync = true;
2344 }
2345
2346 if (d->needsSync) {
2347 d->device->ensureActiveTarget();
2348
2349 d->transferMode(BrushDrawingMode);
2350 d->funcs.glViewport(0, 0, d->width, d->height);
2351 d->needsSync = false;
2352 d->shaderManager->setDirty();
2353 d->syncGlState();
2354 for (int i = 0; i < 3; ++i)
2355 d->vertexAttribPointers[i] = (GLfloat*)-1; // Assume the pointers are clobbered
2356 setState(state());
2357 }
2358}
2359
2361{
2362 Q_Q(QOpenGL2PaintEngineEx);
2363 if (q->state()->clipTestEnabled) {
2364#ifndef QT_NO_DEBUG
2365 if (ctx->format().stencilBufferSize() <= 0)
2366 qWarning("OpenGL paint engine: attempted to use stencil test for clipping without requesting a stencil buffer.");
2367#endif
2368 funcs.glEnable(GL_STENCIL_TEST);
2369 funcs.glStencilFunc(GL_LEQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
2370 } else {
2371 funcs.glDisable(GL_STENCIL_TEST);
2372 funcs.glStencilFunc(GL_ALWAYS, 0, 0xff);
2373 }
2374
2375#ifdef QT_GL_NO_SCISSOR_TEST
2376 currentScissorBounds = QRect(0, 0, width, height);
2377#else
2378 QRect bounds = q->state()->rectangleClip;
2379 if (!q->state()->clipEnabled) {
2380 if (useSystemClip)
2381 bounds = systemClip.boundingRect();
2382 else
2383 bounds = QRect(0, 0, width, height);
2384 } else {
2385 if (useSystemClip)
2386 bounds = bounds.intersected(systemClip.boundingRect());
2387 else
2388 bounds = bounds.intersected(QRect(0, 0, width, height));
2389 }
2390
2391 currentScissorBounds = bounds;
2392
2393 if (bounds == QRect(0, 0, width, height)) {
2394 funcs.glDisable(GL_SCISSOR_TEST);
2395 } else {
2396 funcs.glEnable(GL_SCISSOR_TEST);
2397 setScissor(bounds);
2398 }
2399#endif
2400}
2401
2403{
2404 const int left = rect.left();
2405 const int width = rect.width();
2406 int bottom = height - (rect.top() + rect.height());
2407 if (device->paintFlipped()) {
2408 bottom = rect.top();
2409 }
2410 const int height = rect.height();
2411
2412 funcs.glScissor(left, bottom, width, height);
2413}
2414
2415void QOpenGL2PaintEngineEx::clipEnabledChanged()
2416{
2417 Q_D(QOpenGL2PaintEngineEx);
2418
2419 state()->clipChanged = true;
2420
2421 if (painter()->hasClipping())
2422 d->regenerateClip();
2423 else
2424 d->systemStateChanged();
2425}
2426
2427void QOpenGL2PaintEngineExPrivate::clearClip(uint value)
2428{
2429 dirtyStencilRegion -= currentScissorBounds;
2430
2431 funcs.glStencilMask(0xff);
2432 funcs.glClearStencil(value);
2433 funcs.glClear(GL_STENCIL_BUFFER_BIT);
2434 funcs.glStencilMask(0x0);
2435
2436 q->state()->needsClipBufferClear = false;
2437}
2438
2439void QOpenGL2PaintEngineExPrivate::writeClip(const QVectorPath &path, uint value)
2440{
2441 transferMode(BrushDrawingMode);
2442
2443 if (snapToPixelGrid) {
2444 snapToPixelGrid = false;
2445 matrixDirty = true;
2446 }
2447
2448 if (matrixDirty)
2450
2451 stencilClean = false;
2452
2453 const bool singlePass = !path.hasWindingFill()
2454 && (((q->state()->currentClip == maxClip - 1) && q->state()->clipTestEnabled)
2455 || q->state()->needsClipBufferClear);
2456 const uint referenceClipValue = q->state()->needsClipBufferClear ? 1 : q->state()->currentClip;
2457
2458 if (q->state()->needsClipBufferClear)
2459 clearClip(1);
2460
2461 if (path.isEmpty()) {
2462 funcs.glEnable(GL_STENCIL_TEST);
2463 funcs.glStencilFunc(GL_LEQUAL, value, ~GL_STENCIL_HIGH_BIT);
2464 return;
2465 }
2466
2467 if (q->state()->clipTestEnabled)
2468 funcs.glStencilFunc(GL_LEQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
2469 else
2470 funcs.glStencilFunc(GL_ALWAYS, 0, 0xff);
2471
2472 vertexCoordinateArray.clear();
2473 vertexCoordinateArray.addPath(path, inverseScale, false);
2474
2475 if (!singlePass)
2476 fillStencilWithVertexArray(vertexCoordinateArray, path.hasWindingFill());
2477
2478 funcs.glColorMask(false, false, false, false);
2479 funcs.glEnable(GL_STENCIL_TEST);
2481
2482 if (singlePass) {
2483 // Under these conditions we can set the new stencil value in a single
2484 // pass, by using the current value and the "new value" as the toggles
2485
2486 funcs.glStencilFunc(GL_LEQUAL, referenceClipValue, ~GL_STENCIL_HIGH_BIT);
2487 funcs.glStencilOp(GL_KEEP, GL_INVERT, GL_INVERT);
2488 funcs.glStencilMask(value ^ referenceClipValue);
2489
2490 drawVertexArrays(vertexCoordinateArray, GL_TRIANGLE_FAN);
2491 } else {
2492 funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
2493 funcs.glStencilMask(0xff);
2494
2495 if (!q->state()->clipTestEnabled && path.hasWindingFill()) {
2496 // Pass when any clip bit is set, set high bit
2497 funcs.glStencilFunc(GL_NOTEQUAL, GL_STENCIL_HIGH_BIT, ~GL_STENCIL_HIGH_BIT);
2498 composite(vertexCoordinateArray.boundingRect());
2499 }
2500
2501 // Pass when high bit is set, replace stencil value with new clip value
2502 funcs.glStencilFunc(GL_NOTEQUAL, value, GL_STENCIL_HIGH_BIT);
2503
2504 composite(vertexCoordinateArray.boundingRect());
2505 }
2506
2507 funcs.glStencilFunc(GL_LEQUAL, value, ~GL_STENCIL_HIGH_BIT);
2508 funcs.glStencilMask(0);
2509
2510 funcs.glColorMask(true, true, true, true);
2511}
2512
2513void QOpenGL2PaintEngineEx::clip(const QVectorPath &path, Qt::ClipOperation op)
2514{
2515// qDebug("QOpenGL2PaintEngineEx::clip()");
2516 Q_D(QOpenGL2PaintEngineEx);
2517
2518 state()->clipChanged = true;
2519
2520 ensureActive();
2521
2522 if (op == Qt::ReplaceClip) {
2523 op = Qt::IntersectClip;
2524 if (d->hasClipOperations()) {
2525 d->systemStateChanged();
2526 state()->canRestoreClip = false;
2527 }
2528 }
2529
2530#ifndef QT_GL_NO_SCISSOR_TEST
2531 if (!path.isEmpty() && op == Qt::IntersectClip && (path.shape() == QVectorPath::RectangleHint)) {
2532 const QPointF* const points = reinterpret_cast<const QPointF*>(path.points());
2533 QRectF rect(points[0], points[2]);
2534
2535 if (state()->matrix.type() <= QTransform::TxScale
2536 || (state()->matrix.type() == QTransform::TxRotate
2537 && qFuzzyIsNull(state()->matrix.m11())
2538 && qFuzzyIsNull(state()->matrix.m22())))
2539 {
2540 state()->rectangleClip &= qt_mapFillRect(rect, state()->matrix);
2541 d->updateClipScissorTest();
2542 return;
2543 }
2544 }
2545#endif
2546
2547 const QRect pathRect = state()->matrix.mapRect(path.controlPointRect()).toAlignedRect();
2548
2549 switch (op) {
2550 case Qt::NoClip:
2551 if (d->useSystemClip) {
2552 state()->clipTestEnabled = true;
2553 state()->currentClip = 1;
2554 } else {
2555 state()->clipTestEnabled = false;
2556 }
2557 state()->rectangleClip = QRect(0, 0, d->width, d->height);
2558 state()->canRestoreClip = false;
2559 d->updateClipScissorTest();
2560 break;
2561 case Qt::IntersectClip:
2562 state()->rectangleClip = state()->rectangleClip.intersected(pathRect);
2563 d->updateClipScissorTest();
2564 d->resetClipIfNeeded();
2565 ++d->maxClip;
2566 d->writeClip(path, d->maxClip);
2567 state()->currentClip = d->maxClip;
2568 state()->clipTestEnabled = true;
2569 break;
2570 default:
2571 break;
2572 }
2573}
2574
2576{
2578 replayClipOperations();
2579}
2580
2582{
2583 Q_Q(QOpenGL2PaintEngineEx);
2584
2585 q->state()->clipChanged = true;
2586
2587 if (systemClip.isEmpty()) {
2588 useSystemClip = false;
2589 } else {
2590 if (q->paintDevice()->devType() == QInternal::Widget && currentClipDevice) {
2591 //QWidgetPrivate *widgetPrivate = qt_widget_private(static_cast<QWidget *>(currentClipDevice)->window());
2592 //useSystemClip = widgetPrivate->extra && widgetPrivate->extra->inRenderWithPainter;
2593 useSystemClip = true;
2594 } else {
2595 useSystemClip = true;
2596 }
2597 }
2598
2599 q->state()->clipTestEnabled = false;
2600 q->state()->needsClipBufferClear = true;
2601
2602 q->state()->currentClip = 1;
2603 maxClip = 1;
2604
2605 q->state()->rectangleClip = useSystemClip ? systemClip.boundingRect() : QRect(0, 0, width, height);
2607
2608 if (systemClip.rectCount() == 1) {
2609 if (systemClip.boundingRect() == QRect(0, 0, width, height))
2610 useSystemClip = false;
2611#ifndef QT_GL_NO_SCISSOR_TEST
2612 // scissoring takes care of the system clip
2613 return;
2614#endif
2615 }
2616
2617 if (useSystemClip) {
2618 clearClip(0);
2619
2620 QPainterPath path;
2621 path.addRegion(systemClip);
2622
2623 q->state()->currentClip = 0;
2624 writeClip(qtVectorPathForPath(q->state()->matrix.inverted().map(path)), 1);
2625 q->state()->currentClip = 1;
2626 q->state()->clipTestEnabled = true;
2627 }
2628}
2629
2630void QOpenGL2PaintEngineEx::setState(QPainterState *new_state)
2631{
2632 // qDebug("QOpenGL2PaintEngineEx::setState()");
2633
2634 Q_D(QOpenGL2PaintEngineEx);
2635
2636 QOpenGL2PaintEngineState *s = static_cast<QOpenGL2PaintEngineState *>(new_state);
2637 QOpenGL2PaintEngineState *old_state = state();
2638
2639 QPaintEngineEx::setState(s);
2640
2641 if (s->isNew) {
2642 // Newly created state object. The call to setState()
2643 // will either be followed by a call to begin(), or we are
2644 // setting the state as part of a save().
2645 s->isNew = false;
2646 return;
2647 }
2648
2649 // Setting the state as part of a restore().
2650
2651 if (old_state == s || old_state->renderHintsChanged)
2652 renderHintsChanged();
2653
2654 if (old_state == s || old_state->matrixChanged)
2655 d->matrixDirty = true;
2656
2657 if (old_state == s || old_state->compositionModeChanged)
2658 d->compositionModeDirty = true;
2659
2660 if (old_state == s || old_state->opacityChanged)
2661 d->opacityUniformDirty = true;
2662
2663 if (old_state == s || old_state->clipChanged) {
2664 if (old_state && old_state != s && old_state->canRestoreClip) {
2665 d->updateClipScissorTest();
2666 d->funcs.glDepthFunc(GL_LEQUAL);
2667 } else {
2668 d->regenerateClip();
2669 }
2670 }
2671}
2672
2673QPainterState *QOpenGL2PaintEngineEx::createState(QPainterState *orig) const
2674{
2675 if (orig)
2676 const_cast<QOpenGL2PaintEngineEx *>(this)->ensureActive();
2677
2678 QOpenGL2PaintEngineState *s;
2679 if (!orig)
2680 s = new QOpenGL2PaintEngineState();
2681 else
2682 s = new QOpenGL2PaintEngineState(*static_cast<QOpenGL2PaintEngineState *>(orig));
2683
2684 s->matrixChanged = false;
2685 s->compositionModeChanged = false;
2686 s->opacityChanged = false;
2687 s->renderHintsChanged = false;
2688 s->clipChanged = false;
2689
2690 return s;
2691}
2692
2695{
2696 isNew = true;
2697 needsClipBufferClear = other.needsClipBufferClear;
2698 clipTestEnabled = other.clipTestEnabled;
2699 currentClip = other.currentClip;
2700 canRestoreClip = other.canRestoreClip;
2701 rectangleClip = other.rectangleClip;
2702}
2703
2705{
2706 isNew = true;
2707 needsClipBufferClear = true;
2708 clipTestEnabled = false;
2709 canRestoreClip = true;
2710}
2711
2715
2717{
2718 Q_ASSERT(arrayIndex < QT_GL_VERTEX_ARRAY_TRACKED_COUNT);
2719
2720 if (vertexAttributeArraysEnabledState[arrayIndex] && !enabled)
2721 funcs.glDisableVertexAttribArray(arrayIndex);
2722
2723 if (!vertexAttributeArraysEnabledState[arrayIndex] && enabled)
2724 funcs.glEnableVertexAttribArray(arrayIndex);
2725
2726 vertexAttributeArraysEnabledState[arrayIndex] = enabled;
2727}
2728
2730{
2731 for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i) {
2732 if (vertexAttributeArraysEnabledState[i])
2733 funcs.glEnableVertexAttribArray(i);
2734 else
2735 funcs.glDisableVertexAttribArray(i);
2736 }
2737}
2738
2739
2740QT_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