1// Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "scene3ditem_p.h"
5
6#include <Qt3DCore/qt3dcore_global.h>
7#include <Qt3DCore/qentity.h>
8#include <Qt3DCore/QAspectEngine>
9
10#if QT_CONFIG(qt3d_input)
11#include <Qt3DInput/QInputAspect>
12#include <Qt3DInput/qinputsettings.h>
13#endif
14
15#if QT_CONFIG(qt3d_logic)
16#include <Qt3DLogic/qlogicaspect.h>
17#endif
18
19#if QT_CONFIG(qt3d_animation)
20#include <Qt3DAnimation/qanimationaspect.h>
21#endif
22
23#include <Qt3DRender/QRenderAspect>
24#include <Qt3DRender/qcamera.h>
25#include <Qt3DRender/qrendersurfaceselector.h>
26
27#include <QtGui/qguiapplication.h>
28#include <QtGui/qoffscreensurface.h>
29#include <QtQml/private/qqmlglobal_p.h>
30#include <QtQuick/qquickwindow.h>
31#include <QtQuick/qquickrendercontrol.h>
32
33#include <Qt3DRender/private/qrendersurfaceselector_p.h>
34#include <Qt3DRender/private/qrenderaspect_p.h>
35#include <Qt3DRender/private/rendersettings_p.h>
36#include <Qt3DRender/qt3drender-config.h>
37#include <scene3dlogging_p.h>
38#include <scene3drenderer_p.h>
39#include <scene3dsgnode_p.h>
40
41#include <Qt3DCore/private/qaspectengine_p.h>
42#include <Qt3DCore/private/qaspectmanager_p.h>
43#include <QThread>
44
45QT_BEGIN_NAMESPACE
46
47namespace Qt3DRender {
48
49class AspectEngineDestroyer : public QObject
50{
51 Q_OBJECT
52
53public:
54 AspectEngineDestroyer()
55 : QObject()
56 {}
57
58 ~AspectEngineDestroyer()
59 {
60 }
61
62 void reset(int targetCount)
63 {
64 m_allowed = 0;
65 m_targetAllowed = targetCount;
66 }
67
68 void allowRelease()
69 {
70 ++m_allowed;
71 if (m_allowed == m_targetAllowed) {
72 if (QThread::currentThread() == thread())
73 delete this;
74 else
75 deleteLater();
76 }
77 }
78
79 void setSGNodeAlive(bool alive) { m_sgNodeAlive = alive; }
80 bool sgNodeAlive() const { return m_sgNodeAlive;}
81
82private:
83 int m_allowed = 0;
84 int m_targetAllowed = 0;
85 bool m_sgNodeAlive = false;
86};
87
88/*!
89 \class Qt3DRender::Scene3DItem
90 \internal
91
92 \brief The Scene3DItem class is a QQuickItem subclass used to integrate
93 a Qt3D scene into a QtQuick 2 scene.
94
95 The Scene3DItem class renders a Qt3D scene, provided by a Qt3DCore::QEntity
96 into a multisampled Framebuffer object that is later blitted into a
97 non-multisampled Framebuffer object to be then rendered through the use of a
98 Qt3DCore::Scene3DSGNode with premultiplied alpha.
99 */
100
101/*!
102 \qmltype Scene3D
103 \inherits Item
104 \inqmlmodule QtQuick.Scene3D
105
106 \preliminary
107
108 \brief The Scene3D type is used to integrate a Qt3D scene into a QtQuick 2
109 scene.
110
111 The Scene3D type renders a Qt3D scene, provided by an \l Entity, into a
112 multisampled Framebuffer object. This object is later blitted into a
113 non-multisampled Framebuffer object, which is then rendered with
114 premultiplied alpha. If multisampling is not required, it can be avoided
115 by setting the \l multisample property to \c false. In this case
116 Scene3D will render directly into the non-multisampled Framebuffer object.
117
118 If the scene to be rendered includes non-opaque materials, you may need to
119 modify those materials with custom blend arguments in order for them to be
120 rendered correctly. For example, if working with a \l PhongAlphaMaterial and
121 a scene with an opaque clear color, you will likely want to add:
122
123 \qml
124 sourceAlphaArg: BlendEquationArguments.Zero
125 destinationAlphaArg: BlendEquationArguments.One
126 \endqml
127
128 to that material.
129
130 It is not recommended to instantiate more than a single Scene3D instance
131 per application. The reason for this is that a Scene3D instance
132 instantiates the entire Qt 3D engine (memory managers, thread pool, render
133 ...) under the scene.
134
135 \note Åšetting the visibility of the Scene3D element to false will halt the
136 Qt 3D simulation loop. This means that binding the visible property to an
137 expression that depends on property updates driven by the Qt 3D simulation
138 loop (FrameAction) will never reavaluates.
139 */
140Scene3DItem::Scene3DItem(QQuickItem *parent)
141 : QQuickItem(parent)
142 , m_entity(nullptr)
143 , m_aspectEngine(nullptr)
144 , m_aspectToDelete(nullptr)
145 , m_lastManagerNode(nullptr)
146 , m_aspectEngineDestroyer()
147 , m_multisample(true)
148 , m_dirty(true)
149 , m_wasFrameProcessed(false)
150 , m_wasSGUpdated(false)
151 , m_cameraAspectRatioMode(AutomaticAspectRatio)
152 , m_compositingMode(FBO)
153 , m_dummySurface(nullptr)
154 , m_framesToRender(ms_framesNeededToFlushPipeline)
155{
156 setFlag(flag: QQuickItem::ItemHasContents, enabled: true);
157 setAcceptedMouseButtons(Qt::MouseButtonMask);
158 setAcceptHoverEvents(true);
159 // TO DO: register the event source in the main thread
160
161 // Give a default size so that if nothing is specified by the user
162 // we still won't get ignored by the QtQuick SG when in Underlay mode
163 setWidth(1);
164 setHeight(1);
165
166 if (qgetenv(varName: "QT3D_RENDERER").isEmpty()) {
167#if QT_CONFIG(qt3d_rhi_renderer)
168 qputenv(varName: "QT3D_RENDERER", value: "rhi"); // QtQuick requires RHI
169#else
170 qputenv("QT3D_RENDERER", "opengl"); // QtQuick requires OpenGL
171#endif
172 }
173}
174
175Scene3DItem::~Scene3DItem()
176{
177 // The SceneGraph is non deterministic in the order in which it will
178 // destroy the QSGNode that were created by the item. This unfortunately
179 // makes it difficult to know when it is safe to destroy the QAspectEngine.
180 // To track this we use the AspectEngineDestroyer. It allows keeping the
181 // AspectEngine alive and deleting later when we know that both Scene3DItem
182 // and Scene3DRenderer have been destroyed.
183
184 delete m_aspectToDelete;
185
186 if (m_aspectEngineDestroyer)
187 m_aspectEngineDestroyer->allowRelease();
188
189 if (m_dummySurface)
190 m_dummySurface->deleteLater();
191}
192
193/*!
194 \qmlproperty list<string> Scene3D::aspects
195
196 The list of aspects that should be registered for the 3D scene.
197
198 For example, if the scene makes use of FrameAction, the \c "logic" aspect should be included in the list.
199
200 The \c "render" aspect is hardwired and does not need to be explicitly listed.
201*/
202QStringList Scene3DItem::aspects() const
203{
204 return m_aspects;
205}
206
207/*!
208 \qmlproperty Entity Scene3D::entity
209
210 \qmldefault
211
212 The root entity of the 3D scene to be displayed.
213 */
214Qt3DCore::QEntity *Scene3DItem::entity() const
215{
216 return m_entity;
217}
218
219void Scene3DItem::applyAspects()
220{
221 if (!m_aspectEngine)
222 return;
223
224 // Aspects are owned by the aspect engine
225 for (const QString &aspect : std::as_const(t&: m_aspects)) {
226 if (aspect == QLatin1String("render")) // This one is hardwired anyway
227 continue;
228 if (aspect == QLatin1String("input")) {
229#if QT_CONFIG(qt3d_input)
230 m_aspectEngine->registerAspect(aspect: new Qt3DInput::QInputAspect);
231 continue;
232#else
233 qFatal("Scene3D requested the Qt 3D input aspect but Qt 3D wasn't configured to build the Qt 3D Input aspect");
234#endif
235 }
236 if (aspect == QLatin1String("logic")) {
237#if QT_CONFIG(qt3d_logic)
238 m_aspectEngine->registerAspect(aspect: new Qt3DLogic::QLogicAspect);
239 continue;
240#else
241 qFatal("Scene3D requested the Qt 3D logic aspect but Qt 3D wasn't configured to build the Qt 3D Logic aspect");
242#endif
243 }
244 if (aspect == QLatin1String("animation")) {
245#if QT_CONFIG(qt3d_animation)
246 m_aspectEngine->registerAspect(aspect: new Qt3DAnimation::QAnimationAspect);
247 continue;
248#else
249 qFatal("Scene3D requested the Qt 3D animation aspect but Qt 3D wasn't configured to build the Qt 3D Animation aspect");
250#endif
251 }
252 m_aspectEngine->registerAspect(name: aspect);
253 }
254}
255
256void Scene3DItem::setAspects(const QStringList &aspects)
257{
258 if (!m_aspects.isEmpty()) {
259 qWarning() << "Aspects already set on the Scene3D, ignoring";
260 return;
261 }
262
263 m_aspects = aspects;
264 applyAspects();
265
266 emit aspectsChanged();
267}
268
269void Scene3DItem::setEntity(Qt3DCore::QEntity *entity)
270{
271 if (entity == m_entity)
272 return;
273
274 m_entity = entity;
275 emit entityChanged();
276}
277
278void Scene3DItem::setCameraAspectRatioMode(CameraAspectRatioMode mode)
279{
280 if (m_cameraAspectRatioMode == mode)
281 return;
282
283 m_cameraAspectRatioMode = mode;
284 setCameraAspectModeHelper();
285 emit cameraAspectRatioModeChanged(mode);
286}
287
288void Scene3DItem::setHoverEnabled(bool enabled)
289{
290 if (enabled != acceptHoverEvents()) {
291 setAcceptHoverEvents(enabled);
292 emit hoverEnabledChanged();
293 }
294}
295
296/*!
297 \qmlproperty enumeration Scene3D::compositingMode
298
299 \value FBO
300 Scene is rendered into a Frame Buffer Object which can be costly on
301 some platform and hardware but allows a greater amount of
302 flexibility. Automatic aspect ratio. This is the compositing mode to
303 choose if your Scene3D element shouldn't occupy the entire screen
304 and if you optionally plan on having it resized or animated. In this
305 mode, the position of the Scene3D in the QML file controls its
306 stacking order with regard to the other Qt Quick elements.
307
308 \value Underlay
309 Suitable for full screen 3D scenes where using an FBO might be too
310 resource intensive. Scene3D behaves as a QtQuick underlay.
311 Please note that when using this mode, the size of the Scene3D and
312 its transformations are ignored and the rendering will occupy the
313 whole screen. The position of the Scene3D in the QML file won't have
314 any effect either. The Qt 3D content will be drawn prior to any Qt
315 Quick content. Care has to be taken not to overdraw and hide the Qt
316 3D content by overlapping Qt Quick content.
317 Additionally when using this mode, the window clearBeforeRendering
318 will be set to false automatically.
319
320 The default value is \c FBO.
321 \since 5.14
322 */
323void Scene3DItem::setCompositingMode(Scene3DItem::CompositingMode mode)
324{
325 if (m_compositingMode == mode)
326 return;
327 m_compositingMode = mode;
328 emit compositingModeChanged();
329
330 QQuickItem::update();
331}
332
333/*!
334 \qmlproperty enumeration Scene3D::cameraAspectRatioMode
335
336 \value Scene3D.AutomaticAspectRatio
337 Automatic aspect ratio.
338
339 \value Scene3D.UserAspectRatio
340 User defined aspect ratio.
341 \brief \TODO
342 */
343Scene3DItem::CameraAspectRatioMode Scene3DItem::cameraAspectRatioMode() const
344{
345 return m_cameraAspectRatioMode;
346}
347
348Scene3DItem::CompositingMode Scene3DItem::compositingMode() const
349{
350 return m_compositingMode;
351}
352
353void Scene3DItem::applyRootEntityChange()
354{
355 if (m_aspectEngine->rootEntity().data() != m_entity) {
356
357 Qt3DCore::QEntityPtr entityPtr;
358 // We must reuse the QEntityPtr of the old AspectEngine
359 // otherwise it will delete the Entity once it gets destroyed
360 if (m_aspectToDelete)
361 entityPtr = m_aspectToDelete->rootEntity();
362 else
363 entityPtr = Qt3DCore::QEntityPtr(m_entity);
364
365 m_aspectEngine->setRootEntity(entityPtr);
366
367 /* If we changed window, the old aspect engine must be deleted only after we have set
368 the root entity for the new one so that it doesn't delete the root node. */
369 if (m_aspectToDelete) {
370 delete m_aspectToDelete;
371 m_aspectToDelete = nullptr;
372 }
373
374 // Set the render surface
375 if (!m_entity)
376 return;
377
378 setWindowSurface(entity());
379
380 if (m_cameraAspectRatioMode == AutomaticAspectRatio) {
381 // Set aspect ratio of first camera to match the window
382 QList<Qt3DRender::QCamera *> cameras
383 = m_entity->findChildren<Qt3DRender::QCamera *>();
384 if (cameras.isEmpty()) {
385 qCDebug(Scene3D) << "No camera found and automatic aspect ratio requested";
386 } else {
387 m_camera = cameras.first();
388 setCameraAspectModeHelper();
389 }
390 }
391
392#if QT_CONFIG(qt3d_input)
393 // Set ourselves up as a source of input events for the input aspect
394 Qt3DInput::QInputSettings *inputSettings = m_entity->findChild<Qt3DInput::QInputSettings *>();
395 if (inputSettings) {
396 inputSettings->setEventSource(this);
397 } else {
398 qCDebug(Scene3D) << "No Input Settings found, keyboard and mouse events won't be handled";
399 }
400#endif
401 }
402}
403
404bool Scene3DItem::needsRender(QRenderAspect *renderAspect)
405{
406 // We need the dirty flag which is connected to the change arbiter
407 // receiving updates to know whether something in the scene has changed
408
409 // Ideally we would use shouldRender() alone but given that it becomes true
410 // only after the arbiter has sync the changes and might be reset before
411 // process jobs is completed, we cannot fully rely on it. It would require
412 // splitting processFrame in 2 parts.
413
414 // We only use it for cases where Qt3D render may require several loops of
415 // the simulation to fully process a frame (e.g shaders are loaded in frame
416 // n and we can only build render commands for the new shader at frame n +
417 // This is where renderer->shouldRender() comes into play as it knows
418 // whether some states remain dirty or not (even after processFrame is
419 // called)
420
421 auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(q: renderAspect));
422 const bool dirty = m_dirty
423 || (renderAspectPriv
424 && renderAspectPriv->m_renderer
425 && renderAspectPriv->m_renderer->shouldRender());
426
427 if (m_dirty) {
428 --m_framesToRender;
429 if (m_framesToRender <= 0)
430 m_dirty = false;
431 }
432 return dirty || m_framesToRender > 0;
433}
434
435// This function is triggered in the context of the Main Thread
436// when afterAnimating is emitted
437
438// The QtQuick SG proceeds like indicated below:
439// afterAnimating (Main Thread)
440// beforeSynchronizing (SG Thread and MainThread locked)
441// afterSynchronizing (SG Thread and MainThread locked)
442// beforeRendering (SG Thread)
443
444// Note: we connect to afterAnimating rather than beforeSynchronizing as a
445// direct connection on beforeSynchronizing is executed within the SG Render
446// Thread context. This is executed before the RenderThread is asked to
447// synchronize and render
448// Note: we might still not be done rendering when this is called but
449// processFrame will block and wait for renderer to have been finished
450bool Scene3DItem::prepareQt3DFrame()
451{
452 static bool dontRenderWhenHidden = !qgetenv(varName: "QT3D_SCENE3D_STOP_RENDER_HIDDEN").isEmpty();
453
454 // If we are not visible, don't processFrame changes as we would end up
455 // waiting forever for the scene to be rendered which won't happen
456 // if the Scene3D item is not visible
457 if (!isVisible() && dontRenderWhenHidden)
458 return false;
459 if (!m_aspectEngine)
460 return false;
461 Q_ASSERT(QThread::currentThread() == thread());
462
463 // Since we are in manual mode, trigger jobs for the next frame
464 Qt3DCore::QAspectEnginePrivate *aspectEnginePriv = static_cast<Qt3DCore::QAspectEnginePrivate *>(QObjectPrivate::get(o: m_aspectEngine));
465 if (!aspectEnginePriv->m_initialized)
466 return false;
467
468 Q_ASSERT(m_aspectEngine->runMode() == Qt3DCore::QAspectEngine::Manual);
469 m_aspectEngine->processFrame();
470 // The above essentially sets the number of RV for the RenderQueue and
471 // processes the jobs for the frame (it's blocking) When
472 // Scene3DRender::updatePaintNode is called, following this step, we know
473 // that the RenderQueue target count has been set and that everything
474 // should be ready for rendering
475
476 // processFrame() must absolutely be followed by a single call to
477 // render
478 // At startup, we have no garantee that the QtQuick Render Thread doesn't
479 // start rendering before this function has been called
480 // We add in a safety to skip such frames as this could otherwise
481 // make Qt3D enter a locked state
482
483 // Note: it's too early to request an update at this point as
484 // beforeSync() triggered by afterAnimating is considered
485 // to be as being part of the current frame update
486 return true;
487}
488
489void Scene3DItem::requestUpdate()
490{
491 // When using the FBO mode, only the QQuickItem needs to be updated
492 // When using the Underlay mode, the whole windows needs updating
493 const bool usesFBO = m_compositingMode == FBO;
494 if (usesFBO) {
495 QQuickItem::update();
496 } else {
497 window()->update();
498 }
499}
500
501void Scene3DItem::updateWindowSurface()
502{
503 if (!m_entity || !m_dummySurface)
504 return;
505 Qt3DRender::QRenderSurfaceSelector *surfaceSelector =
506 Qt3DRender::QRenderSurfaceSelectorPrivate::find(rootObject: entity());
507 if (surfaceSelector) {
508 if (QWindow *rw = QQuickRenderControl::renderWindowFor(win: this->window())) {
509 m_dummySurface->deleteLater();
510 createDummySurface(window: rw, surfaceSelector);
511 }
512 }
513}
514
515void Scene3DItem::setWindowSurface(QObject *rootObject)
516{
517 Qt3DRender::QRenderSurfaceSelector *surfaceSelector = Qt3DRender::QRenderSurfaceSelectorPrivate::find(rootObject);
518
519 // Set the item's window surface if it appears
520 // the surface wasn't set on the surfaceSelector
521 if (surfaceSelector && !surfaceSelector->surface()) {
522 // We may not have a real, exposed QQuickWindow when the Quick rendering
523 // is redirected via QQuickRenderControl (f.ex. QQuickWidget).
524 if (QWindow *rw = QQuickRenderControl::renderWindowFor(win: this->window())) {
525 createDummySurface(window: rw, surfaceSelector);
526 } else {
527 surfaceSelector->setSurface(this->window());
528 }
529 }
530}
531
532void Scene3DItem::createDummySurface(QWindow *rw, Qt3DRender::QRenderSurfaceSelector *surfaceSelector)
533{
534 // rw is the top-level window that is backed by a native window. Do
535 // not use that though since we must not clash with e.g. the widget
536 // backingstore compositor in the gui thread.
537 m_dummySurface = new QOffscreenSurface;
538 m_dummySurface->setParent(qGuiApp); // parent to something suitably long-living
539 m_dummySurface->setFormat(rw->format());
540 m_dummySurface->setScreen(rw->screen());
541 m_dummySurface->create();
542 surfaceSelector->setSurface(m_dummySurface);
543}
544/*!
545 \qmlmethod void Scene3D::setItemAreaAndDevicePixelRatio(size area, real devicePixelRatio)
546
547 Sets the item area to \a area and the pixel ratio to \a devicePixelRatio.
548 */
549void Scene3DItem::setItemAreaAndDevicePixelRatio(QSize area, qreal devicePixelRatio)
550{
551 Qt3DCore::QEntity *rootEntity = entity();
552 if (!rootEntity) {
553 return;
554 }
555 Qt3DRender::QRenderSurfaceSelector *surfaceSelector = Qt3DRender::QRenderSurfaceSelectorPrivate::find(rootObject: rootEntity);
556 if (surfaceSelector) {
557 surfaceSelector->setExternalRenderTargetSize(area);
558 surfaceSelector->setSurfacePixelRatio(devicePixelRatio);
559 }
560}
561
562/*!
563 \qmlproperty bool Scene3D::hoverEnabled
564
565 \c true if hover events are accepted.
566 */
567bool Scene3DItem::isHoverEnabled() const
568{
569 return acceptHoverEvents();
570}
571
572void Scene3DItem::setCameraAspectModeHelper()
573{
574 if (m_compositingMode == FBO) {
575 switch (m_cameraAspectRatioMode) {
576 case AutomaticAspectRatio:
577 connect(sender: this, signal: &Scene3DItem::widthChanged, context: this, slot: &Scene3DItem::updateCameraAspectRatio);
578 connect(sender: this, signal: &Scene3DItem::heightChanged, context: this, slot: &Scene3DItem::updateCameraAspectRatio);
579 // Update the aspect ratio the first time the surface is set
580 updateCameraAspectRatio();
581 break;
582 case UserAspectRatio:
583 disconnect(sender: this, signal: &Scene3DItem::widthChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
584 disconnect(sender: this, signal: &Scene3DItem::heightChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
585 break;
586 }
587 } else {
588 // In Underlay mode, we rely on the window for aspect ratio and not the size of the Scene3DItem
589 switch (m_cameraAspectRatioMode) {
590 case AutomaticAspectRatio:
591 connect(sender: window(), signal: &QWindow::widthChanged, context: this, slot: &Scene3DItem::updateCameraAspectRatio);
592 connect(sender: window(), signal: &QWindow::heightChanged, context: this, slot: &Scene3DItem::updateCameraAspectRatio);
593 // Update the aspect ratio the first time the surface is set
594 updateCameraAspectRatio();
595 break;
596 case UserAspectRatio:
597 disconnect(sender: window(), signal: &QWindow::widthChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
598 disconnect(sender: window(), signal: &QWindow::heightChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
599 break;
600 }
601 }
602}
603
604void Scene3DItem::updateCameraAspectRatio()
605{
606 if (m_camera) {
607 if (m_compositingMode == FBO)
608 m_camera->setAspectRatio(static_cast<float>(width()) /
609 static_cast<float>(height()));
610 else
611 m_camera->setAspectRatio(static_cast<float>(window()->width()) /
612 static_cast<float>(window()->height()));
613 }
614}
615
616/*!
617 \qmlproperty bool Scene3D::multisample
618
619 \c true if a multisample render buffer is requested.
620
621 By default multisampling is enabled. If the OpenGL implementation has no
622 support for multisample renderbuffers or framebuffer blits, the request to
623 use multisampling is ignored.
624
625 \note Refrain from changing the value frequently as it involves expensive
626 and potentially slow initialization of framebuffers and other OpenGL
627 resources.
628 */
629bool Scene3DItem::multisample() const
630{
631 return m_multisample;
632}
633
634void Scene3DItem::setMultisample(bool enable)
635{
636 if (m_multisample != enable) {
637 m_multisample = enable;
638 emit multisampleChanged();
639 update();
640 }
641}
642
643// We want to tie the Scene3DRenderer's lifetime to the QSGNode associated with
644// Scene3DItem. This ensures that when the SceneGraph tree gets destroyed, we
645// also shutdown Qt3D properly
646// Everything this class does happens in the QSGRenderThread
647class Scene3DManagerNode : public QSGNode
648{
649public:
650 explicit Scene3DManagerNode(Qt3DCore::QAspectEngine *aspectEngine,
651 AspectEngineDestroyer *destroyer)
652 : m_aspectEngine(aspectEngine)
653 , m_destroyer(destroyer)
654 , m_renderAspect(new QRenderAspect(QRenderAspect::Manual))
655 , m_renderer(new Scene3DRenderer())
656 {
657 m_destroyer->setSGNodeAlive(true);
658 }
659
660 ~Scene3DManagerNode()
661 {
662 // Stop the Qt3D Simulation Loop
663 auto engineD = Qt3DCore::QAspectEnginePrivate::get(engine: m_aspectEngine);
664 engineD->exitSimulationLoop();
665
666 // Shutdown renderer
667 delete m_renderer;
668
669 m_destroyer->setSGNodeAlive(false);
670
671 // Allow AspectEngine destruction
672 m_destroyer->allowRelease();
673 }
674
675 void init()
676 {
677 m_aspectEngine->registerAspect(aspect: m_renderAspect);
678 m_renderer->init(aspectEngine: m_aspectEngine, renderAspect: m_renderAspect);
679 m_wasInitialized = true;
680 }
681
682 inline bool isInitialized() const { return m_wasInitialized; }
683 inline QRenderAspect *renderAspect() const { return m_renderAspect; }
684 inline Scene3DRenderer *renderer() const { return m_renderer; }
685private:
686 Qt3DCore::QAspectEngine *m_aspectEngine;
687 AspectEngineDestroyer *m_destroyer;
688 QRenderAspect *m_renderAspect;
689 Scene3DRenderer *m_renderer;
690 bool m_wasInitialized = false;
691};
692
693
694// QtQuick SG
695// beforeSynchronize // SG Thread (main thread blocked)
696// updatePaintNode (-> Scene3DRenderer::beforeSynchronize) // SG Thread (main thread blocked)
697// beforeRenderering (-> Scene3DRenderer::beforeSynchronize) // SG Thread (main thread unblocked)
698// afterRenderering // SG Thread (main thread unblocked)
699// afterAnimating (-> Scene3DItem::synchronize()) // Main Thread (SG Thread is not yet at beforeSynchronize )
700
701// main thread (afterAnimating)
702void Scene3DItem::synchronize()
703{
704 // Request updates for the next frame
705 requestUpdate();
706
707 if (!window() || !m_wasSGUpdated ||
708 (!m_aspectEngineDestroyer || !m_aspectEngineDestroyer->sgNodeAlive())) {
709 m_wasFrameProcessed = false;
710 return;
711 }
712
713 // Set root Entity on the aspectEngine
714 applyRootEntityChange();
715
716 // Update size of the QSurfaceSelector if needed
717 setItemAreaAndDevicePixelRatio(area: boundingRect().size().toSize(),
718 devicePixelRatio: window()->effectiveDevicePixelRatio());
719
720 // Let Qt3D process the frame and launch jobs
721 m_wasFrameProcessed = prepareQt3DFrame();
722
723 m_wasSGUpdated = false;
724}
725
726// The synchronization point between the main thread and the render thread
727// before any rendering
728QSGNode *Scene3DItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
729{
730 Scene3DManagerNode *managerNode = static_cast<Scene3DManagerNode *>(node);
731 QSGRendererInterface::GraphicsApi windowApi = window()->rendererInterface()->graphicsApi();
732
733 // In case we have no GL context, return early
734 // m_wasSGUpdated will not be set to true and nothing will take place
735 if ((windowApi == QSGRendererInterface::OpenGLRhi ||
736 windowApi == QSGRendererInterface::OpenGL) && !QOpenGLContext::currentContext()) {
737 QQuickItem::update();
738 return node;
739 }
740
741 // Scene3DManagerNode gets automatically destroyed on Window changed, SceneGraph invalidation
742 if (!managerNode) {
743 // Did we have a Scene3DManagerNode in the past?
744 if (m_lastManagerNode != nullptr) {
745 // If so we need to recreate a new AspectEngine as node was destroyed by sceneGraph
746 qCWarning(Scene3D) << "Renderer for Scene3DItem has requested a reset due to the item "
747 "moving to another window";
748 QObject::disconnect(m_windowConnection);
749 // We are in the Render thread, and the m_aspectEngineDestroyer lives in the Main
750 // thread, so we must avoid sending ChildRemoved or ChildAdded events to it with a
751 // QObject::setParent(). QCoreApplication::sendEvent() would fail with "Cannot
752 // send events to objects owned by a different thread."
753 QQml_setParent_noEvent(object: m_aspectEngine, parent: nullptr);
754 // Note: AspectEngine can only be deleted once we have set the root
755 // entity on the new instance
756 m_aspectToDelete = m_aspectEngine;
757 m_aspectEngine = nullptr;
758 }
759
760 // Create or Recreate AspectEngine
761 if (m_aspectEngine == nullptr) {
762 // Use manual drive mode when using Scene3D
763 delete m_aspectEngineDestroyer;
764 m_aspectEngineDestroyer = new AspectEngineDestroyer();
765 m_aspectEngine = new Qt3DCore::QAspectEngine(m_aspectEngineDestroyer);
766 m_aspectEngine->setRunMode(Qt3DCore::QAspectEngine::Manual);
767 applyAspects();
768
769 // Needs to belong in the same thread as the item which is the same as
770 // the original QAspectEngine
771 m_aspectEngineDestroyer->moveToThread(thread: thread());
772
773 // To destroy AspectEngine
774 m_aspectEngineDestroyer->reset(targetCount: 2);
775 }
776
777 // Create new instance and record a pointer (which should only be used
778 // to check if we have had a previous manager node)
779 managerNode = new Scene3DManagerNode(m_aspectEngine,
780 m_aspectEngineDestroyer);
781 m_lastManagerNode = managerNode;
782
783 // Before Synchronizing is in the SG Thread, we want synchronize to be triggered
784 // in the context of the main thread so we use afterAnimating instead
785 m_windowConnection = QObject::connect(sender: window(), signal: &QQuickWindow::afterAnimating,
786 context: this, slot: &Scene3DItem::synchronize, type: Qt::DirectConnection);
787 }
788
789 Scene3DRenderer *renderer = managerNode->renderer();
790 QRenderAspect *renderAspect = managerNode->renderAspect();
791
792 renderer->setBoundingSize(boundingRect().size().toSize());
793 renderer->setMultisample(m_multisample);
794 // Ensure Renderer is working on current window
795 renderer->setWindow(window());
796 // Set compositing mode on renderer
797 renderer->setCompositingMode(m_compositingMode);
798
799 // If the render aspect wasn't created yet, do so now
800 if (!managerNode->isInitialized()) {
801 auto *rw = QQuickRenderControl::renderWindowFor(win: window());
802 auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(q: renderAspect));
803 renderAspectPriv->m_screen = (rw ? rw->screen() : window()->screen());
804 updateWindowSurface();
805
806#if !QT_CONFIG(qt3d_rhi_renderer)
807 QSGRendererInterface::GraphicsApi windowApi = window()->rendererInterface()->graphicsApi();
808
809 if (windowApi != QSGRendererInterface::OpenGLRhi &&
810 windowApi != QSGRendererInterface::OpenGL) {
811
812 qFatal("Qt3D's RHI Renderer is not enabled, please configure RHI to use the OpenGL backend "
813 "by calling qputenv(\"QSG_RHI_BACKEND\", \"opengl\")");
814 }
815#endif
816 managerNode->init();
817 // Note: ChangeArbiter is only set after aspect was registered
818 QObject::connect(
819 sender: renderAspectPriv->m_aspectManager->changeArbiter(),
820 signal: &Qt3DCore::QChangeArbiter::receivedChange, context: this,
821 slot: [this] {
822 m_dirty = true;
823 m_framesToRender = ms_framesNeededToFlushPipeline;
824 },
825 type: Qt::DirectConnection);
826
827 // Give the window a nudge to trigger an update.
828 QMetaObject::invokeMethod(obj: window(), member: "requestUpdate", c: Qt::QueuedConnection);
829 }
830
831 const bool usesFBO = m_compositingMode == FBO;
832 Scene3DSGNode *fboNode = static_cast<Scene3DSGNode *>(managerNode->firstChild());
833
834 // When using Scene3D in Underlay mode
835 // we shouldn't be managing a Scene3DSGNode
836 if (!usesFBO) {
837 if (fboNode != nullptr) {
838 managerNode->removeChildNode(node: fboNode);
839 delete fboNode;
840 fboNode = nullptr;
841 }
842 } else {
843 // Regular Scene3D only case
844 // Create SGNode if using FBO and no Scene3DViews
845 fboNode = renderer->sgNode();
846 if (fboNode) {
847 if (!fboNode->parent())
848 managerNode->appendChildNode(node: fboNode);
849
850 // Depending on the backend in use, we might or might not have
851 // to flip content
852 fboNode->setRect(rect: boundingRect(), mirrorVertically: !renderer->isYUp());
853 }
854 }
855
856 // Set whether we want the Renderer to be allowed to render or not
857 const bool skipFrame = !needsRender(renderAspect);
858 renderer->setSkipFrame(skipFrame);
859 renderer->allowRender();
860
861 // Let the renderer prepare anything it needs to prior to the rendering
862 if (m_wasFrameProcessed)
863 renderer->beforeSynchronize();
864
865 // Force window->beforeRendering to be triggered
866 managerNode->markDirty(bits: QSGNode::DirtyForceUpdate);
867
868 m_wasSGUpdated = true;
869
870 return managerNode;
871}
872
873void Scene3DItem::mousePressEvent(QMouseEvent *event)
874{
875 Q_UNUSED(event);
876 //Prevent subsequent move and release events being disregarded my the default event->ignore() from QQuickItem
877}
878
879} // namespace Qt3DRender
880
881QT_END_NAMESPACE
882
883#include "moc_scene3ditem_p.cpp"
884#include "scene3ditem.moc"
885

source code of qt3d/src/quick3d/imports/scene3d/scene3ditem.cpp