summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Ottens <kevin.ottens@kdab.com>2017-04-26 14:54:20 +0200
committerKevin Ottens <kevin.ottens@kdab.com>2017-06-20 21:35:46 +0000
commit093752faf35e79667a037f038e2f6ebb58a5bb81 (patch)
tree7ec2369ed6e0ec5f9674cebe40a60f9c99790a9d
parentad2faa3c6378a6270e120137ec4eb0449b0d5690 (diff)
[Shader Graph Gen.] Introduce QShaderGraphLoader
This class allows to load a shader graph from its JSON representation. To avoid duplicating the shader snippets inside of the JSON which would be a maintenance nightmare, we instead allow to register a set of node prototypes by their name and only refer to said names inside the JSON. Change-Id: I3e7b39e8b3c25f51f331a0a59dda883ac8e2bc57 Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
-rw-r--r--src/gui/util/qshadergraphloader.cpp204
-rw-r--r--src/gui/util/qshadergraphloader_p.h99
-rw-r--r--src/gui/util/util.pri2
-rw-r--r--tests/auto/gui/util/qshadergraphloader/qshadergraphloader.pro5
-rw-r--r--tests/auto/gui/util/qshadergraphloader/tst_qshadergraphloader.cpp552
-rw-r--r--tests/auto/gui/util/util.pro1
6 files changed, 863 insertions, 0 deletions
diff --git a/src/gui/util/qshadergraphloader.cpp b/src/gui/util/qshadergraphloader.cpp
new file mode 100644
index 0000000000..d246654b9c
--- /dev/null
+++ b/src/gui/util/qshadergraphloader.cpp
@@ -0,0 +1,204 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui 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 "qshadergraphloader_p.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+
+QT_BEGIN_NAMESPACE
+
+QShaderGraphLoader::QShaderGraphLoader() Q_DECL_NOTHROW
+ : m_status(Null),
+ m_device(nullptr)
+{
+}
+
+QShaderGraphLoader::Status QShaderGraphLoader::status() const Q_DECL_NOTHROW
+{
+ return m_status;
+}
+
+QShaderGraph QShaderGraphLoader::graph() const Q_DECL_NOTHROW
+{
+ return m_graph;
+}
+
+QIODevice *QShaderGraphLoader::device() const Q_DECL_NOTHROW
+{
+ return m_device;
+}
+
+void QShaderGraphLoader::setDevice(QIODevice *device) Q_DECL_NOTHROW
+{
+ m_device = device;
+ m_graph = QShaderGraph();
+ m_status = !m_device ? Null
+ : (m_device->openMode() & QIODevice::ReadOnly) ? Waiting
+ : Error;
+}
+
+QHash<QString, QShaderNode> QShaderGraphLoader::prototypes() const Q_DECL_NOTHROW
+{
+ return m_prototypes;
+}
+
+void QShaderGraphLoader::setPrototypes(const QHash<QString, QShaderNode> &prototypes) Q_DECL_NOTHROW
+{
+ m_prototypes = prototypes;
+}
+
+void QShaderGraphLoader::load()
+{
+ if (m_status == Error)
+ return;
+
+ auto error = QJsonParseError();
+ const auto document = QJsonDocument::fromJson(m_device->readAll(), &error);
+
+ if (error.error != QJsonParseError::NoError) {
+ qWarning() << "Invalid JSON document:" << error.errorString();
+ m_status = Error;
+ return;
+ }
+
+ if (document.isEmpty() || !document.isObject()) {
+ qWarning() << "Invalid JSON document, root should be an object";
+ m_status = Error;
+ return;
+ }
+
+ const auto root = document.object();
+
+ const auto nodesValue = root.value(QStringLiteral("nodes"));
+ if (!nodesValue.isArray()) {
+ qWarning() << "Invalid nodes property, should be an array";
+ m_status = Error;
+ return;
+ }
+
+ const auto edgesValue = root.value(QStringLiteral("edges"));
+ if (!edgesValue.isArray()) {
+ qWarning() << "Invalid edges property, should be an array";
+ m_status = Error;
+ return;
+ }
+
+ bool hasError = false;
+
+ const auto nodes = nodesValue.toArray();
+ for (const auto &nodeValue : nodes) {
+ if (!nodeValue.isObject()) {
+ qWarning() << "Invalid node found";
+ hasError = true;
+ continue;
+ }
+
+ const auto nodeObject = nodeValue.toObject();
+
+ const auto uuidString = nodeObject.value(QStringLiteral("uuid")).toString();
+ const auto uuid = QUuid(uuidString);
+ if (uuid.isNull()) {
+ qWarning() << "Invalid UUID found in node:" << uuidString;
+ hasError = true;
+ continue;
+ }
+
+ const auto type = nodeObject.value(QStringLiteral("type")).toString();
+ if (!m_prototypes.contains(type)) {
+ qWarning() << "Unsupported node type found:" << type;
+ hasError = true;
+ continue;
+ }
+
+ auto node = m_prototypes.value(type);
+ node.setUuid(uuid);
+ m_graph.addNode(node);
+ }
+
+ const auto edges = edgesValue.toArray();
+ for (const auto &edgeValue : edges) {
+ if (!edgeValue.isObject()) {
+ qWarning() << "Invalid edge found";
+ hasError = true;
+ continue;
+ }
+
+ const auto edgeObject = edgeValue.toObject();
+
+ const auto sourceUuidString = edgeObject.value(QStringLiteral("sourceUuid")).toString();
+ const auto sourceUuid = QUuid(sourceUuidString);
+ if (sourceUuid.isNull()) {
+ qWarning() << "Invalid source UUID found in edge:" << sourceUuidString;
+ hasError = true;
+ continue;
+ }
+
+ const auto sourcePort = edgeObject.value(QStringLiteral("sourcePort")).toString();
+
+ const auto targetUuidString = edgeObject.value(QStringLiteral("targetUuid")).toString();
+ const auto targetUuid = QUuid(targetUuidString);
+ if (targetUuid.isNull()) {
+ qWarning() << "Invalid target UUID found in edge:" << targetUuidString;
+ hasError = true;
+ continue;
+ }
+
+ const auto targetPort = edgeObject.value(QStringLiteral("targetPort")).toString();
+
+ auto edge = QShaderGraph::Edge();
+ edge.sourceNodeUuid = sourceUuid;
+ edge.sourcePortName = sourcePort;
+ edge.targetNodeUuid = targetUuid;
+ edge.targetPortName = targetPort;
+ m_graph.addEdge(edge);
+ }
+
+ if (hasError) {
+ m_status = Error;
+ m_graph = QShaderGraph();
+ } else {
+ m_status = Ready;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/util/qshadergraphloader_p.h b/src/gui/util/qshadergraphloader_p.h
new file mode 100644
index 0000000000..97cbd8d18c
--- /dev/null
+++ b/src/gui/util/qshadergraphloader_p.h
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui 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 QSHADERGRAPHLOADER_P_H
+#define QSHADERGRAPHLOADER_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 <QtGui/private/qtguiglobal_p.h>
+
+#include <QtGui/private/qshadergraph_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QIODevice;
+
+class QShaderGraphLoader
+{
+public:
+ enum Status : char {
+ Null,
+ Waiting,
+ Ready,
+ Error
+ };
+
+ Q_GUI_EXPORT QShaderGraphLoader() Q_DECL_NOTHROW;
+
+ Q_GUI_EXPORT Status status() const Q_DECL_NOTHROW;
+ Q_GUI_EXPORT QShaderGraph graph() const Q_DECL_NOTHROW;
+
+ Q_GUI_EXPORT QIODevice *device() const Q_DECL_NOTHROW;
+ Q_GUI_EXPORT void setDevice(QIODevice *device) Q_DECL_NOTHROW;
+
+ Q_GUI_EXPORT QHash<QString, QShaderNode> prototypes() const Q_DECL_NOTHROW;
+ Q_GUI_EXPORT void setPrototypes(const QHash<QString, QShaderNode> &prototypes) Q_DECL_NOTHROW;
+
+ Q_GUI_EXPORT void load();
+
+private:
+ Status m_status;
+ QIODevice *m_device;
+ QHash<QString, QShaderNode> m_prototypes;
+ QShaderGraph m_graph;
+};
+
+Q_DECLARE_TYPEINFO(QShaderGraphLoader, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(QShaderGraphLoader)
+Q_DECLARE_METATYPE(QShaderGraphLoader::Status)
+
+#endif // QSHADERGRAPHLOADER_P_H
diff --git a/src/gui/util/util.pri b/src/gui/util/util.pri
index e7feda9a4a..bb7a802fd8 100644
--- a/src/gui/util/util.pri
+++ b/src/gui/util/util.pri
@@ -10,6 +10,7 @@ HEADERS += \
util/qshaderformat_p.h \
util/qshadergenerator_p.h \
util/qshadergraph_p.h \
+ util/qshadergraphloader_p.h \
util/qshadernode_p.h \
util/qshadernodeport_p.h
@@ -22,5 +23,6 @@ SOURCES += \
util/qshaderformat.cpp \
util/qshadergenerator.cpp \
util/qshadergraph.cpp \
+ util/qshadergraphloader.cpp \
util/qshadernode.cpp \
util/qshadernodeport.cpp
diff --git a/tests/auto/gui/util/qshadergraphloader/qshadergraphloader.pro b/tests/auto/gui/util/qshadergraphloader/qshadergraphloader.pro
new file mode 100644
index 0000000000..e80a93f9e8
--- /dev/null
+++ b/tests/auto/gui/util/qshadergraphloader/qshadergraphloader.pro
@@ -0,0 +1,5 @@
+CONFIG += testcase
+QT += testlib gui-private
+
+SOURCES += tst_qshadergraphloader.cpp
+TARGET = tst_qshadergraphloader
diff --git a/tests/auto/gui/util/qshadergraphloader/tst_qshadergraphloader.cpp b/tests/auto/gui/util/qshadergraphloader/tst_qshadergraphloader.cpp
new file mode 100644
index 0000000000..418370fa2a
--- /dev/null
+++ b/tests/auto/gui/util/qshadergraphloader/tst_qshadergraphloader.cpp
@@ -0,0 +1,552 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+** 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/QtTest>
+
+#include <QtCore/qbuffer.h>
+
+#include <QtGui/private/qshadergraphloader_p.h>
+
+using QBufferPointer = QSharedPointer<QBuffer>;
+Q_DECLARE_METATYPE(QBufferPointer);
+
+using PrototypeHash = QHash<QString, QShaderNode>;
+Q_DECLARE_METATYPE(PrototypeHash);
+
+namespace
+{
+ QBufferPointer createBuffer(const QByteArray &data, QIODevice::OpenMode openMode = QIODevice::ReadOnly)
+ {
+ auto buffer = QBufferPointer::create();
+ buffer->setData(data);
+ if (openMode != QIODevice::NotOpen)
+ buffer->open(openMode);
+ return buffer;
+ }
+
+ QShaderFormat createFormat(QShaderFormat::Api api, int majorVersion, int minorVersion)
+ {
+ auto format = QShaderFormat();
+ format.setApi(api);
+ format.setVersion(QVersionNumber(majorVersion, minorVersion));
+ return format;
+ }
+
+ QShaderNodePort createPort(QShaderNodePort::Direction portDirection, const QString &portName)
+ {
+ auto port = QShaderNodePort();
+ port.direction = portDirection;
+ port.name = portName;
+ return port;
+ }
+
+ QShaderNode createNode(const QVector<QShaderNodePort> &ports)
+ {
+ auto node = QShaderNode();
+ node.setUuid(QUuid::createUuid());
+ for (const auto &port : ports)
+ node.addPort(port);
+ return node;
+ }
+
+ QShaderGraph::Edge createEdge(const QUuid &sourceUuid, const QString &sourceName,
+ const QUuid &targetUuid, const QString &targetName)
+ {
+ auto edge = QShaderGraph::Edge();
+ edge.sourceNodeUuid = sourceUuid;
+ edge.sourcePortName = sourceName;
+ edge.targetNodeUuid = targetUuid;
+ edge.targetPortName = targetName;
+ return edge;
+ }
+
+ QShaderGraph createGraph()
+ {
+ const auto openGLES2 = createFormat(QShaderFormat::OpenGLES, 2, 0);
+ const auto openGL3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0);
+
+ auto graph = QShaderGraph();
+
+ auto worldPosition = createNode({
+ createPort(QShaderNodePort::Output, "worldPosition")
+ });
+ worldPosition.setUuid(QUuid("{00000000-0000-0000-0000-000000000001}"));
+ worldPosition.addRule(openGLES2, QShaderNode::Rule("highp vec3 $worldPosition = worldPosition;",
+ QByteArrayList() << "varying highp vec3 worldPosition;"));
+ worldPosition.addRule(openGL3, QShaderNode::Rule("vec3 $worldPosition = worldPosition;",
+ QByteArrayList() << "in vec3 worldPosition;"));
+
+ auto texture = createNode({
+ createPort(QShaderNodePort::Output, "texture")
+ });
+ texture.setUuid(QUuid("{00000000-0000-0000-0000-000000000002}"));
+ texture.addRule(openGLES2, QShaderNode::Rule("sampler2D $texture = texture;",
+ QByteArrayList() << "uniform sampler2D texture;"));
+ texture.addRule(openGL3, QShaderNode::Rule("sampler2D $texture = texture;",
+ QByteArrayList() << "uniform sampler2D texture;"));
+
+ auto texCoord = createNode({
+ createPort(QShaderNodePort::Output, "texCoord")
+ });
+ texCoord.setUuid(QUuid("{00000000-0000-0000-0000-000000000003}"));
+ texCoord.addRule(openGLES2, QShaderNode::Rule("highp vec2 $texCoord = texCoord;",
+ QByteArrayList() << "varying highp vec2 texCoord;"));
+ texCoord.addRule(openGL3, QShaderNode::Rule("vec2 $texCoord = texCoord;",
+ QByteArrayList() << "in vec2 texCoord;"));
+
+ auto lightIntensity = createNode({
+ createPort(QShaderNodePort::Output, "lightIntensity")
+ });
+ lightIntensity.setUuid(QUuid("{00000000-0000-0000-0000-000000000004}"));
+ lightIntensity.addRule(openGLES2, QShaderNode::Rule("highp float $lightIntensity = lightIntensity;",
+ QByteArrayList() << "uniform highp float lightIntensity;"));
+ lightIntensity.addRule(openGL3, QShaderNode::Rule("float $lightIntensity = lightIntensity;",
+ QByteArrayList() << "uniform float lightIntensity;"));
+
+ auto exposure = createNode({
+ createPort(QShaderNodePort::Output, "exposure")
+ });
+ exposure.setUuid(QUuid("{00000000-0000-0000-0000-000000000005}"));
+ exposure.addRule(openGLES2, QShaderNode::Rule("highp float $exposure = exposure;",
+ QByteArrayList() << "uniform highp float exposure;"));
+ exposure.addRule(openGL3, QShaderNode::Rule("float $exposure = exposure;",
+ QByteArrayList() << "uniform float exposure;"));
+
+ auto fragColor = createNode({
+ createPort(QShaderNodePort::Input, "fragColor")
+ });
+ fragColor.setUuid(QUuid("{00000000-0000-0000-0000-000000000006}"));
+ fragColor.addRule(openGLES2, QShaderNode::Rule("gl_fragColor = $fragColor;"));
+ fragColor.addRule(openGL3, QShaderNode::Rule("fragColor = $fragColor;",
+ QByteArrayList() << "out vec4 fragColor;"));
+
+ auto sampleTexture = createNode({
+ createPort(QShaderNodePort::Input, "sampler"),
+ createPort(QShaderNodePort::Input, "coord"),
+ createPort(QShaderNodePort::Output, "color")
+ });
+ sampleTexture.setUuid(QUuid("{00000000-0000-0000-0000-000000000007}"));
+ sampleTexture.addRule(openGLES2, QShaderNode::Rule("highp vec4 $color = texture2D($sampler, $coord);"));
+ sampleTexture.addRule(openGL3, QShaderNode::Rule("vec4 $color = texture2D($sampler, $coord);"));
+
+ auto lightFunction = createNode({
+ createPort(QShaderNodePort::Input, "baseColor"),
+ createPort(QShaderNodePort::Input, "position"),
+ createPort(QShaderNodePort::Input, "lightIntensity"),
+ createPort(QShaderNodePort::Output, "outputColor")
+ });
+ lightFunction.setUuid(QUuid("{00000000-0000-0000-0000-000000000008}"));
+ lightFunction.addRule(openGLES2, QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);",
+ QByteArrayList() << "#pragma include es2/lightmodel.frag.inc"));
+ lightFunction.addRule(openGL3, QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);",
+ QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc"));
+
+ auto exposureFunction = createNode({
+ createPort(QShaderNodePort::Input, "inputColor"),
+ createPort(QShaderNodePort::Input, "exposure"),
+ createPort(QShaderNodePort::Output, "outputColor")
+ });
+ exposureFunction.setUuid(QUuid("{00000000-0000-0000-0000-000000000009}"));
+ exposureFunction.addRule(openGLES2, QShaderNode::Rule("highp vec4 $outputColor = $inputColor * pow(2.0, $exposure);"));
+ exposureFunction.addRule(openGL3, QShaderNode::Rule("vec4 $outputColor = $inputColor * pow(2.0, $exposure);"));
+
+ graph.addNode(worldPosition);
+ graph.addNode(texture);
+ graph.addNode(texCoord);
+ graph.addNode(lightIntensity);
+ graph.addNode(exposure);
+ graph.addNode(fragColor);
+ graph.addNode(sampleTexture);
+ graph.addNode(lightFunction);
+ graph.addNode(exposureFunction);
+
+ graph.addEdge(createEdge(texture.uuid(), "texture", sampleTexture.uuid(), "sampler"));
+ graph.addEdge(createEdge(texCoord.uuid(), "texCoord", sampleTexture.uuid(), "coord"));
+
+ graph.addEdge(createEdge(worldPosition.uuid(), "worldPosition", lightFunction.uuid(), "position"));
+ graph.addEdge(createEdge(sampleTexture.uuid(), "color", lightFunction.uuid(), "baseColor"));
+ graph.addEdge(createEdge(lightIntensity.uuid(), "lightIntensity", lightFunction.uuid(), "lightIntensity"));
+
+ graph.addEdge(createEdge(lightFunction.uuid(), "outputColor", exposureFunction.uuid(), "inputColor"));
+ graph.addEdge(createEdge(exposure.uuid(), "exposure", exposureFunction.uuid(), "exposure"));
+
+ graph.addEdge(createEdge(exposureFunction.uuid(), "outputColor", fragColor.uuid(), "fragColor"));
+
+ return graph;
+ }
+
+ void debugStatement(const QString &prefix, const QShaderGraph::Statement &statement)
+ {
+ qDebug() << prefix << statement.inputs << statement.uuid().toString() << statement.outputs;
+ }
+
+ void dumpStatementsIfNeeded(const QVector<QShaderGraph::Statement> &statements, const QVector<QShaderGraph::Statement> &expected)
+ {
+ if (statements != expected) {
+ for (int i = 0; i < qMax(statements.size(), expected.size()); i++) {
+ qDebug() << "----" << i << "----";
+ if (i < statements.size())
+ debugStatement("A:", statements.at(i));
+ if (i < expected.size())
+ debugStatement("E:", expected.at(i));
+ qDebug() << "-----------";
+ }
+ }
+ }
+}
+
+class tst_QShaderGraphLoader : public QObject
+{
+ Q_OBJECT
+private slots:
+ void shouldManipulateLoaderMembers();
+ void shouldLoadFromJsonStream_data();
+ void shouldLoadFromJsonStream();
+};
+
+void tst_QShaderGraphLoader::shouldManipulateLoaderMembers()
+{
+ // GIVEN
+ auto loader = QShaderGraphLoader();
+
+ // THEN (default state)
+ QCOMPARE(loader.status(), QShaderGraphLoader::Null);
+ QVERIFY(!loader.device());
+ QVERIFY(loader.graph().nodes().isEmpty());
+ QVERIFY(loader.graph().edges().isEmpty());
+ QVERIFY(loader.prototypes().isEmpty());
+
+ // WHEN
+ auto device1 = createBuffer(QByteArray("..........."), QIODevice::NotOpen);
+ loader.setDevice(device1.data());
+
+ // THEN
+ QCOMPARE(loader.status(), QShaderGraphLoader::Error);
+ QCOMPARE(loader.device(), device1.data());
+ QVERIFY(loader.graph().nodes().isEmpty());
+ QVERIFY(loader.graph().edges().isEmpty());
+
+ // WHEN
+ auto device2 = createBuffer(QByteArray("..........."), QIODevice::ReadOnly);
+ loader.setDevice(device2.data());
+
+ // THEN
+ QCOMPARE(loader.status(), QShaderGraphLoader::Waiting);
+ QCOMPARE(loader.device(), device2.data());
+ QVERIFY(loader.graph().nodes().isEmpty());
+ QVERIFY(loader.graph().edges().isEmpty());
+
+
+ // WHEN
+ const auto prototypes = [this]{
+ auto res = QHash<QString, QShaderNode>();
+ res.insert("foo", createNode({}));
+ return res;
+ }();
+ loader.setPrototypes(prototypes);
+
+ // THEN
+ QCOMPARE(loader.prototypes().size(), prototypes.size());
+ QVERIFY(loader.prototypes().contains("foo"));
+ QCOMPARE(loader.prototypes().value("foo").uuid(), prototypes.value("foo").uuid());
+}
+
+void tst_QShaderGraphLoader::shouldLoadFromJsonStream_data()
+{
+ QTest::addColumn<QBufferPointer>("device");
+ QTest::addColumn<PrototypeHash>("prototypes");
+ QTest::addColumn<QShaderGraph>("graph");
+ QTest::addColumn<QShaderGraphLoader::Status>("status");
+
+ QTest::newRow("empty") << createBuffer("", QIODevice::ReadOnly) << PrototypeHash()
+ << QShaderGraph() << QShaderGraphLoader::Error;
+
+ const auto smallJson = "{"
+ " \"nodes\": ["
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000001}\","
+ " \"type\": \"MyInput\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000002}\","
+ " \"type\": \"MyOutput\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000003}\","
+ " \"type\": \"MyFunction\""
+ " }"
+ " ],"
+ " \"edges\": ["
+ " {"
+ " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000001}\","
+ " \"sourcePort\": \"input\","
+ " \"targetUuid\": \"{00000000-0000-0000-0000-000000000003}\","
+ " \"targetPort\": \"functionInput\""
+ " },"
+ " {"
+ " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000003}\","
+ " \"sourcePort\": \"functionOutput\","
+ " \"targetUuid\": \"{00000000-0000-0000-0000-000000000002}\","
+ " \"targetPort\": \"output\""
+ " }"
+ " ]"
+ "}";
+
+ const auto smallProtos = [this]{
+ auto protos = PrototypeHash();
+
+ auto input = createNode({
+ createPort(QShaderNodePort::Output, "input")
+ });
+ protos.insert("MyInput", input);
+
+ auto output = createNode({
+ createPort(QShaderNodePort::Input, "output")
+ });
+ protos.insert("MyOutput", output);
+
+ auto function = createNode({
+ createPort(QShaderNodePort::Input, "functionInput"),
+ createPort(QShaderNodePort::Output, "functionOutput")
+ });
+ protos.insert("MyFunction", function);
+ return protos;
+ }();
+
+ const auto smallGraph = [this]{
+ auto graph = QShaderGraph();
+
+ auto input = createNode({
+ createPort(QShaderNodePort::Output, "input")
+ });
+ input.setUuid(QUuid("{00000000-0000-0000-0000-000000000001}"));
+ auto output = createNode({
+ createPort(QShaderNodePort::Input, "output")
+ });
+ output.setUuid(QUuid("{00000000-0000-0000-0000-000000000002}"));
+ auto function = createNode({
+ createPort(QShaderNodePort::Input, "functionInput"),
+ createPort(QShaderNodePort::Output, "functionOutput")
+ });
+ function.setUuid(QUuid("{00000000-0000-0000-0000-000000000003}"));
+
+ graph.addNode(input);
+ graph.addNode(output);
+ graph.addNode(function);
+ graph.addEdge(createEdge(input.uuid(), "input", function.uuid(), "functionInput"));
+ graph.addEdge(createEdge(function.uuid(), "functionOutput", output.uuid(), "output"));
+
+ return graph;
+ }();
+
+ QTest::newRow("TwoNodesOneEdge") << createBuffer(smallJson) << smallProtos << smallGraph << QShaderGraphLoader::Ready;
+ QTest::newRow("NotOpen") << createBuffer(smallJson, QIODevice::NotOpen) << smallProtos << QShaderGraph() << QShaderGraphLoader::Error;
+ QTest::newRow("NoPrototype") << createBuffer(smallJson) << PrototypeHash() << QShaderGraph() << QShaderGraphLoader::Error;
+
+ const auto complexJson = "{"
+ " \"nodes\": ["
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000001}\","
+ " \"type\": \"worldPosition\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000002}\","
+ " \"type\": \"texture\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000003}\","
+ " \"type\": \"texCoord\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000004}\","
+ " \"type\": \"lightIntensity\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000005}\","
+ " \"type\": \"exposure\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000006}\","
+ " \"type\": \"fragColor\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000007}\","
+ " \"type\": \"sampleTexture\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000008}\","
+ " \"type\": \"lightModel\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000009}\","
+ " \"type\": \"exposureFunction\""
+ " }"
+ " ],"
+ " \"edges\": ["
+ " {"
+ " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000002}\","
+ " \"sourcePort\": \"texture\","
+ " \"targetUuid\": \"{00000000-0000-0000-0000-000000000007}\","
+ " \"targetPort\": \"sampler\""
+ " },"
+ " {"
+ " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000003}\","
+ " \"sourcePort\": \"texCoord\","
+ " \"targetUuid\": \"{00000000-0000-0000-0000-000000000007}\","
+ " \"targetPort\": \"coord\""
+ " },"
+ " {"
+ " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000001}\","
+ " \"sourcePort\": \"worldPosition\","
+ " \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\","
+ " \"targetPort\": \"position\""
+ " },"
+ " {"
+ " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000007}\","
+ " \"sourcePort\": \"color\","
+ " \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\","
+ " \"targetPort\": \"baseColor\""
+ " },"
+ " {"
+ " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000004}\","
+ " \"sourcePort\": \"lightIntensity\","
+ " \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\","
+ " \"targetPort\": \"lightIntensity\""
+ " },"
+ " {"
+ " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000008}\","
+ " \"sourcePort\": \"outputColor\","
+ " \"targetUuid\": \"{00000000-0000-0000-0000-000000000009}\","
+ " \"targetPort\": \"inputColor\""
+ " },"
+ " {"
+ " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000005}\","
+ " \"sourcePort\": \"exposure\","
+ " \"targetUuid\": \"{00000000-0000-0000-0000-000000000009}\","
+ " \"targetPort\": \"exposure\""
+ " },"
+ " {"
+ " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000009}\","
+ " \"sourcePort\": \"outputColor\","
+ " \"targetUuid\": \"{00000000-0000-0000-0000-000000000006}\","
+ " \"targetPort\": \"fragColor\""
+ " }"
+ " ]"
+ "}";
+
+ const auto complexProtos = [this]{
+ auto protos = PrototypeHash();
+
+ auto worldPosition = createNode({
+ createPort(QShaderNodePort::Output, "worldPosition")
+ });
+ protos.insert("worldPosition", worldPosition);
+
+ auto texture = createNode({
+ createPort(QShaderNodePort::Output, "texture")
+ });
+ protos.insert("texture", texture);
+
+ auto texCoord = createNode({
+ createPort(QShaderNodePort::Output, "texCoord")
+ });
+ protos.insert("texCoord", texCoord);
+
+ auto lightIntensity = createNode({
+ createPort(QShaderNodePort::Output, "lightIntensity")
+ });
+ protos.insert("lightIntensity", lightIntensity);
+
+ auto exposure = createNode({
+ createPort(QShaderNodePort::Output, "exposure")
+ });
+ protos.insert("exposure", exposure);
+
+ auto fragColor = createNode({
+ createPort(QShaderNodePort::Input, "fragColor")
+ });
+ protos.insert("fragColor", fragColor);
+
+ auto sampleTexture = createNode({
+ createPort(QShaderNodePort::Input, "sampler"),
+ createPort(QShaderNodePort::Input, "coord"),
+ createPort(QShaderNodePort::Output, "color")
+ });
+ protos.insert("sampleTexture", sampleTexture);
+
+ auto lightModel = createNode({
+ createPort(QShaderNodePort::Input, "baseColor"),
+ createPort(QShaderNodePort::Input, "position"),
+ createPort(QShaderNodePort::Input, "lightIntensity"),
+ createPort(QShaderNodePort::Output, "outputColor")
+ });
+ protos.insert("lightModel", lightModel);
+
+ auto exposureFunction = createNode({
+ createPort(QShaderNodePort::Input, "inputColor"),
+ createPort(QShaderNodePort::Input, "exposure"),
+ createPort(QShaderNodePort::Output, "outputColor")
+ });
+ protos.insert("exposureFunction", exposureFunction);
+
+ return protos;
+ }();
+
+ const auto complexGraph = createGraph();
+
+ QTest::newRow("ComplexGraph") << createBuffer(complexJson) << complexProtos << complexGraph << QShaderGraphLoader::Ready;
+}
+
+void tst_QShaderGraphLoader::shouldLoadFromJsonStream()
+{
+ // GIVEN
+ QFETCH(QBufferPointer, device);
+ QFETCH(PrototypeHash, prototypes);
+
+ auto loader = QShaderGraphLoader();
+
+ // WHEN
+ loader.setPrototypes(prototypes);
+ loader.setDevice(device.data());
+ loader.load();
+
+ // THEN
+ QFETCH(QShaderGraphLoader::Status, status);
+ QCOMPARE(loader.status(), status);
+
+ QFETCH(QShaderGraph, graph);
+ const auto statements = loader.graph().createStatements();
+ const auto expected = graph.createStatements();
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+}
+
+QTEST_MAIN(tst_QShaderGraphLoader)
+
+#include "tst_qshadergraphloader.moc"
diff --git a/tests/auto/gui/util/util.pro b/tests/auto/gui/util/util.pro
index d7aacd7e06..128001a98c 100644
--- a/tests/auto/gui/util/util.pro
+++ b/tests/auto/gui/util/util.pro
@@ -7,5 +7,6 @@ SUBDIRS= \
qregularexpressionvalidator \
qshadergenerator \
qshadergraph \
+ qshadergraphloader \
qshadernodes \