1/****************************************************************************
2**
3** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
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: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 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 Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qaspectmanager_p.h"
41
42#include <Qt3DCore/qabstractaspect.h>
43#include <Qt3DCore/qentity.h>
44#include <QtCore/QAbstractEventDispatcher>
45#include <QtCore/QEventLoop>
46#include <QtCore/QThread>
47#include <QtCore/QWaitCondition>
48#include <QtGui/QSurface>
49
50#include <Qt3DCore/private/corelogging_p.h>
51#include <Qt3DCore/private/qabstractaspect_p.h>
52#include <Qt3DCore/private/qabstractaspectjobmanager_p.h>
53#include <Qt3DCore/private/qabstractframeadvanceservice_p.h>
54// TODO Make the kind of job manager configurable (e.g. ThreadWeaver vs Intel TBB)
55#include <Qt3DCore/private/qaspectjobmanager_p.h>
56#include <Qt3DCore/private/qaspectjob_p.h>
57#include <Qt3DCore/private/qchangearbiter_p.h>
58#include <Qt3DCore/private/qscheduler_p.h>
59#include <Qt3DCore/private/qservicelocator_p.h>
60#include <Qt3DCore/private/qthreadpooler_p.h>
61#include <Qt3DCore/private/qtickclock_p.h>
62#include <Qt3DCore/private/qtickclockservice_p.h>
63
64#if defined(QT3D_CORE_JOB_TIMING)
65#include <QElapsedTimer>
66#endif
67
68QT_BEGIN_NAMESPACE
69
70namespace Qt3DCore {
71
72/*!
73 \class Qt3DCore::QAspectManager
74 \internal
75*/
76QAspectManager::QAspectManager(QObject *parent)
77 : QObject(parent)
78 , m_root(nullptr)
79 , m_scheduler(new QScheduler(this))
80 , m_jobManager(new QAspectJobManager(this))
81 , m_changeArbiter(new QChangeArbiter(this))
82 , m_serviceLocator(new QServiceLocator())
83 , m_waitForEndOfSimulationLoop(0)
84 , m_waitForStartOfSimulationLoop(0)
85 , m_waitForEndOfExecLoop(0)
86 , m_waitForQuit(0)
87{
88 qRegisterMetaType<QSurface *>("QSurface*");
89 m_runSimulationLoop.fetchAndStoreOrdered(0);
90 m_runMainLoop.fetchAndStoreOrdered(1);
91 qCDebug(Aspects) << Q_FUNC_INFO;
92}
93
94QAspectManager::~QAspectManager()
95{
96 delete m_changeArbiter;
97 delete m_jobManager;
98 delete m_scheduler;
99}
100
101// Main thread (called by QAspectEngine)
102void QAspectManager::enterSimulationLoop()
103{
104 qCDebug(Aspects) << Q_FUNC_INFO;
105 m_runSimulationLoop.fetchAndStoreOrdered(1);
106
107 // Wake up QAspectThread's event loop
108 thread()->eventDispatcher()->wakeUp();
109
110 // We wait for the setRootEntity on the aspectManager to have completed
111 // This ensures we cannot shutdown before the aspects have had a chance
112 // to be initialized
113 m_waitForStartOfSimulationLoop.acquire(1);
114}
115
116// Main thread (called by QAspectEngine)
117void QAspectManager::exitSimulationLoop()
118{
119 qCDebug(Aspects) << Q_FUNC_INFO;
120
121 // If this fails, simulation loop is already exited so nothing to do
122 if (!m_runSimulationLoop.testAndSetOrdered(1, 0)) {
123 qCDebug(Aspects) << "Simulation loop was not running. Nothing to do";
124 return;
125 }
126
127 QAbstractFrameAdvanceService *frameAdvanceService =
128 m_serviceLocator->service<QAbstractFrameAdvanceService>(QServiceLocator::FrameAdvanceService);
129 if (frameAdvanceService)
130 frameAdvanceService->stop();
131
132 // Give any aspects a chance to unqueue any asynchronous work they
133 // may have scheduled that would otherwise potentially deadlock or
134 // cause races. For example, the QLogicAspect queues up a vector of
135 // QNodeIds to be processed by a callback on the main thread. However,
136 // if we don't unqueue this work and release its semaphore, the logic
137 // aspect would cause a deadlock when trying to exit the inner loop.
138 // This is because we call this function from the main thread and the
139 // logic aspect is waiting for the main thread to execute the
140 // QLogicComponent::onFrameUpdate() callback.
141 for (QAbstractAspect *aspect : qAsConst(m_aspects))
142 aspect->d_func()->onEngineAboutToShutdown();
143
144
145 qCDebug(Aspects) << "exitSimulationLoop waiting for exec loop to terminate";
146 // Wait until the simulation loop is fully exited and the aspects are done
147 // processing any final changes and have had onEngineShutdown() called on them
148 m_waitForEndOfSimulationLoop.acquire(1);
149 qCDebug(Aspects) << "exitSimulationLoop completed";
150}
151
152bool QAspectManager::isShuttingDown() const
153{
154 return !m_runSimulationLoop.load();
155}
156
157/*!
158 \internal
159
160 Called by the QAspectThread's run() method immediately after the manager
161 has been created
162*/
163void QAspectManager::initialize()
164{
165 qCDebug(Aspects) << Q_FUNC_INFO;
166 m_jobManager->initialize();
167 m_scheduler->setAspectManager(this);
168 m_changeArbiter->initialize(m_jobManager);
169}
170
171/*!
172 \internal
173
174 Called by the QAspectThread's run() method immediately after the manager's
175 exec() function has returned.
176*/
177void QAspectManager::shutdown()
178{
179 qCDebug(Aspects) << Q_FUNC_INFO;
180
181 for (QAbstractAspect *aspect : qAsConst(m_aspects))
182 m_changeArbiter->unregisterSceneObserver(aspect->d_func());
183
184 // Aspects must be deleted in the Thread they were created in
185}
186
187// QAspectThread:: queued invoked by QAspectEngine::setRootEntity
188void QAspectManager::setRootEntity(Qt3DCore::QEntity *root, const QVector<Qt3DCore::QNodeCreatedChangeBasePtr> &changes)
189{
190 qCDebug(Aspects) << Q_FUNC_INFO;
191
192 if (root == m_root)
193 return;
194
195 if (m_root) {
196 // TODO: Delete all backend nodes. This is to be symmetric with how
197 // we create them below in the call to setRootAndCreateNodes
198 }
199
200 m_root = root;
201
202 if (m_root) {
203 for (QAbstractAspect *aspect : qAsConst(m_aspects))
204 aspect->d_func()->setRootAndCreateNodes(m_root, changes);
205 }
206}
207
208/*!
209 * \internal
210 *
211 * Registers a new \a aspect.
212 */
213void QAspectManager::registerAspect(QAbstractAspect *aspect)
214{
215 qCDebug(Aspects) << "Registering aspect";
216
217 if (aspect != nullptr) {
218 m_aspects.append(aspect);
219 QAbstractAspectPrivate::get(aspect)->m_aspectManager = this;
220 QAbstractAspectPrivate::get(aspect)->m_jobManager = m_jobManager;
221 QAbstractAspectPrivate::get(aspect)->m_arbiter = m_changeArbiter;
222 // Register sceneObserver with the QChangeArbiter
223 m_changeArbiter->registerSceneObserver(aspect->d_func());
224
225 // Allow the aspect to do some work now that it is registered
226 aspect->onRegistered();
227 }
228 else {
229 qCWarning(Aspects) << "Failed to register aspect";
230 }
231 qCDebug(Aspects) << "Completed registering aspect";
232}
233
234/*!
235 * \internal
236 *
237 * Calls QAbstractAspect::onUnregistered(), unregisters the aspect from the
238 * change arbiter and unsets the arbiter, job manager and aspect manager.
239 * Operations are performed in the reverse order to registerAspect.
240 */
241void QAspectManager::unregisterAspect(Qt3DCore::QAbstractAspect *aspect)
242{
243 qCDebug(Aspects) << "Unregistering aspect";
244 Q_ASSERT(aspect);
245 aspect->onUnregistered();
246 m_changeArbiter->unregisterSceneObserver(aspect->d_func());
247 QAbstractAspectPrivate::get(aspect)->m_arbiter = nullptr;
248 QAbstractAspectPrivate::get(aspect)->m_jobManager = nullptr;
249 QAbstractAspectPrivate::get(aspect)->m_aspectManager = nullptr;
250 m_aspects.removeOne(aspect);
251 qCDebug(Aspects) << "Completed unregistering aspect";
252}
253
254void QAspectManager::exec()
255{
256 // Gentlemen, start your engines
257 QEventLoop eventLoop;
258
259 // Enter the engine loop
260 qCDebug(Aspects) << Q_FUNC_INFO << "***** Entering main loop *****";
261 while (m_runMainLoop.load()) {
262 // Process events until we're told to start the simulation loop
263 while (m_runMainLoop.load() && !m_runSimulationLoop.load())
264 eventLoop.processEvents(QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents);
265
266 if (!m_runSimulationLoop.load())
267 break;
268
269 // Retrieve the frame advance service. Defaults to timer based if there is no renderer.
270 QAbstractFrameAdvanceService *frameAdvanceService =
271 m_serviceLocator->service<QAbstractFrameAdvanceService>(QServiceLocator::FrameAdvanceService);
272
273 // Start the frameAdvanceService
274 frameAdvanceService->start();
275
276 // We are about to enter the simulation loop. Give aspects a chance to do any last
277 // pieces of initialization
278 qCDebug(Aspects) << "Calling onEngineStartup() for each aspect";
279 for (QAbstractAspect *aspect : qAsConst(m_aspects)) {
280 qCDebug(Aspects) << "\t" << aspect->objectName();
281 aspect->onEngineStartup();
282 }
283 qCDebug(Aspects) << "Done calling onEngineStartup() for each aspect";
284 m_waitForStartOfSimulationLoop.release(1);
285
286 // Only enter main simulation loop once the renderer and other aspects are initialized
287 while (m_runSimulationLoop.load()) {
288 qint64 t = frameAdvanceService->waitForNextFrame();
289
290 // Distribute accumulated changes. This includes changes sent from the frontend
291 // to the backend nodes. We call this before the call to m_scheduler->update() to ensure
292 // that any property changes do not set dirty flags in a data race with the renderer's
293 // submission thread which may be looking for dirty flags, acting upon them and then
294 // clearing the dirty flags.
295 //
296 // Doing this as the first call in the new frame ensures the lock free approach works
297 // without any such data race.
298#if QT_CONFIG(qt3d_profile_jobs)
299 const quint32 arbiterId = 4096;
300 JobRunStats changeArbiterStats;
301 changeArbiterStats.jobId.typeAndInstance[0] = arbiterId;
302 changeArbiterStats.jobId.typeAndInstance[1] = 0;
303 changeArbiterStats.threadId = reinterpret_cast<quint64>(QThread::currentThreadId());
304 changeArbiterStats.startTime = QThreadPooler::m_jobsStatTimer.nsecsElapsed();
305#endif
306 m_changeArbiter->syncChanges();
307#if QT_CONFIG(qt3d_profile_jobs)
308 changeArbiterStats.endTime = QThreadPooler::m_jobsStatTimer.nsecsElapsed();
309 QThreadPooler::addJobLogStatsEntry(changeArbiterStats);
310#endif
311
312 // For each Aspect
313 // Ask them to launch set of jobs for the current frame
314 // Updates matrices, bounding volumes, render bins ...
315#if defined(QT3D_CORE_JOB_TIMING)
316 QElapsedTimer timer;
317 timer.start();
318#endif
319 m_scheduler->scheduleAndWaitForFrameAspectJobs(t);
320#if defined(QT3D_CORE_JOB_TIMING)
321 qDebug() << "Jobs took" << timer.nsecsElapsed() / 1.0e6;
322#endif
323
324 // Process any pending events
325 eventLoop.processEvents();
326 } // End of simulation loop
327
328 // Process any pending changes from the frontend before we shut the aspects down
329 m_changeArbiter->syncChanges();
330
331 // Give aspects a chance to perform any shutdown actions. This may include unqueuing
332 // any blocking work on the main thread that could potentially deadlock during shutdown.
333 qCDebug(Aspects) << "Calling onEngineShutdown() for each aspect";
334 for (QAbstractAspect *aspect : qAsConst(m_aspects)) {
335 qCDebug(Aspects) << "\t" << aspect->objectName();
336 aspect->onEngineShutdown();
337 }
338 qCDebug(Aspects) << "Done calling onEngineShutdown() for each aspect";
339
340 // Wake up the main thread which is waiting for us inside of exitSimulationLoop()
341 m_waitForEndOfSimulationLoop.release(1);
342 } // End of main loop
343 qCDebug(Aspects) << Q_FUNC_INFO << "***** Exited main loop *****";
344
345 m_waitForEndOfExecLoop.release(1);
346 m_waitForQuit.acquire(1);
347}
348
349void QAspectManager::quit()
350{
351 qCDebug(Aspects) << Q_FUNC_INFO;
352
353 Q_ASSERT_X(m_runSimulationLoop.load() == 0, "QAspectManagr::quit()", "Inner loop is still running");
354 m_runMainLoop.fetchAndStoreOrdered(0);
355
356 // Wake up QAspectThread's event loop if needed
357 thread()->eventDispatcher()->wakeUp();
358
359 // We need to wait for the QAspectManager exec loop to terminate
360 m_waitForEndOfExecLoop.acquire(1);
361 m_waitForQuit.release(1);
362
363 qCDebug(Aspects) << Q_FUNC_INFO << "Exiting";
364}
365
366const QVector<QAbstractAspect *> &QAspectManager::aspects() const
367{
368 return m_aspects;
369}
370
371QAbstractAspectJobManager *QAspectManager::jobManager() const
372{
373 return m_jobManager;
374}
375
376QChangeArbiter *QAspectManager::changeArbiter() const
377{
378 return m_changeArbiter;
379}
380
381QServiceLocator *QAspectManager::serviceLocator() const
382{
383 return m_serviceLocator.data();
384}
385
386} // namespace Qt3DCore
387
388QT_END_NAMESPACE
389
390