| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 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 | #include <qtest.h> |
| 30 | |
| 31 | #include <QList> |
| 32 | #include <QByteArray> |
| 33 | #include <private/qquickopenglshadereffect_p.h> |
| 34 | #include <QMatrix4x4> |
| 35 | #include <QtQuick/QQuickView> |
| 36 | #include <QtQml/QQmlEngine> |
| 37 | #include "../../shared/util.h" |
| 38 | |
| 39 | class TestShaderEffect : public QQuickShaderEffect |
| 40 | { |
| 41 | Q_OBJECT |
| 42 | Q_PROPERTY(QVariant source READ dummyRead NOTIFY sourceChanged) |
| 43 | Q_PROPERTY(QVariant _0aA9zZ READ dummyRead NOTIFY dummyChanged) |
| 44 | Q_PROPERTY(QVariant x86 READ dummyRead NOTIFY dummyChanged) |
| 45 | Q_PROPERTY(QVariant X READ dummyRead NOTIFY dummyChanged) |
| 46 | Q_PROPERTY(QMatrix4x4 mat4x4 READ mat4x4Read NOTIFY dummyChanged) |
| 47 | |
| 48 | public: |
| 49 | TestShaderEffect(QQuickItem* parent = nullptr) : QQuickShaderEffect(parent) |
| 50 | { |
| 51 | } |
| 52 | |
| 53 | QMatrix4x4 mat4x4Read() const { return QMatrix4x4(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1); } |
| 54 | QVariant dummyRead() const { return QVariant(); } |
| 55 | |
| 56 | int signalsConnected = 0; |
| 57 | |
| 58 | protected: |
| 59 | void connectNotify(const QMetaMethod &) override { ++signalsConnected; } |
| 60 | void disconnectNotify(const QMetaMethod &) override { --signalsConnected; } |
| 61 | |
| 62 | signals: |
| 63 | void dummyChanged(); |
| 64 | void sourceChanged(); |
| 65 | |
| 66 | private: |
| 67 | }; |
| 68 | |
| 69 | class tst_qquickshadereffect : public QQmlDataTest |
| 70 | { |
| 71 | Q_OBJECT |
| 72 | public: |
| 73 | tst_qquickshadereffect(); |
| 74 | |
| 75 | private slots: |
| 76 | void initTestCase(); |
| 77 | void cleanupTestCase(); |
| 78 | |
| 79 | void lookThroughShaderCode_data(); |
| 80 | void lookThroughShaderCode(); |
| 81 | |
| 82 | void deleteSourceItem(); |
| 83 | void deleteShaderEffectSource(); |
| 84 | void twoImagesOneShaderEffect(); |
| 85 | |
| 86 | void withoutQmlEngine(); |
| 87 | |
| 88 | void hideParent(); |
| 89 | |
| 90 | private: |
| 91 | enum PresenceFlags { |
| 92 | VertexPresent = 0x01, |
| 93 | TexCoordPresent = 0x02, |
| 94 | MatrixPresent = 0x04, |
| 95 | OpacityPresent = 0x08, |
| 96 | SourcePresent = 0x10 |
| 97 | }; |
| 98 | }; |
| 99 | |
| 100 | tst_qquickshadereffect::tst_qquickshadereffect() |
| 101 | { |
| 102 | qmlRegisterType<TestShaderEffect>(uri: "ShaderEffectTest" , versionMajor: 1, versionMinor: 0, qmlName: "TestShaderEffect" ); |
| 103 | } |
| 104 | |
| 105 | void tst_qquickshadereffect::initTestCase() |
| 106 | { |
| 107 | QQmlDataTest::initTestCase(); |
| 108 | } |
| 109 | |
| 110 | void tst_qquickshadereffect::cleanupTestCase() |
| 111 | { |
| 112 | } |
| 113 | |
| 114 | void tst_qquickshadereffect::lookThroughShaderCode_data() |
| 115 | { |
| 116 | QTest::addColumn<QByteArray>(name: "vertexShader" ); |
| 117 | QTest::addColumn<QByteArray>(name: "fragmentShader" ); |
| 118 | QTest::addColumn<int>(name: "presenceFlags" ); |
| 119 | |
| 120 | QTest::newRow(dataTag: "default" ) |
| 121 | << QByteArray("uniform highp mat4 qt_Matrix; \n" |
| 122 | "attribute highp vec4 qt_Vertex; \n" |
| 123 | "attribute highp vec2 qt_MultiTexCoord0; \n" |
| 124 | "varying highp vec2 qt_TexCoord0; \n" |
| 125 | "void main() { \n" |
| 126 | " qt_TexCoord0 = qt_MultiTexCoord0; \n" |
| 127 | " gl_Position = qt_Matrix * qt_Vertex; \n" |
| 128 | "}" ) |
| 129 | << QByteArray("varying highp vec2 qt_TexCoord0; \n" |
| 130 | "uniform sampler2D source; \n" |
| 131 | "uniform lowp float qt_Opacity; \n" |
| 132 | "void main() { \n" |
| 133 | " gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity; \n" |
| 134 | "}" ) |
| 135 | << (VertexPresent | TexCoordPresent | MatrixPresent | OpacityPresent | SourcePresent); |
| 136 | |
| 137 | QTest::newRow(dataTag: "empty" ) |
| 138 | << QByteArray(" " ) // one space -- if completely empty, default will be used instead. |
| 139 | << QByteArray(" " ) |
| 140 | << 0; |
| 141 | |
| 142 | |
| 143 | QTest::newRow(dataTag: "inside line comments" ) |
| 144 | << QByteArray("//uniform highp mat4 qt_Matrix;\n" |
| 145 | "attribute highp vec4 qt_Vertex;\n" |
| 146 | "// attribute highp vec2 qt_MultiTexCoord0;" ) |
| 147 | << QByteArray("uniform int source; // uniform lowp float qt_Opacity;" ) |
| 148 | << (VertexPresent | SourcePresent); |
| 149 | |
| 150 | QTest::newRow(dataTag: "inside block comments" ) |
| 151 | << QByteArray("/*uniform highp mat4 qt_Matrix;\n" |
| 152 | "*/attribute highp vec4 qt_Vertex;\n" |
| 153 | "/*/attribute highp vec2 qt_MultiTexCoord0;//**/" ) |
| 154 | << QByteArray("/**/uniform int source; /* uniform lowp float qt_Opacity; */" ) |
| 155 | << (VertexPresent | SourcePresent); |
| 156 | |
| 157 | QTest::newRow(dataTag: "inside preprocessor directive" ) |
| 158 | << QByteArray("#define uniform\nhighp mat4 qt_Matrix;\n" |
| 159 | "attribute highp vec4 qt_Vertex;\n" |
| 160 | "#if\\\nattribute highp vec2 qt_MultiTexCoord0;" ) |
| 161 | << QByteArray("uniform int source;\n" |
| 162 | " # undef uniform lowp float qt_Opacity;" ) |
| 163 | << (VertexPresent | SourcePresent); |
| 164 | |
| 165 | |
| 166 | QTest::newRow(dataTag: "line comments between" ) |
| 167 | << QByteArray("uniform//foo\nhighp//bar\nmat4//baz\nqt_Matrix;\n" |
| 168 | "attribute//\nhighp//\nvec4//\nqt_Vertex;\n" |
| 169 | " //*/ uniform \n attribute //\\ \n highp //// \n vec2 //* \n qt_MultiTexCoord0;" ) |
| 170 | << QByteArray("uniform// lowp float qt_Opacity;\nsampler2D source;" ) |
| 171 | << (VertexPresent | TexCoordPresent | MatrixPresent | SourcePresent); |
| 172 | |
| 173 | QTest::newRow(dataTag: "block comments between" ) |
| 174 | << QByteArray("uniform/*foo*/highp/*/bar/*/mat4/**//**/qt_Matrix;\n" |
| 175 | "attribute/**/highp/**/vec4/**/qt_Vertex;\n" |
| 176 | " /* * */ attribute /*///*/ highp /****/ vec2 /**/ qt_MultiTexCoord0;" ) |
| 177 | << QByteArray("uniform/*/ uniform//lowp/*float qt_Opacity;*/sampler2D source;" ) |
| 178 | << (VertexPresent | TexCoordPresent | MatrixPresent | SourcePresent); |
| 179 | |
| 180 | QTest::newRow(dataTag: "preprocessor directive between" ) |
| 181 | << QByteArray("uniform\n#foo\nhighp\n#bar\nmat4\n#baz\\\nblimey\nqt_Matrix;\n" |
| 182 | "attribute\n#\nhighp\n#\nvec4\n#\nqt_Vertex;\n" |
| 183 | " #uniform \n attribute \n # foo \n highp \n # bar \n vec2 \n#baz \n qt_MultiTexCoord0;" ) |
| 184 | << QByteArray("uniform\n#if lowp float qt_Opacity;\nsampler2D source;" ) |
| 185 | << (VertexPresent | TexCoordPresent | MatrixPresent | SourcePresent); |
| 186 | |
| 187 | QTest::newRow(dataTag: "newline between" ) |
| 188 | << QByteArray("uniform\nhighp\nmat4\nqt_Matrix\n;\n" |
| 189 | "attribute \t\r\n highp \n vec4 \n\n qt_Vertex ;\n" |
| 190 | " \n attribute \n highp \n vec2 \n qt_Multi\nTexCoord0 \n ;" ) |
| 191 | << QByteArray("uniform\nsampler2D\nsource;" |
| 192 | "uniform lowp float qt_Opacity;" ) |
| 193 | << (VertexPresent | MatrixPresent | OpacityPresent | SourcePresent); |
| 194 | |
| 195 | |
| 196 | QTest::newRow(dataTag: "extra characters #1" ) |
| 197 | << QByteArray("funiform highp mat4 qt_Matrix;\n" |
| 198 | "attribute highp vec4 qt_Vertex_;\n" |
| 199 | "attribute highp vec2 qqt_MultiTexCoord0;" ) |
| 200 | << QByteArray("uniformm int source;\n" |
| 201 | "uniform4 lowp float qt_Opacity;" ) |
| 202 | << 0; |
| 203 | |
| 204 | QTest::newRow(dataTag: "extra characters #2" ) |
| 205 | << QByteArray("attribute phighp vec4 qt_Vertex;\n" |
| 206 | "attribute highpi vec2 qt_MultiTexCoord0;" |
| 207 | "fattribute highp vec4 qt_Vertex;\n" |
| 208 | "attributed highp vec2 qt_MultiTexCoord0;" ) |
| 209 | << QByteArray(" " ) |
| 210 | << 0; |
| 211 | |
| 212 | QTest::newRow(dataTag: "missing characters #1" ) |
| 213 | << QByteArray("unifor highp mat4 qt_Matrix;\n" |
| 214 | "attribute highp vec4 qt_Vert;\n" |
| 215 | "attribute highp vec2 MultiTexCoord0;" ) |
| 216 | << QByteArray("niform int source;\n" |
| 217 | "uniform qt_Opacity;" ) |
| 218 | << 0; |
| 219 | |
| 220 | QTest::newRow(dataTag: "missing characters #2" ) |
| 221 | << QByteArray("attribute high vec4 qt_Vertex;\n" |
| 222 | "attribute ighp vec2 qt_MultiTexCoord0;" |
| 223 | "tribute highp vec4 qt_Vertex;\n" |
| 224 | "attrib highp vec2 qt_MultiTexCoord0;" ) |
| 225 | << QByteArray(" " ) |
| 226 | << 0; |
| 227 | |
| 228 | QTest::newRow(dataTag: "precision" ) |
| 229 | << QByteArray("uniform mat4 qt_Matrix;\n" |
| 230 | "attribute kindofhighp vec4 qt_Vertex;\n" |
| 231 | "attribute highp qt_MultiTexCoord0;\n" ) |
| 232 | << QByteArray("uniform lowp float qt_Opacity;\n" |
| 233 | "uniform mediump float source;\n" ) |
| 234 | << (MatrixPresent | OpacityPresent | SourcePresent); |
| 235 | |
| 236 | |
| 237 | QTest::newRow(dataTag: "property name #1" ) |
| 238 | << QByteArray("uniform highp vec3 _0aA9zZ;" ) |
| 239 | << QByteArray(" " ) |
| 240 | << int(SourcePresent); |
| 241 | |
| 242 | QTest::newRow(dataTag: "property name #2" ) |
| 243 | << QByteArray("uniform mediump vec2 x86;" ) |
| 244 | << QByteArray(" " ) |
| 245 | << int(SourcePresent); |
| 246 | |
| 247 | QTest::newRow(dataTag: "property name #3" ) |
| 248 | << QByteArray("uniform lowp float X;" ) |
| 249 | << QByteArray(" " ) |
| 250 | << int(SourcePresent); |
| 251 | |
| 252 | QTest::newRow(dataTag: "property name #4" ) |
| 253 | << QByteArray("uniform highp mat4 mat4x4;" ) |
| 254 | << QByteArray(" " ) |
| 255 | << int(SourcePresent); |
| 256 | } |
| 257 | |
| 258 | void tst_qquickshadereffect::lookThroughShaderCode() |
| 259 | { |
| 260 | QFETCH(QByteArray, vertexShader); |
| 261 | QFETCH(QByteArray, fragmentShader); |
| 262 | QFETCH(int, presenceFlags); |
| 263 | |
| 264 | QQmlEngine engine; |
| 265 | QQmlComponent component(&engine); |
| 266 | component.setData("import QtQuick 2.0\nimport ShaderEffectTest 1.0\nTestShaderEffect {}" , baseUrl: QUrl()); |
| 267 | QScopedPointer<TestShaderEffect> item(qobject_cast<TestShaderEffect*>(object: component.create())); |
| 268 | QCOMPARE(item->signalsConnected, 0); |
| 269 | |
| 270 | QString expected; |
| 271 | if ((presenceFlags & VertexPresent) == 0) |
| 272 | expected += "Warning: Missing reference to \'qt_Vertex\'.\n" ; |
| 273 | if ((presenceFlags & TexCoordPresent) == 0) |
| 274 | expected += "Warning: Missing reference to \'qt_MultiTexCoord0\'.\n" ; |
| 275 | if ((presenceFlags & MatrixPresent) == 0) |
| 276 | expected += "Warning: Vertex shader is missing reference to \'qt_Matrix\'.\n" ; |
| 277 | if ((presenceFlags & OpacityPresent) == 0) |
| 278 | expected += "Warning: Shaders are missing reference to \'qt_Opacity\'.\n" ; |
| 279 | |
| 280 | item->setVertexShader(vertexShader); |
| 281 | item->setFragmentShader(fragmentShader); |
| 282 | QCOMPARE(item->parseLog(), expected); |
| 283 | |
| 284 | // If the uniform was successfully parsed, the notify signal has been connected to an update slot. |
| 285 | QCOMPARE(item->signalsConnected, (presenceFlags & SourcePresent) ? 1 : 0); |
| 286 | } |
| 287 | |
| 288 | void tst_qquickshadereffect::deleteSourceItem() |
| 289 | { |
| 290 | // purely to ensure that deleting the sourceItem of a shader doesn't cause a crash |
| 291 | QQuickView *view = new QQuickView(nullptr); |
| 292 | view->setSource(QUrl::fromLocalFile(localfile: testFile(fileName: "deleteSourceItem.qml" ))); |
| 293 | view->show(); |
| 294 | QVERIFY(QTest::qWaitForWindowExposed(view)); |
| 295 | QVERIFY(view); |
| 296 | QObject *obj = view->rootObject(); |
| 297 | QVERIFY(obj); |
| 298 | QMetaObject::invokeMethod(obj, member: "setDeletedSourceItem" ); |
| 299 | QTest::qWait(ms: 50); |
| 300 | delete view; |
| 301 | } |
| 302 | |
| 303 | void tst_qquickshadereffect::deleteShaderEffectSource() |
| 304 | { |
| 305 | // purely to ensure that deleting the sourceItem of a shader doesn't cause a crash |
| 306 | QQuickView *view = new QQuickView(nullptr); |
| 307 | view->setSource(QUrl::fromLocalFile(localfile: testFile(fileName: "deleteShaderEffectSource.qml" ))); |
| 308 | view->show(); |
| 309 | QVERIFY(QTest::qWaitForWindowExposed(view)); |
| 310 | QVERIFY(view); |
| 311 | QObject *obj = view->rootObject(); |
| 312 | QVERIFY(obj); |
| 313 | QMetaObject::invokeMethod(obj, member: "setDeletedShaderEffectSource" ); |
| 314 | QTest::qWait(ms: 50); |
| 315 | delete view; |
| 316 | } |
| 317 | |
| 318 | void tst_qquickshadereffect::twoImagesOneShaderEffect() |
| 319 | { |
| 320 | // purely to ensure that deleting the sourceItem of a shader doesn't cause a crash |
| 321 | QQuickView *view = new QQuickView(nullptr); |
| 322 | view->setSource(QUrl::fromLocalFile(localfile: testFile(fileName: "twoImagesOneShaderEffect.qml" ))); |
| 323 | view->show(); |
| 324 | QVERIFY(QTest::qWaitForWindowExposed(view)); |
| 325 | QVERIFY(view); |
| 326 | QObject *obj = view->rootObject(); |
| 327 | QVERIFY(obj); |
| 328 | delete view; |
| 329 | } |
| 330 | |
| 331 | void tst_qquickshadereffect::withoutQmlEngine() |
| 332 | { |
| 333 | // using a shader without QML engine used to crash |
| 334 | auto window = new QQuickWindow; |
| 335 | auto shaderEffect = new TestShaderEffect(window->contentItem()); |
| 336 | shaderEffect->setVertexShader("" ); |
| 337 | QVERIFY(shaderEffect->isComponentComplete()); |
| 338 | delete window; |
| 339 | } |
| 340 | |
| 341 | // QTBUG-86402: hiding the parent of an item that uses an effect should not cause a crash. |
| 342 | void tst_qquickshadereffect::hideParent() |
| 343 | { |
| 344 | QScopedPointer<QQuickView> view(new QQuickView); |
| 345 | view->setSource(testFileUrl(fileName: "hideParent.qml" )); |
| 346 | QCOMPARE(view->status(), QQuickView::Ready); |
| 347 | view->show(); |
| 348 | QVERIFY(QTest::qWaitForWindowExposed(view.data())); |
| 349 | // Should finish without crashing. |
| 350 | QTRY_VERIFY(view->rootObject()->property("finished" ).toBool()); |
| 351 | } |
| 352 | |
| 353 | QTEST_MAIN(tst_qquickshadereffect) |
| 354 | |
| 355 | #include "tst_qquickshadereffect.moc" |
| 356 | |