/**************************************************************************** ** ** Copyright (C) 2015 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "testrenderer.h" typedef Qt3DCore::QNodeId (*UuidMethod)(Qt3DRender::Render::Entity *); typedef QVector (*UuidListMethod)(Qt3DRender::Render::Entity *); using namespace Qt3DCore; using namespace Qt3DRender; using namespace Qt3DRender::Render; QNodeId transformUuid(Entity *entity) { return entity->componentUuid(); } QNodeId cameraLensUuid(Entity *entity) { return entity->componentUuid(); } QNodeId materialUuid(Entity *entity) { return entity->componentUuid(); } QNodeId geometryRendererUuid(Entity *entity) { return entity->componentUuid(); } QNodeId objectPickerUuid(Entity *entity) { return entity->componentUuid(); } QNodeId computeJobUuid(Entity *entity) { return entity->componentUuid(); } QNodeId armatureUuid(Entity *entity) { return entity->componentUuid(); } QVector layersUuid(Entity *entity) { return entity->componentsUuid(); } QVector shadersUuid(Entity *entity) { return entity->componentsUuid(); } QVector environmentLightsUuid(Entity *entity) { return entity->componentsUuid(); } class CompleteVisitor : public EntityVisitor { public: CompleteVisitor(NodeManagers *manager) : EntityVisitor(manager) { } int count = 0; Operation visit(Entity *) { count++; return Continue; } }; class EnabledVisitor : public EntityVisitor { public: EnabledVisitor(NodeManagers *manager) : EntityVisitor(manager) { } int count = 0; Operation visit(Entity *e) { count++; return e->isEnabled() ? Continue : Prune; } }; class tst_RenderEntity : public QObject { Q_OBJECT public: tst_RenderEntity() {} ~tst_RenderEntity() {} private slots: void checkInitialAndCleanUpState_data() { QTest::addColumn< QList >("components"); QList components = QList() << new Qt3DCore::QTransform << new QGeometryRenderer << new QCameraLens << new QMaterial << new QObjectPicker << new QLayer << new QShaderData << new QComputeCommand << new QEnvironmentLight << new QArmature; QTest::newRow("all components") << components; } void checkInitialAndCleanUpState() { // GIVEN QFETCH(const QList, components); TestRenderer renderer; NodeManagers nodeManagers; Qt3DRender::Render::Entity entity; Qt3DCore::QEntity dummyFrontendEntity; entity.setRenderer(&renderer); entity.setNodeManagers(&nodeManagers); // THEN QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentsUuid().isEmpty()); QVERIFY(entity.componentsUuid().isEmpty()); QVERIFY(entity.componentsUuid().isEmpty()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(!entity.isBoundingVolumeDirty()); QVERIFY(entity.childrenHandles().isEmpty()); QVERIFY(entity.layerIds().isEmpty()); // WHEN for (QComponent *component : components) EntityPrivate::get(&entity)->componentAdded(component); Qt3DCore::QEntity dummyFrontendEntityChild; // Create nodes in the backend manager nodeManagers.renderNodesManager()->getOrCreateResource(dummyFrontendEntity.id()); nodeManagers.renderNodesManager()->getOrCreateResource(dummyFrontendEntityChild.id()); // TODOSYNC clean up // // Send children added event to entity // const auto addEntityChange = QPropertyNodeAddedChangePtr::create(dummyFrontendEntity.id(), &dummyFrontendEntityChild); // entity.sceneChangeEvent(addEntityChange); // THEN QVERIFY(!entity.componentUuid().isNull()); QVERIFY(!entity.componentUuid().isNull()); QVERIFY(!entity.componentUuid().isNull()); QVERIFY(!entity.componentUuid().isNull()); QVERIFY(!entity.componentUuid().isNull()); QVERIFY(!entity.componentUuid().isNull()); QVERIFY(!entity.componentsUuid().isEmpty()); QVERIFY(!entity.componentsUuid().isEmpty()); QVERIFY(!entity.componentsUuid().isEmpty()); QVERIFY(!entity.componentUuid().isNull()); QVERIFY(entity.isBoundingVolumeDirty()); QVERIFY(entity.childrenHandles().isEmpty()); QVERIFY(!entity.layerIds().isEmpty()); QVERIFY(renderer.dirtyBits() != 0); bool containsAll = entity.containsComponentsOfType(); QVERIFY(containsAll); // WHEN entity.cleanup(); // THEN QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(entity.componentsUuid().isEmpty()); QVERIFY(entity.componentsUuid().isEmpty()); QVERIFY(entity.componentsUuid().isEmpty()); QVERIFY(entity.componentUuid().isNull()); QVERIFY(!entity.isBoundingVolumeDirty()); QVERIFY(entity.childrenHandles().isEmpty()); QVERIFY(entity.layerIds().isEmpty()); containsAll = entity.containsComponentsOfType(); QVERIFY(!containsAll); } void checkEntityReparenting() { // GIVEN TestRenderer renderer; NodeManagers nodeManagers; Qt3DCore::QEntity frontendEntityA, frontendEntityB, frontendEntityC; auto backendA = createEntity(renderer, nodeManagers, frontendEntityA); auto backendB = createEntity(renderer, nodeManagers, frontendEntityB); auto backendC = createEntity(renderer, nodeManagers, frontendEntityC); // THEN 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 sendParentChange = [&nodeManagers](const Qt3DCore::QEntity &entity) { Entity *backendEntity = nodeManagers.renderNodesManager()->getOrCreateResource(entity.id()); backendEntity->syncFromFrontEnd(&entity, false); }; // reparent B to A and C to B. frontendEntityB.setParent(&frontendEntityA); sendParentChange(frontendEntityB); frontendEntityC.setParent(&frontendEntityB); sendParentChange(frontendEntityC); // THEN QVERIFY(backendA->parent() == nullptr); QVERIFY(backendB->parent() == backendA); QVERIFY(backendC->parent() == backendB); QCOMPARE(backendA->childrenHandles().count(), 1); QCOMPARE(backendB->childrenHandles().count(), 1); QVERIFY(backendC->childrenHandles().isEmpty()); // WHEN - reparent C to A frontendEntityC.setParent(&frontendEntityA); sendParentChange(frontendEntityC); // THEN QVERIFY(backendA->parent() == nullptr); QVERIFY(backendB->parent() == backendA); QVERIFY(backendC->parent() == backendA); QCOMPARE(backendA->childrenHandles().size(), 2); QVERIFY(backendB->childrenHandles().isEmpty()); QVERIFY(backendC->childrenHandles().isEmpty()); // WHEN - reparent B to null. frontendEntityB.setParent(static_cast(nullptr)); sendParentChange(frontendEntityB); // THEN QVERIFY(backendA->parent() == nullptr); QVERIFY(backendB->parent() == nullptr); QVERIFY(backendC->parent() == backendA); QCOMPARE(backendA->childrenHandles().count(), 1); QVERIFY(!backendA->childrenHandles().contains(backendB->handle())); QVERIFY(backendB->childrenHandles().isEmpty()); QVERIFY(backendC->childrenHandles().isEmpty()); } void checkEntityCleanup() { // GIVEN TestRenderer renderer; NodeManagers nodeManagers; Qt3DCore::QEntity frontendEntityA, frontendEntityB, frontendEntityC; auto backendA = createEntity(renderer, nodeManagers, frontendEntityA); auto backendB = createEntity(renderer, nodeManagers, frontendEntityB); auto backendC = createEntity(renderer, nodeManagers, frontendEntityC); // WHEN auto sendParentChange = [&nodeManagers](const Qt3DCore::QEntity &entity) { Entity *backendEntity = nodeManagers.renderNodesManager()->getOrCreateResource(entity.id()); backendEntity->syncFromFrontEnd(&entity, false); }; // reparent B and C to A. frontendEntityB.setParent(&frontendEntityA); sendParentChange(frontendEntityB); frontendEntityC.setParent(&frontendEntityA); sendParentChange(frontendEntityC); // THEN QVERIFY(backendA->parent() == nullptr); QVERIFY(backendB->parent() == backendA); QVERIFY(backendC->parent() == backendA); QCOMPARE(backendA->childrenHandles().count(), 2); QVERIFY(backendB->childrenHandles().isEmpty()); QVERIFY(backendC->childrenHandles().isEmpty()); // WHEN - cleaning up a child backendC->cleanup(); // THEN - the child's parent should be null and it // should be removed from its parent's list of children QVERIFY(backendA->parent() == nullptr); QVERIFY(backendB->parent() == backendA); QVERIFY(backendC->parent() == nullptr); QCOMPARE(backendA->childrenHandles().count(), 1); QVERIFY(!backendA->childrenHandles().contains(backendC->handle())); QVERIFY(backendB->childrenHandles().isEmpty()); QVERIFY(backendC->childrenHandles().isEmpty()); // WHEN (cleaning up parent) backendA->cleanup(); // THEN (it's children's parent should be set to null) 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 backendB->cleanup(); // THEN nothing should change QVERIFY(backendA->childrenHandles().isEmpty()); QVERIFY(backendB->childrenHandles().isEmpty()); QVERIFY(backendC->childrenHandles().isEmpty()); QVERIFY(backendA->parent() == nullptr); QVERIFY(backendB->parent() == nullptr); QVERIFY(backendC->parent() == nullptr); } void shouldHandleSingleComponentEvents_data() { QTest::addColumn("component"); QTest::addColumn("functionPtr"); QComponent *component = new Qt3DCore::QTransform; QTest::newRow("transform") << component << reinterpret_cast(transformUuid); component = new QGeometryRenderer; QTest::newRow("mesh") << component << reinterpret_cast(geometryRendererUuid); component = new QCameraLens; QTest::newRow("camera lens") << component << reinterpret_cast(cameraLensUuid); component = new QMaterial; QTest::newRow("material") << component << reinterpret_cast(materialUuid); component = new QObjectPicker; QTest::newRow("objectPicker") << component << reinterpret_cast(objectPickerUuid); component = new QComputeCommand; QTest::newRow("computeJob") << component << reinterpret_cast(computeJobUuid); component = new QArmature; QTest::newRow("armature") << component << reinterpret_cast(armatureUuid); } void shouldHandleSingleComponentEvents() { // GIVEN QFETCH(QComponent*, component); QFETCH(void*, functionPtr); UuidMethod method = reinterpret_cast(functionPtr); TestRenderer renderer; Qt3DRender::Render::Entity entity; Qt3DCore::QEntity dummyFrontendEntity; entity.setRenderer(&renderer); // THEN QVERIFY(method(&entity).isNull()); // WHEN EntityPrivate::get(&entity)->componentAdded(component); // THEN QCOMPARE(method(&entity), component->id()); QVERIFY(renderer.dirtyBits() != 0); // WHEN renderer.resetDirty(); EntityPrivate::get(&entity)->componentRemoved(component); // THEN QVERIFY(method(&entity).isNull()); QVERIFY(renderer.dirtyBits() != 0); delete component; } void shouldHandleComponentsEvents_data() { QTest::addColumn< QList >("components"); QTest::addColumn("functionPtr"); QList components; components.clear(); components << new QLayer << new QLayer << new QLayer; QTest::newRow("layers") << components << reinterpret_cast(layersUuid); components.clear(); components << new QShaderData << new QShaderData << new QShaderData; QTest::newRow("shader data") << components << reinterpret_cast(shadersUuid); components.clear(); components << new QEnvironmentLight << new QEnvironmentLight << new QEnvironmentLight; QTest::newRow("environmentLights") << components << reinterpret_cast(environmentLightsUuid); } void shouldHandleComponentsEvents() { // GIVEN QFETCH(const QList, components); QFETCH(void*, functionPtr); UuidListMethod method = reinterpret_cast(functionPtr); TestRenderer renderer; Qt3DRender::Render::Entity entity; Qt3DCore::QEntity dummyFrontendEntity; entity.setRenderer(&renderer); // THEN QVERIFY(method(&entity).isEmpty()); // WHEN for (QComponent *component : components) EntityPrivate::get(&entity)->componentAdded(component); // THEN QCOMPARE(method(&entity).size(), components.size()); for (QComponent *component : components) { QVERIFY(method(&entity).contains(component->id())); } QVERIFY(renderer.dirtyBits() != 0); // WHEN renderer.resetDirty(); EntityPrivate::get(&entity)->componentRemoved(components.first()); // THEN QCOMPARE(method(&entity).size(), components.size() - 1); QVERIFY(!method(&entity).contains(components.first()->id())); QVERIFY(renderer.dirtyBits() != 0); qDeleteAll(components); } void traversal() { // GIVEN TestRenderer renderer; NodeManagers nodeManagers; Qt3DCore::QEntity frontendEntityA, frontendEntityB, frontendEntityC; auto backendA = createEntity(renderer, nodeManagers, frontendEntityA); createEntity(renderer, nodeManagers, frontendEntityB); createEntity(renderer, nodeManagers, frontendEntityC); auto sendParentChange = [&nodeManagers](const Qt3DCore::QEntity &entity) { Entity *backendEntity = nodeManagers.renderNodesManager()->getOrCreateResource(entity.id()); backendEntity->syncFromFrontEnd(&entity, false); }; // reparent B to A and C to B. frontendEntityB.setParent(&frontendEntityA); sendParentChange(frontendEntityB); frontendEntityC.setParent(&frontendEntityB); sendParentChange(frontendEntityC); // WHEN int visitCount = 0; auto counter = [&visitCount](const Entity *) { ++visitCount; }; backendA->traverse(counter); // THEN QCOMPARE(visitCount, 3); } void visitor() { // GIVEN TestRenderer renderer; NodeManagers nodeManagers; Qt3DCore::QEntity frontendEntityA, frontendEntityB, frontendEntityC; frontendEntityA.setEnabled(false); frontendEntityB.setEnabled(false); frontendEntityC.setEnabled(false); auto backendA = createEntity(renderer, nodeManagers, frontendEntityA); createEntity(renderer, nodeManagers, frontendEntityB); createEntity(renderer, nodeManagers, frontendEntityC); auto sendParentChange = [&nodeManagers](const Qt3DCore::QEntity &entity) { Entity *backendEntity = nodeManagers.renderNodesManager()->getOrCreateResource(entity.id()); backendEntity->syncFromFrontEnd(&entity, false); }; // reparent B to A and C to B. frontendEntityB.setParent(&frontendEntityA); sendParentChange(frontendEntityB); frontendEntityC.setParent(&frontendEntityB); sendParentChange(frontendEntityC); // WHEN CompleteVisitor v1(&nodeManagers); EnabledVisitor v2(&nodeManagers); CompleteVisitor v3(&nodeManagers); v3.setPruneDisabled(true); v1.apply(backendA); v2.apply(backendA); v3.apply(backendA); // THEN QCOMPARE(v1.count, 3); QCOMPARE(v2.count, 1); // nodes disabled but the first one is still visited before visitation finds out it's disabled QCOMPARE(v3.count, 0); // nodes disabled } void accumulator() { // GIVEN TestRenderer renderer; NodeManagers nodeManagers; Qt3DCore::QEntity frontendEntityA, frontendEntityB, frontendEntityC; frontendEntityA.setEnabled(false); frontendEntityB.setEnabled(false); frontendEntityC.setEnabled(false); auto backendA = createEntity(renderer, nodeManagers, frontendEntityA); createEntity(renderer, nodeManagers, frontendEntityB); createEntity(renderer, nodeManagers, frontendEntityC); auto sendParentChange = [&nodeManagers](const Qt3DCore::QEntity &entity) { Entity *backendEntity = nodeManagers.renderNodesManager()->getOrCreateResource(entity.id()); backendEntity->syncFromFrontEnd(&entity, false); }; // reparent B to A and C to B. frontendEntityB.setParent(&frontendEntityA); sendParentChange(frontendEntityB); frontendEntityC.setParent(&frontendEntityB); sendParentChange(frontendEntityC); // WHEN EntityAccumulator v1(&nodeManagers); EntityAccumulator v2([](Entity *e) { return e->isEnabled(); }, &nodeManagers); const auto r1 = v1.apply(backendA); const auto r2 = v2.apply(backendA); // THEN QCOMPARE(r1.count(), 3); QCOMPARE(r2.count(), 0); } private: Entity *createEntity(TestRenderer &renderer, NodeManagers &nodeManagers, const Qt3DCore::QEntity &frontEndEntity) { HEntity renderNodeHandle = nodeManagers.renderNodesManager()->getOrAcquireHandle(frontEndEntity.id()); Entity *entity = nodeManagers.renderNodesManager()->data(renderNodeHandle); entity->setNodeManagers(&nodeManagers); entity->setHandle(renderNodeHandle); entity->setRenderer(&renderer); entity->syncFromFrontEnd(&frontEndEntity, true); return entity; } }; QTEST_APPLESS_MAIN(tst_RenderEntity) #include "tst_entity.moc"