diff options
author | Jo Asplin <jo.asplin@nokia.com> | 2011-09-08 15:12:01 +0200 |
---|---|---|
committer | Jo Asplin <jo.asplin@nokia.com> | 2011-09-09 10:32:38 +0200 |
commit | 0bfa24106d1465dccabcf7ad38c9cffdc2229f29 (patch) | |
tree | 8a7d4e516b2c5aa6ca54ee8dc799ea4e17dde479 /tests/auto/opengl | |
parent | 0a208dd4135fc16d05f9fd8c417d99e3f39c39fb (diff) |
Moved autotests in category 'opengl' into new directory structure
Task-number: QTBUG-21356
Change-Id: I496680aa670af3409e50e1c254ef2c557b414d33
Reviewed-on: http://codereview.qt-project.org/4452
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Rohan McGovern <rohan.mcgovern@nokia.com>
Diffstat (limited to 'tests/auto/opengl')
-rw-r--r-- | tests/auto/opengl/opengl.pro | 7 | ||||
-rw-r--r-- | tests/auto/opengl/qgl/.gitignore | 1 | ||||
-rw-r--r-- | tests/auto/opengl/qgl/qgl.pro | 15 | ||||
-rw-r--r-- | tests/auto/opengl/qgl/qgl.qrc | 5 | ||||
-rw-r--r-- | tests/auto/opengl/qgl/tst_qgl.cpp | 2400 | ||||
-rw-r--r-- | tests/auto/opengl/qglbuffer/qglbuffer.pro | 11 | ||||
-rw-r--r-- | tests/auto/opengl/qglbuffer/tst_qglbuffer.cpp | 261 | ||||
-rw-r--r-- | tests/auto/opengl/qglfunctions/qglfunctions.pro | 7 | ||||
-rw-r--r-- | tests/auto/opengl/qglfunctions/tst_qglfunctions.cpp | 244 | ||||
-rw-r--r-- | tests/auto/opengl/qglthreads/qglthreads.pro | 14 | ||||
-rw-r--r-- | tests/auto/opengl/qglthreads/tst_qglthreads.cpp | 758 | ||||
-rw-r--r-- | tests/auto/opengl/qglthreads/tst_qglthreads.h | 65 |
12 files changed, 3788 insertions, 0 deletions
diff --git a/tests/auto/opengl/opengl.pro b/tests/auto/opengl/opengl.pro new file mode 100644 index 0000000000..6c8e4ca96b --- /dev/null +++ b/tests/auto/opengl/opengl.pro @@ -0,0 +1,7 @@ +TEMPLATE=subdirs +SUBDIRS=\ + qgl \ + qglthreads \ + qglbuffer \ + qglfunctions \ + diff --git a/tests/auto/opengl/qgl/.gitignore b/tests/auto/opengl/qgl/.gitignore new file mode 100644 index 0000000000..bb6e921035 --- /dev/null +++ b/tests/auto/opengl/qgl/.gitignore @@ -0,0 +1 @@ +tst_qgl diff --git a/tests/auto/opengl/qgl/qgl.pro b/tests/auto/opengl/qgl/qgl.pro new file mode 100644 index 0000000000..c5e26451ed --- /dev/null +++ b/tests/auto/opengl/qgl/qgl.pro @@ -0,0 +1,15 @@ +############################################################ +# Project file for autotest for file qgl.h +############################################################ + +load(qttest_p4) +requires(contains(QT_CONFIG,opengl)) +QT += widgets widgets-private opengl-private gui-private core-private + +contains(QT_CONFIG,egl):DEFINES += QGL_EGL +win32:!wince*: DEFINES += QT_NO_EGL + +SOURCES += tst_qgl.cpp +RESOURCES = qgl.qrc + +CONFIG+=insignificant_test diff --git a/tests/auto/opengl/qgl/qgl.qrc b/tests/auto/opengl/qgl/qgl.qrc new file mode 100644 index 0000000000..70f425c1b9 --- /dev/null +++ b/tests/auto/opengl/qgl/qgl.qrc @@ -0,0 +1,5 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file alias="designer.png">../../gui/image/qpixmap/images/designer.png</file> +</qresource> +</RCC> diff --git a/tests/auto/opengl/qgl/tst_qgl.cpp b/tests/auto/opengl/qgl/tst_qgl.cpp new file mode 100644 index 0000000000..e1c3462560 --- /dev/null +++ b/tests/auto/opengl/qgl/tst_qgl.cpp @@ -0,0 +1,2400 @@ +/**************************************************************************** +** +** 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 test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <qcoreapplication.h> +#include <qdebug.h> +#include <qgl.h> +#include <qglpixelbuffer.h> +#include <qglframebufferobject.h> +#include <qglcolormap.h> +#include <qpaintengine.h> + +#include <QGraphicsView> +#include <QGraphicsProxyWidget> +#include <QVBoxLayout> + +#ifdef QT_BUILD_INTERNAL +#include <qplatformpixmap_qpa.h> +#include <QtOpenGL/private/qgl_p.h> +#include <QtGui/private/qimage_p.h> +#include <QtGui/private/qimagepixmapcleanuphooks_p.h> +#endif + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QGL : public QObject +{ +Q_OBJECT + +public: + tst_QGL(); + virtual ~tst_QGL(); + +private slots: + void getSetCheck(); + void openGLVersionCheck(); + void graphicsViewClipping(); + void partialGLWidgetUpdates_data(); + void partialGLWidgetUpdates(); + void glWidgetWithAlpha(); + void glWidgetRendering(); + void glFBOSimpleRendering(); + void glFBORendering(); + void multipleFBOInterleavedRendering(); + void glFBOUseInGLWidget(); + void glPBufferRendering(); + void glWidgetReparent(); + void glWidgetRenderPixmap(); + void colormap(); + void fboFormat(); + void testDontCrashOnDanglingResources(); + void replaceClipping(); + void clipTest(); + void destroyFBOAfterContext(); + void shareRegister(); + void qglContextDefaultBindTexture(); + void textureCleanup(); + void threadImages(); + void nullRectCrash(); +}; + +tst_QGL::tst_QGL() +{ +} + +tst_QGL::~tst_QGL() +{ +} + +class MyGLContext : public QGLContext +{ +public: + MyGLContext(const QGLFormat& format) : QGLContext(format) {} + bool windowCreated() const { return QGLContext::windowCreated(); } + void setWindowCreated(bool on) { QGLContext::setWindowCreated(on); } + bool initialized() const { return QGLContext::initialized(); } + void setInitialized(bool on) { QGLContext::setInitialized(on); } +}; + +class MyGLWidget : public QGLWidget +{ +public: + MyGLWidget() : QGLWidget() {} + bool autoBufferSwap() const { return QGLWidget::autoBufferSwap(); } + void setAutoBufferSwap(bool on) { QGLWidget::setAutoBufferSwap(on); } +}; + +static int appDefaultDepth() +{ + static int depth = 0; + if (depth == 0) { + QPixmap pm(1, 1); + depth = pm.depth(); + } + return depth; +} + +// Using INT_MIN and INT_MAX will cause failures on systems +// where "int" is 64-bit, so use the explicit values instead. +#define TEST_INT_MIN (-2147483647 - 1) +#define TEST_INT_MAX 2147483647 + +// Testing get/set functions +void tst_QGL::getSetCheck() +{ + if (!QGLFormat::hasOpenGL()) + QSKIP("QGL not supported on this platform", SkipAll); + + QGLFormat obj1; + // int QGLFormat::depthBufferSize() + // void QGLFormat::setDepthBufferSize(int) + QCOMPARE(-1, obj1.depthBufferSize()); + obj1.setDepthBufferSize(0); + QCOMPARE(0, obj1.depthBufferSize()); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setDepthBufferSize: Cannot set negative depth buffer size -2147483648"); + obj1.setDepthBufferSize(TEST_INT_MIN); + QCOMPARE(0, obj1.depthBufferSize()); // Makes no sense with a negative buffer size + obj1.setDepthBufferSize(3); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setDepthBufferSize: Cannot set negative depth buffer size -1"); + obj1.setDepthBufferSize(-1); + QCOMPARE(3, obj1.depthBufferSize()); + obj1.setDepthBufferSize(TEST_INT_MAX); + QCOMPARE(TEST_INT_MAX, obj1.depthBufferSize()); + + // int QGLFormat::accumBufferSize() + // void QGLFormat::setAccumBufferSize(int) + QCOMPARE(-1, obj1.accumBufferSize()); + obj1.setAccumBufferSize(0); + QCOMPARE(0, obj1.accumBufferSize()); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setAccumBufferSize: Cannot set negative accumulate buffer size -2147483648"); + obj1.setAccumBufferSize(TEST_INT_MIN); + QCOMPARE(0, obj1.accumBufferSize()); // Makes no sense with a negative buffer size + obj1.setAccumBufferSize(3); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setAccumBufferSize: Cannot set negative accumulate buffer size -1"); + obj1.setAccumBufferSize(-1); + QCOMPARE(3, obj1.accumBufferSize()); + obj1.setAccumBufferSize(TEST_INT_MAX); + QCOMPARE(TEST_INT_MAX, obj1.accumBufferSize()); + + // int QGLFormat::redBufferSize() + // void QGLFormat::setRedBufferSize(int) + QCOMPARE(-1, obj1.redBufferSize()); + obj1.setRedBufferSize(0); + QCOMPARE(0, obj1.redBufferSize()); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setRedBufferSize: Cannot set negative red buffer size -2147483648"); + obj1.setRedBufferSize(TEST_INT_MIN); + QCOMPARE(0, obj1.redBufferSize()); // Makes no sense with a negative buffer size + obj1.setRedBufferSize(3); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setRedBufferSize: Cannot set negative red buffer size -1"); + obj1.setRedBufferSize(-1); + QCOMPARE(3, obj1.redBufferSize()); + obj1.setRedBufferSize(TEST_INT_MAX); + QCOMPARE(TEST_INT_MAX, obj1.redBufferSize()); + + // int QGLFormat::greenBufferSize() + // void QGLFormat::setGreenBufferSize(int) + QCOMPARE(-1, obj1.greenBufferSize()); + obj1.setGreenBufferSize(0); + QCOMPARE(0, obj1.greenBufferSize()); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setGreenBufferSize: Cannot set negative green buffer size -2147483648"); + obj1.setGreenBufferSize(TEST_INT_MIN); + QCOMPARE(0, obj1.greenBufferSize()); // Makes no sense with a negative buffer size + obj1.setGreenBufferSize(3); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setGreenBufferSize: Cannot set negative green buffer size -1"); + obj1.setGreenBufferSize(-1); + QCOMPARE(3, obj1.greenBufferSize()); + obj1.setGreenBufferSize(TEST_INT_MAX); + QCOMPARE(TEST_INT_MAX, obj1.greenBufferSize()); + + // int QGLFormat::blueBufferSize() + // void QGLFormat::setBlueBufferSize(int) + QCOMPARE(-1, obj1.blueBufferSize()); + obj1.setBlueBufferSize(0); + QCOMPARE(0, obj1.blueBufferSize()); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setBlueBufferSize: Cannot set negative blue buffer size -2147483648"); + obj1.setBlueBufferSize(TEST_INT_MIN); + QCOMPARE(0, obj1.blueBufferSize()); // Makes no sense with a negative buffer size + obj1.setBlueBufferSize(3); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setBlueBufferSize: Cannot set negative blue buffer size -1"); + obj1.setBlueBufferSize(-1); + QCOMPARE(3, obj1.blueBufferSize()); + obj1.setBlueBufferSize(TEST_INT_MAX); + QCOMPARE(TEST_INT_MAX, obj1.blueBufferSize()); + + // int QGLFormat::alphaBufferSize() + // void QGLFormat::setAlphaBufferSize(int) + QCOMPARE(-1, obj1.alphaBufferSize()); + QCOMPARE(false, obj1.alpha()); + QVERIFY(!obj1.testOption(QGL::AlphaChannel)); + QVERIFY(obj1.testOption(QGL::NoAlphaChannel)); + obj1.setAlphaBufferSize(1); + QCOMPARE(true, obj1.alpha()); // setAlphaBufferSize() enables alpha. + QCOMPARE(1, obj1.alphaBufferSize()); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setAlphaBufferSize: Cannot set negative alpha buffer size -2147483648"); + obj1.setAlphaBufferSize(TEST_INT_MIN); + QCOMPARE(1, obj1.alphaBufferSize()); // Makes no sense with a negative buffer size + obj1.setAlphaBufferSize(3); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setAlphaBufferSize: Cannot set negative alpha buffer size -1"); + obj1.setAlphaBufferSize(-1); + QCOMPARE(3, obj1.alphaBufferSize()); + obj1.setAlphaBufferSize(TEST_INT_MAX); + QCOMPARE(TEST_INT_MAX, obj1.alphaBufferSize()); + + // int QGLFormat::stencilBufferSize() + // void QGLFormat::setStencilBufferSize(int) + QCOMPARE(-1, obj1.stencilBufferSize()); + obj1.setStencilBufferSize(1); + QCOMPARE(1, obj1.stencilBufferSize()); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setStencilBufferSize: Cannot set negative stencil buffer size -2147483648"); + obj1.setStencilBufferSize(TEST_INT_MIN); + QCOMPARE(1, obj1.stencilBufferSize()); // Makes no sense with a negative buffer size + obj1.setStencilBufferSize(3); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setStencilBufferSize: Cannot set negative stencil buffer size -1"); + obj1.setStencilBufferSize(-1); + QCOMPARE(3, obj1.stencilBufferSize()); + obj1.setStencilBufferSize(TEST_INT_MAX); + QCOMPARE(TEST_INT_MAX, obj1.stencilBufferSize()); + + // bool QGLFormat::sampleBuffers() + // void QGLFormat::setSampleBuffers(bool) + QCOMPARE(false, obj1.sampleBuffers()); + QVERIFY(!obj1.testOption(QGL::SampleBuffers)); + QVERIFY(obj1.testOption(QGL::NoSampleBuffers)); + + obj1.setSampleBuffers(false); + QCOMPARE(false, obj1.sampleBuffers()); + QVERIFY(obj1.testOption(QGL::NoSampleBuffers)); + obj1.setSampleBuffers(true); + QCOMPARE(true, obj1.sampleBuffers()); + QVERIFY(obj1.testOption(QGL::SampleBuffers)); + + // int QGLFormat::samples() + // void QGLFormat::setSamples(int) + QCOMPARE(-1, obj1.samples()); + obj1.setSamples(0); + QCOMPARE(0, obj1.samples()); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setSamples: Cannot have negative number of samples per pixel -2147483648"); + obj1.setSamples(TEST_INT_MIN); + QCOMPARE(0, obj1.samples()); // Makes no sense with a negative sample size + obj1.setSamples(3); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setSamples: Cannot have negative number of samples per pixel -1"); + obj1.setSamples(-1); + QCOMPARE(3, obj1.samples()); + obj1.setSamples(TEST_INT_MAX); + QCOMPARE(TEST_INT_MAX, obj1.samples()); + + // int QGLFormat::swapInterval() + // void QGLFormat::setSwapInterval(int) + QCOMPARE(-1, obj1.swapInterval()); + obj1.setSwapInterval(0); + QCOMPARE(0, obj1.swapInterval()); + obj1.setSwapInterval(TEST_INT_MIN); + QCOMPARE(TEST_INT_MIN, obj1.swapInterval()); + obj1.setSwapInterval(-1); + QCOMPARE(-1, obj1.swapInterval()); + obj1.setSwapInterval(TEST_INT_MAX); + QCOMPARE(TEST_INT_MAX, obj1.swapInterval()); + + // bool QGLFormat::doubleBuffer() + // void QGLFormat::setDoubleBuffer(bool) + QCOMPARE(true, obj1.doubleBuffer()); + QVERIFY(obj1.testOption(QGL::DoubleBuffer)); + QVERIFY(!obj1.testOption(QGL::SingleBuffer)); + obj1.setDoubleBuffer(false); + QCOMPARE(false, obj1.doubleBuffer()); + QVERIFY(!obj1.testOption(QGL::DoubleBuffer)); + QVERIFY(obj1.testOption(QGL::SingleBuffer)); + obj1.setDoubleBuffer(true); + QCOMPARE(true, obj1.doubleBuffer()); + QVERIFY(obj1.testOption(QGL::DoubleBuffer)); + QVERIFY(!obj1.testOption(QGL::SingleBuffer)); + + // bool QGLFormat::depth() + // void QGLFormat::setDepth(bool) + QCOMPARE(true, obj1.depth()); + QVERIFY(obj1.testOption(QGL::DepthBuffer)); + QVERIFY(!obj1.testOption(QGL::NoDepthBuffer)); + obj1.setDepth(false); + QCOMPARE(false, obj1.depth()); + QVERIFY(!obj1.testOption(QGL::DepthBuffer)); + QVERIFY(obj1.testOption(QGL::NoDepthBuffer)); + obj1.setDepth(true); + QCOMPARE(true, obj1.depth()); + QVERIFY(obj1.testOption(QGL::DepthBuffer)); + QVERIFY(!obj1.testOption(QGL::NoDepthBuffer)); + + // bool QGLFormat::rgba() + // void QGLFormat::setRgba(bool) + QCOMPARE(true, obj1.rgba()); + QVERIFY(obj1.testOption(QGL::Rgba)); + QVERIFY(!obj1.testOption(QGL::ColorIndex)); + obj1.setRgba(false); + QCOMPARE(false, obj1.rgba()); + QVERIFY(!obj1.testOption(QGL::Rgba)); + QVERIFY(obj1.testOption(QGL::ColorIndex)); + obj1.setRgba(true); + QCOMPARE(true, obj1.rgba()); + QVERIFY(obj1.testOption(QGL::Rgba)); + QVERIFY(!obj1.testOption(QGL::ColorIndex)); + + // bool QGLFormat::alpha() + // void QGLFormat::setAlpha(bool) + QVERIFY(obj1.testOption(QGL::AlphaChannel)); + QVERIFY(!obj1.testOption(QGL::NoAlphaChannel)); + obj1.setAlpha(false); + QCOMPARE(false, obj1.alpha()); + QVERIFY(!obj1.testOption(QGL::AlphaChannel)); + QVERIFY(obj1.testOption(QGL::NoAlphaChannel)); + obj1.setAlpha(true); + QCOMPARE(true, obj1.alpha()); + QVERIFY(obj1.testOption(QGL::AlphaChannel)); + QVERIFY(!obj1.testOption(QGL::NoAlphaChannel)); + + // bool QGLFormat::accum() + // void QGLFormat::setAccum(bool) + obj1.setAccumBufferSize(0); + QCOMPARE(false, obj1.accum()); + QVERIFY(!obj1.testOption(QGL::AccumBuffer)); + QVERIFY(obj1.testOption(QGL::NoAccumBuffer)); + obj1.setAccum(false); + QCOMPARE(false, obj1.accum()); + QVERIFY(!obj1.testOption(QGL::AccumBuffer)); + QVERIFY(obj1.testOption(QGL::NoAccumBuffer)); + obj1.setAccum(true); + QCOMPARE(true, obj1.accum()); + QVERIFY(obj1.testOption(QGL::AccumBuffer)); + QVERIFY(!obj1.testOption(QGL::NoAccumBuffer)); + + // bool QGLFormat::stencil() + // void QGLFormat::setStencil(bool) + QCOMPARE(true, obj1.stencil()); + QVERIFY(obj1.testOption(QGL::StencilBuffer)); + QVERIFY(!obj1.testOption(QGL::NoStencilBuffer)); + obj1.setStencil(false); + QCOMPARE(false, obj1.stencil()); + QVERIFY(!obj1.testOption(QGL::StencilBuffer)); + QVERIFY(obj1.testOption(QGL::NoStencilBuffer)); + obj1.setStencil(true); + QCOMPARE(true, obj1.stencil()); + QVERIFY(obj1.testOption(QGL::StencilBuffer)); + QVERIFY(!obj1.testOption(QGL::NoStencilBuffer)); + + // bool QGLFormat::stereo() + // void QGLFormat::setStereo(bool) + QCOMPARE(false, obj1.stereo()); + QVERIFY(!obj1.testOption(QGL::StereoBuffers)); + QVERIFY(obj1.testOption(QGL::NoStereoBuffers)); + obj1.setStereo(false); + QCOMPARE(false, obj1.stereo()); + QVERIFY(!obj1.testOption(QGL::StereoBuffers)); + QVERIFY(obj1.testOption(QGL::NoStereoBuffers)); + obj1.setStereo(true); + QCOMPARE(true, obj1.stereo()); + QVERIFY(obj1.testOption(QGL::StereoBuffers)); + QVERIFY(!obj1.testOption(QGL::NoStereoBuffers)); + + // bool QGLFormat::directRendering() + // void QGLFormat::setDirectRendering(bool) + QCOMPARE(true, obj1.directRendering()); + QVERIFY(obj1.testOption(QGL::DirectRendering)); + QVERIFY(!obj1.testOption(QGL::IndirectRendering)); + obj1.setDirectRendering(false); + QCOMPARE(false, obj1.directRendering()); + QVERIFY(!obj1.testOption(QGL::DirectRendering)); + QVERIFY(obj1.testOption(QGL::IndirectRendering)); + obj1.setDirectRendering(true); + QCOMPARE(true, obj1.directRendering()); + QVERIFY(obj1.testOption(QGL::DirectRendering)); + QVERIFY(!obj1.testOption(QGL::IndirectRendering)); + + // bool QGLFormat::overlay() + // void QGLFormat::setOverlay(bool) + QCOMPARE(false, obj1.hasOverlay()); + QVERIFY(!obj1.testOption(QGL::HasOverlay)); + QVERIFY(obj1.testOption(QGL::NoOverlay)); + obj1.setOverlay(false); + QCOMPARE(false, obj1.hasOverlay()); + QVERIFY(!obj1.testOption(QGL::HasOverlay)); + QVERIFY(obj1.testOption(QGL::NoOverlay)); + obj1.setOverlay(true); + QCOMPARE(true, obj1.hasOverlay()); + QVERIFY(obj1.testOption(QGL::HasOverlay)); + QVERIFY(!obj1.testOption(QGL::NoOverlay)); + + // int QGLFormat::plane() + // void QGLFormat::setPlane(int) + QCOMPARE(0, obj1.plane()); + obj1.setPlane(0); + QCOMPARE(0, obj1.plane()); + obj1.setPlane(TEST_INT_MIN); + QCOMPARE(TEST_INT_MIN, obj1.plane()); + obj1.setPlane(TEST_INT_MAX); + QCOMPARE(TEST_INT_MAX, obj1.plane()); + + // int QGLFormat::major/minorVersion() + // void QGLFormat::setVersion(int, int) + QCOMPARE(obj1.majorVersion(), 1); + QCOMPARE(obj1.minorVersion(), 0); + obj1.setVersion(3, 2); + QCOMPARE(obj1.majorVersion(), 3); + QCOMPARE(obj1.minorVersion(), 2); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setVersion: Cannot set zero or negative version number 0.1"); + obj1.setVersion(0, 1); + QCOMPARE(obj1.majorVersion(), 3); + QCOMPARE(obj1.minorVersion(), 2); + QTest::ignoreMessage(QtWarningMsg, "QGLFormat::setVersion: Cannot set zero or negative version number 3.-1"); + obj1.setVersion(3, -1); + QCOMPARE(obj1.majorVersion(), 3); + QCOMPARE(obj1.minorVersion(), 2); + obj1.setVersion(TEST_INT_MAX, TEST_INT_MAX - 1); + QCOMPARE(obj1.majorVersion(), TEST_INT_MAX); + QCOMPARE(obj1.minorVersion(), TEST_INT_MAX - 1); + + + // operator== and operator!= for QGLFormat + QGLFormat format1; + QGLFormat format2; + + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + format1.setDoubleBuffer(false); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setDoubleBuffer(false); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setDepthBufferSize(8); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setDepthBufferSize(8); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setAccumBufferSize(8); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setAccumBufferSize(8); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setRedBufferSize(8); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setRedBufferSize(8); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setGreenBufferSize(8); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setGreenBufferSize(8); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setBlueBufferSize(8); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setBlueBufferSize(8); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setAlphaBufferSize(8); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setAlphaBufferSize(8); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setStencilBufferSize(8); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setStencilBufferSize(8); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setSamples(8); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setSamples(8); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setSwapInterval(8); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setSwapInterval(8); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setPlane(8); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setPlane(8); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setVersion(3, 2); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setVersion(3, 2); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setProfile(QGLFormat::CoreProfile); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setProfile(QGLFormat::CoreProfile); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + format1.setOption(QGL::NoDeprecatedFunctions); + QVERIFY(!(format1 == format2)); + QVERIFY(format1 != format2); + format2.setOption(QGL::NoDeprecatedFunctions); + QVERIFY(format1 == format2); + QVERIFY(!(format1 != format2)); + + // Copy constructor and assignment for QGLFormat. + QGLFormat format3(format1); + QGLFormat format4; + QVERIFY(format1 == format3); + QVERIFY(format1 != format4); + format4 = format1; + QVERIFY(format1 == format4); + + // Check that modifying a copy doesn't affect the original. + format3.setRedBufferSize(16); + format4.setPlane(16); + QCOMPARE(format1.redBufferSize(), 8); + QCOMPARE(format1.plane(), 8); + + // Check the QGLFormat constructor that takes an option list. + QGLFormat format5 + (QGL::DepthBuffer | QGL::StereoBuffers | QGL::ColorIndex, 3); + QVERIFY(format5.depth()); + QVERIFY(format5.stereo()); + QVERIFY(format5.doubleBuffer()); // From defaultFormat() + QVERIFY(!format5.hasOverlay()); // From defaultFormat() + QVERIFY(!format5.rgba()); + QCOMPARE(format5.plane(), 3); + + // The default format should be the same as QGLFormat(). + QVERIFY(QGLFormat::defaultFormat() == QGLFormat()); + + // Modify the default format and check that it was changed. + QGLFormat::setDefaultFormat(format1); + QVERIFY(QGLFormat::defaultFormat() == format1); + + // Restore the default format. + QGLFormat::setDefaultFormat(QGLFormat()); + QVERIFY(QGLFormat::defaultFormat() == QGLFormat()); + + // Check the default overlay format's expected values. + QGLFormat overlay(QGLFormat::defaultOverlayFormat()); + QCOMPARE(overlay.depthBufferSize(), -1); + QCOMPARE(overlay.accumBufferSize(), -1); + QCOMPARE(overlay.redBufferSize(), -1); + QCOMPARE(overlay.greenBufferSize(), -1); + QCOMPARE(overlay.blueBufferSize(), -1); + QCOMPARE(overlay.alphaBufferSize(), -1); + QCOMPARE(overlay.samples(), -1); + QCOMPARE(overlay.swapInterval(), -1); + QCOMPARE(overlay.plane(), 1); + QVERIFY(!overlay.sampleBuffers()); + QVERIFY(!overlay.doubleBuffer()); + QVERIFY(!overlay.depth()); + QVERIFY(!overlay.rgba()); + QVERIFY(!overlay.alpha()); + QVERIFY(!overlay.accum()); + QVERIFY(!overlay.stencil()); + QVERIFY(!overlay.stereo()); + QVERIFY(overlay.directRendering()); // Only option that should be on. + QVERIFY(!overlay.hasOverlay()); // Overlay doesn't need an overlay! + + // Modify the default overlay format and check that it was changed. + QGLFormat::setDefaultOverlayFormat(format1); + QVERIFY(QGLFormat::defaultOverlayFormat() == format1); + + // Restore the default overlay format. + QGLFormat::setDefaultOverlayFormat(overlay); + QVERIFY(QGLFormat::defaultOverlayFormat() == overlay); + + MyGLContext obj2(obj1); + // bool QGLContext::windowCreated() + // void QGLContext::setWindowCreated(bool) + obj2.setWindowCreated(false); + QCOMPARE(false, obj2.windowCreated()); + obj2.setWindowCreated(true); + QCOMPARE(true, obj2.windowCreated()); + + // bool QGLContext::initialized() + // void QGLContext::setInitialized(bool) + obj2.setInitialized(false); + QCOMPARE(false, obj2.initialized()); + obj2.setInitialized(true); + QCOMPARE(true, obj2.initialized()); + + MyGLWidget obj3; + // bool QGLWidget::autoBufferSwap() + // void QGLWidget::setAutoBufferSwap(bool) + obj3.setAutoBufferSwap(false); + QCOMPARE(false, obj3.autoBufferSwap()); + obj3.setAutoBufferSwap(true); + QCOMPARE(true, obj3.autoBufferSwap()); +} + +#ifdef QT_BUILD_INTERNAL +QT_BEGIN_NAMESPACE +extern QGLFormat::OpenGLVersionFlags qOpenGLVersionFlagsFromString(const QString &versionString); +QT_END_NAMESPACE +#endif + +void tst_QGL::openGLVersionCheck() +{ +#ifdef QT_BUILD_INTERNAL + if (!QGLFormat::hasOpenGL()) + QSKIP("QGL not supported on this platform", SkipAll); + + QString versionString; + QGLFormat::OpenGLVersionFlags expectedFlag; + QGLFormat::OpenGLVersionFlags versionFlag; + + versionString = "1.1 Irix 6.5"; + expectedFlag = QGLFormat::OpenGL_Version_1_1; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "1.2 Microsoft"; + expectedFlag = QGLFormat::OpenGL_Version_1_2 | QGLFormat::OpenGL_Version_1_1; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "1.2.1"; + expectedFlag = QGLFormat::OpenGL_Version_1_2 | QGLFormat::OpenGL_Version_1_1; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "1.3 NVIDIA"; + expectedFlag = QGLFormat::OpenGL_Version_1_3 | QGLFormat::OpenGL_Version_1_2 | QGLFormat::OpenGL_Version_1_1; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "1.4"; + expectedFlag = QGLFormat::OpenGL_Version_1_4 | QGLFormat::OpenGL_Version_1_3 | QGLFormat::OpenGL_Version_1_2 | QGLFormat::OpenGL_Version_1_1; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "1.5 NVIDIA"; + expectedFlag = QGLFormat::OpenGL_Version_1_5 | QGLFormat::OpenGL_Version_1_4 | QGLFormat::OpenGL_Version_1_3 | QGLFormat::OpenGL_Version_1_2 | QGLFormat::OpenGL_Version_1_1; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "2.0.2 NVIDIA 87.62"; + expectedFlag = QGLFormat::OpenGL_Version_2_0 | QGLFormat::OpenGL_Version_1_5 | QGLFormat::OpenGL_Version_1_4 | QGLFormat::OpenGL_Version_1_3 | QGLFormat::OpenGL_Version_1_2 | QGLFormat::OpenGL_Version_1_1; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "2.1 NVIDIA"; + expectedFlag = QGLFormat::OpenGL_Version_2_1 | QGLFormat::OpenGL_Version_2_0 | QGLFormat::OpenGL_Version_1_5 | QGLFormat::OpenGL_Version_1_4 | QGLFormat::OpenGL_Version_1_3 | QGLFormat::OpenGL_Version_1_2 | QGLFormat::OpenGL_Version_1_1; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "2.1"; + expectedFlag = QGLFormat::OpenGL_Version_2_1 | QGLFormat::OpenGL_Version_2_0 | QGLFormat::OpenGL_Version_1_5 | QGLFormat::OpenGL_Version_1_4 | QGLFormat::OpenGL_Version_1_3 | QGLFormat::OpenGL_Version_1_2 | QGLFormat::OpenGL_Version_1_1; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "OpenGL ES-CM 1.0 ATI"; + expectedFlag = QGLFormat::OpenGL_ES_Common_Version_1_0 | QGLFormat::OpenGL_ES_CommonLite_Version_1_0; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "OpenGL ES-CL 1.0 ATI"; + expectedFlag = QGLFormat::OpenGL_ES_CommonLite_Version_1_0; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "OpenGL ES-CM 1.1 ATI"; + expectedFlag = QGLFormat::OpenGL_ES_Common_Version_1_1 | QGLFormat::OpenGL_ES_CommonLite_Version_1_1 | QGLFormat::OpenGL_ES_Common_Version_1_0 | QGLFormat::OpenGL_ES_CommonLite_Version_1_0; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "OpenGL ES-CL 1.1 ATI"; + expectedFlag = QGLFormat::OpenGL_ES_CommonLite_Version_1_1 | QGLFormat::OpenGL_ES_CommonLite_Version_1_0; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "OpenGL ES 2.0 ATI"; + expectedFlag = QGLFormat::OpenGL_ES_Version_2_0; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + versionString = "3.0"; + expectedFlag = QGLFormat::OpenGL_Version_3_0 | QGLFormat::OpenGL_Version_2_1 | QGLFormat::OpenGL_Version_2_0 | QGLFormat::OpenGL_Version_1_5 | QGLFormat::OpenGL_Version_1_4 | QGLFormat::OpenGL_Version_1_3 | QGLFormat::OpenGL_Version_1_2 | QGLFormat::OpenGL_Version_1_1; + versionFlag = qOpenGLVersionFlagsFromString(versionString); + QCOMPARE(versionFlag, expectedFlag); + + QGLWidget glWidget; + glWidget.show(); + glWidget.makeCurrent(); + + // This is unfortunately the only test we can make on the actual openGLVersionFlags() + // However, the complicated parts are in openGLVersionFlags(const QString &versionString) + // tested above + +#if defined(QT_OPENGL_ES_1) + QVERIFY(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Common_Version_1_0); +#elif defined(QT_OPENGL_ES_2) + QVERIFY(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0); +#else + QVERIFY(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_1_1); +#endif //defined(QT_OPENGL_ES_1) +#endif //QT_BUILD_INTERNAL +} + +static bool fuzzyComparePixels(const QRgb testPixel, const QRgb refPixel, const char* file, int line, int x = -1, int y = -1) +{ + static int maxFuzz = 1; + static bool maxFuzzSet = false; + + // On 16 bpp systems, we need to allow for more fuzz: + if (!maxFuzzSet) { + maxFuzzSet = true; + if (appDefaultDepth() < 24) + maxFuzz = 32; + } + + int redFuzz = qAbs(qRed(testPixel) - qRed(refPixel)); + int greenFuzz = qAbs(qGreen(testPixel) - qGreen(refPixel)); + int blueFuzz = qAbs(qBlue(testPixel) - qBlue(refPixel)); + int alphaFuzz = qAbs(qAlpha(testPixel) - qAlpha(refPixel)); + + if (refPixel != 0 && testPixel == 0) { + QString msg; + if (x >= 0) { + msg = QString("Test pixel [%1, %2] is null (black) when it should be (%3,%4,%5,%6)") + .arg(x).arg(y) + .arg(qRed(refPixel)).arg(qGreen(refPixel)).arg(qBlue(refPixel)).arg(qAlpha(refPixel)); + } else { + msg = QString("Test pixel is null (black) when it should be (%2,%3,%4,%5)") + .arg(qRed(refPixel)).arg(qGreen(refPixel)).arg(qBlue(refPixel)).arg(qAlpha(refPixel)); + } + + QTest::qFail(msg.toLatin1(), file, line); + return false; + } + + if (redFuzz > maxFuzz || greenFuzz > maxFuzz || blueFuzz > maxFuzz || alphaFuzz > maxFuzz) { + QString msg; + + if (x >= 0) + msg = QString("Pixel [%1,%2]: ").arg(x).arg(y); + else + msg = QString("Pixel "); + + msg += QString("Max fuzz (%1) exceeded: (%2,%3,%4,%5) vs (%6,%7,%8,%9)") + .arg(maxFuzz) + .arg(qRed(testPixel)).arg(qGreen(testPixel)).arg(qBlue(testPixel)).arg(qAlpha(testPixel)) + .arg(qRed(refPixel)).arg(qGreen(refPixel)).arg(qBlue(refPixel)).arg(qAlpha(refPixel)); + QTest::qFail(msg.toLatin1(), file, line); + return false; + } + return true; +} + +static void fuzzyCompareImages(const QImage &testImage, const QImage &referenceImage, const char* file, int line) +{ + QCOMPARE(testImage.width(), referenceImage.width()); + QCOMPARE(testImage.height(), referenceImage.height()); + + for (int y = 0; y < testImage.height(); y++) { + for (int x = 0; x < testImage.width(); x++) { + if (!fuzzyComparePixels(testImage.pixel(x, y), referenceImage.pixel(x, y), file, line, x, y)) { + // Might as well save the images for easier debugging: + referenceImage.save("referenceImage.png"); + testImage.save("testImage.png"); + return; + } + } + } +} + +#define QFUZZY_COMPARE_IMAGES(A,B) \ + fuzzyCompareImages(A, B, __FILE__, __LINE__) + +#define QFUZZY_COMPARE_PIXELS(A,B) \ + fuzzyComparePixels(A, B, __FILE__, __LINE__) + +class UnclippedWidget : public QWidget +{ +public: + void paintEvent(QPaintEvent *) + { + QPainter p(this); + p.fillRect(rect().adjusted(-1000, -1000, 1000, 1000), Qt::black); + } +}; + +void tst_QGL::graphicsViewClipping() +{ + const int size = 64; + UnclippedWidget *widget = new UnclippedWidget; + widget->setFixedSize(size, size); + + QGraphicsScene scene; + + scene.addWidget(widget)->setPos(0, 0); + + QGraphicsView view(&scene); + view.setBackgroundBrush(Qt::white); +#ifdef Q_WS_QWS + view.setWindowFlags(Qt::FramelessWindowHint); +#endif + view.resize(2*size, 2*size); + + QGLWidget *viewport = new QGLWidget; + view.setViewport(viewport); + view.show(); + + if (!viewport->isValid()) + return; + + scene.setSceneRect(view.viewport()->rect()); + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(&view); +#endif + QTest::qWait(500); + + QImage image = viewport->grabFrameBuffer(); + QImage expected = image; + + QPainter p(&expected); + p.fillRect(expected.rect(), Qt::white); + p.fillRect(QRect(0, 0, size, size), Qt::black); + p.end(); + + QFUZZY_COMPARE_IMAGES(image, expected); +} + +void tst_QGL::partialGLWidgetUpdates_data() +{ + QTest::addColumn<bool>("doubleBufferedContext"); + QTest::addColumn<bool>("autoFillBackground"); + QTest::addColumn<bool>("supportsPartialUpdates"); + + QTest::newRow("Double buffered context") << true << true << false; + QTest::newRow("Double buffered context without auto-fill background") << true << false << false; + QTest::newRow("Single buffered context") << false << true << false; + QTest::newRow("Single buffered context without auto-fill background") << false << false << true; +} + +void tst_QGL::partialGLWidgetUpdates() +{ + if (!QGLFormat::hasOpenGL()) + QSKIP("QGL not supported on this platform", SkipAll); + + QFETCH(bool, doubleBufferedContext); + QFETCH(bool, autoFillBackground); + QFETCH(bool, supportsPartialUpdates); + + class MyGLWidget : public QGLWidget + { + public: + QRegion paintEventRegion; + void paintEvent(QPaintEvent *e) + { + paintEventRegion = e->region(); + } + }; + + QGLFormat format = QGLFormat::defaultFormat(); + format.setDoubleBuffer(doubleBufferedContext); + QGLFormat::setDefaultFormat(format); + + MyGLWidget widget; + widget.setFixedSize(150, 150); + widget.setAutoFillBackground(autoFillBackground); + widget.show(); + + QTest::qWait(200); + + if (widget.format().doubleBuffer() != doubleBufferedContext) + QSKIP("Platform does not support requested format", SkipAll); + + widget.paintEventRegion = QRegion(); + widget.repaint(50, 50, 50, 50); + + if (supportsPartialUpdates) + QCOMPARE(widget.paintEventRegion, QRegion(50, 50, 50, 50)); + else + QCOMPARE(widget.paintEventRegion, QRegion(widget.rect())); +} + + +// This tests that rendering to a QGLPBuffer using QPainter works. +void tst_QGL::glPBufferRendering() +{ + if (!QGLPixelBuffer::hasOpenGLPbuffers()) + QSKIP("QGLPixelBuffer not supported on this platform", SkipSingle); + + QGLPixelBuffer* pbuf = new QGLPixelBuffer(128, 128); + + QPainter p; + bool begun = p.begin(pbuf); + QVERIFY(begun); + + QPaintEngine::Type engineType = p.paintEngine()->type(); + QVERIFY(engineType == QPaintEngine::OpenGL || engineType == QPaintEngine::OpenGL2); + + p.fillRect(0, 0, 128, 128, Qt::red); + p.fillRect(32, 32, 64, 64, Qt::blue); + p.end(); + + QImage fb = pbuf->toImage(); + delete pbuf; + + QImage reference(128, 128, fb.format()); + p.begin(&reference); + p.fillRect(0, 0, 128, 128, Qt::red); + p.fillRect(32, 32, 64, 64, Qt::blue); + p.end(); + + QFUZZY_COMPARE_IMAGES(fb, reference); +} + +void tst_QGL::glWidgetWithAlpha() +{ + QGLWidget* w = new QGLWidget(QGLFormat(QGL::AlphaChannel)); + w->show(); +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(w); +#endif + + delete w; +} + + +void qt_opengl_draw_test_pattern(QPainter* painter, int width, int height) +{ + QPainterPath intersectingPath; + intersectingPath.moveTo(0, 0); + intersectingPath.lineTo(100, 0); + intersectingPath.lineTo(0, 100); + intersectingPath.lineTo(100, 100); + intersectingPath.closeSubpath(); + + QPainterPath trianglePath; + trianglePath.moveTo(50, 0); + trianglePath.lineTo(100, 100); + trianglePath.lineTo(0, 100); + trianglePath.closeSubpath(); + + painter->setTransform(QTransform()); // reset xform + painter->fillRect(-1, -1, width+2, height+2, Qt::red); // Background + painter->translate(14, 14); + painter->fillPath(intersectingPath, Qt::blue); // Test stencil buffer works + painter->translate(128, 0); + painter->setClipPath(trianglePath); // Test depth buffer works + painter->setTransform(QTransform()); // reset xform ready for fill + painter->fillRect(-1, -1, width+2, height+2, Qt::green); +} + +void qt_opengl_check_test_pattern(const QImage& img) +{ + // As we're doing more than trivial painting, we can't just compare to + // an image rendered with raster. Instead, we sample at well-defined + // test-points: + QFUZZY_COMPARE_PIXELS(img.pixel(39, 64), QColor(Qt::red).rgb()); + QFUZZY_COMPARE_PIXELS(img.pixel(89, 64), QColor(Qt::red).rgb()); + QFUZZY_COMPARE_PIXELS(img.pixel(64, 39), QColor(Qt::blue).rgb()); + QFUZZY_COMPARE_PIXELS(img.pixel(64, 89), QColor(Qt::blue).rgb()); + + QFUZZY_COMPARE_PIXELS(img.pixel(167, 39), QColor(Qt::red).rgb()); + QFUZZY_COMPARE_PIXELS(img.pixel(217, 39), QColor(Qt::red).rgb()); + QFUZZY_COMPARE_PIXELS(img.pixel(192, 64), QColor(Qt::green).rgb()); +} + +class GLWidget : public QGLWidget +{ +public: + GLWidget(QWidget* p = 0) + : QGLWidget(p), beginOk(false), engineType(QPaintEngine::MaxUser) {} + bool beginOk; + QPaintEngine::Type engineType; + void paintGL() + { + QPainter p; + beginOk = p.begin(this); + QPaintEngine* pe = p.paintEngine(); + engineType = pe->type(); + + qt_opengl_draw_test_pattern(&p, width(), height()); + + // No p.end() or swap buffers, should be done automatically + } + +}; + +void tst_QGL::glWidgetRendering() +{ + GLWidget w; +#ifdef Q_WS_QWS + w.setWindowFlags(Qt::FramelessWindowHint); +#endif + w.resize(256, 128); + w.show(); + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(&w); +#endif + QTest::qWait(200); + + QVERIFY(w.beginOk); + QVERIFY(w.engineType == QPaintEngine::OpenGL || w.engineType == QPaintEngine::OpenGL2); + + QImage fb = w.grabFrameBuffer(false); + qt_opengl_check_test_pattern(fb); +} + +void tst_QGL::glFBOSimpleRendering() +{ + if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) + QSKIP("QGLFramebufferObject not supported on this platform", SkipSingle); + + QGLWidget glw; + glw.makeCurrent(); + + // No multisample with combined depth/stencil attachment: + QGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QGLFramebufferObject::NoAttachment); + + QGLFramebufferObject *fbo = new QGLFramebufferObject(200, 100, fboFormat); + + fbo->bind(); + + glClearColor(1.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + glFinish(); + + QImage fb = fbo->toImage().convertToFormat(QImage::Format_RGB32); + QImage reference(fb.size(), QImage::Format_RGB32); + reference.fill(0xffff0000); + + QFUZZY_COMPARE_IMAGES(fb, reference); + + delete fbo; +} + +// NOTE: This tests that CombinedDepthStencil attachment works by assuming the +// GL2 engine is being used and is implemented the same way as it was when +// this autotest was written. If this is not the case, there may be some +// false-positives: I.e. The test passes when either the depth or stencil +// buffer is actually missing. But that's probably ok anyway. +void tst_QGL::glFBORendering() +{ + if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) + QSKIP("QGLFramebufferObject not supported on this platform", SkipSingle); + + QGLWidget glw; + glw.makeCurrent(); + + // No multisample with combined depth/stencil attachment: + QGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + + // Don't complicate things by using NPOT: + QGLFramebufferObject *fbo = new QGLFramebufferObject(256, 128, fboFormat); + + if (fbo->attachment() != QGLFramebufferObject::CombinedDepthStencil) { + delete fbo; + QSKIP("FBOs missing combined depth~stencil support", SkipSingle); + } + + QPainter fboPainter; + bool painterBegun = fboPainter.begin(fbo); + QVERIFY(painterBegun); + + qt_opengl_draw_test_pattern(&fboPainter, fbo->width(), fbo->height()); + + fboPainter.end(); + + QImage fb = fbo->toImage().convertToFormat(QImage::Format_RGB32); + delete fbo; + + qt_opengl_check_test_pattern(fb); +} + + +// Tests multiple QPainters active on different FBOs at the same time, with +// interleaving painting. Performance-wise, this is sub-optimal, but it still +// has to work flawlessly +void tst_QGL::multipleFBOInterleavedRendering() +{ + if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) + QSKIP("QGLFramebufferObject not supported on this platform", SkipSingle); + + QGLWidget glw; + glw.makeCurrent(); + + // No multisample with combined depth/stencil attachment: + QGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + + QGLFramebufferObject *fbo1 = new QGLFramebufferObject(256, 128, fboFormat); + QGLFramebufferObject *fbo2 = new QGLFramebufferObject(256, 128, fboFormat); + QGLFramebufferObject *fbo3 = new QGLFramebufferObject(256, 128, fboFormat); + + if ( (fbo1->attachment() != QGLFramebufferObject::CombinedDepthStencil) || + (fbo2->attachment() != QGLFramebufferObject::CombinedDepthStencil) || + (fbo3->attachment() != QGLFramebufferObject::CombinedDepthStencil) ) + { + delete fbo1; + delete fbo2; + delete fbo3; + QSKIP("FBOs missing combined depth~stencil support", SkipSingle); + } + + QPainter fbo1Painter; + QPainter fbo2Painter; + QPainter fbo3Painter; + + QVERIFY(fbo1Painter.begin(fbo1)); + QVERIFY(fbo2Painter.begin(fbo2)); + QVERIFY(fbo3Painter.begin(fbo3)); + + // Confirm we're using the GL2 engine, as interleaved rendering isn't supported + // on the GL1 engine: + if (fbo1Painter.paintEngine()->type() != QPaintEngine::OpenGL2) + QSKIP("Interleaved GL rendering requires OpenGL 2.0 or higher", SkipSingle); + + QPainterPath intersectingPath; + intersectingPath.moveTo(0, 0); + intersectingPath.lineTo(100, 0); + intersectingPath.lineTo(0, 100); + intersectingPath.lineTo(100, 100); + intersectingPath.closeSubpath(); + + QPainterPath trianglePath; + trianglePath.moveTo(50, 0); + trianglePath.lineTo(100, 100); + trianglePath.lineTo(0, 100); + trianglePath.closeSubpath(); + + fbo1Painter.fillRect(0, 0, fbo1->width(), fbo1->height(), Qt::red); // Background + fbo2Painter.fillRect(0, 0, fbo2->width(), fbo2->height(), Qt::green); // Background + fbo3Painter.fillRect(0, 0, fbo3->width(), fbo3->height(), Qt::blue); // Background + + fbo1Painter.translate(14, 14); + fbo2Painter.translate(14, 14); + fbo3Painter.translate(14, 14); + + fbo1Painter.fillPath(intersectingPath, Qt::blue); // Test stencil buffer works + fbo2Painter.fillPath(intersectingPath, Qt::red); // Test stencil buffer works + fbo3Painter.fillPath(intersectingPath, Qt::green); // Test stencil buffer works + + fbo1Painter.translate(128, 0); + fbo2Painter.translate(128, 0); + fbo3Painter.translate(128, 0); + + fbo1Painter.setClipPath(trianglePath); + fbo2Painter.setClipPath(trianglePath); + fbo3Painter.setClipPath(trianglePath); + + fbo1Painter.setTransform(QTransform()); // reset xform + fbo2Painter.setTransform(QTransform()); // reset xform + fbo3Painter.setTransform(QTransform()); // reset xform + + fbo1Painter.fillRect(0, 0, fbo1->width(), fbo1->height(), Qt::green); + fbo2Painter.fillRect(0, 0, fbo2->width(), fbo2->height(), Qt::blue); + fbo3Painter.fillRect(0, 0, fbo3->width(), fbo3->height(), Qt::red); + + fbo1Painter.end(); + fbo2Painter.end(); + fbo3Painter.end(); + + QImage fb1 = fbo1->toImage().convertToFormat(QImage::Format_RGB32); + QImage fb2 = fbo2->toImage().convertToFormat(QImage::Format_RGB32); + QImage fb3 = fbo3->toImage().convertToFormat(QImage::Format_RGB32); + delete fbo1; + delete fbo2; + delete fbo3; + + // As we're doing more than trivial painting, we can't just compare to + // an image rendered with raster. Instead, we sample at well-defined + // test-points: + QFUZZY_COMPARE_PIXELS(fb1.pixel(39, 64), QColor(Qt::red).rgb()); + QFUZZY_COMPARE_PIXELS(fb1.pixel(89, 64), QColor(Qt::red).rgb()); + QFUZZY_COMPARE_PIXELS(fb1.pixel(64, 39), QColor(Qt::blue).rgb()); + QFUZZY_COMPARE_PIXELS(fb1.pixel(64, 89), QColor(Qt::blue).rgb()); + QFUZZY_COMPARE_PIXELS(fb1.pixel(167, 39), QColor(Qt::red).rgb()); + QFUZZY_COMPARE_PIXELS(fb1.pixel(217, 39), QColor(Qt::red).rgb()); + QFUZZY_COMPARE_PIXELS(fb1.pixel(192, 64), QColor(Qt::green).rgb()); + + QFUZZY_COMPARE_PIXELS(fb2.pixel(39, 64), QColor(Qt::green).rgb()); + QFUZZY_COMPARE_PIXELS(fb2.pixel(89, 64), QColor(Qt::green).rgb()); + QFUZZY_COMPARE_PIXELS(fb2.pixel(64, 39), QColor(Qt::red).rgb()); + QFUZZY_COMPARE_PIXELS(fb2.pixel(64, 89), QColor(Qt::red).rgb()); + QFUZZY_COMPARE_PIXELS(fb2.pixel(167, 39), QColor(Qt::green).rgb()); + QFUZZY_COMPARE_PIXELS(fb2.pixel(217, 39), QColor(Qt::green).rgb()); + QFUZZY_COMPARE_PIXELS(fb2.pixel(192, 64), QColor(Qt::blue).rgb()); + + QFUZZY_COMPARE_PIXELS(fb3.pixel(39, 64), QColor(Qt::blue).rgb()); + QFUZZY_COMPARE_PIXELS(fb3.pixel(89, 64), QColor(Qt::blue).rgb()); + QFUZZY_COMPARE_PIXELS(fb3.pixel(64, 39), QColor(Qt::green).rgb()); + QFUZZY_COMPARE_PIXELS(fb3.pixel(64, 89), QColor(Qt::green).rgb()); + QFUZZY_COMPARE_PIXELS(fb3.pixel(167, 39), QColor(Qt::blue).rgb()); + QFUZZY_COMPARE_PIXELS(fb3.pixel(217, 39), QColor(Qt::blue).rgb()); + QFUZZY_COMPARE_PIXELS(fb3.pixel(192, 64), QColor(Qt::red).rgb()); +} + +class FBOUseInGLWidget : public QGLWidget +{ +public: + bool widgetPainterBeginOk; + bool fboPainterBeginOk; + QImage fboImage; +protected: + void paintEvent(QPaintEvent*) + { + QPainter widgetPainter; + widgetPainterBeginOk = widgetPainter.begin(this); + QGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QGLFramebufferObject::NoAttachment); + QGLFramebufferObject *fbo = new QGLFramebufferObject(100, 100, fboFormat); + + QPainter fboPainter; + fboPainterBeginOk = fboPainter.begin(fbo); + fboPainter.fillRect(-1, -1, 130, 130, Qt::red); + fboPainter.end(); + fboImage = fbo->toImage(); + + widgetPainter.fillRect(-1, -1, width()+2, width()+2, Qt::blue); + + delete fbo; + } + +}; + +void tst_QGL::glFBOUseInGLWidget() +{ + if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) + QSKIP("QGLFramebufferObject not supported on this platform", SkipSingle); + + FBOUseInGLWidget w; +#ifdef Q_WS_QWS + w.setWindowFlags(Qt::FramelessWindowHint); +#endif + w.resize(100, 100); + w.show(); + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(&w); +#endif + QTest::qWait(200); + + QVERIFY(w.widgetPainterBeginOk); + QVERIFY(w.fboPainterBeginOk); + + QImage widgetFB = w.grabFrameBuffer(false); + QImage widgetReference(widgetFB.size(), widgetFB.format()); + widgetReference.fill(0xff0000ff); + QFUZZY_COMPARE_IMAGES(widgetFB, widgetReference); + + QImage fboReference(w.fboImage.size(), w.fboImage.format()); + fboReference.fill(0xffff0000); + QFUZZY_COMPARE_IMAGES(w.fboImage, fboReference); +} + +void tst_QGL::glWidgetReparent() +{ + // Try it as a top-level first: + GLWidget *widget = new GLWidget; + widget->setGeometry(0, 0, 200, 30); + widget->show(); + + QWidget grandParentWidget; + grandParentWidget.setPalette(Qt::blue); + QVBoxLayout grandParentLayout(&grandParentWidget); + + QWidget parentWidget(&grandParentWidget); + grandParentLayout.addWidget(&parentWidget); + parentWidget.setPalette(Qt::green); + parentWidget.setAutoFillBackground(true); + QVBoxLayout parentLayout(&parentWidget); + + grandParentWidget.setGeometry(0, 100, 200, 200); + grandParentWidget.show(); + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(widget); + qt_x11_wait_for_window_manager(&parentWidget); +#endif + QTest::qWait(200); + + QVERIFY(parentWidget.children().count() == 1); // The layout + + // Now both widgets should be created & shown, time to re-parent: + parentLayout.addWidget(widget); + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(&parentWidget); +#endif + QTest::qWait(200); + + QVERIFY(parentWidget.children().count() == 2); // Layout & glwidget + QVERIFY(parentWidget.children().contains(widget)); + QVERIFY(widget->height() > 30); + + delete widget; + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(&parentWidget); +#endif + QTest::qWait(200); + + QVERIFY(parentWidget.children().count() == 1); // The layout + + // Now do pretty much the same thing, but don't show the + // widget first: + widget = new GLWidget; + parentLayout.addWidget(widget); + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(&parentWidget); +#endif + QTest::qWait(200); + + QVERIFY(parentWidget.children().count() == 2); // Layout & glwidget + QVERIFY(parentWidget.children().contains(widget)); + QVERIFY(widget->height() > 30); + + delete widget; +} + +class RenderPixmapWidget : public QGLWidget +{ +protected: + void initializeGL() { + // Set some gl state: + glClearColor(1.0, 0.0, 0.0, 1.0); + } + + void paintGL() { + glClear(GL_COLOR_BUFFER_BIT); + } +}; + +void tst_QGL::glWidgetRenderPixmap() +{ + RenderPixmapWidget *w = new RenderPixmapWidget; + + QPixmap pm = w->renderPixmap(100, 100, false); + + delete w; + + QImage fb = pm.toImage().convertToFormat(QImage::Format_RGB32); + QImage reference(fb.size(), QImage::Format_RGB32); + reference.fill(0xffff0000); + +#if defined(QGL_EGL) && !defined(Q_WS_X11) + QSKIP("renderPixmap() not yet supported under EGL on your platform", SkipAll); +#endif + + QFUZZY_COMPARE_IMAGES(fb, reference); +} + +class ColormapExtended : public QGLColormap +{ +public: + ColormapExtended() {} + + Qt::HANDLE handle() { return QGLColormap::handle(); } + void setHandle(Qt::HANDLE handle) { QGLColormap::setHandle(handle); } +}; + +void tst_QGL::colormap() +{ + // Check the properties of the default empty colormap. + QGLColormap cmap1; + QVERIFY(cmap1.isEmpty()); + QCOMPARE(cmap1.size(), 0); + QVERIFY(cmap1.entryRgb(0) == 0); + QVERIFY(cmap1.entryRgb(-1) == 0); + QVERIFY(cmap1.entryRgb(100) == 0); + QVERIFY(!cmap1.entryColor(0).isValid()); + QVERIFY(!cmap1.entryColor(-1).isValid()); + QVERIFY(!cmap1.entryColor(100).isValid()); + QCOMPARE(cmap1.find(qRgb(255, 0, 0)), -1); + QCOMPARE(cmap1.findNearest(qRgb(255, 0, 0)), -1); + + // Set an entry and re-test. + cmap1.setEntry(56, qRgb(255, 0, 0)); + // The colormap is still considered "empty" even though it + // has entries in it now. The isEmpty() method is used to + // detect when the colormap is in use by a GL widget, + // not to detect when it is empty! + QVERIFY(cmap1.isEmpty()); + QCOMPARE(cmap1.size(), 256); + QVERIFY(cmap1.entryRgb(0) == 0); + QVERIFY(cmap1.entryColor(0) == QColor(0, 0, 0, 255)); + QVERIFY(cmap1.entryRgb(56) == qRgb(255, 0, 0)); + QVERIFY(cmap1.entryColor(56) == QColor(255, 0, 0, 255)); + QCOMPARE(cmap1.find(qRgb(255, 0, 0)), 56); + QCOMPARE(cmap1.findNearest(qRgb(255, 0, 0)), 56); + + // Set some more entries. + static QRgb const colors[] = { + qRgb(255, 0, 0), + qRgb(0, 255, 0), + qRgb(255, 255, 255), + qRgb(0, 0, 255), + qRgb(0, 0, 0) + }; + cmap1.setEntry(57, QColor(0, 255, 0)); + cmap1.setEntries(3, colors + 2, 58); + cmap1.setEntries(5, colors, 251); + int idx; + for (idx = 0; idx < 5; ++idx) { + QVERIFY(cmap1.entryRgb(56 + idx) == colors[idx]); + QVERIFY(cmap1.entryColor(56 + idx) == QColor(colors[idx])); + QVERIFY(cmap1.entryRgb(251 + idx) == colors[idx]); + QVERIFY(cmap1.entryColor(251 + idx) == QColor(colors[idx])); + } + QCOMPARE(cmap1.size(), 256); + + // Perform color lookups. + QCOMPARE(cmap1.find(qRgb(255, 0, 0)), 56); + QCOMPARE(cmap1.find(qRgb(0, 0, 0)), 60); // Actually finds 0, 0, 0, 255. + QCOMPARE(cmap1.find(qRgba(0, 0, 0, 0)), 0); + QCOMPARE(cmap1.find(qRgb(0, 255, 0)), 57); + QCOMPARE(cmap1.find(qRgb(255, 255, 255)), 58); + QCOMPARE(cmap1.find(qRgb(0, 0, 255)), 59); + QCOMPARE(cmap1.find(qRgb(140, 0, 0)), -1); + QCOMPARE(cmap1.find(qRgb(0, 140, 0)), -1); + QCOMPARE(cmap1.find(qRgb(0, 0, 140)), -1); + QCOMPARE(cmap1.find(qRgb(64, 0, 0)), -1); + QCOMPARE(cmap1.find(qRgb(0, 64, 0)), -1); + QCOMPARE(cmap1.find(qRgb(0, 0, 64)), -1); + QCOMPARE(cmap1.findNearest(qRgb(255, 0, 0)), 56); + QCOMPARE(cmap1.findNearest(qRgb(0, 0, 0)), 60); + QCOMPARE(cmap1.findNearest(qRgba(0, 0, 0, 0)), 0); + QCOMPARE(cmap1.findNearest(qRgb(0, 255, 0)), 57); + QCOMPARE(cmap1.findNearest(qRgb(255, 255, 255)), 58); + QCOMPARE(cmap1.findNearest(qRgb(0, 0, 255)), 59); + QCOMPARE(cmap1.findNearest(qRgb(140, 0, 0)), 56); + QCOMPARE(cmap1.findNearest(qRgb(0, 140, 0)), 57); + QCOMPARE(cmap1.findNearest(qRgb(0, 0, 140)), 59); + QCOMPARE(cmap1.findNearest(qRgb(64, 0, 0)), 0); + QCOMPARE(cmap1.findNearest(qRgb(0, 64, 0)), 0); + QCOMPARE(cmap1.findNearest(qRgb(0, 0, 64)), 0); + + // Make some copies of the colormap and check that they are the same. + QGLColormap cmap2(cmap1); + QGLColormap cmap3; + cmap3 = cmap1; + QVERIFY(cmap2.isEmpty()); + QVERIFY(cmap3.isEmpty()); + QCOMPARE(cmap2.size(), 256); + QCOMPARE(cmap3.size(), 256); + for (idx = 0; idx < 256; ++idx) { + QCOMPARE(cmap1.entryRgb(idx), cmap2.entryRgb(idx)); + QCOMPARE(cmap1.entryRgb(idx), cmap3.entryRgb(idx)); + } + + // Modify an entry in one of the copies and recheck the original. + cmap2.setEntry(45, qRgb(255, 0, 0)); + for (idx = 0; idx < 256; ++idx) { + if (idx != 45) + QCOMPARE(cmap1.entryRgb(idx), cmap2.entryRgb(idx)); + else + QCOMPARE(cmap2.entryRgb(45), qRgb(255, 0, 0)); + QCOMPARE(cmap1.entryRgb(idx), cmap3.entryRgb(idx)); + } + + // Check that setting the handle will cause isEmpty() to work right. + ColormapExtended cmap4; + cmap4.setEntry(56, qRgb(255, 0, 0)); + QVERIFY(cmap4.isEmpty()); + QCOMPARE(cmap4.size(), 256); + cmap4.setHandle(Qt::HANDLE(42)); + QVERIFY(cmap4.handle() == Qt::HANDLE(42)); + QVERIFY(!cmap4.isEmpty()); + QCOMPARE(cmap4.size(), 256); +} + +#ifndef QT_OPENGL_ES +#define DEFAULT_FORMAT GL_RGBA8 +#else +#define DEFAULT_FORMAT GL_RGBA +#endif + +#ifndef GL_TEXTURE_3D +#define GL_TEXTURE_3D 0x806F +#endif + +#ifndef GL_RGB16 +#define GL_RGB16 0x8054 +#endif + +void tst_QGL::fboFormat() +{ + // Check the initial conditions. + QGLFramebufferObjectFormat format1; + QCOMPARE(format1.samples(), 0); + QVERIFY(format1.attachment() == QGLFramebufferObject::NoAttachment); + QCOMPARE(int(format1.textureTarget()), int(GL_TEXTURE_2D)); + QCOMPARE(int(format1.internalTextureFormat()), int(DEFAULT_FORMAT)); + + // Modify the values and re-check. + format1.setSamples(8); + format1.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + format1.setTextureTarget(GL_TEXTURE_3D); + format1.setInternalTextureFormat(GL_RGB16); + QCOMPARE(format1.samples(), 8); + QVERIFY(format1.attachment() == QGLFramebufferObject::CombinedDepthStencil); + QCOMPARE(int(format1.textureTarget()), int(GL_TEXTURE_3D)); + QCOMPARE(int(format1.internalTextureFormat()), int(GL_RGB16)); + + // Make copies and check that they are the same. + QGLFramebufferObjectFormat format2(format1); + QGLFramebufferObjectFormat format3; + QCOMPARE(format2.samples(), 8); + QVERIFY(format2.attachment() == QGLFramebufferObject::CombinedDepthStencil); + QCOMPARE(int(format2.textureTarget()), int(GL_TEXTURE_3D)); + QCOMPARE(int(format2.internalTextureFormat()), int(GL_RGB16)); + format3 = format1; + QCOMPARE(format3.samples(), 8); + QVERIFY(format3.attachment() == QGLFramebufferObject::CombinedDepthStencil); + QCOMPARE(int(format3.textureTarget()), int(GL_TEXTURE_3D)); + QCOMPARE(int(format3.internalTextureFormat()), int(GL_RGB16)); + + // Modify the copies and check that the original is unchanged. + format2.setSamples(9); + format3.setTextureTarget(GL_TEXTURE_2D); + QCOMPARE(format1.samples(), 8); + QVERIFY(format1.attachment() == QGLFramebufferObject::CombinedDepthStencil); + QCOMPARE(int(format1.textureTarget()), int(GL_TEXTURE_3D)); + QCOMPARE(int(format1.internalTextureFormat()), int(GL_RGB16)); + + // operator== and operator!= for QGLFramebufferObjectFormat. + QGLFramebufferObjectFormat format1c; + QGLFramebufferObjectFormat format2c; + + QVERIFY(format1c == format2c); + QVERIFY(!(format1c != format2c)); + format1c.setSamples(8); + QVERIFY(!(format1c == format2c)); + QVERIFY(format1c != format2c); + format2c.setSamples(8); + QVERIFY(format1c == format2c); + QVERIFY(!(format1c != format2c)); + + format1c.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + QVERIFY(!(format1c == format2c)); + QVERIFY(format1c != format2c); + format2c.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + QVERIFY(format1c == format2c); + QVERIFY(!(format1c != format2c)); + + format1c.setTextureTarget(GL_TEXTURE_3D); + QVERIFY(!(format1c == format2c)); + QVERIFY(format1c != format2c); + format2c.setTextureTarget(GL_TEXTURE_3D); + QVERIFY(format1c == format2c); + QVERIFY(!(format1c != format2c)); + + format1c.setInternalTextureFormat(GL_RGB16); + QVERIFY(!(format1c == format2c)); + QVERIFY(format1c != format2c); + format2c.setInternalTextureFormat(GL_RGB16); + QVERIFY(format1c == format2c); + QVERIFY(!(format1c != format2c)); + + QGLFramebufferObjectFormat format3c(format1c); + QGLFramebufferObjectFormat format4c; + QVERIFY(format1c == format3c); + QVERIFY(!(format1c != format3c)); + format3c.setInternalTextureFormat(DEFAULT_FORMAT); + QVERIFY(!(format1c == format3c)); + QVERIFY(format1c != format3c); + + format4c = format1c; + QVERIFY(format1c == format4c); + QVERIFY(!(format1c != format4c)); + format4c.setInternalTextureFormat(DEFAULT_FORMAT); + QVERIFY(!(format1c == format4c)); + QVERIFY(format1c != format4c); +} + +void tst_QGL::testDontCrashOnDanglingResources() +{ + // We have a number of Q_GLOBAL_STATICS inside the QtOpenGL + // library. This test is verify that we don't crash as a result of + // them calling into libgl on application shutdown. + QWidget *widget = new UnclippedWidget(); + widget->show(); + qApp->processEvents(); + widget->hide(); +} + +class ReplaceClippingGLWidget : public QGLWidget +{ +public: + void paint(QPainter *painter) + { + painter->fillRect(rect(), Qt::white); + + QPainterPath path; + path.addRect(0, 0, 100, 100); + path.addRect(50, 50, 100, 100); + + painter->setClipRect(0, 0, 150, 150); + painter->fillPath(path, Qt::red); + + painter->translate(150, 150); + painter->setClipRect(0, 0, 150, 150); + painter->fillPath(path, Qt::red); + } + +protected: + void paintEvent(QPaintEvent*) + { + // clear the stencil with junk + glStencilMask(0xFFFF); + glClearStencil(0xFFFF); + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + glClear(GL_STENCIL_BUFFER_BIT); + + QPainter painter(this); + paint(&painter); + } +}; + +void tst_QGL::replaceClipping() +{ + ReplaceClippingGLWidget glw; +#ifdef Q_WS_QWS + glw.setWindowFlags(Qt::FramelessWindowHint); +#endif + glw.resize(300, 300); + glw.show(); + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(&glw); +#endif + QTest::qWait(200); + + QImage reference(300, 300, QImage::Format_RGB32); + QPainter referencePainter(&reference); + glw.paint(&referencePainter); + referencePainter.end(); + + const QImage widgetFB = glw.grabFrameBuffer(false).convertToFormat(QImage::Format_RGB32); + + // Sample pixels in a grid pattern which avoids false failures due to + // off-by-one pixel errors on some buggy GL implementations + for (int x = 25; x < reference.width(); x += 50) { + for (int y = 25; y < reference.width(); y += 50) { + QFUZZY_COMPARE_PIXELS(widgetFB.pixel(x, y), reference.pixel(x, y)); + } + } +} + +class ClipTestGLWidget : public QGLWidget +{ +public: + void paint(QPainter *painter) + { + painter->fillRect(-1, -1, width()+2, height()+2, Qt::white); + painter->setClipRect(10, 10, width()-20, height()-20); + painter->fillRect(rect(), Qt::cyan); + + painter->save(); + painter->setClipRect(10, 10, 100, 100, Qt::IntersectClip); + + painter->fillRect(rect(), Qt::blue); + + painter->save(); + painter->setClipRect(10, 10, 50, 50, Qt::IntersectClip); + painter->fillRect(rect(), Qt::red); + painter->restore(); + painter->fillRect(0, 0, 40, 40, Qt::white); + painter->save(); + + painter->setClipRect(0, 0, 35, 35, Qt::IntersectClip); + painter->fillRect(rect(), Qt::black); + painter->restore(); + + painter->fillRect(0, 0, 30, 30, Qt::magenta); + + painter->save(); + painter->setClipRect(60, 10, 50, 50, Qt::ReplaceClip); + painter->fillRect(rect(), Qt::green); + painter->restore(); + + painter->restore(); + + painter->translate(100, 100); + + { + QPainterPath path; + path.addRect(10, 10, 100, 100); + path.addRect(10, 10, 10, 10); + painter->setClipPath(path, Qt::IntersectClip); + } + + painter->fillRect(rect(), Qt::blue); + + painter->save(); + { + QPainterPath path; + path.addRect(10, 10, 50, 50); + path.addRect(10, 10, 10, 10); + painter->setClipPath(path, Qt::IntersectClip); + } + painter->fillRect(rect(), Qt::red); + painter->restore(); + painter->fillRect(0, 0, 40, 40, Qt::white); + painter->save(); + + { + QPainterPath path; + path.addRect(0, 0, 35, 35); + path.addRect(10, 10, 10, 10); + painter->setClipPath(path, Qt::IntersectClip); + } + painter->fillRect(rect(), Qt::black); + painter->restore(); + + painter->fillRect(0, 0, 30, 30, Qt::magenta); + + painter->save(); + { + QPainterPath path; + path.addRect(60, 10, 50, 50); + path.addRect(10, 10, 10, 10); + painter->setClipPath(path, Qt::ReplaceClip); + } + painter->fillRect(rect(), Qt::green); + painter->restore(); + } + +protected: + void paintEvent(QPaintEvent*) + { + QPainter painter(this); + paint(&painter); + } +}; + +void tst_QGL::clipTest() +{ + ClipTestGLWidget glw; +#ifdef Q_WS_QWS + glw.setWindowFlags(Qt::FramelessWindowHint); +#endif + glw.resize(220, 220); + glw.show(); + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(&glw); +#endif + QTest::qWait(200); + + QImage reference(glw.size(), QImage::Format_RGB32); + QPainter referencePainter(&reference); + glw.paint(&referencePainter); + referencePainter.end(); + + const QImage widgetFB = glw.grabFrameBuffer(false).convertToFormat(QImage::Format_RGB32); + + // Sample pixels in a grid pattern which avoids false failures due to + // off-by-one pixel errors on some buggy GL implementations + for (int x = 2; x < reference.width(); x += 5) { + for (int y = 2; y < reference.height(); y += 5) { + QFUZZY_COMPARE_PIXELS(widgetFB.pixel(x, y), reference.pixel(x, y)); + } + } +} + +void tst_QGL::destroyFBOAfterContext() +{ + if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) + QSKIP("QGLFramebufferObject not supported on this platform", SkipSingle); + + QGLWidget *glw = new QGLWidget(); + glw->makeCurrent(); + + // No multisample with combined depth/stencil attachment: + QGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + + // Don't complicate things by using NPOT: + QGLFramebufferObject *fbo = new QGLFramebufferObject(256, 128, fboFormat); + + // The handle should be valid until the context is destroyed. + QVERIFY(fbo->handle() != 0); + QVERIFY(fbo->isValid()); + + delete glw; + + // The handle should now be zero. + QVERIFY(fbo->handle() == 0); + QVERIFY(!fbo->isValid()); + + delete fbo; +} + +#ifdef QT_BUILD_INTERNAL + +class tst_QGLResource +{ +public: + tst_QGLResource(const QGLContext * = 0) {} + ~tst_QGLResource() { ++deletions; } + + static int deletions; +}; + +int tst_QGLResource::deletions = 0; + +#ifdef TODO +Q_GLOBAL_STATIC(QOpenGLContextGroupResource<tst_QGLResource>, qt_shared_test) +#endif //TODO +#endif + +void tst_QGL::shareRegister() +{ +#ifdef TODO +#ifdef QT_BUILD_INTERNAL + // Create a context. + QGLWidget *glw1 = new QGLWidget(); + glw1->makeCurrent(); + + // Nothing should be sharing with glw1's context yet. + QVERIFY(!glw1->isSharing()); + + // Create a guard for the first context. + QOpenGLSharedResourceGuard guard(glw1->context()->contextHandle()); + QVERIFY(guard.id() == 0); + guard.setId(3); + QVERIFY(guard.id() == 3); + + // Request a tst_QGLResource object for the first context. + tst_QGLResource *res1 = qt_shared_test()->value(glw1->context()->contextHandle()); + QVERIFY(res1); + QVERIFY(qt_shared_test()->value(glw1->context()->contextHandle()) == res1); + + // Create another context that shares with the first. + QVERIFY(!glw1->isSharing()); + QGLWidget *glw2 = new QGLWidget(0, glw1); + if (!glw2->isSharing()) { + delete glw2; + delete glw1; + QSKIP("Context sharing is not supported", SkipSingle); + } + QVERIFY(glw1->isSharing()); + QVERIFY(glw1->context() != glw2->context()); + + // Check that the first context's resource is also on the second. + QVERIFY(qt_shared_test()->value(glw1->context()) == res1); + QVERIFY(qt_shared_test()->value(glw2->context()) == res1); + + // Guard should still be the same. + QVERIFY(guard.context() == glw1->context()); + QVERIFY(guard.id() == 3); + + // Check the sharing relationships. + QVERIFY(QGLContext::areSharing(glw1->context(), glw1->context())); + QVERIFY(QGLContext::areSharing(glw2->context(), glw2->context())); + QVERIFY(QGLContext::areSharing(glw1->context(), glw2->context())); + QVERIFY(QGLContext::areSharing(glw2->context(), glw1->context())); + QVERIFY(!QGLContext::areSharing(0, glw2->context())); + QVERIFY(!QGLContext::areSharing(glw1->context(), 0)); + QVERIFY(!QGLContext::areSharing(0, 0)); + + // Create a third context, not sharing with the others. + QGLWidget *glw3 = new QGLWidget(); + QVERIFY(!glw3->isSharing()); + + // Create a guard on the standalone context. + QGLSharedResourceGuard guard3(glw3->context()); + guard3.setId(5); + + // Request a resource to the third context. + tst_QGLResource *res3 = qt_shared_test()->value(glw3->context()); + QVERIFY(res3); + QVERIFY(qt_shared_test()->value(glw1->context()) == res1); + QVERIFY(qt_shared_test()->value(glw2->context()) == res1); + QVERIFY(qt_shared_test()->value(glw3->context()) == res3); + + // Check the sharing relationships again. + QVERIFY(QGLContext::areSharing(glw1->context(), glw1->context())); + QVERIFY(QGLContext::areSharing(glw2->context(), glw2->context())); + QVERIFY(QGLContext::areSharing(glw1->context(), glw2->context())); + QVERIFY(QGLContext::areSharing(glw2->context(), glw1->context())); + QVERIFY(!QGLContext::areSharing(glw1->context(), glw3->context())); + QVERIFY(!QGLContext::areSharing(glw2->context(), glw3->context())); + QVERIFY(!QGLContext::areSharing(glw3->context(), glw1->context())); + QVERIFY(!QGLContext::areSharing(glw3->context(), glw2->context())); + QVERIFY(QGLContext::areSharing(glw3->context(), glw3->context())); + QVERIFY(!QGLContext::areSharing(0, glw2->context())); + QVERIFY(!QGLContext::areSharing(glw1->context(), 0)); + QVERIFY(!QGLContext::areSharing(0, glw3->context())); + QVERIFY(!QGLContext::areSharing(glw3->context(), 0)); + QVERIFY(!QGLContext::areSharing(0, 0)); + + // Shared guard should still be the same. + QVERIFY(guard.context() == glw1->context()); + QVERIFY(guard.id() == 3); + + // Delete the first context. + delete glw1; + + // The second context should no longer register as sharing. + QVERIFY(!glw2->isSharing()); + + // The first context's resource should transfer to the second context. + QCOMPARE(tst_QGLResource::deletions, 0); + QVERIFY(qt_shared_test()->value(glw2->context()) == res1); + QVERIFY(qt_shared_test()->value(glw3->context()) == res3); + + // Shared guard should now be the second context, with the id the same. + QVERIFY(guard.context() == glw2->context()); + QVERIFY(guard.id() == 3); + QVERIFY(guard3.context() == glw3->context()); + QVERIFY(guard3.id() == 5); + + // Clean up and check that the resources are properly deleted. + delete glw2; + QCOMPARE(tst_QGLResource::deletions, 1); + delete glw3; + QCOMPARE(tst_QGLResource::deletions, 2); + + // Guards should now be null and the id zero. + QVERIFY(guard.context() == 0); + QVERIFY(guard.id() == 0); + QVERIFY(guard3.context() == 0); + QVERIFY(guard3.id() == 0); +#endif +#endif //TODO +} + +// Tests QGLContext::bindTexture with default options +void tst_QGL::qglContextDefaultBindTexture() +{ +#ifdef QT_BUILD_INTERNAL + QGLWidget w; + w.makeCurrent(); + QGLContext *ctx = const_cast<QGLContext*>(w.context()); + + QImage *boundImage = new QImage(256, 256, QImage::Format_RGB32); + boundImage->fill(0xFFFFFFFF); + QPixmap *boundPixmap = new QPixmap(256, 256); + boundPixmap->fill(Qt::red); + + int startCacheItemCount = QGLTextureCache::instance()->size(); + + GLuint boundImageTextureId = ctx->bindTexture(*boundImage); + GLuint boundPixmapTextureId = ctx->bindTexture(*boundPixmap); + + // Make sure the image & pixmap have been added to the cache: + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+2); + + // Make sure the image & pixmap have the is_cached flag set: + QVERIFY(QImagePixmapCleanupHooks::isImageCached(*boundImage)); + QVERIFY(QImagePixmapCleanupHooks::isPixmapCached(*boundPixmap)); + + // Make sure the texture IDs returned are valid: + QCOMPARE((bool)glIsTexture(boundImageTextureId), GL_TRUE); + QCOMPARE((bool)glIsTexture(boundPixmapTextureId), GL_TRUE); + + // Make sure the textures are still valid after we delete the image/pixmap: + // Also check that although the textures are left intact, the cache entries are removed: + delete boundImage; + boundImage = 0; + QCOMPARE((bool)glIsTexture(boundImageTextureId), GL_TRUE); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + delete boundPixmap; + boundPixmap = 0; + QCOMPARE((bool)glIsTexture(boundPixmapTextureId), GL_TRUE); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + + // Finally, make sure QGLContext::deleteTexture deletes the texture IDs: + ctx->deleteTexture(boundImageTextureId); + ctx->deleteTexture(boundPixmapTextureId); + QCOMPARE((bool)glIsTexture(boundImageTextureId), GL_FALSE); + QCOMPARE((bool)glIsTexture(boundPixmapTextureId), GL_FALSE); +#endif +} + +void tst_QGL::textureCleanup() +{ +#ifdef QT_BUILD_INTERNAL + QGLWidget w; + w.resize(200,200); + w.show(); + w.makeCurrent(); + + // Test pixmaps which have been loaded via QPixmapCache are removed from the texture cache + // when the pixmap cache is cleared + { + int startCacheItemCount = QGLTextureCache::instance()->size(); + QPainter p(&w); + + QPixmap boundPixmap(":designer.png"); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + + p.drawPixmap(0, 0, boundPixmap); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + // Need to call end for the GL2 paint engine to release references to pixmap if using tfp + p.end(); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + // Check that the texture doesn't get removed from the cache when the pixmap is cleared + // as it should still be in the cache: + boundPixmap = QPixmap(); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + QPixmapCache::clear(); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + } + + // Test pixmaps which have been loaded via QPixmapCache are removed from the texture cache + // when they are explicitly removed from the pixmap cache + { + int startCacheItemCount = QGLTextureCache::instance()->size(); + QPainter p(&w); + + QPixmap boundPixmap(128, 128); + QString cacheKey = QString::fromLatin1("myPixmap"); + QPixmapCache::insert(cacheKey, boundPixmap); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + + p.drawPixmap(0, 0, boundPixmap); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + // Need to call end for the GL2 paint engine to release references to pixmap if using tfp + p.end(); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + // Check that the texture doesn't get removed from the cache when the pixmap is cleared + // as it should still be in the cache: + boundPixmap = QPixmap(); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + // Finally, we check that the texture cache entry is removed when we remove the + // pixmap cache entry, which should hold the last reference: + QPixmapCache::remove(cacheKey); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + } + + // Check images & pixmaps are removed from the cache when they are deleted + { + int startCacheItemCount = QGLTextureCache::instance()->size(); + QPainter p(&w); + + QImage *boundImage = new QImage(256, 256, QImage::Format_RGB32); + boundImage->fill(0xFFFFFFFF); + QPixmap *boundPixmap = new QPixmap(256, 256); + boundPixmap->fill(Qt::red); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + + p.drawImage(0, 0, *boundImage); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + p.drawPixmap(0, 0, *boundPixmap); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+2); + + // Need to call end for the GL2 paint engine to release references to pixmap if using tfp + p.end(); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+2); + + delete boundImage; + boundImage = 0; + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + delete boundPixmap; + boundPixmap = 0; + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + } + + // Check images & pixmaps are removed from the cache when they are assigned to + { + int startCacheItemCount = QGLTextureCache::instance()->size(); + QPainter p(&w); + + QImage boundImage(256, 256, QImage::Format_RGB32); + boundImage.fill(0xFFFFFFFF); + QPixmap boundPixmap(256, 256); + boundPixmap.fill(Qt::red); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + + p.drawImage(0, 0, boundImage); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + p.drawPixmap(0, 0, boundPixmap); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+2); + + // Need to call end for the GL2 paint engine to release references to pixmap if using tfp + p.end(); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+2); + + boundImage = QImage(64, 64, QImage::Format_RGB32); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + boundPixmap = QPixmap(64, 64); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + } + + // Check images & pixmaps are removed from the cache when they are modified (detached) + { + int startCacheItemCount = QGLTextureCache::instance()->size(); + QPainter p(&w); + + QImage boundImage(256, 256, QImage::Format_RGB32); + boundImage.fill(0xFFFFFFFF); + QPixmap boundPixmap(256, 256); + boundPixmap.fill(Qt::red); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + + p.drawImage(0, 0, boundImage); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + p.drawPixmap(0, 0, boundPixmap); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+2); + + // Need to call end for the GL2 paint engine to release references to pixmap if using tfp + p.end(); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+2); + + boundImage.fill(0x00000000); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + boundPixmap.fill(Qt::blue); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + } + + // Check that images/pixmaps aren't removed from the cache if a shallow copy has been made + QImage copyOfImage; + QPixmap copyOfPixmap; + int startCacheItemCount = QGLTextureCache::instance()->size(); + { + QPainter p(&w); + + QImage boundImage(256, 256, QImage::Format_RGB32); + boundImage.fill(0xFFFFFFFF); + QPixmap boundPixmap(256, 256); + boundPixmap.fill(Qt::red); + + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); + + p.drawImage(0, 0, boundImage); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + p.drawPixmap(0, 0, boundPixmap); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+2); + + // Need to call end for the GL2 paint engine to release references to pixmap if using tfp + p.end(); + + copyOfImage = boundImage; + copyOfPixmap = boundPixmap; + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+2); + } // boundImage & boundPixmap would have been deleted when they went out of scope + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+2); + + copyOfImage = QImage(); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount+1); + + copyOfPixmap = QPixmap(); + QCOMPARE(QGLTextureCache::instance()->size(), startCacheItemCount); +#endif +} + +namespace ThreadImages { + +class Producer : public QObject +{ + Q_OBJECT +public: + Producer() + { + startTimer(20); + + QThread *thread = new QThread; + thread->start(); + + connect(this, SIGNAL(destroyed()), thread, SLOT(quit())); + + moveToThread(thread); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + } + +signals: + void imageReady(const QImage &image); + +protected: + void timerEvent(QTimerEvent *) + { + QImage image(256, 256, QImage::Format_RGB32); + QLinearGradient g(0, 0, 0, 256); + g.setColorAt(0, QColor(255, 180, 180)); + g.setColorAt(1, Qt::white); + g.setSpread(QGradient::ReflectSpread); + + QBrush brush(g); + brush.setTransform(QTransform::fromTranslate(0, delta)); + delta += 10; + + QPainter p(&image); + p.fillRect(image.rect(), brush); + + if (images.size() > 10) + images.removeFirst(); + + images.append(image); + + emit imageReady(image); + } + +private: + QList<QImage> images; + int delta; +}; + + +class DisplayWidget : public QGLWidget +{ + Q_OBJECT +public: + DisplayWidget(QWidget *parent) : QGLWidget(parent) {} + void paintEvent(QPaintEvent *) + { + QPainter p(this); + p.drawImage(rect(), m_image); + } + +public slots: + void setImage(const QImage &image) + { + m_image = image; + update(); + } + +private: + QImage m_image; +}; + +class Widget : public QWidget +{ + Q_OBJECT +public: + Widget() + : iterations(0) + , display(0) + , producer(new Producer) + { + startTimer(400); + connect(this, SIGNAL(destroyed()), producer, SLOT(deleteLater())); + } + + int iterations; + +protected: + void timerEvent(QTimerEvent *) + { + ++iterations; + + delete display; + display = new DisplayWidget(this); + connect(producer, SIGNAL(imageReady(const QImage &)), display, SLOT(setImage(const QImage &))); + + display->setGeometry(rect()); + display->show(); + } + +private: + DisplayWidget *display; + Producer *producer; +}; + +} + +void tst_QGL::threadImages() +{ + ThreadImages::Widget *widget = new ThreadImages::Widget; + widget->show(); + + while (widget->iterations <= 5) { + qApp->processEvents(); + } + + delete widget; +} + +void tst_QGL::nullRectCrash() +{ + if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) + QSKIP("QGLFramebufferObject not supported on this platform", SkipSingle); + + QGLWidget glw; + glw.makeCurrent(); + + QGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + + QGLFramebufferObject *fbo = new QGLFramebufferObject(128, 128, fboFormat); + + QPainter fboPainter(fbo); + + fboPainter.setPen(QPen(QColor(255, 127, 127, 127), 2)); + fboPainter.setBrush(QColor(127, 255, 127, 127)); + fboPainter.drawRect(QRectF()); + + fboPainter.end(); +} + +class tst_QGLDummy : public QObject +{ +Q_OBJECT + +public: + tst_QGLDummy() {} + +private slots: + void qglSkipTests() { + QSKIP("QGL not supported on this system.", SkipAll); + } +}; + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + QTEST_DISABLE_KEYPAD_NAVIGATION \ + QGLWidget glWidget; + if (!glWidget.isValid()) { + tst_QGLDummy tc; + return QTest::qExec(&tc, argc, argv); + } + tst_QGL tc; + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_qgl.moc" diff --git a/tests/auto/opengl/qglbuffer/qglbuffer.pro b/tests/auto/opengl/qglbuffer/qglbuffer.pro new file mode 100644 index 0000000000..48bd966797 --- /dev/null +++ b/tests/auto/opengl/qglbuffer/qglbuffer.pro @@ -0,0 +1,11 @@ +############################################################ +# Project file for autotest for file qglbuffer.h +############################################################ + +load(qttest_p4) +requires(contains(QT_CONFIG,opengl)) +QT += opengl widgets + +win32:!wince*: DEFINES += QT_NO_EGL + +SOURCES += tst_qglbuffer.cpp diff --git a/tests/auto/opengl/qglbuffer/tst_qglbuffer.cpp b/tests/auto/opengl/qglbuffer/tst_qglbuffer.cpp new file mode 100644 index 0000000000..9215b5fedd --- /dev/null +++ b/tests/auto/opengl/qglbuffer/tst_qglbuffer.cpp @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** 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$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QtOpenGL/qgl.h> +#include <QtOpenGL/qglbuffer.h> + +class tst_QGLBuffer : public QObject +{ + Q_OBJECT +public: + tst_QGLBuffer() {} + ~tst_QGLBuffer() {} + +private slots: + void vertexBuffer_data(); + void vertexBuffer(); + void indexBuffer_data(); + void indexBuffer(); + void bufferSharing(); + +private: + void testBuffer(QGLBuffer::Type type); +}; + +void tst_QGLBuffer::vertexBuffer_data() +{ + QTest::addColumn<int>("usagePattern"); + + QTest::newRow("StreamDraw") << int(QGLBuffer::StreamDraw); + QTest::newRow("StaticDraw") << int(QGLBuffer::StaticDraw); + QTest::newRow("DynamicDraw") << int(QGLBuffer::DynamicDraw); +} + +void tst_QGLBuffer::vertexBuffer() +{ + testBuffer(QGLBuffer::VertexBuffer); +} + +void tst_QGLBuffer::indexBuffer_data() +{ + vertexBuffer_data(); +} + +void tst_QGLBuffer::indexBuffer() +{ + testBuffer(QGLBuffer::IndexBuffer); +} + +void tst_QGLBuffer::testBuffer(QGLBuffer::Type type) +{ + QFETCH(int, usagePattern); + + QGLWidget w; + w.makeCurrent(); + + // Create the local object, but not the buffer in the server. + QGLBuffer buffer(type); + QVERIFY(buffer.usagePattern() == QGLBuffer::StaticDraw); + buffer.setUsagePattern(QGLBuffer::UsagePattern(usagePattern)); + + // Check the initial state. + QVERIFY(buffer.type() == type); + QVERIFY(!buffer.isCreated()); + QVERIFY(buffer.bufferId() == 0); + QVERIFY(buffer.usagePattern() == QGLBuffer::UsagePattern(usagePattern)); + QCOMPARE(buffer.size(), -1); + + // Should not be able to bind it yet because it isn't created. + QVERIFY(!buffer.bind()); + + // Create the buffer - if this fails, then assume that the + // GL implementation does not support buffers at all. + if (!buffer.create()) + QSKIP("Buffers are not supported on this platform", SkipAll); + + // Should now have a buffer id. + QVERIFY(buffer.bufferId() != 0); + + // Bind the buffer and upload some data. + QVERIFY(buffer.bind()); + static GLfloat const data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + buffer.allocate(data, sizeof(data)); + + // Check the buffer size. + QCOMPARE(buffer.size(), int(sizeof(data))); + + // Map the buffer and read back its contents. + bool haveMap = false; + GLfloat *mapped = reinterpret_cast<GLfloat *> + (buffer.map(QGLBuffer::ReadOnly)); + if (mapped) { + for (int index = 0; index < 9; ++index) + QCOMPARE(mapped[index], data[index]); + buffer.unmap(); + haveMap = true; + } else { + qWarning("QGLBuffer::map() is not supported on this platform"); + } + + // Read back the buffer contents using read(). + bool haveRead = false; + GLfloat readdata[9]; + if (buffer.read(0, readdata, sizeof(readdata))) { + for (int index = 0; index < 9; ++index) + QCOMPARE(readdata[index], data[index]); + haveRead = true; + } else { + qWarning("QGLBuffer::read() is not supported on this platform"); + } + + // Write some different data to a specific location and check it. + static GLfloat const diffdata[] = {11, 12, 13}; + buffer.write(sizeof(GLfloat) * 3, diffdata, sizeof(diffdata)); + if (haveMap) { + mapped = reinterpret_cast<GLfloat *>(buffer.map(QGLBuffer::ReadOnly)); + QVERIFY(mapped != 0); + for (int index = 0; index < 9; ++index) { + if (index >= 3 && index <= 5) + QCOMPARE(mapped[index], diffdata[index - 3]); + else + QCOMPARE(mapped[index], data[index]); + } + buffer.unmap(); + } + if (haveRead) { + QVERIFY(buffer.read(0, readdata, sizeof(readdata))); + for (int index = 0; index < 9; ++index) { + if (index >= 3 && index <= 5) + QCOMPARE(readdata[index], diffdata[index - 3]); + else + QCOMPARE(readdata[index], data[index]); + } + } + + // Write to the buffer using the return value from map. + if (haveMap) { + mapped = reinterpret_cast<GLfloat *>(buffer.map(QGLBuffer::WriteOnly)); + QVERIFY(mapped != 0); + mapped[6] = 14; + buffer.unmap(); + + mapped = reinterpret_cast<GLfloat *>(buffer.map(QGLBuffer::ReadOnly)); + QVERIFY(mapped != 0); + static GLfloat const diff2data[] = {11, 12, 13, 14}; + for (int index = 0; index < 9; ++index) { + if (index >= 3 && index <= 6) + QCOMPARE(mapped[index], diff2data[index - 3]); + else + QCOMPARE(mapped[index], data[index]); + } + buffer.unmap(); + } + + // Resize the buffer. + buffer.allocate(sizeof(GLfloat) * 20); + QCOMPARE(buffer.size(), int(sizeof(GLfloat) * 20)); + buffer.allocate(0, sizeof(GLfloat) * 32); + QCOMPARE(buffer.size(), int(sizeof(GLfloat) * 32)); + + // Release the buffer. + buffer.release(); +} + +void tst_QGLBuffer::bufferSharing() +{ + QGLWidget *w1 = new QGLWidget(); + w1->makeCurrent(); + + QGLWidget *w2 = new QGLWidget(0, w1); + if (!w2->isSharing()) { + delete w2; + delete w1; + QSKIP("Context sharing is not supported on this platform", SkipSingle); + } + + // Bind the buffer in the first context and write some data to it. + QGLBuffer buffer(QGLBuffer::VertexBuffer); + if (!buffer.create()) + QSKIP("Buffers are not supported on this platform", SkipSingle); + QVERIFY(buffer.isCreated()); + QVERIFY(buffer.bind()); + static GLfloat const data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + buffer.allocate(data, sizeof(data)); + QCOMPARE(buffer.size(), int(sizeof(data))); + buffer.release(); + + // Bind the buffer in the second context and read back the data. + w2->makeCurrent(); + QVERIFY(buffer.bind()); + QCOMPARE(buffer.size(), int(sizeof(data))); + GLfloat readdata[9]; + if (buffer.read(0, readdata, sizeof(readdata))) { + for (int index = 0; index < 9; ++index) + QCOMPARE(readdata[index], data[index]); + } + buffer.release(); + + // Delete the first context. + delete w1; + + // Make the second context current again because deleting the first + // one will call doneCurrent() even though it wasn't current! + w2->makeCurrent(); + + // The buffer should still be valid in the second context. + QVERIFY(buffer.bufferId() != 0); + QVERIFY(buffer.isCreated()); + QVERIFY(buffer.bind()); + QCOMPARE(buffer.size(), int(sizeof(data))); + buffer.release(); + + // Delete the second context. + delete w2; + + // The buffer should now be invalid. + QVERIFY(buffer.bufferId() == 0); + QVERIFY(!buffer.isCreated()); +} + +QTEST_MAIN(tst_QGLBuffer) + +#include "tst_qglbuffer.moc" diff --git a/tests/auto/opengl/qglfunctions/qglfunctions.pro b/tests/auto/opengl/qglfunctions/qglfunctions.pro new file mode 100644 index 0000000000..9fe8de2b23 --- /dev/null +++ b/tests/auto/opengl/qglfunctions/qglfunctions.pro @@ -0,0 +1,7 @@ +load(qttest_p4) +requires(contains(QT_CONFIG,opengl)) +QT += opengl widgets + +win32:!wince*: DEFINES += QT_NO_EGL + +SOURCES += tst_qglfunctions.cpp diff --git a/tests/auto/opengl/qglfunctions/tst_qglfunctions.cpp b/tests/auto/opengl/qglfunctions/tst_qglfunctions.cpp new file mode 100644 index 0000000000..ec87f6f857 --- /dev/null +++ b/tests/auto/opengl/qglfunctions/tst_qglfunctions.cpp @@ -0,0 +1,244 @@ +/**************************************************************************** +** +** 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$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QtOpenGL/qglfunctions.h> + +class tst_QGLFunctions : public QObject +{ + Q_OBJECT +public: + tst_QGLFunctions() {} + ~tst_QGLFunctions() {} + +private slots: + void features(); + void multitexture(); + void blendColor(); + +private: + static bool hasExtension(const char *name); +}; + +bool tst_QGLFunctions::hasExtension(const char *name) +{ + QString extensions = + QString::fromLatin1 + (reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS))); + return extensions.split(QLatin1Char(' ')).contains + (QString::fromLatin1(name)); +} + +// Check that the reported features are consistent with the platform. +void tst_QGLFunctions::features() +{ + // Before being associated with a context, there should be + // no features enabled. + QGLFunctions funcs; + QVERIFY(!funcs.openGLFeatures()); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::Multitexture)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::Shaders)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::Buffers)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::Framebuffers)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::BlendColor)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::BlendEquation)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::BlendEquationSeparate)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::BlendFuncSeparate)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::BlendSubtract)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::CompressedTextures)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::Multisample)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::StencilSeparate)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::NPOTTextures)); + + // Make a context current. + QGLWidget glw; + if (!glw.isValid()) + QSKIP("Could not create a GL context", SkipAll); + glw.makeCurrent(); + funcs.initializeGLFunctions(); + + // Validate the features against what we expect for this platform. +#if defined(QT_OPENGL_ES_2) + QGLFunctions::OpenGLFeatures allFeatures = + (QGLFunctions::Multitexture | + QGLFunctions::Shaders | + QGLFunctions::Buffers | + QGLFunctions::Framebuffers | + QGLFunctions::BlendColor | + QGLFunctions::BlendEquation | + QGLFunctions::BlendEquationSeparate | + QGLFunctions::BlendFuncSeparate | + QGLFunctions::BlendSubtract | + QGLFunctions::CompressedTextures | + QGLFunctions::Multisample | + QGLFunctions::StencilSeparate | + QGLFunctions::NPOTTextures); + QVERIFY((funcs.openGLFeatures() & allFeatures) == allFeatures); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::Multitexture)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::Shaders)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::Buffers)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::Framebuffers)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::BlendColor)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::BlendEquation)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::BlendEquationSeparate)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::BlendFuncSeparate)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::BlendSubtract)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::CompressedTextures)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::Multisample)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::StencilSeparate)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::NPOTTextures)); +#elif defined(QT_OPENGL_ES) + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::Multitexture)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::Buffers)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::CompressedTextures)); + QVERIFY(funcs.hasOpenGLFeature(QGLFunctions::Multisample)); + + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::Shaders)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::BlendColor)); + QVERIFY(!funcs.hasOpenGLFeature(QGLFunctions::StencilSeparate)); + + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::Framebuffers), + hasExtension("GL_OES_framebuffer_object")); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::BlendEquationSeparate), + hasExtension("GL_OES_blend_equation_separate")); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::BlendFuncSeparate), + hasExtension("GL_OES_blend_func_separate")); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::BlendSubtract), + hasExtension("GL_OES_blend_subtract")); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::NPOTTextures), + hasExtension("GL_OES_texture_npot")); +#else + // We check for both the extension name and the minimum OpenGL version + // for the feature. This will help us catch situations where a platform + // doesn't list an extension by name but does have the feature by virtue + // of its version number. + QGLFormat::OpenGLVersionFlags versions = QGLFormat::openGLVersionFlags(); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::Multitexture), + hasExtension("GL_ARB_multitexture") || + (versions & QGLFormat::OpenGL_Version_1_3) != 0); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::Shaders), + hasExtension("GL_ARB_shader_objects") || + (versions & QGLFormat::OpenGL_Version_2_0) != 0); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::Buffers), + (versions & QGLFormat::OpenGL_Version_1_5) != 0); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::Framebuffers), + hasExtension("GL_EXT_framebuffer_object") || + hasExtension("GL_ARB_framebuffer_object")); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::BlendColor), + hasExtension("GL_EXT_blend_color") || + (versions & QGLFormat::OpenGL_Version_1_2) != 0); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::BlendEquation), + (versions & QGLFormat::OpenGL_Version_1_2) != 0); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::BlendEquationSeparate), + hasExtension("GL_EXT_blend_equation_separate") || + (versions & QGLFormat::OpenGL_Version_2_0) != 0); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::BlendFuncSeparate), + hasExtension("GL_EXT_blend_func_separate") || + (versions & QGLFormat::OpenGL_Version_1_4) != 0); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::BlendSubtract), + hasExtension("GL_EXT_blend_subtract")); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::CompressedTextures), + hasExtension("GL_ARB_texture_compression") || + (versions & QGLFormat::OpenGL_Version_1_3) != 0); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::Multisample), + hasExtension("GL_ARB_multisample") || + (versions & QGLFormat::OpenGL_Version_1_3) != 0); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::StencilSeparate), + (versions & QGLFormat::OpenGL_Version_2_0) != 0); + QCOMPARE(funcs.hasOpenGLFeature(QGLFunctions::NPOTTextures), + hasExtension("GL_ARB_texture_non_power_of_two") || + (versions & QGLFormat::OpenGL_Version_2_0) != 0); +#endif +} + +// Verify that the multitexture functions appear to resolve and work. +void tst_QGLFunctions::multitexture() +{ + QGLFunctions funcs; + QGLWidget glw; + if (!glw.isValid()) + QSKIP("Could not create a GL context", SkipAll); + glw.makeCurrent(); + funcs.initializeGLFunctions(); + + if (!funcs.hasOpenGLFeature(QGLFunctions::Multitexture)) + QSKIP("Multitexture functions are not supported", SkipSingle); + + funcs.glActiveTexture(GL_TEXTURE1); + + GLint active = 0; + glGetIntegerv(GL_ACTIVE_TEXTURE, &active); + QVERIFY(active == GL_TEXTURE1); + + funcs.glActiveTexture(GL_TEXTURE0); + + active = 0; + glGetIntegerv(GL_ACTIVE_TEXTURE, &active); + QVERIFY(active == GL_TEXTURE0); +} + +// Verify that the glBlendColor() function appears to resolve and work. +void tst_QGLFunctions::blendColor() +{ + QGLFunctions funcs; + QGLWidget glw; + if (!glw.isValid()) + QSKIP("Could not create a GL context", SkipAll); + glw.makeCurrent(); + funcs.initializeGLFunctions(); + + if (!funcs.hasOpenGLFeature(QGLFunctions::BlendColor)) + QSKIP("glBlendColor() is not supported", SkipSingle); + + funcs.glBlendColor(0.0f, 1.0f, 0.0f, 1.0f); + + GLfloat colors[4] = {0.5f, 0.5f, 0.5f, 0.5f}; + glGetFloatv(GL_BLEND_COLOR, colors); + + QCOMPARE(colors[0], 0.0f); + QCOMPARE(colors[1], 1.0f); + QCOMPARE(colors[2], 0.0f); + QCOMPARE(colors[3], 1.0f); +} + +QTEST_MAIN(tst_QGLFunctions) + +#include "tst_qglfunctions.moc" diff --git a/tests/auto/opengl/qglthreads/qglthreads.pro b/tests/auto/opengl/qglthreads/qglthreads.pro new file mode 100644 index 0000000000..d2fd31411f --- /dev/null +++ b/tests/auto/opengl/qglthreads/qglthreads.pro @@ -0,0 +1,14 @@ +load(qttest_p4) +requires(contains(QT_CONFIG,opengl)) +QT += opengl widgets + +win32:!wince*: DEFINES += QT_NO_EGL + +HEADERS += tst_qglthreads.h +SOURCES += tst_qglthreads.cpp + +x11 { + LIBS += $$QMAKE_LIBS_X11 +} + +CONFIG+=insignificant_test diff --git a/tests/auto/opengl/qglthreads/tst_qglthreads.cpp b/tests/auto/opengl/qglthreads/tst_qglthreads.cpp new file mode 100644 index 0000000000..ee05e406c8 --- /dev/null +++ b/tests/auto/opengl/qglthreads/tst_qglthreads.cpp @@ -0,0 +1,758 @@ +/**************************************************************************** +** +** 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 test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QtCore/QtCore> +#include <QtGui/QtGui> +#include <QtWidgets/QApplication> +#include <QtOpenGL/QtOpenGL> +#include "tst_qglthreads.h" + +#ifdef Q_OS_SYMBIAN +#define RUNNING_TIME 2000 // save GPU mem by running shorter time. +#else +#define RUNNING_TIME 5000 +#endif + + + + +tst_QGLThreads::tst_QGLThreads(QObject *parent) + : QObject(parent) +{ +} + +/* + + swapInThread + + The purpose of this testcase is to verify that it is possible to do rendering into + a GL context from the GUI thread, then swap the contents in from a background thread. + + The usecase for this is to have the background thread do the waiting for vertical + sync while the GUI thread is idle. + + Currently the locking is handled directly in the paintEvent(). For the actual usecase + in Qt, the locking is done in the windowsurface before starting any drawing while + unlocking is done after all drawing has been done. + */ + + +class SwapThread : public QThread +{ + Q_OBJECT +public: + SwapThread(QGLWidget *widget) + : m_widget(widget) + { + moveToThread(this); + } + + void run() { + QTime time; + time.start(); + while (time.elapsed() < RUNNING_TIME) { + lock(); + wait(); + + m_widget->makeCurrent(); + m_widget->swapBuffers(); + m_widget->doneCurrent(); + unlock(); + } + } + + void lock() { m_mutex.lock(); } + void unlock() { m_mutex.unlock(); } + + void wait() { m_wait_condition.wait(&m_mutex); } + void notify() { m_wait_condition.wakeAll(); } + +private: + QGLWidget *m_widget; + QMutex m_mutex; + QWaitCondition m_wait_condition; +}; + +class ForegroundWidget : public QGLWidget +{ +public: + ForegroundWidget(const QGLFormat &format) + : QGLWidget(format), m_thread(0) + { + setAutoBufferSwap(false); + } + + void paintEvent(QPaintEvent *) + { + m_thread->lock(); + makeCurrent(); + QPainter p(this); + p.fillRect(rect(), QColor(rand() % 256, rand() % 256, rand() % 256)); + p.setPen(Qt::red); + p.setFont(QFont("SansSerif", 24)); + p.drawText(rect(), Qt::AlignCenter, "This is an autotest"); + p.end(); + doneCurrent(); + m_thread->notify(); + m_thread->unlock(); + + update(); + } + + void setThread(SwapThread *thread) { + m_thread = thread; + } + + SwapThread *m_thread; +}; + +void tst_QGLThreads::swapInThread() +{ +#ifdef Q_OS_MAC + QSKIP("OpenGL threading tests are currently disabled on mac as they were causing reboots", SkipAll); +#endif + + QGLFormat format; + format.setSwapInterval(1); + ForegroundWidget widget(format); + SwapThread thread(&widget); + widget.setThread(&thread); + widget.show(); + + QTest::qWaitForWindowShown(&widget); + thread.start(); + + while (thread.isRunning()) { + qApp->processEvents(); + } + + widget.hide(); + + QVERIFY(true); +} + + + + + + + +/* + textureUploadInThread + + The purpose of this testcase is to verify that doing texture uploads in a background + thread is possible and that it works. + */ + +class CreateAndUploadThread : public QThread +{ + Q_OBJECT +public: + CreateAndUploadThread(QGLWidget *shareWidget) + { + m_gl = new QGLWidget(0, shareWidget); + moveToThread(this); + } + + ~CreateAndUploadThread() + { + delete m_gl; + } + + void run() { + m_gl->makeCurrent(); + QTime time; + time.start(); + while (time.elapsed() < RUNNING_TIME) { + int width = 400; + int height = 300; +#ifdef Q_OS_SYMBIAN + // GPU mem is very scarce resource on Symbian currently. + // That's why we create only small textures. + width = 50; + height = 20; +#endif + QImage image(width, height, QImage::Format_RGB32); + QPainter p(&image); + p.fillRect(image.rect(), QColor(rand() % 256, rand() % 256, rand() % 256)); + p.setPen(Qt::red); + p.setFont(QFont("SansSerif", 24)); + p.drawText(image.rect(), Qt::AlignCenter, "This is an autotest"); + p.end(); + m_gl->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption); + + createdAndUploaded(image); + } + } + +signals: + void createdAndUploaded(const QImage &image); + +private: + QGLWidget *m_gl; +}; + +class TextureDisplay : public QGLWidget +{ + Q_OBJECT +public: + void paintEvent(QPaintEvent *) { + QPainter p(this); + for (int i=0; i<m_images.size(); ++i) { + p.drawImage(m_positions.at(i), m_images.at(i)); + m_positions[i] += QPoint(1, 1); + } + update(); + } + +public slots: + void receiveImage(const QImage &image) { + m_images << image; + m_positions << QPoint(-rand() % width() / 2, -rand() % height() / 2); + + if (m_images.size() > 100) { + m_images.takeFirst(); + m_positions.takeFirst(); + } + } + +private: + QList <QImage> m_images; + QList <QPoint> m_positions; +}; + +void tst_QGLThreads::textureUploadInThread() +{ +#ifdef Q_OS_MAC + QSKIP("OpenGL threading tests are currently disabled on mac as they were causing reboots", SkipAll); +#endif + + TextureDisplay display; + CreateAndUploadThread thread(&display); + + connect(&thread, SIGNAL(createdAndUploaded(QImage)), &display, SLOT(receiveImage(QImage))); + + display.show(); + QTest::qWaitForWindowShown(&display); + + thread.start(); + + while (thread.isRunning()) { + qApp->processEvents(); + } + + QVERIFY(true); +} + + + + + + +/* + renderInThread + + This test sets up a scene and renders it in a different thread. + For simplicity, the scene is simply a bunch of rectangles, but + if that works, we're in good shape.. + */ + +static inline float qrandom() { return (rand() % 100) / 100.f; } + +void renderAScene(int w, int h) +{ +#ifdef QT_OPENGL_ES_2 + QGLShaderProgram program; + program.addShaderFromSourceCode(QGLShader::Vertex, "attribute highp vec2 pos; void main() { gl_Position = vec4(pos.xy, 1.0, 1.0); }"); + program.addShaderFromSourceCode(QGLShader::Fragment, "uniform lowp vec4 color; void main() { gl_FragColor = color; }"); + program.bindAttributeLocation("pos", 0); + program.bind(); + int colorId = program.uniformLocation("color"); + + glEnableVertexAttribArray(0); + + for (int i=0; i<1000; ++i) { + GLfloat pos[] = { + (rand() % 100) / 100., + (rand() % 100) / 100., + (rand() % 100) / 100., + (rand() % 100) / 100., + (rand() % 100) / 100., + (rand() % 100) / 100. + }; + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 3); + } +#else + glViewport(0, 0, w, h); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glFrustum(0, w, h, 0, 1, 100); + glTranslated(0, 0, -1); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + for (int i=0;i<1000; ++i) { + glBegin(GL_TRIANGLES); + glColor3f(qrandom(), qrandom(), qrandom()); + glVertex2f(qrandom() * w, qrandom() * h); + glColor3f(qrandom(), qrandom(), qrandom()); + glVertex2f(qrandom() * w, qrandom() * h); + glColor3f(qrandom(), qrandom(), qrandom()); + glVertex2f(qrandom() * w, qrandom() * h); + glEnd(); + } +#endif +} + +class ThreadSafeGLWidget : public QGLWidget +{ +public: + ThreadSafeGLWidget(QWidget *parent = 0) : QGLWidget(parent) {} + void paintEvent(QPaintEvent *) + { + // ignored as we're anyway swapping as fast as we can + }; + + void resizeEvent(QResizeEvent *e) + { + mutex.lock(); + newSize = e->size(); + mutex.unlock(); + }; + + QMutex mutex; + QSize newSize; +}; + +class SceneRenderingThread : public QThread +{ + Q_OBJECT +public: + SceneRenderingThread(ThreadSafeGLWidget *widget) + : m_widget(widget) + { + moveToThread(this); + m_size = widget->size(); + } + + void run() { + QTime time; + time.start(); + failure = false; + + m_widget->makeCurrent(); + + while (time.elapsed() < RUNNING_TIME && !failure) { + + + m_widget->mutex.lock(); + QSize s = m_widget->newSize; + m_widget->mutex.unlock(); + + if (s != m_size) { + glViewport(0, 0, s.width(), s.height()); + } + + if (QGLContext::currentContext() != m_widget->context()) { + failure = true; + break; + } + + glClear(GL_COLOR_BUFFER_BIT); + + int w = m_widget->width(); + int h = m_widget->height(); + + renderAScene(w, h); + + int color; + glReadPixels(w / 2, h / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &color); + + m_widget->swapBuffers(); + } + + m_widget->doneCurrent(); + } + + bool failure; + +private: + ThreadSafeGLWidget *m_widget; + QSize m_size; +}; + +void tst_QGLThreads::renderInThread_data() +{ + QTest::addColumn<bool>("resize"); + QTest::addColumn<bool>("update"); + + QTest::newRow("basic") << false << false; + QTest::newRow("with-resize") << true << false; + QTest::newRow("with-update") << false << true; + QTest::newRow("with-resize-and-update") << true << true; +} + +void tst_QGLThreads::renderInThread() +{ +#ifdef Q_OS_MAC + QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots", SkipAll); +#endif + +#ifdef Q_OS_SYMBIAN + QSKIP("OpenGL threading tests are disabled on Symbian as accessing RWindow from a secondary thread is not supported", SkipAll); +#endif + + QFETCH(bool, resize); + QFETCH(bool, update); + + ThreadSafeGLWidget widget; + widget.resize(200, 200); + SceneRenderingThread thread(&widget); + + widget.show(); + QTest::qWaitForWindowShown(&widget); + widget.doneCurrent(); + + thread.start(); + + int value = 10; + while (thread.isRunning()) { + if (resize) + widget.resize(200 + value, 200 + value); + if (update) + widget.update(100 + value, 100 + value, 20, 20); + qApp->processEvents(); + value = -value; + + QThread::msleep(100); + } + + QVERIFY(!thread.failure); +} + +class Device +{ +public: + virtual ~Device() {} + virtual QPaintDevice *realPaintDevice() = 0; + virtual void prepareDevice() {} +}; + +class GLWidgetWrapper : public Device +{ +public: + GLWidgetWrapper() { + widget.resize(150, 150); + widget.show(); + QTest::qWaitForWindowShown(&widget); + widget.doneCurrent(); + } + QPaintDevice *realPaintDevice() { return &widget; } + + ThreadSafeGLWidget widget; +}; + +class PixmapWrapper : public Device +{ +public: + PixmapWrapper() { pixmap = new QPixmap(512, 512); } + ~PixmapWrapper() { delete pixmap; } + QPaintDevice *realPaintDevice() { return pixmap; } + + QPixmap *pixmap; +}; + +class PixelBufferWrapper : public Device +{ +public: + PixelBufferWrapper() { pbuffer = new QGLPixelBuffer(512, 512); } + ~PixelBufferWrapper() { delete pbuffer; } + QPaintDevice *realPaintDevice() { return pbuffer; } + + QGLPixelBuffer *pbuffer; +}; + + +class FrameBufferObjectWrapper : public Device +{ +public: + FrameBufferObjectWrapper() { + widget.makeCurrent(); + fbo = new QGLFramebufferObject(512, 512); + widget.doneCurrent(); + } + ~FrameBufferObjectWrapper() { delete fbo; } + QPaintDevice *realPaintDevice() { return fbo; } + void prepareDevice() { widget.makeCurrent(); } + + ThreadSafeGLWidget widget; + QGLFramebufferObject *fbo; +}; + + +class ThreadPainter : public QObject +{ + Q_OBJECT +public: + ThreadPainter(Device *pd) : device(pd), fail(true) { + pixmap = QPixmap(40, 40); + pixmap.fill(Qt::green); + QPainter p(&pixmap); + p.drawLine(0, 0, 40, 40); + p.drawLine(0, 40, 40, 0); + } + +public slots: + void draw() { + bool beginFailed = false; + QTime time; + time.start(); + int rotAngle = 10; + device->prepareDevice(); + QPaintDevice *paintDevice = device->realPaintDevice(); + QSize s(paintDevice->width(), paintDevice->height()); + while (time.elapsed() < RUNNING_TIME) { + QPainter p; + if (!p.begin(paintDevice)) { + beginFailed = true; + break; + } + p.translate(s.width()/2, s.height()/2); + p.rotate(rotAngle); + p.translate(-s.width()/2, -s.height()/2); + p.fillRect(0, 0, s.width(), s.height(), Qt::red); + QRect rect(QPoint(0, 0), s); + p.drawPixmap(10, 10, pixmap); + p.drawTiledPixmap(50, 50, 100, 100, pixmap); + p.drawText(rect.center(), "This is a piece of text"); + p.end(); + rotAngle += 2; + QThread::msleep(20); + } + + fail = beginFailed; + QThread::currentThread()->quit(); + } + + bool failed() { return fail; } + +private: + QPixmap pixmap; + Device *device; + bool fail; +}; + +template <class T> +class PaintThreadManager +{ +public: + PaintThreadManager(int count) : numThreads(count) + { + for (int i=0; i<numThreads; ++i) { + devices.append(new T); + threads.append(new QThread); + painters.append(new ThreadPainter(devices.at(i))); + painters.at(i)->moveToThread(threads.at(i)); + painters.at(i)->connect(threads.at(i), SIGNAL(started()), painters.at(i), SLOT(draw())); + } + } + + ~PaintThreadManager() { + qDeleteAll(threads); + qDeleteAll(painters); + qDeleteAll(devices); + } + + + void start() { + for (int i=0; i<numThreads; ++i) + threads.at(i)->start(); + } + + bool areRunning() { + bool running = false; + for (int i=0; i<numThreads; ++i){ + if (threads.at(i)->isRunning()) + running = true; + } + + return running; + } + + bool failed() { + for (int i=0; i<numThreads; ++i) { + if (painters.at(i)->failed()) + return true; + } + + return false; + } + +private: + QList<QThread *> threads; + QList<Device *> devices; + QList<ThreadPainter *> painters; + int numThreads; +}; + +/* + This test uses QPainter to draw onto different QGLWidgets in + different threads at the same time. The ThreadSafeGLWidget is + necessary to handle paint and resize events that might come from + the main thread at any time while the test is running. The resize + and paint events would cause makeCurrent() calls to be issued from + within the QGLWidget while the widget's context was current in + another thread, which would cause errors. +*/ +void tst_QGLThreads::painterOnGLWidgetInThread() +{ +#ifdef Q_OS_MAC + QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots", SkipAll); +#endif + if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || + (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) { + QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.", SkipAll); + } + + PaintThreadManager<GLWidgetWrapper> painterThreads(5); + painterThreads.start(); + + while (painterThreads.areRunning()) { + qApp->processEvents(); + QThread::msleep(100); + } + QVERIFY(!painterThreads.failed()); +} + +/* + This test uses QPainter to draw onto different QPixmaps in + different threads at the same time. +*/ +void tst_QGLThreads::painterOnPixmapInThread() +{ +#ifdef Q_WS_X11 + QSKIP("Drawing text in threads onto X11 drawables currently crashes on some X11 servers.", SkipAll); +#endif + PaintThreadManager<PixmapWrapper> painterThreads(5); + painterThreads.start(); + + while (painterThreads.areRunning()) { + qApp->processEvents(); + QThread::msleep(100); + } + QVERIFY(!painterThreads.failed()); +} + +/* This test uses QPainter to draw onto different QGLPixelBuffer + objects in different threads at the same time. +*/ +void tst_QGLThreads::painterOnPboInThread() +{ +#ifdef Q_OS_MAC + QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots", SkipAll); +#endif + if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || + (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) { + QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.", SkipAll); + return; + } + + if (!QGLPixelBuffer::hasOpenGLPbuffers()) { + QSKIP("This system doesn't support pbuffers.", SkipAll); + return; + } + + PaintThreadManager<PixelBufferWrapper> painterThreads(5); + painterThreads.start(); + + while (painterThreads.areRunning()) { + qApp->processEvents(); + QThread::msleep(100); + } + QVERIFY(!painterThreads.failed()); +} + +/* This test uses QPainter to draw onto different + QGLFramebufferObjects (bound in a QGLWidget's context) in different + threads at the same time. +*/ +void tst_QGLThreads::painterOnFboInThread() +{ +#ifdef Q_OS_MAC + QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots", SkipAll); +#endif + if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || + (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) { + QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.", SkipAll); + return; + } + + if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) { + QSKIP("This system doesn't support framebuffer objects.", SkipAll); + return; + } + + PaintThreadManager<FrameBufferObjectWrapper> painterThreads(5); + painterThreads.start(); + + while (painterThreads.areRunning()) { + qApp->processEvents(); + QThread::msleep(100); + } + QVERIFY(!painterThreads.failed()); +} + +int main(int argc, char **argv) +{ + QApplication::setAttribute(Qt::AA_X11InitThreads); + QApplication app(argc, argv); + QTEST_DISABLE_KEYPAD_NAVIGATION \ + + tst_QGLThreads tc; + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_qglthreads.moc" diff --git a/tests/auto/opengl/qglthreads/tst_qglthreads.h b/tests/auto/opengl/qglthreads/tst_qglthreads.h new file mode 100644 index 0000000000..7972638085 --- /dev/null +++ b/tests/auto/opengl/qglthreads/tst_qglthreads.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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 test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TST_QGLTHREADS_H +#define TST_QGLTHREADS_H + +#include <QObject> + +class tst_QGLThreads : public QObject +{ +Q_OBJECT +public: + explicit tst_QGLThreads(QObject *parent = 0); + +private slots: + void swapInThread(); + void textureUploadInThread(); + + void renderInThread_data(); + void renderInThread(); + void painterOnGLWidgetInThread(); + void painterOnPixmapInThread(); + void painterOnPboInThread(); + void painterOnFboInThread(); +}; + +#endif // TST_QGLTHREADS_H |