[Shader Graph Gen.] Introduce QShaderGraphLoader
This class allows to load a shader graph from its JSON representation. To avoid duplicating the shader snippets inside of the JSON which would be a maintenance nightmare, we instead allow to register a set of node prototypes by their name and only refer to said names inside the JSON. Change-Id: I3e7b39e8b3c25f51f331a0a59dda883ac8e2bc57 Reviewed-by: Sean Harmer <sean.harmer@kdab.com>bb10
parent
ad2faa3c63
commit
093752faf3
|
|
@ -0,0 +1,204 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtGui module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qshadergraphloader_p.h"
|
||||
|
||||
#include <QtCore/qdebug.h>
|
||||
#include <QtCore/qiodevice.h>
|
||||
#include <QtCore/qjsonarray.h>
|
||||
#include <QtCore/qjsondocument.h>
|
||||
#include <QtCore/qjsonobject.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QShaderGraphLoader::QShaderGraphLoader() Q_DECL_NOTHROW
|
||||
: m_status(Null),
|
||||
m_device(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
QShaderGraphLoader::Status QShaderGraphLoader::status() const Q_DECL_NOTHROW
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
QShaderGraph QShaderGraphLoader::graph() const Q_DECL_NOTHROW
|
||||
{
|
||||
return m_graph;
|
||||
}
|
||||
|
||||
QIODevice *QShaderGraphLoader::device() const Q_DECL_NOTHROW
|
||||
{
|
||||
return m_device;
|
||||
}
|
||||
|
||||
void QShaderGraphLoader::setDevice(QIODevice *device) Q_DECL_NOTHROW
|
||||
{
|
||||
m_device = device;
|
||||
m_graph = QShaderGraph();
|
||||
m_status = !m_device ? Null
|
||||
: (m_device->openMode() & QIODevice::ReadOnly) ? Waiting
|
||||
: Error;
|
||||
}
|
||||
|
||||
QHash<QString, QShaderNode> QShaderGraphLoader::prototypes() const Q_DECL_NOTHROW
|
||||
{
|
||||
return m_prototypes;
|
||||
}
|
||||
|
||||
void QShaderGraphLoader::setPrototypes(const QHash<QString, QShaderNode> &prototypes) Q_DECL_NOTHROW
|
||||
{
|
||||
m_prototypes = prototypes;
|
||||
}
|
||||
|
||||
void QShaderGraphLoader::load()
|
||||
{
|
||||
if (m_status == Error)
|
||||
return;
|
||||
|
||||
auto error = QJsonParseError();
|
||||
const auto document = QJsonDocument::fromJson(m_device->readAll(), &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Invalid JSON document:" << error.errorString();
|
||||
m_status = Error;
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.isEmpty() || !document.isObject()) {
|
||||
qWarning() << "Invalid JSON document, root should be an object";
|
||||
m_status = Error;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto root = document.object();
|
||||
|
||||
const auto nodesValue = root.value(QStringLiteral("nodes"));
|
||||
if (!nodesValue.isArray()) {
|
||||
qWarning() << "Invalid nodes property, should be an array";
|
||||
m_status = Error;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto edgesValue = root.value(QStringLiteral("edges"));
|
||||
if (!edgesValue.isArray()) {
|
||||
qWarning() << "Invalid edges property, should be an array";
|
||||
m_status = Error;
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasError = false;
|
||||
|
||||
const auto nodes = nodesValue.toArray();
|
||||
for (const auto &nodeValue : nodes) {
|
||||
if (!nodeValue.isObject()) {
|
||||
qWarning() << "Invalid node found";
|
||||
hasError = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto nodeObject = nodeValue.toObject();
|
||||
|
||||
const auto uuidString = nodeObject.value(QStringLiteral("uuid")).toString();
|
||||
const auto uuid = QUuid(uuidString);
|
||||
if (uuid.isNull()) {
|
||||
qWarning() << "Invalid UUID found in node:" << uuidString;
|
||||
hasError = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto type = nodeObject.value(QStringLiteral("type")).toString();
|
||||
if (!m_prototypes.contains(type)) {
|
||||
qWarning() << "Unsupported node type found:" << type;
|
||||
hasError = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto node = m_prototypes.value(type);
|
||||
node.setUuid(uuid);
|
||||
m_graph.addNode(node);
|
||||
}
|
||||
|
||||
const auto edges = edgesValue.toArray();
|
||||
for (const auto &edgeValue : edges) {
|
||||
if (!edgeValue.isObject()) {
|
||||
qWarning() << "Invalid edge found";
|
||||
hasError = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto edgeObject = edgeValue.toObject();
|
||||
|
||||
const auto sourceUuidString = edgeObject.value(QStringLiteral("sourceUuid")).toString();
|
||||
const auto sourceUuid = QUuid(sourceUuidString);
|
||||
if (sourceUuid.isNull()) {
|
||||
qWarning() << "Invalid source UUID found in edge:" << sourceUuidString;
|
||||
hasError = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto sourcePort = edgeObject.value(QStringLiteral("sourcePort")).toString();
|
||||
|
||||
const auto targetUuidString = edgeObject.value(QStringLiteral("targetUuid")).toString();
|
||||
const auto targetUuid = QUuid(targetUuidString);
|
||||
if (targetUuid.isNull()) {
|
||||
qWarning() << "Invalid target UUID found in edge:" << targetUuidString;
|
||||
hasError = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto targetPort = edgeObject.value(QStringLiteral("targetPort")).toString();
|
||||
|
||||
auto edge = QShaderGraph::Edge();
|
||||
edge.sourceNodeUuid = sourceUuid;
|
||||
edge.sourcePortName = sourcePort;
|
||||
edge.targetNodeUuid = targetUuid;
|
||||
edge.targetPortName = targetPort;
|
||||
m_graph.addEdge(edge);
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
m_status = Error;
|
||||
m_graph = QShaderGraph();
|
||||
} else {
|
||||
m_status = Ready;
|
||||
}
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtGui module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QSHADERGRAPHLOADER_P_H
|
||||
#define QSHADERGRAPHLOADER_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include <QtGui/private/qtguiglobal_p.h>
|
||||
|
||||
#include <QtGui/private/qshadergraph_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QIODevice;
|
||||
|
||||
class QShaderGraphLoader
|
||||
{
|
||||
public:
|
||||
enum Status : char {
|
||||
Null,
|
||||
Waiting,
|
||||
Ready,
|
||||
Error
|
||||
};
|
||||
|
||||
Q_GUI_EXPORT QShaderGraphLoader() Q_DECL_NOTHROW;
|
||||
|
||||
Q_GUI_EXPORT Status status() const Q_DECL_NOTHROW;
|
||||
Q_GUI_EXPORT QShaderGraph graph() const Q_DECL_NOTHROW;
|
||||
|
||||
Q_GUI_EXPORT QIODevice *device() const Q_DECL_NOTHROW;
|
||||
Q_GUI_EXPORT void setDevice(QIODevice *device) Q_DECL_NOTHROW;
|
||||
|
||||
Q_GUI_EXPORT QHash<QString, QShaderNode> prototypes() const Q_DECL_NOTHROW;
|
||||
Q_GUI_EXPORT void setPrototypes(const QHash<QString, QShaderNode> &prototypes) Q_DECL_NOTHROW;
|
||||
|
||||
Q_GUI_EXPORT void load();
|
||||
|
||||
private:
|
||||
Status m_status;
|
||||
QIODevice *m_device;
|
||||
QHash<QString, QShaderNode> m_prototypes;
|
||||
QShaderGraph m_graph;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(QShaderGraphLoader, Q_MOVABLE_TYPE);
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
Q_DECLARE_METATYPE(QShaderGraphLoader)
|
||||
Q_DECLARE_METATYPE(QShaderGraphLoader::Status)
|
||||
|
||||
#endif // QSHADERGRAPHLOADER_P_H
|
||||
|
|
@ -10,6 +10,7 @@ HEADERS += \
|
|||
util/qshaderformat_p.h \
|
||||
util/qshadergenerator_p.h \
|
||||
util/qshadergraph_p.h \
|
||||
util/qshadergraphloader_p.h \
|
||||
util/qshadernode_p.h \
|
||||
util/qshadernodeport_p.h
|
||||
|
||||
|
|
@ -22,5 +23,6 @@ SOURCES += \
|
|||
util/qshaderformat.cpp \
|
||||
util/qshadergenerator.cpp \
|
||||
util/qshadergraph.cpp \
|
||||
util/qshadergraphloader.cpp \
|
||||
util/qshadernode.cpp \
|
||||
util/qshadernodeport.cpp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
CONFIG += testcase
|
||||
QT += testlib gui-private
|
||||
|
||||
SOURCES += tst_qshadergraphloader.cpp
|
||||
TARGET = tst_qshadergraphloader
|
||||
|
|
@ -0,0 +1,552 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the test suite of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
#include <QtCore/qbuffer.h>
|
||||
|
||||
#include <QtGui/private/qshadergraphloader_p.h>
|
||||
|
||||
using QBufferPointer = QSharedPointer<QBuffer>;
|
||||
Q_DECLARE_METATYPE(QBufferPointer);
|
||||
|
||||
using PrototypeHash = QHash<QString, QShaderNode>;
|
||||
Q_DECLARE_METATYPE(PrototypeHash);
|
||||
|
||||
namespace
|
||||
{
|
||||
QBufferPointer createBuffer(const QByteArray &data, QIODevice::OpenMode openMode = QIODevice::ReadOnly)
|
||||
{
|
||||
auto buffer = QBufferPointer::create();
|
||||
buffer->setData(data);
|
||||
if (openMode != QIODevice::NotOpen)
|
||||
buffer->open(openMode);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
QShaderFormat createFormat(QShaderFormat::Api api, int majorVersion, int minorVersion)
|
||||
{
|
||||
auto format = QShaderFormat();
|
||||
format.setApi(api);
|
||||
format.setVersion(QVersionNumber(majorVersion, minorVersion));
|
||||
return format;
|
||||
}
|
||||
|
||||
QShaderNodePort createPort(QShaderNodePort::Direction portDirection, const QString &portName)
|
||||
{
|
||||
auto port = QShaderNodePort();
|
||||
port.direction = portDirection;
|
||||
port.name = portName;
|
||||
return port;
|
||||
}
|
||||
|
||||
QShaderNode createNode(const QVector<QShaderNodePort> &ports)
|
||||
{
|
||||
auto node = QShaderNode();
|
||||
node.setUuid(QUuid::createUuid());
|
||||
for (const auto &port : ports)
|
||||
node.addPort(port);
|
||||
return node;
|
||||
}
|
||||
|
||||
QShaderGraph::Edge createEdge(const QUuid &sourceUuid, const QString &sourceName,
|
||||
const QUuid &targetUuid, const QString &targetName)
|
||||
{
|
||||
auto edge = QShaderGraph::Edge();
|
||||
edge.sourceNodeUuid = sourceUuid;
|
||||
edge.sourcePortName = sourceName;
|
||||
edge.targetNodeUuid = targetUuid;
|
||||
edge.targetPortName = targetName;
|
||||
return edge;
|
||||
}
|
||||
|
||||
QShaderGraph createGraph()
|
||||
{
|
||||
const auto openGLES2 = createFormat(QShaderFormat::OpenGLES, 2, 0);
|
||||
const auto openGL3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0);
|
||||
|
||||
auto graph = QShaderGraph();
|
||||
|
||||
auto worldPosition = createNode({
|
||||
createPort(QShaderNodePort::Output, "worldPosition")
|
||||
});
|
||||
worldPosition.setUuid(QUuid("{00000000-0000-0000-0000-000000000001}"));
|
||||
worldPosition.addRule(openGLES2, QShaderNode::Rule("highp vec3 $worldPosition = worldPosition;",
|
||||
QByteArrayList() << "varying highp vec3 worldPosition;"));
|
||||
worldPosition.addRule(openGL3, QShaderNode::Rule("vec3 $worldPosition = worldPosition;",
|
||||
QByteArrayList() << "in vec3 worldPosition;"));
|
||||
|
||||
auto texture = createNode({
|
||||
createPort(QShaderNodePort::Output, "texture")
|
||||
});
|
||||
texture.setUuid(QUuid("{00000000-0000-0000-0000-000000000002}"));
|
||||
texture.addRule(openGLES2, QShaderNode::Rule("sampler2D $texture = texture;",
|
||||
QByteArrayList() << "uniform sampler2D texture;"));
|
||||
texture.addRule(openGL3, QShaderNode::Rule("sampler2D $texture = texture;",
|
||||
QByteArrayList() << "uniform sampler2D texture;"));
|
||||
|
||||
auto texCoord = createNode({
|
||||
createPort(QShaderNodePort::Output, "texCoord")
|
||||
});
|
||||
texCoord.setUuid(QUuid("{00000000-0000-0000-0000-000000000003}"));
|
||||
texCoord.addRule(openGLES2, QShaderNode::Rule("highp vec2 $texCoord = texCoord;",
|
||||
QByteArrayList() << "varying highp vec2 texCoord;"));
|
||||
texCoord.addRule(openGL3, QShaderNode::Rule("vec2 $texCoord = texCoord;",
|
||||
QByteArrayList() << "in vec2 texCoord;"));
|
||||
|
||||
auto lightIntensity = createNode({
|
||||
createPort(QShaderNodePort::Output, "lightIntensity")
|
||||
});
|
||||
lightIntensity.setUuid(QUuid("{00000000-0000-0000-0000-000000000004}"));
|
||||
lightIntensity.addRule(openGLES2, QShaderNode::Rule("highp float $lightIntensity = lightIntensity;",
|
||||
QByteArrayList() << "uniform highp float lightIntensity;"));
|
||||
lightIntensity.addRule(openGL3, QShaderNode::Rule("float $lightIntensity = lightIntensity;",
|
||||
QByteArrayList() << "uniform float lightIntensity;"));
|
||||
|
||||
auto exposure = createNode({
|
||||
createPort(QShaderNodePort::Output, "exposure")
|
||||
});
|
||||
exposure.setUuid(QUuid("{00000000-0000-0000-0000-000000000005}"));
|
||||
exposure.addRule(openGLES2, QShaderNode::Rule("highp float $exposure = exposure;",
|
||||
QByteArrayList() << "uniform highp float exposure;"));
|
||||
exposure.addRule(openGL3, QShaderNode::Rule("float $exposure = exposure;",
|
||||
QByteArrayList() << "uniform float exposure;"));
|
||||
|
||||
auto fragColor = createNode({
|
||||
createPort(QShaderNodePort::Input, "fragColor")
|
||||
});
|
||||
fragColor.setUuid(QUuid("{00000000-0000-0000-0000-000000000006}"));
|
||||
fragColor.addRule(openGLES2, QShaderNode::Rule("gl_fragColor = $fragColor;"));
|
||||
fragColor.addRule(openGL3, QShaderNode::Rule("fragColor = $fragColor;",
|
||||
QByteArrayList() << "out vec4 fragColor;"));
|
||||
|
||||
auto sampleTexture = createNode({
|
||||
createPort(QShaderNodePort::Input, "sampler"),
|
||||
createPort(QShaderNodePort::Input, "coord"),
|
||||
createPort(QShaderNodePort::Output, "color")
|
||||
});
|
||||
sampleTexture.setUuid(QUuid("{00000000-0000-0000-0000-000000000007}"));
|
||||
sampleTexture.addRule(openGLES2, QShaderNode::Rule("highp vec4 $color = texture2D($sampler, $coord);"));
|
||||
sampleTexture.addRule(openGL3, QShaderNode::Rule("vec4 $color = texture2D($sampler, $coord);"));
|
||||
|
||||
auto lightFunction = createNode({
|
||||
createPort(QShaderNodePort::Input, "baseColor"),
|
||||
createPort(QShaderNodePort::Input, "position"),
|
||||
createPort(QShaderNodePort::Input, "lightIntensity"),
|
||||
createPort(QShaderNodePort::Output, "outputColor")
|
||||
});
|
||||
lightFunction.setUuid(QUuid("{00000000-0000-0000-0000-000000000008}"));
|
||||
lightFunction.addRule(openGLES2, QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);",
|
||||
QByteArrayList() << "#pragma include es2/lightmodel.frag.inc"));
|
||||
lightFunction.addRule(openGL3, QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);",
|
||||
QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc"));
|
||||
|
||||
auto exposureFunction = createNode({
|
||||
createPort(QShaderNodePort::Input, "inputColor"),
|
||||
createPort(QShaderNodePort::Input, "exposure"),
|
||||
createPort(QShaderNodePort::Output, "outputColor")
|
||||
});
|
||||
exposureFunction.setUuid(QUuid("{00000000-0000-0000-0000-000000000009}"));
|
||||
exposureFunction.addRule(openGLES2, QShaderNode::Rule("highp vec4 $outputColor = $inputColor * pow(2.0, $exposure);"));
|
||||
exposureFunction.addRule(openGL3, QShaderNode::Rule("vec4 $outputColor = $inputColor * pow(2.0, $exposure);"));
|
||||
|
||||
graph.addNode(worldPosition);
|
||||
graph.addNode(texture);
|
||||
graph.addNode(texCoord);
|
||||
graph.addNode(lightIntensity);
|
||||
graph.addNode(exposure);
|
||||
graph.addNode(fragColor);
|
||||
graph.addNode(sampleTexture);
|
||||
graph.addNode(lightFunction);
|
||||
graph.addNode(exposureFunction);
|
||||
|
||||
graph.addEdge(createEdge(texture.uuid(), "texture", sampleTexture.uuid(), "sampler"));
|
||||
graph.addEdge(createEdge(texCoord.uuid(), "texCoord", sampleTexture.uuid(), "coord"));
|
||||
|
||||
graph.addEdge(createEdge(worldPosition.uuid(), "worldPosition", lightFunction.uuid(), "position"));
|
||||
graph.addEdge(createEdge(sampleTexture.uuid(), "color", lightFunction.uuid(), "baseColor"));
|
||||
graph.addEdge(createEdge(lightIntensity.uuid(), "lightIntensity", lightFunction.uuid(), "lightIntensity"));
|
||||
|
||||
graph.addEdge(createEdge(lightFunction.uuid(), "outputColor", exposureFunction.uuid(), "inputColor"));
|
||||
graph.addEdge(createEdge(exposure.uuid(), "exposure", exposureFunction.uuid(), "exposure"));
|
||||
|
||||
graph.addEdge(createEdge(exposureFunction.uuid(), "outputColor", fragColor.uuid(), "fragColor"));
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
void debugStatement(const QString &prefix, const QShaderGraph::Statement &statement)
|
||||
{
|
||||
qDebug() << prefix << statement.inputs << statement.uuid().toString() << statement.outputs;
|
||||
}
|
||||
|
||||
void dumpStatementsIfNeeded(const QVector<QShaderGraph::Statement> &statements, const QVector<QShaderGraph::Statement> &expected)
|
||||
{
|
||||
if (statements != expected) {
|
||||
for (int i = 0; i < qMax(statements.size(), expected.size()); i++) {
|
||||
qDebug() << "----" << i << "----";
|
||||
if (i < statements.size())
|
||||
debugStatement("A:", statements.at(i));
|
||||
if (i < expected.size())
|
||||
debugStatement("E:", expected.at(i));
|
||||
qDebug() << "-----------";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class tst_QShaderGraphLoader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void shouldManipulateLoaderMembers();
|
||||
void shouldLoadFromJsonStream_data();
|
||||
void shouldLoadFromJsonStream();
|
||||
};
|
||||
|
||||
void tst_QShaderGraphLoader::shouldManipulateLoaderMembers()
|
||||
{
|
||||
// GIVEN
|
||||
auto loader = QShaderGraphLoader();
|
||||
|
||||
// THEN (default state)
|
||||
QCOMPARE(loader.status(), QShaderGraphLoader::Null);
|
||||
QVERIFY(!loader.device());
|
||||
QVERIFY(loader.graph().nodes().isEmpty());
|
||||
QVERIFY(loader.graph().edges().isEmpty());
|
||||
QVERIFY(loader.prototypes().isEmpty());
|
||||
|
||||
// WHEN
|
||||
auto device1 = createBuffer(QByteArray("..........."), QIODevice::NotOpen);
|
||||
loader.setDevice(device1.data());
|
||||
|
||||
// THEN
|
||||
QCOMPARE(loader.status(), QShaderGraphLoader::Error);
|
||||
QCOMPARE(loader.device(), device1.data());
|
||||
QVERIFY(loader.graph().nodes().isEmpty());
|
||||
QVERIFY(loader.graph().edges().isEmpty());
|
||||
|
||||
// WHEN
|
||||
auto device2 = createBuffer(QByteArray("..........."), QIODevice::ReadOnly);
|
||||
loader.setDevice(device2.data());
|
||||
|
||||
// THEN
|
||||
QCOMPARE(loader.status(), QShaderGraphLoader::Waiting);
|
||||
QCOMPARE(loader.device(), device2.data());
|
||||
QVERIFY(loader.graph().nodes().isEmpty());
|
||||
QVERIFY(loader.graph().edges().isEmpty());
|
||||
|
||||
|
||||
// WHEN
|
||||
const auto prototypes = [this]{
|
||||
auto res = QHash<QString, QShaderNode>();
|
||||
res.insert("foo", createNode({}));
|
||||
return res;
|
||||
}();
|
||||
loader.setPrototypes(prototypes);
|
||||
|
||||
// THEN
|
||||
QCOMPARE(loader.prototypes().size(), prototypes.size());
|
||||
QVERIFY(loader.prototypes().contains("foo"));
|
||||
QCOMPARE(loader.prototypes().value("foo").uuid(), prototypes.value("foo").uuid());
|
||||
}
|
||||
|
||||
void tst_QShaderGraphLoader::shouldLoadFromJsonStream_data()
|
||||
{
|
||||
QTest::addColumn<QBufferPointer>("device");
|
||||
QTest::addColumn<PrototypeHash>("prototypes");
|
||||
QTest::addColumn<QShaderGraph>("graph");
|
||||
QTest::addColumn<QShaderGraphLoader::Status>("status");
|
||||
|
||||
QTest::newRow("empty") << createBuffer("", QIODevice::ReadOnly) << PrototypeHash()
|
||||
<< QShaderGraph() << QShaderGraphLoader::Error;
|
||||
|
||||
const auto smallJson = "{"
|
||||
" \"nodes\": ["
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000001}\","
|
||||
" \"type\": \"MyInput\""
|
||||
" },"
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000002}\","
|
||||
" \"type\": \"MyOutput\""
|
||||
" },"
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000003}\","
|
||||
" \"type\": \"MyFunction\""
|
||||
" }"
|
||||
" ],"
|
||||
" \"edges\": ["
|
||||
" {"
|
||||
" \"sourceUuid\": \"{00000000-0000-0000-0000-000000000001}\","
|
||||
" \"sourcePort\": \"input\","
|
||||
" \"targetUuid\": \"{00000000-0000-0000-0000-000000000003}\","
|
||||
" \"targetPort\": \"functionInput\""
|
||||
" },"
|
||||
" {"
|
||||
" \"sourceUuid\": \"{00000000-0000-0000-0000-000000000003}\","
|
||||
" \"sourcePort\": \"functionOutput\","
|
||||
" \"targetUuid\": \"{00000000-0000-0000-0000-000000000002}\","
|
||||
" \"targetPort\": \"output\""
|
||||
" }"
|
||||
" ]"
|
||||
"}";
|
||||
|
||||
const auto smallProtos = [this]{
|
||||
auto protos = PrototypeHash();
|
||||
|
||||
auto input = createNode({
|
||||
createPort(QShaderNodePort::Output, "input")
|
||||
});
|
||||
protos.insert("MyInput", input);
|
||||
|
||||
auto output = createNode({
|
||||
createPort(QShaderNodePort::Input, "output")
|
||||
});
|
||||
protos.insert("MyOutput", output);
|
||||
|
||||
auto function = createNode({
|
||||
createPort(QShaderNodePort::Input, "functionInput"),
|
||||
createPort(QShaderNodePort::Output, "functionOutput")
|
||||
});
|
||||
protos.insert("MyFunction", function);
|
||||
return protos;
|
||||
}();
|
||||
|
||||
const auto smallGraph = [this]{
|
||||
auto graph = QShaderGraph();
|
||||
|
||||
auto input = createNode({
|
||||
createPort(QShaderNodePort::Output, "input")
|
||||
});
|
||||
input.setUuid(QUuid("{00000000-0000-0000-0000-000000000001}"));
|
||||
auto output = createNode({
|
||||
createPort(QShaderNodePort::Input, "output")
|
||||
});
|
||||
output.setUuid(QUuid("{00000000-0000-0000-0000-000000000002}"));
|
||||
auto function = createNode({
|
||||
createPort(QShaderNodePort::Input, "functionInput"),
|
||||
createPort(QShaderNodePort::Output, "functionOutput")
|
||||
});
|
||||
function.setUuid(QUuid("{00000000-0000-0000-0000-000000000003}"));
|
||||
|
||||
graph.addNode(input);
|
||||
graph.addNode(output);
|
||||
graph.addNode(function);
|
||||
graph.addEdge(createEdge(input.uuid(), "input", function.uuid(), "functionInput"));
|
||||
graph.addEdge(createEdge(function.uuid(), "functionOutput", output.uuid(), "output"));
|
||||
|
||||
return graph;
|
||||
}();
|
||||
|
||||
QTest::newRow("TwoNodesOneEdge") << createBuffer(smallJson) << smallProtos << smallGraph << QShaderGraphLoader::Ready;
|
||||
QTest::newRow("NotOpen") << createBuffer(smallJson, QIODevice::NotOpen) << smallProtos << QShaderGraph() << QShaderGraphLoader::Error;
|
||||
QTest::newRow("NoPrototype") << createBuffer(smallJson) << PrototypeHash() << QShaderGraph() << QShaderGraphLoader::Error;
|
||||
|
||||
const auto complexJson = "{"
|
||||
" \"nodes\": ["
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000001}\","
|
||||
" \"type\": \"worldPosition\""
|
||||
" },"
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000002}\","
|
||||
" \"type\": \"texture\""
|
||||
" },"
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000003}\","
|
||||
" \"type\": \"texCoord\""
|
||||
" },"
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000004}\","
|
||||
" \"type\": \"lightIntensity\""
|
||||
" },"
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000005}\","
|
||||
" \"type\": \"exposure\""
|
||||
" },"
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000006}\","
|
||||
" \"type\": \"fragColor\""
|
||||
" },"
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000007}\","
|
||||
" \"type\": \"sampleTexture\""
|
||||
" },"
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000008}\","
|
||||
" \"type\": \"lightModel\""
|
||||
" },"
|
||||
" {"
|
||||
" \"uuid\": \"{00000000-0000-0000-0000-000000000009}\","
|
||||
" \"type\": \"exposureFunction\""
|
||||
" }"
|
||||
" ],"
|
||||
" \"edges\": ["
|
||||
" {"
|
||||
" \"sourceUuid\": \"{00000000-0000-0000-0000-000000000002}\","
|
||||
" \"sourcePort\": \"texture\","
|
||||
" \"targetUuid\": \"{00000000-0000-0000-0000-000000000007}\","
|
||||
" \"targetPort\": \"sampler\""
|
||||
" },"
|
||||
" {"
|
||||
" \"sourceUuid\": \"{00000000-0000-0000-0000-000000000003}\","
|
||||
" \"sourcePort\": \"texCoord\","
|
||||
" \"targetUuid\": \"{00000000-0000-0000-0000-000000000007}\","
|
||||
" \"targetPort\": \"coord\""
|
||||
" },"
|
||||
" {"
|
||||
" \"sourceUuid\": \"{00000000-0000-0000-0000-000000000001}\","
|
||||
" \"sourcePort\": \"worldPosition\","
|
||||
" \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\","
|
||||
" \"targetPort\": \"position\""
|
||||
" },"
|
||||
" {"
|
||||
" \"sourceUuid\": \"{00000000-0000-0000-0000-000000000007}\","
|
||||
" \"sourcePort\": \"color\","
|
||||
" \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\","
|
||||
" \"targetPort\": \"baseColor\""
|
||||
" },"
|
||||
" {"
|
||||
" \"sourceUuid\": \"{00000000-0000-0000-0000-000000000004}\","
|
||||
" \"sourcePort\": \"lightIntensity\","
|
||||
" \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\","
|
||||
" \"targetPort\": \"lightIntensity\""
|
||||
" },"
|
||||
" {"
|
||||
" \"sourceUuid\": \"{00000000-0000-0000-0000-000000000008}\","
|
||||
" \"sourcePort\": \"outputColor\","
|
||||
" \"targetUuid\": \"{00000000-0000-0000-0000-000000000009}\","
|
||||
" \"targetPort\": \"inputColor\""
|
||||
" },"
|
||||
" {"
|
||||
" \"sourceUuid\": \"{00000000-0000-0000-0000-000000000005}\","
|
||||
" \"sourcePort\": \"exposure\","
|
||||
" \"targetUuid\": \"{00000000-0000-0000-0000-000000000009}\","
|
||||
" \"targetPort\": \"exposure\""
|
||||
" },"
|
||||
" {"
|
||||
" \"sourceUuid\": \"{00000000-0000-0000-0000-000000000009}\","
|
||||
" \"sourcePort\": \"outputColor\","
|
||||
" \"targetUuid\": \"{00000000-0000-0000-0000-000000000006}\","
|
||||
" \"targetPort\": \"fragColor\""
|
||||
" }"
|
||||
" ]"
|
||||
"}";
|
||||
|
||||
const auto complexProtos = [this]{
|
||||
auto protos = PrototypeHash();
|
||||
|
||||
auto worldPosition = createNode({
|
||||
createPort(QShaderNodePort::Output, "worldPosition")
|
||||
});
|
||||
protos.insert("worldPosition", worldPosition);
|
||||
|
||||
auto texture = createNode({
|
||||
createPort(QShaderNodePort::Output, "texture")
|
||||
});
|
||||
protos.insert("texture", texture);
|
||||
|
||||
auto texCoord = createNode({
|
||||
createPort(QShaderNodePort::Output, "texCoord")
|
||||
});
|
||||
protos.insert("texCoord", texCoord);
|
||||
|
||||
auto lightIntensity = createNode({
|
||||
createPort(QShaderNodePort::Output, "lightIntensity")
|
||||
});
|
||||
protos.insert("lightIntensity", lightIntensity);
|
||||
|
||||
auto exposure = createNode({
|
||||
createPort(QShaderNodePort::Output, "exposure")
|
||||
});
|
||||
protos.insert("exposure", exposure);
|
||||
|
||||
auto fragColor = createNode({
|
||||
createPort(QShaderNodePort::Input, "fragColor")
|
||||
});
|
||||
protos.insert("fragColor", fragColor);
|
||||
|
||||
auto sampleTexture = createNode({
|
||||
createPort(QShaderNodePort::Input, "sampler"),
|
||||
createPort(QShaderNodePort::Input, "coord"),
|
||||
createPort(QShaderNodePort::Output, "color")
|
||||
});
|
||||
protos.insert("sampleTexture", sampleTexture);
|
||||
|
||||
auto lightModel = createNode({
|
||||
createPort(QShaderNodePort::Input, "baseColor"),
|
||||
createPort(QShaderNodePort::Input, "position"),
|
||||
createPort(QShaderNodePort::Input, "lightIntensity"),
|
||||
createPort(QShaderNodePort::Output, "outputColor")
|
||||
});
|
||||
protos.insert("lightModel", lightModel);
|
||||
|
||||
auto exposureFunction = createNode({
|
||||
createPort(QShaderNodePort::Input, "inputColor"),
|
||||
createPort(QShaderNodePort::Input, "exposure"),
|
||||
createPort(QShaderNodePort::Output, "outputColor")
|
||||
});
|
||||
protos.insert("exposureFunction", exposureFunction);
|
||||
|
||||
return protos;
|
||||
}();
|
||||
|
||||
const auto complexGraph = createGraph();
|
||||
|
||||
QTest::newRow("ComplexGraph") << createBuffer(complexJson) << complexProtos << complexGraph << QShaderGraphLoader::Ready;
|
||||
}
|
||||
|
||||
void tst_QShaderGraphLoader::shouldLoadFromJsonStream()
|
||||
{
|
||||
// GIVEN
|
||||
QFETCH(QBufferPointer, device);
|
||||
QFETCH(PrototypeHash, prototypes);
|
||||
|
||||
auto loader = QShaderGraphLoader();
|
||||
|
||||
// WHEN
|
||||
loader.setPrototypes(prototypes);
|
||||
loader.setDevice(device.data());
|
||||
loader.load();
|
||||
|
||||
// THEN
|
||||
QFETCH(QShaderGraphLoader::Status, status);
|
||||
QCOMPARE(loader.status(), status);
|
||||
|
||||
QFETCH(QShaderGraph, graph);
|
||||
const auto statements = loader.graph().createStatements();
|
||||
const auto expected = graph.createStatements();
|
||||
dumpStatementsIfNeeded(statements, expected);
|
||||
QCOMPARE(statements, expected);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QShaderGraphLoader)
|
||||
|
||||
#include "tst_qshadergraphloader.moc"
|
||||
|
|
@ -7,5 +7,6 @@ SUBDIRS= \
|
|||
qregularexpressionvalidator \
|
||||
qshadergenerator \
|
||||
qshadergraph \
|
||||
qshadergraphloader \
|
||||
qshadernodes \
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue