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 Qt3D module 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/QThread>
31#include <QtCore/QAtomicInt>
32#include <QtCore/QMutexLocker>
33#include <QtCore/QMutex>
34#include <QtGui/QVector3D>
35#include <QtGui/QMatrix4x4>
36#include <QtCore/QElapsedTimer>
37#include <QtCore/QTimer>
38
39#include <Qt3DCore/private/qaspectjobmanager_p.h>
40#include <Qt3DCore/private/qabstractaspectjobmanager_p.h>
41#include <Qt3DCore/private/qthreadpooler_p.h>
42#include <Qt3DCore/qaspectjob.h>
43#include <Qt3DCore/qt3dcore_global.h>
44#include <qmath.h>
45
46// Add DEFINES += QT_BUILD_INTERNAL at least to Qt3d's core.pro
47// when running these tests. It makes QAspectJobManager available.
48
49class tst_ThreadPooler : public QObject
50{
51 Q_OBJECT
52
53public:
54 tst_ThreadPooler() {}
55 ~tst_ThreadPooler() {}
56
57private:
58 Qt3DCore::QAspectJobManager *m_jobManager;
59
60private Q_SLOTS:
61 void initTestCase();
62 void cleanupTestCase();
63
64 void defaultPerThread();
65 void defaultAspectQueue();
66 void doubleAspectQueue();
67 void dependencyAspectQueue();
68 void massTest();
69 void perThreadUniqueCall();
70};
71
72typedef Qt3DCore::QAspectJobManager JobManager;
73typedef void (*TestFunction)(QAtomicInt *, int *);
74typedef void (*MassFunction)(QVector3D *data);
75
76void perThreadFunction(void *arg)
77{
78 ((QAtomicInt *)arg)->ref();
79}
80
81// General test AspectJob
82
83class TestAspectJob : public Qt3DCore::QAspectJob
84{
85public:
86 TestAspectJob(TestFunction func, QAtomicInt *counter, int *value);
87
88 void setMutex(QMutex *mutex);
89
90 void run() override;
91
92private:
93 TestFunction m_func;
94 QAtomicInt *m_counter;
95 int *m_value;
96 QMutex *m_mutex;
97};
98
99TestAspectJob::TestAspectJob(TestFunction func, QAtomicInt *counter, int *value)
100 : m_func(func),
101 m_counter(counter),
102 m_value(value)
103{
104}
105
106void TestAspectJob::setMutex(QMutex *mutex)
107{
108 m_mutex = mutex;
109}
110
111void TestAspectJob::run()
112{
113 m_func(m_counter, m_value);
114}
115
116// Mass test AspectJob
117
118class MassAspectJob : public Qt3DCore::QAspectJob
119{
120public:
121 MassAspectJob(MassFunction func, QVector3D *data);
122
123 void run() override;
124
125private:
126 MassFunction m_func;
127 QVector3D *m_data;
128};
129
130struct PerThreadUniqueData
131{
132
133};
134
135MassAspectJob::MassAspectJob(MassFunction func, QVector3D *data)
136 : m_func(func),
137 m_data(data)
138{
139}
140
141void MassAspectJob::run()
142{
143 m_func(m_data);
144}
145
146void incrementFunctionCallCounter(QAtomicInt *counter, int *value)
147{
148 Q_UNUSED(value);
149
150 counter->ref();
151}
152
153void add2(QAtomicInt *counter, int *value)
154{
155 Q_UNUSED(counter);
156
157 // Sleep for a while so that we see that multiply task really
158 // wait for us
159 QThread::currentThread()->msleep(400);
160 *value = *value + 2;
161}
162
163void multiplyBy2(QAtomicInt *counter, int *value)
164{
165 Q_UNUSED(counter);
166
167 *value = *value * 2;
168}
169
170void massTestFunction(QVector3D *data)
171{
172 QVector3D point(4.5f, 4.5f, 4.5f);
173
174 QMatrix4x4 matrix;
175 matrix.lookAt(eye: QVector3D(10.0f, 1.5f, 2.0f), center: QVector3D(1.0f, -1.0f, 1.0f),
176 up: QVector3D(0.0f, 0.0f, 1.0f));
177 QVector3D result = matrix.map(point);
178 data->setX(result.x());
179 data->setY(result.y());
180 data->setZ(result.z());
181}
182
183void tst_ThreadPooler::initTestCase()
184{
185 m_jobManager = new JobManager(nullptr);
186}
187
188void tst_ThreadPooler::cleanupTestCase()
189{
190 delete m_jobManager;
191}
192
193void tst_ThreadPooler::defaultPerThread()
194{
195 // GIVEN
196 QAtomicInt callCounter;
197 int maxThreadCount = QThread::idealThreadCount();
198 callCounter.storeRelaxed(newValue: 0);
199
200 // WHEN
201 m_jobManager->waitForPerThreadFunction(func: perThreadFunction, arg: &callCounter);
202
203 // THEN
204 QVERIFY(maxThreadCount == callCounter.loadRelaxed());
205}
206
207void tst_ThreadPooler::defaultAspectQueue()
208{
209 // GIVEN
210 QAtomicInt callCounter;
211 int value = 0; // Not used in this test
212 QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList;
213 callCounter.storeRelaxed(newValue: 0);
214 const int jobCount = 5;
215
216 // WHEN
217 for (int i = 0; i < jobCount; i++) {
218 QSharedPointer<TestAspectJob> job(new TestAspectJob(incrementFunctionCallCounter,
219 &callCounter, &value));
220 jobList.append(t: job);
221 }
222 m_jobManager->enqueueJobs(jobQueue: jobList);
223 m_jobManager->waitForAllJobs();
224
225 // THEN
226 QVERIFY(jobCount == callCounter.loadRelaxed());
227}
228
229/*
230 * Feeds two list of jobs to queue. The pooler should be able to add jobs on
231 * the second list to execution. Single call to wait finish.
232 */
233void tst_ThreadPooler::doubleAspectQueue()
234{
235 // GIVEN
236 QAtomicInt callCounter;
237 int value = 0; // Not used in this test
238 QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList;
239 callCounter.storeRelaxed(newValue: 0);
240 const int jobCount = 3;
241
242 // WHEN
243 for (int i = 0; i < jobCount; i++) {
244 QSharedPointer<TestAspectJob> job(new TestAspectJob(incrementFunctionCallCounter,
245 &callCounter, &value));
246 jobList.append(t: job);
247 }
248 m_jobManager->enqueueJobs(jobQueue: jobList);
249
250 QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList2;
251 for (int i = 0; i < jobCount; i++) {
252 QSharedPointer<TestAspectJob> job(new TestAspectJob(incrementFunctionCallCounter,
253 &callCounter, &value));
254 jobList2.append(t: job);
255 }
256 m_jobManager->enqueueJobs(jobQueue: jobList2);
257
258 m_jobManager->waitForAllJobs();
259
260 // THEN
261 QVERIFY(jobCount * 2 == callCounter.loadRelaxed());
262}
263
264/*
265 * Default test for jobs that have dependencies.
266 */
267void tst_ThreadPooler::dependencyAspectQueue()
268{
269 // GIVEN
270 QAtomicInt callCounter; // Not used in this test
271 int value = 2;
272 QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList;
273
274 // WHEN
275 QSharedPointer<TestAspectJob> job1(new TestAspectJob(add2, &callCounter, &value));
276 jobList.append(t: job1);
277 QSharedPointer<TestAspectJob> job2(new TestAspectJob(multiplyBy2, &callCounter, &value));
278 job2->addDependency(dependency: job1);
279 jobList.append(t: job2);
280 m_jobManager->enqueueJobs(jobQueue: jobList);
281 m_jobManager->waitForAllJobs();
282
283 // THEN
284 // value should be (2+2)*2 = 8
285 QVERIFY(value == 8);
286}
287
288void tst_ThreadPooler::massTest()
289{
290 // GIVEN
291 const int mass = 600; // 600
292 QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList;
293 QVector3D data[3 * mass];
294
295 // WHEN
296 QElapsedTimer timer;
297 timer.start();
298
299 for (int i = 0; i < mass; i++) {
300 QSharedPointer<MassAspectJob> job1(new MassAspectJob(massTestFunction, &(data[i * 3 + 0])));
301 jobList.append(t: job1);
302 QSharedPointer<MassAspectJob> job2(new MassAspectJob(massTestFunction, &(data[i * 3 + 1])));
303 job2->addDependency(dependency: job1);
304 jobList.append(t: job2);
305 QSharedPointer<MassAspectJob> job3(new MassAspectJob(massTestFunction, &(data[i * 3 + 2])));
306 job3->addDependency(dependency: job2);
307 jobList.append(t: job3);
308 }
309
310 m_jobManager->enqueueJobs(jobQueue: jobList);
311 m_jobManager->waitForAllJobs();
312
313 // THEN
314 qDebug() << "timer.elapsed() = " << timer.elapsed() << " ms";
315}
316
317class PerThreadUniqueTester {
318
319public:
320 PerThreadUniqueTester()
321 {
322 m_globalAtomic.fetchAndStoreOrdered(newValue: 0);
323 m_currentIndex.fetchAndStoreOrdered(newValue: 0);
324 }
325
326 int currentJobIndex()
327 {
328 return m_currentIndex.fetchAndAddOrdered(valueToAdd: 1);
329 }
330
331 void updateGlobalAtomic(int index)
332 {
333 m_globalAtomic.fetchAndAddOrdered(valueToAdd: qPow(x: 3, y: index));
334 }
335
336 quint64 globalAtomicValue() const
337 {
338 return m_globalAtomic.loadRelaxed();
339 }
340
341private:
342 QAtomicInteger<quint64> m_globalAtomic;
343 QAtomicInt m_currentIndex;
344};
345
346void perThreadFunctionUnique(void *arg)
347{
348 PerThreadUniqueTester *tester = reinterpret_cast<PerThreadUniqueTester *>(arg);
349 tester->updateGlobalAtomic(index: tester->currentJobIndex());
350}
351
352void tst_ThreadPooler::perThreadUniqueCall()
353{
354 // GIVEN
355 PerThreadUniqueTester tester;
356 const int maxThreads = QThread::idealThreadCount();
357 quint64 maxValue = 0;
358 for (int i = 0; i < maxThreads; ++i) {
359 maxValue += qPow(x: 3, y: i);
360 }
361
362 // WHEN
363 m_jobManager->waitForPerThreadFunction(func: perThreadFunctionUnique, arg: &tester);
364
365 // THEN
366 QCOMPARE(maxValue, tester.globalAtomicValue());
367}
368
369QTEST_APPLESS_MAIN(tst_ThreadPooler)
370
371#include "tst_threadpooler.moc"
372

source code of qt3d/tests/auto/core/threadpooler/tst_threadpooler.cpp