diff options
26 files changed, 1090 insertions, 93 deletions
diff --git a/examples/3dstudioruntime2/3dstudioruntime2.pro b/examples/3dstudioruntime2/3dstudioruntime2.pro index 71cf65c..37e384d 100644 --- a/examples/3dstudioruntime2/3dstudioruntime2.pro +++ b/examples/3dstudioruntime2/3dstudioruntime2.pro @@ -7,8 +7,7 @@ SUBDIRS += \ qtHaveModule(quick) { SUBDIRS += simpleqml \ qmldatainput \ - layersinquick \ - pureqml3d + layersinquick } qtHaveModule(widgets) { diff --git a/src/imports/studio3d/plugin.cpp b/src/imports/studio3d/plugin.cpp index 7925f40..479af16 100644 --- a/src/imports/studio3d/plugin.cpp +++ b/src/imports/studio3d/plugin.cpp @@ -46,6 +46,7 @@ // API NG #include <private/q3dsstudio3dengine_p.h> #include <private/q3dslayer3d_p.h> +#include <private/q3dsgroup3d_p.h> static void initResources() { @@ -82,6 +83,9 @@ public: // API NG qmlRegisterType<Q3DSStudio3DEngine>(uri, 2, 1, "Studio3DEngine"); qmlRegisterType<Q3DSLayer3D>(uri, 2, 1, "Layer3D"); + qmlRegisterUncreatableType<Q3DSObject3D>(uri, 2, 1, "Object3D", QLatin1String("Object3D is a base class")); + qmlRegisterUncreatableType<Q3DSNode3D>(uri, 2, 1, "Node3D", QLatin1String("Node3D is a base class")); + qmlRegisterType<Q3DSGroup3D>(uri, 2, 1, "Group3D"); } }; diff --git a/src/runtime/aping/aping.pri b/src/runtime/aping/aping.pri index 32e64ef..63ca117 100644 --- a/src/runtime/aping/aping.pri +++ b/src/runtime/aping/aping.pri @@ -1,7 +1,13 @@ SOURCES += \ $$PWD/q3dsstudio3dengine.cpp \ - $$PWD/q3dslayer3d.cpp + $$PWD/q3dslayer3d.cpp \ + $$PWD/q3dsobject3d.cpp \ + $$PWD/q3dsnode3d.cpp \ + $$PWD/q3dsgroup3d.cpp HEADERS += \ $$PWD/q3dsstudio3dengine_p.h \ - $$PWD/q3dslayer3d_p.h + $$PWD/q3dslayer3d_p.h \ + $$PWD/q3dsobject3d_p.h \ + $$PWD/q3dsnode3d_p.h \ + $$PWD/q3dsgroup3d_p.h diff --git a/src/runtime/aping/q3dsgroup3d.cpp b/src/runtime/aping/q3dsgroup3d.cpp new file mode 100644 index 0000000..98f83fe --- /dev/null +++ b/src/runtime/aping/q3dsgroup3d.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL$ +** 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 or (at your option) 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.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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3dsgroup3d_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Group3D + \instantiates Q3DSGroup3D + \inqmlmodule QtStudio3D + \ingroup 3dstudioruntime2 + \inherits Node3D + \since Qt 3D Studio 2.1 + + \internal + */ + +Q3DSGroup3D::Q3DSGroup3D() +{ +} + +Q3DSGroup3D::~Q3DSGroup3D() +{ +} + +Q3DSGraphObject *Q3DSGroup3D::createObject(Q3DSUipPresentation *presentation) +{ + // id is always unique, name does not have to be. We will use the same unique string for both. + const QByteArray id = makeIdAndName(); + + Q3DSGraphObject *obj = presentation->newObject<Q3DSGroupNode>(id); + obj->setName(QString::fromLatin1(id)); + + syncProperties(); + + return obj; +} + +void Q3DSGroup3D::syncProperties() +{ + Q3DSNode3D::syncProperties(); + + // ### +} + +QT_END_NAMESPACE diff --git a/src/runtime/aping/q3dsgroup3d_p.h b/src/runtime/aping/q3dsgroup3d_p.h new file mode 100644 index 0000000..506b08d --- /dev/null +++ b/src/runtime/aping/q3dsgroup3d_p.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL$ +** 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 or (at your option) 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.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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3DSGROUP3D_P_H +#define Q3DSGROUP3D_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "q3dsnode3d_p.h" + +QT_BEGIN_NAMESPACE + +class Q3DSV_PRIVATE_EXPORT Q3DSGroup3D : public Q3DSNode3D +{ + Q_OBJECT + +public: + Q3DSGroup3D(); + ~Q3DSGroup3D(); + + Q3DSGroupNode *groupNode() const { return static_cast<Q3DSGroupNode *>(object()); } + + Q3DSGraphObject *createObject(Q3DSUipPresentation *presentation) override; + void syncProperties() override; +}; + +QT_END_NAMESPACE + +#endif // Q3DSGROUP3D_P_H diff --git a/src/runtime/aping/q3dslayer3d.cpp b/src/runtime/aping/q3dslayer3d.cpp index 0e9b285..6abcb20 100644 --- a/src/runtime/aping/q3dslayer3d.cpp +++ b/src/runtime/aping/q3dslayer3d.cpp @@ -28,6 +28,7 @@ ****************************************************************************/ #include "q3dslayer3d_p.h" +#include "q3dsobject3d_p.h" #include "q3dsstudio3dengine_p.h" #include "q3dsuippresentation_p.h" #include <QImage> @@ -71,23 +72,38 @@ Q3DSLayer3D::Q3DSLayer3D(QQuickItem *parent) setFlag(QQuickItem::ItemHasContents, true); } +// The concept of slides is not exposed in the API. All objects are +// automatically added to an implicit slide. +Q3DSSlide *Q3DSLayer3D::slide() const +{ + return static_cast<Q3DSSlide *>(m_presentation->masterSlide()->firstChild()); +} + Q3DSLayer3D::~Q3DSLayer3D() { if (m_engine) m_engine->unregisterView(this); + // Null out m_layer to prevent children from choking when ~QQuickItem + // generates a parent-is-null change after we return from here. In + // addition, null out m_object as well recursively since the whole + // Q3DSGraphObject tree under m_layer3DS is going to be destroyed. + for (QQuickItem *item : childItems()) { + Q3DSObject3D *child = qobject_cast<Q3DSObject3D *>(item); + if (child) + child->invalidate(Q3DSObject3D::InvalidateLayer | Q3DSObject3D::InvalidateObject); + } + if (m_presentation) { - Q3DSSlide *slide = static_cast<Q3DSSlide *>(m_presentation->masterSlide()->firstChild()); - Q3DSUipPresentation::forAllObjectsInSubTree(m_layer3DS, [slide](Q3DSGraphObject *obj) { - slide->removeObject(obj); + Q3DSUipPresentation::forAllObjectsInSubTree(m_layer3DS, [this](Q3DSGraphObject *obj) { + slide()->removeObject(obj); }); // going away for good, so removeChildNode + unregister id m_presentation->unlinkObject(m_layer3DS); - delete m_layer3DS; + delete m_layer3DS; // also destroys all its children } delete m_textureProvider; - delete m_dummyTexture; } // Relying on updatePaintNode to keep the SG node up-to-date is not feasable @@ -118,6 +134,7 @@ QSGNode *Q3DSLayer3D::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNode n->setRect(0, 0, dummySize.width(), dummySize.height()); n->setSourceRect(0, 0, dummySize.width(), dummySize.height()); n->setTexture(m_dummyTexture); + n->setOwnsTexture(true); } m_node = n; @@ -161,7 +178,7 @@ QObject *Q3DSLayer3D::engine() const void Q3DSLayer3D::setEngine(QObject *e) { Q3DSStudio3DEngine *item = qobject_cast<Q3DSStudio3DEngine *>(e); - if (!item) { + if (e && !item) { qWarning() << e << "is not a Studio3DEngine"; return; } @@ -177,13 +194,16 @@ void Q3DSLayer3D::setEngine(QObject *e) if (m_engine) { m_engine->registerView(this); - sync(); + connect(m_engine, &QObject::destroyed, this, [this](QObject *obj) { + if (obj == m_engine) { + m_engine = nullptr; + m_presentation = nullptr; + m_layer3DS = nullptr; + } + }); } - connect(m_engine, &QObject::destroyed, this, [this](QObject *obj) { - if (obj == m_engine) - m_engine = nullptr; - }); + sync(); emit engineChanged(); if (window()) @@ -198,27 +218,11 @@ static QByteArray makeIdAndName(Q3DSLayer3D *obj) return id; } -static void addTestContent(Q3DSUipPresentation *pres, Q3DSGraphObject *parent) -{ - Q3DSCameraNode *camera1 = pres->newObject<Q3DSCameraNode>(parent->id() + QByteArrayLiteral("_cam")); - parent->appendChildNode(camera1); - - Q3DSLightNode *light1 = pres->newObject<Q3DSLightNode>(parent->id() + QByteArrayLiteral("_light")); - parent->appendChildNode(light1); - - Q3DSModelNode *model1 = pres->newObject<Q3DSModelNode>(parent->id() + QByteArrayLiteral("_model")); - model1->setMesh(QLatin1String("#Sphere")); - parent->appendChildNode(model1); - - Q3DSDefaultMaterial *mat1 = pres->newObject<Q3DSDefaultMaterial>(parent->id() + QByteArrayLiteral("_mat")); - const QColor c = QColor::fromRgbF((qrand() % 256) / 255.0f, (qrand() % 256) / 255.0f, (qrand() % 256) / 255.0f); - mat1->setDiffuse(c); - model1->appendChildNode(mat1); -} - void Q3DSLayer3D::sync() { if (!m_layer3DS) { + m_engineChanged = false; + if (!m_engine) return; @@ -231,54 +235,52 @@ void Q3DSLayer3D::sync() m_layer3DS = m_presentation->newObject<Q3DSLayerNode>(id); m_layer3DS->setName(QString::fromLatin1(id)); - // communicate the explicit size of the layer as early as possible in - // order to avoid creating textures with a dummy initial size + // Communicate the explicit size of the layer as early as possible in + // order to avoid creating textures with a dummy initial size. if (window()) { const QSize sz = size().toSize() * window()->effectiveDevicePixelRatio(); m_engine->handleViewGeometryChange(this, sz); } - Q3DSSlide *slide = static_cast<Q3DSSlide *>(m_presentation->masterSlide()->firstChild()); + syncProperties(); - // ### to be removed - addTestContent(m_presentation, m_layer3DS); + // Notify the waiting Object3D, if there is one, to create its subtree + // and parent it to our m_layer3DS. + emit layerNodeCreated(); - Q3DSUipPresentation::forAllObjectsInSubTree(m_layer3DS, [slide](Q3DSGraphObject *obj) { - slide->addObject(obj); - }); + slide()->addObject(m_layer3DS); + // The children of m_layer3DS are assumed to be already added to the + // slide, that is the responsibility of the layerNodeCreated handler in + // Q3DSObject3D on this path. - // the order of Layer nodes in the Q3DSGraphObject tree does not matter, - // it does not have to match the Layer3D order in the QQuickItem tree + // The order of Layer nodes in the Q3DSGraphObject tree does not matter, + // it does not have to match the Layer3D order in the QQuickItem tree. + // The moment of truth. This is when things become alive. m_presentation->scene()->appendChildNode(m_layer3DS); + } else if (m_engineChanged) { m_engineChanged = false; - } else { - if (m_engineChanged) { - if (m_presentation) { - Q3DSSlide *slide = static_cast<Q3DSSlide *>(m_presentation->masterSlide()->firstChild()); - Q3DSUipPresentation::forAllObjectsInSubTree(m_layer3DS, [slide](Q3DSGraphObject *obj) { - slide->removeObject(obj); - }); - m_presentation->scene()->removeChildNode(m_layer3DS); - } - if (!m_engine) { - m_presentation = nullptr; - return; - } + if (m_presentation) { + Q3DSUipPresentation::forAllObjectsInSubTree(m_layer3DS, [this](Q3DSGraphObject *obj) { + slide()->removeObject(obj); + }); + m_presentation->scene()->removeChildNode(m_layer3DS); + } - m_presentation = m_engine->presentation(); - if (!m_presentation) - return; + if (!m_engine) { + m_presentation = nullptr; + // engine was set to null, keep the layer object around since + // we may get associated with the same or another engine later on + return; + } - Q3DSSlide *slide = static_cast<Q3DSSlide *>(m_presentation->masterSlide()->firstChild()); - Q3DSUipPresentation::forAllObjectsInSubTree(m_layer3DS, [slide](Q3DSGraphObject *obj) { - slide->addObject(obj); + m_presentation = m_engine->presentation(); + if (m_presentation) { + Q3DSUipPresentation::forAllObjectsInSubTree(m_layer3DS, [this](Q3DSGraphObject *obj) { + slide()->addObject(obj); }); - m_presentation->scene()->appendChildNode(m_layer3DS); - - m_engineChanged = false; } } @@ -308,11 +310,6 @@ QSGTextureProvider *Q3DSLayer3D::textureProvider() const return m_textureProvider; } -bool Q3DSLayer3D::nodeHasDummyTexture() const -{ - return m_node && m_node->texture() == m_dummyTexture; -} - void Q3DSLayer3D::notifyTextureChange(QSGTexture *t) { // render thread @@ -323,4 +320,9 @@ void Q3DSLayer3D::notifyTextureChange(QSGTexture *t) m_lastTexture = t; } +void Q3DSLayer3D::syncProperties() +{ + // ### +} + QT_END_NAMESPACE diff --git a/src/runtime/aping/q3dslayer3d_p.h b/src/runtime/aping/q3dslayer3d_p.h index 0e11f86..64ac69c 100644 --- a/src/runtime/aping/q3dslayer3d_p.h +++ b/src/runtime/aping/q3dslayer3d_p.h @@ -41,7 +41,7 @@ // We mean it. // -#include <Qt3DStudioRuntime2/q3dsruntimeglobal.h> +#include <Qt3DStudioRuntime2/private/q3dsruntimeglobal_p.h> #include <QQuickItem> QT_BEGIN_NAMESPACE @@ -50,10 +50,11 @@ class Q3DSStudio3DEngine; class Q3DSLayerTextureProvider; class Q3DSLayerNode; class Q3DSUipPresentation; +class Q3DSSlide; class QSGTexture; class QSGImageNode; -class Q3DSV_EXPORT Q3DSLayer3D : public QQuickItem +class Q3DSV_PRIVATE_EXPORT Q3DSLayer3D : public QQuickItem { Q_OBJECT Q_PROPERTY(QObject *engine READ engine WRITE setEngine NOTIFY engineChanged) @@ -69,13 +70,19 @@ public: QSGTextureProvider *textureProvider() const override; QSGImageNode *node() const { return m_node; } - bool nodeHasDummyTexture() const; void notifyTextureChange(QSGTexture *t); - Q3DSLayerNode *layer() const { return m_layer3DS; } + Q3DSUipPresentation *presentation() const { return m_presentation; } + Q3DSSlide *slide() const; + Q3DSLayerNode *layerNode() const { return m_layer3DS; } + + bool isLive() const { return layerNode() && presentation(); } + + void syncProperties(); signals: void engineChanged(); + void layerNodeCreated(); private: QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *nodeData) override; diff --git a/src/runtime/aping/q3dsnode3d.cpp b/src/runtime/aping/q3dsnode3d.cpp new file mode 100644 index 0000000..58873c3 --- /dev/null +++ b/src/runtime/aping/q3dsnode3d.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL$ +** 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 or (at your option) 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.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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3dsnode3d_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Node3D + \instantiates Q3DSNode3D + \inqmlmodule QtStudio3D + \ingroup 3dstudioruntime2 + \inherits Object3D + \since Qt 3D Studio 2.1 + + \internal + */ + +Q3DSNode3D::Q3DSNode3D() +{ +} + +Q3DSNode3D::~Q3DSNode3D() +{ +} + +void Q3DSNode3D::syncProperties() +{ + Q3DSObject3D::syncProperties(); + + // ### +} + +QT_END_NAMESPACE diff --git a/src/runtime/aping/q3dsnode3d_p.h b/src/runtime/aping/q3dsnode3d_p.h new file mode 100644 index 0000000..9ec5d77 --- /dev/null +++ b/src/runtime/aping/q3dsnode3d_p.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL$ +** 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 or (at your option) 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.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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3DSNODE3D_P_H +#define Q3DSNODE3D_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "q3dsobject3d_p.h" + +QT_BEGIN_NAMESPACE + +class Q3DSV_PRIVATE_EXPORT Q3DSNode3D : public Q3DSObject3D +{ + Q_OBJECT + +public: + Q3DSNode3D(); + ~Q3DSNode3D(); + + Q3DSNode *node() const { return static_cast<Q3DSNode *>(object()); } + + void syncProperties() override; +}; + +QT_END_NAMESPACE + +#endif // Q3DSNODE3D_P_H diff --git a/src/runtime/aping/q3dsobject3d.cpp b/src/runtime/aping/q3dsobject3d.cpp new file mode 100644 index 0000000..28fe872 --- /dev/null +++ b/src/runtime/aping/q3dsobject3d.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL$ +** 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 or (at your option) 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.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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3dsobject3d_p.h" +#include "q3dslayer3d_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Object3D + \instantiates Q3DSObject3D + \inqmlmodule QtStudio3D + \ingroup 3dstudioruntime2 + \inherits Object + \since Qt 3D Studio 2.1 + + \internal + */ + +// No parent parameter here. QML does not need it and not offering it helps +// autotests avoiding the trap of overridden-virtuals-not-called-from-ctor +// problems (like that we lose the ItemParentHasChanged because our +// itemChange() is not getting called) +Q3DSObject3D::Q3DSObject3D() +{ + setFlag(ItemHasContents, false); +} + +Q3DSObject3D::~Q3DSObject3D() +{ + // When the object is not attached to a parent, we own it, + // otherwise the parent takes care of destroying it. + if (m_object && !m_object->parent()) + delete m_object; +} + +// Deferred fun: Creating the underlying Q3DSGraphObject is not posssible +// without the presentation. The presentation comes from the engine via a +// Layer3D which we do not know until we or an ancestor gets parented to one. + +void Q3DSObject3D::itemChange(ItemChange change, const ItemChangeData &changeData) +{ + switch (change) { + case ItemParentHasChanged: + // m_layer == null && newParent == null means we are in the Layer3D's + // ~QQuickItem and there is nothing to do + if (m_object && m_object->parent() && m_layer) { + Q3DSUipPresentation::forAllObjectsInSubTree(m_object, [this](Q3DSGraphObject *obj) { + m_layer->slide()->removeObject(obj); + }); + m_object->parent()->removeChildNode(m_object); + invalidate(InvalidateLayer); + } + if (changeData.item) { + // cancel outstanding operations + m_pendingObjectParent = nullptr; + if (m_hasPendingActivate) { + // in case our parent changes twice before the Layer3D gets added to a window + m_hasPendingActivate = false; + disconnect(m_layerConn); + } + // see if we are parented to a Layer3D or an Object3D + Q3DSLayer3D *layer3D = qobject_cast<Q3DSLayer3D *>(changeData.item); + if (layer3D) { + // if the layer has a backing Q3DSLayerNode and is attached to + // a Studio3DEngine then we can proceed right away + if (layer3D->isLive()) { + m_layer = layer3D; + activate(); + } else { + // defer otherwise + m_hasPendingActivate = true; + m_layerConn = connect(layer3D, &Q3DSLayer3D::layerNodeCreated, this, [this, layer3D] { + m_layer = layer3D; + activate(); + }); + } + } else { + Q3DSObject3D *obj3D = qobject_cast<Q3DSObject3D *>(changeData.item); + if (obj3D) { + // if the backing Q3DSGraphObject is present, check if the + // same is true for the new parent and if it is associated + // with a Layer3D that is attached to a Studio3DEngine + // since things are simple then + if (m_object && obj3D->object() && obj3D->layer() && obj3D->layer()->isLive()) { + // there will not be an activate() so do everything needed right here + setLayer_recursive(obj3D->layer()); + Q3DSUipPresentation::forAllObjectsInSubTree(m_object, [this](Q3DSGraphObject *obj) { + m_layer->slide()->addObject(obj); + }); + obj3D->object()->appendChildNode(m_object); + m_pendingObjectParent = nullptr; + } else { + // defer otherwise, there will be an activate() eventually (or not but that's fine too) + m_pendingObjectParent = obj3D; + } + } + } + } + break; + + default: + break; + } +} + +void Q3DSObject3D::activate_helper(Q3DSLayer3D *layer) +{ + m_layer = layer; + + // m_object may or may not be null. + // + // When this Object3D has never been associated with a subtree belonging to + // a Layer3D attached to a window, then m_object is still null. + // + // Otherwise m_object is valid, that is the case where a fully initialized + // Object3D with an existing underlying Q3DSGraphObject gets moved out from + // under a Layer3D, and then later gets added to another one. + + if (!m_object) { + m_object = createObject(m_layer->presentation()); + if (!m_object) + return; + } + + m_layer->slide()->addObject(m_object); + + if (m_pendingObjectParent) { + Q_ASSERT(m_pendingObjectParent->object()); + m_pendingObjectParent->object()->appendChildNode(m_object); + m_pendingObjectParent = nullptr; + } + + for (QQuickItem *item : childItems()) { + Q3DSObject3D *child = qobject_cast<Q3DSObject3D *>(item); + if (child) + child->activate_helper(m_layer); + } +} + +void Q3DSObject3D::activate() +{ + Q_ASSERT(!m_pendingObjectParent); // this must be an Object3D parented to a Layer3D + Q_ASSERT(m_layer && m_layer->isLive()); + + m_hasPendingActivate = false; + + activate_helper(m_layer); + if (!m_object) // can happen only if the subclass tries a non-unique id, handle it gracefully nonetheless + return; + + // Connect the object and its children to the Q3DSLayerNode. If that is + // parented to a scene then the subtree becomes alive now. + m_layer->layerNode()->appendChildNode(m_object); +} + +void Q3DSObject3D::setLayer_recursive(Q3DSLayer3D *layer) +{ + m_layer = layer; + + for (QQuickItem *item : childItems()) { + Q3DSObject3D *child = qobject_cast<Q3DSObject3D *>(item); + if (child) + child->setLayer_recursive(layer); + } +} + +void Q3DSObject3D::invalidate(InvalidateFlags flags) +{ + if (flags.testFlag(InvalidateLayer)) + m_layer = nullptr; + + if (flags.testFlag(InvalidateObject)) + m_object = nullptr; + + for (QQuickItem *item : childItems()) { + Q3DSObject3D *child = qobject_cast<Q3DSObject3D *>(item); + if (child) + child->invalidate(flags); + } +} + +QByteArray Q3DSObject3D::makeIdAndName() const +{ + QByteArray id = metaObject()->className(); + id += QByteArrayLiteral("_0x"); + id += QByteArray::number((quintptr) this, 16); + return id; +} + +void Q3DSObject3D::syncProperties() +{ + // ### +} + +QT_END_NAMESPACE diff --git a/src/runtime/aping/q3dsobject3d_p.h b/src/runtime/aping/q3dsobject3d_p.h new file mode 100644 index 0000000..e588fdc --- /dev/null +++ b/src/runtime/aping/q3dsobject3d_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL$ +** 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 or (at your option) 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.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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3DSOBJECT3D_P_H +#define Q3DSOBJECT3D_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <Qt3DStudioRuntime2/private/q3dsruntimeglobal_p.h> +#include <QQuickItem> +#include <private/q3dsuippresentation_p.h> + +QT_BEGIN_NAMESPACE + +class Q3DSLayer3D; + +class Q3DSV_PRIVATE_EXPORT Q3DSObject3D : public QQuickItem +{ + Q_OBJECT + +public: + Q3DSObject3D(); + ~Q3DSObject3D(); + + virtual Q3DSGraphObject *createObject(Q3DSUipPresentation *presentation) = 0; + virtual void syncProperties(); + + Q3DSGraphObject *object() const { return m_object; } + Q3DSLayer3D *layer() const { return m_layer; } + + enum InvalidateFlag { + InvalidateObject = 0x01, + InvalidateLayer = 0x02 + }; + Q_DECLARE_FLAGS(InvalidateFlags, InvalidateFlag) + + void invalidate(InvalidateFlags flags); + +protected: + void itemChange(ItemChange, const ItemChangeData &) override; + QByteArray makeIdAndName() const; + +private: + void activate(); + void activate_helper(Q3DSLayer3D *layer); + void setLayer_recursive(Q3DSLayer3D *layer); + + Q3DSGraphObject *m_object = nullptr; + Q3DSLayer3D *m_layer = nullptr; + Q3DSObject3D *m_pendingObjectParent = nullptr; + bool m_hasPendingActivate = false; + QMetaObject::Connection m_layerConn; + + friend class Q3DSLayer3D; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Q3DSObject3D::InvalidateFlags) + +QT_END_NAMESPACE + +#endif // Q3DSOBJECT3D_P_H diff --git a/src/runtime/aping/q3dsstudio3dengine.cpp b/src/runtime/aping/q3dsstudio3dengine.cpp index 3635c5c..8b24add 100644 --- a/src/runtime/aping/q3dsstudio3dengine.cpp +++ b/src/runtime/aping/q3dsstudio3dengine.cpp @@ -342,7 +342,7 @@ void Q3DSStudio3DEngine::updateViews() QVector<Renderer::Layer> layers; // Gather the layer names (or ids) that need to be exposed. for (Q3DSLayer3D *layerItem : m_views) { - Q3DSLayerNode *layer3DS = layerItem->layer(); + Q3DSLayerNode *layer3DS = layerItem->layerNode(); if (!layer3DS) continue; Qt3DCore::QNodeId nodeId = m_engine->layerTextureNodeId(layer3DS); @@ -413,7 +413,9 @@ Q3DSStudio3DEngine::Renderer::~Renderer() void Q3DSStudio3DEngine::Renderer::invalidateEngine() { - m_engineInvalid.store(1); + Q_ASSERT(thread() != m_engine->thread() && thread() != QThread::currentThread()); + QMutexLocker lock(&m_engineInvalidLock); + m_engineInvalid = true; } void Q3DSStudio3DEngine::Renderer::notifyEngineStart() @@ -424,7 +426,8 @@ void Q3DSStudio3DEngine::Renderer::notifyEngineStart() void Q3DSStudio3DEngine::Renderer::render() { // m_engine may be destroyed already - if (m_engineInvalid.load()) + QMutexLocker lock(&m_engineInvalidLock); + if (m_engineInvalid) return; // the end is nigh - sit back and relax til the RendererReleaser comes for us QQuickWindow *w = m_engine->window(); @@ -480,12 +483,10 @@ void Q3DSStudio3DEngine::Renderer::render() if (viewNode) { if (uint(viewNode->texture()->textureId()) != id || viewNode->rect().size() != layer.itemSizeWithoutDpr) { QSGTexture *t = w->createTextureFromId(id, layer.pixelSize, QQuickWindow::TextureHasAlphaChannel); + Q_ASSERT(t); t->setFiltering(QSGTexture::Linear); - if (!layer.item->nodeHasDummyTexture()) - delete viewNode->texture(); - - viewNode->setTexture(t); + viewNode->setTexture(t); // owns, so previous texture is destroyed layer.item->notifyTextureChange(t); viewNode->setRect(QRectF(QPointF(0, 0), layer.itemSizeWithoutDpr)); @@ -523,7 +524,7 @@ void Q3DSStudio3DEngine::handleViewGeometryChange(Q3DSLayer3D *view, const QSize if (!m_engine) return; - Q3DSLayerNode *layer3DS = view->layer(); + Q3DSLayerNode *layer3DS = view->layerNode(); if (!layer3DS) return; diff --git a/src/runtime/aping/q3dsstudio3dengine_p.h b/src/runtime/aping/q3dsstudio3dengine_p.h index 66ff157..70c7a7a 100644 --- a/src/runtime/aping/q3dsstudio3dengine_p.h +++ b/src/runtime/aping/q3dsstudio3dengine_p.h @@ -42,6 +42,7 @@ // #include <Qt3DStudioRuntime2/private/q3dsruntimeglobal_p.h> +#include <QMutex> #include <QQuickItem> #include <Qt3DCore/QNodeId> @@ -115,7 +116,8 @@ private: void render(); Q3DSStudio3DEngine *m_engine; - QAtomicInt m_engineInvalid; + bool m_engineInvalid = false; + QMutex m_engineInvalidLock; QAtomicInt m_engineStarted; Qt3DCore::QAspectEngine *m_aspectEngine; Qt3DRender::QRenderAspect *m_renderAspect = nullptr; diff --git a/src/runtime/profileui/q3dsprofileui.cpp b/src/runtime/profileui/q3dsprofileui.cpp index 331966b..8d347b1 100644 --- a/src/runtime/profileui/q3dsprofileui.cpp +++ b/src/runtime/profileui/q3dsprofileui.cpp @@ -1108,7 +1108,7 @@ Q3DSProfileUi::Q3DSProfileUi(Q3DSGuiData *guiData, Q3DSProfiler *profiler, Conso m_guiItem = static_cast<Q3DSImGuiItem *>(m_data->imguiQuickItem); if (m_guiItem->isVisible()) setVisible(true); - QObject::connect(m_guiItem, &QQuickItem::visibleChanged, [this] { + m_itemVisibleConn = QObject::connect(m_guiItem, &QQuickItem::visibleChanged, [this] { setVisible(m_guiItem->isVisible()); }); return; @@ -1129,6 +1129,9 @@ Q3DSProfileUi::Q3DSProfileUi(Q3DSGuiData *guiData, Q3DSProfiler *profiler, Conso Q3DSProfileUi::~Q3DSProfileUi() { + if (m_itemVisibleConn) + QObject::disconnect(m_itemVisibleConn); + delete m_guiMgr; delete m_view; } diff --git a/src/runtime/profileui/q3dsprofileui_p.h b/src/runtime/profileui/q3dsprofileui_p.h index 4b1da8f..6e9abf2 100644 --- a/src/runtime/profileui/q3dsprofileui_p.h +++ b/src/runtime/profileui/q3dsprofileui_p.h @@ -77,6 +77,7 @@ private: bool m_visible = false; bool m_hadDialogsEnabled = false; QMetaObject::Connection m_itemFrameConn; + QMetaObject::Connection m_itemVisibleConn; }; QT_END_NAMESPACE diff --git a/src/runtime/q3dsinputmanager_p.h b/src/runtime/q3dsinputmanager_p.h index 08a0e31..e7c8fee 100644 --- a/src/runtime/q3dsinputmanager_p.h +++ b/src/runtime/q3dsinputmanager_p.h @@ -102,9 +102,9 @@ private: PickRequest(Q3DSLayerNode *layer3DS_, const QPoint& pos_, const InputState &inputState_) : layer3DS(layer3DS_), pos(pos_), inputState(inputState_) { } + Q3DSLayerNode *layer3DS = nullptr; // set only when the layer to pick from is known in advance QPoint pos; InputState inputState; - Q3DSLayerNode *layer3DS = nullptr; // set only when the layer to pick from is known in advance }; QQueue<PickRequest> m_pickRequests; diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 0d36323..3a06f0e 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -15,6 +15,10 @@ SUBDIRS += \ surfaceviewer \ q3dslancelot -qtHaveModule(quick): SUBDIRS += studio3d +qtHaveModule(quick) { + SUBDIRS += \ + studio3d \ + studio3dengine +} qtHaveModule(widgets): SUBDIRS += widget diff --git a/tests/auto/studio3dengine/data/simple.qml b/tests/auto/studio3dengine/data/simple.qml new file mode 100644 index 0000000..20a6c67 --- /dev/null +++ b/tests/auto/studio3dengine/data/simple.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 +import QtStudio3D 2.1 + +Rectangle { + id: root + color: "lightGray" + + Studio3DEngine { + id: s3d + } + + Layer3D { + engine: s3d + width: 200 + height: 200 + + Group3D { + Group3D { + } + } + } +} diff --git a/tests/auto/studio3dengine/studio3dengine.pro b/tests/auto/studio3dengine/studio3dengine.pro new file mode 100644 index 0000000..5f06355 --- /dev/null +++ b/tests/auto/studio3dengine/studio3dengine.pro @@ -0,0 +1,8 @@ +TARGET = tst_q3dsstudio3dengine +CONFIG += testcase + +QT += qml quick testlib 3dstudioruntime2-private + +SOURCES += tst_studio3dengine.cpp + +RESOURCES += studio3dengine.qrc diff --git a/tests/auto/studio3dengine/studio3dengine.qrc b/tests/auto/studio3dengine/studio3dengine.qrc new file mode 100644 index 0000000..c371efb --- /dev/null +++ b/tests/auto/studio3dengine/studio3dengine.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>data/simple.qml</file> + </qresource> +</RCC> diff --git a/tests/auto/studio3dengine/tst_studio3dengine.cpp b/tests/auto/studio3dengine/tst_studio3dengine.cpp new file mode 100644 index 0000000..4694e81 --- /dev/null +++ b/tests/auto/studio3dengine/tst_studio3dengine.cpp @@ -0,0 +1,334 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite 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 <QtTest/qtest.h> +#include <QtTest/qsignalspy.h> + +#include <QtQuick/qquickitem.h> +#include <QtQuick/qquickview.h> +#include <QtCore/qurl.h> + +#include <private/q3dsuippresentation_p.h> +#include <private/q3dslayer3d_p.h> +#include <private/q3dsgroup3d_p.h> + +#include "../shared/shared.h" + +class tst_Studio3DEngine : public QObject +{ + Q_OBJECT + +public: + tst_Studio3DEngine(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void testSimple(); + void testReparent(); + void testReparentSubTree(); + void testEngineChangeToNullAndBack(); + void testLayerParentNullAndBack(); +}; + +tst_Studio3DEngine::tst_Studio3DEngine() +{ +} + +void tst_Studio3DEngine::initTestCase() +{ + if (!isOpenGLGoodEnough()) + QSKIP("This platform does not support OpenGL proper"); + + QSurfaceFormat::setDefaultFormat(Q3DS::surfaceFormat()); + + // do not bother disabling dialogs, Studio3DEngine is expected to avoid those implicitly +} + +void tst_Studio3DEngine::cleanupTestCase() +{ +} + +void tst_Studio3DEngine::testSimple() +{ + QQuickView view; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl(QLatin1String("qrc:/data/simple.qml"))); + + QVERIFY(view.rootObject()); + QVERIFY(view.rootObject()->childItems().count() > 0); + + QQuickItem *s3d = view.rootObject()->childItems()[0]; + QVERIFY(s3d); + const QMetaObject *s3dMeta = s3d->metaObject(); + QVERIFY(!strcmp(s3dMeta->className(), "Q3DSStudio3DEngine")); + + view.resize(600, 500); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + if (view.rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) + QSKIP("This test needs Qt Quick on OpenGL"); + + Q3DSLayer3D *layer3d = qobject_cast<Q3DSLayer3D *>(view.rootObject()->childItems()[1]); + QVERIFY(layer3d); + QCOMPARE(layer3d->engine(), s3d); + + Q3DSLayerNode *layer3DS = layer3d->layerNode(); + QVERIFY(layer3DS); + QCOMPARE(layer3DS->explicitSize(), layer3d->size() * view.effectiveDevicePixelRatio()); + + QCOMPARE(layer3d->childItems().count(), 1); + Q3DSGroup3D *group3d_1 = qobject_cast<Q3DSGroup3D *>(layer3d->childItems()[0]); + QVERIFY(group3d_1); + QCOMPARE(group3d_1->layer(), layer3d); + + QCOMPARE(group3d_1->object()->type(), Q3DSGraphObject::Group); + Q3DSGroupNode *group3DS_1 = static_cast<Q3DSGroupNode *>(group3d_1->object()); + QVERIFY(group3DS_1); + + QCOMPARE(group3d_1->childItems().count(), 1); + Q3DSGroup3D *group3d_2 = qobject_cast<Q3DSGroup3D *>(group3d_1->childItems()[0]); + QVERIFY(group3d_2); + QCOMPARE(group3d_2->layer(), layer3d); + + QCOMPARE(group3d_2->object()->type(), Q3DSGraphObject::Group); + Q3DSGroupNode *group3DS_2 = static_cast<Q3DSGroupNode *>(group3d_2->object()); + QVERIFY(group3DS_2); + + QCOMPARE(group3DS_2->parent(), group3DS_1); + QCOMPARE(group3DS_1->parent(), layer3DS); + QCOMPARE(layer3DS->parent(), layer3d->presentation()->scene()); + + QVERIFY(layer3d->slide()->parent() == layer3d->presentation()->masterSlide()); + QVERIFY(!layer3d->slide()->nextSibling()); + const QSet<Q3DSGraphObject *> slideObjects = layer3d->slide()->objects(); + QCOMPARE(slideObjects.count(), 3); +} + +void tst_Studio3DEngine::testReparent() +{ + QQuickView view; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl(QLatin1String("qrc:/data/simple.qml"))); + + view.resize(600, 500); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + if (view.rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) + QSKIP("This test needs Qt Quick on OpenGL"); + + Q3DSLayer3D *layer3d = qobject_cast<Q3DSLayer3D *>(view.rootObject()->childItems()[1]); + Q3DSLayerNode *layer3DS = layer3d->layerNode(); + Q3DSGroup3D *group3d_1 = qobject_cast<Q3DSGroup3D *>(layer3d->childItems()[0]); + Q3DSGroupNode *group3DS_1 = static_cast<Q3DSGroupNode *>(group3d_1->object()); + Q3DSGroup3D *group3d_2 = qobject_cast<Q3DSGroup3D *>(group3d_1->childItems()[0]); + Q3DSGroupNode *group3DS_2 = static_cast<Q3DSGroupNode *>(group3d_2->object()); + + // Layer(Group1(Group2)) + QCOMPARE(group3DS_2->parent(), group3DS_1); + QCOMPARE(group3DS_1->parent(), layer3DS); + QCOMPARE(layer3d->slide()->objects().count(), 3); + + // Layer(Group1 Group2) + group3d_2->setParentItem(layer3d); + QCOMPARE(group3DS_2->parent(), layer3DS); + QCOMPARE(group3DS_1->parent(), layer3DS); + QCOMPARE(group3DS_1->nextSibling(), group3DS_2); + QCOMPARE(layer3d->slide()->objects().count(), 3); + + // Layer(Group2 Group1) + group3d_2->setParentItem(nullptr); + QCOMPARE(group3DS_2->parent(), nullptr); + QCOMPARE(group3d_2->layer(), nullptr); + group3d_1->setParentItem(nullptr); + QCOMPARE(group3DS_1->parent(), nullptr); + QCOMPARE(group3d_1->layer(), nullptr); + group3d_2->setParentItem(layer3d); + QCOMPARE(group3DS_2->parent(), layer3DS); + QCOMPARE(group3DS_2->nextSibling(), nullptr); + group3d_1->setParentItem(layer3d); + QCOMPARE(group3DS_1->parent(), layer3DS); + QCOMPARE(group3d_1->layer(), layer3d); + QCOMPARE(group3DS_2->nextSibling(), group3DS_1); + QCOMPARE(group3d_2->layer(), layer3d); + QCOMPARE(layer3d->slide()->objects().count(), 3); + + // Layer(Group1) Group2 + group3d_2->setParentItem(nullptr); + QCOMPARE(group3d_2->object(), group3DS_2); + QCOMPARE(group3DS_2->parent(), nullptr); + QCOMPARE(group3DS_2->nextSibling(), nullptr); + QCOMPARE(group3d_2->layer(), nullptr); + QCOMPARE(group3DS_1->parent(), layer3DS); + QCOMPARE(group3d_1->layer(), layer3d); + QCOMPARE(layer3d->slide()->objects().count(), 2); + + // Layer(Group1(Group2)) + group3d_2->setParentItem(group3d_1); + QCOMPARE(group3d_2->object(), group3DS_2); + QCOMPARE(group3DS_2->parent(), group3DS_1); + QCOMPARE(group3d_2->layer(), layer3d); + QCOMPARE(group3DS_1->parent(), layer3DS); + QCOMPARE(group3d_1->layer(), layer3d); + QCOMPARE(layer3d->slide()->objects().count(), 3); +} + +void tst_Studio3DEngine::testReparentSubTree() +{ + QQuickView view; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl(QLatin1String("qrc:/data/simple.qml"))); + + view.resize(600, 500); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + if (view.rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) + QSKIP("This test needs Qt Quick on OpenGL"); + + Q3DSLayer3D *layer3d = qobject_cast<Q3DSLayer3D *>(view.rootObject()->childItems()[1]); + Q3DSLayerNode *layer3DS = layer3d->layerNode(); + Q3DSGroup3D *group3d_1 = qobject_cast<Q3DSGroup3D *>(layer3d->childItems()[0]); + Q3DSGroupNode *group3DS_1 = static_cast<Q3DSGroupNode *>(group3d_1->object()); + Q3DSGroup3D *group3d_2 = qobject_cast<Q3DSGroup3D *>(group3d_1->childItems()[0]); + Q3DSGroupNode *group3DS_2 = static_cast<Q3DSGroupNode *>(group3d_2->object()); + + // Layer(Group1(Group2)) + QCOMPARE(group3DS_2->parent(), group3DS_1); + QCOMPARE(group3DS_1->parent(), layer3DS); + QCOMPARE(layer3d->slide()->objects().count(), 3); + + // Layer Group1(Group2) + group3d_1->setParentItem(nullptr); + QCOMPARE(group3DS_1->parent(), nullptr); + QCOMPARE(group3d_1->layer(), nullptr); + QCOMPARE(group3DS_2->parent(), group3DS_1); + QCOMPARE(group3d_2->layer(), nullptr); + QCOMPARE(layer3d->slide()->objects().count(), 1); + + // Layer Group1(Group2(Group3)) + // Group3 not activated at this point since the tree it gets added to is not live + Q3DSGroup3D *group3d_3 = new Q3DSGroup3D; + group3d_3->setParentItem(group3d_2); + QCOMPARE(group3d_3->layer(), nullptr); + QCOMPARE(group3d_3->object(), nullptr); + QCOMPARE(layer3d->slide()->objects().count(), 1); + + // Layer(Group1(Group2(Group3))) + group3d_1->setParentItem(layer3d); + QCOMPARE(group3DS_1->parent(), layer3DS); + QCOMPARE(group3DS_2->parent(), group3DS_1); + QCOMPARE(group3d_3->object()->parent(), group3DS_2); + QCOMPARE(group3d_3->layer(), layer3d); + + Q3DSGroupNode *group3DS_3 = group3d_3->groupNode(); + QVERIFY(layer3d->slide()->objects().contains(group3DS_3)); + QCOMPARE(layer3d->slide()->objects().count(), 4); + + // Layer(Group1 Group2(Group3)) + group3d_2->setParentItem(nullptr); + QCOMPARE(group3d_2->object(), group3DS_2); + QCOMPARE(group3DS_2->parent(), nullptr); + QCOMPARE(group3d_2->layer(), nullptr); + QCOMPARE(group3d_3->object(), group3DS_3); + QCOMPARE(group3DS_3->parent(), group3DS_2); + QCOMPARE(group3d_3->layer(), nullptr); + + // Layer(Group1(Group2(Group3)) + group3d_2->setParentItem(group3d_1); + QCOMPARE(group3d_2->object(), group3DS_2); + QCOMPARE(group3DS_2->parent(), group3DS_1); + QCOMPARE(group3d_2->layer(), layer3d); + QCOMPARE(group3d_3->object(), group3DS_3); + QCOMPARE(group3DS_3->parent(), group3DS_2); + QCOMPARE(group3d_3->layer(), layer3d); +} + +void tst_Studio3DEngine::testEngineChangeToNullAndBack() +{ + QQuickView view; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl(QLatin1String("qrc:/data/simple.qml"))); + + view.resize(600, 500); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + if (view.rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) + QSKIP("This test needs Qt Quick on OpenGL"); + + QQuickItem *s3d = view.rootObject()->childItems()[0]; + Q3DSLayer3D *layer3d = qobject_cast<Q3DSLayer3D *>(view.rootObject()->childItems()[1]); + Q3DSLayerNode *layer3DS = layer3d->layerNode(); + + // nulling out the engine is valid, it leads to nulling the presentation + // but keeping all underlying objects alive [NB the engine cannot actually + // change to another Studio3DEngine since object IDs are not transferred + // between presentations] + + QCOMPARE(layer3DS->parent(), layer3d->presentation()->scene()); + layer3d->setEngine(nullptr); + QCOMPARE(layer3d->layerNode(), layer3DS); + QCOMPARE(layer3DS->parent(), nullptr); + QCOMPARE(layer3d->presentation(), nullptr); + + layer3d->setEngine(s3d); + QCOMPARE(layer3d->layerNode(), layer3DS); + QCOMPARE(layer3DS->parent(), layer3d->presentation()->scene()); +} + +void tst_Studio3DEngine::testLayerParentNullAndBack() +{ + QQuickView view; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl(QLatin1String("qrc:/data/simple.qml"))); + + view.resize(600, 500); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + if (view.rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) + QSKIP("This test needs Qt Quick on OpenGL"); + + Q3DSLayer3D *layer3d = qobject_cast<Q3DSLayer3D *>(view.rootObject()->childItems()[1]); + Q3DSLayerNode *layer3DS = layer3d->layerNode(); + + QQuickItem *layerParent = layer3d->parentItem(); + layer3d->setParentItem(nullptr); + QCOMPARE(layer3d->layerNode(), layer3DS); + QVERIFY(layer3d->presentation()); + + layer3d->setParentItem(layerParent); + QCOMPARE(layer3d->layerNode(), layer3DS); +} + +QTEST_MAIN(tst_Studio3DEngine) + +#include "tst_studio3dengine.moc" diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro index 5fe1eb4..6610fe6 100644 --- a/tests/manual/manual.pro +++ b/tests/manual/manual.pro @@ -5,3 +5,7 @@ qtHaveModule(widgets) { qt3dsexplorer \ datamodelgen } +qtHaveModule(quick) { + SUBDIRS += \ + pureqml3d +} diff --git a/examples/3dstudioruntime2/pureqml3d/main.cpp b/tests/manual/pureqml3d/main.cpp index 38f98e0..814a6a0 100644 --- a/examples/3dstudioruntime2/pureqml3d/main.cpp +++ b/tests/manual/pureqml3d/main.cpp @@ -54,6 +54,7 @@ int main(int argc, char *argv[]) { + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); qputenv("QSG_INFO", "1"); QGuiApplication app(argc, argv); @@ -67,7 +68,7 @@ int main(int argc, char *argv[]) viewer.setTitle(QStringLiteral("Qt 3D Studio Pure QML Example")); viewer.setResizeMode(QQuickView::SizeRootObjectToView); - viewer.resize(1280, 720); + viewer.resize(1280, 600); viewer.show(); return app.exec(); diff --git a/examples/3dstudioruntime2/pureqml3d/main.qml b/tests/manual/pureqml3d/main.qml index c747d9f..d858416 100644 --- a/examples/3dstudioruntime2/pureqml3d/main.qml +++ b/tests/manual/pureqml3d/main.qml @@ -75,14 +75,33 @@ Rectangle { Layer3D { engine: s3d anchors.fill: parent + + Group3D { + } } } } } + Button { + anchors.bottom: parent.bottom + text: "Profile UI" + onClicked: prof.visible = !prof.visible + focusPolicy: Qt.NoFocus + } - Studio3DProfiler { + Rectangle { + id: prof + visible: false anchors.fill: parent - focus: true + anchors.margins: 40 + border.color: "green" + border.width: 4 + color: "transparent" + + Studio3DProfiler { + anchors.fill: parent + focus: true + } } } diff --git a/examples/3dstudioruntime2/pureqml3d/pureqml3d.pro b/tests/manual/pureqml3d/pureqml3d.pro index fcc798b..b65b8e1 100644 --- a/examples/3dstudioruntime2/pureqml3d/pureqml3d.pro +++ b/tests/manual/pureqml3d/pureqml3d.pro @@ -1,4 +1,4 @@ -TEMPLATE = app +TARGET = pureqml3d QT += quick 3dstudioruntime2 @@ -9,6 +9,3 @@ RESOURCES += pureqml3d.qrc OTHER_FILES += \ main.qml - -target.path = $$[QT_INSTALL_EXAMPLES]/3dstudioruntime2/$$TARGET -INSTALLS += target diff --git a/examples/3dstudioruntime2/pureqml3d/pureqml3d.qrc b/tests/manual/pureqml3d/pureqml3d.qrc index 5f6483a..5f6483a 100644 --- a/examples/3dstudioruntime2/pureqml3d/pureqml3d.qrc +++ b/tests/manual/pureqml3d/pureqml3d.qrc |