1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the test suite of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include <QtTest/QtTest>
43 #include <QtCore/QtCore>
44 #include <QtGui/QtGui>
45 #include <QtWidgets/QApplication>
46 #include <QtOpenGL/QtOpenGL>
47 #include "tst_qglthreads.h"
49 #define RUNNING_TIME 5000
51 tst_QGLThreads::tst_QGLThreads(QObject *parent)
60 The purpose of this testcase is to verify that it is possible to do rendering into
61 a GL context from the GUI thread, then swap the contents in from a background thread.
63 The usecase for this is to have the background thread do the waiting for vertical
64 sync while the GUI thread is idle.
66 Currently the locking is handled directly in the paintEvent(). For the actual usecase
67 in Qt, the locking is done in the windowsurface before starting any drawing while
68 unlocking is done after all drawing has been done.
72 class SwapThread : public QThread
76 SwapThread(QGLWidget *widget)
85 while (time.elapsed() < RUNNING_TIME) {
89 m_widget->makeCurrent();
90 m_widget->swapBuffers();
91 m_widget->doneCurrent();
96 void lock() { m_mutex.lock(); }
97 void unlock() { m_mutex.unlock(); }
99 void wait() { m_wait_condition.wait(&m_mutex); }
100 void notify() { m_wait_condition.wakeAll(); }
105 QWaitCondition m_wait_condition;
108 class ForegroundWidget : public QGLWidget
111 ForegroundWidget(const QGLFormat &format)
112 : QGLWidget(format), m_thread(0)
114 setAutoBufferSwap(false);
117 void paintEvent(QPaintEvent *)
122 p.fillRect(rect(), QColor(rand() % 256, rand() % 256, rand() % 256));
124 p.setFont(QFont("SansSerif", 24));
125 p.drawText(rect(), Qt::AlignCenter, "This is an autotest");
134 void setThread(SwapThread *thread) {
138 SwapThread *m_thread;
141 void tst_QGLThreads::swapInThread()
144 QSKIP("OpenGL threading tests are currently disabled on mac as they were causing reboots");
148 format.setSwapInterval(1);
149 ForegroundWidget widget(format);
150 SwapThread thread(&widget);
151 widget.setThread(&thread);
154 QVERIFY(QTest::qWaitForWindowExposed(&widget));
157 while (thread.isRunning()) {
158 qApp->processEvents();
173 textureUploadInThread
175 The purpose of this testcase is to verify that doing texture uploads in a background
176 thread is possible and that it works.
179 class CreateAndUploadThread : public QThread
183 CreateAndUploadThread(QGLWidget *shareWidget)
185 m_gl = new QGLWidget(0, shareWidget);
189 ~CreateAndUploadThread()
198 while (time.elapsed() < RUNNING_TIME) {
201 QImage image(width, height, QImage::Format_RGB32);
203 p.fillRect(image.rect(), QColor(rand() % 256, rand() % 256, rand() % 256));
205 p.setFont(QFont("SansSerif", 24));
206 p.drawText(image.rect(), Qt::AlignCenter, "This is an autotest");
208 m_gl->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption);
210 createdAndUploaded(image);
215 void createdAndUploaded(const QImage &image);
221 class TextureDisplay : public QGLWidget
225 void paintEvent(QPaintEvent *) {
227 for (int i=0; i<m_images.size(); ++i) {
228 p.drawImage(m_positions.at(i), m_images.at(i));
229 m_positions[i] += QPoint(1, 1);
235 void receiveImage(const QImage &image) {
237 m_positions << QPoint(-rand() % width() / 2, -rand() % height() / 2);
239 if (m_images.size() > 100) {
240 m_images.takeFirst();
241 m_positions.takeFirst();
246 QList <QImage> m_images;
247 QList <QPoint> m_positions;
250 void tst_QGLThreads::textureUploadInThread()
253 QSKIP("OpenGL threading tests are currently disabled on mac as they were causing reboots");
256 TextureDisplay display;
257 CreateAndUploadThread thread(&display);
259 connect(&thread, SIGNAL(createdAndUploaded(QImage)), &display, SLOT(receiveImage(QImage)));
262 QVERIFY(QTest::qWaitForWindowActive(&display));
266 while (thread.isRunning()) {
267 qApp->processEvents();
281 This test sets up a scene and renders it in a different thread.
282 For simplicity, the scene is simply a bunch of rectangles, but
283 if that works, we're in good shape..
286 static inline float qrandom() { return (rand() % 100) / 100.f; }
288 void renderAScene(int w, int h)
290 #ifdef QT_OPENGL_ES_2
291 QGLShaderProgram program;
292 program.addShaderFromSourceCode(QGLShader::Vertex, "attribute highp vec2 pos; void main() { gl_Position = vec4(pos.xy, 1.0, 1.0); }");
293 program.addShaderFromSourceCode(QGLShader::Fragment, "uniform lowp vec4 color; void main() { gl_FragColor = color; }");
294 program.bindAttributeLocation("pos", 0);
296 int colorId = program.uniformLocation("color");
298 glEnableVertexAttribArray(0);
300 for (int i=0; i<1000; ++i) {
302 (rand() % 100) / 100.,
303 (rand() % 100) / 100.,
304 (rand() % 100) / 100.,
305 (rand() % 100) / 100.,
306 (rand() % 100) / 100.,
307 (rand() % 100) / 100.
310 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos);
311 glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
314 glViewport(0, 0, w, h);
316 glMatrixMode(GL_PROJECTION);
318 glFrustum(0, w, h, 0, 1, 100);
319 glTranslated(0, 0, -1);
321 glMatrixMode(GL_MODELVIEW);
324 for (int i=0;i<1000; ++i) {
325 glBegin(GL_TRIANGLES);
326 glColor3f(qrandom(), qrandom(), qrandom());
327 glVertex2f(qrandom() * w, qrandom() * h);
328 glColor3f(qrandom(), qrandom(), qrandom());
329 glVertex2f(qrandom() * w, qrandom() * h);
330 glColor3f(qrandom(), qrandom(), qrandom());
331 glVertex2f(qrandom() * w, qrandom() * h);
337 class ThreadSafeGLWidget : public QGLWidget
340 ThreadSafeGLWidget(QWidget *parent = 0) : QGLWidget(parent) {}
341 void paintEvent(QPaintEvent *)
343 // ignored as we're anyway swapping as fast as we can
346 void resizeEvent(QResizeEvent *e)
357 class SceneRenderingThread : public QThread
361 SceneRenderingThread(ThreadSafeGLWidget *widget)
365 m_size = widget->size();
373 m_widget->makeCurrent();
375 while (time.elapsed() < RUNNING_TIME && !failure) {
378 m_widget->mutex.lock();
379 QSize s = m_widget->newSize;
380 m_widget->mutex.unlock();
383 glViewport(0, 0, s.width(), s.height());
386 if (QGLContext::currentContext() != m_widget->context()) {
391 glClear(GL_COLOR_BUFFER_BIT);
393 int w = m_widget->width();
394 int h = m_widget->height();
399 glReadPixels(w / 2, h / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &color);
401 m_widget->swapBuffers();
404 m_widget->doneCurrent();
410 ThreadSafeGLWidget *m_widget;
414 void tst_QGLThreads::renderInThread_data()
416 QTest::addColumn<bool>("resize");
417 QTest::addColumn<bool>("update");
419 QTest::newRow("basic") << false << false;
420 QTest::newRow("with-resize") << true << false;
421 QTest::newRow("with-update") << false << true;
422 QTest::newRow("with-resize-and-update") << true << true;
425 void tst_QGLThreads::renderInThread()
428 QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots");
431 QFETCH(bool, resize);
432 QFETCH(bool, update);
434 ThreadSafeGLWidget widget;
435 widget.resize(200, 200);
436 SceneRenderingThread thread(&widget);
439 QVERIFY(QTest::qWaitForWindowExposed(&widget));
440 widget.doneCurrent();
445 while (thread.isRunning()) {
447 widget.resize(200 + value, 200 + value);
449 widget.update(100 + value, 100 + value, 20, 20);
450 qApp->processEvents();
453 QThread::msleep(100);
456 QVERIFY(!thread.failure);
463 virtual QPaintDevice *realPaintDevice() = 0;
464 virtual void prepareDevice() {}
467 class GLWidgetWrapper : public Device
471 widget.resize(150, 150);
473 QTest::qWaitForWindowExposed(&widget);
474 widget.doneCurrent();
476 QPaintDevice *realPaintDevice() { return &widget; }
478 ThreadSafeGLWidget widget;
481 class PixmapWrapper : public Device
484 PixmapWrapper() { pixmap = new QPixmap(512, 512); }
485 ~PixmapWrapper() { delete pixmap; }
486 QPaintDevice *realPaintDevice() { return pixmap; }
491 class PixelBufferWrapper : public Device
494 PixelBufferWrapper() { pbuffer = new QGLPixelBuffer(512, 512); }
495 ~PixelBufferWrapper() { delete pbuffer; }
496 QPaintDevice *realPaintDevice() { return pbuffer; }
498 QGLPixelBuffer *pbuffer;
502 class FrameBufferObjectWrapper : public Device
505 FrameBufferObjectWrapper() {
506 widget.makeCurrent();
507 fbo = new QGLFramebufferObject(512, 512);
508 widget.doneCurrent();
510 ~FrameBufferObjectWrapper() { delete fbo; }
511 QPaintDevice *realPaintDevice() { return fbo; }
512 void prepareDevice() { widget.makeCurrent(); }
514 ThreadSafeGLWidget widget;
515 QGLFramebufferObject *fbo;
519 class ThreadPainter : public QObject
523 ThreadPainter(Device *pd) : device(pd), fail(true) {
524 pixmap = QPixmap(40, 40);
525 pixmap.fill(Qt::green);
527 p.drawLine(0, 0, 40, 40);
528 p.drawLine(0, 40, 40, 0);
533 bool beginFailed = false;
537 device->prepareDevice();
538 QPaintDevice *paintDevice = device->realPaintDevice();
539 QSize s(paintDevice->width(), paintDevice->height());
540 while (time.elapsed() < RUNNING_TIME) {
542 if (!p.begin(paintDevice)) {
546 p.translate(s.width()/2, s.height()/2);
548 p.translate(-s.width()/2, -s.height()/2);
549 p.fillRect(0, 0, s.width(), s.height(), Qt::red);
550 QRect rect(QPoint(0, 0), s);
551 p.drawPixmap(10, 10, pixmap);
552 p.drawTiledPixmap(50, 50, 100, 100, pixmap);
553 p.drawText(rect.center(), "This is a piece of text");
560 QThread::currentThread()->quit();
563 bool failed() { return fail; }
572 class PaintThreadManager
575 PaintThreadManager(int count) : numThreads(count)
577 for (int i=0; i<numThreads; ++i) {
578 devices.append(new T);
579 threads.append(new QThread);
580 painters.append(new ThreadPainter(devices.at(i)));
581 painters.at(i)->moveToThread(threads.at(i));
582 painters.at(i)->connect(threads.at(i), SIGNAL(started()), painters.at(i), SLOT(draw()));
586 ~PaintThreadManager() {
588 qDeleteAll(painters);
594 for (int i=0; i<numThreads; ++i)
595 threads.at(i)->start();
599 bool running = false;
600 for (int i=0; i<numThreads; ++i){
601 if (threads.at(i)->isRunning())
609 for (int i=0; i<numThreads; ++i) {
610 if (painters.at(i)->failed())
618 QList<QThread *> threads;
619 QList<Device *> devices;
620 QList<ThreadPainter *> painters;
625 This test uses QPainter to draw onto different QGLWidgets in
626 different threads at the same time. The ThreadSafeGLWidget is
627 necessary to handle paint and resize events that might come from
628 the main thread at any time while the test is running. The resize
629 and paint events would cause makeCurrent() calls to be issued from
630 within the QGLWidget while the widget's context was current in
631 another thread, which would cause errors.
633 void tst_QGLThreads::painterOnGLWidgetInThread()
636 QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots");
638 if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) ||
639 (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) {
640 QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.");
643 PaintThreadManager<GLWidgetWrapper> painterThreads(5);
644 painterThreads.start();
646 while (painterThreads.areRunning()) {
647 qApp->processEvents();
648 QThread::msleep(100);
650 QVERIFY(!painterThreads.failed());
654 This test uses QPainter to draw onto different QPixmaps in
655 different threads at the same time.
657 void tst_QGLThreads::painterOnPixmapInThread()
660 QSKIP("Drawing text in threads onto X11 drawables currently crashes on some X11 servers.");
662 PaintThreadManager<PixmapWrapper> painterThreads(5);
663 painterThreads.start();
665 while (painterThreads.areRunning()) {
666 qApp->processEvents();
667 QThread::msleep(100);
669 QVERIFY(!painterThreads.failed());
672 /* This test uses QPainter to draw onto different QGLPixelBuffer
673 objects in different threads at the same time.
675 void tst_QGLThreads::painterOnPboInThread()
678 QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots");
680 if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) ||
681 (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) {
682 QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.");
685 if (!QGLPixelBuffer::hasOpenGLPbuffers()) {
686 QSKIP("This system doesn't support pbuffers.");
689 PaintThreadManager<PixelBufferWrapper> painterThreads(5);
690 painterThreads.start();
692 while (painterThreads.areRunning()) {
693 qApp->processEvents();
694 QThread::msleep(100);
696 QVERIFY(!painterThreads.failed());
699 /* This test uses QPainter to draw onto different
700 QGLFramebufferObjects (bound in a QGLWidget's context) in different
701 threads at the same time.
703 void tst_QGLThreads::painterOnFboInThread()
706 QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots");
708 if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) ||
709 (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) {
710 QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.");
713 if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) {
714 QSKIP("This system doesn't support framebuffer objects.");
717 PaintThreadManager<FrameBufferObjectWrapper> painterThreads(5);
718 painterThreads.start();
720 while (painterThreads.areRunning()) {
721 qApp->processEvents();
722 QThread::msleep(100);
724 QVERIFY(!painterThreads.failed());
727 int main(int argc, char **argv)
729 QApplication::setAttribute(Qt::AA_X11InitThreads);
730 QApplication app(argc, argv);
731 QTEST_DISABLE_KEYPAD_NAVIGATION \
734 return QTest::qExec(&tc, argc, argv);
737 #include "tst_qglthreads.moc"