summaryrefslogtreecommitdiffstats
path: root/src/opengl
diff options
context:
space:
mode:
Diffstat (limited to 'src/opengl')
-rw-r--r--src/opengl/gl2paintengineex/qgl2pexvertexarray.cpp175
-rw-r--r--src/opengl/gl2paintengineex/qgl2pexvertexarray_p.h169
-rw-r--r--src/opengl/gl2paintengineex/qglcustomshaderstage.cpp138
-rw-r--r--src/opengl/gl2paintengineex/qglcustomshaderstage_p.h93
-rw-r--r--src/opengl/gl2paintengineex/qglengineshadermanager.cpp838
-rw-r--r--src/opengl/gl2paintengineex/qglengineshadermanager_p.h513
-rw-r--r--src/opengl/gl2paintengineex/qglengineshadersource_p.h519
-rw-r--r--src/opengl/gl2paintengineex/qglgradientcache.cpp206
-rw-r--r--src/opengl/gl2paintengineex/qglgradientcache_p.h98
-rw-r--r--src/opengl/gl2paintengineex/qglshadercache_meego_p.h457
-rw-r--r--src/opengl/gl2paintengineex/qglshadercache_p.h98
-rw-r--r--src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp2458
-rw-r--r--src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h332
-rw-r--r--src/opengl/gl2paintengineex/qtextureglyphcache_gl.cpp396
-rw-r--r--src/opengl/gl2paintengineex/qtextureglyphcache_gl_p.h166
-rw-r--r--src/opengl/gl2paintengineex/qtriangulatingstroker.cpp588
-rw-r--r--src/opengl/gl2paintengineex/qtriangulatingstroker_p.h157
-rw-r--r--src/opengl/gl2paintengineex/qtriangulator.cpp3114
-rw-r--r--src/opengl/gl2paintengineex/qtriangulator_p.h148
-rw-r--r--src/opengl/opengl.pro181
-rw-r--r--src/opengl/opengl.pro.user.2.1pre183
-rw-r--r--src/opengl/qgl.cpp6057
-rw-r--r--src/opengl/qgl.h668
-rw-r--r--src/opengl/qgl_egl.cpp351
-rw-r--r--src/opengl/qgl_egl_p.h69
-rw-r--r--src/opengl/qgl_mac.mm996
-rw-r--r--src/opengl/qgl_p.h903
-rw-r--r--src/opengl/qgl_qpa.cpp394
-rw-r--r--src/opengl/qgl_qws.cpp318
-rw-r--r--src/opengl/qgl_symbian.cpp472
-rw-r--r--src/opengl/qgl_win.cpp1601
-rw-r--r--src/opengl/qgl_wince.cpp636
-rw-r--r--src/opengl/qgl_x11.cpp1908
-rw-r--r--src/opengl/qgl_x11egl.cpp548
-rw-r--r--src/opengl/qglbuffer.cpp576
-rw-r--r--src/opengl/qglbuffer.h132
-rw-r--r--src/opengl/qglcolormap.cpp297
-rw-r--r--src/opengl/qglcolormap.h105
-rw-r--r--src/opengl/qglextensions.cpp430
-rw-r--r--src/opengl/qglextensions_p.h897
-rw-r--r--src/opengl/qglframebufferobject.cpp1398
-rw-r--r--src/opengl/qglframebufferobject.h180
-rw-r--r--src/opengl/qglframebufferobject_p.h161
-rw-r--r--src/opengl/qglfunctions.cpp3705
-rw-r--r--src/opengl/qglfunctions.h2295
-rw-r--r--src/opengl/qglpaintdevice.cpp246
-rw-r--r--src/opengl/qglpaintdevice_p.h115
-rw-r--r--src/opengl/qglpixelbuffer.cpp628
-rw-r--r--src/opengl/qglpixelbuffer.h122
-rw-r--r--src/opengl/qglpixelbuffer_egl.cpp224
-rw-r--r--src/opengl/qglpixelbuffer_mac.mm331
-rw-r--r--src/opengl/qglpixelbuffer_p.h209
-rw-r--r--src/opengl/qglpixelbuffer_stub.cpp84
-rw-r--r--src/opengl/qglpixelbuffer_win.cpp403
-rw-r--r--src/opengl/qglpixelbuffer_x11.cpp290
-rw-r--r--src/opengl/qglpixmapfilter.cpp621
-rw-r--r--src/opengl/qglpixmapfilter_p.h94
-rw-r--r--src/opengl/qglscreen_qws.cpp242
-rw-r--r--src/opengl/qglscreen_qws.h127
-rw-r--r--src/opengl/qglshaderprogram.cpp3354
-rw-r--r--src/opengl/qglshaderprogram.h344
-rw-r--r--src/opengl/qgltexturepool.cpp244
-rw-r--r--src/opengl/qgltexturepool_p.h147
-rw-r--r--src/opengl/qglwindowsurface_qws.cpp133
-rw-r--r--src/opengl/qglwindowsurface_qws_p.h90
-rw-r--r--src/opengl/qgraphicsshadereffect.cpp314
-rw-r--r--src/opengl/qgraphicsshadereffect_p.h94
-rw-r--r--src/opengl/qgraphicssystem_gl.cpp114
-rw-r--r--src/opengl/qgraphicssystem_gl_p.h80
-rw-r--r--src/opengl/qpaintengine_opengl.cpp5680
-rw-r--r--src/opengl/qpaintengine_opengl_p.h157
-rw-r--r--src/opengl/qpixmapdata_gl.cpp829
-rw-r--r--src/opengl/qpixmapdata_gl_p.h247
-rw-r--r--src/opengl/qpixmapdata_poolgl.cpp934
-rw-r--r--src/opengl/qpixmapdata_x11gl_egl.cpp403
-rw-r--r--src/opengl/qpixmapdata_x11gl_p.h98
-rw-r--r--src/opengl/qwindowsurface_gl.cpp1149
-rw-r--r--src/opengl/qwindowsurface_gl_p.h129
-rw-r--r--src/opengl/qwindowsurface_x11gl.cpp213
-rw-r--r--src/opengl/qwindowsurface_x11gl_p.h83
-rw-r--r--src/opengl/util/README-GLSL18
-rw-r--r--src/opengl/util/brush_painter.glsl7
-rw-r--r--src/opengl/util/brushes.conf6
-rw-r--r--src/opengl/util/composition_mode_colorburn.glsl13
-rw-r--r--src/opengl/util/composition_mode_colordodge.glsl15
-rw-r--r--src/opengl/util/composition_mode_darken.glsl9
-rw-r--r--src/opengl/util/composition_mode_difference.glsl9
-rw-r--r--src/opengl/util/composition_mode_exclusion.glsl9
-rw-r--r--src/opengl/util/composition_mode_hardlight.glsl14
-rw-r--r--src/opengl/util/composition_mode_lighten.glsl9
-rw-r--r--src/opengl/util/composition_mode_multiply.glsl9
-rw-r--r--src/opengl/util/composition_mode_overlay.glsl13
-rw-r--r--src/opengl/util/composition_mode_screen.glsl6
-rw-r--r--src/opengl/util/composition_mode_softlight.glsl22
-rw-r--r--src/opengl/util/composition_modes.conf12
-rw-r--r--src/opengl/util/conical_brush.glsl27
-rw-r--r--src/opengl/util/ellipse_aa.glsl58
-rw-r--r--src/opengl/util/fast_painter.glsl19
-rw-r--r--src/opengl/util/fragmentprograms_p.h7287
-rw-r--r--src/opengl/util/generator.cpp500
-rw-r--r--src/opengl/util/generator.pro13
-rwxr-xr-xsrc/opengl/util/glsl_to_include.sh73
-rw-r--r--src/opengl/util/linear_brush.glsl22
-rw-r--r--src/opengl/util/masks.conf2
-rw-r--r--src/opengl/util/meego/main.cpp89
-rw-r--r--src/opengl/util/meego/shader-cache-introspector.pro7
-rw-r--r--src/opengl/util/painter.glsl21
-rw-r--r--src/opengl/util/painter_nomask.glsl9
-rw-r--r--src/opengl/util/pattern_brush.glsl23
-rw-r--r--src/opengl/util/radial_brush.glsl28
-rw-r--r--src/opengl/util/simple_porter_duff.glsl16
-rw-r--r--src/opengl/util/solid_brush.glsl4
-rw-r--r--src/opengl/util/texture_brush.glsl21
-rw-r--r--src/opengl/util/trap_exact_aa.glsl58
114 files changed, 63044 insertions, 0 deletions
diff --git a/src/opengl/gl2paintengineex/qgl2pexvertexarray.cpp b/src/opengl/gl2paintengineex/qgl2pexvertexarray.cpp
new file mode 100644
index 0000000000..4677cc47b4
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qgl2pexvertexarray.cpp
@@ -0,0 +1,175 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgl2pexvertexarray_p.h"
+
+#include <private/qbezier_p.h>
+
+QT_BEGIN_NAMESPACE
+
+void QGL2PEXVertexArray::clear()
+{
+ vertexArray.reset();
+ vertexArrayStops.reset();
+ boundingRectDirty = true;
+}
+
+
+QGLRect QGL2PEXVertexArray::boundingRect() const
+{
+ if (boundingRectDirty)
+ return QGLRect(0.0, 0.0, 0.0, 0.0);
+ else
+ return QGLRect(minX, minY, maxX, maxY);
+}
+
+void QGL2PEXVertexArray::addClosingLine(int index)
+{
+ QPointF point(vertexArray.at(index));
+ if (point != QPointF(vertexArray.last()))
+ vertexArray.add(point);
+}
+
+void QGL2PEXVertexArray::addCentroid(const QVectorPath &path, int subPathIndex)
+{
+ const QPointF *const points = reinterpret_cast<const QPointF *>(path.points());
+ const QPainterPath::ElementType *const elements = path.elements();
+
+ QPointF sum = points[subPathIndex];
+ int count = 1;
+
+ for (int i = subPathIndex + 1; i < path.elementCount() && (!elements || elements[i] != QPainterPath::MoveToElement); ++i) {
+ sum += points[i];
+ ++count;
+ }
+
+ const QPointF centroid = sum / qreal(count);
+ vertexArray.add(centroid);
+}
+
+void QGL2PEXVertexArray::addPath(const QVectorPath &path, GLfloat curveInverseScale, bool outline)
+{
+ const QPointF* const points = reinterpret_cast<const QPointF*>(path.points());
+ const QPainterPath::ElementType* const elements = path.elements();
+
+ if (boundingRectDirty) {
+ minX = maxX = points[0].x();
+ minY = maxY = points[0].y();
+ boundingRectDirty = false;
+ }
+
+ if (!outline && !path.isConvex())
+ addCentroid(path, 0);
+
+ int lastMoveTo = vertexArray.size();
+ vertexArray.add(points[0]); // The first element is always a moveTo
+
+ do {
+ if (!elements) {
+// qDebug("QVectorPath has no elements");
+ // If the path has a null elements pointer, the elements implicitly
+ // start with a moveTo (already added) and continue with lineTos:
+ for (int i=1; i<path.elementCount(); ++i)
+ lineToArray(points[i].x(), points[i].y());
+
+ break;
+ }
+// qDebug("QVectorPath has element types");
+
+ for (int i=1; i<path.elementCount(); ++i) {
+ switch (elements[i]) {
+ case QPainterPath::MoveToElement:
+ if (!outline)
+ addClosingLine(lastMoveTo);
+// qDebug("element[%d] is a MoveToElement", i);
+ vertexArrayStops.add(vertexArray.size());
+ if (!outline) {
+ if (!path.isConvex()) addCentroid(path, i);
+ lastMoveTo = vertexArray.size();
+ }
+ lineToArray(points[i].x(), points[i].y()); // Add the moveTo as a new vertex
+ break;
+ case QPainterPath::LineToElement:
+// qDebug("element[%d] is a LineToElement", i);
+ lineToArray(points[i].x(), points[i].y());
+ break;
+ case QPainterPath::CurveToElement: {
+ QBezier b = QBezier::fromPoints(*(((const QPointF *) points) + i - 1),
+ points[i],
+ points[i+1],
+ points[i+2]);
+ QRectF bounds = b.bounds();
+ // threshold based on same algorithm as in qtriangulatingstroker.cpp
+ int threshold = qMin<float>(64, qMax(bounds.width(), bounds.height()) * 3.14f / (curveInverseScale * 6));
+ if (threshold < 3) threshold = 3;
+ qreal one_over_threshold_minus_1 = qreal(1) / (threshold - 1);
+ for (int t=0; t<threshold; ++t) {
+ QPointF pt = b.pointAt(t * one_over_threshold_minus_1);
+ lineToArray(pt.x(), pt.y());
+ }
+ i += 2;
+ break; }
+ default:
+ break;
+ }
+ }
+ } while (0);
+
+ if (!outline)
+ addClosingLine(lastMoveTo);
+ vertexArrayStops.add(vertexArray.size());
+}
+
+void QGL2PEXVertexArray::lineToArray(const GLfloat x, const GLfloat y)
+{
+ vertexArray.add(QGLPoint(x, y));
+
+ if (x > maxX)
+ maxX = x;
+ else if (x < minX)
+ minX = x;
+ if (y > maxY)
+ maxY = y;
+ else if (y < minY)
+ minY = y;
+}
+
+QT_END_NAMESPACE
diff --git a/src/opengl/gl2paintengineex/qgl2pexvertexarray_p.h b/src/opengl/gl2paintengineex/qgl2pexvertexarray_p.h
new file mode 100644
index 0000000000..99cd8d84b0
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qgl2pexvertexarray_p.h
@@ -0,0 +1,169 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QGL2PEXVERTEXARRAY_P_H
+#define QGL2PEXVERTEXARRAY_P_H
+
+#include <QRectF>
+
+#include <private/qdatabuffer_p.h>
+#include <private/qvectorpath_p.h>
+#include <private/qgl_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGLPoint
+{
+public:
+ QGLPoint(GLfloat new_x, GLfloat new_y) :
+ x(new_x), y(new_y) {};
+
+ QGLPoint(const QPointF &p) :
+ x(p.x()), y(p.y()) {};
+
+ QGLPoint(const QPointF* p) :
+ x(p->x()), y(p->y()) {};
+
+ GLfloat x;
+ GLfloat y;
+
+ operator QPointF() {return QPointF(x,y);}
+ operator QPointF() const {return QPointF(x,y);}
+};
+
+struct QGLRect
+{
+ QGLRect(const QRectF &r)
+ : left(r.left()), top(r.top()), right(r.right()), bottom(r.bottom()) {}
+
+ QGLRect(GLfloat l, GLfloat t, GLfloat r, GLfloat b)
+ : left(l), top(t), right(r), bottom(b) {}
+
+ GLfloat left;
+ GLfloat top;
+ GLfloat right;
+ GLfloat bottom;
+
+ operator QRectF() const {return QRectF(left, top, right-left, bottom-top);}
+};
+
+class QGL2PEXVertexArray
+{
+public:
+ QGL2PEXVertexArray() :
+ vertexArray(0), vertexArrayStops(0),
+ maxX(-2e10), maxY(-2e10), minX(2e10), minY(2e10),
+ boundingRectDirty(true)
+ { }
+
+ inline void addRect(const QRectF &rect)
+ {
+ qreal top = rect.top();
+ qreal left = rect.left();
+ qreal bottom = rect.bottom();
+ qreal right = rect.right();
+
+ vertexArray << QGLPoint(left, top)
+ << QGLPoint(right, top)
+ << QGLPoint(right, bottom)
+ << QGLPoint(right, bottom)
+ << QGLPoint(left, bottom)
+ << QGLPoint(left, top);
+ }
+
+ inline void addQuad(const QRectF &rect)
+ {
+ qreal top = rect.top();
+ qreal left = rect.left();
+ qreal bottom = rect.bottom();
+ qreal right = rect.right();
+
+ vertexArray << QGLPoint(left, top)
+ << QGLPoint(right, top)
+ << QGLPoint(left, bottom)
+ << QGLPoint(right, bottom);
+
+ }
+
+ inline void addVertex(const GLfloat x, const GLfloat y)
+ {
+ vertexArray.add(QGLPoint(x, y));
+ }
+
+ void addPath(const QVectorPath &path, GLfloat curveInverseScale, bool outline = true);
+ void clear();
+
+ QGLPoint* data() {return vertexArray.data();}
+ int *stops() const { return vertexArrayStops.data(); }
+ int stopCount() const { return vertexArrayStops.size(); }
+ QGLRect boundingRect() const;
+
+ int vertexCount() const { return vertexArray.size(); }
+
+ void lineToArray(const GLfloat x, const GLfloat y);
+
+private:
+ QDataBuffer<QGLPoint> vertexArray;
+ QDataBuffer<int> vertexArrayStops;
+
+ GLfloat maxX;
+ GLfloat maxY;
+ GLfloat minX;
+ GLfloat minY;
+ bool boundingRectDirty;
+ void addClosingLine(int index);
+ void addCentroid(const QVectorPath &path, int subPathIndex);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/opengl/gl2paintengineex/qglcustomshaderstage.cpp b/src/opengl/gl2paintengineex/qglcustomshaderstage.cpp
new file mode 100644
index 0000000000..85f1ff257b
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qglcustomshaderstage.cpp
@@ -0,0 +1,138 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qglcustomshaderstage_p.h"
+#include "qglengineshadermanager_p.h"
+#include "qpaintengineex_opengl2_p.h"
+#include <private/qpainter_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGLCustomShaderStagePrivate
+{
+public:
+ QGLCustomShaderStagePrivate() :
+ m_manager(0) {}
+
+ QPointer<QGLEngineShaderManager> m_manager;
+ QByteArray m_source;
+};
+
+
+
+
+QGLCustomShaderStage::QGLCustomShaderStage()
+ : d_ptr(new QGLCustomShaderStagePrivate)
+{
+}
+
+QGLCustomShaderStage::~QGLCustomShaderStage()
+{
+ Q_D(QGLCustomShaderStage);
+ if (d->m_manager) {
+ d->m_manager->removeCustomStage();
+ d->m_manager->sharedShaders->cleanupCustomStage(this);
+ }
+}
+
+void QGLCustomShaderStage::setUniformsDirty()
+{
+ Q_D(QGLCustomShaderStage);
+ if (d->m_manager)
+ d->m_manager->setDirty(); // ### Probably a bit overkill!
+}
+
+bool QGLCustomShaderStage::setOnPainter(QPainter* p)
+{
+ Q_D(QGLCustomShaderStage);
+ if (p->paintEngine()->type() != QPaintEngine::OpenGL2) {
+ qWarning("QGLCustomShaderStage::setOnPainter() - paint engine not OpenGL2");
+ return false;
+ }
+ if (d->m_manager)
+ qWarning("Custom shader is already set on a painter");
+
+ QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx*>(p->paintEngine());
+ d->m_manager = QGL2PaintEngineExPrivate::shaderManagerForEngine(engine);
+ Q_ASSERT(d->m_manager);
+
+ d->m_manager->setCustomStage(this);
+ return true;
+}
+
+void QGLCustomShaderStage::removeFromPainter(QPainter* p)
+{
+ Q_D(QGLCustomShaderStage);
+ if (p->paintEngine()->type() != QPaintEngine::OpenGL2)
+ return;
+
+ QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx*>(p->paintEngine());
+ d->m_manager = QGL2PaintEngineExPrivate::shaderManagerForEngine(engine);
+ Q_ASSERT(d->m_manager);
+
+ // Just set the stage to null, don't call removeCustomStage().
+ // This should leave the program in a compiled/linked state
+ // if the next custom shader stage is this one again.
+ d->m_manager->setCustomStage(0);
+ d->m_manager = 0;
+}
+
+QByteArray QGLCustomShaderStage::source() const
+{
+ Q_D(const QGLCustomShaderStage);
+ return d->m_source;
+}
+
+// Called by the shader manager if another custom shader is attached or
+// the manager is deleted
+void QGLCustomShaderStage::setInactive()
+{
+ Q_D(QGLCustomShaderStage);
+ d->m_manager = 0;
+}
+
+void QGLCustomShaderStage::setSource(const QByteArray& s)
+{
+ Q_D(QGLCustomShaderStage);
+ d->m_source = s;
+}
+
+QT_END_NAMESPACE
diff --git a/src/opengl/gl2paintengineex/qglcustomshaderstage_p.h b/src/opengl/gl2paintengineex/qglcustomshaderstage_p.h
new file mode 100644
index 0000000000..2a7fc5dd09
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qglcustomshaderstage_p.h
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGL_CUSTOM_SHADER_STAGE_H
+#define QGL_CUSTOM_SHADER_STAGE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QGLShaderProgram>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(OpenGL)
+
+class QGLCustomShaderStagePrivate;
+class Q_OPENGL_EXPORT QGLCustomShaderStage
+{
+ Q_DECLARE_PRIVATE(QGLCustomShaderStage)
+public:
+ QGLCustomShaderStage();
+ virtual ~QGLCustomShaderStage();
+ virtual void setUniforms(QGLShaderProgram*) {}
+
+ void setUniformsDirty();
+
+ bool setOnPainter(QPainter*);
+ void removeFromPainter(QPainter*);
+ QByteArray source() const;
+
+ void setInactive();
+protected:
+ void setSource(const QByteArray&);
+
+private:
+ QGLCustomShaderStagePrivate* d_ptr;
+};
+
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+
+#endif
diff --git a/src/opengl/gl2paintengineex/qglengineshadermanager.cpp b/src/opengl/gl2paintengineex/qglengineshadermanager.cpp
new file mode 100644
index 0000000000..8068aa8524
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qglengineshadermanager.cpp
@@ -0,0 +1,838 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qglengineshadermanager_p.h"
+#include "qglengineshadersource_p.h"
+#include "qpaintengineex_opengl2_p.h"
+#include "qglshadercache_p.h"
+
+#if defined(QT_DEBUG)
+#include <QMetaEnum>
+#endif
+
+// #define QT_GL_SHARED_SHADER_DEBUG
+
+QT_BEGIN_NAMESPACE
+
+class QGLShaderStorage
+{
+public:
+ QGLEngineSharedShaders *shadersForThread(const QGLContext *context) {
+ QGLContextGroupResource<QGLEngineSharedShaders> *&shaders = m_storage.localData();
+ if (!shaders)
+ shaders = new QGLContextGroupResource<QGLEngineSharedShaders>();
+ return shaders->value(context);
+ }
+
+private:
+ QThreadStorage<QGLContextGroupResource<QGLEngineSharedShaders> *> m_storage;
+};
+
+Q_GLOBAL_STATIC(QGLShaderStorage, qt_shader_storage);
+
+QGLEngineSharedShaders *QGLEngineSharedShaders::shadersForContext(const QGLContext *context)
+{
+ return qt_shader_storage()->shadersForThread(context);
+}
+
+const char* QGLEngineSharedShaders::qShaderSnippets[] = {
+ 0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0
+};
+
+QGLEngineSharedShaders::QGLEngineSharedShaders(const QGLContext* context)
+ : ctxGuard(context)
+ , blitShaderProg(0)
+ , simpleShaderProg(0)
+{
+
+/*
+ Rather than having the shader source array statically initialised, it is initialised
+ here instead. This is to allow new shader names to be inserted or existing names moved
+ around without having to change the order of the glsl strings. It is hoped this will
+ make future hard-to-find runtime bugs more obvious and generally give more solid code.
+*/
+ static bool snippetsPopulated = false;
+ if (!snippetsPopulated) {
+
+ const char** code = qShaderSnippets; // shortcut
+
+ code[MainVertexShader] = qglslMainVertexShader;
+ code[MainWithTexCoordsVertexShader] = qglslMainWithTexCoordsVertexShader;
+ code[MainWithTexCoordsAndOpacityVertexShader] = qglslMainWithTexCoordsAndOpacityVertexShader;
+
+ code[UntransformedPositionVertexShader] = qglslUntransformedPositionVertexShader;
+ code[PositionOnlyVertexShader] = qglslPositionOnlyVertexShader;
+ code[ComplexGeometryPositionOnlyVertexShader] = qglslComplexGeometryPositionOnlyVertexShader;
+ code[PositionWithPatternBrushVertexShader] = qglslPositionWithPatternBrushVertexShader;
+ code[PositionWithLinearGradientBrushVertexShader] = qglslPositionWithLinearGradientBrushVertexShader;
+ code[PositionWithConicalGradientBrushVertexShader] = qglslPositionWithConicalGradientBrushVertexShader;
+ code[PositionWithRadialGradientBrushVertexShader] = qglslPositionWithRadialGradientBrushVertexShader;
+ code[PositionWithTextureBrushVertexShader] = qglslPositionWithTextureBrushVertexShader;
+ code[AffinePositionWithPatternBrushVertexShader] = qglslAffinePositionWithPatternBrushVertexShader;
+ code[AffinePositionWithLinearGradientBrushVertexShader] = qglslAffinePositionWithLinearGradientBrushVertexShader;
+ code[AffinePositionWithConicalGradientBrushVertexShader] = qglslAffinePositionWithConicalGradientBrushVertexShader;
+ code[AffinePositionWithRadialGradientBrushVertexShader] = qglslAffinePositionWithRadialGradientBrushVertexShader;
+ code[AffinePositionWithTextureBrushVertexShader] = qglslAffinePositionWithTextureBrushVertexShader;
+
+ code[MainFragmentShader_CMO] = qglslMainFragmentShader_CMO;
+ code[MainFragmentShader_CM] = qglslMainFragmentShader_CM;
+ code[MainFragmentShader_MO] = qglslMainFragmentShader_MO;
+ code[MainFragmentShader_M] = qglslMainFragmentShader_M;
+ code[MainFragmentShader_CO] = qglslMainFragmentShader_CO;
+ code[MainFragmentShader_C] = qglslMainFragmentShader_C;
+ code[MainFragmentShader_O] = qglslMainFragmentShader_O;
+ code[MainFragmentShader] = qglslMainFragmentShader;
+ code[MainFragmentShader_ImageArrays] = qglslMainFragmentShader_ImageArrays;
+
+ code[ImageSrcFragmentShader] = qglslImageSrcFragmentShader;
+ code[ImageSrcWithPatternFragmentShader] = qglslImageSrcWithPatternFragmentShader;
+ code[NonPremultipliedImageSrcFragmentShader] = qglslNonPremultipliedImageSrcFragmentShader;
+ code[CustomImageSrcFragmentShader] = qglslCustomSrcFragmentShader; // Calls "customShader", which must be appended
+ code[SolidBrushSrcFragmentShader] = qglslSolidBrushSrcFragmentShader;
+ code[TextureBrushSrcFragmentShader] = qglslTextureBrushSrcFragmentShader;
+ code[TextureBrushSrcWithPatternFragmentShader] = qglslTextureBrushSrcWithPatternFragmentShader;
+ code[PatternBrushSrcFragmentShader] = qglslPatternBrushSrcFragmentShader;
+ code[LinearGradientBrushSrcFragmentShader] = qglslLinearGradientBrushSrcFragmentShader;
+ code[RadialGradientBrushSrcFragmentShader] = qglslRadialGradientBrushSrcFragmentShader;
+ code[ConicalGradientBrushSrcFragmentShader] = qglslConicalGradientBrushSrcFragmentShader;
+ code[ShockingPinkSrcFragmentShader] = qglslShockingPinkSrcFragmentShader;
+
+ code[NoMaskFragmentShader] = "";
+ code[MaskFragmentShader] = qglslMaskFragmentShader;
+ code[RgbMaskFragmentShaderPass1] = qglslRgbMaskFragmentShaderPass1;
+ code[RgbMaskFragmentShaderPass2] = qglslRgbMaskFragmentShaderPass2;
+ code[RgbMaskWithGammaFragmentShader] = ""; //###
+
+ code[NoCompositionModeFragmentShader] = "";
+ code[MultiplyCompositionModeFragmentShader] = ""; //###
+ code[ScreenCompositionModeFragmentShader] = ""; //###
+ code[OverlayCompositionModeFragmentShader] = ""; //###
+ code[DarkenCompositionModeFragmentShader] = ""; //###
+ code[LightenCompositionModeFragmentShader] = ""; //###
+ code[ColorDodgeCompositionModeFragmentShader] = ""; //###
+ code[ColorBurnCompositionModeFragmentShader] = ""; //###
+ code[HardLightCompositionModeFragmentShader] = ""; //###
+ code[SoftLightCompositionModeFragmentShader] = ""; //###
+ code[DifferenceCompositionModeFragmentShader] = ""; //###
+ code[ExclusionCompositionModeFragmentShader] = ""; //###
+
+#if defined(QT_DEBUG)
+ // Check that all the elements have been filled:
+ for (int i = 0; i < TotalSnippetCount; ++i) {
+ if (qShaderSnippets[i] == 0) {
+ qFatal("Shader snippet for %s (#%d) is missing!",
+ snippetNameStr(SnippetName(i)).constData(), i);
+ }
+ }
+#endif
+ snippetsPopulated = true;
+ }
+
+ QGLShader* fragShader;
+ QGLShader* vertexShader;
+ QByteArray vertexSource;
+ QByteArray fragSource;
+
+ // Compile up the simple shader:
+ vertexSource.append(qShaderSnippets[MainVertexShader]);
+ vertexSource.append(qShaderSnippets[PositionOnlyVertexShader]);
+
+ fragSource.append(qShaderSnippets[MainFragmentShader]);
+ fragSource.append(qShaderSnippets[ShockingPinkSrcFragmentShader]);
+
+ simpleShaderProg = new QGLShaderProgram(context, 0);
+
+ CachedShader simpleShaderCache(fragSource, vertexSource);
+
+ bool inCache = simpleShaderCache.load(simpleShaderProg, context);
+
+ if (!inCache) {
+ vertexShader = new QGLShader(QGLShader::Vertex, context, 0);
+ shaders.append(vertexShader);
+ if (!vertexShader->compileSourceCode(vertexSource))
+ qWarning("Vertex shader for simpleShaderProg (MainVertexShader & PositionOnlyVertexShader) failed to compile");
+
+ fragShader = new QGLShader(QGLShader::Fragment, context, 0);
+ shaders.append(fragShader);
+ if (!fragShader->compileSourceCode(fragSource))
+ qWarning("Fragment shader for simpleShaderProg (MainFragmentShader & ShockingPinkSrcFragmentShader) failed to compile");
+
+ simpleShaderProg->addShader(vertexShader);
+ simpleShaderProg->addShader(fragShader);
+
+ simpleShaderProg->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR);
+ simpleShaderProg->bindAttributeLocation("pmvMatrix1", QT_PMV_MATRIX_1_ATTR);
+ simpleShaderProg->bindAttributeLocation("pmvMatrix2", QT_PMV_MATRIX_2_ATTR);
+ simpleShaderProg->bindAttributeLocation("pmvMatrix3", QT_PMV_MATRIX_3_ATTR);
+ }
+
+ simpleShaderProg->link();
+
+ if (simpleShaderProg->isLinked()) {
+ if (!inCache)
+ simpleShaderCache.store(simpleShaderProg, context);
+ } else {
+ qCritical() << "Errors linking simple shader:"
+ << simpleShaderProg->log();
+ }
+
+ // Compile the blit shader:
+ vertexSource.clear();
+ vertexSource.append(qShaderSnippets[MainWithTexCoordsVertexShader]);
+ vertexSource.append(qShaderSnippets[UntransformedPositionVertexShader]);
+
+ fragSource.clear();
+ fragSource.append(qShaderSnippets[MainFragmentShader]);
+ fragSource.append(qShaderSnippets[ImageSrcFragmentShader]);
+
+ blitShaderProg = new QGLShaderProgram(context, 0);
+
+ CachedShader blitShaderCache(fragSource, vertexSource);
+
+ inCache = blitShaderCache.load(blitShaderProg, context);
+
+ if (!inCache) {
+ vertexShader = new QGLShader(QGLShader::Vertex, context, 0);
+ shaders.append(vertexShader);
+ if (!vertexShader->compileSourceCode(vertexSource))
+ qWarning("Vertex shader for blitShaderProg (MainWithTexCoordsVertexShader & UntransformedPositionVertexShader) failed to compile");
+
+ fragShader = new QGLShader(QGLShader::Fragment, context, 0);
+ shaders.append(fragShader);
+ if (!fragShader->compileSourceCode(fragSource))
+ qWarning("Fragment shader for blitShaderProg (MainFragmentShader & ImageSrcFragmentShader) failed to compile");
+
+ blitShaderProg->addShader(vertexShader);
+ blitShaderProg->addShader(fragShader);
+
+ blitShaderProg->bindAttributeLocation("textureCoordArray", QT_TEXTURE_COORDS_ATTR);
+ blitShaderProg->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR);
+ }
+
+ blitShaderProg->link();
+ if (blitShaderProg->isLinked()) {
+ if (!inCache)
+ blitShaderCache.store(blitShaderProg, context);
+ } else {
+ qCritical() << "Errors linking blit shader:"
+ << blitShaderProg->log();
+ }
+
+#ifdef QT_GL_SHARED_SHADER_DEBUG
+ qDebug(" -> QGLEngineSharedShaders() %p for thread %p.", this, QThread::currentThread());
+#endif
+}
+
+QGLEngineSharedShaders::~QGLEngineSharedShaders()
+{
+#ifdef QT_GL_SHARED_SHADER_DEBUG
+ qDebug(" -> ~QGLEngineSharedShaders() %p for thread %p.", this, QThread::currentThread());
+#endif
+ qDeleteAll(shaders);
+ shaders.clear();
+
+ qDeleteAll(cachedPrograms);
+ cachedPrograms.clear();
+
+ if (blitShaderProg) {
+ delete blitShaderProg;
+ blitShaderProg = 0;
+ }
+
+ if (simpleShaderProg) {
+ delete simpleShaderProg;
+ simpleShaderProg = 0;
+ }
+}
+
+#if defined (QT_DEBUG)
+QByteArray QGLEngineSharedShaders::snippetNameStr(SnippetName name)
+{
+ QMetaEnum m = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("SnippetName"));
+ return QByteArray(m.valueToKey(name));
+}
+#endif
+
+// The address returned here will only be valid until next time this function is called.
+// The program is return bound.
+QGLEngineShaderProg *QGLEngineSharedShaders::findProgramInCache(const QGLEngineShaderProg &prog)
+{
+ for (int i = 0; i < cachedPrograms.size(); ++i) {
+ QGLEngineShaderProg *cachedProg = cachedPrograms[i];
+ if (*cachedProg == prog) {
+ // Move the program to the top of the list as a poor-man's cache algo
+ cachedPrograms.move(i, 0);
+ cachedProg->program->bind();
+ return cachedProg;
+ }
+ }
+
+ QScopedPointer<QGLEngineShaderProg> newProg;
+
+ do {
+ QByteArray fragSource;
+ // Insert the custom stage before the srcPixel shader to work around an ATI driver bug
+ // where you cannot forward declare a function that takes a sampler as argument.
+ if (prog.srcPixelFragShader == CustomImageSrcFragmentShader)
+ fragSource.append(prog.customStageSource);
+ fragSource.append(qShaderSnippets[prog.mainFragShader]);
+ fragSource.append(qShaderSnippets[prog.srcPixelFragShader]);
+ if (prog.compositionFragShader)
+ fragSource.append(qShaderSnippets[prog.compositionFragShader]);
+ if (prog.maskFragShader)
+ fragSource.append(qShaderSnippets[prog.maskFragShader]);
+
+ QByteArray vertexSource;
+ vertexSource.append(qShaderSnippets[prog.mainVertexShader]);
+ vertexSource.append(qShaderSnippets[prog.positionVertexShader]);
+
+ QScopedPointer<QGLShaderProgram> shaderProgram(new QGLShaderProgram(ctxGuard.context(), 0));
+
+ CachedShader shaderCache(fragSource, vertexSource);
+ bool inCache = shaderCache.load(shaderProgram.data(), ctxGuard.context());
+
+ if (!inCache) {
+
+ QScopedPointer<QGLShader> fragShader(new QGLShader(QGLShader::Fragment, ctxGuard.context(), 0));
+ QByteArray description;
+#if defined(QT_DEBUG)
+ // Name the shader for easier debugging
+ description.append("Fragment shader: main=");
+ description.append(snippetNameStr(prog.mainFragShader));
+ description.append(", srcPixel=");
+ description.append(snippetNameStr(prog.srcPixelFragShader));
+ if (prog.compositionFragShader) {
+ description.append(", composition=");
+ description.append(snippetNameStr(prog.compositionFragShader));
+ }
+ if (prog.maskFragShader) {
+ description.append(", mask=");
+ description.append(snippetNameStr(prog.maskFragShader));
+ }
+ fragShader->setObjectName(QString::fromLatin1(description));
+#endif
+ if (!fragShader->compileSourceCode(fragSource)) {
+ qWarning() << "Warning:" << description << "failed to compile!";
+ break;
+ }
+
+ QScopedPointer<QGLShader> vertexShader(new QGLShader(QGLShader::Vertex, ctxGuard.context(), 0));
+#if defined(QT_DEBUG)
+ // Name the shader for easier debugging
+ description.clear();
+ description.append("Vertex shader: main=");
+ description.append(snippetNameStr(prog.mainVertexShader));
+ description.append(", position=");
+ description.append(snippetNameStr(prog.positionVertexShader));
+ vertexShader->setObjectName(QString::fromLatin1(description));
+#endif
+ if (!vertexShader->compileSourceCode(vertexSource)) {
+ qWarning() << "Warning:" << description << "failed to compile!";
+ break;
+ }
+
+ shaders.append(vertexShader.data());
+ shaders.append(fragShader.data());
+ shaderProgram->addShader(vertexShader.take());
+ shaderProgram->addShader(fragShader.take());
+
+ // We have to bind the vertex attribute names before the program is linked:
+ shaderProgram->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR);
+ if (prog.useTextureCoords)
+ shaderProgram->bindAttributeLocation("textureCoordArray", QT_TEXTURE_COORDS_ATTR);
+ if (prog.useOpacityAttribute)
+ shaderProgram->bindAttributeLocation("opacityArray", QT_OPACITY_ATTR);
+ if (prog.usePmvMatrixAttribute) {
+ shaderProgram->bindAttributeLocation("pmvMatrix1", QT_PMV_MATRIX_1_ATTR);
+ shaderProgram->bindAttributeLocation("pmvMatrix2", QT_PMV_MATRIX_2_ATTR);
+ shaderProgram->bindAttributeLocation("pmvMatrix3", QT_PMV_MATRIX_3_ATTR);
+ }
+ }
+
+ newProg.reset(new QGLEngineShaderProg(prog));
+ newProg->program = shaderProgram.take();
+
+ newProg->program->link();
+ if (newProg->program->isLinked()) {
+ if (!inCache)
+ shaderCache.store(newProg->program, ctxGuard.context());
+ } else {
+ QLatin1String none("none");
+ QLatin1String br("\n");
+ QString error;
+ error = QLatin1String("Shader program failed to link,");
+#if defined(QT_DEBUG)
+ error += QLatin1String("\n Shaders Used:\n");
+ for (int i = 0; i < newProg->program->shaders().count(); ++i) {
+ QGLShader *shader = newProg->program->shaders().at(i);
+ error += QLatin1String(" ") + shader->objectName() + QLatin1String(": \n")
+ + QLatin1String(shader->sourceCode()) + br;
+ }
+#endif
+ error += QLatin1String(" Error Log:\n")
+ + QLatin1String(" ") + newProg->program->log();
+ qWarning() << error;
+ break;
+ }
+
+ newProg->program->bind();
+
+ if (newProg->maskFragShader != QGLEngineSharedShaders::NoMaskFragmentShader) {
+ GLuint location = newProg->program->uniformLocation("maskTexture");
+ newProg->program->setUniformValue(location, QT_MASK_TEXTURE_UNIT);
+ }
+
+ if (cachedPrograms.count() > 30) {
+ // The cache is full, so delete the last 5 programs in the list.
+ // These programs will be least used, as a program us bumped to
+ // the top of the list when it's used.
+ for (int i = 0; i < 5; ++i) {
+ delete cachedPrograms.last();
+ cachedPrograms.removeLast();
+ }
+ }
+
+ cachedPrograms.insert(0, newProg.data());
+ } while (false);
+
+ return newProg.take();
+}
+
+void QGLEngineSharedShaders::cleanupCustomStage(QGLCustomShaderStage* stage)
+{
+ // Remove any shader programs which has this as the custom shader src:
+ for (int i = 0; i < cachedPrograms.size(); ++i) {
+ QGLEngineShaderProg *cachedProg = cachedPrograms[i];
+ if (cachedProg->customStageSource == stage->source()) {
+ delete cachedProg;
+ cachedPrograms.removeAt(i);
+ i--;
+ }
+ }
+}
+
+
+QGLEngineShaderManager::QGLEngineShaderManager(QGLContext* context)
+ : ctx(context),
+ shaderProgNeedsChanging(true),
+ complexGeometry(false),
+ srcPixelType(Qt::NoBrush),
+ opacityMode(NoOpacity),
+ maskType(NoMask),
+ compositionMode(QPainter::CompositionMode_SourceOver),
+ customSrcStage(0),
+ currentShaderProg(0)
+{
+ sharedShaders = QGLEngineSharedShaders::shadersForContext(context);
+}
+
+QGLEngineShaderManager::~QGLEngineShaderManager()
+{
+ //###
+ removeCustomStage();
+}
+
+GLuint QGLEngineShaderManager::getUniformLocation(Uniform id)
+{
+ if (!currentShaderProg)
+ return 0;
+
+ QVector<uint> &uniformLocations = currentShaderProg->uniformLocations;
+ if (uniformLocations.isEmpty())
+ uniformLocations.fill(GLuint(-1), NumUniforms);
+
+ static const char *uniformNames[] = {
+ "imageTexture",
+ "patternColor",
+ "globalOpacity",
+ "depth",
+ "maskTexture",
+ "fragmentColor",
+ "linearData",
+ "angle",
+ "halfViewportSize",
+ "fmp",
+ "fmp2_m_radius2",
+ "inverse_2_fmp2_m_radius2",
+ "invertedTextureSize",
+ "brushTransform",
+ "brushTexture",
+ "matrix"
+ };
+
+ if (uniformLocations.at(id) == GLuint(-1))
+ uniformLocations[id] = currentShaderProg->program->uniformLocation(uniformNames[id]);
+
+ return uniformLocations.at(id);
+}
+
+
+void QGLEngineShaderManager::optimiseForBrushTransform(QTransform::TransformationType transformType)
+{
+ Q_UNUSED(transformType); // Currently ignored
+}
+
+void QGLEngineShaderManager::setDirty()
+{
+ shaderProgNeedsChanging = true;
+}
+
+void QGLEngineShaderManager::setSrcPixelType(Qt::BrushStyle style)
+{
+ Q_ASSERT(style != Qt::NoBrush);
+ if (srcPixelType == PixelSrcType(style))
+ return;
+
+ srcPixelType = style;
+ shaderProgNeedsChanging = true; //###
+}
+
+void QGLEngineShaderManager::setSrcPixelType(PixelSrcType type)
+{
+ if (srcPixelType == type)
+ return;
+
+ srcPixelType = type;
+ shaderProgNeedsChanging = true; //###
+}
+
+void QGLEngineShaderManager::setOpacityMode(OpacityMode mode)
+{
+ if (opacityMode == mode)
+ return;
+
+ opacityMode = mode;
+ shaderProgNeedsChanging = true; //###
+}
+
+void QGLEngineShaderManager::setMaskType(MaskType type)
+{
+ if (maskType == type)
+ return;
+
+ maskType = type;
+ shaderProgNeedsChanging = true; //###
+}
+
+void QGLEngineShaderManager::setCompositionMode(QPainter::CompositionMode mode)
+{
+ if (compositionMode == mode)
+ return;
+
+ compositionMode = mode;
+ shaderProgNeedsChanging = true; //###
+}
+
+void QGLEngineShaderManager::setCustomStage(QGLCustomShaderStage* stage)
+{
+ if (customSrcStage)
+ removeCustomStage();
+ customSrcStage = stage;
+ shaderProgNeedsChanging = true;
+}
+
+void QGLEngineShaderManager::removeCustomStage()
+{
+ if (customSrcStage)
+ customSrcStage->setInactive();
+ customSrcStage = 0;
+ shaderProgNeedsChanging = true;
+}
+
+QGLShaderProgram* QGLEngineShaderManager::currentProgram()
+{
+ if (currentShaderProg)
+ return currentShaderProg->program;
+ else
+ return sharedShaders->simpleProgram();
+}
+
+void QGLEngineShaderManager::useSimpleProgram()
+{
+ sharedShaders->simpleProgram()->bind();
+ QGLContextPrivate* ctx_d = ctx->d_func();
+ ctx_d->setVertexAttribArrayEnabled(QT_VERTEX_COORDS_ATTR, true);
+ ctx_d->setVertexAttribArrayEnabled(QT_TEXTURE_COORDS_ATTR, false);
+ ctx_d->setVertexAttribArrayEnabled(QT_OPACITY_ATTR, false);
+ shaderProgNeedsChanging = true;
+}
+
+void QGLEngineShaderManager::useBlitProgram()
+{
+ sharedShaders->blitProgram()->bind();
+ QGLContextPrivate* ctx_d = ctx->d_func();
+ ctx_d->setVertexAttribArrayEnabled(QT_VERTEX_COORDS_ATTR, true);
+ ctx_d->setVertexAttribArrayEnabled(QT_TEXTURE_COORDS_ATTR, true);
+ ctx_d->setVertexAttribArrayEnabled(QT_OPACITY_ATTR, false);
+ shaderProgNeedsChanging = true;
+}
+
+QGLShaderProgram* QGLEngineShaderManager::simpleProgram()
+{
+ return sharedShaders->simpleProgram();
+}
+
+QGLShaderProgram* QGLEngineShaderManager::blitProgram()
+{
+ return sharedShaders->blitProgram();
+}
+
+
+
+// Select & use the correct shader program using the current state.
+// Returns true if program needed changing.
+bool QGLEngineShaderManager::useCorrectShaderProg()
+{
+ if (!shaderProgNeedsChanging)
+ return false;
+
+ bool useCustomSrc = customSrcStage != 0;
+ if (useCustomSrc && srcPixelType != QGLEngineShaderManager::ImageSrc && srcPixelType != Qt::TexturePattern) {
+ useCustomSrc = false;
+ qWarning("QGLEngineShaderManager - Ignoring custom shader stage for non image src");
+ }
+
+ QGLEngineShaderProg requiredProgram;
+
+ bool texCoords = false;
+
+ // Choose vertex shader shader position function (which typically also sets
+ // varyings) and the source pixel (srcPixel) fragment shader function:
+ requiredProgram.positionVertexShader = QGLEngineSharedShaders::InvalidSnippetName;
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::InvalidSnippetName;
+ bool isAffine = brushTransform.isAffine();
+ if ( (srcPixelType >= Qt::Dense1Pattern) && (srcPixelType <= Qt::DiagCrossPattern) ) {
+ if (isAffine)
+ requiredProgram.positionVertexShader = QGLEngineSharedShaders::AffinePositionWithPatternBrushVertexShader;
+ else
+ requiredProgram.positionVertexShader = QGLEngineSharedShaders::PositionWithPatternBrushVertexShader;
+
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::PatternBrushSrcFragmentShader;
+ }
+ else switch (srcPixelType) {
+ default:
+ case Qt::NoBrush:
+ qFatal("QGLEngineShaderManager::useCorrectShaderProg() - Qt::NoBrush style is set");
+ break;
+ case QGLEngineShaderManager::ImageSrc:
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::ImageSrcFragmentShader;
+ requiredProgram.positionVertexShader = QGLEngineSharedShaders::PositionOnlyVertexShader;
+ texCoords = true;
+ break;
+ case QGLEngineShaderManager::NonPremultipliedImageSrc:
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::NonPremultipliedImageSrcFragmentShader;
+ requiredProgram.positionVertexShader = QGLEngineSharedShaders::PositionOnlyVertexShader;
+ texCoords = true;
+ break;
+ case QGLEngineShaderManager::PatternSrc:
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::ImageSrcWithPatternFragmentShader;
+ requiredProgram.positionVertexShader = QGLEngineSharedShaders::PositionOnlyVertexShader;
+ texCoords = true;
+ break;
+ case QGLEngineShaderManager::TextureSrcWithPattern:
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::TextureBrushSrcWithPatternFragmentShader;
+ requiredProgram.positionVertexShader = isAffine ? QGLEngineSharedShaders::AffinePositionWithTextureBrushVertexShader
+ : QGLEngineSharedShaders::PositionWithTextureBrushVertexShader;
+ break;
+ case Qt::SolidPattern:
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::SolidBrushSrcFragmentShader;
+ requiredProgram.positionVertexShader = QGLEngineSharedShaders::PositionOnlyVertexShader;
+ break;
+ case Qt::LinearGradientPattern:
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::LinearGradientBrushSrcFragmentShader;
+ requiredProgram.positionVertexShader = isAffine ? QGLEngineSharedShaders::AffinePositionWithLinearGradientBrushVertexShader
+ : QGLEngineSharedShaders::PositionWithLinearGradientBrushVertexShader;
+ break;
+ case Qt::ConicalGradientPattern:
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::ConicalGradientBrushSrcFragmentShader;
+ requiredProgram.positionVertexShader = isAffine ? QGLEngineSharedShaders::AffinePositionWithConicalGradientBrushVertexShader
+ : QGLEngineSharedShaders::PositionWithConicalGradientBrushVertexShader;
+ break;
+ case Qt::RadialGradientPattern:
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::RadialGradientBrushSrcFragmentShader;
+ requiredProgram.positionVertexShader = isAffine ? QGLEngineSharedShaders::AffinePositionWithRadialGradientBrushVertexShader
+ : QGLEngineSharedShaders::PositionWithRadialGradientBrushVertexShader;
+ break;
+ case Qt::TexturePattern:
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::TextureBrushSrcFragmentShader;
+ requiredProgram.positionVertexShader = isAffine ? QGLEngineSharedShaders::AffinePositionWithTextureBrushVertexShader
+ : QGLEngineSharedShaders::PositionWithTextureBrushVertexShader;
+ break;
+ };
+
+ if (useCustomSrc) {
+ requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::CustomImageSrcFragmentShader;
+ requiredProgram.customStageSource = customSrcStage->source();
+ }
+
+ const bool hasCompose = compositionMode > QPainter::CompositionMode_Plus;
+ const bool hasMask = maskType != QGLEngineShaderManager::NoMask;
+
+ // Choose fragment shader main function:
+ if (opacityMode == AttributeOpacity) {
+ Q_ASSERT(!hasCompose && !hasMask);
+ requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_ImageArrays;
+ } else {
+ bool useGlobalOpacity = (opacityMode == UniformOpacity);
+ if (hasCompose && hasMask && useGlobalOpacity)
+ requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_CMO;
+ if (hasCompose && hasMask && !useGlobalOpacity)
+ requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_CM;
+ if (!hasCompose && hasMask && useGlobalOpacity)
+ requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_MO;
+ if (!hasCompose && hasMask && !useGlobalOpacity)
+ requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_M;
+ if (hasCompose && !hasMask && useGlobalOpacity)
+ requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_CO;
+ if (hasCompose && !hasMask && !useGlobalOpacity)
+ requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_C;
+ if (!hasCompose && !hasMask && useGlobalOpacity)
+ requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_O;
+ if (!hasCompose && !hasMask && !useGlobalOpacity)
+ requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader;
+ }
+
+ if (hasMask) {
+ if (maskType == PixelMask) {
+ requiredProgram.maskFragShader = QGLEngineSharedShaders::MaskFragmentShader;
+ texCoords = true;
+ } else if (maskType == SubPixelMaskPass1) {
+ requiredProgram.maskFragShader = QGLEngineSharedShaders::RgbMaskFragmentShaderPass1;
+ texCoords = true;
+ } else if (maskType == SubPixelMaskPass2) {
+ requiredProgram.maskFragShader = QGLEngineSharedShaders::RgbMaskFragmentShaderPass2;
+ texCoords = true;
+ } else if (maskType == SubPixelWithGammaMask) {
+ requiredProgram.maskFragShader = QGLEngineSharedShaders::RgbMaskWithGammaFragmentShader;
+ texCoords = true;
+ } else {
+ qCritical("QGLEngineShaderManager::useCorrectShaderProg() - Unknown mask type");
+ }
+ } else {
+ requiredProgram.maskFragShader = QGLEngineSharedShaders::NoMaskFragmentShader;
+ }
+
+ if (hasCompose) {
+ switch (compositionMode) {
+ case QPainter::CompositionMode_Multiply:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::MultiplyCompositionModeFragmentShader;
+ break;
+ case QPainter::CompositionMode_Screen:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::ScreenCompositionModeFragmentShader;
+ break;
+ case QPainter::CompositionMode_Overlay:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::OverlayCompositionModeFragmentShader;
+ break;
+ case QPainter::CompositionMode_Darken:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::DarkenCompositionModeFragmentShader;
+ break;
+ case QPainter::CompositionMode_Lighten:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::LightenCompositionModeFragmentShader;
+ break;
+ case QPainter::CompositionMode_ColorDodge:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::ColorDodgeCompositionModeFragmentShader;
+ break;
+ case QPainter::CompositionMode_ColorBurn:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::ColorBurnCompositionModeFragmentShader;
+ break;
+ case QPainter::CompositionMode_HardLight:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::HardLightCompositionModeFragmentShader;
+ break;
+ case QPainter::CompositionMode_SoftLight:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::SoftLightCompositionModeFragmentShader;
+ break;
+ case QPainter::CompositionMode_Difference:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::DifferenceCompositionModeFragmentShader;
+ break;
+ case QPainter::CompositionMode_Exclusion:
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::ExclusionCompositionModeFragmentShader;
+ break;
+ default:
+ qWarning("QGLEngineShaderManager::useCorrectShaderProg() - Unsupported composition mode");
+ }
+ } else {
+ requiredProgram.compositionFragShader = QGLEngineSharedShaders::NoCompositionModeFragmentShader;
+ }
+
+ // Choose vertex shader main function
+ if (opacityMode == AttributeOpacity) {
+ Q_ASSERT(texCoords);
+ requiredProgram.mainVertexShader = QGLEngineSharedShaders::MainWithTexCoordsAndOpacityVertexShader;
+ } else if (texCoords) {
+ requiredProgram.mainVertexShader = QGLEngineSharedShaders::MainWithTexCoordsVertexShader;
+ } else {
+ requiredProgram.mainVertexShader = QGLEngineSharedShaders::MainVertexShader;
+ }
+ requiredProgram.useTextureCoords = texCoords;
+ requiredProgram.useOpacityAttribute = (opacityMode == AttributeOpacity);
+ if (complexGeometry && srcPixelType == Qt::SolidPattern) {
+ requiredProgram.positionVertexShader = QGLEngineSharedShaders::ComplexGeometryPositionOnlyVertexShader;
+ requiredProgram.usePmvMatrixAttribute = false;
+ } else {
+ requiredProgram.usePmvMatrixAttribute = true;
+
+ // Force complexGeometry off, since we currently don't support that mode for
+ // non-solid brushes
+ complexGeometry = false;
+ }
+
+ // At this point, requiredProgram is fully populated so try to find the program in the cache
+ currentShaderProg = sharedShaders->findProgramInCache(requiredProgram);
+
+ if (currentShaderProg && useCustomSrc) {
+ customSrcStage->setUniforms(currentShaderProg->program);
+ }
+
+ // Make sure all the vertex attribute arrays the program uses are enabled (and the ones it
+ // doesn't use are disabled)
+ QGLContextPrivate* ctx_d = ctx->d_func();
+ ctx_d->setVertexAttribArrayEnabled(QT_VERTEX_COORDS_ATTR, true);
+ ctx_d->setVertexAttribArrayEnabled(QT_TEXTURE_COORDS_ATTR, currentShaderProg && currentShaderProg->useTextureCoords);
+ ctx_d->setVertexAttribArrayEnabled(QT_OPACITY_ATTR, currentShaderProg && currentShaderProg->useOpacityAttribute);
+
+ shaderProgNeedsChanging = false;
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/opengl/gl2paintengineex/qglengineshadermanager_p.h b/src/opengl/gl2paintengineex/qglengineshadermanager_p.h
new file mode 100644
index 0000000000..7cc9dc3bd4
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qglengineshadermanager_p.h
@@ -0,0 +1,513 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+/*
+ VERTEX SHADERS
+ ==============
+
+ Vertex shaders are specified as multiple (partial) shaders. On desktop,
+ this works fine. On ES, QGLShader & QGLShaderProgram will make partial
+ shaders work by concatenating the source in each QGLShader and compiling
+ it as a single shader. This is abstracted nicely by QGLShaderProgram and
+ the GL2 engine doesn't need to worry about it.
+
+ Generally, there's two vertex shader objects. The position shaders are
+ the ones which set gl_Position. There's also two "main" vertex shaders,
+ one which just calls the position shader and another which also passes
+ through some texture coordinates from a vertex attribute array to a
+ varying. These texture coordinates are used for mask position in text
+ rendering and for the source coordinates in drawImage/drawPixmap. There's
+ also a "Simple" vertex shader for rendering a solid colour (used to render
+ into the stencil buffer where the actual colour value is discarded).
+
+ The position shaders for brushes look scary. This is because many of the
+ calculations which logically belong in the fragment shader have been moved
+ into the vertex shader to improve performance. This is why the position
+ calculation is in a separate shader. Not only does it calculate the
+ position, but it also calculates some data to be passed to the fragment
+ shader as a varying. It is optimal to move as much of the calculation as
+ possible into the vertex shader as this is executed less often.
+
+ The varyings passed to the fragment shaders are interpolated (which is
+ cheap). Unfortunately, GL will apply perspective correction to the
+ interpolation calusing errors. To get around this, the vertex shader must
+ apply perspective correction itself and set the w-value of gl_Position to
+ zero. That way, GL will be tricked into thinking it doesn't need to apply a
+ perspective correction and use linear interpolation instead (which is what
+ we want). Of course, if the brush transform is affeine, no perspective
+ correction is needed and a simpler vertex shader can be used instead.
+
+ So there are the following "main" vertex shaders:
+ qglslMainVertexShader
+ qglslMainWithTexCoordsVertexShader
+
+ And the the following position vertex shaders:
+ qglslPositionOnlyVertexShader
+ qglslPositionWithTextureBrushVertexShader
+ qglslPositionWithPatternBrushVertexShader
+ qglslPositionWithLinearGradientBrushVertexShader
+ qglslPositionWithRadialGradientBrushVertexShader
+ qglslPositionWithConicalGradientBrushVertexShader
+ qglslAffinePositionWithTextureBrushVertexShader
+ qglslAffinePositionWithPatternBrushVertexShader
+ qglslAffinePositionWithLinearGradientBrushVertexShader
+ qglslAffinePositionWithRadialGradientBrushVertexShader
+ qglslAffinePositionWithConicalGradientBrushVertexShader
+
+ Leading to 23 possible vertex shaders
+
+
+ FRAGMENT SHADERS
+ ================
+
+ Fragment shaders are also specified as multiple (partial) shaders. The
+ different fragment shaders represent the different stages in Qt's fragment
+ pipeline. There are 1-3 stages in this pipeline: First stage is to get the
+ fragment's colour value. The next stage is to get the fragment's mask value
+ (coverage value for anti-aliasing) and the final stage is to blend the
+ incoming fragment with the background (for composition modes not supported
+ by GL).
+
+ Of these, the first stage will always be present. If Qt doesn't need to
+ apply anti-aliasing (because it's off or handled by multisampling) then
+ the coverage value doesn't need to be applied. (Note: There are two types
+ of mask, one for regular anti-aliasing and one for sub-pixel anti-
+ aliasing.) If the composition mode is one which GL supports natively then
+ the blending stage doesn't need to be applied.
+
+ As eash stage can have multiple implementations, they are abstracted as
+ GLSL function calls with the following signatures:
+
+ Brushes & image drawing are implementations of "qcolorp vec4 srcPixel()":
+ qglslImageSrcFragShader
+ qglslImageSrcWithPatternFragShader
+ qglslNonPremultipliedImageSrcFragShader
+ qglslSolidBrushSrcFragShader
+ qglslTextureBrushSrcFragShader
+ qglslTextureBrushWithPatternFragShader
+ qglslPatternBrushSrcFragShader
+ qglslLinearGradientBrushSrcFragShader
+ qglslRadialGradientBrushSrcFragShader
+ qglslConicalGradientBrushSrcFragShader
+ NOTE: It is assumed the colour returned by srcPixel() is pre-multiplied
+
+ Masks are implementations of "qcolorp vec4 applyMask(qcolorp vec4 src)":
+ qglslMaskFragmentShader
+ qglslRgbMaskFragmentShaderPass1
+ qglslRgbMaskFragmentShaderPass2
+ qglslRgbMaskWithGammaFragmentShader
+
+ Composition modes are "qcolorp vec4 compose(qcolorp vec4 src)":
+ qglslColorBurnCompositionModeFragmentShader
+ qglslColorDodgeCompositionModeFragmentShader
+ qglslDarkenCompositionModeFragmentShader
+ qglslDifferenceCompositionModeFragmentShader
+ qglslExclusionCompositionModeFragmentShader
+ qglslHardLightCompositionModeFragmentShader
+ qglslLightenCompositionModeFragmentShader
+ qglslMultiplyCompositionModeFragmentShader
+ qglslOverlayCompositionModeFragmentShader
+ qglslScreenCompositionModeFragmentShader
+ qglslSoftLightCompositionModeFragmentShader
+
+
+ Note: In the future, some GLSL compilers will support an extension allowing
+ a new 'color' precision specifier. To support this, qcolorp is used for
+ all color components so it can be defined to colorp or lowp depending upon
+ the implementation.
+
+ So there are differnt frament shader main functions, depending on the
+ number & type of pipelines the fragment needs to go through.
+
+ The choice of which main() fragment shader string to use depends on:
+ - Use of global opacity
+ - Brush style (some brushes apply opacity themselves)
+ - Use & type of mask (TODO: Need to support high quality anti-aliasing & text)
+ - Use of non-GL Composition mode
+
+ Leading to the following fragment shader main functions:
+ gl_FragColor = compose(applyMask(srcPixel()*globalOpacity));
+ gl_FragColor = compose(applyMask(srcPixel()));
+ gl_FragColor = applyMask(srcPixel()*globalOpacity);
+ gl_FragColor = applyMask(srcPixel());
+ gl_FragColor = compose(srcPixel()*globalOpacity);
+ gl_FragColor = compose(srcPixel());
+ gl_FragColor = srcPixel()*globalOpacity;
+ gl_FragColor = srcPixel();
+
+ Called:
+ qglslMainFragmentShader_CMO
+ qglslMainFragmentShader_CM
+ qglslMainFragmentShader_MO
+ qglslMainFragmentShader_M
+ qglslMainFragmentShader_CO
+ qglslMainFragmentShader_C
+ qglslMainFragmentShader_O
+ qglslMainFragmentShader
+
+ Where:
+ M = Mask
+ C = Composition
+ O = Global Opacity
+
+
+ CUSTOM SHADER CODE
+ ==================
+
+ The use of custom shader code is supported by the engine for drawImage and
+ drawPixmap calls. This is implemented via hooks in the fragment pipeline.
+
+ The custom shader is passed to the engine as a partial fragment shader
+ (QGLCustomShaderStage). The shader will implement a pre-defined method name
+ which Qt's fragment pipeline will call:
+
+ lowp vec4 customShader(lowp sampler2d imageTexture, highp vec2 textureCoords)
+
+ The provided src and srcCoords parameters can be used to sample from the
+ source image.
+
+ Transformations, clipping, opacity, and composition modes set using QPainter
+ will be respected when using the custom shader hook.
+*/
+
+#ifndef QGLENGINE_SHADER_MANAGER_H
+#define QGLENGINE_SHADER_MANAGER_H
+
+#include <QGLShader>
+#include <QGLShaderProgram>
+#include <QPainter>
+#include <private/qgl_p.h>
+#include <private/qglcustomshaderstage_p.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(OpenGL)
+
+
+/*
+struct QGLEngineCachedShaderProg
+{
+ QGLEngineCachedShaderProg(QGLEngineShaderManager::ShaderName vertexMain,
+ QGLEngineShaderManager::ShaderName vertexPosition,
+ QGLEngineShaderManager::ShaderName fragMain,
+ QGLEngineShaderManager::ShaderName pixelSrc,
+ QGLEngineShaderManager::ShaderName mask,
+ QGLEngineShaderManager::ShaderName composition);
+
+ int cacheKey;
+ QGLShaderProgram* program;
+}
+*/
+
+static const GLuint QT_VERTEX_COORDS_ATTR = 0;
+static const GLuint QT_TEXTURE_COORDS_ATTR = 1;
+static const GLuint QT_OPACITY_ATTR = 2;
+static const GLuint QT_PMV_MATRIX_1_ATTR = 3;
+static const GLuint QT_PMV_MATRIX_2_ATTR = 4;
+static const GLuint QT_PMV_MATRIX_3_ATTR = 5;
+
+class QGLEngineShaderProg;
+
+class Q_OPENGL_EXPORT QGLEngineSharedShaders
+{
+ Q_GADGET
+public:
+
+ enum SnippetName {
+ MainVertexShader,
+ MainWithTexCoordsVertexShader,
+ MainWithTexCoordsAndOpacityVertexShader,
+
+ // UntransformedPositionVertexShader must be first in the list:
+ UntransformedPositionVertexShader,
+ PositionOnlyVertexShader,
+ ComplexGeometryPositionOnlyVertexShader,
+ PositionWithPatternBrushVertexShader,
+ PositionWithLinearGradientBrushVertexShader,
+ PositionWithConicalGradientBrushVertexShader,
+ PositionWithRadialGradientBrushVertexShader,
+ PositionWithTextureBrushVertexShader,
+ AffinePositionWithPatternBrushVertexShader,
+ AffinePositionWithLinearGradientBrushVertexShader,
+ AffinePositionWithConicalGradientBrushVertexShader,
+ AffinePositionWithRadialGradientBrushVertexShader,
+ AffinePositionWithTextureBrushVertexShader,
+
+ // MainFragmentShader_CMO must be first in the list:
+ MainFragmentShader_CMO,
+ MainFragmentShader_CM,
+ MainFragmentShader_MO,
+ MainFragmentShader_M,
+ MainFragmentShader_CO,
+ MainFragmentShader_C,
+ MainFragmentShader_O,
+ MainFragmentShader,
+ MainFragmentShader_ImageArrays,
+
+ // ImageSrcFragmentShader must be first in the list::
+ ImageSrcFragmentShader,
+ ImageSrcWithPatternFragmentShader,
+ NonPremultipliedImageSrcFragmentShader,
+ CustomImageSrcFragmentShader,
+ SolidBrushSrcFragmentShader,
+ TextureBrushSrcFragmentShader,
+ TextureBrushSrcWithPatternFragmentShader,
+ PatternBrushSrcFragmentShader,
+ LinearGradientBrushSrcFragmentShader,
+ RadialGradientBrushSrcFragmentShader,
+ ConicalGradientBrushSrcFragmentShader,
+ ShockingPinkSrcFragmentShader,
+
+ // NoMaskFragmentShader must be first in the list:
+ NoMaskFragmentShader,
+ MaskFragmentShader,
+ RgbMaskFragmentShaderPass1,
+ RgbMaskFragmentShaderPass2,
+ RgbMaskWithGammaFragmentShader,
+
+ // NoCompositionModeFragmentShader must be first in the list:
+ NoCompositionModeFragmentShader,
+ MultiplyCompositionModeFragmentShader,
+ ScreenCompositionModeFragmentShader,
+ OverlayCompositionModeFragmentShader,
+ DarkenCompositionModeFragmentShader,
+ LightenCompositionModeFragmentShader,
+ ColorDodgeCompositionModeFragmentShader,
+ ColorBurnCompositionModeFragmentShader,
+ HardLightCompositionModeFragmentShader,
+ SoftLightCompositionModeFragmentShader,
+ DifferenceCompositionModeFragmentShader,
+ ExclusionCompositionModeFragmentShader,
+
+ TotalSnippetCount, InvalidSnippetName
+ };
+#if defined (QT_DEBUG)
+ Q_ENUMS(SnippetName)
+ static QByteArray snippetNameStr(SnippetName snippetName);
+#endif
+
+/*
+ // These allow the ShaderName enum to be used as a cache key
+ const int mainVertexOffset = 0;
+ const int positionVertexOffset = (1<<2) - PositionOnlyVertexShader;
+ const int mainFragOffset = (1<<6) - MainFragmentShader_CMO;
+ const int srcPixelOffset = (1<<10) - ImageSrcFragmentShader;
+ const int maskOffset = (1<<14) - NoMaskShader;
+ const int compositionOffset = (1 << 16) - MultiplyCompositionModeFragmentShader;
+*/
+
+ QGLEngineSharedShaders(const QGLContext *context);
+ ~QGLEngineSharedShaders();
+
+ QGLShaderProgram *simpleProgram() { return simpleShaderProg; }
+ QGLShaderProgram *blitProgram() { return blitShaderProg; }
+ // Compile the program if it's not already in the cache, return the item in the cache.
+ QGLEngineShaderProg *findProgramInCache(const QGLEngineShaderProg &prog);
+ // Compile the custom shader if it's not already in the cache, return the item in the cache.
+
+ static QGLEngineSharedShaders *shadersForContext(const QGLContext *context);
+
+ // Ideally, this would be static and cleanup all programs in all contexts which
+ // contain the custom code. Currently it is just a hint and we rely on deleted
+ // custom shaders being cleaned up by being kicked out of the cache when it's
+ // full.
+ void cleanupCustomStage(QGLCustomShaderStage* stage);
+
+private:
+ QGLSharedResourceGuard ctxGuard;
+ QGLShaderProgram *blitShaderProg;
+ QGLShaderProgram *simpleShaderProg;
+ QList<QGLEngineShaderProg*> cachedPrograms;
+ QList<QGLShader *> shaders;
+
+ static const char* qShaderSnippets[TotalSnippetCount];
+};
+
+
+class QGLEngineShaderProg
+{
+public:
+ QGLEngineShaderProg() : program(0) {}
+
+ ~QGLEngineShaderProg() {
+ if (program)
+ delete program;
+ }
+
+ QGLEngineSharedShaders::SnippetName mainVertexShader;
+ QGLEngineSharedShaders::SnippetName positionVertexShader;
+ QGLEngineSharedShaders::SnippetName mainFragShader;
+ QGLEngineSharedShaders::SnippetName srcPixelFragShader;
+ QGLEngineSharedShaders::SnippetName maskFragShader;
+ QGLEngineSharedShaders::SnippetName compositionFragShader;
+
+ QByteArray customStageSource; //TODO: Decent cache key for custom stages
+ QGLShaderProgram* program;
+
+ QVector<uint> uniformLocations;
+
+ bool useTextureCoords;
+ bool useOpacityAttribute;
+ bool usePmvMatrixAttribute;
+
+ bool operator==(const QGLEngineShaderProg& other) {
+ // We don't care about the program
+ return ( mainVertexShader == other.mainVertexShader &&
+ positionVertexShader == other.positionVertexShader &&
+ mainFragShader == other.mainFragShader &&
+ srcPixelFragShader == other.srcPixelFragShader &&
+ maskFragShader == other.maskFragShader &&
+ compositionFragShader == other.compositionFragShader &&
+ customStageSource == other.customStageSource
+ );
+ }
+};
+
+class Q_OPENGL_EXPORT QGLEngineShaderManager : public QObject
+{
+ Q_OBJECT
+public:
+ QGLEngineShaderManager(QGLContext* context);
+ ~QGLEngineShaderManager();
+
+ enum MaskType {NoMask, PixelMask, SubPixelMaskPass1, SubPixelMaskPass2, SubPixelWithGammaMask};
+ enum PixelSrcType {
+ ImageSrc = Qt::TexturePattern+1,
+ NonPremultipliedImageSrc = Qt::TexturePattern+2,
+ PatternSrc = Qt::TexturePattern+3,
+ TextureSrcWithPattern = Qt::TexturePattern+4
+ };
+
+ enum Uniform {
+ ImageTexture,
+ PatternColor,
+ GlobalOpacity,
+ Depth,
+ MaskTexture,
+ FragmentColor,
+ LinearData,
+ Angle,
+ HalfViewportSize,
+ Fmp,
+ Fmp2MRadius2,
+ Inverse2Fmp2MRadius2,
+ InvertedTextureSize,
+ BrushTransform,
+ BrushTexture,
+ Matrix,
+ NumUniforms
+ };
+
+ enum OpacityMode {
+ NoOpacity,
+ UniformOpacity,
+ AttributeOpacity
+ };
+
+ // There are optimizations we can do, depending on the brush transform:
+ // 1) May not have to apply perspective-correction
+ // 2) Can use lower precision for matrix
+ void optimiseForBrushTransform(QTransform::TransformationType transformType);
+ void setSrcPixelType(Qt::BrushStyle);
+ void setSrcPixelType(PixelSrcType); // For non-brush sources, like pixmaps & images
+ void setOpacityMode(OpacityMode);
+ void setMaskType(MaskType);
+ void setCompositionMode(QPainter::CompositionMode);
+ void setCustomStage(QGLCustomShaderStage* stage);
+ void removeCustomStage();
+
+ GLuint getUniformLocation(Uniform id);
+
+ void setDirty(); // someone has manually changed the current shader program
+ bool useCorrectShaderProg(); // returns true if the shader program needed to be changed
+
+ void useSimpleProgram();
+ void useBlitProgram();
+ void setHasComplexGeometry(bool hasComplexGeometry)
+ {
+ complexGeometry = hasComplexGeometry;
+ shaderProgNeedsChanging = true;
+ }
+ bool hasComplexGeometry() const
+ {
+ return complexGeometry;
+ }
+
+ QGLShaderProgram* currentProgram(); // Returns pointer to the shader the manager has chosen
+ QGLShaderProgram* simpleProgram(); // Used to draw into e.g. stencil buffers
+ QGLShaderProgram* blitProgram(); // Used to blit a texture into the framebuffer
+
+ QGLEngineSharedShaders* sharedShaders;
+
+private:
+ QGLContext* ctx;
+ bool shaderProgNeedsChanging;
+ bool complexGeometry;
+
+ // Current state variables which influence the choice of shader:
+ QTransform brushTransform;
+ int srcPixelType;
+ OpacityMode opacityMode;
+ MaskType maskType;
+ QPainter::CompositionMode compositionMode;
+ QGLCustomShaderStage* customSrcStage;
+
+ QGLEngineShaderProg* currentShaderProg;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif //QGLENGINE_SHADER_MANAGER_H
diff --git a/src/opengl/gl2paintengineex/qglengineshadersource_p.h b/src/opengl/gl2paintengineex/qglengineshadersource_p.h
new file mode 100644
index 0000000000..fc8b9ef4db
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qglengineshadersource_p.h
@@ -0,0 +1,519 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+
+#ifndef QGL_ENGINE_SHADER_SOURCE_H
+#define QGL_ENGINE_SHADER_SOURCE_H
+
+#include "qglengineshadermanager_p.h"
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(OpenGL)
+
+
+static const char* const qglslMainVertexShader = "\n\
+ void setPosition(); \n\
+ void main(void) \n\
+ { \n\
+ setPosition(); \n\
+ }\n";
+
+static const char* const qglslMainWithTexCoordsVertexShader = "\n\
+ attribute highp vec2 textureCoordArray; \n\
+ varying highp vec2 textureCoords; \n\
+ void setPosition(); \n\
+ void main(void) \n\
+ { \n\
+ setPosition(); \n\
+ textureCoords = textureCoordArray; \n\
+ }\n";
+
+static const char* const qglslMainWithTexCoordsAndOpacityVertexShader = "\n\
+ attribute highp vec2 textureCoordArray; \n\
+ attribute lowp float opacityArray; \n\
+ varying highp vec2 textureCoords; \n\
+ varying lowp float opacity; \n\
+ void setPosition(); \n\
+ void main(void) \n\
+ { \n\
+ setPosition(); \n\
+ textureCoords = textureCoordArray; \n\
+ opacity = opacityArray; \n\
+ }\n";
+
+// NOTE: We let GL do the perspective correction so texture lookups in the fragment
+// shader are also perspective corrected.
+static const char* const qglslPositionOnlyVertexShader = "\n\
+ attribute highp vec2 vertexCoordsArray; \n\
+ attribute highp vec3 pmvMatrix1; \n\
+ attribute highp vec3 pmvMatrix2; \n\
+ attribute highp vec3 pmvMatrix3; \n\
+ void setPosition(void) \n\
+ { \n\
+ highp mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\
+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\
+ gl_Position = vec4(transformedPos.xy, 0.0, transformedPos.z); \n\
+ }\n";
+
+static const char* const qglslComplexGeometryPositionOnlyVertexShader = "\n\
+ uniform highp mat3 matrix; \n\
+ attribute highp vec2 vertexCoordsArray; \n\
+ void setPosition(void) \n\
+ { \n\
+ gl_Position = vec4(matrix * vec3(vertexCoordsArray, 1), 1);\n\
+ } \n";
+
+static const char* const qglslUntransformedPositionVertexShader = "\n\
+ attribute highp vec4 vertexCoordsArray; \n\
+ void setPosition(void) \n\
+ { \n\
+ gl_Position = vertexCoordsArray; \n\
+ }\n";
+
+// Pattern Brush - This assumes the texture size is 8x8 and thus, the inverted size is 0.125
+static const char* const qglslPositionWithPatternBrushVertexShader = "\n\
+ attribute highp vec2 vertexCoordsArray; \n\
+ attribute highp vec3 pmvMatrix1; \n\
+ attribute highp vec3 pmvMatrix2; \n\
+ attribute highp vec3 pmvMatrix3; \n\
+ uniform mediump vec2 halfViewportSize; \n\
+ uniform highp vec2 invertedTextureSize; \n\
+ uniform highp mat3 brushTransform; \n\
+ varying highp vec2 patternTexCoords; \n\
+ void setPosition(void) \n\
+ { \n\
+ highp mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\
+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\
+ gl_Position.xy = transformedPos.xy / transformedPos.z; \n\
+ mediump vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\
+ mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1.0); \n\
+ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\
+ gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\
+ patternTexCoords.xy = (hTexCoords.xy * 0.125) * invertedHTexCoordsZ; \n\
+ }\n";
+
+static const char* const qglslAffinePositionWithPatternBrushVertexShader
+ = qglslPositionWithPatternBrushVertexShader;
+
+static const char* const qglslPatternBrushSrcFragmentShader = "\n\
+ uniform sampler2D brushTexture; \n\
+ uniform lowp vec4 patternColor; \n\
+ varying highp vec2 patternTexCoords;\n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ return patternColor * (1.0 - texture2D(brushTexture, patternTexCoords).r); \n\
+ }\n";
+
+
+// Linear Gradient Brush
+static const char* const qglslPositionWithLinearGradientBrushVertexShader = "\n\
+ attribute highp vec2 vertexCoordsArray; \n\
+ attribute highp vec3 pmvMatrix1; \n\
+ attribute highp vec3 pmvMatrix2; \n\
+ attribute highp vec3 pmvMatrix3; \n\
+ uniform mediump vec2 halfViewportSize; \n\
+ uniform highp vec3 linearData; \n\
+ uniform highp mat3 brushTransform; \n\
+ varying mediump float index; \n\
+ void setPosition() \n\
+ { \n\
+ highp mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\
+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\
+ gl_Position.xy = transformedPos.xy / transformedPos.z; \n\
+ mediump vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\
+ mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\
+ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\
+ gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\
+ index = (dot(linearData.xy, hTexCoords.xy) * linearData.z) * invertedHTexCoordsZ; \n\
+ }\n";
+
+static const char* const qglslAffinePositionWithLinearGradientBrushVertexShader
+ = qglslPositionWithLinearGradientBrushVertexShader;
+
+static const char* const qglslLinearGradientBrushSrcFragmentShader = "\n\
+ uniform sampler2D brushTexture; \n\
+ varying mediump float index; \n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ mediump vec2 val = vec2(index, 0.5); \n\
+ return texture2D(brushTexture, val); \n\
+ }\n";
+
+
+// Conical Gradient Brush
+static const char* const qglslPositionWithConicalGradientBrushVertexShader = "\n\
+ attribute highp vec2 vertexCoordsArray; \n\
+ attribute highp vec3 pmvMatrix1; \n\
+ attribute highp vec3 pmvMatrix2; \n\
+ attribute highp vec3 pmvMatrix3; \n\
+ uniform mediump vec2 halfViewportSize; \n\
+ uniform highp mat3 brushTransform; \n\
+ varying highp vec2 A; \n\
+ void setPosition(void) \n\
+ { \n\
+ highp mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\
+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\
+ gl_Position.xy = transformedPos.xy / transformedPos.z; \n\
+ mediump vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\
+ mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\
+ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\
+ gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\
+ A = hTexCoords.xy * invertedHTexCoordsZ; \n\
+ }\n";
+
+static const char* const qglslAffinePositionWithConicalGradientBrushVertexShader
+ = qglslPositionWithConicalGradientBrushVertexShader;
+
+static const char* const qglslConicalGradientBrushSrcFragmentShader = "\n\
+ #define INVERSE_2PI 0.1591549430918953358 \n\
+ uniform sampler2D brushTexture; \n\
+ uniform mediump float angle; \n\
+ varying highp vec2 A; \n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ highp float t; \n\
+ if (abs(A.y) == abs(A.x)) \n\
+ t = (atan(-A.y + 0.002, A.x) + angle) * INVERSE_2PI; \n\
+ else \n\
+ t = (atan(-A.y, A.x) + angle) * INVERSE_2PI; \n\
+ return texture2D(brushTexture, vec2(t - floor(t), 0.5)); \n\
+ }\n";
+
+
+// Radial Gradient Brush
+static const char* const qglslPositionWithRadialGradientBrushVertexShader = "\n\
+ attribute highp vec2 vertexCoordsArray;\n\
+ attribute highp vec3 pmvMatrix1; \n\
+ attribute highp vec3 pmvMatrix2; \n\
+ attribute highp vec3 pmvMatrix3; \n\
+ uniform mediump vec2 halfViewportSize; \n\
+ uniform highp mat3 brushTransform; \n\
+ uniform highp vec2 fmp; \n\
+ varying highp float b; \n\
+ varying highp vec2 A; \n\
+ void setPosition(void) \n\
+ {\n\
+ highp mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\
+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\
+ gl_Position.xy = transformedPos.xy / transformedPos.z; \n\
+ mediump vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\
+ mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\
+ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\
+ gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\
+ A = hTexCoords.xy * invertedHTexCoordsZ; \n\
+ b = 2.0 * dot(A, fmp); \n\
+ }\n";
+
+static const char* const qglslAffinePositionWithRadialGradientBrushVertexShader
+ = qglslPositionWithRadialGradientBrushVertexShader;
+
+static const char* const qglslRadialGradientBrushSrcFragmentShader = "\n\
+ uniform sampler2D brushTexture; \n\
+ uniform highp float fmp2_m_radius2; \n\
+ uniform highp float inverse_2_fmp2_m_radius2; \n\
+ varying highp float b; \n\
+ varying highp vec2 A; \n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ highp float c = -dot(A, A); \n\
+ highp vec2 val = vec2((-b + sqrt(b*b - 4.0*fmp2_m_radius2*c)) * inverse_2_fmp2_m_radius2, 0.5); \n\
+ return texture2D(brushTexture, val); \n\
+ }\n";
+
+
+// Texture Brush
+static const char* const qglslPositionWithTextureBrushVertexShader = "\n\
+ attribute highp vec2 vertexCoordsArray; \n\
+ attribute highp vec3 pmvMatrix1; \n\
+ attribute highp vec3 pmvMatrix2; \n\
+ attribute highp vec3 pmvMatrix3; \n\
+ uniform mediump vec2 halfViewportSize; \n\
+ uniform highp vec2 invertedTextureSize; \n\
+ uniform highp mat3 brushTransform; \n\
+ varying highp vec2 brushTextureCoords; \n\
+ void setPosition(void) \n\
+ { \n\
+ highp mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\
+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\
+ gl_Position.xy = transformedPos.xy / transformedPos.z; \n\
+ mediump vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\
+ mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\
+ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\
+ gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\
+ brushTextureCoords.xy = (hTexCoords.xy * invertedTextureSize) * gl_Position.w; \n\
+ }\n";
+
+static const char* const qglslAffinePositionWithTextureBrushVertexShader
+ = qglslPositionWithTextureBrushVertexShader;
+
+#if defined(QT_OPENGL_ES_2)
+// OpenGL ES does not support GL_REPEAT wrap modes for NPOT textures. So instead,
+// we emulate GL_REPEAT by only taking the fractional part of the texture coords.
+// TODO: Special case POT textures which don't need this emulation
+static const char* const qglslTextureBrushSrcFragmentShader = "\n\
+ varying highp vec2 brushTextureCoords; \n\
+ uniform sampler2D brushTexture; \n\
+ lowp vec4 srcPixel() { \n\
+ return texture2D(brushTexture, fract(brushTextureCoords)); \n\
+ }\n";
+#else
+static const char* const qglslTextureBrushSrcFragmentShader = "\n\
+ varying highp vec2 brushTextureCoords; \n\
+ uniform sampler2D brushTexture; \n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ return texture2D(brushTexture, brushTextureCoords); \n\
+ }\n";
+#endif
+
+static const char* const qglslTextureBrushSrcWithPatternFragmentShader = "\n\
+ varying highp vec2 brushTextureCoords; \n\
+ uniform lowp vec4 patternColor; \n\
+ uniform sampler2D brushTexture; \n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ return patternColor * (1.0 - texture2D(brushTexture, brushTextureCoords).r); \n\
+ }\n";
+
+// Solid Fill Brush
+static const char* const qglslSolidBrushSrcFragmentShader = "\n\
+ uniform lowp vec4 fragmentColor; \n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ return fragmentColor; \n\
+ }\n";
+
+static const char* const qglslImageSrcFragmentShader = "\n\
+ varying highp vec2 textureCoords; \n\
+ uniform sampler2D imageTexture; \n\
+ lowp vec4 srcPixel() \n\
+ { \n"
+ "return texture2D(imageTexture, textureCoords); \n"
+ "}\n";
+
+static const char* const qglslCustomSrcFragmentShader = "\n\
+ varying highp vec2 textureCoords; \n\
+ uniform sampler2D imageTexture; \n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ return customShader(imageTexture, textureCoords); \n\
+ }\n";
+
+static const char* const qglslImageSrcWithPatternFragmentShader = "\n\
+ varying highp vec2 textureCoords; \n\
+ uniform lowp vec4 patternColor; \n\
+ uniform sampler2D imageTexture; \n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ return patternColor * (1.0 - texture2D(imageTexture, textureCoords).r); \n\
+ }\n";
+
+static const char* const qglslNonPremultipliedImageSrcFragmentShader = "\n\
+ varying highp vec2 textureCoords; \n\
+ uniform sampler2D imageTexture; \n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ lowp vec4 sample = texture2D(imageTexture, textureCoords); \n\
+ sample.rgb = sample.rgb * sample.a; \n\
+ return sample; \n\
+ }\n";
+
+static const char* const qglslShockingPinkSrcFragmentShader = "\n\
+ lowp vec4 srcPixel() \n\
+ { \n\
+ return vec4(0.98, 0.06, 0.75, 1.0); \n\
+ }\n";
+
+static const char* const qglslMainFragmentShader_ImageArrays = "\n\
+ varying lowp float opacity; \n\
+ lowp vec4 srcPixel(); \n\
+ void main() \n\
+ { \n\
+ gl_FragColor = srcPixel() * opacity; \n\
+ }\n";
+
+static const char* const qglslMainFragmentShader_CMO = "\n\
+ uniform lowp float globalOpacity; \n\
+ lowp vec4 srcPixel(); \n\
+ lowp vec4 applyMask(lowp vec4); \n\
+ lowp vec4 compose(lowp vec4); \n\
+ void main() \n\
+ { \n\
+ gl_FragColor = applyMask(compose(srcPixel()*globalOpacity))); \n\
+ }\n";
+
+static const char* const qglslMainFragmentShader_CM = "\n\
+ lowp vec4 srcPixel(); \n\
+ lowp vec4 applyMask(lowp vec4); \n\
+ lowp vec4 compose(lowp vec4); \n\
+ void main() \n\
+ { \n\
+ gl_FragColor = applyMask(compose(srcPixel())); \n\
+ }\n";
+
+static const char* const qglslMainFragmentShader_MO = "\n\
+ uniform lowp float globalOpacity; \n\
+ lowp vec4 srcPixel(); \n\
+ lowp vec4 applyMask(lowp vec4); \n\
+ void main() \n\
+ { \n\
+ gl_FragColor = applyMask(srcPixel()*globalOpacity); \n\
+ }\n";
+
+static const char* const qglslMainFragmentShader_M = "\n\
+ lowp vec4 srcPixel(); \n\
+ lowp vec4 applyMask(lowp vec4); \n\
+ void main() \n\
+ { \n\
+ gl_FragColor = applyMask(srcPixel()); \n\
+ }\n";
+
+static const char* const qglslMainFragmentShader_CO = "\n\
+ uniform lowp float globalOpacity; \n\
+ lowp vec4 srcPixel(); \n\
+ lowp vec4 compose(lowp vec4); \n\
+ void main() \n\
+ { \n\
+ gl_FragColor = compose(srcPixel()*globalOpacity); \n\
+ }\n";
+
+static const char* const qglslMainFragmentShader_C = "\n\
+ lowp vec4 srcPixel(); \n\
+ lowp vec4 compose(lowp vec4); \n\
+ void main() \n\
+ { \n\
+ gl_FragColor = compose(srcPixel()); \n\
+ }\n";
+
+static const char* const qglslMainFragmentShader_O = "\n\
+ uniform lowp float globalOpacity; \n\
+ lowp vec4 srcPixel(); \n\
+ void main() \n\
+ { \n\
+ gl_FragColor = srcPixel()*globalOpacity; \n\
+ }\n";
+
+static const char* const qglslMainFragmentShader = "\n\
+ lowp vec4 srcPixel(); \n\
+ void main() \n\
+ { \n\
+ gl_FragColor = srcPixel(); \n\
+ }\n";
+
+static const char* const qglslMaskFragmentShader = "\n\
+ varying highp vec2 textureCoords;\n\
+ uniform sampler2D maskTexture;\n\
+ lowp vec4 applyMask(lowp vec4 src) \n\
+ {\n\
+ lowp vec4 mask = texture2D(maskTexture, textureCoords); \n\
+ return src * mask.a; \n\
+ }\n";
+
+// For source over with subpixel antialiasing, the final color is calculated per component as follows
+// (.a is alpha component, .c is red, green or blue component):
+// alpha = src.a * mask.c * opacity
+// dest.c = dest.c * (1 - alpha) + src.c * alpha
+//
+// In the first pass, calculate: dest.c = dest.c * (1 - alpha) with blend funcs: zero, 1 - source color
+// In the second pass, calculate: dest.c = dest.c + src.c * alpha with blend funcs: one, one
+//
+// If source is a solid color (src is constant), only the first pass is needed, with blend funcs: constant, 1 - source color
+
+// For source composition with subpixel antialiasing, the final color is calculated per component as follows:
+// alpha = src.a * mask.c * opacity
+// dest.c = dest.c * (1 - mask.c) + src.c * alpha
+//
+
+static const char* const qglslRgbMaskFragmentShaderPass1 = "\n\
+ varying highp vec2 textureCoords;\n\
+ uniform sampler2D maskTexture;\n\
+ lowp vec4 applyMask(lowp vec4 src) \n\
+ { \n\
+ lowp vec4 mask = texture2D(maskTexture, textureCoords); \n\
+ return src.a * mask; \n\
+ }\n";
+
+static const char* const qglslRgbMaskFragmentShaderPass2 = "\n\
+ varying highp vec2 textureCoords;\n\
+ uniform sampler2D maskTexture;\n\
+ lowp vec4 applyMask(lowp vec4 src) \n\
+ { \n\
+ lowp vec4 mask = texture2D(maskTexture, textureCoords); \n\
+ return src * mask; \n\
+ }\n";
+
+/*
+ Left to implement:
+ RgbMaskFragmentShader,
+ RgbMaskWithGammaFragmentShader,
+
+ MultiplyCompositionModeFragmentShader,
+ ScreenCompositionModeFragmentShader,
+ OverlayCompositionModeFragmentShader,
+ DarkenCompositionModeFragmentShader,
+ LightenCompositionModeFragmentShader,
+ ColorDodgeCompositionModeFragmentShader,
+ ColorBurnCompositionModeFragmentShader,
+ HardLightCompositionModeFragmentShader,
+ SoftLightCompositionModeFragmentShader,
+ DifferenceCompositionModeFragmentShader,
+ ExclusionCompositionModeFragmentShader,
+*/
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // GLGC_SHADER_SOURCE_H
diff --git a/src/opengl/gl2paintengineex/qglgradientcache.cpp b/src/opengl/gl2paintengineex/qglgradientcache.cpp
new file mode 100644
index 0000000000..075f7d15ea
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qglgradientcache.cpp
@@ -0,0 +1,206 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qglgradientcache_p.h"
+#include <private/qdrawhelper_p.h>
+#include <private/qgl_p.h>
+#include <QtCore/qmutex.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGL2GradientCacheWrapper
+{
+public:
+ QGL2GradientCache *cacheForContext(const QGLContext *context) {
+ QMutexLocker lock(&m_mutex);
+ return m_resource.value(context);
+ }
+
+private:
+ QGLContextGroupResource<QGL2GradientCache> m_resource;
+ QMutex m_mutex;
+};
+
+Q_GLOBAL_STATIC(QGL2GradientCacheWrapper, qt_gradient_caches)
+
+QGL2GradientCache *QGL2GradientCache::cacheForContext(const QGLContext *context)
+{
+ return qt_gradient_caches()->cacheForContext(context);
+}
+
+void QGL2GradientCache::cleanCache()
+{
+ QMutexLocker lock(&m_mutex);
+ QGLGradientColorTableHash::const_iterator it = cache.constBegin();
+ for (; it != cache.constEnd(); ++it) {
+ const CacheInfo &cache_info = it.value();
+ glDeleteTextures(1, &cache_info.texId);
+ }
+ cache.clear();
+}
+
+GLuint QGL2GradientCache::getBuffer(const QGradient &gradient, qreal opacity)
+{
+ QMutexLocker lock(&m_mutex);
+ quint64 hash_val = 0;
+
+ QGradientStops stops = gradient.stops();
+ for (int i = 0; i < stops.size() && i <= 2; i++)
+ hash_val += stops[i].second.rgba();
+
+ QGLGradientColorTableHash::const_iterator it = cache.constFind(hash_val);
+
+ if (it == cache.constEnd())
+ return addCacheElement(hash_val, gradient, opacity);
+ else {
+ do {
+ const CacheInfo &cache_info = it.value();
+ if (cache_info.stops == stops && cache_info.opacity == opacity
+ && cache_info.interpolationMode == gradient.interpolationMode())
+ {
+ return cache_info.texId;
+ }
+ ++it;
+ } while (it != cache.constEnd() && it.key() == hash_val);
+ // an exact match for these stops and opacity was not found, create new cache
+ return addCacheElement(hash_val, gradient, opacity);
+ }
+}
+
+
+GLuint QGL2GradientCache::addCacheElement(quint64 hash_val, const QGradient &gradient, qreal opacity)
+{
+ if (cache.size() == maxCacheSize()) {
+ int elem_to_remove = qrand() % maxCacheSize();
+ quint64 key = cache.keys()[elem_to_remove];
+
+ // need to call glDeleteTextures on each removed cache entry:
+ QGLGradientColorTableHash::const_iterator it = cache.constFind(key);
+ do {
+ glDeleteTextures(1, &it.value().texId);
+ } while (++it != cache.constEnd() && it.key() == key);
+ cache.remove(key); // may remove more than 1, but OK
+ }
+
+ CacheInfo cache_entry(gradient.stops(), opacity, gradient.interpolationMode());
+ uint buffer[1024];
+ generateGradientColorTable(gradient, buffer, paletteSize(), opacity);
+ glGenTextures(1, &cache_entry.texId);
+ glBindTexture(GL_TEXTURE_2D, cache_entry.texId);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, paletteSize(), 1,
+ 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
+ return cache.insert(hash_val, cache_entry).value().texId;
+}
+
+
+// GL's expects pixels in RGBA (when using GL_RGBA), bin-endian (ABGR on x86).
+// Qt always stores in ARGB reguardless of the byte-order the mancine uses.
+static inline uint qtToGlColor(uint c)
+{
+ uint o;
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+ o = (c & 0xff00ff00) // alpha & green already in the right place
+ | ((c >> 16) & 0x000000ff) // red
+ | ((c << 16) & 0x00ff0000); // blue
+#else //Q_BIG_ENDIAN
+ o = (c << 8)
+ | ((c >> 24) & 0x000000ff);
+#endif // Q_BYTE_ORDER
+ return o;
+}
+
+//TODO: Let GL generate the texture using an FBO
+void QGL2GradientCache::generateGradientColorTable(const QGradient& gradient, uint *colorTable, int size, qreal opacity) const
+{
+ int pos = 0;
+ QGradientStops s = gradient.stops();
+ QVector<uint> colors(s.size());
+
+ for (int i = 0; i < s.size(); ++i)
+ colors[i] = s[i].second.rgba(); // Qt LIES! It returns ARGB (on little-endian AND on big-endian)
+
+ bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation);
+
+ uint alpha = qRound(opacity * 256);
+ uint current_color = ARGB_COMBINE_ALPHA(colors[0], alpha);
+ qreal incr = 1.0 / qreal(size);
+ qreal fpos = 1.5 * incr;
+ colorTable[pos++] = qtToGlColor(PREMUL(current_color));
+
+ while (fpos <= s.first().first) {
+ colorTable[pos] = colorTable[pos - 1];
+ pos++;
+ fpos += incr;
+ }
+
+ if (colorInterpolation)
+ current_color = PREMUL(current_color);
+
+ for (int i = 0; i < s.size() - 1; ++i) {
+ qreal delta = 1/(s[i+1].first - s[i].first);
+ uint next_color = ARGB_COMBINE_ALPHA(colors[i+1], alpha);
+ if (colorInterpolation)
+ next_color = PREMUL(next_color);
+
+ while (fpos < s[i+1].first && pos < size) {
+ int dist = int(256 * ((fpos - s[i].first) * delta));
+ int idist = 256 - dist;
+ if (colorInterpolation)
+ colorTable[pos] = qtToGlColor(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist));
+ else
+ colorTable[pos] = qtToGlColor(PREMUL(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist)));
+ ++pos;
+ fpos += incr;
+ }
+ current_color = next_color;
+ }
+
+ Q_ASSERT(s.size() > 0);
+
+ uint last_color = qtToGlColor(PREMUL(ARGB_COMBINE_ALPHA(colors[s.size() - 1], alpha)));
+ for (;pos < size; ++pos)
+ colorTable[pos] = last_color;
+
+ // Make sure the last color stop is represented at the end of the table
+ colorTable[size-1] = last_color;
+}
+
+QT_END_NAMESPACE
diff --git a/src/opengl/gl2paintengineex/qglgradientcache_p.h b/src/opengl/gl2paintengineex/qglgradientcache_p.h
new file mode 100644
index 0000000000..d1d56a178a
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qglgradientcache_p.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QMultiHash>
+#include <QObject>
+#include <QtOpenGL/QtOpenGL>
+#include <private/qgl_p.h>
+#include <QtCore/qmutex.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGL2GradientCache
+{
+ struct CacheInfo
+ {
+ inline CacheInfo(QGradientStops s, qreal op, QGradient::InterpolationMode mode) :
+ stops(s), opacity(op), interpolationMode(mode) {}
+
+ GLuint texId;
+ QGradientStops stops;
+ qreal opacity;
+ QGradient::InterpolationMode interpolationMode;
+ };
+
+ typedef QMultiHash<quint64, CacheInfo> QGLGradientColorTableHash;
+
+public:
+ static QGL2GradientCache *cacheForContext(const QGLContext *context);
+
+ QGL2GradientCache(const QGLContext *) {}
+ ~QGL2GradientCache() { cleanCache(); }
+
+ GLuint getBuffer(const QGradient &gradient, qreal opacity);
+ inline int paletteSize() const { return 1024; }
+
+private:
+ inline int maxCacheSize() const { return 60; }
+ inline void generateGradientColorTable(const QGradient& gradient,
+ uint *colorTable,
+ int size, qreal opacity) const;
+ GLuint addCacheElement(quint64 hash_val, const QGradient &gradient, qreal opacity);
+ void cleanCache();
+
+ QGLGradientColorTableHash cache;
+ QMutex m_mutex;
+};
+
+QT_END_NAMESPACE
+
diff --git a/src/opengl/gl2paintengineex/qglshadercache_meego_p.h b/src/opengl/gl2paintengineex/qglshadercache_meego_p.h
new file mode 100644
index 0000000000..d20c731fc3
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qglshadercache_meego_p.h
@@ -0,0 +1,457 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QGLSHADERCACHE_MEEGO_P_H
+#define QGLSHADERCACHE_MEEGO_P_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(QT_MEEGO_EXPERIMENTAL_SHADERCACHE) && defined(QT_OPENGL_ES_2)
+
+#include <QtCore/qcryptographichash.h>
+#include <QtCore/qsharedmemory.h>
+#include <QtCore/qsystemsemaphore.h>
+
+#ifndef QT_BOOTSTRAPPED
+# include <GLES2/gl2ext.h>
+#endif
+#if defined(QT_DEBUG) || defined(QT_MEEGO_EXPERIMENTAL_SHADERCACHE_TRACE)
+# include <syslog.h>
+#endif
+
+QT_BEGIN_HEADER
+
+/*
+ This cache stores internal Qt shader programs in shared memory.
+
+ This header file is ugly on purpose and can only be included once. It is only to be used
+ for the internal shader cache, not as a generic cache for anyone's shaders.
+
+ The cache stores either ShaderCacheMaxEntries shader programs or ShaderCacheDataSize kilobytes
+ of shader programs, whatever limit is reached first.
+
+ The layout of the cache is as outlined in the CachedShaders struct. After some
+ integers, an array of headers is reserved, then comes the space for the actual binaries.
+
+ Shader Programs are identified by the md5sum of their frag and vertex shader source code.
+
+ Shader Programs are never removed. The cache never shrinks or re-shuffles. This is done
+ on purpose to ensure minimum amount of locking, no alignment problems and very few write
+ operations.
+
+ Note: Locking the shader cache could be expensive, because the entire system might hang.
+ That's why the cache is immutable to minimize the time we need to keep it locked.
+
+ Why is it Meego specific?
+
+ First, the size is chosen so that it fits to generic meego usage. Second, on Meego, there's
+ always at least one Qt application active (the launcher), so the cache will never be destroyed.
+ Only when the last Qt app exits, the cache dies, which should only be when someone kills the
+ X11 server. And last but not least it was only tested with Meego's SGX driver.
+
+ There's a small tool in src/opengl/util/meego that dumps the contents of the cache.
+ */
+
+// anonymous namespace, prevent exporting of the private symbols
+namespace
+{
+
+struct CachedShaderHeader
+{
+ /* the index in the data[] member of CachedShaders */
+ int index;
+ /* the size of the binary shader */
+ GLsizei size;
+ /* the format of the binary shader */
+ GLenum format;
+ /* the md5sum of the frag+vertex shaders */
+ char md5Sum[16];
+};
+
+enum
+{
+ /* The maximum amount of shader programs the cache can hold */
+ ShaderCacheMaxEntries = 20
+};
+
+typedef CachedShaderHeader CachedShaderHeaders[ShaderCacheMaxEntries];
+
+enum
+{
+ // ShaderCacheDataSize is 20k minus the other data members of CachedShaders
+ ShaderCacheDataSize = 1024 * ShaderCacheMaxEntries - sizeof(CachedShaderHeaders) - 2 * sizeof(int)
+};
+
+struct CachedShaders
+{
+ /* How much space is still available in the cache */
+ inline int availableSize() const { return ShaderCacheDataSize - dataSize; }
+
+ /* The current amount of cached shaders */
+ int shaderCount;
+
+ /* The current amount (in bytes) of cached data */
+ int dataSize;
+
+ /* The headers describing the shaders */
+ CachedShaderHeaders headers;
+
+ /* The actual binary data of the shader programs */
+ char data[ShaderCacheDataSize];
+};
+
+//#define QT_DEBUG_SHADER_CACHE
+#ifdef QT_DEBUG_SHADER_CACHE
+static QDebug shaderCacheDebug()
+{
+ return QDebug(QtDebugMsg);
+}
+#else
+static inline QNoDebug shaderCacheDebug() { return QNoDebug(); }
+#endif
+
+class ShaderCacheSharedMemory
+{
+public:
+ ShaderCacheSharedMemory()
+ : shm(QLatin1String("qt_gles2_shadercache_" QT_VERSION_STR))
+ {
+ // we need a system semaphore here, since cache creation and initialization must be atomic
+ QSystemSemaphore attachSemaphore(QLatin1String("qt_gles2_shadercache_mutex_" QT_VERSION_STR), 1);
+
+ if (!attachSemaphore.acquire()) {
+ shaderCacheDebug() << "Unable to require shader cache semaphore:" << attachSemaphore.errorString();
+ return;
+ }
+
+ if (shm.attach()) {
+ // success!
+ shaderCacheDebug() << "Attached to shader cache";
+ } else {
+
+ // no cache exists - create and initialize it
+ if (shm.create(sizeof(CachedShaders))) {
+ shaderCacheDebug() << "Created new shader cache";
+ initializeCache();
+ } else {
+ shaderCacheDebug() << "Unable to create shader cache:" << shm.errorString();
+ }
+ }
+
+ attachSemaphore.release();
+ }
+
+ inline bool isAttached() const { return shm.isAttached(); }
+
+ inline bool lock() { return shm.lock(); }
+ inline bool unlock() { return shm.unlock(); }
+ inline void *data() { return shm.data(); }
+ inline QString errorString() { return shm.errorString(); }
+
+ ~ShaderCacheSharedMemory()
+ {
+ if (!shm.detach())
+ shaderCacheDebug() << "Unable to detach shader cache" << shm.errorString();
+ }
+
+private:
+ void initializeCache()
+ {
+ // no need to lock the shared memory since we're already protected by the
+ // attach system semaphore.
+
+ void *data = shm.data();
+ Q_ASSERT(data);
+
+ memset(data, 0, sizeof(CachedShaders));
+ }
+
+ QSharedMemory shm;
+};
+
+class ShaderCacheLocker
+{
+public:
+ inline ShaderCacheLocker(ShaderCacheSharedMemory *cache)
+ : shm(cache->lock() ? cache : (ShaderCacheSharedMemory *)0)
+ {
+ if (!shm)
+ shaderCacheDebug() << "Unable to lock shader cache" << cache->errorString();
+ }
+
+ inline bool isLocked() const { return shm; }
+
+ inline ~ShaderCacheLocker()
+ {
+ if (!shm)
+ return;
+ if (!shm->unlock())
+ shaderCacheDebug() << "Unable to unlock shader cache" << shm->errorString();
+ }
+
+private:
+ ShaderCacheSharedMemory *shm;
+};
+
+#ifdef QT_BOOTSTRAPPED
+} // end namespace
+#else
+
+static void traceCacheOverflow(const char *message)
+{
+#if defined(QT_DEBUG) || defined (QT_MEEGO_EXPERIMENTAL_SHADERCACHE_TRACE)
+ openlog(qPrintable(QCoreApplication::applicationName()), LOG_PID | LOG_ODELAY, LOG_USER);
+ syslog(LOG_DEBUG, message);
+ closelog();
+#endif
+ shaderCacheDebug() << message;
+}
+
+Q_GLOBAL_STATIC(ShaderCacheSharedMemory, shaderCacheSharedMemory)
+
+/*
+ Finds the index of the shader program identified by md5Sum in the cache.
+ Note: Does NOT lock the cache for reading, the cache must already be locked!
+
+ Returns -1 when no shader was found.
+ */
+static int qt_cache_index_unlocked(const QByteArray &md5Sum, CachedShaders *cache)
+{
+ for (int i = 0; i < cache->shaderCount; ++i) {
+ if (qstrncmp(md5Sum.constData(), cache->headers[i].md5Sum, 16) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/* Returns the index of the shader identified by md5Sum */
+static int qt_cache_index(const QByteArray &md5Sum)
+{
+ ShaderCacheSharedMemory *shm = shaderCacheSharedMemory();
+ if (!shm || !shm->isAttached())
+ return false;
+
+ Q_ASSERT(md5Sum.length() == 16);
+
+ ShaderCacheLocker locker(shm);
+ if (!locker.isLocked())
+ return false;
+
+ void *data = shm->data();
+ Q_ASSERT(data);
+
+ CachedShaders *cache = reinterpret_cast<CachedShaders *>(data);
+
+ return qt_cache_index_unlocked(md5Sum, cache);
+}
+
+/* Loads the cached shader at index \a shaderIndex into \a program
+ * Note: Since the cache is immutable, this operation doesn't lock the shared memory.
+ */
+static bool qt_cached_shader(QGLShaderProgram *program, const QGLContext *ctx, int shaderIndex)
+{
+ Q_ASSERT(shaderIndex >= 0 && shaderIndex <= ShaderCacheMaxEntries);
+ Q_ASSERT(program);
+
+ ShaderCacheSharedMemory *shm = shaderCacheSharedMemory();
+ if (!shm || !shm->isAttached())
+ return false;
+
+ void *data = shm->data();
+ Q_ASSERT(data);
+
+ CachedShaders *cache = reinterpret_cast<CachedShaders *>(data);
+
+ shaderCacheDebug() << "fetching cached shader at index" << shaderIndex
+ << "dataIndex" << cache->headers[shaderIndex].index
+ << "size" << cache->headers[shaderIndex].size
+ << "format" << cache->headers[shaderIndex].format;
+
+ // call program->programId first, since that resolves the glProgramBinaryOES symbol
+ GLuint programId = program->programId();
+ glProgramBinaryOES(programId, cache->headers[shaderIndex].format,
+ cache->data + cache->headers[shaderIndex].index,
+ cache->headers[shaderIndex].size);
+
+ return true;
+}
+
+/* Stores the shader program in the cache. Returns false if there's an error with the cache, or
+ if the cache is too small to hold the shader. */
+static bool qt_cache_shader(const QGLShaderProgram *shader, const QGLContext *ctx, const QByteArray &md5Sum)
+{
+ ShaderCacheSharedMemory *shm = shaderCacheSharedMemory();
+ if (!shm || !shm->isAttached())
+ return false;
+
+ void *data = shm->data();
+ Q_ASSERT(data);
+
+ CachedShaders *cache = reinterpret_cast<CachedShaders *>(data);
+
+ ShaderCacheLocker locker(shm);
+ if (!locker.isLocked())
+ return false;
+
+ int cacheIdx = cache->shaderCount;
+ if (cacheIdx >= ShaderCacheMaxEntries) {
+ traceCacheOverflow("Qt OpenGL shader cache index overflow!");
+ return false;
+ }
+
+ // now that we have the lock on the shared memory, make sure no one
+ // inserted the shader already while we were unlocked
+ if (qt_cache_index_unlocked(md5Sum, cache) != -1)
+ return true; // already cached
+
+ shaderCacheDebug() << "Caching shader at index" << cacheIdx;
+
+ GLint binaryLength = 0;
+ glGetProgramiv(shader->programId(), GL_PROGRAM_BINARY_LENGTH_OES, &binaryLength);
+
+ if (!binaryLength) {
+ shaderCacheDebug() << "Unable to determine binary shader size!";
+ return false;
+ }
+
+ if (binaryLength > cache->availableSize()) {
+ traceCacheOverflow("Qt OpenGL shader cache data overflow!");
+ return false;
+ }
+
+ GLsizei size = 0;
+ GLenum format = 0;
+ glGetProgramBinaryOES(shader->programId(), binaryLength, &size, &format,
+ cache->data + cache->dataSize);
+
+ if (!size) {
+ shaderCacheDebug() << "Unable to get binary shader!";
+ return false;
+ }
+
+ cache->headers[cacheIdx].index = cache->dataSize;
+ cache->dataSize += binaryLength;
+ ++cache->shaderCount;
+ cache->headers[cacheIdx].size = binaryLength;
+ cache->headers[cacheIdx].format = format;
+
+ memcpy(cache->headers[cacheIdx].md5Sum, md5Sum.constData(), 16);
+
+ shaderCacheDebug() << "cached shader size" << size
+ << "format" << format
+ << "binarySize" << binaryLength
+ << "cache index" << cacheIdx
+ << "data index" << cache->headers[cacheIdx].index;
+
+ return true;
+}
+
+} // namespace
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(OpenGL)
+
+class CachedShader
+{
+public:
+ CachedShader(const QByteArray &fragSource, const QByteArray &vertexSource)
+ : cacheIdx(-1)
+ {
+ QCryptographicHash md5Hash(QCryptographicHash::Md5);
+
+ md5Hash.addData(fragSource);
+ md5Hash.addData(vertexSource);
+
+ md5Sum = md5Hash.result();
+ }
+
+ bool isCached()
+ {
+ return cacheIndex() != -1;
+ }
+
+ int cacheIndex()
+ {
+ if (cacheIdx != -1)
+ return cacheIdx;
+ cacheIdx = qt_cache_index(md5Sum);
+ return cacheIdx;
+ }
+
+ bool load(QGLShaderProgram *program, const QGLContext *ctx)
+ {
+ if (cacheIndex() == -1)
+ return false;
+ return qt_cached_shader(program, ctx, cacheIdx);
+ }
+
+ bool store(QGLShaderProgram *program, const QGLContext *ctx)
+ {
+ return qt_cache_shader(program, ctx, md5Sum);
+ }
+
+private:
+ QByteArray md5Sum;
+ int cacheIdx;
+};
+
+
+QT_END_NAMESPACE
+
+#endif
+
+QT_END_HEADER
+
+#endif
+#endif
diff --git a/src/opengl/gl2paintengineex/qglshadercache_p.h b/src/opengl/gl2paintengineex/qglshadercache_p.h
new file mode 100644
index 0000000000..6e496abf7c
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qglshadercache_p.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QGLSHADERCACHE_P_H
+#define QGLSHADERCACHE_P_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(QT_MEEGO_EXPERIMENTAL_SHADERCACHE) && defined(QT_OPENGL_ES_2)
+# include "qglshadercache_meego_p.h"
+#else
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(OpenGL)
+
+class QGLShaderProgram;
+class QGLContext;
+
+class CachedShader
+{
+public:
+ inline CachedShader(const QByteArray &, const QByteArray &)
+ {}
+
+ inline bool isCached()
+ {
+ return false;
+ }
+
+ inline bool load(QGLShaderProgram *, const QGLContext *)
+ {
+ return false;
+ }
+
+ inline bool store(QGLShaderProgram *, const QGLContext *)
+ {
+ return false;
+ }
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif
+#endif
diff --git a/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp b/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp
new file mode 100644
index 0000000000..18c684ff1b
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp
@@ -0,0 +1,2458 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*
+ When the active program changes, we need to update it's uniforms.
+ We could track state for each program and only update stale uniforms
+ - Could lead to lots of overhead if there's a lot of programs
+ We could update all the uniforms when the program changes
+ - Could end up updating lots of uniforms which don't need updating
+
+ Updating uniforms should be cheap, so the overhead of updating up-to-date
+ uniforms should be minimal. It's also less complex.
+
+ Things which _may_ cause a different program to be used:
+ - Change in brush/pen style
+ - Change in painter opacity
+ - Change in composition mode
+
+ Whenever we set a mode on the shader manager - it needs to tell us if it had
+ to switch to a different program.
+
+ The shader manager should only switch when we tell it to. E.g. if we set a new
+ brush style and then switch to transparent painter, we only want it to compile
+ and use the correct program when we really need it.
+*/
+
+// #define QT_OPENGL_CACHE_AS_VBOS
+
+#include "qglgradientcache_p.h"
+#include "qpaintengineex_opengl2_p.h"
+
+#include <string.h> //for memcpy
+#include <qmath.h>
+
+#include <private/qgl_p.h>
+#include <private/qmath_p.h>
+#include <private/qpaintengineex_p.h>
+#include <QPaintEngine>
+#include <private/qpainter_p.h>
+#include <private/qfontengine_p.h>
+#include <private/qpixmapdata_gl_p.h>
+#include <private/qdatabuffer_p.h>
+#include <private/qstatictext_p.h>
+#include <private/qtriangulator_p.h>
+
+#include "qglengineshadermanager_p.h"
+#include "qgl2pexvertexarray_p.h"
+#include "qtriangulatingstroker_p.h"
+#include "qtextureglyphcache_gl_p.h"
+
+#include <QDebug>
+
+QT_BEGIN_NAMESPACE
+
+#if defined(Q_WS_WIN)
+extern Q_GUI_EXPORT bool qt_cleartype_enabled;
+#endif
+
+#ifdef Q_WS_MAC
+extern bool qt_applefontsmoothing_enabled;
+#endif
+
+#if !defined(QT_MAX_CACHED_GLYPH_SIZE)
+# define QT_MAX_CACHED_GLYPH_SIZE 64
+#endif
+
+Q_GUI_EXPORT QImage qt_imageForBrush(int brushStyle, bool invert);
+
+////////////////////////////////// Private Methods //////////////////////////////////////////
+
+QGL2PaintEngineExPrivate::~QGL2PaintEngineExPrivate()
+{
+ delete shaderManager;
+
+ while (pathCaches.size()) {
+ QVectorPath::CacheEntry *e = *(pathCaches.constBegin());
+ e->cleanup(e->engine, e->data);
+ e->data = 0;
+ e->engine = 0;
+ }
+
+ if (elementIndicesVBOId != 0) {
+ glDeleteBuffers(1, &elementIndicesVBOId);
+ elementIndicesVBOId = 0;
+ }
+}
+
+void QGL2PaintEngineExPrivate::updateTextureFilter(GLenum target, GLenum wrapMode, bool smoothPixmapTransform, GLuint id)
+{
+// glActiveTexture(GL_TEXTURE0 + QT_BRUSH_TEXTURE_UNIT); //### Is it always this texture unit?
+ if (id != GLuint(-1) && id == lastTextureUsed)
+ return;
+
+ lastTextureUsed = id;
+
+ if (smoothPixmapTransform) {
+ glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ } else {
+ glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ }
+ glTexParameterf(target, GL_TEXTURE_WRAP_S, wrapMode);
+ glTexParameterf(target, GL_TEXTURE_WRAP_T, wrapMode);
+}
+
+
+inline QColor qt_premultiplyColor(QColor c, GLfloat opacity)
+{
+ qreal alpha = c.alphaF() * opacity;
+ c.setAlphaF(alpha);
+ c.setRedF(c.redF() * alpha);
+ c.setGreenF(c.greenF() * alpha);
+ c.setBlueF(c.blueF() * alpha);
+ return c;
+}
+
+
+void QGL2PaintEngineExPrivate::setBrush(const QBrush& brush)
+{
+ if (qbrush_fast_equals(currentBrush, brush))
+ return;
+
+ const Qt::BrushStyle newStyle = qbrush_style(brush);
+ Q_ASSERT(newStyle != Qt::NoBrush);
+
+ currentBrush = brush;
+ if (!currentBrushPixmap.isNull())
+ currentBrushPixmap = QPixmap();
+ brushUniformsDirty = true; // All brushes have at least one uniform
+
+ if (newStyle > Qt::SolidPattern)
+ brushTextureDirty = true;
+
+ if (currentBrush.style() == Qt::TexturePattern
+ && qHasPixmapTexture(brush) && brush.texture().isQBitmap())
+ {
+ shaderManager->setSrcPixelType(QGLEngineShaderManager::TextureSrcWithPattern);
+ } else {
+ shaderManager->setSrcPixelType(newStyle);
+ }
+ shaderManager->optimiseForBrushTransform(currentBrush.transform().type());
+}
+
+
+void QGL2PaintEngineExPrivate::useSimpleShader()
+{
+ shaderManager->useSimpleProgram();
+
+ if (matrixDirty)
+ updateMatrix();
+}
+
+void QGL2PaintEngineExPrivate::updateBrushTexture()
+{
+ Q_Q(QGL2PaintEngineEx);
+// qDebug("QGL2PaintEngineExPrivate::updateBrushTexture()");
+ Qt::BrushStyle style = currentBrush.style();
+
+ if ( (style >= Qt::Dense1Pattern) && (style <= Qt::DiagCrossPattern) ) {
+ // Get the image data for the pattern
+ QImage texImage = qt_imageForBrush(style, false);
+
+ glActiveTexture(GL_TEXTURE0 + QT_BRUSH_TEXTURE_UNIT);
+ ctx->d_func()->bindTexture(texImage, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption);
+ updateTextureFilter(GL_TEXTURE_2D, GL_REPEAT, q->state()->renderHints & QPainter::SmoothPixmapTransform);
+ }
+ else if (style >= Qt::LinearGradientPattern && style <= Qt::ConicalGradientPattern) {
+ // Gradiant brush: All the gradiants use the same texture
+
+ const QGradient* g = currentBrush.gradient();
+
+ // We apply global opacity in the fragment shaders, so we always pass 1.0
+ // for opacity to the cache.
+ GLuint texId = QGL2GradientCache::cacheForContext(ctx)->getBuffer(*g, 1.0);
+
+ glActiveTexture(GL_TEXTURE0 + QT_BRUSH_TEXTURE_UNIT);
+ glBindTexture(GL_TEXTURE_2D, texId);
+
+ if (g->spread() == QGradient::RepeatSpread || g->type() == QGradient::ConicalGradient)
+ updateTextureFilter(GL_TEXTURE_2D, GL_REPEAT, q->state()->renderHints & QPainter::SmoothPixmapTransform);
+ else if (g->spread() == QGradient::ReflectSpread)
+ updateTextureFilter(GL_TEXTURE_2D, GL_MIRRORED_REPEAT_IBM, q->state()->renderHints & QPainter::SmoothPixmapTransform);
+ else
+ updateTextureFilter(GL_TEXTURE_2D, GL_CLAMP_TO_EDGE, q->state()->renderHints & QPainter::SmoothPixmapTransform);
+ }
+ else if (style == Qt::TexturePattern) {
+ currentBrushPixmap = currentBrush.texture();
+
+ int max_texture_size = ctx->d_func()->maxTextureSize();
+ if (currentBrushPixmap.width() > max_texture_size || currentBrushPixmap.height() > max_texture_size)
+ currentBrushPixmap = currentBrushPixmap.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
+
+ glActiveTexture(GL_TEXTURE0 + QT_BRUSH_TEXTURE_UNIT);
+ QGLTexture *tex = ctx->d_func()->bindTexture(currentBrushPixmap, GL_TEXTURE_2D, GL_RGBA,
+ QGLContext::InternalBindOption |
+ QGLContext::CanFlipNativePixmapBindOption);
+ updateTextureFilter(GL_TEXTURE_2D, GL_REPEAT, q->state()->renderHints & QPainter::SmoothPixmapTransform);
+ textureInvertedY = tex->options & QGLContext::InvertedYBindOption ? -1 : 1;
+ }
+ brushTextureDirty = false;
+}
+
+
+void QGL2PaintEngineExPrivate::updateBrushUniforms()
+{
+// qDebug("QGL2PaintEngineExPrivate::updateBrushUniforms()");
+ Qt::BrushStyle style = currentBrush.style();
+
+ if (style == Qt::NoBrush)
+ return;
+
+ QTransform brushQTransform = currentBrush.transform();
+
+ if (style == Qt::SolidPattern) {
+ QColor col = qt_premultiplyColor(currentBrush.color(), (GLfloat)q->state()->opacity);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::FragmentColor), col);
+ }
+ else {
+ // All other brushes have a transform and thus need the translation point:
+ QPointF translationPoint;
+
+ if (style <= Qt::DiagCrossPattern) {
+ QColor col = qt_premultiplyColor(currentBrush.color(), (GLfloat)q->state()->opacity);
+
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::PatternColor), col);
+
+ QVector2D halfViewportSize(width*0.5, height*0.5);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::HalfViewportSize), halfViewportSize);
+ }
+ else if (style == Qt::LinearGradientPattern) {
+ const QLinearGradient *g = static_cast<const QLinearGradient *>(currentBrush.gradient());
+
+ QPointF realStart = g->start();
+ QPointF realFinal = g->finalStop();
+ translationPoint = realStart;
+
+ QPointF l = realFinal - realStart;
+
+ QVector3D linearData(
+ l.x(),
+ l.y(),
+ 1.0f / (l.x() * l.x() + l.y() * l.y())
+ );
+
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::LinearData), linearData);
+
+ QVector2D halfViewportSize(width*0.5, height*0.5);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::HalfViewportSize), halfViewportSize);
+ }
+ else if (style == Qt::ConicalGradientPattern) {
+ const QConicalGradient *g = static_cast<const QConicalGradient *>(currentBrush.gradient());
+ translationPoint = g->center();
+
+ GLfloat angle = -(g->angle() * 2 * Q_PI) / 360.0;
+
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::Angle), angle);
+
+ QVector2D halfViewportSize(width*0.5, height*0.5);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::HalfViewportSize), halfViewportSize);
+ }
+ else if (style == Qt::RadialGradientPattern) {
+ const QRadialGradient *g = static_cast<const QRadialGradient *>(currentBrush.gradient());
+ QPointF realCenter = g->center();
+ QPointF realFocal = g->focalPoint();
+ qreal realRadius = g->radius();
+ translationPoint = realFocal;
+
+ QPointF fmp = realCenter - realFocal;
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::Fmp), fmp);
+
+ GLfloat fmp2_m_radius2 = -fmp.x() * fmp.x() - fmp.y() * fmp.y() + realRadius*realRadius;
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::Fmp2MRadius2), fmp2_m_radius2);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::Inverse2Fmp2MRadius2),
+ GLfloat(1.0 / (2.0*fmp2_m_radius2)));
+
+ QVector2D halfViewportSize(width*0.5, height*0.5);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::HalfViewportSize), halfViewportSize);
+ }
+ else if (style == Qt::TexturePattern) {
+ const QPixmap& texPixmap = currentBrush.texture();
+
+ if (qHasPixmapTexture(currentBrush) && currentBrush.texture().isQBitmap()) {
+ QColor col = qt_premultiplyColor(currentBrush.color(), (GLfloat)q->state()->opacity);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::PatternColor), col);
+ }
+
+ QSizeF invertedTextureSize(1.0 / texPixmap.width(), 1.0 / texPixmap.height());
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::InvertedTextureSize), invertedTextureSize);
+
+ QVector2D halfViewportSize(width*0.5, height*0.5);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::HalfViewportSize), halfViewportSize);
+ }
+ else
+ qWarning("QGL2PaintEngineEx: Unimplemented fill style");
+
+ const QPointF &brushOrigin = q->state()->brushOrigin;
+ QTransform matrix = q->state()->matrix;
+ matrix.translate(brushOrigin.x(), brushOrigin.y());
+
+ QTransform translate(1, 0, 0, 1, -translationPoint.x(), -translationPoint.y());
+ qreal m22 = -1;
+ qreal dy = height;
+ if (device->isFlipped()) {
+ m22 = 1;
+ dy = 0;
+ }
+ QTransform gl_to_qt(1, 0, 0, m22, 0, dy);
+ QTransform inv_matrix;
+ if (style == Qt::TexturePattern && textureInvertedY == -1)
+ inv_matrix = gl_to_qt * (QTransform(1, 0, 0, -1, 0, currentBrush.texture().height()) * brushQTransform * matrix).inverted() * translate;
+ else
+ inv_matrix = gl_to_qt * (brushQTransform * matrix).inverted() * translate;
+
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::BrushTransform), inv_matrix);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::BrushTexture), QT_BRUSH_TEXTURE_UNIT);
+ }
+ brushUniformsDirty = false;
+}
+
+
+// This assumes the shader manager has already setup the correct shader program
+void QGL2PaintEngineExPrivate::updateMatrix()
+{
+// qDebug("QGL2PaintEngineExPrivate::updateMatrix()");
+
+ const QTransform& transform = q->state()->matrix;
+
+ // The projection matrix converts from Qt's coordinate system to GL's coordinate system
+ // * GL's viewport is 2x2, Qt's is width x height
+ // * GL has +y -> -y going from bottom -> top, Qt is the other way round
+ // * GL has [0,0] in the center, Qt has it in the top-left
+ //
+ // This results in the Projection matrix below, which is multiplied by the painter's
+ // transformation matrix, as shown below:
+ //
+ // Projection Matrix Painter Transform
+ // ------------------------------------------------ ------------------------
+ // | 2.0 / width | 0.0 | -1.0 | | m11 | m21 | dx |
+ // | 0.0 | -2.0 / height | 1.0 | * | m12 | m22 | dy |
+ // | 0.0 | 0.0 | 1.0 | | m13 | m23 | m33 |
+ // ------------------------------------------------ ------------------------
+ //
+ // NOTE: The resultant matrix is also transposed, as GL expects column-major matracies
+
+ const GLfloat wfactor = 2.0f / width;
+ GLfloat hfactor = -2.0f / height;
+
+ GLfloat dx = transform.dx();
+ GLfloat dy = transform.dy();
+
+ if (device->isFlipped()) {
+ hfactor *= -1;
+ dy -= height;
+ }
+
+ // Non-integer translates can have strange effects for some rendering operations such as
+ // anti-aliased text rendering. In such cases, we snap the translate to the pixel grid.
+ if (snapToPixelGrid && transform.type() == QTransform::TxTranslate) {
+ // 0.50 needs to rounded down to 0.0 for consistency with raster engine:
+ dx = ceilf(dx - 0.5f);
+ dy = ceilf(dy - 0.5f);
+ }
+ pmvMatrix[0][0] = (wfactor * transform.m11()) - transform.m13();
+ pmvMatrix[1][0] = (wfactor * transform.m21()) - transform.m23();
+ pmvMatrix[2][0] = (wfactor * dx) - transform.m33();
+ pmvMatrix[0][1] = (hfactor * transform.m12()) + transform.m13();
+ pmvMatrix[1][1] = (hfactor * transform.m22()) + transform.m23();
+ pmvMatrix[2][1] = (hfactor * dy) + transform.m33();
+ pmvMatrix[0][2] = transform.m13();
+ pmvMatrix[1][2] = transform.m23();
+ pmvMatrix[2][2] = transform.m33();
+
+ // 1/10000 == 0.0001, so we have good enough res to cover curves
+ // that span the entire widget...
+ inverseScale = qMax(1 / qMax( qMax(qAbs(transform.m11()), qAbs(transform.m22())),
+ qMax(qAbs(transform.m12()), qAbs(transform.m21())) ),
+ qreal(0.0001));
+
+ matrixDirty = false;
+ matrixUniformDirty = true;
+
+ // Set the PMV matrix attribute. As we use an attributes rather than uniforms, we only
+ // need to do this once for every matrix change and persists across all shader programs.
+ glVertexAttrib3fv(QT_PMV_MATRIX_1_ATTR, pmvMatrix[0]);
+ glVertexAttrib3fv(QT_PMV_MATRIX_2_ATTR, pmvMatrix[1]);
+ glVertexAttrib3fv(QT_PMV_MATRIX_3_ATTR, pmvMatrix[2]);
+
+ dasher.setInvScale(inverseScale);
+ stroker.setInvScale(inverseScale);
+}
+
+
+void QGL2PaintEngineExPrivate::updateCompositionMode()
+{
+ // NOTE: The entire paint engine works on pre-multiplied data - which is why some of these
+ // composition modes look odd.
+// qDebug() << "QGL2PaintEngineExPrivate::updateCompositionMode() - Setting GL composition mode for " << q->state()->composition_mode;
+ switch(q->state()->composition_mode) {
+ case QPainter::CompositionMode_SourceOver:
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ break;
+ case QPainter::CompositionMode_DestinationOver:
+ glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
+ break;
+ case QPainter::CompositionMode_Clear:
+ glBlendFunc(GL_ZERO, GL_ZERO);
+ break;
+ case QPainter::CompositionMode_Source:
+ glBlendFunc(GL_ONE, GL_ZERO);
+ break;
+ case QPainter::CompositionMode_Destination:
+ glBlendFunc(GL_ZERO, GL_ONE);
+ break;
+ case QPainter::CompositionMode_SourceIn:
+ glBlendFunc(GL_DST_ALPHA, GL_ZERO);
+ break;
+ case QPainter::CompositionMode_DestinationIn:
+ glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
+ break;
+ case QPainter::CompositionMode_SourceOut:
+ glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ZERO);
+ break;
+ case QPainter::CompositionMode_DestinationOut:
+ glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
+ break;
+ case QPainter::CompositionMode_SourceAtop:
+ glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ break;
+ case QPainter::CompositionMode_DestinationAtop:
+ glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA);
+ break;
+ case QPainter::CompositionMode_Xor:
+ glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ break;
+ case QPainter::CompositionMode_Plus:
+ glBlendFunc(GL_ONE, GL_ONE);
+ break;
+ default:
+ qWarning("Unsupported composition mode");
+ break;
+ }
+
+ compositionModeDirty = false;
+}
+
+static inline void setCoords(GLfloat *coords, const QGLRect &rect)
+{
+ coords[0] = rect.left;
+ coords[1] = rect.top;
+ coords[2] = rect.right;
+ coords[3] = rect.top;
+ coords[4] = rect.right;
+ coords[5] = rect.bottom;
+ coords[6] = rect.left;
+ coords[7] = rect.bottom;
+}
+
+void QGL2PaintEngineExPrivate::drawTexture(const QGLRect& dest, const QGLRect& src, const QSize &textureSize, bool opaque, bool pattern)
+{
+ // Setup for texture drawing
+ currentBrush = noBrush;
+ shaderManager->setSrcPixelType(pattern ? QGLEngineShaderManager::PatternSrc : QGLEngineShaderManager::ImageSrc);
+
+ if (snapToPixelGrid) {
+ snapToPixelGrid = false;
+ matrixDirty = true;
+ }
+
+ if (prepareForDraw(opaque))
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
+
+ if (pattern) {
+ QColor col = qt_premultiplyColor(q->state()->pen.color(), (GLfloat)q->state()->opacity);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::PatternColor), col);
+ }
+
+ GLfloat dx = 1.0 / textureSize.width();
+ GLfloat dy = 1.0 / textureSize.height();
+
+ QGLRect srcTextureRect(src.left*dx, src.top*dy, src.right*dx, src.bottom*dy);
+
+ setCoords(staticVertexCoordinateArray, dest);
+ setCoords(staticTextureCoordinateArray, srcTextureRect);
+
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+}
+
+void QGL2PaintEngineEx::beginNativePainting()
+{
+ Q_D(QGL2PaintEngineEx);
+ ensureActive();
+ d->transferMode(BrushDrawingMode);
+
+ d->nativePaintingActive = true;
+
+ QGLContext *ctx = d->ctx;
+ glUseProgram(0);
+
+ // Disable all the vertex attribute arrays:
+ for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i)
+ glDisableVertexAttribArray(i);
+
+#ifndef QT_OPENGL_ES_2
+ const QGLFormat &fmt = d->device->format();
+ if (fmt.majorVersion() < 3 || (fmt.majorVersion() == 3 && fmt.minorVersion() < 1)
+ || fmt.profile() == QGLFormat::CompatibilityProfile)
+ {
+ // be nice to people who mix OpenGL 1.x code with QPainter commands
+ // by setting modelview and projection matrices to mirror the GL 1
+ // paint engine
+ const QTransform& mtx = state()->matrix;
+
+ float mv_matrix[4][4] =
+ {
+ { float(mtx.m11()), float(mtx.m12()), 0, float(mtx.m13()) },
+ { float(mtx.m21()), float(mtx.m22()), 0, float(mtx.m23()) },
+ { 0, 0, 1, 0 },
+ { float(mtx.dx()), float(mtx.dy()), 0, float(mtx.m33()) }
+ };
+
+ const QSize sz = d->device->size();
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, sz.width(), sz.height(), 0, -999999, 999999);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadMatrixf(&mv_matrix[0][0]);
+ }
+#else
+ Q_UNUSED(ctx);
+#endif
+
+ d->lastTextureUsed = GLuint(-1);
+ d->dirtyStencilRegion = QRect(0, 0, d->width, d->height);
+ d->resetGLState();
+
+ d->shaderManager->setDirty();
+
+ d->needsSync = true;
+}
+
+void QGL2PaintEngineExPrivate::resetGLState()
+{
+ glDisable(GL_BLEND);
+ glActiveTexture(GL_TEXTURE0);
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ glDepthMask(true);
+ glDepthFunc(GL_LESS);
+ glClearDepth(1);
+ glStencilMask(0xff);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ glStencilFunc(GL_ALWAYS, 0, 0xff);
+ ctx->d_func()->setVertexAttribArrayEnabled(QT_TEXTURE_COORDS_ATTR, false);
+ ctx->d_func()->setVertexAttribArrayEnabled(QT_VERTEX_COORDS_ATTR, false);
+ ctx->d_func()->setVertexAttribArrayEnabled(QT_OPACITY_ATTR, false);
+#ifndef QT_OPENGL_ES_2
+ // gl_Color, corresponding to vertex attribute 3, may have been changed
+ float color[] = { 1.0f, 1.0f, 1.0f, 1.0f };
+ glVertexAttrib4fv(3, color);
+#endif
+}
+
+void QGL2PaintEngineEx::endNativePainting()
+{
+ Q_D(QGL2PaintEngineEx);
+ d->needsSync = true;
+ d->nativePaintingActive = false;
+}
+
+void QGL2PaintEngineEx::invalidateState()
+{
+ Q_D(QGL2PaintEngineEx);
+ d->needsSync = true;
+}
+
+bool QGL2PaintEngineEx::isNativePaintingActive() const {
+ Q_D(const QGL2PaintEngineEx);
+ return d->nativePaintingActive;
+}
+
+void QGL2PaintEngineExPrivate::transferMode(EngineMode newMode)
+{
+ if (newMode == mode)
+ return;
+
+ if (mode == TextDrawingMode || mode == ImageDrawingMode || mode == ImageArrayDrawingMode) {
+ lastTextureUsed = GLuint(-1);
+ }
+
+ if (newMode == TextDrawingMode) {
+ shaderManager->setHasComplexGeometry(true);
+ } else {
+ shaderManager->setHasComplexGeometry(false);
+ }
+
+ if (newMode == ImageDrawingMode) {
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray);
+ setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray);
+ }
+
+ if (newMode == ImageArrayDrawingMode) {
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinateArray.data());
+ setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinateArray.data());
+ setVertexAttributePointer(QT_OPACITY_ATTR, (GLfloat*)opacityArray.data());
+ }
+
+ // This needs to change when we implement high-quality anti-aliasing...
+ if (newMode != TextDrawingMode)
+ shaderManager->setMaskType(QGLEngineShaderManager::NoMask);
+
+ mode = newMode;
+}
+
+struct QGL2PEVectorPathCache
+{
+#ifdef QT_OPENGL_CACHE_AS_VBOS
+ GLuint vbo;
+ GLuint ibo;
+#else
+ float *vertices;
+ void *indices;
+#endif
+ int vertexCount;
+ int indexCount;
+ GLenum primitiveType;
+ qreal iscale;
+};
+
+void QGL2PaintEngineExPrivate::cleanupVectorPath(QPaintEngineEx *engine, void *data)
+{
+ QGL2PEVectorPathCache *c = (QGL2PEVectorPathCache *) data;
+#ifdef QT_OPENGL_CACHE_AS_VBOS
+ Q_ASSERT(engine->type() == QPaintEngine::OpenGL2);
+ static_cast<QGL2PaintEngineEx *>(engine)->d_func()->unusedVBOSToClean << c->vbo;
+ if (c->ibo)
+ d->unusedIBOSToClean << c->ibo;
+#else
+ Q_UNUSED(engine);
+ qFree(c->vertices);
+ qFree(c->indices);
+#endif
+ delete c;
+}
+
+// Assumes everything is configured for the brush you want to use
+void QGL2PaintEngineExPrivate::fill(const QVectorPath& path)
+{
+ transferMode(BrushDrawingMode);
+
+ if (snapToPixelGrid) {
+ snapToPixelGrid = false;
+ matrixDirty = true;
+ }
+
+ // Might need to call updateMatrix to re-calculate inverseScale
+ if (matrixDirty)
+ updateMatrix();
+
+ const QPointF* const points = reinterpret_cast<const QPointF*>(path.points());
+
+ // Check to see if there's any hints
+ if (path.shape() == QVectorPath::RectangleHint) {
+ QGLRect rect(points[0].x(), points[0].y(), points[2].x(), points[2].y());
+ prepareForDraw(currentBrush.isOpaque());
+ composite(rect);
+ } else if (path.isConvex()) {
+
+ if (path.isCacheable()) {
+ QVectorPath::CacheEntry *data = path.lookupCacheData(q);
+ QGL2PEVectorPathCache *cache;
+
+ bool updateCache = false;
+
+ if (data) {
+ cache = (QGL2PEVectorPathCache *) data->data;
+ // Check if scale factor is exceeded for curved paths and generate curves if so...
+ if (path.isCurved()) {
+ qreal scaleFactor = cache->iscale / inverseScale;
+ if (scaleFactor < 0.5 || scaleFactor > 2.0) {
+#ifdef QT_OPENGL_CACHE_AS_VBOS
+ glDeleteBuffers(1, &cache->vbo);
+ cache->vbo = 0;
+ Q_ASSERT(cache->ibo == 0);
+#else
+ qFree(cache->vertices);
+ Q_ASSERT(cache->indices == 0);
+#endif
+ updateCache = true;
+ }
+ }
+ } else {
+ cache = new QGL2PEVectorPathCache;
+ data = const_cast<QVectorPath &>(path).addCacheData(q, cache, cleanupVectorPath);
+ updateCache = true;
+ }
+
+ // Flatten the path at the current scale factor and fill it into the cache struct.
+ if (updateCache) {
+ vertexCoordinateArray.clear();
+ vertexCoordinateArray.addPath(path, inverseScale, false);
+ int vertexCount = vertexCoordinateArray.vertexCount();
+ int floatSizeInBytes = vertexCount * 2 * sizeof(float);
+ cache->vertexCount = vertexCount;
+ cache->indexCount = 0;
+ cache->primitiveType = GL_TRIANGLE_FAN;
+ cache->iscale = inverseScale;
+#ifdef QT_OPENGL_CACHE_AS_VBOS
+ glGenBuffers(1, &cache->vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
+ glBufferData(GL_ARRAY_BUFFER, floatSizeInBytes, vertexCoordinateArray.data(), GL_STATIC_DRAW);
+ cache->ibo = 0;
+#else
+ cache->vertices = (float *) qMalloc(floatSizeInBytes);
+ memcpy(cache->vertices, vertexCoordinateArray.data(), floatSizeInBytes);
+ cache->indices = 0;
+#endif
+ }
+
+ prepareForDraw(currentBrush.isOpaque());
+#ifdef QT_OPENGL_CACHE_AS_VBOS
+ glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, 0);
+#else
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, cache->vertices);
+#endif
+ glDrawArrays(cache->primitiveType, 0, cache->vertexCount);
+
+ } else {
+ // printf(" - Marking path as cachable...\n");
+ // Tag it for later so that if the same path is drawn twice, it is assumed to be static and thus cachable
+ path.makeCacheable();
+ vertexCoordinateArray.clear();
+ vertexCoordinateArray.addPath(path, inverseScale, false);
+ prepareForDraw(currentBrush.isOpaque());
+ drawVertexArrays(vertexCoordinateArray, GL_TRIANGLE_FAN);
+ }
+
+ } else {
+ bool useCache = path.isCacheable();
+ if (useCache) {
+ QRectF bbox = path.controlPointRect();
+ // If the path doesn't fit within these limits, it is possible that the triangulation will fail.
+ useCache &= (bbox.left() > -0x8000 * inverseScale)
+ && (bbox.right() < 0x8000 * inverseScale)
+ && (bbox.top() > -0x8000 * inverseScale)
+ && (bbox.bottom() < 0x8000 * inverseScale);
+ }
+
+ if (useCache) {
+ QVectorPath::CacheEntry *data = path.lookupCacheData(q);
+ QGL2PEVectorPathCache *cache;
+
+ bool updateCache = false;
+
+ if (data) {
+ cache = (QGL2PEVectorPathCache *) data->data;
+ // Check if scale factor is exceeded for curved paths and generate curves if so...
+ if (path.isCurved()) {
+ qreal scaleFactor = cache->iscale / inverseScale;
+ if (scaleFactor < 0.5 || scaleFactor > 2.0) {
+#ifdef QT_OPENGL_CACHE_AS_VBOS
+ glDeleteBuffers(1, &cache->vbo);
+ glDeleteBuffers(1, &cache->ibo);
+#else
+ qFree(cache->vertices);
+ qFree(cache->indices);
+#endif
+ updateCache = true;
+ }
+ }
+ } else {
+ cache = new QGL2PEVectorPathCache;
+ data = const_cast<QVectorPath &>(path).addCacheData(q, cache, cleanupVectorPath);
+ updateCache = true;
+ }
+
+ // Flatten the path at the current scale factor and fill it into the cache struct.
+ if (updateCache) {
+ QTriangleSet polys = qTriangulate(path, QTransform().scale(1 / inverseScale, 1 / inverseScale));
+ cache->vertexCount = polys.vertices.size() / 2;
+ cache->indexCount = polys.indices.size();
+ cache->primitiveType = GL_TRIANGLES;
+ cache->iscale = inverseScale;
+#ifdef QT_OPENGL_CACHE_AS_VBOS
+ glGenBuffers(1, &cache->vbo);
+ glGenBuffers(1, &cache->ibo);
+ glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache->ibo);
+
+ if (QGLExtensions::glExtensions() & QGLExtensions::ElementIndexUint)
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quint32) * polys.indices.size(), polys.indices.data(), GL_STATIC_DRAW);
+ else
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quint16) * polys.indices.size(), polys.indices.data(), GL_STATIC_DRAW);
+
+ QVarLengthArray<float> vertices(polys.vertices.size());
+ for (int i = 0; i < polys.vertices.size(); ++i)
+ vertices[i] = float(inverseScale * polys.vertices.at(i));
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
+#else
+ cache->vertices = (float *) qMalloc(sizeof(float) * polys.vertices.size());
+ if (QGLExtensions::glExtensions() & QGLExtensions::ElementIndexUint) {
+ cache->indices = (quint32 *) qMalloc(sizeof(quint32) * polys.indices.size());
+ memcpy(cache->indices, polys.indices.data(), sizeof(quint32) * polys.indices.size());
+ } else {
+ cache->indices = (quint16 *) qMalloc(sizeof(quint16) * polys.indices.size());
+ memcpy(cache->indices, polys.indices.data(), sizeof(quint16) * polys.indices.size());
+ }
+ for (int i = 0; i < polys.vertices.size(); ++i)
+ cache->vertices[i] = float(inverseScale * polys.vertices.at(i));
+#endif
+ }
+
+ prepareForDraw(currentBrush.isOpaque());
+#ifdef QT_OPENGL_CACHE_AS_VBOS
+ glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache->ibo);
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, 0);
+ if (QGLExtensions::glExtensions() & QGLExtensions::ElementIndexUint)
+ glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_INT, 0);
+ else
+ glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_SHORT, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+#else
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, cache->vertices);
+ if (QGLExtensions::glExtensions() & QGLExtensions::ElementIndexUint)
+ glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_INT, (qint32 *)cache->indices);
+ else
+ glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_SHORT, (qint16 *)cache->indices);
+#endif
+
+ } else {
+ // printf(" - Marking path as cachable...\n");
+ // Tag it for later so that if the same path is drawn twice, it is assumed to be static and thus cachable
+ path.makeCacheable();
+
+ if (!device->format().stencil()) {
+ // If there is no stencil buffer, triangulate the path instead.
+
+ QRectF bbox = path.controlPointRect();
+ // If the path doesn't fit within these limits, it is possible that the triangulation will fail.
+ bool withinLimits = (bbox.left() > -0x8000 * inverseScale)
+ && (bbox.right() < 0x8000 * inverseScale)
+ && (bbox.top() > -0x8000 * inverseScale)
+ && (bbox.bottom() < 0x8000 * inverseScale);
+ if (withinLimits) {
+ QTriangleSet polys = qTriangulate(path, QTransform().scale(1 / inverseScale, 1 / inverseScale));
+
+ QVarLengthArray<float> vertices(polys.vertices.size());
+ for (int i = 0; i < polys.vertices.size(); ++i)
+ vertices[i] = float(inverseScale * polys.vertices.at(i));
+
+ prepareForDraw(currentBrush.isOpaque());
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, vertices.constData());
+ if (QGLExtensions::glExtensions() & QGLExtensions::ElementIndexUint)
+ glDrawElements(GL_TRIANGLES, polys.indices.size(), GL_UNSIGNED_INT, polys.indices.data());
+ else
+ glDrawElements(GL_TRIANGLES, polys.indices.size(), GL_UNSIGNED_SHORT, polys.indices.data());
+ } else {
+ // We can't handle big, concave painter paths with OpenGL without stencil buffer.
+ qWarning("Painter path exceeds +/-32767 pixels.");
+ }
+ return;
+ }
+
+ // The path is too complicated & needs the stencil technique
+ vertexCoordinateArray.clear();
+ vertexCoordinateArray.addPath(path, inverseScale, false);
+
+ fillStencilWithVertexArray(vertexCoordinateArray, path.hasWindingFill());
+
+ glStencilMask(0xff);
+ glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+
+ if (q->state()->clipTestEnabled) {
+ // Pass when high bit is set, replace stencil value with current clip
+ glStencilFunc(GL_NOTEQUAL, q->state()->currentClip, GL_STENCIL_HIGH_BIT);
+ } else if (path.hasWindingFill()) {
+ // Pass when any bit is set, replace stencil value with 0
+ glStencilFunc(GL_NOTEQUAL, 0, 0xff);
+ } else {
+ // Pass when high bit is set, replace stencil value with 0
+ glStencilFunc(GL_NOTEQUAL, 0, GL_STENCIL_HIGH_BIT);
+ }
+ prepareForDraw(currentBrush.isOpaque());
+
+ // Stencil the brush onto the dest buffer
+ composite(vertexCoordinateArray.boundingRect());
+ glStencilMask(0);
+ updateClipScissorTest();
+ }
+ }
+}
+
+
+void QGL2PaintEngineExPrivate::fillStencilWithVertexArray(const float *data,
+ int count,
+ int *stops,
+ int stopCount,
+ const QGLRect &bounds,
+ StencilFillMode mode)
+{
+ Q_ASSERT(count || stops);
+
+// qDebug("QGL2PaintEngineExPrivate::fillStencilWithVertexArray()");
+ glStencilMask(0xff); // Enable stencil writes
+
+ if (dirtyStencilRegion.intersects(currentScissorBounds)) {
+ QVector<QRect> clearRegion = dirtyStencilRegion.intersected(currentScissorBounds).rects();
+ glClearStencil(0); // Clear to zero
+ for (int i = 0; i < clearRegion.size(); ++i) {
+#ifndef QT_GL_NO_SCISSOR_TEST
+ setScissor(clearRegion.at(i));
+#endif
+ glClear(GL_STENCIL_BUFFER_BIT);
+ }
+
+ dirtyStencilRegion -= currentScissorBounds;
+
+#ifndef QT_GL_NO_SCISSOR_TEST
+ updateClipScissorTest();
+#endif
+ }
+
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // Disable color writes
+ useSimpleShader();
+ glEnable(GL_STENCIL_TEST); // For some reason, this has to happen _after_ the simple shader is use()'d
+
+ if (mode == WindingFillMode) {
+ Q_ASSERT(stops && !count);
+ if (q->state()->clipTestEnabled) {
+ // Flatten clip values higher than current clip, and set high bit to match current clip
+ glStencilFunc(GL_LEQUAL, GL_STENCIL_HIGH_BIT | q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
+ glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+ composite(bounds);
+
+ glStencilFunc(GL_EQUAL, GL_STENCIL_HIGH_BIT, GL_STENCIL_HIGH_BIT);
+ } else if (!stencilClean) {
+ // Clear stencil buffer within bounding rect
+ glStencilFunc(GL_ALWAYS, 0, 0xff);
+ glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
+ composite(bounds);
+ }
+
+ // Inc. for front-facing triangle
+ glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_INCR_WRAP, GL_INCR_WRAP);
+ // Dec. for back-facing "holes"
+ glStencilOpSeparate(GL_BACK, GL_KEEP, GL_DECR_WRAP, GL_DECR_WRAP);
+ glStencilMask(~GL_STENCIL_HIGH_BIT);
+ drawVertexArrays(data, stops, stopCount, GL_TRIANGLE_FAN);
+
+ if (q->state()->clipTestEnabled) {
+ // Clear high bit of stencil outside of path
+ glStencilFunc(GL_EQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
+ glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+ glStencilMask(GL_STENCIL_HIGH_BIT);
+ composite(bounds);
+ }
+ } else if (mode == OddEvenFillMode) {
+ glStencilMask(GL_STENCIL_HIGH_BIT);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); // Simply invert the stencil bit
+ drawVertexArrays(data, stops, stopCount, GL_TRIANGLE_FAN);
+
+ } else { // TriStripStrokeFillMode
+ Q_ASSERT(count && !stops); // tristrips generated directly, so no vertexArray or stops
+ glStencilMask(GL_STENCIL_HIGH_BIT);
+#if 0
+ glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); // Simply invert the stencil bit
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, data);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
+#else
+
+ glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+ if (q->state()->clipTestEnabled) {
+ glStencilFunc(GL_LEQUAL, q->state()->currentClip | GL_STENCIL_HIGH_BIT,
+ ~GL_STENCIL_HIGH_BIT);
+ } else {
+ glStencilFunc(GL_ALWAYS, GL_STENCIL_HIGH_BIT, 0xff);
+ }
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, data);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
+#endif
+ }
+
+ // Enable color writes & disable stencil writes
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+}
+
+/*
+ If the maximum value in the stencil buffer is GL_STENCIL_HIGH_BIT - 1,
+ restore the stencil buffer to a pristine state. The current clip region
+ is set to 1, and the rest to 0.
+*/
+void QGL2PaintEngineExPrivate::resetClipIfNeeded()
+{
+ if (maxClip != (GL_STENCIL_HIGH_BIT - 1))
+ return;
+
+ Q_Q(QGL2PaintEngineEx);
+
+ useSimpleShader();
+ glEnable(GL_STENCIL_TEST);
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+
+ QRectF bounds = q->state()->matrix.inverted().mapRect(QRectF(0, 0, width, height));
+ QGLRect rect(bounds.left(), bounds.top(), bounds.right(), bounds.bottom());
+
+ // Set high bit on clip region
+ glStencilFunc(GL_LEQUAL, q->state()->currentClip, 0xff);
+ glStencilOp(GL_KEEP, GL_INVERT, GL_INVERT);
+ glStencilMask(GL_STENCIL_HIGH_BIT);
+ composite(rect);
+
+ // Reset clipping to 1 and everything else to zero
+ glStencilFunc(GL_NOTEQUAL, 0x01, GL_STENCIL_HIGH_BIT);
+ glStencilOp(GL_ZERO, GL_REPLACE, GL_REPLACE);
+ glStencilMask(0xff);
+ composite(rect);
+
+ q->state()->currentClip = 1;
+ q->state()->canRestoreClip = false;
+
+ maxClip = 1;
+
+ glStencilMask(0x0);
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+}
+
+bool QGL2PaintEngineExPrivate::prepareForDraw(bool srcPixelsAreOpaque)
+{
+ if (brushTextureDirty && mode != ImageDrawingMode && mode != ImageArrayDrawingMode)
+ updateBrushTexture();
+
+ if (compositionModeDirty)
+ updateCompositionMode();
+
+ if (matrixDirty)
+ updateMatrix();
+
+ const bool stateHasOpacity = q->state()->opacity < 0.99f;
+ if (q->state()->composition_mode == QPainter::CompositionMode_Source
+ || (q->state()->composition_mode == QPainter::CompositionMode_SourceOver
+ && srcPixelsAreOpaque && !stateHasOpacity))
+ {
+ glDisable(GL_BLEND);
+ } else {
+ glEnable(GL_BLEND);
+ }
+
+ QGLEngineShaderManager::OpacityMode opacityMode;
+ if (mode == ImageArrayDrawingMode) {
+ opacityMode = QGLEngineShaderManager::AttributeOpacity;
+ } else {
+ opacityMode = stateHasOpacity ? QGLEngineShaderManager::UniformOpacity
+ : QGLEngineShaderManager::NoOpacity;
+ if (stateHasOpacity && (mode != ImageDrawingMode)) {
+ // Using a brush
+ bool brushIsPattern = (currentBrush.style() >= Qt::Dense1Pattern) &&
+ (currentBrush.style() <= Qt::DiagCrossPattern);
+
+ if ((currentBrush.style() == Qt::SolidPattern) || brushIsPattern)
+ opacityMode = QGLEngineShaderManager::NoOpacity; // Global opacity handled by srcPixel shader
+ }
+ }
+ shaderManager->setOpacityMode(opacityMode);
+
+ bool changed = shaderManager->useCorrectShaderProg();
+ // If the shader program needs changing, we change it and mark all uniforms as dirty
+ if (changed) {
+ // The shader program has changed so mark all uniforms as dirty:
+ brushUniformsDirty = true;
+ opacityUniformDirty = true;
+ matrixUniformDirty = true;
+ }
+
+ if (brushUniformsDirty && mode != ImageDrawingMode && mode != ImageArrayDrawingMode)
+ updateBrushUniforms();
+
+ if (opacityMode == QGLEngineShaderManager::UniformOpacity && opacityUniformDirty) {
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::GlobalOpacity), (GLfloat)q->state()->opacity);
+ opacityUniformDirty = false;
+ }
+
+ if (matrixUniformDirty && shaderManager->hasComplexGeometry()) {
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::Matrix),
+ pmvMatrix);
+ matrixUniformDirty = false;
+ }
+
+ return changed;
+}
+
+void QGL2PaintEngineExPrivate::composite(const QGLRect& boundingRect)
+{
+ setCoords(staticVertexCoordinateArray, boundingRect);
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+}
+
+// Draws the vertex array as a set of <vertexArrayStops.size()> triangle fans.
+void QGL2PaintEngineExPrivate::drawVertexArrays(const float *data, int *stops, int stopCount,
+ GLenum primitive)
+{
+ // Now setup the pointer to the vertex array:
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, (GLfloat*)data);
+
+ int previousStop = 0;
+ for (int i=0; i<stopCount; ++i) {
+ int stop = stops[i];
+/*
+ qDebug("Drawing triangle fan for vertecies %d -> %d:", previousStop, stop-1);
+ for (int i=previousStop; i<stop; ++i)
+ qDebug(" %02d: [%.2f, %.2f]", i, vertexArray.data()[i].x, vertexArray.data()[i].y);
+*/
+ glDrawArrays(primitive, previousStop, stop - previousStop);
+ previousStop = stop;
+ }
+}
+
+/////////////////////////////////// Public Methods //////////////////////////////////////////
+
+QGL2PaintEngineEx::QGL2PaintEngineEx()
+ : QPaintEngineEx(*(new QGL2PaintEngineExPrivate(this)))
+{
+}
+
+QGL2PaintEngineEx::~QGL2PaintEngineEx()
+{
+}
+
+void QGL2PaintEngineEx::fill(const QVectorPath &path, const QBrush &brush)
+{
+ Q_D(QGL2PaintEngineEx);
+
+ if (qbrush_style(brush) == Qt::NoBrush)
+ return;
+ ensureActive();
+ d->setBrush(brush);
+ d->fill(path);
+}
+
+Q_GUI_EXPORT bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp
+
+
+void QGL2PaintEngineEx::stroke(const QVectorPath &path, const QPen &pen)
+{
+ Q_D(QGL2PaintEngineEx);
+
+ const QBrush &penBrush = qpen_brush(pen);
+ if (qpen_style(pen) == Qt::NoPen || qbrush_style(penBrush) == Qt::NoBrush)
+ return;
+
+ QOpenGL2PaintEngineState *s = state();
+ if (pen.isCosmetic() && !qt_scaleForTransform(s->transform(), 0)) {
+ // QTriangulatingStroker class is not meant to support cosmetically sheared strokes.
+ QPaintEngineEx::stroke(path, pen);
+ return;
+ }
+
+ ensureActive();
+ d->setBrush(penBrush);
+ d->stroke(path, pen);
+}
+
+void QGL2PaintEngineExPrivate::stroke(const QVectorPath &path, const QPen &pen)
+{
+ const QOpenGL2PaintEngineState *s = q->state();
+ if (snapToPixelGrid) {
+ snapToPixelGrid = false;
+ matrixDirty = true;
+ }
+
+ const Qt::PenStyle penStyle = qpen_style(pen);
+ const QBrush &penBrush = qpen_brush(pen);
+ const bool opaque = penBrush.isOpaque() && s->opacity > 0.99;
+
+ transferMode(BrushDrawingMode);
+
+ // updateMatrix() is responsible for setting the inverse scale on
+ // the strokers, so we need to call it here and not wait for
+ // prepareForDraw() down below.
+ updateMatrix();
+
+ QRectF clip = q->state()->matrix.inverted().mapRect(q->state()->clipEnabled
+ ? q->state()->rectangleClip
+ : QRectF(0, 0, width, height));
+
+ if (penStyle == Qt::SolidLine) {
+ stroker.process(path, pen, clip);
+
+ } else { // Some sort of dash
+ dasher.process(path, pen, clip);
+
+ QVectorPath dashStroke(dasher.points(),
+ dasher.elementCount(),
+ dasher.elementTypes());
+ stroker.process(dashStroke, pen, clip);
+ }
+
+ if (!stroker.vertexCount())
+ return;
+
+ if (opaque) {
+ prepareForDraw(opaque);
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, stroker.vertices());
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, stroker.vertexCount() / 2);
+
+// QBrush b(Qt::green);
+// d->setBrush(&b);
+// d->prepareForDraw(true);
+// glDrawArrays(GL_LINE_STRIP, 0, d->stroker.vertexCount() / 2);
+
+ } else {
+ qreal width = qpen_widthf(pen) / 2;
+ if (width == 0)
+ width = 0.5;
+ qreal extra = pen.joinStyle() == Qt::MiterJoin
+ ? qMax(pen.miterLimit() * width, width)
+ : width;
+
+ if (pen.isCosmetic())
+ extra = extra * inverseScale;
+
+ QRectF bounds = path.controlPointRect().adjusted(-extra, -extra, extra, extra);
+
+ fillStencilWithVertexArray(stroker.vertices(), stroker.vertexCount() / 2,
+ 0, 0, bounds, QGL2PaintEngineExPrivate::TriStripStrokeFillMode);
+
+ glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+
+ // Pass when any bit is set, replace stencil value with 0
+ glStencilFunc(GL_NOTEQUAL, 0, GL_STENCIL_HIGH_BIT);
+ prepareForDraw(false);
+
+ // Stencil the brush onto the dest buffer
+ composite(bounds);
+
+ glStencilMask(0);
+
+ updateClipScissorTest();
+ }
+}
+
+void QGL2PaintEngineEx::penChanged() { }
+void QGL2PaintEngineEx::brushChanged() { }
+void QGL2PaintEngineEx::brushOriginChanged() { }
+
+void QGL2PaintEngineEx::opacityChanged()
+{
+// qDebug("QGL2PaintEngineEx::opacityChanged()");
+ Q_D(QGL2PaintEngineEx);
+ state()->opacityChanged = true;
+
+ Q_ASSERT(d->shaderManager);
+ d->brushUniformsDirty = true;
+ d->opacityUniformDirty = true;
+}
+
+void QGL2PaintEngineEx::compositionModeChanged()
+{
+// qDebug("QGL2PaintEngineEx::compositionModeChanged()");
+ Q_D(QGL2PaintEngineEx);
+ state()->compositionModeChanged = true;
+ d->compositionModeDirty = true;
+}
+
+void QGL2PaintEngineEx::renderHintsChanged()
+{
+ state()->renderHintsChanged = true;
+
+#if !defined(QT_OPENGL_ES_2)
+ if ((state()->renderHints & QPainter::Antialiasing)
+ || (state()->renderHints & QPainter::HighQualityAntialiasing))
+ glEnable(GL_MULTISAMPLE);
+ else
+ glDisable(GL_MULTISAMPLE);
+#endif
+
+ Q_D(QGL2PaintEngineEx);
+ d->lastTextureUsed = GLuint(-1);
+ d->brushTextureDirty = true;
+// qDebug("QGL2PaintEngineEx::renderHintsChanged() not implemented!");
+}
+
+void QGL2PaintEngineEx::transformChanged()
+{
+ Q_D(QGL2PaintEngineEx);
+ d->matrixDirty = true;
+ state()->matrixChanged = true;
+}
+
+
+static const QRectF scaleRect(const QRectF &r, qreal sx, qreal sy)
+{
+ return QRectF(r.x() * sx, r.y() * sy, r.width() * sx, r.height() * sy);
+}
+
+void QGL2PaintEngineEx::drawPixmap(const QRectF& dest, const QPixmap & pixmap, const QRectF & src)
+{
+ Q_D(QGL2PaintEngineEx);
+ QGLContext *ctx = d->ctx;
+
+ int max_texture_size = ctx->d_func()->maxTextureSize();
+ if (pixmap.width() > max_texture_size || pixmap.height() > max_texture_size) {
+ QPixmap scaled = pixmap.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
+
+ const qreal sx = scaled.width() / qreal(pixmap.width());
+ const qreal sy = scaled.height() / qreal(pixmap.height());
+
+ drawPixmap(dest, scaled, scaleRect(src, sx, sy));
+ return;
+ }
+
+ ensureActive();
+ d->transferMode(ImageDrawingMode);
+
+ QGLContext::BindOptions bindOptions = QGLContext::InternalBindOption|QGLContext::CanFlipNativePixmapBindOption;
+#ifdef QGL_USE_TEXTURE_POOL
+ bindOptions |= QGLContext::TemporarilyCachedBindOption;
+#endif
+
+ glActiveTexture(GL_TEXTURE0 + QT_IMAGE_TEXTURE_UNIT);
+ QGLTexture *texture =
+ ctx->d_func()->bindTexture(pixmap, GL_TEXTURE_2D, GL_RGBA, bindOptions);
+
+ GLfloat top = texture->options & QGLContext::InvertedYBindOption ? (pixmap.height() - src.top()) : src.top();
+ GLfloat bottom = texture->options & QGLContext::InvertedYBindOption ? (pixmap.height() - src.bottom()) : src.bottom();
+ QGLRect srcRect(src.left(), top, src.right(), bottom);
+
+ bool isBitmap = pixmap.isQBitmap();
+ bool isOpaque = !isBitmap && !pixmap.hasAlpha();
+
+ d->updateTextureFilter(GL_TEXTURE_2D, GL_CLAMP_TO_EDGE,
+ state()->renderHints & QPainter::SmoothPixmapTransform, texture->id);
+ d->drawTexture(dest, srcRect, pixmap.size(), isOpaque, isBitmap);
+
+ if (texture->options&QGLContext::TemporarilyCachedBindOption) {
+ // pixmap was temporarily cached as a QImage texture by pooling system
+ // and should be destroyed immediately
+ QGLTextureCache::instance()->remove(ctx, texture->id);
+ }
+}
+
+void QGL2PaintEngineEx::drawImage(const QRectF& dest, const QImage& image, const QRectF& src,
+ Qt::ImageConversionFlags)
+{
+ Q_D(QGL2PaintEngineEx);
+ QGLContext *ctx = d->ctx;
+
+ int max_texture_size = ctx->d_func()->maxTextureSize();
+ if (image.width() > max_texture_size || image.height() > max_texture_size) {
+ QImage scaled = image.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
+
+ const qreal sx = scaled.width() / qreal(image.width());
+ const qreal sy = scaled.height() / qreal(image.height());
+
+ drawImage(dest, scaled, scaleRect(src, sx, sy));
+ return;
+ }
+
+ ensureActive();
+ d->transferMode(ImageDrawingMode);
+
+ glActiveTexture(GL_TEXTURE0 + QT_IMAGE_TEXTURE_UNIT);
+
+ QGLContext::BindOptions bindOptions = QGLContext::InternalBindOption;
+#ifdef QGL_USE_TEXTURE_POOL
+ bindOptions |= QGLContext::TemporarilyCachedBindOption;
+#endif
+
+ QGLTexture *texture = ctx->d_func()->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, bindOptions);
+ GLuint id = texture->id;
+
+ d->updateTextureFilter(GL_TEXTURE_2D, GL_CLAMP_TO_EDGE,
+ state()->renderHints & QPainter::SmoothPixmapTransform, id);
+ d->drawTexture(dest, src, image.size(), !image.hasAlphaChannel());
+
+ if (texture->options&QGLContext::TemporarilyCachedBindOption) {
+ // image was temporarily cached by texture pooling system
+ // and should be destroyed immediately
+ QGLTextureCache::instance()->remove(ctx, texture->id);
+ }
+}
+
+void QGL2PaintEngineEx::drawStaticTextItem(QStaticTextItem *textItem)
+{
+ Q_D(QGL2PaintEngineEx);
+
+ ensureActive();
+
+ QFontEngineGlyphCache::Type glyphType = textItem->fontEngine()->glyphFormat >= 0
+ ? QFontEngineGlyphCache::Type(textItem->fontEngine()->glyphFormat)
+ : d->glyphCacheType;
+ if (glyphType == QFontEngineGlyphCache::Raster_RGBMask) {
+ if (d->device->alphaRequested() || state()->matrix.type() > QTransform::TxTranslate
+ || (state()->composition_mode != QPainter::CompositionMode_Source
+ && state()->composition_mode != QPainter::CompositionMode_SourceOver))
+ {
+ glyphType = QFontEngineGlyphCache::Raster_A8;
+ }
+ }
+
+ d->drawCachedGlyphs(glyphType, textItem);
+}
+
+bool QGL2PaintEngineEx::drawTexture(const QRectF &dest, GLuint textureId, const QSize &size, const QRectF &src)
+{
+ Q_D(QGL2PaintEngineEx);
+ if (!d->shaderManager)
+ return false;
+
+ ensureActive();
+ d->transferMode(ImageDrawingMode);
+
+#ifndef QT_OPENGL_ES_2
+ QGLContext *ctx = d->ctx;
+#endif
+ glActiveTexture(GL_TEXTURE0 + QT_IMAGE_TEXTURE_UNIT);
+ glBindTexture(GL_TEXTURE_2D, textureId);
+
+ QGLRect srcRect(src.left(), src.bottom(), src.right(), src.top());
+
+ d->updateTextureFilter(GL_TEXTURE_2D, GL_CLAMP_TO_EDGE,
+ state()->renderHints & QPainter::SmoothPixmapTransform, textureId);
+ d->drawTexture(dest, srcRect, size, false);
+ return true;
+}
+
+void QGL2PaintEngineEx::drawTextItem(const QPointF &p, const QTextItem &textItem)
+{
+ Q_D(QGL2PaintEngineEx);
+
+ ensureActive();
+ QOpenGL2PaintEngineState *s = state();
+
+ const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
+
+ QTransform::TransformationType txtype = s->matrix.type();
+
+ float det = s->matrix.determinant();
+ bool drawCached = txtype < QTransform::TxProject;
+
+ // don't try to cache huge fonts or vastly transformed fonts
+ const qreal pixelSize = ti.fontEngine->fontDef.pixelSize;
+ if (pixelSize * pixelSize * qAbs(det) >= QT_MAX_CACHED_GLYPH_SIZE * QT_MAX_CACHED_GLYPH_SIZE ||
+ det < 0.25f || det > 4.f)
+ drawCached = false;
+
+ QFontEngineGlyphCache::Type glyphType = ti.fontEngine->glyphFormat >= 0
+ ? QFontEngineGlyphCache::Type(ti.fontEngine->glyphFormat)
+ : d->glyphCacheType;
+
+
+ if (glyphType == QFontEngineGlyphCache::Raster_RGBMask) {
+ if (d->device->alphaRequested() || txtype > QTransform::TxTranslate
+ || (state()->composition_mode != QPainter::CompositionMode_Source
+ && state()->composition_mode != QPainter::CompositionMode_SourceOver))
+ {
+ glyphType = QFontEngineGlyphCache::Raster_A8;
+ }
+ }
+
+ if (drawCached) {
+ QVarLengthArray<QFixedPoint> positions;
+ QVarLengthArray<glyph_t> glyphs;
+ QTransform matrix = QTransform::fromTranslate(p.x(), p.y());
+ ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
+
+ {
+ QStaticTextItem staticTextItem;
+ staticTextItem.chars = const_cast<QChar *>(ti.chars);
+ staticTextItem.setFontEngine(ti.fontEngine);
+ staticTextItem.glyphs = glyphs.data();
+ staticTextItem.numChars = ti.num_chars;
+ staticTextItem.numGlyphs = glyphs.size();
+ staticTextItem.glyphPositions = positions.data();
+
+ d->drawCachedGlyphs(glyphType, &staticTextItem);
+ }
+ return;
+ }
+
+ QPaintEngineEx::drawTextItem(p, ti);
+}
+
+namespace {
+
+ class QOpenGLStaticTextUserData: public QStaticTextUserData
+ {
+ public:
+ QOpenGLStaticTextUserData()
+ : QStaticTextUserData(OpenGLUserData), cacheSize(0, 0), cacheSerialNumber(0)
+ {
+ }
+
+ ~QOpenGLStaticTextUserData()
+ {
+ }
+
+ QSize cacheSize;
+ QGL2PEXVertexArray vertexCoordinateArray;
+ QGL2PEXVertexArray textureCoordinateArray;
+ QFontEngineGlyphCache::Type glyphType;
+ int cacheSerialNumber;
+ };
+
+}
+
+// #define QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO
+
+void QGL2PaintEngineExPrivate::drawCachedGlyphs(QFontEngineGlyphCache::Type glyphType,
+ QStaticTextItem *staticTextItem)
+{
+ Q_Q(QGL2PaintEngineEx);
+
+ QOpenGL2PaintEngineState *s = q->state();
+
+ void *cacheKey = const_cast<QGLContext *>(QGLContextPrivate::contextGroup(ctx)->context());
+ bool recreateVertexArrays = false;
+
+ QGLTextureGlyphCache *cache =
+ (QGLTextureGlyphCache *) staticTextItem->fontEngine()->glyphCache(cacheKey, glyphType, QTransform());
+ if (!cache || cache->cacheType() != glyphType || cache->context() == 0) {
+ cache = new QGLTextureGlyphCache(ctx, glyphType, QTransform());
+ staticTextItem->fontEngine()->setGlyphCache(cacheKey, cache);
+ cache->insert(ctx, cache);
+ recreateVertexArrays = true;
+ }
+
+ if (staticTextItem->userDataNeedsUpdate) {
+ recreateVertexArrays = true;
+ } else if (staticTextItem->userData() == 0) {
+ recreateVertexArrays = true;
+ } else if (staticTextItem->userData()->type != QStaticTextUserData::OpenGLUserData) {
+ recreateVertexArrays = true;
+ } else {
+ QOpenGLStaticTextUserData *userData = static_cast<QOpenGLStaticTextUserData *>(staticTextItem->userData());
+ if (userData->glyphType != glyphType) {
+ recreateVertexArrays = true;
+ } else if (userData->cacheSerialNumber != cache->serialNumber()) {
+ recreateVertexArrays = true;
+ }
+ }
+
+ // We only need to update the cache with new glyphs if we are actually going to recreate the vertex arrays.
+ // If the cache size has changed, we do need to regenerate the vertices, but we don't need to repopulate the
+ // cache so this text is performed before we test if the cache size has changed.
+ if (recreateVertexArrays) {
+ cache->setPaintEnginePrivate(this);
+ if (!cache->populate(staticTextItem->fontEngine(), staticTextItem->numGlyphs,
+ staticTextItem->glyphs, staticTextItem->glyphPositions)) {
+ // No space for glyphs in cache. We need to reset it and try again.
+ cache->clear();
+ cache->populate(staticTextItem->fontEngine(), staticTextItem->numGlyphs,
+ staticTextItem->glyphs, staticTextItem->glyphPositions);
+ }
+ cache->fillInPendingGlyphs();
+ }
+
+ if (cache->width() == 0 || cache->height() == 0)
+ return;
+
+ transferMode(TextDrawingMode);
+
+ int margin = cache->glyphMargin();
+
+ GLfloat dx = 1.0 / cache->width();
+ GLfloat dy = 1.0 / cache->height();
+
+ // Use global arrays by default
+ QGL2PEXVertexArray *vertexCoordinates = &vertexCoordinateArray;
+ QGL2PEXVertexArray *textureCoordinates = &textureCoordinateArray;
+
+ if (staticTextItem->useBackendOptimizations) {
+ QOpenGLStaticTextUserData *userData = 0;
+
+ if (staticTextItem->userData() == 0
+ || staticTextItem->userData()->type != QStaticTextUserData::OpenGLUserData) {
+
+ userData = new QOpenGLStaticTextUserData();
+ staticTextItem->setUserData(userData);
+
+ } else {
+ userData = static_cast<QOpenGLStaticTextUserData*>(staticTextItem->userData());
+ }
+
+ userData->glyphType = glyphType;
+ userData->cacheSerialNumber = cache->serialNumber();
+
+ // Use cache if backend optimizations is turned on
+ vertexCoordinates = &userData->vertexCoordinateArray;
+ textureCoordinates = &userData->textureCoordinateArray;
+
+ QSize size(cache->width(), cache->height());
+ if (userData->cacheSize != size) {
+ recreateVertexArrays = true;
+ userData->cacheSize = size;
+ }
+ }
+
+ if (recreateVertexArrays) {
+ vertexCoordinates->clear();
+ textureCoordinates->clear();
+
+ bool supportsSubPixelPositions = staticTextItem->fontEngine()->supportsSubPixelPositions();
+ for (int i=0; i<staticTextItem->numGlyphs; ++i) {
+ QFixed subPixelPosition;
+ if (supportsSubPixelPositions)
+ subPixelPosition = cache->subPixelPositionForX(staticTextItem->glyphPositions[i].x);
+
+ QTextureGlyphCache::GlyphAndSubPixelPosition glyph(staticTextItem->glyphs[i], subPixelPosition);
+
+ const QTextureGlyphCache::Coord &c = cache->coords[glyph];
+ if (c.isNull())
+ continue;
+
+ int x = qFloor(staticTextItem->glyphPositions[i].x) + c.baseLineX - margin;
+ int y = qFloor(staticTextItem->glyphPositions[i].y) - c.baseLineY - margin;
+
+ vertexCoordinates->addQuad(QRectF(x, y, c.w, c.h));
+ textureCoordinates->addQuad(QRectF(c.x*dx, c.y*dy, c.w * dx, c.h * dy));
+ }
+
+ staticTextItem->userDataNeedsUpdate = false;
+ }
+
+ int numGlyphs = vertexCoordinates->vertexCount() / 4;
+
+ if (elementIndices.size() < numGlyphs*6) {
+ Q_ASSERT(elementIndices.size() % 6 == 0);
+ int j = elementIndices.size() / 6 * 4;
+ while (j < numGlyphs*4) {
+ elementIndices.append(j + 0);
+ elementIndices.append(j + 0);
+ elementIndices.append(j + 1);
+ elementIndices.append(j + 2);
+ elementIndices.append(j + 3);
+ elementIndices.append(j + 3);
+
+ j += 4;
+ }
+
+#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
+ if (elementIndicesVBOId == 0)
+ glGenBuffers(1, &elementIndicesVBOId);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementIndicesVBOId);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementIndices.size() * sizeof(GLushort),
+ elementIndices.constData(), GL_STATIC_DRAW);
+#endif
+ } else {
+#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementIndicesVBOId);
+#endif
+ }
+
+ setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinates->data());
+ setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinates->data());
+
+ if (!snapToPixelGrid) {
+ snapToPixelGrid = true;
+ matrixDirty = true;
+ }
+
+ QBrush pensBrush = q->state()->pen.brush();
+ setBrush(pensBrush);
+
+ if (glyphType == QFontEngineGlyphCache::Raster_RGBMask) {
+
+ // Subpixel antialiasing without gamma correction
+
+ QPainter::CompositionMode compMode = q->state()->composition_mode;
+ Q_ASSERT(compMode == QPainter::CompositionMode_Source
+ || compMode == QPainter::CompositionMode_SourceOver);
+
+ shaderManager->setMaskType(QGLEngineShaderManager::SubPixelMaskPass1);
+
+ if (pensBrush.style() == Qt::SolidPattern) {
+ // Solid patterns can get away with only one pass.
+ QColor c = pensBrush.color();
+ qreal oldOpacity = q->state()->opacity;
+ if (compMode == QPainter::CompositionMode_Source) {
+ c = qt_premultiplyColor(c, q->state()->opacity);
+ q->state()->opacity = 1;
+ opacityUniformDirty = true;
+ }
+
+ compositionModeDirty = false; // I can handle this myself, thank you very much
+ prepareForDraw(false); // Text always causes src pixels to be transparent
+
+ // prepareForDraw() have set the opacity on the current shader, so the opacity state can now be reset.
+ if (compMode == QPainter::CompositionMode_Source) {
+ q->state()->opacity = oldOpacity;
+ opacityUniformDirty = true;
+ }
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR);
+ glBlendColor(c.redF(), c.greenF(), c.blueF(), c.alphaF());
+ } else {
+ // Other brush styles need two passes.
+
+ qreal oldOpacity = q->state()->opacity;
+ if (compMode == QPainter::CompositionMode_Source) {
+ q->state()->opacity = 1;
+ opacityUniformDirty = true;
+ pensBrush = Qt::white;
+ setBrush(pensBrush);
+ }
+
+ compositionModeDirty = false; // I can handle this myself, thank you very much
+ prepareForDraw(false); // Text always causes src pixels to be transparent
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+
+ glActiveTexture(GL_TEXTURE0 + QT_MASK_TEXTURE_UNIT);
+ glBindTexture(GL_TEXTURE_2D, cache->texture());
+ updateTextureFilter(GL_TEXTURE_2D, GL_REPEAT, false);
+
+#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
+ glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0);
+#else
+ glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, elementIndices.data());
+#endif
+
+ shaderManager->setMaskType(QGLEngineShaderManager::SubPixelMaskPass2);
+
+ if (compMode == QPainter::CompositionMode_Source) {
+ q->state()->opacity = oldOpacity;
+ opacityUniformDirty = true;
+ pensBrush = q->state()->pen.brush();
+ setBrush(pensBrush);
+ }
+
+ compositionModeDirty = false;
+ prepareForDraw(false); // Text always causes src pixels to be transparent
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE);
+ }
+ compositionModeDirty = true;
+ } else {
+ // Greyscale/mono glyphs
+
+ shaderManager->setMaskType(QGLEngineShaderManager::PixelMask);
+ prepareForDraw(false); // Text always causes src pixels to be transparent
+ }
+ //### TODO: Gamma correction
+
+ QGLTextureGlyphCache::FilterMode filterMode = (s->matrix.type() > QTransform::TxTranslate)?QGLTextureGlyphCache::Linear:QGLTextureGlyphCache::Nearest;
+ if (lastMaskTextureUsed != cache->texture() || cache->filterMode() != filterMode) {
+
+ glActiveTexture(GL_TEXTURE0 + QT_MASK_TEXTURE_UNIT);
+ if (lastMaskTextureUsed != cache->texture()) {
+ glBindTexture(GL_TEXTURE_2D, cache->texture());
+ lastMaskTextureUsed = cache->texture();
+ }
+
+ if (cache->filterMode() != filterMode) {
+ if (filterMode == QGLTextureGlyphCache::Linear) {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ } else {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ }
+ cache->setFilterMode(filterMode);
+ }
+ }
+
+#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
+ glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+#else
+ glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, elementIndices.data());
+#endif
+}
+
+void QGL2PaintEngineEx::drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragmentCount, const QPixmap &pixmap,
+ QPainter::PixmapFragmentHints hints)
+{
+ Q_D(QGL2PaintEngineEx);
+ // Use fallback for extended composition modes.
+ if (state()->composition_mode > QPainter::CompositionMode_Plus) {
+ QPaintEngineEx::drawPixmapFragments(fragments, fragmentCount, pixmap, hints);
+ return;
+ }
+
+ ensureActive();
+ int max_texture_size = d->ctx->d_func()->maxTextureSize();
+ if (pixmap.width() > max_texture_size || pixmap.height() > max_texture_size) {
+ QPixmap scaled = pixmap.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
+ d->drawPixmapFragments(fragments, fragmentCount, scaled, hints);
+ } else {
+ d->drawPixmapFragments(fragments, fragmentCount, pixmap, hints);
+ }
+}
+
+
+void QGL2PaintEngineExPrivate::drawPixmapFragments(const QPainter::PixmapFragment *fragments,
+ int fragmentCount, const QPixmap &pixmap,
+ QPainter::PixmapFragmentHints hints)
+{
+ GLfloat dx = 1.0f / pixmap.size().width();
+ GLfloat dy = 1.0f / pixmap.size().height();
+
+ vertexCoordinateArray.clear();
+ textureCoordinateArray.clear();
+ opacityArray.reset();
+
+ if (snapToPixelGrid) {
+ snapToPixelGrid = false;
+ matrixDirty = true;
+ }
+
+ bool allOpaque = true;
+
+ for (int i = 0; i < fragmentCount; ++i) {
+ qreal s = 0;
+ qreal c = 1;
+ if (fragments[i].rotation != 0) {
+ s = qFastSin(fragments[i].rotation * Q_PI / 180);
+ c = qFastCos(fragments[i].rotation * Q_PI / 180);
+ }
+
+ qreal right = 0.5 * fragments[i].scaleX * fragments[i].width;
+ qreal bottom = 0.5 * fragments[i].scaleY * fragments[i].height;
+ QGLPoint bottomRight(right * c - bottom * s, right * s + bottom * c);
+ QGLPoint bottomLeft(-right * c - bottom * s, -right * s + bottom * c);
+
+ vertexCoordinateArray.addVertex(bottomRight.x + fragments[i].x, bottomRight.y + fragments[i].y);
+ vertexCoordinateArray.addVertex(-bottomLeft.x + fragments[i].x, -bottomLeft.y + fragments[i].y);
+ vertexCoordinateArray.addVertex(-bottomRight.x + fragments[i].x, -bottomRight.y + fragments[i].y);
+ vertexCoordinateArray.addVertex(-bottomRight.x + fragments[i].x, -bottomRight.y + fragments[i].y);
+ vertexCoordinateArray.addVertex(bottomLeft.x + fragments[i].x, bottomLeft.y + fragments[i].y);
+ vertexCoordinateArray.addVertex(bottomRight.x + fragments[i].x, bottomRight.y + fragments[i].y);
+
+ QGLRect src(fragments[i].sourceLeft * dx, fragments[i].sourceTop * dy,
+ (fragments[i].sourceLeft + fragments[i].width) * dx,
+ (fragments[i].sourceTop + fragments[i].height) * dy);
+
+ textureCoordinateArray.addVertex(src.right, src.bottom);
+ textureCoordinateArray.addVertex(src.right, src.top);
+ textureCoordinateArray.addVertex(src.left, src.top);
+ textureCoordinateArray.addVertex(src.left, src.top);
+ textureCoordinateArray.addVertex(src.left, src.bottom);
+ textureCoordinateArray.addVertex(src.right, src.bottom);
+
+ qreal opacity = fragments[i].opacity * q->state()->opacity;
+ opacityArray << opacity << opacity << opacity << opacity << opacity << opacity;
+ allOpaque &= (opacity >= 0.99f);
+ }
+
+ glActiveTexture(GL_TEXTURE0 + QT_IMAGE_TEXTURE_UNIT);
+ QGLTexture *texture = ctx->d_func()->bindTexture(pixmap, GL_TEXTURE_2D, GL_RGBA,
+ QGLContext::InternalBindOption
+ | QGLContext::CanFlipNativePixmapBindOption);
+
+ if (texture->options & QGLContext::InvertedYBindOption) {
+ // Flip texture y-coordinate.
+ QGLPoint *data = textureCoordinateArray.data();
+ for (int i = 0; i < 6 * fragmentCount; ++i)
+ data[i].y = 1 - data[i].y;
+ }
+
+ transferMode(ImageArrayDrawingMode);
+
+ bool isBitmap = pixmap.isQBitmap();
+ bool isOpaque = !isBitmap && (!pixmap.hasAlpha() || (hints & QPainter::OpaqueHint)) && allOpaque;
+
+ updateTextureFilter(GL_TEXTURE_2D, GL_CLAMP_TO_EDGE,
+ q->state()->renderHints & QPainter::SmoothPixmapTransform, texture->id);
+
+ // Setup for texture drawing
+ currentBrush = noBrush;
+ shaderManager->setSrcPixelType(isBitmap ? QGLEngineShaderManager::PatternSrc
+ : QGLEngineShaderManager::ImageSrc);
+ if (prepareForDraw(isOpaque))
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
+
+ if (isBitmap) {
+ QColor col = qt_premultiplyColor(q->state()->pen.color(), (GLfloat)q->state()->opacity);
+ shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::PatternColor), col);
+ }
+
+ glDrawArrays(GL_TRIANGLES, 0, 6 * fragmentCount);
+}
+
+bool QGL2PaintEngineEx::begin(QPaintDevice *pdev)
+{
+ Q_D(QGL2PaintEngineEx);
+
+// qDebug("QGL2PaintEngineEx::begin()");
+ if (pdev->devType() == QInternal::OpenGL)
+ d->device = static_cast<QGLPaintDevice*>(pdev);
+ else
+ d->device = QGLPaintDevice::getDevice(pdev);
+
+ if (!d->device)
+ return false;
+
+ d->ctx = d->device->context();
+ d->ctx->d_ptr->active_engine = this;
+
+ const QSize sz = d->device->size();
+ d->width = sz.width();
+ d->height = sz.height();
+ d->mode = BrushDrawingMode;
+ d->brushTextureDirty = true;
+ d->brushUniformsDirty = true;
+ d->matrixUniformDirty = true;
+ d->matrixDirty = true;
+ d->compositionModeDirty = true;
+ d->opacityUniformDirty = true;
+ d->needsSync = true;
+ d->useSystemClip = !systemClip().isEmpty();
+ d->currentBrush = QBrush();
+
+ d->dirtyStencilRegion = QRect(0, 0, d->width, d->height);
+ d->stencilClean = true;
+
+ // Calling begin paint should make the correct context current. So, any
+ // code which calls into GL or otherwise needs a current context *must*
+ // go after beginPaint:
+ d->device->beginPaint();
+
+#if !defined(QT_OPENGL_ES_2)
+ bool success = qt_resolve_version_2_0_functions(d->ctx)
+ && qt_resolve_buffer_extensions(d->ctx);
+ Q_ASSERT(success);
+ Q_UNUSED(success);
+#endif
+
+ d->shaderManager = new QGLEngineShaderManager(d->ctx);
+
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_SCISSOR_TEST);
+
+#if !defined(QT_OPENGL_ES_2)
+ glDisable(GL_MULTISAMPLE);
+#endif
+
+ d->glyphCacheType = QFontEngineGlyphCache::Raster_A8;
+
+#if !defined(QT_OPENGL_ES_2)
+#if defined(Q_WS_WIN)
+ if (qt_cleartype_enabled)
+#endif
+#if defined(Q_WS_MAC)
+ if (qt_applefontsmoothing_enabled)
+#endif
+ d->glyphCacheType = QFontEngineGlyphCache::Raster_RGBMask;
+#endif
+
+#if defined(QT_OPENGL_ES_2)
+ // OpenGL ES can't switch MSAA off, so if the gl paint device is
+ // multisampled, it's always multisampled.
+ d->multisamplingAlwaysEnabled = d->device->format().sampleBuffers();
+#else
+ d->multisamplingAlwaysEnabled = false;
+#endif
+
+ return true;
+}
+
+bool QGL2PaintEngineEx::end()
+{
+ Q_D(QGL2PaintEngineEx);
+ QGLContext *ctx = d->ctx;
+
+ glUseProgram(0);
+ d->transferMode(BrushDrawingMode);
+ d->device->endPaint();
+
+#if defined(Q_WS_X11)
+ // On some (probably all) drivers, deleting an X pixmap which has been bound to a texture
+ // before calling glFinish/swapBuffers renders garbage. Presumably this is because X deletes
+ // the pixmap behind the driver's back before it's had a chance to use it. To fix this, we
+ // reference all QPixmaps which have been bound to stop them being deleted and only deref
+ // them here, after swapBuffers, where they can be safely deleted.
+ ctx->d_func()->boundPixmaps.clear();
+#endif
+ d->ctx->d_ptr->active_engine = 0;
+
+ d->resetGLState();
+
+ delete d->shaderManager;
+ d->shaderManager = 0;
+ d->currentBrush = QBrush();
+
+#ifdef QT_OPENGL_CACHE_AS_VBOS
+ if (!d->unusedVBOSToClean.isEmpty()) {
+ glDeleteBuffers(d->unusedVBOSToClean.size(), d->unusedVBOSToClean.constData());
+ d->unusedVBOSToClean.clear();
+ }
+ if (!d->unusedIBOSToClean.isEmpty()) {
+ glDeleteBuffers(d->unusedIBOSToClean.size(), d->unusedIBOSToClean.constData());
+ d->unusedIBOSToClean.clear();
+ }
+#endif
+
+ return false;
+}
+
+void QGL2PaintEngineEx::ensureActive()
+{
+ Q_D(QGL2PaintEngineEx);
+ QGLContext *ctx = d->ctx;
+
+ if (isActive() && ctx->d_ptr->active_engine != this) {
+ ctx->d_ptr->active_engine = this;
+ d->needsSync = true;
+ }
+
+ d->device->ensureActiveTarget();
+
+ if (d->needsSync) {
+ d->transferMode(BrushDrawingMode);
+ glViewport(0, 0, d->width, d->height);
+ d->needsSync = false;
+ d->lastMaskTextureUsed = 0;
+ d->shaderManager->setDirty();
+ d->ctx->d_func()->syncGlState();
+ for (int i = 0; i < 3; ++i)
+ d->vertexAttribPointers[i] = (GLfloat*)-1; // Assume the pointers are clobbered
+ setState(state());
+ }
+}
+
+void QGL2PaintEngineExPrivate::updateClipScissorTest()
+{
+ Q_Q(QGL2PaintEngineEx);
+ if (q->state()->clipTestEnabled) {
+ glEnable(GL_STENCIL_TEST);
+ glStencilFunc(GL_LEQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
+ } else {
+ glDisable(GL_STENCIL_TEST);
+ glStencilFunc(GL_ALWAYS, 0, 0xff);
+ }
+
+#ifdef QT_GL_NO_SCISSOR_TEST
+ currentScissorBounds = QRect(0, 0, width, height);
+#else
+ QRect bounds = q->state()->rectangleClip;
+ if (!q->state()->clipEnabled) {
+ if (useSystemClip)
+ bounds = systemClip.boundingRect();
+ else
+ bounds = QRect(0, 0, width, height);
+ } else {
+ if (useSystemClip)
+ bounds = bounds.intersected(systemClip.boundingRect());
+ else
+ bounds = bounds.intersected(QRect(0, 0, width, height));
+ }
+
+ currentScissorBounds = bounds;
+
+ if (bounds == QRect(0, 0, width, height)) {
+ glDisable(GL_SCISSOR_TEST);
+ } else {
+ glEnable(GL_SCISSOR_TEST);
+ setScissor(bounds);
+ }
+#endif
+}
+
+void QGL2PaintEngineExPrivate::setScissor(const QRect &rect)
+{
+ const int left = rect.left();
+ const int width = rect.width();
+ int bottom = height - (rect.top() + rect.height());
+ if (device->isFlipped()) {
+ bottom = rect.top();
+ }
+ const int height = rect.height();
+
+ glScissor(left, bottom, width, height);
+}
+
+void QGL2PaintEngineEx::clipEnabledChanged()
+{
+ Q_D(QGL2PaintEngineEx);
+
+ state()->clipChanged = true;
+
+ if (painter()->hasClipping())
+ d->regenerateClip();
+ else
+ d->systemStateChanged();
+}
+
+void QGL2PaintEngineExPrivate::clearClip(uint value)
+{
+ dirtyStencilRegion -= currentScissorBounds;
+
+ glStencilMask(0xff);
+ glClearStencil(value);
+ glClear(GL_STENCIL_BUFFER_BIT);
+ glStencilMask(0x0);
+
+ q->state()->needsClipBufferClear = false;
+}
+
+void QGL2PaintEngineExPrivate::writeClip(const QVectorPath &path, uint value)
+{
+ transferMode(BrushDrawingMode);
+
+ if (snapToPixelGrid) {
+ snapToPixelGrid = false;
+ matrixDirty = true;
+ }
+
+ if (matrixDirty)
+ updateMatrix();
+
+ stencilClean = false;
+
+ const bool singlePass = !path.hasWindingFill()
+ && (((q->state()->currentClip == maxClip - 1) && q->state()->clipTestEnabled)
+ || q->state()->needsClipBufferClear);
+ const uint referenceClipValue = q->state()->needsClipBufferClear ? 1 : q->state()->currentClip;
+
+ if (q->state()->needsClipBufferClear)
+ clearClip(1);
+
+ if (path.isEmpty()) {
+ glEnable(GL_STENCIL_TEST);
+ glStencilFunc(GL_LEQUAL, value, ~GL_STENCIL_HIGH_BIT);
+ return;
+ }
+
+ if (q->state()->clipTestEnabled)
+ glStencilFunc(GL_LEQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
+ else
+ glStencilFunc(GL_ALWAYS, 0, 0xff);
+
+ vertexCoordinateArray.clear();
+ vertexCoordinateArray.addPath(path, inverseScale, false);
+
+ if (!singlePass)
+ fillStencilWithVertexArray(vertexCoordinateArray, path.hasWindingFill());
+
+ glColorMask(false, false, false, false);
+ glEnable(GL_STENCIL_TEST);
+ useSimpleShader();
+
+ if (singlePass) {
+ // Under these conditions we can set the new stencil value in a single
+ // pass, by using the current value and the "new value" as the toggles
+
+ glStencilFunc(GL_LEQUAL, referenceClipValue, ~GL_STENCIL_HIGH_BIT);
+ glStencilOp(GL_KEEP, GL_INVERT, GL_INVERT);
+ glStencilMask(value ^ referenceClipValue);
+
+ drawVertexArrays(vertexCoordinateArray, GL_TRIANGLE_FAN);
+ } else {
+ glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+ glStencilMask(0xff);
+
+ if (!q->state()->clipTestEnabled && path.hasWindingFill()) {
+ // Pass when any clip bit is set, set high bit
+ glStencilFunc(GL_NOTEQUAL, GL_STENCIL_HIGH_BIT, ~GL_STENCIL_HIGH_BIT);
+ composite(vertexCoordinateArray.boundingRect());
+ }
+
+ // Pass when high bit is set, replace stencil value with new clip value
+ glStencilFunc(GL_NOTEQUAL, value, GL_STENCIL_HIGH_BIT);
+
+ composite(vertexCoordinateArray.boundingRect());
+ }
+
+ glStencilFunc(GL_LEQUAL, value, ~GL_STENCIL_HIGH_BIT);
+ glStencilMask(0);
+
+ glColorMask(true, true, true, true);
+}
+
+void QGL2PaintEngineEx::clip(const QVectorPath &path, Qt::ClipOperation op)
+{
+// qDebug("QGL2PaintEngineEx::clip()");
+ Q_D(QGL2PaintEngineEx);
+
+ state()->clipChanged = true;
+
+ ensureActive();
+
+ if (op == Qt::ReplaceClip) {
+ op = Qt::IntersectClip;
+ if (d->hasClipOperations()) {
+ d->systemStateChanged();
+ state()->canRestoreClip = false;
+ }
+ }
+
+#ifndef QT_GL_NO_SCISSOR_TEST
+ if (!path.isEmpty() && op == Qt::IntersectClip && (path.shape() == QVectorPath::RectangleHint)) {
+ const QPointF* const points = reinterpret_cast<const QPointF*>(path.points());
+ QRectF rect(points[0], points[2]);
+
+ if (state()->matrix.type() <= QTransform::TxScale
+ || (state()->matrix.type() == QTransform::TxRotate
+ && qFuzzyIsNull(state()->matrix.m11())
+ && qFuzzyIsNull(state()->matrix.m22())))
+ {
+ state()->rectangleClip = state()->rectangleClip.intersected(state()->matrix.mapRect(rect).toRect());
+ d->updateClipScissorTest();
+ return;
+ }
+ }
+#endif
+
+ const QRect pathRect = state()->matrix.mapRect(path.controlPointRect()).toAlignedRect();
+
+ switch (op) {
+ case Qt::NoClip:
+ if (d->useSystemClip) {
+ state()->clipTestEnabled = true;
+ state()->currentClip = 1;
+ } else {
+ state()->clipTestEnabled = false;
+ }
+ state()->rectangleClip = QRect(0, 0, d->width, d->height);
+ state()->canRestoreClip = false;
+ d->updateClipScissorTest();
+ break;
+ case Qt::IntersectClip:
+ state()->rectangleClip = state()->rectangleClip.intersected(pathRect);
+ d->updateClipScissorTest();
+ d->resetClipIfNeeded();
+ ++d->maxClip;
+ d->writeClip(path, d->maxClip);
+ state()->currentClip = d->maxClip;
+ state()->clipTestEnabled = true;
+ break;
+ case Qt::UniteClip: {
+ d->resetClipIfNeeded();
+ ++d->maxClip;
+ if (state()->rectangleClip.isValid()) {
+ QPainterPath path;
+ path.addRect(state()->rectangleClip);
+
+ // flush the existing clip rectangle to the depth buffer
+ d->writeClip(qtVectorPathForPath(state()->matrix.inverted().map(path)), d->maxClip);
+ }
+
+ state()->clipTestEnabled = false;
+#ifndef QT_GL_NO_SCISSOR_TEST
+ QRect oldRectangleClip = state()->rectangleClip;
+
+ state()->rectangleClip = state()->rectangleClip.united(pathRect);
+ d->updateClipScissorTest();
+
+ QRegion extendRegion = QRegion(state()->rectangleClip) - oldRectangleClip;
+
+ if (!extendRegion.isEmpty()) {
+ QPainterPath extendPath;
+ extendPath.addRegion(extendRegion);
+
+ // first clear the depth buffer in the extended region
+ d->writeClip(qtVectorPathForPath(state()->matrix.inverted().map(extendPath)), 0);
+ }
+#endif
+ // now write the clip path
+ d->writeClip(path, d->maxClip);
+ state()->canRestoreClip = false;
+ state()->currentClip = d->maxClip;
+ state()->clipTestEnabled = true;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void QGL2PaintEngineExPrivate::regenerateClip()
+{
+ systemStateChanged();
+ replayClipOperations();
+}
+
+void QGL2PaintEngineExPrivate::systemStateChanged()
+{
+ Q_Q(QGL2PaintEngineEx);
+
+ q->state()->clipChanged = true;
+
+ if (systemClip.isEmpty()) {
+ useSystemClip = false;
+ } else {
+ if (q->paintDevice()->devType() == QInternal::Widget && currentClipWidget) {
+ QWidgetPrivate *widgetPrivate = qt_widget_private(currentClipWidget->window());
+ useSystemClip = widgetPrivate->extra && widgetPrivate->extra->inRenderWithPainter;
+ } else {
+ useSystemClip = true;
+ }
+ }
+
+ q->state()->clipTestEnabled = false;
+ q->state()->needsClipBufferClear = true;
+
+ q->state()->currentClip = 1;
+ maxClip = 1;
+
+ q->state()->rectangleClip = useSystemClip ? systemClip.boundingRect() : QRect(0, 0, width, height);
+ updateClipScissorTest();
+
+ if (systemClip.rectCount() == 1) {
+ if (systemClip.boundingRect() == QRect(0, 0, width, height))
+ useSystemClip = false;
+#ifndef QT_GL_NO_SCISSOR_TEST
+ // scissoring takes care of the system clip
+ return;
+#endif
+ }
+
+ if (useSystemClip) {
+ clearClip(0);
+
+ QPainterPath path;
+ path.addRegion(systemClip);
+
+ q->state()->currentClip = 0;
+ writeClip(qtVectorPathForPath(q->state()->matrix.inverted().map(path)), 1);
+ q->state()->currentClip = 1;
+ q->state()->clipTestEnabled = true;
+ }
+}
+
+void QGL2PaintEngineEx::setState(QPainterState *new_state)
+{
+ // qDebug("QGL2PaintEngineEx::setState()");
+
+ Q_D(QGL2PaintEngineEx);
+
+ QOpenGL2PaintEngineState *s = static_cast<QOpenGL2PaintEngineState *>(new_state);
+ QOpenGL2PaintEngineState *old_state = state();
+
+ QPaintEngineEx::setState(s);
+
+ if (s->isNew) {
+ // Newly created state object. The call to setState()
+ // will either be followed by a call to begin(), or we are
+ // setting the state as part of a save().
+ s->isNew = false;
+ return;
+ }
+
+ // Setting the state as part of a restore().
+
+ if (old_state == s || old_state->renderHintsChanged)
+ renderHintsChanged();
+
+ if (old_state == s || old_state->matrixChanged)
+ d->matrixDirty = true;
+
+ if (old_state == s || old_state->compositionModeChanged)
+ d->compositionModeDirty = true;
+
+ if (old_state == s || old_state->opacityChanged)
+ d->opacityUniformDirty = true;
+
+ if (old_state == s || old_state->clipChanged) {
+ if (old_state && old_state != s && old_state->canRestoreClip) {
+ d->updateClipScissorTest();
+ glDepthFunc(GL_LEQUAL);
+ } else {
+ d->regenerateClip();
+ }
+ }
+}
+
+QPainterState *QGL2PaintEngineEx::createState(QPainterState *orig) const
+{
+ if (orig)
+ const_cast<QGL2PaintEngineEx *>(this)->ensureActive();
+
+ QOpenGL2PaintEngineState *s;
+ if (!orig)
+ s = new QOpenGL2PaintEngineState();
+ else
+ s = new QOpenGL2PaintEngineState(*static_cast<QOpenGL2PaintEngineState *>(orig));
+
+ s->matrixChanged = false;
+ s->compositionModeChanged = false;
+ s->opacityChanged = false;
+ s->renderHintsChanged = false;
+ s->clipChanged = false;
+
+ return s;
+}
+
+QOpenGL2PaintEngineState::QOpenGL2PaintEngineState(QOpenGL2PaintEngineState &other)
+ : QPainterState(other)
+{
+ isNew = true;
+ needsClipBufferClear = other.needsClipBufferClear;
+ clipTestEnabled = other.clipTestEnabled;
+ currentClip = other.currentClip;
+ canRestoreClip = other.canRestoreClip;
+ rectangleClip = other.rectangleClip;
+}
+
+QOpenGL2PaintEngineState::QOpenGL2PaintEngineState()
+{
+ isNew = true;
+ needsClipBufferClear = true;
+ clipTestEnabled = false;
+ canRestoreClip = true;
+}
+
+QOpenGL2PaintEngineState::~QOpenGL2PaintEngineState()
+{
+}
+
+QT_END_NAMESPACE
diff --git a/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h b/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h
new file mode 100644
index 0000000000..f7ec5f5c2f
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h
@@ -0,0 +1,332 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGRAPHICSCONTEXT_OPENGL2_P_H
+#define QGRAPHICSCONTEXT_OPENGL2_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QDebug>
+
+#include <private/qpaintengineex_p.h>
+#include <private/qglengineshadermanager_p.h>
+#include <private/qgl2pexvertexarray_p.h>
+#include <private/qglpaintdevice_p.h>
+#include <private/qglpixmapfilter_p.h>
+#include <private/qfontengine_p.h>
+#include <private/qdatabuffer_p.h>
+#include <private/qtriangulatingstroker_p.h>
+
+enum EngineMode {
+ ImageDrawingMode,
+ TextDrawingMode,
+ BrushDrawingMode,
+ ImageArrayDrawingMode
+};
+
+QT_BEGIN_NAMESPACE
+
+#define GL_STENCIL_HIGH_BIT GLuint(0x80)
+#define QT_BRUSH_TEXTURE_UNIT GLuint(0)
+#define QT_IMAGE_TEXTURE_UNIT GLuint(0) //Can be the same as brush texture unit
+#define QT_MASK_TEXTURE_UNIT GLuint(1)
+#define QT_BACKGROUND_TEXTURE_UNIT GLuint(2)
+
+class QGL2PaintEngineExPrivate;
+
+
+class QOpenGL2PaintEngineState : public QPainterState
+{
+public:
+ QOpenGL2PaintEngineState(QOpenGL2PaintEngineState &other);
+ QOpenGL2PaintEngineState();
+ ~QOpenGL2PaintEngineState();
+
+ uint isNew : 1;
+ uint needsClipBufferClear : 1;
+ uint clipTestEnabled : 1;
+ uint canRestoreClip : 1;
+ uint matrixChanged : 1;
+ uint compositionModeChanged : 1;
+ uint opacityChanged : 1;
+ uint renderHintsChanged : 1;
+ uint clipChanged : 1;
+ uint currentClip : 8;
+
+ QRect rectangleClip;
+};
+
+class Q_OPENGL_EXPORT QGL2PaintEngineEx : public QPaintEngineEx
+{
+ Q_DECLARE_PRIVATE(QGL2PaintEngineEx)
+public:
+ QGL2PaintEngineEx();
+ ~QGL2PaintEngineEx();
+
+ bool begin(QPaintDevice *device);
+ void ensureActive();
+ bool end();
+
+ virtual void clipEnabledChanged();
+ virtual void penChanged();
+ virtual void brushChanged();
+ virtual void brushOriginChanged();
+ virtual void opacityChanged();
+ virtual void compositionModeChanged();
+ virtual void renderHintsChanged();
+ virtual void transformChanged();
+
+ virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr);
+ virtual void drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragmentCount, const QPixmap &pixmap,
+ QPainter::PixmapFragmentHints hints);
+ virtual void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr,
+ Qt::ImageConversionFlags flags = Qt::AutoColor);
+ virtual void drawTextItem(const QPointF &p, const QTextItem &textItem);
+ virtual void fill(const QVectorPath &path, const QBrush &brush);
+ virtual void stroke(const QVectorPath &path, const QPen &pen);
+ virtual void clip(const QVectorPath &path, Qt::ClipOperation op);
+
+ virtual void drawStaticTextItem(QStaticTextItem *textItem);
+
+ bool drawTexture(const QRectF &r, GLuint textureId, const QSize &size, const QRectF &sr);
+
+ Type type() const { return OpenGL2; }
+
+ virtual void setState(QPainterState *s);
+ virtual QPainterState *createState(QPainterState *orig) const;
+ inline QOpenGL2PaintEngineState *state() {
+ return static_cast<QOpenGL2PaintEngineState *>(QPaintEngineEx::state());
+ }
+ inline const QOpenGL2PaintEngineState *state() const {
+ return static_cast<const QOpenGL2PaintEngineState *>(QPaintEngineEx::state());
+ }
+
+ void beginNativePainting();
+ void endNativePainting();
+
+ void invalidateState();
+
+ QPixmapFilter *pixmapFilter(int type, const QPixmapFilter *prototype);
+
+ void setRenderTextActive(bool);
+
+ bool isNativePaintingActive() const;
+private:
+ Q_DISABLE_COPY(QGL2PaintEngineEx)
+};
+
+class QGL2PaintEngineExPrivate : public QPaintEngineExPrivate
+{
+ Q_DECLARE_PUBLIC(QGL2PaintEngineEx)
+public:
+ enum StencilFillMode {
+ OddEvenFillMode,
+ WindingFillMode,
+ TriStripStrokeFillMode
+ };
+
+ QGL2PaintEngineExPrivate(QGL2PaintEngineEx *q_ptr) :
+ q(q_ptr),
+ shaderManager(0),
+ width(0), height(0),
+ ctx(0),
+ useSystemClip(true),
+ elementIndicesVBOId(0),
+ opacityArray(0),
+ snapToPixelGrid(false),
+ nativePaintingActive(false),
+ inverseScale(1),
+ lastMaskTextureUsed(0)
+ { }
+
+ ~QGL2PaintEngineExPrivate();
+
+ void updateBrushTexture();
+ void updateBrushUniforms();
+ void updateMatrix();
+ void updateCompositionMode();
+ void updateTextureFilter(GLenum target, GLenum wrapMode, bool smoothPixmapTransform, GLuint id = -1);
+
+ void resetGLState();
+
+ // fill, stroke, drawTexture, drawPixmaps & drawCachedGlyphs are the main rendering entry-points,
+ // however writeClip can also be thought of as en entry point as it does similar things.
+ void fill(const QVectorPath &path);
+ void stroke(const QVectorPath &path, const QPen &pen);
+ void drawTexture(const QGLRect& dest, const QGLRect& src, const QSize &textureSize, bool opaque, bool pattern = false);
+ void drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragmentCount, const QPixmap &pixmap,
+ QPainter::PixmapFragmentHints hints);
+ void drawCachedGlyphs(QFontEngineGlyphCache::Type glyphType, QStaticTextItem *staticTextItem);
+
+ // Calls glVertexAttributePointer if the pointer has changed
+ inline void setVertexAttributePointer(unsigned int arrayIndex, const GLfloat *pointer);
+
+ // draws whatever is in the vertex array:
+ void drawVertexArrays(const float *data, int *stops, int stopCount, GLenum primitive);
+ void drawVertexArrays(QGL2PEXVertexArray &vertexArray, GLenum primitive) {
+ drawVertexArrays((const float *) vertexArray.data(), vertexArray.stops(), vertexArray.stopCount(), primitive);
+ }
+
+ // Composites the bounding rect onto dest buffer:
+ void composite(const QGLRect& boundingRect);
+
+ // Calls drawVertexArrays to render into stencil buffer:
+ void fillStencilWithVertexArray(const float *data, int count, int *stops, int stopCount, const QGLRect &bounds, StencilFillMode mode);
+ void fillStencilWithVertexArray(QGL2PEXVertexArray& vertexArray, bool useWindingFill) {
+ fillStencilWithVertexArray((const float *) vertexArray.data(), 0, vertexArray.stops(), vertexArray.stopCount(),
+ vertexArray.boundingRect(),
+ useWindingFill ? WindingFillMode : OddEvenFillMode);
+ }
+
+ void setBrush(const QBrush& brush);
+ void transferMode(EngineMode newMode);
+ bool prepareForDraw(bool srcPixelsAreOpaque); // returns true if the program has changed
+ inline void useSimpleShader();
+ inline GLuint location(const QGLEngineShaderManager::Uniform uniform) {
+ return shaderManager->getUniformLocation(uniform);
+ }
+
+ void clearClip(uint value);
+ void writeClip(const QVectorPath &path, uint value);
+ void resetClipIfNeeded();
+
+ void updateClipScissorTest();
+ void setScissor(const QRect &rect);
+ void regenerateClip();
+ void systemStateChanged();
+
+
+ static QGLEngineShaderManager* shaderManagerForEngine(QGL2PaintEngineEx *engine) { return engine->d_func()->shaderManager; }
+ static QGL2PaintEngineExPrivate *getData(QGL2PaintEngineEx *engine) { return engine->d_func(); }
+ static void cleanupVectorPath(QPaintEngineEx *engine, void *data);
+
+
+ QGL2PaintEngineEx* q;
+ QGLEngineShaderManager* shaderManager;
+ QGLPaintDevice* device;
+ int width, height;
+ QGLContext *ctx;
+ EngineMode mode;
+ QFontEngineGlyphCache::Type glyphCacheType;
+
+ // Dirty flags
+ bool matrixDirty; // Implies matrix uniforms are also dirty
+ bool compositionModeDirty;
+ bool brushTextureDirty;
+ bool brushUniformsDirty;
+ bool opacityUniformDirty;
+ bool matrixUniformDirty;
+
+ bool stencilClean; // Has the stencil not been used for clipping so far?
+ bool useSystemClip;
+ QRegion dirtyStencilRegion;
+ QRect currentScissorBounds;
+ uint maxClip;
+
+ QBrush currentBrush; // May not be the state's brush!
+ const QBrush noBrush;
+
+ QPixmap currentBrushPixmap;
+
+ QGL2PEXVertexArray vertexCoordinateArray;
+ QGL2PEXVertexArray textureCoordinateArray;
+ QVector<GLushort> elementIndices;
+ GLuint elementIndicesVBOId;
+ QDataBuffer<GLfloat> opacityArray;
+ GLfloat staticVertexCoordinateArray[8];
+ GLfloat staticTextureCoordinateArray[8];
+
+ bool snapToPixelGrid;
+ bool nativePaintingActive;
+ GLfloat pmvMatrix[3][3];
+ GLfloat inverseScale;
+
+ GLuint lastTextureUsed;
+ GLuint lastMaskTextureUsed;
+
+ bool needsSync;
+ bool multisamplingAlwaysEnabled;
+
+ GLfloat depthRange[2];
+
+ float textureInvertedY;
+
+ QTriangulatingStroker stroker;
+ QDashedStrokeProcessor dasher;
+
+ QScopedPointer<QPixmapFilter> convolutionFilter;
+ QScopedPointer<QPixmapFilter> colorizeFilter;
+ QScopedPointer<QPixmapFilter> blurFilter;
+ QScopedPointer<QPixmapFilter> dropShadowFilter;
+
+ QSet<QVectorPath::CacheEntry *> pathCaches;
+ QVector<GLuint> unusedVBOSToClean;
+ QVector<GLuint> unusedIBOSToClean;
+
+ const GLfloat *vertexAttribPointers[3];
+};
+
+
+void QGL2PaintEngineExPrivate::setVertexAttributePointer(unsigned int arrayIndex, const GLfloat *pointer)
+{
+ Q_ASSERT(arrayIndex < 3);
+ if (pointer == vertexAttribPointers[arrayIndex])
+ return;
+
+ vertexAttribPointers[arrayIndex] = pointer;
+ if (arrayIndex == QT_OPACITY_ATTR)
+ glVertexAttribPointer(arrayIndex, 1, GL_FLOAT, GL_FALSE, 0, pointer);
+ else
+ glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, pointer);
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/opengl/gl2paintengineex/qtextureglyphcache_gl.cpp b/src/opengl/gl2paintengineex/qtextureglyphcache_gl.cpp
new file mode 100644
index 0000000000..c867d60a72
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qtextureglyphcache_gl.cpp
@@ -0,0 +1,396 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextureglyphcache_gl_p.h"
+#include "qpaintengineex_opengl2_p.h"
+#include "private/qglengineshadersource_p.h"
+
+#if defined QT_OPENGL_ES_2 && !defined(QT_NO_EGL)
+#include "private/qeglcontext_p.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+
+#ifdef Q_WS_WIN
+extern Q_GUI_EXPORT bool qt_cleartype_enabled;
+#endif
+
+QBasicAtomicInt qgltextureglyphcache_serial_number = Q_BASIC_ATOMIC_INITIALIZER(1);
+
+QGLTextureGlyphCache::QGLTextureGlyphCache(const QGLContext *context, QFontEngineGlyphCache::Type type, const QTransform &matrix)
+ : QImageTextureGlyphCache(type, matrix), QGLContextGroupResourceBase()
+ , ctx(0)
+ , pex(0)
+ , m_blitProgram(0)
+ , m_filterMode(Nearest)
+ , m_serialNumber(qgltextureglyphcache_serial_number.fetchAndAddRelaxed(1))
+{
+#ifdef QT_GL_TEXTURE_GLYPH_CACHE_DEBUG
+ qDebug(" -> QGLTextureGlyphCache() %p for context %p.", this, ctx);
+#endif
+ setContext(context);
+
+ m_vertexCoordinateArray[0] = -1.0f;
+ m_vertexCoordinateArray[1] = -1.0f;
+ m_vertexCoordinateArray[2] = 1.0f;
+ m_vertexCoordinateArray[3] = -1.0f;
+ m_vertexCoordinateArray[4] = 1.0f;
+ m_vertexCoordinateArray[5] = 1.0f;
+ m_vertexCoordinateArray[6] = -1.0f;
+ m_vertexCoordinateArray[7] = 1.0f;
+
+ m_textureCoordinateArray[0] = 0.0f;
+ m_textureCoordinateArray[1] = 0.0f;
+ m_textureCoordinateArray[2] = 1.0f;
+ m_textureCoordinateArray[3] = 0.0f;
+ m_textureCoordinateArray[4] = 1.0f;
+ m_textureCoordinateArray[5] = 1.0f;
+ m_textureCoordinateArray[6] = 0.0f;
+ m_textureCoordinateArray[7] = 1.0f;
+}
+
+QGLTextureGlyphCache::~QGLTextureGlyphCache()
+{
+#ifdef QT_GL_TEXTURE_GLYPH_CACHE_DEBUG
+ qDebug(" -> ~QGLTextureGlyphCache() %p.", this);
+#endif
+ delete m_blitProgram;
+}
+
+void QGLTextureGlyphCache::setContext(const QGLContext *context)
+{
+ ctx = context;
+ m_h = 0;
+}
+
+void QGLTextureGlyphCache::createTextureData(int width, int height)
+{
+ if (ctx == 0) {
+ qWarning("QGLTextureGlyphCache::createTextureData: Called with no context");
+ return;
+ }
+
+ // create in QImageTextureGlyphCache baseclass is meant to be called
+ // only to create the initial image and does not preserve the content,
+ // so we don't call when this function is called from resize.
+ if (ctx->d_ptr->workaround_brokenFBOReadBack && image().isNull())
+ QImageTextureGlyphCache::createTextureData(width, height);
+
+ // Make the lower glyph texture size 16 x 16.
+ if (width < 16)
+ width = 16;
+ if (height < 16)
+ height = 16;
+
+ QGLGlyphTexture *glyphTexture = m_textureResource.value(ctx);
+ glGenTextures(1, &glyphTexture->m_texture);
+ glBindTexture(GL_TEXTURE_2D, glyphTexture->m_texture);
+
+ glyphTexture->m_width = width;
+ glyphTexture->m_height = height;
+
+ if (m_type == QFontEngineGlyphCache::Raster_RGBMask) {
+ QVarLengthArray<uchar> data(width * height * 4);
+ for (int i = 0; i < data.size(); ++i)
+ data[i] = 0;
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, &data[0]);
+ } else {
+ QVarLengthArray<uchar> data(width * height);
+ for (int i = 0; i < data.size(); ++i)
+ data[i] = 0;
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, &data[0]);
+ }
+
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ m_filterMode = Nearest;
+}
+
+void QGLTextureGlyphCache::resizeTextureData(int width, int height)
+{
+ if (ctx == 0) {
+ qWarning("QGLTextureGlyphCache::resizeTextureData: Called with no context");
+ return;
+ }
+ QGLGlyphTexture *glyphTexture = m_textureResource.value(ctx);
+
+ int oldWidth = glyphTexture->m_width;
+ int oldHeight = glyphTexture->m_height;
+
+ // Make the lower glyph texture size 16 x 16.
+ if (width < 16)
+ width = 16;
+ if (height < 16)
+ height = 16;
+
+ GLuint oldTexture = glyphTexture->m_texture;
+ createTextureData(width, height);
+
+ if (ctx->d_ptr->workaround_brokenFBOReadBack) {
+ QImageTextureGlyphCache::resizeTextureData(width, height);
+ Q_ASSERT(image().depth() == 8);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, oldHeight, GL_ALPHA, GL_UNSIGNED_BYTE, image().constBits());
+ glDeleteTextures(1, &oldTexture);
+ return;
+ }
+
+ // ### the QTextureGlyphCache API needs to be reworked to allow
+ // ### resizeTextureData to fail
+
+ glBindFramebuffer(GL_FRAMEBUFFER_EXT, glyphTexture->m_fbo);
+
+ GLuint tmp_texture;
+ glGenTextures(1, &tmp_texture);
+ glBindTexture(GL_TEXTURE_2D, tmp_texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, oldWidth, oldHeight, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ m_filterMode = Nearest;
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_2D, tmp_texture, 0);
+
+ glActiveTexture(GL_TEXTURE0 + QT_IMAGE_TEXTURE_UNIT);
+ glBindTexture(GL_TEXTURE_2D, oldTexture);
+
+ if (pex != 0)
+ pex->transferMode(BrushDrawingMode);
+
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_BLEND);
+
+ glViewport(0, 0, oldWidth, oldHeight);
+
+ QGLShaderProgram *blitProgram = 0;
+ if (pex == 0) {
+ if (m_blitProgram == 0) {
+ m_blitProgram = new QGLShaderProgram(ctx);
+
+ {
+ QString source;
+ source.append(QLatin1String(qglslMainWithTexCoordsVertexShader));
+ source.append(QLatin1String(qglslUntransformedPositionVertexShader));
+
+ QGLShader *vertexShader = new QGLShader(QGLShader::Vertex, m_blitProgram);
+ vertexShader->compileSourceCode(source);
+
+ m_blitProgram->addShader(vertexShader);
+ }
+
+ {
+ QString source;
+ source.append(QLatin1String(qglslMainFragmentShader));
+ source.append(QLatin1String(qglslImageSrcFragmentShader));
+
+ QGLShader *fragmentShader = new QGLShader(QGLShader::Fragment, m_blitProgram);
+ fragmentShader->compileSourceCode(source);
+
+ m_blitProgram->addShader(fragmentShader);
+ }
+
+ m_blitProgram->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR);
+ m_blitProgram->bindAttributeLocation("textureCoordArray", QT_TEXTURE_COORDS_ATTR);
+
+ m_blitProgram->link();
+ }
+
+ glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, m_vertexCoordinateArray);
+ glVertexAttribPointer(QT_TEXTURE_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, m_textureCoordinateArray);
+
+ m_blitProgram->bind();
+ m_blitProgram->enableAttributeArray(int(QT_VERTEX_COORDS_ATTR));
+ m_blitProgram->enableAttributeArray(int(QT_TEXTURE_COORDS_ATTR));
+ m_blitProgram->disableAttributeArray(int(QT_OPACITY_ATTR));
+
+ blitProgram = m_blitProgram;
+
+ } else {
+ pex->setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, m_vertexCoordinateArray);
+ pex->setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, m_textureCoordinateArray);
+
+ pex->shaderManager->useBlitProgram();
+ blitProgram = pex->shaderManager->blitProgram();
+ }
+
+ blitProgram->setUniformValue("imageTexture", QT_IMAGE_TEXTURE_UNIT);
+
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+ glBindTexture(GL_TEXTURE_2D, glyphTexture->m_texture);
+
+ glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, oldWidth, oldHeight);
+
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_RENDERBUFFER_EXT, 0);
+ glDeleteTextures(1, &tmp_texture);
+ glDeleteTextures(1, &oldTexture);
+
+ glBindFramebuffer(GL_FRAMEBUFFER_EXT, ctx->d_ptr->current_fbo);
+
+ if (pex != 0) {
+ glViewport(0, 0, pex->width, pex->height);
+ pex->updateClipScissorTest();
+ }
+}
+
+void QGLTextureGlyphCache::fillTexture(const Coord &c, glyph_t glyph, QFixed subPixelPosition)
+{
+ if (ctx == 0) {
+ qWarning("QGLTextureGlyphCache::fillTexture: Called with no context");
+ return;
+ }
+
+ QGLGlyphTexture *glyphTexture = m_textureResource.value(ctx);
+ if (ctx->d_ptr->workaround_brokenFBOReadBack) {
+ QImageTextureGlyphCache::fillTexture(c, glyph, subPixelPosition);
+
+ glBindTexture(GL_TEXTURE_2D, glyphTexture->m_texture);
+ const QImage &texture = image();
+ const uchar *bits = texture.constBits();
+ bits += c.y * texture.bytesPerLine() + c.x;
+ for (int i=0; i<c.h; ++i) {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, c.x, c.y + i, c.w, 1, GL_ALPHA, GL_UNSIGNED_BYTE, bits);
+ bits += texture.bytesPerLine();
+ }
+ return;
+ }
+
+ QImage mask = textureMapForGlyph(glyph, subPixelPosition);
+ const int maskWidth = mask.width();
+ const int maskHeight = mask.height();
+
+ if (mask.format() == QImage::Format_Mono) {
+ mask = mask.convertToFormat(QImage::Format_Indexed8);
+ for (int y = 0; y < maskHeight; ++y) {
+ uchar *src = (uchar *) mask.scanLine(y);
+ for (int x = 0; x < maskWidth; ++x)
+ src[x] = -src[x]; // convert 0 and 1 into 0 and 255
+ }
+ } else if (mask.format() == QImage::Format_RGB32) {
+ // Make the alpha component equal to the average of the RGB values.
+ // This is needed when drawing sub-pixel antialiased text on translucent targets.
+ for (int y = 0; y < maskHeight; ++y) {
+ quint32 *src = (quint32 *) mask.scanLine(y);
+ for (int x = 0; x < maskWidth; ++x) {
+ uchar r = src[x] >> 16;
+ uchar g = src[x] >> 8;
+ uchar b = src[x];
+ quint32 avg = (quint32(r) + quint32(g) + quint32(b) + 1) / 3; // "+1" for rounding.
+ src[x] = (src[x] & 0x00ffffff) | (avg << 24);
+ }
+ }
+ }
+
+ glBindTexture(GL_TEXTURE_2D, glyphTexture->m_texture);
+ if (mask.format() == QImage::Format_RGB32) {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, c.x, c.y, maskWidth, maskHeight, GL_BGRA, GL_UNSIGNED_BYTE, mask.bits());
+ } else {
+ // glTexSubImage2D() might cause some garbage to appear in the texture if the mask width is
+ // not a multiple of four bytes. The bug appeared on a computer with 32-bit Windows Vista
+ // and nVidia GeForce 8500GT. GL_UNPACK_ALIGNMENT is set to four bytes, 'mask' has a
+ // multiple of four bytes per line, and most of the glyph shows up correctly in the
+ // texture, which makes me think that this is a driver bug.
+ // One workaround is to make sure the mask width is a multiple of four bytes, for instance
+ // by converting it to a format with four bytes per pixel. Another is to copy one line at a
+ // time.
+
+ if (!ctx->d_ptr->workaround_brokenAlphaTexSubImage_init) {
+ // don't know which driver versions exhibit this bug, so be conservative for now
+ const QByteArray versionString(reinterpret_cast<const char*>(glGetString(GL_VERSION)));
+ ctx->d_ptr->workaround_brokenAlphaTexSubImage = versionString.indexOf("NVIDIA") >= 0;
+ ctx->d_ptr->workaround_brokenAlphaTexSubImage_init = true;
+ }
+
+ if (ctx->d_ptr->workaround_brokenAlphaTexSubImage) {
+ for (int i = 0; i < maskHeight; ++i)
+ glTexSubImage2D(GL_TEXTURE_2D, 0, c.x, c.y + i, maskWidth, 1, GL_ALPHA, GL_UNSIGNED_BYTE, mask.scanLine(i));
+ } else {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, c.x, c.y, maskWidth, maskHeight, GL_ALPHA, GL_UNSIGNED_BYTE, mask.bits());
+ }
+ }
+}
+
+int QGLTextureGlyphCache::glyphPadding() const
+{
+ return 1;
+}
+
+int QGLTextureGlyphCache::maxTextureWidth() const
+{
+ if (ctx == 0)
+ return QImageTextureGlyphCache::maxTextureWidth();
+ else
+ return ctx->d_ptr->maxTextureSize();
+}
+
+int QGLTextureGlyphCache::maxTextureHeight() const
+{
+ if (ctx == 0)
+ return QImageTextureGlyphCache::maxTextureHeight();
+
+ if (ctx->d_ptr->workaround_brokenTexSubImage)
+ return qMin(1024, ctx->d_ptr->maxTextureSize());
+ else
+ return ctx->d_ptr->maxTextureSize();
+}
+
+void QGLTextureGlyphCache::clear()
+{
+ if (ctx != 0) {
+ m_textureResource.cleanup(ctx);
+
+ m_w = 0;
+ m_h = 0;
+ m_cx = 0;
+ m_cy = 0;
+ m_currentRowHeight = 0;
+ coords.clear();
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/opengl/gl2paintengineex/qtextureglyphcache_gl_p.h b/src/opengl/gl2paintengineex/qtextureglyphcache_gl_p.h
new file mode 100644
index 0000000000..133289e81c
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qtextureglyphcache_gl_p.h
@@ -0,0 +1,166 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTUREGLYPHCACHE_GL_P_H
+#define QTEXTUREGLYPHCACHE_GL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the QLibrary class. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtextureglyphcache_p.h>
+#include <private/qgl_p.h>
+#include <qglshaderprogram.h>
+
+// #define QT_GL_TEXTURE_GLYPH_CACHE_DEBUG
+
+QT_BEGIN_NAMESPACE
+
+class QGL2PaintEngineExPrivate;
+
+struct QGLGlyphTexture
+{
+ QGLGlyphTexture(const QGLContext *ctx)
+ : m_width(0)
+ , m_height(0)
+ {
+ if (ctx && !ctx->d_ptr->workaround_brokenFBOReadBack)
+ glGenFramebuffers(1, &m_fbo);
+
+#ifdef QT_GL_TEXTURE_GLYPH_CACHE_DEBUG
+ qDebug(" -> QGLGlyphTexture() %p for context %p.", this, ctx);
+#endif
+ }
+
+ ~QGLGlyphTexture() {
+ const QGLContext *ctx = QGLContext::currentContext();
+#ifdef QT_GL_TEXTURE_GLYPH_CACHE_DEBUG
+ qDebug("~QGLGlyphTexture() %p for context %p.", this, ctx);
+#endif
+ // At this point, the context group is made current, so it's safe to
+ // release resources without a makeCurrent() call
+ if (ctx) {
+ if (!ctx->d_ptr->workaround_brokenFBOReadBack)
+ glDeleteFramebuffers(1, &m_fbo);
+ if (m_width || m_height)
+ glDeleteTextures(1, &m_texture);
+ }
+ }
+
+ GLuint m_texture;
+ GLuint m_fbo;
+ int m_width;
+ int m_height;
+};
+
+class Q_OPENGL_EXPORT QGLTextureGlyphCache : public QImageTextureGlyphCache, public QGLContextGroupResourceBase
+{
+public:
+ QGLTextureGlyphCache(const QGLContext *context, QFontEngineGlyphCache::Type type, const QTransform &matrix);
+ ~QGLTextureGlyphCache();
+
+ virtual void createTextureData(int width, int height);
+ virtual void resizeTextureData(int width, int height);
+ virtual void fillTexture(const Coord &c, glyph_t glyph, QFixed subPixelPosition);
+ virtual int glyphPadding() const;
+ virtual int maxTextureWidth() const;
+ virtual int maxTextureHeight() const;
+
+ inline GLuint texture() const {
+ QGLTextureGlyphCache *that = const_cast<QGLTextureGlyphCache *>(this);
+ QGLGlyphTexture *glyphTexture = that->m_textureResource.value(ctx);
+ return glyphTexture ? glyphTexture->m_texture : 0;
+ }
+
+ inline int width() const {
+ QGLTextureGlyphCache *that = const_cast<QGLTextureGlyphCache *>(this);
+ QGLGlyphTexture *glyphTexture = that->m_textureResource.value(ctx);
+ return glyphTexture ? glyphTexture->m_width : 0;
+ }
+ inline int height() const {
+ QGLTextureGlyphCache *that = const_cast<QGLTextureGlyphCache *>(this);
+ QGLGlyphTexture *glyphTexture = that->m_textureResource.value(ctx);
+ return glyphTexture ? glyphTexture->m_height : 0;
+ }
+
+ inline void setPaintEnginePrivate(QGL2PaintEngineExPrivate *p) { pex = p; }
+
+ void setContext(const QGLContext *context);
+ inline const QGLContext *context() const { return ctx; }
+
+ inline int serialNumber() const { return m_serialNumber; }
+
+ enum FilterMode {
+ Nearest,
+ Linear
+ };
+ FilterMode filterMode() const { return m_filterMode; }
+ void setFilterMode(FilterMode m) { m_filterMode = m; }
+
+ void clear();
+
+ void freeResource(void *) { ctx = 0; }
+
+private:
+ QGLContextGroupResource<QGLGlyphTexture> m_textureResource;
+
+ const QGLContext *ctx;
+ QGL2PaintEngineExPrivate *pex;
+ QGLShaderProgram *m_blitProgram;
+ FilterMode m_filterMode;
+
+ GLfloat m_vertexCoordinateArray[8];
+ GLfloat m_textureCoordinateArray[8];
+
+ int m_serialNumber;
+};
+
+QT_END_NAMESPACE
+
+#endif
+
diff --git a/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp b/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp
new file mode 100644
index 0000000000..f4e170d928
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp
@@ -0,0 +1,588 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtriangulatingstroker_p.h"
+#include <qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+#define CURVE_FLATNESS Q_PI / 8
+
+
+
+
+void QTriangulatingStroker::endCapOrJoinClosed(const qreal *start, const qreal *cur,
+ bool implicitClose, bool endsAtStart)
+{
+ if (endsAtStart) {
+ join(start + 2);
+ } else if (implicitClose) {
+ join(start);
+ lineTo(start);
+ join(start+2);
+ } else {
+ endCap(cur);
+ }
+ int count = m_vertices.size();
+
+ // Copy the (x, y) values because QDataBuffer::add(const float& t)
+ // may resize the buffer, which will leave t pointing at the
+ // previous buffer's memory region if we don't copy first.
+ float x = m_vertices.at(count-2);
+ float y = m_vertices.at(count-1);
+ m_vertices.add(x);
+ m_vertices.add(y);
+}
+
+
+void QTriangulatingStroker::process(const QVectorPath &path, const QPen &pen, const QRectF &)
+{
+ const qreal *pts = path.points();
+ const QPainterPath::ElementType *types = path.elements();
+ int count = path.elementCount();
+ if (count < 2)
+ return;
+
+ float realWidth = qpen_widthf(pen);
+ if (realWidth == 0)
+ realWidth = 1;
+
+ m_width = realWidth / 2;
+
+ bool cosmetic = pen.isCosmetic();
+ if (cosmetic) {
+ m_width = m_width * m_inv_scale;
+ }
+
+ m_join_style = qpen_joinStyle(pen);
+ m_cap_style = qpen_capStyle(pen);
+ m_vertices.reset();
+ m_miter_limit = pen.miterLimit() * qpen_widthf(pen);
+
+ // The curvyness is based on the notion that I originally wanted
+ // roughly one line segment pr 4 pixels. This may seem little, but
+ // because we sample at constantly incrementing B(t) E [0<t<1], we
+ // will get longer segments where the curvature is small and smaller
+ // segments when the curvature is high.
+ //
+ // To get a rough idea of the length of each curve, I pretend that
+ // the curve is a 90 degree arc, whose radius is
+ // qMax(curveBounds.width, curveBounds.height). Based on this
+ // logic we can estimate the length of the outline edges based on
+ // the radius + a pen width and adjusting for scale factors
+ // depending on if the pen is cosmetic or not.
+ //
+ // The curvyness value of PI/14 was based on,
+ // arcLength = 2*PI*r/4 = PI*r/2 and splitting length into somewhere
+ // between 3 and 8 where 5 seemed to be give pretty good results
+ // hence: Q_PI/14. Lower divisors will give more detail at the
+ // direct cost of performance.
+
+ // simplfy pens that are thin in device size (2px wide or less)
+ if (realWidth < 2.5 && (cosmetic || m_inv_scale == 1)) {
+ if (m_cap_style == Qt::RoundCap)
+ m_cap_style = Qt::SquareCap;
+ if (m_join_style == Qt::RoundJoin)
+ m_join_style = Qt::MiterJoin;
+ m_curvyness_add = 0.5;
+ m_curvyness_mul = CURVE_FLATNESS / m_inv_scale;
+ m_roundness = 1;
+ } else if (cosmetic) {
+ m_curvyness_add = realWidth / 2;
+ m_curvyness_mul = CURVE_FLATNESS;
+ m_roundness = qMax<int>(4, realWidth * CURVE_FLATNESS);
+ } else {
+ m_curvyness_add = m_width;
+ m_curvyness_mul = CURVE_FLATNESS / m_inv_scale;
+ m_roundness = qMax<int>(4, realWidth * m_curvyness_mul);
+ }
+
+ // Over this level of segmentation, there doesn't seem to be any
+ // benefit, even for huge penWidth
+ if (m_roundness > 24)
+ m_roundness = 24;
+
+ m_sin_theta = qFastSin(Q_PI / m_roundness);
+ m_cos_theta = qFastCos(Q_PI / m_roundness);
+
+ const qreal *endPts = pts + (count<<1);
+ const qreal *startPts = 0;
+
+ Qt::PenCapStyle cap = m_cap_style;
+
+ if (!types) {
+ // skip duplicate points
+ while((pts + 2) < endPts && pts[0] == pts[2] && pts[1] == pts[3])
+ pts += 2;
+ if ((pts + 2) == endPts)
+ return;
+
+ startPts = pts;
+
+ bool endsAtStart = startPts[0] == *(endPts-2) && startPts[1] == *(endPts-1);
+
+ if (endsAtStart || path.hasImplicitClose())
+ m_cap_style = Qt::FlatCap;
+ moveTo(pts);
+ m_cap_style = cap;
+ pts += 2;
+ lineTo(pts);
+ pts += 2;
+ while (pts < endPts) {
+ if (m_cx != pts[0] || m_cy != pts[1]) {
+ join(pts);
+ lineTo(pts);
+ }
+ pts += 2;
+ }
+
+ endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart);
+
+ } else {
+ bool endsAtStart = false;
+ while (pts < endPts) {
+ switch (*types) {
+ case QPainterPath::MoveToElement: {
+ if (pts != path.points())
+ endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart);
+
+ startPts = pts;
+ int end = (endPts - pts) / 2;
+ int i = 2; // Start looking to ahead since we never have two moveto's in a row
+ while (i<end && types[i] != QPainterPath::MoveToElement) {
+ ++i;
+ }
+ endsAtStart = startPts[0] == pts[i*2 - 2] && startPts[1] == pts[i*2 - 1];
+ if (endsAtStart || path.hasImplicitClose())
+ m_cap_style = Qt::FlatCap;
+
+ moveTo(pts);
+ m_cap_style = cap;
+ pts+=2;
+ ++types;
+ break; }
+ case QPainterPath::LineToElement:
+ if (*(types - 1) != QPainterPath::MoveToElement)
+ join(pts);
+ lineTo(pts);
+ pts+=2;
+ ++types;
+ break;
+ case QPainterPath::CurveToElement:
+ if (*(types - 1) != QPainterPath::MoveToElement)
+ join(pts);
+ cubicTo(pts);
+ pts+=6;
+ types+=3;
+ break;
+ default:
+ Q_ASSERT(false);
+ break;
+ }
+ }
+
+ endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart);
+ }
+}
+
+void QTriangulatingStroker::moveTo(const qreal *pts)
+{
+ m_cx = pts[0];
+ m_cy = pts[1];
+
+ float x2 = pts[2];
+ float y2 = pts[3];
+ normalVector(m_cx, m_cy, x2, y2, &m_nvx, &m_nvy);
+
+
+ // To acheive jumps we insert zero-area tringles. This is done by
+ // adding two identical points in both the end of previous strip
+ // and beginning of next strip
+ bool invisibleJump = m_vertices.size();
+
+ switch (m_cap_style) {
+ case Qt::FlatCap:
+ if (invisibleJump) {
+ m_vertices.add(m_cx + m_nvx);
+ m_vertices.add(m_cy + m_nvy);
+ }
+ break;
+ case Qt::SquareCap: {
+ float sx = m_cx - m_nvy;
+ float sy = m_cy + m_nvx;
+ if (invisibleJump) {
+ m_vertices.add(sx + m_nvx);
+ m_vertices.add(sy + m_nvy);
+ }
+ emitLineSegment(sx, sy, m_nvx, m_nvy);
+ break; }
+ case Qt::RoundCap: {
+ QVarLengthArray<float> points;
+ arcPoints(m_cx, m_cy, m_cx + m_nvx, m_cy + m_nvy, m_cx - m_nvx, m_cy - m_nvy, points);
+ m_vertices.resize(m_vertices.size() + points.size() + 2 * int(invisibleJump));
+ int count = m_vertices.size();
+ int front = 0;
+ int end = points.size() / 2;
+ while (front != end) {
+ m_vertices.at(--count) = points[2 * end - 1];
+ m_vertices.at(--count) = points[2 * end - 2];
+ --end;
+ if (front == end)
+ break;
+ m_vertices.at(--count) = points[2 * front + 1];
+ m_vertices.at(--count) = points[2 * front + 0];
+ ++front;
+ }
+
+ if (invisibleJump) {
+ m_vertices.at(count - 1) = m_vertices.at(count + 1);
+ m_vertices.at(count - 2) = m_vertices.at(count + 0);
+ }
+ break; }
+ default: break; // ssssh gcc...
+ }
+ emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
+}
+
+void QTriangulatingStroker::cubicTo(const qreal *pts)
+{
+ const QPointF *p = (const QPointF *) pts;
+ QBezier bezier = QBezier::fromPoints(*(p - 1), p[0], p[1], p[2]);
+
+ QRectF bounds = bezier.bounds();
+ float rad = qMax(bounds.width(), bounds.height());
+ int threshold = qMin<float>(64, (rad + m_curvyness_add) * m_curvyness_mul);
+ if (threshold < 4)
+ threshold = 4;
+ qreal threshold_minus_1 = threshold - 1;
+ float vx, vy;
+
+ float cx = m_cx, cy = m_cy;
+ float x, y;
+
+ for (int i=1; i<threshold; ++i) {
+ qreal t = qreal(i) / threshold_minus_1;
+ QPointF p = bezier.pointAt(t);
+ x = p.x();
+ y = p.y();
+
+ normalVector(cx, cy, x, y, &vx, &vy);
+
+ emitLineSegment(x, y, vx, vy);
+
+ cx = x;
+ cy = y;
+ }
+
+ m_cx = cx;
+ m_cy = cy;
+
+ m_nvx = vx;
+ m_nvy = vy;
+}
+
+void QTriangulatingStroker::join(const qreal *pts)
+{
+ // Creates a join to the next segment (m_cx, m_cy) -> (pts[0], pts[1])
+ normalVector(m_cx, m_cy, pts[0], pts[1], &m_nvx, &m_nvy);
+
+ switch (m_join_style) {
+ case Qt::BevelJoin:
+ break;
+ case Qt::SvgMiterJoin:
+ case Qt::MiterJoin: {
+ // Find out on which side the join should be.
+ int count = m_vertices.size();
+ float prevNvx = m_vertices.at(count - 2) - m_cx;
+ float prevNvy = m_vertices.at(count - 1) - m_cy;
+ float xprod = prevNvx * m_nvy - prevNvy * m_nvx;
+ float px, py, qx, qy;
+
+ // If the segments are parallel, use bevel join.
+ if (qFuzzyIsNull(xprod))
+ break;
+
+ // Find the corners of the previous and next segment to join.
+ if (xprod < 0) {
+ px = m_vertices.at(count - 2);
+ py = m_vertices.at(count - 1);
+ qx = m_cx - m_nvx;
+ qy = m_cy - m_nvy;
+ } else {
+ px = m_vertices.at(count - 4);
+ py = m_vertices.at(count - 3);
+ qx = m_cx + m_nvx;
+ qy = m_cy + m_nvy;
+ }
+
+ // Find intersection point.
+ float pu = px * prevNvx + py * prevNvy;
+ float qv = qx * m_nvx + qy * m_nvy;
+ float ix = (m_nvy * pu - prevNvy * qv) / xprod;
+ float iy = (prevNvx * qv - m_nvx * pu) / xprod;
+
+ // Check that the distance to the intersection point is less than the miter limit.
+ if ((ix - px) * (ix - px) + (iy - py) * (iy - py) <= m_miter_limit * m_miter_limit) {
+ m_vertices.add(ix);
+ m_vertices.add(iy);
+ m_vertices.add(ix);
+ m_vertices.add(iy);
+ }
+ // else
+ // Do a plain bevel join if the miter limit is exceeded or if
+ // the lines are parallel. This is not what the raster
+ // engine's stroker does, but it is both faster and similar to
+ // what some other graphics API's do.
+
+ break; }
+ case Qt::RoundJoin: {
+ QVarLengthArray<float> points;
+ int count = m_vertices.size();
+ float prevNvx = m_vertices.at(count - 2) - m_cx;
+ float prevNvy = m_vertices.at(count - 1) - m_cy;
+ if (m_nvx * prevNvy - m_nvy * prevNvx < 0) {
+ arcPoints(0, 0, m_nvx, m_nvy, -prevNvx, -prevNvy, points);
+ for (int i = points.size() / 2; i > 0; --i)
+ emitLineSegment(m_cx, m_cy, points[2 * i - 2], points[2 * i - 1]);
+ } else {
+ arcPoints(0, 0, -prevNvx, -prevNvy, m_nvx, m_nvy, points);
+ for (int i = 0; i < points.size() / 2; ++i)
+ emitLineSegment(m_cx, m_cy, points[2 * i + 0], points[2 * i + 1]);
+ }
+ break; }
+ default: break; // gcc warn--
+ }
+
+ emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
+}
+
+void QTriangulatingStroker::endCap(const qreal *)
+{
+ switch (m_cap_style) {
+ case Qt::FlatCap:
+ break;
+ case Qt::SquareCap:
+ emitLineSegment(m_cx + m_nvy, m_cy - m_nvx, m_nvx, m_nvy);
+ break;
+ case Qt::RoundCap: {
+ QVarLengthArray<float> points;
+ int count = m_vertices.size();
+ arcPoints(m_cx, m_cy, m_vertices.at(count - 2), m_vertices.at(count - 1), m_vertices.at(count - 4), m_vertices.at(count - 3), points);
+ int front = 0;
+ int end = points.size() / 2;
+ while (front != end) {
+ m_vertices.add(points[2 * end - 2]);
+ m_vertices.add(points[2 * end - 1]);
+ --end;
+ if (front == end)
+ break;
+ m_vertices.add(points[2 * front + 0]);
+ m_vertices.add(points[2 * front + 1]);
+ ++front;
+ }
+ break; }
+ default: break; // to shut gcc up...
+ }
+}
+
+void QTriangulatingStroker::arcPoints(float cx, float cy, float fromX, float fromY, float toX, float toY, QVarLengthArray<float> &points)
+{
+ float dx1 = fromX - cx;
+ float dy1 = fromY - cy;
+ float dx2 = toX - cx;
+ float dy2 = toY - cy;
+
+ // while more than 180 degrees left:
+ while (dx1 * dy2 - dx2 * dy1 < 0) {
+ float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
+ float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
+ dx1 = tmpx;
+ dy1 = tmpy;
+ points.append(cx + dx1);
+ points.append(cy + dy1);
+ }
+
+ // while more than 90 degrees left:
+ while (dx1 * dx2 + dy1 * dy2 < 0) {
+ float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
+ float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
+ dx1 = tmpx;
+ dy1 = tmpy;
+ points.append(cx + dx1);
+ points.append(cy + dy1);
+ }
+
+ // while more than 0 degrees left:
+ while (dx1 * dy2 - dx2 * dy1 > 0) {
+ float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
+ float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
+ dx1 = tmpx;
+ dy1 = tmpy;
+ points.append(cx + dx1);
+ points.append(cy + dy1);
+ }
+
+ // remove last point which was rotated beyond [toX, toY].
+ if (!points.isEmpty())
+ points.resize(points.size() - 2);
+}
+
+static void qdashprocessor_moveTo(qreal x, qreal y, void *data)
+{
+ ((QDashedStrokeProcessor *) data)->addElement(QPainterPath::MoveToElement, x, y);
+}
+
+static void qdashprocessor_lineTo(qreal x, qreal y, void *data)
+{
+ ((QDashedStrokeProcessor *) data)->addElement(QPainterPath::LineToElement, x, y);
+}
+
+static void qdashprocessor_cubicTo(qreal, qreal, qreal, qreal, qreal, qreal, void *)
+{
+ Q_ASSERT(0); // The dasher should not produce curves...
+}
+
+QDashedStrokeProcessor::QDashedStrokeProcessor()
+ : m_points(0), m_types(0),
+ m_dash_stroker(0), m_inv_scale(1)
+{
+ m_dash_stroker.setMoveToHook(qdashprocessor_moveTo);
+ m_dash_stroker.setLineToHook(qdashprocessor_lineTo);
+ m_dash_stroker.setCubicToHook(qdashprocessor_cubicTo);
+}
+
+void QDashedStrokeProcessor::process(const QVectorPath &path, const QPen &pen, const QRectF &clip)
+{
+
+ const qreal *pts = path.points();
+ const QPainterPath::ElementType *types = path.elements();
+ int count = path.elementCount();
+
+ bool cosmetic = pen.isCosmetic();
+
+ m_points.reset();
+ m_types.reset();
+ m_points.reserve(path.elementCount());
+ m_types.reserve(path.elementCount());
+
+ qreal width = qpen_widthf(pen);
+ if (width == 0)
+ width = 1;
+
+ m_dash_stroker.setDashPattern(pen.dashPattern());
+ m_dash_stroker.setStrokeWidth(cosmetic ? width * m_inv_scale : width);
+ m_dash_stroker.setDashOffset(pen.dashOffset());
+ m_dash_stroker.setMiterLimit(pen.miterLimit());
+ m_dash_stroker.setClipRect(clip);
+
+ float curvynessAdd, curvynessMul, roundness = 0;
+
+ // simplfy pens that are thin in device size (2px wide or less)
+ if (width < 2.5 && (cosmetic || m_inv_scale == 1)) {
+ curvynessAdd = 0.5;
+ curvynessMul = CURVE_FLATNESS / m_inv_scale;
+ roundness = 1;
+ } else if (cosmetic) {
+ curvynessAdd= width / 2;
+ curvynessMul= CURVE_FLATNESS;
+ roundness = qMax<int>(4, width * CURVE_FLATNESS);
+ } else {
+ curvynessAdd = width * m_inv_scale;
+ curvynessMul = CURVE_FLATNESS / m_inv_scale;
+ roundness = qMax<int>(4, width * curvynessMul);
+ }
+
+ if (count < 2)
+ return;
+
+ const qreal *endPts = pts + (count<<1);
+
+ m_dash_stroker.begin(this);
+
+ if (!types) {
+ m_dash_stroker.moveTo(pts[0], pts[1]);
+ pts += 2;
+ while (pts < endPts) {
+ m_dash_stroker.lineTo(pts[0], pts[1]);
+ pts += 2;
+ }
+ } else {
+ while (pts < endPts) {
+ switch (*types) {
+ case QPainterPath::MoveToElement:
+ m_dash_stroker.moveTo(pts[0], pts[1]);
+ pts += 2;
+ ++types;
+ break;
+ case QPainterPath::LineToElement:
+ m_dash_stroker.lineTo(pts[0], pts[1]);
+ pts += 2;
+ ++types;
+ break;
+ case QPainterPath::CurveToElement: {
+ QBezier b = QBezier::fromPoints(*(((const QPointF *) pts) - 1),
+ *(((const QPointF *) pts)),
+ *(((const QPointF *) pts) + 1),
+ *(((const QPointF *) pts) + 2));
+ QRectF bounds = b.bounds();
+ float rad = qMax(bounds.width(), bounds.height());
+ int threshold = qMin<float>(64, (rad + curvynessAdd) * curvynessMul);
+ if (threshold < 4)
+ threshold = 4;
+
+ qreal threshold_minus_1 = threshold - 1;
+ for (int i=0; i<threshold; ++i) {
+ QPointF pt = b.pointAt(i / threshold_minus_1);
+ m_dash_stroker.lineTo(pt.x(), pt.y());
+ }
+ pts += 6;
+ types += 3;
+ break; }
+ default: break;
+ }
+ }
+ }
+
+ m_dash_stroker.end();
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/opengl/gl2paintengineex/qtriangulatingstroker_p.h b/src/opengl/gl2paintengineex/qtriangulatingstroker_p.h
new file mode 100644
index 0000000000..adb5d5264d
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qtriangulatingstroker_p.h
@@ -0,0 +1,157 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTRIANGULATINGSTROKER_P_H
+#define QTRIANGULATINGSTROKER_P_H
+
+#include <private/qdatabuffer_p.h>
+#include <qvarlengtharray.h>
+#include <private/qvectorpath_p.h>
+#include <private/qbezier_p.h>
+#include <private/qnumeric_p.h>
+#include <private/qmath_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QTriangulatingStroker
+{
+public:
+ QTriangulatingStroker() : m_vertices(0) {}
+ void process(const QVectorPath &path, const QPen &pen, const QRectF &clip);
+
+ inline int vertexCount() const { return m_vertices.size(); }
+ inline const float *vertices() const { return m_vertices.data(); }
+
+ inline void setInvScale(qreal invScale) { m_inv_scale = invScale; }
+
+private:
+ inline void emitLineSegment(float x, float y, float nx, float ny);
+ void moveTo(const qreal *pts);
+ inline void lineTo(const qreal *pts);
+ void cubicTo(const qreal *pts);
+ void join(const qreal *pts);
+ inline void normalVector(float x1, float y1, float x2, float y2, float *nx, float *ny);
+ void endCap(const qreal *pts);
+ void arcPoints(float cx, float cy, float fromX, float fromY, float toX, float toY, QVarLengthArray<float> &points);
+ void endCapOrJoinClosed(const qreal *start, const qreal *cur, bool implicitClose, bool endsAtStart);
+
+
+ QDataBuffer<float> m_vertices;
+
+ float m_cx, m_cy; // current points
+ float m_nvx, m_nvy; // normal vector...
+ float m_width;
+ qreal m_miter_limit;
+
+ int m_roundness; // Number of line segments in a round join
+ qreal m_sin_theta; // sin(m_roundness / 360);
+ qreal m_cos_theta; // cos(m_roundness / 360);
+ qreal m_inv_scale;
+ float m_curvyness_mul;
+ float m_curvyness_add;
+
+ Qt::PenJoinStyle m_join_style;
+ Qt::PenCapStyle m_cap_style;
+};
+
+class QDashedStrokeProcessor
+{
+public:
+ QDashedStrokeProcessor();
+
+ void process(const QVectorPath &path, const QPen &pen, const QRectF &clip);
+
+ inline void addElement(QPainterPath::ElementType type, qreal x, qreal y) {
+ m_points.add(x);
+ m_points.add(y);
+ m_types.add(type);
+ }
+
+ inline int elementCount() const { return m_types.size(); }
+ inline qreal *points() const { return m_points.data(); }
+ inline QPainterPath::ElementType *elementTypes() const { return m_types.data(); }
+
+ inline void setInvScale(qreal invScale) { m_inv_scale = invScale; }
+
+private:
+ QDataBuffer<qreal> m_points;
+ QDataBuffer<QPainterPath::ElementType> m_types;
+ QDashStroker m_dash_stroker;
+ qreal m_inv_scale;
+};
+
+inline void QTriangulatingStroker::normalVector(float x1, float y1, float x2, float y2,
+ float *nx, float *ny)
+{
+ float dx = x2 - x1;
+ float dy = y2 - y1;
+
+ float pw;
+
+ if (dx == 0)
+ pw = m_width / qAbs(dy);
+ else if (dy == 0)
+ pw = m_width / qAbs(dx);
+ else
+ pw = m_width / sqrt(dx*dx + dy*dy);
+
+ *nx = -dy * pw;
+ *ny = dx * pw;
+}
+
+inline void QTriangulatingStroker::emitLineSegment(float x, float y, float vx, float vy)
+{
+ m_vertices.add(x + vx);
+ m_vertices.add(y + vy);
+ m_vertices.add(x - vx);
+ m_vertices.add(y - vy);
+}
+
+void QTriangulatingStroker::lineTo(const qreal *pts)
+{
+ emitLineSegment(pts[0], pts[1], m_nvx, m_nvy);
+ m_cx = pts[0];
+ m_cy = pts[1];
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/opengl/gl2paintengineex/qtriangulator.cpp b/src/opengl/gl2paintengineex/qtriangulator.cpp
new file mode 100644
index 0000000000..94024f3d74
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qtriangulator.cpp
@@ -0,0 +1,3114 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtriangulator_p.h"
+
+#include <QtGui/qdialog.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qpainterpath.h>
+#include <QtGui/private/qbezier_p.h>
+#include <QtGui/private/qdatabuffer_p.h>
+#include <QtCore/qbitarray.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtCore/qqueue.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qalgorithms.h>
+#include <QtDebug>
+
+#include <math.h>
+
+#include <private/qgl_p.h>
+
+QT_BEGIN_NAMESPACE
+
+//#define Q_TRIANGULATOR_DEBUG
+
+#define Q_FIXED_POINT_SCALE 32
+
+// Quick sort.
+template <class T, class LessThan>
+#ifdef Q_CC_RVCT // RVCT 2.2 doesn't see recursive _static_ template function
+void sort(T *array, int count, LessThan lessThan)
+#else
+static void sort(T *array, int count, LessThan lessThan)
+#endif
+{
+ // If the number of elements fall below some threshold, use insertion sort.
+ const int INSERTION_SORT_LIMIT = 7; // About 7 is fastest on my computer...
+ if (count <= INSERTION_SORT_LIMIT) {
+ for (int i = 1; i < count; ++i) {
+ T temp = array[i];
+ int j = i;
+ while (j > 0 && lessThan(temp, array[j - 1])) {
+ array[j] = array[j - 1];
+ --j;
+ }
+ array[j] = temp;
+ }
+ return;
+ }
+
+ int high = count - 1;
+ int low = 0;
+ int mid = high / 2;
+ if (lessThan(array[mid], array[low]))
+ qSwap(array[mid], array[low]);
+ if (lessThan(array[high], array[mid]))
+ qSwap(array[high], array[mid]);
+ if (lessThan(array[mid], array[low]))
+ qSwap(array[mid], array[low]);
+
+ --high;
+ ++low;
+ qSwap(array[mid], array[high]);
+ int pivot = high;
+ --high;
+
+ while (low <= high) {
+ while (!lessThan(array[pivot], array[low])) {
+ ++low;
+ if (low > high)
+ goto sort_loop_end;
+ }
+ while (!lessThan(array[high], array[pivot])) {
+ --high;
+ if (low > high)
+ goto sort_loop_end;
+ }
+ qSwap(array[low], array[high]);
+ ++low;
+ --high;
+ }
+sort_loop_end:
+ if (low != pivot)
+ qSwap(array[pivot], array[low]);
+ sort(array, low, lessThan);
+ sort(array + low + 1, count - low - 1, lessThan);
+}
+
+// Quick sort.
+template <class T>
+#ifdef Q_CC_RVCT
+void sort(T *array, int count) // RVCT 2.2 doesn't see recursive _static_ template function
+#else
+static void sort(T *array, int count)
+#endif
+{
+ // If the number of elements fall below some threshold, use insertion sort.
+ const int INSERTION_SORT_LIMIT = 25; // About 25 is fastest on my computer...
+ if (count <= INSERTION_SORT_LIMIT) {
+ for (int i = 1; i < count; ++i) {
+ T temp = array[i];
+ int j = i;
+ while (j > 0 && (temp < array[j - 1])) {
+ array[j] = array[j - 1];
+ --j;
+ }
+ array[j] = temp;
+ }
+ return;
+ }
+
+ int high = count - 1;
+ int low = 0;
+ int mid = high / 2;
+ if ((array[mid] < array[low]))
+ qSwap(array[mid], array[low]);
+ if ((array[high] < array[mid]))
+ qSwap(array[high], array[mid]);
+ if ((array[mid] < array[low]))
+ qSwap(array[mid], array[low]);
+
+ --high;
+ ++low;
+ qSwap(array[mid], array[high]);
+ int pivot = high;
+ --high;
+
+ while (low <= high) {
+ while (!(array[pivot] < array[low])) {
+ ++low;
+ if (low > high)
+ goto sort_loop_end;
+ }
+ while (!(array[high] < array[pivot])) {
+ --high;
+ if (low > high)
+ goto sort_loop_end;
+ }
+ qSwap(array[low], array[high]);
+ ++low;
+ --high;
+ }
+sort_loop_end:
+ if (low != pivot)
+ qSwap(array[pivot], array[low]);
+ sort(array, low);
+ sort(array + low + 1, count - low - 1);
+}
+
+template<typename T>
+struct QVertexSet
+{
+ inline QVertexSet() { }
+ inline QVertexSet(const QVertexSet<T> &other) : vertices(other.vertices), indices(other.indices) { }
+ QVertexSet<T> &operator = (const QVertexSet<T> &other) {vertices = other.vertices; indices = other.indices; return *this;}
+
+ // The vertices of a triangle are given by: (x[i[n]], y[i[n]]), (x[j[n]], y[j[n]]), (x[k[n]], y[k[n]]), n = 0, 1, ...
+ QVector<qreal> vertices; // [x[0], y[0], x[1], y[1], x[2], ...]
+ QVector<T> indices; // [i[0], j[0], k[0], i[1], j[1], k[1], i[2], ...]
+};
+
+//============================================================================//
+// QFraction //
+//============================================================================//
+
+// Fraction must be in the range [0, 1)
+struct QFraction
+{
+ // Comparison operators must not be called on invalid fractions.
+ inline bool operator < (const QFraction &other) const;
+ inline bool operator == (const QFraction &other) const;
+ inline bool operator != (const QFraction &other) const {return !(*this == other);}
+ inline bool operator > (const QFraction &other) const {return other < *this;}
+ inline bool operator >= (const QFraction &other) const {return !(*this < other);}
+ inline bool operator <= (const QFraction &other) const {return !(*this > other);}
+
+ inline bool isValid() const {return denominator != 0;}
+
+ // numerator and denominator must not have common denominators.
+ quint64 numerator, denominator;
+};
+
+static inline quint64 gcd(quint64 x, quint64 y)
+{
+ while (y != 0) {
+ quint64 z = y;
+ y = x % y;
+ x = z;
+ }
+ return x;
+}
+
+static inline int compare(quint64 a, quint64 b)
+{
+ return (a > b) - (a < b);
+}
+
+// Compare a/b with c/d.
+// Return negative if less, 0 if equal, positive if greater.
+// a < b, c < d
+static int qCompareFractions(quint64 a, quint64 b, quint64 c, quint64 d)
+{
+ const quint64 LIMIT = Q_UINT64_C(0x100000000);
+ for (;;) {
+ // If the products 'ad' and 'bc' fit into 64 bits, they can be directly compared.
+ if (b < LIMIT && d < LIMIT)
+ return compare(a * d, b * c);
+
+ if (a == 0 || c == 0)
+ return compare(a, c);
+
+ // a/b < c/d <=> d/c < b/a
+ quint64 b_div_a = b / a;
+ quint64 d_div_c = d / c;
+ if (b_div_a != d_div_c)
+ return compare(d_div_c, b_div_a);
+
+ // floor(d/c) == floor(b/a)
+ // frac(d/c) < frac(b/a) ?
+ // frac(x/y) = (x%y)/y
+ d -= d_div_c * c; //d %= c;
+ b -= b_div_a * a; //b %= a;
+ qSwap(a, d);
+ qSwap(b, c);
+ }
+}
+
+// Fraction must be in the range [0, 1)
+// Assume input is valid.
+static QFraction qFraction(quint64 n, quint64 d) {
+ QFraction result;
+ if (n == 0) {
+ result.numerator = 0;
+ result.denominator = 1;
+ } else {
+ quint64 g = gcd(n, d);
+ result.numerator = n / g;
+ result.denominator = d / g;
+ }
+ return result;
+}
+
+inline bool QFraction::operator < (const QFraction &other) const
+{
+ return qCompareFractions(numerator, denominator, other.numerator, other.denominator) < 0;
+}
+
+inline bool QFraction::operator == (const QFraction &other) const
+{
+ return numerator == other.numerator && denominator == other.denominator;
+}
+
+//============================================================================//
+// QPodPoint //
+//============================================================================//
+
+struct QPodPoint
+{
+ inline bool operator < (const QPodPoint &other) const
+ {
+ if (y != other.y)
+ return y < other.y;
+ return x < other.x;
+ }
+
+ inline bool operator > (const QPodPoint &other) const {return other < *this;}
+ inline bool operator <= (const QPodPoint &other) const {return !(*this > other);}
+ inline bool operator >= (const QPodPoint &other) const {return !(*this < other);}
+ inline bool operator == (const QPodPoint &other) const {return x == other.x && y == other.y;}
+ inline bool operator != (const QPodPoint &other) const {return x != other.x || y != other.y;}
+
+ inline QPodPoint &operator += (const QPodPoint &other) {x += other.x; y += other.y; return *this;}
+ inline QPodPoint &operator -= (const QPodPoint &other) {x -= other.x; y -= other.y; return *this;}
+ inline QPodPoint operator + (const QPodPoint &other) const {QPodPoint result = {x + other.x, y + other.y}; return result;}
+ inline QPodPoint operator - (const QPodPoint &other) const {QPodPoint result = {x - other.x, y - other.y}; return result;}
+
+ int x;
+ int y;
+};
+
+static inline qint64 qCross(const QPodPoint &u, const QPodPoint &v)
+{
+ return qint64(u.x) * qint64(v.y) - qint64(u.y) * qint64(v.x);
+}
+
+static inline qint64 qDot(const QPodPoint &u, const QPodPoint &v)
+{
+ return qint64(u.x) * qint64(v.x) + qint64(u.y) * qint64(v.y);
+}
+
+// Return positive value if 'p' is to the right of the line 'v1'->'v2', negative if left of the
+// line and zero if exactly on the line.
+// The returned value is the z-component of the qCross product between 'v2-v1' and 'p-v1',
+// which is twice the signed area of the triangle 'p'->'v1'->'v2' (positive for CW order).
+static inline qint64 qPointDistanceFromLine(const QPodPoint &p, const QPodPoint &v1, const QPodPoint &v2)
+{
+ return qCross(v2 - v1, p - v1);
+}
+
+static inline bool qPointIsLeftOfLine(const QPodPoint &p, const QPodPoint &v1, const QPodPoint &v2)
+{
+ return QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(p, v1, v2) < 0;
+}
+
+// Return:
+// -1 if u < v
+// 0 if u == v
+// 1 if u > v
+static int comparePoints(const QPodPoint &u, const QPodPoint &v)
+{
+ if (u.y < v.y)
+ return -1;
+ if (u.y > v.y)
+ return 1;
+ if (u.x < v.x)
+ return -1;
+ if (u.x > v.x)
+ return 1;
+ return 0;
+}
+
+//============================================================================//
+// QIntersectionPoint //
+//============================================================================//
+
+struct QIntersectionPoint
+{
+ inline bool isValid() const {return xOffset.isValid() && yOffset.isValid();}
+ QPodPoint round() const;
+ inline bool isAccurate() const {return xOffset.numerator == 0 && yOffset.numerator == 0;}
+ bool operator < (const QIntersectionPoint &other) const;
+ bool operator == (const QIntersectionPoint &other) const;
+ inline bool operator != (const QIntersectionPoint &other) const {return !(*this == other);}
+ inline bool operator > (const QIntersectionPoint &other) const {return other < *this;}
+ inline bool operator >= (const QIntersectionPoint &other) const {return !(*this < other);}
+ inline bool operator <= (const QIntersectionPoint &other) const {return !(*this > other);}
+ bool isOnLine(const QPodPoint &u, const QPodPoint &v) const;
+
+ QPodPoint upperLeft;
+ QFraction xOffset;
+ QFraction yOffset;
+};
+
+static inline QIntersectionPoint qIntersectionPoint(const QPodPoint &point)
+{
+ // upperLeft = point, xOffset = 0/1, yOffset = 0/1.
+ QIntersectionPoint p = {{point.x, point.y}, {0, 1}, {0, 1}};
+ return p;
+}
+
+static inline QIntersectionPoint qIntersectionPoint(int x, int y)
+{
+ // upperLeft = (x, y), xOffset = 0/1, yOffset = 0/1.
+ QIntersectionPoint p = {{x, y}, {0, 1}, {0, 1}};
+ return p;
+}
+
+static QIntersectionPoint qIntersectionPoint(const QPodPoint &u1, const QPodPoint &u2, const QPodPoint &v1, const QPodPoint &v2)
+{
+ QIntersectionPoint result = {{0, 0}, {0, 0}, {0, 0}};
+
+ QPodPoint u = u2 - u1;
+ QPodPoint v = v2 - v1;
+ qint64 d1 = qCross(u, v1 - u1);
+ qint64 d2 = qCross(u, v2 - u1);
+ qint64 det = d2 - d1;
+ qint64 d3 = qCross(v, u1 - v1);
+ qint64 d4 = d3 - det; //qCross(v, u2 - v1);
+
+ // Check that the math is correct.
+ Q_ASSERT(d4 == qCross(v, u2 - v1));
+
+ // The intersection point can be expressed as:
+ // v1 - v * d1/det
+ // v2 - v * d2/det
+ // u1 + u * d3/det
+ // u2 + u * d4/det
+
+ // I'm only interested in lines that are crossing, so ignore parallel lines even if they overlap.
+ if (det == 0)
+ return result;
+
+ if (det < 0) {
+ det = -det;
+ d1 = -d1;
+ d2 = -d2;
+ d3 = -d3;
+ d4 = -d4;
+ }
+
+ // I'm only interested in lines intersecting at their interior, not at their end points.
+ // The lines intersect at their interior if and only if 'd1 < 0', 'd2 > 0', 'd3 < 0' and 'd4 > 0'.
+ if (d1 >= 0 || d2 <= 0 || d3 <= 0 || d4 >= 0)
+ return result;
+
+ // Calculate the intersection point as follows:
+ // v1 - v * d1/det | v1 <= v2 (component-wise)
+ // v2 - v * d2/det | v2 < v1 (component-wise)
+
+ // Assuming 21 bits per vector component.
+ // TODO: Make code path for 31 bits per vector component.
+ if (v.x >= 0) {
+ result.upperLeft.x = v1.x + (-v.x * d1) / det;
+ result.xOffset = qFraction(quint64(-v.x * d1) % quint64(det), quint64(det));
+ } else {
+ result.upperLeft.x = v2.x + (-v.x * d2) / det;
+ result.xOffset = qFraction(quint64(-v.x * d2) % quint64(det), quint64(det));
+ }
+
+ if (v.y >= 0) {
+ result.upperLeft.y = v1.y + (-v.y * d1) / det;
+ result.yOffset = qFraction(quint64(-v.y * d1) % quint64(det), quint64(det));
+ } else {
+ result.upperLeft.y = v2.y + (-v.y * d2) / det;
+ result.yOffset = qFraction(quint64(-v.y * d2) % quint64(det), quint64(det));
+ }
+
+ Q_ASSERT(result.xOffset.isValid());
+ Q_ASSERT(result.yOffset.isValid());
+ return result;
+}
+
+QPodPoint QIntersectionPoint::round() const
+{
+ QPodPoint result = upperLeft;
+ if (2 * xOffset.numerator >= xOffset.denominator)
+ ++result.x;
+ if (2 * yOffset.numerator >= yOffset.denominator)
+ ++result.y;
+ return result;
+}
+
+bool QIntersectionPoint::operator < (const QIntersectionPoint &other) const
+{
+ if (upperLeft.y != other.upperLeft.y)
+ return upperLeft.y < other.upperLeft.y;
+ if (yOffset != other.yOffset)
+ return yOffset < other.yOffset;
+ if (upperLeft.x != other.upperLeft.x)
+ return upperLeft.x < other.upperLeft.x;
+ return xOffset < other.xOffset;
+}
+
+bool QIntersectionPoint::operator == (const QIntersectionPoint &other) const
+{
+ return upperLeft == other.upperLeft && xOffset == other.xOffset && yOffset == other.yOffset;
+}
+
+// Returns true if this point is on the infinite line passing through 'u' and 'v'.
+bool QIntersectionPoint::isOnLine(const QPodPoint &u, const QPodPoint &v) const
+{
+ // TODO: Make code path for coordinates with more than 21 bits.
+ const QPodPoint p = upperLeft - u;
+ const QPodPoint q = v - u;
+ bool isHorizontal = p.y == 0 && yOffset.numerator == 0;
+ bool isVertical = p.x == 0 && xOffset.numerator == 0;
+ if (isHorizontal && isVertical)
+ return true;
+ if (isHorizontal)
+ return q.y == 0;
+ if (q.y == 0)
+ return false;
+ if (isVertical)
+ return q.x == 0;
+ if (q.x == 0)
+ return false;
+
+ // At this point, 'p+offset' and 'q' cannot lie on the x or y axis.
+
+ if (((q.x < 0) == (q.y < 0)) != ((p.x < 0) == (p.y < 0)))
+ return false; // 'p + offset' and 'q' pass through different quadrants.
+
+ // Move all coordinates into the first quadrant.
+ quint64 nx, ny;
+ if (p.x < 0)
+ nx = quint64(-p.x) * xOffset.denominator - xOffset.numerator;
+ else
+ nx = quint64(p.x) * xOffset.denominator + xOffset.numerator;
+ if (p.y < 0)
+ ny = quint64(-p.y) * yOffset.denominator - yOffset.numerator;
+ else
+ ny = quint64(p.y) * yOffset.denominator + yOffset.numerator;
+
+ return qFraction(quint64(qAbs(q.x)) * xOffset.denominator, quint64(qAbs(q.y)) * yOffset.denominator) == qFraction(nx, ny);
+}
+
+//============================================================================//
+// QMaxHeap //
+//============================================================================//
+
+template <class T>
+class QMaxHeap
+{
+public:
+ QMaxHeap() : m_data(0) {}
+ inline int size() const {return m_data.size();}
+ inline bool empty() const {return m_data.isEmpty();}
+ inline bool isEmpty() const {return m_data.isEmpty();}
+ void push(const T &x);
+ T pop();
+ inline const T &top() const {return m_data.first();}
+private:
+ static inline int parent(int i) {return (i - 1) / 2;}
+ static inline int left(int i) {return 2 * i + 1;}
+ static inline int right(int i) {return 2 * i + 2;}
+
+ QDataBuffer<T> m_data;
+};
+
+template <class T>
+void QMaxHeap<T>::push(const T &x)
+{
+ int current = m_data.size();
+ int parent = QMaxHeap::parent(current);
+ m_data.add(x);
+ while (current != 0 && m_data.at(parent) < x) {
+ m_data.at(current) = m_data.at(parent);
+ current = parent;
+ parent = QMaxHeap::parent(current);
+ }
+ m_data.at(current) = x;
+}
+
+template <class T>
+T QMaxHeap<T>::pop()
+{
+ T result = m_data.first();
+ T back = m_data.last();
+ m_data.pop_back();
+ if (!m_data.isEmpty()) {
+ int current = 0;
+ for (;;) {
+ int left = QMaxHeap::left(current);
+ int right = QMaxHeap::right(current);
+ if (left >= m_data.size())
+ break;
+ int greater = left;
+ if (right < m_data.size() && m_data.at(left) < m_data.at(right))
+ greater = right;
+ if (m_data.at(greater) < back)
+ break;
+ m_data.at(current) = m_data.at(greater);
+ current = greater;
+ }
+ m_data.at(current) = back;
+ }
+ return result;
+}
+
+//============================================================================//
+// QRBTree //
+//============================================================================//
+
+template <class T>
+struct QRBTree
+{
+ struct Node
+ {
+ inline Node() : parent(0), left(0), right(0), red(true) { }
+ inline ~Node() {if (left) delete left; if (right) delete right;}
+ T data;
+ Node *parent;
+ Node *left;
+ Node *right;
+ bool red;
+ };
+
+ inline QRBTree() : root(0), freeList(0) { }
+ inline ~QRBTree();
+
+ inline void clear();
+
+ void attachBefore(Node *parent, Node *child);
+ void attachAfter(Node *parent, Node *child);
+
+ inline Node *front(Node *node) const;
+ inline Node *back(Node *node) const;
+ Node *next(Node *node) const;
+ Node *previous(Node *node) const;
+
+ inline void deleteNode(Node *&node);
+ inline Node *newNode();
+
+ // Return 1 if 'left' comes after 'right', 0 if equal, and -1 otherwise.
+ // 'left' and 'right' cannot be null.
+ int order(Node *left, Node *right);
+ inline bool validate() const;
+
+private:
+ void rotateLeft(Node *node);
+ void rotateRight(Node *node);
+ void update(Node *node);
+
+ inline void attachLeft(Node *parent, Node *child);
+ inline void attachRight(Node *parent, Node *child);
+
+ int blackDepth(Node *top) const;
+ bool checkRedBlackProperty(Node *top) const;
+
+ void swapNodes(Node *n1, Node *n2);
+ void detach(Node *node);
+
+ // 'node' must be black. rebalance will reduce the depth of black nodes by one in the sibling tree.
+ void rebalance(Node *node);
+
+public:
+ Node *root;
+private:
+ Node *freeList;
+};
+
+template <class T>
+inline QRBTree<T>::~QRBTree()
+{
+ clear();
+ while (freeList) {
+ // Avoid recursively calling the destructor, as this list may become large.
+ Node *next = freeList->right;
+ freeList->right = 0;
+ delete freeList;
+ freeList = next;
+ }
+}
+
+template <class T>
+inline void QRBTree<T>::clear()
+{
+ if (root)
+ delete root;
+ root = 0;
+}
+
+template <class T>
+void QRBTree<T>::rotateLeft(Node *node)
+{
+ // | | //
+ // N B //
+ // / \ / \ //
+ // A B ---> N D //
+ // / \ / \ //
+ // C D A C //
+
+ Node *&ref = (node->parent ? (node == node->parent->left ? node->parent->left : node->parent->right) : root);
+ ref = node->right;
+ node->right->parent = node->parent;
+
+ // : //
+ // N //
+ // / :| //
+ // A B //
+ // / \ //
+ // C D //
+
+ node->right = ref->left;
+ if (ref->left)
+ ref->left->parent = node;
+
+ // : | //
+ // N B //
+ // / \ : \ //
+ // A C D //
+
+ ref->left = node;
+ node->parent = ref;
+
+ // | //
+ // B //
+ // / \ //
+ // N D //
+ // / \ //
+ // A C //
+}
+
+template <class T>
+void QRBTree<T>::rotateRight(Node *node)
+{
+ // | | //
+ // N A //
+ // / \ / \ //
+ // A B ---> C N //
+ // / \ / \ //
+ // C D D B //
+
+ Node *&ref = (node->parent ? (node == node->parent->left ? node->parent->left : node->parent->right) : root);
+ ref = node->left;
+ node->left->parent = node->parent;
+
+ node->left = ref->right;
+ if (ref->right)
+ ref->right->parent = node;
+
+ ref->right = node;
+ node->parent = ref;
+}
+
+template <class T>
+void QRBTree<T>::update(Node *node) // call this after inserting a node
+{
+ for (;;) {
+ Node *parent = node->parent;
+
+ // if the node is the root, color it black
+ if (!parent) {
+ node->red = false;
+ return;
+ }
+
+ // if the parent is black, the node can be left red
+ if (!parent->red)
+ return;
+
+ // at this point, the parent is red and cannot be the root
+ Node *grandpa = parent->parent;
+ Q_ASSERT(grandpa);
+
+ Node *uncle = (parent == grandpa->left ? grandpa->right : grandpa->left);
+ if (uncle && uncle->red) {
+ // grandpa's black, parent and uncle are red.
+ // let parent and uncle be black, grandpa red and recursively update grandpa.
+ Q_ASSERT(!grandpa->red);
+ parent->red = false;
+ uncle->red = false;
+ grandpa->red = true;
+ node = grandpa;
+ continue;
+ }
+
+ // at this point, uncle is black
+ if (node == parent->right && parent == grandpa->left)
+ rotateLeft(node = parent);
+ else if (node == parent->left && parent == grandpa->right)
+ rotateRight(node = parent);
+ parent = node->parent;
+
+ if (parent == grandpa->left) {
+ rotateRight(grandpa);
+ parent->red = false;
+ grandpa->red = true;
+ } else {
+ rotateLeft(grandpa);
+ parent->red = false;
+ grandpa->red = true;
+ }
+ return;
+ }
+}
+
+template <class T>
+inline void QRBTree<T>::attachLeft(Node *parent, Node *child)
+{
+ Q_ASSERT(!parent->left);
+ parent->left = child;
+ child->parent = parent;
+ update(child);
+}
+
+template <class T>
+inline void QRBTree<T>::attachRight(Node *parent, Node *child)
+{
+ Q_ASSERT(!parent->right);
+ parent->right = child;
+ child->parent = parent;
+ update(child);
+}
+
+template <class T>
+void QRBTree<T>::attachBefore(Node *parent, Node *child)
+{
+ if (!root)
+ update(root = child);
+ else if (!parent)
+ attachRight(back(root), child);
+ else if (parent->left)
+ attachRight(back(parent->left), child);
+ else
+ attachLeft(parent, child);
+}
+
+template <class T>
+void QRBTree<T>::attachAfter(Node *parent, Node *child)
+{
+ if (!root)
+ update(root = child);
+ else if (!parent)
+ attachLeft(front(root), child);
+ else if (parent->right)
+ attachLeft(front(parent->right), child);
+ else
+ attachRight(parent, child);
+}
+
+template <class T>
+void QRBTree<T>::swapNodes(Node *n1, Node *n2)
+{
+ // Since iterators must not be invalidated, it is not sufficient to only swap the data.
+ if (n1->parent == n2) {
+ n1->parent = n2->parent;
+ n2->parent = n1;
+ } else if (n2->parent == n1) {
+ n2->parent = n1->parent;
+ n1->parent = n2;
+ } else {
+ qSwap(n1->parent, n2->parent);
+ }
+
+ qSwap(n1->left, n2->left);
+ qSwap(n1->right, n2->right);
+ qSwap(n1->red, n2->red);
+
+ if (n1->parent) {
+ if (n1->parent->left == n2)
+ n1->parent->left = n1;
+ else
+ n1->parent->right = n1;
+ } else {
+ root = n1;
+ }
+
+ if (n2->parent) {
+ if (n2->parent->left == n1)
+ n2->parent->left = n2;
+ else
+ n2->parent->right = n2;
+ } else {
+ root = n2;
+ }
+
+ if (n1->left)
+ n1->left->parent = n1;
+ if (n1->right)
+ n1->right->parent = n1;
+
+ if (n2->left)
+ n2->left->parent = n2;
+ if (n2->right)
+ n2->right->parent = n2;
+}
+
+template <class T>
+void QRBTree<T>::detach(Node *node) // call this before removing a node.
+{
+ if (node->right)
+ swapNodes(node, front(node->right));
+
+ Node *child = (node->left ? node->left : node->right);
+
+ if (!node->red) {
+ if (child && child->red)
+ child->red = false;
+ else
+ rebalance(node);
+ }
+
+ Node *&ref = (node->parent ? (node == node->parent->left ? node->parent->left : node->parent->right) : root);
+ ref = child;
+ if (child)
+ child->parent = node->parent;
+ node->left = node->right = node->parent = 0;
+}
+
+// 'node' must be black. rebalance will reduce the depth of black nodes by one in the sibling tree.
+template <class T>
+void QRBTree<T>::rebalance(Node *node)
+{
+ Q_ASSERT(!node->red);
+ for (;;) {
+ if (!node->parent)
+ return;
+
+ // at this point, node is not a parent, it is black, thus it must have a sibling.
+ Node *sibling = (node == node->parent->left ? node->parent->right : node->parent->left);
+ Q_ASSERT(sibling);
+
+ if (sibling->red) {
+ sibling->red = false;
+ node->parent->red = true;
+ if (node == node->parent->left)
+ rotateLeft(node->parent);
+ else
+ rotateRight(node->parent);
+ sibling = (node == node->parent->left ? node->parent->right : node->parent->left);
+ Q_ASSERT(sibling);
+ }
+
+ // at this point, the sibling is black.
+ Q_ASSERT(!sibling->red);
+
+ if ((!sibling->left || !sibling->left->red) && (!sibling->right || !sibling->right->red)) {
+ bool parentWasRed = node->parent->red;
+ sibling->red = true;
+ node->parent->red = false;
+ if (parentWasRed)
+ return;
+ node = node->parent;
+ continue;
+ }
+
+ // at this point, at least one of the sibling's children is red.
+
+ if (node == node->parent->left) {
+ if (!sibling->right || !sibling->right->red) {
+ Q_ASSERT(sibling->left);
+ sibling->red = true;
+ sibling->left->red = false;
+ rotateRight(sibling);
+
+ sibling = sibling->parent;
+ Q_ASSERT(sibling);
+ }
+ sibling->red = node->parent->red;
+ node->parent->red = false;
+
+ Q_ASSERT(sibling->right->red);
+ sibling->right->red = false;
+ rotateLeft(node->parent);
+ } else {
+ if (!sibling->left || !sibling->left->red) {
+ Q_ASSERT(sibling->right);
+ sibling->red = true;
+ sibling->right->red = false;
+ rotateLeft(sibling);
+
+ sibling = sibling->parent;
+ Q_ASSERT(sibling);
+ }
+ sibling->red = node->parent->red;
+ node->parent->red = false;
+
+ Q_ASSERT(sibling->left->red);
+ sibling->left->red = false;
+ rotateRight(node->parent);
+ }
+ return;
+ }
+}
+
+template <class T>
+inline typename QRBTree<T>::Node *QRBTree<T>::front(Node *node) const
+{
+ while (node->left)
+ node = node->left;
+ return node;
+}
+
+template <class T>
+inline typename QRBTree<T>::Node *QRBTree<T>::back(Node *node) const
+{
+ while (node->right)
+ node = node->right;
+ return node;
+}
+
+template <class T>
+typename QRBTree<T>::Node *QRBTree<T>::next(Node *node) const
+{
+ if (node->right)
+ return front(node->right);
+ while (node->parent && node == node->parent->right)
+ node = node->parent;
+ return node->parent;
+}
+
+template <class T>
+typename QRBTree<T>::Node *QRBTree<T>::previous(Node *node) const
+{
+ if (node->left)
+ return back(node->left);
+ while (node->parent && node == node->parent->left)
+ node = node->parent;
+ return node->parent;
+}
+
+template <class T>
+int QRBTree<T>::blackDepth(Node *top) const
+{
+ if (!top)
+ return 0;
+ int leftDepth = blackDepth(top->left);
+ int rightDepth = blackDepth(top->right);
+ if (leftDepth != rightDepth)
+ return -1;
+ if (!top->red)
+ ++leftDepth;
+ return leftDepth;
+}
+
+template <class T>
+bool QRBTree<T>::checkRedBlackProperty(Node *top) const
+{
+ if (!top)
+ return true;
+ if (top->left && !checkRedBlackProperty(top->left))
+ return false;
+ if (top->right && !checkRedBlackProperty(top->right))
+ return false;
+ return !(top->red && ((top->left && top->left->red) || (top->right && top->right->red)));
+}
+
+template <class T>
+inline bool QRBTree<T>::validate() const
+{
+ return checkRedBlackProperty(root) && blackDepth(root) != -1;
+}
+
+template <class T>
+inline void QRBTree<T>::deleteNode(Node *&node)
+{
+ Q_ASSERT(node);
+ detach(node);
+ node->right = freeList;
+ freeList = node;
+ node = 0;
+}
+
+template <class T>
+inline typename QRBTree<T>::Node *QRBTree<T>::newNode()
+{
+ if (freeList) {
+ Node *node = freeList;
+ freeList = freeList->right;
+ node->parent = node->left = node->right = 0;
+ node->red = true;
+ return node;
+ }
+ return new Node;
+}
+
+// Return 1 if 'left' comes after 'right', 0 if equal, and -1 otherwise.
+// 'left' and 'right' cannot be null.
+template <class T>
+int QRBTree<T>::order(Node *left, Node *right)
+{
+ Q_ASSERT(left && right);
+ if (left == right)
+ return 0;
+
+ QVector<Node *> leftAncestors;
+ QVector<Node *> rightAncestors;
+ while (left) {
+ leftAncestors.push_back(left);
+ left = left->parent;
+ }
+ while (right) {
+ rightAncestors.push_back(right);
+ right = right->parent;
+ }
+ Q_ASSERT(leftAncestors.back() == root && rightAncestors.back() == root);
+
+ while (!leftAncestors.empty() && !rightAncestors.empty() && leftAncestors.back() == rightAncestors.back()) {
+ leftAncestors.pop_back();
+ rightAncestors.pop_back();
+ }
+
+ if (!leftAncestors.empty())
+ return (leftAncestors.back() == leftAncestors.back()->parent->left ? -1 : 1);
+
+ if (!rightAncestors.empty())
+ return (rightAncestors.back() == rightAncestors.back()->parent->right ? -1 : 1);
+
+ // The code should never reach this point.
+ Q_ASSERT(!leftAncestors.empty() || !rightAncestors.empty());
+ return 0;
+}
+
+//============================================================================//
+// QInt64Hash //
+//============================================================================//
+
+// Copied from qhash.cpp
+static const uchar prime_deltas[] = {
+ 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3,
+ 1, 21, 3, 21, 7, 15, 9, 5, 3, 29, 15, 0, 0, 0, 0, 0
+};
+
+// Copied from qhash.cpp
+static inline int primeForNumBits(int numBits)
+{
+ return (1 << numBits) + prime_deltas[numBits];
+}
+
+static inline int primeForCount(int count)
+{
+ int low = 0;
+ int high = 32;
+ for (int i = 0; i < 5; ++i) {
+ int mid = (high + low) / 2;
+ if (count >= 1 << mid)
+ low = mid;
+ else
+ high = mid;
+ }
+ return primeForNumBits(high);
+}
+
+// Hash set of quint64s. Elements cannot be removed without clearing the
+// entire set. A value of -1 is used to mark unused entries.
+class QInt64Set
+{
+public:
+ inline QInt64Set(int capacity = 64);
+ inline ~QInt64Set() {if (m_array) delete[] m_array;}
+ inline bool isValid() const {return m_array;}
+ void insert(quint64 key);
+ bool contains(quint64 key) const;
+ inline void clear();
+private:
+ bool rehash(int capacity);
+
+ static const quint64 UNUSED;
+
+ quint64 *m_array;
+ int m_capacity;
+ int m_count;
+};
+
+const quint64 QInt64Set::UNUSED = quint64(-1);
+
+inline QInt64Set::QInt64Set(int capacity)
+{
+ m_capacity = primeForCount(capacity);
+ m_array = new quint64[m_capacity];
+ if (m_array)
+ clear();
+ else
+ m_capacity = 0;
+}
+
+bool QInt64Set::rehash(int capacity)
+{
+ quint64 *oldArray = m_array;
+ int oldCapacity = m_capacity;
+
+ m_capacity = capacity;
+ m_array = new quint64[m_capacity];
+ if (m_array) {
+ clear();
+ if (oldArray) {
+ for (int i = 0; i < oldCapacity; ++i) {
+ if (oldArray[i] != UNUSED)
+ insert(oldArray[i]);
+ }
+ delete[] oldArray;
+ }
+ return true;
+ } else {
+ m_capacity = oldCapacity;
+ m_array = oldArray;
+ return false;
+ }
+}
+
+void QInt64Set::insert(quint64 key)
+{
+ if (m_count > 3 * m_capacity / 4)
+ rehash(primeForCount(2 * m_capacity));
+ Q_ASSERT_X(m_array, "QInt64Hash<T>::insert", "Hash set not allocated.");
+ int index = int(key % m_capacity);
+ for (int i = 0; i < m_capacity; ++i) {
+ index += i;
+ if (index >= m_capacity)
+ index -= m_capacity;
+ if (m_array[index] == key)
+ return;
+ if (m_array[index] == UNUSED) {
+ ++m_count;
+ m_array[index] = key;
+ return;
+ }
+ }
+ Q_ASSERT_X(0, "QInt64Hash<T>::insert", "Hash set full.");
+}
+
+bool QInt64Set::contains(quint64 key) const
+{
+ Q_ASSERT_X(m_array, "QInt64Hash<T>::contains", "Hash set not allocated.");
+ int index = int(key % m_capacity);
+ for (int i = 0; i < m_capacity; ++i) {
+ index += i;
+ if (index >= m_capacity)
+ index -= m_capacity;
+ if (m_array[index] == key)
+ return true;
+ if (m_array[index] == UNUSED)
+ return false;
+ }
+ return false;
+}
+
+inline void QInt64Set::clear()
+{
+ Q_ASSERT_X(m_array, "QInt64Hash<T>::clear", "Hash set not allocated.");
+ for (int i = 0; i < m_capacity; ++i)
+ m_array[i] = UNUSED;
+ m_count = 0;
+}
+
+//============================================================================//
+// QRingBuffer //
+//============================================================================//
+
+// T must be POD.
+template <class T>
+class QRingBuffer
+{
+public:
+ inline QRingBuffer() : m_array(0), m_head(0), m_size(0), m_capacity(0) { }
+ inline ~QRingBuffer() {if (m_array) delete[] m_array;}
+ bool reallocate(int capacity);
+ inline const T &head() const {Q_ASSERT(m_size > 0); return m_array[m_head];}
+ inline const T &dequeue();
+ inline void enqueue(const T &x);
+ inline bool isEmpty() const {return m_size == 0;}
+private:
+ T *m_array;
+ int m_head;
+ int m_size;
+ int m_capacity;
+};
+
+template <class T>
+bool QRingBuffer<T>::reallocate(int capacity)
+{
+ T *oldArray = m_array;
+ m_array = new T[capacity];
+ if (m_array) {
+ if (oldArray) {
+ if (m_head + m_size > m_capacity) {
+ memcpy(m_array, oldArray + m_head, (m_capacity - m_head) * sizeof(T));
+ memcpy(m_array + (m_capacity - m_head), oldArray, (m_head + m_size - m_capacity) * sizeof(T));
+ } else {
+ memcpy(m_array, oldArray + m_head, m_size * sizeof(T));
+ }
+ delete[] oldArray;
+ }
+ m_capacity = capacity;
+ m_head = 0;
+ return true;
+ } else {
+ m_array = oldArray;
+ return false;
+ }
+}
+
+template <class T>
+inline const T &QRingBuffer<T>::dequeue()
+{
+ Q_ASSERT(m_size > 0);
+ Q_ASSERT(m_array);
+ Q_ASSERT(m_capacity >= m_size);
+ int index = m_head;
+ if (++m_head >= m_capacity)
+ m_head -= m_capacity;
+ --m_size;
+ return m_array[index];
+}
+
+template <class T>
+inline void QRingBuffer<T>::enqueue(const T &x)
+{
+ if (m_size == m_capacity)
+ reallocate(qMax(2 * m_capacity, 64));
+ int index = m_head + m_size;
+ if (index >= m_capacity)
+ index -= m_capacity;
+ m_array[index] = x;
+ ++m_size;
+}
+
+//============================================================================//
+// QTriangulator //
+//============================================================================//
+template<typename T>
+class QTriangulator
+{
+public:
+ typedef QVarLengthArray<int, 6> ShortArray;
+
+ //================================//
+ // QTriangulator::ComplexToSimple //
+ //================================//
+ friend class ComplexToSimple;
+ class ComplexToSimple
+ {
+ public:
+ inline ComplexToSimple(QTriangulator<T> *parent) : m_parent(parent),
+ m_edges(0), m_events(0), m_splits(0) { }
+ void decompose();
+ private:
+ struct Edge
+ {
+ inline int &upper() {return pointingUp ? to : from;}
+ inline int &lower() {return pointingUp ? from : to;}
+ inline int upper() const {return pointingUp ? to : from;}
+ inline int lower() const {return pointingUp ? from : to;}
+
+ QRBTree<int>::Node *node;
+ int from, to; // vertex
+ int next, previous; // edge
+ int winding;
+ bool mayIntersect;
+ bool pointingUp, originallyPointingUp;
+ };
+
+ friend class CompareEdges;
+ class CompareEdges
+ {
+ public:
+ inline CompareEdges(ComplexToSimple *parent) : m_parent(parent) { }
+ bool operator () (int i, int j) const;
+ private:
+ ComplexToSimple *m_parent;
+ };
+
+ struct Intersection
+ {
+ bool operator < (const Intersection &other) const {return other.intersectionPoint < intersectionPoint;}
+
+ QIntersectionPoint intersectionPoint;
+ int vertex;
+ int leftEdge;
+ int rightEdge;
+ };
+
+ struct Split
+ {
+ int vertex;
+ int edge;
+ bool accurate;
+ };
+
+ struct Event
+ {
+ enum Type {Upper, Lower};
+ inline bool operator < (const Event &other) const;
+
+ QPodPoint point;
+ Type type;
+ int edge;
+ };
+
+#ifdef Q_TRIANGULATOR_DEBUG
+ friend class DebugDialog;
+ friend class QTriangulator;
+ class DebugDialog : public QDialog
+ {
+ public:
+ DebugDialog(ComplexToSimple *parent, int currentVertex);
+ protected:
+ void paintEvent(QPaintEvent *);
+ void wheelEvent(QWheelEvent *);
+ void mouseMoveEvent(QMouseEvent *);
+ void mousePressEvent(QMouseEvent *);
+ private:
+ ComplexToSimple *m_parent;
+ QRectF m_window;
+ QPoint m_lastMousePos;
+ int m_vertex;
+ };
+#endif
+
+ void initEdges();
+ bool calculateIntersection(int left, int right);
+ bool edgeIsLeftOfEdge(int leftEdgeIndex, int rightEdgeIndex) const;
+ QRBTree<int>::Node *searchEdgeLeftOf(int edgeIndex) const;
+ QRBTree<int>::Node *searchEdgeLeftOf(int edgeIndex, QRBTree<int>::Node *after) const;
+ QPair<QRBTree<int>::Node *, QRBTree<int>::Node *> bounds(const QPodPoint &point) const;
+ QPair<QRBTree<int>::Node *, QRBTree<int>::Node *> outerBounds(const QPodPoint &point) const;
+ void splitEdgeListRange(QRBTree<int>::Node *leftmost, QRBTree<int>::Node *rightmost, int vertex, const QIntersectionPoint &intersectionPoint);
+ void reorderEdgeListRange(QRBTree<int>::Node *leftmost, QRBTree<int>::Node *rightmost);
+ void sortEdgeList(const QPodPoint eventPoint);
+ void fillPriorityQueue();
+ void calculateIntersections();
+ int splitEdge(int splitIndex);
+ bool splitEdgesAtIntersections();
+ void insertEdgeIntoVectorIfWanted(ShortArray &orderedEdges, int i);
+ void removeUnwantedEdgesAndConnect();
+ void removeUnusedPoints();
+
+ QTriangulator *m_parent;
+ QDataBuffer<Edge> m_edges;
+ QRBTree<int> m_edgeList;
+ QDataBuffer<Event> m_events;
+ QDataBuffer<Split> m_splits;
+ QMaxHeap<Intersection> m_topIntersection;
+ QInt64Set m_processedEdgePairs;
+ int m_initialPointCount;
+ };
+#ifdef Q_TRIANGULATOR_DEBUG
+ friend class ComplexToSimple::DebugDialog;
+#endif
+
+ //=================================//
+ // QTriangulator::SimpleToMonotone //
+ //=================================//
+ friend class SimpleToMonotone;
+ class SimpleToMonotone
+ {
+ public:
+ inline SimpleToMonotone(QTriangulator<T> *parent) : m_parent(parent), m_edges(0), m_upperVertex(0) { }
+ void decompose();
+ private:
+ enum VertexType {MergeVertex, EndVertex, RegularVertex, StartVertex, SplitVertex};
+
+ struct Edge
+ {
+ QRBTree<int>::Node *node;
+ int helper, twin, next, previous;
+ T from, to;
+ VertexType type;
+ bool pointingUp;
+ int upper() const {return (pointingUp ? to : from);}
+ int lower() const {return (pointingUp ? from : to);}
+ };
+
+ friend class CompareVertices;
+ class CompareVertices
+ {
+ public:
+ CompareVertices(SimpleToMonotone *parent) : m_parent(parent) { }
+ bool operator () (int i, int j) const;
+ private:
+ SimpleToMonotone *m_parent;
+ };
+
+ void setupDataStructures();
+ void removeZeroLengthEdges();
+ void fillPriorityQueue();
+ bool edgeIsLeftOfEdge(int leftEdgeIndex, int rightEdgeIndex) const;
+ // Returns the rightmost edge not to the right of the given edge.
+ QRBTree<int>::Node *searchEdgeLeftOfEdge(int edgeIndex) const;
+ // Returns the rightmost edge left of the given point.
+ QRBTree<int>::Node *searchEdgeLeftOfPoint(int pointIndex) const;
+ void classifyVertex(int i);
+ void classifyVertices();
+ bool pointIsInSector(const QPodPoint &p, const QPodPoint &v1, const QPodPoint &v2, const QPodPoint &v3);
+ bool pointIsInSector(int vertex, int sector);
+ int findSector(int edge, int vertex);
+ void createDiagonal(int lower, int upper);
+ void monotoneDecomposition();
+
+ QTriangulator *m_parent;
+ QRBTree<int> m_edgeList;
+ QDataBuffer<Edge> m_edges;
+ QDataBuffer<int> m_upperVertex;
+ bool m_clockwiseOrder;
+ };
+
+ //====================================//
+ // QTriangulator::MonotoneToTriangles //
+ //====================================//
+ friend class MonotoneToTriangles;
+ class MonotoneToTriangles
+ {
+ public:
+ inline MonotoneToTriangles(QTriangulator<T> *parent) : m_parent(parent) { }
+ void decompose();
+ private:
+ inline T indices(int index) const {return m_parent->m_indices.at(index + m_first);}
+ inline int next(int index) const {return (index + 1) % m_length;}
+ inline int previous(int index) const {return (index + m_length - 1) % m_length;}
+ inline bool less(int i, int j) const {return m_parent->m_vertices.at((qint32)indices(i)) < m_parent->m_vertices.at(indices(j));}
+ inline bool leftOfEdge(int i, int j, int k) const
+ {
+ return qPointIsLeftOfLine(m_parent->m_vertices.at((qint32)indices(i)),
+ m_parent->m_vertices.at((qint32)indices(j)), m_parent->m_vertices.at((qint32)indices(k)));
+ }
+
+ QTriangulator<T> *m_parent;
+ int m_first;
+ int m_length;
+ };
+
+ inline QTriangulator() : m_vertices(0) { }
+
+ // Call this only once.
+ void initialize(const qreal *polygon, int count, uint hint, const QTransform &matrix);
+ // Call this only once.
+ void initialize(const QVectorPath &path, const QTransform &matrix, qreal lod);
+ // Call this only once.
+ void initialize(const QPainterPath &path, const QTransform &matrix, qreal lod);
+ // Call either triangulate() or polyline() only once.
+ QVertexSet<T> triangulate();
+ QVertexSet<T> polyline();
+private:
+ QDataBuffer<QPodPoint> m_vertices;
+ QVector<T> m_indices;
+ uint m_hint;
+};
+
+//============================================================================//
+// QTriangulator //
+//============================================================================//
+
+template <typename T>
+QVertexSet<T> QTriangulator<T>::triangulate()
+{
+ for (int i = 0; i < m_vertices.size(); ++i) {
+ Q_ASSERT(qAbs(m_vertices.at(i).x) < (1 << 21));
+ Q_ASSERT(qAbs(m_vertices.at(i).y) < (1 << 21));
+ }
+
+ if (!(m_hint & (QVectorPath::OddEvenFill | QVectorPath::WindingFill)))
+ m_hint |= QVectorPath::OddEvenFill;
+
+ if (m_hint & QVectorPath::NonConvexShapeMask) {
+ ComplexToSimple c2s(this);
+ c2s.decompose();
+ SimpleToMonotone s2m(this);
+ s2m.decompose();
+ }
+ MonotoneToTriangles m2t(this);
+ m2t.decompose();
+
+ QVertexSet<T> result;
+ result.indices = m_indices;
+ result.vertices.resize(2 * m_vertices.size());
+ for (int i = 0; i < m_vertices.size(); ++i) {
+ result.vertices[2 * i + 0] = qreal(m_vertices.at(i).x) / Q_FIXED_POINT_SCALE;
+ result.vertices[2 * i + 1] = qreal(m_vertices.at(i).y) / Q_FIXED_POINT_SCALE;
+ }
+ return result;
+}
+
+template <typename T>
+QVertexSet<T> QTriangulator<T>::polyline()
+{
+ QVertexSet<T> result;
+ result.indices = m_indices;
+ result.vertices.resize(2 * m_vertices.size());
+ for (int i = 0; i < m_vertices.size(); ++i) {
+ result.vertices[2 * i + 0] = qreal(m_vertices.at(i).x) / Q_FIXED_POINT_SCALE;
+ result.vertices[2 * i + 1] = qreal(m_vertices.at(i).y) / Q_FIXED_POINT_SCALE;
+ }
+ return result;
+}
+
+template <typename T>
+void QTriangulator<T>::initialize(const qreal *polygon, int count, uint hint, const QTransform &matrix)
+{
+ m_hint = hint;
+ m_vertices.resize(count);
+ m_indices.resize(count + 1);
+ for (int i = 0; i < count; ++i) {
+ qreal x, y;
+ matrix.map(polygon[2 * i + 0], polygon[2 * i + 1], &x, &y);
+ m_vertices.at(i).x = qRound(x * Q_FIXED_POINT_SCALE);
+ m_vertices.at(i).y = qRound(y * Q_FIXED_POINT_SCALE);
+ m_indices[i] = i;
+ }
+ m_indices[count] = T(-1); //Q_TRIANGULATE_END_OF_POLYGON
+}
+
+template <typename T>
+void QTriangulator<T>::initialize(const QVectorPath &path, const QTransform &matrix, qreal lod)
+{
+ m_hint = path.hints();
+ // Curved paths will be converted to complex polygons.
+ m_hint &= ~QVectorPath::CurvedShapeMask;
+
+ const qreal *p = path.points();
+ const QPainterPath::ElementType *e = path.elements();
+ if (e) {
+ for (int i = 0; i < path.elementCount(); ++i, ++e, p += 2) {
+ switch (*e) {
+ case QPainterPath::MoveToElement:
+ if (!m_indices.isEmpty())
+ m_indices.push_back(T(-1)); // Q_TRIANGULATE_END_OF_POLYGON
+ // Fall through.
+ case QPainterPath::LineToElement:
+ m_indices.push_back(T(m_vertices.size()));
+ m_vertices.resize(m_vertices.size() + 1);
+ qreal x, y;
+ matrix.map(p[0], p[1], &x, &y);
+ m_vertices.last().x = qRound(x * Q_FIXED_POINT_SCALE);
+ m_vertices.last().y = qRound(y * Q_FIXED_POINT_SCALE);
+ break;
+ case QPainterPath::CurveToElement:
+ {
+ qreal pts[8];
+ for (int i = 0; i < 4; ++i)
+ matrix.map(p[2 * i - 2], p[2 * i - 1], &pts[2 * i + 0], &pts[2 * i + 1]);
+ for (int i = 0; i < 8; ++i)
+ pts[i] *= lod;
+ QBezier bezier = QBezier::fromPoints(QPointF(pts[0], pts[1]), QPointF(pts[2], pts[3]), QPointF(pts[4], pts[5]), QPointF(pts[6], pts[7]));
+ QPolygonF poly = bezier.toPolygon();
+ // Skip first point, it already exists in 'm_vertices'.
+ for (int j = 1; j < poly.size(); ++j) {
+ m_indices.push_back(T(m_vertices.size()));
+ m_vertices.resize(m_vertices.size() + 1);
+ m_vertices.last().x = qRound(poly.at(j).x() * Q_FIXED_POINT_SCALE / lod);
+ m_vertices.last().y = qRound(poly.at(j).y() * Q_FIXED_POINT_SCALE / lod);
+ }
+ }
+ i += 2;
+ e += 2;
+ p += 4;
+ break;
+ default:
+ Q_ASSERT_X(0, "QTriangulator::triangulate", "Unexpected element type.");
+ break;
+ }
+ }
+ } else {
+ for (int i = 0; i < path.elementCount(); ++i, p += 2) {
+ m_indices.push_back(T(m_vertices.size()));
+ m_vertices.resize(m_vertices.size() + 1);
+ qreal x, y;
+ matrix.map(p[0], p[1], &x, &y);
+ m_vertices.last().x = qRound(x * Q_FIXED_POINT_SCALE);
+ m_vertices.last().y = qRound(y * Q_FIXED_POINT_SCALE);
+ }
+ }
+ m_indices.push_back(T(-1)); // Q_TRIANGULATE_END_OF_POLYGON
+}
+
+template <typename T>
+void QTriangulator<T>::initialize(const QPainterPath &path, const QTransform &matrix, qreal lod)
+{
+ initialize(qtVectorPathForPath(path), matrix, lod);
+}
+
+//============================================================================//
+// QTriangulator::ComplexToSimple //
+//============================================================================//
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::decompose()
+{
+ m_initialPointCount = m_parent->m_vertices.size();
+ initEdges();
+ do {
+ calculateIntersections();
+ } while (splitEdgesAtIntersections());
+
+ removeUnwantedEdgesAndConnect();
+ removeUnusedPoints();
+
+ m_parent->m_indices.clear();
+ QBitArray processed(m_edges.size(), false);
+ for (int first = 0; first < m_edges.size(); ++first) {
+ // If already processed, or if unused path, skip.
+ if (processed.at(first) || m_edges.at(first).next == -1)
+ continue;
+
+ int i = first;
+ do {
+ Q_ASSERT(!processed.at(i));
+ Q_ASSERT(m_edges.at(m_edges.at(i).next).previous == i);
+ m_parent->m_indices.push_back(m_edges.at(i).from);
+ processed.setBit(i);
+ i = m_edges.at(i).next; // CCW order
+ } while (i != first);
+ m_parent->m_indices.push_back(T(-1)); // Q_TRIANGULATE_END_OF_POLYGON
+ }
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::initEdges()
+{
+ // Initialize edge structure.
+ // 'next' and 'previous' are not being initialized at this point.
+ int first = 0;
+ for (int i = 0; i < m_parent->m_indices.size(); ++i) {
+ if (m_parent->m_indices.at(i) == T(-1)) { // Q_TRIANGULATE_END_OF_POLYGON
+ if (m_edges.size() != first)
+ m_edges.last().to = m_edges.at(first).from;
+ first = m_edges.size();
+ } else {
+ Q_ASSERT(i + 1 < m_parent->m_indices.size());
+ // {node, from, to, next, previous, winding, mayIntersect, pointingUp, originallyPointingUp}
+ Edge edge = {0, m_parent->m_indices.at(i), m_parent->m_indices.at(i + 1), -1, -1, 0, true, false, false};
+ m_edges.add(edge);
+ }
+ }
+ if (first != m_edges.size())
+ m_edges.last().to = m_edges.at(first).from;
+ for (int i = 0; i < m_edges.size(); ++i) {
+ m_edges.at(i).originallyPointingUp = m_edges.at(i).pointingUp =
+ m_parent->m_vertices.at(m_edges.at(i).to) < m_parent->m_vertices.at(m_edges.at(i).from);
+ }
+}
+
+// Return true if new intersection was found
+template <typename T>
+bool QTriangulator<T>::ComplexToSimple::calculateIntersection(int left, int right)
+{
+ const Edge &e1 = m_edges.at(left);
+ const Edge &e2 = m_edges.at(right);
+
+ const QPodPoint &u1 = m_parent->m_vertices.at((qint32)e1.from);
+ const QPodPoint &u2 = m_parent->m_vertices.at((qint32)e1.to);
+ const QPodPoint &v1 = m_parent->m_vertices.at((qint32)e2.from);
+ const QPodPoint &v2 = m_parent->m_vertices.at((qint32)e2.to);
+ if (qMax(u1.x, u2.x) <= qMin(v1.x, v2.x))
+ return false;
+
+ quint64 key = (left > right ? (quint64(right) << 32) | quint64(left) : (quint64(left) << 32) | quint64(right));
+ if (m_processedEdgePairs.contains(key))
+ return false;
+ m_processedEdgePairs.insert(key);
+
+ Intersection intersection;
+ intersection.leftEdge = left;
+ intersection.rightEdge = right;
+ intersection.intersectionPoint = QT_PREPEND_NAMESPACE(qIntersectionPoint)(u1, u2, v1, v2);
+
+ if (!intersection.intersectionPoint.isValid())
+ return false;
+
+ Q_ASSERT(intersection.intersectionPoint.isOnLine(u1, u2));
+ Q_ASSERT(intersection.intersectionPoint.isOnLine(v1, v2));
+
+ intersection.vertex = m_parent->m_vertices.size();
+ m_topIntersection.push(intersection);
+ m_parent->m_vertices.add(intersection.intersectionPoint.round());
+ return true;
+}
+
+template <typename T>
+bool QTriangulator<T>::ComplexToSimple::edgeIsLeftOfEdge(int leftEdgeIndex, int rightEdgeIndex) const
+{
+ const Edge &leftEdge = m_edges.at(leftEdgeIndex);
+ const Edge &rightEdge = m_edges.at(rightEdgeIndex);
+ const QPodPoint &u = m_parent->m_vertices.at(rightEdge.upper());
+ const QPodPoint &l = m_parent->m_vertices.at(rightEdge.lower());
+ const QPodPoint &upper = m_parent->m_vertices.at(leftEdge.upper());
+ if (upper.x < qMin(l.x, u.x))
+ return true;
+ if (upper.x > qMax(l.x, u.x))
+ return false;
+ qint64 d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(upper, l, u);
+ // d < 0: left, d > 0: right, d == 0: on top
+ if (d == 0)
+ d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(m_parent->m_vertices.at(leftEdge.lower()), l, u);
+ return d < 0;
+}
+
+template <typename T>
+QRBTree<int>::Node *QTriangulator<T>::ComplexToSimple::searchEdgeLeftOf(int edgeIndex) const
+{
+ QRBTree<int>::Node *current = m_edgeList.root;
+ QRBTree<int>::Node *result = 0;
+ while (current) {
+ if (edgeIsLeftOfEdge(edgeIndex, current->data)) {
+ current = current->left;
+ } else {
+ result = current;
+ current = current->right;
+ }
+ }
+ return result;
+}
+
+template <typename T>
+QRBTree<int>::Node *QTriangulator<T>::ComplexToSimple::searchEdgeLeftOf(int edgeIndex, QRBTree<int>::Node *after) const
+{
+ if (!m_edgeList.root)
+ return after;
+ QRBTree<int>::Node *result = after;
+ QRBTree<int>::Node *current = (after ? m_edgeList.next(after) : m_edgeList.front(m_edgeList.root));
+ while (current) {
+ if (edgeIsLeftOfEdge(edgeIndex, current->data))
+ return result;
+ result = current;
+ current = m_edgeList.next(current);
+ }
+ return result;
+}
+
+template <typename T>
+QPair<QRBTree<int>::Node *, QRBTree<int>::Node *> QTriangulator<T>::ComplexToSimple::bounds(const QPodPoint &point) const
+{
+ QRBTree<int>::Node *current = m_edgeList.root;
+ QPair<QRBTree<int>::Node *, QRBTree<int>::Node *> result(0, 0);
+ while (current) {
+ const QPodPoint &v1 = m_parent->m_vertices.at(m_edges.at(current->data).lower());
+ const QPodPoint &v2 = m_parent->m_vertices.at(m_edges.at(current->data).upper());
+ qint64 d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(point, v1, v2);
+ if (d == 0) {
+ result.first = result.second = current;
+ break;
+ }
+ current = (d < 0 ? current->left : current->right);
+ }
+ if (current == 0)
+ return result;
+
+ current = result.first->left;
+ while (current) {
+ const QPodPoint &v1 = m_parent->m_vertices.at(m_edges.at(current->data).lower());
+ const QPodPoint &v2 = m_parent->m_vertices.at(m_edges.at(current->data).upper());
+ qint64 d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(point, v1, v2);
+ Q_ASSERT(d >= 0);
+ if (d == 0) {
+ result.first = current;
+ current = current->left;
+ } else {
+ current = current->right;
+ }
+ }
+
+ current = result.second->right;
+ while (current) {
+ const QPodPoint &v1 = m_parent->m_vertices.at(m_edges.at(current->data).lower());
+ const QPodPoint &v2 = m_parent->m_vertices.at(m_edges.at(current->data).upper());
+ qint64 d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(point, v1, v2);
+ Q_ASSERT(d <= 0);
+ if (d == 0) {
+ result.second = current;
+ current = current->right;
+ } else {
+ current = current->left;
+ }
+ }
+
+ return result;
+}
+
+template <typename T>
+QPair<QRBTree<int>::Node *, QRBTree<int>::Node *> QTriangulator<T>::ComplexToSimple::outerBounds(const QPodPoint &point) const
+{
+ QRBTree<int>::Node *current = m_edgeList.root;
+ QPair<QRBTree<int>::Node *, QRBTree<int>::Node *> result(0, 0);
+
+ while (current) {
+ const QPodPoint &v1 = m_parent->m_vertices.at(m_edges.at(current->data).lower());
+ const QPodPoint &v2 = m_parent->m_vertices.at(m_edges.at(current->data).upper());
+ qint64 d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(point, v1, v2);
+ if (d == 0)
+ break;
+ if (d < 0) {
+ result.second = current;
+ current = current->left;
+ } else {
+ result.first = current;
+ current = current->right;
+ }
+ }
+
+ if (!current)
+ return result;
+
+ QRBTree<int>::Node *mid = current;
+
+ current = mid->left;
+ while (current) {
+ const QPodPoint &v1 = m_parent->m_vertices.at(m_edges.at(current->data).lower());
+ const QPodPoint &v2 = m_parent->m_vertices.at(m_edges.at(current->data).upper());
+ qint64 d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(point, v1, v2);
+ Q_ASSERT(d >= 0);
+ if (d == 0) {
+ current = current->left;
+ } else {
+ result.first = current;
+ current = current->right;
+ }
+ }
+
+ current = mid->right;
+ while (current) {
+ const QPodPoint &v1 = m_parent->m_vertices.at(m_edges.at(current->data).lower());
+ const QPodPoint &v2 = m_parent->m_vertices.at(m_edges.at(current->data).upper());
+ qint64 d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(point, v1, v2);
+ Q_ASSERT(d <= 0);
+ if (d == 0) {
+ current = current->right;
+ } else {
+ result.second = current;
+ current = current->left;
+ }
+ }
+
+ return result;
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::splitEdgeListRange(QRBTree<int>::Node *leftmost, QRBTree<int>::Node *rightmost, int vertex, const QIntersectionPoint &intersectionPoint)
+{
+ Q_ASSERT(leftmost && rightmost);
+
+ // Split.
+ for (;;) {
+ const QPodPoint &u = m_parent->m_vertices.at(m_edges.at(leftmost->data).from);
+ const QPodPoint &v = m_parent->m_vertices.at(m_edges.at(leftmost->data).to);
+ Q_ASSERT(intersectionPoint.isOnLine(u, v));
+ const Split split = {vertex, leftmost->data, intersectionPoint.isAccurate()};
+ if (intersectionPoint.xOffset.numerator != 0 || intersectionPoint.yOffset.numerator != 0 || (intersectionPoint.upperLeft != u && intersectionPoint.upperLeft != v))
+ m_splits.add(split);
+ if (leftmost == rightmost)
+ break;
+ leftmost = m_edgeList.next(leftmost);
+ }
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::reorderEdgeListRange(QRBTree<int>::Node *leftmost, QRBTree<int>::Node *rightmost)
+{
+ Q_ASSERT(leftmost && rightmost);
+
+ QRBTree<int>::Node *storeLeftmost = leftmost;
+ QRBTree<int>::Node *storeRightmost = rightmost;
+
+ // Reorder.
+ while (leftmost != rightmost) {
+ Edge &left = m_edges.at(leftmost->data);
+ Edge &right = m_edges.at(rightmost->data);
+ qSwap(left.node, right.node);
+ qSwap(leftmost->data, rightmost->data);
+ leftmost = m_edgeList.next(leftmost);
+ if (leftmost == rightmost)
+ break;
+ rightmost = m_edgeList.previous(rightmost);
+ }
+
+ rightmost = m_edgeList.next(storeRightmost);
+ leftmost = m_edgeList.previous(storeLeftmost);
+ if (leftmost)
+ calculateIntersection(leftmost->data, storeLeftmost->data);
+ if (rightmost)
+ calculateIntersection(storeRightmost->data, rightmost->data);
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::sortEdgeList(const QPodPoint eventPoint)
+{
+ QIntersectionPoint eventPoint2 = QT_PREPEND_NAMESPACE(qIntersectionPoint)(eventPoint);
+ while (!m_topIntersection.isEmpty() && m_topIntersection.top().intersectionPoint < eventPoint2) {
+ Intersection intersection = m_topIntersection.pop();
+
+ QIntersectionPoint currentIntersectionPoint = intersection.intersectionPoint;
+ int currentVertex = intersection.vertex;
+
+ QRBTree<int>::Node *leftmost = m_edges.at(intersection.leftEdge).node;
+ QRBTree<int>::Node *rightmost = m_edges.at(intersection.rightEdge).node;
+
+ for (;;) {
+ QRBTree<int>::Node *previous = m_edgeList.previous(leftmost);
+ if (!previous)
+ break;
+ const Edge &edge = m_edges.at(previous->data);
+ const QPodPoint &u = m_parent->m_vertices.at((qint32)edge.from);
+ const QPodPoint &v = m_parent->m_vertices.at((qint32)edge.to);
+ if (!currentIntersectionPoint.isOnLine(u, v)) {
+ Q_ASSERT(!currentIntersectionPoint.isAccurate() || qCross(currentIntersectionPoint.upperLeft - u, v - u) != 0);
+ break;
+ }
+ leftmost = previous;
+ }
+
+ for (;;) {
+ QRBTree<int>::Node *next = m_edgeList.next(rightmost);
+ if (!next)
+ break;
+ const Edge &edge = m_edges.at(next->data);
+ const QPodPoint &u = m_parent->m_vertices.at((qint32)edge.from);
+ const QPodPoint &v = m_parent->m_vertices.at((qint32)edge.to);
+ if (!currentIntersectionPoint.isOnLine(u, v)) {
+ Q_ASSERT(!currentIntersectionPoint.isAccurate() || qCross(currentIntersectionPoint.upperLeft - u, v - u) != 0);
+ break;
+ }
+ rightmost = next;
+ }
+
+ Q_ASSERT(leftmost && rightmost);
+ splitEdgeListRange(leftmost, rightmost, currentVertex, currentIntersectionPoint);
+ reorderEdgeListRange(leftmost, rightmost);
+
+ while (!m_topIntersection.isEmpty() && m_topIntersection.top().intersectionPoint <= currentIntersectionPoint)
+ m_topIntersection.pop();
+
+#ifdef Q_TRIANGULATOR_DEBUG
+ DebugDialog dialog(this, intersection.vertex);
+ dialog.exec();
+#endif
+
+ }
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::fillPriorityQueue()
+{
+ m_events.reset();
+ m_events.reserve(m_edges.size() * 2);
+ for (int i = 0; i < m_edges.size(); ++i) {
+ Q_ASSERT(m_edges.at(i).previous == -1 && m_edges.at(i).next == -1);
+ Q_ASSERT(m_edges.at(i).node == 0);
+ Q_ASSERT(m_edges.at(i).pointingUp == m_edges.at(i).originallyPointingUp);
+ Q_ASSERT(m_edges.at(i).pointingUp == (m_parent->m_vertices.at(m_edges.at(i).to) < m_parent->m_vertices.at(m_edges.at(i).from)));
+ // Ignore zero-length edges.
+ if (m_parent->m_vertices.at(m_edges.at(i).to) != m_parent->m_vertices.at(m_edges.at(i).from)) {
+ QPodPoint upper = m_parent->m_vertices.at(m_edges.at(i).upper());
+ QPodPoint lower = m_parent->m_vertices.at(m_edges.at(i).lower());
+ Event upperEvent = {{upper.x, upper.y}, Event::Upper, i};
+ Event lowerEvent = {{lower.x, lower.y}, Event::Lower, i};
+ m_events.add(upperEvent);
+ m_events.add(lowerEvent);
+ }
+ }
+ //qSort(m_events.data(), m_events.data() + m_events.size());
+ sort(m_events.data(), m_events.size());
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::calculateIntersections()
+{
+ fillPriorityQueue();
+
+ Q_ASSERT(m_topIntersection.empty());
+ Q_ASSERT(m_edgeList.root == 0);
+
+ // Find all intersection points.
+ while (!m_events.isEmpty()) {
+ Event event = m_events.last();
+ sortEdgeList(event.point);
+
+ // Find all edges in the edge list that contain the current vertex and mark them to be split later.
+ QPair<QRBTree<int>::Node *, QRBTree<int>::Node *> range = bounds(event.point);
+ QRBTree<int>::Node *leftNode = range.first ? m_edgeList.previous(range.first) : 0;
+ int vertex = (event.type == Event::Upper ? m_edges.at(event.edge).upper() : m_edges.at(event.edge).lower());
+ QIntersectionPoint eventPoint = QT_PREPEND_NAMESPACE(qIntersectionPoint)(event.point);
+
+ if (range.first != 0) {
+ splitEdgeListRange(range.first, range.second, vertex, eventPoint);
+ reorderEdgeListRange(range.first, range.second);
+ }
+
+ // Handle the edges with start or end point in the current vertex.
+ while (!m_events.isEmpty() && m_events.last().point == event.point) {
+ event = m_events.last();
+ m_events.pop_back();
+ int i = event.edge;
+
+ if (m_edges.at(i).node) {
+ // Remove edge from edge list.
+ Q_ASSERT(event.type == Event::Lower);
+ QRBTree<int>::Node *left = m_edgeList.previous(m_edges.at(i).node);
+ QRBTree<int>::Node *right = m_edgeList.next(m_edges.at(i).node);
+ m_edgeList.deleteNode(m_edges.at(i).node);
+ if (!left || !right)
+ continue;
+ calculateIntersection(left->data, right->data);
+ } else {
+ // Insert edge into edge list.
+ Q_ASSERT(event.type == Event::Upper);
+ QRBTree<int>::Node *left = searchEdgeLeftOf(i, leftNode);
+ m_edgeList.attachAfter(left, m_edges.at(i).node = m_edgeList.newNode());
+ m_edges.at(i).node->data = i;
+ QRBTree<int>::Node *right = m_edgeList.next(m_edges.at(i).node);
+ if (left)
+ calculateIntersection(left->data, i);
+ if (right)
+ calculateIntersection(i, right->data);
+ }
+ }
+ while (!m_topIntersection.isEmpty() && m_topIntersection.top().intersectionPoint <= eventPoint)
+ m_topIntersection.pop();
+#ifdef Q_TRIANGULATOR_DEBUG
+ DebugDialog dialog(this, vertex);
+ dialog.exec();
+#endif
+ }
+ m_processedEdgePairs.clear();
+}
+
+// Split an edge into two pieces at the given point.
+// The upper piece is pushed to the end of the 'm_edges' vector.
+// The lower piece replaces the old edge.
+// Return the edge whose 'from' is 'pointIndex'.
+template <typename T>
+int QTriangulator<T>::ComplexToSimple::splitEdge(int splitIndex)
+{
+ const Split &split = m_splits.at(splitIndex);
+ Edge &lowerEdge = m_edges.at(split.edge);
+ Q_ASSERT(lowerEdge.node == 0);
+ Q_ASSERT(lowerEdge.previous == -1 && lowerEdge.next == -1);
+
+ if (lowerEdge.from == split.vertex)
+ return split.edge;
+ if (lowerEdge.to == split.vertex)
+ return lowerEdge.next;
+
+ // Check that angle >= 90 degrees.
+ //Q_ASSERT(qDot(m_points.at(m_edges.at(edgeIndex).from) - m_points.at(pointIndex),
+ // m_points.at(m_edges.at(edgeIndex).to) - m_points.at(pointIndex)) <= 0);
+
+ Edge upperEdge = lowerEdge;
+ upperEdge.mayIntersect |= !split.accurate; // The edge may have been split before at an inaccurate split point.
+ lowerEdge.mayIntersect = !split.accurate;
+ if (lowerEdge.pointingUp) {
+ lowerEdge.to = upperEdge.from = split.vertex;
+ m_edges.add(upperEdge);
+ return m_edges.size() - 1;
+ } else {
+ lowerEdge.from = upperEdge.to = split.vertex;
+ m_edges.add(upperEdge);
+ return split.edge;
+ }
+}
+
+template <typename T>
+bool QTriangulator<T>::ComplexToSimple::splitEdgesAtIntersections()
+{
+ for (int i = 0; i < m_edges.size(); ++i)
+ m_edges.at(i).mayIntersect = false;
+ bool checkForNewIntersections = false;
+ for (int i = 0; i < m_splits.size(); ++i) {
+ splitEdge(i);
+ checkForNewIntersections |= !m_splits.at(i).accurate;
+ }
+ for (int i = 0; i < m_edges.size(); ++i) {
+ m_edges.at(i).originallyPointingUp = m_edges.at(i).pointingUp =
+ m_parent->m_vertices.at(m_edges.at(i).to) < m_parent->m_vertices.at(m_edges.at(i).from);
+ }
+ m_splits.reset();
+ return checkForNewIntersections;
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::insertEdgeIntoVectorIfWanted(ShortArray &orderedEdges, int i)
+{
+ // Edges with zero length should not reach this part.
+ Q_ASSERT(m_parent->m_vertices.at(m_edges.at(i).from) != m_parent->m_vertices.at(m_edges.at(i).to));
+
+ // Skip edges with unwanted winding number.
+ int windingNumber = m_edges.at(i).winding;
+ if (m_edges.at(i).originallyPointingUp)
+ ++windingNumber;
+
+ // Make sure exactly one fill rule is specified.
+ Q_ASSERT(((m_parent->m_hint & QVectorPath::WindingFill) != 0) != ((m_parent->m_hint & QVectorPath::OddEvenFill) != 0));
+
+ if ((m_parent->m_hint & QVectorPath::WindingFill) && windingNumber != 0 && windingNumber != 1)
+ return;
+
+ // Skip cancelling edges.
+ if (!orderedEdges.isEmpty()) {
+ int j = orderedEdges[orderedEdges.size() - 1];
+ // If the last edge is already connected in one end, it should not be cancelled.
+ if (m_edges.at(j).next == -1 && m_edges.at(j).previous == -1
+ && (m_parent->m_vertices.at(m_edges.at(i).from) == m_parent->m_vertices.at(m_edges.at(j).to))
+ && (m_parent->m_vertices.at(m_edges.at(i).to) == m_parent->m_vertices.at(m_edges.at(j).from))) {
+ orderedEdges.removeLast();
+ return;
+ }
+ }
+ orderedEdges.append(i);
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::removeUnwantedEdgesAndConnect()
+{
+ Q_ASSERT(m_edgeList.root == 0);
+ // Initialize priority queue.
+ fillPriorityQueue();
+
+ ShortArray orderedEdges;
+
+ while (!m_events.isEmpty()) {
+ Event event = m_events.last();
+ int edgeIndex = event.edge;
+
+ // Check that all the edges in the list crosses the current scanline
+ //if (m_edgeList.root) {
+ // for (QRBTree<int>::Node *node = m_edgeList.front(m_edgeList.root); node; node = m_edgeList.next(node)) {
+ // Q_ASSERT(event.point <= m_points.at(m_edges.at(node->data).lower()));
+ // }
+ //}
+
+ orderedEdges.clear();
+ QPair<QRBTree<int>::Node *, QRBTree<int>::Node *> b = outerBounds(event.point);
+ if (m_edgeList.root) {
+ QRBTree<int>::Node *current = (b.first ? m_edgeList.next(b.first) : m_edgeList.front(m_edgeList.root));
+ // Process edges that are going to be removed from the edge list at the current event point.
+ while (current != b.second) {
+ Q_ASSERT(current);
+ Q_ASSERT(m_edges.at(current->data).node == current);
+ Q_ASSERT(QT_PREPEND_NAMESPACE(qIntersectionPoint)(event.point).isOnLine(m_parent->m_vertices.at(m_edges.at(current->data).from), m_parent->m_vertices.at(m_edges.at(current->data).to)));
+ Q_ASSERT(m_parent->m_vertices.at(m_edges.at(current->data).from) == event.point || m_parent->m_vertices.at(m_edges.at(current->data).to) == event.point);
+ insertEdgeIntoVectorIfWanted(orderedEdges, current->data);
+ current = m_edgeList.next(current);
+ }
+ }
+
+ // Remove edges above the event point, insert edges below the event point.
+ do {
+ event = m_events.last();
+ m_events.pop_back();
+ edgeIndex = event.edge;
+
+ // Edges with zero length should not reach this part.
+ Q_ASSERT(m_parent->m_vertices.at(m_edges.at(edgeIndex).from) != m_parent->m_vertices.at(m_edges.at(edgeIndex).to));
+
+ if (m_edges.at(edgeIndex).node) {
+ Q_ASSERT(event.type == Event::Lower);
+ Q_ASSERT(event.point == m_parent->m_vertices.at(m_edges.at(event.edge).lower()));
+ m_edgeList.deleteNode(m_edges.at(edgeIndex).node);
+ } else {
+ Q_ASSERT(event.type == Event::Upper);
+ Q_ASSERT(event.point == m_parent->m_vertices.at(m_edges.at(event.edge).upper()));
+ QRBTree<int>::Node *left = searchEdgeLeftOf(edgeIndex, b.first);
+ m_edgeList.attachAfter(left, m_edges.at(edgeIndex).node = m_edgeList.newNode());
+ m_edges.at(edgeIndex).node->data = edgeIndex;
+ }
+ } while (!m_events.isEmpty() && m_events.last().point == event.point);
+
+ if (m_edgeList.root) {
+ QRBTree<int>::Node *current = (b.first ? m_edgeList.next(b.first) : m_edgeList.front(m_edgeList.root));
+
+ // Calculate winding number and turn counter-clockwise.
+ int currentWindingNumber = (b.first ? m_edges.at(b.first->data).winding : 0);
+ while (current != b.second) {
+ Q_ASSERT(current);
+ //Q_ASSERT(b.second == 0 || m_edgeList.order(current, b.second) < 0);
+ int i = current->data;
+ Q_ASSERT(m_edges.at(i).node == current);
+
+ // Winding number.
+ int ccwWindingNumber = m_edges.at(i).winding = currentWindingNumber;
+ if (m_edges.at(i).originallyPointingUp) {
+ --m_edges.at(i).winding;
+ } else {
+ ++m_edges.at(i).winding;
+ ++ccwWindingNumber;
+ }
+ currentWindingNumber = m_edges.at(i).winding;
+
+ // Turn counter-clockwise.
+ if ((ccwWindingNumber & 1) == 0) {
+ Q_ASSERT(m_edges.at(i).previous == -1 && m_edges.at(i).next == -1);
+ qSwap(m_edges.at(i).from, m_edges.at(i).to);
+ m_edges.at(i).pointingUp = !m_edges.at(i).pointingUp;
+ }
+
+ current = m_edgeList.next(current);
+ }
+
+ // Process edges that were inserted into the edge list at the current event point.
+ current = (b.second ? m_edgeList.previous(b.second) : m_edgeList.back(m_edgeList.root));
+ while (current != b.first) {
+ Q_ASSERT(current);
+ Q_ASSERT(m_edges.at(current->data).node == current);
+ insertEdgeIntoVectorIfWanted(orderedEdges, current->data);
+ current = m_edgeList.previous(current);
+ }
+ }
+ if (orderedEdges.isEmpty())
+ continue;
+
+ Q_ASSERT((orderedEdges.size() & 1) == 0);
+
+ // Connect edges.
+ // First make sure the first edge point towards the current point.
+ int i;
+ if (m_parent->m_vertices.at(m_edges.at(orderedEdges[0]).from) == event.point) {
+ i = 1;
+ int copy = orderedEdges[0]; // Make copy in case the append() will cause a reallocation.
+ orderedEdges.append(copy);
+ } else {
+ Q_ASSERT(m_parent->m_vertices.at(m_edges.at(orderedEdges[0]).to) == event.point);
+ i = 0;
+ }
+
+ // Remove references to duplicate points. First find the point with lowest index.
+ int pointIndex = INT_MAX;
+ for (int j = i; j < orderedEdges.size(); j += 2) {
+ Q_ASSERT(j + 1 < orderedEdges.size());
+ Q_ASSERT(m_parent->m_vertices.at(m_edges.at(orderedEdges[j]).to) == event.point);
+ Q_ASSERT(m_parent->m_vertices.at(m_edges.at(orderedEdges[j + 1]).from) == event.point);
+ if (m_edges.at(orderedEdges[j]).to < pointIndex)
+ pointIndex = m_edges.at(orderedEdges[j]).to;
+ if (m_edges.at(orderedEdges[j + 1]).from < pointIndex)
+ pointIndex = m_edges.at(orderedEdges[j + 1]).from;
+ }
+
+ for (; i < orderedEdges.size(); i += 2) {
+ // Remove references to duplicate points by making all edges reference one common point.
+ m_edges.at(orderedEdges[i]).to = m_edges.at(orderedEdges[i + 1]).from = pointIndex;
+
+ Q_ASSERT(m_edges.at(orderedEdges[i]).pointingUp || m_edges.at(orderedEdges[i]).previous != -1);
+ Q_ASSERT(!m_edges.at(orderedEdges[i + 1]).pointingUp || m_edges.at(orderedEdges[i + 1]).next != -1);
+
+ m_edges.at(orderedEdges[i]).next = orderedEdges[i + 1];
+ m_edges.at(orderedEdges[i + 1]).previous = orderedEdges[i];
+ }
+ } // end while
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::removeUnusedPoints() {
+ QBitArray used(m_parent->m_vertices.size(), false);
+ for (int i = 0; i < m_edges.size(); ++i) {
+ Q_ASSERT((m_edges.at(i).previous == -1) == (m_edges.at(i).next == -1));
+ if (m_edges.at(i).next != -1)
+ used.setBit(m_edges.at(i).from);
+ }
+ QDataBuffer<quint32> newMapping(m_parent->m_vertices.size());
+ newMapping.resize(m_parent->m_vertices.size());
+ int count = 0;
+ for (int i = 0; i < m_parent->m_vertices.size(); ++i) {
+ if (used.at(i)) {
+ m_parent->m_vertices.at(count) = m_parent->m_vertices.at(i);
+ newMapping.at(i) = count;
+ ++count;
+ }
+ }
+ m_parent->m_vertices.resize(count);
+ for (int i = 0; i < m_edges.size(); ++i) {
+ m_edges.at(i).from = newMapping.at(m_edges.at(i).from);
+ m_edges.at(i).to = newMapping.at(m_edges.at(i).to);
+ }
+}
+
+template <typename T>
+bool QTriangulator<T>::ComplexToSimple::CompareEdges::operator () (int i, int j) const
+{
+ int cmp = comparePoints(m_parent->m_parent->m_vertices.at(m_parent->m_edges.at(i).from),
+ m_parent->m_parent->m_vertices.at(m_parent->m_edges.at(j).from));
+ if (cmp == 0) {
+ cmp = comparePoints(m_parent->m_parent->m_vertices.at(m_parent->m_edges.at(i).to),
+ m_parent->m_parent->m_vertices.at(m_parent->m_edges.at(j).to));
+ }
+ return cmp > 0;
+}
+
+template <typename T>
+inline bool QTriangulator<T>::ComplexToSimple::Event::operator < (const Event &other) const
+{
+ if (point == other.point)
+ return type < other.type; // 'Lower' has higher priority than 'Upper'.
+ return other.point < point;
+}
+
+//============================================================================//
+// QTriangulator::ComplexToSimple::DebugDialog //
+//============================================================================//
+
+#ifdef Q_TRIANGULATOR_DEBUG
+template <typename T>
+QTriangulator<T>::ComplexToSimple::DebugDialog::DebugDialog(ComplexToSimple *parent, int currentVertex)
+ : m_parent(parent), m_vertex(currentVertex)
+{
+ QDataBuffer<QPodPoint> &vertices = m_parent->m_parent->m_vertices;
+ if (vertices.isEmpty())
+ return;
+
+ int minX, maxX, minY, maxY;
+ minX = maxX = vertices.at(0).x;
+ minY = maxY = vertices.at(0).y;
+ for (int i = 1; i < vertices.size(); ++i) {
+ minX = qMin(minX, vertices.at(i).x);
+ maxX = qMax(maxX, vertices.at(i).x);
+ minY = qMin(minY, vertices.at(i).y);
+ maxY = qMax(maxY, vertices.at(i).y);
+ }
+ int w = maxX - minX;
+ int h = maxY - minY;
+ qreal border = qMin(w, h) / 10.0;
+ m_window = QRectF(minX - border, minY - border, (maxX - minX + 2 * border), (maxY - minY + 2 * border));
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::DebugDialog::paintEvent(QPaintEvent *)
+{
+ QPainter p(this);
+ p.setRenderHint(QPainter::Antialiasing, true);
+ p.fillRect(rect(), Qt::black);
+ QDataBuffer<QPodPoint> &vertices = m_parent->m_parent->m_vertices;
+ if (vertices.isEmpty())
+ return;
+
+ qreal halfPointSize = qMin(m_window.width(), m_window.height()) / 300.0;
+ p.setWindow(m_window.toRect());
+
+ p.setPen(Qt::white);
+
+ QDataBuffer<Edge> &edges = m_parent->m_edges;
+ for (int i = 0; i < edges.size(); ++i) {
+ QPodPoint u = vertices.at(edges.at(i).from);
+ QPodPoint v = vertices.at(edges.at(i).to);
+ p.drawLine(u.x, u.y, v.x, v.y);
+ }
+
+ for (int i = 0; i < vertices.size(); ++i) {
+ QPodPoint q = vertices.at(i);
+ p.fillRect(QRectF(q.x - halfPointSize, q.y - halfPointSize, 2 * halfPointSize, 2 * halfPointSize), Qt::red);
+ }
+
+ Qt::GlobalColor colors[6] = {Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow};
+ p.setOpacity(0.5);
+ int count = 0;
+ if (m_parent->m_edgeList.root) {
+ QRBTree<int>::Node *current = m_parent->m_edgeList.front(m_parent->m_edgeList.root);
+ while (current) {
+ p.setPen(colors[count++ % 6]);
+ QPodPoint u = vertices.at(edges.at(current->data).from);
+ QPodPoint v = vertices.at(edges.at(current->data).to);
+ p.drawLine(u.x, u.y, v.x, v.y);
+ current = m_parent->m_edgeList.next(current);
+ }
+ }
+
+ p.setOpacity(1.0);
+ QPodPoint q = vertices.at(m_vertex);
+ p.fillRect(QRectF(q.x - halfPointSize, q.y - halfPointSize, 2 * halfPointSize, 2 * halfPointSize), Qt::green);
+
+ p.setPen(Qt::gray);
+ QDataBuffer<Split> &splits = m_parent->m_splits;
+ for (int i = 0; i < splits.size(); ++i) {
+ QPodPoint q = vertices.at(splits.at(i).vertex);
+ QPodPoint u = vertices.at(edges.at(splits.at(i).edge).from) - q;
+ QPodPoint v = vertices.at(edges.at(splits.at(i).edge).to) - q;
+ qreal uLen = sqrt(qreal(qDot(u, u)));
+ qreal vLen = sqrt(qreal(qDot(v, v)));
+ if (uLen) {
+ u.x *= 2 * halfPointSize / uLen;
+ u.y *= 2 * halfPointSize / uLen;
+ }
+ if (vLen) {
+ v.x *= 2 * halfPointSize / vLen;
+ v.y *= 2 * halfPointSize / vLen;
+ }
+ u += q;
+ v += q;
+ p.drawLine(u.x, u.y, v.x, v.y);
+ }
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::DebugDialog::wheelEvent(QWheelEvent *event)
+{
+ qreal scale = exp(-0.001 * event->delta());
+ QPointF center = m_window.center();
+ QPointF delta = scale * (m_window.bottomRight() - center);
+ m_window = QRectF(center - delta, center + delta);
+ event->accept();
+ update();
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::DebugDialog::mouseMoveEvent(QMouseEvent *event)
+{
+ if (event->buttons() & Qt::LeftButton) {
+ QPointF delta = event->pos() - m_lastMousePos;
+ delta.setX(delta.x() * m_window.width() / width());
+ delta.setY(delta.y() * m_window.height() / height());
+ m_window.translate(-delta.x(), -delta.y());
+ m_lastMousePos = event->pos();
+ event->accept();
+ update();
+ }
+}
+
+template <typename T>
+void QTriangulator<T>::ComplexToSimple::DebugDialog::mousePressEvent(QMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton)
+ m_lastMousePos = event->pos();
+ event->accept();
+}
+
+
+#endif
+
+//============================================================================//
+// QTriangulator::SimpleToMonotone //
+//============================================================================//
+template <typename T>
+void QTriangulator<T>::SimpleToMonotone::decompose()
+{
+ setupDataStructures();
+ removeZeroLengthEdges();
+ monotoneDecomposition();
+
+ m_parent->m_indices.clear();
+ QBitArray processed(m_edges.size(), false);
+ for (int first = 0; first < m_edges.size(); ++first) {
+ if (processed.at(first))
+ continue;
+ int i = first;
+ do {
+ Q_ASSERT(!processed.at(i));
+ Q_ASSERT(m_edges.at(m_edges.at(i).next).previous == i);
+ m_parent->m_indices.push_back(m_edges.at(i).from);
+ processed.setBit(i);
+ i = m_edges.at(i).next;
+ } while (i != first);
+ if (m_parent->m_indices.size() > 0 && m_parent->m_indices.back() != T(-1)) // Q_TRIANGULATE_END_OF_POLYGON
+ m_parent->m_indices.push_back(T(-1)); // Q_TRIANGULATE_END_OF_POLYGON
+ }
+}
+
+template <typename T>
+void QTriangulator<T>::SimpleToMonotone::setupDataStructures()
+{
+ int i = 0;
+ Edge e;
+ e.node = 0;
+ e.twin = -1;
+
+ while (i + 3 <= m_parent->m_indices.size()) {
+ int start = m_edges.size();
+
+ do {
+ e.from = m_parent->m_indices.at(i);
+ e.type = RegularVertex;
+ e.next = m_edges.size() + 1;
+ e.previous = m_edges.size() - 1;
+ m_edges.add(e);
+ ++i;
+ Q_ASSERT(i < m_parent->m_indices.size());
+ } while (m_parent->m_indices.at(i) != T(-1)); // Q_TRIANGULATE_END_OF_POLYGON
+
+ m_edges.last().next = start;
+ m_edges.at(start).previous = m_edges.size() - 1;
+ ++i; // Skip Q_TRIANGULATE_END_OF_POLYGON.
+ }
+
+ for (i = 0; i < m_edges.size(); ++i) {
+ m_edges.at(i).to = m_edges.at(m_edges.at(i).next).from;
+ m_edges.at(i).pointingUp = m_parent->m_vertices.at(m_edges.at(i).to) < m_parent->m_vertices.at(m_edges.at(i).from);
+ m_edges.at(i).helper = -1; // Not initialized here.
+ }
+}
+
+template <typename T>
+void QTriangulator<T>::SimpleToMonotone::removeZeroLengthEdges()
+{
+ for (int i = 0; i < m_edges.size(); ++i) {
+ if (m_parent->m_vertices.at(m_edges.at(i).from) == m_parent->m_vertices.at(m_edges.at(i).to)) {
+ m_edges.at(m_edges.at(i).previous).next = m_edges.at(i).next;
+ m_edges.at(m_edges.at(i).next).previous = m_edges.at(i).previous;
+ m_edges.at(m_edges.at(i).next).from = m_edges.at(i).from;
+ m_edges.at(i).next = -1; // Mark as removed.
+ }
+ }
+
+ QDataBuffer<int> newMapping(m_edges.size());
+ newMapping.resize(m_edges.size());
+ int count = 0;
+ for (int i = 0; i < m_edges.size(); ++i) {
+ if (m_edges.at(i).next != -1) {
+ m_edges.at(count) = m_edges.at(i);
+ newMapping.at(i) = count;
+ ++count;
+ }
+ }
+ m_edges.resize(count);
+ for (int i = 0; i < m_edges.size(); ++i) {
+ m_edges.at(i).next = newMapping.at(m_edges.at(i).next);
+ m_edges.at(i).previous = newMapping.at(m_edges.at(i).previous);
+ }
+}
+
+template <typename T>
+void QTriangulator<T>::SimpleToMonotone::fillPriorityQueue()
+{
+ m_upperVertex.reset();
+ m_upperVertex.reserve(m_edges.size());
+ for (int i = 0; i < m_edges.size(); ++i)
+ m_upperVertex.add(i);
+ CompareVertices cmp(this);
+ //qSort(m_upperVertex.data(), m_upperVertex.data() + m_upperVertex.size(), cmp);
+ sort(m_upperVertex.data(), m_upperVertex.size(), cmp);
+ //for (int i = 1; i < m_upperVertex.size(); ++i) {
+ // Q_ASSERT(!cmp(m_upperVertex.at(i), m_upperVertex.at(i - 1)));
+ //}
+}
+
+template <typename T>
+bool QTriangulator<T>::SimpleToMonotone::edgeIsLeftOfEdge(int leftEdgeIndex, int rightEdgeIndex) const
+{
+ const Edge &leftEdge = m_edges.at(leftEdgeIndex);
+ const Edge &rightEdge = m_edges.at(rightEdgeIndex);
+ const QPodPoint &u = m_parent->m_vertices.at(rightEdge.upper());
+ const QPodPoint &l = m_parent->m_vertices.at(rightEdge.lower());
+ qint64 d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(m_parent->m_vertices.at(leftEdge.upper()), l, u);
+ // d < 0: left, d > 0: right, d == 0: on top
+ if (d == 0)
+ d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(m_parent->m_vertices.at(leftEdge.lower()), l, u);
+ return d < 0;
+}
+
+// Returns the rightmost edge not to the right of the given edge.
+template <typename T>
+QRBTree<int>::Node *QTriangulator<T>::SimpleToMonotone::searchEdgeLeftOfEdge(int edgeIndex) const
+{
+ QRBTree<int>::Node *current = m_edgeList.root;
+ QRBTree<int>::Node *result = 0;
+ while (current) {
+ if (edgeIsLeftOfEdge(edgeIndex, current->data)) {
+ current = current->left;
+ } else {
+ result = current;
+ current = current->right;
+ }
+ }
+ return result;
+}
+
+// Returns the rightmost edge left of the given point.
+template <typename T>
+QRBTree<int>::Node *QTriangulator<T>::SimpleToMonotone::searchEdgeLeftOfPoint(int pointIndex) const
+{
+ QRBTree<int>::Node *current = m_edgeList.root;
+ QRBTree<int>::Node *result = 0;
+ while (current) {
+ const QPodPoint &p1 = m_parent->m_vertices.at(m_edges.at(current->data).lower());
+ const QPodPoint &p2 = m_parent->m_vertices.at(m_edges.at(current->data).upper());
+ qint64 d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(m_parent->m_vertices.at(pointIndex), p1, p2);
+ if (d <= 0) {
+ current = current->left;
+ } else {
+ result = current;
+ current = current->right;
+ }
+ }
+ return result;
+}
+
+template <typename T>
+void QTriangulator<T>::SimpleToMonotone::classifyVertex(int i)
+{
+ Edge &e2 = m_edges.at(i);
+ const Edge &e1 = m_edges.at(e2.previous);
+
+ bool startOrSplit = (e1.pointingUp && !e2.pointingUp);
+ bool endOrMerge = (!e1.pointingUp && e2.pointingUp);
+
+ const QPodPoint &p1 = m_parent->m_vertices.at(e1.from);
+ const QPodPoint &p2 = m_parent->m_vertices.at(e2.from);
+ const QPodPoint &p3 = m_parent->m_vertices.at(e2.to);
+ qint64 d = QT_PREPEND_NAMESPACE(qPointDistanceFromLine)(p1, p2, p3);
+ Q_ASSERT(d != 0 || (!startOrSplit && !endOrMerge));
+
+ e2.type = RegularVertex;
+
+ if (m_clockwiseOrder) {
+ if (startOrSplit)
+ e2.type = (d < 0 ? SplitVertex : StartVertex);
+ else if (endOrMerge)
+ e2.type = (d < 0 ? MergeVertex : EndVertex);
+ } else {
+ if (startOrSplit)
+ e2.type = (d > 0 ? SplitVertex : StartVertex);
+ else if (endOrMerge)
+ e2.type = (d > 0 ? MergeVertex : EndVertex);
+ }
+}
+
+template <typename T>
+void QTriangulator<T>::SimpleToMonotone::classifyVertices()
+{
+ for (int i = 0; i < m_edges.size(); ++i)
+ classifyVertex(i);
+}
+
+template <typename T>
+bool QTriangulator<T>::SimpleToMonotone::pointIsInSector(const QPodPoint &p, const QPodPoint &v1, const QPodPoint &v2, const QPodPoint &v3)
+{
+ bool leftOfPreviousEdge = !qPointIsLeftOfLine(p, v2, v1);
+ bool leftOfNextEdge = !qPointIsLeftOfLine(p, v3, v2);
+
+ if (qPointIsLeftOfLine(v1, v2, v3))
+ return leftOfPreviousEdge && leftOfNextEdge;
+ else
+ return leftOfPreviousEdge || leftOfNextEdge;
+}
+
+template <typename T>
+bool QTriangulator<T>::SimpleToMonotone::pointIsInSector(int vertex, int sector)
+{
+ const QPodPoint &center = m_parent->m_vertices.at(m_edges.at(sector).from);
+ // Handle degenerate edges.
+ while (m_parent->m_vertices.at(m_edges.at(vertex).from) == center)
+ vertex = m_edges.at(vertex).next;
+ int next = m_edges.at(sector).next;
+ while (m_parent->m_vertices.at(m_edges.at(next).from) == center)
+ next = m_edges.at(next).next;
+ int previous = m_edges.at(sector).previous;
+ while (m_parent->m_vertices.at(m_edges.at(previous).from) == center)
+ previous = m_edges.at(previous).previous;
+
+ const QPodPoint &p = m_parent->m_vertices.at(m_edges.at(vertex).from);
+ const QPodPoint &v1 = m_parent->m_vertices.at(m_edges.at(previous).from);
+ const QPodPoint &v3 = m_parent->m_vertices.at(m_edges.at(next).from);
+ if (m_clockwiseOrder)
+ return pointIsInSector(p, v3, center, v1);
+ else
+ return pointIsInSector(p, v1, center, v3);
+}
+
+template <typename T>
+int QTriangulator<T>::SimpleToMonotone::findSector(int edge, int vertex)
+{
+ while (!pointIsInSector(vertex, edge)) {
+ edge = m_edges.at(m_edges.at(edge).previous).twin;
+ Q_ASSERT(edge != -1);
+ }
+ return edge;
+}
+
+template <typename T>
+void QTriangulator<T>::SimpleToMonotone::createDiagonal(int lower, int upper)
+{
+ lower = findSector(lower, upper);
+ upper = findSector(upper, lower);
+
+ int prevLower = m_edges.at(lower).previous;
+ int prevUpper = m_edges.at(upper).previous;
+
+ Edge e;
+
+ e.twin = m_edges.size() + 1;
+ e.next = upper;
+ e.previous = prevLower;
+ e.from = m_edges.at(lower).from;
+ e.to = m_edges.at(upper).from;
+ m_edges.at(upper).previous = m_edges.at(prevLower).next = int(m_edges.size());
+ m_edges.add(e);
+
+ e.twin = m_edges.size() - 1;
+ e.next = lower;
+ e.previous = prevUpper;
+ e.from = m_edges.at(upper).from;
+ e.to = m_edges.at(lower).from;
+ m_edges.at(lower).previous = m_edges.at(prevUpper).next = int(m_edges.size());
+ m_edges.add(e);
+}
+
+template <typename T>
+void QTriangulator<T>::SimpleToMonotone::monotoneDecomposition()
+{
+ if (m_edges.isEmpty())
+ return;
+
+ Q_ASSERT(!m_edgeList.root);
+ QDataBuffer<QPair<int, int> > diagonals(m_upperVertex.size());
+
+ int i = 0;
+ for (int index = 1; index < m_edges.size(); ++index) {
+ if (m_parent->m_vertices.at(m_edges.at(index).from) < m_parent->m_vertices.at(m_edges.at(i).from))
+ i = index;
+ }
+ Q_ASSERT(i < m_edges.size());
+ int j = m_edges.at(i).previous;
+ Q_ASSERT(j < m_edges.size());
+ m_clockwiseOrder = qPointIsLeftOfLine(m_parent->m_vertices.at((quint32)m_edges.at(i).from),
+ m_parent->m_vertices.at((quint32)m_edges.at(j).from), m_parent->m_vertices.at((quint32)m_edges.at(i).to));
+
+ classifyVertices();
+ fillPriorityQueue();
+
+ // debug: set helpers explicitly (shouldn't be necessary)
+ //for (int i = 0; i < m_edges.size(); ++i)
+ // m_edges.at(i).helper = m_edges.at(i).upper();
+
+ while (!m_upperVertex.isEmpty()) {
+ i = m_upperVertex.last();
+ Q_ASSERT(i < m_edges.size());
+ m_upperVertex.pop_back();
+ j = m_edges.at(i).previous;
+ Q_ASSERT(j < m_edges.size());
+
+ QRBTree<int>::Node *leftEdgeNode = 0;
+
+ switch (m_edges.at(i).type) {
+ case RegularVertex:
+ // If polygon interior is to the right of the vertex...
+ if (m_edges.at(i).pointingUp == m_clockwiseOrder) {
+ if (m_edges.at(i).node) {
+ Q_ASSERT(!m_edges.at(j).node);
+ if (m_edges.at(m_edges.at(i).helper).type == MergeVertex)
+ diagonals.add(QPair<int, int>(i, m_edges.at(i).helper));
+ m_edges.at(j).node = m_edges.at(i).node;
+ m_edges.at(i).node = 0;
+ m_edges.at(j).node->data = j;
+ m_edges.at(j).helper = i;
+ } else if (m_edges.at(j).node) {
+ Q_ASSERT(!m_edges.at(i).node);
+ if (m_edges.at(m_edges.at(j).helper).type == MergeVertex)
+ diagonals.add(QPair<int, int>(i, m_edges.at(j).helper));
+ m_edges.at(i).node = m_edges.at(j).node;
+ m_edges.at(j).node = 0;
+ m_edges.at(i).node->data = i;
+ m_edges.at(i).helper = i;
+ } else {
+ qWarning("Inconsistent polygon. (#1)");
+ }
+ } else {
+ leftEdgeNode = searchEdgeLeftOfPoint(m_edges.at(i).from);
+ if (leftEdgeNode) {
+ if (m_edges.at(m_edges.at(leftEdgeNode->data).helper).type == MergeVertex)
+ diagonals.add(QPair<int, int>(i, m_edges.at(leftEdgeNode->data).helper));
+ m_edges.at(leftEdgeNode->data).helper = i;
+ } else {
+ qWarning("Inconsistent polygon. (#2)");
+ }
+ }
+ break;
+ case SplitVertex:
+ leftEdgeNode = searchEdgeLeftOfPoint(m_edges.at(i).from);
+ if (leftEdgeNode) {
+ diagonals.add(QPair<int, int>(i, m_edges.at(leftEdgeNode->data).helper));
+ m_edges.at(leftEdgeNode->data).helper = i;
+ } else {
+ qWarning("Inconsistent polygon. (#3)");
+ }
+ // Fall through.
+ case StartVertex:
+ if (m_clockwiseOrder) {
+ leftEdgeNode = searchEdgeLeftOfEdge(j);
+ QRBTree<int>::Node *node = m_edgeList.newNode();
+ node->data = j;
+ m_edges.at(j).node = node;
+ m_edges.at(j).helper = i;
+ m_edgeList.attachAfter(leftEdgeNode, node);
+ Q_ASSERT(m_edgeList.validate());
+ } else {
+ leftEdgeNode = searchEdgeLeftOfEdge(i);
+ QRBTree<int>::Node *node = m_edgeList.newNode();
+ node->data = i;
+ m_edges.at(i).node = node;
+ m_edges.at(i).helper = i;
+ m_edgeList.attachAfter(leftEdgeNode, node);
+ Q_ASSERT(m_edgeList.validate());
+ }
+ break;
+ case MergeVertex:
+ leftEdgeNode = searchEdgeLeftOfPoint(m_edges.at(i).from);
+ if (leftEdgeNode) {
+ if (m_edges.at(m_edges.at(leftEdgeNode->data).helper).type == MergeVertex)
+ diagonals.add(QPair<int, int>(i, m_edges.at(leftEdgeNode->data).helper));
+ m_edges.at(leftEdgeNode->data).helper = i;
+ } else {
+ qWarning("Inconsistent polygon. (#4)");
+ }
+ // Fall through.
+ case EndVertex:
+ if (m_clockwiseOrder) {
+ if (m_edges.at(m_edges.at(i).helper).type == MergeVertex)
+ diagonals.add(QPair<int, int>(i, m_edges.at(i).helper));
+ if (m_edges.at(i).node) {
+ m_edgeList.deleteNode(m_edges.at(i).node);
+ Q_ASSERT(m_edgeList.validate());
+ } else {
+ qWarning("Inconsistent polygon. (#5)");
+ }
+ } else {
+ if (m_edges.at(m_edges.at(j).helper).type == MergeVertex)
+ diagonals.add(QPair<int, int>(i, m_edges.at(j).helper));
+ if (m_edges.at(j).node) {
+ m_edgeList.deleteNode(m_edges.at(j).node);
+ Q_ASSERT(m_edgeList.validate());
+ } else {
+ qWarning("Inconsistent polygon. (#6)");
+ }
+ }
+ break;
+ }
+ }
+
+ for (int i = 0; i < diagonals.size(); ++i)
+ createDiagonal(diagonals.at(i).first, diagonals.at(i).second);
+}
+
+template <typename T>
+bool QTriangulator<T>::SimpleToMonotone::CompareVertices::operator () (int i, int j) const
+{
+ if (m_parent->m_edges.at(i).from == m_parent->m_edges.at(j).from)
+ return m_parent->m_edges.at(i).type > m_parent->m_edges.at(j).type;
+ return m_parent->m_parent->m_vertices.at(m_parent->m_edges.at(i).from) >
+ m_parent->m_parent->m_vertices.at(m_parent->m_edges.at(j).from);
+}
+
+//============================================================================//
+// QTriangulator::MonotoneToTriangles //
+//============================================================================//
+template <typename T>
+void QTriangulator<T>::MonotoneToTriangles::decompose()
+{
+ QVector<T> result;
+ QDataBuffer<int> stack(m_parent->m_indices.size());
+ m_first = 0;
+ // Require at least three more indices.
+ while (m_first + 3 <= m_parent->m_indices.size()) {
+ m_length = 0;
+ while (m_parent->m_indices.at(m_first + m_length) != T(-1)) { // Q_TRIANGULATE_END_OF_POLYGON
+ ++m_length;
+ Q_ASSERT(m_first + m_length < m_parent->m_indices.size());
+ }
+ if (m_length < 3) {
+ m_first += m_length + 1;
+ continue;
+ }
+
+ int minimum = 0;
+ while (less(next(minimum), minimum))
+ minimum = next(minimum);
+ while (less(previous(minimum), minimum))
+ minimum = previous(minimum);
+
+ stack.reset();
+ stack.add(minimum);
+ int left = previous(minimum);
+ int right = next(minimum);
+ bool stackIsOnLeftSide;
+ bool clockwiseOrder = leftOfEdge(minimum, left, right);
+
+ if (less(left, right)) {
+ stack.add(left);
+ left = previous(left);
+ stackIsOnLeftSide = true;
+ } else {
+ stack.add(right);
+ right = next(right);
+ stackIsOnLeftSide = false;
+ }
+
+ for (int count = 0; count + 2 < m_length; ++count)
+ {
+ Q_ASSERT(stack.size() >= 2);
+ if (less(left, right)) {
+ if (stackIsOnLeftSide == false) {
+ for (int i = 0; i + 1 < stack.size(); ++i) {
+ result.push_back(indices(stack.at(i + 1)));
+ result.push_back(indices(left));
+ result.push_back(indices(stack.at(i)));
+ }
+ stack.first() = stack.last();
+ stack.resize(1);
+ } else {
+ while (stack.size() >= 2 && (clockwiseOrder ^ !leftOfEdge(left, stack.at(stack.size() - 2), stack.last()))) {
+ result.push_back(indices(stack.at(stack.size() - 2)));
+ result.push_back(indices(left));
+ result.push_back(indices(stack.last()));
+ stack.pop_back();
+ }
+ }
+ stack.add(left);
+ left = previous(left);
+ stackIsOnLeftSide = true;
+ } else {
+ if (stackIsOnLeftSide == true) {
+ for (int i = 0; i + 1 < stack.size(); ++i) {
+ result.push_back(indices(stack.at(i)));
+ result.push_back(indices(right));
+ result.push_back(indices(stack.at(i + 1)));
+ }
+ stack.first() = stack.last();
+ stack.resize(1);
+ } else {
+ while (stack.size() >= 2 && (clockwiseOrder ^ !leftOfEdge(right, stack.last(), stack.at(stack.size() - 2)))) {
+ result.push_back(indices(stack.last()));
+ result.push_back(indices(right));
+ result.push_back(indices(stack.at(stack.size() - 2)));
+ stack.pop_back();
+ }
+ }
+ stack.add(right);
+ right = next(right);
+ stackIsOnLeftSide = false;
+ }
+ }
+
+ m_first += m_length + 1;
+ }
+ m_parent->m_indices = result;
+}
+
+//============================================================================//
+// qTriangulate //
+//============================================================================//
+
+QTriangleSet qTriangulate(const qreal *polygon,
+ int count, uint hint, const QTransform &matrix)
+{
+ QTriangleSet triangleSet;
+ if (QGLExtensions::glExtensions() & QGLExtensions::ElementIndexUint) {
+ QTriangulator<quint32> triangulator;
+ triangulator.initialize(polygon, count, hint, matrix);
+ QVertexSet<quint32> vertexSet = triangulator.triangulate();
+ triangleSet.vertices = vertexSet.vertices;
+ triangleSet.indices.setDataUint(vertexSet.indices);
+
+ } else {
+ QTriangulator<quint16> triangulator;
+ triangulator.initialize(polygon, count, hint, matrix);
+ QVertexSet<quint16> vertexSet = triangulator.triangulate();
+ triangleSet.vertices = vertexSet.vertices;
+ triangleSet.indices.setDataUshort(vertexSet.indices);
+ }
+ return triangleSet;
+}
+
+QTriangleSet qTriangulate(const QVectorPath &path,
+ const QTransform &matrix, qreal lod)
+{
+ QTriangleSet triangleSet;
+ if (QGLExtensions::glExtensions() & QGLExtensions::ElementIndexUint) {
+ QTriangulator<quint32> triangulator;
+ triangulator.initialize(path, matrix, lod);
+ QVertexSet<quint32> vertexSet = triangulator.triangulate();
+ triangleSet.vertices = vertexSet.vertices;
+ triangleSet.indices.setDataUint(vertexSet.indices);
+ } else {
+ QTriangulator<quint16> triangulator;
+ triangulator.initialize(path, matrix, lod);
+ QVertexSet<quint16> vertexSet = triangulator.triangulate();
+ triangleSet.vertices = vertexSet.vertices;
+ triangleSet.indices.setDataUshort(vertexSet.indices);
+ }
+ return triangleSet;
+}
+
+QTriangleSet qTriangulate(const QPainterPath &path,
+ const QTransform &matrix, qreal lod)
+{
+ QTriangleSet triangleSet;
+ if (QGLExtensions::glExtensions() & QGLExtensions::ElementIndexUint) {
+ QTriangulator<quint32> triangulator;
+ triangulator.initialize(path, matrix, lod);
+ QVertexSet<quint32> vertexSet = triangulator.triangulate();
+ triangleSet.vertices = vertexSet.vertices;
+ triangleSet.indices.setDataUint(vertexSet.indices);
+ } else {
+ QTriangulator<quint16> triangulator;
+ triangulator.initialize(path, matrix, lod);
+ QVertexSet<quint16> vertexSet = triangulator.triangulate();
+ triangleSet.vertices = vertexSet.vertices;
+ triangleSet.indices.setDataUshort(vertexSet.indices);
+ }
+ return triangleSet;
+}
+
+QPolylineSet qPolyline(const QVectorPath &path,
+ const QTransform &matrix, qreal lod)
+{
+ QPolylineSet polyLineSet;
+ if (QGLExtensions::glExtensions() & QGLExtensions::ElementIndexUint) {
+ QTriangulator<quint32> triangulator;
+ triangulator.initialize(path, matrix, lod);
+ QVertexSet<quint32> vertexSet = triangulator.polyline();
+ polyLineSet.vertices = vertexSet.vertices;
+ polyLineSet.indices.setDataUint(vertexSet.indices);
+ } else {
+ QTriangulator<quint16> triangulator;
+ triangulator.initialize(path, matrix, lod);
+ QVertexSet<quint16> vertexSet = triangulator.triangulate();
+ polyLineSet.vertices = vertexSet.vertices;
+ polyLineSet.indices.setDataUshort(vertexSet.indices);
+ }
+ return polyLineSet;
+}
+
+QPolylineSet qPolyline(const QPainterPath &path,
+ const QTransform &matrix, qreal lod)
+{
+ QPolylineSet polyLineSet;
+ if (QGLExtensions::glExtensions() & QGLExtensions::ElementIndexUint) {
+ QTriangulator<quint32> triangulator;
+ triangulator.initialize(path, matrix, lod);
+ QVertexSet<quint32> vertexSet = triangulator.polyline();
+ polyLineSet.vertices = vertexSet.vertices;
+ polyLineSet.indices.setDataUint(vertexSet.indices);
+ } else {
+ QTriangulator<quint16> triangulator;
+ triangulator.initialize(path, matrix, lod);
+ QVertexSet<quint16> vertexSet = triangulator.triangulate();
+ polyLineSet.vertices = vertexSet.vertices;
+ polyLineSet.indices.setDataUshort(vertexSet.indices);
+ }
+ return polyLineSet;
+}
+
+QT_END_NAMESPACE
diff --git a/src/opengl/gl2paintengineex/qtriangulator_p.h b/src/opengl/gl2paintengineex/qtriangulator_p.h
new file mode 100644
index 0000000000..a205b923e9
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qtriangulator_p.h
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTRIANGULATOR_P_H
+#define QTRIANGULATOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qvector.h>
+#include <QtGui/private/qvectorpath_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QVertexIndexVector
+{
+public:
+ enum Type {
+ UnsignedInt,
+ UnsignedShort
+ };
+
+ inline Type type() const { return t; }
+
+ inline void setDataUint(const QVector<quint32> &data)
+ {
+ t = UnsignedInt;
+ indices32 = data;
+ }
+
+ inline void setDataUshort(const QVector<quint16> &data)
+ {
+ t = UnsignedShort;
+ indices16 = data;
+ }
+
+ inline const void* data() const
+ {
+ if (t == UnsignedInt)
+ return indices32.data();
+ return indices16.data();
+ }
+
+ inline int size() const
+ {
+ if (t == UnsignedInt)
+ return indices32.size();
+ return indices16.size();
+ }
+
+ inline QVertexIndexVector &operator = (const QVertexIndexVector &other)
+ {
+ if (t == UnsignedInt)
+ indices32 = other.indices32;
+ else
+ indices16 = other.indices16;
+
+ return *this;
+ }
+
+private:
+
+ Type t;
+ QVector<quint32> indices32;
+ QVector<quint16> indices16;
+};
+
+struct QTriangleSet
+{
+ inline QTriangleSet() { }
+ inline QTriangleSet(const QTriangleSet &other) : vertices(other.vertices), indices(other.indices) { }
+ QTriangleSet &operator = (const QTriangleSet &other) {vertices = other.vertices; indices = other.indices; return *this;}
+
+ // The vertices of a triangle are given by: (x[i[n]], y[i[n]]), (x[j[n]], y[j[n]]), (x[k[n]], y[k[n]]), n = 0, 1, ...
+ QVector<qreal> vertices; // [x[0], y[0], x[1], y[1], x[2], ...]
+ QVertexIndexVector indices; // [i[0], j[0], k[0], i[1], j[1], k[1], i[2], ...]
+};
+
+struct QPolylineSet
+{
+ inline QPolylineSet() { }
+ inline QPolylineSet(const QPolylineSet &other) : vertices(other.vertices), indices(other.indices) { }
+ QPolylineSet &operator = (const QPolylineSet &other) {vertices = other.vertices; indices = other.indices; return *this;}
+
+ QVector<qreal> vertices; // [x[0], y[0], x[1], y[1], x[2], ...]
+ QVertexIndexVector indices;
+};
+
+// The vertex coordinates of the returned triangle set will be rounded to a grid with a mesh size
+// of 1/32. The polygon is first transformed, then scaled by 32, the coordinates are rounded to
+// integers, the polygon is triangulated, and then scaled back by 1/32.
+// 'hint' should be a combination of QVectorPath::Hints.
+// 'lod' is the level of detail. Default is 1. Curves are split into more lines when 'lod' is higher.
+QTriangleSet qTriangulate(const qreal *polygon, int count, uint hint = QVectorPath::PolygonHint | QVectorPath::OddEvenFill, const QTransform &matrix = QTransform());
+QTriangleSet qTriangulate(const QVectorPath &path, const QTransform &matrix = QTransform(), qreal lod = 1);
+QTriangleSet qTriangulate(const QPainterPath &path, const QTransform &matrix = QTransform(), qreal lod = 1);
+QPolylineSet qPolyline(const QVectorPath &path, const QTransform &matrix = QTransform(), qreal lod = 1);
+QPolylineSet qPolyline(const QPainterPath &path, const QTransform &matrix = QTransform(), qreal lod = 1);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/opengl/opengl.pro b/src/opengl/opengl.pro
new file mode 100644
index 0000000000..e7c1c446de
--- /dev/null
+++ b/src/opengl/opengl.pro
@@ -0,0 +1,181 @@
+TARGET = QtOpenGL
+QPRO_PWD = $$PWD
+QT = core gui
+DEFINES += QT_BUILD_OPENGL_LIB
+DEFINES += QT_NO_USING_NAMESPACE
+win32-msvc*|win32-icc:QMAKE_LFLAGS += /BASE:0x63000000
+solaris-cc*:QMAKE_CXXFLAGS_RELEASE -= -O2
+irix-cc*:QMAKE_CXXFLAGS += -no_prelink -ptused
+
+unix|win32-g++*:QMAKE_PKGCONFIG_REQUIRES = QtCore QtGui
+
+include(../qbase.pri)
+
+!win32:!embedded:!mac:!symbian:!qpa:CONFIG += x11
+contains(QT_CONFIG, opengl):CONFIG += opengl
+contains(QT_CONFIG, opengles1):CONFIG += opengles1
+contains(QT_CONFIG, opengles2):CONFIG += opengles2
+contains(QT_CONFIG, egl):CONFIG += egl
+
+HEADERS += qgl.h \
+ qgl_p.h \
+ qglcolormap.h \
+ qglfunctions.h \
+ qglpixelbuffer.h \
+ qglpixelbuffer_p.h \
+ qglframebufferobject.h \
+ qglframebufferobject_p.h \
+ qglextensions_p.h \
+ qglpaintdevice_p.h \
+ qglbuffer.h \
+
+
+SOURCES += qgl.cpp \
+ qglcolormap.cpp \
+ qglfunctions.cpp \
+ qglpixelbuffer.cpp \
+ qglframebufferobject.cpp \
+ qglextensions.cpp \
+ qglpaintdevice.cpp \
+ qglbuffer.cpp \
+
+
+!contains(QT_CONFIG, opengles2) {
+ HEADERS += qpaintengine_opengl_p.h
+ SOURCES += qpaintengine_opengl.cpp
+}
+
+!contains(QT_CONFIG, opengles1) {
+ HEADERS += qglshaderprogram.h \
+ qglpixmapfilter_p.h \
+ qgraphicsshadereffect_p.h \
+ qgraphicssystem_gl_p.h \
+ qwindowsurface_gl_p.h \
+ qpixmapdata_gl_p.h \
+ gl2paintengineex/qglgradientcache_p.h \
+ gl2paintengineex/qglengineshadermanager_p.h \
+ gl2paintengineex/qgl2pexvertexarray_p.h \
+ gl2paintengineex/qpaintengineex_opengl2_p.h \
+ gl2paintengineex/qglengineshadersource_p.h \
+ gl2paintengineex/qglcustomshaderstage_p.h \
+ gl2paintengineex/qtriangulatingstroker_p.h \
+ gl2paintengineex/qtriangulator_p.h \
+ gl2paintengineex/qtextureglyphcache_gl_p.h \
+ gl2paintengineex/qglshadercache_p.h \
+ gl2paintengineex/qglshadercache_meego_p.h
+
+ SOURCES += qglshaderprogram.cpp \
+ qglpixmapfilter.cpp \
+ qgraphicsshadereffect.cpp \
+ qgraphicssystem_gl.cpp \
+ qwindowsurface_gl.cpp \
+ qpixmapdata_gl.cpp \
+ gl2paintengineex/qglgradientcache.cpp \
+ gl2paintengineex/qglengineshadermanager.cpp \
+ gl2paintengineex/qgl2pexvertexarray.cpp \
+ gl2paintengineex/qpaintengineex_opengl2.cpp \
+ gl2paintengineex/qglcustomshaderstage.cpp \
+ gl2paintengineex/qtriangulatingstroker.cpp \
+ gl2paintengineex/qtriangulator.cpp \
+ gl2paintengineex/qtextureglyphcache_gl.cpp
+
+}
+
+qpa {
+ SOURCES += qgl_qpa.cpp \
+ qglpixelbuffer_stub.cpp
+}
+
+x11 {
+ contains(QT_CONFIG, egl) {
+ SOURCES += qgl_x11egl.cpp \
+ qglpixelbuffer_egl.cpp \
+ qgl_egl.cpp \
+ qpixmapdata_x11gl_egl.cpp \
+ qwindowsurface_x11gl.cpp
+
+ HEADERS += qgl_egl_p.h \
+ qpixmapdata_x11gl_p.h \
+ qwindowsurface_x11gl_p.h
+
+ } else {
+ SOURCES += qgl_x11.cpp \
+ qglpixelbuffer_x11.cpp
+ }
+
+ contains(QT_CONFIG, fontconfig) {
+ contains(QT_CONFIG, system-freetype) {
+ embedded:CONFIG += opentype
+ # pull in the proper freetype2 include directory
+ include($$QT_SOURCE_TREE/config.tests/unix/freetype/freetype.pri)
+ LIBS_PRIVATE += -lfreetype
+ } else {
+ ### Note: how does this compile with a non-system freetype?
+ # This probably does not compile
+ }
+ } else {
+ DEFINES *= QT_NO_FREETYPE
+ }
+
+ LIBS_PRIVATE += $$QMAKE_LIBS_DYNLOAD
+}
+
+mac:!qpa {
+ OBJECTIVE_SOURCES += qgl_mac.mm \
+ qglpixelbuffer_mac.mm
+ LIBS_PRIVATE += -framework AppKit -framework Carbon
+}
+win32:!wince*: {
+ DEFINES += QT_NO_EGL
+ SOURCES += qgl_win.cpp \
+ qglpixelbuffer_win.cpp
+}
+wince*: {
+ SOURCES += qgl_wince.cpp \
+ qglpixelbuffer_egl.cpp \
+ qgl_egl.cpp
+
+ HEADERS += qgl_egl_p.h
+}
+
+embedded {
+ SOURCES += qgl_qws.cpp \
+ qglpixelbuffer_egl.cpp \
+ qglscreen_qws.cpp \
+ qglwindowsurface_qws.cpp \
+ qgl_egl.cpp
+
+ HEADERS += qglscreen_qws.h \
+ qglwindowsurface_qws_p.h \
+ qgl_egl_p.h
+
+ contains(QT_CONFIG, fontconfig) {
+ include($$QT_SOURCE_TREE/config.tests/unix/freetype/freetype.pri)
+ } else {
+ DEFINES *= QT_NO_FREETYPE
+ }
+}
+
+symbian {
+ DEFINES += QGL_USE_TEXTURE_POOL QGL_NO_PRESERVED_SWAP
+ SOURCES -= qpixmapdata_gl.cpp
+ SOURCES += qgl_symbian.cpp \
+ qpixmapdata_poolgl.cpp \
+ qglpixelbuffer_egl.cpp \
+ qgl_egl.cpp \
+ qgltexturepool.cpp
+
+ HEADERS += qgl_egl_p.h \
+ qgltexturepool_p.h
+
+ contains(QT_CONFIG, freetype) {
+ DEFINES += QT_NO_FONTCONFIG
+ INCLUDEPATH += \
+ ../3rdparty/freetype/src \
+ ../3rdparty/freetype/include
+ }
+
+ symbian:TARGET.UID3 = 0x2002131A
+}
+
+INCLUDEPATH += ../3rdparty/harfbuzz/src
diff --git a/src/opengl/opengl.pro.user.2.1pre1 b/src/opengl/opengl.pro.user.2.1pre1
new file mode 100644
index 0000000000..0c38724b0b
--- /dev/null
+++ b/src/opengl/opengl.pro.user.2.1pre1
@@ -0,0 +1,83 @@
+<!DOCTYPE QtCreatorProject>
+<qtcreator>
+ <data>
+ <variable>ProjectExplorer.Project.ActiveTarget</variable>
+ <value type="int">0</value>
+ </data>
+ <data>
+ <variable>ProjectExplorer.Project.EditorSettings</variable>
+ <valuemap type="QVariantMap">
+ <value key="EditorConfiguration.Codec" type="QByteArray">UTF-8</value>
+ </valuemap>
+ </data>
+ <data>
+ <variable>ProjectExplorer.Project.Target.0</variable>
+ <valuemap type="QVariantMap">
+ <value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Desktop</value>
+ <value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.Target.DesktopTarget</value>
+ <value key="ProjectExplorer.Target.ActiveBuildConfiguration" type="int">0</value>
+ <value key="ProjectExplorer.Target.ActiveRunConfiguration" type="int">0</value>
+ <valuemap key="ProjectExplorer.Target.BuildConfiguration.0" type="QVariantMap">
+ <valuemap key="ProjectExplorer.BuildConfiguration.BuildStep.0" type="QVariantMap">
+ <value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">qmake</value>
+ <value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">QtProjectManager.QMakeBuildStep</value>
+ <valuelist key="QtProjectManager.QMakeBuildStep.QMakeArguments" type="QVariantList">
+ <value type="QString">QMAKE_ABSOLUTE_SOURCE_PATH=/home/jlind/dev/qt/lighthouse-master/src/opengl</value>
+ </valuelist>
+ </valuemap>
+ <valuemap key="ProjectExplorer.BuildConfiguration.BuildStep.1" type="QVariantMap">
+ <value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Make</value>
+ <value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.MakeStep</value>
+ <value key="Qt4ProjectManager.MakeStep.Clean" type="bool">false</value>
+ <valuelist key="Qt4ProjectManager.MakeStep.MakeArguments" type="QVariantList">
+ <value type="QString">-j9</value>
+ </valuelist>
+ <value key="Qt4ProjectManager.MakeStep.MakeCommand" type="QString"></value>
+ </valuemap>
+ <value key="ProjectExplorer.BuildConfiguration.BuildStepsCount" type="int">2</value>
+ <valuemap key="ProjectExplorer.BuildConfiguration.CleanStep.0" type="QVariantMap">
+ <value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Make</value>
+ <value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.MakeStep</value>
+ <value key="Qt4ProjectManager.MakeStep.Clean" type="bool">true</value>
+ <valuelist key="Qt4ProjectManager.MakeStep.MakeArguments" type="QVariantList">
+ <value type="QString">clean</value>
+ </valuelist>
+ <value key="Qt4ProjectManager.MakeStep.MakeCommand" type="QString"></value>
+ </valuemap>
+ <value key="ProjectExplorer.BuildConfiguration.CleanStepsCount" type="int">1</value>
+ <value key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment" type="bool">false</value>
+ <valuelist key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges" type="QVariantList"/>
+ <value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Debug</value>
+ <value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.Qt4BuildConfiguration</value>
+ <value key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration" type="int">2</value>
+ <value key="Qt4ProjectManager.Qt4BuildConfiguration.BuildDirectory" type="QString">/home/jlind/builds/master-lighthouse/src/opengl</value>
+ <value key="Qt4ProjectManager.Qt4BuildConfiguration.QtVersionId" type="int">23</value>
+ <value key="Qt4ProjectManager.Qt4BuildConfiguration.ToolChain" type="int">0</value>
+ <value key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild" type="bool">true</value>
+ </valuemap>
+ <value key="ProjectExplorer.Target.BuildConfigurationCount" type="int">1</value>
+ <valuemap key="ProjectExplorer.Target.RunConfiguration.0" type="QVariantMap">
+ <value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">headers</value>
+ <value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.Qt4RunConfiguration</value>
+ <value key="Qt4ProjectManager.Qt4RunConfiguration.BaseEnvironmentBase" type="int">2</value>
+ <valuelist key="Qt4ProjectManager.Qt4RunConfiguration.CommandLineArguments" type="QVariantList"/>
+ <value key="Qt4ProjectManager.Qt4RunConfiguration.ProFile" type="QString">../../../../../builds/master-lighthouse/include/QtOpenGL/headers.pri</value>
+ <value key="Qt4ProjectManager.Qt4RunConfiguration.UseDyldImageSuffix" type="bool">false</value>
+ <value key="Qt4ProjectManager.Qt4RunConfiguration.UseTerminal" type="bool">false</value>
+ <valuelist key="Qt4ProjectManager.Qt4RunConfiguration.UserEnvironmentChanges" type="QVariantList"/>
+ <value key="Qt4ProjectManager.Qt4RunConfiguration.UserSetName" type="bool">false</value>
+ <value key="Qt4ProjectManager.Qt4RunConfiguration.UserSetWorkingDirectory" type="bool">false</value>
+ <value key="Qt4ProjectManager.Qt4RunConfiguration.UserWorkingDirectory" type="QString"></value>
+ </valuemap>
+ <value key="ProjectExplorer.Target.RunConfigurationCount" type="int">1</value>
+ </valuemap>
+ </data>
+ <data>
+ <variable>ProjectExplorer.Project.TargetCount</variable>
+ <value type="int">1</value>
+ </data>
+ <data>
+ <variable>ProjectExplorer.Project.Updater.FileVersion</variable>
+ <value type="int">4</value>
+ </data>
+</qtcreator>
diff --git a/src/opengl/qgl.cpp b/src/opengl/qgl.cpp
new file mode 100644
index 0000000000..057fb55e2c
--- /dev/null
+++ b/src/opengl/qgl.cpp
@@ -0,0 +1,6057 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qapplication.h"
+#include "qplatformdefs.h"
+#include "qgl.h"
+#include <qdebug.h>
+
+#if defined(Q_WS_X11)
+#include "private/qt_x11_p.h"
+#include "private/qpixmap_x11_p.h"
+#define INT32 dummy_INT32
+#define INT8 dummy_INT8
+#ifdef QT_NO_EGL
+# include <GL/glx.h>
+#endif
+#undef INT32
+#undef INT8
+#include "qx11info_x11.h"
+#elif defined(Q_WS_MAC)
+# include <private/qt_mac_p.h>
+#endif
+
+#include <qdatetime.h>
+
+#include <stdlib.h> // malloc
+
+#include "qpixmap.h"
+#include "qimage.h"
+#include "qgl_p.h"
+
+#if !defined(QT_OPENGL_ES_1)
+#include "gl2paintengineex/qpaintengineex_opengl2_p.h"
+#include <private/qwindowsurface_gl_p.h>
+#endif
+
+#ifndef QT_OPENGL_ES_2
+#include <private/qpaintengine_opengl_p.h>
+#endif
+
+#ifdef Q_WS_QWS
+#include <private/qglwindowsurface_qws_p.h>
+#endif
+
+#ifdef Q_WS_QPA
+#include <QtGui/QPlatformGLContext>
+#endif
+
+#include <qglpixelbuffer.h>
+#include <qglframebufferobject.h>
+
+#include <private/qimage_p.h>
+#include <private/qpixmapdata_p.h>
+#include <private/qpixmapdata_gl_p.h>
+#include <private/qglpixelbuffer_p.h>
+#include <private/qimagepixmapcleanuphooks_p.h>
+#include "qcolormap.h"
+#include "qfile.h"
+#include "qlibrary.h"
+#include <qmutex.h>
+
+#if defined(QT_OPENGL_ES) && !defined(QT_NO_EGL)
+#include <EGL/egl.h>
+#endif
+#ifdef QGL_USE_TEXTURE_POOL
+#include <private/qgltexturepool_p.h>
+#endif
+
+// #define QT_GL_CONTEXT_RESOURCE_DEBUG
+
+QT_BEGIN_NAMESPACE
+
+#if defined(Q_WS_X11) || defined(Q_WS_MAC) || defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN)
+QGLExtensionFuncs QGLContextPrivate::qt_extensionFuncs;
+#endif
+
+#ifdef Q_WS_X11
+extern const QX11Info *qt_x11Info(const QPaintDevice *pd);
+#endif
+
+struct QGLThreadContext {
+ ~QGLThreadContext() {
+ if (context)
+ context->doneCurrent();
+ }
+ QGLContext *context;
+};
+
+#ifndef Q_WS_QPA
+static QThreadStorage<QGLThreadContext *> qgl_context_storage;
+#endif
+
+Q_GLOBAL_STATIC(QGLFormat, qgl_default_format)
+
+class QGLDefaultOverlayFormat: public QGLFormat
+{
+public:
+ inline QGLDefaultOverlayFormat()
+ {
+ setOption(QGL::FormatOption(0xffff << 16)); // turn off all options
+ setOption(QGL::DirectRendering);
+ setPlane(1);
+ }
+};
+Q_GLOBAL_STATIC(QGLDefaultOverlayFormat, defaultOverlayFormatInstance)
+
+Q_GLOBAL_STATIC(QGLSignalProxy, theSignalProxy)
+QGLSignalProxy *QGLSignalProxy::instance()
+{
+ QGLSignalProxy *proxy = theSignalProxy();
+ if (proxy && proxy->thread() != qApp->thread()) {
+ if (proxy->thread() == QThread::currentThread())
+ proxy->moveToThread(qApp->thread());
+ }
+ return proxy;
+}
+
+
+class QGLEngineSelector
+{
+public:
+ QGLEngineSelector() : engineType(QPaintEngine::MaxUser)
+ {
+ }
+
+ void setPreferredPaintEngine(QPaintEngine::Type type) {
+ if (type == QPaintEngine::OpenGL || type == QPaintEngine::OpenGL2)
+ engineType = type;
+ }
+
+ QPaintEngine::Type preferredPaintEngine() {
+#ifdef Q_WS_MAC
+ // The ATI X1600 driver for Mac OS X does not support return
+ // values from functions in GLSL. Since working around this in
+ // the GL2 engine would require a big, ugly rewrite, we're
+ // falling back to the GL 1 engine..
+ static bool mac_x1600_check_done = false;
+ if (!mac_x1600_check_done) {
+ QGLTemporaryContext *tmp = 0;
+ if (!QGLContext::currentContext())
+ tmp = new QGLTemporaryContext();
+ if (strstr((char *) glGetString(GL_RENDERER), "X1600"))
+ engineType = QPaintEngine::OpenGL;
+ if (tmp)
+ delete tmp;
+ mac_x1600_check_done = true;
+ }
+#endif
+ if (engineType == QPaintEngine::MaxUser) {
+ // No user-set engine - use the defaults
+#if defined(QT_OPENGL_ES_2)
+ engineType = QPaintEngine::OpenGL2;
+#else
+ // We can't do this in the constructor for this object because it
+ // needs to be called *before* the QApplication constructor.
+ // Also check for the FragmentShader extension in conjunction with
+ // the 2.0 version flag, to cover the case where we export the display
+ // from an old GL 1.1 server to a GL 2.x client. In that case we can't
+ // use GL 2.0.
+ if ((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0)
+ && (QGLExtensions::glExtensions() & QGLExtensions::FragmentShader)
+ && qgetenv("QT_GL_USE_OPENGL1ENGINE").isEmpty())
+ engineType = QPaintEngine::OpenGL2;
+ else
+ engineType = QPaintEngine::OpenGL;
+#endif
+ }
+ return engineType;
+ }
+
+private:
+ QPaintEngine::Type engineType;
+};
+
+Q_GLOBAL_STATIC(QGLEngineSelector, qgl_engine_selector)
+
+
+bool qt_gl_preferGL2Engine()
+{
+ return qgl_engine_selector()->preferredPaintEngine() == QPaintEngine::OpenGL2;
+}
+
+
+/*!
+ \namespace QGL
+ \inmodule QtOpenGL
+
+ \brief The QGL namespace specifies miscellaneous identifiers used
+ in the Qt OpenGL module.
+
+ \ingroup painting-3D
+*/
+
+/*!
+ \enum QGL::FormatOption
+
+ This enum specifies the format options that can be used to configure an OpenGL
+ context. These are set using QGLFormat::setOption().
+
+ \value DoubleBuffer Specifies the use of double buffering.
+ \value DepthBuffer Enables the use of a depth buffer.
+ \value Rgba Specifies that the context should use RGBA as its pixel format.
+ \value AlphaChannel Enables the use of an alpha channel.
+ \value AccumBuffer Enables the use of an accumulation buffer.
+ \value StencilBuffer Enables the use of a stencil buffer.
+ \value StereoBuffers Enables the use of a stereo buffers for use with visualization hardware.
+ \value DirectRendering Specifies that the context is used for direct rendering to a display.
+ \value HasOverlay Enables the use of an overlay.
+ \value SampleBuffers Enables the use of sample buffers.
+ \value DeprecatedFunctions Enables the use of deprecated functionality for OpenGL 3.x
+ contexts. A context with deprecated functionality enabled is
+ called a full context in the OpenGL specification.
+ \value SingleBuffer Specifies the use of a single buffer, as opposed to double buffers.
+ \value NoDepthBuffer Disables the use of a depth buffer.
+ \value ColorIndex Specifies that the context should use a color index as its pixel format.
+ \value NoAlphaChannel Disables the use of an alpha channel.
+ \value NoAccumBuffer Disables the use of an accumulation buffer.
+ \value NoStencilBuffer Disables the use of a stencil buffer.
+ \value NoStereoBuffers Disables the use of stereo buffers.
+ \value IndirectRendering Specifies that the context is used for indirect rendering to a buffer.
+ \value NoOverlay Disables the use of an overlay.
+ \value NoSampleBuffers Disables the use of sample buffers.
+ \value NoDeprecatedFunctions Disables the use of deprecated functionality for OpenGL 3.x
+ contexts. A context with deprecated functionality disabled is
+ called a forward compatible context in the OpenGL specification.
+
+ \sa {Sample Buffers Example}
+*/
+
+/*!
+ \fn void QGL::setPreferredPaintEngine(QPaintEngine::Type engineType)
+
+ \since 4.6
+
+ Sets the preferred OpenGL paint engine that is used to draw onto
+ QGLWidget, QGLPixelBuffer and QGLFramebufferObject targets with QPainter
+ in Qt.
+
+ The \a engineType parameter specifies which of the GL engines to
+ use. Only \c QPaintEngine::OpenGL and \c QPaintEngine::OpenGL2 are
+ valid parameters to this function. All other values are ignored.
+
+ By default, the \c QPaintEngine::OpenGL2 engine is used if GL/GLES
+ version 2.0 is available, otherwise \c QPaintEngine::OpenGL is
+ used.
+
+ \warning This function must be called before the QApplication
+ constructor is called.
+*/
+void QGL::setPreferredPaintEngine(QPaintEngine::Type engineType)
+{
+ qgl_engine_selector()->setPreferredPaintEngine(engineType);
+}
+
+
+/*****************************************************************************
+ QGLFormat implementation
+ *****************************************************************************/
+
+
+/*!
+ \class QGLFormat
+ \brief The QGLFormat class specifies the display format of an OpenGL
+ rendering context.
+
+ \ingroup painting-3D
+
+ A display format has several characteristics:
+ \list
+ \i \link setDoubleBuffer() Double or single buffering.\endlink
+ \i \link setDepth() Depth buffer.\endlink
+ \i \link setRgba() RGBA or color index mode.\endlink
+ \i \link setAlpha() Alpha channel.\endlink
+ \i \link setAccum() Accumulation buffer.\endlink
+ \i \link setStencil() Stencil buffer.\endlink
+ \i \link setStereo() Stereo buffers.\endlink
+ \i \link setDirectRendering() Direct rendering.\endlink
+ \i \link setOverlay() Presence of an overlay.\endlink
+ \i \link setPlane() Plane of an overlay.\endlink
+ \i \link setSampleBuffers() Multisample buffers.\endlink
+ \endlist
+
+ You can also specify preferred bit depths for the color buffer,
+ depth buffer, alpha buffer, accumulation buffer and the stencil
+ buffer with the functions: setRedBufferSize(), setGreenBufferSize(),
+ setBlueBufferSize(), setDepthBufferSize(), setAlphaBufferSize(),
+ setAccumBufferSize() and setStencilBufferSize().
+
+ Note that even if you specify that you prefer a 32 bit depth
+ buffer (e.g. with setDepthBufferSize(32)), the format that is
+ chosen may not have a 32 bit depth buffer, even if there is a
+ format available with a 32 bit depth buffer. The main reason for
+ this is how the system dependant picking algorithms work on the
+ different platforms, and some format options may have higher
+ precedence than others.
+
+ You create and tell a QGLFormat object what rendering options you
+ want from an OpenGL rendering context.
+
+ OpenGL drivers or accelerated hardware may or may not support
+ advanced features such as alpha channel or stereographic viewing.
+ If you request some features that the driver/hardware does not
+ provide when you create a QGLWidget, you will get a rendering
+ context with the nearest subset of features.
+
+ There are different ways to define the display characteristics of
+ a rendering context. One is to create a QGLFormat and make it the
+ default for the entire application:
+ \snippet doc/src/snippets/code/src_opengl_qgl.cpp 0
+
+ Or you can specify the desired format when creating an object of
+ your QGLWidget subclass:
+ \snippet doc/src/snippets/code/src_opengl_qgl.cpp 1
+
+ After the widget has been created, you can find out which of the
+ requested features the system was able to provide:
+ \snippet doc/src/snippets/code/src_opengl_qgl.cpp 2
+
+ \legalese
+ OpenGL is a trademark of Silicon Graphics, Inc. in the
+ United States and other countries.
+ \endlegalese
+
+ \sa QGLContext, QGLWidget
+*/
+
+#ifndef QT_OPENGL_ES
+
+static inline void transform_point(GLdouble out[4], const GLdouble m[16], const GLdouble in[4])
+{
+#define M(row,col) m[col*4+row]
+ out[0] =
+ M(0, 0) * in[0] + M(0, 1) * in[1] + M(0, 2) * in[2] + M(0, 3) * in[3];
+ out[1] =
+ M(1, 0) * in[0] + M(1, 1) * in[1] + M(1, 2) * in[2] + M(1, 3) * in[3];
+ out[2] =
+ M(2, 0) * in[0] + M(2, 1) * in[1] + M(2, 2) * in[2] + M(2, 3) * in[3];
+ out[3] =
+ M(3, 0) * in[0] + M(3, 1) * in[1] + M(3, 2) * in[2] + M(3, 3) * in[3];
+#undef M
+}
+
+static inline GLint qgluProject(GLdouble objx, GLdouble objy, GLdouble objz,
+ const GLdouble model[16], const GLdouble proj[16],
+ const GLint viewport[4],
+ GLdouble * winx, GLdouble * winy, GLdouble * winz)
+{
+ GLdouble in[4], out[4];
+
+ in[0] = objx;
+ in[1] = objy;
+ in[2] = objz;
+ in[3] = 1.0;
+ transform_point(out, model, in);
+ transform_point(in, proj, out);
+
+ if (in[3] == 0.0)
+ return GL_FALSE;
+
+ in[0] /= in[3];
+ in[1] /= in[3];
+ in[2] /= in[3];
+
+ *winx = viewport[0] + (1 + in[0]) * viewport[2] / 2;
+ *winy = viewport[1] + (1 + in[1]) * viewport[3] / 2;
+
+ *winz = (1 + in[2]) / 2;
+ return GL_TRUE;
+}
+
+#endif // !QT_OPENGL_ES
+
+/*!
+ Constructs a QGLFormat object with the following default settings:
+ \list
+ \i \link setDoubleBuffer() Double buffer:\endlink Enabled.
+ \i \link setDepth() Depth buffer:\endlink Enabled.
+ \i \link setRgba() RGBA:\endlink Enabled (i.e., color index disabled).
+ \i \link setAlpha() Alpha channel:\endlink Disabled.
+ \i \link setAccum() Accumulator buffer:\endlink Disabled.
+ \i \link setStencil() Stencil buffer:\endlink Enabled.
+ \i \link setStereo() Stereo:\endlink Disabled.
+ \i \link setDirectRendering() Direct rendering:\endlink Enabled.
+ \i \link setOverlay() Overlay:\endlink Disabled.
+ \i \link setPlane() Plane:\endlink 0 (i.e., normal plane).
+ \i \link setSampleBuffers() Multisample buffers:\endlink Disabled.
+ \endlist
+*/
+
+QGLFormat::QGLFormat()
+{
+ d = new QGLFormatPrivate;
+}
+
+
+/*!
+ Creates a QGLFormat object that is a copy of the current
+ defaultFormat().
+
+ If \a options is not 0, the default format is modified by the
+ specified format options. The \a options parameter should be
+ QGL::FormatOption values OR'ed together.
+
+ This constructor makes it easy to specify a certain desired format
+ in classes derived from QGLWidget, for example:
+ \snippet doc/src/snippets/code/src_opengl_qgl.cpp 3
+
+ Note that there are QGL::FormatOption values to turn format settings
+ both on and off, e.g. QGL::DepthBuffer and QGL::NoDepthBuffer,
+ QGL::DirectRendering and QGL::IndirectRendering, etc.
+
+ The \a plane parameter defaults to 0 and is the plane which this
+ format should be associated with. Not all OpenGL implementations
+ supports overlay/underlay rendering planes.
+
+ \sa defaultFormat(), setOption(), setPlane()
+*/
+
+QGLFormat::QGLFormat(QGL::FormatOptions options, int plane)
+{
+ d = new QGLFormatPrivate;
+ QGL::FormatOptions newOpts = options;
+ d->opts = defaultFormat().d->opts;
+ d->opts |= (newOpts & 0xffff);
+ d->opts &= ~(newOpts >> 16);
+ d->pln = plane;
+}
+
+/*!
+ \internal
+*/
+void QGLFormat::detach()
+{
+ if (d->ref != 1) {
+ QGLFormatPrivate *newd = new QGLFormatPrivate(d);
+ if (!d->ref.deref())
+ delete d;
+ d = newd;
+ }
+}
+
+/*!
+ Constructs a copy of \a other.
+*/
+
+QGLFormat::QGLFormat(const QGLFormat &other)
+{
+ d = other.d;
+ d->ref.ref();
+}
+
+/*!
+ Assigns \a other to this object.
+*/
+
+QGLFormat &QGLFormat::operator=(const QGLFormat &other)
+{
+ if (d != other.d) {
+ other.d->ref.ref();
+ if (!d->ref.deref())
+ delete d;
+ d = other.d;
+ }
+ return *this;
+}
+
+/*!
+ Destroys the QGLFormat.
+*/
+QGLFormat::~QGLFormat()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ \fn bool QGLFormat::doubleBuffer() const
+
+ Returns true if double buffering is enabled; otherwise returns
+ false. Double buffering is enabled by default.
+
+ \sa setDoubleBuffer()
+*/
+
+/*!
+ If \a enable is true sets double buffering; otherwise sets single
+ buffering.
+
+ Double buffering is enabled by default.
+
+ Double buffering is a technique where graphics are rendered on an
+ off-screen buffer and not directly to the screen. When the drawing
+ has been completed, the program calls a swapBuffers() function to
+ exchange the screen contents with the buffer. The result is
+ flicker-free drawing and often better performance.
+
+ Note that single buffered contexts are currently not supported
+ with EGL.
+
+ \sa doubleBuffer(), QGLContext::swapBuffers(),
+ QGLWidget::swapBuffers()
+*/
+
+void QGLFormat::setDoubleBuffer(bool enable)
+{
+ setOption(enable ? QGL::DoubleBuffer : QGL::SingleBuffer);
+}
+
+
+/*!
+ \fn bool QGLFormat::depth() const
+
+ Returns true if the depth buffer is enabled; otherwise returns
+ false. The depth buffer is enabled by default.
+
+ \sa setDepth(), setDepthBufferSize()
+*/
+
+/*!
+ If \a enable is true enables the depth buffer; otherwise disables
+ the depth buffer.
+
+ The depth buffer is enabled by default.
+
+ The purpose of a depth buffer (or Z-buffering) is to remove hidden
+ surfaces. Pixels are assigned Z values based on the distance to
+ the viewer. A pixel with a high Z value is closer to the viewer
+ than a pixel with a low Z value. This information is used to
+ decide whether to draw a pixel or not.
+
+ \sa depth(), setDepthBufferSize()
+*/
+
+void QGLFormat::setDepth(bool enable)
+{
+ setOption(enable ? QGL::DepthBuffer : QGL::NoDepthBuffer);
+}
+
+
+/*!
+ \fn bool QGLFormat::rgba() const
+
+ Returns true if RGBA color mode is set. Returns false if color
+ index mode is set. The default color mode is RGBA.
+
+ \sa setRgba()
+*/
+
+/*!
+ If \a enable is true sets RGBA mode. If \a enable is false sets
+ color index mode.
+
+ The default color mode is RGBA.
+
+ RGBA is the preferred mode for most OpenGL applications. In RGBA
+ color mode you specify colors as red + green + blue + alpha
+ quadruplets.
+
+ In color index mode you specify an index into a color lookup
+ table.
+
+ \sa rgba()
+*/
+
+void QGLFormat::setRgba(bool enable)
+{
+ setOption(enable ? QGL::Rgba : QGL::ColorIndex);
+}
+
+
+/*!
+ \fn bool QGLFormat::alpha() const
+
+ Returns true if the alpha buffer in the framebuffer is enabled;
+ otherwise returns false. The alpha buffer is disabled by default.
+
+ \sa setAlpha(), setAlphaBufferSize()
+*/
+
+/*!
+ If \a enable is true enables the alpha buffer; otherwise disables
+ the alpha buffer.
+
+ The alpha buffer is disabled by default.
+
+ The alpha buffer is typically used for implementing transparency
+ or translucency. The A in RGBA specifies the transparency of a
+ pixel.
+
+ \sa alpha(), setAlphaBufferSize()
+*/
+
+void QGLFormat::setAlpha(bool enable)
+{
+ setOption(enable ? QGL::AlphaChannel : QGL::NoAlphaChannel);
+}
+
+
+/*!
+ \fn bool QGLFormat::accum() const
+
+ Returns true if the accumulation buffer is enabled; otherwise
+ returns false. The accumulation buffer is disabled by default.
+
+ \sa setAccum(), setAccumBufferSize()
+*/
+
+/*!
+ If \a enable is true enables the accumulation buffer; otherwise
+ disables the accumulation buffer.
+
+ The accumulation buffer is disabled by default.
+
+ The accumulation buffer is used to create blur effects and
+ multiple exposures.
+
+ \sa accum(), setAccumBufferSize()
+*/
+
+void QGLFormat::setAccum(bool enable)
+{
+ setOption(enable ? QGL::AccumBuffer : QGL::NoAccumBuffer);
+}
+
+
+/*!
+ \fn bool QGLFormat::stencil() const
+
+ Returns true if the stencil buffer is enabled; otherwise returns
+ false. The stencil buffer is enabled by default.
+
+ \sa setStencil(), setStencilBufferSize()
+*/
+
+/*!
+ If \a enable is true enables the stencil buffer; otherwise
+ disables the stencil buffer.
+
+ The stencil buffer is enabled by default.
+
+ The stencil buffer masks certain parts of the drawing area so that
+ masked parts are not drawn on.
+
+ \sa stencil(), setStencilBufferSize()
+*/
+
+void QGLFormat::setStencil(bool enable)
+{
+ setOption(enable ? QGL::StencilBuffer: QGL::NoStencilBuffer);
+}
+
+
+/*!
+ \fn bool QGLFormat::stereo() const
+
+ Returns true if stereo buffering is enabled; otherwise returns
+ false. Stereo buffering is disabled by default.
+
+ \sa setStereo()
+*/
+
+/*!
+ If \a enable is true enables stereo buffering; otherwise disables
+ stereo buffering.
+
+ Stereo buffering is disabled by default.
+
+ Stereo buffering provides extra color buffers to generate left-eye
+ and right-eye images.
+
+ \sa stereo()
+*/
+
+void QGLFormat::setStereo(bool enable)
+{
+ setOption(enable ? QGL::StereoBuffers : QGL::NoStereoBuffers);
+}
+
+
+/*!
+ \fn bool QGLFormat::directRendering() const
+
+ Returns true if direct rendering is enabled; otherwise returns
+ false.
+
+ Direct rendering is enabled by default.
+
+ \sa setDirectRendering()
+*/
+
+/*!
+ If \a enable is true enables direct rendering; otherwise disables
+ direct rendering.
+
+ Direct rendering is enabled by default.
+
+ Enabling this option will make OpenGL bypass the underlying window
+ system and render directly from hardware to the screen, if this is
+ supported by the system.
+
+ \sa directRendering()
+*/
+
+void QGLFormat::setDirectRendering(bool enable)
+{
+ setOption(enable ? QGL::DirectRendering : QGL::IndirectRendering);
+}
+
+/*!
+ \fn bool QGLFormat::sampleBuffers() const
+
+ Returns true if multisample buffer support is enabled; otherwise
+ returns false.
+
+ The multisample buffer is disabled by default.
+
+ \sa setSampleBuffers()
+*/
+
+/*!
+ If \a enable is true, a GL context with multisample buffer support
+ is picked; otherwise ignored.
+
+ \sa sampleBuffers(), setSamples(), samples()
+*/
+void QGLFormat::setSampleBuffers(bool enable)
+{
+ setOption(enable ? QGL::SampleBuffers : QGL::NoSampleBuffers);
+}
+
+/*!
+ Returns the number of samples per pixel when multisampling is
+ enabled. By default, the highest number of samples that is
+ available is used.
+
+ \sa setSampleBuffers(), sampleBuffers(), setSamples()
+*/
+int QGLFormat::samples() const
+{
+ return d->numSamples;
+}
+
+/*!
+ Set the preferred number of samples per pixel when multisampling
+ is enabled to \a numSamples. By default, the highest number of
+ samples available is used.
+
+ \sa setSampleBuffers(), sampleBuffers(), samples()
+*/
+void QGLFormat::setSamples(int numSamples)
+{
+ detach();
+ if (numSamples < 0) {
+ qWarning("QGLFormat::setSamples: Cannot have negative number of samples per pixel %d", numSamples);
+ return;
+ }
+ d->numSamples = numSamples;
+ setSampleBuffers(numSamples > 0);
+}
+
+/*!
+ \since 4.2
+
+ Set the preferred swap interval. This can be used to sync the GL
+ drawing into a system window to the vertical refresh of the screen.
+ Setting an \a interval value of 0 will turn the vertical refresh syncing
+ off, any value higher than 0 will turn the vertical syncing on.
+
+ Under Windows and under X11, where the \c{WGL_EXT_swap_control}
+ and \c{GLX_SGI_video_sync} extensions are used, the \a interval
+ parameter can be used to set the minimum number of video frames
+ that are displayed before a buffer swap will occur. In effect,
+ setting the \a interval to 10, means there will be 10 vertical
+ retraces between every buffer swap.
+
+ Under Windows the \c{WGL_EXT_swap_control} extension has to be present,
+ and under X11 the \c{GLX_SGI_video_sync} extension has to be present.
+*/
+void QGLFormat::setSwapInterval(int interval)
+{
+ detach();
+ d->swapInterval = interval;
+}
+
+/*!
+ \since 4.2
+
+ Returns the currently set swap interval. -1 is returned if setting
+ the swap interval isn't supported in the system GL implementation.
+*/
+int QGLFormat::swapInterval() const
+{
+ return d->swapInterval;
+}
+
+/*!
+ \fn bool QGLFormat::hasOverlay() const
+
+ Returns true if overlay plane is enabled; otherwise returns false.
+
+ Overlay is disabled by default.
+
+ \sa setOverlay()
+*/
+
+/*!
+ If \a enable is true enables an overlay plane; otherwise disables
+ the overlay plane.
+
+ Enabling the overlay plane will cause QGLWidget to create an
+ additional context in an overlay plane. See the QGLWidget
+ documentation for further information.
+
+ \sa hasOverlay()
+*/
+
+void QGLFormat::setOverlay(bool enable)
+{
+ setOption(enable ? QGL::HasOverlay : QGL::NoOverlay);
+}
+
+/*!
+ Returns the plane of this format. The default for normal formats
+ is 0, which means the normal plane. The default for overlay
+ formats is 1, which is the first overlay plane.
+
+ \sa setPlane(), defaultOverlayFormat()
+*/
+int QGLFormat::plane() const
+{
+ return d->pln;
+}
+
+/*!
+ Sets the requested plane to \a plane. 0 is the normal plane, 1 is
+ the first overlay plane, 2 is the second overlay plane, etc.; -1,
+ -2, etc. are underlay planes.
+
+ Note that in contrast to other format specifications, the plane
+ specifications will be matched exactly. This means that if you
+ specify a plane that the underlying OpenGL system cannot provide,
+ an \link QGLWidget::isValid() invalid\endlink QGLWidget will be
+ created.
+
+ \sa plane()
+*/
+void QGLFormat::setPlane(int plane)
+{
+ detach();
+ d->pln = plane;
+}
+
+/*!
+ Sets the format option to \a opt.
+
+ \sa testOption()
+*/
+
+void QGLFormat::setOption(QGL::FormatOptions opt)
+{
+ detach();
+ if (opt & 0xffff)
+ d->opts |= opt;
+ else
+ d->opts &= ~(opt >> 16);
+}
+
+
+
+/*!
+ Returns true if format option \a opt is set; otherwise returns false.
+
+ \sa setOption()
+*/
+
+bool QGLFormat::testOption(QGL::FormatOptions opt) const
+{
+ if (opt & 0xffff)
+ return (d->opts & opt) != 0;
+ else
+ return (d->opts & (opt >> 16)) == 0;
+}
+
+/*!
+ Set the minimum depth buffer size to \a size.
+
+ \sa depthBufferSize(), setDepth(), depth()
+*/
+void QGLFormat::setDepthBufferSize(int size)
+{
+ detach();
+ if (size < 0) {
+ qWarning("QGLFormat::setDepthBufferSize: Cannot set negative depth buffer size %d", size);
+ return;
+ }
+ d->depthSize = size;
+ setDepth(size > 0);
+}
+
+/*!
+ Returns the depth buffer size.
+
+ \sa depth(), setDepth(), setDepthBufferSize()
+*/
+int QGLFormat::depthBufferSize() const
+{
+ return d->depthSize;
+}
+
+/*!
+ \since 4.2
+
+ Set the preferred red buffer size to \a size.
+
+ \sa setGreenBufferSize(), setBlueBufferSize(), setAlphaBufferSize()
+*/
+void QGLFormat::setRedBufferSize(int size)
+{
+ detach();
+ if (size < 0) {
+ qWarning("QGLFormat::setRedBufferSize: Cannot set negative red buffer size %d", size);
+ return;
+ }
+ d->redSize = size;
+}
+
+/*!
+ \since 4.2
+
+ Returns the red buffer size.
+
+ \sa setRedBufferSize()
+*/
+int QGLFormat::redBufferSize() const
+{
+ return d->redSize;
+}
+
+/*!
+ \since 4.2
+
+ Set the preferred green buffer size to \a size.
+
+ \sa setRedBufferSize(), setBlueBufferSize(), setAlphaBufferSize()
+*/
+void QGLFormat::setGreenBufferSize(int size)
+{
+ detach();
+ if (size < 0) {
+ qWarning("QGLFormat::setGreenBufferSize: Cannot set negative green buffer size %d", size);
+ return;
+ }
+ d->greenSize = size;
+}
+
+/*!
+ \since 4.2
+
+ Returns the green buffer size.
+
+ \sa setGreenBufferSize()
+*/
+int QGLFormat::greenBufferSize() const
+{
+ return d->greenSize;
+}
+
+/*!
+ \since 4.2
+
+ Set the preferred blue buffer size to \a size.
+
+ \sa setRedBufferSize(), setGreenBufferSize(), setAlphaBufferSize()
+*/
+void QGLFormat::setBlueBufferSize(int size)
+{
+ detach();
+ if (size < 0) {
+ qWarning("QGLFormat::setBlueBufferSize: Cannot set negative blue buffer size %d", size);
+ return;
+ }
+ d->blueSize = size;
+}
+
+/*!
+ \since 4.2
+
+ Returns the blue buffer size.
+
+ \sa setBlueBufferSize()
+*/
+int QGLFormat::blueBufferSize() const
+{
+ return d->blueSize;
+}
+
+/*!
+ Set the preferred alpha buffer size to \a size.
+ This function implicitly enables the alpha channel.
+
+ \sa setRedBufferSize(), setGreenBufferSize(), alphaBufferSize()
+*/
+void QGLFormat::setAlphaBufferSize(int size)
+{
+ detach();
+ if (size < 0) {
+ qWarning("QGLFormat::setAlphaBufferSize: Cannot set negative alpha buffer size %d", size);
+ return;
+ }
+ d->alphaSize = size;
+ setAlpha(size > 0);
+}
+
+/*!
+ Returns the alpha buffer size.
+
+ \sa alpha(), setAlpha(), setAlphaBufferSize()
+*/
+int QGLFormat::alphaBufferSize() const
+{
+ return d->alphaSize;
+}
+
+/*!
+ Set the preferred accumulation buffer size, where \a size is the
+ bit depth for each RGBA component.
+
+ \sa accum(), setAccum(), accumBufferSize()
+*/
+void QGLFormat::setAccumBufferSize(int size)
+{
+ detach();
+ if (size < 0) {
+ qWarning("QGLFormat::setAccumBufferSize: Cannot set negative accumulate buffer size %d", size);
+ return;
+ }
+ d->accumSize = size;
+ setAccum(size > 0);
+}
+
+/*!
+ Returns the accumulation buffer size.
+
+ \sa setAccumBufferSize(), accum(), setAccum()
+*/
+int QGLFormat::accumBufferSize() const
+{
+ return d->accumSize;
+}
+
+/*!
+ Set the preferred stencil buffer size to \a size.
+
+ \sa stencilBufferSize(), setStencil(), stencil()
+*/
+void QGLFormat::setStencilBufferSize(int size)
+{
+ detach();
+ if (size < 0) {
+ qWarning("QGLFormat::setStencilBufferSize: Cannot set negative stencil buffer size %d", size);
+ return;
+ }
+ d->stencilSize = size;
+ setStencil(size > 0);
+}
+
+/*!
+ Returns the stencil buffer size.
+
+ \sa stencil(), setStencil(), setStencilBufferSize()
+*/
+int QGLFormat::stencilBufferSize() const
+{
+ return d->stencilSize;
+}
+
+/*!
+ \since 4.7
+
+ Set the OpenGL version to the \a major and \a minor numbers. If a
+ context compatible with the requested OpenGL version cannot be
+ created, a context compatible with version 1.x is created instead.
+
+ \sa majorVersion(), minorVersion()
+*/
+void QGLFormat::setVersion(int major, int minor)
+{
+ if (major < 1 || minor < 0) {
+ qWarning("QGLFormat::setVersion: Cannot set zero or negative version number %d.%d", major, minor);
+ return;
+ }
+ detach();
+ d->majorVersion = major;
+ d->minorVersion = minor;
+}
+
+/*!
+ \since 4.7
+
+ Returns the OpenGL major version.
+
+ \sa setVersion(), minorVersion()
+*/
+int QGLFormat::majorVersion() const
+{
+ return d->majorVersion;
+}
+
+/*!
+ \since 4.7
+
+ Returns the OpenGL minor version.
+
+ \sa setVersion(), majorVersion()
+*/
+int QGLFormat::minorVersion() const
+{
+ return d->minorVersion;
+}
+
+/*!
+ \enum QGLFormat::OpenGLContextProfile
+ \since 4.7
+
+ This enum describes the OpenGL context profiles that can be
+ specified for contexts implementing OpenGL version 3.2 or
+ higher. These profiles are different from OpenGL ES profiles.
+
+ \value NoProfile OpenGL version is lower than 3.2.
+ \value CoreProfile Functionality deprecated in OpenGL version 3.0 is not available.
+ \value CompatibilityProfile Functionality from earlier OpenGL versions is available.
+*/
+
+/*!
+ \since 4.7
+
+ Set the OpenGL context profile to \a profile. The \a profile is
+ ignored if the requested OpenGL version is less than 3.2.
+
+ \sa profile()
+*/
+void QGLFormat::setProfile(OpenGLContextProfile profile)
+{
+ detach();
+ d->profile = profile;
+}
+
+/*!
+ \since 4.7
+
+ Returns the OpenGL context profile.
+
+ \sa setProfile()
+*/
+QGLFormat::OpenGLContextProfile QGLFormat::profile() const
+{
+ return d->profile;
+}
+
+
+/*!
+ \fn bool QGLFormat::hasOpenGL()
+
+ Returns true if the window system has any OpenGL support;
+ otherwise returns false.
+
+ \warning This function must not be called until the QApplication
+ object has been created.
+*/
+
+
+
+/*!
+ \fn bool QGLFormat::hasOpenGLOverlays()
+
+ Returns true if the window system supports OpenGL overlays;
+ otherwise returns false.
+
+ \warning This function must not be called until the QApplication
+ object has been created.
+*/
+
+QGLFormat::OpenGLVersionFlags Q_AUTOTEST_EXPORT qOpenGLVersionFlagsFromString(const QString &versionString)
+{
+ QGLFormat::OpenGLVersionFlags versionFlags = QGLFormat::OpenGL_Version_None;
+
+ if (versionString.startsWith(QLatin1String("OpenGL ES"))) {
+ QStringList parts = versionString.split(QLatin1Char(' '));
+ if (parts.size() >= 3) {
+ if (parts[2].startsWith(QLatin1String("1."))) {
+ if (parts[1].endsWith(QLatin1String("-CM"))) {
+ versionFlags |= QGLFormat::OpenGL_ES_Common_Version_1_0 |
+ QGLFormat::OpenGL_ES_CommonLite_Version_1_0;
+ if (parts[2].startsWith(QLatin1String("1.1")))
+ versionFlags |= QGLFormat::OpenGL_ES_Common_Version_1_1 |
+ QGLFormat::OpenGL_ES_CommonLite_Version_1_1;
+ } else {
+ // Not -CM, must be CL, CommonLite
+ versionFlags |= QGLFormat::OpenGL_ES_CommonLite_Version_1_0;
+ if (parts[2].startsWith(QLatin1String("1.1")))
+ versionFlags |= QGLFormat::OpenGL_ES_CommonLite_Version_1_1;
+ }
+ } else {
+ // OpenGL ES version 2.0 or higher
+ versionFlags |= QGLFormat::OpenGL_ES_Version_2_0;
+ }
+ } else {
+ // if < 3 parts to the name, it is an unrecognised OpenGL ES
+ qWarning("Unrecognised OpenGL ES version");
+ }
+ } else {
+ // not ES, regular OpenGL, the version numbers are first in the string
+ if (versionString.startsWith(QLatin1String("1."))) {
+ switch (versionString[2].toAscii()) {
+ case '5':
+ versionFlags |= QGLFormat::OpenGL_Version_1_5;
+ case '4':
+ versionFlags |= QGLFormat::OpenGL_Version_1_4;
+ case '3':
+ versionFlags |= QGLFormat::OpenGL_Version_1_3;
+ case '2':
+ versionFlags |= QGLFormat::OpenGL_Version_1_2;
+ case '1':
+ versionFlags |= QGLFormat::OpenGL_Version_1_1;
+ default:
+ break;
+ }
+ } else if (versionString.startsWith(QLatin1String("2."))) {
+ versionFlags |= QGLFormat::OpenGL_Version_1_1 |
+ QGLFormat::OpenGL_Version_1_2 |
+ QGLFormat::OpenGL_Version_1_3 |
+ QGLFormat::OpenGL_Version_1_4 |
+ QGLFormat::OpenGL_Version_1_5 |
+ QGLFormat::OpenGL_Version_2_0;
+ if (versionString[2].toAscii() == '1')
+ versionFlags |= QGLFormat::OpenGL_Version_2_1;
+ } else if (versionString.startsWith(QLatin1String("3."))) {
+ versionFlags |= QGLFormat::OpenGL_Version_1_1 |
+ QGLFormat::OpenGL_Version_1_2 |
+ QGLFormat::OpenGL_Version_1_3 |
+ QGLFormat::OpenGL_Version_1_4 |
+ QGLFormat::OpenGL_Version_1_5 |
+ QGLFormat::OpenGL_Version_2_0 |
+ QGLFormat::OpenGL_Version_2_1 |
+ QGLFormat::OpenGL_Version_3_0;
+ switch (versionString[2].toAscii()) {
+ case '3':
+ versionFlags |= QGLFormat::OpenGL_Version_3_3;
+ case '2':
+ versionFlags |= QGLFormat::OpenGL_Version_3_2;
+ case '1':
+ versionFlags |= QGLFormat::OpenGL_Version_3_1;
+ case '0':
+ break;
+ default:
+ versionFlags |= QGLFormat::OpenGL_Version_3_1 |
+ QGLFormat::OpenGL_Version_3_2 |
+ QGLFormat::OpenGL_Version_3_3;
+ break;
+ }
+ } else if (versionString.startsWith(QLatin1String("4."))) {
+ versionFlags |= QGLFormat::OpenGL_Version_1_1 |
+ QGLFormat::OpenGL_Version_1_2 |
+ QGLFormat::OpenGL_Version_1_3 |
+ QGLFormat::OpenGL_Version_1_4 |
+ QGLFormat::OpenGL_Version_1_5 |
+ QGLFormat::OpenGL_Version_2_0 |
+ QGLFormat::OpenGL_Version_2_1 |
+ QGLFormat::OpenGL_Version_3_0 |
+ QGLFormat::OpenGL_Version_3_1 |
+ QGLFormat::OpenGL_Version_3_2 |
+ QGLFormat::OpenGL_Version_3_3 |
+ QGLFormat::OpenGL_Version_4_0;
+ } else {
+ versionFlags |= QGLFormat::OpenGL_Version_1_1 |
+ QGLFormat::OpenGL_Version_1_2 |
+ QGLFormat::OpenGL_Version_1_3 |
+ QGLFormat::OpenGL_Version_1_4 |
+ QGLFormat::OpenGL_Version_1_5 |
+ QGLFormat::OpenGL_Version_2_0 |
+ QGLFormat::OpenGL_Version_2_1 |
+ QGLFormat::OpenGL_Version_3_0 |
+ QGLFormat::OpenGL_Version_3_1 |
+ QGLFormat::OpenGL_Version_3_2 |
+ QGLFormat::OpenGL_Version_3_3 |
+ QGLFormat::OpenGL_Version_4_0;
+ }
+ }
+ return versionFlags;
+}
+
+/*!
+ \enum QGLFormat::OpenGLVersionFlag
+ \since 4.2
+
+ This enum describes the various OpenGL versions that are
+ recognized by Qt. Use the QGLFormat::openGLVersionFlags() function
+ to identify which versions that are supported at runtime.
+
+ \value OpenGL_Version_None If no OpenGL is present or if no OpenGL context is current.
+
+ \value OpenGL_Version_1_1 OpenGL version 1.1 or higher is present.
+
+ \value OpenGL_Version_1_2 OpenGL version 1.2 or higher is present.
+
+ \value OpenGL_Version_1_3 OpenGL version 1.3 or higher is present.
+
+ \value OpenGL_Version_1_4 OpenGL version 1.4 or higher is present.
+
+ \value OpenGL_Version_1_5 OpenGL version 1.5 or higher is present.
+
+ \value OpenGL_Version_2_0 OpenGL version 2.0 or higher is present.
+ Note that version 2.0 supports all the functionality of version 1.5.
+
+ \value OpenGL_Version_2_1 OpenGL version 2.1 or higher is present.
+
+ \value OpenGL_Version_3_0 OpenGL version 3.0 or higher is present.
+
+ \value OpenGL_Version_3_1 OpenGL version 3.1 or higher is present.
+ Note that OpenGL version 3.1 or higher does not necessarily support all the features of
+ version 3.0 and lower.
+
+ \value OpenGL_Version_3_2 OpenGL version 3.2 or higher is present.
+
+ \value OpenGL_Version_3_3 OpenGL version 3.3 or higher is present.
+
+ \value OpenGL_Version_4_0 OpenGL version 4.0 or higher is present.
+
+ \value OpenGL_ES_CommonLite_Version_1_0 OpenGL ES version 1.0 Common Lite or higher is present.
+
+ \value OpenGL_ES_Common_Version_1_0 OpenGL ES version 1.0 Common or higher is present.
+ The Common profile supports all the features of Common Lite.
+
+ \value OpenGL_ES_CommonLite_Version_1_1 OpenGL ES version 1.1 Common Lite or higher is present.
+
+ \value OpenGL_ES_Common_Version_1_1 OpenGL ES version 1.1 Common or higher is present.
+ The Common profile supports all the features of Common Lite.
+
+ \value OpenGL_ES_Version_2_0 OpenGL ES version 2.0 or higher is present.
+ Note that OpenGL ES version 2.0 does not support all the features of OpenGL ES 1.x.
+ So if OpenGL_ES_Version_2_0 is returned, none of the ES 1.x flags are returned.
+
+ See also \l{http://www.opengl.org} for more information about the different
+ revisions of OpenGL.
+
+ \sa openGLVersionFlags()
+*/
+
+/*!
+ \since 4.2
+
+ Identifies, at runtime, which OpenGL versions that are supported
+ by the current platform.
+
+ Note that if OpenGL version 1.5 is supported, its predecessors
+ (i.e., version 1.4 and lower) are also supported. To identify the
+ support of a particular feature, like multi texturing, test for
+ the version in which the feature was first introduced (i.e.,
+ version 1.3 in the case of multi texturing) to adapt to the largest
+ possible group of runtime platforms.
+
+ This function needs a valid current OpenGL context to work;
+ otherwise it will return OpenGL_Version_None.
+
+ \sa hasOpenGL(), hasOpenGLOverlays()
+*/
+QGLFormat::OpenGLVersionFlags QGLFormat::openGLVersionFlags()
+{
+ static bool cachedDefault = false;
+ static OpenGLVersionFlags defaultVersionFlags = OpenGL_Version_None;
+ QGLContext *currentCtx = const_cast<QGLContext *>(QGLContext::currentContext());
+ QGLTemporaryContext *tmpContext = 0;
+
+ if (currentCtx && currentCtx->d_func()->version_flags_cached)
+ return currentCtx->d_func()->version_flags;
+
+ if (!currentCtx) {
+ if (cachedDefault) {
+ return defaultVersionFlags;
+ } else {
+ if (!hasOpenGL())
+ return defaultVersionFlags;
+ tmpContext = new QGLTemporaryContext;
+ cachedDefault = true;
+ }
+ }
+
+ QString versionString(QLatin1String(reinterpret_cast<const char*>(glGetString(GL_VERSION))));
+ OpenGLVersionFlags versionFlags = qOpenGLVersionFlagsFromString(versionString);
+ if (currentCtx) {
+ currentCtx->d_func()->version_flags_cached = true;
+ currentCtx->d_func()->version_flags = versionFlags;
+ }
+ if (tmpContext) {
+ defaultVersionFlags = versionFlags;
+ delete tmpContext;
+ }
+
+ return versionFlags;
+}
+
+
+/*!
+ Returns the default QGLFormat for the application. All QGLWidget
+ objects that are created use this format unless another format is
+ specified, e.g. when they are constructed.
+
+ If no special default format has been set using
+ setDefaultFormat(), the default format is the same as that created
+ with QGLFormat().
+
+ \sa setDefaultFormat()
+*/
+
+QGLFormat QGLFormat::defaultFormat()
+{
+ return *qgl_default_format();
+}
+
+/*!
+ Sets a new default QGLFormat for the application to \a f. For
+ example, to set single buffering as the default instead of double
+ buffering, your main() might contain code like this:
+ \snippet doc/src/snippets/code/src_opengl_qgl.cpp 4
+
+ \sa defaultFormat()
+*/
+
+void QGLFormat::setDefaultFormat(const QGLFormat &f)
+{
+ *qgl_default_format() = f;
+}
+
+
+/*!
+ Returns the default QGLFormat for overlay contexts.
+
+ The default overlay format is:
+ \list
+ \i \link setDoubleBuffer() Double buffer:\endlink Disabled.
+ \i \link setDepth() Depth buffer:\endlink Disabled.
+ \i \link setRgba() RGBA:\endlink Disabled (i.e., color index enabled).
+ \i \link setAlpha() Alpha channel:\endlink Disabled.
+ \i \link setAccum() Accumulator buffer:\endlink Disabled.
+ \i \link setStencil() Stencil buffer:\endlink Disabled.
+ \i \link setStereo() Stereo:\endlink Disabled.
+ \i \link setDirectRendering() Direct rendering:\endlink Enabled.
+ \i \link setOverlay() Overlay:\endlink Disabled.
+ \i \link setSampleBuffers() Multisample buffers:\endlink Disabled.
+ \i \link setPlane() Plane:\endlink 1 (i.e., first overlay plane).
+ \endlist
+
+ \sa setDefaultFormat()
+*/
+
+QGLFormat QGLFormat::defaultOverlayFormat()
+{
+ return *defaultOverlayFormatInstance();
+}
+
+/*!
+ Sets a new default QGLFormat for overlay contexts to \a f. This
+ format is used whenever a QGLWidget is created with a format that
+ hasOverlay() enabled.
+
+ For example, to get a double buffered overlay context (if
+ available), use code like this:
+
+ \snippet doc/src/snippets/code/src_opengl_qgl.cpp 5
+
+ As usual, you can find out after widget creation whether the
+ underlying OpenGL system was able to provide the requested
+ specification:
+
+ \snippet doc/src/snippets/code/src_opengl_qgl.cpp 6
+
+ \sa defaultOverlayFormat()
+*/
+
+void QGLFormat::setDefaultOverlayFormat(const QGLFormat &f)
+{
+ QGLFormat *defaultFormat = defaultOverlayFormatInstance();
+ *defaultFormat = f;
+ // Make sure the user doesn't request that the overlays themselves
+ // have overlays, since it is unlikely that the system supports
+ // infinitely many planes...
+ defaultFormat->setOverlay(false);
+}
+
+
+/*!
+ Returns true if all the options of the two QGLFormat objects
+ \a a and \a b are equal; otherwise returns false.
+
+ \relates QGLFormat
+*/
+
+bool operator==(const QGLFormat& a, const QGLFormat& b)
+{
+ return (a.d == b.d) || ((int) a.d->opts == (int) b.d->opts
+ && a.d->pln == b.d->pln
+ && a.d->alphaSize == b.d->alphaSize
+ && a.d->accumSize == b.d->accumSize
+ && a.d->stencilSize == b.d->stencilSize
+ && a.d->depthSize == b.d->depthSize
+ && a.d->redSize == b.d->redSize
+ && a.d->greenSize == b.d->greenSize
+ && a.d->blueSize == b.d->blueSize
+ && a.d->numSamples == b.d->numSamples
+ && a.d->swapInterval == b.d->swapInterval
+ && a.d->majorVersion == b.d->majorVersion
+ && a.d->minorVersion == b.d->minorVersion
+ && a.d->profile == b.d->profile);
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QGLFormat &f)
+{
+ const QGLFormatPrivate * const d = f.d;
+
+ dbg.nospace() << "QGLFormat("
+ << "options " << d->opts
+ << ", plane " << d->pln
+ << ", depthBufferSize " << d->depthSize
+ << ", accumBufferSize " << d->accumSize
+ << ", stencilBufferSize " << d->stencilSize
+ << ", redBufferSize " << d->redSize
+ << ", greenBufferSize " << d->greenSize
+ << ", blueBufferSize " << d->blueSize
+ << ", alphaBufferSize " << d->alphaSize
+ << ", samples " << d->numSamples
+ << ", swapInterval " << d->swapInterval
+ << ", majorVersion " << d->majorVersion
+ << ", minorVersion " << d->minorVersion
+ << ", profile " << d->profile
+ << ')';
+
+ return dbg.space();
+}
+#endif
+
+
+/*!
+ Returns false if all the options of the two QGLFormat objects
+ \a a and \a b are equal; otherwise returns true.
+
+ \relates QGLFormat
+*/
+
+bool operator!=(const QGLFormat& a, const QGLFormat& b)
+{
+ return !(a == b);
+}
+
+struct QGLContextGroupList {
+ void append(QGLContextGroup *group) {
+ QMutexLocker locker(&m_mutex);
+ m_list.append(group);
+ }
+
+ void remove(QGLContextGroup *group) {
+ QMutexLocker locker(&m_mutex);
+ m_list.removeOne(group);
+ }
+
+ QList<QGLContextGroup *> m_list;
+ QMutex m_mutex;
+};
+
+Q_GLOBAL_STATIC(QGLContextGroupList, qt_context_groups)
+
+/*****************************************************************************
+ QGLContext implementation
+ *****************************************************************************/
+
+QGLContextGroup::QGLContextGroup(const QGLContext *context)
+ : m_context(context), m_guards(0), m_refs(1)
+{
+ qt_context_groups()->append(this);
+}
+
+QGLContextGroup::~QGLContextGroup()
+{
+ // Clear any remaining QGLSharedResourceGuard objects on the group.
+ QGLSharedResourceGuard *guard = m_guards;
+ while (guard != 0) {
+ guard->m_group = 0;
+ guard->m_id = 0;
+ guard = guard->m_next;
+ }
+ qt_context_groups()->remove(this);
+}
+
+void QGLContextGroup::addGuard(QGLSharedResourceGuard *guard)
+{
+ if (m_guards)
+ m_guards->m_prev = guard;
+ guard->m_next = m_guards;
+ guard->m_prev = 0;
+ m_guards = guard;
+}
+
+void QGLContextGroup::removeGuard(QGLSharedResourceGuard *guard)
+{
+ if (guard->m_next)
+ guard->m_next->m_prev = guard->m_prev;
+ if (guard->m_prev)
+ guard->m_prev->m_next = guard->m_next;
+ else
+ m_guards = guard->m_next;
+}
+
+const QGLContext *qt_gl_transfer_context(const QGLContext *ctx)
+{
+ if (!ctx)
+ return 0;
+ QList<const QGLContext *> shares
+ (QGLContextPrivate::contextGroup(ctx)->shares());
+ if (shares.size() >= 2)
+ return (ctx == shares.at(0)) ? shares.at(1) : shares.at(0);
+ else
+ return 0;
+}
+
+QGLContextPrivate::QGLContextPrivate(QGLContext *context)
+ : internal_context(false)
+ , q_ptr(context)
+{
+ group = new QGLContextGroup(context);
+ texture_destroyer = new QGLTextureDestroyer;
+ texture_destroyer->moveToThread(qApp->thread());
+}
+
+QGLContextPrivate::~QGLContextPrivate()
+{
+ if (!group->m_refs.deref()) {
+ Q_ASSERT(group->context() == q_ptr);
+ delete group;
+ }
+
+ delete texture_destroyer;
+}
+
+void QGLContextPrivate::init(QPaintDevice *dev, const QGLFormat &format)
+{
+ Q_Q(QGLContext);
+ glFormat = reqFormat = format;
+ valid = false;
+ q->setDevice(dev);
+#if defined(Q_WS_X11)
+ pbuf = 0;
+ gpm = 0;
+ vi = 0;
+ screen = QX11Info::appScreen();
+#endif
+#if defined(Q_WS_WIN)
+ dc = 0;
+ win = 0;
+ threadId = 0;
+ pixelFormatId = 0;
+ cmap = 0;
+ hbitmap = 0;
+ hbitmap_hdc = 0;
+#endif
+#if defined(Q_WS_MAC)
+# ifndef QT_MAC_USE_COCOA
+ update = false;
+# endif
+ vi = 0;
+#endif
+#if defined(Q_WS_QPA)
+ platformContext = 0;
+#endif
+#if !defined(QT_NO_EGL)
+ ownsEglContext = false;
+ eglContext = 0;
+ eglSurface = EGL_NO_SURFACE;
+#endif
+ fbo = 0;
+ crWin = false;
+ initDone = false;
+ sharing = false;
+ max_texture_size = -1;
+ version_flags_cached = false;
+ version_flags = QGLFormat::OpenGL_Version_None;
+ extension_flags_cached = false;
+ extension_flags = 0;
+ current_fbo = 0;
+ default_fbo = 0;
+ active_engine = 0;
+ workaround_needsFullClearOnEveryFrame = false;
+ workaround_brokenFBOReadBack = false;
+ workaround_brokenTexSubImage = false;
+ workaroundsCached = false;
+
+ workaround_brokenTextureFromPixmap = false;
+ workaround_brokenTextureFromPixmap_init = false;
+
+ workaround_brokenAlphaTexSubImage = false;
+ workaround_brokenAlphaTexSubImage_init = false;
+
+ for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i)
+ vertexAttributeArraysEnabledState[i] = false;
+}
+
+QGLContext* QGLContext::currentCtx = 0;
+
+/*
+ Read back the contents of the currently bound framebuffer, used in
+ QGLWidget::grabFrameBuffer(), QGLPixelbuffer::toImage() and
+ QGLFramebufferObject::toImage()
+*/
+
+static void convertFromGLImage(QImage &img, int w, int h, bool alpha_format, bool include_alpha)
+{
+ if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
+ // OpenGL gives RGBA; Qt wants ARGB
+ uint *p = (uint*)img.bits();
+ uint *end = p + w*h;
+ if (alpha_format && include_alpha) {
+ while (p < end) {
+ uint a = *p << 24;
+ *p = (*p >> 8) | a;
+ p++;
+ }
+ } else {
+ // This is an old legacy fix for PowerPC based Macs, which
+ // we shouldn't remove
+ while (p < end) {
+ *p = 0xff000000 | (*p>>8);
+ ++p;
+ }
+ }
+ } else {
+ // OpenGL gives ABGR (i.e. RGBA backwards); Qt wants ARGB
+ for (int y = 0; y < h; y++) {
+ uint *q = (uint*)img.scanLine(y);
+ for (int x=0; x < w; ++x) {
+ const uint pixel = *q;
+ if (alpha_format && include_alpha) {
+ *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff)
+ | (pixel & 0xff00ff00);
+ } else {
+ *q = 0xff000000 | ((pixel << 16) & 0xff0000)
+ | ((pixel >> 16) & 0xff) | (pixel & 0x00ff00);
+ }
+
+ q++;
+ }
+ }
+
+ }
+ img = img.mirrored();
+}
+
+QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha)
+{
+ QImage img(size, (alpha_format && include_alpha) ? QImage::Format_ARGB32_Premultiplied
+ : QImage::Format_RGB32);
+ int w = size.width();
+ int h = size.height();
+ glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, img.bits());
+ convertFromGLImage(img, w, h, alpha_format, include_alpha);
+ return img;
+}
+
+QImage qt_gl_read_texture(const QSize &size, bool alpha_format, bool include_alpha)
+{
+ QImage img(size, alpha_format ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32);
+ int w = size.width();
+ int h = size.height();
+#if !defined(QT_OPENGL_ES_2) && !defined(QT_OPENGL_ES_1)
+ //### glGetTexImage not in GL ES 2.0, need to do something else here!
+ glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.bits());
+#endif
+ convertFromGLImage(img, w, h, alpha_format, include_alpha);
+ return img;
+}
+
+// returns the highest number closest to v, which is a power of 2
+// NB! assumes 32 bit ints
+int qt_next_power_of_two(int v)
+{
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+ ++v;
+ return v;
+}
+
+typedef void (*_qt_pixmap_cleanup_hook_64)(qint64);
+typedef void (*_qt_image_cleanup_hook_64)(qint64);
+
+extern Q_GUI_EXPORT _qt_pixmap_cleanup_hook_64 qt_pixmap_cleanup_hook_64;
+extern Q_GUI_EXPORT _qt_image_cleanup_hook_64 qt_image_cleanup_hook_64;
+
+
+Q_GLOBAL_STATIC(QGLTextureCache, qt_gl_texture_cache)
+
+QGLTextureCache::QGLTextureCache()
+ : m_cache(64*1024) // cache ~64 MB worth of textures - this is not accurate though
+{
+ QImagePixmapCleanupHooks::instance()->addPixmapDataModificationHook(cleanupTexturesForPixampData);
+ QImagePixmapCleanupHooks::instance()->addPixmapDataDestructionHook(cleanupBeforePixmapDestruction);
+ QImagePixmapCleanupHooks::instance()->addImageHook(cleanupTexturesForCacheKey);
+}
+
+QGLTextureCache::~QGLTextureCache()
+{
+ QImagePixmapCleanupHooks::instance()->removePixmapDataModificationHook(cleanupTexturesForPixampData);
+ QImagePixmapCleanupHooks::instance()->removePixmapDataDestructionHook(cleanupBeforePixmapDestruction);
+ QImagePixmapCleanupHooks::instance()->removeImageHook(cleanupTexturesForCacheKey);
+}
+
+void QGLTextureCache::insert(QGLContext* ctx, qint64 key, QGLTexture* texture, int cost)
+{
+ QWriteLocker locker(&m_lock);
+ const QGLTextureCacheKey cacheKey = {key, QGLContextPrivate::contextGroup(ctx)};
+ m_cache.insert(cacheKey, texture, cost);
+}
+
+void QGLTextureCache::remove(qint64 key)
+{
+ QWriteLocker locker(&m_lock);
+ QMutexLocker groupLocker(&qt_context_groups()->m_mutex);
+ QList<QGLContextGroup *>::const_iterator it = qt_context_groups()->m_list.constBegin();
+ while (it != qt_context_groups()->m_list.constEnd()) {
+ const QGLTextureCacheKey cacheKey = {key, *it};
+ m_cache.remove(cacheKey);
+ ++it;
+ }
+}
+
+bool QGLTextureCache::remove(QGLContext* ctx, GLuint textureId)
+{
+ QWriteLocker locker(&m_lock);
+ QList<QGLTextureCacheKey> keys = m_cache.keys();
+ for (int i = 0; i < keys.size(); ++i) {
+ QGLTexture *tex = m_cache.object(keys.at(i));
+ if (tex->id == textureId && tex->context == ctx) {
+ tex->options |= QGLContext::MemoryManagedBindOption; // forces a glDeleteTextures() call
+ m_cache.remove(keys.at(i));
+ return true;
+ }
+ }
+ return false;
+}
+
+void QGLTextureCache::removeContextTextures(QGLContext* ctx)
+{
+ QWriteLocker locker(&m_lock);
+ QList<QGLTextureCacheKey> keys = m_cache.keys();
+ for (int i = 0; i < keys.size(); ++i) {
+ const QGLTextureCacheKey &key = keys.at(i);
+ if (m_cache.object(key)->context == ctx)
+ m_cache.remove(key);
+ }
+}
+
+/*
+ a hook that removes textures from the cache when a pixmap/image
+ is deref'ed
+*/
+void QGLTextureCache::cleanupTexturesForCacheKey(qint64 cacheKey)
+{
+ qt_gl_texture_cache()->remove(cacheKey);
+}
+
+
+void QGLTextureCache::cleanupTexturesForPixampData(QPixmapData* pmd)
+{
+ cleanupTexturesForCacheKey(pmd->cacheKey());
+}
+
+void QGLTextureCache::cleanupBeforePixmapDestruction(QPixmapData* pmd)
+{
+ // Remove any bound textures first:
+ cleanupTexturesForPixampData(pmd);
+
+#if defined(Q_WS_X11)
+ if (pmd->classId() == QPixmapData::X11Class) {
+ Q_ASSERT(pmd->ref == 0); // Make sure reference counting isn't broken
+ QGLContextPrivate::destroyGlSurfaceForPixmap(pmd);
+ }
+#endif
+}
+
+QGLTextureCache *QGLTextureCache::instance()
+{
+ return qt_gl_texture_cache();
+}
+
+// DDS format structure
+struct DDSFormat {
+ quint32 dwSize;
+ quint32 dwFlags;
+ quint32 dwHeight;
+ quint32 dwWidth;
+ quint32 dwLinearSize;
+ quint32 dummy1;
+ quint32 dwMipMapCount;
+ quint32 dummy2[11];
+ struct {
+ quint32 dummy3[2];
+ quint32 dwFourCC;
+ quint32 dummy4[5];
+ } ddsPixelFormat;
+};
+
+// compressed texture pixel formats
+#define FOURCC_DXT1 0x31545844
+#define FOURCC_DXT2 0x32545844
+#define FOURCC_DXT3 0x33545844
+#define FOURCC_DXT4 0x34545844
+#define FOURCC_DXT5 0x35545844
+
+#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT
+#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
+#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
+#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
+#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
+#endif
+
+#ifndef GL_GENERATE_MIPMAP_SGIS
+#define GL_GENERATE_MIPMAP_SGIS 0x8191
+#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192
+#endif
+
+/*!
+ \class QGLContext
+ \brief The QGLContext class encapsulates an OpenGL rendering context.
+
+ \ingroup painting-3D
+
+ An OpenGL rendering context is a complete set of OpenGL state
+ variables. The rendering context's \l {QGL::FormatOption} {format}
+ is set in the constructor, but it can also be set later with
+ setFormat(). The format options that are actually set are returned
+ by format(); the options you asked for are returned by
+ requestedFormat(). Note that after a QGLContext object has been
+ constructed, the actual OpenGL context must be created by
+ explicitly calling the \link create() create()\endlink
+ function. The makeCurrent() function makes this context the
+ current rendering context. You can make \e no context current
+ using doneCurrent(). The reset() function will reset the context
+ and make it invalid.
+
+ You can examine properties of the context with, e.g. isValid(),
+ isSharing(), initialized(), windowCreated() and
+ overlayTransparentColor().
+
+ If you're using double buffering you can swap the screen contents
+ with the off-screen buffer using swapBuffers().
+
+ Please note that QGLContext is not thread safe.
+*/
+
+/*!
+ \enum QGLContext::BindOption
+ \since 4.6
+
+ A set of options to decide how to bind a texture using bindTexture().
+
+ \value NoBindOption Don't do anything, pass the texture straight
+ through.
+
+ \value InvertedYBindOption Specifies that the texture should be flipped
+ over the X axis so that the texture coordinate 0,0 corresponds to
+ the top left corner. Inverting the texture implies a deep copy
+ prior to upload.
+
+ \value MipmapBindOption Specifies that bindTexture() should try
+ to generate mipmaps. If the GL implementation supports the \c
+ GL_SGIS_generate_mipmap extension, mipmaps will be automatically
+ generated for the texture. Mipmap generation is only supported for
+ the \c GL_TEXTURE_2D target.
+
+ \value PremultipliedAlphaBindOption Specifies that the image should be
+ uploaded with premultiplied alpha and does a conversion accordingly.
+
+ \value LinearFilteringBindOption Specifies that the texture filtering
+ should be set to GL_LINEAR. Default is GL_NEAREST. If mipmap is
+ also enabled, filtering will be set to GL_LINEAR_MIPMAP_LINEAR.
+
+ \value DefaultBindOption In Qt 4.5 and earlier, bindTexture()
+ would mirror the image and automatically generate mipmaps. This
+ option helps preserve this default behavior.
+
+ \omitvalue CanFlipNativePixmapBindOption Used by x11 from pixmap to choose
+ whether or not it can bind the pixmap upside down or not.
+
+ \omitvalue MemoryManagedBindOption Used by paint engines to
+ indicate that the pixmap should be memory managed along side with
+ the pixmap/image that it stems from, e.g. installing destruction
+ hooks in them.
+
+ \omitvalue TemporarilyCachedBindOption Used by paint engines on some
+ platforms to indicate that the pixmap or image texture is possibly
+ cached only temporarily and must be destroyed immediately after the use.
+
+ \omitvalue InternalBindOption
+*/
+
+/*!
+ \obsolete
+
+ Constructs an OpenGL context for the given paint \a device, which
+ can be a widget or a pixmap. The \a format specifies several
+ display options for the context.
+
+ If the underlying OpenGL/Window system cannot satisfy all the
+ features requested in \a format, the nearest subset of features
+ will be used. After creation, the format() method will return the
+ actual format obtained.
+
+ Note that after a QGLContext object has been constructed, \l
+ create() must be called explicitly to create the actual OpenGL
+ context. The context will be \l {isValid()}{invalid} if it was not
+ possible to obtain a GL context at all.
+*/
+
+QGLContext::QGLContext(const QGLFormat &format, QPaintDevice *device)
+ : d_ptr(new QGLContextPrivate(this))
+{
+ Q_D(QGLContext);
+ d->init(device, format);
+}
+
+/*!
+ Constructs an OpenGL context with the given \a format which
+ specifies several display options for the context.
+
+ If the underlying OpenGL/Window system cannot satisfy all the
+ features requested in \a format, the nearest subset of features
+ will be used. After creation, the format() method will return the
+ actual format obtained.
+
+ Note that after a QGLContext object has been constructed, \l
+ create() must be called explicitly to create the actual OpenGL
+ context. The context will be \l {isValid()}{invalid} if it was not
+ possible to obtain a GL context at all.
+
+ \sa format(), isValid()
+*/
+QGLContext::QGLContext(const QGLFormat &format)
+ : d_ptr(new QGLContextPrivate(this))
+{
+ Q_D(QGLContext);
+ d->init(0, format);
+}
+
+/*!
+ Destroys the OpenGL context and frees its resources.
+*/
+
+QGLContext::~QGLContext()
+{
+ // remove any textures cached in this context
+ QGLTextureCache::instance()->removeContextTextures(this);
+
+ // clean up resources specific to this context
+ d_ptr->cleanup();
+ // clean up resources belonging to this context's group
+ d_ptr->group->cleanupResources(this);
+
+ QGLSignalProxy::instance()->emitAboutToDestroyContext(this);
+ reset();
+}
+
+void QGLContextPrivate::cleanup()
+{
+ QHash<QGLContextResourceBase *, void *>::ConstIterator it;
+ for (it = m_resources.begin(); it != m_resources.end(); ++it)
+ it.key()->freeResource(it.value());
+ m_resources.clear();
+}
+
+#define ctx q_ptr
+void QGLContextPrivate::setVertexAttribArrayEnabled(int arrayIndex, bool enabled)
+{
+ Q_ASSERT(arrayIndex < QT_GL_VERTEX_ARRAY_TRACKED_COUNT);
+#ifdef glEnableVertexAttribArray
+ Q_ASSERT(glEnableVertexAttribArray);
+#endif
+
+ if (vertexAttributeArraysEnabledState[arrayIndex] && !enabled)
+ glDisableVertexAttribArray(arrayIndex);
+
+ if (!vertexAttributeArraysEnabledState[arrayIndex] && enabled)
+ glEnableVertexAttribArray(arrayIndex);
+
+ vertexAttributeArraysEnabledState[arrayIndex] = enabled;
+}
+
+void QGLContextPrivate::syncGlState()
+{
+#ifdef glEnableVertexAttribArray
+ Q_ASSERT(glEnableVertexAttribArray);
+#endif
+ for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i) {
+ if (vertexAttributeArraysEnabledState[i])
+ glEnableVertexAttribArray(i);
+ else
+ glDisableVertexAttribArray(i);
+ }
+
+}
+#undef ctx
+
+#ifdef QT_NO_EGL
+void QGLContextPrivate::swapRegion(const QRegion &)
+{
+ Q_Q(QGLContext);
+ q->swapBuffers();
+}
+#endif
+
+/*!
+ \overload
+
+ Reads the compressed texture file \a fileName and generates a 2D GL
+ texture from it.
+
+ This function can load DirectDrawSurface (DDS) textures in the
+ DXT1, DXT3 and DXT5 DDS formats if the \c GL_ARB_texture_compression
+ and \c GL_EXT_texture_compression_s3tc extensions are supported.
+
+ Since 4.6.1, textures in the ETC1 format can be loaded if the
+ \c GL_OES_compressed_ETC1_RGB8_texture extension is supported
+ and the ETC1 texture has been encapsulated in the PVR container format.
+ Also, textures in the PVRTC2 and PVRTC4 formats can be loaded
+ if the \c GL_IMG_texture_compression_pvrtc extension is supported.
+
+ \sa deleteTexture()
+*/
+
+GLuint QGLContext::bindTexture(const QString &fileName)
+{
+ QGLTexture texture(this);
+ QSize size = texture.bindCompressedTexture(fileName);
+ if (!size.isValid())
+ return 0;
+ return texture.id;
+}
+
+static inline QRgb qt_gl_convertToGLFormatHelper(QRgb src_pixel, GLenum texture_format)
+{
+ if (texture_format == GL_BGRA) {
+ if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
+ return ((src_pixel << 24) & 0xff000000)
+ | ((src_pixel >> 24) & 0x000000ff)
+ | ((src_pixel << 8) & 0x00ff0000)
+ | ((src_pixel >> 8) & 0x0000ff00);
+ } else {
+ return src_pixel;
+ }
+ } else { // GL_RGBA
+ if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
+ return (src_pixel << 8) | ((src_pixel >> 24) & 0xff);
+ } else {
+ return ((src_pixel << 16) & 0xff0000)
+ | ((src_pixel >> 16) & 0xff)
+ | (src_pixel & 0xff00ff00);
+ }
+ }
+}
+
+QRgb qt_gl_convertToGLFormat(QRgb src_pixel, GLenum texture_format)
+{
+ return qt_gl_convertToGLFormatHelper(src_pixel, texture_format);
+}
+
+static void convertToGLFormatHelper(QImage &dst, const QImage &img, GLenum texture_format)
+{
+ Q_ASSERT(dst.depth() == 32);
+ Q_ASSERT(img.depth() == 32);
+
+ if (dst.size() != img.size()) {
+ int target_width = dst.width();
+ int target_height = dst.height();
+ qreal sx = target_width / qreal(img.width());
+ qreal sy = target_height / qreal(img.height());
+
+ quint32 *dest = (quint32 *) dst.scanLine(0); // NB! avoid detach here
+ uchar *srcPixels = (uchar *) img.scanLine(img.height() - 1);
+ int sbpl = img.bytesPerLine();
+ int dbpl = dst.bytesPerLine();
+
+ int ix = int(0x00010000 / sx);
+ int iy = int(0x00010000 / sy);
+
+ quint32 basex = int(0.5 * ix);
+ quint32 srcy = int(0.5 * iy);
+
+ // scale, swizzle and mirror in one loop
+ while (target_height--) {
+ const uint *src = (const quint32 *) (srcPixels - (srcy >> 16) * sbpl);
+ int srcx = basex;
+ for (int x=0; x<target_width; ++x) {
+ dest[x] = qt_gl_convertToGLFormatHelper(src[srcx >> 16], texture_format);
+ srcx += ix;
+ }
+ dest = (quint32 *)(((uchar *) dest) + dbpl);
+ srcy += iy;
+ }
+ } else {
+ const int width = img.width();
+ const int height = img.height();
+ const uint *p = (const uint*) img.scanLine(img.height() - 1);
+ uint *q = (uint*) dst.scanLine(0);
+
+ if (texture_format == GL_BGRA) {
+ if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
+ // mirror + swizzle
+ for (int i=0; i < height; ++i) {
+ const uint *end = p + width;
+ while (p < end) {
+ *q = ((*p << 24) & 0xff000000)
+ | ((*p >> 24) & 0x000000ff)
+ | ((*p << 8) & 0x00ff0000)
+ | ((*p >> 8) & 0x0000ff00);
+ p++;
+ q++;
+ }
+ p -= 2 * width;
+ }
+ } else {
+ const uint bytesPerLine = img.bytesPerLine();
+ for (int i=0; i < height; ++i) {
+ memcpy(q, p, bytesPerLine);
+ q += width;
+ p -= width;
+ }
+ }
+ } else {
+ if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
+ for (int i=0; i < height; ++i) {
+ const uint *end = p + width;
+ while (p < end) {
+ *q = (*p << 8) | ((*p >> 24) & 0xff);
+ p++;
+ q++;
+ }
+ p -= 2 * width;
+ }
+ } else {
+ for (int i=0; i < height; ++i) {
+ const uint *end = p + width;
+ while (p < end) {
+ *q = ((*p << 16) & 0xff0000) | ((*p >> 16) & 0xff) | (*p & 0xff00ff00);
+ p++;
+ q++;
+ }
+ p -= 2 * width;
+ }
+ }
+ }
+ }
+}
+
+#if defined(Q_WS_X11) || defined(Q_WS_MAC) || defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN)
+QGLExtensionFuncs& QGLContextPrivate::extensionFuncs(const QGLContext *)
+{
+ return qt_extensionFuncs;
+}
+#endif
+
+QImage QGLContextPrivate::convertToGLFormat(const QImage &image, bool force_premul,
+ GLenum texture_format)
+{
+ QImage::Format target_format = image.format();
+ if (force_premul || image.format() != QImage::Format_ARGB32)
+ target_format = QImage::Format_ARGB32_Premultiplied;
+
+ QImage result(image.width(), image.height(), target_format);
+ convertToGLFormatHelper(result, image.convertToFormat(target_format), texture_format);
+ return result;
+}
+
+/*! \internal */
+QGLTexture *QGLContextPrivate::bindTexture(const QImage &image, GLenum target, GLint format,
+ QGLContext::BindOptions options)
+{
+ Q_Q(QGLContext);
+
+ const qint64 key = image.cacheKey();
+ QGLTexture *texture = textureCacheLookup(key, target);
+ if (texture) {
+ if (image.paintingActive()) {
+ // A QPainter is active on the image - take the safe route and replace the texture.
+ q->deleteTexture(texture->id);
+ texture = 0;
+ } else {
+ glBindTexture(target, texture->id);
+ return texture;
+ }
+ }
+
+ if (!texture)
+ texture = bindTexture(image, target, format, key, options);
+ // NOTE: bindTexture(const QImage&, GLenum, GLint, const qint64, bool) should never return null
+ Q_ASSERT(texture);
+
+ // Enable the cleanup hooks for this image so that the texture cache entry is removed when the
+ // image gets deleted:
+ QImagePixmapCleanupHooks::enableCleanupHooks(image);
+
+ return texture;
+}
+
+// #define QGL_BIND_TEXTURE_DEBUG
+
+// map from Qt's ARGB endianness-dependent format to GL's big-endian RGBA layout
+static inline void qgl_byteSwapImage(QImage &img, GLenum pixel_type)
+{
+ const int width = img.width();
+ const int height = img.height();
+
+ if (pixel_type == GL_UNSIGNED_INT_8_8_8_8_REV
+ || (pixel_type == GL_UNSIGNED_BYTE && QSysInfo::ByteOrder == QSysInfo::LittleEndian))
+ {
+ for (int i = 0; i < height; ++i) {
+ uint *p = (uint *) img.scanLine(i);
+ for (int x = 0; x < width; ++x)
+ p[x] = ((p[x] << 16) & 0xff0000) | ((p[x] >> 16) & 0xff) | (p[x] & 0xff00ff00);
+ }
+ } else {
+ for (int i = 0; i < height; ++i) {
+ uint *p = (uint *) img.scanLine(i);
+ for (int x = 0; x < width; ++x)
+ p[x] = (p[x] << 8) | ((p[x] >> 24) & 0xff);
+ }
+ }
+}
+
+QGLTexture* QGLContextPrivate::bindTexture(const QImage &image, GLenum target, GLint internalFormat,
+ const qint64 key, QGLContext::BindOptions options)
+{
+ Q_Q(QGLContext);
+
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ printf("QGLContextPrivate::bindTexture(), imageSize=(%d,%d), internalFormat =0x%x, options=%x, key=%llx\n",
+ image.width(), image.height(), internalFormat, int(options), key);
+ QTime time;
+ time.start();
+#endif
+
+#ifndef QT_NO_DEBUG
+ // Reset the gl error stack...git
+ while (glGetError() != GL_NO_ERROR) ;
+#endif
+
+ // Scale the pixmap if needed. GL textures needs to have the
+ // dimensions 2^n+2(border) x 2^m+2(border), unless we're using GL
+ // 2.0 or use the GL_TEXTURE_RECTANGLE texture target
+ int tx_w = qt_next_power_of_two(image.width());
+ int tx_h = qt_next_power_of_two(image.height());
+
+ QImage img = image;
+
+ if (!(QGLExtensions::glExtensions() & QGLExtensions::NPOTTextures)
+ && !(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0)
+ && (target == GL_TEXTURE_2D && (tx_w != image.width() || tx_h != image.height())))
+ {
+ img = img.scaled(tx_w, tx_h);
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ printf(" - upscaled to %dx%d (%d ms)\n", tx_w, tx_h, time.elapsed());
+
+#endif
+ }
+
+ GLuint filtering = options & QGLContext::LinearFilteringBindOption ? GL_LINEAR : GL_NEAREST;
+
+ GLuint tx_id;
+ glGenTextures(1, &tx_id);
+ glBindTexture(target, tx_id);
+ glTexParameterf(target, GL_TEXTURE_MAG_FILTER, filtering);
+
+#if defined(QT_OPENGL_ES_2)
+ bool genMipmap = false;
+#endif
+ if (glFormat.directRendering()
+ && (QGLExtensions::glExtensions() & QGLExtensions::GenerateMipmap)
+ && target == GL_TEXTURE_2D
+ && (options & QGLContext::MipmapBindOption))
+ {
+#if !defined(QT_OPENGL_ES_2)
+ glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST);
+#ifndef QT_OPENGL_ES
+ glTexParameteri(target, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
+#else
+ glTexParameterf(target, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
+#endif
+#else
+ glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
+ genMipmap = true;
+#endif
+ glTexParameterf(target, GL_TEXTURE_MIN_FILTER, options & QGLContext::LinearFilteringBindOption
+ ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST);
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ printf(" - generating mipmaps (%d ms)\n", time.elapsed());
+#endif
+ } else {
+ glTexParameterf(target, GL_TEXTURE_MIN_FILTER, filtering);
+ }
+
+ QImage::Format target_format = img.format();
+ bool premul = options & QGLContext::PremultipliedAlphaBindOption;
+ GLenum externalFormat;
+ GLuint pixel_type;
+ if (QGLExtensions::glExtensions() & QGLExtensions::BGRATextureFormat) {
+ externalFormat = GL_BGRA;
+ if (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_1_2)
+ pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+ else
+ pixel_type = GL_UNSIGNED_BYTE;
+ } else {
+ externalFormat = GL_RGBA;
+ pixel_type = GL_UNSIGNED_BYTE;
+ }
+
+ switch (target_format) {
+ case QImage::Format_ARGB32:
+ if (premul) {
+ img = img.convertToFormat(target_format = QImage::Format_ARGB32_Premultiplied);
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ printf(" - converted ARGB32 -> ARGB32_Premultiplied (%d ms) \n", time.elapsed());
+#endif
+ }
+ break;
+ case QImage::Format_ARGB32_Premultiplied:
+ if (!premul) {
+ img = img.convertToFormat(target_format = QImage::Format_ARGB32);
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ printf(" - converted ARGB32_Premultiplied -> ARGB32 (%d ms)\n", time.elapsed());
+#endif
+ }
+ break;
+ case QImage::Format_RGB16:
+ pixel_type = GL_UNSIGNED_SHORT_5_6_5;
+ externalFormat = GL_RGB;
+ internalFormat = GL_RGB;
+ break;
+ case QImage::Format_RGB32:
+ break;
+ default:
+ if (img.hasAlphaChannel()) {
+ img = img.convertToFormat(premul
+ ? QImage::Format_ARGB32_Premultiplied
+ : QImage::Format_ARGB32);
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ printf(" - converted to 32-bit alpha format (%d ms)\n", time.elapsed());
+#endif
+ } else {
+ img = img.convertToFormat(QImage::Format_RGB32);
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ printf(" - converted to 32-bit (%d ms)\n", time.elapsed());
+#endif
+ }
+ }
+
+ if (options & QGLContext::InvertedYBindOption) {
+ if (img.isDetached()) {
+ int ipl = img.bytesPerLine() / 4;
+ int h = img.height();
+ for (int y=0; y<h/2; ++y) {
+ int *a = (int *) img.scanLine(y);
+ int *b = (int *) img.scanLine(h - y - 1);
+ for (int x=0; x<ipl; ++x)
+ qSwap(a[x], b[x]);
+ }
+ } else {
+ // Create a new image and copy across. If we use the
+ // above in-place code then a full copy of the image is
+ // made before the lines are swapped, which processes the
+ // data twice. This version should only do it once.
+ img = img.mirrored();
+ }
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ printf(" - flipped bits over y (%d ms)\n", time.elapsed());
+#endif
+ }
+
+ if (externalFormat == GL_RGBA) {
+ // The only case where we end up with a depth different from
+ // 32 in the switch above is for the RGB16 case, where we set
+ // the format to GL_RGB
+ Q_ASSERT(img.depth() == 32);
+ qgl_byteSwapImage(img, pixel_type);
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ printf(" - did byte swapping (%d ms)\n", time.elapsed());
+#endif
+ }
+#ifdef QT_OPENGL_ES
+ // OpenGL/ES requires that the internal and external formats be
+ // identical.
+ internalFormat = externalFormat;
+#endif
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ printf(" - uploading, image.format=%d, externalFormat=0x%x, internalFormat=0x%x, pixel_type=0x%x\n",
+ img.format(), externalFormat, internalFormat, pixel_type);
+#endif
+
+ const QImage &constRef = img; // to avoid detach in bits()...
+#ifdef QGL_USE_TEXTURE_POOL
+ QGLTexturePool::instance()->createPermanentTexture(tx_id,
+ target,
+ 0, internalFormat,
+ img.width(), img.height(),
+ externalFormat,
+ pixel_type,
+ constRef.bits());
+#else
+ glTexImage2D(target, 0, internalFormat, img.width(), img.height(), 0, externalFormat,
+ pixel_type, constRef.bits());
+#endif
+#if defined(QT_OPENGL_ES_2)
+ if (genMipmap)
+ glGenerateMipmap(target);
+#endif
+#ifndef QT_NO_DEBUG
+ GLenum error = glGetError();
+ if (error != GL_NO_ERROR) {
+ qWarning(" - texture upload failed, error code 0x%x, enum: %d (%x)\n", error, target, target);
+ }
+#endif
+
+#ifdef QGL_BIND_TEXTURE_DEBUG
+ static int totalUploadTime = 0;
+ totalUploadTime += time.elapsed();
+ printf(" - upload done in %d ms, (accumulated: %d ms)\n", time.elapsed(), totalUploadTime);
+#endif
+
+
+ // this assumes the size of a texture is always smaller than the max cache size
+ int cost = img.width()*img.height()*4/1024;
+ QGLTexture *texture = new QGLTexture(q, tx_id, target, options);
+ QGLTextureCache::instance()->insert(q, key, texture, cost);
+
+ return texture;
+}
+
+QGLTexture *QGLContextPrivate::textureCacheLookup(const qint64 key, GLenum target)
+{
+ Q_Q(QGLContext);
+ QGLTexture *texture = QGLTextureCache::instance()->getTexture(q, key);
+ if (texture && texture->target == target
+ && (texture->context == q || QGLContext::areSharing(q, texture->context)))
+ {
+ return texture;
+ }
+ return 0;
+}
+
+/*! \internal */
+QGLTexture *QGLContextPrivate::bindTexture(const QPixmap &pixmap, GLenum target, GLint format, QGLContext::BindOptions options)
+{
+ Q_Q(QGLContext);
+ QPixmapData *pd = pixmap.pixmapData();
+#if !defined(QT_OPENGL_ES_1)
+ if (target == GL_TEXTURE_2D && pd->classId() == QPixmapData::OpenGLClass) {
+ const QGLPixmapData *data = static_cast<const QGLPixmapData *>(pd);
+
+ if (data->isValidContext(q)) {
+ data->bind();
+ return data->texture();
+ }
+ }
+#else
+ Q_UNUSED(pd);
+#endif
+
+ const qint64 key = pixmap.cacheKey();
+ QGLTexture *texture = textureCacheLookup(key, target);
+ if (texture) {
+ if (pixmap.paintingActive()) {
+ // A QPainter is active on the pixmap - take the safe route and replace the texture.
+ q->deleteTexture(texture->id);
+ texture = 0;
+ } else {
+ glBindTexture(target, texture->id);
+ return texture;
+ }
+ }
+
+#if defined(Q_WS_X11)
+ // Try to use texture_from_pixmap
+ const QX11Info *xinfo = qt_x11Info(paintDevice);
+ if (pd->classId() == QPixmapData::X11Class && pd->pixelType() == QPixmapData::PixmapType
+ && xinfo && xinfo->screen() == pixmap.x11Info().screen()
+ && target == GL_TEXTURE_2D
+ && QApplication::instance()->thread() == QThread::currentThread())
+ {
+ if (!workaround_brokenTextureFromPixmap_init) {
+ workaround_brokenTextureFromPixmap_init = true;
+
+ const QByteArray versionString(reinterpret_cast<const char*>(glGetString(GL_VERSION)));
+ const int pos = versionString.indexOf("NVIDIA ");
+
+ if (pos >= 0) {
+ const QByteArray nvidiaVersionString = versionString.mid(pos + strlen("NVIDIA "));
+
+ if (nvidiaVersionString.startsWith("195") || nvidiaVersionString.startsWith("256"))
+ workaround_brokenTextureFromPixmap = true;
+ }
+ }
+
+ if (!workaround_brokenTextureFromPixmap) {
+ texture = bindTextureFromNativePixmap(const_cast<QPixmap*>(&pixmap), key, options);
+ if (texture) {
+ texture->options |= QGLContext::MemoryManagedBindOption;
+ texture->boundPixmap = pd;
+ boundPixmaps.insert(pd, QPixmap(pixmap));
+ }
+ }
+ }
+#endif
+
+ if (!texture) {
+ QImage image = pixmap.toImage();
+ // If the system depth is 16 and the pixmap doesn't have an alpha channel
+ // then we convert it to RGB16 in the hope that it gets uploaded as a 16
+ // bit texture which is much faster to access than a 32-bit one.
+ if (pixmap.depth() == 16 && !image.hasAlphaChannel() )
+ image = image.convertToFormat(QImage::Format_RGB16);
+ texture = bindTexture(image, target, format, key, options);
+ }
+ // NOTE: bindTexture(const QImage&, GLenum, GLint, const qint64, bool) should never return null
+ Q_ASSERT(texture);
+
+ if (texture->id > 0)
+ QImagePixmapCleanupHooks::enableCleanupHooks(pixmap);
+
+ return texture;
+}
+
+/*! \internal */
+int QGLContextPrivate::maxTextureSize()
+{
+ if (max_texture_size != -1)
+ return max_texture_size;
+
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
+
+#if defined(QT_OPENGL_ES)
+ return max_texture_size;
+#else
+ GLenum proxy = GL_PROXY_TEXTURE_2D;
+
+ GLint size;
+ GLint next = 64;
+ glTexImage2D(proxy, 0, GL_RGBA, next, next, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ glGetTexLevelParameteriv(proxy, 0, GL_TEXTURE_WIDTH, &size);
+ if (size == 0) {
+ return max_texture_size;
+ }
+ do {
+ size = next;
+ next = size * 2;
+
+ if (next > max_texture_size)
+ break;
+ glTexImage2D(proxy, 0, GL_RGBA, next, next, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ glGetTexLevelParameteriv(proxy, 0, GL_TEXTURE_WIDTH, &next);
+ } while (next > size);
+
+ max_texture_size = size;
+ return max_texture_size;
+#endif
+}
+
+/*!
+ Generates and binds a 2D GL texture to the current context, based
+ on \a image. The generated texture id is returned and can be used in
+ later \c glBindTexture() calls.
+
+ \overload
+*/
+GLuint QGLContext::bindTexture(const QImage &image, GLenum target, GLint format)
+{
+ if (image.isNull())
+ return 0;
+
+ Q_D(QGLContext);
+ QGLTexture *texture = d->bindTexture(image, target, format, DefaultBindOption);
+ return texture->id;
+}
+
+/*!
+ \since 4.6
+
+ Generates and binds a 2D GL texture to the current context, based
+ on \a image. The generated texture id is returned and can be used
+ in later \c glBindTexture() calls.
+
+ The \a target parameter specifies the texture target. The default
+ target is \c GL_TEXTURE_2D.
+
+ The \a format parameter sets the internal format for the
+ texture. The default format is \c GL_RGBA.
+
+ The binding \a options are a set of options used to decide how to
+ bind the texture to the context.
+
+ The texture that is generated is cached, so multiple calls to
+ bindTexture() with the same QImage will return the same texture
+ id.
+
+ Note that we assume default values for the glPixelStore() and
+ glPixelTransfer() parameters.
+
+ \sa deleteTexture()
+*/
+GLuint QGLContext::bindTexture(const QImage &image, GLenum target, GLint format, BindOptions options)
+{
+ if (image.isNull())
+ return 0;
+
+ Q_D(QGLContext);
+ QGLTexture *texture = d->bindTexture(image, target, format, options);
+ return texture->id;
+}
+
+#ifdef Q_MAC_COMPAT_GL_FUNCTIONS
+/*! \internal */
+GLuint QGLContext::bindTexture(const QImage &image, QMacCompatGLenum target, QMacCompatGLint format)
+{
+ if (image.isNull())
+ return 0;
+
+ Q_D(QGLContext);
+ QGLTexture *texture = d->bindTexture(image, GLenum(target), GLint(format), DefaultBindOption);
+ return texture->id;
+}
+
+/*! \internal */
+GLuint QGLContext::bindTexture(const QImage &image, QMacCompatGLenum target, QMacCompatGLint format,
+ BindOptions options)
+{
+ if (image.isNull())
+ return 0;
+
+ Q_D(QGLContext);
+ QGLTexture *texture = d->bindTexture(image, GLenum(target), GLint(format), options);
+ return texture->id;
+}
+#endif
+
+/*! \overload
+
+ Generates and binds a 2D GL texture based on \a pixmap.
+*/
+GLuint QGLContext::bindTexture(const QPixmap &pixmap, GLenum target, GLint format)
+{
+ if (pixmap.isNull())
+ return 0;
+
+ Q_D(QGLContext);
+ QGLTexture *texture = d->bindTexture(pixmap, target, format, DefaultBindOption);
+ return texture->id;
+}
+
+/*!
+ \overload
+ \since 4.6
+
+ Generates and binds a 2D GL texture to the current context, based
+ on \a pixmap.
+*/
+GLuint QGLContext::bindTexture(const QPixmap &pixmap, GLenum target, GLint format, BindOptions options)
+{
+ if (pixmap.isNull())
+ return 0;
+
+ Q_D(QGLContext);
+ QGLTexture *texture = d->bindTexture(pixmap, target, format, options);
+ return texture->id;
+}
+
+#ifdef Q_MAC_COMPAT_GL_FUNCTIONS
+/*! \internal */
+GLuint QGLContext::bindTexture(const QPixmap &pixmap, QMacCompatGLenum target, QMacCompatGLint format)
+{
+ if (pixmap.isNull())
+ return 0;
+
+ Q_D(QGLContext);
+ QGLTexture *texture = d->bindTexture(pixmap, GLenum(target), GLint(format), DefaultBindOption);
+ return texture->id;
+}
+/*! \internal */
+GLuint QGLContext::bindTexture(const QPixmap &pixmap, QMacCompatGLenum target, QMacCompatGLint format,
+ BindOptions options)
+{
+ if (pixmap.isNull())
+ return 0;
+
+ Q_D(QGLContext);
+ QGLTexture *texture = d->bindTexture(pixmap, GLenum(target), GLint(format), options);
+ return texture->id;
+}
+#endif
+
+/*!
+ Removes the texture identified by \a id from the texture cache,
+ and calls glDeleteTextures() to delete the texture from the
+ context.
+
+ \sa bindTexture()
+*/
+void QGLContext::deleteTexture(GLuint id)
+{
+ if (QGLTextureCache::instance()->remove(this, id))
+ return;
+ glDeleteTextures(1, &id);
+}
+
+#ifdef Q_MAC_COMPAT_GL_FUNCTIONS
+/*! \internal */
+void QGLContext::deleteTexture(QMacCompatGLuint id)
+{
+ return deleteTexture(GLuint(id));
+}
+#endif
+
+void qt_add_rect_to_array(const QRectF &r, GLfloat *array)
+{
+ qreal left = r.left();
+ qreal right = r.right();
+ qreal top = r.top();
+ qreal bottom = r.bottom();
+
+ array[0] = left;
+ array[1] = top;
+ array[2] = right;
+ array[3] = top;
+ array[4] = right;
+ array[5] = bottom;
+ array[6] = left;
+ array[7] = bottom;
+}
+
+void qt_add_texcoords_to_array(qreal x1, qreal y1, qreal x2, qreal y2, GLfloat *array)
+{
+ array[0] = x1;
+ array[1] = y1;
+ array[2] = x2;
+ array[3] = y1;
+ array[4] = x2;
+ array[5] = y2;
+ array[6] = x1;
+ array[7] = y2;
+}
+
+#if !defined(QT_OPENGL_ES_2)
+
+static void qDrawTextureRect(const QRectF &target, GLint textureWidth, GLint textureHeight, GLenum textureTarget)
+{
+ GLfloat tx = 1.0f;
+ GLfloat ty = 1.0f;
+
+#ifdef QT_OPENGL_ES
+ Q_UNUSED(textureWidth);
+ Q_UNUSED(textureHeight);
+ Q_UNUSED(textureTarget);
+#else
+ if (textureTarget != GL_TEXTURE_2D) {
+ if (textureWidth == -1 || textureHeight == -1) {
+ glGetTexLevelParameteriv(textureTarget, 0, GL_TEXTURE_WIDTH, &textureWidth);
+ glGetTexLevelParameteriv(textureTarget, 0, GL_TEXTURE_HEIGHT, &textureHeight);
+ }
+
+ tx = GLfloat(textureWidth);
+ ty = GLfloat(textureHeight);
+ }
+#endif
+
+ GLfloat texCoordArray[4*2] = {
+ 0, ty, tx, ty, tx, 0, 0, 0
+ };
+
+ GLfloat vertexArray[4*2];
+ qt_add_rect_to_array(target, vertexArray);
+
+ glVertexPointer(2, GL_FLOAT, 0, vertexArray);
+ glTexCoordPointer(2, GL_FLOAT, 0, texCoordArray);
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+}
+
+#endif // !QT_OPENGL_ES_2
+
+/*!
+ \since 4.4
+
+ This function supports the following use cases:
+
+ \list
+ \i On OpenGL and OpenGL ES 1.x it draws the given texture, \a textureId,
+ to the given target rectangle, \a target, in OpenGL model space. The
+ \a textureTarget should be a 2D texture target.
+ \i On OpenGL and OpenGL ES 2.x, if a painter is active, not inside a
+ beginNativePainting / endNativePainting block, and uses the
+ engine with type QPaintEngine::OpenGL2, the function will draw the given
+ texture, \a textureId, to the given target rectangle, \a target,
+ respecting the current painter state. This will let you draw a texture
+ with the clip, transform, render hints, and composition mode set by the
+ painter. Note that the texture target needs to be GL_TEXTURE_2D for this
+ use case, and that this is the only supported use case under OpenGL ES 2.x.
+ \endlist
+
+*/
+void QGLContext::drawTexture(const QRectF &target, GLuint textureId, GLenum textureTarget)
+{
+#if !defined(QT_OPENGL_ES) || defined(QT_OPENGL_ES_2)
+ if (d_ptr->active_engine &&
+ d_ptr->active_engine->type() == QPaintEngine::OpenGL2) {
+ QGL2PaintEngineEx *eng = static_cast<QGL2PaintEngineEx*>(d_ptr->active_engine);
+ if (!eng->isNativePaintingActive()) {
+ QRectF src(0, 0, target.width(), target.height());
+ QSize size(target.width(), target.height());
+ if (eng->drawTexture(target, textureId, size, src))
+ return;
+ }
+ }
+#endif
+
+#ifndef QT_OPENGL_ES_2
+#ifdef QT_OPENGL_ES
+ if (textureTarget != GL_TEXTURE_2D) {
+ qWarning("QGLContext::drawTexture(): texture target must be GL_TEXTURE_2D on OpenGL ES");
+ return;
+ }
+#else
+ const bool wasEnabled = glIsEnabled(GL_TEXTURE_2D);
+ GLint oldTexture;
+ glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTexture);
+#endif
+
+ glEnable(textureTarget);
+ glBindTexture(textureTarget, textureId);
+
+ qDrawTextureRect(target, -1, -1, textureTarget);
+
+#ifdef QT_OPENGL_ES
+ glDisable(textureTarget);
+#else
+ if (!wasEnabled)
+ glDisable(textureTarget);
+ glBindTexture(textureTarget, oldTexture);
+#endif
+#else
+ Q_UNUSED(target);
+ Q_UNUSED(textureId);
+ Q_UNUSED(textureTarget);
+ qWarning("drawTexture() with OpenGL ES 2.0 requires an active OpenGL2 paint engine");
+#endif
+}
+
+#ifdef Q_MAC_COMPAT_GL_FUNCTIONS
+/*! \internal */
+void QGLContext::drawTexture(const QRectF &target, QMacCompatGLuint textureId, QMacCompatGLenum textureTarget)
+{
+ drawTexture(target, GLuint(textureId), GLenum(textureTarget));
+}
+#endif
+
+/*!
+ \since 4.4
+
+ This function supports the following use cases:
+
+ \list
+ \i By default it draws the given texture, \a textureId,
+ at the given \a point in OpenGL model space. The
+ \a textureTarget should be a 2D texture target.
+ \i If a painter is active, not inside a
+ beginNativePainting / endNativePainting block, and uses the
+ engine with type QPaintEngine::OpenGL2, the function will draw the given
+ texture, \a textureId, at the given \a point,
+ respecting the current painter state. This will let you draw a texture
+ with the clip, transform, render hints, and composition mode set by the
+ painter. Note that the texture target needs to be GL_TEXTURE_2D for this
+ use case.
+ \endlist
+
+ \note This function is not supported under any version of OpenGL ES.
+*/
+void QGLContext::drawTexture(const QPointF &point, GLuint textureId, GLenum textureTarget)
+{
+#ifdef QT_OPENGL_ES
+ Q_UNUSED(point);
+ Q_UNUSED(textureId);
+ Q_UNUSED(textureTarget);
+ qWarning("drawTexture(const QPointF &point, GLuint textureId, GLenum textureTarget) not supported with OpenGL ES, use rect version instead");
+#else
+
+ const bool wasEnabled = glIsEnabled(GL_TEXTURE_2D);
+ GLint oldTexture;
+ glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTexture);
+
+ glEnable(textureTarget);
+ glBindTexture(textureTarget, textureId);
+
+ GLint textureWidth;
+ GLint textureHeight;
+
+ glGetTexLevelParameteriv(textureTarget, 0, GL_TEXTURE_WIDTH, &textureWidth);
+ glGetTexLevelParameteriv(textureTarget, 0, GL_TEXTURE_HEIGHT, &textureHeight);
+
+ if (d_ptr->active_engine &&
+ d_ptr->active_engine->type() == QPaintEngine::OpenGL2) {
+ QGL2PaintEngineEx *eng = static_cast<QGL2PaintEngineEx*>(d_ptr->active_engine);
+ if (!eng->isNativePaintingActive()) {
+ QRectF dest(point, QSizeF(textureWidth, textureHeight));
+ QRectF src(0, 0, textureWidth, textureHeight);
+ QSize size(textureWidth, textureHeight);
+ if (eng->drawTexture(dest, textureId, size, src))
+ return;
+ }
+ }
+
+ qDrawTextureRect(QRectF(point, QSizeF(textureWidth, textureHeight)), textureWidth, textureHeight, textureTarget);
+
+ if (!wasEnabled)
+ glDisable(textureTarget);
+ glBindTexture(textureTarget, oldTexture);
+#endif
+}
+
+#ifdef Q_MAC_COMPAT_GL_FUNCTIONS
+/*! \internal */
+void QGLContext::drawTexture(const QPointF &point, QMacCompatGLuint textureId, QMacCompatGLenum textureTarget)
+{
+ drawTexture(point, GLuint(textureId), GLenum(textureTarget));
+}
+#endif
+
+
+/*!
+ This function sets the limit for the texture cache to \a size,
+ expressed in kilobytes.
+
+ By default, the cache limit is approximately 64 MB.
+
+ \sa textureCacheLimit()
+*/
+void QGLContext::setTextureCacheLimit(int size)
+{
+ QGLTextureCache::instance()->setMaxCost(size);
+}
+
+/*!
+ Returns the current texture cache limit in kilobytes.
+
+ \sa setTextureCacheLimit()
+*/
+int QGLContext::textureCacheLimit()
+{
+ return QGLTextureCache::instance()->maxCost();
+}
+
+
+/*!
+ \fn QGLFormat QGLContext::format() const
+
+ Returns the frame buffer format that was obtained (this may be a
+ subset of what was requested).
+
+ \sa requestedFormat()
+*/
+
+/*!
+ \fn QGLFormat QGLContext::requestedFormat() const
+
+ Returns the frame buffer format that was originally requested in
+ the constructor or setFormat().
+
+ \sa format()
+*/
+
+/*!
+ Sets a \a format for this context. The context is \link reset()
+ reset\endlink.
+
+ Call create() to create a new GL context that tries to match the
+ new format.
+
+ \snippet doc/src/snippets/code/src_opengl_qgl.cpp 7
+
+ \sa format(), reset(), create()
+*/
+
+void QGLContext::setFormat(const QGLFormat &format)
+{
+ Q_D(QGLContext);
+ reset();
+ d->glFormat = d->reqFormat = format;
+}
+
+/*!
+ \internal
+*/
+void QGLContext::setDevice(QPaintDevice *pDev)
+{
+ Q_D(QGLContext);
+ if (isValid())
+ reset();
+ d->paintDevice = pDev;
+ if (d->paintDevice && (d->paintDevice->devType() != QInternal::Widget
+ && d->paintDevice->devType() != QInternal::Pixmap
+ && d->paintDevice->devType() != QInternal::Pbuffer)) {
+ qWarning("QGLContext: Unsupported paint device type");
+ }
+}
+
+/*!
+ \fn bool QGLContext::isValid() const
+
+ Returns true if a GL rendering context has been successfully
+ created; otherwise returns false.
+*/
+
+/*!
+ \fn void QGLContext::setValid(bool valid)
+ \internal
+
+ Forces the GL rendering context to be valid.
+*/
+
+/*!
+ \fn bool QGLContext::isSharing() const
+
+ Returns true if this context is sharing its GL context with
+ another QGLContext, otherwise false is returned. Note that context
+ sharing might not be supported between contexts with different
+ formats.
+*/
+
+/*!
+ Returns true if \a context1 and \a context2 are sharing their
+ GL resources such as textures, shader programs, etc;
+ otherwise returns false.
+
+ \since 4.6
+*/
+bool QGLContext::areSharing(const QGLContext *context1, const QGLContext *context2)
+{
+ if (!context1 || !context2)
+ return false;
+ return context1->d_ptr->group == context2->d_ptr->group;
+}
+
+/*!
+ \fn bool QGLContext::deviceIsPixmap() const
+
+ Returns true if the paint device of this context is a pixmap;
+ otherwise returns false.
+*/
+
+/*!
+ \fn bool QGLContext::windowCreated() const
+
+ Returns true if a window has been created for this context;
+ otherwise returns false.
+
+ \sa setWindowCreated()
+*/
+
+/*!
+ \fn void QGLContext::setWindowCreated(bool on)
+
+ If \a on is true the context has had a window created for it. If
+ \a on is false no window has been created for the context.
+
+ \sa windowCreated()
+*/
+
+/*!
+ \fn uint QGLContext::colorIndex(const QColor& c) const
+
+ \internal
+
+ Returns a colormap index for the color c, in ColorIndex mode. Used
+ by qglColor() and qglClearColor().
+*/
+
+
+/*!
+ \fn bool QGLContext::initialized() const
+
+ Returns true if this context has been initialized, i.e. if
+ QGLWidget::initializeGL() has been performed on it; otherwise
+ returns false.
+
+ \sa setInitialized()
+*/
+
+/*!
+ \fn void QGLContext::setInitialized(bool on)
+
+ If \a on is true the context has been initialized, i.e.
+ QGLContext::setInitialized() has been called on it. If \a on is
+ false the context has not been initialized.
+
+ \sa initialized()
+*/
+
+/*!
+ \fn const QGLContext* QGLContext::currentContext()
+
+ Returns the current context, i.e. the context to which any OpenGL
+ commands will currently be directed. Returns 0 if no context is
+ current.
+
+ \sa makeCurrent()
+*/
+
+/*!
+ \fn QColor QGLContext::overlayTransparentColor() const
+
+ If this context is a valid context in an overlay plane, returns
+ the plane's transparent color. Otherwise returns an \link
+ QColor::isValid() invalid \endlink color.
+
+ The returned color's \link QColor::pixel() pixel \endlink value is
+ the index of the transparent color in the colormap of the overlay
+ plane. (Naturally, the color's RGB values are meaningless.)
+
+ The returned QColor object will generally work as expected only
+ when passed as the argument to QGLWidget::qglColor() or
+ QGLWidget::qglClearColor(). Under certain circumstances it can
+ also be used to draw transparent graphics with a QPainter. See the
+ examples/opengl/overlay_x11 example for details.
+*/
+
+
+/*!
+ Creates the GL context. Returns true if it was successful in
+ creating a valid GL rendering context on the paint device
+ specified in the constructor; otherwise returns false (i.e. the
+ context is invalid).
+
+ After successful creation, format() returns the set of features of
+ the created GL rendering context.
+
+ If \a shareContext points to a valid QGLContext, this method will
+ try to establish OpenGL display list and texture object sharing
+ between this context and the \a shareContext. Note that this may
+ fail if the two contexts have different \l {format()} {formats}.
+ Use isSharing() to see if sharing is in effect.
+
+ \warning Implementation note: initialization of C++ class
+ members usually takes place in the class constructor. QGLContext
+ is an exception because it must be simple to customize. The
+ virtual functions chooseContext() (and chooseVisual() for X11) can
+ be reimplemented in a subclass to select a particular context. The
+ problem is that virtual functions are not properly called during
+ construction (even though this is correct C++) because C++
+ constructs class hierarchies from the bottom up. For this reason
+ we need a create() function.
+
+ \sa chooseContext(), format(), isValid()
+*/
+
+bool QGLContext::create(const QGLContext* shareContext)
+{
+ Q_D(QGLContext);
+#ifdef Q_WS_QPA
+ if (!d->paintDevice && !d->platformContext)
+#else
+ if (!d->paintDevice)
+#endif
+ return false;
+
+ reset();
+ d->valid = chooseContext(shareContext);
+ if (d->valid && d->paintDevice && d->paintDevice->devType() == QInternal::Widget) {
+ QWidgetPrivate *wd = qt_widget_private(static_cast<QWidget *>(d->paintDevice));
+ wd->usesDoubleBufferedGLContext = d->glFormat.doubleBuffer();
+ }
+#ifndef Q_WS_QPA //We do this in choose context->setupSharing()
+ if (d->sharing) // ok, we managed to share
+ QGLContextGroup::addShare(this, shareContext);
+#endif
+ return d->valid;
+}
+
+bool QGLContext::isValid() const
+{
+ Q_D(const QGLContext);
+ return d->valid;
+}
+
+void QGLContext::setValid(bool valid)
+{
+ Q_D(QGLContext);
+ d->valid = valid;
+}
+
+bool QGLContext::isSharing() const
+{
+ Q_D(const QGLContext);
+ return d->group->isSharing();
+}
+
+QGLFormat QGLContext::format() const
+{
+ Q_D(const QGLContext);
+ return d->glFormat;
+}
+
+QGLFormat QGLContext::requestedFormat() const
+{
+ Q_D(const QGLContext);
+ return d->reqFormat;
+}
+
+ QPaintDevice* QGLContext::device() const
+{
+ Q_D(const QGLContext);
+ return d->paintDevice;
+}
+
+bool QGLContext::deviceIsPixmap() const
+{
+ Q_D(const QGLContext);
+ return d->paintDevice->devType() == QInternal::Pixmap;
+}
+
+
+bool QGLContext::windowCreated() const
+{
+ Q_D(const QGLContext);
+ return d->crWin;
+}
+
+
+void QGLContext::setWindowCreated(bool on)
+{
+ Q_D(QGLContext);
+ d->crWin = on;
+}
+
+bool QGLContext::initialized() const
+{
+ Q_D(const QGLContext);
+ return d->initDone;
+}
+
+void QGLContext::setInitialized(bool on)
+{
+ Q_D(QGLContext);
+ d->initDone = on;
+}
+
+const QGLContext* QGLContext::currentContext()
+{
+#ifdef Q_WS_QPA
+ if (const QPlatformGLContext *threadContext = QPlatformGLContext::currentContext()) {
+ return QGLContext::fromPlatformGLContext(const_cast<QPlatformGLContext *>(threadContext));
+ }
+ return 0;
+#else
+ QGLThreadContext *threadContext = qgl_context_storage.localData();
+ if (threadContext)
+ return threadContext->context;
+ return 0;
+#endif //Q_WS_QPA
+}
+
+void QGLContextPrivate::setCurrentContext(QGLContext *context)
+{
+#ifdef Q_WS_QPA
+ Q_UNUSED(context);
+#else
+ QGLThreadContext *threadContext = qgl_context_storage.localData();
+ if (!threadContext) {
+ if (!QThread::currentThread()) {
+ // We don't have a current QThread, so just set the static.
+ QGLContext::currentCtx = context;
+ return;
+ }
+ threadContext = new QGLThreadContext;
+ qgl_context_storage.setLocalData(threadContext);
+ }
+ threadContext->context = context;
+ QGLContext::currentCtx = context; // XXX: backwards-compat, not thread-safe
+#endif
+}
+
+/*!
+ \fn bool QGLContext::chooseContext(const QGLContext* shareContext = 0)
+
+ This semi-internal function is called by create(). It creates a
+ system-dependent OpenGL handle that matches the format() of \a
+ shareContext as closely as possible, returning true if successful
+ or false if a suitable handle could not be found.
+
+ On Windows, it calls the virtual function choosePixelFormat(),
+ which finds a matching pixel format identifier. On X11, it calls
+ the virtual function chooseVisual() which finds an appropriate X
+ visual. On other platforms it may work differently.
+*/
+
+/*! \fn int QGLContext::choosePixelFormat(void* dummyPfd, HDC pdc)
+
+ \bold{Win32 only:} This virtual function chooses a pixel format
+ that matches the OpenGL \link setFormat() format\endlink.
+ Reimplement this function in a subclass if you need a custom
+ context.
+
+ \warning The \a dummyPfd pointer and \a pdc are used as a \c
+ PIXELFORMATDESCRIPTOR*. We use \c void to avoid using
+ Windows-specific types in our header files.
+
+ \sa chooseContext()
+*/
+
+/*! \fn void *QGLContext::chooseVisual()
+
+ \bold{X11 only:} This virtual function tries to find a visual that
+ matches the format, reducing the demands if the original request
+ cannot be met.
+
+ The algorithm for reducing the demands of the format is quite
+ simple-minded, so override this method in your subclass if your
+ application has spcific requirements on visual selection.
+
+ \sa chooseContext()
+*/
+
+/*! \fn void *QGLContext::tryVisual(const QGLFormat& f, int bufDepth)
+ \internal
+
+ \bold{X11 only:} This virtual function chooses a visual that matches
+ the OpenGL \link format() format\endlink. Reimplement this function
+ in a subclass if you need a custom visual.
+
+ \sa chooseContext()
+*/
+
+/*!
+ \fn void QGLContext::reset()
+
+ Resets the context and makes it invalid.
+
+ \sa create(), isValid()
+*/
+
+
+/*!
+ \fn void QGLContext::makeCurrent()
+
+ Makes this context the current OpenGL rendering context. All GL
+ functions you call operate on this context until another context
+ is made current.
+
+ In some very rare cases the underlying call may fail. If this
+ occurs an error message is output to stderr.
+*/
+
+
+/*!
+ \fn void QGLContext::swapBuffers() const
+
+ Swaps the screen contents with an off-screen buffer. Only works if
+ the context is in double buffer mode.
+
+ \sa QGLFormat::setDoubleBuffer()
+*/
+
+
+/*!
+ \fn void QGLContext::doneCurrent()
+
+ Makes no GL context the current context. Normally, you do not need
+ to call this function; QGLContext calls it as necessary.
+*/
+
+
+/*!
+ \fn QPaintDevice* QGLContext::device() const
+
+ Returns the paint device set for this context.
+
+ \sa QGLContext::QGLContext()
+*/
+
+/*!
+ \obsolete
+ \fn void QGLContext::generateFontDisplayLists(const QFont& font, int listBase)
+
+ Generates a set of 256 display lists for the 256 first characters
+ in the font \a font. The first list will start at index \a listBase.
+
+ \sa QGLWidget::renderText()
+*/
+
+
+
+/*****************************************************************************
+ QGLWidget implementation
+ *****************************************************************************/
+
+
+/*!
+ \class QGLWidget
+ \brief The QGLWidget class is a widget for rendering OpenGL graphics.
+
+ \ingroup painting-3D
+
+
+ QGLWidget provides functionality for displaying OpenGL graphics
+ integrated into a Qt application. It is very simple to use. You
+ inherit from it and use the subclass like any other QWidget,
+ except that you have the choice between using QPainter and
+ standard OpenGL rendering commands.
+
+ QGLWidget provides three convenient virtual functions that you can
+ reimplement in your subclass to perform the typical OpenGL tasks:
+
+ \list
+ \i paintGL() - Renders the OpenGL scene. Gets called whenever the widget
+ needs to be updated.
+ \i resizeGL() - Sets up the OpenGL viewport, projection, etc. Gets
+ called whenever the widget has been resized (and also when it
+ is shown for the first time because all newly created widgets get a
+ resize event automatically).
+ \i initializeGL() - Sets up the OpenGL rendering context, defines display
+ lists, etc. Gets called once before the first time resizeGL() or
+ paintGL() is called.
+ \endlist
+
+ Here is a rough outline of how a QGLWidget subclass might look:
+
+ \snippet doc/src/snippets/code/src_opengl_qgl.cpp 8
+
+ If you need to trigger a repaint from places other than paintGL()
+ (a typical example is when using \link QTimer timers\endlink to
+ animate scenes), you should call the widget's updateGL() function.
+
+ Your widget's OpenGL rendering context is made current when
+ paintGL(), resizeGL(), or initializeGL() is called. If you need to
+ call the standard OpenGL API functions from other places (e.g. in
+ your widget's constructor or in your own paint functions), you
+ must call makeCurrent() first.
+
+ QGLWidget provides functions for requesting a new display \link
+ QGLFormat format\endlink and you can also create widgets with
+ customized rendering \link QGLContext contexts\endlink.
+
+ You can also share OpenGL display lists between QGLWidget objects (see
+ the documentation of the QGLWidget constructors for details).
+
+ Note that under Windows, the QGLContext belonging to a QGLWidget
+ has to be recreated when the QGLWidget is reparented. This is
+ necessary due to limitations on the Windows platform. This will
+ most likely cause problems for users that have subclassed and
+ installed their own QGLContext on a QGLWidget. It is possible to
+ work around this issue by putting the QGLWidget inside a dummy
+ widget and then reparenting the dummy widget, instead of the
+ QGLWidget. This will side-step the issue altogether, and is what
+ we recommend for users that need this kind of functionality.
+
+ On Mac OS X, when Qt is built with Cocoa support, a QGLWidget
+ can't have any sibling widgets placed ontop of itself. This is due
+ to limitations in the Cocoa API and is not supported by Apple.
+
+ \section1 Overlays
+
+ The QGLWidget creates a GL overlay context in addition to the
+ normal context if overlays are supported by the underlying system.
+
+ If you want to use overlays, you specify it in the \link QGLFormat
+ format\endlink. (Note: Overlay must be requested in the format
+ passed to the QGLWidget constructor.) Your GL widget should also
+ implement some or all of these virtual methods:
+
+ \list
+ \i paintOverlayGL()
+ \i resizeOverlayGL()
+ \i initializeOverlayGL()
+ \endlist
+
+ These methods work in the same way as the normal paintGL() etc.
+ functions, except that they will be called when the overlay
+ context is made current. You can explicitly make the overlay
+ context current by using makeOverlayCurrent(), and you can access
+ the overlay context directly (e.g. to ask for its transparent
+ color) by calling overlayContext().
+
+ On X servers in which the default visual is in an overlay plane,
+ non-GL Qt windows can also be used for overlays.
+
+ \section1 Painting Techniques
+
+ As described above, subclass QGLWidget to render pure 3D content in the
+ following way:
+
+ \list
+ \o Reimplement the QGLWidget::initializeGL() and QGLWidget::resizeGL() to
+ set up the OpenGL state and provide a perspective transformation.
+ \o Reimplement QGLWidget::paintGL() to paint the 3D scene, calling only
+ OpenGL functions to draw on the widget.
+ \endlist
+
+ It is also possible to draw 2D graphics onto a QGLWidget subclass, it is necessary
+ to reimplement QGLWidget::paintEvent() and do the following:
+
+ \list
+ \o Construct a QPainter object.
+ \o Initialize it for use on the widget with the QPainter::begin() function.
+ \o Draw primitives using QPainter's member functions.
+ \o Call QPainter::end() to finish painting.
+ \endlist
+
+ Overpainting 2D content on top of 3D content takes a little more effort.
+ One approach to doing this is shown in the
+ \l{Overpainting Example}{Overpainting} example.
+
+ \section1 Threading
+
+ As of Qt version 4.8, support for doing threaded GL rendering has
+ been improved. There are three scenarios that we currently support:
+ \list
+ \o 1. Buffer swapping in a thread.
+
+ Swapping buffers in a double buffered context may be a
+ synchronous, locking call that may be a costly operation in some
+ GL implementations. Especially so on embedded devices. It's not
+ optimal to have the CPU idling while the GPU is doing a buffer
+ swap. In those cases it is possible to do the rendering in the
+ main thread and do the actual buffer swap in a separate
+ thread. This can be done with the following steps:
+
+ 1. Call doneCurrent() in the main thread when the rendering is
+ finished.
+
+ 2. Notify the swapping thread that it can grab the context.
+
+ 3. Make the rendering context current in the swapping thread with
+ makeCurrent() and then call swapBuffers().
+
+ 4. Call doneCurrent() in the swapping thread and notify the main
+ thread that swapping is done.
+
+ Doing this will free up the main thread so that it can continue
+ with, for example, handling UI events or network requests. Even if
+ there is a context swap involved, it may be preferable compared to
+ having the main thread wait while the GPU finishes the swap
+ operation. Note that this is highly implementation dependent.
+
+ \o 2. Texture uploading in a thread.
+
+ Doing texture uploads in a thread may be very useful for
+ applications handling large amounts of images that needs to be
+ displayed, like for instance a photo gallery application. This is
+ supported in Qt through the existing bindTexture() API. A simple
+ way of doing this is to create two sharing QGLWidgets. One is made
+ current in the main GUI thread, while the other is made current in
+ the texture upload thread. The widget in the uploading thread is
+ never shown, it is only used for sharing textures with the main
+ thread. For each texture that is bound via bindTexture(), notify
+ the main thread so that it can start using the texture.
+
+ \o 3. Using QPainter to draw into a QGLWidget in a thread.
+
+ In Qt 4.8, it is possible to draw into a QGLWidget using a
+ QPainter in a separate thread. Note that this is also possible for
+ QGLPixelBuffers and QGLFramebufferObjects. Since this is only
+ supported in the GL 2 paint engine, OpenGL 2.0 or OpenGL ES 2.0 is
+ required.
+
+ QGLWidgets can only be created in the main GUI thread. This means
+ a call to doneCurrent() is necessary to release the GL context
+ from the main thread, before the widget can be drawn into by
+ another thread. Also, the main GUI thread will dispatch resize and
+ paint events to a QGLWidget when the widget is resized, or parts
+ of it becomes exposed or needs redrawing. It is therefore
+ necessary to handle those events because the default
+ implementations inside QGLWidget will try to make the QGLWidget's
+ context current, which again will interfere with any threads
+ rendering into the widget. Reimplement QGLWidget::paintEvent() and
+ QGLWidget::resizeEvent() to notify the rendering thread that a
+ resize or update is necessary, and be careful not to call the base
+ class implementation. If you are rendering an animation, it might
+ not be necessary to handle the paint event at all since the
+ rendering thread is doing regular updates. Then it would be enough
+ to reimplement QGLWidget::paintEvent() to do nothing.
+
+ \endlist
+
+ As a general rule when doing threaded rendering: be aware that
+ binding and releasing contexts in different threads have to be
+ synchronized by the user. A GL rendering context can only be
+ current in one thread at any time. If you try to open a QPainter
+ on a QGLWidget and the widget's rendering context is current in
+ another thread, it will fail.
+
+ Note that under X11 it is necessary to set the
+ Qt::AA_X11InitThreads application attribute to make the X11
+ library and GLX calls thread safe, otherwise the above scenarios
+ will fail.
+
+ In addition to this, rendering using raw GL calls in a separate
+ thread is supported.
+
+ \e{OpenGL is a trademark of Silicon Graphics, Inc. in the United States and other
+ countries.}
+
+ \sa QGLPixelBuffer, {Hello GL Example}, {2D Painting Example}, {Overpainting Example},
+ {Grabber Example}
+*/
+
+/*!
+ Constructs an OpenGL widget with a \a parent widget.
+
+ The \link QGLFormat::defaultFormat() default format\endlink is
+ used. The widget will be \link isValid() invalid\endlink if the
+ system has no \link QGLFormat::hasOpenGL() OpenGL support\endlink.
+
+ The \a parent and widget flag, \a f, arguments are passed
+ to the QWidget constructor.
+
+ If \a shareWidget is a valid QGLWidget, this widget will share
+ OpenGL display lists and texture objects with \a shareWidget. But
+ if \a shareWidget and this widget have different \l {format()}
+ {formats}, sharing might not be possible. You can check whether
+ sharing is in effect by calling isSharing().
+
+ The initialization of OpenGL rendering state, etc. should be done
+ by overriding the initializeGL() function, rather than in the
+ constructor of your QGLWidget subclass.
+
+ \sa QGLFormat::defaultFormat(), {Textures Example}
+*/
+
+QGLWidget::QGLWidget(QWidget *parent, const QGLWidget* shareWidget, Qt::WindowFlags f)
+ : QWidget(*(new QGLWidgetPrivate), parent, f | Qt::MSWindowsOwnDC)
+{
+ Q_D(QGLWidget);
+ setAttribute(Qt::WA_PaintOnScreen);
+ setAttribute(Qt::WA_NoSystemBackground);
+ setAutoFillBackground(true); // for compatibility
+ d->init(new QGLContext(QGLFormat::defaultFormat(), this), shareWidget);
+}
+
+
+/*!
+ Constructs an OpenGL widget with parent \a parent.
+
+ The \a format argument specifies the desired \link QGLFormat
+ rendering options \endlink. If the underlying OpenGL/Window system
+ cannot satisfy all the features requested in \a format, the
+ nearest subset of features will be used. After creation, the
+ format() method will return the actual format obtained.
+
+ The widget will be \link isValid() invalid\endlink if the system
+ has no \link QGLFormat::hasOpenGL() OpenGL support\endlink.
+
+ The \a parent and widget flag, \a f, arguments are passed
+ to the QWidget constructor.
+
+ If \a shareWidget is a valid QGLWidget, this widget will share
+ OpenGL display lists and texture objects with \a shareWidget. But
+ if \a shareWidget and this widget have different \l {format()}
+ {formats}, sharing might not be possible. You can check whether
+ sharing is in effect by calling isSharing().
+
+ The initialization of OpenGL rendering state, etc. should be done
+ by overriding the initializeGL() function, rather than in the
+ constructor of your QGLWidget subclass.
+
+ \sa QGLFormat::defaultFormat(), isValid()
+*/
+
+QGLWidget::QGLWidget(const QGLFormat &format, QWidget *parent, const QGLWidget* shareWidget,
+ Qt::WindowFlags f)
+ : QWidget(*(new QGLWidgetPrivate), parent, f | Qt::MSWindowsOwnDC)
+{
+ Q_D(QGLWidget);
+ setAttribute(Qt::WA_PaintOnScreen);
+ setAttribute(Qt::WA_NoSystemBackground);
+ setAutoFillBackground(true); // for compatibility
+ d->init(new QGLContext(format, this), shareWidget);
+}
+
+/*!
+ Constructs an OpenGL widget with parent \a parent.
+
+ The \a context argument is a pointer to the QGLContext that
+ you wish to be bound to this widget. This allows you to pass in
+ your own QGLContext sub-classes.
+
+ The widget will be \link isValid() invalid\endlink if the system
+ has no \link QGLFormat::hasOpenGL() OpenGL support\endlink.
+
+ The \a parent and widget flag, \a f, arguments are passed
+ to the QWidget constructor.
+
+ If \a shareWidget is a valid QGLWidget, this widget will share
+ OpenGL display lists and texture objects with \a shareWidget. But
+ if \a shareWidget and this widget have different \l {format()}
+ {formats}, sharing might not be possible. You can check whether
+ sharing is in effect by calling isSharing().
+
+ The initialization of OpenGL rendering state, etc. should be done
+ by overriding the initializeGL() function, rather than in the
+ constructor of your QGLWidget subclass.
+
+ \sa QGLFormat::defaultFormat(), isValid()
+*/
+QGLWidget::QGLWidget(QGLContext *context, QWidget *parent, const QGLWidget *shareWidget,
+ Qt::WindowFlags f)
+ : QWidget(*(new QGLWidgetPrivate), parent, f | Qt::MSWindowsOwnDC)
+{
+ Q_D(QGLWidget);
+ setAttribute(Qt::WA_PaintOnScreen);
+ setAttribute(Qt::WA_NoSystemBackground);
+ setAutoFillBackground(true); // for compatibility
+ d->init(context, shareWidget);
+}
+
+/*!
+ Destroys the widget.
+*/
+
+QGLWidget::~QGLWidget()
+{
+ Q_D(QGLWidget);
+#if defined(GLX_MESA_release_buffers) && defined(QGL_USE_MESA_EXT)
+ bool doRelease = (glcx && glcx->windowCreated());
+#endif
+ delete d->glcx;
+ d->glcx = 0;
+#if defined(Q_WS_WIN)
+ delete d->olcx;
+ d->olcx = 0;
+#endif
+#if defined(GLX_MESA_release_buffers) && defined(QGL_USE_MESA_EXT)
+ if (doRelease)
+ glXReleaseBuffersMESA(x11Display(), winId());
+#endif
+ d->cleanupColormaps();
+
+#ifdef Q_WS_MAC
+ QWidget *current = parentWidget();
+ while (current) {
+ qt_widget_private(current)->glWidgets.removeAll(QWidgetPrivate::GlWidgetInfo(this));
+ if (current->isWindow())
+ break;
+ current = current->parentWidget();
+ };
+#endif
+}
+
+/*!
+ \fn QGLFormat QGLWidget::format() const
+
+ Returns the format of the contained GL rendering context.
+*/
+
+/*!
+ \fn bool QGLWidget::doubleBuffer() const
+
+ Returns true if the contained GL rendering context has double
+ buffering; otherwise returns false.
+
+ \sa QGLFormat::doubleBuffer()
+*/
+
+/*!
+ \fn void QGLWidget::setAutoBufferSwap(bool on)
+
+ If \a on is true automatic GL buffer swapping is switched on;
+ otherwise it is switched off.
+
+ If \a on is true and the widget is using a double-buffered format,
+ the background and foreground GL buffers will automatically be
+ swapped after each paintGL() call.
+
+ The buffer auto-swapping is on by default.
+
+ \sa autoBufferSwap(), doubleBuffer(), swapBuffers()
+*/
+
+/*!
+ \fn bool QGLWidget::autoBufferSwap() const
+
+ Returns true if the widget is doing automatic GL buffer swapping;
+ otherwise returns false.
+
+ \sa setAutoBufferSwap()
+*/
+
+/*!
+ \fn void *QGLContext::getProcAddress(const QString &proc) const
+
+ Returns a function pointer to the GL extension function passed in
+ \a proc. 0 is returned if a pointer to the function could not be
+ obtained.
+*/
+
+/*!
+ \fn bool QGLWidget::isValid() const
+
+ Returns true if the widget has a valid GL rendering context;
+ otherwise returns false. A widget will be invalid if the system
+ has no \link QGLFormat::hasOpenGL() OpenGL support\endlink.
+*/
+
+bool QGLWidget::isValid() const
+{
+ Q_D(const QGLWidget);
+ return d->glcx && d->glcx->isValid();
+}
+
+/*!
+ \fn bool QGLWidget::isSharing() const
+
+ Returns true if this widget's GL context is shared with another GL
+ context, otherwise false is returned. Context sharing might not be
+ possible if the widgets use different formats.
+
+ \sa format()
+*/
+
+bool QGLWidget::isSharing() const
+{
+ Q_D(const QGLWidget);
+ return d->glcx->isSharing();
+}
+
+/*!
+ \fn void QGLWidget::makeCurrent()
+
+ Makes this widget the current widget for OpenGL operations, i.e.
+ makes the widget's rendering context the current OpenGL rendering
+ context.
+*/
+
+void QGLWidget::makeCurrent()
+{
+ Q_D(QGLWidget);
+ d->glcx->makeCurrent();
+}
+
+/*!
+ \fn void QGLWidget::doneCurrent()
+
+ Makes no GL context the current context. Normally, you do not need
+ to call this function; QGLContext calls it as necessary. However,
+ it may be useful in multithreaded environments.
+*/
+
+void QGLWidget::doneCurrent()
+{
+ Q_D(QGLWidget);
+ d->glcx->doneCurrent();
+}
+
+/*!
+ \fn void QGLWidget::swapBuffers()
+
+ Swaps the screen contents with an off-screen buffer. This only
+ works if the widget's format specifies double buffer mode.
+
+ Normally, there is no need to explicitly call this function
+ because it is done automatically after each widget repaint, i.e.
+ each time after paintGL() has been executed.
+
+ \sa doubleBuffer(), setAutoBufferSwap(), QGLFormat::setDoubleBuffer()
+*/
+
+void QGLWidget::swapBuffers()
+{
+ Q_D(QGLWidget);
+ d->glcx->swapBuffers();
+}
+
+
+/*!
+ \fn const QGLContext* QGLWidget::overlayContext() const
+
+ Returns the overlay context of this widget, or 0 if this widget
+ has no overlay.
+
+ \sa context()
+*/
+
+
+
+/*!
+ \fn void QGLWidget::makeOverlayCurrent()
+
+ Makes the overlay context of this widget current. Use this if you
+ need to issue OpenGL commands to the overlay context outside of
+ initializeOverlayGL(), resizeOverlayGL(), and paintOverlayGL().
+
+ Does nothing if this widget has no overlay.
+
+ \sa makeCurrent()
+*/
+
+
+/*!
+ \obsolete
+
+ Sets a new format for this widget.
+
+ If the underlying OpenGL/Window system cannot satisfy all the
+ features requested in \a format, the nearest subset of features will
+ be used. After creation, the format() method will return the actual
+ rendering context format obtained.
+
+ The widget will be assigned a new QGLContext, and the initializeGL()
+ function will be executed for this new context before the first
+ resizeGL() or paintGL().
+
+ This method will try to keep display list and texture object sharing
+ in effect with other QGLWidget objects, but changing the format might make
+ sharing impossible. Use isSharing() to see if sharing is still in
+ effect.
+
+ \sa format(), isSharing(), isValid()
+*/
+
+void QGLWidget::setFormat(const QGLFormat &format)
+{
+ setContext(new QGLContext(format,this));
+}
+
+
+
+
+/*!
+ \fn const QGLContext *QGLWidget::context() const
+
+ Returns the context of this widget.
+
+ It is possible that the context is not valid (see isValid()), for
+ example, if the underlying hardware does not support the format
+ attributes that were requested.
+*/
+
+/*
+ \fn void QGLWidget::setContext(QGLContext *context,
+ const QGLContext* shareContext,
+ bool deleteOldContext)
+ \obsolete
+
+ Sets a new context for this widget. The QGLContext \a context must
+ be created using \e new. QGLWidget will delete \a context when
+ another context is set or when the widget is destroyed.
+
+ If \a context is invalid, QGLContext::create() is performed on
+ it. The initializeGL() function will then be executed for the new
+ context before the first resizeGL() or paintGL().
+
+ If \a context is invalid, this method will try to keep display list
+ and texture object sharing in effect, or (if \a shareContext points
+ to a valid context) start display list and texture object sharing
+ with that context, but sharing might be impossible if the two
+ contexts have different \l {format()} {formats}. Use isSharing() to
+ see whether sharing is in effect.
+
+ If \a deleteOldContext is true (the default), the existing context
+ will be deleted. You may use false here if you have kept a pointer
+ to the old context (as returned by context()), and want to restore
+ that context later.
+
+ \sa context(), isSharing()
+*/
+
+
+
+/*!
+ \fn void QGLWidget::updateGL()
+
+ Updates the widget by calling glDraw().
+*/
+
+void QGLWidget::updateGL()
+{
+ if (updatesEnabled())
+ glDraw();
+}
+
+
+/*!
+ \fn void QGLWidget::updateOverlayGL()
+
+ Updates the widget's overlay (if any). Will cause the virtual
+ function paintOverlayGL() to be executed.
+
+ The widget's rendering context will become the current context and
+ initializeGL() will be called if it hasn't already been called.
+*/
+
+
+/*!
+ This virtual function is called once before the first call to
+ paintGL() or resizeGL(), and then once whenever the widget has
+ been assigned a new QGLContext. Reimplement it in a subclass.
+
+ This function should set up any required OpenGL context rendering
+ flags, defining display lists, etc.
+
+ There is no need to call makeCurrent() because this has already
+ been done when this function is called.
+*/
+
+void QGLWidget::initializeGL()
+{
+}
+
+
+/*!
+ This virtual function is called whenever the widget needs to be
+ painted. Reimplement it in a subclass.
+
+ There is no need to call makeCurrent() because this has already
+ been done when this function is called.
+*/
+
+void QGLWidget::paintGL()
+{
+}
+
+
+/*!
+ \fn void QGLWidget::resizeGL(int width , int height)
+
+ This virtual function is called whenever the widget has been
+ resized. The new size is passed in \a width and \a height.
+ Reimplement it in a subclass.
+
+ There is no need to call makeCurrent() because this has already
+ been done when this function is called.
+*/
+
+void QGLWidget::resizeGL(int, int)
+{
+}
+
+
+
+/*!
+ This virtual function is used in the same manner as initializeGL()
+ except that it operates on the widget's overlay context instead of
+ the widget's main context. This means that initializeOverlayGL()
+ is called once before the first call to paintOverlayGL() or
+ resizeOverlayGL(). Reimplement it in a subclass.
+
+ This function should set up any required OpenGL context rendering
+ flags, defining display lists, etc. for the overlay context.
+
+ There is no need to call makeOverlayCurrent() because this has
+ already been done when this function is called.
+*/
+
+void QGLWidget::initializeOverlayGL()
+{
+}
+
+
+/*!
+ This virtual function is used in the same manner as paintGL()
+ except that it operates on the widget's overlay context instead of
+ the widget's main context. This means that paintOverlayGL() is
+ called whenever the widget's overlay needs to be painted.
+ Reimplement it in a subclass.
+
+ There is no need to call makeOverlayCurrent() because this has
+ already been done when this function is called.
+*/
+
+void QGLWidget::paintOverlayGL()
+{
+}
+
+
+/*!
+ \fn void QGLWidget::resizeOverlayGL(int width , int height)
+
+ This virtual function is used in the same manner as paintGL()
+ except that it operates on the widget's overlay context instead of
+ the widget's main context. This means that resizeOverlayGL() is
+ called whenever the widget has been resized. The new size is
+ passed in \a width and \a height. Reimplement it in a subclass.
+
+ There is no need to call makeOverlayCurrent() because this has
+ already been done when this function is called.
+*/
+
+void QGLWidget::resizeOverlayGL(int, int)
+{
+}
+
+/*! \fn bool QGLWidget::event(QEvent *e)
+ \reimp
+*/
+#if !defined(Q_OS_WINCE) && !defined(Q_WS_QWS) && !defined(Q_WS_QPA)
+bool QGLWidget::event(QEvent *e)
+{
+ Q_D(QGLWidget);
+
+ if (e->type() == QEvent::Paint) {
+ QPoint offset;
+ QPaintDevice *redirectedDevice = d->redirected(&offset);
+ if (redirectedDevice && redirectedDevice->devType() == QInternal::Pixmap) {
+ d->restoreRedirected();
+ QPixmap pixmap = renderPixmap();
+ d->setRedirected(redirectedDevice, offset);
+ QPainter p(redirectedDevice);
+ p.drawPixmap(-offset, pixmap);
+ return true;
+ }
+ }
+
+#if defined(Q_WS_X11)
+ if (e->type() == QEvent::ParentChange) {
+ // if we've reparented a window that has the current context
+ // bound, we need to rebind that context to the new window id
+ if (d->glcx == QGLContext::currentContext())
+ makeCurrent();
+
+ if (d->glcx->d_func()->screen != d->xinfo.screen() || testAttribute(Qt::WA_TranslucentBackground)) {
+ setContext(new QGLContext(d->glcx->requestedFormat(), this));
+ // ### recreating the overlay isn't supported atm
+ }
+ }
+
+#ifndef QT_NO_EGL
+ // A re-parent is likely to destroy the X11 window and re-create it. It is important
+ // that we free the EGL surface _before_ the winID changes - otherwise we can leak.
+ if (e->type() == QEvent::ParentAboutToChange)
+ d->glcx->d_func()->destroyEglSurfaceForDevice();
+
+ if ((e->type() == QEvent::ParentChange) || (e->type() == QEvent::WindowStateChange)) {
+ // The window may have been re-created during re-parent or state change - if so, the EGL
+ // surface will need to be re-created.
+ d->recreateEglSurface();
+ }
+#endif
+#elif defined(Q_WS_WIN)
+ if (e->type() == QEvent::ParentChange) {
+ QGLContext *newContext = new QGLContext(d->glcx->requestedFormat(), this);
+ setContext(newContext, d->glcx);
+
+ // the overlay needs to be recreated as well
+ delete d->olcx;
+ if (isValid() && context()->format().hasOverlay()) {
+ d->olcx = new QGLContext(QGLFormat::defaultOverlayFormat(), this);
+ if (!d->olcx->create(isSharing() ? d->glcx : 0)) {
+ delete d->olcx;
+ d->olcx = 0;
+ d->glcx->d_func()->glFormat.setOverlay(false);
+ }
+ } else {
+ d->olcx = 0;
+ }
+ } else if (e->type() == QEvent::Show) {
+ if (!format().rgba())
+ d->updateColormap();
+ }
+#elif defined(Q_WS_MAC)
+ if (e->type() == QEvent::MacGLWindowChange
+#if 0 //(MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
+ && ((QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5 && isWindow())
+ || QSysInfo::MacintoshVersion <= QSysInfo::MV_10_4)
+#endif
+ ) {
+ if (d->needWindowChange) {
+ d->needWindowChange = false;
+ d->glcx->updatePaintDevice();
+ update();
+ }
+ return true;
+# if defined(QT_MAC_USE_COCOA)
+ } else if (e->type() == QEvent::MacGLClearDrawable) {
+ d->glcx->d_ptr->clearDrawable();
+# endif
+ }
+#elif defined(Q_OS_SYMBIAN)
+ // prevents errors on some systems, where we get a flush to a
+ // hidden widget
+ if (e->type() == QEvent::Hide) {
+ makeCurrent();
+ glFinish();
+ doneCurrent();
+ } else if (e->type() == QEvent::ParentChange) {
+ // if we've reparented a window that has the current context
+ // bound, we need to rebind that context to the new window id
+ if (d->glcx == QGLContext::currentContext())
+ makeCurrent();
+
+ if (testAttribute(Qt::WA_TranslucentBackground))
+ setContext(new QGLContext(d->glcx->requestedFormat(), this));
+ }
+
+ // A re-parent is likely to destroy the Symbian window and re-create it. It is important
+ // that we free the EGL surface _before_ the winID changes - otherwise we can leak.
+ if (e->type() == QEvent::ParentAboutToChange)
+ d->glcx->d_func()->destroyEglSurfaceForDevice();
+
+ if ((e->type() == QEvent::ParentChange) || (e->type() == QEvent::WindowStateChange)) {
+ // The window may have been re-created during re-parent or state change - if so, the EGL
+ // surface will need to be re-created.
+ d->recreateEglSurface();
+ }
+
+#endif
+
+ return QWidget::event(e);
+}
+#endif
+
+/*!
+ \fn void QGLWidget::paintEvent(QPaintEvent *event)
+
+ Handles paint events passed in the \a event parameter. Will cause
+ the virtual paintGL() function to be called.
+
+ The widget's rendering context will become the current context and
+ initializeGL() will be called if it hasn't already been called.
+*/
+
+void QGLWidget::paintEvent(QPaintEvent *)
+{
+ if (updatesEnabled()) {
+ glDraw();
+ updateOverlayGL();
+ }
+}
+
+
+/*!
+ \fn void QGLWidget::resizeEvent(QResizeEvent *event)
+
+ Handles resize events that are passed in the \a event parameter.
+ Calls the virtual function resizeGL().
+*/
+
+
+/*!
+ \fn void QGLWidget::setMouseTracking(bool enable)
+
+ If \a enable is true then mouse tracking is enabled; otherwise it
+ is disabled.
+*/
+
+
+/*!
+ Renders the current scene on a pixmap and returns the pixmap.
+
+ You can use this method on both visible and invisible QGLWidget objects.
+
+ This method will create a pixmap and a temporary QGLContext to
+ render on the pixmap. It will then call initializeGL(),
+ resizeGL(), and paintGL() on this context. Finally, the widget's
+ original GL context is restored.
+
+ The size of the pixmap will be \a w pixels wide and \a h pixels
+ high unless one of these parameters is 0 (the default), in which
+ case the pixmap will have the same size as the widget.
+
+ If \a useContext is true, this method will try to be more
+ efficient by using the existing GL context to render the pixmap.
+ The default is false. Only use true if you understand the risks.
+ Note that under Windows a temporary context has to be created
+ and usage of the \e useContext parameter is not supported.
+
+ Overlays are not rendered onto the pixmap.
+
+ If the GL rendering context and the desktop have different bit
+ depths, the result will most likely look surprising.
+
+ Note that the creation of display lists, modifications of the view
+ frustum etc. should be done from within initializeGL(). If this is
+ not done, the temporary QGLContext will not be initialized
+ properly, and the rendered pixmap may be incomplete/corrupted.
+*/
+
+QPixmap QGLWidget::renderPixmap(int w, int h, bool useContext)
+{
+ Q_D(QGLWidget);
+ QSize sz = size();
+ if ((w > 0) && (h > 0))
+ sz = QSize(w, h);
+
+#if defined(Q_WS_X11)
+ extern int qt_x11_preferred_pixmap_depth;
+ int old_depth = qt_x11_preferred_pixmap_depth;
+ qt_x11_preferred_pixmap_depth = x11Info().depth();
+
+ QPixmapData *data = new QX11PixmapData(QPixmapData::PixmapType);
+ data->resize(sz.width(), sz.height());
+ QPixmap pm(data);
+ qt_x11_preferred_pixmap_depth = old_depth;
+ QX11Info xinfo = x11Info();
+
+ // make sure we use a pixmap with the same depth/visual as the widget
+ if (xinfo.visual() != QX11Info::appVisual()) {
+ QX11InfoData* xd = pm.x11Info().getX11Data(true);
+ xd->depth = xinfo.depth();
+ xd->visual = static_cast<Visual *>(xinfo.visual());
+ const_cast<QX11Info &>(pm.x11Info()).setX11Data(xd);
+ }
+
+#else
+ QPixmap pm(sz);
+#endif
+
+ d->glcx->doneCurrent();
+
+ bool success = true;
+
+ if (useContext && isValid() && d->renderCxPm(&pm))
+ return pm;
+
+ QGLFormat fmt = d->glcx->requestedFormat();
+ fmt.setDirectRendering(false); // Direct is unlikely to work
+ fmt.setDoubleBuffer(false); // We don't need dbl buf
+#ifdef Q_WS_MAC // crash prevention on the Mac - it's unlikely to work anyway
+ fmt.setSampleBuffers(false);
+#endif
+
+ QGLContext* ocx = d->glcx;
+ ocx->doneCurrent();
+ d->glcx = new QGLContext(fmt, &pm);
+ d->glcx->create();
+
+ if (d->glcx->isValid())
+ updateGL();
+ else
+ success = false;
+
+ delete d->glcx;
+ d->glcx = ocx;
+
+ ocx->makeCurrent();
+
+ if (success) {
+#if defined(Q_WS_X11)
+ if (xinfo.visual() != QX11Info::appVisual()) {
+ QImage image = pm.toImage();
+ QPixmap p = QPixmap::fromImage(image);
+ return p;
+ }
+#endif
+ return pm;
+ }
+ return QPixmap();
+}
+
+/*!
+ Returns an image of the frame buffer. If \a withAlpha is true the
+ alpha channel is included.
+
+ Depending on your hardware, you can explicitly select which color
+ buffer to grab with a glReadBuffer() call before calling this
+ function.
+*/
+QImage QGLWidget::grabFrameBuffer(bool withAlpha)
+{
+ makeCurrent();
+ QImage res;
+ int w = width();
+ int h = height();
+ if (format().rgba()) {
+ res = qt_gl_read_framebuffer(QSize(w, h), format().alpha(), withAlpha);
+ } else {
+#if defined (Q_WS_WIN) && !defined(QT_OPENGL_ES)
+ res = QImage(w, h, QImage::Format_Indexed8);
+ glReadPixels(0, 0, w, h, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, res.bits());
+ const QVector<QColor> pal = QColormap::instance().colormap();
+ if (pal.size()) {
+ res.setColorCount(pal.size());
+ for (int i = 0; i < pal.size(); i++)
+ res.setColor(i, pal.at(i).rgb());
+ }
+ res = res.mirrored();
+#endif
+ }
+
+ return res;
+}
+
+
+
+/*!
+ Initializes OpenGL for this widget's context. Calls the virtual
+ function initializeGL().
+*/
+
+void QGLWidget::glInit()
+{
+ Q_D(QGLWidget);
+ if (!isValid())
+ return;
+ makeCurrent();
+ initializeGL();
+ d->glcx->setInitialized(true);
+}
+
+
+/*!
+ Executes the virtual function paintGL().
+
+ The widget's rendering context will become the current context and
+ initializeGL() will be called if it hasn't already been called.
+*/
+
+void QGLWidget::glDraw()
+{
+ Q_D(QGLWidget);
+ if (!isValid())
+ return;
+#ifdef Q_OS_SYMBIAN
+ // Crashes on Symbian if trying to render to invisible surfaces
+ if (!isVisible() && d->glcx->device()->devType() == QInternal::Widget)
+ return;
+#endif
+ makeCurrent();
+#ifndef QT_OPENGL_ES
+ if (d->glcx->deviceIsPixmap())
+ glDrawBuffer(GL_FRONT);
+#endif
+ if (!d->glcx->initialized()) {
+ glInit();
+ resizeGL(d->glcx->device()->width(), d->glcx->device()->height()); // New context needs this "resize"
+ }
+ paintGL();
+ if (doubleBuffer()) {
+ if (d->autoSwap)
+ swapBuffers();
+ } else {
+ glFlush();
+ }
+}
+
+/*!
+ Convenience function for specifying a drawing color to OpenGL.
+ Calls glColor4 (in RGBA mode) or glIndex (in color-index mode)
+ with the color \a c. Applies to this widgets GL context.
+
+ \note This function is not supported on OpenGL/ES 2.0 systems.
+
+ \sa qglClearColor(), QGLContext::currentContext(), QColor
+*/
+
+void QGLWidget::qglColor(const QColor& c) const
+{
+#if !defined(QT_OPENGL_ES_2)
+#ifdef QT_OPENGL_ES
+ glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF());
+#else
+ Q_D(const QGLWidget);
+ const QGLContext *ctx = QGLContext::currentContext();
+ if (ctx) {
+ if (ctx->format().rgba())
+ glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF());
+ else if (!d->cmap.isEmpty()) { // QGLColormap in use?
+ int i = d->cmap.find(c.rgb());
+ if (i < 0)
+ i = d->cmap.findNearest(c.rgb());
+ glIndexi(i);
+ } else
+ glIndexi(ctx->colorIndex(c));
+ }
+#endif //QT_OPENGL_ES
+#else
+ Q_UNUSED(c);
+#endif //QT_OPENGL_ES_2
+}
+
+/*!
+ Convenience function for specifying the clearing color to OpenGL.
+ Calls glClearColor (in RGBA mode) or glClearIndex (in color-index
+ mode) with the color \a c. Applies to this widgets GL context.
+
+ \sa qglColor(), QGLContext::currentContext(), QColor
+*/
+
+void QGLWidget::qglClearColor(const QColor& c) const
+{
+#ifdef QT_OPENGL_ES
+ glClearColor(c.redF(), c.greenF(), c.blueF(), c.alphaF());
+#else
+ Q_D(const QGLWidget);
+ const QGLContext *ctx = QGLContext::currentContext();
+ if (ctx) {
+ if (ctx->format().rgba())
+ glClearColor(c.redF(), c.greenF(), c.blueF(), c.alphaF());
+ else if (!d->cmap.isEmpty()) { // QGLColormap in use?
+ int i = d->cmap.find(c.rgb());
+ if (i < 0)
+ i = d->cmap.findNearest(c.rgb());
+ glClearIndex(i);
+ } else
+ glClearIndex(ctx->colorIndex(c));
+ }
+#endif
+}
+
+
+/*!
+ Converts the image \a img into the unnamed format expected by
+ OpenGL functions such as glTexImage2D(). The returned image is not
+ usable as a QImage, but QImage::width(), QImage::height() and
+ QImage::bits() may be used with OpenGL. The GL format used is
+ \c GL_RGBA.
+
+ \omit ###
+
+ \l opengl/texture example
+ The following few lines are from the texture example. Most of the
+ code is irrelevant, so we just quote the relevant bits:
+
+ \quotefromfile opengl/texture/gltexobj.cpp
+ \skipto tex1
+ \printline tex1
+ \printline gllogo.bmp
+
+ We create \e tex1 (and another variable) for OpenGL, and load a real
+ image into \e buf.
+
+ \skipto convertToGLFormat
+ \printline convertToGLFormat
+
+ A few lines later, we convert \e buf into OpenGL format and store it
+ in \e tex1.
+
+ \skipto glTexImage2D
+ \printline glTexImage2D
+ \printline tex1.bits
+
+ Note the dimension restrictions for texture images as described in
+ the glTexImage2D() documentation. The width must be 2^m + 2*border
+ and the height 2^n + 2*border where m and n are integers and
+ border is either 0 or 1.
+
+ Another function in the same example uses \e tex1 with OpenGL.
+
+ \endomit
+*/
+
+QImage QGLWidget::convertToGLFormat(const QImage& img)
+{
+ QImage res(img.size(), QImage::Format_ARGB32);
+ convertToGLFormatHelper(res, img.convertToFormat(QImage::Format_ARGB32), GL_RGBA);
+ return res;
+}
+
+
+/*!
+ \fn QGLColormap & QGLWidget::colormap() const
+
+ Returns the colormap for this widget.
+
+ Usually it is only top-level widgets that can have different
+ colormaps installed. Asking for the colormap of a child widget
+ will return the colormap for the child's top-level widget.
+
+ If no colormap has been set for this widget, the QGLColormap
+ returned will be empty.
+
+ \sa setColormap(), QGLColormap::isEmpty()
+*/
+
+/*!
+ \fn void QGLWidget::setColormap(const QGLColormap & cmap)
+
+ Set the colormap for this widget to \a cmap. Usually it is only
+ top-level widgets that can have colormaps installed.
+
+ \sa colormap()
+*/
+
+
+/*!
+ \obsolete
+
+ Returns the value of the first display list that is generated for
+ the characters in the given \a font. \a listBase indicates the base
+ value used when generating the display lists for the font. The
+ default value is 2000.
+
+ \note This function is not supported on OpenGL/ES systems.
+*/
+int QGLWidget::fontDisplayListBase(const QFont & font, int listBase)
+{
+#ifndef QT_OPENGL_ES
+ Q_D(QGLWidget);
+ int base;
+
+ if (!d->glcx) { // this can't happen unless we run out of mem
+ return 0;
+ }
+
+ // always regenerate font disp. lists for pixmaps - hw accelerated
+ // contexts can't handle this otherwise
+ bool regenerate = d->glcx->deviceIsPixmap();
+#ifndef QT_NO_FONTCONFIG
+ // font color needs to be part of the font cache key when using
+ // antialiased fonts since one set of glyphs needs to be generated
+ // for each font color
+ QString color_key;
+ if (font.styleStrategy() != QFont::NoAntialias) {
+ GLfloat color[4];
+ glGetFloatv(GL_CURRENT_COLOR, color);
+ color_key.sprintf("%f_%f_%f",color[0], color[1], color[2]);
+ }
+ QString key = font.key() + color_key + QString::number((int) regenerate);
+#else
+ QString key = font.key() + QString::number((int) regenerate);
+#endif
+ if (!regenerate && (d->displayListCache.find(key) != d->displayListCache.end())) {
+ base = d->displayListCache[key];
+ } else {
+ int maxBase = listBase - 256;
+ QMap<QString,int>::ConstIterator it;
+ for (it = d->displayListCache.constBegin(); it != d->displayListCache.constEnd(); ++it) {
+ if (maxBase < it.value()) {
+ maxBase = it.value();
+ }
+ }
+ maxBase += 256;
+ d->glcx->generateFontDisplayLists(font, maxBase);
+ d->displayListCache[key] = maxBase;
+ base = maxBase;
+ }
+ return base;
+#else // QT_OPENGL_ES
+ Q_UNUSED(font);
+ Q_UNUSED(listBase);
+ return 0;
+#endif
+}
+
+#ifndef QT_OPENGL_ES
+
+static void qt_save_gl_state()
+{
+ glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
+ glPushAttrib(GL_ALL_ATTRIB_BITS);
+ glMatrixMode(GL_TEXTURE);
+ glPushMatrix();
+ glLoadIdentity();
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+
+ glShadeModel(GL_FLAT);
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_LIGHTING);
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+static void qt_restore_gl_state()
+{
+ glMatrixMode(GL_TEXTURE);
+ glPopMatrix();
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+ glPopAttrib();
+ glPopClientAttrib();
+}
+
+static void qt_gl_draw_text(QPainter *p, int x, int y, const QString &str,
+ const QFont &font)
+{
+ GLfloat color[4];
+ glGetFloatv(GL_CURRENT_COLOR, &color[0]);
+
+ QColor col;
+ col.setRgbF(color[0], color[1], color[2],color[3]);
+ QPen old_pen = p->pen();
+ QFont old_font = p->font();
+
+ p->setPen(col);
+ p->setFont(font);
+ p->drawText(x, y, str);
+
+ p->setPen(old_pen);
+ p->setFont(old_font);
+}
+
+#endif // !QT_OPENGL_ES
+
+/*!
+ Renders the string \a str into the GL context of this widget.
+
+ \a x and \a y are specified in window coordinates, with the origin
+ in the upper left-hand corner of the window. If \a font is not
+ specified, the currently set application font will be used to
+ render the string. To change the color of the rendered text you can
+ use the glColor() call (or the qglColor() convenience function),
+ just before the renderText() call.
+
+ The \a listBase parameter is obsolete and will be removed in a
+ future version of Qt.
+
+ \note This function clears the stencil buffer.
+
+ \note This function is not supported on OpenGL/ES systems.
+
+ \note This function temporarily disables depth-testing when the
+ text is drawn.
+
+ \note This function can only be used inside a
+ QPainter::beginNativePainting()/QPainter::endNativePainting() block
+ if the default OpenGL paint engine is QPaintEngine::OpenGL. To make
+ QPaintEngine::OpenGL the default GL engine, call
+ QGL::setPreferredPaintEngine(QPaintEngine::OpenGL) before the
+ QApplication constructor.
+
+ \l{Overpainting Example}{Overpaint} with QPainter::drawText() instead.
+*/
+
+void QGLWidget::renderText(int x, int y, const QString &str, const QFont &font, int)
+{
+#ifndef QT_OPENGL_ES
+ Q_D(QGLWidget);
+ if (str.isEmpty() || !isValid())
+ return;
+
+ GLint view[4];
+ bool use_scissor_testing = glIsEnabled(GL_SCISSOR_TEST);
+ if (!use_scissor_testing)
+ glGetIntegerv(GL_VIEWPORT, &view[0]);
+ int width = d->glcx->device()->width();
+ int height = d->glcx->device()->height();
+ bool auto_swap = autoBufferSwap();
+
+ QPaintEngine::Type oldEngineType = qgl_engine_selector()->preferredPaintEngine();
+
+ QPaintEngine *engine = paintEngine();
+ if (engine && (oldEngineType == QPaintEngine::OpenGL2) && engine->isActive()) {
+ qWarning("QGLWidget::renderText(): Calling renderText() while a GL 2 paint engine is"
+ " active on the same device is not allowed.");
+ return;
+ }
+
+ // this changes what paintEngine() returns
+ qgl_engine_selector()->setPreferredPaintEngine(QPaintEngine::OpenGL);
+ engine = paintEngine();
+ QPainter *p;
+ bool reuse_painter = false;
+ if (engine->isActive()) {
+ reuse_painter = true;
+ p = engine->painter();
+ qt_save_gl_state();
+
+ glDisable(GL_DEPTH_TEST);
+ glViewport(0, 0, width, height);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, width, height, 0, 0, 1);
+ glMatrixMode(GL_MODELVIEW);
+
+ glLoadIdentity();
+ } else {
+ setAutoBufferSwap(false);
+ // disable glClear() as a result of QPainter::begin()
+ d->disable_clear_on_painter_begin = true;
+ p = new QPainter(this);
+ }
+
+ QRect viewport(view[0], view[1], view[2], view[3]);
+ if (!use_scissor_testing && viewport != rect()) {
+ // if the user hasn't set a scissor box, we set one that
+ // covers the current viewport
+ glScissor(view[0], view[1], view[2], view[3]);
+ glEnable(GL_SCISSOR_TEST);
+ } else if (use_scissor_testing) {
+ // use the scissor box set by the user
+ glEnable(GL_SCISSOR_TEST);
+ }
+
+ qt_gl_draw_text(p, x, y, str, font);
+
+ if (reuse_painter) {
+ qt_restore_gl_state();
+ } else {
+ p->end();
+ delete p;
+ setAutoBufferSwap(auto_swap);
+ d->disable_clear_on_painter_begin = false;
+ }
+ qgl_engine_selector()->setPreferredPaintEngine(oldEngineType);
+#else // QT_OPENGL_ES
+ Q_UNUSED(x);
+ Q_UNUSED(y);
+ Q_UNUSED(str);
+ Q_UNUSED(font);
+ qWarning("QGLWidget::renderText is not supported under OpenGL/ES");
+#endif
+}
+
+/*! \overload
+
+ \a x, \a y and \a z are specified in scene or object coordinates
+ relative to the currently set projection and model matrices. This
+ can be useful if you want to annotate models with text labels and
+ have the labels move with the model as it is rotated etc.
+
+ \note This function is not supported on OpenGL/ES systems.
+
+ \note If depth testing is enabled before this function is called,
+ then the drawn text will be depth-tested against the models that
+ have already been drawn in the scene. Use \c{glDisable(GL_DEPTH_TEST)}
+ before calling this function to annotate the models without
+ depth-testing the text.
+
+ \l{Overpainting Example}{Overpaint} with QPainter::drawText() instead.
+*/
+void QGLWidget::renderText(double x, double y, double z, const QString &str, const QFont &font, int)
+{
+#ifndef QT_OPENGL_ES
+ Q_D(QGLWidget);
+ if (str.isEmpty() || !isValid())
+ return;
+
+ bool auto_swap = autoBufferSwap();
+
+ int width = d->glcx->device()->width();
+ int height = d->glcx->device()->height();
+ GLdouble model[4][4], proj[4][4];
+ GLint view[4];
+ glGetDoublev(GL_MODELVIEW_MATRIX, &model[0][0]);
+ glGetDoublev(GL_PROJECTION_MATRIX, &proj[0][0]);
+ glGetIntegerv(GL_VIEWPORT, &view[0]);
+ GLdouble win_x = 0, win_y = 0, win_z = 0;
+ qgluProject(x, y, z, &model[0][0], &proj[0][0], &view[0],
+ &win_x, &win_y, &win_z);
+ win_y = height - win_y; // y is inverted
+
+ QPaintEngine::Type oldEngineType = qgl_engine_selector()->preferredPaintEngine();
+ QPaintEngine *engine = paintEngine();
+
+ if (engine && (oldEngineType == QPaintEngine::OpenGL2) && engine->isActive()) {
+ qWarning("QGLWidget::renderText(): Calling renderText() while a GL 2 paint engine is"
+ " active on the same device is not allowed.");
+ return;
+ }
+
+ // this changes what paintEngine() returns
+ qgl_engine_selector()->setPreferredPaintEngine(QPaintEngine::OpenGL);
+ engine = paintEngine();
+ QPainter *p;
+ bool reuse_painter = false;
+ bool use_depth_testing = glIsEnabled(GL_DEPTH_TEST);
+ bool use_scissor_testing = glIsEnabled(GL_SCISSOR_TEST);
+
+ if (engine->isActive()) {
+ reuse_painter = true;
+ p = engine->painter();
+ qt_save_gl_state();
+ } else {
+ setAutoBufferSwap(false);
+ // disable glClear() as a result of QPainter::begin()
+ d->disable_clear_on_painter_begin = true;
+ p = new QPainter(this);
+ }
+
+ QRect viewport(view[0], view[1], view[2], view[3]);
+ if (!use_scissor_testing && viewport != rect()) {
+ glScissor(view[0], view[1], view[2], view[3]);
+ glEnable(GL_SCISSOR_TEST);
+ } else if (use_scissor_testing) {
+ glEnable(GL_SCISSOR_TEST);
+ }
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glViewport(0, 0, width, height);
+ glOrtho(0, width, height, 0, 0, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glAlphaFunc(GL_GREATER, 0.0);
+ glEnable(GL_ALPHA_TEST);
+ if (use_depth_testing)
+ glEnable(GL_DEPTH_TEST);
+ glTranslated(0, 0, -win_z);
+ qt_gl_draw_text(p, qRound(win_x), qRound(win_y), str, font);
+
+ if (reuse_painter) {
+ qt_restore_gl_state();
+ } else {
+ p->end();
+ delete p;
+ setAutoBufferSwap(auto_swap);
+ d->disable_clear_on_painter_begin = false;
+ }
+ qgl_engine_selector()->setPreferredPaintEngine(oldEngineType);
+#else // QT_OPENGL_ES
+ Q_UNUSED(x);
+ Q_UNUSED(y);
+ Q_UNUSED(z);
+ Q_UNUSED(str);
+ Q_UNUSED(font);
+ qWarning("QGLWidget::renderText is not supported under OpenGL/ES");
+#endif
+}
+
+QGLFormat QGLWidget::format() const
+{
+ Q_D(const QGLWidget);
+ return d->glcx->format();
+}
+
+const QGLContext *QGLWidget::context() const
+{
+ Q_D(const QGLWidget);
+ return d->glcx;
+}
+
+bool QGLWidget::doubleBuffer() const
+{
+ Q_D(const QGLWidget);
+ return d->glcx->d_ptr->glFormat.testOption(QGL::DoubleBuffer);
+}
+
+void QGLWidget::setAutoBufferSwap(bool on)
+{
+ Q_D(QGLWidget);
+ d->autoSwap = on;
+}
+
+bool QGLWidget::autoBufferSwap() const
+{
+ Q_D(const QGLWidget);
+ return d->autoSwap;
+}
+
+/*!
+ Calls QGLContext:::bindTexture(\a image, \a target, \a format) on the currently
+ set context.
+
+ \sa deleteTexture()
+*/
+GLuint QGLWidget::bindTexture(const QImage &image, GLenum target, GLint format)
+{
+ if (image.isNull())
+ return 0;
+
+ Q_D(QGLWidget);
+ return d->glcx->bindTexture(image, target, format, QGLContext::DefaultBindOption);
+}
+
+/*!
+ \overload
+ \since 4.6
+
+ The binding \a options are a set of options used to decide how to
+ bind the texture to the context.
+ */
+GLuint QGLWidget::bindTexture(const QImage &image, GLenum target, GLint format, QGLContext::BindOptions options)
+{
+ if (image.isNull())
+ return 0;
+
+ Q_D(QGLWidget);
+ return d->glcx->bindTexture(image, target, format, options);
+}
+
+
+#ifdef Q_MAC_COMPAT_GL_FUNCTIONS
+/*! \internal */
+GLuint QGLWidget::bindTexture(const QImage &image, QMacCompatGLenum target, QMacCompatGLint format)
+{
+ if (image.isNull())
+ return 0;
+
+ Q_D(QGLWidget);
+ return d->glcx->bindTexture(image, GLenum(target), GLint(format), QGLContext::DefaultBindOption);
+}
+
+GLuint QGLWidget::bindTexture(const QImage &image, QMacCompatGLenum target, QMacCompatGLint format,
+ QGLContext::BindOptions options)
+{
+ if (image.isNull())
+ return 0;
+
+ Q_D(QGLWidget);
+ return d->glcx->bindTexture(image, GLenum(target), GLint(format), options);
+}
+#endif
+
+/*!
+ Calls QGLContext:::bindTexture(\a pixmap, \a target, \a format) on the currently
+ set context.
+
+ \sa deleteTexture()
+*/
+GLuint QGLWidget::bindTexture(const QPixmap &pixmap, GLenum target, GLint format)
+{
+ if (pixmap.isNull())
+ return 0;
+
+ Q_D(QGLWidget);
+ return d->glcx->bindTexture(pixmap, target, format, QGLContext::DefaultBindOption);
+}
+
+/*!
+ \overload
+ \since 4.6
+
+ Generates and binds a 2D GL texture to the current context, based
+ on \a pixmap. The generated texture id is returned and can be used in
+
+ The binding \a options are a set of options used to decide how to
+ bind the texture to the context.
+ */
+GLuint QGLWidget::bindTexture(const QPixmap &pixmap, GLenum target, GLint format,
+ QGLContext::BindOptions options)
+{
+ Q_D(QGLWidget);
+ return d->glcx->bindTexture(pixmap, target, format, options);
+}
+
+#ifdef Q_MAC_COMPAT_GL_FUNCTIONS
+/*! \internal */
+GLuint QGLWidget::bindTexture(const QPixmap &pixmap, QMacCompatGLenum target, QMacCompatGLint format)
+{
+ Q_D(QGLWidget);
+ return d->glcx->bindTexture(pixmap, target, format, QGLContext::DefaultBindOption);
+}
+
+GLuint QGLWidget::bindTexture(const QPixmap &pixmap, QMacCompatGLenum target, QMacCompatGLint format,
+ QGLContext::BindOptions options)
+{
+ Q_D(QGLWidget);
+ return d->glcx->bindTexture(pixmap, target, format, options);
+}
+#endif
+
+
+/*! \overload
+
+ Calls QGLContext::bindTexture(\a fileName) on the currently set context.
+
+ \sa deleteTexture()
+*/
+GLuint QGLWidget::bindTexture(const QString &fileName)
+{
+ Q_D(QGLWidget);
+ return d->glcx->bindTexture(fileName);
+}
+
+/*!
+ Calls QGLContext::deleteTexture(\a id) on the currently set
+ context.
+
+ \sa bindTexture()
+*/
+void QGLWidget::deleteTexture(GLuint id)
+{
+ Q_D(QGLWidget);
+ d->glcx->deleteTexture(id);
+}
+
+#ifdef Q_MAC_COMPAT_GL_FUNCTIONS
+/*! \internal */
+void QGLWidget::deleteTexture(QMacCompatGLuint id)
+{
+ Q_D(QGLWidget);
+ d->glcx->deleteTexture(GLuint(id));
+}
+#endif
+
+/*!
+ \since 4.4
+
+ Calls the corresponding QGLContext::drawTexture() with
+ \a target, \a textureId, and \a textureTarget for this
+ widget's context.
+*/
+void QGLWidget::drawTexture(const QRectF &target, GLuint textureId, GLenum textureTarget)
+{
+ Q_D(QGLWidget);
+ d->glcx->drawTexture(target, textureId, textureTarget);
+}
+
+#ifdef Q_MAC_COMPAT_GL_FUNCTIONS
+/*! \internal */
+void QGLWidget::drawTexture(const QRectF &target, QMacCompatGLuint textureId, QMacCompatGLenum textureTarget)
+{
+ Q_D(QGLWidget);
+ d->glcx->drawTexture(target, GLint(textureId), GLenum(textureTarget));
+}
+#endif
+
+/*!
+ \since 4.4
+
+ Calls the corresponding QGLContext::drawTexture() with
+ \a point, \a textureId, and \a textureTarget for this
+ widget's context.
+*/
+void QGLWidget::drawTexture(const QPointF &point, GLuint textureId, GLenum textureTarget)
+{
+ Q_D(QGLWidget);
+ d->glcx->drawTexture(point, textureId, textureTarget);
+}
+
+#ifdef Q_MAC_COMPAT_GL_FUNCTIONS
+/*! \internal */
+void QGLWidget::drawTexture(const QPointF &point, QMacCompatGLuint textureId, QMacCompatGLenum textureTarget)
+{
+ Q_D(QGLWidget);
+ d->glcx->drawTexture(point, GLuint(textureId), GLenum(textureTarget));
+}
+#endif
+
+#ifndef QT_OPENGL_ES_1
+Q_GLOBAL_STATIC(QGLEngineThreadStorage<QGL2PaintEngineEx>, qt_gl_2_engine)
+#endif
+
+#ifndef QT_OPENGL_ES_2
+Q_GLOBAL_STATIC(QGLEngineThreadStorage<QOpenGLPaintEngine>, qt_gl_engine)
+#endif
+
+Q_OPENGL_EXPORT QPaintEngine* qt_qgl_paint_engine()
+{
+#if defined(QT_OPENGL_ES_1)
+ return qt_gl_engine()->engine();
+#elif defined(QT_OPENGL_ES_2)
+ return qt_gl_2_engine()->engine();
+#else
+ if (qt_gl_preferGL2Engine())
+ return qt_gl_2_engine()->engine();
+ else
+ return qt_gl_engine()->engine();
+#endif
+}
+
+/*!
+ \internal
+
+ Returns the GL widget's paint engine. This is normally a
+ QOpenGLPaintEngine.
+*/
+QPaintEngine *QGLWidget::paintEngine() const
+{
+ return qt_qgl_paint_engine();
+}
+
+#ifdef QT3_SUPPORT
+/*!
+ \overload
+ \obsolete
+ */
+QGLWidget::QGLWidget(QWidget *parent, const char *name,
+ const QGLWidget* shareWidget, Qt::WindowFlags f)
+ : QWidget(*(new QGLWidgetPrivate), parent, f | Qt::MSWindowsOwnDC)
+{
+ Q_D(QGLWidget);
+ if (name)
+ setObjectName(QString::fromAscii(name));
+ setAttribute(Qt::WA_PaintOnScreen);
+ setAttribute(Qt::WA_NoSystemBackground);
+ setAutoFillBackground(true); // for compatibility
+ d->init(new QGLContext(QGLFormat::defaultFormat(), this), shareWidget);
+}
+
+/*!
+ \overload
+ \obsolete
+ */
+QGLWidget::QGLWidget(const QGLFormat &format, QWidget *parent,
+ const char *name, const QGLWidget* shareWidget,
+ Qt::WindowFlags f)
+ : QWidget(*(new QGLWidgetPrivate), parent, f | Qt::MSWindowsOwnDC)
+{
+ Q_D(QGLWidget);
+ if (name)
+ setObjectName(QString::fromAscii(name));
+ setAttribute(Qt::WA_PaintOnScreen);
+ setAttribute(Qt::WA_NoSystemBackground);
+ setAutoFillBackground(true); // for compatibility
+ d->init(new QGLContext(format, this), shareWidget);
+}
+
+/*!
+ \overload
+ \obsolete
+ */
+QGLWidget::QGLWidget(QGLContext *context, QWidget *parent,
+ const char *name, const QGLWidget *shareWidget, Qt::WindowFlags f)
+ : QWidget(*(new QGLWidgetPrivate), parent, f | Qt::MSWindowsOwnDC)
+{
+ Q_D(QGLWidget);
+ if (name)
+ setObjectName(QString::fromAscii(name));
+ setAttribute(Qt::WA_PaintOnScreen);
+ setAttribute(Qt::WA_NoSystemBackground);
+ setAutoFillBackground(true); // for compatibility
+ d->init(context, shareWidget);
+}
+
+#endif // QT3_SUPPORT
+
+typedef GLubyte * (*qt_glGetStringi)(GLenum, GLuint);
+
+#ifndef GL_NUM_EXTENSIONS
+#define GL_NUM_EXTENSIONS 0x821D
+#endif
+
+QGLExtensionMatcher::QGLExtensionMatcher(const char *str)
+{
+ init(str);
+}
+
+QGLExtensionMatcher::QGLExtensionMatcher()
+{
+ const char *extensionStr = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
+
+ if (extensionStr) {
+ init(extensionStr);
+ } else {
+ // clear error state
+ while (glGetError()) {}
+
+ const QGLContext *ctx = QGLContext::currentContext();
+ if (ctx) {
+ qt_glGetStringi glGetStringi = (qt_glGetStringi)ctx->getProcAddress(QLatin1String("glGetStringi"));
+
+ GLint numExtensions;
+ glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
+
+ for (int i = 0; i < numExtensions; ++i) {
+ const char *str = reinterpret_cast<const char *>(glGetStringi(GL_EXTENSIONS, i));
+
+ m_offsets << m_extensions.size();
+
+ while (*str != 0)
+ m_extensions.append(*str++);
+ m_extensions.append(' ');
+ }
+ }
+ }
+}
+
+void QGLExtensionMatcher::init(const char *str)
+{
+ m_extensions = str;
+
+ // make sure extension string ends with a space
+ if (!m_extensions.endsWith(' '))
+ m_extensions.append(' ');
+
+ int index = 0;
+ int next = 0;
+ while ((next = m_extensions.indexOf(' ', index)) >= 0) {
+ m_offsets << index;
+ index = next + 1;
+ }
+}
+
+/*
+ Returns the GL extensions for the current context.
+*/
+QGLExtensions::Extensions QGLExtensions::currentContextExtensions()
+{
+ QGLExtensionMatcher extensions;
+ Extensions glExtensions;
+
+ if (extensions.match("GL_ARB_texture_rectangle"))
+ glExtensions |= TextureRectangle;
+ if (extensions.match("GL_ARB_multisample"))
+ glExtensions |= SampleBuffers;
+ if (extensions.match("GL_SGIS_generate_mipmap"))
+ glExtensions |= GenerateMipmap;
+ if (extensions.match("GL_ARB_texture_compression"))
+ glExtensions |= TextureCompression;
+ if (extensions.match("GL_EXT_texture_compression_s3tc"))
+ glExtensions |= DDSTextureCompression;
+ if (extensions.match("GL_OES_compressed_ETC1_RGB8_texture"))
+ glExtensions |= ETC1TextureCompression;
+ if (extensions.match("GL_IMG_texture_compression_pvrtc"))
+ glExtensions |= PVRTCTextureCompression;
+ if (extensions.match("GL_ARB_fragment_program"))
+ glExtensions |= FragmentProgram;
+ if (extensions.match("GL_ARB_fragment_shader"))
+ glExtensions |= FragmentShader;
+ if (extensions.match("GL_ARB_shader_objects"))
+ glExtensions |= FragmentShader;
+ if (extensions.match("GL_ARB_texture_mirrored_repeat"))
+ glExtensions |= MirroredRepeat;
+ if (extensions.match("GL_EXT_framebuffer_object"))
+ glExtensions |= FramebufferObject;
+ if (extensions.match("GL_EXT_stencil_two_side"))
+ glExtensions |= StencilTwoSide;
+ if (extensions.match("GL_EXT_stencil_wrap"))
+ glExtensions |= StencilWrap;
+ if (extensions.match("GL_EXT_packed_depth_stencil"))
+ glExtensions |= PackedDepthStencil;
+ if (extensions.match("GL_NV_float_buffer"))
+ glExtensions |= NVFloatBuffer;
+ if (extensions.match("GL_ARB_pixel_buffer_object"))
+ glExtensions |= PixelBufferObject;
+ if (extensions.match("GL_IMG_texture_format_BGRA8888"))
+ glExtensions |= BGRATextureFormat;
+#if defined(QT_OPENGL_ES_2)
+ glExtensions |= FramebufferObject;
+ glExtensions |= GenerateMipmap;
+ glExtensions |= FragmentShader;
+#endif
+#if defined(QT_OPENGL_ES_1)
+ if (extensions.match("GL_OES_framebuffer_object"))
+ glExtensions |= FramebufferObject;
+#endif
+#if defined(QT_OPENGL_ES)
+ if (extensions.match("GL_OES_packed_depth_stencil"))
+ glExtensions |= PackedDepthStencil;
+ if (extensions.match("GL_OES_element_index_uint"))
+ glExtensions |= ElementIndexUint;
+ if (extensions.match("GL_OES_depth24"))
+ glExtensions |= Depth24;
+#else
+ glExtensions |= ElementIndexUint;
+#endif
+ if (extensions.match("GL_ARB_framebuffer_object")) {
+ // ARB_framebuffer_object also includes EXT_framebuffer_blit.
+ glExtensions |= FramebufferObject;
+ glExtensions |= FramebufferBlit;
+ }
+
+ if (extensions.match("GL_EXT_framebuffer_blit"))
+ glExtensions |= FramebufferBlit;
+
+ if (extensions.match("GL_ARB_texture_non_power_of_two"))
+ glExtensions |= NPOTTextures;
+
+ if (extensions.match("GL_EXT_bgra"))
+ glExtensions |= BGRATextureFormat;
+
+ return glExtensions;
+}
+
+
+class QGLDefaultExtensions
+{
+public:
+ QGLDefaultExtensions() {
+ QGLTemporaryContext tempContext;
+ extensions = QGLExtensions::currentContextExtensions();
+ }
+
+ QGLExtensions::Extensions extensions;
+};
+
+Q_GLOBAL_STATIC(QGLDefaultExtensions, qtDefaultExtensions)
+
+/*
+ Returns the GL extensions for the current QGLContext. If there is no
+ current QGLContext, a default context will be created and the extensions
+ for that context will be returned instead.
+*/
+QGLExtensions::Extensions QGLExtensions::glExtensions()
+{
+ Extensions extensionFlags = 0;
+ QGLContext *currentCtx = const_cast<QGLContext *>(QGLContext::currentContext());
+
+ if (currentCtx && currentCtx->d_func()->extension_flags_cached)
+ return currentCtx->d_func()->extension_flags;
+
+ if (!currentCtx) {
+ extensionFlags = qtDefaultExtensions()->extensions;
+ } else {
+ extensionFlags = currentContextExtensions();
+ currentCtx->d_func()->extension_flags_cached = true;
+ currentCtx->d_func()->extension_flags = extensionFlags;
+ }
+ return extensionFlags;
+}
+
+/*
+ This is the shared initialization for all platforms. Called from QGLWidgetPrivate::init()
+*/
+void QGLWidgetPrivate::initContext(QGLContext *context, const QGLWidget* shareWidget)
+{
+ Q_Q(QGLWidget);
+
+ glDevice.setWidget(q);
+
+ glcx = 0;
+ autoSwap = true;
+
+ if (context && !context->device())
+ context->setDevice(q);
+ q->setContext(context, shareWidget ? shareWidget->context() : 0);
+
+ if (!glcx)
+ glcx = new QGLContext(QGLFormat::defaultFormat(), q);
+}
+
+#if defined(Q_WS_X11) || defined(Q_WS_MAC) || defined(Q_WS_QWS) || defined(Q_WS_QPA)
+Q_GLOBAL_STATIC(QString, qt_gl_lib_name)
+
+Q_OPENGL_EXPORT void qt_set_gl_library_name(const QString& name)
+{
+ qt_gl_lib_name()->operator=(name);
+}
+
+Q_OPENGL_EXPORT const QString qt_gl_library_name()
+{
+ if (qt_gl_lib_name()->isNull()) {
+#ifdef Q_WS_MAC
+ return QLatin1String("/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib");
+#else
+# if defined(QT_OPENGL_ES_1)
+ return QLatin1String("GLES_CM");
+# elif defined(QT_OPENGL_ES_2)
+ return QLatin1String("GLESv2");
+# else
+ return QLatin1String("GL");
+# endif
+#endif // defined Q_WS_MAC
+ }
+ return *qt_gl_lib_name();
+}
+#endif
+
+void QGLContextGroup::addShare(const QGLContext *context, const QGLContext *share) {
+ Q_ASSERT(context && share);
+ if (context->d_ptr->group == share->d_ptr->group)
+ return;
+
+ // Make sure 'context' is not already shared with another group of contexts.
+ Q_ASSERT(context->d_ptr->group->m_refs == 1);
+
+ // Free 'context' group resources and make it use the same resources as 'share'.
+ QGLContextGroup *group = share->d_ptr->group;
+ delete context->d_ptr->group;
+ context->d_ptr->group = group;
+ group->m_refs.ref();
+
+ // Maintain a list of all the contexts in each group of sharing contexts.
+ // The list is empty if the "share" context wasn't sharing already.
+ if (group->m_shares.isEmpty())
+ group->m_shares.append(share);
+ group->m_shares.append(context);
+}
+
+void QGLContextGroup::removeShare(const QGLContext *context) {
+ // Remove the context from the group.
+ QGLContextGroup *group = context->d_ptr->group;
+ if (group->m_shares.isEmpty())
+ return;
+ group->m_shares.removeAll(context);
+
+ // Update context group representative.
+ Q_ASSERT(group->m_shares.size() != 0);
+ if (group->m_context == context)
+ group->m_context = group->m_shares[0];
+
+ // If there is only one context left, then make the list empty.
+ if (group->m_shares.size() == 1)
+ group->m_shares.clear();
+}
+
+QGLContextGroupResourceBase::QGLContextGroupResourceBase()
+ : active(0)
+{
+#ifdef QT_GL_CONTEXT_RESOURCE_DEBUG
+ qDebug("Creating context group resource object %p.", this);
+#endif
+}
+
+QGLContextGroupResourceBase::~QGLContextGroupResourceBase()
+{
+#ifdef QT_GL_CONTEXT_RESOURCE_DEBUG
+ qDebug("Deleting context group resource %p. Group size: %d.", this, m_groups.size());
+#endif
+ for (int i = 0; i < m_groups.size(); ++i) {
+ m_groups.at(i)->m_resources.remove(this);
+ active.deref();
+ }
+#ifndef QT_NO_DEBUG
+ if (active != 0) {
+ qWarning("QtOpenGL: Resources are still available at program shutdown.\n"
+ " This is possibly caused by a leaked QGLWidget, \n"
+ " QGLFramebufferObject or QGLPixelBuffer.");
+ }
+#endif
+}
+
+void QGLContextGroupResourceBase::insert(const QGLContext *context, void *value)
+{
+#ifdef QT_GL_CONTEXT_RESOURCE_DEBUG
+ qDebug("Inserting context group resource %p for context %p, managed by %p.", value, context, this);
+#endif
+ QGLContextGroup *group = QGLContextPrivate::contextGroup(context);
+ Q_ASSERT(!group->m_resources.contains(this));
+ group->m_resources.insert(this, value);
+ m_groups.append(group);
+ active.ref();
+}
+
+void *QGLContextGroupResourceBase::value(const QGLContext *context)
+{
+ QGLContextGroup *group = QGLContextPrivate::contextGroup(context);
+ return group->m_resources.value(this, 0);
+}
+
+void QGLContextGroupResourceBase::cleanup(const QGLContext *ctx)
+{
+ void *resource = value(ctx);
+
+ if (resource != 0) {
+ QGLShareContextScope scope(ctx);
+ freeResource(resource);
+
+ QGLContextGroup *group = QGLContextPrivate::contextGroup(ctx);
+ group->m_resources.remove(this);
+ m_groups.removeOne(group);
+ active.deref();
+ }
+}
+
+void QGLContextGroupResourceBase::cleanup(const QGLContext *ctx, void *value)
+{
+#ifdef QT_GL_CONTEXT_RESOURCE_DEBUG
+ qDebug("Cleaning up context group resource %p, for context %p in thread %p.", this, ctx, QThread::currentThread());
+#endif
+ QGLShareContextScope scope(ctx);
+ freeResource(value);
+ active.deref();
+
+ QGLContextGroup *group = QGLContextPrivate::contextGroup(ctx);
+ m_groups.removeOne(group);
+}
+
+void QGLContextGroup::cleanupResources(const QGLContext *context)
+{
+ // If there are still shares, then no cleanup to be done yet.
+ if (m_shares.size() > 1)
+ return;
+
+ // Iterate over all resources and free each in turn.
+ QHash<QGLContextGroupResourceBase *, void *>::ConstIterator it;
+ for (it = m_resources.begin(); it != m_resources.end(); ++it)
+ it.key()->cleanup(context, it.value());
+}
+
+QGLSharedResourceGuard::~QGLSharedResourceGuard()
+{
+ if (m_group)
+ m_group->removeGuard(this);
+}
+
+void QGLSharedResourceGuard::setContext(const QGLContext *context)
+{
+ if (m_group)
+ m_group->removeGuard(this);
+ if (context) {
+ m_group = QGLContextPrivate::contextGroup(context);
+ m_group->addGuard(this);
+ } else {
+ m_group = 0;
+ }
+}
+
+QSize QGLTexture::bindCompressedTexture
+ (const QString& fileName, const char *format)
+{
+ QFile file(fileName);
+ if (!file.open(QIODevice::ReadOnly))
+ return QSize();
+ QByteArray contents = file.readAll();
+ file.close();
+ return bindCompressedTexture
+ (contents.constData(), contents.size(), format);
+}
+
+// PVR header format for container files that store textures compressed
+// with the ETC1, PVRTC2, and PVRTC4 encodings. Format information from the
+// PowerVR SDK at http://www.imgtec.com/powervr/insider/powervr-sdk.asp
+// "PVRTexTool Reference Manual, version 1.11f".
+struct PvrHeader
+{
+ quint32 headerSize;
+ quint32 height;
+ quint32 width;
+ quint32 mipMapCount;
+ quint32 flags;
+ quint32 dataSize;
+ quint32 bitsPerPixel;
+ quint32 redMask;
+ quint32 greenMask;
+ quint32 blueMask;
+ quint32 alphaMask;
+ quint32 magic;
+ quint32 surfaceCount;
+};
+
+#define PVR_MAGIC 0x21525650 // "PVR!" in little-endian
+
+#define PVR_FORMAT_MASK 0x000000FF
+#define PVR_FORMAT_PVRTC2 0x00000018
+#define PVR_FORMAT_PVRTC4 0x00000019
+#define PVR_FORMAT_ETC1 0x00000036
+
+#define PVR_HAS_MIPMAPS 0x00000100
+#define PVR_TWIDDLED 0x00000200
+#define PVR_NORMAL_MAP 0x00000400
+#define PVR_BORDER_ADDED 0x00000800
+#define PVR_CUBE_MAP 0x00001000
+#define PVR_FALSE_COLOR_MIPMAPS 0x00002000
+#define PVR_VOLUME_TEXTURE 0x00004000
+#define PVR_ALPHA_IN_TEXTURE 0x00008000
+#define PVR_VERTICAL_FLIP 0x00010000
+
+#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG
+#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
+#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
+#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
+#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03
+#endif
+
+#ifndef GL_ETC1_RGB8_OES
+#define GL_ETC1_RGB8_OES 0x8D64
+#endif
+
+bool QGLTexture::canBindCompressedTexture
+ (const char *buf, int len, const char *format, bool *hasAlpha)
+{
+ if (QSysInfo::ByteOrder != QSysInfo::LittleEndian) {
+ // Compressed texture loading only supported on little-endian
+ // systems such as x86 and ARM at the moment.
+ return false;
+ }
+ if (!format) {
+ // Auto-detect the format from the header.
+ if (len >= 4 && !qstrncmp(buf, "DDS ", 4)) {
+ *hasAlpha = true;
+ return true;
+ } else if (len >= 52 && !qstrncmp(buf + 44, "PVR!", 4)) {
+ const PvrHeader *pvrHeader =
+ reinterpret_cast<const PvrHeader *>(buf);
+ *hasAlpha = (pvrHeader->alphaMask != 0);
+ return true;
+ }
+ } else {
+ // Validate the format against the header.
+ if (!qstricmp(format, "DDS")) {
+ if (len >= 4 && !qstrncmp(buf, "DDS ", 4)) {
+ *hasAlpha = true;
+ return true;
+ }
+ } else if (!qstricmp(format, "PVR") || !qstricmp(format, "ETC1")) {
+ if (len >= 52 && !qstrncmp(buf + 44, "PVR!", 4)) {
+ const PvrHeader *pvrHeader =
+ reinterpret_cast<const PvrHeader *>(buf);
+ *hasAlpha = (pvrHeader->alphaMask != 0);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+#define ctx QGLContext::currentContext()
+
+QSize QGLTexture::bindCompressedTexture
+ (const char *buf, int len, const char *format)
+{
+ if (QSysInfo::ByteOrder != QSysInfo::LittleEndian) {
+ // Compressed texture loading only supported on little-endian
+ // systems such as x86 and ARM at the moment.
+ return QSize();
+ }
+#if !defined(QT_OPENGL_ES)
+ if (!glCompressedTexImage2D) {
+ if (!(QGLExtensions::glExtensions() & QGLExtensions::TextureCompression)) {
+ qWarning("QGLContext::bindTexture(): The GL implementation does "
+ "not support texture compression extensions.");
+ return QSize();
+ }
+ glCompressedTexImage2D = (_glCompressedTexImage2DARB) ctx->getProcAddress(QLatin1String("glCompressedTexImage2DARB"));
+ if (!glCompressedTexImage2D) {
+ qWarning("QGLContext::bindTexture(): could not resolve "
+ "glCompressedTexImage2DARB.");
+ return QSize();
+ }
+ }
+#endif
+ if (!format) {
+ // Auto-detect the format from the header.
+ if (len >= 4 && !qstrncmp(buf, "DDS ", 4))
+ return bindCompressedTextureDDS(buf, len);
+ else if (len >= 52 && !qstrncmp(buf + 44, "PVR!", 4))
+ return bindCompressedTexturePVR(buf, len);
+ } else {
+ // Validate the format against the header.
+ if (!qstricmp(format, "DDS")) {
+ if (len >= 4 && !qstrncmp(buf, "DDS ", 4))
+ return bindCompressedTextureDDS(buf, len);
+ } else if (!qstricmp(format, "PVR") || !qstricmp(format, "ETC1")) {
+ if (len >= 52 && !qstrncmp(buf + 44, "PVR!", 4))
+ return bindCompressedTexturePVR(buf, len);
+ }
+ }
+ return QSize();
+}
+
+QSize QGLTexture::bindCompressedTextureDDS(const char *buf, int len)
+{
+ // We only support 2D texture loading at present.
+ if (target != GL_TEXTURE_2D)
+ return QSize();
+
+ // Bail out if the necessary extension is not present.
+ if (!(QGLExtensions::glExtensions() & QGLExtensions::DDSTextureCompression)) {
+ qWarning("QGLContext::bindTexture(): DDS texture compression is not supported.");
+ return QSize();
+ }
+
+ const DDSFormat *ddsHeader = reinterpret_cast<const DDSFormat *>(buf + 4);
+ if (!ddsHeader->dwLinearSize) {
+ qWarning("QGLContext::bindTexture(): DDS image size is not valid.");
+ return QSize();
+ }
+
+ int blockSize = 16;
+ GLenum format;
+
+ switch(ddsHeader->ddsPixelFormat.dwFourCC) {
+ case FOURCC_DXT1:
+ format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ blockSize = 8;
+ break;
+ case FOURCC_DXT3:
+ format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ break;
+ case FOURCC_DXT5:
+ format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ break;
+ default:
+ qWarning("QGLContext::bindTexture(): DDS image format not supported.");
+ return QSize();
+ }
+
+ const GLubyte *pixels =
+ reinterpret_cast<const GLubyte *>(buf + ddsHeader->dwSize + 4);
+
+ glGenTextures(1, &id);
+ glBindTexture(GL_TEXTURE_2D, id);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+ int size;
+ int offset = 0;
+ int available = len - int(ddsHeader->dwSize + 4);
+ int w = ddsHeader->dwWidth;
+ int h = ddsHeader->dwHeight;
+
+ // load mip-maps
+ for(int i = 0; i < (int) ddsHeader->dwMipMapCount; ++i) {
+ if (w == 0) w = 1;
+ if (h == 0) h = 1;
+
+ size = ((w+3)/4) * ((h+3)/4) * blockSize;
+ if (size > available)
+ break;
+ glCompressedTexImage2D(GL_TEXTURE_2D, i, format, w, h, 0,
+ size, pixels + offset);
+ offset += size;
+ available -= size;
+
+ // half size for each mip-map level
+ w = w/2;
+ h = h/2;
+ }
+
+ // DDS images are not inverted.
+ options &= ~QGLContext::InvertedYBindOption;
+
+ return QSize(ddsHeader->dwWidth, ddsHeader->dwHeight);
+}
+
+QSize QGLTexture::bindCompressedTexturePVR(const char *buf, int len)
+{
+ // We only support 2D texture loading at present. Cube maps later.
+ if (target != GL_TEXTURE_2D)
+ return QSize();
+
+ // Determine which texture format we will be loading.
+ const PvrHeader *pvrHeader = reinterpret_cast<const PvrHeader *>(buf);
+ GLenum textureFormat;
+ quint32 minWidth, minHeight;
+ switch (pvrHeader->flags & PVR_FORMAT_MASK) {
+ case PVR_FORMAT_PVRTC2:
+ if (pvrHeader->alphaMask)
+ textureFormat = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
+ else
+ textureFormat = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
+ minWidth = 16;
+ minHeight = 8;
+ break;
+
+ case PVR_FORMAT_PVRTC4:
+ if (pvrHeader->alphaMask)
+ textureFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+ else
+ textureFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
+ minWidth = 8;
+ minHeight = 8;
+ break;
+
+ case PVR_FORMAT_ETC1:
+ textureFormat = GL_ETC1_RGB8_OES;
+ minWidth = 4;
+ minHeight = 4;
+ break;
+
+ default:
+ qWarning("QGLContext::bindTexture(): PVR image format 0x%x not supported.", int(pvrHeader->flags & PVR_FORMAT_MASK));
+ return QSize();
+ }
+
+ // Bail out if the necessary extension is not present.
+ if (textureFormat == GL_ETC1_RGB8_OES) {
+ if (!(QGLExtensions::glExtensions() &
+ QGLExtensions::ETC1TextureCompression)) {
+ qWarning("QGLContext::bindTexture(): ETC1 texture compression is not supported.");
+ return QSize();
+ }
+ } else {
+ if (!(QGLExtensions::glExtensions() &
+ QGLExtensions::PVRTCTextureCompression)) {
+ qWarning("QGLContext::bindTexture(): PVRTC texture compression is not supported.");
+ return QSize();
+ }
+ }
+
+ // Boundary check on the buffer size.
+ quint32 bufferSize = pvrHeader->headerSize + pvrHeader->dataSize;
+ if (bufferSize > quint32(len)) {
+ qWarning("QGLContext::bindTexture(): PVR image size is not valid.");
+ return QSize();
+ }
+
+ // Create the texture.
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ glGenTextures(1, &id);
+ glBindTexture(GL_TEXTURE_2D, id);
+ if (pvrHeader->mipMapCount) {
+ if ((options & QGLContext::LinearFilteringBindOption) != 0) {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ } else {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
+ }
+ } else if ((options & QGLContext::LinearFilteringBindOption) != 0) {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ } else {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ }
+
+ // Load the compressed mipmap levels.
+ const GLubyte *buffer =
+ reinterpret_cast<const GLubyte *>(buf + pvrHeader->headerSize);
+ bufferSize = pvrHeader->dataSize;
+ quint32 level = 0;
+ quint32 width = pvrHeader->width;
+ quint32 height = pvrHeader->height;
+ while (bufferSize > 0 && level <= pvrHeader->mipMapCount) {
+ quint32 size =
+ (qMax(width, minWidth) * qMax(height, minHeight) *
+ pvrHeader->bitsPerPixel) / 8;
+ if (size > bufferSize)
+ break;
+ glCompressedTexImage2D(GL_TEXTURE_2D, GLint(level), textureFormat,
+ GLsizei(width), GLsizei(height), 0,
+ GLsizei(size), buffer);
+ width /= 2;
+ height /= 2;
+ buffer += size;
+ ++level;
+ }
+
+ // Restore the default pixel alignment for later texture uploads.
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+
+ // Set the invert flag for the texture. The "vertical flip"
+ // flag in PVR is the opposite sense to our sense of inversion.
+ if ((pvrHeader->flags & PVR_VERTICAL_FLIP) != 0)
+ options &= ~QGLCont