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 
47 QT_BEGIN_NAMESPACE
48 
49 Q_LOGGING_CATEGORY(ShaderGenerator, "ShaderGenerator", QtWarningMsg)
50 
51 namespace
52 {
toGlsl(QShaderLanguage::StorageQualifier qualifier,const QShaderFormat & format)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 
toGlsl(QShaderLanguage::VariableType type)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 
replaceParameters(const QByteArray & original,const QShaderNode & node,const QShaderFormat & format)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 
createShaderCode(const QStringList & enabledLayers) const295 QByteArray 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     QVector<QString> globalInputVariables;
326     const QRegularExpression globalInputExtractRegExp(QStringLiteral("^.*\\s+(\\w+).*;$"));
327 
328     const QVector<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 vector
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         QVector<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     QVector<Variable> temporaryVariables;
419     // Reserve more than enough space to ensure no reallocation will take place
420     temporaryVariables.reserve(nodes.size() * 8);
421 
422     QVector<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 QVector<QShaderNodePort> ports = node.ports();
460 
461         struct VariableReplacement {
462             QByteArray placeholder;
463             QByteArray variable;
464         };
465 
466         QVector<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 
638 QT_END_NAMESPACE
639