diff options
author | Paul Lemire <paul.lemire@kdab.com> | 2020-08-21 11:09:44 +0200 |
---|---|---|
committer | Paul Lemire <paul.lemire@kdab.com> | 2020-08-25 16:45:59 +0200 |
commit | 09eb4028e1221a8aaca8b563a1dba2ae6c39b92c (patch) | |
tree | 796ec16f1cec8b65368587e8e734ee9c536706d6 /src | |
parent | 4c7cfd85abdd4a914a5b80e7c406b7cc823840a6 (diff) |
Rework Scene3D to fix potential crash on shutdown
- Rework Scene3DRenderer/Scene3DItem to remove coupling and help
simplify the flow
- Introduce a Scene3DManagerNode to manager lifetime of the
Scene3DRenderer. Rely on the Scene3DManagerNode dtor to know
to shutdown the Qt3D renderer in the proper thread.
- Try to handle the fact that destruction order between Item and SGNode
is random by using an AspectEngineDestroyer helper
- Stop using a sharedptr to store the QEntity on the Scene3DItem side.
This can lead to crashes as the AspectEngine assumes it is the sole
owner of the Entity ptr.
Change-Id: I14915705eb9ab1195b2b783cbbb45076acc2ac1a
Task-number: QTBUG-84847
Reviewed-by: Mike Krus <mike.krus@kdab.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/core/aspects/qaspectengine.cpp | 3 | ||||
-rw-r--r-- | src/plugins/renderers/opengl/renderer/renderer.cpp | 8 | ||||
-rw-r--r-- | src/quick3d/imports/scene3d/scene3ditem.cpp | 364 | ||||
-rw-r--r-- | src/quick3d/imports/scene3d/scene3ditem_p.h | 15 | ||||
-rw-r--r-- | src/quick3d/imports/scene3d/scene3drenderer.cpp | 128 | ||||
-rw-r--r-- | src/quick3d/imports/scene3d/scene3drenderer_p.h | 21 | ||||
-rw-r--r-- | src/render/frontend/qrenderaspect.cpp | 3 |
7 files changed, 333 insertions, 209 deletions
diff --git a/src/core/aspects/qaspectengine.cpp b/src/core/aspects/qaspectengine.cpp index e52435eed..f91286914 100644 --- a/src/core/aspects/qaspectengine.cpp +++ b/src/core/aspects/qaspectengine.cpp @@ -301,7 +301,8 @@ void QAspectEnginePrivate::shutdown() void QAspectEnginePrivate::exitSimulationLoop() { - m_aspectManager->exitSimulationLoop(); + if (m_aspectManager != nullptr) + m_aspectManager->exitSimulationLoop(); } /*! diff --git a/src/plugins/renderers/opengl/renderer/renderer.cpp b/src/plugins/renderers/opengl/renderer/renderer.cpp index 27e00d9cb..27c3a61ca 100644 --- a/src/plugins/renderers/opengl/renderer/renderer.cpp +++ b/src/plugins/renderers/opengl/renderer/renderer.cpp @@ -534,7 +534,11 @@ void Renderer::shutdown() QMutexLocker lock(&m_hasBeenInitializedMutex); qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer shutdown"; - m_running.storeRelaxed(0); + const bool wasRunning = m_running.testAndSetRelaxed(1, 0); + + // We might have already been shutdown + if (!wasRunning) + return; // We delete any renderqueue that we may not have had time to render // before the surface was destroyed @@ -1780,7 +1784,7 @@ bool Renderer::shouldRender() const { // Only render if something changed during the last frame, or the last frame // was not rendered successfully (or render-on-demand is disabled) - return (m_settings->renderPolicy() == QRenderSettings::Always + return ((m_settings && m_settings->renderPolicy() == QRenderSettings::Always) || m_dirtyBits.marked != 0 || m_dirtyBits.remaining != 0 || !m_lastFrameCorrect.loadRelaxed()); diff --git a/src/quick3d/imports/scene3d/scene3ditem.cpp b/src/quick3d/imports/scene3d/scene3ditem.cpp index 4426a70eb..586de9938 100644 --- a/src/quick3d/imports/scene3d/scene3ditem.cpp +++ b/src/quick3d/imports/scene3d/scene3ditem.cpp @@ -81,6 +81,45 @@ QT_BEGIN_NAMESPACE namespace Qt3DRender { +class AspectEngineDestroyer : public QObject +{ + Q_OBJECT + +public: + AspectEngineDestroyer() + : QObject() + {} + + ~AspectEngineDestroyer() + { + } + + void reset(int targetCount) + { + m_allowed = 0; + m_targetAllowed = targetCount; + } + + void allowRelease() + { + ++m_allowed; + if (m_allowed == m_targetAllowed) { + if (QThread::currentThread() == thread()) + delete this; + else + deleteLater(); + } + } + + void setSGNodeAlive(bool alive) { m_sgNodeAlive = alive; } + bool sgNodeAlive() const { return m_sgNodeAlive;} + +private: + int m_allowed = 0; + int m_targetAllowed = 0; + bool m_sgNodeAlive = false; +}; + /*! \class Qt3DRender::Scene3DItem \internal @@ -147,15 +186,17 @@ Scene3DItem::Scene3DItem(QQuickItem *parent) , m_entity(nullptr) , m_viewHolderEntity(nullptr) , m_viewHolderFG(nullptr) - , m_aspectEngine(new Qt3DCore::QAspectEngine()) + , m_aspectEngine(nullptr) , m_aspectToDelete(nullptr) - , m_renderAspect(nullptr) - , m_renderer(nullptr) + , m_lastManagerNode(nullptr) + , m_aspectEngineDestroyer() , m_multisample(true) , m_dirty(true) , m_dirtyViews(false) , m_clearsWindowByDefault(true) , m_disableClearWindow(false) + , m_wasFrameProcessed(false) + , m_wasSGUpdated(false) , m_cameraAspectRatioMode(AutomaticAspectRatio) , m_compositingMode(FBO) , m_dummySurface(nullptr) @@ -165,9 +206,6 @@ Scene3DItem::Scene3DItem(QQuickItem *parent) setAcceptHoverEvents(true); // TO DO: register the event source in the main thread - // Use manual drive mode when using Scene3D - m_aspectEngine->setRunMode(Qt3DCore::QAspectEngine::Manual); - // Give a default size so that if nothing is specified by the user // we still won't get ignored by the QtQuick SG when in Underlay mode setWidth(1); @@ -176,11 +214,18 @@ Scene3DItem::Scene3DItem(QQuickItem *parent) Scene3DItem::~Scene3DItem() { - // When the window is closed, it first destroys all of its children. At - // this point, Scene3DItem is destroyed but the Renderer, AspectEngine and - // Scene3DSGNode still exist and will perform their cleanup on their own. - m_aspectEngine->deleteLater(); - m_renderer->deleteLater(); + // The SceneGraph is non deterministic in the order in which it will + // destroy the QSGNode that were created by the item. This unfortunately + // makes it difficult to know when it is safe to destroy the QAspectEngine. + // To track this we use the AspectEngineDestroyer. It allows keeping the + // AspectEngine alive and deleting later when we know that both Scene3DItem + // and Scene3DRenderer have been destroyed. + + delete m_aspectToDelete; + + if (m_aspectEngineDestroyer) + m_aspectEngineDestroyer->allowRelease(); + if (m_dummySurface) m_dummySurface->deleteLater(); } @@ -208,11 +253,14 @@ QStringList Scene3DItem::aspects() const */ Qt3DCore::QEntity *Scene3DItem::entity() const { - return m_entity.data(); + return m_entity; } void Scene3DItem::applyAspects() { + if (!m_aspectEngine) + return; + // Aspects are owned by the aspect engine for (const QString &aspect : qAsConst(m_aspects)) { if (aspect == QLatin1String("render")) // This one is hardwired anyway @@ -260,10 +308,10 @@ void Scene3DItem::setAspects(const QStringList &aspects) void Scene3DItem::setEntity(Qt3DCore::QEntity *entity) { - if (entity == m_entity.data()) + if (entity == m_entity) return; - m_entity.reset(entity); + m_entity = entity; emit entityChanged(); } @@ -404,8 +452,17 @@ void Scene3DItem::removeView(Scene3DView *view) void Scene3DItem::applyRootEntityChange() { - if (m_aspectEngine->rootEntity() != m_entity.data()) { - m_aspectEngine->setRootEntity(m_entity); + if (m_aspectEngine->rootEntity().data() != m_entity) { + + Qt3DCore::QEntityPtr entityPtr; + // We must reuse the QEntityPtr of the old AspectEngine + // otherwise it will delete the Entity once it gets destroyed + if (m_aspectToDelete) + entityPtr = m_aspectToDelete->rootEntity(); + else + entityPtr = Qt3DCore::QEntityPtr(m_entity); + + m_aspectEngine->setRootEntity(entityPtr); /* If we changed window, the old aspect engine must be deleted only after we have set the root entity for the new one so that it doesn't delete the root node. */ @@ -444,7 +501,7 @@ void Scene3DItem::applyRootEntityChange() } } -bool Scene3DItem::needsRender() +bool Scene3DItem::needsRender(QRenderAspect *renderAspect) { // We need the dirty flag which is connected to the change arbiter // receiving updates to know whether something in the scene has changed @@ -461,7 +518,7 @@ bool Scene3DItem::needsRender() // whether some states remain dirty or not (even after processFrame is // called) - auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect)); + auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(renderAspect)); const bool dirty = m_dirty || (renderAspectPriv && renderAspectPriv->m_renderer @@ -485,7 +542,7 @@ bool Scene3DItem::needsRender() // synchronize and render // Note: we might still not be done rendering when this is called but // processFrame will block and wait for renderer to have been finished -void Scene3DItem::onBeforeSync() +bool Scene3DItem::prepareQt3DFrame() { static bool dontRenderWhenHidden = !qgetenv("QT3D_SCENE3D_STOP_RENDER_HIDDEN").isEmpty(); @@ -493,34 +550,15 @@ void Scene3DItem::onBeforeSync() // waiting forever for the scene to be rendered which won't happen // if the Scene3D item is not visible if (!isVisible() && dontRenderWhenHidden) - return; - if (m_renderer->m_resetRequested) - return; - + return false; + if (!m_aspectEngine) + return false; Q_ASSERT(QThread::currentThread() == thread()); // Since we are in manual mode, trigger jobs for the next frame Qt3DCore::QAspectEnginePrivate *aspectEnginePriv = static_cast<Qt3DCore::QAspectEnginePrivate *>(QObjectPrivate::get(m_aspectEngine)); - if (!aspectEnginePriv->m_initialized || !m_renderer) - return; - - // Set compositing mode on renderer - m_renderer->setCompositingMode(m_compositingMode); - const bool usesFBO = m_compositingMode == FBO; - - // Make renderer aware of any Scene3DView we are dealing with - if (m_dirtyViews) { - // Scene3DViews checks - if (entity() != m_viewHolderEntity) { - qCWarning(Scene3D) << "Scene3DView is not supported if the Scene3D entity property has been set"; - } - if (!usesFBO) { - qCWarning(Scene3D) << "Scene3DView is only supported when Scene3D compositingMode is set to FBO"; - } - // The Scene3DRender will take care of providing the texture containing the 3D scene - m_renderer->setScene3DViews(m_views); - m_dirtyViews = false; - } + if (!aspectEnginePriv->m_initialized) + return false; Q_ASSERT(m_aspectEngine->runMode() == Qt3DCore::QAspectEngine::Manual); m_aspectEngine->processFrame(); @@ -530,19 +568,17 @@ void Scene3DItem::onBeforeSync() // that the RenderQueue target count has been set and that everything // should be ready for rendering - // processFrame() must absolutely be followed by a single call to // render // At startup, we have no garantee that the QtQuick Render Thread doesn't // start rendering before this function has been called // We add in a safety to skip such frames as this could otherwise // make Qt3D enter a locked state - m_renderer->setSkipFrame(!needsRender()); - m_renderer->allowRender(); // Note: it's too early to request an update at this point as // beforeSync() triggered by afterAnimating is considered // to be as being part of the current frame update + return true; } void Scene3DItem::requestUpdate() @@ -698,71 +734,182 @@ void Scene3DItem::setMultisample(bool enable) } } -QSGNode *Scene3DItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *) +// We want to tie the Scene3DRenderer's lifetime to the QSGNode associated with +// Scene3DItem. This ensures that when the SceneGraph tree gets destroyed, we +// also shutdown Qt3D properly +// Everything this class does happens in the QSGRenderThread +class Scene3DManagerNode : public QSGNode { - // m_resetRequested is set to true by Scene3DRenderer::shutdown() - if (m_renderer && m_renderer->m_resetRequested) { - qCWarning(Scene3D) << "Renderer for Scene3DItem has requested a reset due to the item " - "moving to another window"; - QObject::disconnect(m_windowConnection); - m_aspectEngine->unregisterAspect(m_renderAspect); // Deletes the renderAspect - m_renderAspect = nullptr; - m_aspectToDelete = m_aspectEngine; - m_aspectEngine = new Qt3DCore::QAspectEngine(); - m_aspectEngine->setRunMode(Qt3DCore::QAspectEngine::Manual); - applyAspects(); - // Needs to belong in the same thread as the item which is the same as the original - // QAspectEngine - m_aspectEngine->moveToThread(thread()); - m_renderer->m_resetRequested = false; +public: + explicit Scene3DManagerNode(Qt3DCore::QAspectEngine *aspectEngine, + AspectEngineDestroyer *destroyer) + : m_aspectEngine(aspectEngine) + , m_destroyer(destroyer) + , m_renderAspect(new QRenderAspect(QRenderAspect::Synchronous)) + , m_renderer(new Scene3DRenderer()) + { + m_destroyer->setSGNodeAlive(true); } - // If the render aspect wasn't created yet, do so now - if (m_renderAspect == nullptr) { - m_renderAspect = new QRenderAspect(QRenderAspect::Synchronous); - auto *rw = QQuickRenderControl::renderWindowFor(window()); - static_cast<Qt3DRender::QRenderAspectPrivate *>(Qt3DRender::QRenderAspectPrivate::get(m_renderAspect))->m_screen = - (rw ? rw->screen() : window()->screen()); + + ~Scene3DManagerNode() + { + // Stop the Qt3D Simulation Loop + auto engineD = Qt3DCore::QAspectEnginePrivate::get(m_aspectEngine); + engineD->exitSimulationLoop(); + + // Shutdown GL renderer + static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->renderShutdown(); + delete m_renderer; + + m_destroyer->setSGNodeAlive(false); + + // Allow AspectEngine destruction + m_destroyer->allowRelease(); + } + + void init() + { m_aspectEngine->registerAspect(m_renderAspect); + m_renderer->init(m_aspectEngine, m_renderAspect); + m_wasInitialized = true; + } + + inline bool isInitialized() const { return m_wasInitialized; } + inline QRenderAspect *renderAspect() const { return m_renderAspect; } + inline Scene3DRenderer *renderer() const { return m_renderer; } +private: + Qt3DCore::QAspectEngine *m_aspectEngine; + AspectEngineDestroyer *m_destroyer; + QRenderAspect *m_renderAspect; + Scene3DRenderer *m_renderer; + bool m_wasInitialized = false; +}; + + +// QtQuick SG +// beforeSynchronize // SG Thread (main thread blocked) +// updatePaintNode (-> Scene3DRenderer::beforeSynchronize) // SG Thread (main thread blocked) +// beforeRenderering (-> Scene3DRenderer::beforeSynchronize) // SG Thread (main thread unblocked) +// afterRenderering // SG Thread (main thread unblocked) +// afterAnimating (-> Scene3DItem::synchronize()) // Main Thread (SG Thread is not yet at beforeSynchronize ) + +// main thread (afterAnimating) +void Scene3DItem::synchronize() +{ + // Request updates for the next frame + requestUpdate(); + + if (!window() || !m_wasSGUpdated || + (!m_aspectEngineDestroyer || !m_aspectEngineDestroyer->sgNodeAlive())) { + m_wasFrameProcessed = false; + return; + } - // Before Synchronizing is in the SG Thread, we want beforeSync to be triggered - // in the context of the main thread + // Set root Entity on the aspectEngine + applyRootEntityChange(); + + // Update size of the QSurfaceSelector if needed + setItemAreaAndDevicePixelRatio(boundingRect().size().toSize(), + window()->effectiveDevicePixelRatio()); + + // Let Qt3D process the frame and launch jobs + m_wasFrameProcessed = prepareQt3DFrame(); + + m_wasSGUpdated = false; +} + +// The synchronization point between the main thread and the render thread +// before any rendering +QSGNode *Scene3DItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *) +{ + Scene3DManagerNode *managerNode = static_cast<Scene3DManagerNode *>(node); + + // In case we have no GL context, return early + // m_wasSGUpdated will not be set to true and nothing will take place + if (!QOpenGLContext::currentContext()) { + QQuickItem::update(); + return node; + } + + // Scene3DManagerNode gets automatically destroyed on Window changed, SceneGraph invalidation + if (!managerNode) { + // Did we have a Scene3DManagerNode in the past? + if (m_lastManagerNode != nullptr) { + // If so we need to recreate a new AspectEngine as node was destroyed by sceneGraph + qCWarning(Scene3D) << "Renderer for Scene3DItem has requested a reset due to the item " + "moving to another window"; + QObject::disconnect(m_windowConnection); + // Note: AspectEngine can only be deleted once we have set the root + // entity on the new instance + m_aspectEngine->setParent(nullptr); + m_aspectToDelete = m_aspectEngine; + m_aspectEngine = nullptr; + } + + // Create or Recreate AspectEngine + if (m_aspectEngine == nullptr) { + // Use manual drive mode when using Scene3D + delete m_aspectEngineDestroyer; + m_aspectEngineDestroyer = new AspectEngineDestroyer(); + m_aspectEngine = new Qt3DCore::QAspectEngine(m_aspectEngineDestroyer); + m_aspectEngine->setRunMode(Qt3DCore::QAspectEngine::Manual); + applyAspects(); + + // Needs to belong in the same thread as the item which is the same as + // the original QAspectEngine + m_aspectEngineDestroyer->moveToThread(thread()); + + // To destroy AspectEngine + m_aspectEngineDestroyer->reset(2); + } + + // Create new instance and record a pointer (which should only be used + // to check if we have had a previous manager node) + managerNode = new Scene3DManagerNode(m_aspectEngine, + m_aspectEngineDestroyer); + m_lastManagerNode = managerNode; + + // Before Synchronizing is in the SG Thread, we want synchronize to be triggered + // in the context of the main thread so we use afterAnimating instead m_windowConnection = QObject::connect(window(), &QQuickWindow::afterAnimating, - this, &Scene3DItem::onBeforeSync, Qt::DirectConnection); - auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect)); - QObject::connect(renderAspectPriv->m_aspectManager->changeArbiter(), &Qt3DCore::QChangeArbiter::receivedChange, - this, [this] { m_dirty = true; }, Qt::DirectConnection); + this, &Scene3DItem::synchronize, Qt::DirectConnection); } - if (m_renderer == nullptr) { - m_renderer = new Scene3DRenderer(); - m_renderer->init(this, m_aspectEngine, m_renderAspect); - } else if (m_renderer->renderAspect() != m_renderAspect) { - // If the renderer's renderAspect is not equal to the aspect used - // by the item, then it means that we have created a new one due to - // the fact that shutdown() was called on the renderer previously. - // This is a typical situation when the window the item is in has - // moved from one screen to another. + Scene3DRenderer *renderer = managerNode->renderer(); + QRenderAspect *renderAspect = managerNode->renderAspect(); + + // If the render aspect wasn't created yet, do so now + if (!managerNode->isInitialized()) { + auto *rw = QQuickRenderControl::renderWindowFor(window()); + auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(renderAspect)); + renderAspectPriv->m_screen = (rw ? rw->screen() : window()->screen()); updateWindowSurface(); - m_renderer->init(this, m_aspectEngine, m_renderAspect); + managerNode->init(); + // Note: ChangeArbiter is only set after aspect was registered + QObject::connect(renderAspectPriv->m_aspectManager->changeArbiter(), &Qt3DCore::QChangeArbiter::receivedChange, + this, [this] { m_dirty = true; }, Qt::DirectConnection); } + const bool usesFBO = m_compositingMode == FBO; const bool hasScene3DViews = !m_views.empty(); - Scene3DSGNode *fboNode = static_cast<Scene3DSGNode *>(node); + Scene3DSGNode *fboNode = static_cast<Scene3DSGNode *>(managerNode->firstChild()); // When usin Scene3DViews or Scene3D in Underlay mode // we shouldn't be managing a Scene3DSGNode if (!usesFBO || hasScene3DViews) { if (fboNode != nullptr) { + managerNode->removeChildNode(fboNode); delete fboNode; fboNode = nullptr; - m_renderer->setSGNode(fboNode); + renderer->setSGNode(fboNode); } } else { // Regular Scene3D only case // Create SGNode if using FBO and no Scene3DViews if (fboNode == nullptr) { fboNode = new Scene3DSGNode(); - m_renderer->setSGNode(fboNode); + renderer->setSGNode(fboNode); + managerNode->appendChildNode(fboNode); } fboNode->setRect(boundingRect()); } @@ -780,13 +927,42 @@ QSGNode *Scene3DItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNode window()->setClearBeforeRendering(false); } - // Request update for next frame so that we can check whether we need to - // render again or not - static int requestUpdateMethodIdx = Scene3DItem::staticMetaObject.indexOfMethod("requestUpdate()"); - static QMetaMethod requestUpdateMethod =Scene3DItem::staticMetaObject.method(requestUpdateMethodIdx); - requestUpdateMethod.invoke(this, Qt::QueuedConnection); + renderer->setBoundingSize(boundingRect().size().toSize()); + renderer->setMultisample(m_multisample); + // Ensure Renderer is working on current window + renderer->setWindow(window()); + // Set compositing mode on renderer + renderer->setCompositingMode(m_compositingMode); + // Set whether we want the Renderer to be allowed to render or not + const bool skipFrame = !needsRender(renderAspect); + renderer->setSkipFrame(skipFrame); + renderer->allowRender(); + + // Make renderer aware of any Scene3DView we are dealing with + if (m_dirtyViews) { + const bool usesFBO = m_compositingMode == FBO; + // Scene3DViews checks + if (entity() != m_viewHolderEntity) { + qCWarning(Scene3D) << "Scene3DView is not supported if the Scene3D entity property has been set"; + } + if (!usesFBO) { + qCWarning(Scene3D) << "Scene3DView is only supported when Scene3D compositingMode is set to FBO"; + } + // The Scene3DRender will take care of providing the texture containing the 3D scene + renderer->setScene3DViews(m_views); + m_dirtyViews = false; + } + + // Let the renderer prepare anything it needs to prior to the rendering + if (m_wasFrameProcessed) + renderer->beforeSynchronize(); - return fboNode; + // Force window->beforeRendering to be triggered + managerNode->markDirty(QSGNode::DirtyForceUpdate); + + m_wasSGUpdated = true; + + return managerNode; } void Scene3DItem::mousePressEvent(QMouseEvent *event) @@ -798,3 +974,5 @@ void Scene3DItem::mousePressEvent(QMouseEvent *event) } // namespace Qt3DRender QT_END_NAMESPACE + +#include "scene3ditem.moc" diff --git a/src/quick3d/imports/scene3d/scene3ditem_p.h b/src/quick3d/imports/scene3d/scene3ditem_p.h index 0beaf94c0..4a939aaab 100644 --- a/src/quick3d/imports/scene3d/scene3ditem_p.h +++ b/src/quick3d/imports/scene3d/scene3ditem_p.h @@ -72,6 +72,7 @@ class Scene3DCleaner; class Scene3DView; class QFrameGraphNode; class QRenderSurfaceSelector; +class AspectEngineDestroyer; class Scene3DItem : public QQuickItem { @@ -129,36 +130,38 @@ Q_SIGNALS: private Q_SLOTS: void applyRootEntityChange(); - void onBeforeSync(); void requestUpdate(); private: + void synchronize(); + bool prepareQt3DFrame(); QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *nodeData) override; void setWindowSurface(QObject *rootObject); void setCameraAspectModeHelper(); void updateCameraAspectRatio(); void mousePressEvent(QMouseEvent *event) override; - bool needsRender(); + bool needsRender(QRenderAspect *renderAspect); void updateWindowSurface(); void createDummySurface(QWindow *window, QRenderSurfaceSelector *surfaceSelector); void applyAspects(); QStringList m_aspects; - // Store as shared pointer so that aspect engine doesn't delete it. - QSharedPointer<Qt3DCore::QEntity> m_entity; + Qt3DCore::QEntity *m_entity; Qt3DCore::QEntity *m_viewHolderEntity; Qt3DRender::QFrameGraphNode *m_viewHolderFG; Qt3DCore::QAspectEngine *m_aspectEngine; Qt3DCore::QAspectEngine *m_aspectToDelete; - QRenderAspect *m_renderAspect; - Scene3DRenderer *m_renderer; + QSGNode *m_lastManagerNode; + AspectEngineDestroyer *m_aspectEngineDestroyer; bool m_multisample; bool m_dirty; bool m_dirtyViews; bool m_clearsWindowByDefault; bool m_disableClearWindow; + bool m_wasFrameProcessed; + bool m_wasSGUpdated; QPointer<Qt3DRender::QCamera> m_camera; CameraAspectRatioMode m_cameraAspectRatioMode; diff --git a/src/quick3d/imports/scene3d/scene3drenderer.cpp b/src/quick3d/imports/scene3d/scene3drenderer.cpp index 9a7960393..d39410836 100644 --- a/src/quick3d/imports/scene3d/scene3drenderer.cpp +++ b/src/quick3d/imports/scene3d/scene3drenderer.cpp @@ -62,17 +62,6 @@ QT_BEGIN_NAMESPACE namespace Qt3DRender { -namespace { - -inline QMetaMethod setItemAreaAndDevicePixelRatioMethod() -{ - const int idx = Scene3DItem::staticMetaObject.indexOfMethod("setItemAreaAndDevicePixelRatio(QSize,qreal)"); - Q_ASSERT(idx != -1); - return Scene3DItem::staticMetaObject.method(idx); -} - -} // anonymous - class ContextSaver { public: @@ -145,7 +134,6 @@ private: */ Scene3DRenderer::Scene3DRenderer() : QObject() - , m_item(nullptr) , m_aspectEngine(nullptr) , m_renderAspect(nullptr) , m_multisampledFBO(nullptr) @@ -166,38 +154,37 @@ Scene3DRenderer::Scene3DRenderer() } -void Scene3DRenderer::init(Scene3DItem *item, Qt3DCore::QAspectEngine *aspectEngine, +void Scene3DRenderer::init(Qt3DCore::QAspectEngine *aspectEngine, QRenderAspect *renderAspect) { - m_item = item; m_aspectEngine = aspectEngine; m_renderAspect = renderAspect; m_needsShutdown = true; - Q_CHECK_PTR(m_item); - Q_CHECK_PTR(m_item->window()); - - m_window = m_item->window(); - QObject::connect(m_item->window(), &QQuickWindow::beforeSynchronizing, this, &Scene3DRenderer::beforeSynchronize, Qt::DirectConnection); - QObject::connect(m_item->window(), &QQuickWindow::beforeRendering, this, &Scene3DRenderer::render, Qt::DirectConnection); - QObject::connect(m_item->window(), &QQuickWindow::sceneGraphInvalidated, this, &Scene3DRenderer::onSceneGraphInvalidated, Qt::DirectConnection); - // So that we can schedule the cleanup - QObject::connect(m_item, &QQuickItem::windowChanged, this, &Scene3DRenderer::onWindowChanged, Qt::QueuedConnection); - // Main thread -> updates the rendering window - QObject::connect(m_item, &QQuickItem::windowChanged, this, [this] (QQuickWindow *w) { - QMutexLocker l(&m_windowMutex); - m_window = w; - }); - Q_ASSERT(QOpenGLContext::currentContext()); ContextSaver saver; static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->renderInitialize(saver.context()); - scheduleRootEntityChange(); +} + +void Scene3DRenderer::setWindow(QQuickWindow *window) +{ + if (window == m_window) + return; + + QObject::disconnect(m_window); + m_window = window; + + if (m_window) { + QObject::connect(m_window, &QQuickWindow::beforeRendering, this, &Scene3DRenderer::render, Qt::DirectConnection); + } else { + shutdown(); + } } Scene3DRenderer::~Scene3DRenderer() { qCDebug(Scene3D) << Q_FUNC_INFO << QThread::currentThread(); + shutdown(); } @@ -219,67 +206,20 @@ QOpenGLFramebufferObject *Scene3DRenderer::createFramebufferObject(const QSize & return new QOpenGLFramebufferObject(size, format); } -void Scene3DRenderer::scheduleRootEntityChange() -{ - QMetaObject::invokeMethod(m_item, "applyRootEntityChange", Qt::QueuedConnection); -} - // Executed in the QtQuick render thread (which may even be the gui/main with QQuickWidget / RenderControl). void Scene3DRenderer::shutdown() { - qCDebug(Scene3D) << Q_FUNC_INFO << QThread::currentThread(); - - // In case the same item is rendered on another window reset it - m_resetRequested = true; - - // Set to null so that subsequent calls to render - // would return early - m_item = nullptr; - - // Exit the simulation loop so no more jobs are asked for. Once this - // returns it is safe to shutdown the renderer. - if (m_aspectEngine) { - auto engineD = Qt3DCore::QAspectEnginePrivate::get(m_aspectEngine); - engineD->exitSimulationLoop(); - } - - // Shutdown the Renderer Aspect while the OpenGL context - // is still valid - if (m_renderAspect) { - static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->renderShutdown(); - m_renderAspect = nullptr; - } - m_aspectEngine = nullptr; + if (!m_needsShutdown) + return; + m_needsShutdown = false; m_finalFBO.reset(); m_multisampledFBO.reset(); } -// QtQuick render thread (which may also be the gui/main thread with QQuickWidget / RenderControl) -void Scene3DRenderer::onSceneGraphInvalidated() -{ - qCDebug(Scene3D) << Q_FUNC_INFO << QThread::currentThread(); - if (m_needsShutdown) { - m_needsShutdown = false; - shutdown(); - } -} - -void Scene3DRenderer::onWindowChanged(QQuickWindow *w) -{ - qCDebug(Scene3D) << Q_FUNC_INFO << QThread::currentThread() << w; - if (!w) { - if (m_needsShutdown) { - m_needsShutdown = false; - shutdown(); - } - } -} - // Render Thread, GUI locked void Scene3DRenderer::beforeSynchronize() { - if (m_item && m_window) { - + if (m_window) { // Only render if we are sure aspectManager->processFrame was called prior // We could otherwise enter a deadlock state if (!m_allowRendering.tryAcquire(std::max(m_allowRendering.available(), 1))) @@ -301,9 +241,7 @@ void Scene3DRenderer::beforeSynchronize() m_shouldRender = true; // Check size / multisampling - m_multisample = m_item->multisample(); - const QSize boundingRectSize = m_item->boundingRect().size().toSize(); - const QSize currentSize = boundingRectSize * m_window->effectiveDevicePixelRatio(); + const QSize currentSize = m_boundingRectSize * m_window->effectiveDevicePixelRatio(); const bool sizeHasChanged = currentSize != m_lastSize; const bool multisampleHasChanged = m_multisample != m_lastMultisample; const bool forceRecreate = sizeHasChanged || multisampleHasChanged; @@ -312,12 +250,6 @@ void Scene3DRenderer::beforeSynchronize() m_lastSize = currentSize; m_lastMultisample = m_multisample; - if (sizeHasChanged) { - static const QMetaMethod setItemAreaAndDevicePixelRatio = setItemAreaAndDevicePixelRatioMethod(); - setItemAreaAndDevicePixelRatio.invoke(m_item, Qt::QueuedConnection, Q_ARG(QSize, boundingRectSize), - Q_ARG(qreal, m_window->effectiveDevicePixelRatio())); - } - // Rebuild FBO if size/multisampling has changed const bool usesFBO = m_compositingMode == Scene3DItem::FBO; if (usesFBO) { @@ -359,18 +291,12 @@ void Scene3DRenderer::beforeSynchronize() } } - if (m_aspectEngine->rootEntity() != m_item->entity()) { - scheduleRootEntityChange(); - } - // Mark SGNodes as dirty so that QQuick will trigger some rendering if (m_node) m_node->markDirty(QSGNode::DirtyMaterial); for (Scene3DView *view : qAsConst(m_views)) view->markSGNodeDirty(); - - m_item->update(); } } @@ -389,6 +315,16 @@ void Scene3DRenderer::setSkipFrame(bool skip) m_skipFrame = skip; } +void Scene3DRenderer::setMultisample(bool multisample) +{ + m_multisample = multisample; +} + +void Scene3DRenderer::setBoundingSize(const QSize &size) +{ + m_boundingRectSize = size; +} + // Main Thread, Render Thread locked void Scene3DRenderer::setScene3DViews(const QVector<Scene3DView *> views) { diff --git a/src/quick3d/imports/scene3d/scene3drenderer_p.h b/src/quick3d/imports/scene3d/scene3drenderer_p.h index 5674f21c2..22ce478e1 100644 --- a/src/quick3d/imports/scene3d/scene3drenderer_p.h +++ b/src/quick3d/imports/scene3d/scene3drenderer_p.h @@ -86,26 +86,26 @@ public: void allowRender(); void setCompositingMode(Scene3DItem::CompositingMode mode); void setSkipFrame(bool skip); + void setMultisample(bool multisample); + void setBoundingSize(const QSize &size); + void setScene3DViews(const QVector<Scene3DView *> views); - void init(Scene3DItem *item, Qt3DCore::QAspectEngine *aspectEngine, QRenderAspect *renderAspect); + void init(Qt3DCore::QAspectEngine *aspectEngine, QRenderAspect *renderAspect); + + void beforeSynchronize(); + void setWindow(QQuickWindow *window); - QRenderAspect *renderAspect() const - { - return m_renderAspect; - } + bool hasShutdown() const { return !m_needsShutdown; } + + QRenderAspect *renderAspect() const { return m_renderAspect; } public Q_SLOTS: void render(); void shutdown(); - void onSceneGraphInvalidated(); - void onWindowChanged(QQuickWindow *w); private: QOpenGLFramebufferObject *createMultisampledFramebufferObject(const QSize &size); QOpenGLFramebufferObject *createFramebufferObject(const QSize &size); - void beforeSynchronize(); - void scheduleRootEntityChange(); - Scene3DItem *m_item; // Will be released by the QQuickWindow/QML Engine Qt3DCore::QAspectEngine *m_aspectEngine; // Will be released by the Scene3DItem QRenderAspect *m_renderAspect; // Will be released by the aspectEngine QScopedPointer<QOpenGLFramebufferObject> m_multisampledFBO; @@ -115,6 +115,7 @@ private: QQuickWindow *m_window; QMutex m_windowMutex; QSize m_lastSize; + QSize m_boundingRectSize; bool m_multisample; bool m_lastMultisample; bool m_needsShutdown; diff --git a/src/render/frontend/qrenderaspect.cpp b/src/render/frontend/qrenderaspect.cpp index de99840b2..4e8967e6e 100644 --- a/src/render/frontend/qrenderaspect.cpp +++ b/src/render/frontend/qrenderaspect.cpp @@ -629,7 +629,8 @@ void QRenderAspectPrivate::renderSynchronous(bool swapBuffers) */ void QRenderAspectPrivate::renderShutdown() { - m_renderer->shutdown(); + if (m_renderer != nullptr) + m_renderer->shutdown(); } QVector<Qt3DCore::QAspectJobPtr> QRenderAspect::jobsToExecute(qint64 time) |