QShaderGenerator: Don't crash when a node has multiple outputs

It was already possible to declare a node prototype with multiple
outputs, but trying to assign to all those outputs was not possible and
instead resulted in a crash.

It is now possible to declare nodes like this without crashing:
"SEPERATE_XYZ": {
  "inputs": ["vector"],
  "outputs": ["x", "y", "z"],
  "rules": [
    {
        "substitution": "float $x = $vector.x;
                         float $y = $vector.y;
                         float $z = $vector.z;"
    }
  ]
}

Change-Id: I748e77e84c9120dc688c573eee33dc13c6bfbace
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
(cherry picked from commit 39994e0705f11afc45e20872b95fb3a6e684c913)
This commit is contained in:
Nicolas Guichard 2020-02-18 17:16:14 +01:00 committed by Paul Lemire
parent c2827e617c
commit b150901525
2 changed files with 112 additions and 28 deletions

View File

@ -346,10 +346,10 @@ QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers)
code << QByteArrayLiteral("void main()");
code << QByteArrayLiteral("{");
const QRegularExpression localToGlobalRegExp(QStringLiteral("^.*\\s+(\\w+)\\s*=\\s*((?:\\w+\\(.*\\))|(?:\\w+)).*;$"));
const QRegularExpression temporaryVariableToAssignmentRegExp(QStringLiteral("^(.*\\s+(v\\d+))\\s*=\\s*(.*);$"));
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*(.*);$"));
const QRegularExpression outputToTemporaryAssignmentRegExp(QStringLiteral("\\s*(\\w+)\\s*=\\s*([^;]*);"));
struct Variable;
@ -517,14 +517,31 @@ QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers)
// Substitute variable names by generated vN variable names
const QByteArray substitutionedLine = replaceParameters(line, node, format);
Variable *v = nullptr;
QRegularExpressionMatchIterator matches;
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 QRegularExpressionMatch match = localToGlobalRegExp.match(QString::fromUtf8(substitutionedLine));
if (match.hasMatch()) {
case QShaderNode::Input:
matches = localToGlobalRegExp.globalMatch(QString::fromUtf8(substitutionedLine));
break;
case QShaderNode::Function:
matches = temporaryVariableToAssignmentRegExp.globalMatch(QString::fromUtf8(substitutionedLine));
break;
case QShaderNode::Output:
matches = outputToTemporaryAssignmentRegExp.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);
@ -535,13 +552,10 @@ QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers)
Assignment assignment;
assignment.expression = globalVariable;
v->assignment = assignment;
break;
}
break;
}
case QShaderNode::Function: {
const QRegularExpressionMatch match = temporaryVariableToAssignmentRegExp.match(QString::fromUtf8(substitutionedLine));
if (match.hasMatch()) {
case QShaderNode::Function: {
const QString localVariableDeclaration = match.captured(1);
const QString localVariableName = match.captured(2);
const QString assignmentContent = match.captured(3);
@ -554,13 +568,10 @@ QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers)
// Find variables that may be referenced in the assignment
gatherTemporaryVariablesFromAssignment(v, assignmentContent);
break;
}
break;
}
case QShaderNode::Output: {
const QRegularExpressionMatch match = outputToTemporaryAssignmentRegExp.match(QString::fromUtf8(substitutionedLine));
if (match.hasMatch()) {
case QShaderNode::Output: {
const QString outputDeclaration = match.captured(1);
const QString assignmentContent = match.captured(2);
@ -575,17 +586,17 @@ QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers)
// Find variables that may be referenced in the assignment
gatherTemporaryVariablesFromAssignment(v, assignmentContent);
break;
}
case QShaderNode::Invalid:
break;
}
break;
}
case QShaderNode::Invalid:
break;
}
LineContent lineContent;
lineContent.rawContent = QByteArray(QByteArrayLiteral(" ") + substitutionedLine);
lineContent.var = v;
lines << lineContent;
LineContent lineContent;
lineContent.rawContent = QByteArray(QByteArrayLiteral(" ") + substitutionedLine);
lineContent.var = v;
lines << lineContent;
}
}
// Go through all lines

View File

@ -199,6 +199,7 @@ private slots:
void shouldUseGlobalVariableRatherThanTemporaries();
void shouldGenerateTemporariesWisely();
void shouldHandlePortNamesPrefixingOneAnother();
void shouldHandleNodesWithMultipleOutputPorts();
};
void tst_QShaderGenerator::shouldHaveDefaultState()
@ -1299,6 +1300,78 @@ void tst_QShaderGenerator::shouldHandlePortNamesPrefixingOneAnother()
QCOMPARE(code, expected.join("\n"));
}
void tst_QShaderGenerator::shouldHandleNodesWithMultipleOutputPorts()
{
// GIVEN
const auto gl4 = createFormat(QShaderFormat::OpenGLCoreProfile, 4, 0);
auto input = createNode({
createPort(QShaderNodePort::Output, "output0"),
createPort(QShaderNodePort::Output, "output1")
});
input.addRule(gl4, QShaderNode::Rule("vec4 $output0 = globalIn0;"
"float $output1 = globalIn1;",
QByteArrayList() << "in vec4 globalIn0;" << "in float globalIn1;"));
auto function = createNode({
createPort(QShaderNodePort::Input, "input0"),
createPort(QShaderNodePort::Input, "input1"),
createPort(QShaderNodePort::Output, "output0"),
createPort(QShaderNodePort::Output, "output1")
});
function.addRule(gl4, QShaderNode::Rule("vec4 $output0 = $input0;"
"float $output1 = $input1;"));
auto output = createNode({
createPort(QShaderNodePort::Input, "input0"),
createPort(QShaderNodePort::Input, "input1")
});
output.addRule(gl4, QShaderNode::Rule("globalOut0 = $input0;"
"globalOut1 = $input1;",
QByteArrayList() << "out vec4 globalOut0;" << "out float globalOut1;"));
// WHEN
const auto graph = [=] {
auto res = QShaderGraph();
res.addNode(input);
res.addNode(function);
res.addNode(output);
res.addEdge(createEdge(input.uuid(), "output0", function.uuid(), "input0"));
res.addEdge(createEdge(input.uuid(), "output1", function.uuid(), "input1"));
res.addEdge(createEdge(function.uuid(), "output0", output.uuid(), "input0"));
res.addEdge(createEdge(function.uuid(), "output1", output.uuid(), "input1"));
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 globalIn0;"
<< "in float globalIn1;"
<< "out vec4 globalOut0;"
<< "out float globalOut1;"
<< ""
<< "void main()"
<< "{"
<< " globalOut0 = globalIn0;"
<< " globalOut1 = globalIn1;"
<< "}"
<< "";
QCOMPARE(code, expected.join("\n"));
}
QTEST_MAIN(tst_QShaderGenerator)
#include "tst_qshadergenerator.moc"