From 093752faf35e79667a037f038e2f6ebb58a5bb81 Mon Sep 17 00:00:00 2001 From: Kevin Ottens Date: Wed, 26 Apr 2017 14:54:20 +0200 Subject: [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 --- src/gui/util/qshadergraphloader.cpp | 204 ++++++++ src/gui/util/qshadergraphloader_p.h | 99 ++++ src/gui/util/util.pri | 2 + .../util/qshadergraphloader/qshadergraphloader.pro | 5 + .../qshadergraphloader/tst_qshadergraphloader.cpp | 552 +++++++++++++++++++++ tests/auto/gui/util/util.pro | 1 + 6 files changed, 863 insertions(+) create mode 100644 src/gui/util/qshadergraphloader.cpp create mode 100644 src/gui/util/qshadergraphloader_p.h create mode 100644 tests/auto/gui/util/qshadergraphloader/qshadergraphloader.pro create mode 100644 tests/auto/gui/util/qshadergraphloader/tst_qshadergraphloader.cpp 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 +#include +#include +#include +#include + +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 QShaderGraphLoader::prototypes() const Q_DECL_NOTHROW +{ + return m_prototypes; +} + +void QShaderGraphLoader::setPrototypes(const QHash &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 + +#include + +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 prototypes() const Q_DECL_NOTHROW; + Q_GUI_EXPORT void setPrototypes(const QHash &prototypes) Q_DECL_NOTHROW; + + Q_GUI_EXPORT void load(); + +private: + Status m_status; + QIODevice *m_device; + QHash 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 + +#include + +#include + +using QBufferPointer = QSharedPointer; +Q_DECLARE_METATYPE(QBufferPointer); + +using PrototypeHash = QHash; +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 &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 &statements, const QVector &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(); + 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("device"); + QTest::addColumn("prototypes"); + QTest::addColumn("graph"); + QTest::addColumn("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 \ -- cgit v1.2.3