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 QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qshadergenerator_p.h"
41
42#include "qshaderlanguage_p.h"
43#include <QRegularExpression>
44
45#include <cctype>
46#include <qshaderprogram_p.h>
47
48QT_BEGIN_NAMESPACE
49namespace Qt3DRender {
50Q_LOGGING_CATEGORY(ShaderGenerator, "ShaderGenerator", QtWarningMsg)
51
52namespace
53{
54 QByteArray toGlsl(QShaderLanguage::StorageQualifier qualifier, const QShaderFormat &format) noexcept
55 {
56 if (format.version().majorVersion() <= 2 && format.api() != QShaderFormat::RHI) {
57 // Note we're assuming fragment shader only here, it'd be different
58 // values for vertex shader, will need to be fixed properly at some
59 // point but isn't necessary yet (this problem already exists in past
60 // commits anyway)
61 switch (qualifier) {
62 case QShaderLanguage::Const:
63 return "const";
64 case QShaderLanguage::Input:
65 if (format.shaderType() == QShaderFormat::Vertex)
66 return "attribute";
67 else
68 return "varying";
69 case QShaderLanguage::Output:
70 return ""; // Although fragment shaders for <=2 only have fixed outputs
71 case QShaderLanguage::Uniform:
72 return "uniform";
73 case QShaderLanguage::BuiltIn:
74 return "//";
75 }
76 } else {
77 switch (qualifier) {
78 case QShaderLanguage::Const:
79 return "const";
80 case QShaderLanguage::Input:
81 return "in";
82 case QShaderLanguage::Output:
83 return "out";
84 case QShaderLanguage::Uniform:
85 return "uniform";
86 case QShaderLanguage::BuiltIn:
87 return "//";
88 }
89 }
90
91 Q_UNREACHABLE();
92 }
93
94 QByteArray toGlsl(QShaderLanguage::VariableType type) noexcept
95 {
96 switch (type) {
97 case QShaderLanguage::Bool:
98 return "bool";
99 case QShaderLanguage::Int:
100 return "int";
101 case QShaderLanguage::Uint:
102 return "uint";
103 case QShaderLanguage::Float:
104 return "float";
105 case QShaderLanguage::Double:
106 return "double";
107 case QShaderLanguage::Vec2:
108 return "vec2";
109 case QShaderLanguage::Vec3:
110 return "vec3";
111 case QShaderLanguage::Vec4:
112 return "vec4";
113 case QShaderLanguage::DVec2:
114 return "dvec2";
115 case QShaderLanguage::DVec3:
116 return "dvec3";
117 case QShaderLanguage::DVec4:
118 return "dvec4";
119 case QShaderLanguage::BVec2:
120 return "bvec2";
121 case QShaderLanguage::BVec3:
122 return "bvec3";
123 case QShaderLanguage::BVec4:
124 return "bvec4";
125 case QShaderLanguage::IVec2:
126 return "ivec2";
127 case QShaderLanguage::IVec3:
128 return "ivec3";
129 case QShaderLanguage::IVec4:
130 return "ivec4";
131 case QShaderLanguage::UVec2:
132 return "uvec2";
133 case QShaderLanguage::UVec3:
134 return "uvec3";
135 case QShaderLanguage::UVec4:
136 return "uvec4";
137 case QShaderLanguage::Mat2:
138 return "mat2";
139 case QShaderLanguage::Mat3:
140 return "mat3";
141 case QShaderLanguage::Mat4:
142 return "mat4";
143 case QShaderLanguage::Mat2x2:
144 return "mat2x2";
145 case QShaderLanguage::Mat2x3:
146 return "mat2x3";
147 case QShaderLanguage::Mat2x4:
148 return "mat2x4";
149 case QShaderLanguage::Mat3x2:
150 return "mat3x2";
151 case QShaderLanguage::Mat3x3:
152 return "mat3x3";
153 case QShaderLanguage::Mat3x4:
154 return "mat3x4";
155 case QShaderLanguage::Mat4x2:
156 return "mat4x2";
157 case QShaderLanguage::Mat4x3:
158 return "mat4x3";
159 case QShaderLanguage::Mat4x4:
160 return "mat4x4";
161 case QShaderLanguage::DMat2:
162 return "dmat2";
163 case QShaderLanguage::DMat3:
164 return "dmat3";
165 case QShaderLanguage::DMat4:
166 return "dmat4";
167 case QShaderLanguage::DMat2x2:
168 return "dmat2x2";
169 case QShaderLanguage::DMat2x3:
170 return "dmat2x3";
171 case QShaderLanguage::DMat2x4:
172 return "dmat2x4";
173 case QShaderLanguage::DMat3x2:
174 return "dmat3x2";
175 case QShaderLanguage::DMat3x3:
176 return "dmat3x3";
177 case QShaderLanguage::DMat3x4:
178 return "dmat3x4";
179 case QShaderLanguage::DMat4x2:
180 return "dmat4x2";
181 case QShaderLanguage::DMat4x3:
182 return "dmat4x3";
183 case QShaderLanguage::DMat4x4:
184 return "dmat4x4";
185 case QShaderLanguage::Sampler1D:
186 return "sampler1D";
187 case QShaderLanguage::Sampler2D:
188 return "sampler2D";
189 case QShaderLanguage::Sampler3D:
190 return "sampler3D";
191 case QShaderLanguage::SamplerCube:
192 return "samplerCube";
193 case QShaderLanguage::Sampler2DRect:
194 return "sampler2DRect";
195 case QShaderLanguage::Sampler2DMs:
196 return "sampler2DMS";
197 case QShaderLanguage::SamplerBuffer:
198 return "samplerBuffer";
199 case QShaderLanguage::Sampler1DArray:
200 return "sampler1DArray";
201 case QShaderLanguage::Sampler2DArray:
202 return "sampler2DArray";
203 case QShaderLanguage::Sampler2DMsArray:
204 return "sampler2DMSArray";
205 case QShaderLanguage::SamplerCubeArray:
206 return "samplerCubeArray";
207 case QShaderLanguage::Sampler1DShadow:
208 return "sampler1DShadow";
209 case QShaderLanguage::Sampler2DShadow:
210 return "sampler2DShadow";
211 case QShaderLanguage::Sampler2DRectShadow:
212 return "sampler2DRectShadow";
213 case QShaderLanguage::Sampler1DArrayShadow:
214 return "sampler1DArrayShadow";
215 case QShaderLanguage::Sampler2DArrayShadow:
216 return "sampler2DArrayShadow";
217 case QShaderLanguage::SamplerCubeShadow:
218 return "samplerCubeShadow";
219 case QShaderLanguage::SamplerCubeArrayShadow:
220 return "samplerCubeArrayShadow";
221 case QShaderLanguage::ISampler1D:
222 return "isampler1D";
223 case QShaderLanguage::ISampler2D:
224 return "isampler2D";
225 case QShaderLanguage::ISampler3D:
226 return "isampler3D";
227 case QShaderLanguage::ISamplerCube:
228 return "isamplerCube";
229 case QShaderLanguage::ISampler2DRect:
230 return "isampler2DRect";
231 case QShaderLanguage::ISampler2DMs:
232 return "isampler2DMS";
233 case QShaderLanguage::ISamplerBuffer:
234 return "isamplerBuffer";
235 case QShaderLanguage::ISampler1DArray:
236 return "isampler1DArray";
237 case QShaderLanguage::ISampler2DArray:
238 return "isampler2DArray";
239 case QShaderLanguage::ISampler2DMsArray:
240 return "isampler2DMSArray";
241 case QShaderLanguage::ISamplerCubeArray:
242 return "isamplerCubeArray";
243 case QShaderLanguage::USampler1D:
244 return "usampler1D";
245 case QShaderLanguage::USampler2D:
246 return "usampler2D";
247 case QShaderLanguage::USampler3D:
248 return "usampler3D";
249 case QShaderLanguage::USamplerCube:
250 return "usamplerCube";
251 case QShaderLanguage::USampler2DRect:
252 return "usampler2DRect";
253 case QShaderLanguage::USampler2DMs:
254 return "usampler2DMS";
255 case QShaderLanguage::USamplerBuffer:
256 return "usamplerBuffer";
257 case QShaderLanguage::USampler1DArray:
258 return "usampler1DArray";
259 case QShaderLanguage::USampler2DArray:
260 return "usampler2DArray";
261 case QShaderLanguage::USampler2DMsArray:
262 return "usampler2DMSArray";
263 case QShaderLanguage::USamplerCubeArray:
264 return "usamplerCubeArray";
265 }
266
267 Q_UNREACHABLE();
268 }
269
270 QByteArray replaceParameters(const QByteArray &original, const QShaderNode &node,
271 const QShaderFormat &format) noexcept
272 {
273 QByteArray result = original;
274
275 const QStringList parameterNames = node.parameterNames();
276 for (const QString &parameterName : parameterNames) {
277 const QByteArray placeholder = QByteArray(QByteArrayLiteral("$") + parameterName.toUtf8());
278 const QVariant parameter = node.parameter(name: parameterName);
279 if (parameter.userType() == qMetaTypeId<QShaderLanguage::StorageQualifier>()) {
280 const QShaderLanguage::StorageQualifier qualifier =
281 qvariant_cast<QShaderLanguage::StorageQualifier>(v: parameter);
282 const QByteArray value = toGlsl(qualifier, format);
283 result.replace(before: placeholder, after: value);
284 } else if (parameter.userType() == qMetaTypeId<QShaderLanguage::VariableType>()) {
285 const QShaderLanguage::VariableType type =
286 qvariant_cast<QShaderLanguage::VariableType>(v: parameter);
287 const QByteArray value = toGlsl(type);
288 result.replace(before: placeholder, after: value);
289 } else {
290 const QByteArray value = parameter.toString().toUtf8();
291 result.replace(before: placeholder, after: value);
292 }
293 }
294
295 return result;
296 }
297
298 struct ShaderGenerationState
299 {
300 ShaderGenerationState(const QShaderGenerator &gen,
301 QVector<QShaderGraph::Statement> statements)
302 : generator { gen }, statements { statements }
303 {
304
305 }
306
307 const QShaderGenerator &generator;
308 QVector<QShaderGraph::Statement> statements;
309 QByteArrayList code;
310 };
311
312 class GLSL45HeaderWriter
313 {
314 public:
315 void writeHeader(ShaderGenerationState &state)
316 {
317 const auto &format = state.generator.format;
318 auto &code = state.code;
319 for (const QShaderGraph::Statement &statement : state.statements) {
320 const QShaderNode &node = statement.node;
321 const QByteArrayList &headerSnippets = node.rule(format).headerSnippets;
322 for (const QByteArray &snippet : headerSnippets) {
323 auto replacedSnippet = replaceParameters(original: snippet, node, format).trimmed();
324
325 if (replacedSnippet.startsWith(QByteArrayLiteral("add-input"))) {
326 onInOut(code, snippet: replacedSnippet);
327 } else if (replacedSnippet.startsWith(QByteArrayLiteral("add-uniform"))) {
328 onNamedUniform(ubo, snippet: replacedSnippet);
329 } else if (replacedSnippet.startsWith(QByteArrayLiteral("add-sampler"))) {
330 onNamedSampler(code, snippet: replacedSnippet);
331 } else if (replacedSnippet.startsWith(QByteArrayLiteral("#pragma include "))) {
332 onInclude(code, snippet: replacedSnippet);
333 } else {
334 code << replacedSnippet;
335 }
336 }
337 }
338
339 if (!ubo.isEmpty()) {
340 code << QByteArrayLiteral("layout(std140, binding = ")
341 + QByteArray::number(currentBinding++)
342 + QByteArrayLiteral(") uniform qt3d_shadergraph_generated_uniforms {");
343 code << ubo;
344 code << "};";
345 }
346 }
347
348 private:
349 void onInOut(QByteArrayList &code, const QByteArray &snippet) noexcept
350 {
351 const auto split = snippet.split(sep: ' ');
352 if (split.size() < 4) {
353 qDebug() << "Invalid header snippet: " << snippet;
354 return;
355 }
356 const auto &qualifier = split[1];
357 const auto &type = split[2];
358 const auto &name = split[3];
359
360 if (qualifier == QByteArrayLiteral("in")) {
361 code << (QByteArrayLiteral("layout(location = ")
362 + QByteArray::number(currentInputLocation++) + QByteArrayLiteral(") in ")
363 + type + ' ' + name + QByteArrayLiteral(";"));
364 } else if (qualifier == QByteArrayLiteral("out")) {
365 code << (QByteArrayLiteral("layout(location = ")
366 + QByteArray::number(currentOutputLocation++) + QByteArrayLiteral(") out ")
367 + type + ' ' + name + QByteArrayLiteral(";"));
368 } else if (qualifier == QByteArrayLiteral("uniform")) {
369 ubo << (type + ' ' + name + ';');
370 }
371 }
372
373 void onNamedUniform(QByteArrayList &ubo, const QByteArray &snippet) noexcept
374 {
375 const auto split = snippet.split(sep: ' ');
376 if (split.size() < 3) {
377 qDebug() << "Invalid header snippet: " << snippet;
378 return;
379 }
380
381 const auto &type = split[1];
382 const auto &name = split[2];
383
384 ubo << (type + ' ' + name + ';');
385 }
386
387 void onNamedSampler(QByteArrayList &code, const QByteArray &snippet) noexcept
388 {
389 const auto split = snippet.split(sep: ' ');
390 if (split.size() < 3) {
391 qDebug() << "Invalid header snippet: " << snippet;
392 return;
393 }
394 const auto binding = QByteArray::number(currentBinding++);
395 const auto &type = split[1];
396 const auto &name = split[2];
397
398 code << (QByteArrayLiteral("layout(binding = ") + binding + QByteArrayLiteral(") uniform ")
399 + type + ' ' + name + QByteArrayLiteral(";"));
400 }
401
402 void onInclude(QByteArrayList &code, const QByteArray &snippet) noexcept
403 {
404 const auto filepath = QString::fromUtf8(str: snippet.mid(index: strlen(s: "#pragma include ")));
405 QString deincluded = QString::fromUtf8(str: QShaderProgramPrivate::deincludify(filePath: filepath));
406
407 // This lambda will replace all occurrences of a string (e.g. "binding = auto") by another,
408 // with the incremented int passed as argument (e.g. "binding = 1", "binding = 2" ...)
409 const auto replaceAndIncrement = [&deincluded](const QRegularExpression &regexp,
410 int &variable,
411 const QString &replacement) noexcept {
412 int matchStart = 0;
413 do {
414 matchStart = deincluded.indexOf(re: regexp, from: matchStart);
415 if (matchStart != -1) {
416 const auto match = regexp.match(subjectRef: deincluded.midRef(position: matchStart));
417 const auto length = match.capturedLength(nth: 0);
418
419 deincluded.replace(i: matchStart, len: length, after: replacement.arg(a: variable++));
420 }
421 } while (matchStart != -1);
422 };
423
424 // 1. Handle uniforms
425 {
426 thread_local const QRegularExpression bindings(
427 QStringLiteral("binding\\s?+=\\s?+auto"));
428
429 replaceAndIncrement(bindings, currentBinding, QStringLiteral("binding = %1"));
430 }
431
432 // 2. Handle inputs
433 {
434 thread_local const QRegularExpression inLocations(
435 QStringLiteral("location\\s?+=\\s?+auto\\s?+\\)\\s?+in\\s+"));
436
437 replaceAndIncrement(inLocations, currentInputLocation,
438 QStringLiteral("location = %1) in "));
439 }
440
441 // 3. Handle outputs
442 {
443 thread_local const QRegularExpression outLocations(
444 QStringLiteral("location\\s?+=\\s?+auto\\s?+\\)\\s?+out\\s+"));
445
446 replaceAndIncrement(outLocations, currentOutputLocation,
447 QStringLiteral("location = %1) out "));
448 }
449
450 code << deincluded.toUtf8();
451 }
452
453 int currentInputLocation { 0 };
454 int currentOutputLocation { 0 };
455 int currentBinding { 2 };
456 QByteArrayList ubo;
457 };
458
459 struct GLSLHeaderWriter
460 {
461 void writeHeader(ShaderGenerationState &state)
462 {
463 const auto &format = state.generator.format;
464 auto &code = state.code;
465 for (const QShaderGraph::Statement &statement : state.statements) {
466 const QShaderNode &node = statement.node;
467 const QByteArrayList &headerSnippets = node.rule(format).headerSnippets;
468 for (const QByteArray &snippet : headerSnippets) {
469 code << replaceParameters(original: snippet, node, format);
470 }
471 }
472 }
473 };
474
475 QByteArray versionString(const QShaderFormat &format) noexcept
476 {
477 if (!format.isValid())
478 return {};
479
480 switch (format.api()) {
481 case QShaderFormat::RHI: {
482 return QByteArrayLiteral("#version 450");
483 }
484 case QShaderFormat::VulkanFlavoredGLSL: {
485 const int major = format.version().majorVersion();
486 const int minor = format.version().minorVersion();
487 return (QByteArrayLiteral("#version ") + QByteArray::number(major * 100 + minor * 10));
488 }
489 default: {
490 const bool isGLES = format.api() == QShaderFormat::OpenGLES;
491 const int major = format.version().majorVersion();
492 const int minor = format.version().minorVersion();
493
494 const int version = major == 2 && isGLES ? 100
495 : major == 3 && isGLES ? 300
496 : major == 2 ? 100 + 10 * (minor + 1)
497 : major == 3 && minor <= 2 ? 100 + 10 * (minor + 3)
498 : major * 100 + minor * 10;
499
500 const QByteArray profile =
501 isGLES && version > 100 ? QByteArrayLiteral(" es")
502 : version >= 150 && format.api() == QShaderFormat::OpenGLCoreProfile ? QByteArrayLiteral(" core")
503 : version >= 150 && format.api() == QShaderFormat::OpenGLCompatibilityProfile ? QByteArrayLiteral(" compatibility")
504 : QByteArray();
505
506 return (QByteArrayLiteral("#version ") + QByteArray::number(version) + profile);
507 }
508 }
509 }
510
511 QByteArrayList layerDefines(const QStringList &enabledLayers) noexcept
512 {
513 QByteArrayList defines;
514 const QString defineTemplate = QStringLiteral("#define LAYER_%1");
515
516 for (const QString &layer : enabledLayers)
517 defines << defineTemplate.arg(a: layer).toUtf8();
518
519 return defines;
520 }
521}
522
523QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers) const
524{
525 const QVector<QShaderNode> nodes = graph.nodes();
526 const auto statements = graph.createStatements(enabledLayers);
527 ShaderGenerationState state(*this, statements);
528 QByteArrayList &code = state.code;
529
530 code << versionString(format);
531 code << QByteArray();
532 code << layerDefines(enabledLayers);
533
534 if (format.api() == QShaderFormat::VulkanFlavoredGLSL || format.api() == QShaderFormat::RHI) {
535 GLSL45HeaderWriter builder;
536 builder.writeHeader(state);
537 } else {
538 GLSLHeaderWriter builder;
539 builder.writeHeader(state);
540 }
541
542 code << QByteArray();
543 code << QByteArrayLiteral("void main()");
544 code << QByteArrayLiteral("{");
545
546 const QRegularExpression temporaryVariableToAssignmentRegExp(
547 QStringLiteral("([^;]*\\s+(v\\d+))\\s*=\\s*([^;]*);"));
548 const QRegularExpression temporaryVariableInAssignmentRegExp(QStringLiteral("\\W*(v\\d+)\\W*"));
549 const QRegularExpression statementRegExp(QStringLiteral("\\s*(\\w+)\\s*=\\s*([^;]*);"));
550
551 struct Variable;
552
553 struct Assignment
554 {
555 QString expression;
556 QVector<Variable *> referencedVariables;
557 };
558
559 struct Variable
560 {
561 enum Type { GlobalInput, TemporaryAssignment, Output };
562
563 QString name;
564 QString declaration;
565 int referenceCount = 0;
566 Assignment assignment;
567 Type type = TemporaryAssignment;
568 bool substituted = false;
569
570 static void substitute(Variable *v)
571 {
572 if (v->substituted)
573 return;
574
575 qCDebug(ShaderGenerator)
576 << "Begin Substituting " << v->name << " = " << v->assignment.expression;
577 for (Variable *ref : qAsConst(t&: v->assignment.referencedVariables)) {
578 // Recursively substitute
579 Variable::substitute(v: ref);
580
581 // Replace all variables referenced only once in the assignment
582 // by their actual expression
583 if (ref->referenceCount == 1 || ref->type == Variable::GlobalInput) {
584 const QRegularExpression r(QStringLiteral("(.*\\b)(%1)(\\b.*)").arg(a: ref->name));
585 if (v->assignment.referencedVariables.size() == 1)
586 v->assignment.expression.replace(
587 re: r, QStringLiteral("\\1%2\\3").arg(a: ref->assignment.expression));
588 else
589 v->assignment.expression.replace(
590 re: r, QStringLiteral("(\\1%2\\3)").arg(a: ref->assignment.expression));
591 }
592 }
593 qCDebug(ShaderGenerator)
594 << "Done Substituting " << v->name << " = " << v->assignment.expression;
595 v->substituted = true;
596 }
597 };
598
599 struct LineContent
600 {
601 QByteArray rawContent;
602 Variable *var = nullptr;
603 };
604
605 // Table to store temporary variables that should be replaced:
606 // - If variable references a a global variables
607 // -> we will use the global variable directly
608 // - If variable references a function results
609 // -> will be kept only if variable is referenced more than once.
610 // This avoids having vec3 v56 = vertexPosition; when we could
611 // just use vertexPosition directly.
612 // The added benefit is when having arrays, we don't try to create
613 // mat4 v38 = skinningPalelette[100] which would be invalid
614 QVector<Variable> temporaryVariables;
615 // Reserve more than enough space to ensure no reallocation will take place
616 temporaryVariables.reserve(asize: nodes.size() * 8);
617
618 QVector<LineContent> lines;
619
620 auto createVariable = [&] () -> Variable * {
621 Q_ASSERT(temporaryVariables.capacity() > 0);
622 temporaryVariables.resize(asize: temporaryVariables.size() + 1);
623 return &temporaryVariables.last();
624 };
625
626 auto findVariable = [&] (const QString &name) -> Variable * {
627 const auto end = temporaryVariables.end();
628 auto it = std::find_if(first: temporaryVariables.begin(), last: end,
629 pred: [=] (const Variable &a) { return a.name == name; });
630 if (it != end)
631 return &(*it);
632 return nullptr;
633 };
634
635 auto gatherTemporaryVariablesFromAssignment = [&](Variable *v,
636 const QString &assignmentContent) {
637 QRegularExpressionMatchIterator subMatchIt =
638 temporaryVariableInAssignmentRegExp.globalMatch(subject: assignmentContent);
639 while (subMatchIt.hasNext()) {
640 const QRegularExpressionMatch subMatch = subMatchIt.next();
641 const QString variableName = subMatch.captured(nth: 1);
642
643 // Variable we care about should already exists -> an expression cannot reference a
644 // variable that hasn't been defined
645 Variable *u = findVariable(variableName);
646 Q_ASSERT(u);
647
648 // Increase reference count for u
649 ++u->referenceCount;
650 // Insert u as reference for variable v
651 v->assignment.referencedVariables.push_back(t: u);
652 }
653 };
654
655 for (const QShaderGraph::Statement &statement : statements) {
656 const QShaderNode node = statement.node;
657 QByteArray line = node.rule(format).substitution;
658 const QVector<QShaderNodePort> ports = node.ports();
659
660 struct VariableReplacement
661 {
662 QByteArray placeholder;
663 QByteArray variable;
664 };
665
666 QVector<VariableReplacement> variableReplacements;
667
668 // Generate temporary variable names vN
669 for (const QShaderNodePort &port : ports) {
670 const QString portName = port.name;
671 const QShaderNodePort::Direction portDirection = port.direction;
672 const bool isInput = port.direction == QShaderNodePort::Input;
673
674 const int portIndex = statement.portIndex(direction: portDirection, portName);
675
676 Q_ASSERT(portIndex >= 0);
677
678 const int variableIndex =
679 isInput ? statement.inputs.at(i: portIndex) : statement.outputs.at(i: portIndex);
680 if (variableIndex < 0)
681 continue;
682
683 VariableReplacement replacement;
684 replacement.placeholder = QByteArrayLiteral("$") + portName.toUtf8();
685 replacement.variable = QByteArrayLiteral("v") + QByteArray::number(variableIndex);
686
687 variableReplacements.append(t: std::move(replacement));
688 }
689
690 int begin = 0;
691 while ((begin = line.indexOf(c: '$', from: begin)) != -1) {
692 int end = begin + 1;
693 char endChar = line.at(i: end);
694 const int size = line.size();
695 while (end < size && (std::isalnum(endChar) || endChar == '_')) {
696 ++end;
697 endChar = line.at(i: end);
698 }
699
700 const int placeholderLength = end - begin;
701
702 const QByteArray variableName = line.mid(index: begin, len: placeholderLength);
703 const auto replacementIt =
704 std::find_if(first: variableReplacements.cbegin(), last: variableReplacements.cend(),
705 pred: [&variableName](const VariableReplacement &replacement) {
706 return variableName == replacement.placeholder;
707 });
708
709 if (replacementIt != variableReplacements.cend()) {
710 line.replace(index: begin, len: placeholderLength, s: replacementIt->variable);
711 begin += replacementIt->variable.length();
712 } else {
713 begin = end;
714 }
715 }
716
717 // Substitute variable names by generated vN variable names
718 const QByteArray substitutionedLine = replaceParameters(original: line, node, format);
719
720 QRegularExpressionMatchIterator matches;
721
722 switch (node.type()) {
723 case QShaderNode::Input:
724 case QShaderNode::Output:
725 matches = statementRegExp.globalMatch(subject: QString::fromUtf8(str: substitutionedLine));
726 break;
727 case QShaderNode::Function:
728 matches = temporaryVariableToAssignmentRegExp.globalMatch(
729 subject: QString::fromUtf8(str: substitutionedLine));
730 break;
731 case QShaderNode::Invalid:
732 break;
733 }
734
735 while (matches.hasNext()) {
736 QRegularExpressionMatch match = matches.next();
737
738 Variable *v = nullptr;
739
740 switch (node.type()) {
741 // Record name of temporary variable that possibly references a global input
742 // We will replace the temporary variables by the matching global variables later
743 case QShaderNode::Input: {
744 const QString localVariable = match.captured(nth: 1);
745 const QString globalVariable = match.captured(nth: 2);
746
747 v = createVariable();
748 v->name = localVariable;
749 v->type = Variable::GlobalInput;
750
751 Assignment assignment;
752 assignment.expression = globalVariable;
753 v->assignment = assignment;
754 break;
755 }
756
757 case QShaderNode::Function: {
758 const QString localVariableDeclaration = match.captured(nth: 1);
759 const QString localVariableName = match.captured(nth: 2);
760 const QString assignmentContent = match.captured(nth: 3);
761
762 // Add new variable -> it cannot exist already
763 v = createVariable();
764 v->name = localVariableName;
765 v->declaration = localVariableDeclaration;
766 v->assignment.expression = assignmentContent;
767
768 // Find variables that may be referenced in the assignment
769 gatherTemporaryVariablesFromAssignment(v, assignmentContent);
770 break;
771 }
772
773 case QShaderNode::Output: {
774 const QString outputDeclaration = match.captured(nth: 1);
775 const QString assignmentContent = match.captured(nth: 2);
776
777 v = createVariable();
778 v->name = outputDeclaration;
779 v->declaration = outputDeclaration;
780 v->type = Variable::Output;
781
782 Assignment assignment;
783 assignment.expression = assignmentContent;
784 v->assignment = assignment;
785
786 // Find variables that may be referenced in the assignment
787 gatherTemporaryVariablesFromAssignment(v, assignmentContent);
788 break;
789 }
790 case QShaderNode::Invalid:
791 break;
792 }
793
794 LineContent lineContent;
795 lineContent.rawContent = QByteArray(QByteArrayLiteral(" ") + substitutionedLine);
796 lineContent.var = v;
797 lines << lineContent;
798 }
799 }
800
801 // Go through all lines
802 // Perform substitution of line with temporary variables substitution
803 for (LineContent &lineContent : lines) {
804 Variable *v = lineContent.var;
805 qCDebug(ShaderGenerator) << lineContent.rawContent;
806 if (v != nullptr) {
807 Variable::substitute(v);
808
809 qCDebug(ShaderGenerator)
810 << "Line " << lineContent.rawContent << "is assigned to temporary" << v->name;
811
812 // Check number of occurrences a temporary variable is referenced
813 if (v->referenceCount == 1 || v->type == Variable::GlobalInput) {
814 // If it is referenced only once, no point in creating a temporary
815 // Clear content for current line
816 lineContent.rawContent.clear();
817 // We assume expression that were referencing vN will have vN properly substituted
818 } else {
819 lineContent.rawContent = QStringLiteral(" %1 = %2;")
820 .arg(a: v->declaration)
821 .arg(a: v->assignment.expression)
822 .toUtf8();
823 }
824
825 qCDebug(ShaderGenerator) << "Updated Line is " << lineContent.rawContent;
826 }
827 }
828
829 // Go throug all lines and insert content
830 for (const LineContent &lineContent : qAsConst(t&: lines)) {
831 if (!lineContent.rawContent.isEmpty()) {
832 code << lineContent.rawContent;
833 }
834 }
835
836 code << QByteArrayLiteral("}");
837 code << QByteArray();
838
839 return code.join(sep: '\n');
840}
841
842}
843QT_END_NAMESPACE
844

source code of qt3d/src/render/shadergraph/qshadergenerator.cpp