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