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-26 15:44:23 +0200 |
commit | 54c8d77ef2f4590c4d125274844665f2ea2c4f65 (patch) | |
tree | 0655de77b7874906743a6d30227c69f4856d5a81 | |
parent | 6ab35d627447830ca293dc6749b715de20b9fd23 (diff) |
rhi: apply Scene3D changes from 5.15
Should fix potential crash on shutdown
Change-Id: If1b2180532d23ad8187b25014d0d3a51a7eb1ebe
Reviewed-by: Mike Krus <mike.krus@kdab.com>
-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 | 372 | ||||
-rw-r--r-- | src/quick3d/imports/scene3d/scene3ditem_p.h | 15 | ||||
-rw-r--r-- | src/quick3d/imports/scene3d/scene3drenderer.cpp | 160 | ||||
-rw-r--r-- | src/quick3d/imports/scene3d/scene3drenderer_p.h | 30 |
6 files changed, 361 insertions, 227 deletions
diff --git a/src/core/aspects/qaspectengine.cpp b/src/core/aspects/qaspectengine.cpp index df352c38d..6d3fd7566 100644 --- a/src/core/aspects/qaspectengine.cpp +++ b/src/core/aspects/qaspectengine.cpp @@ -284,7 +284,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 55a9fa4e4..eca9172e7 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 @@ -1699,7 +1703,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 f7596f748..2ca7c2327 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,13 +186,15 @@ 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_wasFrameProcessed(false) + , m_wasSGUpdated(false) , m_cameraAspectRatioMode(AutomaticAspectRatio) , m_compositingMode(FBO) , m_dummySurface(nullptr) @@ -163,9 +204,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); @@ -174,11 +212,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(); } @@ -206,11 +251,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 @@ -258,10 +306,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(); } @@ -402,8 +450,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. */ @@ -442,7 +499,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 @@ -459,7 +516,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 @@ -483,7 +540,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(); @@ -491,34 +548,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(); @@ -528,19 +566,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() @@ -696,83 +732,227 @@ void Scene3DItem::setMultisample(bool enable) } } +// 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 +{ +public: + explicit Scene3DManagerNode(Qt3DCore::QAspectEngine *aspectEngine, + AspectEngineDestroyer *destroyer) + : m_aspectEngine(aspectEngine) + , m_destroyer(destroyer) + , m_renderAspect(new QRenderAspect(QRenderAspect::Manual)) + , m_renderer(new Scene3DRenderer()) + { + m_destroyer->setSGNodeAlive(true); + } + + ~Scene3DManagerNode() + { + // Stop the Qt3D Simulation Loop + auto engineD = Qt3DCore::QAspectEnginePrivate::get(m_aspectEngine); + engineD->exitSimulationLoop(); + + // Shutdown renderer + 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; + } + + // 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 *) { - // 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; + Scene3DManagerNode *managerNode = static_cast<Scene3DManagerNode *>(node); + QSGRendererInterface::GraphicsApi windowApi = window()->rendererInterface()->graphicsApi(); + + // In case we have no GL context, return early + // m_wasSGUpdated will not be set to true and nothing will take place + if ((windowApi == QSGRendererInterface::OpenGLRhi || + windowApi == QSGRendererInterface::OpenGL) && !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::synchronize, Qt::DirectConnection); + } + + Scene3DRenderer *renderer = managerNode->renderer(); + QRenderAspect *renderAspect = managerNode->renderAspect(); + + 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); + // If the render aspect wasn't created yet, do so now - if (m_renderAspect == nullptr) { - m_renderAspect = new QRenderAspect(QRenderAspect::Manual); - QSGRendererInterface::GraphicsApi windowApi = window()->rendererInterface()->graphicsApi(); + if (!managerNode->isInitialized()) { // Requested API of OpenGLRhi implies OpenGL renderer if (windowApi != QSGRendererInterface::OpenGLRhi && qgetenv("QT3D_RENDERER").isEmpty()) qputenv("QT3D_RENDERER", "rhi"); // else Qt3D still defaults to OpenGL - auto *rw = QQuickRenderControl::renderWindowFor(window()); - static_cast<Qt3DRender::QRenderAspectPrivate *>(Qt3DRender::QRenderAspectPrivate::get(m_renderAspect))->m_screen = - (rw ? rw->screen() : window()->screen()); - m_aspectEngine->registerAspect(m_renderAspect); - // Before Synchronizing is in the SG Thread, we want beforeSync to be triggered - // in the context of the main thread - m_windowConnection = QObject::connect(window(), &QQuickWindow::afterAnimating, - this, &Scene3DItem::onBeforeSync, Qt::DirectConnection); - auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect)); + auto *rw = QQuickRenderControl::renderWindowFor(window()); + auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(renderAspect)); + renderAspectPriv->m_screen = (rw ? rw->screen() : window()->screen()); + updateWindowSurface(); + 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); } - 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. - updateWindowSurface(); - m_renderer->init(this, m_aspectEngine, m_renderAspect); - } 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 using 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; } } else { // Regular Scene3D only case - // SGNode != nullptr if using FBO and no Scene3DViews - fboNode = m_renderer->sgNode(); - if (fboNode) + // Create SGNode if using FBO and no Scene3DViews + fboNode = renderer->sgNode(); + if (fboNode) { + if (!fboNode->parent()) + managerNode->appendChildNode(fboNode); fboNode->setRect(boundingRect()); + } } - // 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); + // 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(); + + // Force window->beforeRendering to be triggered + managerNode->markDirty(QSGNode::DirtyForceUpdate); - return fboNode; + // Set whether we want the Renderer to be allowed to render or not + const bool skipFrame = !needsRender(renderAspect); + renderer->setSkipFrame(skipFrame); + renderer->allowRender(); + + m_wasSGUpdated = true; + + return managerNode; } void Scene3DItem::mousePressEvent(QMouseEvent *event) @@ -784,3 +964,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 03de6ca0f..eb38149fb 100644 --- a/src/quick3d/imports/scene3d/scene3ditem_p.h +++ b/src/quick3d/imports/scene3d/scene3ditem_p.h @@ -73,6 +73,7 @@ class Scene3DCleaner; class Scene3DView; class QFrameGraphNode; class QRenderSurfaceSelector; +class AspectEngineDestroyer; class Scene3DItem : public QQuickItem { @@ -130,34 +131,36 @@ 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_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 0d5d2cf5c..6cdef96c3 100644 --- a/src/quick3d/imports/scene3d/scene3drenderer.cpp +++ b/src/quick3d/imports/scene3d/scene3drenderer.cpp @@ -65,17 +65,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: @@ -148,7 +137,6 @@ private: */ Scene3DRenderer::Scene3DRenderer() : QObject() - , m_item(nullptr) , m_aspectEngine(nullptr) , m_renderAspect(nullptr) , m_node(nullptr) @@ -163,22 +151,16 @@ 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(); - // Detect which Rendering backend Qt3D is using - Qt3DRender::QRenderAspectPrivate *aspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect)); - Qt3DRender::Render::AbstractRenderer *renderer = aspectPriv->m_renderer; + Qt3DRender::QRenderAspectPrivate *aspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect)); + Qt3DRender::Render::AbstractRenderer *renderer = aspectPriv->m_renderer; const bool isRHI = renderer->api() == API::RHI; if (isRHI) @@ -186,29 +168,30 @@ void Scene3DRenderer::init(Scene3DItem *item, Qt3DCore::QAspectEngine *aspectEng else m_quickRenderer = new Scene3DRenderer::GLRenderer; m_quickRenderer->initialize(this, renderer); +} + +void Scene3DRenderer::setWindow(QQuickWindow *window) +{ + if (window == m_window) + return; + + QObject::disconnect(m_window); + m_window = window; - QObject::connect(m_item->window(), &QQuickWindow::beforeSynchronizing, - this, [this] () { m_quickRenderer->beforeSynchronize(this); }, Qt::DirectConnection); - QObject::connect(m_item->window(), &QQuickWindow::beforeRendering, this, - [this] () { m_quickRenderer->beforeRendering(this); }, Qt::DirectConnection); - QObject::connect(m_item->window(), &QQuickWindow::beforeRenderPassRecording, this, - [this] () { m_quickRenderer->beforeRenderPassRecording(this); }, 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; - }); - scheduleRootEntityChange(); + if (m_window) { + QObject::connect(m_window, &QQuickWindow::beforeRendering, this, + [this] () { m_quickRenderer->beforeRendering(this); }, Qt::DirectConnection); + QObject::connect(m_window, &QQuickWindow::beforeRenderPassRecording, this, + [this] () { m_quickRenderer->beforeRenderPassRecording(this); }, Qt::DirectConnection); + } else { + shutdown(); + } } Scene3DRenderer::~Scene3DRenderer() { qCDebug(Scene3D) << Q_FUNC_INFO << QThread::currentThread(); + shutdown(); } Scene3DSGNode *Scene3DRenderer::sgNode() const @@ -216,58 +199,22 @@ Scene3DSGNode *Scene3DRenderer::sgNode() const return m_node; } -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(); - } + if (!m_needsShutdown) + return; + m_needsShutdown = false; m_quickRenderer->shutdown(this); - - m_renderAspect = nullptr; - m_aspectEngine = nullptr; - delete m_quickRenderer; m_quickRenderer = nullptr; } -// 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) +// Render Thread, GUI locked +void Scene3DRenderer::beforeSynchronize() { - qCDebug(Scene3D) << Q_FUNC_INFO << QThread::currentThread() << w; - if (!w) { - if (m_needsShutdown) { - m_needsShutdown = false; - shutdown(); - } - } + m_quickRenderer->beforeSynchronize(this); } void Scene3DRenderer::allowRender() @@ -285,6 +232,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 QList<Scene3DView *> &views) { @@ -327,10 +284,9 @@ void Scene3DRenderer::GLRenderer::initialize(Scene3DRenderer *scene3DRenderer, void Scene3DRenderer::GLRenderer::beforeSynchronize(Scene3DRenderer *scene3DRenderer) { // Check size / multisampling - Scene3DItem *item = scene3DRenderer->m_item; QQuickWindow *window = scene3DRenderer->m_window; - if (!item || !window) + if (!window) return; // Only render if we are sure aspectManager->processFrame was called prior @@ -353,22 +309,15 @@ void Scene3DRenderer::GLRenderer::beforeSynchronize(Scene3DRenderer *scene3DRend scene3DRenderer->m_shouldRender = true; - m_multisample = item->multisample(); - const QSize boundingRectSize = item->boundingRect().size().toSize(); + const QSize boundingRectSize = scene3DRenderer->boundingSize(); const QSize currentSize = boundingRectSize * window->effectiveDevicePixelRatio(); const bool sizeHasChanged = currentSize != m_lastSize; - const bool multisampleHasChanged = m_multisample != m_lastMultisample; + const bool multisampleHasChanged = scene3DRenderer->multisample() != m_lastMultisample; const bool forceRecreate = sizeHasChanged || multisampleHasChanged; // Store the current size as a comparison // point for the next frame m_lastSize = currentSize; - m_lastMultisample = m_multisample; - - if (sizeHasChanged) { - static const QMetaMethod setItemAreaAndDevicePixelRatio = setItemAreaAndDevicePixelRatioMethod(); - setItemAreaAndDevicePixelRatio.invoke(item, Qt::QueuedConnection, Q_ARG(QSize, boundingRectSize), - Q_ARG(qreal, window->effectiveDevicePixelRatio())); - } + m_lastMultisample = scene3DRenderer->multisample(); // Rebuild FBO if size/multisampling has changed const bool usesFBO = scene3DRenderer->m_compositingMode == Scene3DItem::FBO; @@ -416,17 +365,12 @@ void Scene3DRenderer::GLRenderer::beforeSynchronize(Scene3DRenderer *scene3DRend } } - if (scene3DRenderer->m_aspectEngine->rootEntity() != item->entity()) - scene3DRenderer->scheduleRootEntityChange(); - // Mark SGNodes as dirty so that QQuick will trigger some rendering if (node) node->markDirty(QSGNode::DirtyMaterial); for (Scene3DView *view : qAsConst(scene3DRenderer->m_views)) view->markSGNodeDirty(); - - item->update(); } void Scene3DRenderer::GLRenderer::beforeRendering(Scene3DRenderer *scene3DRenderer) @@ -527,10 +471,9 @@ void Scene3DRenderer::RHIRenderer::initialize(Scene3DRenderer *scene3DRenderer, void Scene3DRenderer::RHIRenderer::beforeSynchronize(Scene3DRenderer *scene3DRenderer) { // Check size / multisampling - Scene3DItem *item = scene3DRenderer->m_item; QQuickWindow *window = scene3DRenderer->m_window; - if (!item || !window) + if (!window) return; // Only render if we are sure aspectManager->processFrame was called prior @@ -552,27 +495,21 @@ void Scene3DRenderer::RHIRenderer::beforeSynchronize(Scene3DRenderer *scene3DRen scene3DRenderer->m_shouldRender = true; - const QSize boundingRectSize = item->boundingRect().size().toSize(); + const QSize boundingRectSize = scene3DRenderer->boundingSize(); const QSize currentSize = boundingRectSize * window->effectiveDevicePixelRatio(); const bool sizeHasChanged = currentSize != m_lastSize; - const bool multisampleHasChanged = item->multisample() != m_lastMultisample; + const bool multisampleHasChanged = scene3DRenderer->multisample() != m_lastMultisample; // Store the current size and multisample as a comparison point for the next frame - m_lastMultisample = item->multisample(); + m_lastMultisample = scene3DRenderer->multisample(); m_lastSize = currentSize; - if (sizeHasChanged) { - static const QMetaMethod setItemAreaAndDevicePixelRatio = setItemAreaAndDevicePixelRatioMethod(); - setItemAreaAndDevicePixelRatio.invoke(item, Qt::QueuedConnection, Q_ARG(QSize, boundingRectSize), - Q_ARG(qreal, window->effectiveDevicePixelRatio())); - } - const bool forceRecreate = sizeHasChanged || multisampleHasChanged; const bool usesFBO = scene3DRenderer->m_compositingMode == Scene3DItem::FBO; // Not sure how we could support underlay rendering properly given Qt3D RHI will render into its own // RHI RenderPasses prior to QtQuick and beginning a new RenderPass clears the screen Q_ASSERT(usesFBO); const bool generateNewTexture = m_texture.isNull() || forceRecreate; - const int samples = item->multisample() ? 4 : 1; + const int samples = m_lastMultisample ? 4 : 1; if (generateNewTexture) { releaseRHIResources(); @@ -626,11 +563,6 @@ void Scene3DRenderer::RHIRenderer::beforeSynchronize(Scene3DRenderer *scene3DRen // Mark SGNodes as dirty so that QQuick will trigger some rendering node->markDirty(QSGNode::DirtyMaterial); - - if (scene3DRenderer->m_aspectEngine->rootEntity() != item->entity()) - scene3DRenderer->scheduleRootEntityChange(); - - item->update(); } void Scene3DRenderer::RHIRenderer::beforeRendering(Scene3DRenderer *scene3DRenderer) diff --git a/src/quick3d/imports/scene3d/scene3drenderer_p.h b/src/quick3d/imports/scene3d/scene3drenderer_p.h index c88094cf7..f42167981 100644 --- a/src/quick3d/imports/scene3d/scene3drenderer_p.h +++ b/src/quick3d/imports/scene3d/scene3drenderer_p.h @@ -95,20 +95,27 @@ public: void allowRender(); void setCompositingMode(Scene3DItem::CompositingMode mode); void setSkipFrame(bool skip); - void setScene3DViews(const QList<Scene3DView *> &views); void init(Scene3DItem *item, Qt3DCore::QAspectEngine *aspectEngine, QRenderAspect *renderAspect); - QRenderAspect *renderAspect() const - { - return m_renderAspect; - } + void setMultisample(bool multisample); + void setBoundingSize(const QSize &size); + + bool multisample() const { return m_multisample; } + QSize boundingSize() const { return m_boundingRectSize; } + + void setScene3DViews(const QList<Scene3DView *> &views); + void init(Qt3DCore::QAspectEngine *aspectEngine, QRenderAspect *renderAspect); + + void beforeSynchronize(); + void setWindow(QQuickWindow *window); + + bool hasShutdown() const { return !m_needsShutdown; } + + QRenderAspect *renderAspect() const { return m_renderAspect; } public Q_SLOTS: void shutdown(); - void onSceneGraphInvalidated(); - void onWindowChanged(QQuickWindow *w); private: - void scheduleRootEntityChange(); class QuickRenderer { @@ -173,12 +180,17 @@ private: QRhi *m_rhi = nullptr; }; - 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 Scene3DSGNode *m_node; // Will be released by the QtQuick SceneGraph QQuickWindow *m_window; QMutex m_windowMutex; + + QSize m_lastSize; + QSize m_boundingRectSize; + bool m_multisample; + bool m_lastMultisample; + bool m_needsShutdown; bool m_shouldRender; bool m_dirtyViews; |