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
80QT_BEGIN_NAMESPACE
81
82namespace Qt3DRender {
83
84class AspectEngineDestroyer : public QObject
85{
86 Q_OBJECT
87
88public:
89 AspectEngineDestroyer()
90 : QObject()
91 {}
92
93 ~AspectEngineDestroyer()
94 {
95 }
96
97 void reset(int targetCount)
98 {
99 m_allowed = 0;
100 m_targetAllowed = targetCount;
101 }
102
103 Qt3DCore::QAspectEngine *aspectEngine() const
104 {
105 if (children().empty())
106 return nullptr;
107 return qobject_cast<Qt3DCore::QAspectEngine *>(object: children().first());
108 }
109
110 bool releaseRootEntity() const { return m_releaseRootEntity; }
111 void setReleaseRootEntity(bool release) { m_releaseRootEntity = release; }
112
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
132 void setSGNodeAlive(bool alive) { m_sgNodeAlive = alive; }
133 bool sgNodeAlive() const { return m_sgNodeAlive;}
134
135private:
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 */
203Scene3DItem::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(flag: QQuickItem::ItemHasContents, enabled: 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
235Scene3DItem::~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*/
262QStringList 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 */
274Qt3DCore::QEntity *Scene3DItem::entity() const
275{
276 return m_entity;
277}
278
279void Scene3DItem::applyAspects()
280{
281 if (!m_aspectEngine)
282 return;
283
284 // Aspects are owned by the aspect engine
285 for (const QString &aspect : qAsConst(t&: 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(aspect: 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(aspect: 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(aspect: 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(name: aspect);
313 }
314}
315
316void 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
329void Scene3DItem::setEntity(Qt3DCore::QEntity *entity)
330{
331 if (entity == m_entity)
332 return;
333
334 m_entity = entity;
335 emit entityChanged();
336}
337
338void 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
348void 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 */
383void 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 */
403Scene3DItem::CameraAspectRatioMode Scene3DItem::cameraAspectRatioMode() const
404{
405 return m_cameraAspectRatioMode;
406}
407
408Scene3DItem::CompositingMode Scene3DItem::compositingMode() const
409{
410 return m_compositingMode;
411}
412
413// MainThread called by Scene3DView
414void Scene3DItem::addView(Scene3DView *view)
415{
416 if (m_views.contains(t: 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(comp: 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(t: view);
453 m_dirtyViews |= true;
454}
455
456// MainThread called by Scene3DView
457void Scene3DItem::removeView(Scene3DView *view)
458{
459 if (!m_views.contains(t: 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(t: view);
470 m_dirtyViews |= true;
471}
472
473void 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
524bool 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(q: 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
570bool Scene3DItem::prepareQt3DFrame()
571{
572 static bool dontRenderWhenHidden = !qgetenv(varName: "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(o: 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
609void 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
623void Scene3DItem::updateWindowSurface()
624{
625 if (!m_entity || !m_dummySurface)
626 return;
627 Qt3DRender::QRenderSurfaceSelector *surfaceSelector =
628 Qt3DRender::QRenderSurfaceSelectorPrivate::find(rootObject: entity());
629 if (surfaceSelector) {
630 if (QWindow *rw = QQuickRenderControl::renderWindowFor(win: this->window())) {
631 m_dummySurface->deleteLater();
632 createDummySurface(window: rw, surfaceSelector);
633 }
634 }
635}
636
637void 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(win: this->window())) {
647 createDummySurface(window: rw, surfaceSelector);
648 } else {
649 surfaceSelector->setSurface(this->window());
650 }
651 }
652}
653
654void 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 */
671void Scene3DItem::setItemAreaAndDevicePixelRatio(QSize area, qreal devicePixelRatio)
672{
673 Qt3DRender::QRenderSurfaceSelector *surfaceSelector
674 = Qt3DRender::QRenderSurfaceSelectorPrivate::find(rootObject: 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 */
686bool Scene3DItem::isHoverEnabled() const
687{
688 return acceptHoverEvents();
689}
690
691void Scene3DItem::setCameraAspectModeHelper()
692{
693 if (m_compositingMode == FBO) {
694 switch (m_cameraAspectRatioMode) {
695 case AutomaticAspectRatio:
696 connect(sender: this, signal: &Scene3DItem::widthChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
697 connect(sender: this, signal: &Scene3DItem::heightChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
698 // Update the aspect ratio the first time the surface is set
699 updateCameraAspectRatio();
700 break;
701 case UserAspectRatio:
702 disconnect(sender: this, signal: &Scene3DItem::widthChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
703 disconnect(sender: this, signal: &Scene3DItem::heightChanged, receiver: this, slot: &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(sender: window(), signal: &QWindow::widthChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
711 connect(sender: window(), signal: &QWindow::heightChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
712 // Update the aspect ratio the first time the surface is set
713 updateCameraAspectRatio();
714 break;
715 case UserAspectRatio:
716 disconnect(sender: window(), signal: &QWindow::widthChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
717 disconnect(sender: window(), signal: &QWindow::heightChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio);
718 break;
719 }
720 }
721}
722
723void 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 */
748bool Scene3DItem::multisample() const
749{
750 return m_multisample;
751}
752
753void 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
766class Scene3DManagerNode : public QSGNode
767{
768public:
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
779 ~Scene3DManagerNode()
780 {
781 // Stop the Qt3D Simulation Loop
782 auto engineD = Qt3DCore::QAspectEnginePrivate::get(engine: m_aspectEngine);
783 engineD->exitSimulationLoop();
784
785 // Shutdown GL renderer
786 static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(q: m_renderAspect))->renderShutdown();
787 delete m_renderer;
788
789 m_destroyer->setSGNodeAlive(false);
790
791 // Allow AspectEngine destruction
792 m_destroyer->allowRelease();
793 }
794
795 void init()
796 {
797 m_aspectEngine->registerAspect(aspect: m_renderAspect);
798 m_renderer->init(aspectEngine: m_aspectEngine, renderAspect: m_renderAspect);
799 m_wasInitialized = true;
800 }
801
802 inline bool isInitialized() const { return m_wasInitialized; }
803 inline QRenderAspect *renderAspect() const { return m_renderAspect; }
804 inline Scene3DRenderer *renderer() const { return m_renderer; }
805private:
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)
822void 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(area: boundingRect().size().toSize(),
838 devicePixelRatio: 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
848QSGNode *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: thread());
886
887 // To destroy AspectEngine
888 m_aspectEngineDestroyer->reset(targetCount: 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(sender: window(), signal: &QQuickWindow::afterAnimating,
900 receiver: this, slot: &Scene3DItem::synchronize, type: 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(win: 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(q: 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 sender: renderAspectPriv->m_aspectManager->changeArbiter(),
922 signal: &Qt3DCore::QChangeArbiter::receivedChange, context: this,
923 slot: [this] {
924 m_dirty = true;
925 m_framesToRender = ms_framesNeededToFlushPipeline;
926 },
927 type: Qt::DirectConnection);
928 // Give the window a nudge to trigger an update.
929 QMetaObject::invokeMethod(obj: window(), member: "requestUpdate", type: 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(node: 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(node: fboNode);
952 }
953 fboNode->setRect(rect: 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(bits: QSGNode::DirtyForceUpdate);
1001
1002 m_wasSGUpdated = true;
1003
1004 return managerNode;
1005}
1006
1007void 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
1015QT_END_NAMESPACE
1016
1017#include "scene3ditem.moc"
1018

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