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/qglthreads | |
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/qglthreads')
-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 |
3 files changed, 837 insertions, 0 deletions
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 |