Flatten uniform arrays

This checkin adds a --flatten-uniform-arrays option which can break
uniform arrays of samplers, textures, or UBOs up into individual
scalars named (e.g) myarray[0], myarray[1], etc.  These appear as
individual linkage objects.

Code notes:

- shouldFlatten internally calls shouldFlattenIO, and shouldFlattenUniform,
  but is the only flattening query directly called.

- flattenVariable will handle structs or arrays (but not yet arrayed structs;
  this is tested an an error is generated).

- There's some error checking around unhandled situations.  E.g, flattening
  uniform arrays with initializer lists is not implemented.

- This piggybacks on as much of the existing mechanism for struct flattening
  as it can.  E.g, it uses the same flattenMap, and the same
  flattenAccess() method.

- handleAssign() has been generalized to cope with either structs or arrays.

- Extended test infrastructure to test flattening ability.
This commit is contained in:
steve-lunarg
2016-09-16 13:26:37 -06:00
parent 6714bcc2ca
commit e0b9debda2
11 changed files with 872 additions and 46 deletions

View File

@@ -403,12 +403,19 @@ TIntermTyped* HlslParseContext::handleBracketDereference(const TSourceLoc& loc,
if (base->getAsSymbolNode() && isIoResizeArray(base->getType()))
handleIoResizeArrayAccess(loc, base);
if (index->getQualifier().storage == EvqConst) {
if (base->getType().isImplicitlySizedArray())
updateImplicitArraySize(loc, base, indexValue);
result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
if (base->getAsSymbolNode() && shouldFlatten(base->getType())) {
if (index->getQualifier().storage != EvqConst)
error(loc, "Invalid variable index to flattened uniform array", base->getAsSymbolNode()->getName().c_str(), "");
result = flattenAccess(base, indexValue);
} else {
result = intermediate.addIndex(EOpIndexIndirect, base, index, loc);
if (index->getQualifier().storage == EvqConst) {
if (base->getType().isImplicitlySizedArray())
updateImplicitArraySize(loc, base, indexValue);
result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
} else {
result = intermediate.addIndex(EOpIndexIndirect, base, index, loc);
}
}
}
@@ -701,9 +708,9 @@ TIntermTyped* HlslParseContext::handleDotDereference(const TSourceLoc& loc, TInt
return result;
}
// Is this an aggregate that can't be passed down the stack?
// Is this an IO variable that can't be passed down the stack?
// E.g., pipeline inputs to the vertex stage and outputs from the fragment stage.
bool HlslParseContext::shouldFlatten(const TType& type) const
bool HlslParseContext::shouldFlattenIO(const TType& type) const
{
if (! inEntryPoint)
return false;
@@ -715,6 +722,33 @@ bool HlslParseContext::shouldFlatten(const TType& type) const
qualifier == EvqVaryingOut);
}
// Is this a uniform array which should be flattened?
bool HlslParseContext::shouldFlattenUniform(const TType& type) const
{
const TStorageQualifier qualifier = type.getQualifier().storage;
return type.isArray() &&
intermediate.getFlattenUniformArrays() &&
qualifier == EvqUniform;
}
void HlslParseContext::flatten(const TSourceLoc& loc, const TVariable& variable)
{
const TType& type = variable.getType();
// Presently, flattening of structure arrays is unimplemented.
// We handle one, or the other.
if (type.isArray() && type.isStruct()) {
error(loc, "cannot flatten structure array", variable.getName().c_str(), "");
}
if (type.isStruct())
flattenStruct(variable);
if (type.isArray())
flattenArray(loc, variable);
}
// Figure out the mapping between an aggregate's top members and an
// equivalent set of individual variables.
//
@@ -724,7 +758,7 @@ bool HlslParseContext::shouldFlatten(const TType& type) const
// Assumes shouldFlatten() or equivalent was called first.
//
// TODO: generalize this to arbitrary nesting?
void HlslParseContext::flatten(const TVariable& variable)
void HlslParseContext::flattenStruct(const TVariable& variable)
{
TVector<TVariable*> memberVariables;
@@ -742,8 +776,54 @@ void HlslParseContext::flatten(const TVariable& variable)
flattenMap[variable.getUniqueId()] = memberVariables;
}
// Turn an access into aggregate that was flattened to instead be
// an access to the individual variable the element/member was flattened to.
// Figure out mapping between an array's members and an
// equivalent set of individual variables.
//
// Assumes shouldFlatten() or equivalent was called first.
void HlslParseContext::flattenArray(const TSourceLoc& loc, const TVariable& variable)
{
const TType& type = variable.getType();
assert(type.isArray());
if (type.isImplicitlySizedArray())
error(loc, "cannot flatten implicitly sized array", variable.getName().c_str(), "");
if (type.getArraySizes()->getNumDims() != 1)
error(loc, "cannot flatten multi-dimensional array", variable.getName().c_str(), "");
const int size = type.getCumulativeArraySize();
TVector<TVariable*> memberVariables;
const TType dereferencedType(type, 0);
int binding = type.getQualifier().layoutBinding;
if (dereferencedType.isStruct() || dereferencedType.isArray()) {
error(loc, "cannot flatten array of aggregate types", variable.getName().c_str(), "");
}
for (int element=0; element < size; ++element) {
char elementNumBuf[20]; // sufficient for MAXINT
snprintf(elementNumBuf, sizeof(elementNumBuf)-1, "[%d]", element);
const TString memberName = variable.getName() + elementNumBuf;
TVariable* memberVariable = makeInternalVariable(memberName.c_str(), dereferencedType);
memberVariable->getWritableType().getQualifier() = variable.getType().getQualifier();
memberVariable->getWritableType().getQualifier().layoutBinding = binding;
if (binding != TQualifier::layoutBindingEnd)
++binding;
memberVariables.push_back(memberVariable);
intermediate.addSymbolLinkageNode(linkage, *memberVariable);
}
flattenMap[variable.getUniqueId()] = memberVariables;
}
// Turn an access into an aggregate that was flattened to instead be
// an access to the individual variable the member was flattened to.
// Assumes shouldFlatten() or equivalent was called first.
TIntermTyped* HlslParseContext::flattenAccess(TIntermTyped* base, int member)
{
@@ -864,7 +944,7 @@ TIntermAggregate* HlslParseContext::handleFunctionDefinition(const TSourceLoc& l
remapEntryPointIO(function);
if (entryPointOutput) {
if (shouldFlatten(entryPointOutput->getType()))
flatten(*entryPointOutput);
flatten(loc, *entryPointOutput);
assignLocations(*entryPointOutput);
}
} else
@@ -896,7 +976,7 @@ TIntermAggregate* HlslParseContext::handleFunctionDefinition(const TSourceLoc& l
// get IO straightened out
if (inEntryPoint) {
if (shouldFlatten(*param.type))
flatten(*variable);
flatten(loc, *variable);
assignLocations(*variable);
}
@@ -1051,40 +1131,68 @@ TIntermTyped* HlslParseContext::handleAssign(const TSourceLoc& loc, TOperator op
flattenMap.find(node.getAsSymbolNode()->getId()) != flattenMap.end();
};
bool flattenLeft = mustFlatten(*left);
bool flattenRight = mustFlatten(*right);
const bool flattenLeft = mustFlatten(*left);
const bool flattenRight = mustFlatten(*right);
if (! flattenLeft && ! flattenRight)
return intermediate.addAssign(op, left, right, loc);
// If we get here, we are assigning to or from a whole struct that must be
// flattened, so have to do member-by-member assignment:
const auto& members = *left->getType().getStruct();
const auto getMember = [&](bool flatten, TIntermTyped* node,
const TVector<TVariable*>& memberVariables, int member) {
TIntermTyped* subTree;
if (flatten)
subTree = intermediate.addSymbol(*memberVariables[member]);
else {
subTree = intermediate.addIndex(EOpIndexDirectStruct, node,
intermediate.addConstantUnion(member, loc), loc);
subTree->setType(*members[member].type);
}
return subTree;
};
TIntermAggregate* assignList = nullptr;
const TVector<TVariable*>* leftVariables = nullptr;
const TVector<TVariable*>* rightVariables = nullptr;
if (flattenLeft)
leftVariables = &flattenMap.find(left->getAsSymbolNode()->getId())->second;
if (flattenRight)
rightVariables = &flattenMap.find(right->getAsSymbolNode()->getId())->second;
TIntermAggregate* assignList = nullptr;
for (int member = 0; member < (int)members.size(); ++member) {
TIntermTyped* subRight = getMember(flattenRight, right, *rightVariables, member);
TIntermTyped* subLeft = getMember(flattenLeft, left, *leftVariables, member);
assignList = intermediate.growAggregate(assignList, intermediate.addAssign(op, subLeft, subRight, loc));
const auto getMember = [&](bool flatten, TIntermTyped* node,
const TVector<TVariable*>& memberVariables, int member,
TOperator op, const TType& memberType) {
TIntermTyped* subTree;
if (flatten)
subTree = intermediate.addSymbol(*memberVariables[member]);
else {
subTree = intermediate.addIndex(op, node, intermediate.addConstantUnion(member, loc), loc);
subTree->setType(memberType);
}
return subTree;
};
// Handle struct assignment
if (left->getType().isStruct()) {
// If we get here, we are assigning to or from a whole struct that must be
// flattened, so have to do member-by-member assignment:
const auto& members = *left->getType().getStruct();
for (int member = 0; member < (int)members.size(); ++member) {
TIntermTyped* subRight = getMember(flattenRight, right, *rightVariables, member,
EOpIndexDirectStruct, *members[member].type);
TIntermTyped* subLeft = getMember(flattenLeft, left, *leftVariables, member,
EOpIndexDirectStruct, *members[member].type);
assignList = intermediate.growAggregate(assignList, intermediate.addAssign(op, subLeft, subRight, loc));
}
}
// Handle array assignment
if (left->getType().isArray()) {
// If we get here, we are assigning to or from a whole array that must be
// flattened, so have to do member-by-member assignment:
const TType dereferencedType(left->getType(), 0);
const int size = left->getType().getCumulativeArraySize();
for (int element=0; element < size; ++element) {
TIntermTyped* subRight = getMember(flattenRight, right, *rightVariables, element,
EOpIndexDirect, dereferencedType);
TIntermTyped* subLeft = getMember(flattenLeft, left, *leftVariables, element,
EOpIndexDirect, dereferencedType);
assignList = intermediate.growAggregate(assignList, intermediate.addAssign(op, subLeft, subRight, loc));
}
}
assert(assignList != nullptr);
assignList->setOperator(EOpSequence);
return assignList;
@@ -4095,13 +4203,21 @@ TIntermNode* HlslParseContext::declareVariable(const TSourceLoc& loc, TString& i
inheritGlobalDefaults(type.getQualifier());
bool flattenVar = false;
// Declare the variable
if (arraySizes || type.isArray()) {
// Arrayness is potentially coming both from the type and from the
// variable: "int[] a[];" or just one or the other.
// Merge it all to the type, so all arrayness is part of the type.
arrayDimMerge(type, arraySizes);
arrayDimMerge(type, arraySizes); // Safe if there are no arraySizes
declareArray(loc, identifier, type, symbol, newDeclaration);
flattenVar = shouldFlatten(type);
if (flattenVar)
flatten(loc, *symbol->getAsVariable());
} else {
// non-array case
if (! symbol)
@@ -4116,6 +4232,9 @@ TIntermNode* HlslParseContext::declareVariable(const TSourceLoc& loc, TString& i
// Deal with initializer
TIntermNode* initNode = nullptr;
if (symbol && initializer) {
if (flattenVar)
error(loc, "flattened array with initializer list unsupported", identifier.c_str(), "");
TVariable* variable = symbol->getAsVariable();
if (! variable) {
error(loc, "initializer requires a variable, not a member", identifier.c_str(), "");
@@ -4124,9 +4243,12 @@ TIntermNode* HlslParseContext::declareVariable(const TSourceLoc& loc, TString& i
initNode = executeInitializer(loc, initializer, variable);
}
// see if it's a linker-level object to track
if (newDeclaration && symbolTable.atGlobalLevel())
intermediate.addSymbolLinkageNode(linkage, *symbol);
// see if it's a linker-level object to track. if it's flattened above,
// that process added linkage objects for the flattened symbols, we don't
// add the aggregate here.
if (!flattenVar)
if (newDeclaration && symbolTable.atGlobalLevel())
intermediate.addSymbolLinkageNode(linkage, *symbol);
return initNode;
}
@@ -4257,7 +4379,7 @@ TIntermNode* HlslParseContext::executeInitializer(const TSourceLoc& loc, TInterm
// normal assigning of a value to a variable...
specializationCheck(loc, initializer->getType(), "initializer");
TIntermSymbol* intermSymbol = intermediate.addSymbol(*variable, loc);
TIntermNode* initNode = intermediate.addAssign(EOpAssign, intermSymbol, initializer, loc);
TIntermNode* initNode = handleAssign(loc, EOpAssign, intermSymbol, initializer);
if (! initNode)
assignError(loc, "=", intermSymbol->getCompleteString(), initializer->getCompleteString());