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/qmetaobject.h> |
33 | #include <QtGui/private/qshadergenerator_p.h> |
34 | #include <QtGui/private/qshaderlanguage_p.h> |
35 | |
36 | namespace |
37 | { |
38 | QShaderFormat createFormat(QShaderFormat::Api api, int majorVersion, int minorVersion, |
39 | QShaderFormat::ShaderType shaderType= QShaderFormat::Fragment) |
40 | { |
41 | auto format = QShaderFormat(); |
42 | format.setApi(api); |
43 | format.setVersion(QVersionNumber(majorVersion, minorVersion)); |
44 | format.setShaderType(shaderType); |
45 | return format; |
46 | } |
47 | |
48 | QShaderNodePort createPort(QShaderNodePort::Direction portDirection, const QString &portName) |
49 | { |
50 | auto port = QShaderNodePort(); |
51 | port.direction = portDirection; |
52 | port.name = portName; |
53 | return port; |
54 | } |
55 | |
56 | QShaderNode createNode(const QVector<QShaderNodePort> &ports, const QStringList &layers = QStringList()) |
57 | { |
58 | auto node = QShaderNode(); |
59 | node.setUuid(QUuid::createUuid()); |
60 | node.setLayers(layers); |
61 | for (const auto &port : ports) |
62 | node.addPort(port); |
63 | return node; |
64 | } |
65 | |
66 | QShaderGraph::Edge createEdge(const QUuid &sourceUuid, const QString &sourceName, |
67 | const QUuid &targetUuid, const QString &targetName, |
68 | const QStringList &layers = QStringList()) |
69 | { |
70 | auto edge = QShaderGraph::Edge(); |
71 | edge.sourceNodeUuid = sourceUuid; |
72 | edge.sourcePortName = sourceName; |
73 | edge.targetNodeUuid = targetUuid; |
74 | edge.targetPortName = targetName; |
75 | edge.layers = layers; |
76 | return edge; |
77 | } |
78 | |
79 | QShaderGraph createFragmentShaderGraph() |
80 | { |
81 | const auto openGLES2 = createFormat(QShaderFormat::OpenGLES, 2, 0); |
82 | const auto openGL3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0); |
83 | |
84 | auto graph = QShaderGraph(); |
85 | |
86 | auto worldPosition = createNode({ |
87 | createPort(QShaderNodePort::Output, "value" ) |
88 | }); |
89 | worldPosition.setParameter("name" , "worldPosition" ); |
90 | worldPosition.addRule(openGLES2, QShaderNode::Rule("highp vec3 $value = $name;" , |
91 | QByteArrayList() << "varying highp vec3 $name;" )); |
92 | worldPosition.addRule(openGL3, QShaderNode::Rule("vec3 $value = $name;" , |
93 | QByteArrayList() << "in vec3 $name;" )); |
94 | |
95 | auto texture = createNode({ |
96 | createPort(QShaderNodePort::Output, "texture" ) |
97 | }); |
98 | texture.addRule(openGLES2, QShaderNode::Rule("sampler2D $texture = texture;" , |
99 | QByteArrayList() << "uniform sampler2D texture;" )); |
100 | texture.addRule(openGL3, QShaderNode::Rule("sampler2D $texture = texture;" , |
101 | QByteArrayList() << "uniform sampler2D texture;" )); |
102 | |
103 | auto texCoord = createNode({ |
104 | createPort(QShaderNodePort::Output, "texCoord" ) |
105 | }); |
106 | texCoord.addRule(openGLES2, QShaderNode::Rule("highp vec2 $texCoord = texCoord;" , |
107 | QByteArrayList() << "varying highp vec2 texCoord;" )); |
108 | texCoord.addRule(openGL3, QShaderNode::Rule("vec2 $texCoord = texCoord;" , |
109 | QByteArrayList() << "in vec2 texCoord;" )); |
110 | |
111 | auto lightIntensity = createNode({ |
112 | createPort(QShaderNodePort::Output, "lightIntensity" ) |
113 | }); |
114 | lightIntensity.addRule(openGLES2, QShaderNode::Rule("highp float $lightIntensity = lightIntensity;" , |
115 | QByteArrayList() << "uniform highp float lightIntensity;" )); |
116 | lightIntensity.addRule(openGL3, QShaderNode::Rule("float $lightIntensity = lightIntensity;" , |
117 | QByteArrayList() << "uniform float lightIntensity;" )); |
118 | |
119 | auto exposure = createNode({ |
120 | createPort(QShaderNodePort::Output, "exposure" ) |
121 | }); |
122 | exposure.addRule(openGLES2, QShaderNode::Rule("highp float $exposure = exposure;" , |
123 | QByteArrayList() << "uniform highp float exposure;" )); |
124 | exposure.addRule(openGL3, QShaderNode::Rule("float $exposure = exposure;" , |
125 | QByteArrayList() << "uniform float exposure;" )); |
126 | |
127 | auto fragColor = createNode({ |
128 | createPort(QShaderNodePort::Input, "fragColor" ) |
129 | }); |
130 | fragColor.addRule(openGLES2, QShaderNode::Rule("gl_fragColor = $fragColor;" )); |
131 | fragColor.addRule(openGL3, QShaderNode::Rule("fragColor = $fragColor;" , |
132 | QByteArrayList() << "out vec4 fragColor;" )); |
133 | |
134 | auto sampleTexture = createNode({ |
135 | createPort(QShaderNodePort::Input, "sampler" ), |
136 | createPort(QShaderNodePort::Input, "coord" ), |
137 | createPort(QShaderNodePort::Output, "color" ) |
138 | }); |
139 | sampleTexture.addRule(openGLES2, QShaderNode::Rule("highp vec4 $color = texture2D($sampler, $coord);" )); |
140 | sampleTexture.addRule(openGL3, QShaderNode::Rule("vec4 $color = texture($sampler, $coord);" )); |
141 | |
142 | auto lightFunction = createNode({ |
143 | createPort(QShaderNodePort::Input, "baseColor" ), |
144 | createPort(QShaderNodePort::Input, "position" ), |
145 | createPort(QShaderNodePort::Input, "lightIntensity" ), |
146 | createPort(QShaderNodePort::Output, "outputColor" ) |
147 | }); |
148 | lightFunction.addRule(openGLES2, QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
149 | QByteArrayList() << "#pragma include es2/lightmodel.frag.inc" )); |
150 | lightFunction.addRule(openGL3, QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
151 | QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc" )); |
152 | |
153 | auto exposureFunction = createNode({ |
154 | createPort(QShaderNodePort::Input, "inputColor" ), |
155 | createPort(QShaderNodePort::Input, "exposure" ), |
156 | createPort(QShaderNodePort::Output, "outputColor" ) |
157 | }); |
158 | exposureFunction.addRule(openGLES2, QShaderNode::Rule("highp vec4 $outputColor = $inputColor * pow(2.0, $exposure);" )); |
159 | exposureFunction.addRule(openGL3, QShaderNode::Rule("vec4 $outputColor = $inputColor * pow(2.0, $exposure);" )); |
160 | |
161 | graph.addNode(worldPosition); |
162 | graph.addNode(texture); |
163 | graph.addNode(texCoord); |
164 | graph.addNode(lightIntensity); |
165 | graph.addNode(exposure); |
166 | graph.addNode(fragColor); |
167 | graph.addNode(sampleTexture); |
168 | graph.addNode(lightFunction); |
169 | graph.addNode(exposureFunction); |
170 | |
171 | graph.addEdge(createEdge(texture.uuid(), "texture" , sampleTexture.uuid(), "sampler" )); |
172 | graph.addEdge(createEdge(texCoord.uuid(), "texCoord" , sampleTexture.uuid(), "coord" )); |
173 | |
174 | graph.addEdge(createEdge(worldPosition.uuid(), "value" , lightFunction.uuid(), "position" )); |
175 | graph.addEdge(createEdge(sampleTexture.uuid(), "color" , lightFunction.uuid(), "baseColor" )); |
176 | graph.addEdge(createEdge(lightIntensity.uuid(), "lightIntensity" , lightFunction.uuid(), "lightIntensity" )); |
177 | |
178 | graph.addEdge(createEdge(lightFunction.uuid(), "outputColor" , exposureFunction.uuid(), "inputColor" )); |
179 | graph.addEdge(createEdge(exposure.uuid(), "exposure" , exposureFunction.uuid(), "exposure" )); |
180 | |
181 | graph.addEdge(createEdge(exposureFunction.uuid(), "outputColor" , fragColor.uuid(), "fragColor" )); |
182 | |
183 | return graph; |
184 | } |
185 | } |
186 | |
187 | class tst_QShaderGenerator : public QObject |
188 | { |
189 | Q_OBJECT |
190 | private slots: |
191 | void shouldHaveDefaultState(); |
192 | void shouldGenerateShaderCode_data(); |
193 | void shouldGenerateShaderCode(); |
194 | void shouldGenerateVersionCommands_data(); |
195 | void shouldGenerateVersionCommands(); |
196 | void shouldProcessLanguageQualifierAndTypeEnums_data(); |
197 | void shouldProcessLanguageQualifierAndTypeEnums(); |
198 | void shouldGenerateDifferentCodeDependingOnActiveLayers(); |
199 | void shouldUseGlobalVariableRatherThanTemporaries(); |
200 | void shouldGenerateTemporariesWisely(); |
201 | }; |
202 | |
203 | void tst_QShaderGenerator::shouldHaveDefaultState() |
204 | { |
205 | // GIVEN |
206 | auto generator = QShaderGenerator(); |
207 | |
208 | // THEN |
209 | QVERIFY(generator.graph.nodes().isEmpty()); |
210 | QVERIFY(generator.graph.edges().isEmpty()); |
211 | QVERIFY(!generator.format.isValid()); |
212 | } |
213 | |
214 | void tst_QShaderGenerator::shouldGenerateShaderCode_data() |
215 | { |
216 | QTest::addColumn<QShaderGraph>("graph" ); |
217 | QTest::addColumn<QShaderFormat>("format" ); |
218 | QTest::addColumn<QByteArray>("expectedCode" ); |
219 | |
220 | const auto graph = createFragmentShaderGraph(); |
221 | |
222 | const auto openGLES2 = createFormat(QShaderFormat::OpenGLES, 2, 0); |
223 | const auto openGL3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0); |
224 | const auto openGL32 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 2); |
225 | const auto openGL4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0); |
226 | |
227 | const auto versionGLES2 = QByteArrayList() << "#version 100" << "" ; |
228 | const auto versionGL3 = QByteArrayList() << "#version 130" << "" ; |
229 | const auto versionGL32 = QByteArrayList() << "#version 150 core" << "" ; |
230 | const auto versionGL4 = QByteArrayList() << "#version 400 core" << "" ; |
231 | |
232 | const auto es2Code = QByteArrayList() << "varying highp vec3 worldPosition;" |
233 | << "uniform sampler2D texture;" |
234 | << "varying highp vec2 texCoord;" |
235 | << "uniform highp float lightIntensity;" |
236 | << "uniform highp float exposure;" |
237 | << "#pragma include es2/lightmodel.frag.inc" |
238 | << "" |
239 | << "void main()" |
240 | << "{" |
241 | << " gl_fragColor = (((((lightModel(((texture2D(texture, texCoord))), worldPosition, lightIntensity)))) * pow(2.0, exposure)));" |
242 | << "}" |
243 | << "" ; |
244 | |
245 | const auto gl3Code = QByteArrayList() << "in vec3 worldPosition;" |
246 | << "uniform sampler2D texture;" |
247 | << "in vec2 texCoord;" |
248 | << "uniform float lightIntensity;" |
249 | << "uniform float exposure;" |
250 | << "out vec4 fragColor;" |
251 | << "#pragma include gl3/lightmodel.frag.inc" |
252 | << "" |
253 | << "void main()" |
254 | << "{" |
255 | << " fragColor = (((((lightModel(((texture(texture, texCoord))), worldPosition, lightIntensity)))) * pow(2.0, exposure)));" |
256 | << "}" |
257 | << "" ; |
258 | |
259 | QTest::newRow("EmptyGraphAndFormat" ) << QShaderGraph() << QShaderFormat() << QByteArrayLiteral("\nvoid main()\n{\n}\n" ); |
260 | QTest::newRow("LightExposureGraphAndES2" ) << graph << openGLES2 << (versionGLES2 + es2Code).join('\n'); |
261 | QTest::newRow("LightExposureGraphAndGL3" ) << graph << openGL3 << (versionGL3 + gl3Code).join('\n'); |
262 | QTest::newRow("LightExposureGraphAndGL32" ) << graph << openGL32 << (versionGL32 + gl3Code).join('\n'); |
263 | QTest::newRow("LightExposureGraphAndGL4" ) << graph << openGL4 << (versionGL4 + gl3Code).join('\n'); |
264 | } |
265 | |
266 | void tst_QShaderGenerator::shouldGenerateShaderCode() |
267 | { |
268 | // GIVEN |
269 | QFETCH(QShaderGraph, graph); |
270 | QFETCH(QShaderFormat, format); |
271 | |
272 | auto generator = QShaderGenerator(); |
273 | generator.graph = graph; |
274 | generator.format = format; |
275 | |
276 | // WHEN |
277 | const auto code = generator.createShaderCode(); |
278 | |
279 | // THEN |
280 | QFETCH(QByteArray, expectedCode); |
281 | QCOMPARE(code, expectedCode); |
282 | } |
283 | |
284 | void tst_QShaderGenerator::shouldGenerateVersionCommands_data() |
285 | { |
286 | QTest::addColumn<QShaderFormat>("format" ); |
287 | QTest::addColumn<QByteArray>("version" ); |
288 | |
289 | QTest::newRow("GLES2" ) << createFormat(QShaderFormat::OpenGLES, 2, 0) << QByteArrayLiteral("#version 100" ); |
290 | QTest::newRow("GLES3" ) << createFormat(QShaderFormat::OpenGLES, 3, 0) << QByteArrayLiteral("#version 300 es" ); |
291 | |
292 | QTest::newRow("GL20" ) << createFormat(QShaderFormat::OpenGLNoProfile, 2, 0) << QByteArrayLiteral("#version 110" ); |
293 | QTest::newRow("GL21" ) << createFormat(QShaderFormat::OpenGLNoProfile, 2, 1) << QByteArrayLiteral("#version 120" ); |
294 | QTest::newRow("GL30" ) << createFormat(QShaderFormat::OpenGLNoProfile, 3, 0) << QByteArrayLiteral("#version 130" ); |
295 | QTest::newRow("GL31" ) << createFormat(QShaderFormat::OpenGLNoProfile, 3, 1) << QByteArrayLiteral("#version 140" ); |
296 | QTest::newRow("GL32" ) << createFormat(QShaderFormat::OpenGLNoProfile, 3, 2) << QByteArrayLiteral("#version 150" ); |
297 | QTest::newRow("GL33" ) << createFormat(QShaderFormat::OpenGLNoProfile, 3, 3) << QByteArrayLiteral("#version 330" ); |
298 | QTest::newRow("GL40" ) << createFormat(QShaderFormat::OpenGLNoProfile, 4, 0) << QByteArrayLiteral("#version 400" ); |
299 | QTest::newRow("GL41" ) << createFormat(QShaderFormat::OpenGLNoProfile, 4, 1) << QByteArrayLiteral("#version 410" ); |
300 | QTest::newRow("GL42" ) << createFormat(QShaderFormat::OpenGLNoProfile, 4, 2) << QByteArrayLiteral("#version 420" ); |
301 | QTest::newRow("GL43" ) << createFormat(QShaderFormat::OpenGLNoProfile, 4, 3) << QByteArrayLiteral("#version 430" ); |
302 | |
303 | QTest::newRow("GL20core" ) << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 0) << QByteArrayLiteral("#version 110" ); |
304 | QTest::newRow("GL21core" ) << createFormat(QShaderFormat::OpenGLCoreProfile, 2, 1) << QByteArrayLiteral("#version 120" ); |
305 | QTest::newRow("GL30core" ) << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0) << QByteArrayLiteral("#version 130" ); |
306 | QTest::newRow("GL31core" ) << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 1) << QByteArrayLiteral("#version 140" ); |
307 | QTest::newRow("GL32core" ) << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 2) << QByteArrayLiteral("#version 150 core" ); |
308 | QTest::newRow("GL33core" ) << createFormat(QShaderFormat::OpenGLCoreProfile, 3, 3) << QByteArrayLiteral("#version 330 core" ); |
309 | QTest::newRow("GL40core" ) << createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0) << QByteArrayLiteral("#version 400 core" ); |
310 | QTest::newRow("GL41core" ) << createFormat(QShaderFormat::OpenGLCoreProfile, 4, 1) << QByteArrayLiteral("#version 410 core" ); |
311 | QTest::newRow("GL42core" ) << createFormat(QShaderFormat::OpenGLCoreProfile, 4, 2) << QByteArrayLiteral("#version 420 core" ); |
312 | QTest::newRow("GL43core" ) << createFormat(QShaderFormat::OpenGLCoreProfile, 4, 3) << QByteArrayLiteral("#version 430 core" ); |
313 | |
314 | QTest::newRow("GL20compatibility" ) << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 0) << QByteArrayLiteral("#version 110" ); |
315 | QTest::newRow("GL21compatibility" ) << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 2, 1) << QByteArrayLiteral("#version 120" ); |
316 | QTest::newRow("GL30compatibility" ) << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 3, 0) << QByteArrayLiteral("#version 130" ); |
317 | QTest::newRow("GL31compatibility" ) << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 3, 1) << QByteArrayLiteral("#version 140" ); |
318 | QTest::newRow("GL32compatibility" ) << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 3, 2) << QByteArrayLiteral("#version 150 compatibility" ); |
319 | QTest::newRow("GL33compatibility" ) << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 3, 3) << QByteArrayLiteral("#version 330 compatibility" ); |
320 | QTest::newRow("GL40compatibility" ) << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 4, 0) << QByteArrayLiteral("#version 400 compatibility" ); |
321 | QTest::newRow("GL41compatibility" ) << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 4, 1) << QByteArrayLiteral("#version 410 compatibility" ); |
322 | QTest::newRow("GL42compatibility" ) << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 4, 2) << QByteArrayLiteral("#version 420 compatibility" ); |
323 | QTest::newRow("GL43compatibility" ) << createFormat(QShaderFormat::OpenGLCompatibilityProfile, 4, 3) << QByteArrayLiteral("#version 430 compatibility" ); |
324 | } |
325 | |
326 | void tst_QShaderGenerator::shouldGenerateVersionCommands() |
327 | { |
328 | // GIVEN |
329 | QFETCH(QShaderFormat, format); |
330 | |
331 | auto generator = QShaderGenerator(); |
332 | generator.format = format; |
333 | |
334 | // WHEN |
335 | const auto code = generator.createShaderCode(); |
336 | |
337 | // THEN |
338 | QFETCH(QByteArray, version); |
339 | const auto expectedCode = (QByteArrayList() << version |
340 | << "" |
341 | << "" |
342 | << "void main()" |
343 | << "{" |
344 | << "}" |
345 | << "" ).join('\n'); |
346 | QCOMPARE(code, expectedCode); |
347 | } |
348 | |
349 | |
350 | namespace { |
351 | QString toGlsl(QShaderLanguage::StorageQualifier qualifier, const QShaderFormat &format) |
352 | { |
353 | if (format.version().majorVersion() <= 2) { |
354 | // Note we're assuming fragment shader only here, it'd be different |
355 | // values for vertex shader, will need to be fixed properly at some |
356 | // point but isn't necessary yet (this problem already exists in past |
357 | // commits anyway) |
358 | switch (qualifier) { |
359 | case QShaderLanguage::Const: |
360 | return "const" ; |
361 | case QShaderLanguage::Input: |
362 | return "varying" ; |
363 | case QShaderLanguage::BuiltIn: |
364 | return "//" ; |
365 | case QShaderLanguage::Output: |
366 | return "" ; // Although fragment shaders for <=2 only have fixed outputs |
367 | case QShaderLanguage::Uniform: |
368 | return "uniform" ; |
369 | } |
370 | } else { |
371 | switch (qualifier) { |
372 | case QShaderLanguage::Const: |
373 | return "const" ; |
374 | case QShaderLanguage::Input: |
375 | return "in" ; |
376 | case QShaderLanguage::BuiltIn: |
377 | return "//" ; |
378 | case QShaderLanguage::Output: |
379 | return "out" ; |
380 | case QShaderLanguage::Uniform: |
381 | return "uniform" ; |
382 | } |
383 | } |
384 | |
385 | Q_UNREACHABLE(); |
386 | } |
387 | |
388 | QString toGlsl(QShaderLanguage::VariableType type) |
389 | { |
390 | switch (type) { |
391 | case QShaderLanguage::Bool: |
392 | return "bool" ; |
393 | case QShaderLanguage::Int: |
394 | return "int" ; |
395 | case QShaderLanguage::Uint: |
396 | return "uint" ; |
397 | case QShaderLanguage::Float: |
398 | return "float" ; |
399 | case QShaderLanguage::Double: |
400 | return "double" ; |
401 | case QShaderLanguage::Vec2: |
402 | return "vec2" ; |
403 | case QShaderLanguage::Vec3: |
404 | return "vec3" ; |
405 | case QShaderLanguage::Vec4: |
406 | return "vec4" ; |
407 | case QShaderLanguage::DVec2: |
408 | return "dvec2" ; |
409 | case QShaderLanguage::DVec3: |
410 | return "dvec3" ; |
411 | case QShaderLanguage::DVec4: |
412 | return "dvec4" ; |
413 | case QShaderLanguage::BVec2: |
414 | return "bvec2" ; |
415 | case QShaderLanguage::BVec3: |
416 | return "bvec3" ; |
417 | case QShaderLanguage::BVec4: |
418 | return "bvec4" ; |
419 | case QShaderLanguage::IVec2: |
420 | return "ivec2" ; |
421 | case QShaderLanguage::IVec3: |
422 | return "ivec3" ; |
423 | case QShaderLanguage::IVec4: |
424 | return "ivec4" ; |
425 | case QShaderLanguage::UVec2: |
426 | return "uvec2" ; |
427 | case QShaderLanguage::UVec3: |
428 | return "uvec3" ; |
429 | case QShaderLanguage::UVec4: |
430 | return "uvec4" ; |
431 | case QShaderLanguage::Mat2: |
432 | return "mat2" ; |
433 | case QShaderLanguage::Mat3: |
434 | return "mat3" ; |
435 | case QShaderLanguage::Mat4: |
436 | return "mat4" ; |
437 | case QShaderLanguage::Mat2x2: |
438 | return "mat2x2" ; |
439 | case QShaderLanguage::Mat2x3: |
440 | return "mat2x3" ; |
441 | case QShaderLanguage::Mat2x4: |
442 | return "mat2x4" ; |
443 | case QShaderLanguage::Mat3x2: |
444 | return "mat3x2" ; |
445 | case QShaderLanguage::Mat3x3: |
446 | return "mat3x3" ; |
447 | case QShaderLanguage::Mat3x4: |
448 | return "mat3x4" ; |
449 | case QShaderLanguage::Mat4x2: |
450 | return "mat4x2" ; |
451 | case QShaderLanguage::Mat4x3: |
452 | return "mat4x3" ; |
453 | case QShaderLanguage::Mat4x4: |
454 | return "mat4x4" ; |
455 | case QShaderLanguage::DMat2: |
456 | return "dmat2" ; |
457 | case QShaderLanguage::DMat3: |
458 | return "dmat3" ; |
459 | case QShaderLanguage::DMat4: |
460 | return "dmat4" ; |
461 | case QShaderLanguage::DMat2x2: |
462 | return "dmat2x2" ; |
463 | case QShaderLanguage::DMat2x3: |
464 | return "dmat2x3" ; |
465 | case QShaderLanguage::DMat2x4: |
466 | return "dmat2x4" ; |
467 | case QShaderLanguage::DMat3x2: |
468 | return "dmat3x2" ; |
469 | case QShaderLanguage::DMat3x3: |
470 | return "dmat3x3" ; |
471 | case QShaderLanguage::DMat3x4: |
472 | return "dmat3x4" ; |
473 | case QShaderLanguage::DMat4x2: |
474 | return "dmat4x2" ; |
475 | case QShaderLanguage::DMat4x3: |
476 | return "dmat4x3" ; |
477 | case QShaderLanguage::DMat4x4: |
478 | return "dmat4x4" ; |
479 | case QShaderLanguage::Sampler1D: |
480 | return "sampler1D" ; |
481 | case QShaderLanguage::Sampler2D: |
482 | return "sampler2D" ; |
483 | case QShaderLanguage::Sampler3D: |
484 | return "sampler3D" ; |
485 | case QShaderLanguage::SamplerCube: |
486 | return "samplerCube" ; |
487 | case QShaderLanguage::Sampler2DRect: |
488 | return "sampler2DRect" ; |
489 | case QShaderLanguage::Sampler2DMs: |
490 | return "sampler2DMS" ; |
491 | case QShaderLanguage::SamplerBuffer: |
492 | return "samplerBuffer" ; |
493 | case QShaderLanguage::Sampler1DArray: |
494 | return "sampler1DArray" ; |
495 | case QShaderLanguage::Sampler2DArray: |
496 | return "sampler2DArray" ; |
497 | case QShaderLanguage::Sampler2DMsArray: |
498 | return "sampler2DMSArray" ; |
499 | case QShaderLanguage::SamplerCubeArray: |
500 | return "samplerCubeArray" ; |
501 | case QShaderLanguage::Sampler1DShadow: |
502 | return "sampler1DShadow" ; |
503 | case QShaderLanguage::Sampler2DShadow: |
504 | return "sampler2DShadow" ; |
505 | case QShaderLanguage::Sampler2DRectShadow: |
506 | return "sampler2DRectShadow" ; |
507 | case QShaderLanguage::Sampler1DArrayShadow: |
508 | return "sampler1DArrayShadow" ; |
509 | case QShaderLanguage::Sampler2DArrayShadow: |
510 | return "sample2DArrayShadow" ; |
511 | case QShaderLanguage::SamplerCubeShadow: |
512 | return "samplerCubeShadow" ; |
513 | case QShaderLanguage::SamplerCubeArrayShadow: |
514 | return "samplerCubeArrayShadow" ; |
515 | case QShaderLanguage::ISampler1D: |
516 | return "isampler1D" ; |
517 | case QShaderLanguage::ISampler2D: |
518 | return "isampler2D" ; |
519 | case QShaderLanguage::ISampler3D: |
520 | return "isampler3D" ; |
521 | case QShaderLanguage::ISamplerCube: |
522 | return "isamplerCube" ; |
523 | case QShaderLanguage::ISampler2DRect: |
524 | return "isampler2DRect" ; |
525 | case QShaderLanguage::ISampler2DMs: |
526 | return "isampler2DMS" ; |
527 | case QShaderLanguage::ISamplerBuffer: |
528 | return "isamplerBuffer" ; |
529 | case QShaderLanguage::ISampler1DArray: |
530 | return "isampler1DArray" ; |
531 | case QShaderLanguage::ISampler2DArray: |
532 | return "isampler2DArray" ; |
533 | case QShaderLanguage::ISampler2DMsArray: |
534 | return "isampler2DMSArray" ; |
535 | case QShaderLanguage::ISamplerCubeArray: |
536 | return "isamplerCubeArray" ; |
537 | case QShaderLanguage::USampler1D: |
538 | return "usampler1D" ; |
539 | case QShaderLanguage::USampler2D: |
540 | return "usampler2D" ; |
541 | case QShaderLanguage::USampler3D: |
542 | return "usampler3D" ; |
543 | case QShaderLanguage::USamplerCube: |
544 | return "usamplerCube" ; |
545 | case QShaderLanguage::USampler2DRect: |
546 | return "usampler2DRect" ; |
547 | case QShaderLanguage::USampler2DMs: |
548 | return "usampler2DMS" ; |
549 | case QShaderLanguage::USamplerBuffer: |
550 | return "usamplerBuffer" ; |
551 | case QShaderLanguage::USampler1DArray: |
552 | return "usampler1DArray" ; |
553 | case QShaderLanguage::USampler2DArray: |
554 | return "usampler2DArray" ; |
555 | case QShaderLanguage::USampler2DMsArray: |
556 | return "usampler2DMSArray" ; |
557 | case QShaderLanguage::USamplerCubeArray: |
558 | return "usamplerCubeArray" ; |
559 | } |
560 | |
561 | Q_UNREACHABLE(); |
562 | } |
563 | } |
564 | |
565 | void tst_QShaderGenerator::shouldProcessLanguageQualifierAndTypeEnums_data() |
566 | { |
567 | QTest::addColumn<QShaderGraph>("graph" ); |
568 | QTest::addColumn<QShaderFormat>("format" ); |
569 | QTest::addColumn<QByteArray>("expectedCode" ); |
570 | |
571 | { |
572 | const auto es2 = createFormat(QShaderFormat::OpenGLES, 2, 0); |
573 | const auto es3 = createFormat(QShaderFormat::OpenGLES, 3, 0); |
574 | const auto gl2 = createFormat(QShaderFormat::OpenGLNoProfile, 2, 0); |
575 | const auto gl3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0); |
576 | const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0); |
577 | |
578 | const auto qualifierEnum = QMetaEnum::fromType<QShaderLanguage::StorageQualifier>(); |
579 | const auto typeEnum = QMetaEnum::fromType<QShaderLanguage::VariableType>(); |
580 | |
581 | for (int qualifierIndex = 0; qualifierIndex < qualifierEnum.keyCount(); qualifierIndex++) { |
582 | const auto qualifierName = qualifierEnum.key(qualifierIndex); |
583 | const auto qualifierValue = static_cast<QShaderLanguage::StorageQualifier>(qualifierEnum.value(qualifierIndex)); |
584 | |
585 | for (int typeIndex = 0; typeIndex < typeEnum.keyCount(); typeIndex++) { |
586 | const auto typeName = typeEnum.key(typeIndex); |
587 | const auto typeValue = static_cast<QShaderLanguage::VariableType>(typeEnum.value(typeIndex)); |
588 | |
589 | auto graph = QShaderGraph(); |
590 | |
591 | auto worldPosition = createNode({ |
592 | createPort(QShaderNodePort::Output, "value" ) |
593 | }); |
594 | worldPosition.setParameter("name" , "worldPosition" ); |
595 | worldPosition.setParameter("qualifier" , QVariant::fromValue<QShaderLanguage::StorageQualifier>(qualifierValue)); |
596 | worldPosition.setParameter("type" , QVariant::fromValue<QShaderLanguage::VariableType>(typeValue)); |
597 | worldPosition.addRule(es2, QShaderNode::Rule("highp $type $value = $name;" , |
598 | QByteArrayList() << "$qualifier highp $type $name;" )); |
599 | worldPosition.addRule(gl2, QShaderNode::Rule("$type $value = $name;" , |
600 | QByteArrayList() << "$qualifier $type $name;" )); |
601 | worldPosition.addRule(gl3, QShaderNode::Rule("$type $value = $name;" , |
602 | QByteArrayList() << "$qualifier $type $name;" )); |
603 | |
604 | auto fragColor = createNode({ |
605 | createPort(QShaderNodePort::Input, "fragColor" ) |
606 | }); |
607 | fragColor.addRule(es2, QShaderNode::Rule("gl_fragColor = $fragColor;" )); |
608 | fragColor.addRule(gl2, QShaderNode::Rule("gl_fragColor = $fragColor;" )); |
609 | fragColor.addRule(gl3, QShaderNode::Rule("fragColor = $fragColor;" , |
610 | QByteArrayList() << "out vec4 fragColor;" )); |
611 | |
612 | graph.addNode(worldPosition); |
613 | graph.addNode(fragColor); |
614 | |
615 | graph.addEdge(createEdge(worldPosition.uuid(), "value" , fragColor.uuid(), "fragColor" )); |
616 | |
617 | const auto gl2Code = (QByteArrayList() << "#version 110" |
618 | << "" |
619 | << QStringLiteral("%1 %2 worldPosition;" ).arg(toGlsl(qualifierValue, gl2)) |
620 | .arg(toGlsl(typeValue)) |
621 | .toUtf8() |
622 | << "" |
623 | << "void main()" |
624 | << "{" |
625 | << " gl_fragColor = worldPosition;" |
626 | << "}" |
627 | << "" ).join("\n" ); |
628 | const auto gl3Code = (QByteArrayList() << "#version 130" |
629 | << "" |
630 | << QStringLiteral("%1 %2 worldPosition;" ).arg(toGlsl(qualifierValue, gl3)) |
631 | .arg(toGlsl(typeValue)) |
632 | .toUtf8() |
633 | << "out vec4 fragColor;" |
634 | << "" |
635 | << "void main()" |
636 | << "{" |
637 | << " fragColor = worldPosition;" |
638 | << "}" |
639 | << "" ).join("\n" ); |
640 | const auto gl4Code = (QByteArrayList() << "#version 400 core" |
641 | << "" |
642 | << QStringLiteral("%1 %2 worldPosition;" ).arg(toGlsl(qualifierValue, gl4)) |
643 | .arg(toGlsl(typeValue)) |
644 | .toUtf8() |
645 | << "out vec4 fragColor;" |
646 | << "" |
647 | << "void main()" |
648 | << "{" |
649 | << " fragColor = worldPosition;" |
650 | << "}" |
651 | << "" ).join("\n" ); |
652 | const auto es2Code = (QByteArrayList() << "#version 100" |
653 | << "" |
654 | << QStringLiteral("%1 highp %2 worldPosition;" ).arg(toGlsl(qualifierValue, es2)) |
655 | .arg(toGlsl(typeValue)) |
656 | .toUtf8() |
657 | << "" |
658 | << "void main()" |
659 | << "{" |
660 | << " gl_fragColor = worldPosition;" |
661 | << "}" |
662 | << "" ).join("\n" ); |
663 | const auto es3Code = (QByteArrayList() << "#version 300 es" |
664 | << "" |
665 | << QStringLiteral("%1 highp %2 worldPosition;" ).arg(toGlsl(qualifierValue, es3)) |
666 | .arg(toGlsl(typeValue)) |
667 | .toUtf8() |
668 | << "" |
669 | << "void main()" |
670 | << "{" |
671 | << " gl_fragColor = worldPosition;" |
672 | << "}" |
673 | << "" ).join("\n" ); |
674 | |
675 | QTest::addRow("%s %s ES2" , qualifierName, typeName) << graph << es2 << es2Code; |
676 | QTest::addRow("%s %s ES3" , qualifierName, typeName) << graph << es3 << es3Code; |
677 | QTest::addRow("%s %s GL2" , qualifierName, typeName) << graph << gl2 << gl2Code; |
678 | QTest::addRow("%s %s GL3" , qualifierName, typeName) << graph << gl3 << gl3Code; |
679 | QTest::addRow("%s %s GL4" , qualifierName, typeName) << graph << gl4 << gl4Code; |
680 | } |
681 | } |
682 | } |
683 | |
684 | { |
685 | const auto es2 = createFormat(QShaderFormat::OpenGLES, 2, 0, QShaderFormat::Vertex); |
686 | const auto es3 = createFormat(QShaderFormat::OpenGLES, 3, 0, QShaderFormat::Vertex); |
687 | const auto gl2 = createFormat(QShaderFormat::OpenGLNoProfile, 2, 0, QShaderFormat::Vertex); |
688 | const auto gl3 = createFormat(QShaderFormat::OpenGLCoreProfile, 3, 0, QShaderFormat::Vertex); |
689 | const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0, QShaderFormat::Vertex); |
690 | |
691 | auto graph = QShaderGraph(); |
692 | |
693 | auto vertexPosition = createNode({ |
694 | createPort(QShaderNodePort::Output, "value" ) |
695 | }); |
696 | vertexPosition.setParameter("name" , "vertexPosition" ); |
697 | vertexPosition.setParameter("qualifier" , QVariant::fromValue<QShaderLanguage::StorageQualifier>(QShaderLanguage::Input)); |
698 | vertexPosition.setParameter("type" , QVariant::fromValue<QShaderLanguage::VariableType>(QShaderLanguage::Vec4)); |
699 | |
700 | vertexPosition.addRule(es2, QShaderNode::Rule("" , |
701 | QByteArrayList() << "$qualifier highp $type $name;" )); |
702 | vertexPosition.addRule(gl2, QShaderNode::Rule("" , |
703 | QByteArrayList() << "$qualifier $type $name;" )); |
704 | vertexPosition.addRule(gl3, QShaderNode::Rule("" , |
705 | QByteArrayList() << "$qualifier $type $name;" )); |
706 | |
707 | graph.addNode(vertexPosition); |
708 | |
709 | const auto gl2Code = (QByteArrayList() << "#version 110" |
710 | << "" |
711 | << "attribute vec4 vertexPosition;" |
712 | << "" |
713 | << "void main()" |
714 | << "{" |
715 | << "}" |
716 | << "" ).join("\n" ); |
717 | const auto gl3Code = (QByteArrayList() << "#version 130" |
718 | << "" |
719 | << "in vec4 vertexPosition;" |
720 | << "" |
721 | << "void main()" |
722 | << "{" |
723 | << "}" |
724 | << "" ).join("\n" ); |
725 | const auto gl4Code = (QByteArrayList() << "#version 400 core" |
726 | << "" |
727 | << "in vec4 vertexPosition;" |
728 | << "" |
729 | << "void main()" |
730 | << "{" |
731 | << "}" |
732 | << "" ).join("\n" ); |
733 | const auto es2Code = (QByteArrayList() << "#version 100" |
734 | << "" |
735 | << "attribute highp vec4 vertexPosition;" |
736 | << "" |
737 | << "void main()" |
738 | << "{" |
739 | << "}" |
740 | << "" ).join("\n" ); |
741 | const auto es3Code = (QByteArrayList() << "#version 300 es" |
742 | << "" |
743 | << "in highp vec4 vertexPosition;" |
744 | << "" |
745 | << "void main()" |
746 | << "{" |
747 | << "}" |
748 | << "" ).join("\n" ); |
749 | |
750 | QTest::addRow("Attribute header substitution ES2" ) << graph << es2 << es2Code; |
751 | QTest::addRow("Attribute header substitution ES3" ) << graph << es3 << es3Code; |
752 | QTest::addRow("Attribute header substitution GL2" ) << graph << gl2 << gl2Code; |
753 | QTest::addRow("Attribute header substitution GL3" ) << graph << gl3 << gl3Code; |
754 | QTest::addRow("Attribute header substitution GL4" ) << graph << gl4 << gl4Code; |
755 | } |
756 | } |
757 | |
758 | void tst_QShaderGenerator::shouldProcessLanguageQualifierAndTypeEnums() |
759 | { |
760 | // GIVEN |
761 | QFETCH(QShaderGraph, graph); |
762 | QFETCH(QShaderFormat, format); |
763 | |
764 | auto generator = QShaderGenerator(); |
765 | generator.graph = graph; |
766 | generator.format = format; |
767 | |
768 | // WHEN |
769 | const auto code = generator.createShaderCode(); |
770 | |
771 | // THEN |
772 | QFETCH(QByteArray, expectedCode); |
773 | QCOMPARE(code, expectedCode); |
774 | } |
775 | |
776 | void tst_QShaderGenerator::shouldGenerateDifferentCodeDependingOnActiveLayers() |
777 | { |
778 | // GIVEN |
779 | const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0); |
780 | |
781 | auto texCoord = createNode({ |
782 | createPort(QShaderNodePort::Output, "texCoord" ) |
783 | }, { |
784 | "diffuseTexture" , |
785 | "normalTexture" |
786 | }); |
787 | texCoord.addRule(gl4, QShaderNode::Rule("vec2 $texCoord = texCoord;" , |
788 | QByteArrayList() << "in vec2 texCoord;" )); |
789 | auto diffuseUniform = createNode({ |
790 | createPort(QShaderNodePort::Output, "color" ) |
791 | }, {"diffuseUniform" }); |
792 | diffuseUniform.addRule(gl4, QShaderNode::Rule("vec4 $color = diffuseUniform;" , |
793 | QByteArrayList() << "uniform vec4 diffuseUniform;" )); |
794 | auto diffuseTexture = createNode({ |
795 | createPort(QShaderNodePort::Input, "coord" ), |
796 | createPort(QShaderNodePort::Output, "color" ) |
797 | }, {"diffuseTexture" }); |
798 | diffuseTexture.addRule(gl4, QShaderNode::Rule("vec4 $color = texture2D(diffuseTexture, $coord);" , |
799 | QByteArrayList() << "uniform sampler2D diffuseTexture;" )); |
800 | auto normalUniform = createNode({ |
801 | createPort(QShaderNodePort::Output, "normal" ) |
802 | }, {"normalUniform" }); |
803 | normalUniform.addRule(gl4, QShaderNode::Rule("vec3 $normal = normalUniform;" , |
804 | QByteArrayList() << "uniform vec3 normalUniform;" )); |
805 | auto normalTexture = createNode({ |
806 | createPort(QShaderNodePort::Input, "coord" ), |
807 | createPort(QShaderNodePort::Output, "normal" ) |
808 | }, {"normalTexture" }); |
809 | normalTexture.addRule(gl4, QShaderNode::Rule("vec3 $normal = texture2D(normalTexture, $coord).rgb;" , |
810 | QByteArrayList() << "uniform sampler2D normalTexture;" )); |
811 | auto lightFunction = createNode({ |
812 | createPort(QShaderNodePort::Input, "color" ), |
813 | createPort(QShaderNodePort::Input, "normal" ), |
814 | createPort(QShaderNodePort::Output, "output" ) |
815 | }); |
816 | lightFunction.addRule(gl4, QShaderNode::Rule("vec4 $output = lightModel($color, $normal);" , |
817 | QByteArrayList() << "#pragma include gl4/lightmodel.frag.inc" )); |
818 | auto fragColor = createNode({ |
819 | createPort(QShaderNodePort::Input, "fragColor" ) |
820 | }); |
821 | fragColor.addRule(gl4, QShaderNode::Rule("fragColor = $fragColor;" , |
822 | QByteArrayList() << "out vec4 fragColor;" )); |
823 | |
824 | const auto graph = [=] { |
825 | auto res = QShaderGraph(); |
826 | |
827 | res.addNode(texCoord); |
828 | res.addNode(diffuseUniform); |
829 | res.addNode(diffuseTexture); |
830 | res.addNode(normalUniform); |
831 | res.addNode(normalTexture); |
832 | res.addNode(lightFunction); |
833 | res.addNode(fragColor); |
834 | |
835 | res.addEdge(createEdge(diffuseUniform.uuid(), "color" , lightFunction.uuid(), "color" , {"diffuseUniform" })); |
836 | res.addEdge(createEdge(texCoord.uuid(), "texCoord" , diffuseTexture.uuid(), "coord" , {"diffuseTexture" })); |
837 | res.addEdge(createEdge(diffuseTexture.uuid(), "color" , lightFunction.uuid(), "color" , {"diffuseTexture" })); |
838 | |
839 | res.addEdge(createEdge(normalUniform.uuid(), "normal" , lightFunction.uuid(), "normal" , {"normalUniform" })); |
840 | res.addEdge(createEdge(texCoord.uuid(), "texCoord" , normalTexture.uuid(), "coord" , {"normalTexture" })); |
841 | res.addEdge(createEdge(normalTexture.uuid(), "normal" , lightFunction.uuid(), "normal" , {"normalTexture" })); |
842 | |
843 | res.addEdge(createEdge(lightFunction.uuid(), "output" , fragColor.uuid(), "fragColor" )); |
844 | |
845 | return res; |
846 | }(); |
847 | |
848 | auto generator = QShaderGenerator(); |
849 | generator.graph = graph; |
850 | generator.format = gl4; |
851 | |
852 | { |
853 | // WHEN |
854 | const auto code = generator.createShaderCode({"diffuseUniform" , "normalUniform" }); |
855 | |
856 | // THEN |
857 | const auto expected = QByteArrayList() |
858 | << "#version 400 core" |
859 | << "" |
860 | << "uniform vec4 diffuseUniform;" |
861 | << "uniform vec3 normalUniform;" |
862 | << "#pragma include gl4/lightmodel.frag.inc" |
863 | << "out vec4 fragColor;" |
864 | << "" |
865 | << "void main()" |
866 | << "{" |
867 | << " fragColor = ((lightModel(diffuseUniform, normalUniform)));" |
868 | << "}" |
869 | << "" ; |
870 | QCOMPARE(code, expected.join("\n" )); |
871 | } |
872 | |
873 | { |
874 | // WHEN |
875 | const auto code = generator.createShaderCode({"diffuseUniform" , "normalTexture" }); |
876 | |
877 | // THEN |
878 | const auto expected = QByteArrayList() |
879 | << "#version 400 core" |
880 | << "" |
881 | << "in vec2 texCoord;" |
882 | << "uniform vec4 diffuseUniform;" |
883 | << "uniform sampler2D normalTexture;" |
884 | << "#pragma include gl4/lightmodel.frag.inc" |
885 | << "out vec4 fragColor;" |
886 | << "" |
887 | << "void main()" |
888 | << "{" |
889 | << " fragColor = ((lightModel(diffuseUniform, texture2D(normalTexture, texCoord).rgb)));" |
890 | << "}" |
891 | << "" ; |
892 | QCOMPARE(code, expected.join("\n" )); |
893 | } |
894 | |
895 | { |
896 | // WHEN |
897 | const auto code = generator.createShaderCode({"diffuseTexture" , "normalUniform" }); |
898 | |
899 | // THEN |
900 | const auto expected = QByteArrayList() |
901 | << "#version 400 core" |
902 | << "" |
903 | << "in vec2 texCoord;" |
904 | << "uniform sampler2D diffuseTexture;" |
905 | << "uniform vec3 normalUniform;" |
906 | << "#pragma include gl4/lightmodel.frag.inc" |
907 | << "out vec4 fragColor;" |
908 | << "" |
909 | << "void main()" |
910 | << "{" |
911 | << " fragColor = ((lightModel(texture2D(diffuseTexture, texCoord), normalUniform)));" |
912 | << "}" |
913 | << "" ; |
914 | QCOMPARE(code, expected.join("\n" )); |
915 | } |
916 | |
917 | { |
918 | // WHEN |
919 | const auto code = generator.createShaderCode({"diffuseTexture" , "normalTexture" }); |
920 | |
921 | // THEN |
922 | const auto expected = QByteArrayList() |
923 | << "#version 400 core" |
924 | << "" |
925 | << "in vec2 texCoord;" |
926 | << "uniform sampler2D diffuseTexture;" |
927 | << "uniform sampler2D normalTexture;" |
928 | << "#pragma include gl4/lightmodel.frag.inc" |
929 | << "out vec4 fragColor;" |
930 | << "" |
931 | << "void main()" |
932 | << "{" |
933 | << " fragColor = ((lightModel(texture2D(diffuseTexture, texCoord), texture2D(normalTexture, texCoord).rgb)));" |
934 | << "}" |
935 | << "" ; |
936 | QCOMPARE(code, expected.join("\n" )); |
937 | } |
938 | } |
939 | |
940 | void tst_QShaderGenerator::shouldUseGlobalVariableRatherThanTemporaries() |
941 | { |
942 | // GIVEN |
943 | const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0); |
944 | |
945 | { |
946 | // WHEN |
947 | auto vertexPosition = createNode({ |
948 | createPort(QShaderNodePort::Output, "vertexPosition" ) |
949 | }); |
950 | vertexPosition.addRule(gl4, QShaderNode::Rule("vec4 $vertexPosition = vertexPosition;" , |
951 | QByteArrayList() << "in vec4 vertexPosition;" )); |
952 | |
953 | auto fakeMultiPlyNoSpace = createNode({ |
954 | createPort(QShaderNodePort::Input, "varName" ), |
955 | createPort(QShaderNodePort::Output, "out" ) |
956 | }); |
957 | fakeMultiPlyNoSpace.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName*speed;" )); |
958 | |
959 | auto fakeMultiPlySpace = createNode({ |
960 | createPort(QShaderNodePort::Input, "varName" ), |
961 | createPort(QShaderNodePort::Output, "out" ) |
962 | }); |
963 | fakeMultiPlySpace.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName * speed;" )); |
964 | |
965 | auto fakeJoinNoSpace = createNode({ |
966 | createPort(QShaderNodePort::Input, "varName" ), |
967 | createPort(QShaderNodePort::Output, "out" ) |
968 | }); |
969 | fakeJoinNoSpace.addRule(gl4, QShaderNode::Rule("vec4 $out = vec4($varName.xyz,$varName.w);" )); |
970 | |
971 | auto fakeJoinSpace = createNode({ |
972 | createPort(QShaderNodePort::Input, "varName" ), |
973 | createPort(QShaderNodePort::Output, "out" ) |
974 | }); |
975 | fakeJoinSpace.addRule(gl4, QShaderNode::Rule("vec4 $out = vec4($varName.xyz, $varName.w);" )); |
976 | |
977 | auto fakeAdd = createNode({ |
978 | createPort(QShaderNodePort::Input, "varName" ), |
979 | createPort(QShaderNodePort::Output, "out" ) |
980 | }); |
981 | fakeAdd.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName.xyzw + $varName;" )); |
982 | |
983 | auto fakeSub = createNode({ |
984 | createPort(QShaderNodePort::Input, "varName" ), |
985 | createPort(QShaderNodePort::Output, "out" ) |
986 | }); |
987 | fakeSub.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName.xyzw - $varName;" )); |
988 | |
989 | auto fakeDiv = createNode({ |
990 | createPort(QShaderNodePort::Input, "varName" ), |
991 | createPort(QShaderNodePort::Output, "out" ) |
992 | }); |
993 | fakeDiv.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName / v0;" )); |
994 | |
995 | auto fragColor = createNode({ |
996 | createPort(QShaderNodePort::Input, "input1" ), |
997 | createPort(QShaderNodePort::Input, "input2" ), |
998 | createPort(QShaderNodePort::Input, "input3" ), |
999 | createPort(QShaderNodePort::Input, "input4" ), |
1000 | createPort(QShaderNodePort::Input, "input5" ), |
1001 | createPort(QShaderNodePort::Input, "input6" ), |
1002 | createPort(QShaderNodePort::Input, "input7" ) |
1003 | }); |
1004 | fragColor.addRule(gl4, QShaderNode::Rule("fragColor = $input1 + $input2 + $input3 + $input4 + $input5 + $input6 + $input7;" , |
1005 | QByteArrayList() << "out vec4 fragColor;" )); |
1006 | |
1007 | const auto graph = [=] { |
1008 | auto res = QShaderGraph(); |
1009 | |
1010 | res.addNode(vertexPosition); |
1011 | res.addNode(fakeMultiPlyNoSpace); |
1012 | res.addNode(fakeMultiPlySpace); |
1013 | res.addNode(fakeJoinNoSpace); |
1014 | res.addNode(fakeJoinSpace); |
1015 | res.addNode(fakeAdd); |
1016 | res.addNode(fakeSub); |
1017 | res.addNode(fakeDiv); |
1018 | res.addNode(fragColor); |
1019 | |
1020 | res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition" , fakeMultiPlyNoSpace.uuid(), "varName" )); |
1021 | res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition" , fakeMultiPlySpace.uuid(), "varName" )); |
1022 | res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition" , fakeJoinNoSpace.uuid(), "varName" )); |
1023 | res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition" , fakeJoinSpace.uuid(), "varName" )); |
1024 | res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition" , fakeAdd.uuid(), "varName" )); |
1025 | res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition" , fakeSub.uuid(), "varName" )); |
1026 | res.addEdge(createEdge(vertexPosition.uuid(), "vertexPosition" , fakeDiv.uuid(), "varName" )); |
1027 | res.addEdge(createEdge(fakeMultiPlyNoSpace.uuid(), "out" , fragColor.uuid(), "input1" )); |
1028 | res.addEdge(createEdge(fakeMultiPlySpace.uuid(), "out" , fragColor.uuid(), "input2" )); |
1029 | res.addEdge(createEdge(fakeJoinNoSpace.uuid(), "out" , fragColor.uuid(), "input3" )); |
1030 | res.addEdge(createEdge(fakeJoinSpace.uuid(), "out" , fragColor.uuid(), "input4" )); |
1031 | res.addEdge(createEdge(fakeAdd.uuid(), "out" , fragColor.uuid(), "input5" )); |
1032 | res.addEdge(createEdge(fakeSub.uuid(), "out" , fragColor.uuid(), "input6" )); |
1033 | res.addEdge(createEdge(fakeDiv.uuid(), "out" , fragColor.uuid(), "input7" )); |
1034 | |
1035 | return res; |
1036 | }(); |
1037 | |
1038 | auto generator = QShaderGenerator(); |
1039 | generator.graph = graph; |
1040 | generator.format = gl4; |
1041 | |
1042 | const auto code = generator.createShaderCode({"diffuseUniform" , "normalUniform" }); |
1043 | |
1044 | // THEN |
1045 | const auto expected = QByteArrayList() |
1046 | << "#version 400 core" |
1047 | << "" |
1048 | << "in vec4 vertexPosition;" |
1049 | << "out vec4 fragColor;" |
1050 | << "" |
1051 | << "void main()" |
1052 | << "{" |
1053 | << " fragColor = (((((((vertexPosition*speed + vertexPosition * speed + ((vec4(vertexPosition.xyz,vertexPosition.w))) + ((vec4(vertexPosition.xyz, vertexPosition.w))) + ((vertexPosition.xyzw + vertexPosition)) + ((vertexPosition.xyzw - vertexPosition)) + ((vertexPosition / vertexPosition)))))))));" |
1054 | << "}" |
1055 | << "" ; |
1056 | QCOMPARE(code, expected.join("\n" )); |
1057 | } |
1058 | } |
1059 | |
1060 | void tst_QShaderGenerator::shouldGenerateTemporariesWisely() |
1061 | { |
1062 | // GIVEN |
1063 | const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0); |
1064 | |
1065 | { |
1066 | auto attribute = createNode({ |
1067 | createPort(QShaderNodePort::Output, "vertexPosition" ) |
1068 | }); |
1069 | attribute.addRule(gl4, QShaderNode::Rule("vec4 $vertexPosition = vertexPosition;" , |
1070 | QByteArrayList() << "in vec4 vertexPosition;" )); |
1071 | |
1072 | auto complexFunction = createNode({ |
1073 | createPort(QShaderNodePort::Input, "inputVarName" ), |
1074 | createPort(QShaderNodePort::Output, "out" ) |
1075 | }); |
1076 | complexFunction.addRule(gl4, QShaderNode::Rule("vec4 $out = $inputVarName * 2.0;" )); |
1077 | |
1078 | auto complexFunction2 = createNode({ |
1079 | createPort(QShaderNodePort::Input, "inputVarName" ), |
1080 | createPort(QShaderNodePort::Output, "out" ) |
1081 | }); |
1082 | complexFunction2.addRule(gl4, QShaderNode::Rule("vec4 $out = $inputVarName * 4.0;" )); |
1083 | |
1084 | auto complexFunction3 = createNode({ |
1085 | createPort(QShaderNodePort::Input, "a" ), |
1086 | createPort(QShaderNodePort::Input, "b" ), |
1087 | createPort(QShaderNodePort::Output, "out" ) |
1088 | }); |
1089 | complexFunction3.addRule(gl4, QShaderNode::Rule("vec4 $out = $a + $b;" )); |
1090 | |
1091 | auto shaderOutput1 = createNode({ |
1092 | createPort(QShaderNodePort::Input, "input" ) |
1093 | }); |
1094 | |
1095 | shaderOutput1.addRule(gl4, QShaderNode::Rule("shaderOutput1 = $input;" , |
1096 | QByteArrayList() << "out vec4 shaderOutput1;" )); |
1097 | |
1098 | auto shaderOutput2 = createNode({ |
1099 | createPort(QShaderNodePort::Input, "input" ) |
1100 | }); |
1101 | |
1102 | shaderOutput2.addRule(gl4, QShaderNode::Rule("shaderOutput2 = $input;" , |
1103 | QByteArrayList() << "out vec4 shaderOutput2;" )); |
1104 | |
1105 | { |
1106 | // WHEN |
1107 | const auto graph = [=] { |
1108 | auto res = QShaderGraph(); |
1109 | |
1110 | res.addNode(attribute); |
1111 | res.addNode(complexFunction); |
1112 | res.addNode(shaderOutput1); |
1113 | |
1114 | res.addEdge(createEdge(attribute.uuid(), "vertexPosition" , complexFunction.uuid(), "inputVarName" )); |
1115 | res.addEdge(createEdge(complexFunction.uuid(), "out" , shaderOutput1.uuid(), "input" )); |
1116 | |
1117 | return res; |
1118 | }(); |
1119 | |
1120 | auto generator = QShaderGenerator(); |
1121 | generator.graph = graph; |
1122 | generator.format = gl4; |
1123 | |
1124 | const auto code = generator.createShaderCode(); |
1125 | |
1126 | // THEN |
1127 | const auto expected = QByteArrayList() |
1128 | << "#version 400 core" |
1129 | << "" |
1130 | << "in vec4 vertexPosition;" |
1131 | << "out vec4 shaderOutput1;" |
1132 | << "" |
1133 | << "void main()" |
1134 | << "{" |
1135 | << " shaderOutput1 = vertexPosition * 2.0;" |
1136 | << "}" |
1137 | << "" ; |
1138 | QCOMPARE(code, expected.join("\n" )); |
1139 | } |
1140 | |
1141 | { |
1142 | // WHEN |
1143 | const auto graph = [=] { |
1144 | auto res = QShaderGraph(); |
1145 | |
1146 | res.addNode(attribute); |
1147 | res.addNode(complexFunction); |
1148 | res.addNode(shaderOutput1); |
1149 | res.addNode(shaderOutput2); |
1150 | |
1151 | res.addEdge(createEdge(attribute.uuid(), "vertexPosition" , complexFunction.uuid(), "inputVarName" )); |
1152 | res.addEdge(createEdge(complexFunction.uuid(), "out" , shaderOutput1.uuid(), "input" )); |
1153 | res.addEdge(createEdge(complexFunction.uuid(), "out" , shaderOutput2.uuid(), "input" )); |
1154 | |
1155 | return res; |
1156 | }(); |
1157 | |
1158 | auto generator = QShaderGenerator(); |
1159 | generator.graph = graph; |
1160 | generator.format = gl4; |
1161 | |
1162 | const auto code = generator.createShaderCode(); |
1163 | |
1164 | // THEN |
1165 | const auto expected = QByteArrayList() |
1166 | << "#version 400 core" |
1167 | << "" |
1168 | << "in vec4 vertexPosition;" |
1169 | << "out vec4 shaderOutput1;" |
1170 | << "out vec4 shaderOutput2;" |
1171 | << "" |
1172 | << "void main()" |
1173 | << "{" |
1174 | << " vec4 v1 = vertexPosition * 2.0;" |
1175 | << " shaderOutput2 = v1;" |
1176 | << " shaderOutput1 = v1;" |
1177 | << "}" |
1178 | << "" ; |
1179 | QCOMPARE(code, expected.join("\n" )); |
1180 | } |
1181 | |
1182 | { |
1183 | // WHEN |
1184 | const auto graph = [=] { |
1185 | auto res = QShaderGraph(); |
1186 | |
1187 | res.addNode(attribute); |
1188 | res.addNode(complexFunction); |
1189 | res.addNode(complexFunction2); |
1190 | res.addNode(complexFunction3); |
1191 | res.addNode(shaderOutput1); |
1192 | res.addNode(shaderOutput2); |
1193 | |
1194 | res.addEdge(createEdge(attribute.uuid(), "vertexPosition" , complexFunction.uuid(), "inputVarName" )); |
1195 | res.addEdge(createEdge(attribute.uuid(), "vertexPosition" , complexFunction2.uuid(), "inputVarName" )); |
1196 | |
1197 | res.addEdge(createEdge(complexFunction.uuid(), "out" , complexFunction3.uuid(), "a" )); |
1198 | res.addEdge(createEdge(complexFunction2.uuid(), "out" , complexFunction3.uuid(), "b" )); |
1199 | |
1200 | res.addEdge(createEdge(complexFunction3.uuid(), "out" , shaderOutput1.uuid(), "input" )); |
1201 | res.addEdge(createEdge(complexFunction2.uuid(), "out" , shaderOutput2.uuid(), "input" )); |
1202 | |
1203 | return res; |
1204 | }(); |
1205 | |
1206 | auto generator = QShaderGenerator(); |
1207 | generator.graph = graph; |
1208 | generator.format = gl4; |
1209 | |
1210 | const auto code = generator.createShaderCode(); |
1211 | |
1212 | // THEN |
1213 | const auto expected = QByteArrayList() |
1214 | << "#version 400 core" |
1215 | << "" |
1216 | << "in vec4 vertexPosition;" |
1217 | << "out vec4 shaderOutput1;" |
1218 | << "out vec4 shaderOutput2;" |
1219 | << "" |
1220 | << "void main()" |
1221 | << "{" |
1222 | << " vec4 v2 = vertexPosition * 4.0;" |
1223 | << " shaderOutput2 = v2;" |
1224 | << " shaderOutput1 = (vertexPosition * 2.0 + v2);" |
1225 | << "}" |
1226 | << "" ; |
1227 | QCOMPARE(code, expected.join("\n" )); |
1228 | } |
1229 | } |
1230 | } |
1231 | |
1232 | QTEST_MAIN(tst_QShaderGenerator) |
1233 | |
1234 | #include "tst_qshadergenerator.moc" |
1235 | |