/**************************************************************************** ** ** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include using QBufferPointer = QSharedPointer; Q_DECLARE_METATYPE(QBufferPointer); using PrototypeHash = QHash; Q_DECLARE_METATYPE(PrototypeHash); namespace { QBufferPointer createBuffer(const QByteArray &data, QIODevice::OpenMode openMode = QIODevice::ReadOnly) { auto buffer = QBufferPointer::create(); buffer->setData(data); if (openMode != QIODevice::NotOpen) buffer->open(openMode); return buffer; } QShaderFormat createFormat(QShaderFormat::Api api, int majorVersion, int minorVersion) { auto format = QShaderFormat(); format.setApi(api); format.setVersion(QVersionNumber(majorVersion, minorVersion)); return format; } QShaderNodePort createPort(QShaderNodePort::Direction portDirection, const QString &portName) { auto port = QShaderNodePort(); port.direction = portDirection; port.name = portName; return port; } QShaderNode createNode(const QVector &ports, 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::Input)); worldPosition.setParameter("type", QVariant::fromValue(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::Uniform)); lightIntensity.setParameter("type", QVariant::fromValue(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 &statements, const QVector &expected) { if (statements != expected) { for (int i = 0; i < qMax(statements.size(), expected.size()); i++) { qDebug() << "----" << i << "----"; if (i < statements.size()) debugStatement("A:", statements.at(i)); if (i < expected.size()) debugStatement("E:", expected.at(i)); qDebug() << "-----------"; } } } } class tst_QShaderGraphLoader : public QObject { Q_OBJECT private slots: void shouldManipulateLoaderMembers(); void shouldLoadFromJsonStream_data(); void shouldLoadFromJsonStream(); }; void tst_QShaderGraphLoader::shouldManipulateLoaderMembers() { // GIVEN auto loader = QShaderGraphLoader(); // THEN (default state) QCOMPARE(loader.status(), QShaderGraphLoader::Null); QVERIFY(!loader.device()); QVERIFY(loader.graph().nodes().isEmpty()); QVERIFY(loader.graph().edges().isEmpty()); QVERIFY(loader.prototypes().isEmpty()); // WHEN auto device1 = createBuffer(QByteArray("..........."), QIODevice::NotOpen); loader.setDevice(device1.data()); // THEN QCOMPARE(loader.status(), QShaderGraphLoader::Error); QCOMPARE(loader.device(), device1.data()); QVERIFY(loader.graph().nodes().isEmpty()); QVERIFY(loader.graph().edges().isEmpty()); // WHEN auto device2 = createBuffer(QByteArray("..........."), QIODevice::ReadOnly); loader.setDevice(device2.data()); // THEN QCOMPARE(loader.status(), QShaderGraphLoader::Waiting); QCOMPARE(loader.device(), device2.data()); QVERIFY(loader.graph().nodes().isEmpty()); QVERIFY(loader.graph().edges().isEmpty()); // WHEN const auto prototypes = [this]{ auto res = QHash(); res.insert("foo", createNode({})); return res; }(); loader.setPrototypes(prototypes); // THEN QCOMPARE(loader.prototypes().size(), prototypes.size()); QVERIFY(loader.prototypes().contains("foo")); QCOMPARE(loader.prototypes().value("foo").uuid(), prototypes.value("foo").uuid()); } void tst_QShaderGraphLoader::shouldLoadFromJsonStream_data() { QTest::addColumn("device"); QTest::addColumn("prototypes"); QTest::addColumn("graph"); QTest::addColumn("status"); QTest::newRow("empty") << createBuffer("", QIODevice::ReadOnly) << PrototypeHash() << QShaderGraph() << QShaderGraphLoader::Error; const auto smallJson = "{" " \"nodes\": [" " {" " \"uuid\": \"{00000000-0000-0000-0000-000000000001}\"," " \"type\": \"MyInput\"," " \"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::Uniform)); inputValue.setParameter("type", QVariant::fromValue(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"