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