Revision control

++ // Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++
++ #include <QtTest/QTest>
++
++ #include <QtCore/qbuffer.h>
++
++ #include <Qt3DRender/private/qshadernodesloader_p.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 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 QList<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 initTestCase();
++ void shouldManipulateLoaderMembers();
++ void shouldLoadFromJsonStream_data();
++ void shouldLoadFromJsonStream();
++ };
++
++ void tst_QShaderNodesLoader::initTestCase()
++ {
++ // Make sure we register the meta types. This is done by the
++ // QShaderGraphLoader ctor usually but in this test, no QShaderGraphLoader
++ // is used so we need to to it ourselves
++ QShaderGraphLoader l;
++ Q_UNUSED(l);
++ }
++
++ 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 = []{
++ 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"