Collect static use information during parsing
We now collect metadata for variables in the symbol table. The
metadata is stored in a map using the variable unique id as a key, so
we can store the variables themselves as constexpr while still having
dynamic metadata.
For now we collect whether a variable is statically read or written.
This can be used to more accurately determine whether a variable is
statically used, but can also enable more optimizations in the future,
such as pruning variables that are never read or folding variables
that are never written after initialization. The collection is done
during parsing, so that nothing is pruned from the AST before the
static use is recorded.
Static writes are flagged in ParseContext::checkCanBeLValue, as that
function is already called for all variables that are written.
Static reads are flagged whenever there's an operation that requires
a variable to be read. This includes:
* Unary and binary math ops
* Comma ops
* Ternary ops
* Assignments
* Returning the variable
* Passing the variable as an in or inout argument to a function
* Using the variable as a constructor argument
* Using the variable as an if statement condition
* Using the variable as a loop condition or expression
* Using the variable as an index
* Using the variable as a switch statement init expression
In case there are statements that simply refer to a variable without
doing operations on it, the variable is being treated as statically
read. Examples of such statements:
my_var;
my_arr[2];
These are a bit of a corner case, but it makes sense to treat them as
static use for validation purposes.
Collecting correct static use information costs us a bit of compiler
performance, but the regression is on the order of just a few percent
in the compiler perf tests.
BUG=angleproject:2262
TEST=angle_unittests, angle_end2end_tests
Change-Id: Ib0d7add7e4a7d11bffeb2a4861eeea982c562234
Reviewed-on: https://chromium-review.googlesource.com/977964
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/compiler/translator/CollectVariables.cpp b/src/compiler/translator/CollectVariables.cpp
index 30294dc..05196fd 100644
--- a/src/compiler/translator/CollectVariables.cpp
+++ b/src/compiler/translator/CollectVariables.cpp
@@ -68,21 +68,21 @@
return nullptr;
}
-// Note that this shouldn't be called for interface blocks - static use information is collected for
+// Note that this shouldn't be called for interface blocks - active information is collected for
// individual fields in case of interface blocks.
-void MarkStaticallyUsed(ShaderVariable *variable)
+void MarkActive(ShaderVariable *variable)
{
- if (!variable->staticUse)
+ if (!variable->active)
{
if (variable->isStruct())
{
// Conservatively assume all fields are statically used as well.
for (auto &field : variable->fields)
{
- MarkStaticallyUsed(&field);
+ MarkActive(&field);
}
}
- variable->staticUse = true;
+ ASSERT(variable->staticUse);
variable->active = true;
}
}
@@ -126,9 +126,12 @@
private:
std::string getMappedName(const TSymbol *symbol) const;
- void setFieldOrVariableProperties(const TType &type, ShaderVariable *variableOut) const;
+ void setFieldOrVariableProperties(const TType &type,
+ bool staticUse,
+ ShaderVariable *variableOut) const;
void setFieldProperties(const TType &type,
const ImmutableString &name,
+ bool staticUse,
ShaderVariable *variableOut) const;
void setCommonVariableProperties(const TType &type,
const TVariable &variable,
@@ -322,8 +325,6 @@
ASSERT(glInType.getQualifier() == EvqPerVertexIn);
InterfaceBlock info;
recordInterfaceBlock("gl_in", glInType, &info);
- info.staticUse = true;
- info.active = true;
mPerVertexInAdded = true;
mInBlocks->push_back(info);
@@ -551,15 +552,18 @@
}
if (var)
{
- MarkStaticallyUsed(var);
+ MarkActive(var);
}
}
void CollectVariablesTraverser::setFieldOrVariableProperties(const TType &type,
+ bool staticUse,
ShaderVariable *variableOut) const
{
ASSERT(variableOut);
+ variableOut->staticUse = staticUse;
+
const TStructure *structure = type.getStruct();
if (!structure)
{
@@ -582,7 +586,7 @@
// Regardless of the variable type (uniform, in/out etc.) its fields are always plain
// ShaderVariable objects.
ShaderVariable fieldVariable;
- setFieldProperties(*field->type(), field->name(), &fieldVariable);
+ setFieldProperties(*field->type(), field->name(), staticUse, &fieldVariable);
variableOut->fields.push_back(fieldVariable);
}
}
@@ -594,10 +598,11 @@
void CollectVariablesTraverser::setFieldProperties(const TType &type,
const ImmutableString &name,
+ bool staticUse,
ShaderVariable *variableOut) const
{
ASSERT(variableOut);
- setFieldOrVariableProperties(type, variableOut);
+ setFieldOrVariableProperties(type, staticUse, variableOut);
variableOut->name.assign(name.data(), name.length());
variableOut->mappedName = HashName(name, mHashFunction, nullptr).data();
}
@@ -608,7 +613,8 @@
{
ASSERT(variableOut);
- setFieldOrVariableProperties(type, variableOut);
+ variableOut->staticUse = mSymbolTable->isStaticallyUsed(variable);
+ setFieldOrVariableProperties(type, variableOut->staticUse, variableOut);
ASSERT(variable.symbolType() != SymbolType::Empty);
variableOut->name.assign(variable.name().data(), variable.name().length());
variableOut->mappedName = getMappedName(&variable);
@@ -684,6 +690,18 @@
if (instanceName != nullptr)
{
interfaceBlock->instanceName = instanceName;
+ const TSymbol *blockSymbol = nullptr;
+ if (strncmp(instanceName, "gl_in", 5u) == 0)
+ {
+ blockSymbol = mSymbolTable->getGlInVariableWithArraySize();
+ }
+ else
+ {
+ blockSymbol = mSymbolTable->findGlobal(ImmutableString(instanceName));
+ }
+ ASSERT(blockSymbol && blockSymbol->isVariable());
+ interfaceBlock->staticUse =
+ mSymbolTable->isStaticallyUsed(*static_cast<const TVariable *>(blockSymbol));
}
ASSERT(!interfaceBlockType.isArrayOfArrays()); // Disallowed by GLSL ES 3.10 section 4.3.9
interfaceBlock->arraySize = interfaceBlockType.isArray() ? interfaceBlockType.getOutermostArraySize() : 0;
@@ -699,16 +717,36 @@
}
// Gather field information
+ bool anyFieldStaticallyUsed = false;
for (const TField *field : blockType->fields())
{
const TType &fieldType = *field->type();
+ bool staticUse = false;
+ if (instanceName == nullptr)
+ {
+ // Static use of individual fields has been recorded, since they are present in the
+ // symbol table as variables.
+ const TSymbol *fieldSymbol = mSymbolTable->findGlobal(field->name());
+ ASSERT(fieldSymbol && fieldSymbol->isVariable());
+ staticUse =
+ mSymbolTable->isStaticallyUsed(*static_cast<const TVariable *>(fieldSymbol));
+ if (staticUse)
+ {
+ anyFieldStaticallyUsed = true;
+ }
+ }
+
InterfaceBlockField fieldVariable;
- setFieldProperties(fieldType, field->name(), &fieldVariable);
+ setFieldProperties(fieldType, field->name(), staticUse, &fieldVariable);
fieldVariable.isRowMajorLayout =
(fieldType.getLayoutQualifier().matrixPacking == EmpRowMajor);
interfaceBlock->fields.push_back(fieldVariable);
}
+ if (anyFieldStaticallyUsed)
+ {
+ interfaceBlock->staticUse = true;
+ }
}
Uniform CollectVariablesTraverser::recordUniform(const TIntermSymbol &variable) const
@@ -826,7 +864,7 @@
{
if (binaryNode->getOp() == EOpIndexDirectInterfaceBlock)
{
- // NOTE: we do not determine static use for individual blocks of an array
+ // NOTE: we do not determine static use / activeness for individual blocks of an array.
TIntermTyped *blockNode = binaryNode->getLeft()->getAsTyped();
ASSERT(blockNode);
@@ -860,10 +898,13 @@
namedBlock = findNamedInterfaceBlock(interfaceBlock->name());
}
ASSERT(namedBlock);
- namedBlock->staticUse = true;
+ ASSERT(namedBlock->staticUse);
namedBlock->active = true;
unsigned int fieldIndex = static_cast<unsigned int>(constantUnion->getIConst(0));
ASSERT(fieldIndex < namedBlock->fields.size());
+ // TODO(oetuaho): Would be nicer to record static use of fields of named interface blocks
+ // more accurately at parse time - now we only mark the fields statically used if they are
+ // active. http://anglebug.com/2440
namedBlock->fields[fieldIndex].staticUse = true;
namedBlock->fields[fieldIndex].active = true;
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index b4fcbb7..81c72d4 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -219,8 +219,7 @@
mGeometryShaderInvocations(0),
mGeometryShaderMaxVertices(-1),
mMaxGeometryShaderInvocations(resources.MaxGeometryShaderInvocations),
- mMaxGeometryShaderMaxVertices(resources.MaxGeometryOutputVertices),
- mGlInVariableWithArraySize(nullptr)
+ mMaxGeometryShaderMaxVertices(resources.MaxGeometryOutputVertices)
{
}
@@ -443,6 +442,36 @@
}
}
+void TParseContext::markStaticReadIfSymbol(TIntermNode *node)
+{
+ TIntermSwizzle *swizzleNode = node->getAsSwizzleNode();
+ if (swizzleNode)
+ {
+ markStaticReadIfSymbol(swizzleNode->getOperand());
+ return;
+ }
+ TIntermBinary *binaryNode = node->getAsBinaryNode();
+ if (binaryNode)
+ {
+ switch (binaryNode->getOp())
+ {
+ case EOpIndexDirect:
+ case EOpIndexIndirect:
+ case EOpIndexDirectStruct:
+ case EOpIndexDirectInterfaceBlock:
+ markStaticReadIfSymbol(binaryNode->getLeft());
+ return;
+ default:
+ return;
+ }
+ }
+ TIntermSymbol *symbolNode = node->getAsSymbolNode();
+ if (symbolNode)
+ {
+ symbolTable.markStaticRead(symbolNode->variable());
+ }
+}
+
// Both test and if necessary, spit out an error, to see if the node is really
// an l-value that can be operated on this way.
bool TParseContext::checkCanBeLValue(const TSourceLoc &line, const char *op, TIntermTyped *node)
@@ -584,6 +613,7 @@
TIntermSymbol *symNode = node->getAsSymbolNode();
if (message.empty() && symNode != nullptr)
{
+ symbolTable.markStaticWrite(symNode->variable());
return true;
}
@@ -689,6 +719,7 @@
for (TIntermNode *arg : arguments)
{
+ markStaticReadIfSymbol(arg);
const TIntermTyped *argTyped = arg->getAsTyped();
ASSERT(argTyped != nullptr);
if (type.getBasicType() != EbtStruct && IsOpaqueType(argTyped->getBasicType()))
@@ -1665,15 +1696,20 @@
{
TQualifier qual = fnCandidate->getParam(i)->getType().getQualifier();
TIntermTyped *argument = (*(fnCall->getSequence()))[i]->getAsTyped();
- if (!IsImage(argument->getBasicType()) && (IsQualifierUnspecified(qual) || qual == EvqIn ||
- qual == EvqInOut || qual == EvqConstReadOnly))
+ bool argumentIsRead = (IsQualifierUnspecified(qual) || qual == EvqIn || qual == EvqInOut ||
+ qual == EvqConstReadOnly);
+ if (argumentIsRead)
{
- if (argument->getMemoryQualifier().writeonly)
+ markStaticReadIfSymbol(argument);
+ if (!IsImage(argument->getBasicType()))
{
- error(argument->getLine(),
- "Writeonly value cannot be passed for 'in' or 'inout' parameters.",
- fnCall->functionName());
- return;
+ if (argument->getMemoryQualifier().writeonly)
+ {
+ error(argument->getLine(),
+ "Writeonly value cannot be passed for 'in' or 'inout' parameters.",
+ fnCall->functionName());
+ return;
+ }
}
}
if (qual == EvqOut || qual == EvqInOut)
@@ -1878,8 +1914,8 @@
else if ((mGeometryShaderInputPrimitiveType != EptUndefined) &&
(variableType.getQualifier() == EvqPerVertexIn))
{
- ASSERT(mGlInVariableWithArraySize != nullptr);
- node = new TIntermSymbol(mGlInVariableWithArraySize);
+ ASSERT(symbolTable.getGlInVariableWithArraySize() != nullptr);
+ node = new TIntermSymbol(symbolTable.getGlInVariableWithArraySize());
}
else
{
@@ -1994,6 +2030,7 @@
}
*initNode = new TIntermBinary(EOpInitialize, intermSymbol, initializer);
+ markStaticReadIfSymbol(initializer);
(*initNode)->setLine(line);
return true;
}
@@ -2036,8 +2073,19 @@
TIntermTyped *typedCond = nullptr;
if (cond)
{
+ markStaticReadIfSymbol(cond);
typedCond = cond->getAsTyped();
}
+ if (expr)
+ {
+ markStaticReadIfSymbol(expr);
+ }
+ // In case the loop body was not parsed as a block and contains a statement that simply refers
+ // to a variable, we need to mark it as statically used.
+ if (body)
+ {
+ markStaticReadIfSymbol(body);
+ }
if (cond == nullptr || typedCond)
{
if (type == ELoopDoWhile)
@@ -2084,6 +2132,16 @@
const TSourceLoc &loc)
{
bool isScalarBool = checkIsScalarBool(loc, cond);
+ // In case the conditional statements were not parsed as blocks and contain a statement that
+ // simply refers to a variable, we need to mark them as statically used.
+ if (code.node1)
+ {
+ markStaticReadIfSymbol(code.node1);
+ }
+ if (code.node2)
+ {
+ markStaticReadIfSymbol(code.node2);
+ }
// For compile time constant conditions, prune the code now.
if (isScalarBool && cond->getAsConstantUnion())
@@ -2099,6 +2157,7 @@
}
TIntermIfElse *node = new TIntermIfElse(cond, EnsureBlock(code.node1), EnsureBlock(code.node2));
+ markStaticReadIfSymbol(cond);
node->setLine(loc);
return node;
@@ -2346,9 +2405,9 @@
// input primitive declaration.
if (mGeometryShaderInputPrimitiveType != EptUndefined)
{
- ASSERT(mGlInVariableWithArraySize != nullptr);
+ ASSERT(symbolTable.getGlInVariableWithArraySize() != nullptr);
type->sizeOutermostUnsizedArray(
- mGlInVariableWithArraySize->getType().getOutermostArraySize());
+ symbolTable.getGlInVariableWithArraySize()->getType().getOutermostArraySize());
}
else
{
@@ -2818,17 +2877,7 @@
void TParseContext::setGeometryShaderInputArraySize(unsigned int inputArraySize,
const TSourceLoc &line)
{
- if (mGlInVariableWithArraySize == nullptr)
- {
- const TSymbol *glPerVertex = symbolTable.findBuiltIn(ImmutableString("gl_PerVertex"), 310);
- const TInterfaceBlock *glPerVertexBlock = static_cast<const TInterfaceBlock *>(glPerVertex);
- TType *glInType = new TType(glPerVertexBlock, EvqPerVertexIn, TLayoutQualifier::Create());
- glInType->makeArray(inputArraySize);
- mGlInVariableWithArraySize =
- new TVariable(&symbolTable, ImmutableString("gl_in"), glInType, SymbolType::BuiltIn,
- TExtension::EXT_geometry_shader);
- }
- else if (mGlInVariableWithArraySize->getType().getOutermostArraySize() != inputArraySize)
+ if (!symbolTable.setGlInArraySize(inputArraySize))
{
error(line,
"Array size or input primitive declaration doesn't match the size of earlier sized "
@@ -4010,6 +4059,7 @@
}
}
+ markStaticReadIfSymbol(indexExpression);
TIntermBinary *node = new TIntermBinary(EOpIndexIndirect, baseExpression, indexExpression);
node->setLine(location);
// Indirect indexing can never be constant folded.
@@ -4793,6 +4843,7 @@
return nullptr;
}
+ markStaticReadIfSymbol(init);
TIntermSwitch *node = new TIntermSwitch(init, statementList);
node->setLine(loc);
return node;
@@ -4889,6 +4940,7 @@
return nullptr;
}
+ markStaticReadIfSymbol(child);
TIntermUnary *node = new TIntermUnary(op, child);
node->setLine(loc);
@@ -5292,6 +5344,9 @@
}
TIntermBinary *node = new TIntermBinary(op, left, right);
+ ASSERT(op != EOpAssign);
+ markStaticReadIfSymbol(left);
+ markStaticReadIfSymbol(right);
node->setLine(loc);
return expressionOrFoldedResult(node);
}
@@ -5354,6 +5409,11 @@
assignError(loc, "assign", left->getCompleteString(), right->getCompleteString());
return left;
}
+ if (op != EOpAssign)
+ {
+ markStaticReadIfSymbol(left);
+ }
+ markStaticReadIfSymbol(right);
node->setLine(loc);
return node;
}
@@ -5375,6 +5435,9 @@
}
TIntermBinary *commaNode = TIntermBinary::CreateComma(left, right, mShaderVersion);
+ markStaticReadIfSymbol(left);
+ markStaticReadIfSymbol(right);
+ commaNode->setLine(loc);
return expressionOrFoldedResult(commaNode);
}
@@ -5420,6 +5483,7 @@
{
if (expression != nullptr)
{
+ markStaticReadIfSymbol(expression);
ASSERT(op == EOpReturn);
mFunctionReturnsValue = true;
if (mCurrentFunctionType->getBasicType() == EbtVoid)
@@ -5436,6 +5500,15 @@
return node;
}
+void TParseContext::appendStatement(TIntermBlock *block, TIntermNode *statement)
+{
+ if (statement != nullptr)
+ {
+ markStaticReadIfSymbol(statement);
+ block->appendStatement(statement);
+ }
+}
+
void TParseContext::checkTextureGather(TIntermAggregate *functionCall)
{
ASSERT(functionCall->getOp() == EOpCallBuiltInFunction);
@@ -5895,6 +5968,9 @@
}
TIntermTernary *node = new TIntermTernary(cond, trueExpression, falseExpression);
+ markStaticReadIfSymbol(cond);
+ markStaticReadIfSymbol(trueExpression);
+ markStaticReadIfSymbol(falseExpression);
node->setLine(loc);
return expressionOrFoldedResult(node);
}
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index 4dc69e9..0c05120 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -419,6 +419,8 @@
TIntermBranch *addBranch(TOperator op, const TSourceLoc &loc);
TIntermBranch *addBranch(TOperator op, TIntermTyped *expression, const TSourceLoc &loc);
+ void appendStatement(TIntermBlock *block, TIntermNode *statement);
+
void checkTextureGather(TIntermAggregate *functionCall);
void checkTextureOffsetConst(TIntermAggregate *functionCall);
void checkImageMemoryAccessForBuiltinFunctions(TIntermAggregate *functionCall);
@@ -464,6 +466,8 @@
// Note that there may be tests in AtomicCounter_test that will need to be updated as well.
constexpr static size_t kAtomicCounterArrayStride = 4;
+ void markStaticReadIfSymbol(TIntermNode *node);
+
// Returns a clamped index. If it prints out an error message, the token is "[]".
int checkIndexLessThan(bool outOfRangeIndexIsError,
const TSourceLoc &location,
@@ -638,10 +642,6 @@
int mGeometryShaderMaxVertices;
int mMaxGeometryShaderInvocations;
int mMaxGeometryShaderMaxVertices;
-
- // Store gl_in variable with its array size once the array size can be determined. The array
- // size can also be checked against latter input primitive type declaration.
- const TVariable *mGlInVariableWithArraySize;
};
int PaParseStrings(size_t count,
diff --git a/src/compiler/translator/SymbolTable.cpp b/src/compiler/translator/SymbolTable.cpp
index efc1d8d..228aa12 100644
--- a/src/compiler/translator/SymbolTable.cpp
+++ b/src/compiler/translator/SymbolTable.cpp
@@ -79,7 +79,8 @@
return (*it).second;
}
-TSymbolTable::TSymbolTable() : mUniqueIdCounter(0), mShaderType(GL_FRAGMENT_SHADER)
+TSymbolTable::TSymbolTable()
+ : mUniqueIdCounter(0), mShaderType(GL_FRAGMENT_SHADER), mGlInVariableWithArraySize(nullptr)
{
}
@@ -137,6 +138,56 @@
return firstDeclaration;
}
+bool TSymbolTable::setGlInArraySize(unsigned int inputArraySize)
+{
+ if (mGlInVariableWithArraySize)
+ {
+ return mGlInVariableWithArraySize->getType().getOutermostArraySize() == inputArraySize;
+ }
+ const TInterfaceBlock *glPerVertex = mVar_gl_PerVertex;
+ TType *glInType = new TType(glPerVertex, EvqPerVertexIn, TLayoutQualifier::Create());
+ glInType->makeArray(inputArraySize);
+ mGlInVariableWithArraySize =
+ new TVariable(this, ImmutableString("gl_in"), glInType, SymbolType::BuiltIn,
+ TExtension::EXT_geometry_shader);
+ return true;
+}
+
+TVariable *TSymbolTable::getGlInVariableWithArraySize() const
+{
+ return mGlInVariableWithArraySize;
+}
+
+void TSymbolTable::markStaticWrite(const TVariable &variable)
+{
+ int id = variable.uniqueId().get();
+ auto iter = mVariableMetadata.find(id);
+ if (iter == mVariableMetadata.end())
+ {
+ iter = mVariableMetadata.insert(std::make_pair(id, VariableMetadata())).first;
+ }
+ iter->second.staticWrite = true;
+}
+
+void TSymbolTable::markStaticRead(const TVariable &variable)
+{
+ int id = variable.uniqueId().get();
+ auto iter = mVariableMetadata.find(id);
+ if (iter == mVariableMetadata.end())
+ {
+ iter = mVariableMetadata.insert(std::make_pair(id, VariableMetadata())).first;
+ }
+ iter->second.staticRead = true;
+}
+
+bool TSymbolTable::isStaticallyUsed(const TVariable &variable) const
+{
+ ASSERT(!variable.getConstPointer());
+ int id = variable.uniqueId().get();
+ auto iter = mVariableMetadata.find(id);
+ return iter != mVariableMetadata.end();
+}
+
const TSymbol *TSymbolTable::find(const ImmutableString &name, int shaderVersion) const
{
int userDefinedLevel = static_cast<int>(mTable.size()) - 1;
@@ -238,6 +289,8 @@
void TSymbolTable::clearCompilationResults()
{
mUniqueIdCounter = kLastBuiltInId + 1;
+ mVariableMetadata.clear();
+ mGlInVariableWithArraySize = nullptr;
// User-defined scopes should have already been cleared when the compilation finished.
ASSERT(mTable.size() == 0u);
@@ -297,4 +350,8 @@
setDefaultPrecision(samplerType, EbpLow);
}
+TSymbolTable::VariableMetadata::VariableMetadata() : staticRead(false), staticWrite(false)
+{
+}
+
} // namespace sh
diff --git a/src/compiler/translator/SymbolTable.h b/src/compiler/translator/SymbolTable.h
index 256e048..baf2338 100644
--- a/src/compiler/translator/SymbolTable.h
+++ b/src/compiler/translator/SymbolTable.h
@@ -90,6 +90,16 @@
const TFunction *setFunctionParameterNamesFromDefinition(const TFunction *function,
bool *wasDefinedOut);
+ // Return false if the gl_in array size has already been initialized with a mismatching value.
+ bool setGlInArraySize(unsigned int inputArraySize);
+ TVariable *getGlInVariableWithArraySize() const;
+
+ void markStaticRead(const TVariable &variable);
+ void markStaticWrite(const TVariable &variable);
+
+ // Note: Should not call this for constant variables.
+ bool isStaticallyUsed(const TVariable &variable) const;
+
// find() is guaranteed not to retain a reference to the ImmutableString, so an ImmutableString
// with a reference to a short-lived char * is fine to pass here.
const TSymbol *find(const ImmutableString &name, int shaderVersion) const;
@@ -154,6 +164,20 @@
sh::GLenum mShaderType;
ShBuiltInResources mResources;
+
+ struct VariableMetadata
+ {
+ VariableMetadata();
+ bool staticRead;
+ bool staticWrite;
+ };
+
+ // Indexed by unique id. Map instead of vector since the variables are fairly sparse.
+ std::map<int, VariableMetadata> mVariableMetadata;
+
+ // Store gl_in variable with its array size once the array size can be determined. The array
+ // size can also be checked against latter input primitive type declaration.
+ TVariable *mGlInVariableWithArraySize;
};
} // namespace sh
diff --git a/src/compiler/translator/glslang.y b/src/compiler/translator/glslang.y
index d3935fc..499fda2 100644
--- a/src/compiler/translator/glslang.y
+++ b/src/compiler/translator/glslang.y
@@ -1304,11 +1304,11 @@
statement_list
: statement {
$$ = new TIntermBlock();
- $$->appendStatement($1);
+ context->appendStatement($$, $1);
}
| statement_list statement {
$$ = $1;
- $$->appendStatement($2);
+ context->appendStatement($$, $2);
}
;
diff --git a/src/compiler/translator/glslang_tab.cpp b/src/compiler/translator/glslang_tab.cpp
index 3bdc065..f974748 100644
--- a/src/compiler/translator/glslang_tab.cpp
+++ b/src/compiler/translator/glslang_tab.cpp
@@ -4630,7 +4630,7 @@
{
(yyval.interm.intermBlock) = new TIntermBlock();
- (yyval.interm.intermBlock)->appendStatement((yyvsp[0].interm.intermNode));
+ context->appendStatement((yyval.interm.intermBlock), (yyvsp[0].interm.intermNode));
}
break;
@@ -4639,7 +4639,7 @@
{
(yyval.interm.intermBlock) = (yyvsp[-1].interm.intermBlock);
- (yyval.interm.intermBlock)->appendStatement((yyvsp[0].interm.intermNode));
+ context->appendStatement((yyval.interm.intermBlock), (yyvsp[0].interm.intermNode));
}
break;
diff --git a/src/libANGLE/VaryingPacking.cpp b/src/libANGLE/VaryingPacking.cpp
index 6bc8ed5..e88c019 100644
--- a/src/libANGLE/VaryingPacking.cpp
+++ b/src/libANGLE/VaryingPacking.cpp
@@ -292,13 +292,13 @@
const sh::Varying *output = ref.second.fragment;
// Only pack statically used varyings that have a matched input or output, plus special
- // builtins. Note that we pack all statically used varyings even if they are not active.
- // GLES specs are a bit vague on whether it's allowed to only pack active varyings, though
- // GLES 3.1 spec section 11.1.2.1 says that "device-dependent optimizations" may be used to
- // make vertex shader outputs fit.
+ // builtins. Note that we pack all statically used user-defined varyings even if they are
+ // not active. GLES specs are a bit vague on whether it's allowed to only pack active
+ // varyings, though GLES 3.1 spec section 11.1.2.1 says that "device-dependent
+ // optimizations" may be used to make vertex shader outputs fit.
if ((input && output && output->staticUse) ||
- (input && input->isBuiltIn() && input->staticUse) ||
- (output && output->isBuiltIn() && output->staticUse))
+ (input && input->isBuiltIn() && input->active) ||
+ (output && output->isBuiltIn() && output->active))
{
const sh::Varying *varying = output ? output : input;
diff --git a/src/libANGLE/renderer/d3d/DynamicHLSL.cpp b/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
index 854a5c4..879fe76 100644
--- a/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
+++ b/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
@@ -793,8 +793,8 @@
ASSERT(!varying.isBuiltIn() && !varying.isStruct());
// Don't reference VS-only transform feedback varyings in the PS. Note that we're relying on
- // that the staticUse flag is set according to usage in the fragment shader.
- if (packedVarying.vertexOnly || !varying.staticUse)
+ // that the active flag is set according to usage in the fragment shader.
+ if (packedVarying.vertexOnly || !varying.active)
continue;
pixelStream << " ";
diff --git a/src/tests/compiler_tests/CollectVariables_test.cpp b/src/tests/compiler_tests/CollectVariables_test.cpp
index d1d252f..f2812e1 100644
--- a/src/tests/compiler_tests/CollectVariables_test.cpp
+++ b/src/tests/compiler_tests/CollectVariables_test.cpp
@@ -113,6 +113,7 @@
const OutputVariable &outputVariable = outputVariables[varIndex];
EXPECT_EQ(-1, outputVariable.location);
EXPECT_TRUE(outputVariable.staticUse);
+ EXPECT_TRUE(outputVariable.active);
EXPECT_EQ(varName, outputVariable.name);
*outResult = &outputVariable;
}
@@ -125,6 +126,17 @@
void compile(const std::string &shaderString) { compile(shaderString, 0u); }
+ void checkUniformStaticallyUsedButNotActive(const char *name)
+ {
+ const auto &uniforms = mTranslator->getUniforms();
+ ASSERT_EQ(1u, uniforms.size());
+
+ const Uniform &uniform = uniforms[0];
+ EXPECT_EQ(name, uniform.name);
+ EXPECT_TRUE(uniform.staticUse);
+ EXPECT_FALSE(uniform.active);
+ }
+
::GLenum mShaderType;
std::unique_ptr<TranslatorGLSL> mTranslator;
};
@@ -247,6 +259,7 @@
EXPECT_EQ(-1, outputVariable.location);
EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable.precision);
EXPECT_TRUE(outputVariable.staticUse);
+ EXPECT_TRUE(outputVariable.active);
EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, outputVariable.type);
EXPECT_EQ("out_fragColor", outputVariable.name);
}
@@ -272,6 +285,7 @@
EXPECT_EQ(5, outputVariable.location);
EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable.precision);
EXPECT_TRUE(outputVariable.staticUse);
+ EXPECT_TRUE(outputVariable.active);
EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, outputVariable.type);
EXPECT_EQ("out_fragColor", outputVariable.name);
}
@@ -296,6 +310,7 @@
EXPECT_EQ(5, attribute.location);
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, attribute.precision);
EXPECT_TRUE(attribute.staticUse);
+ EXPECT_TRUE(attribute.active);
EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, attribute.type);
EXPECT_EQ("in_Position", attribute.name);
}
@@ -322,6 +337,7 @@
EXPECT_EQ(BLOCKLAYOUT_SHARED, interfaceBlock.layout);
EXPECT_EQ("b", interfaceBlock.name);
EXPECT_TRUE(interfaceBlock.staticUse);
+ EXPECT_TRUE(interfaceBlock.active);
ASSERT_EQ(1u, interfaceBlock.fields.size());
@@ -329,6 +345,7 @@
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
EXPECT_TRUE(field.staticUse);
+ EXPECT_TRUE(field.active);
EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
EXPECT_EQ("f", field.name);
EXPECT_FALSE(field.isRowMajorLayout);
@@ -358,6 +375,7 @@
EXPECT_EQ("b", interfaceBlock.name);
EXPECT_EQ("blockInstance", interfaceBlock.instanceName);
EXPECT_TRUE(interfaceBlock.staticUse);
+ EXPECT_TRUE(interfaceBlock.active);
ASSERT_EQ(1u, interfaceBlock.fields.size());
@@ -365,6 +383,7 @@
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
EXPECT_TRUE(field.staticUse);
+ EXPECT_TRUE(field.active);
EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
EXPECT_EQ("f", field.name);
EXPECT_FALSE(field.isRowMajorLayout);
@@ -395,25 +414,27 @@
EXPECT_EQ("b", interfaceBlock.name);
EXPECT_EQ(DecorateName("b"), interfaceBlock.mappedName);
EXPECT_TRUE(interfaceBlock.staticUse);
+ EXPECT_TRUE(interfaceBlock.active);
ASSERT_EQ(1u, interfaceBlock.fields.size());
- const InterfaceBlockField &field = interfaceBlock.fields[0];
+ const InterfaceBlockField &blockField = interfaceBlock.fields[0];
- EXPECT_TRUE(field.isStruct());
- EXPECT_TRUE(field.staticUse);
- EXPECT_EQ("s", field.name);
- EXPECT_EQ(DecorateName("s"), field.mappedName);
- EXPECT_FALSE(field.isRowMajorLayout);
+ EXPECT_TRUE(blockField.isStruct());
+ EXPECT_TRUE(blockField.staticUse);
+ EXPECT_TRUE(blockField.active);
+ EXPECT_EQ("s", blockField.name);
+ EXPECT_EQ(DecorateName("s"), blockField.mappedName);
+ EXPECT_FALSE(blockField.isRowMajorLayout);
- const ShaderVariable &member = field.fields[0];
+ const ShaderVariable &structField = blockField.fields[0];
- // NOTE: we don't currently mark struct members as statically used or not
- EXPECT_FALSE(member.isStruct());
- EXPECT_EQ("f", member.name);
- EXPECT_EQ(DecorateName("f"), member.mappedName);
- EXPECT_GLENUM_EQ(GL_FLOAT, member.type);
- EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, member.precision);
+ // NOTE: we don't track static use or active at individual struct member granularity.
+ EXPECT_FALSE(structField.isStruct());
+ EXPECT_EQ("f", structField.name);
+ EXPECT_EQ(DecorateName("f"), structField.mappedName);
+ EXPECT_GLENUM_EQ(GL_FLOAT, structField.type);
+ EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, structField.precision);
}
TEST_F(CollectVertexVariablesTest, StructInstancedInterfaceBlock)
@@ -441,25 +462,27 @@
EXPECT_EQ(DecorateName("b"), interfaceBlock.mappedName);
EXPECT_EQ("instanceName", interfaceBlock.instanceName);
EXPECT_TRUE(interfaceBlock.staticUse);
+ EXPECT_TRUE(interfaceBlock.active);
ASSERT_EQ(1u, interfaceBlock.fields.size());
- const InterfaceBlockField &field = interfaceBlock.fields[0];
+ const InterfaceBlockField &blockField = interfaceBlock.fields[0];
- EXPECT_TRUE(field.isStruct());
- EXPECT_TRUE(field.staticUse);
- EXPECT_EQ("s", field.name);
- EXPECT_EQ(DecorateName("s"), field.mappedName);
- EXPECT_FALSE(field.isRowMajorLayout);
+ EXPECT_TRUE(blockField.isStruct());
+ EXPECT_TRUE(blockField.staticUse);
+ EXPECT_TRUE(blockField.active);
+ EXPECT_EQ("s", blockField.name);
+ EXPECT_EQ(DecorateName("s"), blockField.mappedName);
+ EXPECT_FALSE(blockField.isRowMajorLayout);
- const ShaderVariable &member = field.fields[0];
+ const ShaderVariable &structField = blockField.fields[0];
- // NOTE: we don't currently mark struct members as statically used or not
- EXPECT_FALSE(member.isStruct());
- EXPECT_EQ("f", member.name);
- EXPECT_EQ(DecorateName("f"), member.mappedName);
- EXPECT_GLENUM_EQ(GL_FLOAT, member.type);
- EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, member.precision);
+ // NOTE: we don't track static use or active at individual struct member granularity.
+ EXPECT_FALSE(structField.isStruct());
+ EXPECT_EQ("f", structField.name);
+ EXPECT_EQ(DecorateName("f"), structField.mappedName);
+ EXPECT_GLENUM_EQ(GL_FLOAT, structField.type);
+ EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, structField.precision);
}
TEST_F(CollectVertexVariablesTest, NestedStructRowMajorInterfaceBlock)
@@ -486,25 +509,27 @@
EXPECT_EQ("b", interfaceBlock.name);
EXPECT_EQ(DecorateName("b"), interfaceBlock.mappedName);
EXPECT_TRUE(interfaceBlock.staticUse);
+ EXPECT_TRUE(interfaceBlock.active);
ASSERT_EQ(1u, interfaceBlock.fields.size());
- const InterfaceBlockField &field = interfaceBlock.fields[0];
+ const InterfaceBlockField &blockField = interfaceBlock.fields[0];
- EXPECT_TRUE(field.isStruct());
- EXPECT_TRUE(field.staticUse);
- EXPECT_EQ("s", field.name);
- EXPECT_EQ(DecorateName("s"), field.mappedName);
- EXPECT_TRUE(field.isRowMajorLayout);
+ EXPECT_TRUE(blockField.isStruct());
+ EXPECT_TRUE(blockField.staticUse);
+ EXPECT_TRUE(blockField.active);
+ EXPECT_EQ("s", blockField.name);
+ EXPECT_EQ(DecorateName("s"), blockField.mappedName);
+ EXPECT_TRUE(blockField.isRowMajorLayout);
- const ShaderVariable &member = field.fields[0];
+ const ShaderVariable &structField = blockField.fields[0];
- // NOTE: we don't currently mark struct members as statically used or not
- EXPECT_FALSE(member.isStruct());
- EXPECT_EQ("m", member.name);
- EXPECT_EQ(DecorateName("m"), member.mappedName);
- EXPECT_GLENUM_EQ(GL_FLOAT_MAT2, member.type);
- EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, member.precision);
+ // NOTE: we don't track static use or active at individual struct member granularity.
+ EXPECT_FALSE(structField.isStruct());
+ EXPECT_EQ("m", structField.name);
+ EXPECT_EQ(DecorateName("m"), structField.mappedName);
+ EXPECT_GLENUM_EQ(GL_FLOAT_MAT2, structField.type);
+ EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, structField.precision);
}
TEST_F(CollectVertexVariablesTest, VaryingInterpolation)
@@ -533,6 +558,7 @@
EXPECT_FALSE(varying->isArray());
EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, varying->precision);
EXPECT_TRUE(varying->staticUse);
+ EXPECT_TRUE(varying->active);
EXPECT_GLENUM_EQ(GL_FLOAT, varying->type);
EXPECT_EQ("vary", varying->name);
EXPECT_EQ(DecorateName("vary"), varying->mappedName);
@@ -795,6 +821,7 @@
EXPECT_EQ("blockInstance", interfaceBlock.instanceName);
EXPECT_EQ("webgl_9", interfaceBlock.mappedName);
EXPECT_TRUE(interfaceBlock.staticUse);
+ EXPECT_TRUE(interfaceBlock.active);
ASSERT_EQ(1u, interfaceBlock.fields.size());
@@ -802,6 +829,7 @@
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
EXPECT_TRUE(field.staticUse);
+ EXPECT_TRUE(field.active);
EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
EXPECT_EQ("field", field.name);
EXPECT_EQ("webgl_5", field.mappedName);
@@ -837,13 +865,17 @@
EXPECT_EQ("webgl_1", uniform.mappedName);
EXPECT_EQ("sType", uniform.structName);
EXPECT_TRUE(uniform.staticUse);
+ EXPECT_TRUE(uniform.active);
ASSERT_EQ(1u, uniform.fields.size());
const ShaderVariable &field = uniform.fields[0];
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
- // EXPECT_TRUE(field.staticUse); // we don't yet support struct static use
+ // We don't yet support tracking static use per field, but fields are marked statically used in
+ // case the struct is.
+ EXPECT_TRUE(field.staticUse);
+ EXPECT_TRUE(field.active);
EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
EXPECT_EQ("field", field.name);
EXPECT_EQ("webgl_5", field.mappedName);
@@ -877,13 +909,17 @@
EXPECT_EQ("webgl_1", uniform.mappedName);
EXPECT_EQ("", uniform.structName);
EXPECT_TRUE(uniform.staticUse);
+ EXPECT_TRUE(uniform.active);
ASSERT_EQ(1u, uniform.fields.size());
const ShaderVariable &field = uniform.fields[0];
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
- // EXPECT_TRUE(field.staticUse); // we don't yet support struct static use
+ // We don't yet support tracking static use per field, but fields are marked statically used in
+ // case the struct is.
+ EXPECT_TRUE(field.staticUse);
+ EXPECT_TRUE(field.active);
EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
EXPECT_EQ("field", field.name);
EXPECT_EQ("webgl_5", field.mappedName);
@@ -913,6 +949,7 @@
EXPECT_FALSE(uniform.isArray());
EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, uniform.precision);
EXPECT_TRUE(uniform.staticUse);
+ EXPECT_TRUE(uniform.active);
EXPECT_GLENUM_EQ(GL_FLOAT, uniform.type);
EXPECT_EQ("uA", uniform.name);
@@ -920,6 +957,7 @@
EXPECT_FALSE(uniformB.isArray());
EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, uniformB.precision);
EXPECT_TRUE(uniformB.staticUse);
+ EXPECT_TRUE(uniformB.active);
EXPECT_GLENUM_EQ(GL_FLOAT, uniformB.type);
EXPECT_EQ("uB", uniformB.name);
}
@@ -946,6 +984,7 @@
EXPECT_FALSE(uniformB.isArray());
EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, uniformB.precision);
EXPECT_TRUE(uniformB.staticUse);
+ EXPECT_TRUE(uniformB.active);
EXPECT_GLENUM_EQ(GL_FLOAT, uniformB.type);
EXPECT_EQ("uB", uniformB.name);
}
@@ -1008,6 +1047,7 @@
EXPECT_EQ("gl_PerVertex", inBlock->name);
EXPECT_EQ("gl_in", inBlock->instanceName);
EXPECT_TRUE(inBlock->staticUse);
+ EXPECT_TRUE(inBlock->active);
EXPECT_TRUE(inBlock->isBuiltIn());
ASSERT_EQ(1u, inBlock->fields.size());
@@ -1017,6 +1057,7 @@
EXPECT_FALSE(glPositionField.isArray());
EXPECT_FALSE(glPositionField.isStruct());
EXPECT_TRUE(glPositionField.staticUse);
+ EXPECT_TRUE(glPositionField.active);
EXPECT_TRUE(glPositionField.isBuiltIn());
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, glPositionField.precision);
EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, glPositionField.type);
@@ -1076,6 +1117,7 @@
EXPECT_FALSE(varying->isArray());
EXPECT_FALSE(varying->isStruct());
EXPECT_TRUE(varying->staticUse);
+ EXPECT_TRUE(varying->active);
EXPECT_TRUE(varying->isBuiltIn());
EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
EXPECT_GLENUM_EQ(GL_INT, varying->type);
@@ -1108,6 +1150,7 @@
EXPECT_FALSE(varying->isArray());
EXPECT_FALSE(varying->isStruct());
EXPECT_TRUE(varying->staticUse);
+ EXPECT_TRUE(varying->active);
EXPECT_TRUE(varying->isBuiltIn());
EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
EXPECT_GLENUM_EQ(GL_INT, varying->type);
@@ -1169,6 +1212,7 @@
EXPECT_FALSE(varying->isArray());
EXPECT_FALSE(varying->isStruct());
EXPECT_TRUE(varying->staticUse);
+ EXPECT_TRUE(varying->active);
EXPECT_TRUE(varying->isBuiltIn());
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, varying->precision);
EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, varying->type);
@@ -1200,6 +1244,7 @@
EXPECT_FALSE(varying->isArray());
EXPECT_FALSE(varying->isStruct());
EXPECT_TRUE(varying->staticUse);
+ EXPECT_TRUE(varying->active);
EXPECT_TRUE(varying->isBuiltIn());
EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
EXPECT_GLENUM_EQ(GL_INT, varying->type);
@@ -1231,6 +1276,7 @@
EXPECT_FALSE(varying->isArray());
EXPECT_FALSE(varying->isStruct());
EXPECT_TRUE(varying->staticUse);
+ EXPECT_TRUE(varying->active);
EXPECT_TRUE(varying->isBuiltIn());
EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
EXPECT_GLENUM_EQ(GL_INT, varying->type);
@@ -1262,6 +1308,7 @@
EXPECT_FALSE(varying->isArray());
EXPECT_FALSE(varying->isStruct());
EXPECT_TRUE(varying->staticUse);
+ EXPECT_TRUE(varying->active);
EXPECT_TRUE(varying->isBuiltIn());
EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
EXPECT_GLENUM_EQ(GL_INT, varying->type);
@@ -1293,6 +1340,7 @@
EXPECT_FALSE(varying->isArray());
EXPECT_FALSE(varying->isStruct());
EXPECT_TRUE(varying->staticUse);
+ EXPECT_TRUE(varying->active);
EXPECT_TRUE(varying->isBuiltIn());
EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
EXPECT_GLENUM_EQ(GL_INT, varying->type);
@@ -1385,6 +1433,7 @@
EXPECT_TRUE(varying.isArray());
EXPECT_FALSE(varying.isStruct());
EXPECT_TRUE(varying.staticUse);
+ EXPECT_TRUE(varying.active);
EXPECT_FALSE(varying.isBuiltIn());
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, varying.precision);
EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, varying.type);
@@ -1548,8 +1597,442 @@
EXPECT_FALSE(varying->isArray());
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, varying->precision);
EXPECT_TRUE(varying->staticUse);
+ EXPECT_TRUE(varying->active);
EXPECT_GLENUM_EQ(GL_FLOAT, varying->type);
EXPECT_EQ("vary", varying->name);
EXPECT_EQ(DecorateName("vary"), varying->mappedName);
EXPECT_EQ(INTERPOLATION_CENTROID, varying->interpolation);
}
+
+// Test a variable that is statically used but not active. The variable is used in a branch of a
+// ternary op that is not evaluated.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveInTernaryOp)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform float u;
+ void main()
+ {
+ out_fragColor = vec4(true ? 0.0 : u);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is a return value in an
+// unused function.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsReturnValue)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform float u;
+ float f() {
+ return u;
+ }
+ void main()
+ {
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is an if statement condition
+// inside a block that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsIfCondition)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform bool u;
+ void main()
+ {
+ if (false) {
+ if (u) {
+ out_fragColor = vec4(1.0);
+ }
+ }
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is a constructor argument in
+// a block that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsConstructorArgument)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform float u;
+ void main()
+ {
+ if (false) {
+ out_fragColor = vec4(u);
+ }
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is a binary operator operand
+// in a block that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsBinaryOpOperand)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform vec4 u;
+ void main()
+ {
+ if (false) {
+ out_fragColor = u + 1.0;
+ }
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is a comparison operator
+// operand in a block that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsComparisonOpOperand)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform vec4 u;
+ void main()
+ {
+ if (false) {
+ if (u == vec4(1.0))
+ {
+ out_fragColor = vec4(1.0);
+ }
+ }
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is an unary operator operand
+// in a block that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsUnaryOpOperand)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform vec4 u;
+ void main()
+ {
+ if (false) {
+ out_fragColor = -u;
+ }
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is an rvalue in an assigment
+// in a block that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsAssignmentRValue)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform vec4 u;
+ void main()
+ {
+ if (false) {
+ out_fragColor = u;
+ }
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is a comma operator operand
+// in a block that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsCommaOperand)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform vec4 u;
+ void main()
+ {
+ if (false) {
+ out_fragColor = u, vec4(1.0);
+ }
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is a switch init statement
+// in a block that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsSwitchInitStatement)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform int u;
+ void main()
+ {
+ if (false)
+ {
+ switch (u)
+ {
+ case 1:
+ out_fragColor = vec4(2.0);
+ default:
+ out_fragColor = vec4(1.0);
+ }
+ }
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is a loop condition in a
+// block that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsLoopCondition)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform bool u;
+ void main()
+ {
+ int counter = 0;
+ if (false)
+ {
+ while (u)
+ {
+ if (++counter > 2)
+ {
+ break;
+ }
+ }
+ }
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is a loop expression in a
+// block that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsLoopExpression)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform bool u;
+ void main()
+ {
+ if (false)
+ {
+ for (int i = 0; i < 3; u)
+ {
+ ++i;
+ }
+ }
+ out_fragColor = vec4(0.0);
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is a vector index in a block
+// that is not executed.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsVectorIndex)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform int u;
+ void main()
+ {
+ vec4 color = vec4(0.0);
+ if (false)
+ {
+ color[u] = 1.0;
+ }
+ out_fragColor = color;
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is referenced in a block
+// that's not executed. This is a bit of a corner case with some room for interpretation, but we
+// treat the variable as statically used.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveJustAReference)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform int u;
+ void main()
+ {
+ vec4 color = vec4(0.0);
+ if (false)
+ {
+ u;
+ }
+ out_fragColor = color;
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is statically used but not active. The variable is referenced in a block
+// without braces that's not executed. This is a bit of a corner case with some room for
+// interpretation, but we treat the variable as statically used.
+TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveJustAReferenceNoBracesIf)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform int u;
+ void main()
+ {
+ vec4 color = vec4(0.0);
+ if (false)
+ u;
+ out_fragColor = color;
+ })";
+
+ compile(shaderString);
+ checkUniformStaticallyUsedButNotActive("u");
+}
+
+// Test a variable that is referenced in a loop body without braces.
+TEST_F(CollectFragmentVariablesTest, JustAVariableReferenceInNoBracesLoop)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ precision mediump float;
+ out vec4 out_fragColor;
+ uniform int u;
+ void main()
+ {
+ vec4 color = vec4(0.0);
+ while (false)
+ u;
+ out_fragColor = color;
+ })";
+
+ compile(shaderString);
+
+ const auto &uniforms = mTranslator->getUniforms();
+ ASSERT_EQ(1u, uniforms.size());
+
+ const Uniform &uniform = uniforms[0];
+ EXPECT_EQ("u", uniform.name);
+ EXPECT_TRUE(uniform.staticUse);
+ // Note that we don't check the active flag here - the usage of the uniform is not currently
+ // being optimized away.
+}
+
+// Test an interface block member variable that is statically used but not active.
+TEST_F(CollectVertexVariablesTest, StaticallyUsedButNotActiveSimpleInterfaceBlock)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ uniform b
+ {
+ float f;
+ };
+ void main() {
+ gl_Position = vec4(true ? 0.0 : f);
+ })";
+
+ compile(shaderString);
+
+ const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
+ ASSERT_EQ(1u, interfaceBlocks.size());
+ const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
+
+ EXPECT_EQ("b", interfaceBlock.name);
+ EXPECT_TRUE(interfaceBlock.staticUse);
+ EXPECT_FALSE(interfaceBlock.active);
+
+ ASSERT_EQ(1u, interfaceBlock.fields.size());
+ const InterfaceBlockField &field = interfaceBlock.fields[0];
+
+ EXPECT_EQ("f", field.name);
+ EXPECT_TRUE(field.staticUse);
+ EXPECT_FALSE(field.active);
+}
+
+// Test an interface block instance variable that is statically used but not active.
+TEST_F(CollectVertexVariablesTest, StaticallyUsedButNotActiveInstancedInterfaceBlock)
+{
+ const std::string &shaderString =
+ R"(#version 300 es
+ uniform b
+ {
+ float f;
+ } blockInstance;
+ void main() {
+ gl_Position = vec4(true ? 0.0 : blockInstance.f);
+ })";
+
+ compile(shaderString);
+
+ const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
+ ASSERT_EQ(1u, interfaceBlocks.size());
+ const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
+
+ EXPECT_EQ("b", interfaceBlock.name);
+ EXPECT_TRUE(interfaceBlock.staticUse);
+ EXPECT_FALSE(interfaceBlock.active);
+
+ ASSERT_EQ(1u, interfaceBlock.fields.size());
+ const InterfaceBlockField &field = interfaceBlock.fields[0];
+
+ EXPECT_EQ("f", field.name);
+ // See TODO in CollectVariables.cpp about tracking instanced interface block field static use.
+ // EXPECT_TRUE(field.staticUse);
+ EXPECT_FALSE(field.active);
+}
\ No newline at end of file
diff --git a/src/tests/compiler_tests/ShaderValidation_test.cpp b/src/tests/compiler_tests/ShaderValidation_test.cpp
index b6f399c..88a7b8f 100644
--- a/src/tests/compiler_tests/ShaderValidation_test.cpp
+++ b/src/tests/compiler_tests/ShaderValidation_test.cpp
@@ -5884,3 +5884,38 @@
FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
}
}
+
+// Test that a fragment shader with nested if statements without braces compiles successfully.
+TEST_F(FragmentShaderValidationTest, HandleIfInnerIfStatementAlwaysTriviallyPruned)
+{
+ const std::string &shaderString =
+ R"(precision mediump float;
+ void main()
+ {
+ if (true)
+ if (false)
+ gl_FragColor = vec4(0.0);
+ })";
+ if (!compile(shaderString))
+ {
+ FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+ }
+}
+
+// Test that a fragment shader with an if statement nested in a loop without braces compiles
+// successfully.
+TEST_F(FragmentShaderValidationTest, HandleLoopInnerIfStatementAlwaysTriviallyPruned)
+{
+ const std::string &shaderString =
+ R"(precision mediump float;
+ void main()
+ {
+ while (false)
+ if (false)
+ gl_FragColor = vec4(0.0);
+ })";
+ if (!compile(shaderString))
+ {
+ FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+ }
+}
diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
index 45ef2d5..d591060 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -4148,6 +4148,66 @@
"interface block 'S' member 'S.val1.t2'");
}
+// Test a vertex shader that doesn't declare any varyings with a fragment shader that statically
+// uses a varying, but in a statement that gets trivially optimized out by the compiler.
+TEST_P(GLSLTest_ES3, FragmentShaderStaticallyUsesVaryingMissingFromVertex)
+{
+ const std::string &vertexShader =
+ R"(#version 300 es
+
+ precision mediump float;
+
+ void main()
+ {
+ gl_Position = vec4(0, 1, 0, 1);
+ })";
+
+ const std::string &fragmentShader =
+ R"(#version 300 es
+
+ precision mediump float;
+ in float foo;
+ out vec4 my_FragColor;
+
+ void main()
+ {
+ if (false)
+ {
+ float unreferenced = foo;
+ }
+ my_FragColor = vec4(0, 1, 0, 1);
+ })";
+
+ validateComponentsInErrorMessage(vertexShader, fragmentShader, "does not match any", "foo");
+}
+
+// Test a varying that is statically used but not active in the fragment shader.
+TEST_P(GLSLTest_ES3, VaryingStaticallyUsedButNotActiveInFragmentShader)
+{
+ const std::string &vertexShader =
+ R"(#version 300 es
+ precision mediump float;
+ in vec4 iv;
+ out vec4 v;
+ void main()
+ {
+ gl_Position = iv;
+ v = iv;
+ })";
+
+ const std::string &fragmentShader =
+ R"(#version 300 es
+ precision mediump float;
+ in vec4 v;
+ out vec4 color;
+ void main()
+ {
+ color = true ? vec4(0.0) : v;
+ })";
+
+ ANGLE_GL_PROGRAM(program, vertexShader, fragmentShader);
+}
+
// Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
ANGLE_INSTANTIATE_TEST(GLSLTest,
ES2_D3D9(),