blob: f80502fccaf53555b42a8e0e4dc617acad1e7d77 [file] [log] [blame]
/****************************************************************************
**
** 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 &parameterName : 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 &regexp,
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