1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtTest/QtTest> |
30 | #include <QtCore/QtCore> |
31 | #include <QtGui/QtGui> |
32 | #include <private/qguiapplication_p.h> |
33 | #include <qpa/qplatformintegration.h> |
34 | #include <QtWidgets/QApplication> |
35 | #include <QtOpenGL/QtOpenGL> |
36 | #include <qelapsedtimer.h> |
37 | #include "tst_qglthreads.h" |
38 | |
39 | #ifndef QT_OPENGL_ES_2 |
40 | #include <QtGui/QOpenGLFunctions_1_0> |
41 | #endif |
42 | |
43 | #define RUNNING_TIME 5000 |
44 | |
45 | tst_QGLThreads::tst_QGLThreads(QObject *parent) |
46 | : QObject(parent) |
47 | { |
48 | } |
49 | |
50 | /* |
51 | |
52 | swapInThread |
53 | |
54 | The purpose of this testcase is to verify that it is possible to do rendering into |
55 | a GL context from the GUI thread, then swap the contents in from a background thread. |
56 | |
57 | The usecase for this is to have the background thread do the waiting for vertical |
58 | sync while the GUI thread is idle. |
59 | |
60 | Currently the locking is handled directly in the paintEvent(). For the actual usecase |
61 | in Qt, the locking is done in the windowsurface before starting any drawing while |
62 | unlocking is done after all drawing has been done. |
63 | */ |
64 | |
65 | |
66 | class SwapThread : public QThread |
67 | { |
68 | Q_OBJECT |
69 | public: |
70 | SwapThread(QGLWidget *widget) |
71 | : m_context(widget->context()) |
72 | , m_swapTriggered(false) |
73 | { |
74 | moveToThread(thread: this); |
75 | } |
76 | |
77 | void run() { |
78 | QElapsedTimer timer; |
79 | timer.start(); |
80 | while (timer.elapsed() < RUNNING_TIME) { |
81 | lock(); |
82 | waitForReadyToSwap(); |
83 | |
84 | m_context->makeCurrent(); |
85 | m_context->swapBuffers(); |
86 | m_context->doneCurrent(); |
87 | |
88 | m_context->moveToThread(qApp->thread()); |
89 | |
90 | signalSwapDone(); |
91 | unlock(); |
92 | } |
93 | |
94 | m_swapTriggered = false; |
95 | } |
96 | |
97 | void lock() { m_mutex.lock(); } |
98 | void unlock() { m_mutex.unlock(); } |
99 | |
100 | void waitForSwapDone() { if (m_swapTriggered) m_swapDone.wait(lockedMutex: &m_mutex); } |
101 | void waitForReadyToSwap() { if (!m_swapTriggered) m_readyToSwap.wait(lockedMutex: &m_mutex); } |
102 | |
103 | void signalReadyToSwap() |
104 | { |
105 | if (!isRunning()) |
106 | return; |
107 | m_readyToSwap.wakeAll(); |
108 | m_swapTriggered = true; |
109 | } |
110 | |
111 | void signalSwapDone() |
112 | { |
113 | m_swapTriggered = false; |
114 | m_swapDone.wakeAll(); |
115 | } |
116 | |
117 | private: |
118 | QGLContext *m_context; |
119 | QMutex m_mutex; |
120 | QWaitCondition m_readyToSwap; |
121 | QWaitCondition m_swapDone; |
122 | |
123 | bool m_swapTriggered; |
124 | }; |
125 | |
126 | class ForegroundWidget : public QGLWidget |
127 | { |
128 | public: |
129 | ForegroundWidget(const QGLFormat &format) |
130 | : QGLWidget(format), m_thread(0) |
131 | { |
132 | setAutoBufferSwap(false); |
133 | } |
134 | |
135 | void resizeEvent(QResizeEvent *e) |
136 | { |
137 | m_thread->lock(); |
138 | QGLWidget::resizeEvent(e); |
139 | m_thread->unlock(); |
140 | } |
141 | |
142 | void paintEvent(QPaintEvent *) |
143 | { |
144 | m_thread->lock(); |
145 | m_thread->waitForSwapDone(); |
146 | |
147 | makeCurrent(); |
148 | QPainter p(this); |
149 | p.fillRect(rect(), color: QColor(QRandomGenerator::global()->bounded(highest: 256), QRandomGenerator::global()->bounded(highest: 256), QRandomGenerator::global()->bounded(highest: 256))); |
150 | p.setPen(Qt::red); |
151 | p.setFont(QFont("SansSerif" , 24)); |
152 | p.drawText(r: rect(), flags: Qt::AlignCenter, text: "This is an autotest" ); |
153 | p.end(); |
154 | doneCurrent(); |
155 | |
156 | if (m_thread->isRunning()) { |
157 | context()->moveToThread(thread: m_thread); |
158 | m_thread->signalReadyToSwap(); |
159 | } |
160 | |
161 | m_thread->unlock(); |
162 | |
163 | update(); |
164 | } |
165 | |
166 | void setThread(SwapThread *thread) { |
167 | m_thread = thread; |
168 | } |
169 | |
170 | SwapThread *m_thread; |
171 | }; |
172 | |
173 | void tst_QGLThreads::swapInThread() |
174 | { |
175 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::ThreadedOpenGL)) |
176 | QSKIP("No platformsupport for ThreadedOpenGL" ); |
177 | QGLFormat format; |
178 | format.setSwapInterval(1); |
179 | ForegroundWidget widget(format); |
180 | SwapThread thread(&widget); |
181 | widget.setThread(&thread); |
182 | widget.show(); |
183 | |
184 | QVERIFY(QTest::qWaitForWindowExposed(&widget)); |
185 | thread.start(); |
186 | |
187 | while (thread.isRunning()) { |
188 | qApp->processEvents(); |
189 | } |
190 | |
191 | widget.hide(); |
192 | |
193 | QVERIFY(true); |
194 | } |
195 | |
196 | /* |
197 | renderInThread |
198 | |
199 | This test sets up a scene and renders it in a different thread. |
200 | For simplicity, the scene is simply a bunch of rectangles, but |
201 | if that works, we're in good shape.. |
202 | */ |
203 | |
204 | static inline float qrandom() { return (QRandomGenerator::global()->bounded(highest: 100)) / 100.f; } |
205 | |
206 | void renderAScene(int w, int h) |
207 | { |
208 | QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); |
209 | |
210 | if (QOpenGLContext::currentContext()->isOpenGLES()) { |
211 | Q_UNUSED(w); |
212 | Q_UNUSED(h); |
213 | QGLShaderProgram program; |
214 | program.addShaderFromSourceCode(type: QGLShader::Vertex, source: "attribute highp vec2 pos; void main() { gl_Position = vec4(pos.xy, 1.0, 1.0); }" ); |
215 | program.addShaderFromSourceCode(type: QGLShader::Fragment, source: "uniform lowp vec4 color; void main() { gl_FragColor = color; }" ); |
216 | program.bindAttributeLocation(name: "pos" , location: 0); |
217 | program.bind(); |
218 | |
219 | funcs->glEnableVertexAttribArray(index: 0); |
220 | |
221 | for (int i=0; i<1000; ++i) { |
222 | GLfloat pos[] = { |
223 | (QRandomGenerator::global()->bounded(highest: 100)) / 100.f, |
224 | (QRandomGenerator::global()->bounded(highest: 100)) / 100.f, |
225 | (QRandomGenerator::global()->bounded(highest: 100)) / 100.f, |
226 | (QRandomGenerator::global()->bounded(highest: 100)) / 100.f, |
227 | (QRandomGenerator::global()->bounded(highest: 100)) / 100.f, |
228 | (QRandomGenerator::global()->bounded(highest: 100)) / 100.f |
229 | }; |
230 | |
231 | funcs->glVertexAttribPointer(indx: 0, size: 2, GL_FLOAT, GL_FALSE, stride: 0, ptr: pos); |
232 | funcs->glDrawArrays(GL_TRIANGLE_STRIP, first: 0, count: 3); |
233 | } |
234 | } else { |
235 | #ifndef QT_OPENGL_ES_2 |
236 | QOpenGLFunctions_1_0 *gl1funcs = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_1_0>(); |
237 | gl1funcs->initializeOpenGLFunctions(); |
238 | |
239 | gl1funcs->glViewport(x: 0, y: 0, width: w, height: h); |
240 | |
241 | gl1funcs->glMatrixMode(GL_PROJECTION); |
242 | gl1funcs->glLoadIdentity(); |
243 | gl1funcs->glFrustum(left: 0, right: w, bottom: h, top: 0, zNear: 1, zFar: 100); |
244 | gl1funcs->glTranslated(x: 0, y: 0, z: -1); |
245 | |
246 | gl1funcs->glMatrixMode(GL_MODELVIEW); |
247 | gl1funcs->glLoadIdentity(); |
248 | |
249 | for (int i=0;i<1000; ++i) { |
250 | gl1funcs->glBegin(GL_TRIANGLES); |
251 | gl1funcs->glColor3f(red: qrandom(), green: qrandom(), blue: qrandom()); |
252 | gl1funcs->glVertex2f(x: qrandom() * w, y: qrandom() * h); |
253 | gl1funcs->glColor3f(red: qrandom(), green: qrandom(), blue: qrandom()); |
254 | gl1funcs->glVertex2f(x: qrandom() * w, y: qrandom() * h); |
255 | gl1funcs->glColor3f(red: qrandom(), green: qrandom(), blue: qrandom()); |
256 | gl1funcs->glVertex2f(x: qrandom() * w, y: qrandom() * h); |
257 | gl1funcs->glEnd(); |
258 | } |
259 | #endif |
260 | } |
261 | } |
262 | |
263 | class ThreadSafeGLWidget : public QGLWidget |
264 | { |
265 | public: |
266 | ThreadSafeGLWidget(QWidget *parent = 0) : QGLWidget(parent) {} |
267 | void paintEvent(QPaintEvent *) |
268 | { |
269 | // ignored as we're anyway swapping as fast as we can |
270 | }; |
271 | |
272 | void resizeEvent(QResizeEvent *e) |
273 | { |
274 | mutex.lock(); |
275 | newSize = e->size(); |
276 | mutex.unlock(); |
277 | }; |
278 | |
279 | QMutex mutex; |
280 | QSize newSize; |
281 | }; |
282 | |
283 | class SceneRenderingThread : public QThread |
284 | { |
285 | Q_OBJECT |
286 | public: |
287 | SceneRenderingThread(ThreadSafeGLWidget *widget) |
288 | : m_widget(widget) |
289 | { |
290 | moveToThread(thread: this); |
291 | m_size = widget->size(); |
292 | } |
293 | |
294 | void run() { |
295 | QElapsedTimer timer; |
296 | timer.start(); |
297 | failure = false; |
298 | |
299 | while (timer.elapsed() < RUNNING_TIME && !failure) { |
300 | |
301 | m_widget->makeCurrent(); |
302 | |
303 | m_widget->mutex.lock(); |
304 | QSize s = m_widget->newSize; |
305 | m_widget->mutex.unlock(); |
306 | |
307 | QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); |
308 | if (s != m_size) { |
309 | funcs->glViewport(x: 0, y: 0, width: s.width(), height: s.height()); |
310 | } |
311 | |
312 | if (QGLContext::currentContext() != m_widget->context()) { |
313 | failure = true; |
314 | break; |
315 | } |
316 | |
317 | funcs->glClear(GL_COLOR_BUFFER_BIT); |
318 | |
319 | int w = m_widget->width(); |
320 | int h = m_widget->height(); |
321 | |
322 | renderAScene(w, h); |
323 | |
324 | int color; |
325 | funcs->glReadPixels(x: w / 2, y: h / 2, width: 1, height: 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels: &color); |
326 | |
327 | m_widget->swapBuffers(); |
328 | } |
329 | |
330 | m_widget->doneCurrent(); |
331 | } |
332 | |
333 | bool failure; |
334 | |
335 | private: |
336 | ThreadSafeGLWidget *m_widget; |
337 | QSize m_size; |
338 | }; |
339 | |
340 | void tst_QGLThreads::renderInThread_data() |
341 | { |
342 | QTest::addColumn<bool>(name: "resize" ); |
343 | QTest::addColumn<bool>(name: "update" ); |
344 | |
345 | QTest::newRow(dataTag: "basic" ) << false << false; |
346 | QTest::newRow(dataTag: "with-resize" ) << true << false; |
347 | QTest::newRow(dataTag: "with-update" ) << false << true; |
348 | QTest::newRow(dataTag: "with-resize-and-update" ) << true << true; |
349 | } |
350 | |
351 | void tst_QGLThreads::renderInThread() |
352 | { |
353 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::ThreadedOpenGL)) |
354 | QSKIP("No platformsupport for ThreadedOpenGL" ); |
355 | |
356 | QFETCH(bool, resize); |
357 | QFETCH(bool, update); |
358 | |
359 | #if defined(Q_OS_MACOS) |
360 | if (resize) |
361 | QSKIP("gldSetZero crashes in render thread, QTBUG-68524" ); |
362 | #endif |
363 | |
364 | ThreadSafeGLWidget widget; |
365 | widget.resize(w: 200, h: 200); |
366 | SceneRenderingThread thread(&widget); |
367 | |
368 | widget.show(); |
369 | QVERIFY(QTest::qWaitForWindowExposed(&widget)); |
370 | widget.doneCurrent(); |
371 | |
372 | widget.context()->moveToThread(thread: &thread); |
373 | |
374 | thread.start(); |
375 | |
376 | int value = 10; |
377 | while (thread.isRunning()) { |
378 | if (resize) |
379 | widget.resize(w: 200 + value, h: 200 + value); |
380 | if (update) |
381 | widget.update(ax: 100 + value, ay: 100 + value, aw: 20, ah: 20); |
382 | qApp->processEvents(); |
383 | value = -value; |
384 | |
385 | QThread::msleep(100); |
386 | } |
387 | |
388 | QVERIFY(!thread.failure); |
389 | } |
390 | |
391 | class Device |
392 | { |
393 | public: |
394 | virtual ~Device() {} |
395 | virtual QPaintDevice *realPaintDevice() = 0; |
396 | virtual void prepareDevice() {} |
397 | virtual void moveToThread(QThread *) {} |
398 | }; |
399 | |
400 | class GLWidgetWrapper : public Device |
401 | { |
402 | public: |
403 | GLWidgetWrapper() { |
404 | widget.resize(w: 150, h: 150); |
405 | widget.show(); |
406 | QVERIFY(QTest::qWaitForWindowExposed(&widget)); |
407 | widget.doneCurrent(); |
408 | } |
409 | QPaintDevice *realPaintDevice() { return &widget; } |
410 | void moveToThread(QThread *thread) { widget.context()->moveToThread(thread); } |
411 | |
412 | ThreadSafeGLWidget widget; |
413 | }; |
414 | |
415 | class PixmapWrapper : public Device |
416 | { |
417 | public: |
418 | PixmapWrapper() { pixmap = new QPixmap(512, 512); } |
419 | ~PixmapWrapper() { delete pixmap; } |
420 | QPaintDevice *realPaintDevice() { return pixmap; } |
421 | |
422 | QPixmap *pixmap; |
423 | }; |
424 | |
425 | class PixelBufferWrapper : public Device |
426 | { |
427 | public: |
428 | PixelBufferWrapper() { pbuffer = new QGLPixelBuffer(512, 512); } |
429 | ~PixelBufferWrapper() { delete pbuffer; } |
430 | QPaintDevice *realPaintDevice() { return pbuffer; } |
431 | void moveToThread(QThread *thread) { pbuffer->context()->moveToThread(thread); } |
432 | |
433 | QGLPixelBuffer *pbuffer; |
434 | }; |
435 | |
436 | |
437 | class FrameBufferObjectWrapper : public Device |
438 | { |
439 | public: |
440 | FrameBufferObjectWrapper() { |
441 | widget.makeCurrent(); |
442 | fbo = new QGLFramebufferObject(512, 512); |
443 | widget.doneCurrent(); |
444 | } |
445 | ~FrameBufferObjectWrapper() { delete fbo; } |
446 | QPaintDevice *realPaintDevice() { return fbo; } |
447 | void prepareDevice() { widget.makeCurrent(); } |
448 | void moveToThread(QThread *thread) { widget.context()->moveToThread(thread); } |
449 | |
450 | ThreadSafeGLWidget widget; |
451 | QGLFramebufferObject *fbo; |
452 | }; |
453 | |
454 | |
455 | class ThreadPainter : public QObject |
456 | { |
457 | Q_OBJECT |
458 | public: |
459 | ThreadPainter(Device *pd) : device(pd), fail(true) { |
460 | pixmap = QPixmap(40, 40); |
461 | pixmap.fill(fillColor: Qt::green); |
462 | QPainter p(&pixmap); |
463 | p.drawLine(x1: 0, y1: 0, x2: 40, y2: 40); |
464 | p.drawLine(x1: 0, y1: 40, x2: 40, y2: 0); |
465 | } |
466 | |
467 | public slots: |
468 | void draw() { |
469 | bool beginFailed = false; |
470 | QElapsedTimer timer; |
471 | timer.start(); |
472 | int rotAngle = 10; |
473 | device->prepareDevice(); |
474 | QPaintDevice *paintDevice = device->realPaintDevice(); |
475 | QSize s(paintDevice->width(), paintDevice->height()); |
476 | while (timer.elapsed() < RUNNING_TIME) { |
477 | QPainter p; |
478 | if (!p.begin(paintDevice)) { |
479 | beginFailed = true; |
480 | break; |
481 | } |
482 | p.translate(dx: s.width()/2, dy: s.height()/2); |
483 | p.rotate(a: rotAngle); |
484 | p.translate(dx: -s.width()/2, dy: -s.height()/2); |
485 | p.fillRect(x: 0, y: 0, w: s.width(), h: s.height(), c: Qt::red); |
486 | QRect rect(QPoint(0, 0), s); |
487 | p.drawPixmap(x: 10, y: 10, pm: pixmap); |
488 | p.drawTiledPixmap(x: 50, y: 50, w: 100, h: 100, pm: pixmap); |
489 | p.drawText(p: rect.center(), s: "This is a piece of text" ); |
490 | p.end(); |
491 | rotAngle += 2; |
492 | QThread::msleep(20); |
493 | } |
494 | |
495 | device->moveToThread(qApp->thread()); |
496 | |
497 | fail = beginFailed; |
498 | QThread::currentThread()->quit(); |
499 | } |
500 | |
501 | bool failed() { return fail; } |
502 | |
503 | private: |
504 | QPixmap pixmap; |
505 | Device *device; |
506 | bool fail; |
507 | }; |
508 | |
509 | template <class T> |
510 | class PaintThreadManager |
511 | { |
512 | public: |
513 | PaintThreadManager(int count) : numThreads(count) |
514 | { |
515 | for (int i=0; i<numThreads; ++i) |
516 | devices.append(new T); |
517 | // Wait until resize events are processed on the internal |
518 | // QGLWidgets of the buffers to suppress errors |
519 | // about makeCurrent() from the wrong thread. |
520 | QCoreApplication::processEvents(); |
521 | for (int i=0; i<numThreads; ++i) { |
522 | devices.append(new T); |
523 | threads.append(t: new QThread); |
524 | painters.append(t: new ThreadPainter(devices.at(i))); |
525 | painters.at(i)->moveToThread(thread: threads.at(i)); |
526 | painters.at(i)->connect(sender: threads.at(i), SIGNAL(started()), receiver: painters.at(i), SLOT(draw())); |
527 | devices.at(i)->moveToThread(threads.at(i)); |
528 | } |
529 | } |
530 | |
531 | ~PaintThreadManager() { |
532 | qDeleteAll(c: threads); |
533 | qDeleteAll(c: painters); |
534 | qDeleteAll(c: devices); |
535 | } |
536 | |
537 | |
538 | void start() { |
539 | for (int i=0; i<numThreads; ++i) |
540 | threads.at(i)->start(); |
541 | } |
542 | |
543 | bool areRunning() { |
544 | bool running = false; |
545 | for (int i=0; i<numThreads; ++i){ |
546 | if (threads.at(i)->isRunning()) |
547 | running = true; |
548 | } |
549 | |
550 | return running; |
551 | } |
552 | |
553 | bool failed() { |
554 | for (int i=0; i<numThreads; ++i) { |
555 | if (painters.at(i)->failed()) |
556 | return true; |
557 | } |
558 | |
559 | return false; |
560 | } |
561 | |
562 | private: |
563 | QList<QThread *> threads; |
564 | QList<Device *> devices; |
565 | QList<ThreadPainter *> painters; |
566 | int numThreads; |
567 | }; |
568 | |
569 | /* |
570 | This test uses QPainter to draw onto different QGLWidgets in |
571 | different threads at the same time. The ThreadSafeGLWidget is |
572 | necessary to handle paint and resize events that might come from |
573 | the main thread at any time while the test is running. The resize |
574 | and paint events would cause makeCurrent() calls to be issued from |
575 | within the QGLWidget while the widget's context was current in |
576 | another thread, which would cause errors. |
577 | */ |
578 | void tst_QGLThreads::painterOnGLWidgetInThread() |
579 | { |
580 | //QTBUG-46446 tst_qglthreads is unstable on windows 7 |
581 | if (QGuiApplication::platformName().compare(s: "windows 7" , cs: Qt::CaseInsensitive)) |
582 | QSKIP("Doesn't work on this platform. QTBUG-46446" ); |
583 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::ThreadedOpenGL)) |
584 | QSKIP("No platformsupport for ThreadedOpenGL" ); |
585 | if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || |
586 | (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) { |
587 | QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0." ); |
588 | } |
589 | |
590 | PaintThreadManager<GLWidgetWrapper> painterThreads(5); |
591 | painterThreads.start(); |
592 | |
593 | while (painterThreads.areRunning()) { |
594 | qApp->processEvents(); |
595 | QThread::msleep(100); |
596 | } |
597 | QVERIFY(!painterThreads.failed()); |
598 | } |
599 | |
600 | /* |
601 | This test uses QPainter to draw onto different QPixmaps in |
602 | different threads at the same time. |
603 | */ |
604 | void tst_QGLThreads::painterOnPixmapInThread() |
605 | { |
606 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::ThreadedOpenGL) |
607 | || !QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::ThreadedPixmaps)) |
608 | QSKIP("No platformsupport for ThreadedOpenGL or ThreadedPixmaps" ); |
609 | PaintThreadManager<PixmapWrapper> painterThreads(5); |
610 | painterThreads.start(); |
611 | |
612 | while (painterThreads.areRunning()) { |
613 | qApp->processEvents(); |
614 | QThread::msleep(100); |
615 | } |
616 | QVERIFY(!painterThreads.failed()); |
617 | } |
618 | |
619 | /* This test uses QPainter to draw onto different QGLPixelBuffer |
620 | objects in different threads at the same time. |
621 | */ |
622 | void tst_QGLThreads::painterOnPboInThread() |
623 | { |
624 | //QTBUG-46446 tst_qglthreads is unstable on windows 7 |
625 | if (QGuiApplication::platformName().compare(s: "windows 7" , cs: Qt::CaseInsensitive)) |
626 | QSKIP("Doesn't work on this platform. QTBUG-46446" ); |
627 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::ThreadedOpenGL)) |
628 | QSKIP("No platformsupport for ThreadedOpenGL" ); |
629 | if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || |
630 | (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) { |
631 | QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0." ); |
632 | } |
633 | |
634 | if (!QGLPixelBuffer::hasOpenGLPbuffers()) { |
635 | QSKIP("This system doesn't support pbuffers." ); |
636 | } |
637 | |
638 | PaintThreadManager<PixelBufferWrapper> painterThreads(5); |
639 | painterThreads.start(); |
640 | |
641 | while (painterThreads.areRunning()) { |
642 | qApp->processEvents(); |
643 | QThread::msleep(100); |
644 | } |
645 | QVERIFY(!painterThreads.failed()); |
646 | } |
647 | |
648 | /* This test uses QPainter to draw onto different |
649 | QGLFramebufferObjects (bound in a QGLWidget's context) in different |
650 | threads at the same time. |
651 | */ |
652 | void tst_QGLThreads::painterOnFboInThread() |
653 | { |
654 | //QTBUG-46446 tst_qglthreads is unstable on windows 7 |
655 | if (QGuiApplication::platformName().compare(s: "windows 7" , cs: Qt::CaseInsensitive)) |
656 | QSKIP("Doesn't work on this platform. QTBUG-46446" ); |
657 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::ThreadedOpenGL)) |
658 | QSKIP("No platformsupport for ThreadedOpenGL" ); |
659 | if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || |
660 | (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) { |
661 | QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0." ); |
662 | } |
663 | |
664 | if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) { |
665 | QSKIP("This system doesn't support framebuffer objects." ); |
666 | } |
667 | |
668 | PaintThreadManager<FrameBufferObjectWrapper> painterThreads(5); |
669 | painterThreads.start(); |
670 | |
671 | while (painterThreads.areRunning()) { |
672 | qApp->processEvents(); |
673 | QThread::msleep(100); |
674 | } |
675 | QVERIFY(!painterThreads.failed()); |
676 | } |
677 | |
678 | int main(int argc, char **argv) |
679 | { |
680 | QApplication app(argc, argv); |
681 | QTEST_DISABLE_KEYPAD_NAVIGATION \ |
682 | |
683 | tst_QGLThreads tc; |
684 | return QTest::qExec(testObject: &tc, argc, argv); |
685 | } |
686 | |
687 | #include "tst_qglthreads.moc" |
688 | |