summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Michaël Celerier <jean-michael.celerier@kdab.com>2020-03-13 10:56:30 +0100
committerJean-Michaël Celerier <jean-michael.celerier@kdab.com>2020-03-19 07:14:12 +0100
commita6ffce09ce6d4fd39fbb50eb58f7b5d1027f4d60 (patch)
treefc5bbe90e61ce562cc880b38011cda75eabc808d
parent662c47d6e9c806f6c0d654e7ecce9a66a4f35154 (diff)
Move shadergraph to Qt3D
Change-Id: I3ed10747175a58513933b29eac66ddf92fe87d07 Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
-rw-r--r--src/render/materialsystem/shaderbuilder.cpp14
-rw-r--r--src/render/render.pro1
-rw-r--r--src/render/shadergraph/qshaderformat.cpp146
-rw-r--r--src/render/shadergraph/qshaderformat_p.h126
-rw-r--r--src/render/shadergraph/qshadergenerator.cpp640
-rw-r--r--src/render/shadergraph/qshadergenerator_p.h81
-rw-r--r--src/render/shadergraph/qshadergraph.cpp317
-rw-r--r--src/render/shadergraph/qshadergraph_p.h126
-rw-r--r--src/render/shadergraph/qshadergraphloader.cpp271
-rw-r--r--src/render/shadergraph/qshadergraphloader_p.h102
-rw-r--r--src/render/shadergraph/qshaderlanguage.cpp57
-rw-r--r--src/render/shadergraph/qshaderlanguage_p.h164
-rw-r--r--src/render/shadergraph/qshadernode.cpp174
-rw-r--r--src/render/shadergraph/qshadernode_p.h131
-rw-r--r--src/render/shadergraph/qshadernodeport.cpp58
-rw-r--r--src/render/shadergraph/qshadernodeport_p.h91
-rw-r--r--src/render/shadergraph/qshadernodesloader.cpp292
-rw-r--r--src/render/shadergraph/qshadernodesloader_p.h99
-rw-r--r--src/render/shadergraph/shadergraph.pri21
-rw-r--r--tests/auto/render/render.pro3
-rw-r--r--tests/auto/render/shadergraph/qshadergenerator/qshadergenerator.pro5
-rw-r--r--tests/auto/render/shadergraph/qshadergenerator/tst_qshadergenerator.cpp1428
-rw-r--r--tests/auto/render/shadergraph/qshadergraph/qshadergraph.pro5
-rw-r--r--tests/auto/render/shadergraph/qshadergraph/tst_qshadergraph.cpp821
-rw-r--r--tests/auto/render/shadergraph/qshadergraphloader/qshadergraphloader.pro5
-rw-r--r--tests/auto/render/shadergraph/qshadergraphloader/tst_qshadergraphloader.cpp627
-rw-r--r--tests/auto/render/shadergraph/qshadernodes/qshadernodes.pro5
-rw-r--r--tests/auto/render/shadergraph/qshadernodes/tst_qshadernodes.cpp549
-rw-r--r--tests/auto/render/shadergraph/qshadernodesloader/qshadernodesloader.pro5
-rw-r--r--tests/auto/render/shadergraph/qshadernodesloader/tst_qshadernodesloader.cpp325
-rw-r--r--tests/auto/render/shadergraph/shadergraph.pro10
31 files changed, 6691 insertions, 8 deletions
diff --git a/src/render/materialsystem/shaderbuilder.cpp b/src/render/materialsystem/shaderbuilder.cpp
index 7434cd901..b902e3d8c 100644
--- a/src/render/materialsystem/shaderbuilder.cpp
+++ b/src/render/materialsystem/shaderbuilder.cpp
@@ -46,10 +46,10 @@
#include <Qt3DRender/private/qshaderprogram_p.h>
#include <Qt3DRender/private/qurlhelper_p.h>
-#include <QtGui/private/qshaderformat_p.h>
-#include <QtGui/private/qshadergraphloader_p.h>
-#include <QtGui/private/qshadergenerator_p.h>
-#include <QtGui/private/qshadernodesloader_p.h>
+#include <Qt3DRender/private/qshaderformat_p.h>
+#include <Qt3DRender/private/qshadergraphloader_p.h>
+#include <Qt3DRender/private/qshadergenerator_p.h>
+#include <Qt3DRender/private/qshadernodesloader_p.h>
#include <QFile>
#include <QFileInfo>
@@ -84,7 +84,7 @@ public:
load();
}
- QHash<QString, QShaderNode> prototypes() const
+ QHash<QString, Qt3DRender::QShaderNode> prototypes() const
{
return m_prototypes;
}
@@ -98,14 +98,14 @@ private:
return;
}
- QShaderNodesLoader loader;
+ Qt3DRender::QShaderNodesLoader loader;
loader.setDevice(&file);
loader.load();
m_prototypes = loader.nodes();
}
QString m_fileName;
- QHash<QString, QShaderNode> m_prototypes;
+ QHash<QString, Qt3DRender::QShaderNode> m_prototypes;
};
Q_GLOBAL_STATIC(GlobalShaderPrototypes, qt3dGlobalShaderPrototypes)
diff --git a/src/render/render.pro b/src/render/render.pro
index bf7ef072f..528546230 100644
--- a/src/render/render.pro
+++ b/src/render/render.pro
@@ -17,6 +17,7 @@ include (io/io.pri)
include (picking/picking.pri)
include (raycasting/raycasting.pri)
include (services/services.pri)
+include (shadergraph/shadergraph.pri)
include (texture/texture.pri)
gcov {
diff --git a/src/render/shadergraph/qshaderformat.cpp b/src/render/shadergraph/qshaderformat.cpp
new file mode 100644
index 000000000..98643fb24
--- /dev/null
+++ b/src/render/shadergraph/qshaderformat.cpp
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** 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 "qshaderformat_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace Qt3DRender
+{
+QShaderFormat::QShaderFormat() noexcept
+ : m_api(NoApi)
+ , m_shaderType(Fragment)
+{
+}
+
+QShaderFormat::Api QShaderFormat::api() const noexcept
+{
+ return m_api;
+}
+
+void QShaderFormat::setApi(QShaderFormat::Api api) noexcept
+{
+ m_api = api;
+}
+
+QVersionNumber QShaderFormat::version() const noexcept
+{
+ return m_version;
+}
+
+void QShaderFormat::setVersion(const QVersionNumber &version) noexcept
+{
+ m_version = version;
+}
+
+QStringList QShaderFormat::extensions() const noexcept
+{
+ return m_extensions;
+}
+
+void QShaderFormat::setExtensions(const QStringList &extensions) noexcept
+{
+ m_extensions = extensions;
+ m_extensions.sort();
+}
+
+QString QShaderFormat::vendor() const noexcept
+{
+ return m_vendor;
+}
+
+void QShaderFormat::setVendor(const QString &vendor) noexcept
+{
+ m_vendor = vendor;
+}
+
+bool QShaderFormat::isValid() const noexcept
+{
+ return m_api != NoApi && m_version.majorVersion() > 0;
+}
+
+bool QShaderFormat::supports(const QShaderFormat &other) const noexcept
+{
+ if (!isValid() || !other.isValid())
+ return false;
+
+ if (m_api == OpenGLES && m_api != other.m_api)
+ return false;
+
+ if (m_api == OpenGLCoreProfile && m_api != other.m_api)
+ return false;
+
+ if (m_version < other.m_version)
+ return false;
+
+ if (m_shaderType != other.m_shaderType)
+ return false;
+
+ const auto containsAllExtensionsFromOther = std::includes(m_extensions.constBegin(),
+ m_extensions.constEnd(),
+ other.m_extensions.constBegin(),
+ other.m_extensions.constEnd());
+ if (!containsAllExtensionsFromOther)
+ return false;
+
+ if (!other.m_vendor.isEmpty() && m_vendor != other.m_vendor)
+ return false;
+
+ return true;
+}
+
+QShaderFormat::ShaderType QShaderFormat::shaderType() const Q_DECL_NOTHROW
+{
+ return m_shaderType;
+}
+
+void QShaderFormat::setShaderType(QShaderFormat::ShaderType shaderType) Q_DECL_NOTHROW
+{
+ m_shaderType = shaderType;
+}
+
+bool operator==(const QShaderFormat &lhs, const QShaderFormat &rhs) noexcept
+{
+ return lhs.api() == rhs.api()
+ && lhs.version() == rhs.version()
+ && lhs.extensions() == rhs.extensions()
+ && lhs.vendor() == rhs.vendor()
+ && lhs.shaderType() == rhs.shaderType();
+}
+}
+QT_END_NAMESPACE
diff --git a/src/render/shadergraph/qshaderformat_p.h b/src/render/shadergraph/qshaderformat_p.h
new file mode 100644
index 000000000..ad9898bd0
--- /dev/null
+++ b/src/render/shadergraph/qshaderformat_p.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** 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 QT3DRENDER_QSHADERFORMAT_P_H
+#define QT3DRENDER_QSHADERFORMAT_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 <Qt3DRender/private/qt3drender_global_p.h>
+
+#include <QtCore/qstringlist.h>
+#include <QtCore/qversionnumber.h>
+
+QT_BEGIN_NAMESPACE
+namespace Qt3DRender
+{
+class QShaderFormat
+{
+public:
+ enum Api : int {
+ NoApi,
+ OpenGLNoProfile,
+ OpenGLCoreProfile,
+ OpenGLCompatibilityProfile,
+ OpenGLES,
+ VulkanFlavoredGLSL,
+ RHI
+ };
+
+ enum ShaderType : int {
+ Vertex = 0,
+ TessellationControl,
+ TessellationEvaluation,
+ Geometry,
+ Fragment,
+ Compute
+ };
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QShaderFormat() noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT Api api() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setApi(Api api) noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QVersionNumber version() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setVersion(const QVersionNumber &version) noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QStringList extensions() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setExtensions(const QStringList &extensions) noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QString vendor() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setVendor(const QString &vendor) noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT bool isValid() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT bool supports(const QShaderFormat &other) const noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT ShaderType shaderType() const Q_DECL_NOTHROW;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setShaderType(ShaderType shaderType) Q_DECL_NOTHROW;
+
+private:
+ Api m_api;
+ QVersionNumber m_version;
+ QStringList m_extensions;
+ QString m_vendor;
+ ShaderType m_shaderType;
+};
+
+Q_3DRENDERSHARED_PRIVATE_EXPORT bool operator==(const QShaderFormat &lhs, const QShaderFormat &rhs) noexcept;
+
+inline bool operator!=(const QShaderFormat &lhs, const QShaderFormat &rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+
+
+}
+Q_DECLARE_TYPEINFO(Qt3DRender::QShaderFormat, Q_MOVABLE_TYPE);
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(Qt3DRender::QShaderFormat)
+
+#endif // QT3DRENDER_QSHADERFORMAT_P_H
diff --git a/src/render/shadergraph/qshadergenerator.cpp b/src/render/shadergraph/qshadergenerator.cpp
new file mode 100644
index 000000000..4f37cfef1
--- /dev/null
+++ b/src/render/shadergraph/qshadergenerator.cpp
@@ -0,0 +1,640 @@
+/****************************************************************************
+**
+** 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 "qshadergenerator_p.h"
+
+#include "qshaderlanguage_p.h"
+#include <QRegularExpression>
+
+#include <cctype>
+
+QT_BEGIN_NAMESPACE
+namespace Qt3DRender
+{
+Q_LOGGING_CATEGORY(ShaderGenerator, "ShaderGenerator", QtWarningMsg)
+
+namespace
+{
+ QByteArray toGlsl(QShaderLanguage::StorageQualifier qualifier, const QShaderFormat &format)
+ {
+ if (format.version().majorVersion() <= 2) {
+ // Note we're assuming fragment shader only here, it'd be different
+ // values for vertex shader, will need to be fixed properly at some
+ // point but isn't necessary yet (this problem already exists in past
+ // commits anyway)
+ switch (qualifier) {
+ case QShaderLanguage::Const:
+ return "const";
+ case QShaderLanguage::Input:
+ if (format.shaderType() == QShaderFormat::Vertex)
+ return "attribute";
+ else
+ return "varying";
+ case QShaderLanguage::Output:
+ return ""; // Although fragment shaders for <=2 only have fixed outputs
+ case QShaderLanguage::Uniform:
+ return "uniform";
+ case QShaderLanguage::BuiltIn:
+ return "//";
+ }
+ } else {
+ switch (qualifier) {
+ case QShaderLanguage::Const:
+ return "const";
+ case QShaderLanguage::Input:
+ return "in";
+ case QShaderLanguage::Output:
+ return "out";
+ case QShaderLanguage::Uniform:
+ return "uniform";
+ case QShaderLanguage::BuiltIn:
+ return "//";
+ }
+ }
+
+ Q_UNREACHABLE();
+ }
+
+ QByteArray toGlsl(QShaderLanguage::VariableType type)
+ {
+ switch (type) {
+ case QShaderLanguage::Bool:
+ return "bool";
+ case QShaderLanguage::Int:
+ return "int";
+ case QShaderLanguage::Uint:
+ return "uint";
+ case QShaderLanguage::Float:
+ return "float";
+ case QShaderLanguage::Double:
+ return "double";
+ case QShaderLanguage::Vec2:
+ return "vec2";
+ case QShaderLanguage::Vec3:
+ return "vec3";
+ case QShaderLanguage::Vec4:
+ return "vec4";
+ case QShaderLanguage::DVec2:
+ return "dvec2";
+ case QShaderLanguage::DVec3:
+ return "dvec3";
+ case QShaderLanguage::DVec4:
+ return "dvec4";
+ case QShaderLanguage::BVec2:
+ return "bvec2";
+ case QShaderLanguage::BVec3:
+ return "bvec3";
+ case QShaderLanguage::BVec4:
+ return "bvec4";
+ case QShaderLanguage::IVec2:
+ return "ivec2";
+ case QShaderLanguage::IVec3:
+ return "ivec3";
+ case QShaderLanguage::IVec4:
+ return "ivec4";
+ case QShaderLanguage::UVec2:
+ return "uvec2";
+ case QShaderLanguage::UVec3:
+ return "uvec3";
+ case QShaderLanguage::UVec4:
+ return "uvec4";
+ case QShaderLanguage::Mat2:
+ return "mat2";
+ case QShaderLanguage::Mat3:
+ return "mat3";
+ case QShaderLanguage::Mat4:
+ return "mat4";
+ case QShaderLanguage::Mat2x2:
+ return "mat2x2";
+ case QShaderLanguage::Mat2x3:
+ return "mat2x3";
+ case QShaderLanguage::Mat2x4:
+ return "mat2x4";
+ case QShaderLanguage::Mat3x2:
+ return "mat3x2";
+ case QShaderLanguage::Mat3x3:
+ return "mat3x3";
+ case QShaderLanguage::Mat3x4:
+ return "mat3x4";
+ case QShaderLanguage::Mat4x2:
+ return "mat4x2";
+ case QShaderLanguage::Mat4x3:
+ return "mat4x3";
+ case QShaderLanguage::Mat4x4:
+ return "mat4x4";
+ case QShaderLanguage::DMat2:
+ return "dmat2";
+ case QShaderLanguage::DMat3:
+ return "dmat3";
+ case QShaderLanguage::DMat4:
+ return "dmat4";
+ case QShaderLanguage::DMat2x2:
+ return "dmat2x2";
+ case QShaderLanguage::DMat2x3:
+ return "dmat2x3";
+ case QShaderLanguage::DMat2x4:
+ return "dmat2x4";
+ case QShaderLanguage::DMat3x2:
+ return "dmat3x2";
+ case QShaderLanguage::DMat3x3:
+ return "dmat3x3";
+ case QShaderLanguage::DMat3x4:
+ return "dmat3x4";
+ case QShaderLanguage::DMat4x2:
+ return "dmat4x2";
+ case QShaderLanguage::DMat4x3:
+ return "dmat4x3";
+ case QShaderLanguage::DMat4x4:
+ return "dmat4x4";
+ case QShaderLanguage::Sampler1D:
+ return "sampler1D";
+ case QShaderLanguage::Sampler2D:
+ return "sampler2D";
+ case QShaderLanguage::Sampler3D:
+ return "sampler3D";
+ case QShaderLanguage::SamplerCube:
+ return "samplerCube";
+ case QShaderLanguage::Sampler2DRect:
+ return "sampler2DRect";
+ case QShaderLanguage::Sampler2DMs:
+ return "sampler2DMS";
+ case QShaderLanguage::SamplerBuffer:
+ return "samplerBuffer";
+ case QShaderLanguage::Sampler1DArray:
+ return "sampler1DArray";
+ case QShaderLanguage::Sampler2DArray:
+ return "sampler2DArray";
+ case QShaderLanguage::Sampler2DMsArray:
+ return "sampler2DMSArray";
+ case QShaderLanguage::SamplerCubeArray:
+ return "samplerCubeArray";
+ case QShaderLanguage::Sampler1DShadow:
+ return "sampler1DShadow";
+ case QShaderLanguage::Sampler2DShadow:
+ return "sampler2DShadow";
+ case QShaderLanguage::Sampler2DRectShadow:
+ return "sampler2DRectShadow";
+ case QShaderLanguage::Sampler1DArrayShadow:
+ return "sampler1DArrayShadow";
+ case QShaderLanguage::Sampler2DArrayShadow:
+ return "sample2DArrayShadow";
+ case QShaderLanguage::SamplerCubeShadow:
+ return "samplerCubeShadow";
+ case QShaderLanguage::SamplerCubeArrayShadow:
+ return "samplerCubeArrayShadow";
+ case QShaderLanguage::ISampler1D:
+ return "isampler1D";
+ case QShaderLanguage::ISampler2D:
+ return "isampler2D";
+ case QShaderLanguage::ISampler3D:
+ return "isampler3D";
+ case QShaderLanguage::ISamplerCube:
+ return "isamplerCube";
+ case QShaderLanguage::ISampler2DRect:
+ return "isampler2DRect";
+ case QShaderLanguage::ISampler2DMs:
+ return "isampler2DMS";
+ case QShaderLanguage::ISamplerBuffer:
+ return "isamplerBuffer";
+ case QShaderLanguage::ISampler1DArray:
+ return "isampler1DArray";
+ case QShaderLanguage::ISampler2DArray:
+ return "isampler2DArray";
+ case QShaderLanguage::ISampler2DMsArray:
+ return "isampler2DMSArray";
+ case QShaderLanguage::ISamplerCubeArray:
+ return "isamplerCubeArray";
+ case QShaderLanguage::USampler1D:
+ return "usampler1D";
+ case QShaderLanguage::USampler2D:
+ return "usampler2D";
+ case QShaderLanguage::USampler3D:
+ return "usampler3D";
+ case QShaderLanguage::USamplerCube:
+ return "usamplerCube";
+ case QShaderLanguage::USampler2DRect:
+ return "usampler2DRect";
+ case QShaderLanguage::USampler2DMs:
+ return "usampler2DMS";
+ case QShaderLanguage::USamplerBuffer:
+ return "usamplerBuffer";
+ case QShaderLanguage::USampler1DArray:
+ return "usampler1DArray";
+ case QShaderLanguage::USampler2DArray:
+ return "usampler2DArray";
+ case QShaderLanguage::USampler2DMsArray:
+ return "usampler2DMSArray";
+ case QShaderLanguage::USamplerCubeArray:
+ return "usamplerCubeArray";
+ }
+
+ Q_UNREACHABLE();
+ }
+
+ QByteArray replaceParameters(const QByteArray &original, const QShaderNode &node, const QShaderFormat &format)
+ {
+ QByteArray result = original;
+
+ const QStringList parameterNames = node.parameterNames();
+ for (const QString &parameterName : parameterNames) {
+ const QByteArray placeholder = QByteArray(QByteArrayLiteral("$") + parameterName.toUtf8());
+ const QVariant parameter = node.parameter(parameterName);
+ if (parameter.userType() == qMetaTypeId<QShaderLanguage::StorageQualifier>()) {
+ const QShaderLanguage::StorageQualifier qualifier = qvariant_cast<QShaderLanguage::StorageQualifier>(parameter);
+ const QByteArray value = toGlsl(qualifier, format);
+ result.replace(placeholder, value);
+ } else if (parameter.userType() == qMetaTypeId<QShaderLanguage::VariableType>()) {
+ const QShaderLanguage::VariableType type = qvariant_cast<QShaderLanguage::VariableType>(parameter);
+ const QByteArray value = toGlsl(type);
+ result.replace(placeholder, value);
+ } else {
+ const QByteArray value = parameter.toString().toUtf8();
+ result.replace(placeholder, value);
+ }
+ }
+
+ return result;
+ }
+}
+
+QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers) const
+{
+ auto code = QByteArrayList();
+
+ if (format.isValid()) {
+ const bool isGLES = format.api() == QShaderFormat::OpenGLES;
+ const int major = format.version().majorVersion();
+ const int minor = format.version().minorVersion();
+
+ const int version = major == 2 && isGLES ? 100
+ : major == 3 && isGLES ? 300
+ : major == 2 ? 100 + 10 * (minor + 1)
+ : major == 3 && minor <= 2 ? 100 + 10 * (minor + 3)
+ : major * 100 + minor * 10
+ ;
+
+ const QByteArray profile = isGLES && version > 100 ? QByteArrayLiteral(" es")
+ : version >= 150 && format.api() == QShaderFormat::OpenGLCoreProfile ? QByteArrayLiteral(" core")
+ : version >= 150 && format.api() == QShaderFormat::OpenGLCompatibilityProfile ? QByteArrayLiteral(" compatibility")
+ : QByteArray();
+
+ code << (QByteArrayLiteral("#version ") + QByteArray::number(version) + profile);
+ code << QByteArray();
+ }
+
+ const auto intersectsEnabledLayers = [enabledLayers] (const QStringList &layers) {
+ return layers.isEmpty()
+ || std::any_of(layers.cbegin(), layers.cend(),
+ [enabledLayers] (const QString &s) { return enabledLayers.contains(s); });
+ };
+
+ QVector<QString> globalInputVariables;
+ const QRegularExpression globalInputExtractRegExp(QStringLiteral("^.*\\s+(\\w+).*;$"));
+
+ const QVector<QShaderNode> nodes = graph.nodes();
+ for (const QShaderNode &node : nodes) {
+ if (intersectsEnabledLayers(node.layers())) {
+ const QByteArrayList headerSnippets = node.rule(format).headerSnippets;
+ for (const QByteArray &snippet : headerSnippets) {
+ code << replaceParameters(snippet, node, format);
+
+ // If node is an input, record the variable name into the globalInputVariables vector
+ if (node.type() == QShaderNode::Input) {
+ const QRegularExpressionMatch match = globalInputExtractRegExp.match(QString::fromUtf8(code.last()));
+ if (match.hasMatch())
+ globalInputVariables.push_back(match.captured(1));
+ }
+ }
+ }
+ }
+
+ code << QByteArray();
+ code << QByteArrayLiteral("void main()");
+ code << QByteArrayLiteral("{");
+
+ const QRegularExpression temporaryVariableToAssignmentRegExp(QStringLiteral("([^;]*\\s+(v\\d+))\\s*=\\s*([^;]*);"));
+ const QRegularExpression temporaryVariableInAssignmentRegExp(QStringLiteral("\\W*(v\\d+)\\W*"));
+ const QRegularExpression statementRegExp(QStringLiteral("\\s*(\\w+)\\s*=\\s*([^;]*);"));
+
+ struct Variable;
+
+ struct Assignment
+ {
+ QString expression;
+ QVector<Variable *> referencedVariables;
+ };
+
+ struct Variable
+ {
+ enum Type {
+ GlobalInput,
+ TemporaryAssignment,
+ Output
+ };
+
+ QString name;
+ QString declaration;
+ int referenceCount = 0;
+ Assignment assignment;
+ Type type = TemporaryAssignment;
+ bool substituted = false;
+
+ static void substitute(Variable *v)
+ {
+ if (v->substituted)
+ return;
+
+ qCDebug(ShaderGenerator) << "Begin Substituting " << v->name << " = " << v->assignment.expression;
+ for (Variable *ref : qAsConst(v->assignment.referencedVariables)) {
+ // Recursively substitute
+ Variable::substitute(ref);
+
+ // Replace all variables referenced only once in the assignment
+ // by their actual expression
+ if (ref->referenceCount == 1 || ref->type == Variable::GlobalInput) {
+ const QRegularExpression r(QStringLiteral("(.*\\b)(%1)(\\b.*)").arg(ref->name));
+ if (v->assignment.referencedVariables.size() == 1)
+ v->assignment.expression.replace(r,
+ QStringLiteral("\\1%2\\3").arg(ref->assignment.expression));
+ else
+ v->assignment.expression.replace(r,
+ QStringLiteral("(\\1%2\\3)").arg(ref->assignment.expression));
+ }
+ }
+ qCDebug(ShaderGenerator) << "Done Substituting " << v->name << " = " << v->assignment.expression;
+ v->substituted = true;
+ }
+ };
+
+ struct LineContent
+ {
+ QByteArray rawContent;
+ Variable *var = nullptr;
+ };
+
+ // Table to store temporary variables that should be replaced:
+ // - If variable references a a global variables
+ // -> we will use the global variable directly
+ // - If variable references a function results
+ // -> will be kept only if variable is referenced more than once.
+ // This avoids having vec3 v56 = vertexPosition; when we could
+ // just use vertexPosition directly.
+ // The added benefit is when having arrays, we don't try to create
+ // mat4 v38 = skinningPalelette[100] which would be invalid
+ QVector<Variable> temporaryVariables;
+ // Reserve more than enough space to ensure no reallocation will take place
+ temporaryVariables.reserve(nodes.size() * 8);
+
+ QVector<LineContent> lines;
+
+ auto createVariable = [&] () -> Variable * {
+ Q_ASSERT(temporaryVariables.capacity() > 0);
+ temporaryVariables.resize(temporaryVariables.size() + 1);
+ return &temporaryVariables.last();
+ };
+
+ auto findVariable = [&] (const QString &name) -> Variable * {
+ const auto end = temporaryVariables.end();
+ auto it = std::find_if(temporaryVariables.begin(), end,
+ [=] (const Variable &a) { return a.name == name; });
+ if (it != end)
+ return &(*it);
+ return nullptr;
+ };
+
+ auto gatherTemporaryVariablesFromAssignment = [&] (Variable *v, const QString &assignmentContent) {
+ QRegularExpressionMatchIterator subMatchIt = temporaryVariableInAssignmentRegExp.globalMatch(assignmentContent);
+ while (subMatchIt.hasNext()) {
+ const QRegularExpressionMatch subMatch = subMatchIt.next();
+ const QString variableName = subMatch.captured(1);
+
+ // Variable we care about should already exists -> an expression cannot reference a variable that hasn't been defined
+ Variable *u = findVariable(variableName);
+ Q_ASSERT(u);
+
+ // Increase reference count for u
+ ++u->referenceCount;
+ // Insert u as reference for variable v
+ v->assignment.referencedVariables.push_back(u);
+ }
+ };
+
+ for (const QShaderGraph::Statement &statement : graph.createStatements(enabledLayers)) {
+ const QShaderNode node = statement.node;
+ QByteArray line = node.rule(format).substitution;
+ const QVector<QShaderNodePort> ports = node.ports();
+
+ struct VariableReplacement {
+ QByteArray placeholder;
+ QByteArray variable;
+ };
+
+ QVector<VariableReplacement> variableReplacements;
+
+ // Generate temporary variable names vN
+ for (const QShaderNodePort &port : ports) {
+ const QString portName = port.name;
+ const QShaderNodePort::Direction portDirection = port.direction;
+ const bool isInput = port.direction == QShaderNodePort::Input;
+
+ const int portIndex = statement.portIndex(portDirection, portName);
+
+ Q_ASSERT(portIndex >= 0);
+
+ const int variableIndex = isInput ? statement.inputs.at(portIndex)
+ : statement.outputs.at(portIndex);
+ if (variableIndex < 0)
+ continue;
+
+ VariableReplacement replacement;
+ replacement.placeholder = QByteArrayLiteral("$") + portName.toUtf8();
+ replacement.variable = QByteArrayLiteral("v") + QByteArray::number(variableIndex);
+
+ variableReplacements.append(std::move(replacement));
+ }
+
+ int begin = 0;
+ while ((begin = line.indexOf('$', begin)) != -1) {
+ int end = begin + 1;
+ char endChar = line.at(end);
+ const int size = line.size();
+ while (end < size && (std::isalnum(endChar) || endChar == '_')) {
+ ++end;
+ endChar = line.at(end);
+ }
+
+ const int placeholderLength = end - begin;
+
+ const QByteArray variableName = line.mid(begin, placeholderLength);
+ const auto replacementIt = std::find_if(variableReplacements.cbegin(), variableReplacements.cend(),
+ [&variableName](const VariableReplacement &replacement) {
+ return variableName == replacement.placeholder;
+ });
+
+ if (replacementIt != variableReplacements.cend()) {
+ line.replace(begin, placeholderLength, replacementIt->variable);
+ begin += replacementIt->variable.length();
+ } else {
+ begin = end;
+ }
+ }
+
+ // Substitute variable names by generated vN variable names
+ const QByteArray substitutionedLine = replaceParameters(line, node, format);
+
+ QRegularExpressionMatchIterator matches;
+
+ switch (node.type()) {
+ case QShaderNode::Input:
+ case QShaderNode::Output:
+ matches = statementRegExp.globalMatch(QString::fromUtf8(substitutionedLine));
+ break;
+ case QShaderNode::Function:
+ matches = temporaryVariableToAssignmentRegExp.globalMatch(QString::fromUtf8(substitutionedLine));
+ break;
+ case QShaderNode::Invalid:
+ break;
+ }
+
+ while (matches.hasNext()) {
+ QRegularExpressionMatch match = matches.next();
+
+ Variable *v = nullptr;
+
+ switch (node.type()) {
+ // Record name of temporary variable that possibly references a global input
+ // We will replace the temporary variables by the matching global variables later
+ case QShaderNode::Input: {
+ const QString localVariable = match.captured(1);
+ const QString globalVariable = match.captured(2);
+
+ v = createVariable();
+ v->name = localVariable;
+ v->type = Variable::GlobalInput;
+
+ Assignment assignment;
+ assignment.expression = globalVariable;
+ v->assignment = assignment;
+ break;
+ }
+
+ case QShaderNode::Function: {
+ const QString localVariableDeclaration = match.captured(1);
+ const QString localVariableName = match.captured(2);
+ const QString assignmentContent = match.captured(3);
+
+ // Add new variable -> it cannot exist already
+ v = createVariable();
+ v->name = localVariableName;
+ v->declaration = localVariableDeclaration;
+ v->assignment.expression = assignmentContent;
+
+ // Find variables that may be referenced in the assignment
+ gatherTemporaryVariablesFromAssignment(v, assignmentContent);
+ break;
+ }
+
+ case QShaderNode::Output: {
+ const QString outputDeclaration = match.captured(1);
+ const QString assignmentContent = match.captured(2);
+
+ v = createVariable();
+ v->name = outputDeclaration;
+ v->declaration = outputDeclaration;
+ v->type = Variable::Output;
+
+ Assignment assignment;
+ assignment.expression = assignmentContent;
+ v->assignment = assignment;
+
+ // Find variables that may be referenced in the assignment
+ gatherTemporaryVariablesFromAssignment(v, assignmentContent);
+ break;
+ }
+ case QShaderNode::Invalid:
+ break;
+ }
+
+ LineContent lineContent;
+ lineContent.rawContent = QByteArray(QByteArrayLiteral(" ") + substitutionedLine);
+ lineContent.var = v;
+ lines << lineContent;
+ }
+ }
+
+ // Go through all lines
+ // Perform substitution of line with temporary variables substitution
+ for (LineContent &lineContent : lines) {
+ Variable *v = lineContent.var;
+ qCDebug(ShaderGenerator) << lineContent.rawContent;
+ if (v != nullptr) {
+ Variable::substitute(v);
+
+ qCDebug(ShaderGenerator) << "Line " << lineContent.rawContent << "is assigned to temporary" << v->name;
+
+ // Check number of occurrences a temporary variable is referenced
+ if (v->referenceCount == 1 || v->type == Variable::GlobalInput) {
+ // If it is referenced only once, no point in creating a temporary
+ // Clear content for current line
+ lineContent.rawContent.clear();
+ // We assume expression that were referencing vN will have vN properly substituted
+ } else {
+ lineContent.rawContent = QStringLiteral(" %1 = %2;").arg(v->declaration)
+ .arg(v->assignment.expression)
+ .toUtf8();
+ }
+
+ qCDebug(ShaderGenerator) << "Updated Line is " << lineContent.rawContent;
+ }
+ }
+
+ // Go throug all lines and insert content
+ for (const LineContent &lineContent : qAsConst(lines)) {
+ if (!lineContent.rawContent.isEmpty()) {
+ code << lineContent.rawContent;
+ }
+ }
+
+ code << QByteArrayLiteral("}");
+ code << QByteArray();
+
+ return code.join('\n');
+}
+}
+QT_END_NAMESPACE
diff --git a/src/render/shadergraph/qshadergenerator_p.h b/src/render/shadergraph/qshadergenerator_p.h
new file mode 100644
index 000000000..aebeaa8f2
--- /dev/null
+++ b/src/render/shadergraph/qshadergenerator_p.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** 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 QT3DRENDER_QSHADERGENERATOR_P_H
+#define QT3DRENDER_QSHADERGENERATOR_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 <Qt3DRender/private/qt3drender_global_p.h>
+
+#include <Qt3DRender/private/qshadergraph_p.h>
+#include <QtCore/QLoggingCategory>
+
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender
+{
+Q_DECLARE_LOGGING_CATEGORY(ShaderGenerator)
+
+class QShaderGenerator
+{
+public:
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QByteArray createShaderCode(const QStringList &enabledLayers = QStringList()) const;
+
+ QShaderGraph graph;
+ QShaderFormat format;
+};
+
+}
+Q_DECLARE_TYPEINFO(Qt3DRender::QShaderGenerator, Q_MOVABLE_TYPE);
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(Qt3DRender::QShaderGenerator)
+
+#endif // QT3DRENDER_QSHADERGENERATOR_P_H
diff --git a/src/render/shadergraph/qshadergraph.cpp b/src/render/shadergraph/qshadergraph.cpp
new file mode 100644
index 000000000..c2f3c343e
--- /dev/null
+++ b/src/render/shadergraph/qshadergraph.cpp
@@ -0,0 +1,317 @@
+/****************************************************************************
+**
+** 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 "qshadergraph_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace Qt3DRender
+{
+
+namespace
+{
+ QVector<QShaderNode> copyOutputNodes(const QVector<QShaderNode> &nodes, const QVector<QShaderGraph::Edge> &edges)
+ {
+ auto res = QVector<QShaderNode>();
+ std::copy_if(nodes.cbegin(), nodes.cend(),
+ std::back_inserter(res),
+ [&edges] (const QShaderNode &node) {
+ return node.type() == QShaderNode::Output ||
+ (node.type() == QShaderNode::Function &&
+ !std::any_of(edges.cbegin(),
+ edges.cend(),
+ [&node] (const QShaderGraph::Edge &edge) {
+ return edge.sourceNodeUuid ==
+ node.uuid();
+ }));
+ });
+ return res;
+ }
+
+ QVector<QShaderGraph::Edge> incomingEdges(const QVector<QShaderGraph::Edge> &edges, const QUuid &uuid)
+ {
+ auto res = QVector<QShaderGraph::Edge>();
+ std::copy_if(edges.cbegin(), edges.cend(),
+ std::back_inserter(res),
+ [uuid] (const QShaderGraph::Edge &edge) {
+ return edge.sourceNodeUuid == uuid;
+ });
+ return res;
+ }
+
+ QVector<QShaderGraph::Edge> outgoingEdges(const QVector<QShaderGraph::Edge> &edges, const QUuid &uuid)
+ {
+ auto res = QVector<QShaderGraph::Edge>();
+ std::copy_if(edges.cbegin(), edges.cend(),
+ std::back_inserter(res),
+ [uuid] (const QShaderGraph::Edge &edge) {
+ return edge.targetNodeUuid == uuid;
+ });
+ return res;
+ }
+
+ QShaderGraph::Statement nodeToStatement(const QShaderNode &node, int &nextVarId)
+ {
+ auto statement = QShaderGraph::Statement();
+ statement.node = node;
+
+ const QVector<QShaderNodePort> ports = node.ports();
+ for (const QShaderNodePort &port : ports) {
+ if (port.direction == QShaderNodePort::Input) {
+ statement.inputs.append(-1);
+ } else {
+ statement.outputs.append(nextVarId);
+ nextVarId++;
+ }
+ }
+ return statement;
+ }
+
+ QShaderGraph::Statement completeStatement(const QHash<QUuid, QShaderGraph::Statement> &idHash,
+ const QVector<QShaderGraph::Edge> edges,
+ const QUuid &uuid)
+ {
+ auto targetStatement = idHash.value(uuid);
+ for (const QShaderGraph::Edge &edge : edges) {
+ if (edge.targetNodeUuid != uuid)
+ continue;
+
+ const QShaderGraph::Statement sourceStatement = idHash.value(edge.sourceNodeUuid);
+ const int sourcePortIndex = sourceStatement.portIndex(QShaderNodePort::Output, edge.sourcePortName);
+ const int targetPortIndex = targetStatement.portIndex(QShaderNodePort::Input, edge.targetPortName);
+
+ if (sourcePortIndex < 0 || targetPortIndex < 0)
+ continue;
+
+ const QVector<int> sourceOutputs = sourceStatement.outputs;
+ QVector<int> &targetInputs = targetStatement.inputs;
+ targetInputs[targetPortIndex] = sourceOutputs[sourcePortIndex];
+ }
+ return targetStatement;
+ }
+
+ void removeNodesWithUnboundInputs(QVector<QShaderGraph::Statement> &statements,
+ const QVector<QShaderGraph::Edge> &allEdges)
+ {
+ // A node is invalid if any of its input ports is disconected
+ // or connected to the output port of another invalid node.
+
+ // Keeps track of the edges from the nodes we know to be valid
+ // to unvisited nodes
+ auto currentEdges = QVector<QShaderGraph::Edge>();
+
+ statements.erase(std::remove_if(statements.begin(),
+ statements.end(),
+ [&currentEdges, &allEdges] (const QShaderGraph::Statement &statement) {
+ const QShaderNode &node = statement.node;
+ const QVector<QShaderGraph::Edge> outgoing = outgoingEdges(currentEdges, node.uuid());
+ const QVector<QShaderNodePort> ports = node.ports();
+
+ bool allInputsConnected = true;
+ for (const QShaderNodePort &port : node.ports()) {
+ if (port.direction == QShaderNodePort::Output)
+ continue;
+
+ const auto edgeIt = std::find_if(outgoing.cbegin(),
+ outgoing.cend(),
+ [&port] (const QShaderGraph::Edge &edge) {
+ return edge.targetPortName == port.name;
+ });
+
+ if (edgeIt != outgoing.cend())
+ currentEdges.removeAll(*edgeIt);
+ else
+ allInputsConnected = false;
+ }
+
+ if (allInputsConnected) {
+ const QVector<QShaderGraph::Edge> incoming = incomingEdges(allEdges, node.uuid());
+ currentEdges.append(incoming);
+ }
+
+ return !allInputsConnected;
+ }),
+ statements.end());
+ }
+}
+
+QUuid QShaderGraph::Statement::uuid() const noexcept
+{
+ return node.uuid();
+}
+
+int QShaderGraph::Statement::portIndex(QShaderNodePort::Direction direction, const QString &portName) const noexcept
+{
+ const QVector<QShaderNodePort> ports = node.ports();
+ int index = 0;
+ for (const QShaderNodePort &port : ports) {
+ if (port.name == portName && port.direction == direction)
+ return index;
+ else if (port.direction == direction)
+ index++;
+ }
+ return -1;
+}
+
+void QShaderGraph::addNode(const QShaderNode &node)
+{
+ removeNode(node);
+ m_nodes.append(node);
+}
+
+void QShaderGraph::removeNode(const QShaderNode &node)
+{
+ const auto it = std::find_if(m_nodes.begin(), m_nodes.end(),
+ [node] (const QShaderNode &n) { return n.uuid() == node.uuid(); });
+ if (it != m_nodes.end())
+ m_nodes.erase(it);
+}
+
+QVector<QShaderNode> QShaderGraph::nodes() const noexcept
+{
+ return m_nodes;
+}
+
+void QShaderGraph::addEdge(const QShaderGraph::Edge &edge)
+{
+ if (m_edges.contains(edge))
+ return;
+ m_edges.append(edge);
+}
+
+void QShaderGraph::removeEdge(const QShaderGraph::Edge &edge)
+{
+ m_edges.removeAll(edge);
+}
+
+QVector<QShaderGraph::Edge> QShaderGraph::edges() const noexcept
+{
+ return m_edges;
+}
+
+QVector<QShaderGraph::Statement> QShaderGraph::createStatements(const QStringList &enabledLayers) const
+{
+ const auto intersectsEnabledLayers = [enabledLayers] (const QStringList &layers) {
+ return layers.isEmpty()
+ || std::any_of(layers.cbegin(), layers.cend(),
+ [enabledLayers] (const QString &s) { return enabledLayers.contains(s); });
+ };
+
+ const QVector<QShaderNode> enabledNodes = [this, intersectsEnabledLayers] {
+ auto res = QVector<QShaderNode>();
+ std::copy_if(m_nodes.cbegin(), m_nodes.cend(),
+ std::back_inserter(res),
+ [intersectsEnabledLayers] (const QShaderNode &node) {
+ return intersectsEnabledLayers(node.layers());
+ });
+ return res;
+ }();
+
+ const QVector<Edge> enabledEdges = [this, intersectsEnabledLayers] {
+ auto res = QVector<Edge>();
+ std::copy_if(m_edges.cbegin(), m_edges.cend(),
+ std::back_inserter(res),
+ [intersectsEnabledLayers] (const Edge &edge) {
+ return intersectsEnabledLayers(edge.layers);
+ });
+ return res;
+ }();
+
+ const QHash<QUuid, Statement> idHash = [enabledNodes] {
+ auto nextVarId = 0;
+ auto res = QHash<QUuid, Statement>();
+ for (const QShaderNode &node : enabledNodes)
+ res.insert(node.uuid(), nodeToStatement(node, nextVarId));
+ return res;
+ }();
+
+ auto result = QVector<Statement>();
+ QVector<Edge> currentEdges = enabledEdges;
+ QVector<QUuid> currentUuids = [enabledNodes, enabledEdges] {
+ const QVector<QShaderNode> inputs = copyOutputNodes(enabledNodes, enabledEdges);
+ auto res = QVector<QUuid>();
+ std::transform(inputs.cbegin(), inputs.cend(),
+ std::back_inserter(res),
+ [](const QShaderNode &node) { return node.uuid(); });
+ return res;
+ }();
+
+ // Implements Kahn's algorithm to flatten the graph
+ // https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
+ //
+ // We implement it with a small twist though, we follow the edges backward
+ // because we want to track the dependencies from the output nodes and not the
+ // input nodes
+ while (!currentUuids.isEmpty()) {
+ const QUuid uuid = currentUuids.takeFirst();
+ result.append(completeStatement(idHash, enabledEdges, uuid));
+
+ const QVector<QShaderGraph::Edge> outgoing = outgoingEdges(currentEdges, uuid);
+ for (const QShaderGraph::Edge &outgoingEdge : outgoing) {
+ currentEdges.removeAll(outgoingEdge);
+ const QUuid nextUuid = outgoingEdge.sourceNodeUuid;
+ const QVector<QShaderGraph::Edge> incoming = incomingEdges(currentEdges, nextUuid);
+ if (incoming.isEmpty()) {
+ currentUuids.append(nextUuid);
+ }
+ }
+ }
+
+ std::reverse(result.begin(), result.end());
+
+ removeNodesWithUnboundInputs(result, enabledEdges);
+
+ return result;
+}
+
+bool operator==(const QShaderGraph::Edge &lhs, const QShaderGraph::Edge &rhs) noexcept
+{
+ return lhs.sourceNodeUuid == rhs.sourceNodeUuid
+ && lhs.sourcePortName == rhs.sourcePortName
+ && lhs.targetNodeUuid == rhs.targetNodeUuid
+ && lhs.targetPortName == rhs.targetPortName;
+}
+
+bool operator==(const QShaderGraph::Statement &lhs, const QShaderGraph::Statement &rhs) noexcept
+{
+ return lhs.inputs == rhs.inputs
+ && lhs.outputs == rhs.outputs
+ && lhs.node.uuid() == rhs.node.uuid();
+}
+}
+QT_END_NAMESPACE
diff --git a/src/render/shadergraph/qshadergraph_p.h b/src/render/shadergraph/qshadergraph_p.h
new file mode 100644
index 000000000..de746f7b2
--- /dev/null
+++ b/src/render/shadergraph/qshadergraph_p.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** 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 QT3DRENDER_QSHADERGRAPH_P_H
+#define QT3DRENDER_QSHADERGRAPH_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 <Qt3DRender/private/qt3drender_global_p.h>
+
+#include <Qt3DRender/private/qshadernode_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender
+{
+class QShaderGraph
+{
+public:
+ class Edge
+ {
+ public:
+ QStringList layers;
+ QUuid sourceNodeUuid;
+ QString sourcePortName;
+ QUuid targetNodeUuid;
+ QString targetPortName;
+ };
+
+ class Statement
+ {
+ public:
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QUuid uuid() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT int portIndex(QShaderNodePort::Direction direction, const QString &portName) const noexcept;
+
+ QShaderNode node;
+ QVector<int> inputs;
+ QVector<int> outputs;
+ };
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void addNode(const QShaderNode &node);
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void removeNode(const QShaderNode &node);
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QVector<QShaderNode> nodes() const noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void addEdge(const Edge &edge);
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void removeEdge(const Edge &edge);
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QVector<Edge> edges() const noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QVector<Statement> createStatements(const QStringList &enabledLayers = QStringList()) const;
+
+private:
+ QVector<QShaderNode> m_nodes;
+ QVector<Edge> m_edges;
+};
+
+Q_3DRENDERSHARED_PRIVATE_EXPORT bool operator==(const QShaderGraph::Edge &lhs, const QShaderGraph::Edge &rhs) noexcept;
+
+inline bool operator!=(const QShaderGraph::Edge &lhs, const QShaderGraph::Edge &rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+
+Q_3DRENDERSHARED_PRIVATE_EXPORT bool operator==(const QShaderGraph::Statement &lhs, const QShaderGraph::Statement &rhs) noexcept;
+
+inline bool operator!=(const QShaderGraph::Statement &lhs, const QShaderGraph::Statement &rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+
+}
+
+Q_DECLARE_TYPEINFO(Qt3DRender::QShaderGraph, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(Qt3DRender::QShaderGraph::Edge, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(Qt3DRender::QShaderGraph::Statement, Q_MOVABLE_TYPE);
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(Qt3DRender::QShaderGraph)
+Q_DECLARE_METATYPE(Qt3DRender::QShaderGraph::Edge)
+Q_DECLARE_METATYPE(Qt3DRender::QShaderGraph::Statement)
+
+#endif // QT3DRENDER_QSHADERGRAPH_P_H
diff --git a/src/render/shadergraph/qshadergraphloader.cpp b/src/render/shadergraph/qshadergraphloader.cpp
new file mode 100644
index 000000000..64f159e7b
--- /dev/null
+++ b/src/render/shadergraph/qshadergraphloader.cpp
@@ -0,0 +1,271 @@
+/****************************************************************************
+**
+** 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 "qshadernodesloader_p.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qmetaobject.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender
+{
+void qt_register_ShaderLanguage_enums();
+
+QShaderGraphLoader::QShaderGraphLoader() noexcept
+ : m_status(Null),
+ m_device(nullptr)
+{
+ qt_register_ShaderLanguage_enums();
+}
+
+QShaderGraphLoader::Status QShaderGraphLoader::status() const noexcept
+{
+ return m_status;
+}
+
+QShaderGraph QShaderGraphLoader::graph() const noexcept
+{
+ return m_graph;
+}
+
+QIODevice *QShaderGraphLoader::device() const noexcept
+{
+ return m_device;
+}
+
+void QShaderGraphLoader::setDevice(QIODevice *device) noexcept
+{
+ m_device = device;
+ m_graph = QShaderGraph();
+ m_status = !m_device ? Null
+ : (m_device->openMode() & QIODevice::ReadOnly) ? Waiting
+ : Error;
+}
+
+QHash<QString, QShaderNode> QShaderGraphLoader::prototypes() const noexcept
+{
+ return m_prototypes;
+}
+
+void QShaderGraphLoader::setPrototypes(const QHash<QString, QShaderNode> &prototypes) noexcept
+{
+ m_prototypes = prototypes;
+}
+
+void QShaderGraphLoader::load()
+{
+ if (m_status == Error)
+ return;
+
+ auto error = QJsonParseError();
+ const QJsonDocument 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 QJsonObject root = document.object();
+
+ const QJsonValue nodesValue = root.value(QStringLiteral("nodes"));
+ if (!nodesValue.isArray()) {
+ qWarning() << "Invalid nodes property, should be an array";
+ m_status = Error;
+ return;
+ }
+
+ const QJsonValue edgesValue = root.value(QStringLiteral("edges"));
+ if (!edgesValue.isArray()) {
+ qWarning() << "Invalid edges property, should be an array";
+ m_status = Error;
+ return;
+ }
+
+ bool hasError = false;
+
+ const QJsonValue prototypesValue = root.value(QStringLiteral("prototypes"));
+ if (!prototypesValue.isUndefined()) {
+ if (prototypesValue.isObject()) {
+ QShaderNodesLoader loader;
+ loader.load(prototypesValue.toObject());
+ m_prototypes.insert(loader.nodes());
+ } else {
+ qWarning() << "Invalid prototypes property, should be an object";
+ m_status = Error;
+ return;
+ }
+ }
+
+ const QJsonArray nodes = nodesValue.toArray();
+ for (const QJsonValue &nodeValue : nodes) {
+ if (!nodeValue.isObject()) {
+ qWarning() << "Invalid node found";
+ hasError = true;
+ continue;
+ }
+
+ const QJsonObject nodeObject = nodeValue.toObject();
+
+ const QString uuidString = nodeObject.value(QStringLiteral("uuid")).toString();
+ const QUuid uuid = QUuid(uuidString);
+ if (uuid.isNull()) {
+ qWarning() << "Invalid UUID found in node:" << uuidString;
+ hasError = true;
+ continue;
+ }
+
+ const QString type = nodeObject.value(QStringLiteral("type")).toString();
+ if (!m_prototypes.contains(type)) {
+ qWarning() << "Unsupported node type found:" << type;
+ hasError = true;
+ continue;
+ }
+
+ const QJsonArray layersArray = nodeObject.value(QStringLiteral("layers")).toArray();
+ auto layers = QStringList();
+ for (const QJsonValue &layerValue : layersArray) {
+ layers.append(layerValue.toString());
+ }
+
+ QShaderNode node = m_prototypes.value(type);
+ node.setUuid(uuid);
+ node.setLayers(layers);
+
+ const QJsonValue parametersValue = nodeObject.value(QStringLiteral("parameters"));
+ if (parametersValue.isObject()) {
+ const QJsonObject parametersObject = parametersValue.toObject();
+ for (const QString &parameterName : parametersObject.keys()) {
+ const QJsonValue parameterValue = parametersObject.value(parameterName);
+ if (parameterValue.isObject()) {
+ const QJsonObject parameterObject = parameterValue.toObject();
+ const QString type = parameterObject.value(QStringLiteral("type")).toString();
+ const int typeId = QMetaType::type(type.toUtf8());
+
+ const QString value = parameterObject.value(QStringLiteral("value")).toString();
+ auto variant = QVariant(value);
+
+ if (QMetaType::typeFlags(typeId) & QMetaType::IsEnumeration) {
+ const QMetaObject *metaObject = QMetaType::metaObjectForType(typeId);
+ const char *className = metaObject->className();
+ const QByteArray enumName = type.mid(static_cast<int>(qstrlen(className)) + 2).toUtf8();
+ const QMetaEnum metaEnum = metaObject->enumerator(metaObject->indexOfEnumerator(enumName));
+ const int enumValue = metaEnum.keyToValue(value.toUtf8());
+ variant = QVariant(enumValue);
+ variant.convert(typeId);
+ } else {
+ variant.convert(typeId);
+ }
+ node.setParameter(parameterName, variant);
+ } else {
+ node.setParameter(parameterName, parameterValue.toVariant());
+ }
+ }
+ }
+
+ m_graph.addNode(node);
+ }
+
+ const QJsonArray edges = edgesValue.toArray();
+ for (const QJsonValue &edgeValue : edges) {
+ if (!edgeValue.isObject()) {
+ qWarning() << "Invalid edge found";
+ hasError = true;
+ continue;
+ }
+
+ const QJsonObject edgeObject = edgeValue.toObject();
+
+ const QString sourceUuidString = edgeObject.value(QStringLiteral("sourceUuid")).toString();
+ const QUuid sourceUuid = QUuid(sourceUuidString);
+ if (sourceUuid.isNull()) {
+ qWarning() << "Invalid source UUID found in edge:" << sourceUuidString;
+ hasError = true;
+ continue;
+ }
+
+ const QString sourcePort = edgeObject.value(QStringLiteral("sourcePort")).toString();
+
+ const QString targetUuidString = edgeObject.value(QStringLiteral("targetUuid")).toString();
+ const QUuid targetUuid = QUuid(targetUuidString);
+ if (targetUuid.isNull()) {
+ qWarning() << "Invalid target UUID found in edge:" << targetUuidString;
+ hasError = true;
+ continue;
+ }
+
+ const QString targetPort = edgeObject.value(QStringLiteral("targetPort")).toString();
+
+ const QJsonArray layersArray = edgeObject.value(QStringLiteral("layers")).toArray();
+ auto layers = QStringList();
+ for (const QJsonValue &layerValue : layersArray) {
+ layers.append(layerValue.toString());
+ }
+
+ auto edge = QShaderGraph::Edge();
+ edge.sourceNodeUuid = sourceUuid;
+ edge.sourcePortName = sourcePort;
+ edge.targetNodeUuid = targetUuid;
+ edge.targetPortName = targetPort;
+ edge.layers = layers;
+ m_graph.addEdge(edge);
+ }
+
+ if (hasError) {
+ m_status = Error;
+ m_graph = QShaderGraph();
+ } else {
+ m_status = Ready;
+ }
+}
+}
+QT_END_NAMESPACE
diff --git a/src/render/shadergraph/qshadergraphloader_p.h b/src/render/shadergraph/qshadergraphloader_p.h
new file mode 100644
index 000000000..20589d724
--- /dev/null
+++ b/src/render/shadergraph/qshadergraphloader_p.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** 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 QT3DRENDER_QSHADERGRAPHLOADER_P_H
+#define QT3DRENDER_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 <Qt3DRender/private/qt3drender_global_p.h>
+
+#include <Qt3DRender/private/qshadergraph_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QIODevice;
+namespace Qt3DRender
+{
+
+class QShaderGraphLoader
+{
+public:
+ enum Status : char {
+ Null,
+ Waiting,
+ Ready,
+ Error
+ };
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QShaderGraphLoader() noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT Status status() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QShaderGraph graph() const noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QIODevice *device() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setDevice(QIODevice *device) noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QHash<QString, QShaderNode> prototypes() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setPrototypes(const QHash<QString, QShaderNode> &prototypes) noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void load();
+
+private:
+ Status m_status;
+ QIODevice *m_device;
+ QHash<QString, QShaderNode> m_prototypes;
+ QShaderGraph m_graph;
+};
+
+
+}
+Q_DECLARE_TYPEINFO(Qt3DRender::QShaderGraphLoader, Q_MOVABLE_TYPE);
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(Qt3DRender::QShaderGraphLoader)
+Q_DECLARE_METATYPE(Qt3DRender::QShaderGraphLoader::Status)
+
+#endif // QT3DRENDER_QSHADERGRAPHLOADER_P_H
diff --git a/src/render/shadergraph/qshaderlanguage.cpp b/src/render/shadergraph/qshaderlanguage.cpp
new file mode 100644
index 000000000..1630e3de8
--- /dev/null
+++ b/src/render/shadergraph/qshaderlanguage.cpp
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** 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 "qshaderlanguage_p.h"
+
+#include <QtCore/qcoreapplication.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender
+{
+// Note: to be invoked explicitly. Relying for example on
+// Q_COREAPP_STARTUP_FUNCTION would not be acceptable in static builds.
+void qt_register_ShaderLanguage_enums()
+{
+ qRegisterMetaType<QShaderLanguage::StorageQualifier>();
+ qRegisterMetaType<QShaderLanguage::VariableType>();
+}
+}
+
+QT_END_NAMESPACE
diff --git a/src/render/shadergraph/qshaderlanguage_p.h b/src/render/shadergraph/qshaderlanguage_p.h
new file mode 100644
index 000000000..1a56d7476
--- /dev/null
+++ b/src/render/shadergraph/qshaderlanguage_p.h
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** 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 QT3DRENDER_QSHADERLANGUAGE_P_H
+#define QT3DRENDER_QSHADERLANGUAGE_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 <Qt3DRender/private/qt3drender_global_p.h>
+
+#include <QtCore/qmetatype.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QShaderLanguage
+{
+ Q_NAMESPACE_EXPORT(Q_3DRENDERSHARED_PRIVATE_EXPORT)
+
+ enum StorageQualifier : char {
+ Const = 1,
+ Input,
+ BuiltIn,
+ Output,
+ Uniform
+ };
+ Q_ENUM_NS(StorageQualifier)
+
+ enum VariableType : int {
+ Bool = 1,
+ Int,
+ Uint,
+ Float,
+ Double,
+ Vec2,
+ Vec3,
+ Vec4,
+ DVec2,
+ DVec3,
+ DVec4,
+ BVec2,
+ BVec3,
+ BVec4,
+ IVec2,
+ IVec3,
+ IVec4,
+ UVec2,
+ UVec3,
+ UVec4,
+ Mat2,
+ Mat3,
+ Mat4,
+ Mat2x2,
+ Mat2x3,
+ Mat2x4,
+ Mat3x2,
+ Mat3x3,
+ Mat3x4,
+ Mat4x2,
+ Mat4x3,
+ Mat4x4,
+ DMat2,
+ DMat3,
+ DMat4,
+ DMat2x2,
+ DMat2x3,
+ DMat2x4,
+ DMat3x2,
+ DMat3x3,
+ DMat3x4,
+ DMat4x2,
+ DMat4x3,
+ DMat4x4,
+ Sampler1D,
+ Sampler2D,
+ Sampler3D,
+ SamplerCube,
+ Sampler2DRect,
+ Sampler2DMs,
+ SamplerBuffer,
+ Sampler1DArray,
+ Sampler2DArray,
+ Sampler2DMsArray,
+ SamplerCubeArray,
+ Sampler1DShadow,
+ Sampler2DShadow,
+ Sampler2DRectShadow,
+ Sampler1DArrayShadow,
+ Sampler2DArrayShadow,
+ SamplerCubeShadow,
+ SamplerCubeArrayShadow,
+ ISampler1D,
+ ISampler2D,
+ ISampler3D,
+ ISamplerCube,
+ ISampler2DRect,
+ ISampler2DMs,
+ ISamplerBuffer,
+ ISampler1DArray,
+ ISampler2DArray,
+ ISampler2DMsArray,
+ ISamplerCubeArray,
+ USampler1D,
+ USampler2D,
+ USampler3D,
+ USamplerCube,
+ USampler2DRect,
+ USampler2DMs,
+ USamplerBuffer,
+ USampler1DArray,
+ USampler2DArray,
+ USampler2DMsArray,
+ USamplerCubeArray
+ };
+ Q_ENUM_NS(VariableType)
+}
+
+QT_END_NAMESPACE
+
+#endif // QT3DRENDER_QSHADERLANGUAGE_P_H
diff --git a/src/render/shadergraph/qshadernode.cpp b/src/render/shadergraph/qshadernode.cpp
new file mode 100644
index 000000000..e0421e006
--- /dev/null
+++ b/src/render/shadergraph/qshadernode.cpp
@@ -0,0 +1,174 @@
+/****************************************************************************
+**
+** 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 "qshadernode_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender
+{
+QShaderNode::Type QShaderNode::type() const noexcept
+{
+ int inputCount = 0;
+ int outputCount = 0;
+ for (const auto &port : qAsConst(m_ports)) {
+ switch (port.direction) {
+ case QShaderNodePort::Input:
+ inputCount++;
+ break;
+ case QShaderNodePort::Output:
+ outputCount++;
+ break;
+ }
+ }
+
+ return (inputCount == 0 && outputCount == 0) ? Invalid
+ : (inputCount > 0 && outputCount == 0) ? Output
+ : (inputCount == 0 && outputCount > 0) ? Input
+ : Function;
+}
+
+QUuid QShaderNode::uuid() const noexcept
+{
+ return m_uuid;
+}
+
+void QShaderNode::setUuid(const QUuid &uuid) noexcept
+{
+ m_uuid = uuid;
+}
+
+QStringList QShaderNode::layers() const noexcept
+{
+ return m_layers;
+}
+
+void QShaderNode::setLayers(const QStringList &layers) noexcept
+{
+ m_layers = layers;
+}
+
+QVector<QShaderNodePort> QShaderNode::ports() const noexcept
+{
+ return m_ports;
+}
+
+void QShaderNode::addPort(const QShaderNodePort &port)
+{
+ removePort(port);
+ m_ports.append(port);
+}
+
+void QShaderNode::removePort(const QShaderNodePort &port)
+{
+ const auto it = std::find_if(m_ports.begin(), m_ports.end(),
+ [port](const QShaderNodePort &p) {
+ return p.name == port.name;
+ });
+ if (it != m_ports.end())
+ m_ports.erase(it);
+}
+
+QStringList QShaderNode::parameterNames() const
+{
+ return m_parameters.keys();
+}
+
+QVariant QShaderNode::parameter(const QString &name) const
+{
+ return m_parameters.value(name);
+}
+
+void QShaderNode::setParameter(const QString &name, const QVariant &value)
+{
+ m_parameters.insert(name, value);
+}
+
+void QShaderNode::clearParameter(const QString &name)
+{
+ m_parameters.remove(name);
+}
+
+void QShaderNode::addRule(const QShaderFormat &format, const QShaderNode::Rule &rule)
+{
+ removeRule(format);
+ m_rules << qMakePair(format, rule);
+}
+
+void QShaderNode::removeRule(const QShaderFormat &format)
+{
+ const auto it = std::find_if(m_rules.begin(), m_rules.end(),
+ [format](const QPair<QShaderFormat, Rule> &entry) {
+ return entry.first == format;
+ });
+ if (it != m_rules.end())
+ m_rules.erase(it);
+}
+
+QVector<QShaderFormat> QShaderNode::availableFormats() const
+{
+ auto res = QVector<QShaderFormat>();
+ std::transform(m_rules.cbegin(), m_rules.cend(),
+ std::back_inserter(res),
+ [](const QPair<QShaderFormat, Rule> &entry) { return entry.first; });
+ return res;
+}
+
+QShaderNode::Rule QShaderNode::rule(const QShaderFormat &format) const
+{
+ const auto it = std::find_if(m_rules.crbegin(), m_rules.crend(),
+ [format](const QPair<QShaderFormat, Rule> &entry) {
+ return format.supports(entry.first);
+ });
+ return it != m_rules.crend() ? it->second : Rule();
+}
+
+QShaderNode::Rule::Rule(const QByteArray &subs, const QByteArrayList &snippets) noexcept
+ : substitution(subs),
+ headerSnippets(snippets)
+{
+}
+
+bool operator==(const QShaderNode::Rule &lhs, const QShaderNode::Rule &rhs) noexcept
+{
+ return lhs.substitution == rhs.substitution
+ && lhs.headerSnippets == rhs.headerSnippets;
+}
+}
+QT_END_NAMESPACE
diff --git a/src/render/shadergraph/qshadernode_p.h b/src/render/shadergraph/qshadernode_p.h
new file mode 100644
index 000000000..92e0e8e14
--- /dev/null
+++ b/src/render/shadergraph/qshadernode_p.h
@@ -0,0 +1,131 @@
+/****************************************************************************
+**
+** 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 QT3DRENDER_QSHADERNODE_P_H
+#define QT3DRENDER_QSHADERNODE_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 <Qt3DRender/private/qt3drender_global_p.h>
+
+#include <Qt3DRender/private/qshaderformat_p.h>
+#include <Qt3DRender/private/qshadernodeport_p.h>
+
+#include <QtCore/quuid.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender
+{
+class QShaderNode
+{
+public:
+ enum Type : char {
+ Invalid,
+ Input,
+ Output,
+ Function
+ };
+
+ class Rule
+ {
+ public:
+ Q_3DRENDERSHARED_PRIVATE_EXPORT Rule(const QByteArray &substitution = QByteArray(), const QByteArrayList &headerSnippets = QByteArrayList()) noexcept;
+
+ QByteArray substitution;
+ QByteArrayList headerSnippets;
+ };
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT Type type() const noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QUuid uuid() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setUuid(const QUuid &uuid) noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QStringList layers() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setLayers(const QStringList &layers) noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QVector<QShaderNodePort> ports() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void addPort(const QShaderNodePort &port);
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void removePort(const QShaderNodePort &port);
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QStringList parameterNames() const;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QVariant parameter(const QString &name) const;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setParameter(const QString &name, const QVariant &value);
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void clearParameter(const QString &name);
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void addRule(const QShaderFormat &format, const Rule &rule);
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void removeRule(const QShaderFormat &format);
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QVector<QShaderFormat> availableFormats() const;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT Rule rule(const QShaderFormat &format) const;
+
+private:
+ QUuid m_uuid;
+ QStringList m_layers;
+ QVector<QShaderNodePort> m_ports;
+ QHash<QString, QVariant> m_parameters;
+ QVector<QPair<QShaderFormat, QShaderNode::Rule>> m_rules;
+};
+
+Q_3DRENDERSHARED_PRIVATE_EXPORT bool operator==(const QShaderNode::Rule &lhs, const QShaderNode::Rule &rhs) noexcept;
+
+inline bool operator!=(const QShaderNode::Rule &lhs, const QShaderNode::Rule &rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+
+}
+
+Q_DECLARE_TYPEINFO(Qt3DRender::QShaderNode, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(Qt3DRender::QShaderNode::Rule, Q_MOVABLE_TYPE);
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(Qt3DRender::QShaderNode)
+Q_DECLARE_METATYPE(Qt3DRender::QShaderNode::Rule)
+
+#endif // QT3DRENDER_QSHADERNODE_P_H
diff --git a/src/render/shadergraph/qshadernodeport.cpp b/src/render/shadergraph/qshadernodeport.cpp
new file mode 100644
index 000000000..a79c32234
--- /dev/null
+++ b/src/render/shadergraph/qshadernodeport.cpp
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** 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 "qshadernodeport_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender
+{
+QShaderNodePort::QShaderNodePort() noexcept
+ : direction(Output)
+{
+}
+
+bool operator==(const QShaderNodePort &lhs, const QShaderNodePort &rhs) noexcept
+{
+ return lhs.direction == rhs.direction
+ && lhs.name == rhs.name;
+}
+}
+
+QT_END_NAMESPACE
diff --git a/src/render/shadergraph/qshadernodeport_p.h b/src/render/shadergraph/qshadernodeport_p.h
new file mode 100644
index 000000000..d83cdb8c4
--- /dev/null
+++ b/src/render/shadergraph/qshadernodeport_p.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** 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 QT3DRENDER_QSHADERNODEPORT_P_H
+#define QT3DRENDER_QSHADERNODEPORT_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 <Qt3DRender/private/qt3drender_global_p.h>
+
+#include <QtCore/qstring.h>
+#include <QtCore/qvariant.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender
+{
+class QShaderNodePort
+{
+public:
+ enum Direction : char {
+ Input,
+ Output
+ };
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QShaderNodePort() noexcept;
+
+ QShaderNodePort::Direction direction;
+ QString name;
+};
+
+Q_3DRENDERSHARED_PRIVATE_EXPORT bool operator==(const QShaderNodePort &lhs, const QShaderNodePort &rhs) noexcept;
+
+inline bool operator!=(const QShaderNodePort &lhs, const QShaderNodePort &rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+
+
+}
+Q_DECLARE_TYPEINFO(Qt3DRender::QShaderNodePort, Q_MOVABLE_TYPE);
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(Qt3DRender::QShaderNodePort)
+
+#endif // QT3DRENDER_QSHADERNODEPORT_P_H
diff --git a/src/render/shadergraph/qshadernodesloader.cpp b/src/render/shadergraph/qshadernodesloader.cpp
new file mode 100644
index 000000000..8346ed3d5
--- /dev/null
+++ b/src/render/shadergraph/qshadernodesloader.cpp
@@ -0,0 +1,292 @@
+/****************************************************************************
+**
+** 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 "qshadernodesloader_p.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qmetaobject.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender
+{
+QShaderNodesLoader::QShaderNodesLoader() noexcept
+ : m_status(Null),
+ m_device(nullptr)
+{
+}
+
+QShaderNodesLoader::Status QShaderNodesLoader::status() const noexcept
+{
+ return m_status;
+}
+
+QHash<QString, QShaderNode> QShaderNodesLoader::nodes() const noexcept
+{
+ return m_nodes;
+}
+
+QIODevice *QShaderNodesLoader::device() const noexcept
+{
+ return m_device;
+}
+
+void QShaderNodesLoader::setDevice(QIODevice *device) noexcept
+{
+ m_device = device;
+ m_nodes.clear();
+ m_status = !m_device ? Null
+ : (m_device->openMode() & QIODevice::ReadOnly) ? Waiting
+ : Error;
+}
+
+void QShaderNodesLoader::load()
+{
+ if (m_status == Error)
+ return;
+
+ auto error = QJsonParseError();
+ const QJsonDocument 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 QJsonObject root = document.object();
+ load(root);
+}
+
+void QShaderNodesLoader::load(const QJsonObject &prototypesObject)
+{
+ bool hasError = false;
+
+ for (const QString &property : prototypesObject.keys()) {
+ const QJsonValue nodeValue = prototypesObject.value(property);
+ if (!nodeValue.isObject()) {
+ qWarning() << "Invalid node found";
+ hasError = true;
+ break;
+ }
+
+ const QJsonObject nodeObject = nodeValue.toObject();
+
+ auto node = QShaderNode();
+
+ const QJsonValue inputsValue = nodeObject.value(QStringLiteral("inputs"));
+ if (inputsValue.isArray()) {
+ const QJsonArray inputsArray = inputsValue.toArray();
+ for (const QJsonValue &inputValue : inputsArray) {
+ if (!inputValue.isString()) {
+ qWarning() << "Non-string value in inputs";
+ hasError = true;
+ break;
+ }
+
+ auto input = QShaderNodePort();
+ input.direction = QShaderNodePort::Input;
+ input.name = inputValue.toString();
+ node.addPort(input);
+ }
+ }
+
+ const QJsonValue outputsValue = nodeObject.value(QStringLiteral("outputs"));
+ if (outputsValue.isArray()) {
+ const QJsonArray outputsArray = outputsValue.toArray();
+ for (const QJsonValue &outputValue : outputsArray) {
+ if (!outputValue.isString()) {
+ qWarning() << "Non-string value in outputs";
+ hasError = true;
+ break;
+ }
+
+ auto output = QShaderNodePort();
+ output.direction = QShaderNodePort::Output;
+ output.name = outputValue.toString();
+ node.addPort(output);
+ }
+ }
+
+ const QJsonValue parametersValue = nodeObject.value(QStringLiteral("parameters"));
+ if (parametersValue.isObject()) {
+ const QJsonObject parametersObject = parametersValue.toObject();
+ for (const QString &parameterName : parametersObject.keys()) {
+ const QJsonValue parameterValue = parametersObject.value(parameterName);
+ if (parameterValue.isObject()) {
+ const QJsonObject parameterObject = parameterValue.toObject();
+ const QString type = parameterObject.value(QStringLiteral("type")).toString();
+ const int typeId = QMetaType::type(type.toUtf8());
+
+ const QString value = parameterObject.value(QStringLiteral("value")).toString();
+ auto variant = QVariant(value);
+
+ if (QMetaType::typeFlags(typeId) & QMetaType::IsEnumeration) {
+ const QMetaObject *metaObject = QMetaType::metaObjectForType(typeId);
+ const char *className = metaObject->className();
+ const QByteArray enumName = type.mid(static_cast<int>(qstrlen(className)) + 2).toUtf8();
+ const QMetaEnum metaEnum = metaObject->enumerator(metaObject->indexOfEnumerator(enumName));
+ const int enumValue = metaEnum.keyToValue(value.toUtf8());
+ variant = QVariant(enumValue);
+ variant.convert(typeId);
+ } else {
+ variant.convert(typeId);
+ }
+ node.setParameter(parameterName, variant);
+ } else {
+ node.setParameter(parameterName, parameterValue.toVariant());
+ }
+ }
+ }
+
+ const QJsonValue rulesValue = nodeObject.value(QStringLiteral("rules"));
+ if (rulesValue.isArray()) {
+ const QJsonArray rulesArray = rulesValue.toArray();
+ for (const QJsonValue &ruleValue : rulesArray) {
+ if (!ruleValue.isObject()) {
+ qWarning() << "Rules should be objects";
+ hasError = true;
+ break;
+ }
+
+ const QJsonObject ruleObject = ruleValue.toObject();
+
+ const QJsonValue formatValue = ruleObject.value(QStringLiteral("format"));
+ if (!formatValue.isObject()) {
+ qWarning() << "Format is mandatory in rules and should be an object";
+ hasError = true;
+ break;
+ }
+
+ const QJsonObject formatObject = formatValue.toObject();
+ auto format = QShaderFormat();
+
+ const QJsonValue apiValue = formatObject.value(QStringLiteral("api"));
+ if (!apiValue.isString()) {
+ qWarning() << "Format API must be a string";
+ hasError = true;
+ break;
+ }
+
+ const QString api = apiValue.toString();
+ format.setApi(api == QStringLiteral("OpenGLES") ? QShaderFormat::OpenGLES
+ : api == QStringLiteral("OpenGLNoProfile") ? QShaderFormat::OpenGLNoProfile
+ : api == QStringLiteral("OpenGLCoreProfile") ? QShaderFormat::OpenGLCoreProfile
+ : api == QStringLiteral("OpenGLCompatibilityProfile") ? QShaderFormat::OpenGLCompatibilityProfile
+ : api == QStringLiteral("VulkanFlavoredGLSL") ? QShaderFormat::VulkanFlavoredGLSL
+ : QShaderFormat::NoApi);
+ if (format.api() == QShaderFormat::NoApi) {
+ qWarning() << "Format API must be one of: OpenGLES, OpenGLNoProfile, OpenGLCoreProfile or OpenGLCompatibilityProfile, VulkanFlavoredGLSL";
+ hasError = true;
+ break;
+ }
+
+ const QJsonValue majorValue = formatObject.value(QStringLiteral("major"));
+ const QJsonValue minorValue = formatObject.value(QStringLiteral("minor"));
+ if (!majorValue.isDouble() || !minorValue.isDouble()) {
+ qWarning() << "Format major and minor version must be values";
+ hasError = true;
+ break;
+ }
+ format.setVersion(QVersionNumber(majorValue.toInt(), minorValue.toInt()));
+
+ const QJsonValue extensionsValue = formatObject.value(QStringLiteral("extensions"));
+ const QJsonArray extensionsArray = extensionsValue.toArray();
+ auto extensions = QStringList();
+ std::transform(extensionsArray.constBegin(), extensionsArray.constEnd(),
+ std::back_inserter(extensions),
+ [] (const QJsonValue &extensionValue) { return extensionValue.toString(); });
+ format.setExtensions(extensions);
+
+ const QString vendor = formatObject.value(QStringLiteral("vendor")).toString();
+ format.setVendor(vendor);
+
+ const QJsonValue substitutionValue = ruleObject.value(QStringLiteral("substitution"));
+ if (!substitutionValue.isString()) {
+ qWarning() << "Substitution needs to be a string";
+ hasError = true;
+ break;
+ }
+
+ // We default out to a Fragment ShaderType if nothing is specified
+ // as that was the initial behavior we introduced
+ const QString shaderType = formatObject.value(QStringLiteral("shaderType")).toString();
+ format.setShaderType(shaderType == QStringLiteral("Fragment") ? QShaderFormat::Fragment
+ : shaderType == QStringLiteral("Vertex") ? QShaderFormat::Vertex
+ : shaderType == QStringLiteral("TessellationControl") ? QShaderFormat::TessellationControl
+ : shaderType == QStringLiteral("TessellationEvaluation") ? QShaderFormat::TessellationEvaluation
+ : shaderType == QStringLiteral("Geometry") ? QShaderFormat::Geometry
+ : shaderType == QStringLiteral("Compute") ? QShaderFormat::Compute
+ : QShaderFormat::Fragment);
+
+ const QByteArray substitution = substitutionValue.toString().toUtf8();
+
+ const QJsonValue snippetsValue = ruleObject.value(QStringLiteral("headerSnippets"));
+ const QJsonArray snippetsArray = snippetsValue.toArray();
+ auto snippets = QByteArrayList();
+ std::transform(snippetsArray.constBegin(), snippetsArray.constEnd(),
+ std::back_inserter(snippets),
+ [] (const QJsonValue &snippetValue) { return snippetValue.toString().toUtf8(); });
+
+ node.addRule(format, QShaderNode::Rule(substitution, snippets));
+ }
+ }
+
+ m_nodes.insert(property, node);
+ }
+
+ if (hasError) {
+ m_status = Error;
+ m_nodes.clear();
+ } else {
+ m_status = Ready;
+ }
+}
+}
+QT_END_NAMESPACE
diff --git a/src/render/shadergraph/qshadernodesloader_p.h b/src/render/shadergraph/qshadernodesloader_p.h
new file mode 100644
index 000000000..80d75a0bc
--- /dev/null
+++ b/src/render/shadergraph/qshadernodesloader_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 QT3DRENDER_QSHADERNODESLOADER_P_H
+#define QT3DRENDER_QSHADERNODESLOADER_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 <Qt3DRender/private/qt3drender_global_p.h>
+
+#include <Qt3DRender/private/qshadergraph_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QIODevice;
+
+namespace Qt3DRender
+{
+class QShaderNodesLoader
+{
+public:
+ enum Status : char {
+ Null,
+ Waiting,
+ Ready,
+ Error
+ };
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QShaderNodesLoader() noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT Status status() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QHash<QString, QShaderNode> nodes() const noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT QIODevice *device() const noexcept;
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void setDevice(QIODevice *device) noexcept;
+
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void load();
+ Q_3DRENDERSHARED_PRIVATE_EXPORT void load(const QJsonObject &prototypesObject);
+
+private:
+ Status m_status;
+ QIODevice *m_device;
+ QHash<QString, QShaderNode> m_nodes;
+};
+
+
+}
+Q_DECLARE_TYPEINFO(Qt3DRender::QShaderNodesLoader, Q_MOVABLE_TYPE);
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(Qt3DRender::QShaderNodesLoader)
+Q_DECLARE_METATYPE(Qt3DRender::QShaderNodesLoader::Status)
+
+#endif // QT3DRENDER_QSHADERNODESLOADER_P_H
diff --git a/src/render/shadergraph/shadergraph.pri b/src/render/shadergraph/shadergraph.pri
new file mode 100644
index 000000000..27bb8c6e9
--- /dev/null
+++ b/src/render/shadergraph/shadergraph.pri
@@ -0,0 +1,21 @@
+INCLUDEPATH += $$PWD
+
+HEADERS += \
+ $$PWD/qshaderformat_p.h \
+ $$PWD/qshadergenerator_p.h \
+ $$PWD/qshadergraphloader_p.h \
+ $$PWD/qshadergraph_p.h \
+ $$PWD/qshaderlanguage_p.h \
+ $$PWD/qshadernode_p.h \
+ $$PWD/qshadernodeport_p.h \
+ $$PWD/qshadernodesloader_p.h
+
+SOURCES += \
+ $$PWD/qshaderformat.cpp \
+ $$PWD/qshadergenerator.cpp \
+ $$PWD/qshadergraph.cpp \
+ $$PWD/qshadergraphloader.cpp \
+ $$PWD/qshaderlanguage.cpp \
+ $$PWD/qshadernode.cpp \
+ $$PWD/qshadernodeport.cpp \
+ $$PWD/qshadernodesloader.cpp
diff --git a/tests/auto/render/render.pro b/tests/auto/render/render.pro
index 09dbe68a7..c70a6c2a0 100644
--- a/tests/auto/render/render.pro
+++ b/tests/auto/render/render.pro
@@ -104,7 +104,8 @@ qtConfig(private_tests) {
waitfence \
qtexturedataupdate \
qshaderimage \
- shaderimage
+ shaderimage \
+ shadergraph
QT_FOR_CONFIG = 3dcore-private
# TO DO: These could be restored to be executed in all cases
diff --git a/tests/auto/render/shadergraph/qshadergenerator/qshadergenerator.pro b/tests/auto/render/shadergraph/qshadergenerator/qshadergenerator.pro
new file mode 100644
index 000000000..edd0be9eb
--- /dev/null
+++ b/tests/auto/render/shadergraph/qshadergenerator/qshadergenerator.pro
@@ -0,0 +1,5 @@
+CONFIG += testcase
+QT += testlib 3drender-private
+
+SOURCES += tst_qshadergenerator.cpp
+TARGET = tst_qshadergenerator
diff --git a/tests/auto/render/shadergraph/qshadergenerator/tst_qshadergenerator.cpp b/tests/auto/render/shadergraph/qshadergenerator/tst_qshadergenerator.cpp
new file mode 100644
index 000000000..780038976
--- /dev/null
+++ b/tests/auto/render/shadergraph/qshadergenerator/tst_qshadergenerator.cpp
@@ -0,0 +1,1428 @@
+/****************************************************************************
+**
+** 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/qmetaobject.h>
+#include <Qt3DRender/private/qshadergenerator_p.h>
+#include <Qt3DRender/private/qshaderlanguage_p.h>
+
+using namespace Qt3DRender;
+namespace
+{
+ QShaderFormat createFormat(QShaderFormat::Api api, int majorVersion, int minorVersion,
+ QShaderFormat::ShaderType shaderType= QShaderFormat::Fragment)
+ {
+ auto format = QShaderFormat();
+ format.setApi(api);
+ format.setVersion(QVersionNumber(majorVersion, minorVersion));
+ format.setShaderType(shaderType);
+ 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, const QStringList &layers = QStringList())
+ {
+ auto node = QShaderNode();
+ node.setUuid(QUuid::createUuid());
+ node.setLayers(layers);
+ 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,
+ const QStringList &layers = QStringList())
+ {
+ auto edge = QShaderGraph::Edge();
+ edge.sourceNodeUuid = sourceUuid;
+ edge.sourcePortName = sourceName;
+ edge.targetNodeUuid = targetUuid;
+ edge.targetPortName = targetName;
+ edge.layers = layers;
+ return edge;
+ }
+
+ QShaderGraph createFragmentShaderGraph()
+ {
+ 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, "value")
+ });
+ worldPosition.setParameter("name", "worldPosition");
+ worldPosition.addRule(openGLES2, QShaderNode::Rule("highp vec3 $value = $name;",
+ QByteArrayList() << "varying highp vec3 $name;"));
+ worldPosition.addRule(openGL3, QShaderNode::Rule("vec3 $value = $name;",
+ QByteArrayList() << "in vec3 $name;"));
+
+ auto texture = createNode({
+ createPort(QShaderNodePort::Output, "texture")
+ });
+ 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.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.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.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.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.addRule(openGLES2, QShaderNode::Rule("highp vec4 $color = texture2D($sampler, $coord);"));
+ sampleTexture.addRule(openGL3, QShaderNode::Rule("vec4 $color = texture($sampler, $coord);"));
+
+ auto lightFunction = createNode({
+ createPort(QShaderNodePort::Input, "baseColor"),
+ createPort(QShaderNodePort::Input, "position"),
+ createPort(QShaderNodePort::Input, "lightIntensity"),
+ createPort(QShaderNodePort::Output, "outputColor")
+ });
+ 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.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(), "value", 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;
+ }
+}
+
+class tst_QShaderGenerator : public QObject
+{
+ Q_OBJECT
+private slots:
+ void shouldHaveDefaultState();
+ void shouldGenerateShaderCode_data();
+ void shouldGenerateShaderCode();
+ void shouldGenerateVersionCommands_data();
+ void shouldGenerateVersionCommands();
+ void shouldProcessLanguageQualifierAndTypeEnums_data();
+ void shouldProcessLanguageQualifierAndTypeEnums();
+ void shouldGenerateDifferentCodeDependingOnActiveLayers();
+ void shouldUseGlobalVariableRatherThanTemporaries();
+ void shouldGenerateTemporariesWisely();
+ void shouldHandlePortNamesPrefixingOneAnother();
+ void shouldHandleNodesWithMultipleOutputPorts();
+ void shouldHandleExpressionsInInputNodes();
+};
+
+void tst_QShaderGenerator::shouldHaveDefaultState()
+{
+ // GIVEN
+ auto generator = QShaderGenerator();
+
+ // THEN
+ QVERIFY(generator.graph.nodes().isEmpty());
+ QVERIFY(generator.graph.edges().isEmpty());
+ QVERIFY(!generator.format.isValid());
+}
+
+void tst_QShaderGenerator::shouldGenerateShaderCode_data()
+{
+ QTest::addColumn<QShaderGraph>("graph");
+ QTest::addColumn<QShaderFormat>("format");
+ QTest::addColumn<QByteArray>("expectedCode");
+
+ const auto graph = createFragmentShaderGraph();
+
+ const auto openGLES2 = createFormat(QShaderFormat::OpenGLES, 2, 0);
+ const auto openGL3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0);
+ const auto openGL32 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 2);
+ const auto openGL4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
+
+ const auto versionGLES2 = QByteArrayList() << "#version 100" << "";
+ const auto versionGL3 = QByteArrayList() << "#version 130" << "";
+ const auto versionGL32 = QByteArrayList() << "#version 150 core" << "";
+ const auto versionGL4 = QByteArrayList() << "#version 400 core" << "";
+
+ const auto es2Code = QByteArrayList() << "varying highp vec3 worldPosition;"
+ << "uniform sampler2D texture;"
+ << "varying highp vec2 texCoord;"
+ << "uniform highp float lightIntensity;"
+ << "uniform highp float exposure;"
+ << "#pragma include es2/lightmodel.frag.inc"
+ << ""
+ << "void main()"
+ << "{"
+ << " gl_fragColor = (((((lightModel(((texture2D(texture, texCoord))), worldPosition, lightIntensity)))) * pow(2.0, exposure)));"
+ << "}"
+ << "";
+
+ const auto gl3Code = QByteArrayList() << "in vec3 worldPosition;"
+ << "uniform sampler2D texture;"
+ << "in vec2 texCoord;"
+ << "uniform float lightIntensity;"
+ << "uniform float exposure;"
+ << "out vec4 fragColor;"
+ << "#pragma include gl3/lightmodel.frag.inc"
+ << ""
+ << "void main()"
+ << "{"
+ << " fragColor = (((((lightModel(((texture(texture, texCoord))), worldPosition, lightIntensity)))) * pow(2.0, exposure)));"
+ << "}"
+ << "";
+
+ QTest::newRow("EmptyGraphAndFormat") << QShaderGraph() << QShaderFormat() << QByteArrayLiteral("\nvoid main()\n{\n}\n");
+ QTest::newRow("LightExposureGraphAndES2") << graph << openGLES2 << (versionGLES2 + es2Code).join('\n');
+ QTest::newRow("LightExposureGraphAndGL3") << graph << openGL3 << (versionGL3 + gl3Code).join('\n');
+ QTest::newRow("LightExposureGraphAndGL32") << graph << openGL32 << (versionGL32 + gl3Code).join('\n');
+ QTest::newRow("LightExposureGraphAndGL4") << graph << openGL4 << (versionGL4 + gl3Code).join('\n');
+}
+
+void tst_QShaderGenerator::shouldGenerateShaderCode()
+{
+ // GIVEN
+ QFETCH(QShaderGraph, graph);
+ QFETCH(QShaderFormat, format);
+
+ auto generator = QShaderGenerator();
+ generator.graph = graph;
+ generator.format = format;
+
+ // WHEN
+ const auto code = generator.createShaderCode();
+
+ // THEN
+ QFETCH(QByteArray, expectedCode);
+ QCOMPARE(code, expectedCode);
+}
+
+void tst_QShaderGenerator::shouldGenerateVersionCommands_data()
+{
+ QTest::addColumn<QShaderFormat>("format");
+ QTest::addColumn<QByteArray>("version");
+
+ QTest::newRow("GLES2") << createFormat(QShaderFormat::OpenGLES, 2, 0) << QByteArrayLiteral("#version 100");
+ QTest::newRow("GLES3") << createFormat(QShaderFormat::OpenGLES, 3, 0) << QByteArrayLiteral("#version 300 es");
+
+ QTest::newRow("GL20") << createFormat(QShaderFormat::OpenGLNoProfile, 2, 0) << QByteArrayLiteral("#version 110");
+ QTest::newRow("GL21") << createFormat(QShaderFormat::OpenGLNoProfile, 2, 1) << QByteArrayLiteral("#version 120");
+ QTest::newRow("GL30") << createFormat(QShaderFormat::OpenGLNoProfile, 3, 0) << QByteArrayLiteral("#version 130");
+ QTest::newRow("GL31") << createFormat(QShaderFormat::OpenGLNoProfile, 3, 1) << QByteArrayLiteral("#version 140");
+ QTest::newRow("GL32") << createFormat(QShaderFormat::OpenGLNoProfile, 3, 2) << QByteArrayLiteral("#version 150");
+ QTest::newRow("GL33") << createFormat(QShaderFormat::OpenGLNoProfile, 3, 3) << QByteArrayLiteral("#version 330");
+ QTest::newRow("GL40") << createFormat(QShaderFormat::OpenGLNoProfile, 4, 0) << QByteArrayLiteral("#version 400");
+ QTest::newRow("GL41") << createFormat(QShaderFormat::OpenGLNoProfile, 4, 1) << QByteArrayLiteral("#version 410");
+ QTest::newRow("GL42") << createFormat(QShaderFormat::OpenGLNoProfile, 4, 2) << QByteArrayLiteral("#version 420");
+ QTest::newRow("GL43") << createFormat(QShaderFormat::OpenGLNoProfile, 4, 3) << QByteArrayLiteral("#version 430");
+
+ QTest::newRow("GL20core") << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0) << QByteArrayLiteral("#version 110");
+ QTest::newRow("GL21core") << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 1) << QByteArrayLiteral("#version 120");
+ QTest::newRow("GL30core") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0) << QByteArrayLiteral("#version 130");
+ QTest::newRow("GL31core") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 1) << QByteArrayLiteral("#version 140");
+ QTest::newRow("GL32core") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 2) << QByteArrayLiteral("#version 150 core");
+ QTest::newRow("GL33core") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 3) << QByteArrayLiteral("#version 330 core");
+ QTest::newRow("GL40core") << createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0) << QByteArrayLiteral("#version 400 core");
+ QTest::newRow("GL41core") << createFormat(QShaderFormat::OpenGLCoreProfile, 4, 1) << QByteArrayLiteral("#version 410 core");
+ QTest::newRow("GL42core") << createFormat(QShaderFormat::OpenGLCoreProfile, 4, 2) << QByteArrayLiteral("#version 420 core");
+ QTest::newRow("GL43core") << createFormat(QShaderFormat::OpenGLCoreProfile, 4, 3) << QByteArrayLiteral("#version 430 core");
+
+ QTest::newRow("GL20compatibility") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 0) << QByteArrayLiteral("#version 110");
+ QTest::newRow("GL21compatibility") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 1) << QByteArrayLiteral("#version 120");
+ QTest::newRow("GL30compatibility") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 3, 0) << QByteArrayLiteral("#version 130");
+ QTest::newRow("GL31compatibility") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 3, 1) << QByteArrayLiteral("#version 140");
+ QTest::newRow("GL32compatibility") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 3, 2) << QByteArrayLiteral("#version 150 compatibility");
+ QTest::newRow("GL33compatibility") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 3, 3) << QByteArrayLiteral("#version 330 compatibility");
+ QTest::newRow("GL40compatibility") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 4, 0) << QByteArrayLiteral("#version 400 compatibility");
+ QTest::newRow("GL41compatibility") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 4, 1) << QByteArrayLiteral("#version 410 compatibility");
+ QTest::newRow("GL42compatibility") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 4, 2) << QByteArrayLiteral("#version 420 compatibility");
+ QTest::newRow("GL43compatibility") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 4, 3) << QByteArrayLiteral("#version 430 compatibility");
+}
+
+void tst_QShaderGenerator::shouldGenerateVersionCommands()
+{
+ // GIVEN
+ QFETCH(QShaderFormat, format);
+
+ auto generator = QShaderGenerator();
+ generator.format = format;
+
+ // WHEN
+ const auto code = generator.createShaderCode();
+
+ // THEN
+ QFETCH(QByteArray, version);
+ const auto expectedCode = (QByteArrayList() << version
+ << ""
+ << ""
+ << "void main()"
+ << "{"
+ << "}"
+ << "").join('\n');
+ QCOMPARE(code, expectedCode);
+}
+
+
+namespace {
+ QString toGlsl(QShaderLanguage::StorageQualifier qualifier, const QShaderFormat &format)
+ {
+ if (format.version().majorVersion() <= 2) {
+ // Note we're assuming fragment shader only here, it'd be different
+ // values for vertex shader, will need to be fixed properly at some
+ // point but isn't necessary yet (this problem already exists in past
+ // commits anyway)
+ switch (qualifier) {
+ case QShaderLanguage::Const:
+ return "const";
+ case QShaderLanguage::Input:
+ return "varying";
+ case QShaderLanguage::BuiltIn:
+ return "//";
+ case QShaderLanguage::Output:
+ return ""; // Although fragment shaders for <=2 only have fixed outputs
+ case QShaderLanguage::Uniform:
+ return "uniform";
+ }
+ } else {
+ switch (qualifier) {
+ case QShaderLanguage::Const:
+ return "const";
+ case QShaderLanguage::Input:
+ return "in";
+ case QShaderLanguage::BuiltIn:
+ return "//";
+ case QShaderLanguage::Output:
+ return "out";
+ case QShaderLanguage::Uniform:
+ return "uniform";
+ }
+ }
+
+ Q_UNREACHABLE();
+ }
+
+ QString toGlsl(QShaderLanguage::VariableType type)
+ {
+ switch (type) {
+ case QShaderLanguage::Bool:
+ return "bool";
+ case QShaderLanguage::Int:
+ return "int";
+ case QShaderLanguage::Uint:
+ return "uint";
+ case QShaderLanguage::Float:
+ return "float";
+ case QShaderLanguage::Double:
+ return "double";
+ case QShaderLanguage::Vec2:
+ return "vec2";
+ case QShaderLanguage::Vec3:
+ return "vec3";
+ case QShaderLanguage::Vec4:
+ return "vec4";
+ case QShaderLanguage::DVec2:
+ return "dvec2";
+ case QShaderLanguage::DVec3:
+ return "dvec3";
+ case QShaderLanguage::DVec4:
+ return "dvec4";
+ case QShaderLanguage::BVec2:
+ return "bvec2";
+ case QShaderLanguage::BVec3:
+ return "bvec3";
+ case QShaderLanguage::BVec4:
+ return "bvec4";
+ case QShaderLanguage::IVec2:
+ return "ivec2";
+ case QShaderLanguage::IVec3:
+ return "ivec3";
+ case QShaderLanguage::IVec4:
+ return "ivec4";
+ case QShaderLanguage::UVec2:
+ return "uvec2";
+ case QShaderLanguage::UVec3:
+ return "uvec3";
+ case QShaderLanguage::UVec4:
+ return "uvec4";
+ case QShaderLanguage::Mat2:
+ return "mat2";
+ case QShaderLanguage::Mat3:
+ return "mat3";
+ case QShaderLanguage::Mat4:
+ return "mat4";
+ case QShaderLanguage::Mat2x2:
+ return "mat2x2";
+ case QShaderLanguage::Mat2x3:
+ return "mat2x3";
+ case QShaderLanguage::Mat2x4:
+ return "mat2x4";
+ case QShaderLanguage::Mat3x2:
+ return "mat3x2";
+ case QShaderLanguage::Mat3x3:
+ return "mat3x3";
+ case QShaderLanguage::Mat3x4:
+ return "mat3x4";
+ case QShaderLanguage::Mat4x2:
+ return "mat4x2";
+ case QShaderLanguage::Mat4x3:
+ return "mat4x3";
+ case QShaderLanguage::Mat4x4:
+ return "mat4x4";
+ case QShaderLanguage::DMat2:
+ return "dmat2";
+ case QShaderLanguage::DMat3:
+ return "dmat3";
+ case QShaderLanguage::DMat4:
+ return "dmat4";
+ case QShaderLanguage::DMat2x2:
+ return "dmat2x2";
+ case QShaderLanguage::DMat2x3:
+ return "dmat2x3";
+ case QShaderLanguage::DMat2x4:
+ return "dmat2x4";
+ case QShaderLanguage::DMat3x2:
+ return "dmat3x2";
+ case QShaderLanguage::DMat3x3:
+ return "dmat3x3";
+ case QShaderLanguage::DMat3x4:
+ return "dmat3x4";
+ case QShaderLanguage::DMat4x2:
+ return "dmat4x2";
+ case QShaderLanguage::DMat4x3:
+ return "dmat4x3";
+ case QShaderLanguage::DMat4x4:
+ return "dmat4x4";
+ case QShaderLanguage::Sampler1D:
+ return "sampler1D";
+ case QShaderLanguage::Sampler2D:
+ return "sampler2D";
+ case QShaderLanguage::Sampler3D:
+ return "sampler3D";
+ case QShaderLanguage::SamplerCube:
+ return "samplerCube";
+ case QShaderLanguage::Sampler2DRect:
+ return "sampler2DRect";
+ case QShaderLanguage::Sampler2DMs:
+ return "sampler2DMS";
+ case QShaderLanguage::SamplerBuffer:
+ return "samplerBuffer";
+ case QShaderLanguage::Sampler1DArray:
+ return "sampler1DArray";
+ case QShaderLanguage::Sampler2DArray:
+ return "sampler2DArray";
+ case QShaderLanguage::Sampler2DMsArray:
+ return "sampler2DMSArray";
+ case QShaderLanguage::SamplerCubeArray:
+ return "samplerCubeArray";
+ case QShaderLanguage::Sampler1DShadow:
+ return "sampler1DShadow";
+ case QShaderLanguage::Sampler2DShadow:
+ return "sampler2DShadow";
+ case QShaderLanguage::Sampler2DRectShadow:
+ return "sampler2DRectShadow";
+ case QShaderLanguage::Sampler1DArrayShadow:
+ return "sampler1DArrayShadow";
+ case QShaderLanguage::Sampler2DArrayShadow:
+ return "sample2DArrayShadow";
+ case QShaderLanguage::SamplerCubeShadow:
+ return "samplerCubeShadow";
+ case QShaderLanguage::SamplerCubeArrayShadow:
+ return "samplerCubeArrayShadow";
+ case QShaderLanguage::ISampler1D:
+ return "isampler1D";
+ case QShaderLanguage::ISampler2D:
+ return "isampler2D";
+ case QShaderLanguage::ISampler3D:
+ return "isampler3D";
+ case QShaderLanguage::ISamplerCube:
+ return "isamplerCube";
+ case QShaderLanguage::ISampler2DRect:
+ return "isampler2DRect";
+ case QShaderLanguage::ISampler2DMs:
+ return "isampler2DMS";
+ case QShaderLanguage::ISamplerBuffer:
+ return "isamplerBuffer";
+ case QShaderLanguage::ISampler1DArray:
+ return "isampler1DArray";
+ case QShaderLanguage::ISampler2DArray:
+ return "isampler2DArray";
+ case QShaderLanguage::ISampler2DMsArray:
+ return "isampler2DMSArray";
+ case QShaderLanguage::ISamplerCubeArray:
+ return "isamplerCubeArray";
+ case QShaderLanguage::USampler1D:
+ return "usampler1D";
+ case QShaderLanguage::USampler2D:
+ return "usampler2D";
+ case QShaderLanguage::USampler3D:
+ return "usampler3D";
+ case QShaderLanguage::USamplerCube:
+ return "usamplerCube";
+ case QShaderLanguage::USampler2DRect:
+ return "usampler2DRect";
+ case QShaderLanguage::USampler2DMs:
+ return "usampler2DMS";
+ case QShaderLanguage::USamplerBuffer:
+ return "usamplerBuffer";
+ case QShaderLanguage::USampler1DArray:
+ return "usampler1DArray";
+ case QShaderLanguage::USampler2DArray:
+ return "usampler2DArray";
+ case QShaderLanguage::USampler2DMsArray:
+ return "usampler2DMSArray";
+ case QShaderLanguage::USamplerCubeArray:
+ return "usamplerCubeArray";
+ }
+
+ Q_UNREACHABLE();
+ }
+}
+
+void tst_QShaderGenerator::shouldProcessLanguageQualifierAndTypeEnums_data()
+{
+ QTest::addColumn<QShaderGraph>("graph");
+ QTest::addColumn<QShaderFormat>("format");
+ QTest::addColumn<QByteArray>("expectedCode");
+
+ {
+ const auto es2 = createFormat(QShaderFormat::OpenGLES, 2, 0);
+ const auto es3 = createFormat(QShaderFormat::OpenGLES, 3, 0);
+ const auto gl2 = createFormat(QShaderFormat::OpenGLNoProfile, 2, 0);
+ const auto gl3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0);
+ const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
+
+ const auto qualifierEnum = QMetaEnum::fromType<QShaderLanguage::StorageQualifier>();
+ const auto typeEnum = QMetaEnum::fromType<QShaderLanguage::VariableType>();
+
+ for (int qualifierIndex = 0; qualifierIndex < qualifierEnum.keyCount(); qualifierIndex++) {
+ const auto qualifierName = qualifierEnum.key(qualifierIndex);
+ const auto qualifierValue = static_cast<QShaderLanguage::StorageQualifier>(qualifierEnum.value(qualifierIndex));
+
+ for (int typeIndex = 0; typeIndex < typeEnum.keyCount(); typeIndex++) {
+ const auto typeName = typeEnum.key(typeIndex);
+ const auto typeValue = static_cast<QShaderLanguage::VariableType>(typeEnum.value(typeIndex));
+
+ auto graph = QShaderGraph();
+
+ auto worldPosition = createNode({
+ createPort(QShaderNodePort::Output, "value")
+ });
+ worldPosition.setParameter("name", "worldPosition");
+ worldPosition.setParameter("qualifier", QVariant::fromValue<QShaderLanguage::StorageQualifier>(qualifierValue));
+ worldPosition.setParameter("type", QVariant::fromValue<QShaderLanguage::VariableType>(typeValue));
+ worldPosition.addRule(es2, QShaderNode::Rule("highp $type $value = $name;",
+ QByteArrayList() << "$qualifier highp $type $name;"));
+ worldPosition.addRule(gl2, QShaderNode::Rule("$type $value = $name;",
+ QByteArrayList() << "$qualifier $type $name;"));
+ worldPosition.addRule(gl3, QShaderNode::Rule("$type $value = $name;",
+ QByteArrayList() << "$qualifier $type $name;"));
+
+ auto fragColor = createNode({
+ createPort(QShaderNodePort::Input, "fragColor")
+ });
+ fragColor.addRule(es2, QShaderNode::Rule("gl_fragColor = $fragColor;"));
+ fragColor.addRule(gl2, QShaderNode::Rule("gl_fragColor = $fragColor;"));
+ fragColor.addRule(gl3, QShaderNode::Rule("fragColor = $fragColor;",
+ QByteArrayList() << "out vec4 fragColor;"));
+
+ graph.addNode(worldPosition);
+ graph.addNode(fragColor);
+
+ graph.addEdge(createEdge(worldPosition.uuid(), "value", fragColor.uuid(), "fragColor"));
+
+ const auto gl2Code = (QByteArrayList() << "#version 110"
+ << ""
+ << QStringLiteral("%1 %2 worldPosition;").arg(toGlsl(qualifierValue, gl2))
+ .arg(toGlsl(typeValue))
+ .toUtf8()
+ << ""
+ << "void main()"
+ << "{"
+ << " gl_fragColor = worldPosition;"
+ << "}"
+ << "").join("\n");
+ const auto gl3Code = (QByteArrayList() << "#version 130"
+ << ""
+ << QStringLiteral("%1 %2 worldPosition;").arg(toGlsl(qualifierValue, gl3))
+ .arg(toGlsl(typeValue))
+ .toUtf8()
+ << "out vec4 fragColor;"
+ << ""
+ << "void main()"
+ << "{"
+ << " fragColor = worldPosition;"
+ << "}"
+ << "").join("\n");
+ const auto gl4Code = (QByteArrayList() << "#version 400 core"
+ << ""
+ << QStringLiteral("%1 %2 worldPosition;").arg(toGlsl(qualifierValue, gl4))
+ .arg(toGlsl(typeValue))
+ .toUtf8()
+ << "out vec4 fragColor;"
+ << ""
+ << "void main()"
+ << "{"
+ << " fragColor = worldPosition;"
+ << "}"
+ << "").join("\n");
+ const auto es2Code = (QByteArrayList() << "#version 100"
+ << ""
+ << QStringLiteral("%1 highp %2 worldPosition;").arg(toGlsl(qualifierValue, es2))
+ .arg(toGlsl(typeValue))
+ .toUtf8()
+ << ""
+ << "void main()"
+ << "{"
+ << " gl_fragColor = worldPosition;"
+ << "}"
+ << "").join("\n");
+ const auto es3Code = (QByteArrayList() << "#version 300 es"
+ << ""
+ << QStringLiteral("%1 highp %2 worldPosition;").arg(toGlsl(qualifierValue, es3))
+ .arg(toGlsl(typeValue))
+ .toUtf8()
+ << ""
+ << "void main()"
+ << "{"
+ << " gl_fragColor = worldPosition;"
+ << "}"
+ << "").join("\n");
+
+ QTest::addRow("%s %s ES2", qualifierName, typeName) << graph << es2 << es2Code;
+ QTest::addRow("%s %s ES3", qualifierName, typeName) << graph << es3 << es3Code;
+ QTest::addRow("%s %s GL2", qualifierName, typeName) << graph << gl2 << gl2Code;
+ QTest::addRow("%s %s GL3", qualifierName, typeName) << graph << gl3 << gl3Code;
+ QTest::addRow("%s %s GL4", qualifierName, typeName) << graph << gl4 << gl4Code;
+ }
+ }
+ }
+
+ {
+ const auto es2 = createFormat(QShaderFormat::OpenGLES, 2, 0, QShaderFormat::Vertex);
+ const auto es3 = createFormat(QShaderFormat::OpenGLES, 3, 0, QShaderFormat::Vertex);
+ const auto gl2 = createFormat(QShaderFormat::OpenGLNoProfile, 2, 0, QShaderFormat::Vertex);
+ const auto gl3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, QShaderFormat::Vertex);
+ const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0, QShaderFormat::Vertex);
+
+ auto graph = QShaderGraph();
+
+ auto vertexPosition = createNode({
+ createPort(QShaderNodePort::Output, "value")
+ });
+ vertexPosition.setParameter("name", "vertexPosition");
+ vertexPosition.setParameter("qualifier", QVariant::fromValue<QShaderLanguage::StorageQualifier>(QShaderLanguage::Input));
+ vertexPosition.setParameter("type", QVariant::fromValue<QShaderLanguage::VariableType>(QShaderLanguage::Vec4));
+
+ vertexPosition.addRule(es2, QShaderNode::Rule("",
+ QByteArrayList() << "$qualifier highp $type $name;"));
+ vertexPosition.addRule(gl2, QShaderNode::Rule("",
+ QByteArrayList() << "$qualifier $type $name;"));
+ vertexPosition.addRule(gl3, QShaderNode::Rule("",
+ QByteArrayList() << "$qualifier $type $name;"));
+
+ graph.addNode(vertexPosition);
+
+ const auto gl2Code = (QByteArrayList() << "#version 110"
+ << ""
+ << "attribute vec4 vertexPosition;"
+ << ""
+ << "void main()"
+ << "{"
+ << "}"
+ << "").join("\n");
+ const auto gl3Code = (QByteArrayList() << "#version 130"
+ << ""
+ << "in vec4 vertexPosition;"
+ << ""
+ << "void main()"
+ << "{"
+ << "}"
+ << "").join("\n");
+ const auto gl4Code = (QByteArrayList() << "#version 400 core"
+ << ""
+ << "in vec4 vertexPosition;"
+ << ""
+ << "void main()"
+ << "{"
+ << "}"
+ << "").join("\n");
+ const auto es2Code = (QByteArrayList() << "#version 100"
+ << ""
+ << "attribute highp vec4 vertexPosition;"
+ << ""
+ << "void main()"
+ << "{"
+ << "}"
+ << "").join("\n");
+ const auto es3Code = (QByteArrayList() << "#version 300 es"
+ << ""
+ << "in highp vec4 vertexPosition;"
+ << ""
+ << "void main()"
+ << "{"
+ << "}"
+ << "").join("\n");
+
+ QTest::addRow("Attribute header substitution ES2") << graph << es2 << es2Code;
+ QTest::addRow("Attribute header substitution ES3") << graph << es3 << es3Code;
+ QTest::addRow("Attribute header substitution GL2") << graph << gl2 << gl2Code;
+ QTest::addRow("Attribute header substitution GL3") << graph << gl3 << gl3Code;
+ QTest::addRow("Attribute header substitution GL4") << graph << gl4 << gl4Code;
+ }
+}
+
+void tst_QShaderGenerator::shouldProcessLanguageQualifierAndTypeEnums()
+{
+ // GIVEN
+ QFETCH(QShaderGraph, graph);
+ QFETCH(QShaderFormat, format);
+
+ auto generator = QShaderGenerator();
+ generator.graph = graph;
+ generator.format = format;
+
+ // WHEN
+ const auto code = generator.createShaderCode();
+
+ // THEN
+ QFETCH(QByteArray, expectedCode);
+ QCOMPARE(code, expectedCode);
+}
+
+void tst_QShaderGenerator::shouldGenerateDifferentCodeDependingOnActiveLayers()
+{
+ // GIVEN
+ const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
+
+ auto texCoord = createNode({
+ createPort(QShaderNodePort::Output, "texCoord")
+ }, {
+ "diffuseTexture",
+ "normalTexture"
+ });
+ texCoord.addRule(gl4, QShaderNode::Rule("vec2 $texCoord = texCoord;",
+ QByteArrayList() << "in vec2 texCoord;"));
+ auto diffuseUniform = createNode({
+ createPort(QShaderNodePort::Output, "color")
+ }, {"diffuseUniform"});
+ diffuseUniform.addRule(gl4, QShaderNode::Rule("vec4 $color = diffuseUniform;",
+ QByteArrayList() << "uniform vec4 diffuseUniform;"));
+ auto diffuseTexture = createNode({
+ createPort(QShaderNodePort::Input, "coord"),
+ createPort(QShaderNodePort::Output, "color")
+ }, {"diffuseTexture"});
+ diffuseTexture.addRule(gl4, QShaderNode::Rule("vec4 $color = texture2D(diffuseTexture, $coord);",
+ QByteArrayList() << "uniform sampler2D diffuseTexture;"));
+ auto normalUniform = createNode({
+ createPort(QShaderNodePort::Output, "normal")
+ }, {"normalUniform"});
+ normalUniform.addRule(gl4, QShaderNode::Rule("vec3 $normal = normalUniform;",
+ QByteArrayList() << "uniform vec3 normalUniform;"));
+ auto normalTexture = createNode({
+ createPort(QShaderNodePort::Input, "coord"),
+ createPort(QShaderNodePort::Output, "normal")
+ }, {"normalTexture"});
+ normalTexture.addRule(gl4, QShaderNode::Rule("vec3 $normal = texture2D(normalTexture, $coord).rgb;",
+ QByteArrayList() << "uniform sampler2D normalTexture;"));
+ auto lightFunction = createNode({
+ createPort(QShaderNodePort::Input, "color"),
+ createPort(QShaderNodePort::Input, "normal"),
+ createPort(QShaderNodePort::Output, "output")
+ });
+ lightFunction.addRule(gl4, QShaderNode::Rule("vec4 $output = lightModel($color, $normal);",
+ QByteArrayList() << "#pragma include gl4/lightmodel.frag.inc"));
+ auto fragColor = createNode({
+ createPort(QShaderNodePort::Input, "fragColor")
+ });
+ fragColor.addRule(gl4, QShaderNode::Rule("fragColor = $fragColor;",
+ QByteArrayList() << "out vec4 fragColor;"));
+
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+
+ res.addNode(texCoord);
+ res.addNode(diffuseUniform);
+ res.addNode(diffuseTexture);
+ res.addNode(normalUniform);
+ res.addNode(normalTexture);
+ res.addNode(lightFunction);
+ res.addNode(fragColor);
+
+ res.addEdge(createEdge(diffuseUniform.uuid(), "color", lightFunction.uuid(), "color", {"diffuseUniform"}));
+ res.addEdge(createEdge(texCoord.uuid(), "texCoord", diffuseTexture.uuid(), "coord", {"diffuseTexture"}));
+ res.addEdge(createEdge(diffuseTexture.uuid(), "color", lightFunction.uuid(), "color", {"diffuseTexture"}));
+
+ res.addEdge(createEdge(normalUniform.uuid(), "normal", lightFunction.uuid(), "normal", {"normalUniform"}));
+ res.addEdge(createEdge(texCoord.uuid(), "texCoord", normalTexture.uuid(), "coord", {"normalTexture"}));
+ res.addEdge(createEdge(normalTexture.uuid(), "normal", lightFunction.uuid(), "normal", {"normalTexture"}));
+
+ res.addEdge(createEdge(lightFunction.uuid(), "output", fragColor.uuid(), "fragColor"));
+
+ return res;
+ }();
+
+ auto generator = QShaderGenerator();
+ generator.graph = graph;
+ generator.format = gl4;
+
+ {
+ // WHEN
+ const auto code = generator.createShaderCode({"diffuseUniform", "normalUniform"});
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "uniform vec4 diffuseUniform;"
+ << "uniform vec3 normalUniform;"
+ << "#pragma include gl4/lightmodel.frag.inc"
+ << "out vec4 fragColor;"
+ << ""
+ << "void main()"
+ << "{"
+ << " fragColor = ((lightModel(diffuseUniform, normalUniform)));"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+ }
+
+ {
+ // WHEN
+ const auto code = generator.createShaderCode({"diffuseUniform", "normalTexture"});
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "in vec2 texCoord;"
+ << "uniform vec4 diffuseUniform;"
+ << "uniform sampler2D normalTexture;"
+ << "#pragma include gl4/lightmodel.frag.inc"
+ << "out vec4 fragColor;"
+ << ""
+ << "void main()"
+ << "{"
+ << " fragColor = ((lightModel(diffuseUniform, texture2D(normalTexture, texCoord).rgb)));"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+ }
+
+ {
+ // WHEN
+ const auto code = generator.createShaderCode({"diffuseTexture", "normalUniform"});
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "in vec2 texCoord;"
+ << "uniform sampler2D diffuseTexture;"
+ << "uniform vec3 normalUniform;"
+ << "#pragma include gl4/lightmodel.frag.inc"
+ << "out vec4 fragColor;"
+ << ""
+ << "void main()"
+ << "{"
+ << " fragColor = ((lightModel(texture2D(diffuseTexture, texCoord), normalUniform)));"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+ }
+
+ {
+ // WHEN
+ const auto code = generator.createShaderCode({"diffuseTexture", "normalTexture"});
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "in vec2 texCoord;"
+ << "uniform sampler2D diffuseTexture;"
+ << "uniform sampler2D normalTexture;"
+ << "#pragma include gl4/lightmodel.frag.inc"
+ << "out vec4 fragColor;"
+ << ""
+ << "void main()"
+ << "{"
+ << " fragColor = ((lightModel(texture2D(diffuseTexture, texCoord), texture2D(normalTexture, texCoord).rgb)));"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+ }
+}
+
+void tst_QShaderGenerator::shouldUseGlobalVariableRatherThanTemporaries()
+{
+ // GIVEN
+ const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
+
+ {
+ // WHEN
+ auto vertexPosition = createNode({
+ createPort(QShaderNodePort::Output, "vertexPosition")
+ });
+ vertexPosition.addRule(gl4, QShaderNode::Rule("vec4 $vertexPosition = vertexPosition;",
+ QByteArrayList() << "in vec4 vertexPosition;"));
+
+ auto fakeMultiPlyNoSpace = createNode({
+ createPort(QShaderNodePort::Input, "varName"),
+ createPort(QShaderNodePort::Output, "out")
+ });
+ fakeMultiPlyNoSpace.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName*speed;"));
+
+ auto fakeMultiPlySpace = createNode({
+ createPort(QShaderNodePort::Input, "varName"),
+ createPort(QShaderNodePort::Output, "out")
+ });
+ fakeMultiPlySpace.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName * speed;"));
+
+ auto fakeJoinNoSpace = createNode({
+ createPort(QShaderNodePort::Input, "varName"),
+ createPort(QShaderNodePort::Output, "out")
+ });
+ fakeJoinNoSpace.addRule(gl4, QShaderNode::Rule("vec4 $out = vec4($varName.xyz,$varName.w);"));
+
+ auto fakeJoinSpace = createNode({
+ createPort(QShaderNodePort::Input, "varName"),
+ createPort(QShaderNodePort::Output, "out")
+ });
+ fakeJoinSpace.addRule(gl4, QShaderNode::Rule("vec4 $out = vec4($varName.xyz, $varName.w);"));
+
+ auto fakeAdd = createNode({
+ createPort(QShaderNodePort::Input, "varName"),
+ createPort(QShaderNodePort::Output, "out")
+ });
+ fakeAdd.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName.xyzw + $varName;"));
+
+ auto fakeSub = createNode({
+ createPort(QShaderNodePort::Input, "varName"),
+ createPort(QShaderNodePort::Output, "out")
+ });
+ fakeSub.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName.xyzw - $varName;"));
+
+ auto fakeDiv = createNode({
+ createPort(QShaderNodePort::Input, "varName"),
+ createPort(QShaderNodePort::Output, "out")
+ });
+ fakeDiv.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName / v0;"));
+
+ auto fragColor = createNode({
+ createPort(QShaderNodePort::Input, "input1"),
+ createPort(QShaderNodePort::Input, "input2"),
+ createPort(QShaderNodePort::Input, "input3"),
+ createPort(QShaderNodePort::Input, "input4"),
+ createPort(QShaderNodePort::Input, "input5"),
+ createPort(QShaderNodePort::Input, "input6"),
+ createPort(QShaderNodePort::Input, "input7")
+ });
+ fragColor.addRule(gl4, QShaderNode::Rule("fragColor = $input1 + $input2 + $input3 + $input4 + $input5 + $input6 + $input7;",
+ QByteArrayList() << "out vec4 fragColor;"));
+
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+
+ res.addNode(vertexPosition);
+ res.addNode(fakeMultiPlyNoSpace);
+ res.addNode(fakeMultiPlySpace);
+ res.addNode(fakeJoinNoSpace);
+ res.addNode(fakeJoinSpace);
+ res.addNode(fakeAdd);
+ res.addNode(fakeSub);
+ res.addNode(fakeDiv);
+ res.addNode(fragColor);
+
+ res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition", fakeMultiPlyNoSpace.uuid(), "varName"));
+ res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition", fakeMultiPlySpace.uuid(), "varName"));
+ res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition", fakeJoinNoSpace.uuid(), "varName"));
+ res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition", fakeJoinSpace.uuid(), "varName"));
+ res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition", fakeAdd.uuid(), "varName"));
+ res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition", fakeSub.uuid(), "varName"));
+ res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition", fakeDiv.uuid(), "varName"));
+ res.addEdge(createEdge(fakeMultiPlyNoSpace.uuid(), "out", fragColor.uuid(), "input1"));
+ res.addEdge(createEdge(fakeMultiPlySpace.uuid(), "out", fragColor.uuid(), "input2"));
+ res.addEdge(createEdge(fakeJoinNoSpace.uuid(), "out", fragColor.uuid(), "input3"));
+ res.addEdge(createEdge(fakeJoinSpace.uuid(), "out", fragColor.uuid(), "input4"));
+ res.addEdge(createEdge(fakeAdd.uuid(), "out", fragColor.uuid(), "input5"));
+ res.addEdge(createEdge(fakeSub.uuid(), "out", fragColor.uuid(), "input6"));
+ res.addEdge(createEdge(fakeDiv.uuid(), "out", fragColor.uuid(), "input7"));
+
+ return res;
+ }();
+
+ auto generator = QShaderGenerator();
+ generator.graph = graph;
+ generator.format = gl4;
+
+ const auto code = generator.createShaderCode({"diffuseUniform", "normalUniform"});
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "in vec4 vertexPosition;"
+ << "out vec4 fragColor;"
+ << ""
+ << "void main()"
+ << "{"
+ << " fragColor = (((((((vertexPosition*speed + vertexPosition * speed + ((vec4(vertexPosition.xyz,vertexPosition.w))) + ((vec4(vertexPosition.xyz, vertexPosition.w))) + ((vertexPosition.xyzw + vertexPosition)) + ((vertexPosition.xyzw - vertexPosition)) + ((vertexPosition / vertexPosition)))))))));"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+ }
+}
+
+void tst_QShaderGenerator::shouldGenerateTemporariesWisely()
+{
+ // GIVEN
+ const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
+
+ {
+ auto attribute = createNode({
+ createPort(QShaderNodePort::Output, "vertexPosition")
+ });
+ attribute.addRule(gl4, QShaderNode::Rule("vec4 $vertexPosition = vertexPosition;",
+ QByteArrayList() << "in vec4 vertexPosition;"));
+
+ auto complexFunction = createNode({
+ createPort(QShaderNodePort::Input, "inputVarName"),
+ createPort(QShaderNodePort::Output, "out")
+ });
+ complexFunction.addRule(gl4, QShaderNode::Rule("vec4 $out = $inputVarName * 2.0;"));
+
+ auto complexFunction2 = createNode({
+ createPort(QShaderNodePort::Input, "inputVarName"),
+ createPort(QShaderNodePort::Output, "out")
+ });
+ complexFunction2.addRule(gl4, QShaderNode::Rule("vec4 $out = $inputVarName * 4.0;"));
+
+ auto complexFunction3 = createNode({
+ createPort(QShaderNodePort::Input, "a"),
+ createPort(QShaderNodePort::Input, "b"),
+ createPort(QShaderNodePort::Output, "out")
+ });
+ complexFunction3.addRule(gl4, QShaderNode::Rule("vec4 $out = $a + $b;"));
+
+ auto shaderOutput1 = createNode({
+ createPort(QShaderNodePort::Input, "input")
+ });
+
+ shaderOutput1.addRule(gl4, QShaderNode::Rule("shaderOutput1 = $input;",
+ QByteArrayList() << "out vec4 shaderOutput1;"));
+
+ auto shaderOutput2 = createNode({
+ createPort(QShaderNodePort::Input, "input")
+ });
+
+ shaderOutput2.addRule(gl4, QShaderNode::Rule("shaderOutput2 = $input;",
+ QByteArrayList() << "out vec4 shaderOutput2;"));
+
+ {
+ // WHEN
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+
+ res.addNode(attribute);
+ res.addNode(complexFunction);
+ res.addNode(shaderOutput1);
+
+ res.addEdge(createEdge(attribute.uuid(), "vertexPosition", complexFunction.uuid(), "inputVarName"));
+ res.addEdge(createEdge(complexFunction.uuid(), "out", shaderOutput1.uuid(), "input"));
+
+ return res;
+ }();
+
+ auto generator = QShaderGenerator();
+ generator.graph = graph;
+ generator.format = gl4;
+
+ const auto code = generator.createShaderCode();
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "in vec4 vertexPosition;"
+ << "out vec4 shaderOutput1;"
+ << ""
+ << "void main()"
+ << "{"
+ << " shaderOutput1 = vertexPosition * 2.0;"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+ }
+
+ {
+ // WHEN
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+
+ res.addNode(attribute);
+ res.addNode(complexFunction);
+ res.addNode(shaderOutput1);
+ res.addNode(shaderOutput2);
+
+ res.addEdge(createEdge(attribute.uuid(), "vertexPosition", complexFunction.uuid(), "inputVarName"));
+ res.addEdge(createEdge(complexFunction.uuid(), "out", shaderOutput1.uuid(), "input"));
+ res.addEdge(createEdge(complexFunction.uuid(), "out", shaderOutput2.uuid(), "input"));
+
+ return res;
+ }();
+
+ auto generator = QShaderGenerator();
+ generator.graph = graph;
+ generator.format = gl4;
+
+ const auto code = generator.createShaderCode();
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "in vec4 vertexPosition;"
+ << "out vec4 shaderOutput1;"
+ << "out vec4 shaderOutput2;"
+ << ""
+ << "void main()"
+ << "{"
+ << " vec4 v1 = vertexPosition * 2.0;"
+ << " shaderOutput2 = v1;"
+ << " shaderOutput1 = v1;"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+ }
+
+ {
+ // WHEN
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+
+ res.addNode(attribute);
+ res.addNode(complexFunction);
+ res.addNode(complexFunction2);
+ res.addNode(complexFunction3);
+ res.addNode(shaderOutput1);
+ res.addNode(shaderOutput2);
+
+ res.addEdge(createEdge(attribute.uuid(), "vertexPosition", complexFunction.uuid(), "inputVarName"));
+ res.addEdge(createEdge(attribute.uuid(), "vertexPosition", complexFunction2.uuid(), "inputVarName"));
+
+ res.addEdge(createEdge(complexFunction.uuid(), "out", complexFunction3.uuid(), "a"));
+ res.addEdge(createEdge(complexFunction2.uuid(), "out", complexFunction3.uuid(), "b"));
+
+ res.addEdge(createEdge(complexFunction3.uuid(), "out", shaderOutput1.uuid(), "input"));
+ res.addEdge(createEdge(complexFunction2.uuid(), "out", shaderOutput2.uuid(), "input"));
+
+ return res;
+ }();
+
+ auto generator = QShaderGenerator();
+ generator.graph = graph;
+ generator.format = gl4;
+
+ const auto code = generator.createShaderCode();
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "in vec4 vertexPosition;"
+ << "out vec4 shaderOutput1;"
+ << "out vec4 shaderOutput2;"
+ << ""
+ << "void main()"
+ << "{"
+ << " vec4 v2 = vertexPosition * 4.0;"
+ << " shaderOutput2 = v2;"
+ << " shaderOutput1 = (vertexPosition * 2.0 + v2);"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+ }
+ }
+}
+
+void tst_QShaderGenerator::shouldHandlePortNamesPrefixingOneAnother()
+{
+ // GIVEN
+ const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
+
+ auto color1 = createNode({
+ createPort(QShaderNodePort::Output, "output")
+ });
+ color1.addRule(gl4, QShaderNode::Rule("vec4 $output = color1;",
+ QByteArrayList() << "in vec4 color1;"));
+
+ auto color2 = createNode({
+ createPort(QShaderNodePort::Output, "output")
+ });
+ color2.addRule(gl4, QShaderNode::Rule("vec4 $output = color2;",
+ QByteArrayList() << "in vec4 color2;"));
+
+ auto addColor = createNode({
+ createPort(QShaderNodePort::Output, "color"),
+ createPort(QShaderNodePort::Input, "color1"),
+ createPort(QShaderNodePort::Input, "color2"),
+ });
+ addColor.addRule(gl4, QShaderNode::Rule("vec4 $color = $color1 + $color2;"));
+
+ auto shaderOutput = createNode({
+ createPort(QShaderNodePort::Input, "input")
+ });
+
+ shaderOutput.addRule(gl4, QShaderNode::Rule("shaderOutput = $input;",
+ QByteArrayList() << "out vec4 shaderOutput;"));
+
+ // WHEN
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+
+ res.addNode(color1);
+ res.addNode(color2);
+ res.addNode(addColor);
+ res.addNode(shaderOutput);
+
+ res.addEdge(createEdge(color1.uuid(), "output", addColor.uuid(), "color1"));
+ res.addEdge(createEdge(color2.uuid(), "output", addColor.uuid(), "color2"));
+ res.addEdge(createEdge(addColor.uuid(), "color", shaderOutput.uuid(), "input"));
+
+ return res;
+ }();
+
+ auto generator = QShaderGenerator();
+ generator.graph = graph;
+ generator.format = gl4;
+
+ const auto code = generator.createShaderCode();
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "in vec4 color1;"
+ << "in vec4 color2;"
+ << "out vec4 shaderOutput;"
+ << ""
+ << "void main()"
+ << "{"
+ << " shaderOutput = ((color1 + color2));"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+}
+
+void tst_QShaderGenerator::shouldHandleNodesWithMultipleOutputPorts()
+{
+ // GIVEN
+ const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
+
+ auto input = createNode({
+ createPort(QShaderNodePort::Output, "output0"),
+ createPort(QShaderNodePort::Output, "output1")
+ });
+ input.addRule(gl4, QShaderNode::Rule("vec4 $output0 = globalIn0;"
+ "float $output1 = globalIn1;",
+ QByteArrayList() << "in vec4 globalIn0;" << "in float globalIn1;"));
+
+ auto function = createNode({
+ createPort(QShaderNodePort::Input, "input0"),
+ createPort(QShaderNodePort::Input, "input1"),
+ createPort(QShaderNodePort::Output, "output0"),
+ createPort(QShaderNodePort::Output, "output1")
+ });
+ function.addRule(gl4, QShaderNode::Rule("vec4 $output0 = $input0;"
+ "float $output1 = $input1;"));
+
+ auto output = createNode({
+ createPort(QShaderNodePort::Input, "input0"),
+ createPort(QShaderNodePort::Input, "input1")
+ });
+
+ output.addRule(gl4, QShaderNode::Rule("globalOut0 = $input0;"
+ "globalOut1 = $input1;",
+ QByteArrayList() << "out vec4 globalOut0;" << "out float globalOut1;"));
+
+ // WHEN
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+
+ res.addNode(input);
+ res.addNode(function);
+ res.addNode(output);
+
+ res.addEdge(createEdge(input.uuid(), "output0", function.uuid(), "input0"));
+ res.addEdge(createEdge(input.uuid(), "output1", function.uuid(), "input1"));
+
+ res.addEdge(createEdge(function.uuid(), "output0", output.uuid(), "input0"));
+ res.addEdge(createEdge(function.uuid(), "output1", output.uuid(), "input1"));
+
+ return res;
+ }();
+
+ auto generator = QShaderGenerator();
+ generator.graph = graph;
+ generator.format = gl4;
+
+ const auto code = generator.createShaderCode();
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "in vec4 globalIn0;"
+ << "in float globalIn1;"
+ << "out vec4 globalOut0;"
+ << "out float globalOut1;"
+ << ""
+ << "void main()"
+ << "{"
+ << " globalOut0 = globalIn0;"
+ << " globalOut1 = globalIn1;"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+}
+
+void tst_QShaderGenerator::shouldHandleExpressionsInInputNodes()
+{
+ // GIVEN
+ const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
+
+ auto input = createNode({
+ createPort(QShaderNodePort::Output, "output")
+ });
+ input.addRule(gl4, QShaderNode::Rule("float $output = 3 + 4;"));
+
+ auto output = createNode({
+ createPort(QShaderNodePort::Input, "input")
+ });
+
+ output.addRule(gl4, QShaderNode::Rule("globalOut = $input;",
+ QByteArrayList() << "out float globalOut;"));
+
+ // WHEN
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+
+ res.addNode(input);
+ res.addNode(output);
+
+ res.addEdge(createEdge(input.uuid(), "output", output.uuid(), "input"));
+
+ return res;
+ }();
+
+ auto generator = QShaderGenerator();
+ generator.graph = graph;
+ generator.format = gl4;
+
+ const auto code = generator.createShaderCode();
+
+ // THEN
+ const auto expected = QByteArrayList()
+ << "#version 400 core"
+ << ""
+ << "out float globalOut;"
+ << ""
+ << "void main()"
+ << "{"
+ << " globalOut = 3 + 4;"
+ << "}"
+ << "";
+ QCOMPARE(code, expected.join("\n"));
+}
+
+QTEST_MAIN(tst_QShaderGenerator)
+
+#include "tst_qshadergenerator.moc"
diff --git a/tests/auto/render/shadergraph/qshadergraph/qshadergraph.pro b/tests/auto/render/shadergraph/qshadergraph/qshadergraph.pro
new file mode 100644
index 000000000..2348ccb24
--- /dev/null
+++ b/tests/auto/render/shadergraph/qshadergraph/qshadergraph.pro
@@ -0,0 +1,5 @@
+CONFIG += testcase
+QT += testlib 3drender-private
+
+SOURCES += tst_qshadergraph.cpp
+TARGET = tst_qshadergraph
diff --git a/tests/auto/render/shadergraph/qshadergraph/tst_qshadergraph.cpp b/tests/auto/render/shadergraph/qshadergraph/tst_qshadergraph.cpp
new file mode 100644
index 000000000..6336763f5
--- /dev/null
+++ b/tests/auto/render/shadergraph/qshadergraph/tst_qshadergraph.cpp
@@ -0,0 +1,821 @@
+/****************************************************************************
+**
+** 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 <Qt3DRender/private/qshadergraph_p.h>
+
+using namespace Qt3DRender;
+namespace
+{
+ 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, const QStringList &layers = QStringList())
+ {
+ auto node = QShaderNode();
+ node.setUuid(QUuid::createUuid());
+ node.setLayers(layers);
+ 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,
+ const QStringList &layers = QStringList())
+ {
+ auto edge = QShaderGraph::Edge();
+ edge.sourceNodeUuid = sourceUuid;
+ edge.sourcePortName = sourceName;
+ edge.targetNodeUuid = targetUuid;
+ edge.targetPortName = targetName;
+ edge.layers = layers;
+ return edge;
+ }
+
+ QShaderGraph::Statement createStatement(const QShaderNode &node,
+ const QVector<int> &inputs = QVector<int>(),
+ const QVector<int> &outputs = QVector<int>())
+ {
+ auto statement = QShaderGraph::Statement();
+ statement.node = node;
+ statement.inputs = inputs;
+ statement.outputs = outputs;
+ return statement;
+ }
+
+ 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_QShaderGraph : public QObject
+{
+ Q_OBJECT
+private slots:
+ void shouldHaveEdgeDefaultState();
+ void shouldTestEdgesEquality_data();
+ void shouldTestEdgesEquality();
+ void shouldManipulateStatementMembers();
+ void shouldTestStatementsEquality_data();
+ void shouldTestStatementsEquality();
+ void shouldFindIndexFromPortNameInStatements_data();
+ void shouldFindIndexFromPortNameInStatements();
+ void shouldManageNodeList();
+ void shouldManageEdgeList();
+ void shouldSerializeGraphForCodeGeneration();
+ void shouldHandleUnboundPortsDuringGraphSerialization();
+ void shouldSurviveCyclesDuringGraphSerialization();
+ void shouldDealWithEdgesJumpingOverLayers();
+ void shouldGenerateDifferentStatementsDependingOnActiveLayers();
+ void shouldDealWithBranchesWithoutOutput();
+};
+
+void tst_QShaderGraph::shouldHaveEdgeDefaultState()
+{
+ // GIVEN
+ auto edge = QShaderGraph::Edge();
+
+ // THEN
+ QVERIFY(edge.sourceNodeUuid.isNull());
+ QVERIFY(edge.sourcePortName.isEmpty());
+ QVERIFY(edge.targetNodeUuid.isNull());
+ QVERIFY(edge.targetPortName.isEmpty());
+}
+
+void tst_QShaderGraph::shouldTestEdgesEquality_data()
+{
+ QTest::addColumn<QShaderGraph::Edge>("left");
+ QTest::addColumn<QShaderGraph::Edge>("right");
+ QTest::addColumn<bool>("expected");
+
+ const auto sourceUuid1 = QUuid::createUuid();
+ const auto sourceUuid2 = QUuid::createUuid();
+ const auto targetUuid1 = QUuid::createUuid();
+ const auto targetUuid2 = QUuid::createUuid();
+
+ QTest::newRow("Equals") << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
+ << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
+ << true;
+ QTest::newRow("SourceUuid") << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
+ << createEdge(sourceUuid2, "foo", targetUuid1, "bar")
+ << false;
+ QTest::newRow("SourceName") << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
+ << createEdge(sourceUuid1, "bleh", targetUuid1, "bar")
+ << false;
+ QTest::newRow("TargetUuid") << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
+ << createEdge(sourceUuid1, "foo", targetUuid2, "bar")
+ << false;
+ QTest::newRow("TargetName") << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
+ << createEdge(sourceUuid1, "foo", targetUuid1, "bleh")
+ << false;
+}
+
+void tst_QShaderGraph::shouldTestEdgesEquality()
+{
+ // GIVEN
+ QFETCH(QShaderGraph::Edge, left);
+ QFETCH(QShaderGraph::Edge, right);
+
+ // WHEN
+ const auto equal = (left == right);
+ const auto notEqual = (left != right);
+
+ // THEN
+ QFETCH(bool, expected);
+ QCOMPARE(equal, expected);
+ QCOMPARE(notEqual, !expected);
+}
+
+void tst_QShaderGraph::shouldManipulateStatementMembers()
+{
+ // GIVEN
+ auto statement = QShaderGraph::Statement();
+
+ // THEN (default state)
+ QVERIFY(statement.inputs.isEmpty());
+ QVERIFY(statement.outputs.isEmpty());
+ QVERIFY(statement.node.uuid().isNull());
+ QVERIFY(statement.uuid().isNull());
+
+ // WHEN
+ const auto node = createNode({});
+ statement.node = node;
+
+ // THEN
+ QCOMPARE(statement.uuid(), node.uuid());
+
+ // WHEN
+ statement.node = QShaderNode();
+
+ // THEN
+ QVERIFY(statement.uuid().isNull());
+}
+
+void tst_QShaderGraph::shouldTestStatementsEquality_data()
+{
+ QTest::addColumn<QShaderGraph::Statement>("left");
+ QTest::addColumn<QShaderGraph::Statement>("right");
+ QTest::addColumn<bool>("expected");
+
+ const auto node1 = createNode({});
+ const auto node2 = createNode({});
+
+ QTest::newRow("EqualNodes") << createStatement(node1, {1, 2}, {3, 4})
+ << createStatement(node1, {1, 2}, {3, 4})
+ << true;
+ QTest::newRow("EqualInvalids") << createStatement(QShaderNode(), {1, 2}, {3, 4})
+ << createStatement(QShaderNode(), {1, 2}, {3, 4})
+ << true;
+ QTest::newRow("Nodes") << createStatement(node1, {1, 2}, {3, 4})
+ << createStatement(node2, {1, 2}, {3, 4})
+ << false;
+ QTest::newRow("Inputs") << createStatement(node1, {1, 2}, {3, 4})
+ << createStatement(node1, {1, 2, 0}, {3, 4})
+ << false;
+ QTest::newRow("Outputs") << createStatement(node1, {1, 2}, {3, 4})
+ << createStatement(node1, {1, 2}, {3, 0, 4})
+ << false;
+}
+
+void tst_QShaderGraph::shouldTestStatementsEquality()
+{
+ // GIVEN
+ QFETCH(QShaderGraph::Statement, left);
+ QFETCH(QShaderGraph::Statement, right);
+
+ // WHEN
+ const auto equal = (left == right);
+ const auto notEqual = (left != right);
+
+ // THEN
+ QFETCH(bool, expected);
+ QCOMPARE(equal, expected);
+ QCOMPARE(notEqual, !expected);
+}
+
+void tst_QShaderGraph::shouldFindIndexFromPortNameInStatements_data()
+{
+ QTest::addColumn<QShaderGraph::Statement>("statement");
+ QTest::addColumn<QString>("portName");
+ QTest::addColumn<int>("expectedInputIndex");
+ QTest::addColumn<int>("expectedOutputIndex");
+
+ const auto inputNodeStatement = createStatement(createNode({
+ createPort(QShaderNodePort::Output, "input")
+ }));
+ const auto outputNodeStatement = createStatement(createNode({
+ createPort(QShaderNodePort::Input, "output")
+ }));
+ const auto functionNodeStatement = createStatement(createNode({
+ createPort(QShaderNodePort::Input, "input1"),
+ createPort(QShaderNodePort::Output, "output1"),
+ createPort(QShaderNodePort::Input, "input2"),
+ createPort(QShaderNodePort::Output, "output2"),
+ createPort(QShaderNodePort::Output, "output3"),
+ createPort(QShaderNodePort::Input, "input3")
+ }));
+
+ QTest::newRow("Invalid") << QShaderGraph::Statement() << "foo" << -1 << -1;
+ QTest::newRow("InputNodeWrongName") << inputNodeStatement << "foo" << -1 << -1;
+ QTest::newRow("InputNodeExistingName") << inputNodeStatement << "input" << -1 << 0;
+ QTest::newRow("OutputNodeWrongName") << outputNodeStatement << "foo" << -1 << -1;
+ QTest::newRow("OutputNodeExistingName") << outputNodeStatement << "output" << 0 << -1;
+ QTest::newRow("FunctionNodeWrongName") << functionNodeStatement << "foo" << -1 << -1;
+ QTest::newRow("FunctionNodeInput1") << functionNodeStatement << "input1" << 0 << -1;
+ QTest::newRow("FunctionNodeOutput1") << functionNodeStatement << "output1" << -1 << 0;
+ QTest::newRow("FunctionNodeInput2") << functionNodeStatement << "input2" << 1 << -1;
+ QTest::newRow("FunctionNodeOutput2") << functionNodeStatement << "output2" << -1 << 1;
+ QTest::newRow("FunctionNodeInput3") << functionNodeStatement << "input3" << 2 << -1;
+ QTest::newRow("FunctionNodeOutput3") << functionNodeStatement << "output3" << -1 << 2;
+}
+
+void tst_QShaderGraph::shouldFindIndexFromPortNameInStatements()
+{
+ // GIVEN
+ QFETCH(QShaderGraph::Statement, statement);
+ QFETCH(QString, portName);
+ QFETCH(int, expectedInputIndex);
+ QFETCH(int, expectedOutputIndex);
+
+ // WHEN
+ const auto inputIndex = statement.portIndex(QShaderNodePort::Input, portName);
+ const auto outputIndex = statement.portIndex(QShaderNodePort::Output, portName);
+
+ // THEN
+ QCOMPARE(inputIndex, expectedInputIndex);
+ QCOMPARE(outputIndex, expectedOutputIndex);
+}
+
+void tst_QShaderGraph::shouldManageNodeList()
+{
+ // GIVEN
+ const auto node1 = createNode({createPort(QShaderNodePort::Output, "node1")});
+ const auto node2 = createNode({createPort(QShaderNodePort::Output, "node2")});
+
+ auto graph = QShaderGraph();
+
+ // THEN (default state)
+ QVERIFY(graph.nodes().isEmpty());
+
+ // WHEN
+ graph.addNode(node1);
+
+ // THEN
+ QCOMPARE(graph.nodes().size(), 1);
+ QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid());
+ QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name);
+
+ // WHEN
+ graph.addNode(node2);
+
+ // THEN
+ QCOMPARE(graph.nodes().size(), 2);
+ QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid());
+ QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name);
+ QCOMPARE(graph.nodes().at(1).uuid(), node2.uuid());
+ QCOMPARE(graph.nodes().at(1).ports().at(0).name, node2.ports().at(0).name);
+
+
+ // WHEN
+ graph.removeNode(node2);
+
+ // THEN
+ QCOMPARE(graph.nodes().size(), 1);
+ QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid());
+ QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name);
+
+ // WHEN
+ graph.addNode(node2);
+
+ // THEN
+ QCOMPARE(graph.nodes().size(), 2);
+ QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid());
+ QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name);
+ QCOMPARE(graph.nodes().at(1).uuid(), node2.uuid());
+ QCOMPARE(graph.nodes().at(1).ports().at(0).name, node2.ports().at(0).name);
+
+ // WHEN
+ const auto node1bis = [node1] {
+ auto res = node1;
+ auto port = res.ports().at(0);
+ port.name = QStringLiteral("node1bis");
+ res.addPort(port);
+ return res;
+ }();
+ graph.addNode(node1bis);
+
+ // THEN
+ QCOMPARE(graph.nodes().size(), 2);
+ QCOMPARE(graph.nodes().at(0).uuid(), node2.uuid());
+ QCOMPARE(graph.nodes().at(0).ports().at(0).name, node2.ports().at(0).name);
+ QCOMPARE(graph.nodes().at(1).uuid(), node1bis.uuid());
+ QCOMPARE(graph.nodes().at(1).ports().at(0).name, node1bis.ports().at(0).name);
+}
+
+void tst_QShaderGraph::shouldManageEdgeList()
+{
+ // GIVEN
+ const auto edge1 = createEdge(QUuid::createUuid(), "foo", QUuid::createUuid(), "bar");
+ const auto edge2 = createEdge(QUuid::createUuid(), "baz", QUuid::createUuid(), "boo");
+
+ auto graph = QShaderGraph();
+
+ // THEN (default state)
+ QVERIFY(graph.edges().isEmpty());
+
+ // WHEN
+ graph.addEdge(edge1);
+
+ // THEN
+ QCOMPARE(graph.edges().size(), 1);
+ QCOMPARE(graph.edges().at(0), edge1);
+
+ // WHEN
+ graph.addEdge(edge2);
+
+ // THEN
+ QCOMPARE(graph.edges().size(), 2);
+ QCOMPARE(graph.edges().at(0), edge1);
+ QCOMPARE(graph.edges().at(1), edge2);
+
+
+ // WHEN
+ graph.removeEdge(edge2);
+
+ // THEN
+ QCOMPARE(graph.edges().size(), 1);
+ QCOMPARE(graph.edges().at(0), edge1);
+
+ // WHEN
+ graph.addEdge(edge2);
+
+ // THEN
+ QCOMPARE(graph.edges().size(), 2);
+ QCOMPARE(graph.edges().at(0), edge1);
+ QCOMPARE(graph.edges().at(1), edge2);
+
+ // WHEN
+ graph.addEdge(edge1);
+
+ // THEN
+ QCOMPARE(graph.edges().size(), 2);
+ QCOMPARE(graph.edges().at(0), edge1);
+ QCOMPARE(graph.edges().at(1), edge2);
+}
+
+void tst_QShaderGraph::shouldSerializeGraphForCodeGeneration()
+{
+ // GIVEN
+ const auto input1 = createNode({
+ createPort(QShaderNodePort::Output, "input1Value")
+ });
+ const auto input2 = createNode({
+ createPort(QShaderNodePort::Output, "input2Value")
+ });
+ const auto output1 = createNode({
+ createPort(QShaderNodePort::Input, "output1Value")
+ });
+ const auto output2 = createNode({
+ createPort(QShaderNodePort::Input, "output2Value")
+ });
+ const auto function1 = createNode({
+ createPort(QShaderNodePort::Input, "function1Input"),
+ createPort(QShaderNodePort::Output, "function1Output")
+ });
+ const auto function2 = createNode({
+ createPort(QShaderNodePort::Input, "function2Input1"),
+ createPort(QShaderNodePort::Input, "function2Input2"),
+ createPort(QShaderNodePort::Output, "function2Output")
+ });
+ const auto function3 = createNode({
+ createPort(QShaderNodePort::Input, "function3Input1"),
+ createPort(QShaderNodePort::Input, "function3Input2"),
+ createPort(QShaderNodePort::Output, "function3Output1"),
+ createPort(QShaderNodePort::Output, "function3Output2")
+ });
+
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+ res.addNode(input1);
+ res.addNode(input2);
+ res.addNode(output1);
+ res.addNode(output2);
+ res.addNode(function1);
+ res.addNode(function2);
+ res.addNode(function3);
+ res.addEdge(createEdge(input1.uuid(), "input1Value", function1.uuid(), "function1Input"));
+ res.addEdge(createEdge(input1.uuid(), "input1Value", function2.uuid(), "function2Input1"));
+ res.addEdge(createEdge(input2.uuid(), "input2Value", function2.uuid(), "function2Input2"));
+ res.addEdge(createEdge(function1.uuid(), "function1Output", function3.uuid(), "function3Input1"));
+ res.addEdge(createEdge(function2.uuid(), "function2Output", function3.uuid(), "function3Input2"));
+ res.addEdge(createEdge(function3.uuid(), "function3Output1", output1.uuid(), "output1Value"));
+ res.addEdge(createEdge(function3.uuid(), "function3Output2", output2.uuid(), "output2Value"));
+ return res;
+ }();
+
+ // WHEN
+ const auto statements = graph.createStatements();
+
+ // THEN
+ const auto expected = QVector<QShaderGraph::Statement>()
+ << createStatement(input2, {}, {1})
+ << createStatement(input1, {}, {0})
+ << createStatement(function2, {0, 1}, {3})
+ << createStatement(function1, {0}, {2})
+ << createStatement(function3, {2, 3}, {4, 5})
+ << createStatement(output2, {5}, {})
+ << createStatement(output1, {4}, {});
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+}
+
+void tst_QShaderGraph::shouldHandleUnboundPortsDuringGraphSerialization()
+{
+ // GIVEN
+ const auto input = createNode({
+ createPort(QShaderNodePort::Output, "input")
+ });
+ const auto unboundInput = createNode({
+ createPort(QShaderNodePort::Output, "unbound")
+ });
+ const auto output = createNode({
+ createPort(QShaderNodePort::Input, "output")
+ });
+ const auto unboundOutput = createNode({
+ createPort(QShaderNodePort::Input, "unbound")
+ });
+ const auto function = createNode({
+ createPort(QShaderNodePort::Input, "functionInput1"),
+ createPort(QShaderNodePort::Input, "functionInput2"),
+ createPort(QShaderNodePort::Input, "functionInput3"),
+ createPort(QShaderNodePort::Output, "functionOutput1"),
+ createPort(QShaderNodePort::Output, "functionOutput2"),
+ createPort(QShaderNodePort::Output, "functionOutput3")
+ });
+
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+ res.addNode(input);
+ res.addNode(unboundInput);
+ res.addNode(output);
+ res.addNode(unboundOutput);
+ res.addNode(function);
+ res.addEdge(createEdge(input.uuid(), "input", function.uuid(), "functionInput2"));
+ res.addEdge(createEdge(function.uuid(), "functionOutput2", output.uuid(), "output"));
+ return res;
+ }();
+
+ // WHEN
+ const auto statements = graph.createStatements();
+
+ // THEN
+ // Note that no statement has any unbound input
+ const auto expected = QVector<QShaderGraph::Statement>()
+ << createStatement(input, {}, {0});
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+}
+
+void tst_QShaderGraph::shouldSurviveCyclesDuringGraphSerialization()
+{
+ // GIVEN
+ const auto input = createNode({
+ createPort(QShaderNodePort::Output, "input")
+ });
+ const auto output = createNode({
+ createPort(QShaderNodePort::Input, "output")
+ });
+ const auto function1 = createNode({
+ createPort(QShaderNodePort::Input, "function1Input1"),
+ createPort(QShaderNodePort::Input, "function1Input2"),
+ createPort(QShaderNodePort::Output, "function1Output")
+ });
+ const auto function2 = createNode({
+ createPort(QShaderNodePort::Input, "function2Input"),
+ createPort(QShaderNodePort::Output, "function2Output")
+ });
+ const auto function3 = createNode({
+ createPort(QShaderNodePort::Input, "function3Input"),
+ createPort(QShaderNodePort::Output, "function3Output")
+ });
+
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+ res.addNode(input);
+ res.addNode(output);
+ res.addNode(function1);
+ res.addNode(function2);
+ res.addNode(function3);
+ res.addEdge(createEdge(input.uuid(), "input", function1.uuid(), "function1Input1"));
+ res.addEdge(createEdge(function1.uuid(), "function1Output", function2.uuid(), "function2Input"));
+ res.addEdge(createEdge(function2.uuid(), "function2Output", function3.uuid(), "function3Input"));
+ res.addEdge(createEdge(function3.uuid(), "function3Output", function1.uuid(), "function1Input2"));
+ res.addEdge(createEdge(function2.uuid(), "function2Output", output.uuid(), "output"));
+ return res;
+ }();
+
+ // WHEN
+ const auto statements = graph.createStatements();
+
+ // THEN
+ // The cycle is ignored
+ const auto expected = QVector<QShaderGraph::Statement>();
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+}
+
+void tst_QShaderGraph::shouldDealWithEdgesJumpingOverLayers()
+{
+ // GIVEN
+ const auto worldPosition = createNode({
+ createPort(QShaderNodePort::Output, "worldPosition")
+ });
+ const auto texture = createNode({
+ createPort(QShaderNodePort::Output, "texture")
+ });
+ const auto texCoord = createNode({
+ createPort(QShaderNodePort::Output, "texCoord")
+ });
+ const auto lightIntensity = createNode({
+ createPort(QShaderNodePort::Output, "lightIntensity")
+ });
+ const auto exposure = createNode({
+ createPort(QShaderNodePort::Output, "exposure")
+ });
+ const auto fragColor = createNode({
+ createPort(QShaderNodePort::Input, "fragColor")
+ });
+ const auto sampleTexture = createNode({
+ createPort(QShaderNodePort::Input, "sampler"),
+ createPort(QShaderNodePort::Input, "coord"),
+ createPort(QShaderNodePort::Output, "color")
+ });
+ const auto lightFunction = createNode({
+ createPort(QShaderNodePort::Input, "baseColor"),
+ createPort(QShaderNodePort::Input, "position"),
+ createPort(QShaderNodePort::Input, "lightIntensity"),
+ createPort(QShaderNodePort::Output, "outputColor")
+ });
+ const auto exposureFunction = createNode({
+ createPort(QShaderNodePort::Input, "inputColor"),
+ createPort(QShaderNodePort::Input, "exposure"),
+ createPort(QShaderNodePort::Output, "outputColor")
+ });
+
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+
+ res.addNode(worldPosition);
+ res.addNode(texture);
+ res.addNode(texCoord);
+ res.addNode(lightIntensity);
+ res.addNode(exposure);
+ res.addNode(fragColor);
+ res.addNode(sampleTexture);
+ res.addNode(lightFunction);
+ res.addNode(exposureFunction);
+
+ res.addEdge(createEdge(texture.uuid(), "texture", sampleTexture.uuid(), "sampler"));
+ res.addEdge(createEdge(texCoord.uuid(), "texCoord", sampleTexture.uuid(), "coord"));
+
+ res.addEdge(createEdge(worldPosition.uuid(), "worldPosition", lightFunction.uuid(), "position"));
+ res.addEdge(createEdge(sampleTexture.uuid(), "color", lightFunction.uuid(), "baseColor"));
+ res.addEdge(createEdge(lightIntensity.uuid(), "lightIntensity", lightFunction.uuid(), "lightIntensity"));
+
+ res.addEdge(createEdge(lightFunction.uuid(), "outputColor", exposureFunction.uuid(), "inputColor"));
+ res.addEdge(createEdge(exposure.uuid(), "exposure", exposureFunction.uuid(), "exposure"));
+
+ res.addEdge(createEdge(exposureFunction.uuid(), "outputColor", fragColor.uuid(), "fragColor"));
+
+ return res;
+ }();
+
+ // WHEN
+ const auto statements = graph.createStatements();
+
+ // THEN
+ const auto expected = QVector<QShaderGraph::Statement>()
+ << createStatement(texCoord, {}, {2})
+ << createStatement(texture, {}, {1})
+ << createStatement(lightIntensity, {}, {3})
+ << createStatement(sampleTexture, {1, 2}, {5})
+ << createStatement(worldPosition, {}, {0})
+ << createStatement(exposure, {}, {4})
+ << createStatement(lightFunction, {5, 0, 3}, {6})
+ << createStatement(exposureFunction, {6, 4}, {7})
+ << createStatement(fragColor, {7}, {});
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+}
+
+void tst_QShaderGraph::shouldGenerateDifferentStatementsDependingOnActiveLayers()
+{
+ // GIVEN
+ const auto texCoord = createNode({
+ createPort(QShaderNodePort::Output, "texCoord")
+ }, {
+ "diffuseTexture",
+ "normalTexture"
+ });
+ const auto diffuseUniform = createNode({
+ createPort(QShaderNodePort::Output, "color")
+ }, {"diffuseUniform"});
+ const auto diffuseTexture = createNode({
+ createPort(QShaderNodePort::Input, "coord"),
+ createPort(QShaderNodePort::Output, "color")
+ }, {"diffuseTexture"});
+ const auto normalUniform = createNode({
+ createPort(QShaderNodePort::Output, "normal")
+ }, {"normalUniform"});
+ const auto normalTexture = createNode({
+ createPort(QShaderNodePort::Input, "coord"),
+ createPort(QShaderNodePort::Output, "normal")
+ }, {"normalTexture"});
+ const auto lightFunction = createNode({
+ createPort(QShaderNodePort::Input, "color"),
+ createPort(QShaderNodePort::Input, "normal"),
+ createPort(QShaderNodePort::Output, "output")
+ });
+ const auto fragColor = createNode({
+ createPort(QShaderNodePort::Input, "fragColor")
+ });
+
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+
+ res.addNode(texCoord);
+ res.addNode(diffuseUniform);
+ res.addNode(diffuseTexture);
+ res.addNode(normalUniform);
+ res.addNode(normalTexture);
+ res.addNode(lightFunction);
+ res.addNode(fragColor);
+
+ res.addEdge(createEdge(diffuseUniform.uuid(), "color", lightFunction.uuid(), "color", {"diffuseUniform"}));
+ res.addEdge(createEdge(texCoord.uuid(), "texCoord", diffuseTexture.uuid(), "coord", {"diffuseTexture"}));
+ res.addEdge(createEdge(diffuseTexture.uuid(), "color", lightFunction.uuid(), "color", {"diffuseTexture"}));
+
+ res.addEdge(createEdge(normalUniform.uuid(), "normal", lightFunction.uuid(), "normal", {"normalUniform"}));
+ res.addEdge(createEdge(texCoord.uuid(), "texCoord", normalTexture.uuid(), "coord", {"normalTexture"}));
+ res.addEdge(createEdge(normalTexture.uuid(), "normal", lightFunction.uuid(), "normal", {"normalTexture"}));
+
+ res.addEdge(createEdge(lightFunction.uuid(), "output", fragColor.uuid(), "fragColor"));
+
+ return res;
+ }();
+
+ {
+ // WHEN
+ const auto statements = graph.createStatements({"diffuseUniform", "normalUniform"});
+
+ // THEN
+ const auto expected = QVector<QShaderGraph::Statement>()
+ << createStatement(normalUniform, {}, {1})
+ << createStatement(diffuseUniform, {}, {0})
+ << createStatement(lightFunction, {0, 1}, {2})
+ << createStatement(fragColor, {2}, {});
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+ }
+
+ {
+ // WHEN
+ const auto statements = graph.createStatements({"diffuseUniform", "normalTexture"});
+
+ // THEN
+ const auto expected = QVector<QShaderGraph::Statement>()
+ << createStatement(texCoord, {}, {0})
+ << createStatement(normalTexture, {0}, {2})
+ << createStatement(diffuseUniform, {}, {1})
+ << createStatement(lightFunction, {1, 2}, {3})
+ << createStatement(fragColor, {3}, {});
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+ }
+
+ {
+ // WHEN
+ const auto statements = graph.createStatements({"diffuseTexture", "normalUniform"});
+
+ // THEN
+ const auto expected = QVector<QShaderGraph::Statement>()
+ << createStatement(texCoord, {}, {0})
+ << createStatement(normalUniform, {}, {2})
+ << createStatement(diffuseTexture, {0}, {1})
+ << createStatement(lightFunction, {1, 2}, {3})
+ << createStatement(fragColor, {3}, {});
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+ }
+
+ {
+ // WHEN
+ const auto statements = graph.createStatements({"diffuseTexture", "normalTexture"});
+
+ // THEN
+ const auto expected = QVector<QShaderGraph::Statement>()
+ << createStatement(texCoord, {}, {0})
+ << createStatement(normalTexture, {0}, {2})
+ << createStatement(diffuseTexture, {0}, {1})
+ << createStatement(lightFunction, {1, 2}, {3})
+ << createStatement(fragColor, {3}, {});
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+ }
+}
+
+void tst_QShaderGraph::shouldDealWithBranchesWithoutOutput()
+{
+ // GIVEN
+ const auto input = createNode({
+ createPort(QShaderNodePort::Output, "input")
+ });
+ const auto output = createNode({
+ createPort(QShaderNodePort::Input, "output")
+ });
+ const auto danglingFunction = createNode({
+ createPort(QShaderNodePort::Input, "functionInput"),
+ createPort(QShaderNodePort::Output, "unbound")
+ });
+ const auto function = createNode({
+ createPort(QShaderNodePort::Input, "functionInput"),
+ createPort(QShaderNodePort::Output, "functionOutput")
+ });
+
+ const auto graph = [=] {
+ auto res = QShaderGraph();
+ res.addNode(input);
+ res.addNode(function);
+ res.addNode(danglingFunction);
+ res.addNode(output);
+ res.addEdge(createEdge(input.uuid(), "input", function.uuid(), "functionInput"));
+ res.addEdge(createEdge(input.uuid(), "input", danglingFunction.uuid(), "functionInput"));
+ res.addEdge(createEdge(function.uuid(), "functionOutput", output.uuid(), "output"));
+ return res;
+ }();
+
+ // WHEN
+ const auto statements = graph.createStatements();
+
+ // THEN
+ // Note that no edge leads to the unbound input
+ const auto expected = QVector<QShaderGraph::Statement>()
+ << createStatement(input, {}, {0})
+ << createStatement(function, {0}, {1})
+ << createStatement(output, {1}, {})
+ << createStatement(danglingFunction, {0}, {2});
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+}
+
+QTEST_MAIN(tst_QShaderGraph)
+
+#include "tst_qshadergraph.moc"
diff --git a/tests/auto/render/shadergraph/qshadergraphloader/qshadergraphloader.pro b/tests/auto/render/shadergraph/qshadergraphloader/qshadergraphloader.pro
new file mode 100644
index 000000000..754f801e0
--- /dev/null
+++ b/tests/auto/render/shadergraph/qshadergraphloader/qshadergraphloader.pro
@@ -0,0 +1,5 @@
+CONFIG += testcase
+QT += testlib 3drender-private
+
+SOURCES += tst_qshadergraphloader.cpp
+TARGET = tst_qshadergraphloader
diff --git a/tests/auto/render/shadergraph/qshadergraphloader/tst_qshadergraphloader.cpp b/tests/auto/render/shadergraph/qshadergraphloader/tst_qshadergraphloader.cpp
new file mode 100644
index 000000000..25e71d50b
--- /dev/null
+++ b/tests/auto/render/shadergraph/qshadergraphloader/tst_qshadergraphloader.cpp
@@ -0,0 +1,627 @@
+/****************************************************************************
+**
+** 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 <Qt3DRender/private/qshadergraphloader_p.h>
+#include <Qt3DRender/private/qshaderlanguage_p.h>
+
+using namespace Qt3DRender;
+
+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, const QStringList &layers = QStringList())
+ {
+ auto node = QShaderNode();
+ node.setUuid(QUuid::createUuid());
+ node.setLayers(layers);
+ 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,
+ const QStringList &layers = QStringList())
+ {
+ auto edge = QShaderGraph::Edge();
+ edge.sourceNodeUuid = sourceUuid;
+ edge.sourcePortName = sourceName;
+ edge.targetNodeUuid = targetUuid;
+ edge.targetPortName = targetName;
+ edge.layers = layers;
+ 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, "value")
+ });
+ worldPosition.setUuid(QUuid("{00000000-0000-0000-0000-000000000001}"));
+ worldPosition.setParameter("name", "worldPosition");
+ worldPosition.setParameter("qualifier", QVariant::fromValue<QShaderLanguage::StorageQualifier>(QShaderLanguage::Input));
+ worldPosition.setParameter("type", QVariant::fromValue<QShaderLanguage::VariableType>(QShaderLanguage::Vec3));
+ worldPosition.addRule(openGLES2, QShaderNode::Rule("highp $type $value = $name;",
+ QByteArrayList() << "$qualifier highp $type $name;"));
+ worldPosition.addRule(openGL3, QShaderNode::Rule("$type $value = $name;",
+ QByteArrayList() << "$qualifier $type $name;"));
+
+ 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, "value")
+ });
+ lightIntensity.setUuid(QUuid("{00000000-0000-0000-0000-000000000004}"));
+ lightIntensity.setParameter("name", "defaultName");
+ lightIntensity.setParameter("qualifier", QVariant::fromValue<QShaderLanguage::StorageQualifier>(QShaderLanguage::Uniform));
+ lightIntensity.setParameter("type", QVariant::fromValue<QShaderLanguage::VariableType>(QShaderLanguage::Float));
+ lightIntensity.addRule(openGLES2, QShaderNode::Rule("highp $type $value = $name;",
+ QByteArrayList() << "$qualifier highp $type $name;"));
+ lightIntensity.addRule(openGL3, QShaderNode::Rule("$type $value = $name;",
+ QByteArrayList() << "$qualifier $type $name;"));
+
+ 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(), "value", lightFunction.uuid(), "position"));
+ graph.addEdge(createEdge(sampleTexture.uuid(), "color", lightFunction.uuid(), "baseColor"));
+ graph.addEdge(createEdge(lightIntensity.uuid(), "value", 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\","
+ " \"layers\": [\"foo\", \"bar\"]"
+ " },"
+ " {"
+ " \"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\","
+ " \"layers\": [\"bar\", \"baz\"]"
+ " },"
+ " {"
+ " \"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")
+ }, {"foo", "bar"});
+ 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", {"bar", "baz"}));
+ 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\": \"inputValue\","
+ " \"parameters\": {"
+ " \"name\": \"worldPosition\","
+ " \"qualifier\": {"
+ " \"type\": \"QShaderLanguage::StorageQualifier\","
+ " \"value\": \"QShaderLanguage::Input\""
+ " },"
+ " \"type\": {"
+ " \"type\": \"QShaderLanguage::VariableType\","
+ " \"value\": \"QShaderLanguage::Vec3\""
+ " }"
+ " }"
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000002}\","
+ " \"type\": \"texture\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000003}\","
+ " \"type\": \"texCoord\""
+ " },"
+ " {"
+ " \"uuid\": \"{00000000-0000-0000-0000-000000000004}\","
+ " \"type\": \"inputValue\""
+ " },"
+ " {"
+ " \"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\": \"value\","
+ " \"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\": \"value\","
+ " \"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]{
+ const auto openGLES2 = createFormat(QShaderFormat::OpenGLES, 2, 0);
+ const auto openGL3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0);
+
+ auto protos = PrototypeHash();
+
+ auto inputValue = createNode({
+ createPort(QShaderNodePort::Output, "value")
+ });
+ inputValue.setParameter("name", "defaultName");
+ inputValue.setParameter("qualifier", QVariant::fromValue<QShaderLanguage::StorageQualifier>(QShaderLanguage::Uniform));
+ inputValue.setParameter("type", QVariant::fromValue<QShaderLanguage::VariableType>(QShaderLanguage::Float));
+ inputValue.addRule(openGLES2, QShaderNode::Rule("highp $type $value = $name;",
+ QByteArrayList() << "$qualifier highp $type $name;"));
+ inputValue.addRule(openGL3, QShaderNode::Rule("$type $value = $name;",
+ QByteArrayList() << "$qualifier $type $name;"));
+ protos.insert("inputValue", inputValue);
+
+ auto texture = createNode({
+ createPort(QShaderNodePort::Output, "texture")
+ });
+ texture.addRule(openGLES2, QShaderNode::Rule("sampler2D $texture = texture;",
+ QByteArrayList() << "uniform sampler2D texture;"));
+ texture.addRule(openGL3, QShaderNode::Rule("sampler2D $texture = texture;",
+ QByteArrayList() << "uniform sampler2D texture;"));
+ protos.insert("texture", texture);
+
+ auto texCoord = createNode({
+ createPort(QShaderNodePort::Output, "texCoord")
+ });
+ 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;"));
+ protos.insert("texCoord", texCoord);
+
+ auto exposure = createNode({
+ createPort(QShaderNodePort::Output, "exposure")
+ });
+ 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;"));
+ protos.insert("exposure", exposure);
+
+ auto fragColor = createNode({
+ createPort(QShaderNodePort::Input, "fragColor")
+ });
+ fragColor.addRule(openGLES2, QShaderNode::Rule("gl_fragColor = $fragColor;"));
+ fragColor.addRule(openGL3, QShaderNode::Rule("fragColor = $fragColor;",
+ QByteArrayList() << "out vec4 fragColor;"));
+ protos.insert("fragColor", fragColor);
+
+ auto sampleTexture = createNode({
+ createPort(QShaderNodePort::Input, "sampler"),
+ createPort(QShaderNodePort::Input, "coord"),
+ createPort(QShaderNodePort::Output, "color")
+ });
+ sampleTexture.addRule(openGLES2, QShaderNode::Rule("highp vec4 $color = texture2D($sampler, $coord);"));
+ sampleTexture.addRule(openGL3, QShaderNode::Rule("vec4 $color = texture2D($sampler, $coord);"));
+ protos.insert("sampleTexture", sampleTexture);
+
+ auto lightModel = createNode({
+ createPort(QShaderNodePort::Input, "baseColor"),
+ createPort(QShaderNodePort::Input, "position"),
+ createPort(QShaderNodePort::Input, "lightIntensity"),
+ createPort(QShaderNodePort::Output, "outputColor")
+ });
+ lightModel.addRule(openGLES2, QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);",
+ QByteArrayList() << "#pragma include es2/lightmodel.frag.inc"));
+ lightModel.addRule(openGL3, QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);",
+ QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc"));
+ protos.insert("lightModel", lightModel);
+
+ auto exposureFunction = createNode({
+ createPort(QShaderNodePort::Input, "inputColor"),
+ createPort(QShaderNodePort::Input, "exposure"),
+ createPort(QShaderNodePort::Output, "outputColor")
+ });
+ 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);"));
+ 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({"foo", "bar", "baz"});
+ const auto expected = graph.createStatements({"foo", "bar", "baz"});
+ dumpStatementsIfNeeded(statements, expected);
+ QCOMPARE(statements, expected);
+
+ const auto sortedParameters = [](const QShaderNode &node) {
+ auto res = node.parameterNames();
+ res.sort();
+ return res;
+ };
+
+ for (int i = 0; i < statements.size(); i++) {
+ const auto actualNode = statements.at(i).node;
+ const auto expectedNode = expected.at(i).node;
+
+ QCOMPARE(actualNode.layers(), expectedNode.layers());
+ QCOMPARE(actualNode.ports(), expectedNode.ports());
+ QCOMPARE(sortedParameters(actualNode), sortedParameters(expectedNode));
+ for (const auto &name : expectedNode.parameterNames()) {
+ QCOMPARE(actualNode.parameter(name), expectedNode.parameter(name));
+ }
+ QCOMPARE(actualNode.availableFormats(), expectedNode.availableFormats());
+ for (const auto &format : expectedNode.availableFormats()) {
+ QCOMPARE(actualNode.rule(format), expectedNode.rule(format));
+ }
+ }
+}
+
+QTEST_MAIN(tst_QShaderGraphLoader)
+
+#include "tst_qshadergraphloader.moc"
diff --git a/tests/auto/render/shadergraph/qshadernodes/qshadernodes.pro b/tests/auto/render/shadergraph/qshadernodes/qshadernodes.pro
new file mode 100644
index 000000000..754cde9e1
--- /dev/null
+++ b/tests/auto/render/shadergraph/qshadernodes/qshadernodes.pro
@@ -0,0 +1,5 @@
+CONFIG += testcase
+QT += testlib 3drender-private
+
+SOURCES += tst_qshadernodes.cpp
+TARGET = tst_qshadernodes
diff --git a/tests/auto/render/shadergraph/qshadernodes/tst_qshadernodes.cpp b/tests/auto/render/shadergraph/qshadernodes/tst_qshadernodes.cpp
new file mode 100644
index 000000000..2cd2ff90d
--- /dev/null
+++ b/tests/auto/render/shadergraph/qshadernodes/tst_qshadernodes.cpp
@@ -0,0 +1,549 @@
+/****************************************************************************
+**
+** 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 <Qt3DRender/private/qshaderformat_p.h>
+#include <Qt3DRender/private/qshadernode_p.h>
+#include <Qt3DRender/private/qshadernodeport_p.h>
+
+using namespace Qt3DRender;
+namespace
+{
+ QShaderFormat createFormat(QShaderFormat::Api api, int majorVersion, int minorVersion,
+ const QStringList &extensions = QStringList(),
+ const QString &vendor = QString())
+ {
+ auto format = QShaderFormat();
+ format.setApi(api);
+ format.setVersion(QVersionNumber(majorVersion, minorVersion));
+ format.setExtensions(extensions);
+ format.setVendor(vendor);
+ return format;
+ }
+
+ QShaderNodePort createPort(QShaderNodePort::Direction direction, const QString &name)
+ {
+ auto port = QShaderNodePort();
+ port.direction = direction;
+ port.name = name;
+ return port;
+ }
+}
+
+class tst_QShaderNodes : public QObject
+{
+ Q_OBJECT
+private slots:
+ void shouldManipulateFormatMembers();
+ void shouldVerifyFormatsEquality_data();
+ void shouldVerifyFormatsEquality();
+ void shouldVerifyFormatsCompatibilities_data();
+ void shouldVerifyFormatsCompatibilities();
+
+ void shouldHaveDefaultPortState();
+ void shouldVerifyPortsEquality_data();
+ void shouldVerifyPortsEquality();
+
+ void shouldManipulateNodeMembers();
+ void shouldHandleNodeRulesSupportAndOrder();
+};
+
+void tst_QShaderNodes::shouldManipulateFormatMembers()
+{
+ // GIVEN
+ auto format = QShaderFormat();
+
+ // THEN (default state)
+ QCOMPARE(format.api(), QShaderFormat::NoApi);
+ QCOMPARE(format.version().majorVersion(), 0);
+ QCOMPARE(format.version().minorVersion(), 0);
+ QCOMPARE(format.extensions(), QStringList());
+ QCOMPARE(format.vendor(), QString());
+ QVERIFY(!format.isValid());
+
+ // WHEN
+ format.setApi(QShaderFormat::OpenGLES);
+
+ // THEN
+ QCOMPARE(format.api(), QShaderFormat::OpenGLES);
+ QCOMPARE(format.version().majorVersion(), 0);
+ QCOMPARE(format.version().minorVersion(), 0);
+ QCOMPARE(format.extensions(), QStringList());
+ QCOMPARE(format.vendor(), QString());
+ QVERIFY(!format.isValid());
+
+ // WHEN
+ format.setVersion(QVersionNumber(3));
+
+ // THEN
+ QCOMPARE(format.api(), QShaderFormat::OpenGLES);
+ QCOMPARE(format.version().majorVersion(), 3);
+ QCOMPARE(format.version().minorVersion(), 0);
+ QCOMPARE(format.extensions(), QStringList());
+ QCOMPARE(format.vendor(), QString());
+ QVERIFY(format.isValid());
+
+ // WHEN
+ format.setVersion(QVersionNumber(3, 2));
+
+ // THEN
+ QCOMPARE(format.api(), QShaderFormat::OpenGLES);
+ QCOMPARE(format.version().majorVersion(), 3);
+ QCOMPARE(format.version().minorVersion(), 2);
+ QCOMPARE(format.extensions(), QStringList());
+ QCOMPARE(format.vendor(), QString());
+ QVERIFY(format.isValid());
+
+ // WHEN
+ format.setExtensions({"foo", "bar"});
+
+ // THEN
+ QCOMPARE(format.api(), QShaderFormat::OpenGLES);
+ QCOMPARE(format.version().majorVersion(), 3);
+ QCOMPARE(format.version().minorVersion(), 2);
+ QCOMPARE(format.extensions(), QStringList({"bar", "foo"}));
+ QCOMPARE(format.vendor(), QString());
+ QVERIFY(format.isValid());
+
+ // WHEN
+ format.setVendor(QStringLiteral("KDAB"));
+
+ // THEN
+ QCOMPARE(format.api(), QShaderFormat::OpenGLES);
+ QCOMPARE(format.version().majorVersion(), 3);
+ QCOMPARE(format.version().minorVersion(), 2);
+ QCOMPARE(format.extensions(), QStringList({"bar", "foo"}));
+ QCOMPARE(format.vendor(), QStringLiteral("KDAB"));
+ QVERIFY(format.isValid());
+}
+
+void tst_QShaderNodes::shouldVerifyFormatsEquality_data()
+{
+ QTest::addColumn<QShaderFormat>("left");
+ QTest::addColumn<QShaderFormat>("right");
+ QTest::addColumn<bool>("expected");
+
+ QTest::newRow("Equals") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo", "bar"}, "KDAB")
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo", "bar"}, "KDAB")
+ << true;
+ QTest::newRow("Apis") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo", "bar"}, "KDAB")
+ << createFormat(QShaderFormat::OpenGLNoProfile, 3, 0, {"foo", "bar"}, "KDAB")
+ << false;
+ QTest::newRow("Major") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo", "bar"}, "KDAB")
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0, {"foo", "bar"}, "KDAB")
+ << false;
+ QTest::newRow("Minor") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo", "bar"}, "KDAB")
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 1, {"foo", "bar"}, "KDAB")
+ << false;
+ QTest::newRow("Extensions") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo", "bar"}, "KDAB")
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo"}, "KDAB")
+ << false;
+ QTest::newRow("Vendor") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo", "bar"}, "KDAB")
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo", "bar"})
+ << false;
+}
+
+void tst_QShaderNodes::shouldVerifyFormatsEquality()
+{
+ // GIVEN
+ QFETCH(QShaderFormat, left);
+ QFETCH(QShaderFormat, right);
+
+ // WHEN
+ const auto equal = (left == right);
+ const auto notEqual = (left != right);
+
+ // THEN
+ QFETCH(bool, expected);
+ QCOMPARE(equal, expected);
+ QCOMPARE(notEqual, !expected);
+}
+
+void tst_QShaderNodes::shouldVerifyFormatsCompatibilities_data()
+{
+ QTest::addColumn<QShaderFormat>("reference");
+ QTest::addColumn<QShaderFormat>("tested");
+ QTest::addColumn<bool>("expected");
+
+ QTest::newRow("NoProfileVsES") << createFormat(QShaderFormat::OpenGLNoProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLES, 2, 0)
+ << true;
+ QTest::newRow("CoreProfileVsES") << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLES, 2, 0)
+ << false;
+ QTest::newRow("CompatProfileVsES") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLES, 2, 0)
+ << true;
+
+ QTest::newRow("ESVsNoProfile") << createFormat(QShaderFormat::OpenGLES, 2, 0)
+ << createFormat(QShaderFormat::OpenGLNoProfile, 2, 0)
+ << false;
+ QTest::newRow("ESVsCoreProfile") << createFormat(QShaderFormat::OpenGLES, 2, 0)
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0)
+ << false;
+ QTest::newRow("ESVsCompatProfile") << createFormat(QShaderFormat::OpenGLES, 2, 0)
+ << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 0)
+ << false;
+
+ QTest::newRow("CoreVsNoProfile") << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLNoProfile, 2, 0)
+ << false;
+ QTest::newRow("CoreVsCompat") << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 0)
+ << false;
+ QTest::newRow("CoreVsCore") << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0)
+ << true;
+
+ QTest::newRow("NoProfileVsCore") << createFormat(QShaderFormat::OpenGLNoProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0)
+ << true;
+ QTest::newRow("NoProvileVsCompat") << createFormat(QShaderFormat::OpenGLNoProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 0)
+ << true;
+ QTest::newRow("NoProfileVsNoProfile") << createFormat(QShaderFormat::OpenGLNoProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLNoProfile, 2, 0)
+ << true;
+
+ QTest::newRow("CompatVsCore") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0)
+ << true;
+ QTest::newRow("CompatVsCompat") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 0)
+ << true;
+ QTest::newRow("CompatVsNoProfile") << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLNoProfile, 2, 0)
+ << true;
+
+ QTest::newRow("MajorForwardCompat_1") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0)
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0)
+ << true;
+ QTest::newRow("MajorForwardCompat_2") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0)
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 4)
+ << true;
+ QTest::newRow("MajorForwardCompat_3") << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0)
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0)
+ << false;
+ QTest::newRow("MajorForwardCompat_4") << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 4)
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0)
+ << false;
+
+ QTest::newRow("MinorForwardCompat_1") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 1)
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0)
+ << true;
+ QTest::newRow("MinorForwardCompat_2") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0)
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 1)
+ << false;
+
+ QTest::newRow("Extensions_1") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo", "bar"})
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo"})
+ << true;
+ QTest::newRow("Extensions_2") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo"})
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {"foo", "bar"})
+ << false;
+
+ QTest::newRow("Vendor_1") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {}, "KDAB")
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {})
+ << true;
+ QTest::newRow("Vendor_2") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {})
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {}, "KDAB")
+ << false;
+ QTest::newRow("Vendor_2") << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {}, "KDAB")
+ << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, {}, "KDAB")
+ << true;
+}
+
+void tst_QShaderNodes::shouldVerifyFormatsCompatibilities()
+{
+ // GIVEN
+ QFETCH(QShaderFormat, reference);
+ QFETCH(QShaderFormat, tested);
+
+ // WHEN
+ const auto supported = reference.supports(tested);
+
+ // THEN
+ QFETCH(bool, expected);
+ QCOMPARE(supported, expected);
+}
+
+void tst_QShaderNodes::shouldHaveDefaultPortState()
+{
+ // GIVEN
+ auto port = QShaderNodePort();
+
+ // THEN
+ QCOMPARE(port.direction, QShaderNodePort::Output);
+ QVERIFY(port.name.isEmpty());
+}
+
+void tst_QShaderNodes::shouldVerifyPortsEquality_data()
+{
+ QTest::addColumn<QShaderNodePort>("left");
+ QTest::addColumn<QShaderNodePort>("right");
+ QTest::addColumn<bool>("expected");
+
+ QTest::newRow("Equals") << createPort(QShaderNodePort::Input, "foo")
+ << createPort(QShaderNodePort::Input, "foo")
+ << true;
+ QTest::newRow("Direction") << createPort(QShaderNodePort::Input, "foo")
+ << createPort(QShaderNodePort::Output, "foo")
+ << false;
+ QTest::newRow("Name") << createPort(QShaderNodePort::Input, "foo")
+ << createPort(QShaderNodePort::Input, "bar")
+ << false;
+}
+
+void tst_QShaderNodes::shouldVerifyPortsEquality()
+{
+ // GIVEN
+ QFETCH(QShaderNodePort, left);
+ QFETCH(QShaderNodePort, right);
+
+ // WHEN
+ const auto equal = (left == right);
+ const auto notEqual = (left != right);
+
+ // THEN
+ QFETCH(bool, expected);
+ QCOMPARE(equal, expected);
+ QCOMPARE(notEqual, !expected);
+}
+
+void tst_QShaderNodes::shouldManipulateNodeMembers()
+{
+ // GIVEN
+ const auto openGLES2 = createFormat(QShaderFormat::OpenGLES, 2, 0);
+ const auto openGL3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0);
+
+ const auto es2Rule = QShaderNode::Rule(QByteArrayLiteral("gles2"), {"#pragma include es2/foo.inc", "#pragma include es2/bar.inc"});
+ const auto gl3Rule = QShaderNode::Rule(QByteArrayLiteral("gl3"), {"#pragma include gl3/foo.inc", "#pragma include gl3/bar.inc"});
+ const auto gl3bisRule = QShaderNode::Rule(QByteArrayLiteral("gl3bis"), {"#pragma include gl3/foo.inc", "#pragma include gl3/bar.inc"});
+
+ auto node = QShaderNode();
+
+ // THEN (default state)
+ QCOMPARE(node.type(), QShaderNode::Invalid);
+ QVERIFY(node.uuid().isNull());
+ QVERIFY(node.layers().isEmpty());
+ QVERIFY(node.ports().isEmpty());
+ QVERIFY(node.parameterNames().isEmpty());
+ QVERIFY(node.availableFormats().isEmpty());
+
+ // WHEN
+ const auto uuid = QUuid::createUuid();
+ node.setUuid(uuid);
+
+ // THEN
+ QCOMPARE(node.uuid(), uuid);
+
+ // WHEN
+ node.setLayers({"foo", "bar"});
+
+ // THEN
+ QCOMPARE(node.layers(), QStringList({"foo", "bar"}));
+
+ // WHEN
+ auto firstPort = QShaderNodePort();
+ firstPort.direction = QShaderNodePort::Input;
+ firstPort.name = QStringLiteral("foo");
+ node.addPort(firstPort);
+
+ // THEN
+ QCOMPARE(node.type(), QShaderNode::Output);
+ QCOMPARE(node.ports().size(), 1);
+ QCOMPARE(node.ports().at(0), firstPort);
+ QVERIFY(node.availableFormats().isEmpty());
+
+ // WHEN
+ auto secondPort = QShaderNodePort();
+ secondPort.direction = QShaderNodePort::Output;
+ secondPort.name = QStringLiteral("bar");
+ node.addPort(secondPort);
+
+ // THEN
+ QCOMPARE(node.type(), QShaderNode::Function);
+ QCOMPARE(node.ports().size(), 2);
+ QCOMPARE(node.ports().at(0), firstPort);
+ QCOMPARE(node.ports().at(1), secondPort);
+ QVERIFY(node.availableFormats().isEmpty());
+
+ // WHEN
+ node.removePort(firstPort);
+
+ // THEN
+ QCOMPARE(node.type(), QShaderNode::Input);
+ QCOMPARE(node.ports().size(), 1);
+ QCOMPARE(node.ports().at(0), secondPort);
+ QVERIFY(node.availableFormats().isEmpty());
+
+ // WHEN
+ node.setParameter(QStringLiteral("baz"), 42);
+
+ // THEN
+ QCOMPARE(node.type(), QShaderNode::Input);
+ QCOMPARE(node.ports().size(), 1);
+ QCOMPARE(node.ports().at(0), secondPort);
+ auto parameterNames = node.parameterNames();
+ parameterNames.sort();
+ QCOMPARE(parameterNames.size(), 1);
+ QCOMPARE(parameterNames.at(0), QStringLiteral("baz"));
+ QCOMPARE(node.parameter(QStringLiteral("baz")), QVariant(42));
+ QVERIFY(node.availableFormats().isEmpty());
+
+ // WHEN
+ node.setParameter(QStringLiteral("bleh"), QStringLiteral("value"));
+
+ // THEN
+ QCOMPARE(node.type(), QShaderNode::Input);
+ QCOMPARE(node.ports().size(), 1);
+ QCOMPARE(node.ports().at(0), secondPort);
+ parameterNames = node.parameterNames();
+ parameterNames.sort();
+ QCOMPARE(parameterNames.size(), 2);
+ QCOMPARE(parameterNames.at(0), QStringLiteral("baz"));
+ QCOMPARE(parameterNames.at(1), QStringLiteral("bleh"));
+ QCOMPARE(node.parameter(QStringLiteral("baz")), QVariant(42));
+ QCOMPARE(node.parameter(QStringLiteral("bleh")), QVariant(QStringLiteral("value")));
+ QVERIFY(node.availableFormats().isEmpty());
+
+ // WHEN
+ node.clearParameter(QStringLiteral("baz"));
+
+ // THEN
+ QCOMPARE(node.type(), QShaderNode::Input);
+ QCOMPARE(node.ports().size(), 1);
+ QCOMPARE(node.ports().at(0), secondPort);
+ parameterNames = node.parameterNames();
+ parameterNames.sort();
+ QCOMPARE(parameterNames.size(), 1);
+ QCOMPARE(parameterNames.at(0), QStringLiteral("bleh"));
+ QCOMPARE(node.parameter(QStringLiteral("baz")), QVariant());
+ QCOMPARE(node.parameter(QStringLiteral("bleh")), QVariant(QStringLiteral("value")));
+ QVERIFY(node.availableFormats().isEmpty());
+
+ // WHEN
+ node.addRule(openGLES2, es2Rule);
+ node.addRule(openGL3, gl3Rule);
+
+ // THEN
+ QCOMPARE(node.availableFormats().size(), 2);
+ QCOMPARE(node.availableFormats().at(0), openGLES2);
+ QCOMPARE(node.availableFormats().at(1), openGL3);
+ QCOMPARE(node.rule(openGLES2), es2Rule);
+ QCOMPARE(node.rule(openGL3), gl3Rule);
+
+ // WHEN
+ node.removeRule(openGLES2);
+
+ // THEN
+ QCOMPARE(node.availableFormats().size(), 1);
+ QCOMPARE(node.availableFormats().at(0), openGL3);
+ QCOMPARE(node.rule(openGL3), gl3Rule);
+
+ // WHEN
+ node.addRule(openGLES2, es2Rule);
+
+ // THEN
+ QCOMPARE(node.availableFormats().size(), 2);
+ QCOMPARE(node.availableFormats().at(0), openGL3);
+ QCOMPARE(node.availableFormats().at(1), openGLES2);
+ QCOMPARE(node.rule(openGLES2), es2Rule);
+ QCOMPARE(node.rule(openGL3), gl3Rule);
+
+ // WHEN
+ node.addRule(openGL3, gl3bisRule);
+
+ // THEN
+ QCOMPARE(node.availableFormats().size(), 2);
+ QCOMPARE(node.availableFormats().at(0), openGLES2);
+ QCOMPARE(node.availableFormats().at(1), openGL3);
+ QCOMPARE(node.rule(openGLES2), es2Rule);
+ QCOMPARE(node.rule(openGL3), gl3bisRule);
+}
+
+void tst_QShaderNodes::shouldHandleNodeRulesSupportAndOrder()
+{
+ // GIVEN
+ const auto openGLES2 = createFormat(QShaderFormat::OpenGLES, 2, 0);
+ const auto openGL3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0);
+ const auto openGL32 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 2);
+ const auto openGL4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
+
+ const auto es2Rule = QShaderNode::Rule(QByteArrayLiteral("gles2"), {"#pragma include es2/foo.inc", "#pragma include es2/bar.inc"});
+ const auto gl3Rule = QShaderNode::Rule(QByteArrayLiteral("gl3"), {"#pragma include gl3/foo.inc", "#pragma include gl3/bar.inc"});
+ const auto gl32Rule = QShaderNode::Rule(QByteArrayLiteral("gl32"), {"#pragma include gl32/foo.inc", "#pragma include gl32/bar.inc"});
+ const auto gl3bisRule = QShaderNode::Rule(QByteArrayLiteral("gl3bis"), {"#pragma include gl3/foo.inc", "#pragma include gl3/bar.inc"});
+
+ auto node = QShaderNode();
+
+ // WHEN
+ node.addRule(openGLES2, es2Rule);
+ node.addRule(openGL3, gl3Rule);
+
+ // THEN
+ QCOMPARE(node.availableFormats().size(), 2);
+ QCOMPARE(node.availableFormats().at(0), openGLES2);
+ QCOMPARE(node.availableFormats().at(1), openGL3);
+ QCOMPARE(node.rule(openGLES2), es2Rule);
+ QCOMPARE(node.rule(openGL3), gl3Rule);
+ QCOMPARE(node.rule(openGL32), gl3Rule);
+ QCOMPARE(node.rule(openGL4), gl3Rule);
+
+ // WHEN
+ node.addRule(openGL32, gl32Rule);
+
+ // THEN
+ QCOMPARE(node.availableFormats().size(), 3);
+ QCOMPARE(node.availableFormats().at(0), openGLES2);
+ QCOMPARE(node.availableFormats().at(1), openGL3);
+ QCOMPARE(node.availableFormats().at(2), openGL32);
+ QCOMPARE(node.rule(openGLES2), es2Rule);
+ QCOMPARE(node.rule(openGL3), gl3Rule);
+ QCOMPARE(node.rule(openGL32), gl32Rule);
+ QCOMPARE(node.rule(openGL4), gl32Rule);
+
+ // WHEN
+ node.addRule(openGL3, gl3bisRule);
+
+ // THEN
+ QCOMPARE(node.availableFormats().size(), 3);
+ QCOMPARE(node.availableFormats().at(0), openGLES2);
+ QCOMPARE(node.availableFormats().at(1), openGL32);
+ QCOMPARE(node.availableFormats().at(2), openGL3);
+ QCOMPARE(node.rule(openGLES2), es2Rule);
+ QCOMPARE(node.rule(openGL3), gl3bisRule);
+ QCOMPARE(node.rule(openGL32), gl3bisRule);
+ QCOMPARE(node.rule(openGL4), gl3bisRule);
+}
+
+QTEST_MAIN(tst_QShaderNodes)
+
+#include "tst_qshadernodes.moc"
diff --git a/tests/auto/render/shadergraph/qshadernodesloader/qshadernodesloader.pro b/tests/auto/render/shadergraph/qshadernodesloader/qshadernodesloader.pro
new file mode 100644
index 000000000..7da336b5e
--- /dev/null
+++ b/tests/auto/render/shadergraph/qshadernodesloader/qshadernodesloader.pro
@@ -0,0 +1,5 @@
+CONFIG += testcase
+QT += testlib 3drender-private
+
+SOURCES += tst_qshadernodesloader.cpp
+TARGET = tst_qshadernodesloader
diff --git a/tests/auto/render/shadergraph/qshadernodesloader/tst_qshadernodesloader.cpp b/tests/auto/render/shadergraph/qshadernodesloader/tst_qshadernodesloader.cpp
new file mode 100644
index 000000000..27cf7f425
--- /dev/null
+++ b/tests/auto/render/shadergraph/qshadernodesloader/tst_qshadernodesloader.cpp
@@ -0,0 +1,325 @@
+/****************************************************************************
+**
+** 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 <Qt3DRender/private/qshadernodesloader_p.h>
+#include <Qt3DRender/private/qshaderlanguage_p.h>
+
+using namespace Qt3DRender;
+
+using QBufferPointer = QSharedPointer<QBuffer>;
+Q_DECLARE_METATYPE(QBufferPointer);
+
+using NodeHash = QHash<QString, QShaderNode>;
+Q_DECLARE_METATYPE(NodeHash);
+
+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,
+ const QStringList &extensions = QStringList(),
+ const QString &vendor = QString())
+ {
+ auto format = QShaderFormat();
+ format.setApi(api);
+ format.setVersion(QVersionNumber(majorVersion, minorVersion));
+ format.setExtensions(extensions);
+ format.setVendor(vendor);
+ 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();
+ for (const auto &port : ports)
+ node.addPort(port);
+ return node;
+ }
+}
+
+class tst_QShaderNodesLoader : public QObject
+{
+ Q_OBJECT
+private slots:
+ void shouldManipulateLoaderMembers();
+ void shouldLoadFromJsonStream_data();
+ void shouldLoadFromJsonStream();
+};
+
+void tst_QShaderNodesLoader::shouldManipulateLoaderMembers()
+{
+ // GIVEN
+ auto loader = QShaderNodesLoader();
+
+ // THEN (default state)
+ QCOMPARE(loader.status(), QShaderNodesLoader::Null);
+ QVERIFY(!loader.device());
+ QVERIFY(loader.nodes().isEmpty());
+
+ // WHEN
+ auto device1 = createBuffer(QByteArray("..........."), QIODevice::NotOpen);
+ loader.setDevice(device1.data());
+
+ // THEN
+ QCOMPARE(loader.status(), QShaderNodesLoader::Error);
+ QCOMPARE(loader.device(), device1.data());
+ QVERIFY(loader.nodes().isEmpty());
+
+ // WHEN
+ auto device2 = createBuffer(QByteArray("..........."), QIODevice::ReadOnly);
+ loader.setDevice(device2.data());
+
+ // THEN
+ QCOMPARE(loader.status(), QShaderNodesLoader::Waiting);
+ QCOMPARE(loader.device(), device2.data());
+ QVERIFY(loader.nodes().isEmpty());
+}
+
+void tst_QShaderNodesLoader::shouldLoadFromJsonStream_data()
+{
+ QTest::addColumn<QBufferPointer>("device");
+ QTest::addColumn<NodeHash>("nodes");
+ QTest::addColumn<QShaderNodesLoader::Status>("status");
+
+ QTest::newRow("empty") << createBuffer("", QIODevice::ReadOnly) << NodeHash() << QShaderNodesLoader::Error;
+
+ const auto smallJson = "{"
+ " \"inputValue\": {"
+ " \"outputs\": ["
+ " \"value\""
+ " ],"
+ " \"parameters\": {"
+ " \"name\": \"defaultName\","
+ " \"qualifier\": {"
+ " \"type\": \"QShaderLanguage::StorageQualifier\","
+ " \"value\": \"QShaderLanguage::Uniform\""
+ " },"
+ " \"type\": {"
+ " \"type\": \"QShaderLanguage::VariableType\","
+ " \"value\": \"QShaderLanguage::Vec3\""
+ " },"
+ " \"defaultValue\": {"
+ " \"type\": \"float\","
+ " \"value\": \"1.25\""
+ " }"
+ " },"
+ " \"rules\": ["
+ " {"
+ " \"format\": {"
+ " \"api\": \"OpenGLES\","
+ " \"major\": 2,"
+ " \"minor\": 0"
+ " },"
+ " \"substitution\": \"highp vec3 $value = $name;\","
+ " \"headerSnippets\": [ \"varying highp vec3 $name;\" ]"
+ " },"
+ " {"
+ " \"format\": {"
+ " \"api\": \"OpenGLCompatibilityProfile\","
+ " \"major\": 2,"
+ " \"minor\": 1"
+ " },"
+ " \"substitution\": \"vec3 $value = $name;\","
+ " \"headerSnippets\": [ \"in vec3 $name;\" ]"
+ " }"
+ " ]"
+ " },"
+ " \"fragColor\": {"
+ " \"inputs\": ["
+ " \"fragColor\""
+ " ],"
+ " \"rules\": ["
+ " {"
+ " \"format\": {"
+ " \"api\": \"OpenGLES\","
+ " \"major\": 2,"
+ " \"minor\": 0"
+ " },"
+ " \"substitution\": \"gl_fragColor = $fragColor;\""
+ " },"
+ " {"
+ " \"format\": {"
+ " \"api\": \"OpenGLNoProfile\","
+ " \"major\": 4,"
+ " \"minor\": 0"
+ " },"
+ " \"substitution\": \"fragColor = $fragColor;\","
+ " \"headerSnippets\": [ \"out vec4 fragColor;\" ]"
+ " }"
+ " ]"
+ " },"
+ " \"lightModel\": {"
+ " \"inputs\": ["
+ " \"baseColor\","
+ " \"position\","
+ " \"lightIntensity\""
+ " ],"
+ " \"outputs\": ["
+ " \"outputColor\""
+ " ],"
+ " \"rules\": ["
+ " {"
+ " \"format\": {"
+ " \"api\": \"OpenGLES\","
+ " \"major\": 2,"
+ " \"minor\": 0,"
+ " \"extensions\": [ \"ext1\", \"ext2\" ],"
+ " \"vendor\": \"kdab\""
+ " },"
+ " \"substitution\": \"highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);\","
+ " \"headerSnippets\": [ \"#pragma include es2/lightmodel.frag.inc\" ]"
+ " },"
+ " {"
+ " \"format\": {"
+ " \"api\": \"OpenGLCoreProfile\","
+ " \"major\": 3,"
+ " \"minor\": 3"
+ " },"
+ " \"substitution\": \"vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);\","
+ " \"headerSnippets\": [ \"#pragma include gl3/lightmodel.frag.inc\" ]"
+ " }"
+ " ]"
+ " }"
+ "}";
+
+ const auto smallProtos = [this]{
+ const auto openGLES2 = createFormat(QShaderFormat::OpenGLES, 2, 0);
+ const auto openGLES2Extended = createFormat(QShaderFormat::OpenGLES, 2, 0, {"ext1", "ext2"}, "kdab");
+ const auto openGL2 = createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 1);
+ const auto openGL3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 3);
+ const auto openGL4 = createFormat(QShaderFormat::OpenGLNoProfile, 4, 0);
+
+ auto protos = NodeHash();
+
+ auto inputValue = createNode({
+ createPort(QShaderNodePort::Output, "value")
+ });
+ inputValue.setParameter("name", "defaultName");
+ inputValue.setParameter("qualifier", QVariant::fromValue<QShaderLanguage::StorageQualifier>(QShaderLanguage::Uniform));
+ inputValue.setParameter("type", QVariant::fromValue<QShaderLanguage::VariableType>(QShaderLanguage::Vec3));
+ inputValue.setParameter("defaultValue", QVariant(1.25f));
+ inputValue.addRule(openGLES2, QShaderNode::Rule("highp vec3 $value = $name;",
+ QByteArrayList() << "varying highp vec3 $name;"));
+ inputValue.addRule(openGL2, QShaderNode::Rule("vec3 $value = $name;",
+ QByteArrayList() << "in vec3 $name;"));
+ protos.insert("inputValue", inputValue);
+
+ auto fragColor = createNode({
+ createPort(QShaderNodePort::Input, "fragColor")
+ });
+ fragColor.addRule(openGLES2, QShaderNode::Rule("gl_fragColor = $fragColor;"));
+ fragColor.addRule(openGL4, QShaderNode::Rule("fragColor = $fragColor;",
+ QByteArrayList() << "out vec4 fragColor;"));
+ protos.insert(QStringLiteral("fragColor"), fragColor);
+
+ auto lightModel = createNode({
+ createPort(QShaderNodePort::Input, "baseColor"),
+ createPort(QShaderNodePort::Input, "position"),
+ createPort(QShaderNodePort::Input, "lightIntensity"),
+ createPort(QShaderNodePort::Output, "outputColor")
+ });
+ lightModel.addRule(openGLES2Extended, QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);",
+ QByteArrayList() << "#pragma include es2/lightmodel.frag.inc"));
+ lightModel.addRule(openGL3, QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);",
+ QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc"));
+ protos.insert("lightModel", lightModel);
+
+ return protos;
+ }();
+
+ QTest::newRow("NotOpen") << createBuffer(smallJson, QIODevice::NotOpen) << NodeHash() << QShaderNodesLoader::Error;
+ QTest::newRow("CorrectJSON") << createBuffer(smallJson) << smallProtos << QShaderNodesLoader::Ready;
+}
+
+void tst_QShaderNodesLoader::shouldLoadFromJsonStream()
+{
+ // GIVEN
+ QFETCH(QBufferPointer, device);
+
+ auto loader = QShaderNodesLoader();
+
+ // WHEN
+ loader.setDevice(device.data());
+ loader.load();
+
+ // THEN
+ QFETCH(QShaderNodesLoader::Status, status);
+ QCOMPARE(loader.status(), status);
+
+ QFETCH(NodeHash, nodes);
+ const auto sortedKeys = [](const NodeHash &nodes) {
+ auto res = nodes.keys();
+ res.sort();
+ return res;
+ };
+ const auto sortedParameters = [](const QShaderNode &node) {
+ auto res = node.parameterNames();
+ res.sort();
+ return res;
+ };
+ QCOMPARE(sortedKeys(loader.nodes()), sortedKeys(nodes));
+ for (const auto &key : nodes.keys()) {
+ const auto actual = loader.nodes().value(key);
+ const auto expected = nodes.value(key);
+
+ QVERIFY(actual.uuid().isNull());
+ QCOMPARE(actual.ports(), expected.ports());
+ QCOMPARE(sortedParameters(actual), sortedParameters(expected));
+ for (const auto &name : expected.parameterNames()) {
+ QCOMPARE(actual.parameter(name), expected.parameter(name));
+ }
+ QCOMPARE(actual.availableFormats(), expected.availableFormats());
+ for (const auto &format : expected.availableFormats()) {
+ QCOMPARE(actual.rule(format), expected.rule(format));
+ }
+ }
+}
+
+QTEST_MAIN(tst_QShaderNodesLoader)
+
+#include "tst_qshadernodesloader.moc"
diff --git a/tests/auto/render/shadergraph/shadergraph.pro b/tests/auto/render/shadergraph/shadergraph.pro
new file mode 100644
index 000000000..653a9a694
--- /dev/null
+++ b/tests/auto/render/shadergraph/shadergraph.pro
@@ -0,0 +1,10 @@
+TEMPLATE = subdirs
+
+qtConfig(private_tests) {
+ SUBDIRS += \
+ qshadergenerator \
+ qshadergraph \
+ qshadergraphloader \
+ qshadernodes \
+ qshadernodesloader
+}