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/qsysteminformationservice_p_p.h>
61#include <Qt3DCore/private/qthreadpooler_p.h>
62#include <Qt3DCore/private/qtickclock_p.h>
63#include <Qt3DCore/private/qtickclockservice_p.h>
64#include <Qt3DCore/private/qnodevisitor_p.h>
65#include <Qt3DCore/private/qnode_p.h>
66#include <Qt3DCore/private/qscene_p.h>
67
68#include <QtCore/QCoreApplication>
69#include <QtCore/QAbstractAnimation>
70
71QT_BEGIN_NAMESPACE
72
73namespace Qt3DCore {
74
75#if QT_CONFIG(animation)
76class RequestFrameAnimation final : public QAbstractAnimation
77{
78public:
79 RequestFrameAnimation(QObject *parent)
80 : QAbstractAnimation(parent)
81 {
82 }
83
84 ~RequestFrameAnimation() override;
85
86 int duration() const override { return 1; }
87 void updateCurrentTime(int currentTime) override { Q_UNUSED(currentTime) }
88};
89
90RequestFrameAnimation::~RequestFrameAnimation() = default;
91#else
92namespace {
93
94class RequestFrameEvent : public QEvent
95{
96public:
97 RequestFrameEvent()
98 : QEvent(static_cast<QEvent::Type>(RequestFrameEvent::requestEventType))
99 {}
100
101 static int eventType() { return RequestFrameEvent::requestEventType; }
102
103private:
104 static int requestEventType;
105};
106
107int RequestFrameEvent::requestEventType = QEvent::registerEventType();
108
109} // anonymous
110#endif
111
112/*!
113 \class Qt3DCore::QAspectManager
114 \internal
115*/
116QAspectManager::QAspectManager(QAspectEngine *parent)
117 : QObject(parent)
118 , m_engine(parent)
119 , m_root(nullptr)
120 , m_scheduler(new QScheduler(this))
121 , m_jobManager(new QAspectJobManager(this))
122 , m_changeArbiter(new QChangeArbiter(this))
123 , m_serviceLocator(new QServiceLocator(parent))
124 , m_simulationLoopRunning(false)
125 , m_driveMode(QAspectEngine::Automatic)
126 , m_postConstructorInit(nullptr)
127#if QT_CONFIG(animation)
128 , m_simulationAnimation(nullptr)
129#endif
130 , m_jobsInLastFrame(0)
131 , m_dumpJobs(false)
132{
133 qRegisterMetaType<QSurface *>(typeName: "QSurface*");
134 qCDebug(Aspects) << Q_FUNC_INFO;
135}
136
137QAspectManager::~QAspectManager()
138{
139 delete m_changeArbiter;
140 delete m_jobManager;
141 delete m_scheduler;
142}
143
144void QAspectManager::setRunMode(QAspectEngine::RunMode mode)
145{
146 qCDebug(Aspects) << Q_FUNC_INFO << "Running Loop Drive Mode set to" << mode;
147 m_driveMode = mode;
148}
149
150// Main thread (called by QAspectEngine)
151void QAspectManager::enterSimulationLoop()
152{
153 qCDebug(Aspects) << Q_FUNC_INFO;
154 m_simulationLoopRunning = true;
155
156 // Retrieve the frame advance service. Defaults to timer based if there is no renderer.
157 QAbstractFrameAdvanceService *frameAdvanceService =
158 m_serviceLocator->service<QAbstractFrameAdvanceService>(serviceType: QServiceLocator::FrameAdvanceService);
159
160 // Start the frameAdvanceService
161 frameAdvanceService->start();
162
163 // We are about to enter the simulation loop. Give aspects a chance to do any last
164 // pieces of initialization
165 qCDebug(Aspects) << "Calling onEngineStartup() for each aspect";
166 for (QAbstractAspect *aspect : qAsConst(t&: m_aspects)) {
167 qCDebug(Aspects) << "\t" << aspect->objectName();
168 aspect->onEngineStartup();
169 }
170 qCDebug(Aspects) << "Done calling onEngineStartup() for each aspect";
171
172 // Start running loop if Qt3D is in charge of driving it
173 if (m_driveMode == QAspectEngine::Automatic) {
174#if QT_CONFIG(animation)
175 if (!m_simulationAnimation) {
176 m_simulationAnimation = new RequestFrameAnimation(this);
177 connect(sender: m_simulationAnimation, signal: &QAbstractAnimation::finished, context: this, slot: [this]() {
178 processFrame();
179 if (m_simulationLoopRunning && m_driveMode == QAspectEngine::Automatic)
180 requestNextFrame();
181 });
182 }
183#endif
184 requestNextFrame();
185 }
186}
187
188// Main thread (called by QAspectEngine)
189void QAspectManager::exitSimulationLoop()
190{
191 qCDebug(Aspects) << Q_FUNC_INFO;
192
193 // If this fails, simulation loop is already exited so nothing to do
194 if (!m_simulationLoopRunning) {
195 qCDebug(Aspects) << "Simulation loop was not running. Nothing to do";
196 return;
197 }
198
199#if QT_CONFIG(animation)
200 if (m_simulationAnimation)
201 m_simulationAnimation->stop();
202#endif
203
204 QAbstractFrameAdvanceService *frameAdvanceService =
205 m_serviceLocator->service<QAbstractFrameAdvanceService>(serviceType: QServiceLocator::FrameAdvanceService);
206 if (frameAdvanceService)
207 frameAdvanceService->stop();
208
209 // Give any aspects a chance to unqueue any asynchronous work they
210 // may have scheduled that would otherwise potentially deadlock or
211 // cause races. For example, the QLogicAspect queues up a vector of
212 // QNodeIds to be processed by a callback on the main thread. However,
213 // if we don't unqueue this work and release its semaphore, the logic
214 // aspect would cause a deadlock when trying to exit the inner loop.
215 // This is because we call this function from the main thread and the
216 // logic aspect is waiting for the main thread to execute the
217 // QLogicComponent::onFrameUpdate() callback.
218 for (QAbstractAspect *aspect : qAsConst(t&: m_aspects))
219 aspect->d_func()->onEngineAboutToShutdown();
220
221 // Process any pending changes from the frontend before we shut the aspects down
222 m_changeArbiter->syncChanges();
223
224 // Give aspects a chance to perform any shutdown actions. This may include unqueuing
225 // any blocking work on the main thread that could potentially deadlock during shutdown.
226 qCDebug(Aspects) << "Calling onEngineShutdown() for each aspect";
227 for (QAbstractAspect *aspect : qAsConst(t&: m_aspects)) {
228 qCDebug(Aspects) << "\t" << aspect->objectName();
229 aspect->onEngineShutdown();
230 }
231 qCDebug(Aspects) << "Done calling onEngineShutdown() for each aspect";
232
233 m_simulationLoopRunning = false;
234 qCDebug(Aspects) << "exitSimulationLoop completed";
235}
236
237bool QAspectManager::isShuttingDown() const
238{
239 return !m_simulationLoopRunning;
240}
241
242/*!
243 \internal
244
245 Called by the QAspectThread's run() method immediately after the manager
246 has been created
247*/
248void QAspectManager::initialize()
249{
250 qCDebug(Aspects) << Q_FUNC_INFO;
251 m_jobManager->initialize();
252 m_scheduler->setAspectManager(this);
253 m_changeArbiter->initialize(jobManager: m_jobManager);
254}
255
256/*!
257 \internal
258
259 Called by the QAspectThread's run() method immediately after the manager's
260 exec() function has returned.
261*/
262void QAspectManager::shutdown()
263{
264 qCDebug(Aspects) << Q_FUNC_INFO;
265
266 // Aspects must be deleted in the Thread they were created in
267}
268
269// MainThread called by QAspectEngine::setRootEntity
270void QAspectManager::setRootEntity(Qt3DCore::QEntity *root, const QVector<QNode *> &nodes)
271{
272 qCDebug(Aspects) << Q_FUNC_INFO;
273
274 if (root == m_root)
275 return;
276
277 if (m_root) {
278 // TODO: Delete all backend nodes. This is to be symmetric with how
279 // we create them below in the call to setRootAndCreateNodes
280 }
281
282 m_root = root;
283
284 if (m_root) {
285
286 QVector<NodeTreeChange> nodeTreeChanges;
287 nodeTreeChanges.reserve(asize: nodes.size());
288
289 for (QNode *n : nodes) {
290 nodeTreeChanges.push_back(t: {
291 .id: n->id(),
292 .metaObj: QNodePrivate::get(q: n)->m_typeInfo,
293 .type: NodeTreeChange::Added,
294 .node: n
295 });
296 }
297
298 for (QAbstractAspect *aspect : qAsConst(t&: m_aspects))
299 aspect->d_func()->setRootAndCreateNodes(rootObject: m_root, nodesTreeChanges: nodeTreeChanges);
300 }
301}
302
303
304// Main Thread -> immediately following node insertion
305void QAspectManager::addNodes(const QVector<QNode *> &nodes)
306{
307 // We record the nodes added information, which we will actually use when
308 // processFrame is called (later but within the same loop of the even loop
309 // as this call) The idea is we want to avoid modifying the backend tree if
310 // the Renderer hasn't allowed processFrame to continue yet
311
312 QVector<NodeTreeChange> treeChanges;
313 treeChanges.reserve(asize: nodes.size());
314
315 for (QNode *node : nodes) {
316 treeChanges.push_back(t: { .id: node->id(),
317 .metaObj: QNodePrivate::get(q: node)->m_typeInfo,
318 .type: NodeTreeChange::Added,
319 .node: node });
320 }
321
322 m_nodeTreeChanges += treeChanges;
323}
324
325// Main Thread -> immediately following node destruction (call from QNode dtor)
326void QAspectManager::removeNodes(const QVector<QNode *> &nodes)
327{
328 // We record the nodes removed information, which we will actually use when
329 // processFrame is called (later but within the same loop of the even loop
330 // as this call) The idea is we want to avoid modifying the backend tree if
331 // the Renderer hasn't allowed processFrame to continue yet The drawback is
332 // that when processFrame is processed, the QNode* pointer might be invalid by
333 // that point. Therefore we record all we need to remove the object.
334
335 for (QNode *node : nodes) {
336 // In addition, we check if we contain an Added change for a given node
337 // that is now about to be destroyed. If so we remove the Added change
338 // entirely
339
340 m_nodeTreeChanges.erase(abegin: std::remove_if(first: m_nodeTreeChanges.begin(),
341 last: m_nodeTreeChanges.end(),
342 pred: [&node] (const NodeTreeChange &change) { return change.id == node->id(); }),
343 aend: m_nodeTreeChanges.end());
344
345 m_nodeTreeChanges.push_back(t: { .id: node->id(),
346 .metaObj: QNodePrivate::get(q: node)->m_typeInfo,
347 .type: NodeTreeChange::Removed,
348 .node: nullptr });
349 }
350}
351
352/*!
353 * \internal
354 *
355 * Registers a new \a aspect.
356 */
357void QAspectManager::registerAspect(QAbstractAspect *aspect)
358{
359 qCDebug(Aspects) << "Registering aspect";
360
361 if (aspect != nullptr) {
362 m_aspects.append(t: aspect);
363 QAbstractAspectPrivate::get(aspect)->m_aspectManager = this;
364 QAbstractAspectPrivate::get(aspect)->m_jobManager = m_jobManager;
365 QAbstractAspectPrivate::get(aspect)->m_arbiter = m_changeArbiter;
366
367 // Allow the aspect to do some work now that it is registered
368 aspect->onRegistered();
369 }
370 else {
371 qCWarning(Aspects) << "Failed to register aspect";
372 }
373 qCDebug(Aspects) << "Completed registering aspect";
374}
375
376/*!
377 * \internal
378 *
379 * Calls QAbstractAspect::onUnregistered(), unregisters the aspect from the
380 * change arbiter and unsets the arbiter, job manager and aspect manager.
381 * Operations are performed in the reverse order to registerAspect.
382 */
383void QAspectManager::unregisterAspect(Qt3DCore::QAbstractAspect *aspect)
384{
385 qCDebug(Aspects) << "Unregistering aspect";
386 Q_ASSERT(aspect);
387 aspect->onUnregistered();
388 QAbstractAspectPrivate::get(aspect)->m_arbiter = nullptr;
389 QAbstractAspectPrivate::get(aspect)->m_jobManager = nullptr;
390 QAbstractAspectPrivate::get(aspect)->m_aspectManager = nullptr;
391 m_aspects.removeOne(t: aspect);
392 qCDebug(Aspects) << "Completed unregistering aspect";
393}
394
395const QVector<QAbstractAspect *> &QAspectManager::aspects() const
396{
397 return m_aspects;
398}
399
400QAbstractAspectJobManager *QAspectManager::jobManager() const
401{
402 return m_jobManager;
403}
404
405QChangeArbiter *QAspectManager::changeArbiter() const
406{
407 return m_changeArbiter;
408}
409
410QServiceLocator *QAspectManager::serviceLocator() const
411{
412 return m_serviceLocator.data();
413}
414
415void QAspectManager::setPostConstructorInit(NodePostConstructorInit *postConstructorInit)
416{
417 m_postConstructorInit = postConstructorInit;
418}
419
420QNode *QAspectManager::lookupNode(QNodeId id) const
421{
422 if (!m_root)
423 return nullptr;
424
425 QNodePrivate *d = QNodePrivate::get(q: m_root);
426 return d->m_scene ? d->m_scene->lookupNode(id) : nullptr;
427}
428
429QVector<QNode *> QAspectManager::lookupNodes(const QVector<QNodeId> &ids) const
430{
431 if (!m_root)
432 return {};
433
434 QNodePrivate *d = QNodePrivate::get(q: m_root);
435 return d->m_scene ? d->m_scene->lookupNodes(ids) : QVector<QNode *>{};
436}
437
438void QAspectManager::dumpJobsOnNextFrame()
439{
440 m_dumpJobs = true;
441}
442
443#if !QT_CONFIG(animation)
444/*!
445 \internal
446 \brief Drives the Qt3D simulation loop in the main thread
447 */
448bool QAspectManager::event(QEvent *e)
449{
450 if (e->type() == RequestFrameEvent::eventType()) {
451
452 // Process current frame
453 processFrame();
454
455 // Request next frame if we are still running and if Qt3D is driving
456 // the loop
457 if (m_simulationLoopRunning && m_driveMode == QAspectEngine::Automatic)
458 requestNextFrame();
459
460 return true;
461 }
462
463 return QObject::event(e);
464}
465#endif
466
467void QAspectManager::requestNextFrame()
468{
469 qCDebug(Aspects) << "Requesting new Frame";
470 // Post event in the event loop to force
471 // next frame to be processed
472#if QT_CONFIG(animation)
473 m_simulationAnimation->start();
474#else
475 QCoreApplication::postEvent(this, new RequestFrameEvent());
476#endif
477}
478
479void QAspectManager::processFrame()
480{
481 qCDebug(Aspects) << "Processing Frame";
482
483 // Retrieve the frame advance service. Defaults to timer based if there is no renderer.
484 QAbstractFrameAdvanceService *frameAdvanceService =
485 m_serviceLocator->service<QAbstractFrameAdvanceService>(serviceType: QServiceLocator::FrameAdvanceService);
486
487 const qint64 t = frameAdvanceService->waitForNextFrame();
488 if (t < 0)
489 return;
490
491 // Distribute accumulated changes. This includes changes sent from the frontend
492 // to the backend nodes. We call this before the call to m_scheduler->update() to ensure
493 // that any property changes do not set dirty flags in a data race with the renderer's
494 // submission thread which may be looking for dirty flags, acting upon them and then
495 // clearing the dirty flags.
496 //
497 // Doing this as the first call in the new frame ensures the lock free approach works
498 // without any such data race.
499 {
500 // scope for QTaskLogger
501 QTaskLogger logger(m_serviceLocator->systemInformation(), 4096, 0, QTaskLogger::AspectJob);
502
503 // Tell the NodePostConstructorInit to process any pending nodes which will add them to our list of
504 // tree changes
505 m_postConstructorInit->processNodes();
506
507 // Add and Remove Nodes
508 const QVector<NodeTreeChange> nodeTreeChanges = std::move(m_nodeTreeChanges);
509 for (const NodeTreeChange &change : nodeTreeChanges) {
510 // Buckets ensure that even if we have intermingled node added / removed
511 // buckets, we preserve the order of the sequences
512
513 for (QAbstractAspect *aspect : qAsConst(t&: m_aspects)) {
514 switch (change.type) {
515 case NodeTreeChange::Added:
516 aspect->d_func()->createBackendNode(change);
517 break;
518 case NodeTreeChange::Removed:
519 aspect->d_func()->clearBackendNode(change);
520 break;
521 }
522 }
523 }
524
525 // Sync node / subnode relationship changes
526 const auto dirtySubNodes = m_changeArbiter->takeDirtyFrontEndSubNodes();
527 if (dirtySubNodes.size())
528 for (QAbstractAspect *aspect : qAsConst(t&: m_aspects))
529 QAbstractAspectPrivate::get(aspect)->syncDirtyFrontEndSubNodes(nodes: dirtySubNodes);
530
531 // Sync property updates
532 const auto dirtyFrontEndNodes = m_changeArbiter->takeDirtyFrontEndNodes();
533 if (dirtyFrontEndNodes.size())
534 for (QAbstractAspect *aspect : qAsConst(t&: m_aspects))
535 QAbstractAspectPrivate::get(aspect)->syncDirtyFrontEndNodes(nodes: dirtyFrontEndNodes);
536
537 // TO DO: Having this done in the main thread actually means aspects could just
538 // as simply read info out of the Frontend classes without risk of introducing
539 // races. This could therefore be removed for Qt 6.
540 m_changeArbiter->syncChanges();
541 }
542
543 // For each Aspect
544 // Ask them to launch set of jobs for the current frame
545 // Updates matrices, bounding volumes, render bins ...
546 m_jobsInLastFrame = m_scheduler->scheduleAndWaitForFrameAspectJobs(time: t, dumpJobs: m_dumpJobs);
547 m_dumpJobs = false;
548
549 // Tell the aspect the frame is complete (except rendering)
550 for (QAbstractAspect *aspect : qAsConst(t&: m_aspects))
551 QAbstractAspectPrivate::get(aspect)->frameDone();
552}
553
554} // namespace Qt3DCore
555
556QT_END_NAMESPACE
557

source code of qt3d/src/core/aspects/qaspectmanager.cpp