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

source code of qt3d/src/quick3d/quick3dscene3d/scene3ditem.cpp