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