summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Lemire <paul.lemire@kdab.com>2020-08-21 11:09:44 +0200
committerPaul Lemire <paul.lemire@kdab.com>2020-08-25 16:45:59 +0200
commit09eb4028e1221a8aaca8b563a1dba2ae6c39b92c (patch)
tree796ec16f1cec8b65368587e8e734ee9c536706d6 /src
parent4c7cfd85abdd4a914a5b80e7c406b7cc823840a6 (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.cpp3
-rw-r--r--src/plugins/renderers/opengl/renderer/renderer.cpp8
-rw-r--r--src/quick3d/imports/scene3d/scene3ditem.cpp364
-rw-r--r--src/quick3d/imports/scene3d/scene3ditem_p.h15
-rw-r--r--src/quick3d/imports/scene3d/scene3drenderer.cpp128
-rw-r--r--src/quick3d/imports/scene3d/scene3drenderer_p.h21
-rw-r--r--src/render/frontend/qrenderaspect.cpp3
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)