summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Albamont <jim.albamont@kdab.com>2019-03-13 13:23:21 -0500
committerJames Turner <james.turner@kdab.com>2019-04-04 09:15:50 +0000
commitbf2c2e9bb2dd0b13cb2cb6728de0c2421fbafbb7 (patch)
tree27ac13c4706c134ae476071a6b8b9646ac97ee35
parentf0f5a2de1da2e05e3587d2a6486687ebbe649339 (diff)
Fix Entity parenting hierarchy
When the initial Entity backend node hierarchy is created it skips over any non-entity nodes to ensure that Entities are only parented to other Entities. Calling QNode::setParent breaks this when reparenting Entities to non-entity nodes. Fix by sending a new "parentEntityUpdated" property update that backend Entity nodes listen for. They keep the id of their new parent and flag the need to rebuild the entity hierarchy. This triggers a new job to clear the children and parents of every backend Entity, then rebuilds the hierarchy using the stored parent ID in each Entity. This is much more forgiving of creation/parenting ordering issues and shouldn't be less performant because any Entity reparent was previously marking everything dirty anyway. Add a new test from QTBUG-73905 that creates 4 cylinders and manipulates the parents in different ways. Add a new test to tst_nodes to reparent a QEntity to a QNode and ensure the entity finds it's correct QEntity parent. Add a new test to tst_entity to ensure backend nodes correctly handle the new parenting events. Task-number: QTBUG-73905 Change-Id: Iab0203947d89bbed2868b3629fbde879675fe568 Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
-rw-r--r--src/core/nodes/qentity.cpp15
-rw-r--r--src/core/nodes/qentity.h3
-rw-r--r--src/render/backend/abstractrenderer_p.h1
-rw-r--r--src/render/backend/entity.cpp74
-rw-r--r--src/render/backend/entity_p.h6
-rw-r--r--src/render/jobs/job_common_p.h1
-rw-r--r--src/render/jobs/jobs.pri2
-rw-r--r--src/render/jobs/updateentityhierarchyjob.cpp80
-rw-r--r--src/render/jobs/updateentityhierarchyjob_p.h91
-rw-r--r--src/render/jobs/updateentitylayersjob_p.h2
-rw-r--r--src/render/renderers/opengl/renderer/renderer.cpp19
-rw-r--r--src/render/renderers/opengl/renderer/renderer_p.h2
-rw-r--r--tests/auto/core/nodes/tst_nodes.cpp67
-rw-r--r--tests/auto/render/boundingsphere/tst_boundingsphere.cpp5
-rw-r--r--tests/auto/render/entity/tst_entity.cpp119
-rw-r--r--tests/auto/render/layerfiltering/tst_layerfiltering.cpp6
-rw-r--r--tests/auto/render/pickboundingvolumejob/tst_pickboundingvolumejob.cpp9
-rw-r--r--tests/auto/render/proximityfiltering/tst_proximityfiltering.cpp5
-rw-r--r--tests/auto/render/qcamera/tst_qcamera.cpp5
-rw-r--r--tests/auto/render/raycastingjob/tst_raycastingjob.cpp5
-rw-r--r--tests/auto/render/renderer/tst_renderer.cpp25
-rw-r--r--tests/auto/render/updateshaderdatatransformjob/tst_updateshaderdatatransformjob.cpp5
-rw-r--r--tests/manual/cylinder-parent-test/cylinder-parent-test.pro9
-rw-r--r--tests/manual/cylinder-parent-test/main.cpp243
-rw-r--r--tests/manual/manual.pro1
25 files changed, 759 insertions, 41 deletions
diff --git a/src/core/nodes/qentity.cpp b/src/core/nodes/qentity.cpp
index 2ef78deb6..6a24b1956 100644
--- a/src/core/nodes/qentity.cpp
+++ b/src/core/nodes/qentity.cpp
@@ -232,6 +232,10 @@ QNodeId QEntityPrivate::parentEntityId() const
QNodeCreatedChangeBasePtr QEntity::createNodeCreationChange() const
{
+ // connect to the parentChanged signal here rather than constructor because
+ // until now there's no backend node to notify when parent changes
+ connect(this, &QNode::parentChanged, this, &QEntity::onParentChanged);
+
auto creationChange = QNodeCreatedChangePtr<QEntityData>::create(this);
auto &data = creationChange->data;
@@ -261,6 +265,17 @@ QNodeCreatedChangeBasePtr QEntity::createNodeCreationChange() const
return creationChange;
}
+void QEntity::onParentChanged(QObject *)
+{
+ const auto parentID = parentEntity() ? parentEntity()->id() : Qt3DCore::QNodeId();
+ auto parentChange = Qt3DCore::QPropertyUpdatedChangePtr::create(id());
+ parentChange->setPropertyName("parentEntityUpdated");
+ parentChange->setValue(QVariant::fromValue(parentID));
+ const bool blocked = blockNotifications(false);
+ notifyObservers(parentChange);
+ blockNotifications(blocked);
+}
+
} // namespace Qt3DCore
QT_END_NAMESPACE
diff --git a/src/core/nodes/qentity.h b/src/core/nodes/qentity.h
index dc7dc62c1..f6044ce5e 100644
--- a/src/core/nodes/qentity.h
+++ b/src/core/nodes/qentity.h
@@ -70,6 +70,9 @@ public:
protected:
explicit QEntity(QEntityPrivate &dd, QNode *parent = nullptr);
+private Q_SLOTS:
+ void onParentChanged(QObject *);
+
private:
Q_DECLARE_PRIVATE(QEntity)
diff --git a/src/render/backend/abstractrenderer_p.h b/src/render/backend/abstractrenderer_p.h
index f1bdca7be..eea6cbe9f 100644
--- a/src/render/backend/abstractrenderer_p.h
+++ b/src/render/backend/abstractrenderer_p.h
@@ -114,6 +114,7 @@ public:
JointDirty = 1 << 11,
LayersDirty = 1 << 12,
TechniquesDirty = 1 << 13,
+ EntityHierarchyDirty= 1 << 14,
AllDirty = 0xffffff
};
Q_DECLARE_FLAGS(BackendNodeDirtySet, BackendNodeDirtyFlag)
diff --git a/src/render/backend/entity.cpp b/src/render/backend/entity.cpp
index 8d03cd75f..d8d04aef1 100644
--- a/src/render/backend/entity.cpp
+++ b/src/render/backend/entity.cpp
@@ -65,8 +65,6 @@
#include <Qt3DCore/qpropertyupdatedchange.h>
#include <Qt3DCore/qtransform.h>
#include <Qt3DCore/private/qentity_p.h>
-#include <Qt3DCore/qpropertynoderemovedchange.h>
-#include <Qt3DCore/qpropertynodeaddedchange.h>
#include <Qt3DCore/qnodecreatedchange.h>
#include <QMatrix4x4>
@@ -95,14 +93,13 @@ Entity::~Entity()
void Entity::cleanup()
{
if (m_nodeManagers != nullptr) {
- Entity *parentEntity = parent();
- if (parentEntity != nullptr)
- parentEntity->removeChildHandle(m_handle);
m_nodeManagers->worldMatrixManager()->releaseResource(peerId());
-
qCDebug(Render::RenderNodes) << Q_FUNC_INFO;
-
}
+ if (!m_parentEntityId.isNull())
+ markDirty(AbstractRenderer::EntityHierarchyDirty);
+
+ m_parentEntityId = Qt3DCore::QNodeId();
m_worldTransform = HMatrix();
// Release all component will have to perform their own release when they receive the
// NodeDeleted notification
@@ -132,12 +129,8 @@ void Entity::cleanup()
void Entity::setParentHandle(HEntity parentHandle)
{
Q_ASSERT(m_nodeManagers);
- // Remove ourselves from previous parent children list
- Entity *parent = m_nodeManagers->renderNodesManager()->data(parentHandle);
- if (parent != nullptr && parent->m_childrenHandles.contains(m_handle))
- parent->m_childrenHandles.removeAll(m_handle);
m_parentHandle = parentHandle;
- parent = m_nodeManagers->renderNodesManager()->data(parentHandle);
+ auto parent = m_nodeManagers->renderNodesManager()->data(parentHandle);
if (parent != nullptr && !parent->m_childrenHandles.contains(m_handle))
parent->m_childrenHandles.append(m_handle);
}
@@ -159,8 +152,8 @@ void Entity::initializeFromPeer(const QNodeCreatedChangeBasePtr &change)
// Note this is *not* the parentId as that is the ID of the parent QNode, which is not
// necessarily the same as the parent QEntity (which may be further up the tree).
- const QNodeId parentEntityId = data.parentEntityId;
- qCDebug(Render::RenderNodes) << "Creating Entity id =" << peerId() << "parentId =" << parentEntityId;
+ m_parentEntityId = data.parentEntityId;
+ qCDebug(Render::RenderNodes) << "Creating Entity id =" << peerId() << "parentId =" << m_parentEntityId;
// TODO: Store string id instead and only in debug mode
//m_objectName = peer->objectName();
@@ -187,10 +180,7 @@ void Entity::initializeFromPeer(const QNodeCreatedChangeBasePtr &change)
for (const auto &idAndType : qAsConst(data.componentIdsAndTypes))
addComponent(idAndType);
- if (!parentEntityId.isNull())
- setParentHandle(m_nodeManagers->renderNodesManager()->lookupHandle(parentEntityId));
- else
- qCDebug(Render::RenderNodes) << Q_FUNC_INFO << "No parent entity found for Entity" << peerId();
+ markDirty(AbstractRenderer::EntityHierarchyDirty);
}
void Entity::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &e)
@@ -214,30 +204,21 @@ void Entity::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &e)
break;
}
- case PropertyValueAdded: {
- QPropertyNodeAddedChangePtr change = qSharedPointerCast<QPropertyNodeAddedChange>(e);
- if (change->metaObject()->inherits(&QEntity::staticMetaObject)) {
- appendChildHandle(m_nodeManagers->renderNodesManager()->lookupHandle(change->addedNodeId()));
- markDirty(AbstractRenderer::AllDirty);
- }
- break;
- }
-
- case PropertyValueRemoved: {
- QPropertyNodeRemovedChangePtr change = qSharedPointerCast<QPropertyNodeRemovedChange>(e);
- if (change->metaObject()->inherits(&QEntity::staticMetaObject)) {
- removeChildHandle(m_nodeManagers->renderNodesManager()->lookupHandle(change->removedNodeId()));
- markDirty(AbstractRenderer::AllDirty);
- }
- break;
- }
-
case PropertyUpdated: {
QPropertyUpdatedChangePtr change = qSharedPointerCast<QPropertyUpdatedChange>(e);
if (change->propertyName() == QByteArrayLiteral("enabled")) {
// We only mark as dirty the renderer
markDirty(AbstractRenderer::EntityEnabledDirty);
// We let QBackendNode::sceneChangeEvent change the enabled property
+ } else if (change->propertyName() == QByteArrayLiteral("parentEntityUpdated")) {
+ auto newParent = change->value().value<Qt3DCore::QNodeId>();
+ qCDebug(Render::RenderNodes) << "Setting parent for " << peerId() << ", new parentId =" << newParent;
+ if (m_parentEntityId != newParent) {
+ m_parentEntityId = newParent;
+ // TODO: change to EventHierarchyDirty and update renderer to
+ // ensure all jobs are run that depend on Entity hierarchy.
+ markDirty(AbstractRenderer::AllDirty);
+ }
}
break;
@@ -265,6 +246,27 @@ Entity *Entity::parent() const
return m_nodeManagers->renderNodesManager()->data(m_parentHandle);
}
+
+// clearEntityHierarchy and rebuildEntityHierarchy should only be called
+// from UpdateEntityHierarchyJob to update the entity hierarchy for the
+// entire scene at once
+void Entity::clearEntityHierarchy()
+{
+ m_childrenHandles.clear();
+ m_parentHandle = HEntity();
+}
+
+// clearEntityHierarchy and rebuildEntityHierarchy should only be called
+// from UpdateEntityHierarchyJob to update the entity hierarchy for the
+// entire scene at once
+void Entity::rebuildEntityHierarchy()
+{
+ if (!m_parentEntityId.isNull())
+ setParentHandle(m_nodeManagers->renderNodesManager()->lookupHandle(m_parentEntityId));
+ else
+ qCDebug(Render::RenderNodes) << Q_FUNC_INFO << "No parent entity found for Entity" << peerId();
+}
+
void Entity::appendChildHandle(HEntity childHandle)
{
if (!m_childrenHandles.contains(childHandle)) {
diff --git a/src/render/backend/entity_p.h b/src/render/backend/entity_p.h
index 075871d85..afa7dbf23 100644
--- a/src/render/backend/entity_p.h
+++ b/src/render/backend/entity_p.h
@@ -97,6 +97,10 @@ public:
HEntity handle() const { return m_handle; }
Entity *parent() const;
HEntity parentHandle() const { return m_parentHandle; }
+ Qt3DCore::QNodeId parentEntityId() const { return m_parentEntityId; }
+
+ void clearEntityHierarchy();
+ void rebuildEntityHierarchy();
void appendChildHandle(HEntity childHandle);
void removeChildHandle(HEntity childHandle) { m_childrenHandles.removeOne(childHandle); }
@@ -181,6 +185,8 @@ private:
HEntity m_parentHandle;
QVector<HEntity > m_childrenHandles;
+ Qt3DCore::QNodeId m_parentEntityId;
+
HMatrix m_worldTransform;
QSharedPointer<Sphere> m_localBoundingVolume;
QSharedPointer<Sphere> m_worldBoundingVolume;
diff --git a/src/render/jobs/job_common_p.h b/src/render/jobs/job_common_p.h
index 2ae7d81ff..8f8242ec8 100644
--- a/src/render/jobs/job_common_p.h
+++ b/src/render/jobs/job_common_p.h
@@ -107,6 +107,7 @@ namespace JobTypes {
SyncFilterEntityByLayer,
SyncMaterialGatherer,
UpdateLayerEntity,
+ UpdateEntityHierarchy,
SendTextureChangesToFrontend
};
diff --git a/src/render/jobs/jobs.pri b/src/render/jobs/jobs.pri
index 472531681..0c326c0b5 100644
--- a/src/render/jobs/jobs.pri
+++ b/src/render/jobs/jobs.pri
@@ -31,6 +31,7 @@ HEADERS += \
$$PWD/filterproximitydistancejob_p.h \
$$PWD/abstractpickingjob_p.h \
$$PWD/raycastingjob_p.h \
+ $$PWD/updateentityhierarchyjob_p.h \
$$PWD/updateentitylayersjob_p.h
SOURCES += \
@@ -61,5 +62,6 @@ SOURCES += \
$$PWD/filterproximitydistancejob.cpp \
$$PWD/abstractpickingjob.cpp \
$$PWD/raycastingjob.cpp \
+ $$PWD/updateentityhierarchyjob.cpp \
$$PWD/updateentitylayersjob.cpp
diff --git a/src/render/jobs/updateentityhierarchyjob.cpp b/src/render/jobs/updateentityhierarchyjob.cpp
new file mode 100644
index 000000000..7c18514bb
--- /dev/null
+++ b/src/render/jobs/updateentityhierarchyjob.cpp
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Klaralvdalens Datakonsult AB (KDAB).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "updateentityhierarchyjob_p.h"
+#include <Qt3DRender/private/managers_p.h>
+#include <Qt3DRender/private/nodemanagers_p.h>
+#include <Qt3DRender/private/entity_p.h>
+#include <Qt3DRender/private/job_common_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender {
+
+namespace Render {
+
+UpdateEntityHierarchyJob::UpdateEntityHierarchyJob()
+ : m_manager(nullptr)
+{
+ SET_JOB_RUN_STAT_TYPE(this, JobTypes::UpdateEntityHierarchy, 0);
+}
+
+void UpdateEntityHierarchyJob::run()
+{
+ Q_ASSERT(m_manager);
+ EntityManager *entityManager = m_manager->renderNodesManager();
+
+ const QVector<HEntity> handles = entityManager->activeHandles();
+
+ // Clear the parents and children
+ for (const HEntity &handle : handles) {
+ Entity *entity = entityManager->data(handle);
+ entity->clearEntityHierarchy();
+ }
+ for (const HEntity &handle : handles) {
+ Entity *entity = entityManager->data(handle);
+ entity->rebuildEntityHierarchy();
+ }
+}
+
+} // Render
+
+} // Qt3DRender
+
+QT_END_NAMESPACE
diff --git a/src/render/jobs/updateentityhierarchyjob_p.h b/src/render/jobs/updateentityhierarchyjob_p.h
new file mode 100644
index 000000000..f6ba2d584
--- /dev/null
+++ b/src/render/jobs/updateentityhierarchyjob_p.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Klaralvdalens Datakonsult AB (KDAB).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef QT3DRENDER_RENDER_UPDATEENTITYHIERARCHYJOB_P_H
+#define QT3DRENDER_RENDER_UPDATEENTITYHIERARCHYJOB_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <Qt3DRender/private/qt3drender_global_p.h>
+#include <Qt3DCore/qaspectjob.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender {
+
+namespace Render {
+
+class Entity;
+class NodeManagers;
+
+class QT3DRENDERSHARED_PRIVATE_EXPORT UpdateEntityHierarchyJob: public Qt3DCore::QAspectJob
+{
+public:
+ UpdateEntityHierarchyJob();
+
+ inline void setManager(NodeManagers *manager) { m_manager = manager; }
+ inline NodeManagers *manager() const { return m_manager; }
+
+ // QAspectJob interface
+ void run() final;
+
+private:
+ NodeManagers *m_manager;
+};
+
+
+using UpdateEntityHierarchyJobPtr = QSharedPointer<UpdateEntityHierarchyJob>;
+
+} // Render
+
+} // Qt3DRender
+
+QT_END_NAMESPACE
+
+#endif // QT3DRENDER_RENDER_UPDATEENTITYHIERARCHYJOB_P_H
diff --git a/src/render/jobs/updateentitylayersjob_p.h b/src/render/jobs/updateentitylayersjob_p.h
index 8c73899d9..f2e63e32d 100644
--- a/src/render/jobs/updateentitylayersjob_p.h
+++ b/src/render/jobs/updateentitylayersjob_p.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2018 Klaralvdalens Datakonsult AB (KDAB).
+** Copyright (C) 2019 Klaralvdalens Datakonsult AB (KDAB).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt3D module of the Qt Toolkit.
diff --git a/src/render/renderers/opengl/renderer/renderer.cpp b/src/render/renderers/opengl/renderer/renderer.cpp
index 4b70710f2..a023b82f9 100644
--- a/src/render/renderers/opengl/renderer/renderer.cpp
+++ b/src/render/renderers/opengl/renderer/renderer.cpp
@@ -198,6 +198,7 @@ Renderer::Renderer(QRenderAspect::RenderType type)
, m_sendTextureChangesToFrontendJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { sendTextureChangesToFrontend(); }, JobTypes::SendTextureChangesToFrontend))
, m_introspectShaderJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { reloadDirtyShaders(); }, JobTypes::DirtyShaderGathering))
, m_syncTextureLoadingJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([] {}, JobTypes::SyncTextureLoading))
+ , m_updateEntityHierarchyJob(Render::UpdateEntityHierarchyJobPtr::create())
, m_ownedContext(false)
, m_offscreenHelper(nullptr)
#if QT_CONFIG(qt3d_profile_jobs)
@@ -210,6 +211,9 @@ Renderer::Renderer(QRenderAspect::RenderType type)
if (m_renderThread)
m_renderThread->waitForStart();
+ m_worldTransformJob->addDependency(m_updateEntityHierarchyJob);
+ m_updateEntityLayersJob->addDependency(m_updateEntityHierarchyJob);
+
// Create jobs to update transforms and bounding volumes
// We can only update bounding volumes once all world transforms are known
m_updateWorldBoundingVolumeJob->addDependency(m_worldTransformJob);
@@ -299,6 +303,7 @@ void Renderer::setNodeManagers(NodeManagers *managers)
m_updateMeshTriangleListJob->setManagers(m_nodesManager);
m_filterCompatibleTechniqueJob->setManager(m_nodesManager->techniqueManager());
m_updateEntityLayersJob->setManager(m_nodesManager);
+ m_updateEntityHierarchyJob->setManager(m_nodesManager);
}
void Renderer::setServices(QServiceLocator *services)
@@ -1665,14 +1670,17 @@ QVector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs()
// Add jobs
const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty;
- if (entitiesEnabledDirty) {
+ const bool entityHierarchyNeedsToBeRebuilt = dirtyBitsForFrame & AbstractRenderer::EntityHierarchyDirty;
+ if (entitiesEnabledDirty || entityHierarchyNeedsToBeRebuilt) {
renderBinJobs.push_back(m_updateTreeEnabledJob);
// This dependency is added here because we clear all dependencies
// at the start of this function.
m_calculateBoundingVolumeJob->addDependency(m_updateTreeEnabledJob);
+ m_calculateBoundingVolumeJob->addDependency(m_updateEntityHierarchyJob);
}
- if (dirtyBitsForFrame & AbstractRenderer::TransformDirty) {
+ if (dirtyBitsForFrame & AbstractRenderer::TransformDirty ||
+ dirtyBitsForFrame & AbstractRenderer::EntityHierarchyDirty) {
renderBinJobs.push_back(m_worldTransformJob);
renderBinJobs.push_back(m_updateWorldBoundingVolumeJob);
renderBinJobs.push_back(m_updateShaderDataTransformJob);
@@ -1685,6 +1693,7 @@ QVector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs()
}
if (dirtyBitsForFrame & AbstractRenderer::GeometryDirty ||
+ dirtyBitsForFrame & AbstractRenderer::EntityHierarchyDirty ||
dirtyBitsForFrame & AbstractRenderer::TransformDirty) {
renderBinJobs.push_back(m_expandBoundingVolumeJob);
}
@@ -1722,11 +1731,15 @@ QVector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs()
// Layer cache is dependent on layers, layer filters (hence FG structure
// changes) and the enabled flag on entities
const bool frameGraphDirty = dirtyBitsForFrame & AbstractRenderer::FrameGraphDirty;
- const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty;
+ const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty || entityHierarchyNeedsToBeRebuilt;
const bool layersCacheNeedsToBeRebuilt = layersDirty || entitiesEnabledDirty || frameGraphDirty;
const bool materialDirty = dirtyBitsForFrame & AbstractRenderer::MaterialDirty;
const bool materialCacheNeedsToBeRebuilt = materialDirty || frameGraphDirty;
+ // Rebuild Entity Hierarchy if dirty
+ if (entityHierarchyNeedsToBeRebuilt)
+ renderBinJobs.push_back(m_updateEntityHierarchyJob);
+
// Rebuild Entity Layers list if layers are dirty
if (layersDirty)
renderBinJobs.push_back(m_updateEntityLayersJob);
diff --git a/src/render/renderers/opengl/renderer/renderer_p.h b/src/render/renderers/opengl/renderer/renderer_p.h
index b89c1e7c0..6443215a4 100644
--- a/src/render/renderers/opengl/renderer/renderer_p.h
+++ b/src/render/renderers/opengl/renderer/renderer_p.h
@@ -78,6 +78,7 @@
#include <Qt3DRender/private/filtercompatibletechniquejob_p.h>
#include <Qt3DRender/private/updateskinningpalettejob_p.h>
#include <Qt3DRender/private/updateentitylayersjob_p.h>
+#include <Qt3DRender/private/updateentityhierarchyjob_p.h>
#include <Qt3DRender/private/renderercache_p.h>
#include <Qt3DRender/private/texture_p.h>
@@ -361,6 +362,7 @@ private:
UpdateMeshTriangleListJobPtr m_updateMeshTriangleListJob;
FilterCompatibleTechniqueJobPtr m_filterCompatibleTechniqueJob;
UpdateEntityLayersJobPtr m_updateEntityLayersJob;
+ UpdateEntityHierarchyJobPtr m_updateEntityHierarchyJob;
QVector<Qt3DCore::QNodeId> m_pendingRenderCaptureSendRequests;
diff --git a/tests/auto/core/nodes/tst_nodes.cpp b/tests/auto/core/nodes/tst_nodes.cpp
index 3f7fb4a75..193d88c83 100644
--- a/tests/auto/core/nodes/tst_nodes.cpp
+++ b/tests/auto/core/nodes/tst_nodes.cpp
@@ -81,6 +81,7 @@ private slots:
void removingChildEntitiesFromNode();
void checkConstructionSetParentMix(); // QTBUG-60612
+ void checkParentingQEntityToQNode(); // QTBUG-73905
void checkConstructionWithParent();
void checkConstructionWithNonRootParent(); // QTBUG-73986
void checkConstructionAsListElement();
@@ -1079,6 +1080,72 @@ void tst_Nodes::checkConstructionSetParentMix()
QCOMPARE(lastEvent->addedNodeId(), subTreeRoot->id());
}
+void tst_Nodes::checkParentingQEntityToQNode()
+{
+ // GIVEN
+ ObserverSpy spy;
+ Qt3DCore::QScene scene;
+ QScopedPointer<MyQNode> root(new MyQNode());
+
+ // WHEN
+ root->setArbiterAndScene(&spy, &scene);
+ root->setSimulateBackendCreated(true);
+
+ // THEN
+ QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
+
+ // WHEN
+ auto subTreeRoot = new Qt3DCore::QEntity(root.data());
+ auto childEntity = new Qt3DCore::QEntity(subTreeRoot);
+ auto childNode = new Qt3DCore::QNode(subTreeRoot);
+
+ // THEN
+ QCoreApplication::processEvents();
+
+ // Ensure first event is subTreeRoot creation
+ const Qt3DCore::QNodeCreatedChangeBasePtr firstEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QNodeCreatedChangeBase>();
+ QVERIFY(!firstEvent.isNull());
+ QCOMPARE(firstEvent->subjectId(), subTreeRoot->id());
+ QCOMPARE(firstEvent->parentId(), root->id());
+
+ // Ensure 2nd event is childEntity creation
+ const Qt3DCore::QNodeCreatedChangeBasePtr secondEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QNodeCreatedChangeBase>();
+ QVERIFY(!secondEvent.isNull());
+ QCOMPARE(secondEvent->subjectId(), childEntity->id());
+ QCOMPARE(secondEvent->parentId(), subTreeRoot->id());
+
+ // Ensure 3rd event is childNode creation
+ const Qt3DCore::QNodeCreatedChangeBasePtr thirdEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QNodeCreatedChangeBase>();
+ QVERIFY(!thirdEvent.isNull());
+ QCOMPARE(thirdEvent->subjectId(), childNode->id());
+ QCOMPARE(thirdEvent->parentId(), subTreeRoot->id());
+
+
+ // WHEN we reparent the childEntity to the childNode (QNode)
+
+ spy.events.clear();
+ childEntity->setParent(childNode);
+ // THEN we should get
+ // - one child removed change for childEntity->subTreeRoot,
+ // - one child added change for childEntity->childNode,
+ // - and one property updated event specifying the correct QEntity parent (subTreeRoot)
+ QCOMPARE(spy.events.size(), 3);
+
+ const auto removedEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeRemovedChange>();
+ QVERIFY(!removedEvent.isNull());
+ QCOMPARE(removedEvent->subjectId(), subTreeRoot->id());
+
+ const auto addedEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
+ QVERIFY(!addedEvent.isNull());
+ QCOMPARE(addedEvent->subjectId(), childNode->id());
+
+ const auto parentChangeEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyUpdatedChange>();
+ QVERIFY(!parentChangeEvent.isNull());
+ QCOMPARE(parentChangeEvent->subjectId(), childEntity->id());
+ QCOMPARE(parentChangeEvent->propertyName(), "parentEntityUpdated");
+ QCOMPARE(parentChangeEvent->value().value<Qt3DCore::QNodeId>(), subTreeRoot->id());
+}
+
void tst_Nodes::checkConstructionWithParent()
{
// GIVEN
diff --git a/tests/auto/render/boundingsphere/tst_boundingsphere.cpp b/tests/auto/render/boundingsphere/tst_boundingsphere.cpp
index 992e643d2..b35c6d31a 100644
--- a/tests/auto/render/boundingsphere/tst_boundingsphere.cpp
+++ b/tests/auto/render/boundingsphere/tst_boundingsphere.cpp
@@ -55,6 +55,7 @@
#include <Qt3DRender/private/calcboundingvolumejob_p.h>
#include <Qt3DRender/private/calcgeometrytrianglevolumes_p.h>
#include <Qt3DRender/private/loadbufferjob_p.h>
+#include <Qt3DRender/private/updateentityhierarchyjob_p.h>
#include <Qt3DRender/private/buffermanager_p.h>
#include <Qt3DRender/private/geometryrenderermanager_p.h>
#include <Qt3DRender/private/sphere_p.h>
@@ -116,6 +117,10 @@ namespace {
void runRequiredJobs(Qt3DRender::TestAspect *test)
{
+ Qt3DRender::Render::UpdateEntityHierarchyJob updateEntitiesJob;
+ updateEntitiesJob.setManager(test->nodeManagers());
+ updateEntitiesJob.run();
+
Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform;
updateWorldTransform.setRoot(test->sceneRoot());
updateWorldTransform.run();
diff --git a/tests/auto/render/entity/tst_entity.cpp b/tests/auto/render/entity/tst_entity.cpp
index 6ad958451..123a648d6 100644
--- a/tests/auto/render/entity/tst_entity.cpp
+++ b/tests/auto/render/entity/tst_entity.cpp
@@ -151,7 +151,7 @@ private slots:
QVERIFY(!entity.componentsUuid<EnvironmentLight>().isEmpty());
QVERIFY(!entity.componentUuid<Armature>().isNull());
QVERIFY(entity.isBoundingVolumeDirty());
- QVERIFY(!entity.childrenHandles().isEmpty());
+ QVERIFY(entity.childrenHandles().isEmpty());
QVERIFY(!entity.layerIds().isEmpty());
QVERIFY(renderer.dirtyBits() != 0);
bool containsAll = entity.containsComponentsOfType<Transform,
@@ -162,6 +162,7 @@ private slots:
entity.cleanup();
// THEN
+ QVERIFY(entity.parentEntityId().isNull());
QVERIFY(entity.componentUuid<Transform>().isNull());
QVERIFY(entity.componentUuid<CameraLens>().isNull());
QVERIFY(entity.componentUuid<Material>().isNull());
@@ -180,6 +181,122 @@ private slots:
QVERIFY(!containsAll);
}
+ void checkRebuildingEntityHierarchy()
+ {
+ // GIVEN
+ TestRenderer renderer;
+ NodeManagers nodeManagers;
+ Qt3DCore::QEntity frontendEntityA, frontendEntityB, frontendEntityC;
+
+ auto entityCreator = [&nodeManagers, &renderer](const Qt3DCore::QEntity &frontEndEntity) {
+ Entity *entity = nodeManagers.renderNodesManager()->getOrCreateResource(frontEndEntity.id());
+ entity->setNodeManagers(&nodeManagers);
+ entity->setRenderer(&renderer);
+ return entity;
+ };
+
+ auto backendA = entityCreator(frontendEntityA);
+ auto backendB = entityCreator(frontendEntityB);
+ auto backendC = entityCreator(frontendEntityC);
+
+ // THEN
+ QVERIFY(backendA->parentEntityId().isNull());
+ QVERIFY(backendB->parentEntityId().isNull());
+ QVERIFY(backendC->parentEntityId().isNull());
+
+ QVERIFY(backendA->parent() == nullptr);
+ QVERIFY(backendB->parent() == nullptr);
+ QVERIFY(backendC->parent() == nullptr);
+
+ QVERIFY(backendA->childrenHandles().isEmpty());
+ QVERIFY(backendB->childrenHandles().isEmpty());
+ QVERIFY(backendC->childrenHandles().isEmpty());
+
+ // WHEN
+ renderer.clearDirtyBits(0);
+ QVERIFY(renderer.dirtyBits() == 0);
+
+ auto sendParentChange = [&nodeManagers](const Qt3DCore::QEntity &entity) {
+ const auto parentChange = QPropertyUpdatedChangePtr::create(entity.id());
+ parentChange->setPropertyName("parentEntityUpdated");
+ auto parent = entity.parentEntity();
+ parentChange->setValue(QVariant::fromValue(parent ? parent->id() : Qt3DCore::QNodeId()));
+
+ Entity *backendEntity = nodeManagers.renderNodesManager()->getOrCreateResource(entity.id());
+ backendEntity->sceneChangeEvent(parentChange);
+ };
+
+ // reparent B to A and C to B.
+ frontendEntityB.setParent(&frontendEntityA);
+ sendParentChange(frontendEntityB);
+ frontendEntityC.setParent(&frontendEntityB);
+ sendParentChange(frontendEntityC);
+
+ // THEN
+ QVERIFY(renderer.dirtyBits() & AbstractRenderer::EntityHierarchyDirty);
+
+ QVERIFY(backendA->parentEntityId().isNull());
+ QVERIFY(backendB->parentEntityId() == frontendEntityA.id());
+ QVERIFY(backendC->parentEntityId() == frontendEntityB.id());
+
+ QVERIFY(backendA->parent() == nullptr);
+ QVERIFY(backendB->parent() == nullptr);
+ QVERIFY(backendC->parent() == nullptr);
+
+ QVERIFY(backendA->childrenHandles().isEmpty());
+ QVERIFY(backendB->childrenHandles().isEmpty());
+ QVERIFY(backendC->childrenHandles().isEmpty());
+
+ // WHEN
+ auto rebuildHierarchy = [](Entity *backend) {
+ backend->clearEntityHierarchy();
+ backend->rebuildEntityHierarchy();
+ };
+ rebuildHierarchy(backendA);
+ rebuildHierarchy(backendB);
+ rebuildHierarchy(backendC);
+
+ // THEN
+ QVERIFY(backendA->parent() == nullptr);
+ QVERIFY(backendB->parent() == backendA);
+ QVERIFY(backendC->parent() == backendB);
+
+ QVERIFY(!backendA->childrenHandles().isEmpty());
+ QVERIFY(!backendB->childrenHandles().isEmpty());
+ QVERIFY(backendC->childrenHandles().isEmpty());
+
+ // WHEN - reparent B to null.
+ frontendEntityB.setParent(static_cast<Qt3DCore::QNode *>(nullptr));
+ sendParentChange(frontendEntityB);
+ rebuildHierarchy(backendA);
+ rebuildHierarchy(backendB);
+ rebuildHierarchy(backendC);
+
+ QVERIFY(backendA->parentEntityId().isNull());
+ QVERIFY(backendB->parentEntityId().isNull());
+ QVERIFY(backendC->parentEntityId() == frontendEntityB.id());
+
+ QVERIFY(backendA->parent() == nullptr);
+ QVERIFY(backendB->parent() == nullptr);
+ QVERIFY(backendC->parent() == backendB);
+
+ QVERIFY(backendA->childrenHandles().isEmpty());
+ QVERIFY(!backendB->childrenHandles().isEmpty());
+ QVERIFY(backendC->childrenHandles().isEmpty());
+
+ // WHEN - cleanup
+ backendA->cleanup();
+ backendB->cleanup();
+ backendC->cleanup();
+
+ // THEN
+ QVERIFY(backendA->parentEntityId().isNull());
+ QVERIFY(backendB->parentEntityId().isNull());
+ QVERIFY(backendC->parentEntityId().isNull());
+
+ QVERIFY(renderer.dirtyBits() != 0);
+ }
+
void shouldHandleSingleComponentEvents_data()
{
QTest::addColumn<QComponent*>("component");
diff --git a/tests/auto/render/layerfiltering/tst_layerfiltering.cpp b/tests/auto/render/layerfiltering/tst_layerfiltering.cpp
index eeffc69b2..9b8636f49 100644
--- a/tests/auto/render/layerfiltering/tst_layerfiltering.cpp
+++ b/tests/auto/render/layerfiltering/tst_layerfiltering.cpp
@@ -33,6 +33,7 @@
#include <Qt3DRender/private/entity_p.h>
#include <Qt3DRender/private/filterlayerentityjob_p.h>
#include <Qt3DRender/private/updatetreeenabledjob_p.h>
+#include <Qt3DRender/private/updateentityhierarchyjob_p.h>
#include <Qt3DRender/qlayer.h>
#include <Qt3DRender/qlayerfilter.h>
#include "testaspect.h"
@@ -632,6 +633,11 @@ private Q_SLOTS:
// WHEN
Qt3DRender::Render::Entity *backendRoot = aspect->nodeManagers()->renderNodesManager()->getOrCreateResource(entitySubtree->id());
+
+ Qt3DRender::Render::UpdateEntityHierarchyJob updateEntitiesJob;
+ updateEntitiesJob.setManager(aspect->nodeManagers());
+ updateEntitiesJob.run();
+
Qt3DRender::Render::UpdateTreeEnabledJob updateTreeEnabledJob;
updateTreeEnabledJob.setRoot(backendRoot);
updateTreeEnabledJob.run();
diff --git a/tests/auto/render/pickboundingvolumejob/tst_pickboundingvolumejob.cpp b/tests/auto/render/pickboundingvolumejob/tst_pickboundingvolumejob.cpp
index 60b60eb6e..21f75b7a4 100644
--- a/tests/auto/render/pickboundingvolumejob/tst_pickboundingvolumejob.cpp
+++ b/tests/auto/render/pickboundingvolumejob/tst_pickboundingvolumejob.cpp
@@ -47,6 +47,7 @@
#include <Qt3DRender/private/qrenderaspect_p.h>
#include <Qt3DRender/private/pickboundingvolumejob_p.h>
#include <Qt3DRender/private/pickboundingvolumeutils_p.h>
+#include <Qt3DRender/private/updateentityhierarchyjob_p.h>
#include <Qt3DRender/private/updatemeshtrianglelistjob_p.h>
#include <Qt3DRender/private/updateworldboundingvolumejob_p.h>
#include <Qt3DRender/private/updateworldtransformjob_p.h>
@@ -107,6 +108,10 @@ namespace {
void runRequiredJobs(Qt3DRender::TestAspect *test)
{
+ Qt3DRender::Render::UpdateEntityHierarchyJob updateEntitiesJob;
+ updateEntitiesJob.setManager(test->nodeManagers());
+ updateEntitiesJob.run();
+
Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform;
updateWorldTransform.setRoot(test->sceneRoot());
updateWorldTransform.run();
@@ -252,6 +257,10 @@ private Q_SLOTS:
QVERIFY(root);
QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
+ Qt3DRender::Render::UpdateEntityHierarchyJob updateEntitiesJob;
+ updateEntitiesJob.setManager(test->nodeManagers());
+ updateEntitiesJob.run();
+
// THEN
QList<Qt3DCore::QEntity *> frontendEntities;
frontendEntities << qobject_cast<Qt3DCore::QEntity *>(root.data()) << root->findChildren<Qt3DCore::QEntity *>();
diff --git a/tests/auto/render/proximityfiltering/tst_proximityfiltering.cpp b/tests/auto/render/proximityfiltering/tst_proximityfiltering.cpp
index 7a5648271..7bb3c16a7 100644
--- a/tests/auto/render/proximityfiltering/tst_proximityfiltering.cpp
+++ b/tests/auto/render/proximityfiltering/tst_proximityfiltering.cpp
@@ -246,6 +246,11 @@ private Q_SLOTS:
// WHEN
Qt3DRender::Render::Entity *backendRoot = aspect->nodeManagers()->renderNodesManager()->getOrCreateResource(entitySubtree->id());
+
+ Qt3DRender::Render::UpdateEntityHierarchyJob updateEntitiesJob;
+ updateEntitiesJob.setManager(aspect->nodeManagers());
+ updateEntitiesJob.run();
+
Qt3DRender::Render::UpdateTreeEnabledJob updateTreeEnabledJob;
updateTreeEnabledJob.setRoot(backendRoot);
updateTreeEnabledJob.run();
diff --git a/tests/auto/render/qcamera/tst_qcamera.cpp b/tests/auto/render/qcamera/tst_qcamera.cpp
index b630c447a..7aef2af7d 100644
--- a/tests/auto/render/qcamera/tst_qcamera.cpp
+++ b/tests/auto/render/qcamera/tst_qcamera.cpp
@@ -35,6 +35,7 @@
#include <Qt3DCore/QEntity>
#include <Qt3DCore/private/qnodecreatedchangegenerator_p.h>
+#include <Qt3DRender/private/updateentityhierarchyjob_p.h>
#include <Qt3DCore/private/qaspectjobmanager_p.h>
#include <Qt3DCore/qpropertyupdatedchange.h>
#include <Qt3DCore/qnodecreatedchange.h>
@@ -99,6 +100,10 @@ namespace {
void runRequiredJobs(Qt3DRender::TestAspect *test)
{
+ Qt3DRender::Render::UpdateEntityHierarchyJob updateEntitiesJob;
+ updateEntitiesJob.setManager(test->nodeManagers());
+ updateEntitiesJob.run();
+
Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform;
updateWorldTransform.setRoot(test->sceneRoot());
updateWorldTransform.run();
diff --git a/tests/auto/render/raycastingjob/tst_raycastingjob.cpp b/tests/auto/render/raycastingjob/tst_raycastingjob.cpp
index 4980bfc30..411bb9160 100644
--- a/tests/auto/render/raycastingjob/tst_raycastingjob.cpp
+++ b/tests/auto/render/raycastingjob/tst_raycastingjob.cpp
@@ -51,6 +51,7 @@
#include <Qt3DRender/private/updateworldtransformjob_p.h>
#include <Qt3DRender/private/expandboundingvolumejob_p.h>
#include <Qt3DRender/private/calcboundingvolumejob_p.h>
+#include <Qt3DRender/private/updateentityhierarchyjob_p.h>
#include <Qt3DRender/private/calcgeometrytrianglevolumes_p.h>
#include <Qt3DRender/private/loadbufferjob_p.h>
#include <Qt3DRender/private/buffermanager_p.h>
@@ -106,6 +107,10 @@ namespace {
void runRequiredJobs(Qt3DRender::TestAspect *test)
{
+ Qt3DRender::Render::UpdateEntityHierarchyJob updateEntitiesJob;
+ updateEntitiesJob.setManager(test->nodeManagers());
+ updateEntitiesJob.run();
+
Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform;
updateWorldTransform.setRoot(test->sceneRoot());
updateWorldTransform.run();
diff --git a/tests/auto/render/renderer/tst_renderer.cpp b/tests/auto/render/renderer/tst_renderer.cpp
index 9321f5303..e67e0ed55 100644
--- a/tests/auto/render/renderer/tst_renderer.cpp
+++ b/tests/auto/render/renderer/tst_renderer.cpp
@@ -252,6 +252,30 @@ private Q_SLOTS:
renderQueue->reset();
// WHEN
+ renderer.markDirty(Qt3DRender::Render::AbstractRenderer::EntityHierarchyDirty, nullptr);
+ jobs = renderer.renderBinJobs();
+
+ // THEN
+ QCOMPARE(jobs.size(),
+ 1 + // EntityEnabledDirty
+ 1 + // EntityHierarchyJob
+ 1 + // WorldTransformJob
+ 1 + // UpdateWorldBoundingVolume
+ 1 + // UpdateShaderDataTransform
+ 1 + // ExpandBoundingVolumeJob
+ 1 + // CalculateBoundingVolumeJob
+ 1 + // UpdateEntityLayersJob
+ 1 + // updateLevelOfDetailJob
+ 1 + // updateSkinningPaletteJob
+ 1 + // cleanupJob
+ 1 + // sendBufferCaptureJob
+ singleRenderViewJobCount +
+ layerCacheJobCount);
+
+ renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
+ renderQueue->reset();
+
+ // WHEN
renderer.markDirty(Qt3DRender::Render::AbstractRenderer::AllDirty, nullptr);
jobs = renderer.renderBinJobs();
@@ -259,6 +283,7 @@ private Q_SLOTS:
// and ShaderGathererJob are not added here)
QCOMPARE(jobs.size(),
1 + // EntityEnabledDirty
+ 1 + // EntityHierarchyDirty
1 + // WorldTransformJob
1 + // UpdateWorldBoundingVolume
1 + // UpdateShaderDataTransform
diff --git a/tests/auto/render/updateshaderdatatransformjob/tst_updateshaderdatatransformjob.cpp b/tests/auto/render/updateshaderdatatransformjob/tst_updateshaderdatatransformjob.cpp
index 67ddccd9b..4bab46423 100644
--- a/tests/auto/render/updateshaderdatatransformjob/tst_updateshaderdatatransformjob.cpp
+++ b/tests/auto/render/updateshaderdatatransformjob/tst_updateshaderdatatransformjob.cpp
@@ -29,6 +29,7 @@
#include <QtTest/QTest>
#include <Qt3DRender/private/updateshaderdatatransformjob_p.h>
#include <Qt3DRender/private/updateworldtransformjob_p.h>
+#include <Qt3DRender/private/updateentityhierarchyjob_p.h>
#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/private/managers_p.h>
#include <Qt3DRender/qrenderaspect.h>
@@ -88,6 +89,10 @@ namespace {
void runRequiredJobs(Qt3DRender::TestAspect *test)
{
+ Qt3DRender::Render::UpdateEntityHierarchyJob updateEntitiesJob;
+ updateEntitiesJob.setManager(test->nodeManagers());
+ updateEntitiesJob.run();
+
Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform;
updateWorldTransform.setRoot(test->sceneRoot());
updateWorldTransform.run();
diff --git a/tests/manual/cylinder-parent-test/cylinder-parent-test.pro b/tests/manual/cylinder-parent-test/cylinder-parent-test.pro
new file mode 100644
index 000000000..d3db3bc76
--- /dev/null
+++ b/tests/manual/cylinder-parent-test/cylinder-parent-test.pro
@@ -0,0 +1,9 @@
+!include( ../manual.pri ) {
+ error( "Couldn't find the manual.pri file!" )
+}
+
+QT += 3dcore 3drender 3dinput 3dextras
+
+SOURCES += main.cpp
+
+
diff --git a/tests/manual/cylinder-parent-test/main.cpp b/tests/manual/cylinder-parent-test/main.cpp
new file mode 100644
index 000000000..823461a28
--- /dev/null
+++ b/tests/manual/cylinder-parent-test/main.cpp
@@ -0,0 +1,243 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Klaralvdalens Datakonsult AB (KDAB).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QGuiApplication>
+#include <QTimer>
+
+#include <Qt3DInput/QInputAspect>
+
+#include <Qt3DRender/qcamera.h>
+#include <Qt3DRender/qcameralens.h>
+#include <Qt3DExtras/qcylindermesh.h>
+#include <Qt3DRender/qmesh.h>
+#include <Qt3DRender/qtechnique.h>
+#include <Qt3DExtras/qphongmaterial.h>
+#include <Qt3DRender/qeffect.h>
+#include <Qt3DRender/qtexture.h>
+#include <Qt3DRender/qrenderpass.h>
+#include <Qt3DRender/qrenderaspect.h>
+#include <Qt3DExtras/qforwardrenderer.h>
+
+#include <Qt3DCore/qentity.h>
+#include <Qt3DCore/qtransform.h>
+#include <Qt3DCore/qaspectengine.h>
+
+#include <Qt3DExtras/qt3dwindow.h>
+#include <Qt3DExtras/qorbitcameracontroller.h>
+#include <QThread>
+#include <QLoggingCategory>
+
+#include <type_traits>
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+ Qt3DExtras::Qt3DWindow view;
+
+ QLoggingCategory::setFilterRules("Qt3D.Renderer.RenderNodes=true");
+
+ // Root entity
+ Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity();
+ view.setRootEntity(rootEntity);
+ rootEntity->setObjectName("Root Entity");
+
+ // Set root object of the scene
+ view.show();
+
+ // Camera
+ Qt3DRender::QCamera *camera = view.camera();
+ camera->lens()->setPerspectiveProjection(45.0f, 16.0f / 9.0f, 0.1f, 1000.0f);
+ camera->setPosition(QVector3D(0, 0, 20.0f));
+ camera->setUpVector(QVector3D(0, 1, 0));
+ camera->setViewCenter(QVector3D(0, 0, 0));
+
+ // For camera controls
+ Qt3DExtras::QOrbitCameraController *cameraController = new Qt3DExtras::QOrbitCameraController(rootEntity);
+ cameraController->setCamera(camera);
+
+ // Cylinder shape data
+ Qt3DExtras::QCylinderMesh *mesh = new Qt3DExtras::QCylinderMesh();
+
+ qDebug() << "Setup complete. Creating cylinders\n";
+
+ // simple setParent from nullptr (OK for QTBUG-73905)
+ // green cylinder, bottom left
+ {
+ Qt3DCore::QTransform *leftTransform = new Qt3DCore::QTransform;
+ leftTransform->setTranslation(QVector3D(-5, -2, 0));
+ leftTransform->setObjectName("Green transform");
+
+ Qt3DExtras::QPhongMaterial *greenMaterial = new Qt3DExtras::QPhongMaterial(rootEntity);
+ greenMaterial->setObjectName("Green Material");
+ greenMaterial->setDiffuse(Qt::green);
+
+ Qt3DCore::QEntity *grandParentNode = new Qt3DCore::QEntity();
+ Qt3DCore::QEntity *parentNode = new Qt3DCore::QEntity();
+ Qt3DCore::QEntity *leafNode = new Qt3DCore::QEntity();
+ grandParentNode->setObjectName("Green Grandparent");
+ parentNode->setObjectName("Green Parent");
+ leafNode->setObjectName("Green Leaf");
+
+ leafNode->addComponent(mesh);
+ leafNode->addComponent(greenMaterial);
+ parentNode->addComponent(leftTransform);
+
+ grandParentNode->setParent(rootEntity);
+ parentNode->setParent(grandParentNode);
+ leafNode->setParent(parentNode);
+ }
+
+ // simple setParent from rootEntity (doesn't work QTBUG-73905)
+ // yellow cylinder, top left
+ {
+ Qt3DCore::QTransform *leftTransform = new Qt3DCore::QTransform;
+ leftTransform->setTranslation(QVector3D(-5, 2, 0));
+ leftTransform->setObjectName("Yellow Transform");
+
+ Qt3DExtras::QPhongMaterial *yellowMaterial = new Qt3DExtras::QPhongMaterial(rootEntity);
+ yellowMaterial->setObjectName("Yellow Material");
+ yellowMaterial->setDiffuse(Qt::yellow);
+
+ Qt3DCore::QEntity *grandParentNode = new Qt3DCore::QEntity(rootEntity);
+ Qt3DCore::QEntity *parentNode = new Qt3DCore::QEntity(rootEntity);
+ Qt3DCore::QEntity *leafNode = new Qt3DCore::QEntity(rootEntity);
+ leafNode->setObjectName("Yellow Leaf");
+ grandParentNode->setObjectName("Yellow Grandparent");
+ parentNode->setObjectName("Yellow Parent");
+
+ leafNode->addComponent(mesh);
+ leafNode->addComponent(yellowMaterial);
+ parentNode->addComponent(leftTransform);
+
+ // sometimes this can change things
+ //QCoreApplication::processEvents();
+
+ grandParentNode->setParent(rootEntity);
+ parentNode->setParent(grandParentNode);
+ leafNode->setParent(parentNode);
+ }
+
+ // complex setParent from nullptr (OK QTBUG-73905?)
+ // red cylinder, Bottom-right
+ {
+ Qt3DCore::QNode *tree1node1 = new Qt3DCore::QNode();
+ Qt3DCore::QEntity *tree1node2 = new Qt3DCore::QEntity();
+ Qt3DCore::QNode *tree1node3 = new Qt3DCore::QNode();
+ tree1node1->setObjectName("Red Tree1-Node1");
+ tree1node2->setObjectName("Red Tree1-Node2");
+ tree1node3->setObjectName("Red Tree1-Node3");
+
+ Qt3DCore::QNode *tree2node1 = new Qt3DCore::QNode();
+ Qt3DCore::QEntity *tree2node2 = new Qt3DCore::QEntity();
+ Qt3DCore::QNode *tree2node3 = new Qt3DCore::QNode();
+ tree2node1->setObjectName("Red Tree2-Node1");
+ tree2node2->setObjectName("Red Tree2-Node2");
+ tree2node3->setObjectName("Red Tree2-Node3");
+
+ Qt3DCore::QTransform *wrongRedTransform = new Qt3DCore::QTransform;
+ wrongRedTransform->setTranslation(QVector3D(1, -1, 0));
+ Qt3DCore::QTransform *bottomRightTransform = new Qt3DCore::QTransform;
+ bottomRightTransform->setTranslation(QVector3D(5, -2, 0));
+ bottomRightTransform->setObjectName("Red BR Transform");
+ wrongRedTransform->setObjectName("Red Wrong Transform");
+
+ Qt3DExtras::QPhongMaterial *redMaterial = new Qt3DExtras::QPhongMaterial(rootEntity);
+ redMaterial->setDiffuse(Qt::red);
+ redMaterial->setObjectName("Red Material");
+ Qt3DCore::QEntity *leafNode = new Qt3DCore::QEntity();
+ leafNode->setObjectName("Red Leaf");
+ leafNode->addComponent(mesh);
+ leafNode->addComponent(redMaterial);
+
+ tree1node2->addComponent(wrongRedTransform);
+ tree2node2->addComponent(bottomRightTransform);
+
+ tree1node1->setParent(rootEntity);
+ tree1node2->setParent(tree1node1);
+ tree1node3->setParent(tree1node2);
+
+ tree2node1->setParent(rootEntity);
+ tree2node2->setParent(tree2node1);
+ tree2node3->setParent(tree2node2);
+
+ leafNode->setParent(tree1node3);
+ leafNode->setParent(tree2node3);
+ }
+
+ // complex setParent from rootEntity (doesn't work QTBUG-73905)
+ // blue cylinder, top right
+ {
+ Qt3DCore::QNode *tree1node1 = new Qt3DCore::QNode(rootEntity);
+ Qt3DCore::QEntity *tree1node2 = new Qt3DCore::QEntity(rootEntity);
+ Qt3DCore::QNode *tree1node3 = new Qt3DCore::QNode(rootEntity);
+ tree1node1->setObjectName("Blue Tree1-Node1");
+ tree1node2->setObjectName("Blue Tree1-Node2");
+ tree1node3->setObjectName("Blue Tree1-Node3");
+
+ Qt3DCore::QNode *tree2node1 = new Qt3DCore::QNode(rootEntity);
+ Qt3DCore::QEntity *tree2node2 = new Qt3DCore::QEntity(rootEntity);
+ Qt3DCore::QNode *tree2node3 = new Qt3DCore::QNode(rootEntity);
+ tree2node1->setObjectName("Blue Tree2-Node1");
+ tree2node2->setObjectName("Blue Tree2-Node2");
+ tree2node3->setObjectName("Blue Tree2-Node3");
+
+ Qt3DCore::QTransform *wrongBlueTransform = new Qt3DCore::QTransform;
+ wrongBlueTransform->setTranslation(QVector3D(1, 1, 0));
+ Qt3DCore::QTransform *topRightTransform = new Qt3DCore::QTransform;
+ topRightTransform->setTranslation(QVector3D(5, 2, 0));
+ wrongBlueTransform->setObjectName("Blue Wrong Transform");
+ topRightTransform->setObjectName("Blue TR Transform");
+
+ Qt3DExtras::QPhongMaterial *blueMaterial = new Qt3DExtras::QPhongMaterial(rootEntity);
+ blueMaterial->setObjectName("Blue Material");
+ blueMaterial->setDiffuse(Qt::blue);
+ Qt3DCore::QEntity *leafNode = new Qt3DCore::QEntity(rootEntity);
+ leafNode->addComponent(mesh);
+ leafNode->addComponent(blueMaterial);
+ leafNode->setObjectName("Blue Leaf");
+
+ // sometimes this can change things
+ //QCoreApplication::processEvents();
+
+ tree1node2->addComponent(wrongBlueTransform);
+ tree2node2->addComponent(topRightTransform);
+
+ tree1node1->setParent(rootEntity);
+ tree1node2->setParent(tree1node1);
+ tree1node3->setParent(tree1node2);
+
+ tree2node1->setParent(rootEntity);
+ tree2node2->setParent(tree2node1);
+ tree2node3->setParent(tree2node2);
+
+ leafNode->setParent(tree1node3);
+ leafNode->setParent(tree2node3);
+ }
+
+ return app.exec();
+}
diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro
index 6666245ca..4900add69 100644
--- a/tests/manual/manual.pro
+++ b/tests/manual/manual.pro
@@ -13,6 +13,7 @@ SUBDIRS += \
custom-mesh-update-data-cpp \
custom-mesh-update-data-qml \
cylinder-cpp \
+ cylinder-parent-test \
cylinder-qml \
deferred-renderer-cpp \
deferred-renderer-qml \