| 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/qshadernodesloader_p.h> |
| 35 | #include <QtGui/private/qshaderlanguage_p.h> |
| 36 | |
| 37 | using QBufferPointer = QSharedPointer<QBuffer>; |
| 38 | Q_DECLARE_METATYPE(QBufferPointer); |
| 39 | |
| 40 | using NodeHash = QHash<QString, QShaderNode>; |
| 41 | Q_DECLARE_METATYPE(NodeHash); |
| 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 | const QStringList &extensions = QStringList(), |
| 56 | const QString &vendor = QString()) |
| 57 | { |
| 58 | auto format = QShaderFormat(); |
| 59 | format.setApi(api); |
| 60 | format.setVersion(QVersionNumber(majorVersion, minorVersion)); |
| 61 | format.setExtensions(extensions); |
| 62 | format.setVendor(vendor); |
| 63 | return format; |
| 64 | } |
| 65 | |
| 66 | QShaderNodePort createPort(QShaderNodePort::Direction portDirection, const QString &portName) |
| 67 | { |
| 68 | auto port = QShaderNodePort(); |
| 69 | port.direction = portDirection; |
| 70 | port.name = portName; |
| 71 | return port; |
| 72 | } |
| 73 | |
| 74 | QShaderNode createNode(const QVector<QShaderNodePort> &ports) |
| 75 | { |
| 76 | auto node = QShaderNode(); |
| 77 | for (const auto &port : ports) |
| 78 | node.addPort(port); |
| 79 | return node; |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | class tst_QShaderNodesLoader : public QObject |
| 84 | { |
| 85 | Q_OBJECT |
| 86 | private slots: |
| 87 | void shouldManipulateLoaderMembers(); |
| 88 | void shouldLoadFromJsonStream_data(); |
| 89 | void shouldLoadFromJsonStream(); |
| 90 | }; |
| 91 | |
| 92 | void tst_QShaderNodesLoader::shouldManipulateLoaderMembers() |
| 93 | { |
| 94 | // GIVEN |
| 95 | auto loader = QShaderNodesLoader(); |
| 96 | |
| 97 | // THEN (default state) |
| 98 | QCOMPARE(loader.status(), QShaderNodesLoader::Null); |
| 99 | QVERIFY(!loader.device()); |
| 100 | QVERIFY(loader.nodes().isEmpty()); |
| 101 | |
| 102 | // WHEN |
| 103 | auto device1 = createBuffer(data: QByteArray("..........." ), openMode: QIODevice::NotOpen); |
| 104 | loader.setDevice(device1.data()); |
| 105 | |
| 106 | // THEN |
| 107 | QCOMPARE(loader.status(), QShaderNodesLoader::Error); |
| 108 | QCOMPARE(loader.device(), device1.data()); |
| 109 | QVERIFY(loader.nodes().isEmpty()); |
| 110 | |
| 111 | // WHEN |
| 112 | auto device2 = createBuffer(data: QByteArray("..........." ), openMode: QIODevice::ReadOnly); |
| 113 | loader.setDevice(device2.data()); |
| 114 | |
| 115 | // THEN |
| 116 | QCOMPARE(loader.status(), QShaderNodesLoader::Waiting); |
| 117 | QCOMPARE(loader.device(), device2.data()); |
| 118 | QVERIFY(loader.nodes().isEmpty()); |
| 119 | } |
| 120 | |
| 121 | void tst_QShaderNodesLoader::shouldLoadFromJsonStream_data() |
| 122 | { |
| 123 | QTest::addColumn<QBufferPointer>(name: "device" ); |
| 124 | QTest::addColumn<NodeHash>(name: "nodes" ); |
| 125 | QTest::addColumn<QShaderNodesLoader::Status>(name: "status" ); |
| 126 | |
| 127 | QTest::newRow(dataTag: "empty" ) << createBuffer(data: "" , openMode: QIODevice::ReadOnly) << NodeHash() << QShaderNodesLoader::Error; |
| 128 | |
| 129 | const auto smallJson = "{" |
| 130 | " \"inputValue\": {" |
| 131 | " \"outputs\": [" |
| 132 | " \"value\"" |
| 133 | " ]," |
| 134 | " \"parameters\": {" |
| 135 | " \"name\": \"defaultName\"," |
| 136 | " \"qualifier\": {" |
| 137 | " \"type\": \"QShaderLanguage::StorageQualifier\"," |
| 138 | " \"value\": \"QShaderLanguage::Uniform\"" |
| 139 | " }," |
| 140 | " \"type\": {" |
| 141 | " \"type\": \"QShaderLanguage::VariableType\"," |
| 142 | " \"value\": \"QShaderLanguage::Vec3\"" |
| 143 | " }," |
| 144 | " \"defaultValue\": {" |
| 145 | " \"type\": \"float\"," |
| 146 | " \"value\": \"1.25\"" |
| 147 | " }" |
| 148 | " }," |
| 149 | " \"rules\": [" |
| 150 | " {" |
| 151 | " \"format\": {" |
| 152 | " \"api\": \"OpenGLES\"," |
| 153 | " \"major\": 2," |
| 154 | " \"minor\": 0" |
| 155 | " }," |
| 156 | " \"substitution\": \"highp vec3 $value = $name;\"," |
| 157 | " \"headerSnippets\": [ \"varying highp vec3 $name;\" ]" |
| 158 | " }," |
| 159 | " {" |
| 160 | " \"format\": {" |
| 161 | " \"api\": \"OpenGLCompatibilityProfile\"," |
| 162 | " \"major\": 2," |
| 163 | " \"minor\": 1" |
| 164 | " }," |
| 165 | " \"substitution\": \"vec3 $value = $name;\"," |
| 166 | " \"headerSnippets\": [ \"in vec3 $name;\" ]" |
| 167 | " }" |
| 168 | " ]" |
| 169 | " }," |
| 170 | " \"fragColor\": {" |
| 171 | " \"inputs\": [" |
| 172 | " \"fragColor\"" |
| 173 | " ]," |
| 174 | " \"rules\": [" |
| 175 | " {" |
| 176 | " \"format\": {" |
| 177 | " \"api\": \"OpenGLES\"," |
| 178 | " \"major\": 2," |
| 179 | " \"minor\": 0" |
| 180 | " }," |
| 181 | " \"substitution\": \"gl_fragColor = $fragColor;\"" |
| 182 | " }," |
| 183 | " {" |
| 184 | " \"format\": {" |
| 185 | " \"api\": \"OpenGLNoProfile\"," |
| 186 | " \"major\": 4," |
| 187 | " \"minor\": 0" |
| 188 | " }," |
| 189 | " \"substitution\": \"fragColor = $fragColor;\"," |
| 190 | " \"headerSnippets\": [ \"out vec4 fragColor;\" ]" |
| 191 | " }" |
| 192 | " ]" |
| 193 | " }," |
| 194 | " \"lightModel\": {" |
| 195 | " \"inputs\": [" |
| 196 | " \"baseColor\"," |
| 197 | " \"position\"," |
| 198 | " \"lightIntensity\"" |
| 199 | " ]," |
| 200 | " \"outputs\": [" |
| 201 | " \"outputColor\"" |
| 202 | " ]," |
| 203 | " \"rules\": [" |
| 204 | " {" |
| 205 | " \"format\": {" |
| 206 | " \"api\": \"OpenGLES\"," |
| 207 | " \"major\": 2," |
| 208 | " \"minor\": 0," |
| 209 | " \"extensions\": [ \"ext1\", \"ext2\" ]," |
| 210 | " \"vendor\": \"kdab\"" |
| 211 | " }," |
| 212 | " \"substitution\": \"highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);\"," |
| 213 | " \"headerSnippets\": [ \"#pragma include es2/lightmodel.frag.inc\" ]" |
| 214 | " }," |
| 215 | " {" |
| 216 | " \"format\": {" |
| 217 | " \"api\": \"OpenGLCoreProfile\"," |
| 218 | " \"major\": 3," |
| 219 | " \"minor\": 3" |
| 220 | " }," |
| 221 | " \"substitution\": \"vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);\"," |
| 222 | " \"headerSnippets\": [ \"#pragma include gl3/lightmodel.frag.inc\" ]" |
| 223 | " }" |
| 224 | " ]" |
| 225 | " }" |
| 226 | "}" ; |
| 227 | |
| 228 | const auto smallProtos = [this]{ |
| 229 | const auto openGLES2 = createFormat(api: QShaderFormat::OpenGLES, majorVersion: 2, minorVersion: 0); |
| 230 | const auto openGLES2Extended = createFormat(api: QShaderFormat::OpenGLES, majorVersion: 2, minorVersion: 0, extensions: {"ext1" , "ext2" }, vendor: "kdab" ); |
| 231 | const auto openGL2 = createFormat(api: QShaderFormat::OpenGLCompatibilityProfile, majorVersion: 2, minorVersion: 1); |
| 232 | const auto openGL3 = createFormat(api: QShaderFormat::OpenGLCoreProfile, majorVersion: 3, minorVersion: 3); |
| 233 | const auto openGL4 = createFormat(api: QShaderFormat::OpenGLNoProfile, majorVersion: 4, minorVersion: 0); |
| 234 | |
| 235 | auto protos = NodeHash(); |
| 236 | |
| 237 | auto inputValue = createNode(ports: { |
| 238 | createPort(portDirection: QShaderNodePort::Output, portName: "value" ) |
| 239 | }); |
| 240 | inputValue.setParameter(name: "name" , value: "defaultName" ); |
| 241 | inputValue.setParameter(name: "qualifier" , value: QVariant::fromValue<QShaderLanguage::StorageQualifier>(value: QShaderLanguage::Uniform)); |
| 242 | inputValue.setParameter(name: "type" , value: QVariant::fromValue<QShaderLanguage::VariableType>(value: QShaderLanguage::Vec3)); |
| 243 | inputValue.setParameter(name: "defaultValue" , value: QVariant(1.25f)); |
| 244 | inputValue.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec3 $value = $name;" , |
| 245 | QByteArrayList() << "varying highp vec3 $name;" )); |
| 246 | inputValue.addRule(format: openGL2, rule: QShaderNode::Rule("vec3 $value = $name;" , |
| 247 | QByteArrayList() << "in vec3 $name;" )); |
| 248 | protos.insert(akey: "inputValue" , avalue: inputValue); |
| 249 | |
| 250 | auto fragColor = createNode(ports: { |
| 251 | createPort(portDirection: QShaderNodePort::Input, portName: "fragColor" ) |
| 252 | }); |
| 253 | fragColor.addRule(format: openGLES2, rule: QShaderNode::Rule("gl_fragColor = $fragColor;" )); |
| 254 | fragColor.addRule(format: openGL4, rule: QShaderNode::Rule("fragColor = $fragColor;" , |
| 255 | QByteArrayList() << "out vec4 fragColor;" )); |
| 256 | protos.insert(QStringLiteral("fragColor" ), avalue: fragColor); |
| 257 | |
| 258 | auto lightModel = createNode(ports: { |
| 259 | createPort(portDirection: QShaderNodePort::Input, portName: "baseColor" ), |
| 260 | createPort(portDirection: QShaderNodePort::Input, portName: "position" ), |
| 261 | createPort(portDirection: QShaderNodePort::Input, portName: "lightIntensity" ), |
| 262 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
| 263 | }); |
| 264 | lightModel.addRule(format: openGLES2Extended, rule: QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
| 265 | QByteArrayList() << "#pragma include es2/lightmodel.frag.inc" )); |
| 266 | lightModel.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
| 267 | QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc" )); |
| 268 | protos.insert(akey: "lightModel" , avalue: lightModel); |
| 269 | |
| 270 | return protos; |
| 271 | }(); |
| 272 | |
| 273 | QTest::newRow(dataTag: "NotOpen" ) << createBuffer(data: smallJson, openMode: QIODevice::NotOpen) << NodeHash() << QShaderNodesLoader::Error; |
| 274 | QTest::newRow(dataTag: "CorrectJSON" ) << createBuffer(data: smallJson) << smallProtos << QShaderNodesLoader::Ready; |
| 275 | } |
| 276 | |
| 277 | void tst_QShaderNodesLoader::shouldLoadFromJsonStream() |
| 278 | { |
| 279 | // GIVEN |
| 280 | QFETCH(QBufferPointer, device); |
| 281 | |
| 282 | auto loader = QShaderNodesLoader(); |
| 283 | |
| 284 | // WHEN |
| 285 | loader.setDevice(device.data()); |
| 286 | loader.load(); |
| 287 | |
| 288 | // THEN |
| 289 | QFETCH(QShaderNodesLoader::Status, status); |
| 290 | QCOMPARE(loader.status(), status); |
| 291 | |
| 292 | QFETCH(NodeHash, nodes); |
| 293 | const auto sortedKeys = [](const NodeHash &nodes) { |
| 294 | auto res = nodes.keys(); |
| 295 | res.sort(); |
| 296 | return res; |
| 297 | }; |
| 298 | const auto sortedParameters = [](const QShaderNode &node) { |
| 299 | auto res = node.parameterNames(); |
| 300 | res.sort(); |
| 301 | return res; |
| 302 | }; |
| 303 | QCOMPARE(sortedKeys(loader.nodes()), sortedKeys(nodes)); |
| 304 | for (const auto &key : nodes.keys()) { |
| 305 | const auto actual = loader.nodes().value(akey: key); |
| 306 | const auto expected = nodes.value(akey: key); |
| 307 | |
| 308 | QVERIFY(actual.uuid().isNull()); |
| 309 | QCOMPARE(actual.ports(), expected.ports()); |
| 310 | QCOMPARE(sortedParameters(actual), sortedParameters(expected)); |
| 311 | for (const auto &name : expected.parameterNames()) { |
| 312 | QCOMPARE(actual.parameter(name), expected.parameter(name)); |
| 313 | } |
| 314 | QCOMPARE(actual.availableFormats(), expected.availableFormats()); |
| 315 | for (const auto &format : expected.availableFormats()) { |
| 316 | QCOMPARE(actual.rule(format), expected.rule(format)); |
| 317 | } |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | QTEST_MAIN(tst_QShaderNodesLoader) |
| 322 | |
| 323 | #include "tst_qshadernodesloader.moc" |
| 324 | |