Remove usage of deprecated qWaitForWindowShown(QWidget *) method.
[profile/ivi/qtbase.git] / tests / auto / opengl / qglthreads / tst_qglthreads.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
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"
48
49 #define RUNNING_TIME 5000
50
51 tst_QGLThreads::tst_QGLThreads(QObject *parent)
52     : QObject(parent)
53 {
54 }
55
56 /*
57
58    swapInThread
59
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.
62
63    The usecase for this is to have the background thread do the waiting for vertical
64    sync while the GUI thread is idle.
65
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.
69  */
70
71
72 class SwapThread : public QThread
73 {
74     Q_OBJECT
75 public:
76     SwapThread(QGLWidget *widget)
77         : m_widget(widget)
78     {
79         moveToThread(this);
80     }
81
82     void run() {
83         QTime time;
84         time.start();
85         while (time.elapsed() < RUNNING_TIME) {
86             lock();
87             wait();
88
89             m_widget->makeCurrent();
90             m_widget->swapBuffers();
91             m_widget->doneCurrent();
92             unlock();
93         }
94     }
95
96     void lock() { m_mutex.lock(); }
97     void unlock() { m_mutex.unlock(); }
98
99     void wait() { m_wait_condition.wait(&m_mutex); }
100     void notify() { m_wait_condition.wakeAll(); }
101
102 private:
103     QGLWidget *m_widget;
104     QMutex m_mutex;
105     QWaitCondition m_wait_condition;
106 };
107
108 class ForegroundWidget : public QGLWidget
109 {
110 public:
111     ForegroundWidget(const QGLFormat &format)
112         : QGLWidget(format), m_thread(0)
113     {
114         setAutoBufferSwap(false);
115     }
116
117     void paintEvent(QPaintEvent *)
118     {
119         m_thread->lock();
120         makeCurrent();
121         QPainter p(this);
122         p.fillRect(rect(), QColor(rand() % 256, rand() % 256, rand() % 256));
123         p.setPen(Qt::red);
124         p.setFont(QFont("SansSerif", 24));
125         p.drawText(rect(), Qt::AlignCenter, "This is an autotest");
126         p.end();
127         doneCurrent();
128         m_thread->notify();
129         m_thread->unlock();
130
131         update();
132     }
133
134     void setThread(SwapThread *thread) {
135         m_thread = thread;
136     }
137
138     SwapThread *m_thread;
139 };
140
141 void tst_QGLThreads::swapInThread()
142 {
143 #ifdef Q_OS_MAC
144     QSKIP("OpenGL threading tests are currently disabled on mac as they were causing reboots");
145 #endif
146
147     QGLFormat format;
148     format.setSwapInterval(1);
149     ForegroundWidget widget(format);
150     SwapThread thread(&widget);
151     widget.setThread(&thread);
152     widget.show();
153
154     QVERIFY(QTest::qWaitForWindowExposed(&widget));
155     thread.start();
156
157     while (thread.isRunning()) {
158         qApp->processEvents();
159     }
160
161     widget.hide();
162
163     QVERIFY(true);
164 }
165
166
167
168
169
170
171
172 /*
173    textureUploadInThread
174
175    The purpose of this testcase is to verify that doing texture uploads in a background
176    thread is possible and that it works.
177  */
178
179 class CreateAndUploadThread : public QThread
180 {
181     Q_OBJECT
182 public:
183     CreateAndUploadThread(QGLWidget *shareWidget)
184     {
185         m_gl = new QGLWidget(0, shareWidget);
186         moveToThread(this);
187     }
188
189     ~CreateAndUploadThread()
190     {
191         delete m_gl;
192     }
193
194     void run() {
195         m_gl->makeCurrent();
196         QTime time;
197         time.start();
198         while (time.elapsed() < RUNNING_TIME) {
199             int width = 400;
200             int height = 300;
201             QImage image(width, height, QImage::Format_RGB32);
202             QPainter p(&image);
203             p.fillRect(image.rect(), QColor(rand() % 256, rand() % 256, rand() % 256));
204             p.setPen(Qt::red);
205             p.setFont(QFont("SansSerif", 24));
206             p.drawText(image.rect(), Qt::AlignCenter, "This is an autotest");
207             p.end();
208             m_gl->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption);
209
210             createdAndUploaded(image);
211         }
212     }
213
214 signals:
215     void createdAndUploaded(const QImage &image);
216
217 private:
218     QGLWidget *m_gl;
219 };
220
221 class TextureDisplay : public QGLWidget
222 {
223     Q_OBJECT
224 public:
225     void paintEvent(QPaintEvent *) {
226         QPainter p(this);
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);
230         }
231         update();
232     }
233
234 public slots:
235     void receiveImage(const QImage &image) {
236         m_images << image;
237         m_positions << QPoint(-rand() % width() / 2, -rand() % height() / 2);
238
239         if (m_images.size() > 100) {
240             m_images.takeFirst();
241             m_positions.takeFirst();
242         }
243     }
244
245 private:
246     QList <QImage> m_images;
247     QList <QPoint> m_positions;
248 };
249
250 void tst_QGLThreads::textureUploadInThread()
251 {
252 #ifdef Q_OS_MAC
253     QSKIP("OpenGL threading tests are currently disabled on mac as they were causing reboots");
254 #endif
255
256     TextureDisplay display;
257     CreateAndUploadThread thread(&display);
258
259     connect(&thread, SIGNAL(createdAndUploaded(QImage)), &display, SLOT(receiveImage(QImage)));
260
261     display.show();
262     QVERIFY(QTest::qWaitForWindowActive(&display));
263
264     thread.start();
265
266     while (thread.isRunning()) {
267         qApp->processEvents();
268     }
269
270     QVERIFY(true);
271 }
272
273
274
275
276
277
278 /*
279    renderInThread
280
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..
284  */
285
286 static inline float qrandom() { return (rand() % 100) / 100.f; }
287
288 void renderAScene(int w, int h)
289 {
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);
295             program.bind();
296             int colorId = program.uniformLocation("color");
297
298             glEnableVertexAttribArray(0);
299
300             for (int i=0; i<1000; ++i) {
301                 GLfloat pos[] = {
302                     (rand() % 100) / 100.,
303                     (rand() % 100) / 100.,
304                     (rand() % 100) / 100.,
305                     (rand() % 100) / 100.,
306                     (rand() % 100) / 100.,
307                     (rand() % 100) / 100.
308                 };
309
310                 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos);
311                 glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
312             }
313 #else
314             glViewport(0, 0, w, h);
315
316             glMatrixMode(GL_PROJECTION);
317             glLoadIdentity();
318             glFrustum(0, w, h, 0, 1, 100);
319             glTranslated(0, 0, -1);
320
321             glMatrixMode(GL_MODELVIEW);
322             glLoadIdentity();
323
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);
332                 glEnd();
333             }
334 #endif
335 }
336
337 class ThreadSafeGLWidget : public QGLWidget
338 {
339 public:
340     ThreadSafeGLWidget(QWidget *parent = 0) : QGLWidget(parent) {}
341     void paintEvent(QPaintEvent *)
342     {
343         // ignored as we're anyway swapping as fast as we can
344     };
345
346     void resizeEvent(QResizeEvent *e)
347     {
348         mutex.lock();
349         newSize = e->size();
350         mutex.unlock();
351     };
352
353     QMutex mutex;
354     QSize newSize;
355 };
356
357 class SceneRenderingThread : public QThread
358 {
359     Q_OBJECT
360 public:
361     SceneRenderingThread(ThreadSafeGLWidget *widget)
362         : m_widget(widget)
363     {
364         moveToThread(this);
365         m_size = widget->size();
366     }
367
368     void run() {
369         QTime time;
370         time.start();
371         failure = false;
372
373         m_widget->makeCurrent();
374
375         while (time.elapsed() < RUNNING_TIME && !failure) {
376
377
378             m_widget->mutex.lock();
379             QSize s = m_widget->newSize;
380             m_widget->mutex.unlock();
381
382             if (s != m_size) {
383                 glViewport(0, 0, s.width(), s.height());
384             }
385
386             if (QGLContext::currentContext() != m_widget->context()) {
387                 failure = true;
388                 break;
389             }
390
391             glClear(GL_COLOR_BUFFER_BIT);
392
393             int w = m_widget->width();
394             int h = m_widget->height();
395
396             renderAScene(w, h);
397
398             int color;
399             glReadPixels(w / 2, h / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &color);
400
401             m_widget->swapBuffers();
402         }
403
404         m_widget->doneCurrent();
405     }
406
407     bool failure;
408
409 private:
410     ThreadSafeGLWidget *m_widget;
411     QSize m_size;
412 };
413
414 void tst_QGLThreads::renderInThread_data()
415 {
416     QTest::addColumn<bool>("resize");
417     QTest::addColumn<bool>("update");
418
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;
423 }
424
425 void tst_QGLThreads::renderInThread()
426 {
427 #ifdef Q_OS_MAC
428     QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots");
429 #endif
430
431     QFETCH(bool, resize);
432     QFETCH(bool, update);
433
434     ThreadSafeGLWidget widget;
435     widget.resize(200, 200);
436     SceneRenderingThread thread(&widget);
437
438     widget.show();
439     QVERIFY(QTest::qWaitForWindowExposed(&widget));
440     widget.doneCurrent();
441
442     thread.start();
443
444     int value = 10;
445     while (thread.isRunning()) {
446         if (resize)
447             widget.resize(200 + value, 200 + value);
448         if (update)
449             widget.update(100 + value, 100 + value, 20, 20);
450         qApp->processEvents();
451         value = -value;
452
453         QThread::msleep(100);
454     }
455
456     QVERIFY(!thread.failure);
457 }
458
459 class Device
460 {
461 public:
462     virtual ~Device() {}
463     virtual QPaintDevice *realPaintDevice() = 0;
464     virtual void prepareDevice() {}
465 };
466
467 class GLWidgetWrapper : public Device
468 {
469 public:
470     GLWidgetWrapper() {
471         widget.resize(150, 150);
472         widget.show();
473         QTest::qWaitForWindowExposed(&widget);
474         widget.doneCurrent();
475     }
476     QPaintDevice *realPaintDevice() { return &widget; }
477
478     ThreadSafeGLWidget widget;
479 };
480
481 class PixmapWrapper : public Device
482 {
483 public:
484     PixmapWrapper() { pixmap = new QPixmap(512, 512); }
485     ~PixmapWrapper() { delete pixmap; }
486     QPaintDevice *realPaintDevice() { return pixmap; }
487
488     QPixmap *pixmap;
489 };
490
491 class PixelBufferWrapper : public Device
492 {
493 public:
494     PixelBufferWrapper() { pbuffer = new QGLPixelBuffer(512, 512); }
495     ~PixelBufferWrapper() { delete pbuffer; }
496     QPaintDevice *realPaintDevice() { return pbuffer; }
497
498     QGLPixelBuffer *pbuffer;
499 };
500
501
502 class FrameBufferObjectWrapper : public Device
503 {
504 public:
505     FrameBufferObjectWrapper() {
506         widget.makeCurrent();
507         fbo = new QGLFramebufferObject(512, 512);
508         widget.doneCurrent();
509     }
510     ~FrameBufferObjectWrapper() { delete fbo; }
511     QPaintDevice *realPaintDevice() { return fbo; }
512     void prepareDevice() { widget.makeCurrent(); }
513
514     ThreadSafeGLWidget widget;
515     QGLFramebufferObject *fbo;
516 };
517
518
519 class ThreadPainter : public QObject
520 {
521     Q_OBJECT
522 public:
523     ThreadPainter(Device *pd) : device(pd), fail(true) {
524         pixmap = QPixmap(40, 40);
525         pixmap.fill(Qt::green);
526         QPainter p(&pixmap);
527         p.drawLine(0, 0, 40, 40);
528         p.drawLine(0, 40, 40, 0);
529     }
530
531 public slots:
532     void draw() {
533         bool beginFailed = false;
534         QTime time;
535         time.start();
536         int rotAngle = 10;
537         device->prepareDevice();
538         QPaintDevice *paintDevice = device->realPaintDevice();
539         QSize s(paintDevice->width(), paintDevice->height());
540         while (time.elapsed() < RUNNING_TIME) {
541             QPainter p;
542             if (!p.begin(paintDevice)) {
543                 beginFailed = true;
544                 break;
545             }
546             p.translate(s.width()/2, s.height()/2);
547             p.rotate(rotAngle);
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");
554             p.end();
555             rotAngle += 2;
556             QThread::msleep(20);
557         }
558
559         fail = beginFailed;
560         QThread::currentThread()->quit();
561     }
562
563     bool failed() { return fail; }
564
565 private:
566     QPixmap pixmap;
567     Device *device;
568     bool fail;
569 };
570
571 template <class T>
572 class PaintThreadManager
573 {
574 public:
575     PaintThreadManager(int count) : numThreads(count)
576     {
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()));
583         }
584     }
585
586     ~PaintThreadManager() {
587         qDeleteAll(threads);
588         qDeleteAll(painters);
589         qDeleteAll(devices);
590     }
591
592
593     void start() {
594         for (int i=0; i<numThreads; ++i)
595             threads.at(i)->start();
596     }
597
598     bool areRunning() {
599         bool running = false;
600         for (int i=0; i<numThreads; ++i){
601             if (threads.at(i)->isRunning())
602                 running = true;
603         }
604
605         return running;
606     }
607
608     bool failed() {
609         for (int i=0; i<numThreads; ++i) {
610             if (painters.at(i)->failed())
611                 return true;
612         }
613
614         return false;
615     }
616
617 private:
618     QList<QThread *> threads;
619     QList<Device *> devices;
620     QList<ThreadPainter *> painters;
621     int numThreads;
622 };
623
624 /*
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.
632 */
633 void tst_QGLThreads::painterOnGLWidgetInThread()
634 {
635 #ifdef Q_OS_MAC
636     QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots");
637 #endif
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.");
641     }
642
643     PaintThreadManager<GLWidgetWrapper> painterThreads(5);
644     painterThreads.start();
645
646     while (painterThreads.areRunning()) {
647         qApp->processEvents();
648         QThread::msleep(100);
649     }
650     QVERIFY(!painterThreads.failed());
651 }
652
653 /*
654    This test uses QPainter to draw onto different QPixmaps in
655    different threads at the same time.
656 */
657 void tst_QGLThreads::painterOnPixmapInThread()
658 {
659 #ifdef Q_WS_X11
660     QSKIP("Drawing text in threads onto X11 drawables currently crashes on some X11 servers.");
661 #endif
662     PaintThreadManager<PixmapWrapper> painterThreads(5);
663     painterThreads.start();
664
665     while (painterThreads.areRunning()) {
666         qApp->processEvents();
667         QThread::msleep(100);
668     }
669     QVERIFY(!painterThreads.failed());
670 }
671
672 /* This test uses QPainter to draw onto different QGLPixelBuffer
673    objects in different threads at the same time.
674 */
675 void tst_QGLThreads::painterOnPboInThread()
676 {
677 #ifdef Q_OS_MAC
678     QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots");
679 #endif
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.");
683     }
684
685     if (!QGLPixelBuffer::hasOpenGLPbuffers()) {
686         QSKIP("This system doesn't support pbuffers.");
687     }
688
689     PaintThreadManager<PixelBufferWrapper> painterThreads(5);
690     painterThreads.start();
691
692     while (painterThreads.areRunning()) {
693         qApp->processEvents();
694         QThread::msleep(100);
695     }
696     QVERIFY(!painterThreads.failed());
697 }
698
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.
702 */
703 void tst_QGLThreads::painterOnFboInThread()
704 {
705 #ifdef Q_OS_MAC
706     QSKIP("OpenGL threading tests are currently disabled on Mac as they were causing reboots");
707 #endif
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.");
711     }
712
713     if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) {
714         QSKIP("This system doesn't support framebuffer objects.");
715     }
716
717     PaintThreadManager<FrameBufferObjectWrapper> painterThreads(5);
718     painterThreads.start();
719
720     while (painterThreads.areRunning()) {
721         qApp->processEvents();
722         QThread::msleep(100);
723     }
724     QVERIFY(!painterThreads.failed());
725 }
726
727 int main(int argc, char **argv)
728 {
729     QApplication::setAttribute(Qt::AA_X11InitThreads);
730     QApplication app(argc, argv);
731     QTEST_DISABLE_KEYPAD_NAVIGATION \
732
733     tst_QGLThreads tc;
734     return QTest::qExec(&tc, argc, argv);
735 }
736
737 #include "tst_qglthreads.moc"