1
/****************************************************************************
3
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4
** Contact: http://www.qt-project.org/legal
6
** This file is part of the test suite of the Qt Toolkit.
8
** $QT_BEGIN_LICENSE:LGPL$
9
** Commercial License Usage
10
** Licensees holding valid commercial Qt licenses may use this file in
11
** accordance with the commercial license agreement provided with the
12
** Software or, alternatively, in accordance with the terms contained in
13
** a written agreement between you and Digia. For licensing terms and
14
** conditions see http://qt.digia.com/licensing. For further information
15
** use the contact form at http://qt.digia.com/contact-us.
17
** GNU Lesser General Public License Usage
18
** Alternatively, this file may be used under the terms of the GNU Lesser
19
** General Public License version 2.1 as published by the Free Software
20
** Foundation and appearing in the file LICENSE.LGPL included in the
21
** packaging of this file. Please review the following information to
22
** ensure the GNU Lesser General Public License version 2.1 requirements
23
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25
** In addition, as a special exception, Digia gives you certain additional
26
** rights. These rights are described in the Digia Qt LGPL Exception
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29
** GNU General Public License Usage
30
** Alternatively, this file may be used under the terms of the GNU
31
** General Public License version 3.0 as published by the Free Software
32
** Foundation and appearing in the file LICENSE.GPL included in the
33
** packaging of this file. Please review the following information to
34
** ensure the GNU General Public License version 3.0 requirements will be
35
** met: http://www.gnu.org/copyleft/gpl.html.
40
****************************************************************************/
42
#include <QtTest/QtTest>
43
#include <QtCore/QtCore>
44
#include <QtGui/QtGui>
45
#include <private/qguiapplication_p.h>
46
#include <qpa/qplatformintegration.h>
47
#include <QtWidgets/QApplication>
48
#include <QtOpenGL/QtOpenGL>
49
#include "tst_qglthreads.h"
51
#define RUNNING_TIME 5000
53
tst_QGLThreads::tst_QGLThreads(QObject *parent)
62
The purpose of this testcase is to verify that it is possible to do rendering into
63
a GL context from the GUI thread, then swap the contents in from a background thread.
65
The usecase for this is to have the background thread do the waiting for vertical
66
sync while the GUI thread is idle.
68
Currently the locking is handled directly in the paintEvent(). For the actual usecase
69
in Qt, the locking is done in the windowsurface before starting any drawing while
70
unlocking is done after all drawing has been done.
74
class SwapThread : public QThread
78
SwapThread(QGLWidget *widget)
79
: m_context(widget->context())
80
, m_swapTriggered(false)
88
while (time.elapsed() < RUNNING_TIME) {
92
m_context->makeCurrent();
93
m_context->swapBuffers();
94
m_context->doneCurrent();
96
m_context->moveToThread(qApp->thread());
102
m_swapTriggered = false;
105
void lock() { m_mutex.lock(); }
106
void unlock() { m_mutex.unlock(); }
108
void waitForSwapDone() { if (m_swapTriggered) m_swapDone.wait(&m_mutex); }
109
void waitForReadyToSwap() { if (!m_swapTriggered) m_readyToSwap.wait(&m_mutex); }
111
void signalReadyToSwap()
115
m_readyToSwap.wakeAll();
116
m_swapTriggered = true;
119
void signalSwapDone()
121
m_swapTriggered = false;
122
m_swapDone.wakeAll();
126
QGLContext *m_context;
128
QWaitCondition m_readyToSwap;
129
QWaitCondition m_swapDone;
131
bool m_swapTriggered;
134
class ForegroundWidget : public QGLWidget
137
ForegroundWidget(const QGLFormat &format)
138
: QGLWidget(format), m_thread(0)
140
setAutoBufferSwap(false);
143
void paintEvent(QPaintEvent *)
146
m_thread->waitForSwapDone();
150
p.fillRect(rect(), QColor(rand() % 256, rand() % 256, rand() % 256));
152
p.setFont(QFont("SansSerif", 24));
153
p.drawText(rect(), Qt::AlignCenter, "This is an autotest");
157
if (m_thread->isRunning()) {
158
context()->moveToThread(m_thread);
159
m_thread->signalReadyToSwap();
167
void setThread(SwapThread *thread) {
171
SwapThread *m_thread;
174
void tst_QGLThreads::swapInThread()
176
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
177
QSKIP("No platformsupport for ThreadedOpenGL");
179
format.setSwapInterval(1);
180
ForegroundWidget widget(format);
181
SwapThread thread(&widget);
182
widget.setThread(&thread);
185
QVERIFY(QTest::qWaitForWindowExposed(&widget));
188
while (thread.isRunning()) {
189
qApp->processEvents();
204
textureUploadInThread
206
The purpose of this testcase is to verify that doing texture uploads in a background
207
thread is possible and that it works.
210
class CreateAndUploadThread : public QThread
214
CreateAndUploadThread(QGLWidget *shareWidget, QSemaphore *semaphore)
215
: m_semaphore(semaphore)
217
m_gl = new QGLWidget(0, shareWidget);
219
m_gl->context()->moveToThread(this);
222
~CreateAndUploadThread()
231
while (time.elapsed() < RUNNING_TIME) {
234
QImage image(width, height, QImage::Format_RGB32);
236
p.fillRect(image.rect(), QColor(rand() % 256, rand() % 256, rand() % 256));
238
p.setFont(QFont("SansSerif", 24));
239
p.drawText(image.rect(), Qt::AlignCenter, "This is an autotest");
241
m_gl->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption);
243
m_semaphore->acquire(1);
245
createdAndUploaded(image);
250
void createdAndUploaded(const QImage &image);
254
QSemaphore *m_semaphore;
257
class TextureDisplay : public QGLWidget
261
TextureDisplay(QSemaphore *semaphore)
262
: m_semaphore(semaphore)
266
void paintEvent(QPaintEvent *) {
268
for (int i=0; i<m_images.size(); ++i) {
269
p.drawImage(m_positions.at(i), m_images.at(i));
270
m_positions[i] += QPoint(1, 1);
276
void receiveImage(const QImage &image) {
278
m_positions << QPoint(-rand() % width() / 2, -rand() % height() / 2);
280
m_semaphore->release(1);
282
if (m_images.size() > 100) {
283
m_images.takeFirst();
284
m_positions.takeFirst();
289
QList <QImage> m_images;
290
QList <QPoint> m_positions;
292
QSemaphore *m_semaphore;
295
void tst_QGLThreads::textureUploadInThread()
297
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
298
QSKIP("No platformsupport for ThreadedOpenGL");
300
// prevent producer thread from queuing up too many images
301
QSemaphore semaphore(100);
302
TextureDisplay display(&semaphore);
303
CreateAndUploadThread thread(&display, &semaphore);
305
connect(&thread, SIGNAL(createdAndUploaded(QImage)), &display, SLOT(receiveImage(QImage)));
308
QVERIFY(QTest::qWaitForWindowActive(&display));
312
while (thread.isRunning()) {
313
qApp->processEvents();
327
This test sets up a scene and renders it in a different thread.
328
For simplicity, the scene is simply a bunch of rectangles, but
329
if that works, we're in good shape..
332
static inline float qrandom() { return (rand() % 100) / 100.f; }
334
void renderAScene(int w, int h)
336
#ifdef QT_OPENGL_ES_2
337
QGLShaderProgram program;
338
program.addShaderFromSourceCode(QGLShader::Vertex, "attribute highp vec2 pos; void main() { gl_Position = vec4(pos.xy, 1.0, 1.0); }");
339
program.addShaderFromSourceCode(QGLShader::Fragment, "uniform lowp vec4 color; void main() { gl_FragColor = color; }");
340
program.bindAttributeLocation("pos", 0);
342
int colorId = program.uniformLocation("color");
344
glEnableVertexAttribArray(0);
346
for (int i=0; i<1000; ++i) {
348
(rand() % 100) / 100.,
349
(rand() % 100) / 100.,
350
(rand() % 100) / 100.,
351
(rand() % 100) / 100.,
352
(rand() % 100) / 100.,
353
(rand() % 100) / 100.
356
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos);
357
glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
360
glViewport(0, 0, w, h);
362
glMatrixMode(GL_PROJECTION);
364
glFrustum(0, w, h, 0, 1, 100);
365
glTranslated(0, 0, -1);
367
glMatrixMode(GL_MODELVIEW);
370
for (int i=0;i<1000; ++i) {
371
glBegin(GL_TRIANGLES);
372
glColor3f(qrandom(), qrandom(), qrandom());
373
glVertex2f(qrandom() * w, qrandom() * h);
374
glColor3f(qrandom(), qrandom(), qrandom());
375
glVertex2f(qrandom() * w, qrandom() * h);
376
glColor3f(qrandom(), qrandom(), qrandom());
377
glVertex2f(qrandom() * w, qrandom() * h);
383
class ThreadSafeGLWidget : public QGLWidget
386
ThreadSafeGLWidget(QWidget *parent = 0) : QGLWidget(parent) {}
387
void paintEvent(QPaintEvent *)
389
// ignored as we're anyway swapping as fast as we can
392
void resizeEvent(QResizeEvent *e)
403
class SceneRenderingThread : public QThread
407
SceneRenderingThread(ThreadSafeGLWidget *widget)
411
m_size = widget->size();
419
while (time.elapsed() < RUNNING_TIME && !failure) {
421
m_widget->makeCurrent();
423
m_widget->mutex.lock();
424
QSize s = m_widget->newSize;
425
m_widget->mutex.unlock();
428
glViewport(0, 0, s.width(), s.height());
431
if (QGLContext::currentContext() != m_widget->context()) {
436
glClear(GL_COLOR_BUFFER_BIT);
438
int w = m_widget->width();
439
int h = m_widget->height();
444
glReadPixels(w / 2, h / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &color);
446
m_widget->swapBuffers();
449
m_widget->doneCurrent();
455
ThreadSafeGLWidget *m_widget;
459
void tst_QGLThreads::renderInThread_data()
461
QTest::addColumn<bool>("resize");
462
QTest::addColumn<bool>("update");
464
QTest::newRow("basic") << false << false;
465
QTest::newRow("with-resize") << true << false;
466
QTest::newRow("with-update") << false << true;
467
QTest::newRow("with-resize-and-update") << true << true;
470
void tst_QGLThreads::renderInThread()
472
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
473
QSKIP("No platformsupport for ThreadedOpenGL");
475
QFETCH(bool, resize);
476
QFETCH(bool, update);
478
ThreadSafeGLWidget widget;
479
widget.resize(200, 200);
480
SceneRenderingThread thread(&widget);
483
QVERIFY(QTest::qWaitForWindowExposed(&widget));
484
widget.doneCurrent();
486
widget.context()->moveToThread(&thread);
491
while (thread.isRunning()) {
493
widget.resize(200 + value, 200 + value);
495
widget.update(100 + value, 100 + value, 20, 20);
496
qApp->processEvents();
499
QThread::msleep(100);
502
QVERIFY(!thread.failure);
509
virtual QPaintDevice *realPaintDevice() = 0;
510
virtual void prepareDevice() {}
511
virtual void moveToThread(QThread *) {}
514
class GLWidgetWrapper : public Device
518
widget.resize(150, 150);
520
QTest::qWaitForWindowExposed(&widget);
521
widget.doneCurrent();
523
QPaintDevice *realPaintDevice() { return &widget; }
524
void moveToThread(QThread *thread) { widget.context()->moveToThread(thread); }
526
ThreadSafeGLWidget widget;
529
class PixmapWrapper : public Device
532
PixmapWrapper() { pixmap = new QPixmap(512, 512); }
533
~PixmapWrapper() { delete pixmap; }
534
QPaintDevice *realPaintDevice() { return pixmap; }
539
class PixelBufferWrapper : public Device
542
PixelBufferWrapper() { pbuffer = new QGLPixelBuffer(512, 512); }
543
~PixelBufferWrapper() { delete pbuffer; }
544
QPaintDevice *realPaintDevice() { return pbuffer; }
545
void moveToThread(QThread *thread) { pbuffer->context()->moveToThread(thread); }
547
QGLPixelBuffer *pbuffer;
551
class FrameBufferObjectWrapper : public Device
554
FrameBufferObjectWrapper() {
555
widget.makeCurrent();
556
fbo = new QGLFramebufferObject(512, 512);
557
widget.doneCurrent();
559
~FrameBufferObjectWrapper() { delete fbo; }
560
QPaintDevice *realPaintDevice() { return fbo; }
561
void prepareDevice() { widget.makeCurrent(); }
562
void moveToThread(QThread *thread) { widget.context()->moveToThread(thread); }
564
ThreadSafeGLWidget widget;
565
QGLFramebufferObject *fbo;
569
class ThreadPainter : public QObject
573
ThreadPainter(Device *pd) : device(pd), fail(true) {
574
pixmap = QPixmap(40, 40);
575
pixmap.fill(Qt::green);
577
p.drawLine(0, 0, 40, 40);
578
p.drawLine(0, 40, 40, 0);
583
bool beginFailed = false;
587
device->prepareDevice();
588
QPaintDevice *paintDevice = device->realPaintDevice();
589
QSize s(paintDevice->width(), paintDevice->height());
590
while (time.elapsed() < RUNNING_TIME) {
592
if (!p.begin(paintDevice)) {
596
p.translate(s.width()/2, s.height()/2);
598
p.translate(-s.width()/2, -s.height()/2);
599
p.fillRect(0, 0, s.width(), s.height(), Qt::red);
600
QRect rect(QPoint(0, 0), s);
601
p.drawPixmap(10, 10, pixmap);
602
p.drawTiledPixmap(50, 50, 100, 100, pixmap);
603
p.drawText(rect.center(), "This is a piece of text");
609
device->moveToThread(qApp->thread());
612
QThread::currentThread()->quit();
615
bool failed() { return fail; }
624
class PaintThreadManager
627
PaintThreadManager(int count) : numThreads(count)
629
for (int i=0; i<numThreads; ++i) {
630
devices.append(new T);
631
threads.append(new QThread);
632
painters.append(new ThreadPainter(devices.at(i)));
633
painters.at(i)->moveToThread(threads.at(i));
634
painters.at(i)->connect(threads.at(i), SIGNAL(started()), painters.at(i), SLOT(draw()));
635
devices.at(i)->moveToThread(threads.at(i));
639
~PaintThreadManager() {
641
qDeleteAll(painters);
647
for (int i=0; i<numThreads; ++i)
648
threads.at(i)->start();
652
bool running = false;
653
for (int i=0; i<numThreads; ++i){
654
if (threads.at(i)->isRunning())
662
for (int i=0; i<numThreads; ++i) {
663
if (painters.at(i)->failed())
671
QList<QThread *> threads;
672
QList<Device *> devices;
673
QList<ThreadPainter *> painters;
678
This test uses QPainter to draw onto different QGLWidgets in
679
different threads at the same time. The ThreadSafeGLWidget is
680
necessary to handle paint and resize events that might come from
681
the main thread at any time while the test is running. The resize
682
and paint events would cause makeCurrent() calls to be issued from
683
within the QGLWidget while the widget's context was current in
684
another thread, which would cause errors.
686
void tst_QGLThreads::painterOnGLWidgetInThread()
688
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
689
QSKIP("No platformsupport for ThreadedOpenGL");
690
if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) ||
691
(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) {
692
QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.");
695
PaintThreadManager<GLWidgetWrapper> painterThreads(5);
696
painterThreads.start();
698
while (painterThreads.areRunning()) {
699
qApp->processEvents();
700
QThread::msleep(100);
702
QVERIFY(!painterThreads.failed());
706
This test uses QPainter to draw onto different QPixmaps in
707
different threads at the same time.
709
void tst_QGLThreads::painterOnPixmapInThread()
711
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)
712
|| !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedPixmaps))
713
QSKIP("No platformsupport for ThreadedOpenGL or ThreadedPixmaps");
715
QSKIP("Drawing text in threads onto X11 drawables currently crashes on some X11 servers.");
717
PaintThreadManager<PixmapWrapper> painterThreads(5);
718
painterThreads.start();
720
while (painterThreads.areRunning()) {
721
qApp->processEvents();
722
QThread::msleep(100);
724
QVERIFY(!painterThreads.failed());
727
/* This test uses QPainter to draw onto different QGLPixelBuffer
728
objects in different threads at the same time.
730
void tst_QGLThreads::painterOnPboInThread()
732
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
733
QSKIP("No platformsupport for ThreadedOpenGL");
734
if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) ||
735
(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) {
736
QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.");
739
if (!QGLPixelBuffer::hasOpenGLPbuffers()) {
740
QSKIP("This system doesn't support pbuffers.");
743
PaintThreadManager<PixelBufferWrapper> painterThreads(5);
744
painterThreads.start();
746
while (painterThreads.areRunning()) {
747
qApp->processEvents();
748
QThread::msleep(100);
750
QVERIFY(!painterThreads.failed());
753
/* This test uses QPainter to draw onto different
754
QGLFramebufferObjects (bound in a QGLWidget's context) in different
755
threads at the same time.
757
void tst_QGLThreads::painterOnFboInThread()
759
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
760
QSKIP("No platformsupport for ThreadedOpenGL");
761
if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) ||
762
(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) {
763
QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.");
766
if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) {
767
QSKIP("This system doesn't support framebuffer objects.");
770
PaintThreadManager<FrameBufferObjectWrapper> painterThreads(5);
771
painterThreads.start();
773
while (painterThreads.areRunning()) {
774
qApp->processEvents();
775
QThread::msleep(100);
777
QVERIFY(!painterThreads.failed());
780
int main(int argc, char **argv)
782
QApplication::setAttribute(Qt::AA_X11InitThreads);
783
QApplication app(argc, argv);
784
QTEST_DISABLE_KEYPAD_NAVIGATION \
787
return QTest::qExec(&tc, argc, argv);
790
#include "tst_qglthreads.moc"