diff options
author | Jean-Michaël Celerier <jean-michael.celerier@kdab.com> | 2020-03-13 10:56:30 +0100 |
---|---|---|
committer | Jean-Michaël Celerier <jean-michael.celerier@kdab.com> | 2020-03-19 07:14:12 +0100 |
commit | a6ffce09ce6d4fd39fbb50eb58f7b5d1027f4d60 (patch) | |
tree | fc5bbe90e61ce562cc880b38011cda75eabc808d | |
parent | 662c47d6e9c806f6c0d654e7ecce9a66a4f35154 (diff) | |
download | qt3d-a6ffce09ce6d4fd39fbb50eb58f7b5d1027f4d60.tar.gz |
Move shadergraph to Qt3D
Change-Id: I3ed10747175a58513933b29eac66ddf92fe87d07
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
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 ¶meterName : 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(), + [¤tEdges, &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 ¶meterName : 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 ¶meterName : 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 +} |