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
47QT_BEGIN_NAMESPACE
48
49Q_LOGGING_CATEGORY(ShaderGenerator, "ShaderGenerator", QtWarningMsg)
50
51namespace
52{
53 QByteArray toGlsl(QShaderLanguage::StorageQualifier qualifier, const QShaderFormat &format)
54 {
55 if (format.version().majorVersion() <= 2) {
56 // Note we're assuming fragment shader only here, it'd be different
57 // values for vertex shader, will need to be fixed properly at some
58 // point but isn't necessary yet (this problem already exists in past
59 // commits anyway)
60 switch (qualifier) {
61 case QShaderLanguage::Const:
62 return "const";
63 case QShaderLanguage::Input:
64 if (format.shaderType() == QShaderFormat::Vertex)
65 return "attribute";
66 else
67 return "varying";
68 case QShaderLanguage::Output:
69 return ""; // Although fragment shaders for <=2 only have fixed outputs
70 case QShaderLanguage::Uniform:
71 return "uniform";
72 case QShaderLanguage::BuiltIn:
73 return "//";
74 }
75 } else {
76 switch (qualifier) {
77 case QShaderLanguage::Const:
78 return "const";
79 case QShaderLanguage::Input:
80 return "in";
81 case QShaderLanguage::Output:
82 return "out";
83 case QShaderLanguage::Uniform:
84 return "uniform";
85 case QShaderLanguage::BuiltIn:
86 return "//";
87 }
88 }
89
90 Q_UNREACHABLE();
91 }
92
93 QByteArray toGlsl(QShaderLanguage::VariableType type)
94 {
95 switch (type) {
96 case QShaderLanguage::Bool:
97 return "bool";
98 case QShaderLanguage::Int:
99 return "int";
100 case QShaderLanguage::Uint:
101 return "uint";
102 case QShaderLanguage::Float:
103 return "float";
104 case QShaderLanguage::Double:
105 return "double";
106 case QShaderLanguage::Vec2:
107 return "vec2";
108 case QShaderLanguage::Vec3:
109 return "vec3";
110 case QShaderLanguage::Vec4:
111 return "vec4";
112 case QShaderLanguage::DVec2:
113 return "dvec2";
114 case QShaderLanguage::DVec3:
115 return "dvec3";
116 case QShaderLanguage::DVec4:
117 return "dvec4";
118 case QShaderLanguage::BVec2:
119 return "bvec2";
120 case QShaderLanguage::BVec3:
121 return "bvec3";
122 case QShaderLanguage::BVec4:
123 return "bvec4";
124 case QShaderLanguage::IVec2:
125 return "ivec2";
126 case QShaderLanguage::IVec3:
127 return "ivec3";
128 case QShaderLanguage::IVec4:
129 return "ivec4";
130 case QShaderLanguage::UVec2:
131 return "uvec2";
132 case QShaderLanguage::UVec3:
133 return "uvec3";
134 case QShaderLanguage::UVec4:
135 return "uvec4";
136 case QShaderLanguage::Mat2:
137 return "mat2";
138 case QShaderLanguage::Mat3:
139 return "mat3";
140 case QShaderLanguage::Mat4:
141 return "mat4";
142 case QShaderLanguage::Mat2x2:
143 return "mat2x2";
144 case QShaderLanguage::Mat2x3:
145 return "mat2x3";
146 case QShaderLanguage::Mat2x4:
147 return "mat2x4";
148 case QShaderLanguage::Mat3x2:
149 return "mat3x2";
150 case QShaderLanguage::Mat3x3:
151 return "mat3x3";
152 case QShaderLanguage::Mat3x4:
153 return "mat3x4";
154 case QShaderLanguage::Mat4x2:
155 return "mat4x2";
156 case QShaderLanguage::Mat4x3:
157 return "mat4x3";
158 case QShaderLanguage::Mat4x4:
159 return "mat4x4";
160 case QShaderLanguage::DMat2:
161 return "dmat2";
162 case QShaderLanguage::DMat3:
163 return "dmat3";
164 case QShaderLanguage::DMat4:
165 return "dmat4";
166 case QShaderLanguage::DMat2x2:
167 return "dmat2x2";
168 case QShaderLanguage::DMat2x3:
169 return "dmat2x3";
170 case QShaderLanguage::DMat2x4:
171 return "dmat2x4";
172 case QShaderLanguage::DMat3x2:
173 return "dmat3x2";
174 case QShaderLanguage::DMat3x3:
175 return "dmat3x3";
176 case QShaderLanguage::DMat3x4:
177 return "dmat3x4";
178 case QShaderLanguage::DMat4x2:
179 return "dmat4x2";
180 case QShaderLanguage::DMat4x3:
181 return "dmat4x3";
182 case QShaderLanguage::DMat4x4:
183 return "dmat4x4";
184 case QShaderLanguage::Sampler1D:
185 return "sampler1D";
186 case QShaderLanguage::Sampler2D:
187 return "sampler2D";
188 case QShaderLanguage::Sampler3D:
189 return "sampler3D";
190 case QShaderLanguage::SamplerCube:
191 return "samplerCube";
192 case QShaderLanguage::Sampler2DRect:
193 return "sampler2DRect";
194 case QShaderLanguage::Sampler2DMs:
195 return "sampler2DMS";
196 case QShaderLanguage::SamplerBuffer:
197 return "samplerBuffer";
198 case QShaderLanguage::Sampler1DArray:
199 return "sampler1DArray";
200 case QShaderLanguage::Sampler2DArray:
201 return "sampler2DArray";
202 case QShaderLanguage::Sampler2DMsArray:
203 return "sampler2DMSArray";
204 case QShaderLanguage::SamplerCubeArray:
205 return "samplerCubeArray";
206 case QShaderLanguage::Sampler1DShadow:
207 return "sampler1DShadow";
208 case QShaderLanguage::Sampler2DShadow:
209 return "sampler2DShadow";
210 case QShaderLanguage::Sampler2DRectShadow:
211 return "sampler2DRectShadow";
212 case QShaderLanguage::Sampler1DArrayShadow:
213 return "sampler1DArrayShadow";
214 case QShaderLanguage::Sampler2DArrayShadow:
215 return "sample2DArrayShadow";
216 case QShaderLanguage::SamplerCubeShadow:
217 return "samplerCubeShadow";
218 case QShaderLanguage::SamplerCubeArrayShadow:
219 return "samplerCubeArrayShadow";
220 case QShaderLanguage::ISampler1D:
221 return "isampler1D";
222 case QShaderLanguage::ISampler2D:
223 return "isampler2D";
224 case QShaderLanguage::ISampler3D:
225 return "isampler3D";
226 case QShaderLanguage::ISamplerCube:
227 return "isamplerCube";
228 case QShaderLanguage::ISampler2DRect:
229 return "isampler2DRect";
230 case QShaderLanguage::ISampler2DMs:
231 return "isampler2DMS";
232 case QShaderLanguage::ISamplerBuffer:
233 return "isamplerBuffer";
234 case QShaderLanguage::ISampler1DArray:
235 return "isampler1DArray";
236 case QShaderLanguage::ISampler2DArray:
237 return "isampler2DArray";
238 case QShaderLanguage::ISampler2DMsArray:
239 return "isampler2DMSArray";
240 case QShaderLanguage::ISamplerCubeArray:
241 return "isamplerCubeArray";
242 case QShaderLanguage::USampler1D:
243 return "usampler1D";
244 case QShaderLanguage::USampler2D:
245 return "usampler2D";
246 case QShaderLanguage::USampler3D:
247 return "usampler3D";
248 case QShaderLanguage::USamplerCube:
249 return "usamplerCube";
250 case QShaderLanguage::USampler2DRect:
251 return "usampler2DRect";
252 case QShaderLanguage::USampler2DMs:
253 return "usampler2DMS";
254 case QShaderLanguage::USamplerBuffer:
255 return "usamplerBuffer";
256 case QShaderLanguage::USampler1DArray:
257 return "usampler1DArray";
258 case QShaderLanguage::USampler2DArray:
259 return "usampler2DArray";
260 case QShaderLanguage::USampler2DMsArray:
261 return "usampler2DMSArray";
262 case QShaderLanguage::USamplerCubeArray:
263 return "usamplerCubeArray";
264 }
265
266 Q_UNREACHABLE();
267 }
268
269 QByteArray replaceParameters(const QByteArray &original, const QShaderNode &node, const QShaderFormat &format)
270 {
271 QByteArray result = original;
272
273 const QStringList parameterNames = node.parameterNames();
274 for (const QString &parameterName : parameterNames) {
275 const QByteArray placeholder = QByteArray(QByteArrayLiteral("$") + parameterName.toUtf8());
276 const QVariant parameter = node.parameter(parameterName);
277 if (parameter.userType() == qMetaTypeId<QShaderLanguage::StorageQualifier>()) {
278 const QShaderLanguage::StorageQualifier qualifier = qvariant_cast<QShaderLanguage::StorageQualifier>(parameter);
279 const QByteArray value = toGlsl(qualifier, format);
280 result.replace(placeholder, value);
281 } else if (parameter.userType() == qMetaTypeId<QShaderLanguage::VariableType>()) {
282 const QShaderLanguage::VariableType type = qvariant_cast<QShaderLanguage::VariableType>(parameter);
283 const QByteArray value = toGlsl(type);
284 result.replace(placeholder, value);
285 } else {
286 const QByteArray value = parameter.toString().toUtf8();
287 result.replace(placeholder, value);
288 }
289 }
290
291 return result;
292 }
293}
294
295QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers) const
296{
297 auto code = QByteArrayList();
298
299 if (format.isValid()) {
300 const bool isGLES = format.api() == QShaderFormat::OpenGLES;
301 const int major = format.version().majorVersion();
302 const int minor = format.version().minorVersion();
303
304 const int version = major == 2 && isGLES ? 100
305 : major == 3 && isGLES ? 300
306 : major == 2 ? 100 + 10 * (minor + 1)
307 : major == 3 && minor <= 2 ? 100 + 10 * (minor + 3)
308 : major * 100 + minor * 10;
309
310 const QByteArray profile = isGLES && version > 100 ? QByteArrayLiteral(" es")
311 : version >= 150 && format.api() == QShaderFormat::OpenGLCoreProfile ? QByteArrayLiteral(" core")
312 : version >= 150 && format.api() == QShaderFormat::OpenGLCompatibilityProfile ? QByteArrayLiteral(" compatibility")
313 : QByteArray();
314
315 code << (QByteArrayLiteral("#version ") + QByteArray::number(version) + profile);
316 code << QByteArray();
317 }
318
319 const auto intersectsEnabledLayers = [enabledLayers] (const QStringList &layers) {
320 return layers.isEmpty()
321 || std::any_of(layers.cbegin(), layers.cend(),
322 [enabledLayers] (const QString &s) { return enabledLayers.contains(s); });
323 };
324
325 QList<QString> globalInputVariables;
326 const QRegularExpression globalInputExtractRegExp(QStringLiteral("^.*\\s+(\\w+).*;$"));
327
328 const QList<QShaderNode> nodes = graph.nodes();
329 for (const QShaderNode &node : nodes) {
330 if (intersectsEnabledLayers(node.layers())) {
331 const QByteArrayList headerSnippets = node.rule(format).headerSnippets;
332 for (const QByteArray &snippet : headerSnippets) {
333 code << replaceParameters(snippet, node, format);
334
335 // If node is an input, record the variable name into the globalInputVariables list
336 if (node.type() == QShaderNode::Input) {
337 const QRegularExpressionMatch match = globalInputExtractRegExp.match(QString::fromUtf8(code.last()));
338 if (match.hasMatch())
339 globalInputVariables.push_back(match.captured(1));
340 }
341 }
342 }
343 }
344
345 code << QByteArray();
346 code << QByteArrayLiteral("void main()");
347 code << QByteArrayLiteral("{");
348
349 const QRegularExpression temporaryVariableToAssignmentRegExp(QStringLiteral("([^;]*\\s+(v\\d+))\\s*=\\s*([^;]*);"));
350 const QRegularExpression temporaryVariableInAssignmentRegExp(QStringLiteral("\\W*(v\\d+)\\W*"));
351 const QRegularExpression statementRegExp(QStringLiteral("\\s*(\\w+)\\s*=\\s*([^;]*);"));
352
353 struct Variable;
354
355 struct Assignment
356 {
357 QString expression;
358 QList<Variable *> referencedVariables;
359 };
360
361 struct Variable
362 {
363 enum Type {
364 GlobalInput,
365 TemporaryAssignment,
366 Output
367 };
368
369 QString name;
370 QString declaration;
371 int referenceCount = 0;
372 Assignment assignment;
373 Type type = TemporaryAssignment;
374 bool substituted = false;
375
376 static void substitute(Variable *v)
377 {
378 if (v->substituted)
379 return;
380
381 qCDebug(ShaderGenerator) << "Begin Substituting " << v->name << " = " << v->assignment.expression;
382 for (Variable *ref : qAsConst(v->assignment.referencedVariables)) {
383 // Recursively substitute
384 Variable::substitute(ref);
385
386 // Replace all variables referenced only once in the assignment
387 // by their actual expression
388 if (ref->referenceCount == 1 || ref->type == Variable::GlobalInput) {
389 const QRegularExpression r(QStringLiteral("(.*\\b)(%1)(\\b.*)").arg(ref->name));
390 if (v->assignment.referencedVariables.size() == 1)
391 v->assignment.expression.replace(r,
392 QStringLiteral("\\1%2\\3").arg(ref->assignment.expression));
393 else
394 v->assignment.expression.replace(r,
395 QStringLiteral("(\\1%2\\3)").arg(ref->assignment.expression));
396 }
397 }
398 qCDebug(ShaderGenerator) << "Done Substituting " << v->name << " = " << v->assignment.expression;
399 v->substituted = true;
400 }
401 };
402
403 struct LineContent
404 {
405 QByteArray rawContent;
406 Variable *var = nullptr;
407 };
408
409 // Table to store temporary variables that should be replaced:
410 // - If variable references a a global variables
411 // -> we will use the global variable directly
412 // - If variable references a function results
413 // -> will be kept only if variable is referenced more than once.
414 // This avoids having vec3 v56 = vertexPosition; when we could
415 // just use vertexPosition directly.
416 // The added benefit is when having arrays, we don't try to create
417 // mat4 v38 = skinningPalelette[100] which would be invalid
418 QList<Variable> temporaryVariables;
419 // Reserve more than enough space to ensure no reallocation will take place
420 temporaryVariables.reserve(nodes.size() * 8);
421
422 QList<LineContent> lines;
423
424 auto createVariable = [&] () -> Variable * {
425 Q_ASSERT(temporaryVariables.capacity() > 0);
426 temporaryVariables.resize(temporaryVariables.size() + 1);
427 return &temporaryVariables.last();
428 };
429
430 auto findVariable = [&] (const QString &name) -> Variable * {
431 const auto end = temporaryVariables.end();
432 auto it = std::find_if(temporaryVariables.begin(), end,
433 [=] (const Variable &a) { return a.name == name; });
434 if (it != end)
435 return &(*it);
436 return nullptr;
437 };
438
439 auto gatherTemporaryVariablesFromAssignment = [&] (Variable *v, const QString &assignmentContent) {
440 QRegularExpressionMatchIterator subMatchIt = temporaryVariableInAssignmentRegExp.globalMatch(assignmentContent);
441 while (subMatchIt.hasNext()) {
442 const QRegularExpressionMatch subMatch = subMatchIt.next();
443 const QString variableName = subMatch.captured(1);
444
445 // Variable we care about should already exists -> an expression cannot reference a variable that hasn't been defined
446 Variable *u = findVariable(variableName);
447 Q_ASSERT(u);
448
449 // Increase reference count for u
450 ++u->referenceCount;
451 // Insert u as reference for variable v
452 v->assignment.referencedVariables.push_back(u);
453 }
454 };
455
456 for (const QShaderGraph::Statement &statement : graph.createStatements(enabledLayers)) {
457 const QShaderNode node = statement.node;
458 QByteArray line = node.rule(format).substitution;
459 const QList<QShaderNodePort> ports = node.ports();
460
461 struct VariableReplacement {
462 QByteArray placeholder;
463 QByteArray variable;
464 };
465
466 QList<VariableReplacement> variableReplacements;
467
468 // Generate temporary variable names vN
469 for (const QShaderNodePort &port : ports) {
470 const QString portName = port.name;
471 const QShaderNodePort::Direction portDirection = port.direction;
472 const bool isInput = port.direction == QShaderNodePort::Input;
473
474 const int portIndex = statement.portIndex(portDirection, portName);
475
476 Q_ASSERT(portIndex >= 0);
477
478 const int variableIndex = isInput ? statement.inputs.at(portIndex)
479 : statement.outputs.at(portIndex);
480 if (variableIndex < 0)
481 continue;
482
483 VariableReplacement replacement;
484 replacement.placeholder = QByteArrayLiteral("$") + portName.toUtf8();
485 replacement.variable = QByteArrayLiteral("v") + QByteArray::number(variableIndex);
486
487 variableReplacements.append(std::move(replacement));
488 }
489
490 int begin = 0;
491 while ((begin = line.indexOf('$', begin)) != -1) {
492 int end = begin + 1;
493 char endChar = line.at(end);
494 const int size = line.size();
495 while (end < size && (std::isalnum(endChar) || endChar == '_')) {
496 ++end;
497 endChar = line.at(end);
498 }
499
500 const int placeholderLength = end - begin;
501
502 const QByteArray variableName = line.mid(begin, placeholderLength);
503 const auto replacementIt = std::find_if(variableReplacements.cbegin(), variableReplacements.cend(),
504 [&variableName](const VariableReplacement &replacement) {
505 return variableName == replacement.placeholder;
506 });
507
508 if (replacementIt != variableReplacements.cend()) {
509 line.replace(begin, placeholderLength, replacementIt->variable);
510 begin += replacementIt->variable.length();
511 } else {
512 begin = end;
513 }
514 }
515
516 // Substitute variable names by generated vN variable names
517 const QByteArray substitutionedLine = replaceParameters(line, node, format);
518
519 QRegularExpressionMatchIterator matches;
520
521 switch (node.type()) {
522 case QShaderNode::Input:
523 case QShaderNode::Output:
524 matches = statementRegExp.globalMatch(QString::fromUtf8(substitutionedLine));
525 break;
526 case QShaderNode::Function:
527 matches = temporaryVariableToAssignmentRegExp.globalMatch(QString::fromUtf8(substitutionedLine));
528 break;
529 case QShaderNode::Invalid:
530 break;
531 }
532
533 while (matches.hasNext()) {
534 QRegularExpressionMatch match = matches.next();
535
536 Variable *v = nullptr;
537
538 switch (node.type()) {
539 // Record name of temporary variable that possibly references a global input
540 // We will replace the temporary variables by the matching global variables later
541 case QShaderNode::Input: {
542 const QString localVariable = match.captured(1);
543 const QString globalVariable = match.captured(2);
544
545 v = createVariable();
546 v->name = localVariable;
547 v->type = Variable::GlobalInput;
548
549 Assignment assignment;
550 assignment.expression = globalVariable;
551 v->assignment = assignment;
552 break;
553 }
554
555 case QShaderNode::Function: {
556 const QString localVariableDeclaration = match.captured(1);
557 const QString localVariableName = match.captured(2);
558 const QString assignmentContent = match.captured(3);
559
560 // Add new variable -> it cannot exist already
561 v = createVariable();
562 v->name = localVariableName;
563 v->declaration = localVariableDeclaration;
564 v->assignment.expression = assignmentContent;
565
566 // Find variables that may be referenced in the assignment
567 gatherTemporaryVariablesFromAssignment(v, assignmentContent);
568 break;
569 }
570
571 case QShaderNode::Output: {
572 const QString outputDeclaration = match.captured(1);
573 const QString assignmentContent = match.captured(2);
574
575 v = createVariable();
576 v->name = outputDeclaration;
577 v->declaration = outputDeclaration;
578 v->type = Variable::Output;
579
580 Assignment assignment;
581 assignment.expression = assignmentContent;
582 v->assignment = assignment;
583
584 // Find variables that may be referenced in the assignment
585 gatherTemporaryVariablesFromAssignment(v, assignmentContent);
586 break;
587 }
588 case QShaderNode::Invalid:
589 break;
590 }
591
592 LineContent lineContent;
593 lineContent.rawContent = QByteArray(QByteArrayLiteral(" ") + substitutionedLine);
594 lineContent.var = v;
595 lines << lineContent;
596 }
597 }
598
599 // Go through all lines
600 // Perform substitution of line with temporary variables substitution
601 for (LineContent &lineContent : lines) {
602 Variable *v = lineContent.var;
603 qCDebug(ShaderGenerator) << lineContent.rawContent;
604 if (v != nullptr) {
605 Variable::substitute(v);
606
607 qCDebug(ShaderGenerator) << "Line " << lineContent.rawContent << "is assigned to temporary" << v->name;
608
609 // Check number of occurrences a temporary variable is referenced
610 if (v->referenceCount == 1 || v->type == Variable::GlobalInput) {
611 // If it is referenced only once, no point in creating a temporary
612 // Clear content for current line
613 lineContent.rawContent.clear();
614 // We assume expression that were referencing vN will have vN properly substituted
615 } else {
616 lineContent.rawContent = QStringLiteral(" %1 = %2;").arg(v->declaration)
617 .arg(v->assignment.expression)
618 .toUtf8();
619 }
620
621 qCDebug(ShaderGenerator) << "Updated Line is " << lineContent.rawContent;
622 }
623 }
624
625 // Go throug all lines and insert content
626 for (const LineContent &lineContent : qAsConst(lines)) {
627 if (!lineContent.rawContent.isEmpty()) {
628 code << lineContent.rawContent;
629 }
630 }
631
632 code << QByteArrayLiteral("}");
633 code << QByteArray();
634
635 return code.join('\n');
636}
637
638QT_END_NAMESPACE
639