summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf2
-rw-r--r--src/core/qscene.cpp9
-rw-r--r--src/core/services/qdownloadhelperservice.cpp216
-rw-r--r--src/core/services/qdownloadhelperservice_p.h130
-rw-r--r--src/core/services/qdownloadhelperservice_p_p.h76
-rw-r--r--src/core/services/qdownloadnetworkworker.cpp148
-rw-r--r--src/core/services/qdownloadnetworkworker_p.h101
-rw-r--r--src/core/services/qservicelocator.cpp11
-rw-r--r--src/core/services/qservicelocator_p.h3
-rw-r--r--src/core/services/services.pri8
-rw-r--r--src/plugins/sceneparsers/gltf/gltfimporter.cpp4
-rw-r--r--src/plugins/sceneparsers/gltfexport/gltfexporter.cpp3
-rw-r--r--src/render/frontend/qrenderaspect.cpp15
-rw-r--r--src/render/frontend/qrenderaspect_p.h2
-rw-r--r--src/render/geometry/qmesh.cpp159
-rw-r--r--src/render/geometry/qmesh_p.h26
-rw-r--r--src/render/jobs/calcboundingvolumejob.cpp44
-rw-r--r--src/render/texture/gltexture.cpp26
-rw-r--r--src/render/texture/qabstracttexture.cpp5
-rw-r--r--src/render/texture/qabstracttexture_p.h1
-rw-r--r--src/render/texture/qtexture.cpp185
-rw-r--r--src/render/texture/qtexture_p.h28
-rw-r--r--src/render/texture/texture.cpp8
-rw-r--r--src/render/texture/texture_p.h3
-rw-r--r--tests/auto/render/qmesh/tst_qmesh.cpp6
-rw-r--r--tests/manual/custom-mesh-update-data-cpp/main.cpp2
-rw-r--r--tests/manual/downloading/downloading.pro14
-rw-r--r--tests/manual/downloading/downloading.qrc5
-rw-r--r--tests/manual/downloading/main.cpp63
-rw-r--r--tests/manual/downloading/main.qml105
-rw-r--r--tests/manual/manual.pro1
31 files changed, 1275 insertions, 134 deletions
diff --git a/.qmake.conf b/.qmake.conf
index 0dcf6d9b0..138038d54 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -1,3 +1,3 @@
load(qt_build_config)
-MODULE_VERSION = 5.9.1
+MODULE_VERSION = 5.10.0
diff --git a/src/core/qscene.cpp b/src/core/qscene.cpp
index 043b3f11b..b5895c7aa 100644
--- a/src/core/qscene.cpp
+++ b/src/core/qscene.cpp
@@ -222,13 +222,8 @@ bool QScene::hasEntityForComponent(QNodeId componentUuid, QNodeId entityUuid)
{
Q_D(QScene);
QReadLocker lock(&d->m_lock);
- auto it = d->m_componentToEntities.find(componentUuid);
- while (it != d->m_componentToEntities.end() && it.key() == componentUuid) {
- if (it.value() == entityUuid)
- return true;
- ++it;
- }
- return false;
+ const auto range = d->m_componentToEntities.equal_range(componentUuid);
+ return std::find(range.first, range.second, entityUuid) != range.second;
}
QScene::NodePropertyTrackData QScene::lookupNodePropertyTrackData(QNodeId id) const
diff --git a/src/core/services/qdownloadhelperservice.cpp b/src/core/services/qdownloadhelperservice.cpp
new file mode 100644
index 000000000..231e9172b
--- /dev/null
+++ b/src/core/services/qdownloadhelperservice.cpp
@@ -0,0 +1,216 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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 "qdownloadhelperservice_p.h"
+#include "qdownloadnetworkworker_p.h"
+#include <Qt3DCore/QAspectEngine>
+#include <Qt3DCore/private/qabstractserviceprovider_p.h>
+#include <Qt3DCore/private/qaspectengine_p.h>
+#include <Qt3DCore/private/qaspectthread_p.h>
+#include <Qt3DCore/private/qaspectmanager_p.h>
+#include <Qt3DCore/private/qservicelocator_p.h>
+
+#include <QFile>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DCore {
+
+QDownloadRequest::QDownloadRequest(const QUrl &url)
+ : m_url(url)
+ , m_succeeded(false)
+ , m_cancelled(false)
+{
+
+}
+
+QDownloadRequest::~QDownloadRequest()
+{
+
+}
+
+void QDownloadRequest::onDownloaded()
+{
+ // this is called in dl thread. It's an opportunity to do long running tasks
+ // like loading the data into a QImage
+}
+
+
+class Q_AUTOTEST_EXPORT QDownloadHelperServicePrivate : public QAbstractServiceProviderPrivate
+{
+public:
+ explicit QDownloadHelperServicePrivate(const QString &description);
+ ~QDownloadHelperServicePrivate();
+
+ void init();
+ void shutdown();
+ void _q_onRequestCompleted(const QDownloadRequestPtr &request);
+
+ Q_DECLARE_PUBLIC(QDownloadHelperService)
+
+ QThread *m_downloadThread;
+ QDownloadNetworkWorker *m_downloadWorker;
+};
+
+
+QDownloadHelperServicePrivate::QDownloadHelperServicePrivate(const QString &description)
+ : QAbstractServiceProviderPrivate(QServiceLocator::DownloadHelperService, description)
+ , m_downloadThread(nullptr)
+ , m_downloadWorker(nullptr)
+{
+}
+
+QDownloadHelperServicePrivate::~QDownloadHelperServicePrivate()
+{
+}
+
+void QDownloadHelperServicePrivate::init()
+{
+ Q_Q(QDownloadHelperService);
+ m_downloadThread = new QThread(q);
+ m_downloadWorker = new QDownloadNetworkWorker;
+ m_downloadWorker->moveToThread(m_downloadThread);
+ QObject::connect(m_downloadWorker, SIGNAL(requestDownloaded(const Qt3DCore::QDownloadRequestPtr &)),
+ q, SLOT(_q_onRequestCompleted(const Qt3DCore::QDownloadRequestPtr &)));
+ m_downloadThread->start();
+}
+
+void QDownloadHelperServicePrivate::shutdown()
+{
+ m_downloadWorker->cancelAllRequests();
+ m_downloadThread->exit();
+ m_downloadThread->wait();
+ m_downloadWorker->deleteLater();
+}
+
+void QDownloadHelperServicePrivate::_q_onRequestCompleted(const Qt3DCore::QDownloadRequestPtr &request)
+{
+ request->onCompleted();
+}
+
+
+QDownloadHelperService::QDownloadHelperService(const QString &description)
+ : QAbstractServiceProvider(*new QDownloadHelperServicePrivate(description))
+{
+ Q_D(QDownloadHelperService);
+ d->init();
+ qRegisterMetaType<Qt3DCore::QDownloadRequestPtr>();
+}
+
+QDownloadHelperService::~QDownloadHelperService()
+{
+ Q_D(QDownloadHelperService);
+ d->shutdown();
+}
+
+void QDownloadHelperService::submitRequest(const Qt3DCore::QDownloadRequestPtr &request)
+{
+ Q_D(QDownloadHelperService);
+
+ if (isLocal(request->url())) {
+ QFile file(urlToLocalFileOrQrc(request->url()));
+ if (file.open(QIODevice::ReadOnly)) {
+ request->m_data = file.readAll();
+ file.close();
+ request->m_succeeded = true;
+ } else {
+ request->m_succeeded = false;
+ }
+ request->onCompleted();
+ } else {
+ emit d->m_downloadWorker->submitRequest(request);
+ }
+}
+
+void QDownloadHelperService::cancelRequest(const Qt3DCore::QDownloadRequestPtr &request)
+{
+ Q_D(QDownloadHelperService);
+ request->m_cancelled = true;
+ emit d->m_downloadWorker->cancelRequest(request);
+}
+
+void QDownloadHelperService::cancelAllRequests()
+{
+ Q_D(QDownloadHelperService);
+ emit d->m_downloadWorker->cancelAllRequests();
+}
+
+QString QDownloadHelperService::urlToLocalFileOrQrc(const QUrl &url)
+{
+ const QString scheme(url.scheme().toLower());
+ if (scheme == QLatin1String("qrc")) {
+ if (url.authority().isEmpty())
+ return QLatin1Char(':') + url.path();
+ return QString();
+ }
+
+#if defined(Q_OS_ANDROID)
+ if (scheme == QLatin1String("assets")) {
+ if (url.authority().isEmpty())
+ return url.toString();
+ return QString();
+ }
+#endif
+
+ return url.toLocalFile();
+}
+
+QDownloadHelperService *QDownloadHelperService::getService(QAspectEngine *engine)
+{
+ auto enginePrivate = Qt3DCore::QAspectEnginePrivate::get(engine);
+ return enginePrivate->m_aspectThread->aspectManager()->serviceLocator()->downloadHelperService();
+}
+
+bool QDownloadHelperService::isLocal(const QUrl &url)
+{
+ const QString scheme(url.scheme().toLower());
+ if (scheme == QLatin1String("file") || scheme == QLatin1String("qrc"))
+ return true;
+#if defined(Q_OS_ANDROID)
+ if (scheme == QLatin1String("assets"))
+ return true;
+#endif
+ return false;
+}
+
+} // namespace Qt3DCore
+
+QT_END_NAMESPACE
+
+#include "moc_qdownloadhelperservice_p.cpp"
diff --git a/src/core/services/qdownloadhelperservice_p.h b/src/core/services/qdownloadhelperservice_p.h
new file mode 100644
index 000000000..780ed4806
--- /dev/null
+++ b/src/core/services/qdownloadhelperservice_p.h
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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 QT3DCORE_QDOWNLOADHELPERSERVICE_P_H
+#define QT3DCORE_QDOWNLOADHELPERSERVICE_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 <QSharedPointer>
+#include <QObject>
+#include <QUrl>
+
+#include <Qt3DCore/qaspectjob.h>
+#include <Qt3DCore/qt3dcore_global.h>
+#include <Qt3DCore/private/qservicelocator_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QThread;
+class QNetworkAccessManager;
+class QNetworkReply;
+
+namespace Qt3DCore {
+
+class QAspectEngine;
+class QDownloadNetworkWorker;
+class QDownloadHelperService;
+class QDownloadHelperServicePrivate;
+
+class QT3DCORESHARED_EXPORT QDownloadRequest
+{
+public:
+ QDownloadRequest(const QUrl &url);
+ virtual ~QDownloadRequest();
+
+ QUrl url() const { return m_url; }
+ bool succeeded() const { return m_succeeded; }
+ bool cancelled() const { return m_cancelled; }
+
+ virtual void onDownloaded(); // this is called in dl thread
+ virtual void onCompleted() = 0; // this is called in job thread
+
+protected:
+ QUrl m_url;
+ QByteArray m_data;
+
+private:
+ friend class QDownloadNetworkWorker;
+ friend class QDownloadHelperService;
+ bool m_succeeded;
+ bool m_cancelled;
+};
+
+typedef QSharedPointer<QDownloadRequest> QDownloadRequestPtr;
+
+
+class QT3DCORESHARED_EXPORT QDownloadHelperService : public QAbstractServiceProvider
+{
+ Q_OBJECT
+public:
+ explicit QDownloadHelperService(const QString &description = QString());
+ ~QDownloadHelperService();
+
+ void submitRequest(const QDownloadRequestPtr &request);
+ void cancelRequest(const QDownloadRequestPtr &request);
+ void cancelAllRequests();
+
+ static QString urlToLocalFileOrQrc(const QUrl &url);
+ static bool isLocal(const QUrl &url);
+ static QDownloadHelperService *getService(QAspectEngine *engine);
+
+private:
+ Q_DECLARE_PRIVATE(QDownloadHelperService)
+ Q_PRIVATE_SLOT(d_func(), void _q_onRequestCompleted(const Qt3DCore::QDownloadRequestPtr &))
+};
+
+typedef QSharedPointer<QDownloadHelperService> QDownloadHelperServicePtr;
+
+} // namespace Qt3DCore
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(Qt3DCore::QDownloadRequestPtr) // LCOV_EXCL_LINE
+
+#endif // QT3DCORE_QDOWNLOADHELPERSERVICE_P_H
diff --git a/src/core/services/qdownloadhelperservice_p_p.h b/src/core/services/qdownloadhelperservice_p_p.h
new file mode 100644
index 000000000..202ae5236
--- /dev/null
+++ b/src/core/services/qdownloadhelperservice_p_p.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 Paul Lemire
+** 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 QT3DCORE_QDOWNLOADHELPERSERVICE_P_P_H
+#define QT3DCORE_QDOWNLOADHELPERSERVICE_P_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 <QSharedPointer>
+#include <QObject>
+#include <QUrl>
+
+#include <Qt3DCore/qaspectjob.h>
+#include <Qt3DCore/private/qservicelocator_p.h>
+#include <Qt3DCore/private/qdownloadhelperservice_p.h>
+#include <Qt3DCore/private/qabstractserviceprovider_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QThread;
+class QNetworkAccessManager;
+class QNetworkReply;
+
+namespace Qt3DCore {
+
+
+} // namespace Qt3DCore
+
+QT_END_NAMESPACE
+
+#endif // QT3DCORE_QDOWNLOADHELPERSERVICE_P_P_H
diff --git a/src/core/services/qdownloadnetworkworker.cpp b/src/core/services/qdownloadnetworkworker.cpp
new file mode 100644
index 000000000..c728a1779
--- /dev/null
+++ b/src/core/services/qdownloadnetworkworker.cpp
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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 "qdownloadhelperservice_p.h"
+#include "qdownloadnetworkworker_p.h"
+
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QDataStream>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DCore {
+
+QDownloadNetworkWorker::QDownloadNetworkWorker(QObject *parent)
+ : QObject(parent)
+ , m_networkManager(nullptr)
+{
+ connect(this, &QDownloadNetworkWorker::submitRequest,
+ this, &QDownloadNetworkWorker::onRequestSubmited);
+ connect(this, &QDownloadNetworkWorker::cancelRequest,
+ this, &QDownloadNetworkWorker::onRequestCancelled);
+ connect(this, &QDownloadNetworkWorker::cancelAllRequests,
+ this, &QDownloadNetworkWorker::onAllRequestsCancelled);
+}
+
+void QDownloadNetworkWorker::onRequestSubmited(const QDownloadRequestPtr &request)
+{
+ QMutexLocker l(&m_mutex);
+ if (!m_networkManager) {
+ m_networkManager = new QNetworkAccessManager(this);
+ connect(m_networkManager, &QNetworkAccessManager::finished,
+ this, &QDownloadNetworkWorker::onRequestFinished);
+ }
+ auto reply = m_networkManager->get(QNetworkRequest(request->url()));
+ m_requests << QPair<QDownloadRequestPtr, QNetworkReply *>(request, reply);
+ connect(reply, &QNetworkReply::downloadProgress, this, &QDownloadNetworkWorker::onDownloadProgressed);
+}
+
+void QDownloadNetworkWorker::onRequestCancelled(const QDownloadRequestPtr &request)
+{
+ QMutexLocker l(&m_mutex);
+ auto it = std::find_if(m_requests.begin(), m_requests.end(),
+ [request](QPair<QDownloadRequestPtr, QNetworkReply *> e) {
+ return e.first == request;
+ });
+ if (it == m_requests.end())
+ return;
+
+ (*it).first->m_cancelled = true;
+ (*it).second->abort();
+}
+
+void QDownloadNetworkWorker::onAllRequestsCancelled()
+{
+ QMutexLocker l(&m_mutex);
+ for (auto e: qAsConst(m_requests)) {
+ e.first->m_cancelled = true;
+ e.second->abort();
+ }
+ m_requests.clear();
+}
+
+void QDownloadNetworkWorker::onRequestFinished(QNetworkReply *reply)
+{
+ QMutexLocker l(&m_mutex);
+ auto it = std::find_if(m_requests.begin(), m_requests.end(),
+ [reply](QPair<QDownloadRequestPtr, QNetworkReply *> e) {
+ return e.second == reply;
+ });
+ if (it == m_requests.end())
+ return;
+
+ auto request = (*it).first;
+ if (reply->error() == QNetworkReply::NoError) {
+ request->m_succeeded = true;
+ }
+ request->onDownloaded();
+ emit requestDownloaded(request);
+
+ m_requests.erase(it);
+}
+
+void QDownloadNetworkWorker::onDownloadProgressed(qint64 bytesReceived, qint64 bytesTotal)
+{
+ Q_UNUSED(bytesReceived);
+ Q_UNUSED(bytesTotal);
+ // TODO forward progress details somewhere
+
+ QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
+ if (!reply)
+ return;
+
+ QMutexLocker l(&m_mutex);
+ auto it = std::find_if(m_requests.begin(), m_requests.end(),
+ [reply](QPair<QDownloadRequestPtr, QNetworkReply *> e) {
+ return e.second == reply;
+ });
+ if (it == m_requests.end())
+ return;
+
+ auto request = (*it).first;
+ QDataStream stream(&request->m_data, QIODevice::Append);
+ QByteArray data = reply->readAll();
+ stream.writeRawData(data.data(), data.size());
+}
+
+} // namespace Qt3DCore
+
+QT_END_NAMESPACE
+
diff --git a/src/core/services/qdownloadnetworkworker_p.h b/src/core/services/qdownloadnetworkworker_p.h
new file mode 100644
index 000000000..faecbca2f
--- /dev/null
+++ b/src/core/services/qdownloadnetworkworker_p.h
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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 QT3DCORE_QDOWNLOADNETWORKWORKER_P_H
+#define QT3DCORE_QDOWNLOADNETWORKWORKER_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 <QSharedPointer>
+#include <QObject>
+#include <QUrl>
+#include <Qt3DCore/private/qdownloadhelperservice_p.h>
+#include <QMutex>
+
+QT_BEGIN_NAMESPACE
+
+class QThread;
+class QNetworkAccessManager;
+class QNetworkReply;
+
+namespace Qt3DCore {
+
+class QDownloadRequest;
+typedef QSharedPointer<QDownloadRequest> QDownloadRequestPtr;
+
+class QDownloadNetworkWorker : public QObject
+{
+ Q_OBJECT
+public:
+ QDownloadNetworkWorker(QObject *parent = nullptr);
+
+signals:
+ void submitRequest(const Qt3DCore::QDownloadRequestPtr &request);
+ void cancelRequest(const Qt3DCore::QDownloadRequestPtr &request);
+ void cancelAllRequests();
+
+ void requestDownloaded(const Qt3DCore::QDownloadRequestPtr &request);
+
+private Q_SLOTS:
+ void onRequestSubmited(const Qt3DCore::QDownloadRequestPtr &request);
+ void onRequestCancelled(const Qt3DCore::QDownloadRequestPtr &request);
+ void onAllRequestsCancelled();
+ void onRequestFinished(QNetworkReply *reply);
+ void onDownloadProgressed(qint64 bytesReceived, qint64 bytesTotal);
+
+private:
+ QNetworkAccessManager *m_networkManager;
+ QVector< QPair<QDownloadRequestPtr, QNetworkReply *> > m_requests;
+ QMutex m_mutex;
+};
+
+} // namespace Qt3DCore
+
+QT_END_NAMESPACE
+
+#endif // QT3DCORE_QDOWNLOADNETWORKWORKER_P_H
diff --git a/src/core/services/qservicelocator.cpp b/src/core/services/qservicelocator.cpp
index 9d12cfe2c..bdcb4a521 100644
--- a/src/core/services/qservicelocator.cpp
+++ b/src/core/services/qservicelocator.cpp
@@ -43,9 +43,11 @@
#include <Qt3DCore/private/nullservices_p.h>
#include <Qt3DCore/private/qabstractserviceprovider_p.h>
+#include <Qt3DCore/private/qdownloadhelperservice_p.h>
#include <Qt3DCore/private/qeventfilterservice_p.h>
#include <Qt3DCore/private/qtickclockservice_p.h>
+
QT_BEGIN_NAMESPACE
namespace Qt3DCore {
@@ -96,6 +98,7 @@ public:
NullOpenGLInformationService m_nullOpenGLInfo;
QTickClockService m_defaultFrameAdvanceService;
QEventFilterService m_eventFilterService;
+ QDownloadHelperService m_downloadHelperService;
int m_nonNullDefaultServices;
};
@@ -229,6 +232,12 @@ QEventFilterService *QServiceLocator::eventFilterService()
return static_cast<QEventFilterService *>(d->m_services.value(EventFilterService, &d->m_eventFilterService));
}
+QDownloadHelperService *QServiceLocator::downloadHelperService()
+{
+ Q_D(QServiceLocator);
+ return static_cast<QDownloadHelperService *>(d->m_services.value(DownloadHelperService, &d->m_downloadHelperService));
+}
+
/*
\internal
*/
@@ -244,6 +253,8 @@ QAbstractServiceProvider *QServiceLocator::_q_getServiceHelper(int type)
return frameAdvanceService();
case EventFilterService:
return eventFilterService();
+ case DownloadHelperService:
+ return downloadHelperService();
default:
return d->m_services.value(type, nullptr);
}
diff --git a/src/core/services/qservicelocator_p.h b/src/core/services/qservicelocator_p.h
index 1c5db4a5b..9534b33ce 100644
--- a/src/core/services/qservicelocator_p.h
+++ b/src/core/services/qservicelocator_p.h
@@ -84,6 +84,7 @@ class QOpenGLInformationService;
class QSystemInformationService;
class QServiceLocatorPrivate;
class QEventFilterService;
+class QDownloadHelperService;
class QT3DCORESHARED_EXPORT QServiceLocator
{
@@ -97,6 +98,7 @@ public:
CollisionService,
FrameAdvanceService,
EventFilterService,
+ DownloadHelperService,
#if !defined(Q_QDOC)
DefaultServiceCount, // Add additional default services before here
#endif
@@ -120,6 +122,7 @@ public:
QOpenGLInformationService *openGLInformation();
QAbstractFrameAdvanceService *frameAdvanceService();
QEventFilterService *eventFilterService();
+ QDownloadHelperService *downloadHelperService();
private:
Q_DISABLE_COPY(QServiceLocator)
diff --git a/src/core/services/services.pri b/src/core/services/services.pri
index ae0cfd9f8..f311b8329 100644
--- a/src/core/services/services.pri
+++ b/src/core/services/services.pri
@@ -5,7 +5,9 @@ SOURCES += \
$$PWD/qopenglinformationservice.cpp \
$$PWD/qtickclockservice.cpp \
$$PWD/qabstractframeadvanceservice.cpp \
- $$PWD/qeventfilterservice.cpp
+ $$PWD/qeventfilterservice.cpp \
+ $$PWD/qdownloadhelperservice.cpp \
+ $$PWD/qdownloadnetworkworker.cpp
HEADERS += \
$$PWD/qservicelocator_p.h \
@@ -18,6 +20,8 @@ HEADERS += \
$$PWD/qtickclockservice_p.h \
$$PWD/qabstractframeadvanceservice_p.h \
$$PWD/qabstractframeadvanceservice_p_p.h \
- $$PWD/qeventfilterservice_p.h
+ $$PWD/qeventfilterservice_p.h \
+ $$PWD/qdownloadhelperservice_p.h \
+ $$PWD/qdownloadnetworkworker_p.h
INCLUDEPATH += $$PWD
diff --git a/src/plugins/sceneparsers/gltf/gltfimporter.cpp b/src/plugins/sceneparsers/gltf/gltfimporter.cpp
index 6f61d2ed9..78e230969 100644
--- a/src/plugins/sceneparsers/gltf/gltfimporter.cpp
+++ b/src/plugins/sceneparsers/gltf/gltfimporter.cpp
@@ -1072,8 +1072,8 @@ void GLTFImporter::cleanup()
m_shaderPaths.clear();
delete_if_without_parent(m_programs);
m_programs.clear();
- for (auto it = m_techniqueParameters.begin(); it != m_techniqueParameters.end(); ++it)
- delete_if_without_parent(it.value());
+ for (auto params : qAsConst(m_techniqueParameters))
+ delete_if_without_parent(params);
m_techniqueParameters.clear();
delete_if_without_parent(m_techniques);
m_techniques.clear();
diff --git a/src/plugins/sceneparsers/gltfexport/gltfexporter.cpp b/src/plugins/sceneparsers/gltfexport/gltfexporter.cpp
index 4b1d0fb40..7921fce64 100644
--- a/src/plugins/sceneparsers/gltfexport/gltfexporter.cpp
+++ b/src/plugins/sceneparsers/gltfexport/gltfexporter.cpp
@@ -1253,8 +1253,7 @@ bool GLTFExporter::saveScene()
m_obj["meshes"] = meshes;
QJsonObject cameras;
- for (auto it = m_cameraInfo.begin(); it != m_cameraInfo.end(); ++it) {
- const auto &camInfo = it.value();
+ for (auto camInfo : qAsConst(m_cameraInfo)) {
QJsonObject camera;
QJsonObject proj;
proj["znear"] = camInfo.znear;
diff --git a/src/render/frontend/qrenderaspect.cpp b/src/render/frontend/qrenderaspect.cpp
index 1f88cb006..11c03e7db 100644
--- a/src/render/frontend/qrenderaspect.cpp
+++ b/src/render/frontend/qrenderaspect.cpp
@@ -140,6 +140,7 @@
#include <Qt3DCore/qtransform.h>
#include <Qt3DCore/qnode.h>
+#include <Qt3DCore/QAspectEngine>
#include <Qt3DCore/private/qservicelocator_p.h>
#include <QDebug>
@@ -186,6 +187,17 @@ QRenderAspectPrivate::~QRenderAspectPrivate()
m_instances.removeAll(this);
}
+QRenderAspectPrivate *QRenderAspectPrivate::findPrivate(Qt3DCore::QAspectEngine *engine)
+{
+ const QVector<QAbstractAspect*> aspects = engine->aspects();
+ for (QAbstractAspect* aspect : aspects) {
+ QRenderAspect *renderAspect = qobject_cast<QRenderAspect *>(aspect);
+ if (renderAspect)
+ return static_cast<QRenderAspectPrivate *>(renderAspect->d_ptr.data());
+ }
+ return nullptr;
+}
+
/*! \internal */
void QRenderAspectPrivate::registerBackendTypes()
{
@@ -505,7 +517,8 @@ void QRenderAspect::onRegistered()
advanceService);
}
- d->m_renderer->setServices(d->services());
+ if (d->services())
+ d->m_renderer->setServices(d->services());
d->m_initialized = true;
}
diff --git a/src/render/frontend/qrenderaspect_p.h b/src/render/frontend/qrenderaspect_p.h
index 4f9983d32..b8c8538ee 100644
--- a/src/render/frontend/qrenderaspect_p.h
+++ b/src/render/frontend/qrenderaspect_p.h
@@ -83,6 +83,8 @@ public:
Q_DECLARE_PUBLIC(QRenderAspect)
+ static QRenderAspectPrivate* findPrivate(Qt3DCore::QAspectEngine *engine);
+
void registerBackendTypes();
void unregisterBackendTypes();
void loadSceneParsers();
diff --git a/src/render/geometry/qmesh.cpp b/src/render/geometry/qmesh.cpp
index 1b5d02723..40a4c2f52 100644
--- a/src/render/geometry/qmesh.cpp
+++ b/src/render/geometry/qmesh.cpp
@@ -45,11 +45,23 @@
#include <QFile>
#include <QFileInfo>
#include <QScopedPointer>
-#include <Qt3DRender/private/qgeometryloaderinterface_p.h>
+#include <QMimeDatabase>
+#include <QMimeType>
+#include <QtCore/QBuffer>
+#include <Qt3DRender/QRenderAspect>
+#include <Qt3DCore/QAspectEngine>
#include <Qt3DCore/qpropertyupdatedchange.h>
+#include <Qt3DCore/private/qscene_p.h>
+#include <Qt3DCore/private/qdownloadhelperservice_p.h>
+#include <Qt3DRender/private/qrenderaspect_p.h>
+#include <Qt3DRender/private/nodemanagers_p.h>
+#include <Qt3DRender/private/qgeometryloaderinterface_p.h>
#include <Qt3DRender/private/renderlogging_p.h>
#include <Qt3DRender/private/qurlhelper_p.h>
#include <Qt3DRender/private/qgeometryloaderfactory_p.h>
+#include <Qt3DRender/private/geometryrenderermanager_p.h>
+
+#include <algorithm>
QT_BEGIN_NAMESPACE
@@ -62,6 +74,24 @@ QMeshPrivate::QMeshPrivate()
{
}
+QMeshPrivate *QMeshPrivate::get(QMesh *q)
+{
+ return q->d_func();
+}
+
+void QMeshPrivate::setScene(Qt3DCore::QScene *scene)
+{
+ QGeometryRendererPrivate::setScene(scene);
+ updateFunctor();
+}
+
+void QMeshPrivate::updateFunctor()
+{
+ Q_Q(QMesh);
+ Qt3DCore::QAspectEngine *engine = m_scene ? m_scene->engine() : nullptr;
+ q->setGeometryFactory(QGeometryFactoryPtr(new MeshLoaderFunctor(q, engine)));
+}
+
/*!
* \qmltype Mesh
* \instantiates Qt3DRender::QMesh
@@ -141,8 +171,7 @@ void QMesh::setSource(const QUrl& source)
if (d->m_source == source)
return;
d->m_source = source;
- // update the functor
- QGeometryRenderer::setGeometryFactory(QGeometryFactoryPtr(new MeshFunctor(d->m_source, d->m_meshName)));
+ d->updateFunctor();
const bool blocked = blockNotifications(true);
emit sourceChanged(source);
blockNotifications(blocked);
@@ -165,8 +194,7 @@ void QMesh::setMeshName(const QString &meshName)
if (d->m_meshName == meshName)
return;
d->m_meshName = meshName;
- // update the functor
- QGeometryRenderer::setGeometryFactory(QGeometryFactoryPtr(new MeshFunctor(d->m_source, d->m_meshName)));
+ d->updateFunctor();
const bool blocked = blockNotifications(true);
emit meshNameChanged(meshName);
blockNotifications(blocked);
@@ -186,51 +214,89 @@ QString QMesh::meshName() const
/*!
* \internal
*/
-MeshFunctor::MeshFunctor(const QUrl &sourcePath, const QString& meshName)
+MeshLoaderFunctor::MeshLoaderFunctor(QMesh *mesh, Qt3DCore::QAspectEngine *engine, const QByteArray &sourceData)
: QGeometryFactory()
- , m_sourcePath(sourcePath)
- , m_meshName(meshName)
+ , m_mesh(mesh->id())
+ , m_sourcePath(mesh->source())
+ , m_meshName(mesh->meshName())
+ , m_engine(engine)
+ , m_sourceData(sourceData)
{
}
/*!
* \internal
*/
-QGeometry *MeshFunctor::operator()()
+QGeometry *MeshLoaderFunctor::operator()()
{
if (m_sourcePath.isEmpty()) {
qCWarning(Render::Jobs) << Q_FUNC_INFO << "Mesh is empty, nothing to load";
return nullptr;
}
- // TO DO: Handle file download if remote url
- QString filePath = Qt3DRender::QUrlHelper::urlToLocalFileOrQrc(m_sourcePath);
+ QStringList ext;
+ if (!Qt3DCore::QDownloadHelperService::isLocal(m_sourcePath)) {
+ if (m_sourceData.isEmpty()) {
+ if (m_mesh) {
+ auto downloadService = Qt3DCore::QDownloadHelperService::getService(m_engine);
+ Qt3DCore::QDownloadRequestPtr request(new MeshDownloadRequest(m_mesh, m_sourcePath, m_engine));
+ downloadService->submitRequest(request);
+ }
+ return nullptr;
+ }
- QFileInfo finfo(filePath);
- auto ext = finfo.suffix();
- if (ext.isEmpty())
- ext = QLatin1String("obj");
+ QMimeDatabase db;
+ QMimeType mtype = db.mimeTypeForData(m_sourceData);
+ if (mtype.isValid()) {
+ ext = mtype.suffixes();
+ }
+ QFileInfo finfo(m_sourcePath.path());
+ ext << finfo.suffix();
+ ext.removeAll(QLatin1String(""));
+ if (!ext.contains(QLatin1String("obj")))
+ ext << QLatin1String("obj");
+ } else {
+ QString filePath = Qt3DRender::QUrlHelper::urlToLocalFileOrQrc(m_sourcePath);
+ QFileInfo finfo(filePath);
+ if (finfo.suffix().isEmpty())
+ ext << QLatin1String("obj");
+ else
+ ext << finfo.suffix();
+ }
QScopedPointer<QGeometryLoaderInterface> loader;
-
- loader.reset(qLoadPlugin<QGeometryLoaderInterface, QGeometryLoaderFactory>(geometryLoader(), ext));
+ for (QString e: qAsConst(ext)) {
+ loader.reset(qLoadPlugin<QGeometryLoaderInterface, QGeometryLoaderFactory>(geometryLoader(), e));
+ if (loader)
+ break;
+ }
if (!loader) {
- qCWarning(Render::Jobs, "unsupported format encountered (%s)", qPrintable(ext));
+ qCWarning(Render::Jobs, "unsupported format encountered (%s)", qPrintable(ext.join(QLatin1String(", "))));
return nullptr;
}
- QFile file(filePath);
- if (!file.open(QIODevice::ReadOnly)) {
- qCDebug(Render::Jobs) << "Could not open file" << filePath << "for reading";
- return nullptr;
- }
+ if (m_sourceData.isEmpty()) {
+ QString filePath = Qt3DRender::QUrlHelper::urlToLocalFileOrQrc(m_sourcePath);
+ QFile file(filePath);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCDebug(Render::Jobs) << "Could not open file" << filePath << "for reading";
+ return nullptr;
+ }
- qCDebug(Render::Jobs) << Q_FUNC_INFO << "Loading mesh from" << m_sourcePath << " part:" << m_meshName;
+ if (loader->load(&file, m_meshName))
+ return loader->geometry();
+ qCWarning(Render::Jobs) << Q_FUNC_INFO << "Mesh loading failure for:" << filePath;
+ } else {
+ QT_PREPEND_NAMESPACE(QBuffer) buffer(&m_sourceData);
+ if (!buffer.open(QIODevice::ReadOnly)) {
+ return nullptr;
+ }
- if (loader->load(&file, m_meshName))
- return loader->geometry();
+ if (loader->load(&buffer, m_meshName))
+ return loader->geometry();
- qCWarning(Render::Jobs) << Q_FUNC_INFO << "Mesh loading failure for:" << filePath;
+ qCWarning(Render::Jobs) << Q_FUNC_INFO << "Mesh loading failure for:" << m_sourcePath;
+ }
return nullptr;
}
@@ -238,14 +304,47 @@ QGeometry *MeshFunctor::operator()()
/*!
* \internal
*/
-bool MeshFunctor::operator ==(const QGeometryFactory &other) const
+bool MeshLoaderFunctor::operator ==(const QGeometryFactory &other) const
{
- const MeshFunctor *otherFunctor = functor_cast<MeshFunctor>(&other);
+ const MeshLoaderFunctor *otherFunctor = functor_cast<MeshLoaderFunctor>(&other);
if (otherFunctor != nullptr)
- return (otherFunctor->m_sourcePath == m_sourcePath);
+ return (otherFunctor->m_sourcePath == m_sourcePath &&
+ otherFunctor->m_sourceData.isEmpty() == m_sourceData.isEmpty() &&
+ otherFunctor->m_engine == m_engine);
return false;
}
+/*!
+ * \internal
+ */
+MeshDownloadRequest::MeshDownloadRequest(Qt3DCore::QNodeId mesh, QUrl source, Qt3DCore::QAspectEngine *engine)
+ : Qt3DCore::QDownloadRequest(source)
+ , m_mesh(mesh)
+ , m_engine(engine)
+{
+
+}
+
+void MeshDownloadRequest::onCompleted()
+{
+ if (cancelled() || !succeeded())
+ return;
+
+ QRenderAspectPrivate* d_aspect = QRenderAspectPrivate::findPrivate(m_engine);
+ if (!d_aspect)
+ return;
+
+ Render::GeometryRenderer *renderer = d_aspect->m_nodeManagers->geometryRendererManager()->lookupResource(m_mesh);
+ if (!renderer)
+ return;
+
+ QSharedPointer<MeshLoaderFunctor> functor = qSharedPointerCast<MeshLoaderFunctor>(renderer->geometryFactory());
+ functor->m_sourceData = m_data;
+
+ // mark the component as dirty so that the functor runs again in the correct job
+ d_aspect->m_nodeManagers->geometryRendererManager()->addDirtyGeometryRenderer(m_mesh);
+}
+
} // namespace Qt3DRender
QT_END_NAMESPACE
diff --git a/src/render/geometry/qmesh_p.h b/src/render/geometry/qmesh_p.h
index a621525cc..f7f8079eb 100644
--- a/src/render/geometry/qmesh_p.h
+++ b/src/render/geometry/qmesh_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <Qt3DCore/private/qdownloadhelperservice_p.h>
#include <Qt3DRender/private/qgeometryrenderer_p.h>
#include <QUrl>
@@ -66,23 +67,40 @@ public:
QMeshPrivate();
Q_DECLARE_PUBLIC(QMesh)
+ static QMeshPrivate *get(QMesh *q);
+
+ void setScene(Qt3DCore::QScene *scene) override;
+ void updateFunctor();
QUrl m_source;
QString m_meshName;
};
+class Q_AUTOTEST_EXPORT MeshDownloadRequest : public Qt3DCore::QDownloadRequest
+{
+public:
+ MeshDownloadRequest(Qt3DCore::QNodeId mesh, QUrl source, Qt3DCore::QAspectEngine *engine);
+
+ void onCompleted() Q_DECL_OVERRIDE;
-class Q_AUTOTEST_EXPORT MeshFunctor : public QGeometryFactory
+private:
+ Qt3DCore::QNodeId m_mesh;
+ Qt3DCore::QAspectEngine *m_engine;
+};
+
+class Q_AUTOTEST_EXPORT MeshLoaderFunctor : public QGeometryFactory
{
public :
- MeshFunctor(const QUrl &sourcePath, const QString &meshName = QString());
+ MeshLoaderFunctor(QMesh *mesh, Qt3DCore::QAspectEngine *engine, const QByteArray &sourceData = QByteArray());
QGeometry *operator()() Q_DECL_OVERRIDE;
bool operator ==(const QGeometryFactory &other) const Q_DECL_OVERRIDE;
- QT3D_FUNCTOR(MeshFunctor)
+ QT3D_FUNCTOR(MeshLoaderFunctor)
-private:
+ Qt3DCore::QNodeId m_mesh;
QUrl m_sourcePath;
QString m_meshName;
+ Qt3DCore::QAspectEngine *m_engine;
+ QByteArray m_sourceData;
};
diff --git a/src/render/jobs/calcboundingvolumejob.cpp b/src/render/jobs/calcboundingvolumejob.cpp
index 7bbab307c..e81836502 100644
--- a/src/render/jobs/calcboundingvolumejob.cpp
+++ b/src/render/jobs/calcboundingvolumejob.cpp
@@ -216,30 +216,28 @@ void calculateLocalBoundingVolume(NodeManagers *manager, Entity *node)
return;
}
- if (positionAttribute) {
- Buffer *buf = manager->lookupResource<Buffer, BufferManager>(positionAttribute->bufferId());
- // No point in continuing if the positionAttribute doesn't have a suitable buffer
- if (!buf) {
- qWarning() << "ObjectPicker position Attribute not referencing a valid buffer";
- return;
- }
+ Buffer *buf = manager->lookupResource<Buffer, BufferManager>(positionAttribute->bufferId());
+ // No point in continuing if the positionAttribute doesn't have a suitable buffer
+ if (!buf) {
+ qWarning() << "ObjectPicker position Attribute not referencing a valid buffer";
+ return;
+ }
- // Buf will be set to not dirty once it's loaded
- // in a job executed after this one
- // We need to recompute the bounding volume
- // If anything in the GeometryRenderer has changed
- if (buf->isDirty() ||
- node->isBoundingVolumeDirty() ||
- positionAttribute->isDirty() ||
- geom->isDirty() ||
- gRenderer->isDirty()) {
-
- BoundingVolumeCalculator reader(manager);
- if (reader.apply(positionAttribute)) {
- node->localBoundingVolume()->setCenter(reader.result().center());
- node->localBoundingVolume()->setRadius(reader.result().radius());
- node->unsetBoundingVolumeDirty();
- }
+ // Buf will be set to not dirty once it's loaded
+ // in a job executed after this one
+ // We need to recompute the bounding volume
+ // If anything in the GeometryRenderer has changed
+ if (buf->isDirty() ||
+ node->isBoundingVolumeDirty() ||
+ positionAttribute->isDirty() ||
+ geom->isDirty() ||
+ gRenderer->isDirty()) {
+
+ BoundingVolumeCalculator reader(manager);
+ if (reader.apply(positionAttribute)) {
+ node->localBoundingVolume()->setCenter(reader.result().center());
+ node->localBoundingVolume()->setRadius(reader.result().radius());
+ node->unsetBoundingVolumeDirty();
}
}
}
diff --git a/src/render/texture/gltexture.cpp b/src/render/texture/gltexture.cpp
index 606681bd5..854789e94 100644
--- a/src/render/texture/gltexture.cpp
+++ b/src/render/texture/gltexture.cpp
@@ -283,20 +283,24 @@ void GLTexture::setImages(const QVector<Image> &images)
void GLTexture::setGenerator(const QTextureGeneratorPtr &generator)
{
- if (m_dataFunctor != generator) {
- if (m_dataFunctor)
- m_textureDataManager->releaseData(m_dataFunctor, this);
+ // Note: we do not compare if the generator is different
+ // as in some cases we may want to reset the same generator to force a reload
+ // e.g when using remote urls for textures
+ if (m_dataFunctor)
+ m_textureDataManager->releaseData(m_dataFunctor, this);
- m_textureData.reset();
- m_dataFunctor = generator;
+ m_textureData.reset();
+ m_dataFunctor = generator;
- if (m_dataFunctor) {
- m_textureDataManager->requestData(m_dataFunctor, this);
- requestUpload();
- }
+ if (m_dataFunctor) {
+ m_textureDataManager->requestData(m_dataFunctor, this);
+ requestUpload();
}
}
+// Return nullptr if
+// - context cannot be obtained
+// - texture hasn't yet been loaded
QOpenGLTexture *GLTexture::buildGLTexture()
{
QOpenGLContext *ctx = QOpenGLContext::currentContext();
@@ -306,7 +310,9 @@ QOpenGLTexture *GLTexture::buildGLTexture()
}
if (m_actualTarget == QAbstractTexture::TargetAutomatic) {
- qWarning() << Q_FUNC_INFO << "something went wrong, target shouldn't be automatic at this point";
+ // If the target is automatic at this point, it means that the texture
+ // hasn't been loaded yet (case of remote urls) and that loading failed
+ // and that target format couldn't be deduced
return nullptr;
}
diff --git a/src/render/texture/qabstracttexture.cpp b/src/render/texture/qabstracttexture.cpp
index 7703933a4..1bd002104 100644
--- a/src/render/texture/qabstracttexture.cpp
+++ b/src/render/texture/qabstracttexture.cpp
@@ -69,6 +69,11 @@ QAbstractTexturePrivate::QAbstractTexturePrivate()
{
}
+QTextureGeneratorPtr QAbstractTexturePrivate::dataFunctor() const
+{
+ return m_dataFunctor;
+}
+
void QAbstractTexturePrivate::setDataFunctor(const QTextureGeneratorPtr &generator)
{
if (generator != m_dataFunctor) {
diff --git a/src/render/texture/qabstracttexture_p.h b/src/render/texture/qabstracttexture_p.h
index c245a78af..a27ae3729 100644
--- a/src/render/texture/qabstracttexture_p.h
+++ b/src/render/texture/qabstracttexture_p.h
@@ -87,6 +87,7 @@ public :
int m_layers;
int m_samples;
+ QTextureGeneratorPtr dataFunctor() const;
void setDataFunctor(const QTextureGeneratorPtr &generator);
private:
diff --git a/src/render/texture/qtexture.cpp b/src/render/texture/qtexture.cpp
index f75a47f95..931d66c64 100644
--- a/src/render/texture/qtexture.cpp
+++ b/src/render/texture/qtexture.cpp
@@ -44,7 +44,17 @@
#include "qtexture.h"
#include "qtexture_p.h"
#include <QFileInfo>
+#include <QMimeDatabase>
+#include <QMimeType>
#include <qendian.h>
+#include <Qt3DCore/private/qscene_p.h>
+#include <Qt3DCore/qaspectengine.h>
+#include <Qt3DCore/private/qdownloadhelperservice_p.h>
+#include <Qt3DRender/private/qrenderaspect_p.h>
+#include <Qt3DRender/private/nodemanagers_p.h>
+#include <Qt3DRender/private/managers_p.h>
+#include <Qt3DRender/private/texture_p.h>
+#include <Qt3DRender/private/qurlhelper_p.h>
QT_BEGIN_NAMESPACE
@@ -424,9 +434,8 @@ enum CompressedFormatExtension {
PKM
};
-CompressedFormatExtension texturedCompressedFormat(const QString &source)
+CompressedFormatExtension texturedCompressedFormat(const QString &suffix)
{
- const QString suffix = QFileInfo(source).suffix();
if (suffix == QStringLiteral("pkm"))
return PKM;
if (suffix == QStringLiteral("dds"))
@@ -434,19 +443,14 @@ CompressedFormatExtension texturedCompressedFormat(const QString &source)
return None;
}
-QTextureImageDataPtr setPkmFile(const QString &source)
+QTextureImageDataPtr setPkmFile(QIODevice *source)
{
QTextureImageDataPtr imageData;
- QFile f(source);
- if (!f.open(QIODevice::ReadOnly)) {
- qWarning() << "Failed to open" << source;
- return imageData;
- }
- // ETC1 in PKM, as generated by f.ex. Android's etc1tool
+ // ETC1 in PKM, as generated by source->ex. Android's etc1tool
static const char pkmMagic[] = { 'P', 'K', 'M', ' ', '1', '0' };
const int pkmHeaderSize = 6 + 2 + 4 * 2;
- const QByteArray header = f.read(pkmHeaderSize);
+ const QByteArray header = source->read(pkmHeaderSize);
if (header.size() >= pkmHeaderSize && !qstrncmp(header.constData(), pkmMagic, 6)) {
imageData = QTextureImageDataPtr::create();
imageData->setTarget(QOpenGLTexture::Target2D);
@@ -455,7 +459,7 @@ QTextureImageDataPtr setPkmFile(const QString &source)
imageData->setWidth(qFromBigEndian(*(reinterpret_cast<const quint16 *>(header.constData() + 6 + 2))));
imageData->setHeight(qFromBigEndian(*(reinterpret_cast<const quint16 *>(header.constData() + 6 + 2 + 2))));
imageData->setDepth(1);
- const QByteArray data = f.readAll();
+ const QByteArray data = source->readAll();
if (data.size() < (imageData->width() / 4) * (imageData->height() / 4) * 8)
qWarning() << "Unexpected end of ETC1 data in" << source;
const bool isCompressed = true;
@@ -467,17 +471,12 @@ QTextureImageDataPtr setPkmFile(const QString &source)
return imageData;
}
-QTextureImageDataPtr setDdsFile(const QString &source)
+QTextureImageDataPtr setDdsFile(QIODevice *source)
{
QTextureImageDataPtr imageData;
- QFile f(source);
- if (!f.open(QIODevice::ReadOnly)) {
- qWarning() << "Failed to open" << source;
- return imageData;
- }
DdsHeader header;
- if ((f.read(reinterpret_cast<char *>(&header), sizeof header) != sizeof header)
+ if ((source->read(reinterpret_cast<char *>(&header), sizeof header) != sizeof header)
|| (qstrncmp(header.magic, "DDS ", 4) != 0))
return imageData;
@@ -490,7 +489,7 @@ QTextureImageDataPtr setDdsFile(const QString &source)
if (fourCC == DdsFourCC<'D', 'X', '1', '0'>::value) {
// DX10 texture
DdsDX10Header dx10Header;
- if (f.read(reinterpret_cast<char *>(&dx10Header), sizeof dx10Header) != sizeof dx10Header)
+ if (source->read(reinterpret_cast<char *>(&dx10Header), sizeof dx10Header) != sizeof dx10Header)
return imageData;
layers = qFromLittleEndian(dx10Header.arraySize);
@@ -582,13 +581,13 @@ QTextureImageDataPtr setDdsFile(const QString &source)
// data
const int dataSize = layers * layerSize;
- const QByteArray data = f.read(dataSize);
+ const QByteArray data = source->read(dataSize);
if (data.size() < dataSize) {
qWarning() << "Unexpected end of data in" << source;
return imageData;
}
- if (!f.atEnd())
+ if (!source->atEnd())
qWarning() << "Unrecognized data in" << source;
imageData = QTextureImageDataPtr::create();
@@ -626,26 +625,39 @@ QTextureImageDataPtr TextureLoadingHelper::loadTextureData(const QUrl &url, bool
#endif
) {
const QString source = Qt3DRender::QUrlHelper::urlToLocalFileOrQrc(url);
- const CompressedFormatExtension formatExtension = texturedCompressedFormat(source);
- switch (formatExtension) {
- case DDS:
- textureData = setDdsFile(source);
- break;
- case PKM:
- textureData = setPkmFile(source);
- break;
- default:
- QImage img;
- if (img.load(source)) {
- textureData = QTextureImageDataPtr::create();
- textureData->setImage(mirrored ? img.mirrored() : img);
- }
- break;
- }
+ QFile f(source);
+ if (!f.open(QIODevice::ReadOnly))
+ qWarning() << "Failed to open" << source;
+ else
+ textureData = loadTextureData(&f, QFileInfo(source).suffix(), allow3D, mirrored);
+ }
+ return textureData;
+}
- if (!allow3D && textureData && (textureData->layers() > 1 || textureData->depth() > 1))
- qWarning() << "Texture data has a 3rd dimension which wasn't expected";
+QTextureImageDataPtr TextureLoadingHelper::loadTextureData(QIODevice *data, const QString& suffix,
+ bool allow3D, bool mirrored)
+{
+ QTextureImageDataPtr textureData;
+ const CompressedFormatExtension formatExtension = texturedCompressedFormat(suffix);
+ switch (formatExtension) {
+ case DDS:
+ textureData = setDdsFile(data);
+ break;
+ case PKM:
+ textureData = setPkmFile(data);
+ break;
+ default: {
+ QImage img;
+ if (img.load(data, suffix.toLatin1())) {
+ textureData = QTextureImageDataPtr::create();
+ textureData->setImage(mirrored ? img.mirrored() : img);
+ }
+ break;
+ }
}
+
+ if (!allow3D && textureData && (textureData->layers() > 1 || textureData->depth() > 1))
+ qWarning() << "Texture data has a 3rd dimension which wasn't expected";
return textureData;
}
@@ -653,8 +665,43 @@ QTextureDataPtr QTextureFromSourceGenerator::operator ()()
{
QTextureDataPtr generatedData = QTextureDataPtr::create();
m_status = QAbstractTexture::Loading;
+ QTextureImageDataPtr textureData;
+
+ if (!Qt3DCore::QDownloadHelperService::isLocal(m_url)) {
+ if (m_sourceData.isEmpty()) {
+ // first time around, trigger a download
+ if (m_texture) {
+ auto downloadService = Qt3DCore::QDownloadHelperService::getService(m_engine);
+ Qt3DCore::QDownloadRequestPtr request(new TextureDownloadRequest(m_texture, m_url,
+ m_engine));
+ downloadService->submitRequest(request);
+ }
+ return generatedData;
+ }
+
+ // second time around, we have the data
+ QT_PREPEND_NAMESPACE(QBuffer) buffer(&m_sourceData);
+ if (buffer.open(QIODevice::ReadOnly)) {
+ QString suffix = m_url.toString();
+ suffix = suffix.right(suffix.length() - suffix.lastIndexOf('.'));
+
+ QStringList ext(suffix);
- const QTextureImageDataPtr textureData = TextureLoadingHelper::loadTextureData(m_url, true, m_mirrored);
+ QMimeDatabase db;
+ QMimeType mtype = db.mimeTypeForData(m_sourceData);
+ if (mtype.isValid()) {
+ ext << mtype.suffixes();
+ }
+
+ for (QString s: qAsConst(ext)) {
+ textureData = TextureLoadingHelper::loadTextureData(&buffer, suffix, true, m_mirrored);
+ if (textureData && textureData->data().length() > 0)
+ break;
+ }
+ }
+ } else {
+ textureData = TextureLoadingHelper::loadTextureData(m_url, true, m_mirrored);
+ }
if (textureData && textureData->data().length() > 0) {
generatedData->setTarget(static_cast<QAbstractTexture::Target>(textureData->target()));
@@ -672,12 +719,55 @@ QTextureDataPtr QTextureFromSourceGenerator::operator ()()
return generatedData;
}
+TextureDownloadRequest::TextureDownloadRequest(Qt3DCore::QNodeId texture, const QUrl& source,
+ Qt3DCore::QAspectEngine *engine)
+ : Qt3DCore::QDownloadRequest(source)
+ , m_texture(texture)
+ , m_engine(engine)
+{
+
+}
+
+// Executed in download thread
+void TextureDownloadRequest::onCompleted()
+{
+ if (cancelled() || !succeeded())
+ return;
+
+ QRenderAspectPrivate* d_aspect = QRenderAspectPrivate::findPrivate(m_engine);
+ if (!d_aspect)
+ return;
+
+ Render::Texture *texture = d_aspect->m_nodeManagers->textureManager()->lookupResource(m_texture);
+ if (!texture)
+ return;
+
+ QSharedPointer<QTextureFromSourceGenerator> functor =
+ qSharedPointerCast<QTextureFromSourceGenerator>(texture->dataGenerator());
+ functor->m_sourceData = m_data;
+
+ // mark the component as dirty so that the functor runs again in the correct job
+ texture->addDirtyFlag(Render::Texture::DirtyDataGenerator);
+}
+
QTextureLoaderPrivate::QTextureLoaderPrivate()
: QAbstractTexturePrivate()
, m_mirrored(true)
{
}
+void QTextureLoaderPrivate::setScene(Qt3DCore::QScene *scene)
+{
+ QAbstractTexturePrivate::setScene(scene);
+ updateFunctor();
+}
+
+void QTextureLoaderPrivate::updateFunctor()
+{
+ Qt3DCore::QAspectEngine *engine = m_scene ? m_scene->engine() : nullptr;
+ setDataFunctor(QTextureFromSourceGeneratorPtr::create(m_id, m_source, m_mirrored, engine));
+}
+
/*!
\class Qt3DRender::QTexture1D
\inmodule Qt3DRender
@@ -951,7 +1041,7 @@ void QTextureLoader::setSource(const QUrl& source)
Q_D(QTextureLoader);
if (source != d->m_source) {
d->m_source = source;
- d->setDataFunctor(QTextureFromSourceGeneratorPtr::create(d->m_source, d->m_mirrored));
+ d->updateFunctor();
const bool blocked = blockNotifications(true);
emit sourceChanged(source);
blockNotifications(blocked);
@@ -999,7 +1089,7 @@ void QTextureLoader::setMirrored(bool mirrored)
Q_D(QTextureLoader);
if (mirrored != d->m_mirrored) {
d->m_mirrored = mirrored;
- d->setDataFunctor(QTextureFromSourceGeneratorPtr::create(d->m_source, d->m_mirrored));
+ d->updateFunctor();
const bool blocked = blockNotifications(true);
emit mirroredChanged(mirrored);
blockNotifications(blocked);
@@ -1015,7 +1105,8 @@ bool QTextureFromSourceGenerator::operator ==(const QTextureGenerator &other) co
const QTextureFromSourceGenerator *otherFunctor = functor_cast<QTextureFromSourceGenerator>(&other);
return (otherFunctor != nullptr &&
otherFunctor->m_url == m_url &&
- otherFunctor->m_mirrored == m_mirrored);
+ otherFunctor->m_mirrored == m_mirrored &&
+ otherFunctor->m_engine == m_engine);
}
QUrl QTextureFromSourceGenerator::url() const
@@ -1033,16 +1124,18 @@ bool QTextureFromSourceGenerator::isMirrored() const
* instance with \a url.
* \param url
*/
-QTextureFromSourceGenerator::QTextureFromSourceGenerator(const QUrl &url, bool mirrored)
+QTextureFromSourceGenerator::QTextureFromSourceGenerator(Qt3DCore::QNodeId texture,
+ const QUrl &url, bool mirrored,
+ Qt3DCore::QAspectEngine *engine)
: QTextureGenerator()
, m_url(url)
, m_status(QAbstractTexture::None)
, m_mirrored(mirrored)
+ , m_texture(texture)
+ , m_engine(engine)
{
}
} // namespace Qt3DRender
QT_END_NAMESPACE
-
-
diff --git a/src/render/texture/qtexture_p.h b/src/render/texture/qtexture_p.h
index a0ea71a58..4afb14d62 100644
--- a/src/render/texture/qtexture_p.h
+++ b/src/render/texture/qtexture_p.h
@@ -51,8 +51,11 @@
// We mean it.
//
+#include <Qt3DCore/QNodeId>
+#include <Qt3DCore/private/qdownloadhelperservice_p.h>
#include <Qt3DRender/private/qabstracttexture_p.h>
#include <Qt3DRender/qtexturegenerator.h>
+#include <Qt3DRender/qtexture.h>
QT_BEGIN_NAMESPACE
@@ -63,14 +66,30 @@ class QTextureLoaderPrivate : public QAbstractTexturePrivate
public:
QTextureLoaderPrivate();
+ void setScene(Qt3DCore::QScene *scene) override;
+ void updateFunctor();
+
QUrl m_source;
bool m_mirrored;
};
+class Q_AUTOTEST_EXPORT TextureDownloadRequest : public Qt3DCore::QDownloadRequest
+{
+public:
+ TextureDownloadRequest(Qt3DCore::QNodeId texture, const QUrl &url, Qt3DCore::QAspectEngine *engine);
+
+ void onCompleted() Q_DECL_OVERRIDE;
+
+private:
+ Qt3DCore::QNodeId m_texture;
+ Qt3DCore::QAspectEngine *m_engine;
+};
+
class Q_AUTOTEST_EXPORT QTextureFromSourceGenerator : public QTextureGenerator
{
public:
- explicit QTextureFromSourceGenerator(const QUrl &url, bool mirrored);
+ explicit QTextureFromSourceGenerator(Qt3DCore::QNodeId texture, const QUrl &url,
+ bool mirrored, Qt3DCore::QAspectEngine *engine);
QTextureDataPtr operator ()() Q_DECL_OVERRIDE;
bool operator ==(const QTextureGenerator &other) const Q_DECL_OVERRIDE;
inline QAbstractTexture::Status status() const { return m_status; }
@@ -81,9 +100,14 @@ public:
bool isMirrored() const;
private:
+ friend class TextureDownloadRequest;
+
QUrl m_url;
QAbstractTexture::Status m_status;
bool m_mirrored;
+ QByteArray m_sourceData;
+ Qt3DCore::QNodeId m_texture;
+ Qt3DCore::QAspectEngine *m_engine;
};
typedef QSharedPointer<QTextureFromSourceGenerator> QTextureFromSourceGeneratorPtr;
@@ -91,6 +115,8 @@ class Q_AUTOTEST_EXPORT TextureLoadingHelper
{
public:
static QTextureImageDataPtr loadTextureData(const QUrl &source, bool allow3D, bool mirrored);
+ static QTextureImageDataPtr loadTextureData(QIODevice *data, const QString& suffix,
+ bool allow3D, bool mirrored);
};
} // namespace Qt3DRender
diff --git a/src/render/texture/texture.cpp b/src/render/texture/texture.cpp
index 13991ec4a..21f29d0b6 100644
--- a/src/render/texture/texture.cpp
+++ b/src/render/texture/texture.cpp
@@ -81,11 +81,19 @@ void Texture::setTextureImageManager(TextureImageManager *manager)
void Texture::addDirtyFlag(DirtyFlags flags)
{
+ QMutexLocker lock(&m_flagsMutex);
m_dirty |= flags;
}
+Texture::DirtyFlags Texture::dirtyFlags()
+{
+ QMutexLocker lock(&m_flagsMutex);
+ return m_dirty;
+}
+
void Texture::unsetDirty()
{
+ QMutexLocker lock(&m_flagsMutex);
m_dirty = Texture::NotDirty;
}
diff --git a/src/render/texture/texture_p.h b/src/render/texture/texture_p.h
index 4fe4e2c7c..1f3ba729c 100644
--- a/src/render/texture/texture_p.h
+++ b/src/render/texture/texture_p.h
@@ -143,7 +143,7 @@ public:
void setTextureImageManager(TextureImageManager *manager);
void addDirtyFlag(DirtyFlags flags);
- inline DirtyFlags dirtyFlags() const { return m_dirty; }
+ DirtyFlags dirtyFlags();
void unsetDirty();
void addTextureImage(Qt3DCore::QNodeId id);
@@ -169,6 +169,7 @@ private:
QVector<HTextureImage> m_textureImages;
TextureImageManager *m_textureImageManager;
+ QMutex m_flagsMutex;
};
class TextureFunctor : public Qt3DCore::QBackendNodeMapper
diff --git a/tests/auto/render/qmesh/tst_qmesh.cpp b/tests/auto/render/qmesh/tst_qmesh.cpp
index 2122d7eb5..a0278f4e4 100644
--- a/tests/auto/render/qmesh/tst_qmesh.cpp
+++ b/tests/auto/render/qmesh/tst_qmesh.cpp
@@ -121,7 +121,7 @@ private Q_SLOTS:
const auto creationChangeData = qSharedPointerCast<Qt3DCore::QNodeCreatedChange<Qt3DRender::QGeometryRendererData>>(creationChanges.first());
const Qt3DRender::QGeometryRendererData cloneData = creationChangeData->data;
- Qt3DRender::MeshFunctor meshFunctor(mesh.source(), mesh.meshName());
+ Qt3DRender::MeshLoaderFunctor meshFunctor(&mesh, nullptr);
QVERIFY(meshFunctor == *cloneData.geometryFactory);
QCOMPARE(mesh.id(), creationChangeData->subjectId());
@@ -169,7 +169,7 @@ private Q_SLOTS:
QCOMPARE(change->propertyName(), "geometryFactory");
QCOMPARE(change->type(), Qt3DCore::PropertyUpdated);
- Qt3DRender::MeshFunctor meshFunctor(mesh.source());
+ Qt3DRender::MeshLoaderFunctor meshFunctor(&mesh, nullptr);
Qt3DRender::QGeometryFactoryPtr factory = change->value().value<Qt3DRender::QGeometryFactoryPtr>();
QVERIFY(meshFunctor == *factory);
@@ -205,7 +205,7 @@ private Q_SLOTS:
QCOMPARE(change->propertyName(), "geometryFactory");
QCOMPARE(change->type(), Qt3DCore::PropertyUpdated);
- Qt3DRender::MeshFunctor meshFunctor(QUrl(), mesh.meshName());
+ Qt3DRender::MeshLoaderFunctor meshFunctor(&mesh, nullptr);
Qt3DRender::QGeometryFactoryPtr factory = change->value().value<Qt3DRender::QGeometryFactoryPtr>();
QVERIFY(meshFunctor == *factory);
diff --git a/tests/manual/custom-mesh-update-data-cpp/main.cpp b/tests/manual/custom-mesh-update-data-cpp/main.cpp
index c7f5742dc..80ee2088d 100644
--- a/tests/manual/custom-mesh-update-data-cpp/main.cpp
+++ b/tests/manual/custom-mesh-update-data-cpp/main.cpp
@@ -277,7 +277,7 @@ int main(int argc, char* argv[])
void TimerObject::timeout()
{
- angle += float(M_PI / 360.0);
+ angle += qDegreesToRadians(0.5f);
QByteArray updateData;
updateData.resize(3*sizeof(float));
diff --git a/tests/manual/downloading/downloading.pro b/tests/manual/downloading/downloading.pro
new file mode 100644
index 000000000..6f1b6e3d5
--- /dev/null
+++ b/tests/manual/downloading/downloading.pro
@@ -0,0 +1,14 @@
+!include( ../manual.pri ) {
+ error( "Couldn't find the manual.pri file!" )
+}
+
+QT += 3dcore 3drender 3dinput 3dquick qml quick 3dquickextras
+
+SOURCES += \
+ main.cpp
+
+OTHER_FILES += \
+ main.qml
+
+RESOURCES += \
+ downloading.qrc
diff --git a/tests/manual/downloading/downloading.qrc b/tests/manual/downloading/downloading.qrc
new file mode 100644
index 000000000..5f6483ac3
--- /dev/null
+++ b/tests/manual/downloading/downloading.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/downloading/main.cpp b/tests/manual/downloading/main.cpp
new file mode 100644
index 000000000..dba6e0bff
--- /dev/null
+++ b/tests/manual/downloading/main.cpp
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <Qt3DQuickExtras/qt3dquickwindow.h>
+#include <QGuiApplication>
+
+int main(int argc, char* argv[])
+{
+ QGuiApplication app(argc, argv);
+ Qt3DExtras::Quick::Qt3DQuickWindow view;
+
+ view.setSource(QUrl("qrc:/main.qml"));
+ view.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/downloading/main.qml b/tests/manual/downloading/main.qml
new file mode 100644
index 000000000..e2f42284d
--- /dev/null
+++ b/tests/manual/downloading/main.qml
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import Qt3D.Core 2.0
+import Qt3D.Render 2.0
+import Qt3D.Input 2.0
+import Qt3D.Extras 2.0
+
+Entity {
+ id: sceneRoot
+
+ Camera {
+ id: camera
+ position: Qt.vector3d( 0.0, 0.5, 15.0 )
+ viewCenter: Qt.vector3d( 0.0, 0.5, 0.0 )
+ }
+
+ OrbitCameraController { camera: camera }
+
+ RenderSettings {
+ id : external_forward_renderer
+ activeFrameGraph : ForwardRenderer {
+ camera: camera
+ clearColor: "white"
+ }
+ }
+
+ // Event Source will be set by the Qt3DQuickWindow
+ InputSettings { id: inputSettings }
+
+ DirectionalLight { id: light; worldDirection: camera.viewVector }
+
+ components: [external_forward_renderer, inputSettings, light]
+
+ Mesh {
+ id: mesh
+ source: "https://codereview.qt-project.org/gitweb?p=qt/qt3d.git;a=blob_plain;hb=refs/heads/dev;f=examples/qt3d/exampleresources/assets/chest/Chest.obj"
+ }
+
+ Transform {
+ id: transform
+ scale: 0.03
+ rotation: fromAxisAndAngle(Qt.vector3d(1, 0, 0), 30)
+ }
+
+ DiffuseMapMaterial {
+ id: material
+ diffuse: TextureLoader { source: "https://codereview.qt-project.org/gitweb?p=qt/qt3d.git;a=blob_plain;hb=refs/heads/dev;f=examples/qt3d/planets-qml/images/earthmap1k.jpg" }
+ specular: Qt.rgba( 0.2, 0.2, 0.2, 1.0 )
+ shininess: 2.0
+ }
+
+ Entity {
+ id: mainEntity
+ objectName: "mainEntity"
+ components: [ mesh, material, transform ]
+ }
+}
diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro
index fa7b016f9..1dd418500 100644
--- a/tests/manual/manual.pro
+++ b/tests/manual/manual.pro
@@ -16,6 +16,7 @@ SUBDIRS += \
cylinder-qml \
deferred-renderer-cpp \
deferred-renderer-qml \
+ downloading \
dragging \
dynamicscene-cpp \
enabled-qml \