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