diff options
author | Jean-Michaël Celerier <jean-michael.celerier@kdab.com> | 2020-03-13 10:56:30 +0100 |
---|---|---|
committer | Jean-Michaël Celerier <jean-michael.celerier@kdab.com> | 2020-03-19 07:14:12 +0100 |
commit | a6ffce09ce6d4fd39fbb50eb58f7b5d1027f4d60 (patch) | |
tree | fc5bbe90e61ce562cc880b38011cda75eabc808d /src/render/shadergraph/qshadergenerator.cpp | |
parent | 662c47d6e9c806f6c0d654e7ecce9a66a4f35154 (diff) |
Move shadergraph to Qt3D
Change-Id: I3ed10747175a58513933b29eac66ddf92fe87d07
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
Diffstat (limited to 'src/render/shadergraph/qshadergenerator.cpp')
-rw-r--r-- | src/render/shadergraph/qshadergenerator.cpp | 640 |
1 files changed, 640 insertions, 0 deletions
diff --git a/src/render/shadergraph/qshadergenerator.cpp b/src/render/shadergraph/qshadergenerator.cpp new file mode 100644 index 000000000..4f37cfef1 --- /dev/null +++ b/src/render/shadergraph/qshadergenerator.cpp @@ -0,0 +1,640 @@ +/**************************************************************************** +** +** 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> + +QT_BEGIN_NAMESPACE +namespace Qt3DRender +{ +Q_LOGGING_CATEGORY(ShaderGenerator, "ShaderGenerator", QtWarningMsg) + +namespace +{ + QByteArray toGlsl(QShaderLanguage::StorageQualifier qualifier, const QShaderFormat &format) + { + if (format.version().majorVersion() <= 2) { + // 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) + { + 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) + { + 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; + } +} + +QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers) const +{ + auto code = QByteArrayList(); + + if (format.isValid()) { + 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(); + + code << (QByteArrayLiteral("#version ") + QByteArray::number(version) + profile); + code << QByteArray(); + } + + const auto intersectsEnabledLayers = [enabledLayers] (const QStringList &layers) { + return layers.isEmpty() + || std::any_of(layers.cbegin(), layers.cend(), + [enabledLayers] (const QString &s) { return enabledLayers.contains(s); }); + }; + + QVector<QString> globalInputVariables; + const QRegularExpression globalInputExtractRegExp(QStringLiteral("^.*\\s+(\\w+).*;$")); + + const QVector<QShaderNode> nodes = graph.nodes(); + for (const QShaderNode &node : nodes) { + if (intersectsEnabledLayers(node.layers())) { + const QByteArrayList headerSnippets = node.rule(format).headerSnippets; + for (const QByteArray &snippet : headerSnippets) { + code << replaceParameters(snippet, node, format); + + // If node is an input, record the variable name into the globalInputVariables vector + if (node.type() == QShaderNode::Input) { + const QRegularExpressionMatch match = globalInputExtractRegExp.match(QString::fromUtf8(code.last())); + if (match.hasMatch()) + globalInputVariables.push_back(match.captured(1)); + } + } + } + } + + 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 : graph.createStatements(enabledLayers)) { + 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 |