From 81c84b8649d483145e3397d3e34df608e704d108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20R=C3=B8dal?= Date: Mon, 29 Aug 2011 12:26:08 +0200 Subject: [PATCH] Added qopengl auto-test. For testing the gui/opengl functionality. Change-Id: Ied0e977ad537c0be301bb08091adaf9cf779306b Reviewed-on: http://codereview.qt.nokia.com/3729 Reviewed-by: Qt Sanity Bot Reviewed-by: Gunnar Sletta --- src/gui/kernel/qopenglcontext.h | 1 + tests/auto/gui.pro | 1 + tests/auto/qopengl/qopengl.pro | 10 + tests/auto/qopengl/tst_qopengl.cpp | 345 +++++++++++++++++++++++++++++ 4 files changed, 357 insertions(+) create mode 100644 tests/auto/qopengl/qopengl.pro create mode 100644 tests/auto/qopengl/tst_qopengl.cpp diff --git a/src/gui/kernel/qopenglcontext.h b/src/gui/kernel/qopenglcontext.h index cf334709ec..ff1017c896 100644 --- a/src/gui/kernel/qopenglcontext.h +++ b/src/gui/kernel/qopenglcontext.h @@ -43,6 +43,7 @@ #define QOPENGLCONTEXT_H #include +#include #include #include diff --git a/tests/auto/gui.pro b/tests/auto/gui.pro index aa8056ebaa..4228c0b89a 100644 --- a/tests/auto/gui.pro +++ b/tests/auto/gui.pro @@ -115,6 +115,7 @@ SUBDIRS=\ qmovie \ qvolatileimage \ qnetworkaccessmanager_and_qprogressdialog \ + qopengl \ qpaintengine \ qpainterpath \ qpainterpathstroker \ diff --git a/tests/auto/qopengl/qopengl.pro b/tests/auto/qopengl/qopengl.pro new file mode 100644 index 0000000000..b049518f7a --- /dev/null +++ b/tests/auto/qopengl/qopengl.pro @@ -0,0 +1,10 @@ +############################################################ +# Project file for autotest for gui/opengl functionality +############################################################ + +load(qttest_p4) +QT += gui gui-private core-private + +SOURCES += tst_qopengl.cpp + +CONFIG += insignificant_test diff --git a/tests/auto/qopengl/tst_qopengl.cpp b/tests/auto/qopengl/tst_qopengl.cpp new file mode 100644 index 0000000000..1df7985df9 --- /dev/null +++ b/tests/auto/qopengl/tst_qopengl.cpp @@ -0,0 +1,345 @@ +/**************************************************************************** +** +** 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 +#include + +#include + +class tst_QOpenGL : public QObject +{ +Q_OBJECT + +private slots: + void sharedResourceCleanup(); + void fboSimpleRendering(); + void fboRendering(); +}; + +struct SharedResourceTracker +{ + SharedResourceTracker() + { + reset(); + } + + void reset() + { + invalidateResourceCalls = 0; + freeResourceCalls = 0; + destructorCalls = 0; + } + + int invalidateResourceCalls; + int freeResourceCalls; + int destructorCalls; +}; + +struct SharedResource : public QOpenGLSharedResource +{ + SharedResource(SharedResourceTracker *t) + : QOpenGLSharedResource(QOpenGLContextGroup::currentContextGroup()) + , resource(1) + , tracker(t) + { + } + + ~SharedResource() + { + tracker->destructorCalls++; + } + + void invalidateResource() + { + resource = 0; + tracker->invalidateResourceCalls++; + } + + void freeResource(QOpenGLContext *context) + { + Q_ASSERT(context == QOpenGLContext::currentContext()); + resource = 0; + tracker->freeResourceCalls++; + } + + int resource; + SharedResourceTracker *tracker; +}; + +void tst_QOpenGL::sharedResourceCleanup() +{ + QWindow window; + window.setGeometry(0, 0, 10, 10); + window.create(); + + QOpenGLContext *ctx = new QOpenGLContext; + ctx->create(); + ctx->makeCurrent(&window); + + SharedResourceTracker tracker; + SharedResource *resource = new SharedResource(&tracker); + resource->free(); + + QCOMPARE(tracker.invalidateResourceCalls, 0); + QCOMPARE(tracker.freeResourceCalls, 1); + QCOMPARE(tracker.destructorCalls, 1); + + tracker.reset(); + + resource = new SharedResource(&tracker); + + QOpenGLContext *ctx2 = new QOpenGLContext; + ctx2->setShareContext(ctx); + ctx2->create(); + delete ctx; + + resource->free(); + + // no current context, freeResource() delayed + QCOMPARE(tracker.invalidateResourceCalls, 0); + QCOMPARE(tracker.freeResourceCalls, 0); + QCOMPARE(tracker.destructorCalls, 0); + + ctx2->makeCurrent(&window); + + // freeResource() should now have been called + QCOMPARE(tracker.invalidateResourceCalls, 0); + QCOMPARE(tracker.freeResourceCalls, 1); + QCOMPARE(tracker.destructorCalls, 1); + + delete ctx2; +} + +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 (QGuiApplication::primaryScreen()->depth() < 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__) + +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()); +} + + +void tst_QOpenGL::fboSimpleRendering() +{ + QWindow window; + window.setGeometry(0, 0, 10, 10); + window.create(); + QOpenGLContext ctx; + ctx.create(); + + ctx.makeCurrent(&window); + + if (!QOpenGLFramebufferObject::hasOpenGLFramebufferObjects()) + QSKIP("QOpenGLFramebufferObject not supported on this platform", SkipSingle); + + // No multisample with combined depth/stencil attachment: + QOpenGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QOpenGLFramebufferObject::NoAttachment); + + QOpenGLFramebufferObject *fbo = new QOpenGLFramebufferObject(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_QOpenGL::fboRendering() +{ + QWindow window; + window.setGeometry(0, 0, 10, 10); + window.create(); + QOpenGLContext ctx; + ctx.create(); + + ctx.makeCurrent(&window); + + if (!QOpenGLFramebufferObject::hasOpenGLFramebufferObjects()) + QSKIP("QOpenGLFramebufferObject not supported on this platform", SkipSingle); + + // No multisample with combined depth/stencil attachment: + QOpenGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + + // Uncomplicate things by using NPOT: + QOpenGLFramebufferObject *fbo = new QOpenGLFramebufferObject(256, 128, fboFormat); + + if (fbo->attachment() != QOpenGLFramebufferObject::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); +} + +QTEST_MAIN(tst_QOpenGL) +#include "tst_qopengl.moc"