|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the test suite of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
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 <QtOpenGL/QtOpenGL> |
|
46 #include "tst_qglthreads.h" |
|
47 |
|
48 #ifdef Q_WS_X11 |
|
49 #include <private/qt_x11_p.h> |
|
50 #endif |
|
51 |
|
52 #define RUNNING_TIME 5000 |
|
53 |
|
54 tst_QGLThreads::tst_QGLThreads(QObject *parent) |
|
55 : QObject(parent) |
|
56 { |
|
57 } |
|
58 |
|
59 |
|
60 |
|
61 /* |
|
62 |
|
63 swapInThread |
|
64 |
|
65 The purpose of this testcase is to verify that it is possible to do rendering into |
|
66 a GL context from the GUI thread, then swap the contents in from a background thread. |
|
67 |
|
68 The usecase for this is to have the background thread do the waiting for vertical |
|
69 sync while the GUI thread is idle. |
|
70 |
|
71 Currently the locking is handled directly in the paintEvent(). For the actual usecase |
|
72 in Qt, the locking is done in the windowsurface before starting any drawing while |
|
73 unlocking is done after all drawing has been done. |
|
74 */ |
|
75 |
|
76 |
|
77 class SwapThread : public QThread |
|
78 { |
|
79 Q_OBJECT |
|
80 public: |
|
81 SwapThread(QGLWidget *widget) |
|
82 : m_widget(widget) |
|
83 { |
|
84 moveToThread(this); |
|
85 } |
|
86 |
|
87 void run() { |
|
88 QTime time; |
|
89 time.start(); |
|
90 while (time.elapsed() < RUNNING_TIME) { |
|
91 lock(); |
|
92 wait(); |
|
93 |
|
94 m_widget->makeCurrent(); |
|
95 m_widget->swapBuffers(); |
|
96 m_widget->doneCurrent(); |
|
97 unlock(); |
|
98 } |
|
99 } |
|
100 |
|
101 void lock() { m_mutex.lock(); } |
|
102 void unlock() { m_mutex.unlock(); } |
|
103 |
|
104 void wait() { m_wait_condition.wait(&m_mutex); } |
|
105 void notify() { m_wait_condition.wakeAll(); } |
|
106 |
|
107 private: |
|
108 QGLWidget *m_widget; |
|
109 QMutex m_mutex; |
|
110 QWaitCondition m_wait_condition; |
|
111 }; |
|
112 |
|
113 class ForegroundWidget : public QGLWidget |
|
114 { |
|
115 public: |
|
116 ForegroundWidget(const QGLFormat &format) |
|
117 : QGLWidget(format), m_thread(0) |
|
118 { |
|
119 setAutoBufferSwap(false); |
|
120 } |
|
121 |
|
122 void paintEvent(QPaintEvent *) |
|
123 { |
|
124 m_thread->lock(); |
|
125 makeCurrent(); |
|
126 QPainter p(this); |
|
127 p.fillRect(rect(), QColor(rand() % 256, rand() % 256, rand() % 256)); |
|
128 p.setPen(Qt::red); |
|
129 p.setFont(QFont("SansSerif", 24)); |
|
130 p.drawText(rect(), Qt::AlignCenter, "This is an autotest"); |
|
131 p.end(); |
|
132 doneCurrent(); |
|
133 m_thread->notify(); |
|
134 m_thread->unlock(); |
|
135 |
|
136 update(); |
|
137 } |
|
138 |
|
139 void setThread(SwapThread *thread) { |
|
140 m_thread = thread; |
|
141 } |
|
142 |
|
143 SwapThread *m_thread; |
|
144 }; |
|
145 |
|
146 void tst_QGLThreads::swapInThread() |
|
147 { |
|
148 #ifdef Q_OS_MAC |
|
149 QSKIP("OpenGL threading tests are currently disabled on mac as they were causing reboots", SkipAll); |
|
150 #endif |
|
151 |
|
152 QGLFormat format; |
|
153 format.setSwapInterval(1); |
|
154 ForegroundWidget widget(format); |
|
155 SwapThread thread(&widget); |
|
156 widget.setThread(&thread); |
|
157 widget.show(); |
|
158 |
|
159 QTest::qWaitForWindowShown(&widget); |
|
160 thread.start(); |
|
161 |
|
162 while (thread.isRunning()) { |
|
163 qApp->processEvents(); |
|
164 } |
|
165 |
|
166 widget.hide(); |
|
167 |
|
168 QVERIFY(true); |
|
169 } |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 /* |
|
178 textureUploadInThread |
|
179 |
|
180 The purpose of this testcase is to verify that doing texture uploads in a background |
|
181 thread is possible and that it works. |
|
182 */ |
|
183 |
|
184 class CreateAndUploadThread : public QThread |
|
185 { |
|
186 Q_OBJECT |
|
187 public: |
|
188 CreateAndUploadThread(QGLWidget *shareWidget) |
|
189 { |
|
190 m_gl = new QGLWidget(0, shareWidget); |
|
191 moveToThread(this); |
|
192 } |
|
193 |
|
194 ~CreateAndUploadThread() |
|
195 { |
|
196 delete m_gl; |
|
197 } |
|
198 |
|
199 void run() { |
|
200 m_gl->makeCurrent(); |
|
201 QTime time; |
|
202 time.start(); |
|
203 while (time.elapsed() < RUNNING_TIME) { |
|
204 QImage image(400, 300, QImage::Format_RGB32); |
|
205 QPainter p(&image); |
|
206 p.fillRect(image.rect(), QColor(rand() % 256, rand() % 256, rand() % 256)); |
|
207 p.setPen(Qt::red); |
|
208 p.setFont(QFont("SansSerif", 24)); |
|
209 p.drawText(image.rect(), Qt::AlignCenter, "This is an autotest"); |
|
210 p.end(); |
|
211 m_gl->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption); |
|
212 createdAndUploaded(image); |
|
213 } |
|
214 } |
|
215 |
|
216 signals: |
|
217 void createdAndUploaded(const QImage &image); |
|
218 |
|
219 private: |
|
220 QGLWidget *m_gl; |
|
221 }; |
|
222 |
|
223 class TextureDisplay : public QGLWidget |
|
224 { |
|
225 Q_OBJECT |
|
226 public: |
|
227 void paintEvent(QPaintEvent *) { |
|
228 QPainter p(this); |
|
229 for (int i=0; i<m_images.size(); ++i) { |
|
230 p.drawImage(m_positions.at(i), m_images.at(i)); |
|
231 m_positions[i] += QPoint(1, 1); |
|
232 } |
|
233 update(); |
|
234 } |
|
235 |
|
236 public slots: |
|
237 void receiveImage(const QImage &image) { |
|
238 m_images << image; |
|
239 m_positions << QPoint(-rand() % width() / 2, -rand() % height() / 2); |
|
240 |
|
241 if (m_images.size() > 100) { |
|
242 m_images.takeFirst(); |
|
243 m_positions.takeFirst(); |
|
244 } |
|
245 } |
|
246 |
|
247 private: |
|
248 QList <QImage> m_images; |
|
249 QList <QPoint> m_positions; |
|
250 }; |
|
251 |
|
252 void tst_QGLThreads::textureUploadInThread() |
|
253 { |
|
254 #ifdef Q_OS_MAC |
|
255 QSKIP("OpenGL threading tests are currently disabled on mac as they were causing reboots", SkipAll); |
|
256 #endif |
|
257 |
|
258 TextureDisplay display; |
|
259 CreateAndUploadThread thread(&display); |
|
260 |
|
261 connect(&thread, SIGNAL(createdAndUploaded(QImage)), &display, SLOT(receiveImage(QImage))); |
|
262 |
|
263 display.show(); |
|
264 QTest::qWaitForWindowShown(&display); |
|
265 |
|
266 thread.start(); |
|
267 |
|
268 while (thread.isRunning()) { |
|
269 qApp->processEvents(); |
|
270 } |
|
271 |
|
272 QVERIFY(true); |
|
273 } |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 /* |
|
281 renderInThread |
|
282 |
|
283 This test sets up a scene and renders it in a different thread. |
|
284 For simplicity, the scene is simply a bunch of rectangles, but |
|
285 if that works, we're in good shape.. |
|
286 */ |
|
287 |
|
288 static inline float qrandom() { return (rand() % 100) / 100.f; } |
|
289 |
|
290 void renderAScene(int w, int h) |
|
291 { |
|
292 #ifdef QT_OPENGL_ES_2 |
|
293 QGLShaderProgram program; |
|
294 program.addShaderFromSourceCode(QGLShader::Vertex, "attribute highp vec2 pos; void main() { gl_Position = vec4(pos.xy, 1.0, 1.0); }"); |
|
295 program.addShaderFromSourceCode(QGLShader::Fragment, "uniform lowp vec4 color; void main() { gl_FragColor = color; }"); |
|
296 program.bindAttributeLocation("pos", 0); |
|
297 program.bind(); |
|
298 int colorId = program.uniformLocation("color"); |
|
299 |
|
300 glEnableVertexAttribArray(0); |
|
301 |
|
302 for (int i=0; i<1000; ++i) { |
|
303 GLfloat pos[] = { |
|
304 (rand() % 100) / 100., |
|
305 (rand() % 100) / 100., |
|
306 (rand() % 100) / 100., |
|
307 (rand() % 100) / 100., |
|
308 (rand() % 100) / 100., |
|
309 (rand() % 100) / 100. |
|
310 }; |
|
311 |
|
312 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos); |
|
313 glDrawArrays(GL_TRIANGLE_STRIP, 0, 3); |
|
314 } |
|
315 #else |
|
316 glViewport(0, 0, w, h); |
|
317 |
|
318 glMatrixMode(GL_PROJECTION); |
|
319 glLoadIdentity(); |
|
320 glFrustum(0, w, h, 0, 1, 100); |
|
321 glTranslated(0, 0, -1); |
|
322 |
|
323 glMatrixMode(GL_MODELVIEW); |
|
324 glLoadIdentity(); |
|
325 |
|
326 for (int i=0;i<1000; ++i) { |
|
327 glBegin(GL_TRIANGLES); |
|
328 glColor3f(qrandom(), qrandom(), qrandom()); |
|
329 glVertex2f(qrandom() * w, qrandom() * h); |
|
330 glColor3f(qrandom(), qrandom(), qrandom()); |
|
331 glVertex2f(qrandom() * w, qrandom() * h); |
|
332 glColor3f(qrandom(), qrandom(), qrandom()); |
|
333 glVertex2f(qrandom() * w, qrandom() * h); |
|
334 glEnd(); |
|
335 } |
|
336 #endif |
|
337 } |
|
338 |
|
339 class ThreadSafeGLWidget : public QGLWidget |
|
340 { |
|
341 public: |
|
342 void paintEvent(QPaintEvent *) |
|
343 { |
|
344 // ignored as we're anyway swapping as fast as we can |
|
345 }; |
|
346 |
|
347 void resizeEvent(QResizeEvent *e) |
|
348 { |
|
349 mutex.lock(); |
|
350 newSize = e->size(); |
|
351 mutex.unlock(); |
|
352 }; |
|
353 |
|
354 QMutex mutex; |
|
355 QSize newSize; |
|
356 }; |
|
357 |
|
358 class SceneRenderingThread : public QThread |
|
359 { |
|
360 Q_OBJECT |
|
361 public: |
|
362 SceneRenderingThread(ThreadSafeGLWidget *widget) |
|
363 : m_widget(widget) |
|
364 { |
|
365 moveToThread(this); |
|
366 m_size = widget->size(); |
|
367 } |
|
368 |
|
369 void run() { |
|
370 QTime time; |
|
371 time.start(); |
|
372 failure = false; |
|
373 |
|
374 m_widget->makeCurrent(); |
|
375 |
|
376 while (time.elapsed() < RUNNING_TIME && !failure) { |
|
377 |
|
378 |
|
379 m_widget->mutex.lock(); |
|
380 QSize s = m_widget->newSize; |
|
381 m_widget->mutex.unlock(); |
|
382 |
|
383 if (s != m_size) { |
|
384 glViewport(0, 0, s.width(), s.height()); |
|
385 } |
|
386 |
|
387 if (QGLContext::currentContext() != m_widget->context()) { |
|
388 failure = true; |
|
389 break; |
|
390 } |
|
391 |
|
392 glClear(GL_COLOR_BUFFER_BIT); |
|
393 |
|
394 int w = m_widget->width(); |
|
395 int h = m_widget->height(); |
|
396 |
|
397 renderAScene(w, h); |
|
398 |
|
399 int color; |
|
400 glReadPixels(w / 2, h / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &color); |
|
401 |
|
402 m_widget->swapBuffers(); |
|
403 } |
|
404 |
|
405 m_widget->doneCurrent(); |
|
406 } |
|
407 |
|
408 bool failure; |
|
409 |
|
410 private: |
|
411 ThreadSafeGLWidget *m_widget; |
|
412 QSize m_size; |
|
413 }; |
|
414 |
|
415 void tst_QGLThreads::renderInThread_data() |
|
416 { |
|
417 QTest::addColumn<bool>("resize"); |
|
418 QTest::addColumn<bool>("update"); |
|
419 |
|
420 QTest::newRow("basic") << false << false; |
|
421 QTest::newRow("with-resize") << true << false; |
|
422 QTest::newRow("with-update") << false << true; |
|
423 QTest::newRow("with-resize-and-update") << true << true; |
|
424 } |
|
425 |
|
426 void tst_QGLThreads::renderInThread() |
|
427 { |
|
428 #ifdef Q_OS_MAC |
|
429 QSKIP("OpenGL threading tests are currently disabled on mac as they were causing reboots", SkipAll); |
|
430 #endif |
|
431 |
|
432 QFETCH(bool, resize); |
|
433 QFETCH(bool, update); |
|
434 |
|
435 ThreadSafeGLWidget widget; |
|
436 widget.resize(200, 200); |
|
437 SceneRenderingThread thread(&widget); |
|
438 |
|
439 widget.show(); |
|
440 QTest::qWaitForWindowShown(&widget); |
|
441 widget.doneCurrent(); |
|
442 |
|
443 thread.start(); |
|
444 |
|
445 int value = 10; |
|
446 while (thread.isRunning()) { |
|
447 if (resize) |
|
448 widget.resize(200 + value, 200 + value); |
|
449 if (update) |
|
450 widget.update(100 + value, 100 + value, 20, 20); |
|
451 qApp->processEvents(); |
|
452 value = -value; |
|
453 |
|
454 #ifdef Q_WS_WIN |
|
455 Sleep(100); |
|
456 #else |
|
457 usleep(100 * 1000); |
|
458 #endif |
|
459 } |
|
460 |
|
461 QVERIFY(!thread.failure); |
|
462 } |
|
463 |
|
464 |
|
465 |
|
466 |
|
467 int main(int argc, char **argv) |
|
468 { |
|
469 #ifdef Q_WS_X11 |
|
470 XInitThreads(); |
|
471 #endif |
|
472 |
|
473 QApplication app(argc, argv); |
|
474 QTEST_DISABLE_KEYPAD_NAVIGATION \ |
|
475 |
|
476 tst_QGLThreads tc; |
|
477 return QTest::qExec(&tc, argc, argv); |
|
478 } |
|
479 |
|
480 #include "tst_qglthreads.moc" |