| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the test suite of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | |
| 30 | #include <QtTest/QtTest> |
| 31 | |
| 32 | #include <QtCore/qbuffer.h> |
| 33 | |
| 34 | #include <QtGui/private/qshadergraphloader_p.h> |
| 35 | #include <QtGui/private/qshaderlanguage_p.h> |
| 36 | |
| 37 | using QBufferPointer = QSharedPointer<QBuffer>; |
| 38 | Q_DECLARE_METATYPE(QBufferPointer); |
| 39 | |
| 40 | using PrototypeHash = QHash<QString, QShaderNode>; |
| 41 | Q_DECLARE_METATYPE(PrototypeHash); |
| 42 | |
| 43 | namespace |
| 44 | { |
| 45 | QBufferPointer createBuffer(const QByteArray &data, QIODevice::OpenMode openMode = QIODevice::ReadOnly) |
| 46 | { |
| 47 | auto buffer = QBufferPointer::create(); |
| 48 | buffer->setData(data); |
| 49 | if (openMode != QIODevice::NotOpen) |
| 50 | buffer->open(openMode); |
| 51 | return buffer; |
| 52 | } |
| 53 | |
| 54 | QShaderFormat createFormat(QShaderFormat::Api api, int majorVersion, int minorVersion) |
| 55 | { |
| 56 | auto format = QShaderFormat(); |
| 57 | format.setApi(api); |
| 58 | format.setVersion(QVersionNumber(majorVersion, minorVersion)); |
| 59 | return format; |
| 60 | } |
| 61 | |
| 62 | QShaderNodePort createPort(QShaderNodePort::Direction portDirection, const QString &portName) |
| 63 | { |
| 64 | auto port = QShaderNodePort(); |
| 65 | port.direction = portDirection; |
| 66 | port.name = portName; |
| 67 | return port; |
| 68 | } |
| 69 | |
| 70 | QShaderNode createNode(const QVector<QShaderNodePort> &ports, const QStringList &layers = QStringList()) |
| 71 | { |
| 72 | auto node = QShaderNode(); |
| 73 | node.setUuid(QUuid::createUuid()); |
| 74 | node.setLayers(layers); |
| 75 | for (const auto &port : ports) |
| 76 | node.addPort(port); |
| 77 | return node; |
| 78 | } |
| 79 | |
| 80 | QShaderGraph::Edge createEdge(const QUuid &sourceUuid, const QString &sourceName, |
| 81 | const QUuid &targetUuid, const QString &targetName, |
| 82 | const QStringList &layers = QStringList()) |
| 83 | { |
| 84 | auto edge = QShaderGraph::Edge(); |
| 85 | edge.sourceNodeUuid = sourceUuid; |
| 86 | edge.sourcePortName = sourceName; |
| 87 | edge.targetNodeUuid = targetUuid; |
| 88 | edge.targetPortName = targetName; |
| 89 | edge.layers = layers; |
| 90 | return edge; |
| 91 | } |
| 92 | |
| 93 | QShaderGraph createGraph() |
| 94 | { |
| 95 | const auto openGLES2 = createFormat(api: QShaderFormat::OpenGLES, majorVersion: 2, minorVersion: 0); |
| 96 | const auto openGL3 = createFormat(api: QShaderFormat::OpenGLCoreProfile, majorVersion: 3, minorVersion: 0); |
| 97 | |
| 98 | auto graph = QShaderGraph(); |
| 99 | |
| 100 | auto worldPosition = createNode(ports: { |
| 101 | createPort(portDirection: QShaderNodePort::Output, portName: "value" ) |
| 102 | }); |
| 103 | worldPosition.setUuid(QUuid("{00000000-0000-0000-0000-000000000001}" )); |
| 104 | worldPosition.setParameter(name: "name" , value: "worldPosition" ); |
| 105 | worldPosition.setParameter(name: "qualifier" , value: QVariant::fromValue<QShaderLanguage::StorageQualifier>(value: QShaderLanguage::Input)); |
| 106 | worldPosition.setParameter(name: "type" , value: QVariant::fromValue<QShaderLanguage::VariableType>(value: QShaderLanguage::Vec3)); |
| 107 | worldPosition.addRule(format: openGLES2, rule: QShaderNode::Rule("highp $type $value = $name;" , |
| 108 | QByteArrayList() << "$qualifier highp $type $name;" )); |
| 109 | worldPosition.addRule(format: openGL3, rule: QShaderNode::Rule("$type $value = $name;" , |
| 110 | QByteArrayList() << "$qualifier $type $name;" )); |
| 111 | |
| 112 | auto texture = createNode(ports: { |
| 113 | createPort(portDirection: QShaderNodePort::Output, portName: "texture" ) |
| 114 | }); |
| 115 | texture.setUuid(QUuid("{00000000-0000-0000-0000-000000000002}" )); |
| 116 | texture.addRule(format: openGLES2, rule: QShaderNode::Rule("sampler2D $texture = texture;" , |
| 117 | QByteArrayList() << "uniform sampler2D texture;" )); |
| 118 | texture.addRule(format: openGL3, rule: QShaderNode::Rule("sampler2D $texture = texture;" , |
| 119 | QByteArrayList() << "uniform sampler2D texture;" )); |
| 120 | |
| 121 | auto texCoord = createNode(ports: { |
| 122 | createPort(portDirection: QShaderNodePort::Output, portName: "texCoord" ) |
| 123 | }); |
| 124 | texCoord.setUuid(QUuid("{00000000-0000-0000-0000-000000000003}" )); |
| 125 | texCoord.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec2 $texCoord = texCoord;" , |
| 126 | QByteArrayList() << "varying highp vec2 texCoord;" )); |
| 127 | texCoord.addRule(format: openGL3, rule: QShaderNode::Rule("vec2 $texCoord = texCoord;" , |
| 128 | QByteArrayList() << "in vec2 texCoord;" )); |
| 129 | |
| 130 | auto lightIntensity = createNode(ports: { |
| 131 | createPort(portDirection: QShaderNodePort::Output, portName: "value" ) |
| 132 | }); |
| 133 | lightIntensity.setUuid(QUuid("{00000000-0000-0000-0000-000000000004}" )); |
| 134 | lightIntensity.setParameter(name: "name" , value: "defaultName" ); |
| 135 | lightIntensity.setParameter(name: "qualifier" , value: QVariant::fromValue<QShaderLanguage::StorageQualifier>(value: QShaderLanguage::Uniform)); |
| 136 | lightIntensity.setParameter(name: "type" , value: QVariant::fromValue<QShaderLanguage::VariableType>(value: QShaderLanguage::Float)); |
| 137 | lightIntensity.addRule(format: openGLES2, rule: QShaderNode::Rule("highp $type $value = $name;" , |
| 138 | QByteArrayList() << "$qualifier highp $type $name;" )); |
| 139 | lightIntensity.addRule(format: openGL3, rule: QShaderNode::Rule("$type $value = $name;" , |
| 140 | QByteArrayList() << "$qualifier $type $name;" )); |
| 141 | |
| 142 | auto exposure = createNode(ports: { |
| 143 | createPort(portDirection: QShaderNodePort::Output, portName: "exposure" ) |
| 144 | }); |
| 145 | exposure.setUuid(QUuid("{00000000-0000-0000-0000-000000000005}" )); |
| 146 | exposure.addRule(format: openGLES2, rule: QShaderNode::Rule("highp float $exposure = exposure;" , |
| 147 | QByteArrayList() << "uniform highp float exposure;" )); |
| 148 | exposure.addRule(format: openGL3, rule: QShaderNode::Rule("float $exposure = exposure;" , |
| 149 | QByteArrayList() << "uniform float exposure;" )); |
| 150 | |
| 151 | auto fragColor = createNode(ports: { |
| 152 | createPort(portDirection: QShaderNodePort::Input, portName: "fragColor" ) |
| 153 | }); |
| 154 | fragColor.setUuid(QUuid("{00000000-0000-0000-0000-000000000006}" )); |
| 155 | fragColor.addRule(format: openGLES2, rule: QShaderNode::Rule("gl_fragColor = $fragColor;" )); |
| 156 | fragColor.addRule(format: openGL3, rule: QShaderNode::Rule("fragColor = $fragColor;" , |
| 157 | QByteArrayList() << "out vec4 fragColor;" )); |
| 158 | |
| 159 | auto sampleTexture = createNode(ports: { |
| 160 | createPort(portDirection: QShaderNodePort::Input, portName: "sampler" ), |
| 161 | createPort(portDirection: QShaderNodePort::Input, portName: "coord" ), |
| 162 | createPort(portDirection: QShaderNodePort::Output, portName: "color" ) |
| 163 | }); |
| 164 | sampleTexture.setUuid(QUuid("{00000000-0000-0000-0000-000000000007}" )); |
| 165 | sampleTexture.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $color = texture2D($sampler, $coord);" )); |
| 166 | sampleTexture.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $color = texture2D($sampler, $coord);" )); |
| 167 | |
| 168 | auto lightFunction = createNode(ports: { |
| 169 | createPort(portDirection: QShaderNodePort::Input, portName: "baseColor" ), |
| 170 | createPort(portDirection: QShaderNodePort::Input, portName: "position" ), |
| 171 | createPort(portDirection: QShaderNodePort::Input, portName: "lightIntensity" ), |
| 172 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
| 173 | }); |
| 174 | lightFunction.setUuid(QUuid("{00000000-0000-0000-0000-000000000008}" )); |
| 175 | lightFunction.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
| 176 | QByteArrayList() << "#pragma include es2/lightmodel.frag.inc" )); |
| 177 | lightFunction.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
| 178 | QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc" )); |
| 179 | |
| 180 | auto exposureFunction = createNode(ports: { |
| 181 | createPort(portDirection: QShaderNodePort::Input, portName: "inputColor" ), |
| 182 | createPort(portDirection: QShaderNodePort::Input, portName: "exposure" ), |
| 183 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
| 184 | }); |
| 185 | exposureFunction.setUuid(QUuid("{00000000-0000-0000-0000-000000000009}" )); |
| 186 | exposureFunction.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $outputColor = $inputColor * pow(2.0, $exposure);" )); |
| 187 | exposureFunction.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $outputColor = $inputColor * pow(2.0, $exposure);" )); |
| 188 | |
| 189 | graph.addNode(node: worldPosition); |
| 190 | graph.addNode(node: texture); |
| 191 | graph.addNode(node: texCoord); |
| 192 | graph.addNode(node: lightIntensity); |
| 193 | graph.addNode(node: exposure); |
| 194 | graph.addNode(node: fragColor); |
| 195 | graph.addNode(node: sampleTexture); |
| 196 | graph.addNode(node: lightFunction); |
| 197 | graph.addNode(node: exposureFunction); |
| 198 | |
| 199 | graph.addEdge(edge: createEdge(sourceUuid: texture.uuid(), sourceName: "texture" , targetUuid: sampleTexture.uuid(), targetName: "sampler" )); |
| 200 | graph.addEdge(edge: createEdge(sourceUuid: texCoord.uuid(), sourceName: "texCoord" , targetUuid: sampleTexture.uuid(), targetName: "coord" )); |
| 201 | |
| 202 | graph.addEdge(edge: createEdge(sourceUuid: worldPosition.uuid(), sourceName: "value" , targetUuid: lightFunction.uuid(), targetName: "position" )); |
| 203 | graph.addEdge(edge: createEdge(sourceUuid: sampleTexture.uuid(), sourceName: "color" , targetUuid: lightFunction.uuid(), targetName: "baseColor" )); |
| 204 | graph.addEdge(edge: createEdge(sourceUuid: lightIntensity.uuid(), sourceName: "value" , targetUuid: lightFunction.uuid(), targetName: "lightIntensity" )); |
| 205 | |
| 206 | graph.addEdge(edge: createEdge(sourceUuid: lightFunction.uuid(), sourceName: "outputColor" , targetUuid: exposureFunction.uuid(), targetName: "inputColor" )); |
| 207 | graph.addEdge(edge: createEdge(sourceUuid: exposure.uuid(), sourceName: "exposure" , targetUuid: exposureFunction.uuid(), targetName: "exposure" )); |
| 208 | |
| 209 | graph.addEdge(edge: createEdge(sourceUuid: exposureFunction.uuid(), sourceName: "outputColor" , targetUuid: fragColor.uuid(), targetName: "fragColor" )); |
| 210 | |
| 211 | return graph; |
| 212 | } |
| 213 | |
| 214 | void debugStatement(const QString &prefix, const QShaderGraph::Statement &statement) |
| 215 | { |
| 216 | qDebug() << prefix << statement.inputs << statement.uuid().toString() << statement.outputs; |
| 217 | } |
| 218 | |
| 219 | void dumpStatementsIfNeeded(const QVector<QShaderGraph::Statement> &statements, const QVector<QShaderGraph::Statement> &expected) |
| 220 | { |
| 221 | if (statements != expected) { |
| 222 | for (int i = 0; i < qMax(a: statements.size(), b: expected.size()); i++) { |
| 223 | qDebug() << "----" << i << "----" ; |
| 224 | if (i < statements.size()) |
| 225 | debugStatement(prefix: "A:" , statement: statements.at(i)); |
| 226 | if (i < expected.size()) |
| 227 | debugStatement(prefix: "E:" , statement: expected.at(i)); |
| 228 | qDebug() << "-----------" ; |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | class tst_QShaderGraphLoader : public QObject |
| 235 | { |
| 236 | Q_OBJECT |
| 237 | private slots: |
| 238 | void shouldManipulateLoaderMembers(); |
| 239 | void shouldLoadFromJsonStream_data(); |
| 240 | void shouldLoadFromJsonStream(); |
| 241 | }; |
| 242 | |
| 243 | void tst_QShaderGraphLoader::shouldManipulateLoaderMembers() |
| 244 | { |
| 245 | // GIVEN |
| 246 | auto loader = QShaderGraphLoader(); |
| 247 | |
| 248 | // THEN (default state) |
| 249 | QCOMPARE(loader.status(), QShaderGraphLoader::Null); |
| 250 | QVERIFY(!loader.device()); |
| 251 | QVERIFY(loader.graph().nodes().isEmpty()); |
| 252 | QVERIFY(loader.graph().edges().isEmpty()); |
| 253 | QVERIFY(loader.prototypes().isEmpty()); |
| 254 | |
| 255 | // WHEN |
| 256 | auto device1 = createBuffer(data: QByteArray("..........." ), openMode: QIODevice::NotOpen); |
| 257 | loader.setDevice(device1.data()); |
| 258 | |
| 259 | // THEN |
| 260 | QCOMPARE(loader.status(), QShaderGraphLoader::Error); |
| 261 | QCOMPARE(loader.device(), device1.data()); |
| 262 | QVERIFY(loader.graph().nodes().isEmpty()); |
| 263 | QVERIFY(loader.graph().edges().isEmpty()); |
| 264 | |
| 265 | // WHEN |
| 266 | auto device2 = createBuffer(data: QByteArray("..........." ), openMode: QIODevice::ReadOnly); |
| 267 | loader.setDevice(device2.data()); |
| 268 | |
| 269 | // THEN |
| 270 | QCOMPARE(loader.status(), QShaderGraphLoader::Waiting); |
| 271 | QCOMPARE(loader.device(), device2.data()); |
| 272 | QVERIFY(loader.graph().nodes().isEmpty()); |
| 273 | QVERIFY(loader.graph().edges().isEmpty()); |
| 274 | |
| 275 | |
| 276 | // WHEN |
| 277 | const auto prototypes = [this]{ |
| 278 | auto res = QHash<QString, QShaderNode>(); |
| 279 | res.insert(akey: "foo" , avalue: createNode(ports: {})); |
| 280 | return res; |
| 281 | }(); |
| 282 | loader.setPrototypes(prototypes); |
| 283 | |
| 284 | // THEN |
| 285 | QCOMPARE(loader.prototypes().size(), prototypes.size()); |
| 286 | QVERIFY(loader.prototypes().contains("foo" )); |
| 287 | QCOMPARE(loader.prototypes().value("foo" ).uuid(), prototypes.value("foo" ).uuid()); |
| 288 | } |
| 289 | |
| 290 | void tst_QShaderGraphLoader::shouldLoadFromJsonStream_data() |
| 291 | { |
| 292 | QTest::addColumn<QBufferPointer>(name: "device" ); |
| 293 | QTest::addColumn<PrototypeHash>(name: "prototypes" ); |
| 294 | QTest::addColumn<QShaderGraph>(name: "graph" ); |
| 295 | QTest::addColumn<QShaderGraphLoader::Status>(name: "status" ); |
| 296 | |
| 297 | QTest::newRow(dataTag: "empty" ) << createBuffer(data: "" , openMode: QIODevice::ReadOnly) << PrototypeHash() |
| 298 | << QShaderGraph() << QShaderGraphLoader::Error; |
| 299 | |
| 300 | const auto smallJson = "{" |
| 301 | " \"nodes\": [" |
| 302 | " {" |
| 303 | " \"uuid\": \"{00000000-0000-0000-0000-000000000001}\"," |
| 304 | " \"type\": \"MyInput\"," |
| 305 | " \"layers\": [\"foo\", \"bar\"]" |
| 306 | " }," |
| 307 | " {" |
| 308 | " \"uuid\": \"{00000000-0000-0000-0000-000000000002}\"," |
| 309 | " \"type\": \"MyOutput\"" |
| 310 | " }," |
| 311 | " {" |
| 312 | " \"uuid\": \"{00000000-0000-0000-0000-000000000003}\"," |
| 313 | " \"type\": \"MyFunction\"" |
| 314 | " }" |
| 315 | " ]," |
| 316 | " \"edges\": [" |
| 317 | " {" |
| 318 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000001}\"," |
| 319 | " \"sourcePort\": \"input\"," |
| 320 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000003}\"," |
| 321 | " \"targetPort\": \"functionInput\"," |
| 322 | " \"layers\": [\"bar\", \"baz\"]" |
| 323 | " }," |
| 324 | " {" |
| 325 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000003}\"," |
| 326 | " \"sourcePort\": \"functionOutput\"," |
| 327 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000002}\"," |
| 328 | " \"targetPort\": \"output\"" |
| 329 | " }" |
| 330 | " ]" |
| 331 | "}" ; |
| 332 | |
| 333 | const auto smallProtos = [this]{ |
| 334 | auto protos = PrototypeHash(); |
| 335 | |
| 336 | auto input = createNode(ports: { |
| 337 | createPort(portDirection: QShaderNodePort::Output, portName: "input" ) |
| 338 | }); |
| 339 | protos.insert(akey: "MyInput" , avalue: input); |
| 340 | |
| 341 | auto output = createNode(ports: { |
| 342 | createPort(portDirection: QShaderNodePort::Input, portName: "output" ) |
| 343 | }); |
| 344 | protos.insert(akey: "MyOutput" , avalue: output); |
| 345 | |
| 346 | auto function = createNode(ports: { |
| 347 | createPort(portDirection: QShaderNodePort::Input, portName: "functionInput" ), |
| 348 | createPort(portDirection: QShaderNodePort::Output, portName: "functionOutput" ) |
| 349 | }); |
| 350 | protos.insert(akey: "MyFunction" , avalue: function); |
| 351 | return protos; |
| 352 | }(); |
| 353 | |
| 354 | const auto smallGraph = [this]{ |
| 355 | auto graph = QShaderGraph(); |
| 356 | |
| 357 | auto input = createNode(ports: { |
| 358 | createPort(portDirection: QShaderNodePort::Output, portName: "input" ) |
| 359 | }, layers: {"foo" , "bar" }); |
| 360 | input.setUuid(QUuid("{00000000-0000-0000-0000-000000000001}" )); |
| 361 | auto output = createNode(ports: { |
| 362 | createPort(portDirection: QShaderNodePort::Input, portName: "output" ) |
| 363 | }); |
| 364 | output.setUuid(QUuid("{00000000-0000-0000-0000-000000000002}" )); |
| 365 | auto function = createNode(ports: { |
| 366 | createPort(portDirection: QShaderNodePort::Input, portName: "functionInput" ), |
| 367 | createPort(portDirection: QShaderNodePort::Output, portName: "functionOutput" ) |
| 368 | }); |
| 369 | function.setUuid(QUuid("{00000000-0000-0000-0000-000000000003}" )); |
| 370 | |
| 371 | graph.addNode(node: input); |
| 372 | graph.addNode(node: output); |
| 373 | graph.addNode(node: function); |
| 374 | graph.addEdge(edge: createEdge(sourceUuid: input.uuid(), sourceName: "input" , targetUuid: function.uuid(), targetName: "functionInput" , layers: {"bar" , "baz" })); |
| 375 | graph.addEdge(edge: createEdge(sourceUuid: function.uuid(), sourceName: "functionOutput" , targetUuid: output.uuid(), targetName: "output" )); |
| 376 | |
| 377 | return graph; |
| 378 | }(); |
| 379 | |
| 380 | QTest::newRow(dataTag: "TwoNodesOneEdge" ) << createBuffer(data: smallJson) << smallProtos << smallGraph << QShaderGraphLoader::Ready; |
| 381 | QTest::newRow(dataTag: "NotOpen" ) << createBuffer(data: smallJson, openMode: QIODevice::NotOpen) << smallProtos << QShaderGraph() << QShaderGraphLoader::Error; |
| 382 | QTest::newRow(dataTag: "NoPrototype" ) << createBuffer(data: smallJson) << PrototypeHash() << QShaderGraph() << QShaderGraphLoader::Error; |
| 383 | |
| 384 | const auto complexJson = "{" |
| 385 | " \"nodes\": [" |
| 386 | " {" |
| 387 | " \"uuid\": \"{00000000-0000-0000-0000-000000000001}\"," |
| 388 | " \"type\": \"inputValue\"," |
| 389 | " \"parameters\": {" |
| 390 | " \"name\": \"worldPosition\"," |
| 391 | " \"qualifier\": {" |
| 392 | " \"type\": \"QShaderLanguage::StorageQualifier\"," |
| 393 | " \"value\": \"QShaderLanguage::Input\"" |
| 394 | " }," |
| 395 | " \"type\": {" |
| 396 | " \"type\": \"QShaderLanguage::VariableType\"," |
| 397 | " \"value\": \"QShaderLanguage::Vec3\"" |
| 398 | " }" |
| 399 | " }" |
| 400 | " }," |
| 401 | " {" |
| 402 | " \"uuid\": \"{00000000-0000-0000-0000-000000000002}\"," |
| 403 | " \"type\": \"texture\"" |
| 404 | " }," |
| 405 | " {" |
| 406 | " \"uuid\": \"{00000000-0000-0000-0000-000000000003}\"," |
| 407 | " \"type\": \"texCoord\"" |
| 408 | " }," |
| 409 | " {" |
| 410 | " \"uuid\": \"{00000000-0000-0000-0000-000000000004}\"," |
| 411 | " \"type\": \"inputValue\"" |
| 412 | " }," |
| 413 | " {" |
| 414 | " \"uuid\": \"{00000000-0000-0000-0000-000000000005}\"," |
| 415 | " \"type\": \"exposure\"" |
| 416 | " }," |
| 417 | " {" |
| 418 | " \"uuid\": \"{00000000-0000-0000-0000-000000000006}\"," |
| 419 | " \"type\": \"fragColor\"" |
| 420 | " }," |
| 421 | " {" |
| 422 | " \"uuid\": \"{00000000-0000-0000-0000-000000000007}\"," |
| 423 | " \"type\": \"sampleTexture\"" |
| 424 | " }," |
| 425 | " {" |
| 426 | " \"uuid\": \"{00000000-0000-0000-0000-000000000008}\"," |
| 427 | " \"type\": \"lightModel\"" |
| 428 | " }," |
| 429 | " {" |
| 430 | " \"uuid\": \"{00000000-0000-0000-0000-000000000009}\"," |
| 431 | " \"type\": \"exposureFunction\"" |
| 432 | " }" |
| 433 | " ]," |
| 434 | " \"edges\": [" |
| 435 | " {" |
| 436 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000002}\"," |
| 437 | " \"sourcePort\": \"texture\"," |
| 438 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000007}\"," |
| 439 | " \"targetPort\": \"sampler\"" |
| 440 | " }," |
| 441 | " {" |
| 442 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000003}\"," |
| 443 | " \"sourcePort\": \"texCoord\"," |
| 444 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000007}\"," |
| 445 | " \"targetPort\": \"coord\"" |
| 446 | " }," |
| 447 | " {" |
| 448 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000001}\"," |
| 449 | " \"sourcePort\": \"value\"," |
| 450 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\"," |
| 451 | " \"targetPort\": \"position\"" |
| 452 | " }," |
| 453 | " {" |
| 454 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000007}\"," |
| 455 | " \"sourcePort\": \"color\"," |
| 456 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\"," |
| 457 | " \"targetPort\": \"baseColor\"" |
| 458 | " }," |
| 459 | " {" |
| 460 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000004}\"," |
| 461 | " \"sourcePort\": \"value\"," |
| 462 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\"," |
| 463 | " \"targetPort\": \"lightIntensity\"" |
| 464 | " }," |
| 465 | " {" |
| 466 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000008}\"," |
| 467 | " \"sourcePort\": \"outputColor\"," |
| 468 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000009}\"," |
| 469 | " \"targetPort\": \"inputColor\"" |
| 470 | " }," |
| 471 | " {" |
| 472 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000005}\"," |
| 473 | " \"sourcePort\": \"exposure\"," |
| 474 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000009}\"," |
| 475 | " \"targetPort\": \"exposure\"" |
| 476 | " }," |
| 477 | " {" |
| 478 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000009}\"," |
| 479 | " \"sourcePort\": \"outputColor\"," |
| 480 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000006}\"," |
| 481 | " \"targetPort\": \"fragColor\"" |
| 482 | " }" |
| 483 | " ]" |
| 484 | "}" ; |
| 485 | |
| 486 | const auto complexProtos = [this]{ |
| 487 | const auto openGLES2 = createFormat(api: QShaderFormat::OpenGLES, majorVersion: 2, minorVersion: 0); |
| 488 | const auto openGL3 = createFormat(api: QShaderFormat::OpenGLCoreProfile, majorVersion: 3, minorVersion: 0); |
| 489 | |
| 490 | auto protos = PrototypeHash(); |
| 491 | |
| 492 | auto inputValue = createNode(ports: { |
| 493 | createPort(portDirection: QShaderNodePort::Output, portName: "value" ) |
| 494 | }); |
| 495 | inputValue.setParameter(name: "name" , value: "defaultName" ); |
| 496 | inputValue.setParameter(name: "qualifier" , value: QVariant::fromValue<QShaderLanguage::StorageQualifier>(value: QShaderLanguage::Uniform)); |
| 497 | inputValue.setParameter(name: "type" , value: QVariant::fromValue<QShaderLanguage::VariableType>(value: QShaderLanguage::Float)); |
| 498 | inputValue.addRule(format: openGLES2, rule: QShaderNode::Rule("highp $type $value = $name;" , |
| 499 | QByteArrayList() << "$qualifier highp $type $name;" )); |
| 500 | inputValue.addRule(format: openGL3, rule: QShaderNode::Rule("$type $value = $name;" , |
| 501 | QByteArrayList() << "$qualifier $type $name;" )); |
| 502 | protos.insert(akey: "inputValue" , avalue: inputValue); |
| 503 | |
| 504 | auto texture = createNode(ports: { |
| 505 | createPort(portDirection: QShaderNodePort::Output, portName: "texture" ) |
| 506 | }); |
| 507 | texture.addRule(format: openGLES2, rule: QShaderNode::Rule("sampler2D $texture = texture;" , |
| 508 | QByteArrayList() << "uniform sampler2D texture;" )); |
| 509 | texture.addRule(format: openGL3, rule: QShaderNode::Rule("sampler2D $texture = texture;" , |
| 510 | QByteArrayList() << "uniform sampler2D texture;" )); |
| 511 | protos.insert(akey: "texture" , avalue: texture); |
| 512 | |
| 513 | auto texCoord = createNode(ports: { |
| 514 | createPort(portDirection: QShaderNodePort::Output, portName: "texCoord" ) |
| 515 | }); |
| 516 | texCoord.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec2 $texCoord = texCoord;" , |
| 517 | QByteArrayList() << "varying highp vec2 texCoord;" )); |
| 518 | texCoord.addRule(format: openGL3, rule: QShaderNode::Rule("vec2 $texCoord = texCoord;" , |
| 519 | QByteArrayList() << "in vec2 texCoord;" )); |
| 520 | protos.insert(akey: "texCoord" , avalue: texCoord); |
| 521 | |
| 522 | auto exposure = createNode(ports: { |
| 523 | createPort(portDirection: QShaderNodePort::Output, portName: "exposure" ) |
| 524 | }); |
| 525 | exposure.addRule(format: openGLES2, rule: QShaderNode::Rule("highp float $exposure = exposure;" , |
| 526 | QByteArrayList() << "uniform highp float exposure;" )); |
| 527 | exposure.addRule(format: openGL3, rule: QShaderNode::Rule("float $exposure = exposure;" , |
| 528 | QByteArrayList() << "uniform float exposure;" )); |
| 529 | protos.insert(akey: "exposure" , avalue: exposure); |
| 530 | |
| 531 | auto fragColor = createNode(ports: { |
| 532 | createPort(portDirection: QShaderNodePort::Input, portName: "fragColor" ) |
| 533 | }); |
| 534 | fragColor.addRule(format: openGLES2, rule: QShaderNode::Rule("gl_fragColor = $fragColor;" )); |
| 535 | fragColor.addRule(format: openGL3, rule: QShaderNode::Rule("fragColor = $fragColor;" , |
| 536 | QByteArrayList() << "out vec4 fragColor;" )); |
| 537 | protos.insert(akey: "fragColor" , avalue: fragColor); |
| 538 | |
| 539 | auto sampleTexture = createNode(ports: { |
| 540 | createPort(portDirection: QShaderNodePort::Input, portName: "sampler" ), |
| 541 | createPort(portDirection: QShaderNodePort::Input, portName: "coord" ), |
| 542 | createPort(portDirection: QShaderNodePort::Output, portName: "color" ) |
| 543 | }); |
| 544 | sampleTexture.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $color = texture2D($sampler, $coord);" )); |
| 545 | sampleTexture.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $color = texture2D($sampler, $coord);" )); |
| 546 | protos.insert(akey: "sampleTexture" , avalue: sampleTexture); |
| 547 | |
| 548 | auto lightModel = createNode(ports: { |
| 549 | createPort(portDirection: QShaderNodePort::Input, portName: "baseColor" ), |
| 550 | createPort(portDirection: QShaderNodePort::Input, portName: "position" ), |
| 551 | createPort(portDirection: QShaderNodePort::Input, portName: "lightIntensity" ), |
| 552 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
| 553 | }); |
| 554 | lightModel.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
| 555 | QByteArrayList() << "#pragma include es2/lightmodel.frag.inc" )); |
| 556 | lightModel.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
| 557 | QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc" )); |
| 558 | protos.insert(akey: "lightModel" , avalue: lightModel); |
| 559 | |
| 560 | auto exposureFunction = createNode(ports: { |
| 561 | createPort(portDirection: QShaderNodePort::Input, portName: "inputColor" ), |
| 562 | createPort(portDirection: QShaderNodePort::Input, portName: "exposure" ), |
| 563 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
| 564 | }); |
| 565 | exposureFunction.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $outputColor = $inputColor * pow(2.0, $exposure);" )); |
| 566 | exposureFunction.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $outputColor = $inputColor * pow(2.0, $exposure);" )); |
| 567 | protos.insert(akey: "exposureFunction" , avalue: exposureFunction); |
| 568 | |
| 569 | return protos; |
| 570 | }(); |
| 571 | |
| 572 | const auto complexGraph = createGraph(); |
| 573 | |
| 574 | QTest::newRow(dataTag: "ComplexGraph" ) << createBuffer(data: complexJson) << complexProtos << complexGraph << QShaderGraphLoader::Ready; |
| 575 | } |
| 576 | |
| 577 | void tst_QShaderGraphLoader::shouldLoadFromJsonStream() |
| 578 | { |
| 579 | // GIVEN |
| 580 | QFETCH(QBufferPointer, device); |
| 581 | QFETCH(PrototypeHash, prototypes); |
| 582 | |
| 583 | auto loader = QShaderGraphLoader(); |
| 584 | |
| 585 | // WHEN |
| 586 | loader.setPrototypes(prototypes); |
| 587 | loader.setDevice(device.data()); |
| 588 | loader.load(); |
| 589 | |
| 590 | // THEN |
| 591 | QFETCH(QShaderGraphLoader::Status, status); |
| 592 | QCOMPARE(loader.status(), status); |
| 593 | |
| 594 | QFETCH(QShaderGraph, graph); |
| 595 | const auto statements = loader.graph().createStatements(enabledLayers: {"foo" , "bar" , "baz" }); |
| 596 | const auto expected = graph.createStatements(enabledLayers: {"foo" , "bar" , "baz" }); |
| 597 | dumpStatementsIfNeeded(statements, expected); |
| 598 | QCOMPARE(statements, expected); |
| 599 | |
| 600 | const auto sortedParameters = [](const QShaderNode &node) { |
| 601 | auto res = node.parameterNames(); |
| 602 | res.sort(); |
| 603 | return res; |
| 604 | }; |
| 605 | |
| 606 | for (int i = 0; i < statements.size(); i++) { |
| 607 | const auto actualNode = statements.at(i).node; |
| 608 | const auto expectedNode = expected.at(i).node; |
| 609 | |
| 610 | QCOMPARE(actualNode.layers(), expectedNode.layers()); |
| 611 | QCOMPARE(actualNode.ports(), expectedNode.ports()); |
| 612 | QCOMPARE(sortedParameters(actualNode), sortedParameters(expectedNode)); |
| 613 | for (const auto &name : expectedNode.parameterNames()) { |
| 614 | QCOMPARE(actualNode.parameter(name), expectedNode.parameter(name)); |
| 615 | } |
| 616 | QCOMPARE(actualNode.availableFormats(), expectedNode.availableFormats()); |
| 617 | for (const auto &format : expectedNode.availableFormats()) { |
| 618 | QCOMPARE(actualNode.rule(format), expectedNode.rule(format)); |
| 619 | } |
| 620 | } |
| 621 | } |
| 622 | |
| 623 | QTEST_MAIN(tst_QShaderGraphLoader) |
| 624 | |
| 625 | #include "tst_qshadergraphloader.moc" |
| 626 | |