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 | |
71 | QT_BEGIN_NAMESPACE |
72 | |
73 | namespace Qt3DCore { |
74 | |
75 | #if QT_CONFIG(animation) |
76 | class RequestFrameAnimation final : public QAbstractAnimation |
77 | { |
78 | public: |
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 | |
90 | RequestFrameAnimation::~RequestFrameAnimation() = default; |
91 | #else |
92 | namespace { |
93 | |
94 | class RequestFrameEvent : public QEvent |
95 | { |
96 | public: |
97 | RequestFrameEvent() |
98 | : QEvent(static_cast<QEvent::Type>(RequestFrameEvent::requestEventType)) |
99 | {} |
100 | |
101 | static int eventType() { return RequestFrameEvent::requestEventType; } |
102 | |
103 | private: |
104 | static int requestEventType; |
105 | }; |
106 | |
107 | int RequestFrameEvent::requestEventType = QEvent::registerEventType(); |
108 | |
109 | } // anonymous |
110 | #endif |
111 | |
112 | /*! |
113 | \class Qt3DCore::QAspectManager |
114 | \internal |
115 | */ |
116 | QAspectManager::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 | |
137 | QAspectManager::~QAspectManager() |
138 | { |
139 | delete m_changeArbiter; |
140 | delete m_jobManager; |
141 | delete m_scheduler; |
142 | } |
143 | |
144 | void 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) |
151 | void 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) |
189 | void 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 | |
237 | bool 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 | */ |
248 | void 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 | */ |
262 | void 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 |
270 | void 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 |
305 | void 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) |
326 | void 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 | */ |
357 | void 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 | */ |
383 | void 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 | |
395 | const QVector<QAbstractAspect *> &QAspectManager::aspects() const |
396 | { |
397 | return m_aspects; |
398 | } |
399 | |
400 | QAbstractAspectJobManager *QAspectManager::jobManager() const |
401 | { |
402 | return m_jobManager; |
403 | } |
404 | |
405 | QChangeArbiter *QAspectManager::changeArbiter() const |
406 | { |
407 | return m_changeArbiter; |
408 | } |
409 | |
410 | QServiceLocator *QAspectManager::serviceLocator() const |
411 | { |
412 | return m_serviceLocator.data(); |
413 | } |
414 | |
415 | void QAspectManager::setPostConstructorInit(NodePostConstructorInit *postConstructorInit) |
416 | { |
417 | m_postConstructorInit = postConstructorInit; |
418 | } |
419 | |
420 | QNode *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 | |
429 | QVector<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 | |
438 | void 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 | */ |
448 | bool 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 | |
467 | void 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 | |
479 | void 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 | |
556 | QT_END_NAMESPACE |
557 | |