summaryrefslogtreecommitdiffstats
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-26 15:44:23 +0200
commit54c8d77ef2f4590c4d125274844665f2ea2c4f65 (patch)
tree0655de77b7874906743a6d30227c69f4856d5a81
parent6ab35d627447830ca293dc6749b715de20b9fd23 (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.cpp3
-rw-r--r--src/plugins/renderers/opengl/renderer/renderer.cpp8
-rw-r--r--src/quick3d/imports/scene3d/scene3ditem.cpp372
-rw-r--r--src/quick3d/imports/scene3d/scene3ditem_p.h15
-rw-r--r--src/quick3d/imports/scene3d/scene3drenderer.cpp160
-rw-r--r--src/quick3d/imports/scene3d/scene3drenderer_p.h30
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;