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 | |
49 | class tst_ThreadPooler : public QObject |
50 | { |
51 | Q_OBJECT |
52 | |
53 | public: |
54 | tst_ThreadPooler() {} |
55 | ~tst_ThreadPooler() {} |
56 | |
57 | private: |
58 | Qt3DCore::QAspectJobManager *m_jobManager; |
59 | |
60 | private 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 | |
72 | typedef Qt3DCore::QAspectJobManager JobManager; |
73 | typedef void (*TestFunction)(QAtomicInt *, int *); |
74 | typedef void (*MassFunction)(QVector3D *data); |
75 | |
76 | void perThreadFunction(void *arg) |
77 | { |
78 | ((QAtomicInt *)arg)->ref(); |
79 | } |
80 | |
81 | // General test AspectJob |
82 | |
83 | class TestAspectJob : public Qt3DCore::QAspectJob |
84 | { |
85 | public: |
86 | TestAspectJob(TestFunction func, QAtomicInt *counter, int *value); |
87 | |
88 | void setMutex(QMutex *mutex); |
89 | |
90 | void run() override; |
91 | |
92 | private: |
93 | TestFunction m_func; |
94 | QAtomicInt *m_counter; |
95 | int *m_value; |
96 | QMutex *m_mutex; |
97 | }; |
98 | |
99 | TestAspectJob::TestAspectJob(TestFunction func, QAtomicInt *counter, int *value) |
100 | : m_func(func), |
101 | m_counter(counter), |
102 | m_value(value) |
103 | { |
104 | } |
105 | |
106 | void TestAspectJob::setMutex(QMutex *mutex) |
107 | { |
108 | m_mutex = mutex; |
109 | } |
110 | |
111 | void TestAspectJob::run() |
112 | { |
113 | m_func(m_counter, m_value); |
114 | } |
115 | |
116 | // Mass test AspectJob |
117 | |
118 | class MassAspectJob : public Qt3DCore::QAspectJob |
119 | { |
120 | public: |
121 | MassAspectJob(MassFunction func, QVector3D *data); |
122 | |
123 | void run() override; |
124 | |
125 | private: |
126 | MassFunction m_func; |
127 | QVector3D *m_data; |
128 | }; |
129 | |
130 | struct PerThreadUniqueData |
131 | { |
132 | |
133 | }; |
134 | |
135 | MassAspectJob::MassAspectJob(MassFunction func, QVector3D *data) |
136 | : m_func(func), |
137 | m_data(data) |
138 | { |
139 | } |
140 | |
141 | void MassAspectJob::run() |
142 | { |
143 | m_func(m_data); |
144 | } |
145 | |
146 | void incrementFunctionCallCounter(QAtomicInt *counter, int *value) |
147 | { |
148 | Q_UNUSED(value); |
149 | |
150 | counter->ref(); |
151 | } |
152 | |
153 | void 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 | |
163 | void multiplyBy2(QAtomicInt *counter, int *value) |
164 | { |
165 | Q_UNUSED(counter); |
166 | |
167 | *value = *value * 2; |
168 | } |
169 | |
170 | void 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 | |
183 | void tst_ThreadPooler::initTestCase() |
184 | { |
185 | m_jobManager = new JobManager(nullptr); |
186 | } |
187 | |
188 | void tst_ThreadPooler::cleanupTestCase() |
189 | { |
190 | delete m_jobManager; |
191 | } |
192 | |
193 | void 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 | |
207 | void 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 | */ |
233 | void 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 | */ |
267 | void 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 | |
288 | void 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 | |
317 | class PerThreadUniqueTester { |
318 | |
319 | public: |
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 | |
341 | private: |
342 | QAtomicInteger<quint64> m_globalAtomic; |
343 | QAtomicInt m_currentIndex; |
344 | }; |
345 | |
346 | void perThreadFunctionUnique(void *arg) |
347 | { |
348 | PerThreadUniqueTester *tester = reinterpret_cast<PerThreadUniqueTester *>(arg); |
349 | tester->updateGlobalAtomic(index: tester->currentJobIndex()); |
350 | } |
351 | |
352 | void 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 | |
369 | QTEST_APPLESS_MAIN(tst_ThreadPooler) |
370 | |
371 | #include "tst_threadpooler.moc" |
372 | |