| 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: | 
| 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 |  | 
| 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 |  */ | 
| 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(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 |  | 
| 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 | */ | 
| 262 | 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 |  */ | 
| 274 | Qt3DCore::QEntity *Scene3DItem::entity() const | 
| 275 | { | 
| 276 |     return m_entity; | 
| 277 | } | 
| 278 |  | 
| 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(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 |  | 
| 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 |  | 
| 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 |  | 
| 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 |  | 
| 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 |  */ | 
| 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 |  */ | 
| 403 | Scene3DItem::CameraAspectRatioMode Scene3DItem::cameraAspectRatioMode() const | 
| 404 | { | 
| 405 |     return m_cameraAspectRatioMode; | 
| 406 | } | 
| 407 |  | 
| 408 | Scene3DItem::CompositingMode Scene3DItem::compositingMode() const | 
| 409 | { | 
| 410 |     return m_compositingMode; | 
| 411 | } | 
| 412 |  | 
| 413 | // MainThread called by Scene3DView | 
| 414 | void 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 | 
| 457 | void 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 |  | 
| 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 |  | 
| 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(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 | 
| 570 | bool 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 |  | 
| 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 |  | 
| 623 | void 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 |  | 
| 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(win: this->window())) { | 
| 647 |             createDummySurface(window: rw, surfaceSelector); | 
| 648 |         } else { | 
| 649 |             surfaceSelector->setSurface(this->window()); | 
| 650 |         } | 
| 651 |     } | 
| 652 | } | 
| 653 |  | 
| 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 |  */ | 
| 671 | void 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 |  */ | 
| 686 | bool Scene3DItem::isHoverEnabled() const | 
| 687 | { | 
| 688 |     return acceptHoverEvents(); | 
| 689 | } | 
| 690 |  | 
| 691 | void 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 |  | 
| 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 |  */ | 
| 748 | bool Scene3DItem::multisample() const | 
| 749 | { | 
| 750 |     return m_multisample; | 
| 751 | } | 
| 752 |  | 
| 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: | 
| 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; } | 
| 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) | 
| 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(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 | 
| 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: 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 |  | 
| 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 |  |