// Copyright (C) 2016 Paul Lemire // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #include #include #include #include #include #include #include #include #include "qmlscenereader.h" QT_BEGIN_NAMESPACE namespace Qt3DRender { QList getNodesForCreation(Qt3DCore::QNode *root) { using namespace Qt3DCore; QList nodes; Qt3DCore::QNodeVisitor visitor; visitor.traverse(root, [&nodes](QNode *node) { nodes.append(node); // Store the metaobject of the node in the QNode so that we have it available // to us during destruction in the QNode destructor. This allows us to send // the QNodeId and the metaobject as typeinfo to the backend aspects so they // in turn can find the correct QBackendNodeMapper object to handle the destruction // of the corresponding backend nodes. QNodePrivate *d = QNodePrivate::get(node); d->m_typeInfo = const_cast(QNodePrivate::findStaticMetaObject(node->metaObject())); // Mark this node as having been handled for creation so that it is picked up d->m_hasBackendNode = true; }); return nodes; } QList nodeTreeChangesForNodes(const QList nodes) { QList nodeTreeChanges; nodeTreeChanges.reserve(nodes.size()); for (Qt3DCore::QNode *n : nodes) { nodeTreeChanges.push_back({ n->id(), Qt3DCore::QNodePrivate::get(n)->m_typeInfo, Qt3DCore::NodeTreeChange::Added, n }); } return nodeTreeChanges; } class TestAspect : public Qt3DRender::QRenderAspect { public: TestAspect(Qt3DCore::QNode *root) : Qt3DRender::QRenderAspect() , m_sceneRoot(nullptr) { Qt3DRender::QRenderAspect::onRegistered(); const QList nodes = getNodesForCreation(root); d_func()->setRootAndCreateNodes(qobject_cast(root), nodeTreeChangesForNodes(nodes)); Qt3DRender::Render::Entity *rootEntity = nodeManagers()->lookupResource(rootEntityId()); Q_ASSERT(rootEntity); m_sceneRoot = rootEntity; } ~TestAspect() { QRenderAspect::onUnregistered(); } void onRegistered() override { Qt3DRender::QRenderAspect::onRegistered(); } void onUnregistered() override { Qt3DRender::QRenderAspect::onUnregistered(); } Qt3DRender::Render::NodeManagers *nodeManagers() const { return d_func()->m_renderer->nodeManagers(); } Qt3DRender::Render::FrameGraphNode *frameGraphRoot() const { return d_func()->m_renderer->frameGraphRoot(); } Qt3DRender::Render::RenderSettings *renderSettings() const { return d_func()->m_renderer->settings(); } Qt3DRender::Render::Entity *sceneRoot() const { return m_sceneRoot; } private: Qt3DRender::Render::Entity *m_sceneRoot; }; } // Qt3DRender QT_END_NAMESPACE namespace { void runRequiredJobs(Qt3DRender::TestAspect *test) { Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform; updateWorldTransform.setRoot(test->sceneRoot()); updateWorldTransform.setManagers(test->nodeManagers()); updateWorldTransform.run(); } struct NodeCollection { explicit NodeCollection(Qt3DRender::TestAspect *aspect, QObject *frontendRoot) : shaderData(frontendRoot->findChildren()) { // THEN QCOMPARE(aspect->nodeManagers()->shaderDataManager()->activeHandles().size(), size_t(shaderData.size())); for (const Qt3DRender::QShaderData *s : std::as_const(shaderData)) { Qt3DRender::Render::ShaderData *backend = aspect->nodeManagers()->shaderDataManager()->lookupResource(s->id()); QVERIFY(backend != nullptr); backendShaderData.push_back(backend); } } QList shaderData; QList backendShaderData; }; } // anonymous class tst_UpdateShaderDataTransformJob : public QObject { Q_OBJECT private Q_SLOTS: void checkInitialState() { // GIVEN Qt3DRender::Render::UpdateShaderDataTransformJob backendUpdateShaderDataTransformJob; // THEN QVERIFY(backendUpdateShaderDataTransformJob.managers() == nullptr); } void checkInitializeState() { // GIVEN Qt3DRender::Render::UpdateShaderDataTransformJob backendUpdateShaderDataTransformJob; Qt3DRender::Render::NodeManagers managers; // WHEN backendUpdateShaderDataTransformJob.setManagers(&managers); // THEN QVERIFY(backendUpdateShaderDataTransformJob.managers() == &managers); } void checkRunModelToEye() { // GIVEN QmlSceneReader sceneReader(QUrl("qrc:/test_scene_model_to_eye.qml")); QScopedPointer root(qobject_cast(sceneReader.root())); QVERIFY(root); QScopedPointer test(new Qt3DRender::TestAspect(root.data())); // Properly compute the world transforms runRequiredJobs(test.data()); // WHEN Qt3DRender::QCamera *camera = root->findChild(); const NodeCollection collection(test.data(), root.data()); // THEN QCOMPARE(collection.shaderData.size(), 1); QVERIFY(camera != nullptr); // WHEN Qt3DRender::Render::ShaderData *backendShaderData = collection.backendShaderData.first(); const QHash &props = backendShaderData->properties(); // THEN QCOMPARE(props.size(), 2); QVERIFY(props.contains(QStringLiteral("eyePosition"))); QVERIFY(props.contains(QStringLiteral("eyePositionTransformed"))); QCOMPARE(props[QStringLiteral("eyePosition")].value.value(), QVector3D(1.0f, 1.0f, 1.0f)); QCOMPARE(props[QStringLiteral("eyePositionTransformed")].value.toInt(), int(Qt3DRender::Render::ShaderData::ModelToEye)); // WHEN Qt3DRender::Render::UpdateShaderDataTransformJob backendUpdateShaderDataTransformJob; backendUpdateShaderDataTransformJob.setManagers(test->nodeManagers()); backendUpdateShaderDataTransformJob.run(); // THEN // See scene file to find translation const Qt3DRender::Render::ShaderData::PropertyValue propertyValue = props[QStringLiteral("eyePosition")]; QCOMPARE(backendShaderData->getTransformedProperty(&propertyValue, Matrix4x4(camera->viewMatrix())).value(), Matrix4x4(camera->viewMatrix()) * (Vector3D(1.0f, 1.0f, 1.0f) + Vector3D(0.0f, 5.0f, 0.0f))); } void checkRunModelToWorld() { // GIVEN QmlSceneReader sceneReader(QUrl("qrc:/test_scene_model_to_world.qml")); QScopedPointer root(qobject_cast(sceneReader.root())); QVERIFY(root); QScopedPointer test(new Qt3DRender::TestAspect(root.data())); // Properly compute the world transforms runRequiredJobs(test.data()); // WHEN Qt3DRender::QCamera *camera = root->findChild(); const NodeCollection collection(test.data(), root.data()); // THEN QCOMPARE(collection.shaderData.size(), 1); QVERIFY(camera != nullptr); // WHEN Qt3DRender::Render::ShaderData *backendShaderData = collection.backendShaderData.first(); const QHash &props = backendShaderData->properties(); // THEN QCOMPARE(props.size(), 2); QVERIFY(props.contains(QStringLiteral("position"))); QVERIFY(props.contains(QStringLiteral("positionTransformed"))); QCOMPARE(props[QStringLiteral("position")].value.value(), QVector3D(1.0f, 1.0f, 1.0f)); QCOMPARE(props[QStringLiteral("positionTransformed")].value.toInt(), int(Qt3DRender::Render::ShaderData::ModelToWorld)); // WHEN Qt3DRender::Render::UpdateShaderDataTransformJob backendUpdateShaderDataTransformJob; backendUpdateShaderDataTransformJob.setManagers(test->nodeManagers()); backendUpdateShaderDataTransformJob.run(); // THEN // See scene file to find translation const Qt3DRender::Render::ShaderData::PropertyValue propertyValue = props[QStringLiteral("position")]; QCOMPARE(backendShaderData->getTransformedProperty(&propertyValue, Matrix4x4(camera->viewMatrix())).value(), Vector3D(1.0f, 1.0f, 1.0f) + Vector3D(5.0f, 5.0f, 5.0f)); } }; QTEST_MAIN(tst_UpdateShaderDataTransformJob) #include "tst_updateshaderdatatransformjob.moc"