| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtGui module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qshadergenerator_p.h" |
| |
| #include "qshaderlanguage_p.h" |
| #include <QRegularExpression> |
| |
| #include <cctype> |
| #include <qshaderprogram_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| namespace Qt3DRender { |
| Q_LOGGING_CATEGORY(ShaderGenerator, "ShaderGenerator", QtWarningMsg) |
| |
| namespace |
| { |
| QByteArray toGlsl(QShaderLanguage::StorageQualifier qualifier, const QShaderFormat &format) noexcept |
| { |
| if (format.version().majorVersion() <= 2 && format.api() != QShaderFormat::RHI) { |
| // Note we're assuming fragment shader only here, it'd be different |
| // values for vertex shader, will need to be fixed properly at some |
| // point but isn't necessary yet (this problem already exists in past |
| // commits anyway) |
| switch (qualifier) { |
| case QShaderLanguage::Const: |
| return "const"; |
| case QShaderLanguage::Input: |
| if (format.shaderType() == QShaderFormat::Vertex) |
| return "attribute"; |
| else |
| return "varying"; |
| case QShaderLanguage::Output: |
| return ""; // Although fragment shaders for <=2 only have fixed outputs |
| case QShaderLanguage::Uniform: |
| return "uniform"; |
| case QShaderLanguage::BuiltIn: |
| return "//"; |
| } |
| } else { |
| switch (qualifier) { |
| case QShaderLanguage::Const: |
| return "const"; |
| case QShaderLanguage::Input: |
| return "in"; |
| case QShaderLanguage::Output: |
| return "out"; |
| case QShaderLanguage::Uniform: |
| return "uniform"; |
| case QShaderLanguage::BuiltIn: |
| return "//"; |
| } |
| } |
| |
| Q_UNREACHABLE(); |
| } |
| |
| QByteArray toGlsl(QShaderLanguage::VariableType type) noexcept |
| { |
| switch (type) { |
| case QShaderLanguage::Bool: |
| return "bool"; |
| case QShaderLanguage::Int: |
| return "int"; |
| case QShaderLanguage::Uint: |
| return "uint"; |
| case QShaderLanguage::Float: |
| return "float"; |
| case QShaderLanguage::Double: |
| return "double"; |
| case QShaderLanguage::Vec2: |
| return "vec2"; |
| case QShaderLanguage::Vec3: |
| return "vec3"; |
| case QShaderLanguage::Vec4: |
| return "vec4"; |
| case QShaderLanguage::DVec2: |
| return "dvec2"; |
| case QShaderLanguage::DVec3: |
| return "dvec3"; |
| case QShaderLanguage::DVec4: |
| return "dvec4"; |
| case QShaderLanguage::BVec2: |
| return "bvec2"; |
| case QShaderLanguage::BVec3: |
| return "bvec3"; |
| case QShaderLanguage::BVec4: |
| return "bvec4"; |
| case QShaderLanguage::IVec2: |
| return "ivec2"; |
| case QShaderLanguage::IVec3: |
| return "ivec3"; |
| case QShaderLanguage::IVec4: |
| return "ivec4"; |
| case QShaderLanguage::UVec2: |
| return "uvec2"; |
| case QShaderLanguage::UVec3: |
| return "uvec3"; |
| case QShaderLanguage::UVec4: |
| return "uvec4"; |
| case QShaderLanguage::Mat2: |
| return "mat2"; |
| case QShaderLanguage::Mat3: |
| return "mat3"; |
| case QShaderLanguage::Mat4: |
| return "mat4"; |
| case QShaderLanguage::Mat2x2: |
| return "mat2x2"; |
| case QShaderLanguage::Mat2x3: |
| return "mat2x3"; |
| case QShaderLanguage::Mat2x4: |
| return "mat2x4"; |
| case QShaderLanguage::Mat3x2: |
| return "mat3x2"; |
| case QShaderLanguage::Mat3x3: |
| return "mat3x3"; |
| case QShaderLanguage::Mat3x4: |
| return "mat3x4"; |
| case QShaderLanguage::Mat4x2: |
| return "mat4x2"; |
| case QShaderLanguage::Mat4x3: |
| return "mat4x3"; |
| case QShaderLanguage::Mat4x4: |
| return "mat4x4"; |
| case QShaderLanguage::DMat2: |
| return "dmat2"; |
| case QShaderLanguage::DMat3: |
| return "dmat3"; |
| case QShaderLanguage::DMat4: |
| return "dmat4"; |
| case QShaderLanguage::DMat2x2: |
| return "dmat2x2"; |
| case QShaderLanguage::DMat2x3: |
| return "dmat2x3"; |
| case QShaderLanguage::DMat2x4: |
| return "dmat2x4"; |
| case QShaderLanguage::DMat3x2: |
| return "dmat3x2"; |
| case QShaderLanguage::DMat3x3: |
| return "dmat3x3"; |
| case QShaderLanguage::DMat3x4: |
| return "dmat3x4"; |
| case QShaderLanguage::DMat4x2: |
| return "dmat4x2"; |
| case QShaderLanguage::DMat4x3: |
| return "dmat4x3"; |
| case QShaderLanguage::DMat4x4: |
| return "dmat4x4"; |
| case QShaderLanguage::Sampler1D: |
| return "sampler1D"; |
| case QShaderLanguage::Sampler2D: |
| return "sampler2D"; |
| case QShaderLanguage::Sampler3D: |
| return "sampler3D"; |
| case QShaderLanguage::SamplerCube: |
| return "samplerCube"; |
| case QShaderLanguage::Sampler2DRect: |
| return "sampler2DRect"; |
| case QShaderLanguage::Sampler2DMs: |
| return "sampler2DMS"; |
| case QShaderLanguage::SamplerBuffer: |
| return "samplerBuffer"; |
| case QShaderLanguage::Sampler1DArray: |
| return "sampler1DArray"; |
| case QShaderLanguage::Sampler2DArray: |
| return "sampler2DArray"; |
| case QShaderLanguage::Sampler2DMsArray: |
| return "sampler2DMSArray"; |
| case QShaderLanguage::SamplerCubeArray: |
| return "samplerCubeArray"; |
| case QShaderLanguage::Sampler1DShadow: |
| return "sampler1DShadow"; |
| case QShaderLanguage::Sampler2DShadow: |
| return "sampler2DShadow"; |
| case QShaderLanguage::Sampler2DRectShadow: |
| return "sampler2DRectShadow"; |
| case QShaderLanguage::Sampler1DArrayShadow: |
| return "sampler1DArrayShadow"; |
| case QShaderLanguage::Sampler2DArrayShadow: |
| return "sample2DArrayShadow"; |
| case QShaderLanguage::SamplerCubeShadow: |
| return "samplerCubeShadow"; |
| case QShaderLanguage::SamplerCubeArrayShadow: |
| return "samplerCubeArrayShadow"; |
| case QShaderLanguage::ISampler1D: |
| return "isampler1D"; |
| case QShaderLanguage::ISampler2D: |
| return "isampler2D"; |
| case QShaderLanguage::ISampler3D: |
| return "isampler3D"; |
| case QShaderLanguage::ISamplerCube: |
| return "isamplerCube"; |
| case QShaderLanguage::ISampler2DRect: |
| return "isampler2DRect"; |
| case QShaderLanguage::ISampler2DMs: |
| return "isampler2DMS"; |
| case QShaderLanguage::ISamplerBuffer: |
| return "isamplerBuffer"; |
| case QShaderLanguage::ISampler1DArray: |
| return "isampler1DArray"; |
| case QShaderLanguage::ISampler2DArray: |
| return "isampler2DArray"; |
| case QShaderLanguage::ISampler2DMsArray: |
| return "isampler2DMSArray"; |
| case QShaderLanguage::ISamplerCubeArray: |
| return "isamplerCubeArray"; |
| case QShaderLanguage::USampler1D: |
| return "usampler1D"; |
| case QShaderLanguage::USampler2D: |
| return "usampler2D"; |
| case QShaderLanguage::USampler3D: |
| return "usampler3D"; |
| case QShaderLanguage::USamplerCube: |
| return "usamplerCube"; |
| case QShaderLanguage::USampler2DRect: |
| return "usampler2DRect"; |
| case QShaderLanguage::USampler2DMs: |
| return "usampler2DMS"; |
| case QShaderLanguage::USamplerBuffer: |
| return "usamplerBuffer"; |
| case QShaderLanguage::USampler1DArray: |
| return "usampler1DArray"; |
| case QShaderLanguage::USampler2DArray: |
| return "usampler2DArray"; |
| case QShaderLanguage::USampler2DMsArray: |
| return "usampler2DMSArray"; |
| case QShaderLanguage::USamplerCubeArray: |
| return "usamplerCubeArray"; |
| } |
| |
| Q_UNREACHABLE(); |
| } |
| |
| QByteArray replaceParameters(const QByteArray &original, const QShaderNode &node, |
| const QShaderFormat &format) noexcept |
| { |
| QByteArray result = original; |
| |
| const QStringList parameterNames = node.parameterNames(); |
| for (const QString ¶meterName : parameterNames) { |
| const QByteArray placeholder = QByteArray(QByteArrayLiteral("$") + parameterName.toUtf8()); |
| const QVariant parameter = node.parameter(parameterName); |
| if (parameter.userType() == qMetaTypeId<QShaderLanguage::StorageQualifier>()) { |
| const QShaderLanguage::StorageQualifier qualifier = |
| qvariant_cast<QShaderLanguage::StorageQualifier>(parameter); |
| const QByteArray value = toGlsl(qualifier, format); |
| result.replace(placeholder, value); |
| } else if (parameter.userType() == qMetaTypeId<QShaderLanguage::VariableType>()) { |
| const QShaderLanguage::VariableType type = |
| qvariant_cast<QShaderLanguage::VariableType>(parameter); |
| const QByteArray value = toGlsl(type); |
| result.replace(placeholder, value); |
| } else { |
| const QByteArray value = parameter.toString().toUtf8(); |
| result.replace(placeholder, value); |
| } |
| } |
| |
| return result; |
| } |
| |
| struct ShaderGenerationState |
| { |
| ShaderGenerationState(const QShaderGenerator &gen, |
| QVector<QShaderGraph::Statement> statements) |
| : generator { gen }, statements { statements } |
| { |
| |
| } |
| |
| const QShaderGenerator &generator; |
| QVector<QShaderGraph::Statement> statements; |
| QByteArrayList code; |
| }; |
| |
| class GLSL45HeaderWriter |
| { |
| public: |
| void writeHeader(ShaderGenerationState &state) |
| { |
| const auto &format = state.generator.format; |
| auto &code = state.code; |
| for (const QShaderGraph::Statement &statement : state.statements) { |
| const QShaderNode &node = statement.node; |
| const QByteArrayList &headerSnippets = node.rule(format).headerSnippets; |
| for (const QByteArray &snippet : headerSnippets) { |
| auto replacedSnippet = replaceParameters(snippet, node, format).trimmed(); |
| |
| if (replacedSnippet.startsWith(QByteArrayLiteral("add-input"))) { |
| onInOut(code, replacedSnippet); |
| } else if (replacedSnippet.startsWith(QByteArrayLiteral("add-uniform"))) { |
| onNamedUniform(ubo, replacedSnippet); |
| } else if (replacedSnippet.startsWith(QByteArrayLiteral("add-sampler"))) { |
| onNamedSampler(code, replacedSnippet); |
| } else if (replacedSnippet.startsWith(QByteArrayLiteral("#pragma include "))) { |
| onInclude(code, replacedSnippet); |
| } else { |
| code << replacedSnippet; |
| } |
| } |
| } |
| |
| if (!ubo.isEmpty()) { |
| code << QByteArrayLiteral("layout(std140, binding = ") |
| + QByteArray::number(currentBinding++) |
| + QByteArrayLiteral(") uniform qt3d_shadergraph_generated_uniforms {"); |
| code << ubo; |
| code << "};"; |
| } |
| } |
| |
| private: |
| void onInOut(QByteArrayList &code, const QByteArray &snippet) noexcept |
| { |
| const auto split = snippet.split(' '); |
| if (split.size() < 4) { |
| qDebug() << "Invalid header snippet: " << snippet; |
| return; |
| } |
| const auto &qualifier = split[1]; |
| const auto &type = split[2]; |
| const auto &name = split[3]; |
| |
| if (qualifier == QByteArrayLiteral("in")) { |
| code << (QByteArrayLiteral("layout(location = ") |
| + QByteArray::number(currentInputLocation++) + QByteArrayLiteral(") in ") |
| + type + ' ' + name + QByteArrayLiteral(";")); |
| } else if (qualifier == QByteArrayLiteral("out")) { |
| code << (QByteArrayLiteral("layout(location = ") |
| + QByteArray::number(currentOutputLocation++) + QByteArrayLiteral(") out ") |
| + type + ' ' + name + QByteArrayLiteral(";")); |
| } else if (qualifier == QByteArrayLiteral("uniform")) { |
| ubo << (type + ' ' + name + ';'); |
| } |
| } |
| |
| void onNamedUniform(QByteArrayList &ubo, const QByteArray &snippet) noexcept |
| { |
| const auto split = snippet.split(' '); |
| if (split.size() < 3) { |
| qDebug() << "Invalid header snippet: " << snippet; |
| return; |
| } |
| |
| const auto &type = split[1]; |
| const auto &name = split[2]; |
| |
| ubo << (type + ' ' + name + ';'); |
| } |
| |
| void onNamedSampler(QByteArrayList &code, const QByteArray &snippet) noexcept |
| { |
| const auto split = snippet.split(' '); |
| if (split.size() < 3) { |
| qDebug() << "Invalid header snippet: " << snippet; |
| return; |
| } |
| const auto binding = QByteArray::number(currentBinding++); |
| const auto &type = split[1]; |
| const auto &name = split[2]; |
| |
| code << (QByteArrayLiteral("layout(binding = ") + binding + QByteArrayLiteral(") uniform ") |
| + type + ' ' + name + QByteArrayLiteral(";")); |
| } |
| |
| void onInclude(QByteArrayList &code, const QByteArray &snippet) noexcept |
| { |
| const auto filepath = QString::fromUtf8(snippet.mid(strlen("#pragma include "))); |
| QString deincluded = QString::fromUtf8(QShaderProgramPrivate::deincludify(filepath)); |
| |
| // This lambda will replace all occurrences of a string (e.g. "binding = auto") by another, |
| // with the incremented int passed as argument (e.g. "binding = 1", "binding = 2" ...) |
| const auto replaceAndIncrement = [&deincluded](const QRegularExpression ®exp, |
| int &variable, |
| const QString &replacement) noexcept { |
| int matchStart = 0; |
| do { |
| matchStart = deincluded.indexOf(regexp, matchStart); |
| if (matchStart != -1) { |
| const auto match = regexp.match(deincluded.midRef(matchStart)); |
| const auto length = match.capturedLength(0); |
| |
| deincluded.replace(matchStart, length, replacement.arg(variable++)); |
| } |
| } while (matchStart != -1); |
| }; |
| |
| // 1. Handle uniforms |
| { |
| thread_local const QRegularExpression bindings( |
| QStringLiteral("binding\\s?+=\\s?+auto")); |
| |
| replaceAndIncrement(bindings, currentBinding, QStringLiteral("binding = %1")); |
| } |
| |
| // 2. Handle inputs |
| { |
| thread_local const QRegularExpression inLocations( |
| QStringLiteral("location\\s?+=\\s?+auto\\s?+\\)\\s?+in\\s+")); |
| |
| replaceAndIncrement(inLocations, currentInputLocation, |
| QStringLiteral("location = %1) in ")); |
| } |
| |
| // 3. Handle outputs |
| { |
| thread_local const QRegularExpression outLocations( |
| QStringLiteral("location\\s?+=\\s?+auto\\s?+\\)\\s?+out\\s+")); |
| |
| replaceAndIncrement(outLocations, currentOutputLocation, |
| QStringLiteral("location = %1) out ")); |
| } |
| |
| code << deincluded.toUtf8(); |
| } |
| |
| int currentInputLocation { 0 }; |
| int currentOutputLocation { 0 }; |
| int currentBinding { 2 }; |
| QByteArrayList ubo; |
| }; |
| |
| struct GLSLHeaderWriter |
| { |
| void writeHeader(ShaderGenerationState &state) |
| { |
| const auto &format = state.generator.format; |
| auto &code = state.code; |
| for (const QShaderGraph::Statement &statement : state.statements) { |
| const QShaderNode &node = statement.node; |
| const QByteArrayList &headerSnippets = node.rule(format).headerSnippets; |
| for (const QByteArray &snippet : headerSnippets) { |
| code << replaceParameters(snippet, node, format); |
| } |
| } |
| } |
| }; |
| |
| QByteArray versionString(const QShaderFormat &format) noexcept |
| { |
| if (!format.isValid()) |
| return {}; |
| |
| switch (format.api()) { |
| case QShaderFormat::RHI: { |
| return QByteArrayLiteral("#version 450"); |
| } |
| case QShaderFormat::VulkanFlavoredGLSL: { |
| const int major = format.version().majorVersion(); |
| const int minor = format.version().minorVersion(); |
| return (QByteArrayLiteral("#version ") + QByteArray::number(major * 100 + minor * 10)); |
| } |
| default: { |
| const bool isGLES = format.api() == QShaderFormat::OpenGLES; |
| const int major = format.version().majorVersion(); |
| const int minor = format.version().minorVersion(); |
| |
| const int version = major == 2 && isGLES ? 100 |
| : major == 3 && isGLES ? 300 |
| : major == 2 ? 100 + 10 * (minor + 1) |
| : major == 3 && minor <= 2 ? 100 + 10 * (minor + 3) |
| : major * 100 + minor * 10; |
| |
| const QByteArray profile = |
| isGLES && version > 100 ? QByteArrayLiteral(" es") |
| : version >= 150 && format.api() == QShaderFormat::OpenGLCoreProfile ? QByteArrayLiteral(" core") |
| : version >= 150 && format.api() == QShaderFormat::OpenGLCompatibilityProfile ? QByteArrayLiteral(" compatibility") |
| : QByteArray(); |
| |
| return (QByteArrayLiteral("#version ") + QByteArray::number(version) + profile); |
| } |
| } |
| } |
| } |
| |
| QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers) const |
| { |
| const QVector<QShaderNode> nodes = graph.nodes(); |
| const auto statements = graph.createStatements(enabledLayers); |
| ShaderGenerationState state(*this, statements); |
| QByteArrayList &code = state.code; |
| |
| code << versionString(format); |
| code << QByteArray(); |
| |
| if (format.api() == QShaderFormat::VulkanFlavoredGLSL || format.api() == QShaderFormat::RHI) { |
| GLSL45HeaderWriter builder; |
| builder.writeHeader(state); |
| } else { |
| GLSLHeaderWriter builder; |
| builder.writeHeader(state); |
| } |
| |
| code << QByteArray(); |
| code << QByteArrayLiteral("void main()"); |
| code << QByteArrayLiteral("{"); |
| |
| const QRegularExpression temporaryVariableToAssignmentRegExp( |
| QStringLiteral("([^;]*\\s+(v\\d+))\\s*=\\s*([^;]*);")); |
| const QRegularExpression temporaryVariableInAssignmentRegExp(QStringLiteral("\\W*(v\\d+)\\W*")); |
| const QRegularExpression statementRegExp(QStringLiteral("\\s*(\\w+)\\s*=\\s*([^;]*);")); |
| |
| struct Variable; |
| |
| struct Assignment |
| { |
| QString expression; |
| QVector<Variable *> referencedVariables; |
| }; |
| |
| struct Variable |
| { |
| enum Type { GlobalInput, TemporaryAssignment, Output }; |
| |
| QString name; |
| QString declaration; |
| int referenceCount = 0; |
| Assignment assignment; |
| Type type = TemporaryAssignment; |
| bool substituted = false; |
| |
| static void substitute(Variable *v) |
| { |
| if (v->substituted) |
| return; |
| |
| qCDebug(ShaderGenerator) |
| << "Begin Substituting " << v->name << " = " << v->assignment.expression; |
| for (Variable *ref : qAsConst(v->assignment.referencedVariables)) { |
| // Recursively substitute |
| Variable::substitute(ref); |
| |
| // Replace all variables referenced only once in the assignment |
| // by their actual expression |
| if (ref->referenceCount == 1 || ref->type == Variable::GlobalInput) { |
| const QRegularExpression r(QStringLiteral("(.*\\b)(%1)(\\b.*)").arg(ref->name)); |
| if (v->assignment.referencedVariables.size() == 1) |
| v->assignment.expression.replace( |
| r, QStringLiteral("\\1%2\\3").arg(ref->assignment.expression)); |
| else |
| v->assignment.expression.replace( |
| r, QStringLiteral("(\\1%2\\3)").arg(ref->assignment.expression)); |
| } |
| } |
| qCDebug(ShaderGenerator) |
| << "Done Substituting " << v->name << " = " << v->assignment.expression; |
| v->substituted = true; |
| } |
| }; |
| |
| struct LineContent |
| { |
| QByteArray rawContent; |
| Variable *var = nullptr; |
| }; |
| |
| // Table to store temporary variables that should be replaced: |
| // - If variable references a a global variables |
| // -> we will use the global variable directly |
| // - If variable references a function results |
| // -> will be kept only if variable is referenced more than once. |
| // This avoids having vec3 v56 = vertexPosition; when we could |
| // just use vertexPosition directly. |
| // The added benefit is when having arrays, we don't try to create |
| // mat4 v38 = skinningPalelette[100] which would be invalid |
| QVector<Variable> temporaryVariables; |
| // Reserve more than enough space to ensure no reallocation will take place |
| temporaryVariables.reserve(nodes.size() * 8); |
| |
| QVector<LineContent> lines; |
| |
| auto createVariable = [&] () -> Variable * { |
| Q_ASSERT(temporaryVariables.capacity() > 0); |
| temporaryVariables.resize(temporaryVariables.size() + 1); |
| return &temporaryVariables.last(); |
| }; |
| |
| auto findVariable = [&] (const QString &name) -> Variable * { |
| const auto end = temporaryVariables.end(); |
| auto it = std::find_if(temporaryVariables.begin(), end, |
| [=] (const Variable &a) { return a.name == name; }); |
| if (it != end) |
| return &(*it); |
| return nullptr; |
| }; |
| |
| auto gatherTemporaryVariablesFromAssignment = [&](Variable *v, |
| const QString &assignmentContent) { |
| QRegularExpressionMatchIterator subMatchIt = |
| temporaryVariableInAssignmentRegExp.globalMatch(assignmentContent); |
| while (subMatchIt.hasNext()) { |
| const QRegularExpressionMatch subMatch = subMatchIt.next(); |
| const QString variableName = subMatch.captured(1); |
| |
| // Variable we care about should already exists -> an expression cannot reference a |
| // variable that hasn't been defined |
| Variable *u = findVariable(variableName); |
| Q_ASSERT(u); |
| |
| // Increase reference count for u |
| ++u->referenceCount; |
| // Insert u as reference for variable v |
| v->assignment.referencedVariables.push_back(u); |
| } |
| }; |
| |
| for (const QShaderGraph::Statement &statement : statements) { |
| const QShaderNode node = statement.node; |
| QByteArray line = node.rule(format).substitution; |
| const QVector<QShaderNodePort> ports = node.ports(); |
| |
| struct VariableReplacement |
| { |
| QByteArray placeholder; |
| QByteArray variable; |
| }; |
| |
| QVector<VariableReplacement> variableReplacements; |
| |
| // Generate temporary variable names vN |
| for (const QShaderNodePort &port : ports) { |
| const QString portName = port.name; |
| const QShaderNodePort::Direction portDirection = port.direction; |
| const bool isInput = port.direction == QShaderNodePort::Input; |
| |
| const int portIndex = statement.portIndex(portDirection, portName); |
| |
| Q_ASSERT(portIndex >= 0); |
| |
| const int variableIndex = |
| isInput ? statement.inputs.at(portIndex) : statement.outputs.at(portIndex); |
| if (variableIndex < 0) |
| continue; |
| |
| VariableReplacement replacement; |
| replacement.placeholder = QByteArrayLiteral("$") + portName.toUtf8(); |
| replacement.variable = QByteArrayLiteral("v") + QByteArray::number(variableIndex); |
| |
| variableReplacements.append(std::move(replacement)); |
| } |
| |
| int begin = 0; |
| while ((begin = line.indexOf('$', begin)) != -1) { |
| int end = begin + 1; |
| char endChar = line.at(end); |
| const int size = line.size(); |
| while (end < size && (std::isalnum(endChar) || endChar == '_')) { |
| ++end; |
| endChar = line.at(end); |
| } |
| |
| const int placeholderLength = end - begin; |
| |
| const QByteArray variableName = line.mid(begin, placeholderLength); |
| const auto replacementIt = |
| std::find_if(variableReplacements.cbegin(), variableReplacements.cend(), |
| [&variableName](const VariableReplacement &replacement) { |
| return variableName == replacement.placeholder; |
| }); |
| |
| if (replacementIt != variableReplacements.cend()) { |
| line.replace(begin, placeholderLength, replacementIt->variable); |
| begin += replacementIt->variable.length(); |
| } else { |
| begin = end; |
| } |
| } |
| |
| // Substitute variable names by generated vN variable names |
| const QByteArray substitutionedLine = replaceParameters(line, node, format); |
| |
| QRegularExpressionMatchIterator matches; |
| |
| switch (node.type()) { |
| case QShaderNode::Input: |
| case QShaderNode::Output: |
| matches = statementRegExp.globalMatch(QString::fromUtf8(substitutionedLine)); |
| break; |
| case QShaderNode::Function: |
| matches = temporaryVariableToAssignmentRegExp.globalMatch( |
| QString::fromUtf8(substitutionedLine)); |
| break; |
| case QShaderNode::Invalid: |
| break; |
| } |
| |
| while (matches.hasNext()) { |
| QRegularExpressionMatch match = matches.next(); |
| |
| Variable *v = nullptr; |
| |
| switch (node.type()) { |
| // Record name of temporary variable that possibly references a global input |
| // We will replace the temporary variables by the matching global variables later |
| case QShaderNode::Input: { |
| const QString localVariable = match.captured(1); |
| const QString globalVariable = match.captured(2); |
| |
| v = createVariable(); |
| v->name = localVariable; |
| v->type = Variable::GlobalInput; |
| |
| Assignment assignment; |
| assignment.expression = globalVariable; |
| v->assignment = assignment; |
| break; |
| } |
| |
| case QShaderNode::Function: { |
| const QString localVariableDeclaration = match.captured(1); |
| const QString localVariableName = match.captured(2); |
| const QString assignmentContent = match.captured(3); |
| |
| // Add new variable -> it cannot exist already |
| v = createVariable(); |
| v->name = localVariableName; |
| v->declaration = localVariableDeclaration; |
| v->assignment.expression = assignmentContent; |
| |
| // Find variables that may be referenced in the assignment |
| gatherTemporaryVariablesFromAssignment(v, assignmentContent); |
| break; |
| } |
| |
| case QShaderNode::Output: { |
| const QString outputDeclaration = match.captured(1); |
| const QString assignmentContent = match.captured(2); |
| |
| v = createVariable(); |
| v->name = outputDeclaration; |
| v->declaration = outputDeclaration; |
| v->type = Variable::Output; |
| |
| Assignment assignment; |
| assignment.expression = assignmentContent; |
| v->assignment = assignment; |
| |
| // Find variables that may be referenced in the assignment |
| gatherTemporaryVariablesFromAssignment(v, assignmentContent); |
| break; |
| } |
| case QShaderNode::Invalid: |
| break; |
| } |
| |
| LineContent lineContent; |
| lineContent.rawContent = QByteArray(QByteArrayLiteral(" ") + substitutionedLine); |
| lineContent.var = v; |
| lines << lineContent; |
| } |
| } |
| |
| // Go through all lines |
| // Perform substitution of line with temporary variables substitution |
| for (LineContent &lineContent : lines) { |
| Variable *v = lineContent.var; |
| qCDebug(ShaderGenerator) << lineContent.rawContent; |
| if (v != nullptr) { |
| Variable::substitute(v); |
| |
| qCDebug(ShaderGenerator) |
| << "Line " << lineContent.rawContent << "is assigned to temporary" << v->name; |
| |
| // Check number of occurrences a temporary variable is referenced |
| if (v->referenceCount == 1 || v->type == Variable::GlobalInput) { |
| // If it is referenced only once, no point in creating a temporary |
| // Clear content for current line |
| lineContent.rawContent.clear(); |
| // We assume expression that were referencing vN will have vN properly substituted |
| } else { |
| lineContent.rawContent = QStringLiteral(" %1 = %2;") |
| .arg(v->declaration) |
| .arg(v->assignment.expression) |
| .toUtf8(); |
| } |
| |
| qCDebug(ShaderGenerator) << "Updated Line is " << lineContent.rawContent; |
| } |
| } |
| |
| // Go throug all lines and insert content |
| for (const LineContent &lineContent : qAsConst(lines)) { |
| if (!lineContent.rawContent.isEmpty()) { |
| code << lineContent.rawContent; |
| } |
| } |
| |
| code << QByteArrayLiteral("}"); |
| code << QByteArray(); |
| |
| return code.join('\n'); |
| } |
| |
| } |
| QT_END_NAMESPACE |