Only generate temporaries when it makes sense

- Never for global inputs
- Otherwise only if the temporary is referenced more than once
  -> meaning it's actually caching the result of some operation

Tests updated accordingly.

Change-Id: Ic76615370d23dee3965ca6350d5257a8be5a3e22
Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
bb10
Paul Lemire 2019-04-11 10:28:49 +02:00
parent ef3daddae1
commit 389dec3e7c
3 changed files with 391 additions and 61 deletions

View File

@ -44,6 +44,8 @@
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(ShaderGenerator, "ShaderGenerator", QtWarningMsg)
namespace
{
QByteArray toGlsl(QShaderLanguage::StorageQualifier qualifier, const QShaderFormat &format)
@ -342,18 +344,120 @@ QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers)
code << QByteArrayLiteral("void main()");
code << QByteArrayLiteral("{");
// Table to store temporary variables that should be replaced by global
// variables. This avoids having vec3 v56 = vertexPosition; when we could
const QRegularExpression localToGlobalRegExp(QStringLiteral("^.*\\s+(\\w+)\\s*=\\s*((?:\\w+\\(.*\\))|(?:\\w+)).*;$"));
const QRegularExpression temporaryVariableToAssignmentRegExp(QStringLiteral("^(.*\\s+(v\\d+))\\s*=\\s*(.*);$"));
const QRegularExpression temporaryVariableInAssignmentRegExp(QStringLiteral("\\W*(v\\d+)\\W*"));
const QRegularExpression outputToTemporaryAssignmentRegExp(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
QHash<QString, QString> localReferencesToGlobalInputs;
const QRegularExpression localToGlobalRegExp(QStringLiteral("^.*\\s+(\\w+)\\s*=\\s*(\\w+).*;$"));
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();
// Generate temporary variable names vN
for (const QShaderNodePort &port : ports) {
const QString portName = port.name;
const QShaderNodePort::Direction portDirection = port.direction;
@ -374,47 +478,117 @@ QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers)
line.replace(placeholder, variable);
}
// Substitute variable names by generated vN variable names
const QByteArray substitutionedLine = replaceParameters(line, node, format);
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
bool isAGlobalInputVariable = false;
if (node.type() == QShaderNode::Input) {
case QShaderNode::Input: {
const QRegularExpressionMatch match = localToGlobalRegExp.match(QString::fromUtf8(substitutionedLine));
if (match.hasMatch()) {
const QString localVariable = match.captured(1);
const QString globalVariable = match.captured(2);
if (globalInputVariables.contains(globalVariable)) {
const QString localVariable = match.captured(1);
// TO DO: Clean globalVariable (remove brackets ...)
localReferencesToGlobalInputs.insert(localVariable, globalVariable);
isAGlobalInputVariable = true;
}
v = createVariable();
v->name = localVariable;
v->type = Variable::GlobalInput;
Assignment assignment;
assignment.expression = globalVariable;
v->assignment = assignment;
}
break;
}
// Only insert content for lines aren't inputs or have not matching
// globalVariables for now
if (!isAGlobalInputVariable)
code << QByteArrayLiteral(" ") + substitutionedLine;
case QShaderNode::Function: {
const QRegularExpressionMatch match = temporaryVariableToAssignmentRegExp.match(QString::fromUtf8(substitutionedLine));
if (match.hasMatch()) {
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 QRegularExpressionMatch match = outputToTemporaryAssignmentRegExp.match(QString::fromUtf8(substitutionedLine));
if (match.hasMatch()) {
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();
// Replace occurrences of local variables which reference a global variable
// by the global variables directly
auto it = localReferencesToGlobalInputs.cbegin();
const auto end = localReferencesToGlobalInputs.cend();
QString codeString = QString::fromUtf8(code.join('\n'));
while (it != end) {
const QRegularExpression r(QStringLiteral("\\b(%1)([\\b|\\.|;|\\)|\\[|\\s|\\*|\\+|\\/|\\-|,])").arg(it.key()),
QRegularExpression::MultilineOption);
codeString.replace(r, QStringLiteral("%1\\2").arg(it.value()));
++it;
}
return codeString.toUtf8();
return code.join('\n');
}
QT_END_NAMESPACE

View File

@ -54,9 +54,13 @@
#include <QtGui/private/qtguiglobal_p.h>
#include <QtGui/private/qshadergraph_p.h>
#include <QtCore/QLoggingCategory>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(ShaderGenerator)
class QShaderGenerator
{
public:

View File

@ -137,7 +137,7 @@ namespace
createPort(QShaderNodePort::Output, "color")
});
sampleTexture.addRule(openGLES2, QShaderNode::Rule("highp vec4 $color = texture2D($sampler, $coord);"));
sampleTexture.addRule(openGL3, QShaderNode::Rule("vec4 $color = texture2D($sampler, $coord);"));
sampleTexture.addRule(openGL3, QShaderNode::Rule("vec4 $color = texture($sampler, $coord);"));
auto lightFunction = createNode({
createPort(QShaderNodePort::Input, "baseColor"),
@ -197,6 +197,7 @@ private slots:
void shouldProcessLanguageQualifierAndTypeEnums();
void shouldGenerateDifferentCodeDependingOnActiveLayers();
void shouldUseGlobalVariableRatherThanTemporaries();
void shouldGenerateTemporariesWisely();
};
void tst_QShaderGenerator::shouldHaveDefaultState()
@ -237,10 +238,7 @@ void tst_QShaderGenerator::shouldGenerateShaderCode_data()
<< ""
<< "void main()"
<< "{"
<< " highp vec4 v5 = texture2D(texture, texCoord);"
<< " highp vec4 v6 = lightModel(v5, worldPosition, lightIntensity);"
<< " highp vec4 v7 = v6 * pow(2.0, exposure);"
<< " gl_fragColor = v7;"
<< " gl_fragColor = (((((lightModel(((texture2D(texture, texCoord))), worldPosition, lightIntensity)))) * pow(2.0, exposure)));"
<< "}"
<< "";
@ -254,10 +252,7 @@ void tst_QShaderGenerator::shouldGenerateShaderCode_data()
<< ""
<< "void main()"
<< "{"
<< " vec4 v5 = texture2D(texture, texCoord);"
<< " vec4 v6 = lightModel(v5, worldPosition, lightIntensity);"
<< " vec4 v7 = v6 * pow(2.0, exposure);"
<< " fragColor = v7;"
<< " fragColor = (((((lightModel(((texture(texture, texCoord))), worldPosition, lightIntensity)))) * pow(2.0, exposure)));"
<< "}"
<< "";
@ -869,8 +864,7 @@ void tst_QShaderGenerator::shouldGenerateDifferentCodeDependingOnActiveLayers()
<< ""
<< "void main()"
<< "{"
<< " vec4 v2 = lightModel(diffuseUniform, normalUniform);"
<< " fragColor = v2;"
<< " fragColor = ((lightModel(diffuseUniform, normalUniform)));"
<< "}"
<< "";
QCOMPARE(code, expected.join("\n"));
@ -892,9 +886,7 @@ void tst_QShaderGenerator::shouldGenerateDifferentCodeDependingOnActiveLayers()
<< ""
<< "void main()"
<< "{"
<< " vec3 v2 = texture2D(normalTexture, texCoord).rgb;"
<< " vec4 v3 = lightModel(diffuseUniform, v2);"
<< " fragColor = v3;"
<< " fragColor = ((lightModel(diffuseUniform, texture2D(normalTexture, texCoord).rgb)));"
<< "}"
<< "";
QCOMPARE(code, expected.join("\n"));
@ -916,9 +908,7 @@ void tst_QShaderGenerator::shouldGenerateDifferentCodeDependingOnActiveLayers()
<< ""
<< "void main()"
<< "{"
<< " vec4 v1 = texture2D(diffuseTexture, texCoord);"
<< " vec4 v3 = lightModel(v1, normalUniform);"
<< " fragColor = v3;"
<< " fragColor = ((lightModel(texture2D(diffuseTexture, texCoord), normalUniform)));"
<< "}"
<< "";
QCOMPARE(code, expected.join("\n"));
@ -940,10 +930,7 @@ void tst_QShaderGenerator::shouldGenerateDifferentCodeDependingOnActiveLayers()
<< ""
<< "void main()"
<< "{"
<< " vec3 v2 = texture2D(normalTexture, texCoord).rgb;"
<< " vec4 v1 = texture2D(diffuseTexture, texCoord);"
<< " vec4 v3 = lightModel(v1, v2);"
<< " fragColor = v3;"
<< " fragColor = ((lightModel(texture2D(diffuseTexture, texCoord), texture2D(normalTexture, texCoord).rgb)));"
<< "}"
<< "";
QCOMPARE(code, expected.join("\n"));
@ -967,13 +954,13 @@ void tst_QShaderGenerator::shouldUseGlobalVariableRatherThanTemporaries()
createPort(QShaderNodePort::Input, "varName"),
createPort(QShaderNodePort::Output, "out")
});
fakeMultiPlyNoSpace.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName*v11;"));
fakeMultiPlyNoSpace.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName*speed;"));
auto fakeMultiPlySpace = createNode({
createPort(QShaderNodePort::Input, "varName"),
createPort(QShaderNodePort::Output, "out")
});
fakeMultiPlySpace.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName * v11;"));
fakeMultiPlySpace.addRule(gl4, QShaderNode::Rule("vec4 $out = $varName * speed;"));
auto fakeJoinNoSpace = createNode({
createPort(QShaderNodePort::Input, "varName"),
@ -1063,20 +1050,185 @@ void tst_QShaderGenerator::shouldUseGlobalVariableRatherThanTemporaries()
<< ""
<< "void main()"
<< "{"
<< " vec4 v7 = vertexPosition / vertexPosition;"
<< " vec4 v6 = vertexPosition.xyzw - vertexPosition;"
<< " vec4 v5 = vertexPosition.xyzw + vertexPosition;"
<< " vec4 v4 = vec4(vertexPosition.xyz, vertexPosition.w);"
<< " vec4 v3 = vec4(vertexPosition.xyz,vertexPosition.w);"
<< " vec4 v2 = vertexPosition * v11;"
<< " vec4 v1 = vertexPosition*v11;"
<< " fragColor = v1 + v2 + v3 + v4 + v5 + v6 + v7;"
<< " fragColor = (((((((vertexPosition*speed + vertexPosition * speed + ((vec4(vertexPosition.xyz,vertexPosition.w))) + ((vec4(vertexPosition.xyz, vertexPosition.w))) + ((vertexPosition.xyzw + vertexPosition)) + ((vertexPosition.xyzw - vertexPosition)) + ((vertexPosition / vertexPosition)))))))));"
<< "}"
<< "";
QCOMPARE(code, expected.join("\n"));
}
}
void tst_QShaderGenerator::shouldGenerateTemporariesWisely()
{
// GIVEN
const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
{
auto attribute = createNode({
createPort(QShaderNodePort::Output, "vertexPosition")
});
attribute.addRule(gl4, QShaderNode::Rule("vec4 $vertexPosition = vertexPosition;",
QByteArrayList() << "in vec4 vertexPosition;"));
auto complexFunction = createNode({
createPort(QShaderNodePort::Input, "inputVarName"),
createPort(QShaderNodePort::Output, "out")
});
complexFunction.addRule(gl4, QShaderNode::Rule("vec4 $out = $inputVarName * 2.0;"));
auto complexFunction2 = createNode({
createPort(QShaderNodePort::Input, "inputVarName"),
createPort(QShaderNodePort::Output, "out")
});
complexFunction2.addRule(gl4, QShaderNode::Rule("vec4 $out = $inputVarName * 4.0;"));
auto complexFunction3 = createNode({
createPort(QShaderNodePort::Input, "a"),
createPort(QShaderNodePort::Input, "b"),
createPort(QShaderNodePort::Output, "out")
});
complexFunction3.addRule(gl4, QShaderNode::Rule("vec4 $out = $a + $b;"));
auto shaderOutput1 = createNode({
createPort(QShaderNodePort::Input, "input")
});
shaderOutput1.addRule(gl4, QShaderNode::Rule("shaderOutput1 = $input;",
QByteArrayList() << "out vec4 shaderOutput1;"));
auto shaderOutput2 = createNode({
createPort(QShaderNodePort::Input, "input")
});
shaderOutput2.addRule(gl4, QShaderNode::Rule("shaderOutput2 = $input;",
QByteArrayList() << "out vec4 shaderOutput2;"));
{
// WHEN
const auto graph = [=] {
auto res = QShaderGraph();
res.addNode(attribute);
res.addNode(complexFunction);
res.addNode(shaderOutput1);
res.addEdge(createEdge(attribute.uuid(), "vertexPosition", complexFunction.uuid(), "inputVarName"));
res.addEdge(createEdge(complexFunction.uuid(), "out", shaderOutput1.uuid(), "input"));
return res;
}();
auto generator = QShaderGenerator();
generator.graph = graph;
generator.format = gl4;
const auto code = generator.createShaderCode();
// THEN
const auto expected = QByteArrayList()
<< "#version 400 core"
<< ""
<< "in vec4 vertexPosition;"
<< "out vec4 shaderOutput1;"
<< ""
<< "void main()"
<< "{"
<< " shaderOutput1 = vertexPosition * 2.0;"
<< "}"
<< "";
QCOMPARE(code, expected.join("\n"));
}
{
// WHEN
const auto graph = [=] {
auto res = QShaderGraph();
res.addNode(attribute);
res.addNode(complexFunction);
res.addNode(shaderOutput1);
res.addNode(shaderOutput2);
res.addEdge(createEdge(attribute.uuid(), "vertexPosition", complexFunction.uuid(), "inputVarName"));
res.addEdge(createEdge(complexFunction.uuid(), "out", shaderOutput1.uuid(), "input"));
res.addEdge(createEdge(complexFunction.uuid(), "out", shaderOutput2.uuid(), "input"));
return res;
}();
auto generator = QShaderGenerator();
generator.graph = graph;
generator.format = gl4;
const auto code = generator.createShaderCode();
// THEN
const auto expected = QByteArrayList()
<< "#version 400 core"
<< ""
<< "in vec4 vertexPosition;"
<< "out vec4 shaderOutput1;"
<< "out vec4 shaderOutput2;"
<< ""
<< "void main()"
<< "{"
<< " vec4 v1 = vertexPosition * 2.0;"
<< " shaderOutput2 = v1;"
<< " shaderOutput1 = v1;"
<< "}"
<< "";
QCOMPARE(code, expected.join("\n"));
}
{
// WHEN
const auto graph = [=] {
auto res = QShaderGraph();
res.addNode(attribute);
res.addNode(complexFunction);
res.addNode(complexFunction2);
res.addNode(complexFunction3);
res.addNode(shaderOutput1);
res.addNode(shaderOutput2);
res.addEdge(createEdge(attribute.uuid(), "vertexPosition", complexFunction.uuid(), "inputVarName"));
res.addEdge(createEdge(attribute.uuid(), "vertexPosition", complexFunction2.uuid(), "inputVarName"));
res.addEdge(createEdge(complexFunction.uuid(), "out", complexFunction3.uuid(), "a"));
res.addEdge(createEdge(complexFunction2.uuid(), "out", complexFunction3.uuid(), "b"));
res.addEdge(createEdge(complexFunction3.uuid(), "out", shaderOutput1.uuid(), "input"));
res.addEdge(createEdge(complexFunction2.uuid(), "out", shaderOutput2.uuid(), "input"));
return res;
}();
auto generator = QShaderGenerator();
generator.graph = graph;
generator.format = gl4;
const auto code = generator.createShaderCode();
// THEN
const auto expected = QByteArrayList()
<< "#version 400 core"
<< ""
<< "in vec4 vertexPosition;"
<< "out vec4 shaderOutput1;"
<< "out vec4 shaderOutput2;"
<< ""
<< "void main()"
<< "{"
<< " vec4 v2 = vertexPosition * 4.0;"
<< " shaderOutput2 = v2;"
<< " shaderOutput1 = (vertexPosition * 2.0 + v2);"
<< "}"
<< "";
QCOMPARE(code, expected.join("\n"));
}
}
}
QTEST_MAIN(tst_QShaderGenerator)
#include "tst_qshadergenerator.moc"